在 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>
如何将<br /><strong>foo</strong> bar
部分作为字符串获取?
【问题讨论】:
请注意以下一些答案:永远不要使用基于文本解析的解决方案。考虑这样的输出:<?xml version="1.0"?> <!-- Comment is also a node >:-) /> --> <rootElement/> <[CDATA[ <.../> ]]>
【参考方案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】:如果您不想求助于外部库,以下解决方案可能会派上用场。如果您有一个节点 <parent><child name="Nina"/></parent>
并且您想提取父元素的子元素,请执行以下操作:
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()
将为您提供当前节点的文本,如果它是 Attribute
、CDATA
或 Text
节点。因此,您需要使用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;
【讨论】:
如果您的输入是:<user nick=">(((°>"> ... </user>
?
我同意 - 我的解决方案不是最好的。我根据 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:你的意思是什么子节点? 我有像<parent><child><subchild><data>Data1</data></subchild></child><child><subchild><data>Data2</data></subchild></child></parent>
这样的 XML。我想将<parent>
的内部xml作为字符串<child><subchild><data>Data1</data></subchild></child><child><subchild><data>Data2</data></subchild></child>
如果我将parent
节点作为DOMSource
传递,我会得到包含<parent></parent>
的结果。此外,parent
在原始document
中有一个父节点,其中定义了xmlns
。这个xmlns
像<parent xmlns=""></parent>
一样添加到结果中。是否有可能从结果中省略 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 作为字符串的主要内容,如果未能解决你的问题,请参考以下文章