Java中如何获取XML指定节点并将其删除?
问题:Java XPath查询带命名空间的XML节点失败,需实现节点删除
XML文件结构
待处理的XML结构如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"> <md:EntityDescriptor xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" ID="_id-83bbfdd3-e4c4-42cf-a024-e4733569a4ae" entityID="https://id.eht.eu"> <md:Organization> <md:OrganizationName xml:lang="it">EtnaHitech</md:OrganizationName> <md:OrganizationName xml:lang="en">EtnaHitech</md:OrganizationName> <md:OrganizationDisplayName xml:lang="it">EHT</md:OrganizationDisplayName> <md:OrganizationDisplayName xml:lang="en">EHT</md:OrganizationDisplayName> </md:Organization> </md:EntityDescriptor> <md:EntityDescriptor xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchemainstance" ID="_gh3s48d19e23e85be40k4ab5ey331e7k4f04f73fb5" entityID="https://id.lepida.it/idp/shibboleth"> <md:Organization> <md:OrganizationName xml:lang="it">Lepida</md:OrganizationName> <md:OrganizationDisplayName xml:lang="it">Lepida</md:OrganizationDisplayName> </md:Organization> </md:EntityDescriptor> </md:EntitiesDescriptor>
尝试的代码
我试图用Java XPath获取entityID="https://id.eht.eu"的md:EntityDescriptor节点,代码如下:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse("C:/outputTest/input.xml"); XPathFactory xPathfactory = XPathFactory.newInstance(); XPath xpath = xPathfactory.newXPath(); XPathExpression expr = xpath.compile("//md:EntityDescriptor[@entityID='https://id.eht.eu']"); NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
遍历结果的代码:
if (nl != null && nl.getLength() != 0) { for (int i = 0; i < nl.getLength(); i++) { System.out.println(nl.item(i).getNodeValue()); } }
遇到的问题
执行后NodeList始终为空,无法获取目标节点。需要解决查询问题,并实现将指定entityID的节点从XML中删除的功能。
解决方案
问题根源在于XML使用了命名空间,但XPath未绑定对应的前缀映射,导致无法识别md:前缀的节点。
1. 实现命名空间上下文
创建NamespaceContext实现类,将md前缀映射到XML中声明的命名空间URI:
import javax.xml.namespace.NamespaceContext; import java.util.Iterator; public class SamlNamespaceContext implements NamespaceContext { @Override public String getNamespaceURI(String prefix) { if ("md".equals(prefix)) { return "urn:oasis:names:tc:SAML:2.0:metadata"; } return null; } @Override public String getPrefix(String namespaceURI) { if ("urn:oasis:names:tc:SAML:2.0:metadata".equals(namespaceURI)) { return "md"; } return null; } @Override public Iterator<String> getPrefixes(String namespaceURI) { return null; } }
2. 修改XPath代码,开启命名空间支持并绑定上下文
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // 必须开启命名空间支持 factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse("C:/outputTest/input.xml"); XPathFactory xPathfactory = XPathFactory.newInstance(); XPath xpath = xPathfactory.newXPath(); // 绑定自定义命名空间上下文 xpath.setNamespaceContext(new SamlNamespaceContext()); XPathExpression expr = xpath.compile("//md:EntityDescriptor[@entityID='https://id.eht.eu']"); NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
3. 遍历节点并删除,可选保存修改后的文档
import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.File; // ... 前面的解析代码 ... if (nl != null && nl.getLength() > 0) { for (int i = 0; i < nl.getLength(); i++) { Node targetNode = nl.item(i); // 通过父节点删除目标节点 targetNode.getParentNode().removeChild(targetNode); System.out.println("已删除节点:" + targetNode.getAttributes().getNamedItem("entityID").getNodeValue()); } // 将修改后的XML保存到文件 try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); // 格式化输出XML(可选) transformer.setOutputProperty(OutputKeys.INDENT, "yes"); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(new File("C:/outputTest/modified.xml")); transformer.transform(source, result); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("未找到目标节点"); }
关键注意事项
- 必须调用
factory.setNamespaceAware(true),否则解析器会忽略XML中的命名空间信息。 - 命名空间URI必须与XML中
xmlns:md的值完全一致,不能有拼写错误。 - 删除节点必须通过父节点的
removeChild方法执行,节点自身无法直接删除。
内容的提问来源于stack exchange,提问作者Echatl
相关产品推荐
相关产品推荐

