SAX中有一个定位器,它跟踪当前位置。但是,当我在startElement()中调用它时,它总是返回xml标记的结束位置。
如何获取标签的起始位置?有没有办法优雅地解决这个问题?
答案 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;
}
}
}
}