在 Java DOM 中获取节点的内部 XML 作为字符串

Posted

技术标签:

【中文标题】在 Java DOM 中获取节点的内部 XML 作为字符串【英文标题】:Get a node's inner XML as String in Java DOM 【发布时间】:2011-03-19 01:38:07 【问题描述】:

我有一个如下所示的 XML org.w3c.dom.Node:

<variable name="variableName">
    <br /><strong>foo</strong> bar
</variable>

如何将&lt;br /&gt;&lt;strong&gt;foo&lt;/strong&gt; bar 部分作为字符串获取?

【问题讨论】:

请注意以下一些答案:永远不要使用基于文本解析的解决方案。考虑这样的输出:&lt;?xml version="1.0"?&gt; &lt;!-- Comment is also a node &gt;:-) /&gt; --&gt; &lt;rootElement/&gt; &lt;[CDATA[ &lt;.../&gt; ]]&gt; 【参考方案1】:

我想扩展 Andrey M. 的非常好的答案:

可能会发生节点不可序列化的情况,这会导致某些实现出现以下异常:

org.w3c.dom.ls.LSException: unable-to-serialize-node: 
            unable-to-serialize-node: The node could not be serialized.

我在 Wildfly 13 上运行的实现“org.apache.xml.serialize.DOMSerializerImpl.writeToString(DOMSerializerImpl)”遇到了这个问题。

为了解决这个问题,我建议稍微更改一下 Andrey M. 的代码示例:

private static String innerXml(Node node) 
    DOMImplementationLS lsImpl = (DOMImplementationLS) node.getOwnerDocument().getImplementation().getFeature("LS", "3.0");
    LSSerializer lsSerializer = lsImpl.createLSSerializer();
    lsSerializer.getDomConfig().setParameter("xml-declaration", false); 
    NodeList childNodes = node.getChildNodes();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < childNodes.getLength(); i++) 
        Node innerNode = childNodes.item(i);
        if (innerNode!=null) 
            if (innerNode.hasChildNodes()) 
                sb.append(lsSerializer.writeToString(innerNode));
             else 
                sb.append(innerNode.getNodeValue());
            
        
    
    return sb.toString();

我还添加了 Nyerguds 的评论。这在wildfly 13中对我有用。

【讨论】:

【参考方案2】:

在 Lukas Eder 的解决方案之上,我们可以像在 .NET 中一样提取 innerXml,如下所示

    public static String innerXml(Node node,String tag)
            String xmlstring = toString(node);
            xmlstring = xmlstring.replaceFirst("<[/]?"+tag+">","");
            return xmlstring;       


public static String toString(Node node)       
    String xmlString = "";
    Transformer transformer;
    try 
        transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        //transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StreamResult result = new StreamResult(new StringWriter());

        xmlString = nodeToStream(node, transformer, result);

     catch (TransformerConfigurationException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
     catch (TransformerFactoryConfigurationError e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
     catch (TransformerException e) 
        // TODO Auto-generated catch block
        e.printStackTrace();
    catch (Exception ex)
        ex.printStackTrace();
    

    return xmlString;               

例如:

If Node name points to xml with string representation "<Name><em>Chris</em>tian<em>Bale</em></Name>" 
String innerXml = innerXml(name,"Name"); //returns "<em>Chris</em>tian<em>Bale</em>"

【讨论】:

1) 您的解决方案似乎不完整。实际发生在哪里? nodeToStream()在哪里? 2) 我考虑了一段时间,但认为这是一个不好的解决方案。如果 XML 有 5 GB 怎么办?【参考方案3】:

到目前为止,最好的解决方案是 Andrey M,需要一个特定的实现,这可能会在未来引起问题。这是相同的方法,但只是使用 JDK 为您提供的任何序列化(即,配置为使用什么)。

public static String innerXml(Node node) throws Exception

        StringWriter writer = new StringWriter();
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

        NodeList childNodes = node.getFirstChild().getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) 
            transformer.transform(new DOMSource(childNodes.item(i)), new StreamResult(writer));
        
        return writer.toString();

如果您正在处理文档而不是节点,则必须深入一层并使用node.getFirstChild().getChildNodes(); 但是,要使其更健壮,您应该找到第一个元素,而不是想当然地认为只有一个节点。 XML 必须有一个根元素,但可以有多个节点,包括 cmets、实体和空白文本。

        Node rootElement = docRootNode.getFirstChild();
        while (rootElement != null && rootElement.getNodeType() != Node.ELEMENT_NODE)
            rootElement = rootElement.getNextSibling();
        if (rootElement == null)
            throw new RuntimeException("No root element found in given document node.");

        NodeList childNodes = rootElement.getChildNodes();

如果我应该推荐一个库来处理它,请尝试 JSoup,它主要用于 html,但 works with XML too。不过我还没有测试过。

Document doc = Jsoup.parse(xml, "", Parser.xmlParser());
fileContents.put(Attributes.BODY, document.body().html());
// versus: document.body().outerHtml()

【讨论】:

【参考方案4】:

如果您不想求助于外部库,以下解决方案可能会派上用场。如果您有一个节点 &lt;parent&gt;&lt;child name="Nina"/&gt;&lt;/parent&gt; 并且您想提取父元素的子元素,请执行以下操作:

    StringBuilder resultBuilder = new StringBuilder();
    // Get all children of the given parent node
    NodeList children = parent.getChildNodes();
    try 

        // Set up the output transformer
        TransformerFactory transfac = TransformerFactory.newInstance();
        Transformer trans = transfac.newTransformer();
        trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        trans.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);

        for (int index = 0; index < children.getLength(); index++) 
            Node child = children.item(index);

            // Print the DOM node
            DOMSource source = new DOMSource(child);
            trans.transform(source, streamResult);
            // Append child to end result
            resultBuilder.append(stringWriter.toString());
        
     catch (TransformerException e) 
        //Error handling goes here
    
    return resultBuilder.toString();

【讨论】:

【参考方案5】:

org.w3c.dom.Node 对此没有简单的方法。 getTextContent() 给出连接在一起的每个子节点的文本。 getNodeValue() 将为您提供当前节点的文本,如果它是 AttributeCDATAText 节点。因此,您需要使用getChildNodes()getNodeName()getNodeValue() 的组合来序列化节点以构建字符串。

您也可以使用现有的各种 XML 序列化库之一来实现。有XStream 甚至 JAXB。这在这里讨论:XML serialization in Java?

【讨论】:

【参考方案6】:

这是提取 org.w3c.dom.Node 内容的替代解决方案。 如果节点内容不包含 xml 标签,此解决方案也适用:

private static String innerXml(Node node) throws TransformerFactoryConfigurationError, TransformerException 
    StringWriter writer = new StringWriter();
    String xml = null;
    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    transformer.transform(new DOMSource(node), new StreamResult(writer));
    // now remove the outer tag....
    xml = writer.toString();
    xml = xml.substring(xml.indexOf(">") + 1, xml.lastIndexOf("</"));
    return xml;

【讨论】:

如果您的输入是:&lt;user nick="&gt;(((°&gt;"&gt; ... &lt;/user&gt; 我同意 - 我的解决方案不是最好的。我根据 Andrey M.s 的回答添加了另一个解决方案【参考方案7】:

扩展 Andrey M 的回答,我不得不稍微修改代码以获得完整的 DOM 文档。如果你只是使用

 NodeList childNodes = node.getChildNodes();

它不包括我的根元素。要包含我使用的根元素(并获取完整的 .xml 文档):

 public String innerXml(Node node) 
     DOMImplementationLS lsImpl = (DOMImplementationLS)node.getOwnerDocument().getImplementation().getFeature("LS", "3.0");
     LSSerializer lsSerializer = lsImpl.createLSSerializer();
     lsSerializer.getDomConfig().setParameter("xml-declaration", false);
     StringBuilder sb = new StringBuilder();
     sb.append(lsSerializer.writeToString(node));
     return sb.toString(); 
 

【讨论】:

那么...你想不想得到 inner XML?【参考方案8】:

我在最后一个答案中遇到了问题,即方法“nodeToStream()”未定义;因此,我的版本在这里:

    public static String toString(Node node)
    String xmlString = "";
    try 
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        //transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        Source source = new DOMSource(node);

        StringWriter sw = new StringWriter();
        StreamResult result = new StreamResult(sw);

        transformer.transform(source, result);
        xmlString = sw.toString ();

     catch (Exception ex) 
        ex.printStackTrace ();
    

    return xmlString;

【讨论】:

【参考方案9】:

如果你使用jOOX,你可以用类似jquery的语法包装你的节点,然后调用toString()

$(node).toString();

它在内部使用身份转换器,如下所示:

ByteArrayOutputStream out = new ByteArrayOutputStream();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
Source source = new DOMSource(element);
Result target = new StreamResult(out);
transformer.transform(source, target);
return out.toString();

【讨论】:

它会转换所有子节点吗? @MohammadFaisal:你的意思是什么子节点? 我有像 &lt;parent&gt;&lt;child&gt;&lt;subchild&gt;&lt;data&gt;Data1&lt;/data&gt;&lt;/subchild&gt;&lt;/child&gt;&lt;child&gt;&lt;subchild&gt;&lt;data&gt;Data2&lt;/data&gt;&lt;/subchild&gt;&lt;/child&gt;&lt;/parent&gt; 这样的 XML。我想将&lt;parent&gt;的内部xml作为字符串&lt;child&gt;&lt;subchild&gt;&lt;data&gt;Data1&lt;/data&gt;&lt;/subchild&gt;&lt;/child&gt;&lt;child&gt;&lt;subchild&gt;&lt;data&gt;Data2&lt;/data&gt;&lt;/subchild&gt;&lt;/child&gt; 如果我将parent 节点作为DOMSource 传递,我会得到包含&lt;parent&gt;&lt;/parent&gt; 的结果。此外,parent 在原始document 中有一个父节点,其中定义了xmlns。这个xmlns&lt;parent xmlns=""&gt;&lt;/parent&gt; 一样添加到结果中。是否有可能从结果中省略 xmlns? 我尝试迭代父级的childNodes,但现在它在每个标签中添加xmlns。 :(【参考方案10】:

同样的问题。为了解决这个问题,我编写了这个辅助函数:

public String innerXml(Node node) 
    DOMImplementationLS lsImpl = (DOMImplementationLS)node.getOwnerDocument().getImplementation().getFeature("LS", "3.0");
    LSSerializer lsSerializer = lsImpl.createLSSerializer();
    NodeList childNodes = node.getChildNodes();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < childNodes.getLength(); i++) 
       sb.append(lsSerializer.writeToString(childNodes.item(i)));
    
    return sb.toString(); 

【讨论】:

这个方法一直在字符串的前面添加XML定义标签...有什么办法可以防止这种情况,除了之后简单地修剪它? 我解决了。解决方案是添加行lsSerializer.getDomConfig().setParameter("xml-declaration", false); 只使用 XSL 更容易吗?:

以上是关于在 Java DOM 中获取节点的内部 XML 作为字符串的主要内容,如果未能解决你的问题,请参考以下文章

Java获取XML节点总结之读取XML文档节点

java中dom4j解析xml文件怎么获取节点属性

使用 Java DOM 获取 XML 节点文本值

Word docx 内部的 Dom 节点说明

java解析xml文件,会把节点属性中的换行转换成空格,怎样才能避免此类转换,即保留换行

java语句如何获取XML文件的节点值