基本上,我想在源文档中找到源于某个XML元素的行,给定元素实例.我希望这只是为了更好的诊断错误消息 – XML是配置文件的一部分,如果出现问题,我希望能够将读取器的错误消息指向XML文档中正确的位置所以他可以纠正错误.
我了解标准的Scala XML支持可能没有这样的内置功能.毕竟,使用这些信息来注释每一个NodeSeq实例都是浪费的,而不是每个XML元素甚至都有一个源文档被解析.在我看来,标准的Scala XML解析器将线信息丢弃,之后就无法检索它.
但是切换到另一个XML框架不是一个选择.为了更好的诊断错误消息,添加另一个库依赖关系似乎不适合我.另外,尽管有一些缺点,我真的很喜欢内置的XML模式匹配支持.
我唯一的希望是,您可以向我显示一种方法来更改或子类化标准的Scala XML解析器,以使其生成的节点将以源行编号进行注释.也许可以创建一个NodeSeq的一个特殊的子类.或者也许只有Atom可以被子类化,因为NodeSeq太动态了?我不知道.
无论如何,我的希望接近于零.我不认为解析器中有一个地方可以挂钩改变节点的创建方式,而在那个位置,线路信息是可用的.不过,我想知道为什么我以前没有发现这个问题.如果这是重复的,请指出原件.
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) } }
接下来,让我们创建我们自己的加载器覆盖XMLLoader的适配器以包含我们的特征:
import scala.xml.{factory,parsing,Elem} object MyLoader extends factory.XMLLoader[Elem] { override def adapter = new parsing.NoBindingFactoryAdapter with WithLocation }
这就是它的一切!对象XML对XMLLoader几乎没有增加 – 基本上是保存方法.如果您需要完全替换,您可能需要查看其源代码.但是,只有当您想自己处理所有这些时,才可以,因为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中可以改进.
好问题我学到了很多!