diff --git a/xml/pom.xml b/xml/pom.xml
index 123875b1d9..540b1fc03b 100644
--- a/xml/pom.xml
+++ b/xml/pom.xml
@@ -1,4 +1,5 @@
-
4.0.0
xml
@@ -14,7 +15,7 @@
- dom4j
+ org.dom4j
dom4j
${dom4j.version}
@@ -23,18 +24,31 @@
jaxen
${jaxen.version}
-
+
+ org.jooq
+ joox-java-6
+ ${joox.version}
+
org.jdom
jdom2
${jdom2.version}
-
- javax.xml
+ javax.xml.bind
jaxb-api
${jaxb-api.version}
+
+ com.sun.xml.bind
+ jaxb-impl
+ ${jaxb-impl.version}
+
+
+ com.sun.xml.bind
+ jaxb-core
+ ${jaxb-core.version}
+
javax.xml
jaxp-api
@@ -45,7 +59,17 @@
stax-api
${stax-api.version}
-
+
+
+ org.openjdk.jmh
+ jmh-core
+ ${jmh.version}
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+
commons-io
@@ -76,6 +100,31 @@
commons-lang
${commons-lang.version}
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ ${junit-jupiter.version}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${assertj-core.version}
+ test
+
+
+ org.xmlunit
+ xmlunit-assertj
+ ${xmlunit-assertj.version}
+ test
+
@@ -86,6 +135,16 @@
true
+
+
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
@@ -213,7 +272,8 @@
-
+
maven-assembly-plugin
${project.basedir}
@@ -232,8 +292,10 @@
- make-assembly
- package
+ make-assembly
+ package
attached
@@ -246,17 +308,31 @@
- 1.6.1
- 1.1.6
+ 2.1.1
+ 1.2.0
2.0.6
+ 1.6.2
+ 2.5
4.1
1.2.4.5
- 2.1
+ 2.3.1
1.4.2
+ 2.3.0.1
+ 2.3.2
1.0-2
+ 3.12.2
+ 2.6.3
+ 5.5.0
+ 1.21
+
+ 3.5
+ 2.4
+ 1.8
1.3.1
+ 3.8.0
+ 2.22.2
diff --git a/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java
new file mode 100644
index 0000000000..a1922ad224
--- /dev/null
+++ b/xml/src/main/java/com/baeldung/xml/attribute/Dom4jTransformer.java
@@ -0,0 +1,45 @@
+package com.baeldung.xml.attribute;
+
+import org.dom4j.*;
+import org.dom4j.io.DocumentSource;
+import org.dom4j.io.SAXReader;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.List;
+
+public class Dom4jTransformer {
+ private final Document input;
+
+ public Dom4jTransformer(String resourcePath) throws DocumentException {
+ // 1- Build the doc from the XML file
+ SAXReader xmlReader = new SAXReader();
+ this.input = xmlReader.read(resourcePath);
+ }
+
+ public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerException {
+ // 2- Locate the node(s) with xpath, we can use index and iterator too.
+ String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
+ XPath xpath = DocumentHelper.createXPath(expr);
+ List nodes = xpath.selectNodes(input);
+ // 3- Make the change on the selected nodes
+ for (int i = 0; i < nodes.size(); i++) {
+ Element element = (Element) nodes.get(i);
+ element.addAttribute(attribute, newValue);
+ }
+ // 4- Save the result to a new XML doc
+ 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 DocumentSource(input), new StreamResult(output));
+ return output.toString();
+ }
+}
diff --git a/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java
new file mode 100644
index 0000000000..a2266a2b44
--- /dev/null
+++ b/xml/src/main/java/com/baeldung/xml/attribute/JaxpTransformer.java
@@ -0,0 +1,63 @@
+package com.baeldung.xml.attribute;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+public class JaxpTransformer {
+
+ private Document input;
+
+ public JaxpTransformer(String resourcePath) throws SAXException, IOException, ParserConfigurationException {
+ // 1- Build the doc from the XML file
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ input = factory.newDocumentBuilder()
+ .parse(resourcePath);
+ }
+
+ public String modifyAttribute(String attribute, String oldValue, String newValue) throws XPathExpressionException, TransformerFactoryConfigurationError, TransformerException {
+ // 2- Locate 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);
+ // 3- Make the change on the selected nodes
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element value = (Element) nodes.item(i);
+ value.setAttribute(attribute, newValue);
+ }
+ //Stream api syntax
+ // IntStream
+ // .range(0, nodes.getLength())
+ // .mapToObj(i -> (Element) nodes.item(i))
+ // .forEach(value -> value.setAttribute(attribute, newValue));
+ // 4- Save the result to a new XML doc
+ 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));
+ return output.toString();
+ }
+}
diff --git a/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java b/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java
new file mode 100644
index 0000000000..d36d60ec59
--- /dev/null
+++ b/xml/src/main/java/com/baeldung/xml/attribute/JooxTransformer.java
@@ -0,0 +1,38 @@
+package com.baeldung.xml.attribute;
+
+import org.joox.JOOX;
+import org.joox.Match;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import java.io.IOException;
+
+import static org.joox.JOOX.$;
+
+public class JooxTransformer {
+
+ private final Document input;
+
+ public JooxTransformer(String resourcePath) throws SAXException, IOException {
+ // 1- Build the doc from the XML file
+ DocumentBuilder builder = JOOX.builder();
+ input = builder.parse(resourcePath);
+ }
+
+ public String modifyAttribute(String attribute, String oldValue, String newValue) throws TransformerFactoryConfigurationError {
+ // 2- Select the document
+ Match $ = $(input);
+ // 3 - Find node to modify
+ String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
+ $
+ // .find("to") or with xpath
+ .xpath(expr)
+ .get()
+ .stream()
+ .forEach(e -> e.setAttribute(attribute, newValue));
+ // 4- Return result as String
+ return $.toString();
+ }
+}
diff --git a/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java b/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java
new file mode 100644
index 0000000000..064e181713
--- /dev/null
+++ b/xml/src/main/java/com/baeldung/xml/attribute/jmh/AttributeBenchMark.java
@@ -0,0 +1,72 @@
+package com.baeldung.xml.attribute.jmh;
+
+import org.dom4j.DocumentException;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.xml.sax.SAXException;
+
+import com.baeldung.xml.attribute.Dom4jTransformer;
+import com.baeldung.xml.attribute.JaxpTransformer;
+import com.baeldung.xml.attribute.JooxTransformer;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.xpath.XPathExpressionException;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+public class AttributeBenchMark {
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(AttributeBenchMark.class.getSimpleName())
+ .forks(1)
+ .build();
+ new Runner(opt).run();
+ }
+
+ @Benchmark
+ public String dom4jBenchmark() throws DocumentException, TransformerException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ Dom4jTransformer transformer = new Dom4jTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ return transformer.modifyAttribute(attribute, oldValue, newValue);
+ }
+
+ @Benchmark
+ public String jooxBenchmark() throws IOException, SAXException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ JooxTransformer transformer = new JooxTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ return transformer.modifyAttribute(attribute, oldValue, newValue);
+ }
+
+ @Benchmark
+ public String jaxpBenchmark() throws TransformerException, ParserConfigurationException, SAXException, IOException, XPathExpressionException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ JaxpTransformer transformer = new JaxpTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ return transformer.modifyAttribute(attribute, oldValue, newValue);
+ }
+}
diff --git a/xml/src/main/resources/xml/attribute.xml b/xml/src/main/resources/xml/attribute.xml
new file mode 100644
index 0000000000..c8fa3f1071
--- /dev/null
+++ b/xml/src/main/resources/xml/attribute.xml
@@ -0,0 +1,5 @@
+
+
+ john@email.com
+ mary@email.com
+
\ No newline at end of file
diff --git a/xml/src/main/resources/xml/attribute_expected.xml b/xml/src/main/resources/xml/attribute_expected.xml
new file mode 100644
index 0000000000..1d5d7b0cea
--- /dev/null
+++ b/xml/src/main/resources/xml/attribute_expected.xml
@@ -0,0 +1,5 @@
+
+
+ john@email.com
+ mary@email.com
+
\ No newline at end of file
diff --git a/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java
new file mode 100644
index 0000000000..485744f9a5
--- /dev/null
+++ b/xml/src/test/java/com/baeldung/xml/attribute/Dom4jProcessorUnitTest.java
@@ -0,0 +1,55 @@
+package com.baeldung.xml.attribute;
+
+import org.dom4j.DocumentException;
+import org.junit.jupiter.api.Test;
+
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.xmlunit.assertj.XmlAssert.assertThat;
+
+/**
+ * Unit test for {@link Dom4jTransformer}.
+ */
+public class Dom4jProcessorUnitTest {
+
+ @Test
+ public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws TransformerFactoryConfigurationError, TransformerException, DocumentException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ Dom4jTransformer transformer = new Dom4jTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ String result = transformer.modifyAttribute(attribute, oldValue, newValue);
+
+ assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
+ }
+
+ @Test
+ public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, TransformerException, URISyntaxException, DocumentException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ Dom4jTransformer transformer = new Dom4jTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+ String expectedXml = new String(Files.readAllBytes((Paths.get(getClass()
+ .getResource("/xml/attribute_expected.xml")
+ .toURI()))));
+
+ String result = transformer.modifyAttribute(attribute, oldValue, newValue);
+
+ assertThat(result)
+ .and(expectedXml)
+ .areSimilar();
+ }
+
+}
diff --git a/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java
new file mode 100644
index 0000000000..8394016dbd
--- /dev/null
+++ b/xml/src/test/java/com/baeldung/xml/attribute/JaxpProcessorUnitTest.java
@@ -0,0 +1,34 @@
+package com.baeldung.xml.attribute;
+
+import static org.xmlunit.assertj.XmlAssert.assertThat;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.xpath.XPathExpressionException;
+
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+/**
+ * Unit test for {@link JaxpTransformer}.
+ */
+public class JaxpProcessorUnitTest {
+
+ @Test
+ public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, ParserConfigurationException, XPathExpressionException, TransformerFactoryConfigurationError, TransformerException {
+ String path = getClass().getResource("/xml/attribute.xml")
+ .toString();
+ JaxpTransformer transformer = new JaxpTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ String result = transformer.modifyAttribute(attribute, oldValue, newValue);
+
+ assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
+ }
+
+}
diff --git a/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java b/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java
new file mode 100644
index 0000000000..38c7c59789
--- /dev/null
+++ b/xml/src/test/java/com/baeldung/xml/attribute/JooxProcessorUnitTest.java
@@ -0,0 +1,54 @@
+package com.baeldung.xml.attribute;
+
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.xmlunit.assertj.XmlAssert.assertThat;
+
+/**
+ * Unit test for {@link JooxTransformer}.
+ */
+public class JooxProcessorUnitTest {
+
+ @Test
+ public void givenXmlWithAttributes_whenModifyAttribute_thenGetXmlUpdated() throws IOException, SAXException, TransformerFactoryConfigurationError {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ JooxTransformer transformer = new JooxTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+
+ String result = transformer.modifyAttribute(attribute, oldValue, newValue);
+
+ assertThat(result).hasXPath("//*[contains(@customer, 'false')]");
+ }
+
+ @Test
+ public void givenTwoXml_whenModifyAttribute_thenGetSimilarXml() throws IOException, TransformerFactoryConfigurationError, URISyntaxException, SAXException {
+ String path = getClass()
+ .getResource("/xml/attribute.xml")
+ .toString();
+ JooxTransformer transformer = new JooxTransformer(path);
+ String attribute = "customer";
+ String oldValue = "true";
+ String newValue = "false";
+ String expectedXml = new String(Files.readAllBytes((Paths.get(getClass()
+ .getResource("/xml/attribute_expected.xml")
+ .toURI()))));
+
+ String result = transformer.modifyAttribute(attribute, oldValue, newValue);
+
+ assertThat(result)
+ .and(expectedXml)
+ .areSimilar();
+ }
+
+}
diff --git a/xml/src/test/resources/xml/attribute.xml b/xml/src/test/resources/xml/attribute.xml
new file mode 100644
index 0000000000..c8fa3f1071
--- /dev/null
+++ b/xml/src/test/resources/xml/attribute.xml
@@ -0,0 +1,5 @@
+
+
+ john@email.com
+ mary@email.com
+
\ No newline at end of file
diff --git a/xml/src/test/resources/xml/attribute_expected.xml b/xml/src/test/resources/xml/attribute_expected.xml
new file mode 100644
index 0000000000..1d5d7b0cea
--- /dev/null
+++ b/xml/src/test/resources/xml/attribute_expected.xml
@@ -0,0 +1,5 @@
+
+
+ john@email.com
+ mary@email.com
+
\ No newline at end of file