DL/DR;
我正在使用
StAX
流式解析 XML 文件以提取一些数据。
我遇到的问题是,当我遇到一个同时包含文本和另一个
element
的 element
时,当我尝试使用 xmlEventReader.getElementText()
提取文本时,它会抛出异常,因为该方法期望父级 element
仅包含文本。
<div>Hello <i>World</i>!</div>
事实上,
div
标签直接包含文本和i
标签,导致文本提取失败。
我希望能够从上面的示例 XML 中提取
Hello World!
。
整个故事
我正在编写一个小型 Java 应用程序来导出当前存储在 Evernote 中的食谱,然后将它们导入到另一个应用程序中。
我只是厌倦了 Evernote 不断涌现的注册 PRO 的行为。
我正在使用
StAX
来流式解析包含我的笔记的 XML 文件,每个笔记都包含一个食谱。
我可以从 Evernote 导出所有笔记,现在我需要解析这些笔记以提取我的食谱数据。成分和说明以
CDATA element
内的 HTML 标记存储在笔记正文中。
我基本上解析了所有 XML/HTML 元素,一旦到达
li
标签,我就设置我处于 list-item
内的状态,并将其中的任何文本连接在一起,从而删除任何文本作为格式插入的 HTML 标记。
它工作得很好,但是当有一个
element
包含 text
和另一个 element
时,我遇到了一个小问题。
当我到达该父元素并调用
xmlEventReader.getElementText()
时,它会抛出异常,因为该方法期望 element
仅包含文本。
示例 XML
我使这个示例非常简单,仅包含一些说明,删除了成分逻辑。
<en-note>
<ol>
<li>
<div>Add all ingredients to a </div>
<div>ziplock freezer bag</div>
</li>
<li>
<div>Freezer until needed (<i>maximum 2 months</i>).</div>
</li>
</ol>
</en-note>
这是解析笔记正文的代码。
我简化了这段代码,删除了与成分相关的任何逻辑,因为它与问题无关。
上述 XML 将被加载到代码示例顶部的
recipeContent
变量中。
由于
listItemValueSb.append(xmlEventReader.getElementText());
标签,代码在解析第二个方向时在 i
处中断。
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
...
private static void parseRecipeContent(
final String recipeContent,
final RecipeContentHandler recipeContentHandler
)
throws XMLStreamException
{
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
XMLEventReader xmlEventReader = null;
try(
final StringReader stringReader = new StringReader(recipeContent);
)
{
xmlEventReader = xmlInputFactory.createXMLEventReader(stringReader);
XMLEvent currentEvent;
StartElement tmpStartElement;
EndElement tmpEndElement;
boolean directionsMode = false;
boolean withinLiTag = false;
StringBuilder listItemValueSb = null;
final List<String> directions = new ArrayList<>();
while (xmlEventReader.hasNext())
{
currentEvent = xmlEventReader.nextEvent();
// If the current event is an END-EVENT, then potentially end this recipe.
if (currentEvent.isEndElement())
{
tmpEndElement = currentEvent.asEndElement();
switch (tmpEndElement.getName().getLocalPart().toLowerCase())
{
case "en-note":
// Inform the calling code of the newly found recipe.
recipeContentHandler.handleRecipeContent(
ingredients,
directions
);
break;
case "li":
withinLiTag = false;
if (
(listItemValueSb != null)
&&
(listItemValueSb.length() > 0)
)
{
directions.add(listItemValueSb.toString());
}
break;
default:
break;
}
continue;
}
// If the current event is a START-EVENT, then extract the relevant data.
if (!currentEvent.isStartElement())
{
continue;
}
tmpStartElement = currentEvent.asStartElement();
switch (tmpStartElement.getName().getLocalPart().toLowerCase())
{
case "en-note":
withinLiTag = false;
directions.clear();
break;
case "li":
withinLiTag = true;
listItemValueSb = new StringBuilder();
break;
default:
final XMLEvent nextXMLEvent = xmlEventReader.peek();
if (
(nextXMLEvent == null)
||
!nextXMLEvent.isCharacters()
)
{
break;
}
if (withinLiTag)
{
listItemValueSb.append(xmlEventReader.getElementText());
}
break;
}
}
}
finally
{
if (xmlEventReader != null)
{
xmlEventReader.close();
}
}
}
您的要求似乎是
li
内的所有文本都很重要,无论格式如何。使用 peek
事件并不能很好地处理这种情况。相反,如果您在当前“继续”的分支上获取所有文本内容,并在此处进行更改,则会更容易:
// If the current event is a START-EVENT, then extract the relevant data.
if (!currentEvent.isStartElement()) {
// This ensures all character content inside <li> is recorded
if (withinLiTag && currentEvent.isCharacters()) {
listItemValueSb.append(currentEvent.asCharacters().getData());
}
continue;
}
上面这行收集了
li
的内部文本,并使所有窥视流的“默认”代码变得不必要,只需注释掉即可:
switch (tmpStartElement.getName().getLocalPart().toLowerCase()) {
...
default:
// comment out the default handling:
// final XMLEvent nextXMLEvent = xmlEventReader.peek();
// ...