转换XML以在根元素上声明所有名称空间

时间:2016-06-28 14:36:56

标签: java xml xslt sax stax

是否有一种简单的Java 方法方法将XML文档的所有XML名称空间声明“移动”到根元素?由于一个未命名的大公司的解析器实现中的一个错误,我需要以root元素声明所有使用的命名空间的方式以编程方式重写我们格式良好且有效的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>

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

为了澄清,this answer指的是我想要在根元素的根元素范围(整个文档)中重新声明命名空间声明。基本上相关的问题是问为什么为什么有人会实现我现在需要解决的问题。

2 个答案:

答案 0 :(得分:1)

下面是一个简单的应用程序,它根据XPath和VTD-XML重新声明命名空间。

import com.ximpleware.*;
import java.io.*;

public class moveNSDeclaration {

    public static void main(String[] args) throws IOException, VTDException{
        // TODO Auto-generated method stub
        VTDGen vg = new VTDGen();
        String xml="<document-element xmlns=\"uri:ns1\">\n"+
                "<foo>\n"+
                "<bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n"+
                "<ns3:foobar/>\n"+
                "<ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n"+
                "</bar>\n"+
                "</foo>\n"+
                "</document-element>\n";
        vg.setDoc(xml.getBytes());
        vg.parse(false); // namespace unaware to all name space nodes addressable using xpath @*
        VTDNav vn = vg.getNav();
        XMLModifier xm = new XMLModifier(vn);
        FastIntBuffer fib = new FastIntBuffer();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // get the index value of xmlns declaration of root element
        AutoPilot ap =new AutoPilot (vn);
        ap.selectXPath("//@*");
        int i=0;
        //remove all ns node under root element
        //save those nodes to be re-inserted into the root element up on verification of uniqueness

        while((i=ap.evalXPath())!=-1){
            if (vn.getTokenType(i)==VTDNav.TOKEN_ATTR_NS){
                xm.remove(); //remove all ns node
                fib.append(i);
            }
        }

        //remove redundant ns nodes
        for (int j=0;j<fib.size();j++){
            if (fib.intAt(j)!=-1){
                for (i=j+1;i<fib.size();i++){
                    if (fib.intAt(i)!=-1)
                    if (vn.compareTokens(fib.intAt(j), vn, fib.intAt(i))==0){
                        fib.modifyEntry(i, -1);
                    }
                }
            }
        }


        // compose a string to insert back into the root element containing all subordinate ns nodes

        for (int j=0;j<fib.size();j++){
            if (fib.intAt(j)!=-1){
                int os = vn.getTokenOffset(fib.intAt(j));
                int len = vn.getTokenOffset(fib.intAt(j)+1)+vn.getTokenLength(fib.intAt(j)+1)+1-os;
                //System.out.println(" os  len "+ os + " "+len);
                //System.out.println(vn.toString(os,len));
                baos.write(" ".getBytes());
                baos.write(vn.getXML().getBytes(),os,len);
            }
        }
        byte[] attrBytes = baos.toByteArray();
        vn.toElement(VTDNav.ROOT);
        xm.insertAttribute(attrBytes);
        //System.out.println(baos.toString());
        baos.reset();
        xm.output(baos);
        System.out.println(baos.toString());
    }
}

输出看起来像

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

答案 1 :(得分:0)

写了一个两遍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));
    }
}

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