【问题标题】:Get a node's inner XML as String in Java DOM在 Java DOM 中获取节点的内部 XML 作为字符串
【发布时间】: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;

标签: java xml dom


【解决方案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 更容易吗?:
            猜你喜欢
            • 2022-01-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-09-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多