如何从 XML 中删除不需要的标签

How to remove unwanted tags from XML

我有一个巨大的 XML,我想从中删除不需要的标签。例如'

<orgs>
    <org name="Test1">
        <item>a</item>
        <item>b</item>
    </org>
    <org name="Test2">
        <item>c</item>
        <item>b</item>
        <item>e</item>
    </org>
</orgs>

我想从这个 xml 中删除所有 <item>b</item>。哪个解析器 api 应该用于此,因为 xml 非常大,如何实现它。

一种方法是使用文档对象模型 (DOM),缺点是顾名思义,它需要将整个文档加载到内存中,并且 Java' s DOM API 非常耗费内存。好处是,您可以利用 XPath 找到有问题的节点

仔细查看 Java API for XML Processing (JAXP) 了解更多详情和其他 APIs

步骤:1 加载文档

DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(new File("..."));

第 2 组:查找有问题的节点

XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression xExpress = xPath.compile("/orgs/org/item[text()='b']");
NodeList nodeList = (NodeList) xExpress.evaluate(doc.getDocumentElement(), XPathConstants.NODESET);

第 3 组:删除有问题的节点

好吧,这并不像它应该的那么简单。删除节点可以在文档中留下空白space,这将是"nice"以进行清理。下面的方法是我根据我发现的一些互联网代码改编的一个简单的库方法,它将删除指定的 Node,但也会删除任何白色 space/text 节点以及

public static void removeNode(Node node) {
    if (node != null) {
        while (node.hasChildNodes()) {
            removeNode(node.getFirstChild());
        }

        Node parent = node.getParentNode();
        if (parent != null) {
            parent.removeChild(node);
            NodeList childNodes = parent.getChildNodes();
            if (childNodes.getLength() > 0) {
                List<Node> lstTextNodes = new ArrayList<Node>(childNodes.getLength());
                for (int index = 0; index < childNodes.getLength(); index++) {
                    Node childNode = childNodes.item(index);
                    if (childNode.getNodeType() == Node.TEXT_NODE) {
                        lstTextNodes.add(childNode);
                    }
                }
                for (Node txtNodes : lstTextNodes) {
                    removeNode(txtNodes);
                }
            }
        }
    }
}

遍历有问题的节点...

for (int index = 0; index < nodeList.getLength(); index++) {
    Node node = nodeList.item(index);
    removeNode(node);
}

第 4 步:保存结果

Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.INDENT, "yes");
tf.setOutputProperty(OutputKeys.METHOD, "xml");
tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

DOMSource domSource = new DOMSource(doc);
StreamResult sr = new StreamResult(System.out);
tf.transform(domSource, sr);

输出类似...

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<orgs>
  <org name="Test1">
    <item>a</item>
  </org>
  <org name="Test2">
    <item>c</item>
    <item>e</item>
  </org>
</orgs>

如果您的数据不适合您的内存,您需要一个不会一次性加载所有文件的 pull parser。 如果您的数据适合内存,则使用 data projection(我所属的项目)有一个非常短的解决方案:

public class RemoveTags {

    public interface Projection {
        @XBDelete("//item[text()='b']")
        void deleteAllItems();
    }

    public static void main(String[] args) throws IOException {
        XBProjector projector = new XBProjector();
        Projection projection = projector.io().file("data.xml").read(Projection.class);
        projection.deleteAllItems();
        projector.io().file("withoutItems.xml").write(projection);
    }

}

执行此操作的标准方法是使用 XSLT。您需要一个包含两个规则的样式表:一个复制不变内容的身份规则:

<xsl:template match="*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>

第二条规则删除不需要的元素:

<xsl:template match="item[. = 'b']"/>

与基于 DOM 的方法一样,如果您的文档太大而无法放入内存,这可能会出现问题。在 XSLT 3.0 中,您可以使用流式传输解决此问题。 XSLT 3.0 还使 "identity" 转换更易于编写,因此整个代码现在变为:

<xsl:transform version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:mode streamable="yes" on-no-match="shallow-copy"/>
  <xsl:template match="item[. = 'b']"/>
</xsl:transform>