如何跟踪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)
}
}
接下来,让我们来创建自己的装载机覆盖XMLLoader
的adapter
,包括我们的特点:
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
。
有趣的问题。我学到了很多! :-)
我对斯卡拉一无所知,但同样的问题在其他环境中弹出。例如,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我发现可能有助于查看这是否可行。
对于什么是值得的,斯塔克斯的XMLStreamReader具有的getLocation(),它同样提供了位置(输入(文件名),行,列)。虽然有更好的开源替代方案(Woodstox),但JDK 1.6带有默认实现(Sun Sjsxp)。 – StaxMan 2010-12-15 18:13:46
同意,但我不确定我在斯卡拉支持stax。 – 2010-12-15 18:16:00
尽管您表示不想使用不同的库或框架,但值得注意的是,所有优秀的Java流式解析器(用于Sax,Woodstox和Aalto for Stax的Xerces)确实可以为所有事件/令牌提供位置信息他们服务。
尽管这些信息并不总是被像DOM树这样的更高级别的抽象保留下来(由于需要额外的存储空间;性能并不是很大的问题,因为位置信息总是被跟踪,因为无论如何都需要进行错误报告),这可能是容易或至少可以修复。
抱歉回答这么晚。你的答案很棒。我没有想到一个真正的解决方案,但你实际上找到了一个。非常感谢! – Madoc 2010-12-31 11:56:39
现在,如果只有你或某人能够显示如何获得起始行号:P – Jus12 2014-01-27 09:11:19