簡體   English   中英

如何查找和替換 XML 中的屬性值

[英]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 ,你需要

  1. 將文件加載到dom Document
  2. 用xpath選擇想要的節點。 在這里,我使用包含字符串的屬性value搜索文檔中的所有節點!Here xpath表達式是//*[contains(@value, '!Here')]
  3. 在每個選定的節點上進行所需的轉換。 在這里,我只是改變!Here通過What?

  4. 將修改后的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中有一些替代方案。

  • 首先, JAXP (自1.4版以來它已與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指南以及其他信息。

  • 另一種選擇是dom4j 它是一個用於處理XML的開源框架,它與XPath集成,完全支持DOM,SAX,JAXP和Java平台等Java平台。

我們需要將以下依賴項添加到我們的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。

  • 另一個不錯的選擇是jOOX ,這個庫在jQuery中激發了它的API。

我們需要將以下依賴項添加到我們的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM