You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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);
    }
}

代码说明

  1. 辅助方法extractAllText:负责遍历目标元素的所有子事件,收集所有字符数据,自动忽略子元素的标签,最后返回清理后的合并文本。
  2. 替换原出错逻辑:原代码中调用getElementText()的部分,改为调用自定义的extractAllText方法,传入当前元素名称,确保能正确处理包含子元素的情况。
  3. 文本清理:在收集文本时自动去除首尾空格,合并连续空格,保证输出文本的整洁。

运行修改后的代码,会正确输出:

1) Add all ingredients to a ziplock freezer bag
2) Freezer until needed (maximum 2 months).

内容的提问来源于stack exchange,提问作者MattWeiler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 06:55:55