如何更改DOM中每个节点上的命名空间?

时间:2009-08-22 19:17:55

标签: java xml dom

如果给定一个w3c DOM(Java的默认实现,特别是)更改该DOM中每个元素/属性/节点的命名空间,我该怎么办?有效地,优选地。 DOM上似乎没有setNamespaceURI方法,这很不方便。

我已经尝试过XSL方法,但是他们无法在JAXP变换器中工作(虽然它们在Saxon9B中可以正常工作,但由于其他各种原因我无法使用它。)

基本上,我需要一个纯粹的核心java解决方案,它允许我获取一个文档并更改其命名空间。

8 个答案:

答案 0 :(得分:9)

这在名称空间感知DOM上效率不高。您必须在要更改其名称空间的每个后代元素上使用DOM Level 3 Core方法Document.renameNodejavadoc)。 (通常不需要更改这么多的Attr节点,因为没有前缀的Attr节点的命名空间始终为null,而不是Element的命名空间。)

如果您要做的只是将一个命名空间替换为另一个命名空间,那么使用名称空间不知道的DOM可能会更快,只需更改有问题的xmlns属性即可。你应该能够通过将DOMConfiguration'namespaces'参数设置为false来获得一个名称空间不知道的DOM,但是我没有在Java中尝试过这个,而且这是一种模糊的东西,因为DOM imps会出错。< / p>

答案 1 :(得分:3)

基于我极度偏见的意见,你想要的东西将是一个巨大的痛苦。我可以看到类型转换地狱和许多递归for循环所需要的可靠性!啊,Java的默认实现,我如何讨厌你的NPE:s内部,逆向逻辑,简单操作所需的额外步骤!

所以是的,我的建议是对每个可能的节点类型进行类型转换的循环是递归的,根据我个人的经验,Java的默认实现很糟糕。

答案 2 :(得分:2)

如果intent只是更改名称空间,那么只需使用一些流编辑器将NS映射更改为URL。

Namspace或多或少是命名空间前缀和URI之间的绑定。为了快速更改名称空间,只需更改映射:

在: 的xmlns:myns名字= “我的名称空间URI”

在: 的xmlns:myns名字= “我的新的命名空间-URI”

如果intent只是改变命名空间,那么基本上改变映射就足够了。此外,如果XML Document具有默认命名空间,则更改默认命名空间URL值将更改整个文档的命名空间。

在: 的xmlns = “我的名称空间URI”

在: 的xmlns = “我的新的命名空间-URI”

答案 3 :(得分:2)

  

如果给定一个w3c DOM(Java的默认实现,特别是)更改该DOM中每个元素/属性/节点的命名空间,我该怎么办?有效,最好。

我认为没有一种有效的解决方案也很强大。你不能只重命名根元素上的东西。请考虑以下文件:

文档1

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:all" xmlns:f="urn:fleet" xmlns:m="urn:mission">
  <f:starfleet>
    <m:bold>
      <f:ship name="Enterprise" />
    </m:bold>
  </f:starfleet>
</root>

文档2

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:all">
  <starfleet xmlns="urn:fleet">
    <bold xmlns="urn:mission">
      <ship xmlns="urn:fleet" name="Enterprise" />
    </bold>
  </starfleet>
</root>

文档3

<?xml version="1.0" encoding="UTF-8"?>
<r:root xmlns:r="urn:all">
  <r:starfleet xmlns:r="urn:fleet">
    <r:bold xmlns:r="urn:mission">
      <r:ship xmlns:r="urn:fleet" name="Enterprise" />
    </r:bold>
  </r:starfleet>
</r:root>

这三个文档在名称空间感知DOM中是等效的。您可以针对其中任何一个运行相同的namespaced XPath queries

由于DOM允许您准确指定节点应该如何命名空间,因此不存在更改命名空间的全部一步调用。您需要在不考虑前缀和URI值的情况下使用DOM,而是在任何给定时间考虑scope

此XSLT可与Transformer一起使用,将名为urn:fleet的命名空间更改为urn:new的命名空间:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:f="urn:fleet" version="1.0">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="*">
    <xsl:copy>
      <xsl:copy-of select="@*" />
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="f:*">
    <xsl:variable name="var.foo" select="local-name()" />
    <xsl:element namespace="urn:new" name="{$var.foo}">
      <xsl:copy-of select="@*" />
      <xsl:apply-templates />
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

警告:需要进一步调整以处理命名空间属性;摇晃的urn:fleet声明可以留下来,这很混乱,但很大程度上无关紧要;可能是其他我没想过的东西。

答案 4 :(得分:1)

给定DOM文档的代码将返回一个新的DOM文档,其中应用了一组给定的名称空间URI翻译(uriMap)。键必须是源文档中的URI,值是目标文档中的替换URI。未知的名称空间URI通过不变。它知道更改xmlns:*属性的值,但不会更改可能碰巧将名称空间URI作为其值的其他属性(例如XSD targetNamespace)

private static Node makeClone(Node kid, Node to, Map<String, String> uriMap) {
   Document doc = to.getNodeType() == Node.DOCUMENT_NODE ?
           (Document) to :
           to.getOwnerDocument();
   if (kid.getNodeType() == Node.ELEMENT_NODE) {
      String newURI =
              uriMap.containsKey(kid.getNamespaceURI()) ?
              uriMap.get(kid.getNamespaceURI()) :
              kid.getNamespaceURI();
      Element clone = doc.createElementNS(newURI, kid.getNodeName());
      to.appendChild(clone);
      for (int i = 0; i < kid.getAttributes().getLength(); i++) {
         Attr attr = (Attr) kid.getAttributes().item(i);
         String newAttrURI =
                 uriMap.containsKey(attr.getNamespaceURI()) ?
                 uriMap.get(attr.getNamespaceURI()) :
                 attr.getNamespaceURI();
         String newValue = attr.getValue();
         if (attr.getNamespaceURI() != null &&
                 attr.getNamespaceURI().equals(
                 "http://www.w3.org/2000/xmlns/") &&
                 uriMap.containsKey(attr.getValue()))
            newValue = uriMap.get(attr.getValue());
         clone.setAttributeNS(newAttrURI, attr.getNodeName(), newValue);
      }
      return clone;
   }
   Node clone = kid.cloneNode(false);
   doc.adoptNode(clone);
   to.appendChild(clone);
   return clone;
}

private static void copyKidsChangingNS(Node from, Node to,
        Map<String, String> uriMap) {
   NodeList kids = from.getChildNodes();
   for (int i = 0; i < kids.getLength(); i++) {
      Node kid = kids.item(i);
      Node clone = makeClone(kid, to, uriMap);
      copyKidsChangingNS(kid, clone, uriMap);
   }
}

public static Document changeDocNS(Document doc, Map<String, String> uriMap)
        throws Exception {
   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   dbf.setNamespaceAware(true);
   DocumentBuilder db = dbf.newDocumentBuilder();
   Document newDoc = db.newDocument();
   copyKidsChangingNS(doc, newDoc, uriMap);
   return newDoc;
}

答案 5 :(得分:0)

通过将targetnamespace属性应用于根元素,在没有定义的名称空间前缀的每个元素上更改名称空间。执行此操作还需要您使用命名空间前缀更改每个元素。您可以手动更改此前缀,也可以编写一些脚本逻辑来遍历DOM树,以便仅在必要时应用它。

以下是有关targetnamespace属性和nonamespaceschema属性的更多内容:

http://www.xml.com/pub/a/2000/11/29/schemas/part1.html?page=8 http://www.computerpoweruser.com/editorial/article.asp?article=articles%2Farchive%2Fc0407%2F48c07%2F48c07.asp

答案 6 :(得分:0)

您可以将DOM树复制到另一棵树,并在处理过程中进行一些调整。 例如,使用org.apache.xml.utils.DOMBuilder作为ContentHandler的实现,您可以以这种方式覆盖方法:

public void startElement(String ns, String localName, String name, Attributes atts) throws SAXException {
        super.startElement("new_namespace", localName, name, atts);
    }

DOMBuilder将在复制期间处理所有脏工作,只留下命名空间替换逻辑。

答案 7 :(得分:0)

如果您可以使用Xerces类,则可以创建一个DOMParser,用您修复的URI替换属性和元素的URI:

import org.apache.xerces.parsers.DOMParser;

public static class MyDOMParser extends DOMParser {
    private Map<String, String> fixupMap = ...;

    @Override
    protected Attr createAttrNode(QName attrQName)
    {
        if (fixupMap.containsKey(attrQName.uri))
            attrQName.uri = fixupMap.get(attrQName.uri);
        return super.createAttrNode(attrQName);
    }

    @Override
    protected Element createElementNode(QName qName)
    {
        if (fixupMap.containsKey(qName.uri))
            qName.uri = fixupMap.get(qName.uri);
        return super.createElementNode(qName);
    }       
}

在其他地方,您可以解析为DOM文档:

DOMParse p = new MyDOMParser(...);
p.parse(new InputSource(inputStream));
Document doc = p.getDocument();