使用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
相关产品推荐
相关产品推荐

