java - 转换 XML 以声明根元素上的所有命名空间

标签 java xml xslt sax stax

是否有一种简单的 Java 方法 方法可以将 XML 文档的所有 XML 命名空间声明“移动”到根元素?由于一家未命名的大公司的解析器实现中存在错误,我需要以根元素声明所有使用的命名空间的方式以编程方式重写我们格式良好且有效的 RPC 请求。

不行:

<document-element xmlns="uri:ns1">
    <foo>
        <bar xmlns="uri:ns2" xmlns:ns3="uri:ns3">
            <ns3:foobar/>
            <ns1:sigh xmlns:ns1="uri:ns1"/>
        </bar>
    </foo>
</document-element>

好的:

<document-element xmlns="uri:ns1" xmlns:ns1="uri:ns1" xmlns:ns2="uri:ns2" xmlns:ns3="uri:ns3">
    <foo>
        <ns2:bar>
            <ns3:foobar/>
            <ns1:sigh/>
        </ns2:bar>
    </foo>
</document-element>

缺少前缀的通用名称是可以接受的。只要在根元素上定义了默认 namespace ,就可以保留或替换/添加默认 namespace 。我并不介意使用哪种特定的 XML 技术来实现这一点(不过我更愿意避免使用 DOM)。

澄清一下,this answer指的是我希望在根元素的根元素范围(整个文档)内重新声明 namespace 声明来实现的目标。本质上,相关的问题是问为什么为什么有人会实现我现在需要解决的问题。

最佳答案

编写了一个两遍 StAX 读取器/编写器,这很简单。

import java.io.*;
import java.util.*;
import javax.xml.stream.*;
import javax.xml.stream.events.*;

public class NamespacesToRoot {

    private static final String GENERATED_PREFIX = "pfx";

    private final XMLInputFactory inputFact;
    private final XMLOutputFactory outputFact;
    private final XMLEventFactory eventFactory;

    private NamespacesToRoot() {
        inputFact = XMLInputFactory.newInstance();
        outputFact = XMLOutputFactory.newInstance();
        eventFactory = XMLEventFactory.newInstance();
    }

    public String transform(String xmlString) throws XMLStreamException {
        Map<String, String> pfxToNs = new HashMap<String, String>();
        XMLEventReader reader = null;

        // first pass - analyze
        try {
            if (xmlString == null || xmlString.isEmpty()) {
                throw new IllegalArgumentException("xmlString is null or empty");
            }
            StringReader stringReader = new StringReader(xmlString);
            XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader);
            reader = inputFact.createXMLEventReader(streamReader);

            while (reader.hasNext()) {
                XMLEvent event = reader.nextEvent();                
                if (event.isStartElement()) {
                    buildNamespaces(event, pfxToNs);
                }
            }

            System.out.println(pfxToNs);
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (XMLStreamException ex) {
            }
        }

        // reverse mapping, also gets rid of duplicates
        Map<String, String> nsToPfx = new HashMap<String, String>();
        for (Map.Entry<String, String> entry : pfxToNs.entrySet()) {
            nsToPfx.put(entry.getValue(), entry.getKey());
        }
        List<Namespace> namespaces = new ArrayList<Namespace>(nsToPfx.size());
        for (Map.Entry<String, String> entry : nsToPfx.entrySet()) {
            namespaces.add(eventFactory.createNamespace(entry.getValue(), entry.getKey()));
        }

        // second pass - rewrite
        XMLEventWriter writer = null;
        try {
            StringWriter stringWriter = new StringWriter();
            writer = outputFact.createXMLEventWriter(stringWriter);

            StringReader stringReader = new StringReader(xmlString);
            XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader);
            reader = inputFact.createXMLEventReader(streamReader);

            boolean rootElement = true;
            while (reader.hasNext()) {
                XMLEvent event = reader.nextEvent();                
                if (event.isStartElement()) {
                    StartElement origStartElement = event.asStartElement();
                    String prefix = nsToPfx.get(origStartElement.getName().getNamespaceURI());
                    String namespace = origStartElement.getName().getNamespaceURI();
                    String localName = origStartElement.getName().getLocalPart();
                    Iterator attributes = origStartElement.getAttributes();
                    Iterator namespaces_;
                    if (rootElement) {
                        namespaces_ = namespaces.iterator();                        
                        rootElement = false;
                    } else {
                        namespaces_ = null;
                    }
                    writer.add(eventFactory.createStartElement(
                            prefix, namespace, localName, attributes, namespaces_));
                } else {
                    writer.add(event);
                }                
            }

            writer.flush();

            return stringWriter.toString();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (XMLStreamException ex) {
            }
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (XMLStreamException ex) {
            }
        }
    }

    private void buildNamespaces(XMLEvent event, Map<String, String> pfxToNs) {
        System.out.println("el: " + event);
        StartElement startElement = event.asStartElement();
        Iterator nsIternator = startElement.getNamespaces();
        while (nsIternator.hasNext()) {
            Namespace nsAttr = (Namespace) nsIternator.next();
            if (nsAttr.isDefaultNamespaceDeclaration()) {
                System.out.println("need to generate a prefix for " + nsAttr.getNamespaceURI());
                generatePrefix(nsAttr.getNamespaceURI(), pfxToNs);
            } else {
                System.out.println("add prefix binding for " + nsAttr.getPrefix() + " --> " + nsAttr.getNamespaceURI());
                addPrefix(nsAttr.getPrefix(), nsAttr.getNamespaceURI(), pfxToNs);
            }
        }
    }

    private void generatePrefix(String namespace, Map<String, String> pfxToNs) {
        int i = 1;
        String prefix = GENERATED_PREFIX + i;
        while (pfxToNs.keySet().contains(prefix)) {
            i++;
            prefix = GENERATED_PREFIX + i;
        }
        pfxToNs.put(prefix, namespace);
    }

    private void addPrefix(String prefix, String namespace, Map<String, String> pfxToNs) {
        String existingNs = pfxToNs.get(prefix);
        if (existingNs != null) {
            if (existingNs.equals(namespace)) {
                // nothing to do
            } else {
                // prefix clash, need to rename this prefix or reuse an existing
                // one
                if (pfxToNs.values().contains(namespace)) {
                    // reuse matching prefix
                } else {
                    // rename
                    generatePrefix(namespace, pfxToNs);
                }                
            }
        } else {
            // need to add this prefix
            pfxToNs.put(prefix, namespace);
        }        
    }

    public static void main(String[] args) throws XMLStreamException {
        String xmlString = "" +
"<document-element xmlns=\"uri:ns1\" attr=\"1\">\n" +
"    <foo>\n" +
"        <bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n" +
"            <ns3:foobar ns3:attr1=\"meh\" />\n" +
"            <ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n" +
"        </bar>\n" +
"    </foo>\n" +
"</document-element>";
        System.out.println(xmlString);
        NamespacesToRoot transformer = new NamespacesToRoot();
        System.out.println(transformer.transform(xmlString));
    }
}

请注意,这只是快速示例代码,可以进行一些调整,但对于遇到类似问题的任何人来说也是一个良好的开始。

关于java - 转换 XML 以声明根元素上的所有命名空间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38079187/

相关文章:

java - showInputDialog 使 "if"工作错误

XSLT 如何在元素说到时修剪元素前后的空间?

java - 求最后一列的平均值

java - 在 OpenCV for Java 中执行 HSV 阈值

android - 错误 : Invalid Resource directory name in Eclipse

xml - xpath/xslt 来确定上下文节点相对于所有同名节点的索引?

html - 在 HTML 页面中使用引文本体

xslt - 在 xslt 中重复使用 document() 时的性能

java - 使用 ITextRenderer,图像不是 PDF 格式

java - 将 Java 代理检测传输到生成的 JVM