如何跟踪XML元素的源代码行(位置)?

如何跟踪XML元素的源代码行(位置)?

问题描述:

我认为这个问题可能没有令人满意的答案,但无论如何,如果我错过了某些东西,我会问。如何跟踪XML元素的源代码行(位置)?

基本上,我想查找源文档中从哪个XML元素开始的行,给定元素实例。我只希望这个更好的诊断错误消息 - XML是配置文件的一部分,如果它有什么问题,我希望能够将错误消息的读者指向XML文档中的正确位置所以他可以纠正错误。

我知道标准的Scala XML支持可能没有像这样的内置功能。毕竟,用这样的信息注释每个单个实例都是浪费的,并且并非每个XML元素甚至都有一个源文档可以从中解析出来。在我看来,标准Scala XML解析器会抛出线条信息,之后无法检索它。

但切换到另一个XML框架不是一种选择。为了更好的诊断错误消息,“仅添加”另一个库依赖关系似乎不适合我。另外,尽管存在一些缺陷,我非常喜欢XML的内置模式匹配支持。

我唯一的希望是,你可以告诉我一种方法来改变或继承标准的Scala XML解析器,使得它生成的节点将用源代码行的编号进行注释。也许可以为此创建一个NodeSeq的特殊子类。或者,也许只有Atom可以被分类,因为NodeSeq太动态?我不知道。

无论如何,我的希望接近于零。我认为解析器中没有可以插入的地方来改变节点的创建方式,并且在那个地方线路信息是可用的。不过,我想知道为什么我以前没有找到这个问题。如果这是重复的,请指出原文。

我不知道该怎么做,但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

我对斯卡拉一无所知,但同样的问题在其他环境中弹出。例如,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,但是您可以将相同的原则应用于您可用的任何工具。

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

尽管您表示不想使用不同的库或框架,但值得注意的是,所有优秀的Java流式解析器(用于Sax,Woodstox和Aalto for Stax的Xerces)确实可以为所有事件/令牌提供位置信息他们服务。

尽管这些信息并不总是被像DOM树这样的更高级别的抽象保留下来(由于需要额外的存储空间;性能并不是很大的问题,因为位置信息总是被跟踪,因为无论如何都需要进行错误报告),这可能是容易或至少可以修复。