![](/img/trans.png)
[英]How do I use xpath in Java to find a node value or attribute in an xml and replace it with another value?
[英]How to find and replace an attribute value in a XML
我正在用 Java 構建一個“XML 掃描器”,它可以找到以“!Here:”開頭的屬性值。 屬性值包含稍后替換的說明。 例如我有這個 xml 文件充滿了像
<bean value="!Here:Sring:HashKey"></bean>
只有知道它以"!Here:"
開頭,我才能找到和替換屬性值?
為了修改XML文件中的某些元素或屬性值,在仍然尊重XML結構的同時,您需要使用XML解析器。 它只涉及String$replace()
......
給出一個示例XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using -->
<property name="beanTwo" ref="anotherBean"/>
<property name="integerProperty" value="!Here:Integer:Foo"/>
</bean>
<bean id="anotherBean" class="examples.AnotherBean">
<property name="stringProperty" value="!Here:String:Bar"/>
</bean>
</beans>
為了改變2個標記!Here
,你需要
Document
, value
搜索文檔中的所有節點!Here
xpath表達式是//*[contains(@value, '!Here')]
。 在每個選定的節點上進行所需的轉換。 在這里,我只是改變!Here
通過What?
。
將修改后的dom Document
保存到新文件中。
static String inputFile = "./beans.xml";
static String outputFile = "./beans_new.xml";
// 1- Build the doc from the XML file
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new InputSource(inputFile));
// 2- Locate the node(s) with xpath
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList)xpath.evaluate("//*[contains(@value, '!Here')]",
doc, XPathConstants.NODESET);
// 3- Make the change on the selected nodes
for (int idx = 0; idx < nodes.getLength(); idx++) {
Node value = nodes.item(idx).getAttributes().getNamedItem("value");
String val = value.getNodeValue();
value.setNodeValue(val.replaceAll("!Here", "What?"));
}
// 4- Save the result to a new XML doc
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.transform(new DOMSource(doc), new StreamResult(new File(outputFile)));
生成的XML文件是:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans>
<bean class="examples.ExampleBean" id="exampleBean">
<!-- setter injection using -->
<property name="beanTwo" ref="anotherBean"/>
<property name="integerProperty" value="What?:Integer:Foo"/>
</bean>
<bean class="examples.AnotherBean" id="anotherBean">
<property name="stringProperty" value="What?:String:Bar"/>
</bean>
</beans>
我們在Java中有一些替代方案。
假設我們需要在此XML中將屬性customer
更改為false
:
<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
<to customer="true">john@email.com</to>
<from>mary@email.com</from>
</notification>
使用JAXP(此實現基於@ t-gounelle示例),我們可以這樣做:
//Load the document
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Document input = factory.newDocumentBuilder().parse(resourcePath);
//Select the node(s) with XPath
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList) xpath.evaluate(String.format("//*[contains(@%s, '%s')]", attribute, oldValue), input, XPathConstants.NODESET);
// Updated the selected nodes (here, we use the Stream API, but we can use a for loop too)
IntStream
.range(0, nodes.getLength())
.mapToObj(i -> (Element) nodes.item(i))
.forEach(value -> value.setAttribute(attribute, newValue));
// Get the result as a String
TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DOMSource(input), new StreamResult(output));
String result = output.toString();
請注意,為了禁用DocumentBuilderFactory
類的外部實體處理( XXE ),我們配置XMLConstants.FEATURE_SECURE_PROCESSING
功能 。 在解析不受信任的XML文件時配置它是一個很好的做法。 查看此OWASP指南以及其他信息。
我們需要將以下依賴項添加到我們的pom.xml中以使用它:
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
該實現與JAXP等效非常相似:
// Load the document
SAXReader xmlReader = new SAXReader();
Document input = xmlReader.read(resourcePath);
// Features to prevent XXE
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Select the nodes
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
XPath xpath = DocumentHelper.createXPath(expr);
List<Node> nodes = xpath.selectNodes(input);
// Updated the selected nodes
IntStream
.range(0, nodes.getLength())
.mapToObj(i -> (Element) nodes.get(i);)
.forEach(value -> value.addAttribute(attribute, newValue));
// We can get the representation as String in the same way as the previous JAXP snippet.
請注意,使用此方法,盡管名稱,如果給定名稱已存在屬性,它將被替換,否則將添加它。 我們可以在這里找到javadoc。
我們需要將以下依賴項添加到我們的pom.xml中以使用jOOX。
與Java 9+一起使用:
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joox</artifactId>
<version>1.6.2</version>
</dependency>
用於Java 6+:
<dependency>
<groupId>org.jooq</groupId>
<artifactId>joox-java-6</artifactId>
<version>1.6.2</version>
</dependency>
我們可以像這樣實現我們的屬性轉換器:
// Load the document
DocumentBuilder builder = JOOX.builder();
Document input = builder.parse(resourcePath);
Match $ = $(input);
// Select the nodes
$
.find("to") // We can use and XPATH expresion too.
.get()
.stream()
.forEach(e -> e.setAttribute(attribute, newValue));
// Get the String reprentation
$.toString();
正如我們在此示例中看到的,語法比JAXP和dom4j樣本更簡潔。
我將3個實現與JMH進行了比較,得到了以下結果:
| Benchmark Mode Cnt Score Error Units |
|--------------------------------------------------------------------|
| AttributeBenchMark.dom4jBenchmark avgt 5 0.167 ± 0.050 ms/op |
| AttributeBenchMark.jaxpBenchmark avgt 5 0.185 ± 0.047 ms/op |
| AttributeBenchMark.jooxBenchmark avgt 5 0.307 ± 0.110 ms/op |
如果你需要看一下,我把這些例子放在這里 。
Gounelle的答案是正確的,但是,它是基於事先知道屬性名稱的事實。
如果要僅根據其值查找所有屬性,請將此表達式用於xpath:
NodeList attributes = (NodeList) xpath.evaluate(
"//*/@*[contains(. , '!Here')]",
doc,
XPathConstants.NODESET
)
在這里,您可以通過設置//*/@*
選擇所有屬性。 然后你可以設置一個像我上面提到的條件。
順便說一下,如果搜索單個屬性,則可以使用Attr
而不是Node
Attr attribute = (Attr) xpath.evaluate(
"//*/@*[contains(. , '!Here')]",
doc,
XPathConstants.NODE
)
attribute.setValue("What!");
如果要按特定值查找屬性,請使用
"//*/@*[ . = '!Here:String:HashKey' ]"
如果您使用數字比較搜索屬性,例如,如果您有
<bean value="999"></bean>
<bean value="1337"></bean>
然后你可以通過設置表達式來選擇第二個bean
"//*/@*[ . > 1000]"
我最近在當前項目中遇到了類似的問題。 我意識到這個解決方案可能無法解決最初的問題,因為它沒有考慮到關於
屬性值包含以后替換的說明
不過,有人可能會發現它很有用。 我們已經使用 apache commons 中的StringSubstitutor.java來替換 JSON 文件中的值。
事實證明,在我們的案例中,它與 XML 文本一樣有效。 它確實對字符串進行操作,這可能不適用於所有情況。
給定一個像這樣的簡單 XML:
<?xml version="1.0" encoding="UTF-8"?>
<Foo>
<Bar>${replaceThis:-defaultValueHere}</Bar>
<bean value="${!Here}:Sring:HashKey"></bean>
</Foo>
StringSubstitutor
允許你用任何東西替換${replaceThis:-defaultValueHere}
。 在 Java 11 中,簡單示例可能如下所示:
// Read the file as a string. (Java 11+)
String xml = Files.readString(path, StandardCharsets.US_ASCII);
// Specify what to replace
Map<String, String> replacementMappings = Map.of(
"replaceThis", "Something else",
"!Here","Bean"
);
String xmlWithStringsReplaced = new StringSubstitutor(replacementMappings).replace(testFile);
然后xmlWithStringsReplaced
應如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Foo>
<Bar>Something Else</Bar>
<bean value="Bean:Sring:HashKey"></bean>
</Foo>
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.