今天在写一个小项目的时候用到了SAX解析,遇到了一点小问题,网上找了好久都没有解决,最后还是自己发现了解决方法,特地和大家分享一下!
先来看下要解析XML文件:
<dict num="219" id="219" name="219"><key>big</key><ps>bɪg</ps><pron>http://res.iciba.com/resource/amp3/oxford/0/dd/3e/dd3eded85f7085dc92210aa496e92ba9.mp3</pron><ps>bɪɡ</ps><pron>http://res.iciba.com/resource/amp3/1/0/d8/61/d861877da56b8b4ceb35c8cbfdf65bb4.mp3</pron><pos>adj.</pos><acceptation>大的;重要的;(计划)庞大的;大方的;
</acceptation><pos>adv.</pos><acceptation>大量地;成功地;夸大地;宽宏大量地;
</acceptation><pos>n.</pos><acceptation>大亨;大公司;
</acceptation><sent><orig>
The word " big " has one syllable and the word " Africa " has three syllables.
</orig><trans>
“ big ” 这个词只有一个音节而 “ Africa ” 有三个音节.
</trans></sent><sent><orig>
There remains a tension at the heart of BIG s predicament.
</orig><trans>
目前,BIG心里仍维持着一种紧张的态势.
</trans></sent><sent><orig>
BIG might sell its Bsian arm to another insurance firm; Prudential was not the only bidder.
</orig><trans>
BIG也许会将其亚洲分支出售给另外一家保险公司, 保信并不是独一的竞价人.
</trans></sent><sent><orig>
Carrie fell for the wealthy Mr. Big.
</orig><trans>
Carrie爱上了那位富有的Big先生.
</trans></sent><sent><orig>
In this sentence, the word BIG is in capitals.
</orig><trans>
本句中BIG一词用的是大写字母.
</trans></sent></dict>
大家都知道SAX解析的关键就是setContentHandler()方法中传入的DefaultHandler的子类,一开始我是这样写的:
public class WordsHandler extends DefaultHandler {
//记录当前节点
private String nodeName;
private Words words;
//单词的词性与词义
private StringBuilder posAcceptation;
//例句
private StringBuilder sent;
/**获取解析后的words对象*/
public Words getWords() {
return words;
}
//开始解析XML时调用
@Override
public void startDocument() throws SAXException {
//初始化
words = new Words();
posAcceptation = new StringBuilder();
sent = new StringBuilder();
}
//结束解析XML时调用
@Override
public void endDocument() throws SAXException {
//将所有解析出来的内容赋予words
words.setPosAcceptation(posAcceptation.toString().trim());
words.setSent(sent.toString().trim());
}
//开始解析节点时调用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
nodeName = localName;
}
//结束解析节点时调用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
}
//获取节点中内容时调用
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String a = new String(ch, start, length);
//去掉文本中原有的换行
for (int i = start; i < start + length; i++) {
if (ch[i] == '\n')
return;
}
//将节点的内容存入Words对象对应的属性中
if ("key".equals(nodeName)) {
words.setKey(a);
} else if ("ps".equals(nodeName)) {
if (words.getPsE().length() <= 0) {
words.setPsE(a);
} else {
words.setPsA(a);
}
} else if ("pron".equals(nodeName)) {
if (words.getPronE().length() <= 0) {
words.setPronE(a);
} else {
words.setPronA(a);
}
} else if ("pos".equals(nodeName)) {
posAcceptation.append(a);
} else if ("acceptation".equals(nodeName)) {
posAcceptation.append(a);
posAcceptation.append("\n");//重新排版换行
} else if ("orig".equals(nodeName)) {
sent.append(a);
sent.append("\n");
} else if ("trans".equals(nodeName)) {
sent.append(a);
sent.append("\n");
} else if ("fy".equals(nodeName)) {
words.setFy(a);
words.setIsChinese(true);
}
}
}
这样的代码基本没什么问题,运行一下,大部分情况都能按照想要结果返回。
但是那,如果解析的字段中出现了“‘ < >这样的特殊符号时,就会发生问题 ,例如这句:
The word " big " has one syllable and the word " Africa " has three syllables.
运行后会发现解析结果是这样的:
The word
" big "
has one syllable and the word
" Africa "
has three syllables.
和想要的结果不一样,于是我找了好多资料,终于在理解的情况下解决了。
characters()方法在解析的时候会不停地调用,每次读取都是以 ’ ” < > 等这些字段为节点,上述情况就是因为characters()方法一次只读到文本中 ” 位置,并没有读完整个节点,因此添加了换行,而后再次调用characters()方法,读取的是“之后的内容,还是在这个节点中。正是这种同一个节点中不同地调用characters()方法,导致产生很多不需要的换行。
修改WordsHandler:
public class WordsHandler extends DefaultHandler {
//记录当前节点
private String nodeName;
private Words words;
//单词的词性与词义
private StringBuilder posAcceptation;
//例句
private StringBuilder sent;
/**获取解析后的words对象*/
public Words getWords() {
return words;
}
//开始解析XML时调用
@Override
public void startDocument() throws SAXException {
//初始化
words = new Words();
posAcceptation = new StringBuilder();
sent = new StringBuilder();
}
//结束解析XML时调用
@Override
public void endDocument() throws SAXException {
//将所有解析出来的内容赋予words
words.setPosAcceptation(posAcceptation.toString().trim());
words.setSent(sent.toString().trim());
}
//开始解析节点时调用
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
nodeName = localName;
}
//结束解析节点时调用
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
//在读完整个节点后换行
if("acceptation".equals(localName)){
posAcceptation.append("\n");
}else if("orig".equals(localName)){
sent.append("\n");
}else if("trans".equals(localName)){
sent.append("\n");
sent.append("\n");
}
}
//获取节点中内容时调用
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String a = new String(ch, start, length);
//去掉文本中原有的换行
for (int i = start; i < start + length; i++) {
if (ch[i] == '\n')
return;
}
//将节点的内容存入Words对象对应的属性中
if ("key".equals(nodeName)) {
words.setKey(a);
} else if ("ps".equals(nodeName)) {
if (words.getPsE().length() <= 0) {
words.setPsE(a);
} else {
words.setPsA(a);
}
} else if ("pron".equals(nodeName)) {
if (words.getPronE().length() <= 0) {
words.setPronE(a);
} else {
words.setPronA(a);
}
} else if ("pos".equals(nodeName)) {
posAcceptation.append(a);
} else if ("acceptation".equals(nodeName)) {
posAcceptation.append(a);
} else if ("orig".equals(nodeName)) {
sent.append(a);
} else if ("trans".equals(nodeName)) {
sent.append(a);
} else if ("fy".equals(nodeName)) {
words.setFy(a);
words.setIsChinese(true);
}
}
}
仅仅是把重新排版的过程放到了endElement()方法执行,此方法是在节点结束时调用,即读到 </xxx>
这个标志时, 在这个方法中添加我们想要的换行就可以了。
上述讲到的那句:The word ” big ” has one syllable and the word ” Africa ” has three syllables.是在一个<org>
</org>
中的,因此也只会在读取到 </org>
后才换行,达到我们想要的换行方式。