如何使用SAX获取xml标记的正确起始/结束位置?

时间:2009-07-03 05:18:01

标签: java sax

SAX中有一个定位器,它跟踪当前位置。但是,当我在startElement()中调用它时,它总是返回xml标记的结束位置。

如何获取标签的起始位置?有没有办法优雅地解决这个问题?

3 个答案:

答案 0 :(得分:2)

不幸的是,Locator包中的Java系统库提供的org.xml.sax接口不允许按定义提供有关文档位置的更多详细信息。引用getColumnNumber方法的documentation(由我添加的重点):

  

为了诊断,该方法的返回值仅用于作为近似值;它无意提供足够的信息来编辑原始XML文档的字符内容。例如,当行包含组合字符序列,宽字符,代理项对或双向文本时,该值可能与文本编辑器显示中的列不对应

根据该规范,您将始终根据SAX驱动程序的最大努力获得“与文档事件相关联的文本之后的第一个字符的 ”。因此,对问题第一部分的简短回答是:不,Locator不提供有关代码起始位置的信息。此外,如果您处理文档中的多字节字符,例如中文或日文文本,您从SAX驱动程序获得的位置可能不是您想要的。

如果您正在查找标签的确切位置,或者想要了解有关属性,属性内容等的更细粒度信息,则必须实施自己的位置提供程序。

由于涉及所有潜在的编码问题,Unicode字符等,我想这是一个太大的项目要发布在这里,实现还将取决于您的具体要求。

从个人经验中快速警告:在传递到SAX解析器的InputStream周围编写一个包装器是危险的,因为您不知道SAX解析器何时根据它已经读取的内容报告它的事件来自溪流。

除了使用characters(char[], int, int)信息之外,您还可以通过检查换行符,标签页等,在ContentHandler的{​​{1}}方法中对自己进行一些计数。应该让你更好地了解你实际上在文档中的位置。通过记住最后一个事件的位置,您可以计算当前事件的起始位置。但请注意,您可能看不到所有换行符,因为这些换行符可能出现在Locator中您看不到的标记内,但您可以从characters信息中推断出这些换行符。

答案 1 :(得分:1)

您使用的SAX解析器是什么?据我所知,有些人不提供定位设施。

下面的简单Python程序的输出将为您提供XML文件中每个元素的起始行和列号,例如:如果你在XML中缩进两个空格:

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()

答案 2 :(得分:0)

这是我最终想出的解决方案。 (但是我太懒了,不好意思。)这里的characters(),endElement()和ignorableWhitespace()方法很重要,有一个定位器,它们指向标签的可能起点。 characters()中的定位符指向非标记信息的cloest结束点,endElement()中的定位符指向最后一个标记的结束位置,如果它们粘在一起,它可能是此标记的起始点,并且ignorableWhitespace()中的定位器指向一系列空白区域和制表符的末尾。只要我们跟踪这三种方法的结束位置,我们就可以找到这个标记的起点,我们已经可以使用endElement()中的定位符获得该标记的结束位置。因此,可以成功找到xml的起点和终点。

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.location.getLineNumber(),
            this.location.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;
        }
    }
}

}