在Java DOM文档中设置命名空间和前缀

14

我正在尝试将ResultSet转换为XML文件。 我首先使用了这个序列化的例子。

import  org.w3c.dom.bootstrap.DOMImplementationRegistry;
import  org.w3c.dom.Document;
import  org.w3c.dom.ls.DOMImplementationLS;
import  org.w3c.dom.ls.LSSerializer;

...

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();

DOMImplementationLS impl = 
    (DOMImplementationLS)registry.getDOMImplementation("LS");

...     

LSSerializer writer = impl.createLSSerializer();
String str = writer.writeToString(document);

完成这个程序后,我尝试验证我的XML文件,结果出现了几个警告。其中一个是关于没有文档类型声明。因此,我尝试了另一种实现方式。我发现了Transformer类,该类允许我设置编码、文档类型等。

之前的实现支持自动命名空间修复,而下面的实现则不支持。

private static Document toDocument(ResultSet rs) throws Exception {   
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document doc = builder.newDocument();

    URL namespaceURL = new URL("http://www.w3.org/2001/XMLSchema-instance");
    String namespace = "xmlns:xsi="+namespaceURL.toString();

    Element messages = doc.createElementNS(namespace, "messages");
    doc.appendChild(messages);

    ResultSetMetaData rsmd = rs.getMetaData();
    int colCount = rsmd.getColumnCount();

    String attributeValue = "true";
    String attribute = "xsi:nil";

    rs.beforeFirst();

    while(rs.next()) {
        amountOfRecords = 0;
        Element message = doc.createElement("message");
        messages.appendChild(message);

        for(int i = 1; i <= colCount; i++) {

            Object value = rs.getObject(i);
            String columnName = rsmd.getColumnName(i);

            Element messageNode = doc.createElement(columnName);

            if(value != null) {
                messageNode.appendChild(doc.createTextNode(value.toString()));
            } else {
                messageNode.setAttribute(attribute, attributeValue);
            }
            message.appendChild(messageNode);
        }
        amountOfRecords++;
    }
    logger.info("Amount of records archived: " + amountOfRecords);

    TransformerFactory tff = TransformerFactory.newInstance();
    Transformer tf = tff.newTransformer();
    tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    tf.setOutputProperty(OutputKeys.INDENT, "yes");

    BufferedWriter bf = createFile();
    StreamResult sr = new StreamResult(bf);
    DOMSource source = new DOMSource(doc);
    tf.transform(source, sr);

    return doc;
}

在测试先前的实现时,我遇到了一个TransformationException错误:未声明“xsi”前缀的名称空间。正如您所看到的,我尝试为文档的根元素添加一个带有xsi前缀的命名空间。经过测试后,我仍然收到了异常。设置命名空间及其前缀的正确方法是什么?

编辑:我在第一种实现中遇到的另一个问题是XML文档中的最后一个元素没有最后三个关闭标签。


3个回答

36

在 namespaceAware 文档中设置节点的正确方法是使用:

rootNode.createElementNS("http://example/namespace", "PREFIX:aNodeName");

您可以将“PREFIX”替换为自定义前缀,并将“aNodeName”替换为节点名称。为避免每个节点都有其自己的命名空间声明,您可以将命名空间定义为根节点上的属性,如下所示:

rootNode.setAttribute("xmlns:PREFIX", "http://example/namespace");
请务必设置:

documentBuilderFactory.setNamespaceAware(true)
否则,您将没有命名空间感知能力。

1
+1,虽然我不需要调用setNamespaceAware。该函数的文档表明它与解析相关。 - Drew Noakes
1
请注意,有一个setPrefix(prefix);方法可以动态设置前缀。 - Christophe Roussy

11
请注意,使用setAttribute设置xmlns-prefix是错误的。如果您想要例如签署您的DOM,则必须使用setAttributeNS: element.setAttributeNS(“http://www.w3.org/2000/xmlns/”,“xmlns:PREFIX”,“http://example/namespace”);

1
你注意到任何特定的例子有区别吗?当我将 DOM 转换为文件时,使用 setAttribute 或 setAttributeNS 得到相同的输出。 您是否注意到转换修复的运行时 DOM 中存在一些差异? - JBert
这可能有效,但是你有一个硬编码的命名空间。除非它是对标准模式的引用,否则我真的喜欢在配置文件中使用这些东西。 - TastyWheat
不管值来自哪里,重要的是如果你使用命名空间,就要使用setAttributeNS。 - jokster
1
@JBert 如果仍然相关(2015年):我们在使用setAttribute而不是setAttributeNS的代码中遇到了XML签名问题。签名无法验证。 - jokster
谢谢您的回复。因此,“setAttributesNS”似乎正确地操作了内存中的DOM以准备进行签名,而“setAttribute”仅在序列化后才提供正确的结构(也许是序列化器修复了问题)。然后我可以看到内存中DOM上的小差异可能会导致不同的XML规范化结果。验证器显然会读取序列化的XML,因此如果签名是从具有“无效”状态的DOM生成的,则规范化中的这些差异确实可能会导致创建无效的签名。 - JBert
那正是情况。 - jokster

9
你没有在根节点中添加命名空间声明;你只是在命名空间中声明了根节点,这是两个完全不同的事情。 当构建DOM时,您需要在每个相关节点上引用命名空间。 换句话说,在添加属性时,您需要定义其命名空间(例如,setAttributeNS)。
附注:虽然XML命名空间看起来像URL,但实际上并不是。 这里没有使用URL类的必要。

1
谢谢,现在它可以工作了。今天又学到了新东西,就像每一天一样。 - TrashCan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接