2010-12-15 5 views
10

私はおそらくこの質問に満足のいく答えがないと仮定しますが、私が何かを逃した場合に備えて、とにかく尋ねます。XML要素のソース行(場所)を追跡する方法は?

基本的に、要素インスタンスが与えられている特定のXML要素の元の行をソース文書内で探したいとします。私はこれをより良い診断エラーメッセージのためだけにしたい - XMLは設定ファイルの一部であり、何か問題がある場合は、エラーメッセージの読者にXML文書の正確な場所を指し示すことができるようにしたい彼はエラーを修正することができます。

標準のScala XMLサポートには、このような組み込み機能がない可能性があることを理解します。結局のところ、すべての単一のNodeSeqインスタンスにそのような情報を注釈するのは無駄でしょうし、すべてのXML要素が解析されたソース文書を持っているわけではありません。標準のScala XMLパーサは、行情報を捨てて、後でそれを取得する方法がないようです。

しかし、別のXMLフレームワークに切り替えることはできません。より良い診断エラーメッセージのために別のライブラリ依存関係を「のみ」追加することは、私には不適切です。また、いくつかの欠点にもかかわらず、私は本当にXMLのための組み込みパターンマッチングサポートが好きです。

私の唯一の希望は、標準のScala XMLパーサーを変更またはサブクラス化する方法を示すことができます。これにより、作成するノードにソース行の番号が注釈されます。おそらくNodeSeqの特別なサブクラスをこのために作成することができます。 NodeSeqがあまりにも動的なので、Atomしかサブクラス化できませんか?知りません。

とにかく、私の希望はゼロに近いです。私はノードが作成される方法を変更するためにパーサに場所があるとは思っていません。その場所では、ライン情報が利用可能です。それでも、なぜ私はこの質問を以前に見つけていないのだろうかと思います。これが重複している場合は、私にオリジナルを指摘してください。

答えて

11

どうすればいいのか分かりませんでしたが、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メソッドです。完全な置き換えが必要な場合は、ソースコードを参照してください。しかし、これはあなたがScalaは、既にエラーを生産する形質を持っているので、このすべてを自分で処理したい場合にのみ、次のとおりです。

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

ConsoleErrorHandler形質は仕方によって、例外からその行と番号情報を抽出します。私たちの目的のために、私たちは例外の外の場所も必要とします(私は仮定しています)。

ノードの作成自体を変更するには、scala.xml.factory.FactoryAdapter抽象メソッドを見てください。 createNodeに解決しましたが、NoBindingFactoryAdapterレベルでオーバーライドしています。これはNodeの代わりにElemを返すため、属性を追加できます。だから、:

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 
} 

結果:それは最後の位置、終了タグの1つを得たことを

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

注意。これは、startElementをオーバーライドして各要素がスタック内で開始された場所を把握し、このスタックからvarによって使用されるになるようにendElementをポップすることで改善できます。

良い質問。私は多くのことを学びました! :-)

+0

とても遅れて申し訳ありません。あなたの答えは素晴らしいです。私は本当の解決策を期待していませんでしたが、あなたは実際に解決策を見つけました。どうもありがとう! – Madoc

+0

あなたや他人だけが開始行番号を取得する方法を示すことができます:P – Jus12

2

Scalaについてはわかりませんが、他の環境でも同じ問題が発生します。たとえば、XML変換はその結果をSAXパイプラインを介してバリデーターに送信し、バリデーターがバリデーション・エラーの行番号を見つけようとすると、その行は消えます。また、問題のXMLは決してシリアル化されたり解析されたりしなかったので、行番号は決してありませんでした。

問題を解決する1つの方法は、エラーが発生した場所を示す(人間が読める)XPath式を生成することです。これらは行番号と同じくらい使いやすいものではありませんが、ノードを一意に識別し、人間が解釈するのはかなり簡単です(特にXMLエディタがある場合)。

例えば、Schematronのが使用するケン・ホルマンこの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を使用しますが、利用可能なツールであれば同じ原則を適用できます。

4

see that scalaは、内部で解析するためにSAXを使用します。 SAXではContentHandlerのロケータを設定することができ、current location where the error occurredの検索に使用できます。私はあなたがScalaの内部動作をどのように利用することができるかはわかりません。 Here is one articleこれは実行可能かどうかを知る助けになるかもしれません。

+0

Stax XMLStreamReaderには、同様に場所(input(filename)、row、column)を与えるgetLocation()があります。 JDK 1.6にはデフォルト実装(Sun Sjsxp)が付属していますが、より良いオープンソースの代替製品(Woodstox)もあります。 – StaxMan

+0

同意しますが、私はstaxがScalaでサポートされているかどうかわかりません。 –

2

異なるライブラリやフレームワークを使用したくないと述べたが、すべての優れたJavaストリーミングパーサー(Xerces for Sax、Woodstox、Aalto for Stax)がすべてのイベント/トークン彼らは役立つ。

この情報はDOMツリーのような上位レベルの抽象化によって常に保持されるわけではありませんが(追加のストレージが必要なため、エラー報告に必要な場所情報が常に追跡されるためパフォーマンスは重要ではありません)容易に、または少なくとも修正することが可能である。

関連する問題