9

所以我们有一个带有自定义命名空间的 XML 文档。(XML 是由我们无法控制的软件生成的。它由不知道命名空间的DOM 解析器解析;标准的 Java7SE/Xerces 东西,但也在我们的有效控制之外。)输入数据如下所示:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
    .... 18 blarzillion lines of XML ....
    <Thing CustomAttr:gibberish="borkborkbork" ... />
    .... another 27 blarzillion lines ....
</MainTag>

我们得到的 Document 是可用的、xpath 可查询的和可遍历的等等。

将此文档转换为文本格式以写入数据接收器使用一百个 SO“如何将我的 XML 文档更改为 Java 字符串?”中描述的标准 Transformer 方法。问题:

Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter stringwriter = new StringWriter();
transformer.transform (new DOMSource(theXMLDocument), new StreamResult(stringwriter));
return stringwriter.toString();

它工作得很好。

但现在我想将该文档中的单个任意节点转换为字符串。DOMSource构造函数接受 Node 指针就像它接受 a 一样(Document实际上 Document 只是 Node 的一个子类,所以据我所知,它是相同的 API)。因此,在上面的代码片段中传入一个单独的节点来代替“theXMLDocument”效果很好……直到我们到达Thing.

此时,transform()抛出异常:

java.lang.RuntimeException: Namespace for prefix 'CustomAttr' has not been declared.
    at com.sun.org.apache.xml.internal.serializer.SerializerBase.getNamespaceURI(Unknown Source)
    at com.sun.org.apache.xml.internal.serializer.SerializerBase.addAttribute(Unknown Source)
    at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.addAttribute(Unknown Source)
    ......

这就说得通了。(“com.sun.org.apache”读起来很奇怪,但无论如何。)这是有道理的,因为自定义属性的命名空间是在根节点声明的,但现在转换器从子节点开始,并且可以在树中看不到它“上方”的声明。所以我想我理解这个问题,或者至少是症状,但我不知道如何解决它。

  • 如果这是一个字符串到文档的转换,我们将使用一个DocumentBuilderFactory实例并可以调用.setNamespaceAware(false),但这是另一个方向。

  • 没有任何可用属性transformer.setOutputProperty()影响 namespaceURI 查找,这是有道理的。

  • 没有这种对应setInputProperty或类似的功能。

  • 输入解析器不知道命名空间,这就是“上游”代码如何创建它的 Document 交给我们的方式。我不知道如何将特定的状态标志传递给转换代码,我认为这是我真正想做的事情。

  • 相信可以(以某种方式)xmlns:CustomAttr="http://BlitherBlither"向 Thing 节点添加一个属性,与根 MainTag 相同。但是此时输出不再与读入的 XML 相同,即使它“意味着”相同的东西,并且文本字符串最终将在未来进行比较。在抛出异常之前,我们不知道是否需要它,然后我们可以添加它并重试...... ick。就此而言,更改节点会更改原始文档,这确实应该是只读操作。

建议?有什么方法可以告诉 Transformer,“看,不要对输出是否是孤立的合法 XML 施加压力,它不会被自己解析回来(但你不知道) ,只需生成文本并让我们担心它的上下文”?

4

2 回答 2

6

鉴于您引用的错误消息“尚未声明前缀 'CustomAttr' 的命名空间。”,我假设您的伪代码类似于:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
    .... 18 blarzillion lines of XML ....
    <Thing CustomAttr:attributeName="borkborkbork" ... />
    .... another 27 blarzillion lines ....
</MainTag>

有了这个假设,我的建议是:所以您想从“大”XML 中提取“Thing”节点。标准方法是使用一点 XSLT 来做到这一点。您准备 XSL 转换:

Transformer transformer = transformerFactory.newTransformer(new StreamSource(new File("isolate-the-thing-node.xslt")));
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setParameter("elementName", stringWithCurrentThing);    // parameterize transformation for each Thing
...

编辑:@Ti,请注意上面的参数化指令(和下面的 xslt)。

文件“isolate-the-thing-node.xslt”可能是以下内容:

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:custom0="http://BlahBlahBlah"
    xmlns:custom1="http://BlitherBlither"
    version="1.0">
    <xsl:param name="elementName">to-be-parameterized</xsl:param>
    <xsl:output encoding="utf-8" indent="yes" method="xml" omit-xml-declaration="no" />

    <xsl:template match="/*" priority="2" >
            <!--<xsl:apply-templates select="//custom0:Thing" />-->
            <!-- changed to parameterized selection: -->
            <xsl:apply-templates select="custom0:*[local-name()=$elementName]" />
    </xsl:template>

    <xsl:template match="node() | @*" priority="1">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

希望这能让你克服“事情”的问题:)

于 2013-02-14T23:59:01.680 回答
0

我已经设法解析提供的文档,获取Thing节点并毫无问题地打印它。

看一下工作示例

Node rootElement = d.getDocumentElement();
System.out.println("Whole document: \n");  
System.out.println(nodeToString(rootElement));
Node thing = rootElement.getChildNodes().item(1);
System.out.println("Just Thing: \n");  
System.out.println(nodeToString(thing));

节点字符串

private static String nodeToString(Node node) {
  StringWriter sw = new StringWriter();
  try {
    Transformer t = TransformerFactory.newInstance().newTransformer();
    t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
    t.setOutputProperty(OutputKeys.INDENT, "yes");
    t.transform(new DOMSource(node), new StreamResult(sw));
  } catch (TransformerException te) {
    System.out.println("nodeToString Transformer Exception");
  }
  return sw.toString();
}

输出

Whole document: 

<?xml version="1.0" encoding="UTF-8"?><MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
    <Thing CustomAttr="borkborkbork"/>
</MainTag>

Just Thing: 

<?xml version="1.0" encoding="UTF-8"?><Thing CustomAttr="borkborkbork"/>

当我尝试使用CustomAttr:attributeName@marty 建议的相同代码时,它会因原始异常而失败,因此看起来您在原始 XML 中的某处使用该自定义CustomAttr命名空间作为属性或节点的前缀。

在后一种情况下,您可以利用 的问题setNamespaceAware(true),这将包括Thing节点本身的命名空间信息。

<?xml version="1.0" encoding="UTF-8"?><Thing xmlns:CustomAttr="http://BlitherBlither" CustomAttr:attributeName="borkborkbork" xmlns="http://BlahBlahBlah"/>
于 2013-02-15T18:40:40.300 回答