Как получить правильные начальные / конечные местоположения тега xml с SAX?

В SAX есть локатор, который отслеживает текущее местоположение. Однако, когда я вызываю его в своем startElement(), он всегда возвращает мне конечное местоположение тега xml.

Как я могу получить начальное местоположение тега? Есть ли способ изящно решить эту проблему?

3 ответа

Решение

К сожалению, 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() указывает на конец ряда пробелов и табуляции. Пока мы отслеживаем конечную позицию этих трех методов, мы можем найти начальную точку для этого тега и уже можем получить конечную позицию этого тега с помощью локатора в 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;
        }
    }
}

}

Какой SAX-парсер вы используете? Некоторые, как мне сказали, не предоставляют услуги Locator.

Вывод простой программы на 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()
Другие вопросы по тегам