如何在XML中查找和替换属性值

时间:2015-03-03 17:09:22

标签: java xml dom

我正在构建一个" XML扫描程序"在Java中查找以&#34开头的属性值;在这里:"。属性值包含稍后要替换的指令。 例如,我有这个xml文件填充像

这样的记录
<bean value="!Here:Sring:HashKey"></bean>

如何才能找到并替换属性值,只知道它以"!Here:"开头?

3 个答案:

答案 0 :(得分:15)

为了修改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保存到新文件中。


  5. 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>
    

答案 1 :(得分:0)

我们在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);
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文件时,最好配置它。

  • 另一种选择是 dom4j 。这是一个用于处理XML的开源框架,该框架与XPath集成在一起,并完全支持DOM,SAX,JAXP和Java平台(例如Java Collections)。

我们需要在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);
// 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 here

  • 另一个不错的选择是 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样本更为冗长。

我将这三种实现与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 |

如果您需要看一下,我会举一些示例here

答案 2 :(得分:0)

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]"