2010-12-15 40 views
10

我認爲這個問題可能沒有令人滿意的答案,但無論如何,如果我錯過了某些東西,我會問。如何跟蹤XML元素的源代碼行(位置)?

基本上,我想查找源文檔中從哪個XML元素開始的行,給定元素實例。我只希望這個更好的診斷錯誤消息 - XML是配置文件的一部分,如果它有什麼問題,我希望能夠將錯誤消息的讀者指向XML文檔中的正確位置所以他可以糾正錯誤。

我知道標準的Scala XML支持可能沒有像這樣的內置功能。畢竟,用這樣的信息註釋每個單個實例都是浪費的,並且並非每個XML元素甚至都有一個源文檔可以從中解析出來。在我看來,標準Scala XML解析器會拋出線條信息,之後無法檢索它。

但切換到另一個XML框架不是一種選擇。爲了更好的診斷錯誤消息,「僅添加」另一個庫依賴關係似乎不適合我。另外,儘管存在一些缺陷,我非常喜歡XML的內置模式匹配支持。

我唯一的希望是,你可以告訴我一種方法來改變或繼承標準的Scala XML解析器,使得它生成的節點將用源代碼行的編號進行註釋。也許可以爲此創建一個NodeSeq的特殊子類。或者,也許只有Atom可以被分類,因爲NodeSeq太動態?我不知道。

無論如何,我的希望接近於零。我認爲解析器中沒有可以插入的地方來改變節點的創建方式,並且在那個地方線路信息是可用的。不過,我想知道爲什麼我以前沒有找到這個問題。如果這是重複的,請指出原文。

回答

11

我不知道該怎麼做,但Pangeashowed me the way。首先,讓我們創建一個特徵來處理地點:

import org.xml.sax.{helpers, Locator, SAXParseException} 
trait WithLocation extends helpers.DefaultHandler { 
    var locator: org.xml.sax.Locator = _ 
    def printLocation(msg: String) { 
     println("%s at line %d, column %d" format (msg, locator.getLineNumber, locator.getColumnNumber)) 
    } 

    // Get location 
    abstract override def setDocumentLocator(locator: Locator) { 
     this.locator = locator 
     super.setDocumentLocator(locator) 
    } 

    // Display location messages 
    abstract override def warning(e: SAXParseException) { 
     printLocation("warning") 
     super.warning(e) 
    } 
    abstract override def error(e: SAXParseException) { 
     printLocation("error") 
     super.error(e) 
    } 
    abstract override def fatalError(e: SAXParseException) { 
     printLocation("fatal error") 
     super.fatalError(e) 
    } 
} 

接下來,讓我們來創建自己的裝載機覆蓋XMLLoaderadapter,包括我們的特點:

import scala.xml.{factory, parsing, Elem} 
object MyLoader extends factory.XMLLoader[Elem] { 
    override def adapter = new parsing.NoBindingFactoryAdapter with WithLocation 
} 

而這一切就是這麼簡單!對象XML幾乎沒有添加到XMLLoader - 基本上,save方法。如果您覺得需要全面更換,您可能需要查看其源代碼。但這只是如果你想處理這一切的自己,因爲斯卡拉已經有一個特點產生錯誤:

object MyLoader extends factory.XMLLoader[Elem] { 
    override def adapter = new parsing.NoBindingFactoryAdapter with parsing.ConsoleErrorHandler 
} 

ConsoleErrorHandler特點,從異常提取其行和數量的信息,順便說一句。對於我們的目的,我們需要的地點也是例外(我假設)。

現在,修改節點創作本身,看看scala.xml.factory.FactoryAdapter抽象方法。我已經看中了createNode,但我在NoBindingFactoryAdapter水平壓倒一切,因爲它返回Elem代替Node,使我添加屬性。所以:

import org.xml.sax.Locator 
import scala.xml._ 
import parsing.NoBindingFactoryAdapter 
trait WithLocation extends NoBindingFactoryAdapter { 
    var locator: org.xml.sax.Locator = _ 

    // Get location 
    abstract override def setDocumentLocator(locator: Locator) { 
     this.locator = locator 
     super.setDocumentLocator(locator) 
    } 

    abstract override def createNode(pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: List[Node]): Elem = (
     super.createNode(pre, label, attrs, scope, children) 
     % Attribute("line", Text(locator.getLineNumber.toString), Null) 
     % Attribute("column", Text(locator.getColumnNumber.toString), Null) 
    ) 
} 

object MyLoader extends factory.XMLLoader[Elem] { 
    // Keeping ConsoleErrorHandler for good measure 
    override def adapter = new parsing.NoBindingFactoryAdapter with parsing.ConsoleErrorHandler with WithLocation 
} 

結果:

scala> MyLoader.loadString("<a><b/></a>") 
res4: scala.xml.Elem = <a line="1" column="12"><b line="1" column="8"></b></a> 

注意,它拿到了最後一個位置,一個在結束標記。這是一兩件事,可以通過覆蓋startElement跟蹤,其中每個元素在堆棧中開始得到改善,endElement從這個堆棧彈出成createNode使用的var

有趣的問題。我學到了很多! :-)

+0

抱歉回答這麼晚。你的答案很棒。我沒有想到一個真正的解決方案,但你實際上找到了一個。非常感謝! – Madoc 2010-12-31 11:56:39

+0

現在,如果只有你或某人能夠顯示如何獲得起始行號:P – Jus12 2014-01-27 09:11:19

2

我對斯卡拉一無所知,但同樣的問題在其他環境中彈出。例如,XML轉換將其結果通過SAX管道發送到驗證程序,並且驗證程序嘗試查找其驗證錯誤的行號時,它們就消失了。或者所討論的XML從未序列化或解析過,因此從來沒有行號。

解決此問題的一種方法是通過生成(可讀的)XPath表達式來說出錯發生的位置。這些並不像行號那樣容易使用,但它們比沒有更好:它們唯一標識一個節點,而且它們對於人類來說通常很容易解釋(尤其是如果它們具有XML編輯器的話)。

例如,由肯·霍爾曼這個XSLT模板(我認爲)由Schematron的使用產生的XPath表達式來描述上下文節點的位置/身份:

<xsl:template match="node() | @*" mode="schematron-get-full-path-2"> 
    <!--report the element hierarchy--> 
    <xsl:for-each select="ancestor-or-self::*"> 
     <xsl:text>/</xsl:text> 
     <xsl:value-of select="name(.)"/> 
     <xsl:if test="preceding-sibling::*[name(.)=name(current())]"> 
     <xsl:text>[</xsl:text> 
     <xsl:value-of 
      select="count(preceding-sibling::*[name(.)=name(current())])+1"/> 
     <xsl:text>]</xsl:text> 
     </xsl:if> 
    </xsl:for-each> 
    <!--report the attribute--> 
    <xsl:if test="not(self::*)"> 
     <xsl:text/>/@<xsl:value-of select="name(.)"/> 
    </xsl:if> 
</xsl:template> 

我不知道,如果你能在您的場景中使用XSLT,但是您可以將相同的原則應用於您可用的任何工具。

4

I see that scala內部使用SAX進行分析。 SAX允許您在ContentHandler上設置一個定位器,該定位器可用於檢索current location where the error occurred。我不知道你如何能夠利用Scala的內部工作。 Here is one article我發現可能有助於查看這是否可行。

+0

對於什麼是值得的,斯塔克斯的XMLStreamReader具有的getLocation(),它同樣提供了位置(輸入(文件名),行,列)。雖然有更好的開源替代方案(Woodstox),但JDK 1.6帶有默認實現(Sun Sjsxp)。 – StaxMan 2010-12-15 18:13:46

+0

同意,但我不確定我在斯卡拉支持stax。 – 2010-12-15 18:16:00

2

儘管您表示不想使用不同的庫或框架,但值得注意的是,所有優秀的Java流式解析器(用於Sax,Woodstox和Aalto for Stax的Xerces)確實可以爲所有事件/令牌提供位置信息他們服務。

儘管這些信息並不總是被像DOM樹這樣的更高級別的抽象保留下來(由於需要額外的存儲空間;性能並不是很大的問題,因爲位置信息總是被跟蹤,因爲無論如何都需要進行錯誤報告),這可能是容易或至少可以修復。