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

Mad*_*doc 10 xml scala scala-2.8 scala-xml

我认为这个问题可能没有令人满意的答案,但无论如何我都会问它,以防我错过了什么.

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

我知道标准的Scala XML支持可能没有这样的内置功能.毕竟,NodeSeq用这样的信息注释每个单独的实例是浪费的,并不是每个XML元素都有一个源文档,从中解析它.在我看来,标准的Scala XML解析器抛出了行信息,后来无法检索它.

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

我唯一的希望是,您可以向我展示一种方法来更改或子类化标准Scala XML解析器,以便它生成的节点将使用源行的编号进行注释.也许NodeSeq可以为此创建一个特殊的子类.或者也许只能Atom进行子类化,因为NodeSeq它太动态了?我不知道.

无论如何,我的希望接近于零.我不认为解析器中有一个位置可以挂钩以更改节点的创建方式,并且在该位置可以获得行信息.不过,我想知道为什么我之前没有找到这个问题.如果这是重复的,请指出原件.

Dan*_*ral 11

我不知道该怎么做,但Pangea 给我指路.首先,让我们创建一个特征来处理位置:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,让我们来创建自己的装载机压倒一切XMLLoaderadapter,包括我们的特点:

import scala.xml.{factory, parsing, Elem}
object MyLoader extends factory.XMLLoader[Elem] {
    override def adapter = new parsing.NoBindingFactoryAdapter with WithLocation
}
Run Code Online (Sandbox Code Playgroud)

这就是它的全部!该对象XML几乎没有增加XMLLoader- 基本上,save方法.如果您觉得需要完全替换,可能需要查看其源代码.但这只是你想自己处理所有这些,因为Scala已经有了产生错误的特性:

object MyLoader extends factory.XMLLoader[Elem] {
    override def adapter = new parsing.NoBindingFactoryAdapter with parsing.ConsoleErrorHandler
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

结果:

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

请注意,它得到了最后一个位置,即结束标记处的位置.这是可以通过重写startElement来跟踪每个元素在堆栈中的起始位置以及endElement从此堆栈弹出到var使用过的内容来改进的一件事createNode.

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