XML Tutorial · XML in the Real World · Lesson 26 of 28
XML Configuration Files
Reading and editing XML configuration found in real projects: Maven pom.xml, Android manifests, .NET project files and more.
Where you'll meet XML configuration
| File | Ecosystem |
|---|---|
pom.xml |
Java (Maven) builds and dependencies |
AndroidManifest.xml, res/layout/*.xml |
Android apps |
*.csproj, web.config, app.config |
.NET |
phpunit.xml, phpcs.xml |
PHP tooling |
log4j2.xml, logback.xml |
Java logging |
.idea/*.xml |
JetBrains IDE settings |
Reading a Maven POM
POM files are namespaced, a frequent source of "my XPath returns nothing" bugs:
from lxml import etree
pom = etree.fromstring(b"""<project xmlns="http://maven.apache.org/POM/4.0.0">
<groupId>com.readycamel</groupId>
<artifactId>shop</artifactId>
<version>1.4.0</version>
<dependencies>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.7.4</version></dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.11.3</version><scope>test</scope></dependency>
</dependencies>
</project>""")
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
print(pom.findtext("m:artifactId", namespaces=ns), pom.findtext("m:version", namespaces=ns))
for dep in pom.iterfind("m:dependencies/m:dependency", ns):
scope = dep.findtext("m:scope", default="compile", namespaces=ns)
print(f"- {dep.findtext('m:groupId', namespaces=ns)}:{dep.findtext('m:artifactId', namespaces=ns)}:"
f"{dep.findtext('m:version', namespaces=ns)} ({scope})")
Output
shop 1.4.0 - org.postgresql:postgresql:42.7.4 (compile) - org.junit.jupiter:junit-jupiter:5.11.3 (test)
<?php
$doc = new DOMDocument();
$doc->loadXML('<project xmlns="http://maven.apache.org/POM/4.0.0">
<groupId>com.readycamel</groupId>
<artifactId>shop</artifactId>
<version>1.4.0</version>
<dependencies>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.7.4</version></dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.11.3</version><scope>test</scope></dependency>
</dependencies>
</project>');
$xp = new DOMXPath($doc);
$xp->registerNamespace("m", "http://maven.apache.org/POM/4.0.0");
echo $xp->evaluate("string(/m:project/m:artifactId)"), " ", $xp->evaluate("string(/m:project/m:version)"), "\n";
foreach ($xp->query("//m:dependency") as $dep) {
$scope = $xp->evaluate("string(m:scope)", $dep) ?: "compile";
printf("- %s:%s:%s (%s)\n", $xp->evaluate("string(m:groupId)", $dep),
$xp->evaluate("string(m:artifactId)", $dep), $xp->evaluate("string(m:version)", $dep), $scope);
}
Output
shop 1.4.0 - org.postgresql:postgresql:42.7.4 (compile) - org.junit.jupiter:junit-jupiter:5.11.3 (test)
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
import java.io.ByteArrayInputStream;
public class Main {
static final String M = "http://maven.apache.org/POM/4.0.0";
static String child(Element e, String name, String fallback) {
for (Node n = e.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n instanceof Element c && name.equals(c.getLocalName())) return c.getTextContent();
}
return fallback;
}
public static void main(String[] args) throws Exception {
String xml = """
<project xmlns="http://maven.apache.org/POM/4.0.0">
<groupId>com.readycamel</groupId>
<artifactId>shop</artifactId>
<version>1.4.0</version>
<dependencies>
<dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><version>42.7.4</version></dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.11.3</version><scope>test</scope></dependency>
</dependencies>
</project>""";
var factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
Element project = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))).getDocumentElement();
System.out.println(child(project, "artifactId", "") + " " + child(project, "version", ""));
NodeList deps = project.getElementsByTagNameNS(M, "dependency");
for (int i = 0; i < deps.getLength(); i++) {
Element d = (Element) deps.item(i);
System.out.println("- " + child(d, "groupId", "") + ":" + child(d, "artifactId", "") + ":"
+ child(d, "version", "") + " (" + child(d, "scope", "compile") + ")");
}
}
}
Output
shop 1.4.0 - org.postgresql:postgresql:42.7.4 (compile) - org.junit.jupiter:junit-jupiter:5.11.3 (test)
Run this on your computer — see “Get Started” for setup.
Editing configuration safely
Tools that rewrite config files should change only what they must, keeping comments and formatting. lxml keeps
comments and, with remove_blank_text=False (the default), the original whitespace:
from lxml import etree
source = b"""<configuration>
<!-- connection pool: tuned for production -->
<pool size="10" timeout="30"/>
<logging level="info"/>
</configuration>"""
config = etree.fromstring(source)
config.find("pool").set("size", "25")
config.find("logging").set("level", "warn")
print(etree.tostring(config).decode())
Output
<configuration> <!-- connection pool: tuned for production --> <pool size="25" timeout="30"/> <logging level="warn"/> </configuration>
Test yourself
Why does //dependency return nothing on a Maven pom.xml?
The POM uses a default namespaceMaven files aren't XMLXPath can't read attributesDependencies are in a separate file
Show answer
Answer: The POM uses a default namespace
POM elements are in http://maven.apache.org/POM/4.0.0; map a prefix to it in your query.