使用StAX解析含文本与子元素的父元素时遇异常的解决求助
解决StAX解析混合内容XML元素的文本提取问题
当使用StAX解析包含文本和子元素的XML元素时,xmlEventReader.getElementText()会抛出异常——因为该方法要求元素只能包含纯文本。要提取类似<div>Hello <i>World</i>!</div>中的Hello World!这类混合内容的文本,需要自定义逻辑来遍历元素内的所有事件并收集文本。
核心思路
放弃使用getElementText(),转而实现一个辅助方法:
- 遍历当前元素下的所有事件
- 遇到字符事件时,收集并清理文本(去首尾空格、合并多余空格)
- 遇到子元素的开始/结束事件时直接跳过,继续处理内部内容
- 直到遇到当前元素的结束标签时停止遍历
修改后的代码
首先添加一个提取文本的辅助方法,然后替换原代码中出错的逻辑:
完整修改后的代码
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; public class Test { public static void main(final String[] args) throws XMLStreamException { final String recipeContent = """ <en-note> <div><i>hello</i></div> <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> """; Test.parseRecipeContent( recipeContent, (final List<String> directions) -> { for (int n = 0; n < directions.size(); n++) { System.out.println((n + 1) + ") " + directions.get(n)); } } ); } 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 withinLiTag = false; StringBuilder listItemValueSb = null; final List<String> directions = new ArrayList<>(); while (xmlEventReader.hasNext()) { currentEvent = xmlEventReader.nextEvent(); if (currentEvent.isEndElement()) { tmpEndElement = currentEvent.asEndElement(); switch (tmpEndElement.getName().getLocalPart().toLowerCase()) { case "en-note": recipeContentHandler.handleRecipeContent(directions); break; case "li": withinLiTag = false; if (listItemValueSb != null && listItemValueSb.length() > 0) { directions.add(listItemValueSb.toString().trim().replaceAll(" ", " ")); } break; default: break; } continue; } if (!currentEvent.isStartElement()) { if (withinLiTag && currentEvent.isCharacters()) { String text = currentEvent.asCharacters().getData().trim(); if (!text.isEmpty()) { listItemValueSb.append(text).append(" "); } } 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: if (withinLiTag) { String elementName = tmpStartElement.getName().getLocalPart(); String text = extractAllText(xmlEventReader, elementName); if (!text.isEmpty()) { listItemValueSb.append(text).append(" "); } } break; } } } finally { if (xmlEventReader != null) { xmlEventReader.close(); } } } /** * 提取指定元素内的所有文本内容,忽略子元素标签 */ private static String extractAllText(XMLEventReader reader, String parentElementName) throws XMLStreamException { StringBuilder sb = new StringBuilder(); XMLEvent event; while (reader.hasNext()) { event = reader.nextEvent(); if (event.isEndElement()) { EndElement endElement = event.asEndElement(); if (endElement.getName().getLocalPart().equalsIgnoreCase(parentElementName)) { break; } continue; } if (event.isCharacters()) { String text = event.asCharacters().getData().trim(); if (!text.isEmpty()) { sb.append(text).append(" "); } } // 子元素的开始事件无需处理,继续遍历即可收集子元素内的文本 } return sb.toString().trim().replaceAll(" ", " "); } private static interface RecipeContentHandler { void handleRecipeContent(List<String> directions); } }
代码说明
- 辅助方法
extractAllText:负责遍历目标元素的所有子事件,收集所有字符数据,自动忽略子元素的标签,最后返回清理后的合并文本。 - 替换原出错逻辑:原代码中调用
getElementText()的部分,改为调用自定义的extractAllText方法,传入当前元素名称,确保能正确处理包含子元素的情况。 - 文本清理:在收集文本时自动去除首尾空格,合并连续空格,保证输出文本的整洁。
运行修改后的代码,会正确输出:
1) Add all ingredients to a ziplock freezer bag 2) Freezer until needed (maximum 2 months).
内容的提问来源于stack exchange,提问作者MattWeiler
相关产品推荐
相关产品推荐

