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

使用TransformerFactoryImpl拆分无根XML遇格式错误的解决方案问询

无统一根标签XML文件的拆分解决方案(无需添加虚拟根)

问题背景

使用com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl拆分XML时,抛出错误:The markup in the document following the root element must be well-formed.,原因是待拆分XML为同级<AB>标签结构,无单一根节点,不符合XML规范,导致解析和转换报错。

方案1:直接用StAX API手动处理(绕过Transformer)

Transformer要求输入为合法XML结构,因此可以直接使用StAX的XMLStreamReader遍历元素,手动写入每个<AB>节点到输出文件,无需依赖Transformer。

修改后的核心代码:

public void run(final String inFile, final String outFilePath, final String splitTag, final String chunkNumber) {
    XMLStreamReader xsr = null;
    XMLStreamWriter xsw = null;
    final int defaultChunkNumber = StringUtils.isBlank(chunkNumber) ? 20000 : Integer.parseInt(chunkNumber);
    try {
        final XMLInputFactory xif = XMLInputFactory.newInstance();
        // 关闭DTD和外部实体支持,适配非标准XML片段
        xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
        xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
        xsr = xif.createXMLStreamReader(new FileReader(inFile));
        
        int fileNumber = 0;
        int dataRepetitions = 0;
        xsw = write(outFilePath, ++fileNumber);
        
        while (xsr.hasNext()) {
            int eventType = xsr.next();
            if (eventType == XMLEvent.START_ELEMENT && xsr.getLocalName().equals(splitTag)) {
                if (dataRepetitions >= defaultChunkNumber) {
                    xsw.close();
                    xsw = write(outFilePath, ++fileNumber);
                    dataRepetitions = 0;
                }
                // 递归写入当前元素的完整内容
                writeFullElement(xsr, xsw);
                dataRepetitions++;
                // 写入换行分隔元素
                xsw.writeCharacters(SplitHelper.Constants.RegExp.BREAK_LINE);
            }
        }
    } catch (final FileNotFoundException | XMLStreamException e) {
        throw new SplitXmlRuntimeException(e.getMessage());
    } finally {
        try {
            if (xsr != null) xsr.close();
            if (xsw != null) {
                xsw.flush();
                xsw.close();
            }
        } catch (final XMLStreamException e) {
            LOG.error(e.getMessage());
        }
    }
}

private void writeFullElement(XMLStreamReader reader, XMLStreamWriter writer) throws XMLStreamException {
    // 写入开始标签及属性
    writer.writeStartElement(reader.getPrefix(), reader.getLocalName(), reader.getNamespaceURI());
    for (int i = 0; i < reader.getAttributeCount(); i++) {
        writer.writeAttribute(reader.getAttributePrefix(i), reader.getAttributeNamespace(i), 
                              reader.getAttributeLocalName(i), reader.getAttributeValue(i));
    }
    // 写入命名空间声明
    for (int i = 0; i < reader.getNamespaceCount(); i++) {
        writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
    }
    
    // 遍历元素内部事件,直到遇到结束标签
    while (reader.hasNext()) {
        int eventType = reader.next();
        switch (eventType) {
            case XMLEvent.CHARACTERS:
                writer.writeCharacters(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
                break;
            case XMLEvent.START_ELEMENT:
                writeFullElement(reader, writer);
                break;
            case XMLEvent.END_ELEMENT:
                writer.writeEndElement();
                return;
            case XMLEvent.COMMENT:
                writer.writeComment(reader.getText());
                break;
            case XMLEvent.PROCESSING_INSTRUCTION:
                writer.writeProcessingInstruction(reader.getPITarget(), reader.getPIData());
                break;
            default:
                break;
        }
    }
}

private XMLStreamWriter write(String outfilePath, int fileNumber) throws XMLStreamException, FileNotFoundException  {
    XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
    XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(
        new FileOutputStream(new File(outfilePath, SplitHelper.Methods.File.getXmlFileName((byte) fileNumber)), true));
    writer.writeStartDocument();
    writer.writeCharacters(SplitHelper.Constants.RegExp.BREAK_LINE);
    return writer;
}

说明:

  • 直接操作StAX读写API,不依赖Transformer对完整XML的要求
  • 递归方法writeFullElement保证每个<AB>元素及其子节点被完整写入
  • 关闭StAX的DTD和外部实体支持,避免解析非标准XML时的限制

方案2:用XMLFilter动态补全根节点(保留Transformer)

若需保留Transformer,可通过XMLFilter在解析阶段动态添加虚拟根节点,无需修改原文件,仅在内存中修复XML结构。

核心代码示例:

public void run(final String inFile, final String outFilePath, final String splitTag, final String chunkNumber) {
    XMLStreamReader xsr = null;
    XMLStreamWriter xsw = null;
    final int defaultChunkNumber = StringUtils.isBlank(chunkNumber) ? 20000 : Integer.parseInt(chunkNumber);
    try {
        final XMLInputFactory xif = XMLInputFactory.newInstance();
        xsr = xif.createXMLStreamReader(new FileReader(inFile));
        
        // 创建XMLFilter,动态包裹虚拟根节点
        XMLFilterImpl filter = new XMLFilterImpl() {
            private boolean rootAdded = false;
            private int elementDepth = 0;
            
            @Override
            public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
                if (!rootAdded) {
                    super.startElement("", "dummyRoot", "dummyRoot", new AttributesImpl());
                    rootAdded = true;
                }
                super.startElement(uri, localName, qName, atts);
                elementDepth++;
            }
            
            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException {
                super.endElement(uri, localName, qName);
                elementDepth--;
                if (rootAdded && elementDepth == 0) {
                    super.endElement("", "dummyRoot", "dummyRoot");
                    rootAdded = false;
                }
            }
        };
        
        // 绑定SAX解析器
        filter.setParent(SAXParserFactory.newInstance().newSAXParser());
        // 将StAXReader转换为SAX输入源
        SAXSource saxSource = new SAXSource(new StAXSAXParser(xsr), new InputSource());
        
        int fileNumber = 0;
        int dataRepetitions = 0;
        xsw = write(outFilePath, ++fileNumber);
        
        // 自定义SAX处理器,捕获目标元素并写入输出
        DefaultHandler handler = new DefaultHandler() {
            private boolean inTargetElement = false;
            private final XMLStreamWriter currentWriter;
            private StringWriter elementBuffer;
            
            public DefaultHandler(XMLStreamWriter writer) {
                this.currentWriter = writer;
            }
            
            @Override
            public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
                if (localName.equals(splitTag)) {
                    inTargetElement = true;
                    elementBuffer = new StringWriter();
                    try (XMLStreamWriter tempWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(elementBuffer)) {
                        tempWriter.writeStartElement(localName);
                        for (int i = 0; i < atts.getLength(); i++) {
                            tempWriter.writeAttribute(atts.getLocalName(i), atts.getValue(i));
                        }
                    } catch (XMLStreamException e) {
                        throw new SAXException(e);
                    }
                } else if (inTargetElement) {
                    try (XMLStreamWriter tempWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(elementBuffer)) {
                        tempWriter.writeStartElement(localName);
                        for (int i = 0; i < atts.getLength(); i++) {
                            tempWriter.writeAttribute(atts.getLocalName(i), atts.getValue(i));
                        }
                    } catch (XMLStreamException e) {
                        throw new SAXException(e);
                    }
                }
            }
            
            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException {
                if (localName.equals(splitTag)) {
                    inTargetElement = false;
                    try (XMLStreamWriter tempWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(elementBuffer)) {
                        tempWriter.writeEndElement();
                    } catch (XMLStreamException e) {
                        throw new SAXException(e);
                    }
                    // 写入捕获的元素到输出文件
                    try {
                        currentWriter.writeRaw(elementBuffer.toString());
                        currentWriter.writeCharacters(SplitHelper.Constants.RegExp.BREAK_LINE);
                        dataRepetitions++;
                        if (dataRepetitions >= defaultChunkNumber) {
                            currentWriter.close();
                            fileNumber++;
                            currentWriter = write(outFilePath, fileNumber);
                            dataRepetitions = 0;
                        }
                    } catch (XMLStreamException e) {
                        throw new SAXException(e);
                    }
                } else if (inTargetElement) {
                    try (XMLStreamWriter tempWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(elementBuffer)) {
                        tempWriter.writeEndElement();
                    } catch (XMLStreamException e) {
                        throw new SAXException(e);
                    }
                }
            }
            
            @Override
            public void characters(char[] ch, int start, int length) throws SAXException {
                if (inTargetElement) {
                    elementBuffer.write(ch, start, length);
                }
            }
        };
        
        filter.setContentHandler(handler);
        filter.parse(saxSource.getInputSource());
        
    } catch (final Exception e) {
        throw new SplitXmlRuntimeException(e.getMessage());
    } finally {
        try {
            if (xsr != null) xsr.close();
            if (xsw != null) {
                xsw.flush();
                xsw.close();
            }
        } catch (final XMLStreamException e) {
            LOG.error(e.getMessage());
        }
    }
}

说明:

  • 通过XMLFilter在SAX解析时动态添加虚拟根,让Transformer能处理合法XML
  • 自定义SAXHandler捕获目标<AB>元素,写入到输出文件实现拆分
  • 无需修改原XML文件,仅在内存中完成结构修复

关键注意事项

  • Transformer必须输入合法XML,因此若使用该组件,必须通过动态过滤或内存补全的方式保证结构合规
  • 直接使用StAX手动处理的方式性能更优,适合大体积非标准XML片段
  • 处理时需注意保留XML的命名空间、属性、注释等细节,避免数据丢失

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 00:52:50