4

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

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

4

3 回答 3

2

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

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

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

如果您需要标签的准确位置,或者想要关于属性、属性内容等的更细粒度的信息,您必须实现自己的位置提供程序。

由于涉及到所有潜在的编码问题、Unicode 字符等,我想这是一个太大的项目,无法在此处发布,实施也将取决于您的具体要求。

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

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

于 2009-07-05T02:39:35.447 回答
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()
于 2009-07-03T06:09:24.113 回答
1

这是我终于想出来的解决方案。(但我太懒了,抱歉。)这里 characters()、endElement() 和 ignorableWhitespace() 方法是至关重要的,它们使用定位器指向标签的可能起点。characters() 中的定位器指向非标签信息的最近结束点,endElement() 中的定位器指向最后一个标签的结束位置,如果它们粘在一起,则可能是该标签的起点,并且 ignorableWhitespace() 中的定位器指向一系列空白和制表符的末尾。只要我们跟踪这三个方法的结束位置,我们就可以找到这个标签的起点,并且我们已经可以通过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;
            }
        }
    }
}
于 2009-07-12T01:18:59.923 回答