10

この質問に対する満足のいく答えはおそらくないと思いますが、何か聞き逃した場合に備えて質問します。

基本的に、特定の要素インスタンスから、特定の XML 要素のソース ドキュメント内の行を見つけたいと考えています。これは、診断エラー メッセージを改善するためだけに必要です。XML は構成ファイルの一部であり、何か問題がある場合は、エラー メッセージの読者に XML ドキュメントの正確な場所を示すことができるようにしたいと考えています。エラーを修正できるようにします。

標準の Scala XML サポートには、おそらくこのような組み込み機能がないことを理解しています。結局のところ、すべてのNodeSeqインスタンスにそのような情報で注釈を付けるのは無駄であり、すべての XML 要素が解析元のソース ドキュメントを持っているわけではありません。標準の Scala XML パーサーは行情報を捨ててしまい、後でそれを取得する方法がないように思えます。

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

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

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

4

4 に答える 4

11

どうすればいいのかわからなかったのですが、パンゲア が道を教えてくれました。まず、場所を処理するためのトレイトを作成しましょう。

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にほとんど追加しません。完全に置き換える必要があると感じた場合は、そのソースコードを確認することをお勧めします。しかし、これは、Scalaにはすでにエラーを生成する特性があるため、これらすべてを自分で処理したい場合にのみ発生します。XMLLoadersave

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

ちなみに、ConsoleErrorHandlerトレイトは例外からその行と番号の情報を抽出します。私たちの目的のために、例外の外の場所も必要です(私は仮定しています)。

ここで、ノードの作成自体を変更するには、scala.xml.factory.FactoryAdapter抽象メソッドを見てください。に落ち着きましたcreateNodeが、レベルでオーバーライドしています。これは、属性を追加できるのではなく、NoBindingFactoryAdapterが返されるためです。それで:ElemNode

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このスタックからvar使用者にポップするようにオーバーライドすることで改善できることの1つですcreateNode

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

于 2010-12-15T12:07:15.983 に答える
4

scalaは内部的に解析に SAX を使用していることがわかります。SAX を使用すると、ContentHandlerに Locator を設定できます。これを使用して、エラーが発生した現在の場所を取得できます。ただし、Scala の内部動作をどのように活用できるかはわかりません。これが実行可能かどうかを確認するのに役立つと思われる、私が見つけた1 つの記事を次に示します。

于 2010-12-15T04:52:53.153 に答える
2

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

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

たとえば、Schematron で使用されている Ken Holman (と思われる) によるこの XSLT テンプレートは、コンテキスト ノードの場所/ID を記述する 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 を使用できるかどうかはわかりませんが、利用可能などのツールでも同じ原則を適用できます。

于 2010-12-15T04:25:25.307 に答える
2

別のライブラリやフレームワークを使用したくないとおっしゃいましたが、すべての優れた Java ストリーミング パーサー (Sax の Xerces、Stax の Woodstox、および Aalto) は、それらが提供するすべてのイベント/トークンで位置情報を利用できるようにすることに注意してください。

この情報は、DOM ツリーなどの上位レベルの抽象化によって常に保持されるとは限りませんが (追加のストレージが必要なためです。位置情報はエラー報告に必要なため常に追跡されるため、パフォーマンスは大きな問題ではありません)、これは簡単であるか、少なくとも可能性があります。修正可能。

于 2010-12-15T06:53:36.887 に答える