【问题标题】:How to track the source line (location) of an XML element?如何跟踪 XML 元素的源代码行(位置)?
【发布时间】:2011-05-25 16:08:27
【问题描述】:

我认为这个问题可能没有令人满意的答案,但我还是会问它以防我错过了什么。

基本上,在给定元素实例的情况下,我想找出源文档中某个 XML 元素源自的行。我希望这只是为了更好地诊断错误消息 - XML 是配置文件的一部分,如果它有问题,我希望能够将错误消息的阅读器指向 XML 文档中的正确位置这样他就可以改正错误了。

我知道标准的 Scala XML 支持可能没有这样的内置功能。毕竟,用这样的信息来注释每个NodeSeq 实例是很浪费的,而且并不是每个 XML 元素甚至都有一个源文档来解析它。在我看来,标准的 Scala XML 解析器将行信息扔掉了,后来没有办法检索它。

但是切换到另一个 XML 框架不是一种选择。为了更好的诊断错误消息而“仅”添加另一个库依赖项对我来说似乎不合适。此外,尽管有一些缺点,但我真的很喜欢 XML 的内置模式匹配支持。

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

无论如何,我的希望几乎为零。我认为解析器中没有一个地方可以用来改变节点的创建方式,并且在那个地方可以使用行信息。不过,我想知道为什么我以前没有发现这个问题。如有重复请指点原文。

【问题讨论】:

    标签: xml scala scala-2.8 scala-xml


    【解决方案1】:

    我对 Scala 一无所知,但在其他环境中也会出现同样的问题。例如,XML 转换将其结果通过 SAX 管道发送到验证器,当验证器尝试为其验证错误查找行号时,它们就消失了。或者有问题的 XML 从未被序列化或解析,因此从未有行号。

    解决问题的一种方法是生成(人类可读的)XPath 表达式来说明错误发生的位置。这些不像行号那么容易使用,但它们总比没有好:它们唯一地标识一个节点,并且它们通常很容易被人类解释(尤其是如果他们有 XML 编辑器)。

    例如,Schematron 使用的 Ken Holman(我认为)的这个 XSLT 模板生成了一个 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,但您可以将相同的原则应用到您可用的任何工具上。

    【讨论】:

      【解决方案2】:

      see that scala 在内部使用 SAX 进行解析。 SAX 允许您在ContentHandler 上设置定位器,可用于检索current location where the error occurred。不过,我不确定如何利用 Scala 的内部工作原理。 Here is one article 我发现这可能对看看这是否可行有所帮助。

      【讨论】:

      • 值得一提的是,Stax XMLStreamReader 具有 getLocation() ,它同样提供位置(输入(文件名)、行、列)。 JDK 1.6 带有一个默认实现 (Sun Sjsxp),尽管也有更好的开源替代方案 (Woodstox)。
      • 同意,但我不确定 Scala 是否支持 stax。
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        我不知道该怎么做,但是 Pangea showed me the way。首先,让我们创建一个 trait 来处理位置:

        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 以包含我们的 trait:

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

        仅此而已!对象XMLXMLLoader 几乎没有添加——基本上是save 方法。如果您觉得需要完全替换,您可能想查看它的源代码。但这只是当你想自己处理所有这些时,因为 Scala 已经具有产生错误的特性:

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

        顺便说一句,ConsoleErrorHandler trait 从异常中提取其行和编号信息。出于我们的目的,我们也需要异常之外的位置(我假设)。

        现在,要修改节点创建本身,请查看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 来改进这一点。

        好问题。我学到了很多! :-)

        【讨论】:

        • 抱歉这么晚才回复。你的回答太棒了。我没想到一个真正的解决方案,但你实际上找到了一个。非常感谢!
        • 现在如果只有您或某人可以展示如何获取起始行号:P
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        • 2010-11-30
        • 2011-12-05
        • 1970-01-01
        • 1970-01-01
        • 2020-10-25
        相关资源
        最近更新 更多