SAX には Locator があり、現在の位置を追跡します。ただし、startElement() で呼び出すと、常に xml タグの終了位置が返されます。
タグの開始位置を取得するにはどうすればよいですか? この問題を優雅に解決する方法はありますか?
残念ながら、パッケージLocator
内の Java システム ライブラリによって提供されるインターフェイスでは、org.xml.sax
定義上、ドキュメントの場所に関する詳細情報を取得できません。メソッドのドキュメントから引用するにはgetColumnNumber
(私が追加したハイライト):
メソッドからの戻り値は、診断のための概算値としてのみ意図されています。元の XML ドキュメントの文字コンテンツを編集するための十分な情報を提供することを意図したものではありません。たとえば、行に組み合わせ文字シーケンス、ワイド文字、サロゲート ペア、または双方向テキストが含まれている場合、値はテキスト エディタの表示の列に対応しない場合があります。
その仕様によれば、SAX ドライバーによるベスト エフォートに基づいて、"ドキュメント イベントに関連付けられたテキストの後の最初の文字" の位置を常に取得します。したがって、質問の最初の部分に対する短い答えは次のとおりです。いいえ、Locator
タグの開始位置に関する情報は提供されません。また、ドキュメントでマルチバイト文字 (中国語や日本語のテキストなど) を扱っている場合、SAX ドライバーから得られる位置はおそらく希望どおりのものではありません。
タグの正確な位置を知りたい場合、または属性や属性の内容などについてさらに詳細な情報が必要な場合は、独自のロケーション プロバイダーを実装する必要があります。
すべての潜在的なエンコードの問題、Unicode 文字などが関係しているため、これはここに投稿するには大きすぎるプロジェクトだと思います。実装は、特定の要件にも依存します。
個人的な経験からの簡単な警告: InputStream
SAX パーサーに渡すラッパーを記述することは危険です。なぜなら、SAX パーサーがストリームから既に読み取ったものに基づいてイベントを報告するタイミングがわからないからです。
情報を使用することに加えて、改行、タブなどをチェックすることにより、characters(char[], int, int)
メソッドで独自のカウントを行うことから始めることができます。これにより、ドキュメント内の実際の場所をよりよく把握できます。最後のイベントの位置を記憶することで、現在のイベントの開始位置を計算できます。ただし、すべての改行が表示されない可能性があることを考慮してください。改行は では表示されないタグ内に表示される可能性がありますが、情報からそれらを推測することはできます。ContentHandler
Locator
characters
Locator
ここに私が最終的に考え出した解決策があります。(しかし、私はそれを出すのが面倒でした。申し訳ありません。) ここで、characters()、endElement()、および ignorableWhitespace() メソッドが重要であり、ロケーターを使用して、タグの可能な開始点を指します。characters() のロケーターは、非タグ情報の最も近い終点を指し、endElement() のロケーターは、最後のタグの終了位置を指します。これらがくっついている場合、このタグがこのタグの開始点になる可能性があります。 ignorableWhitespace() のロケータは、一連の空白とタブの終わりを指しています。これら 3 つのメソッドの終了位置を追跡している限り、このタグの開始点を見つけることができ、endElement() のロケーターを使用してこのタグの終了位置を既に取得できます。したがって、
class Example extends DefaultHandler{
private Locator locator;
private SourcePosition startElePoint = new SourcePosition();
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
/**
* <a> <- the locator points to here
* <b>
* </a>
*/
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
}
/**
* <a>
* <b>
* </a> <- the locator points to here
*/
public void endElement(String uri, String localName, String qName) {
/* here we can get our source position */
SourcePosition tag_source_starting_position = this.startElePoint;
SourcePosition tag_source_ending_position =
new SourcePosition(this.locator.getLineNumber(),
this.locator.getColumnNumber());
// do your things here
//update the starting point for the next tag
this.updateElePoint(this.locator);
}
/**
* some other words <- the locator points to here
* <a>
* <b>
* </a>
*/
public void characters(char[] ch, int start, int length) {
this.updateElePoint(this.locator);//update the starting point
}
/**
*the locator points to here-> <a>
* <b>
* </a>
*/
public void ignorableWhitespace(char[] ch, int start, int length) {
this.updateElePoint(this.locator);//update the starting point
}
private void updateElePoint(Locator lo){
SourcePosition item = new SourcePosition(lo.getLineNumber(), lo.getColumnNumber());
if(this.startElePoint.compareTo(item)<0){
this.startElePoint = item;
}
}
class SourcePosition<SourcePosition> implements Comparable<SourcePosition>{
private int line;
private int column;
public SourcePosition(){
this.line = 1;
this.column = 1;
}
public SourcePosition(int line, int col){
this.line = line;
this.column = col;
}
public int getLine(){
return this.line;
}
public int getColumn(){
return this.column;
}
public void setLine(int line){
this.line = line;
}
public void setColumn(int col){
this.column = col;
}
public int compareTo(SourcePosition o) {
if(o.getLine() > this.getLine() ||
(o.getLine() == this.getLine()
&& o.getColumn() > this.getColumn()) ){
return -1;
}else if(o.getLine() == this.getLine() &&
o.getColumn() == this.getColumn()){
return 0;
}else{
return 1;
}
}
}
}
どの SAX パーサーを使用していますか? ロケータ機能を提供していないものもあります。
以下の単純な Python プログラムの出力は、XML ファイル内のすべての要素の開始行番号と列番号を示します。たとえば、XML で 2 つのスペースをインデントした場合です。
Element: MyRootElem
starts at row 2 and column 0
Element: my_first_elem
starts at row 3 and column 2
Element: my_second_elem
starts at row 4 and column 4
次のように実行します。python sax_parser_filename.py my_xml_file.xml
#!/usr/bin/python
import sys
from xml.sax import ContentHandler, make_parser
from xml.sax.xmlreader import Locator
class MySaxDocumentHandler(ContentHandler):
"""
the document handler class will serve
to instantiate an event handler which will
acts on various events coming from the parser
"""
def __init__(self):
self.setDocumentLocator(Locator())
def startElement(self, name, attrs):
print "Element: %s" % name
print "starts at row %s" % self._locator.getLineNumber(), \
"and column %s\n" % self._locator.getColumnNumber()
def endElement(self, name):
pass
def mysaxparser(inFileName):
# create a handler
handler = MySaxDocumentHandler()
# create a parser
parser = make_parser()
# associate our content handler to the parser
parser.setContentHandler(handler)
inFile = open(inFileName, 'r')
# start parser
parser.parse(inFile)
inFile.close()
def main():
mysaxparser(sys.argv[1])
if __name__ == '__main__':
main()