XML#

XML (eXtensible Markup Language) is the older sibling of JSON: a hierarchical, text-based format with a rich ecosystem of schemas, queries, and transformations. Less fashionable in 2026, but deeply embedded in finance, healthcare, government, telecom, document formats (DOCX, ODT, SVG), and SOAP-era enterprise APIs.

Example#

<?xml version="1.0" encoding="UTF-8"?>
<book id="42" lang="en">
  <title>The Pragmatic Programmer</title>
  <authors>
    <author>Andrew Hunt</author>
    <author>David Thomas</author>
  </authors>
  <year>1999</year>
  <!-- inline comment -->
</book>

Structure#

XML’s syntax pieces. Most have direct JSON equivalents, but a few are XML-specific: CDATA for raw text, processing instructions for inline metadata, entities for escaping. Every XML document has exactly one root element.

  • Elements, <tag>...</tag> or self-closing <tag/>.

  • Attributes, name/value pairs on the start tag (id="42").

  • Text content, <title>The Pragmatic Programmer</title>.

  • Comments, <!-- ... -->.

  • Processing instructions, <?xml ... ?>, <?xml-stylesheet ?>.

  • CDATA sections, <![CDATA[ raw <text> & ]]> for unescaped text.

  • Entities, &amp;, &lt;, &gt;, &quot;, &apos;, numeric &#123;.

Every XML document has exactly one root element.

Namespaces#

Avoid name collisions across vocabularies. Namespaces are how XML documents combine multiple schemas in one file – each prefix maps to a URI that uniquely identifies the vocabulary. The default namespace covers unprefixed elements.

<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:dc="http://purl.org/dc/elements/1.1/">
  <title>My Feed</title>
  <dc:creator>Ada</dc:creator>
</feed>

Default namespace applies to unprefixed elements; prefixed names use the mapped URI.

Schemas#

XML has multiple schema languages, each with its own trade-offs around expressiveness, conciseness, and tooling. Most enterprise work uses XSD; RELAX NG is the cleaner alternative; Schematron pairs with either for rule-based validation.

  • DTD, the original; lightweight; minimal type system.

  • XML Schema (XSD), the most common; complex but expressive.

  • RELAX NG, compact alternative; preferred by some communities.

  • Schematron, rule-based assertions; pairs well with the others.

XPath#

XPath selects nodes from a document, the standard query language for XML, similar in spirit to JSONPath / JMESPath for JSON. Modern parsers ship XPath 1.0 as the baseline; 3.x adds maps, arrays, and higher-order functions for the cases where 1.0 isn’t enough.

/book/title                       # selects the title element
//author                           # any author at any depth
/book/@id                          # the id attribute
//book[@lang='en']                 # filter by attribute
//book[year > 2000]                # filter by child value
//author[1]                        # first author
count(//author)                    # function calls

XPath 3.x adds maps, arrays, higher-order functions; XPath 1.0 is the ubiquitous baseline.

XSLT#

XSLT transforms XML to other XML, HTML, or text via XPath-driven templates. Once everywhere; now mostly seen in publishing pipelines and legacy systems. Modern transformations more often use scripts in a general-purpose language.

Tooling#

The XML toolkit operators reach for. xmllint is the standard validator and formatter; xmlstarlet is the jq-equivalent for command-line querying; Saxon is the standard XSLT/XPath processor used in publishing pipelines.

  • xmllint, validate, format, query.

    $ xmllint --noout --schema schema.xsd doc.xml
    $ xmllint --xpath '//author' doc.xml
    $ xmllint --format messy.xml > clean.xml
    
  • xmlstarlet, jq-style CLI for XML.

  • Saxon, the standard XSLT/XPath processor.

  • Editor support is mature in IDEs and dedicated XML editors (oXygen, XMLSpy).

Common XML-Family Formats#

XML is the substrate under far more formats than people realize. The list below covers the major XML dialects an operator is likely to encounter, in web standards, document formats, enterprise APIs, regulatory filings, and identity federation.

  • HTML, not XML, but related; XHTML was the XML-strict variant.

  • SVG, vector graphics; an XML dialect, embeddable in HTML.

  • Atom / RSS, syndication feeds.

  • SOAP, the XML-RPC successor; still common in enterprise APIs.

  • WSDL, describes SOAP services.

  • Office Open XML / ODF, DOCX / XLSX / ODT under the hood.

  • SAML, federated identity; XML signatures are central.

  • XBRL, financial reporting (regulatory filings).

Security#

XML parsers have a long, painful CVE history, enough that “never parse XML with default settings on untrusted input” is the standard safety advice. The two main attack classes below are still common; mitigations are well-known but easy to miss.

  • XXE (XML External Entity), the parser fetches external resources named by the document. Disable external entities by default.

  • Billion laughs / quadratic blowup, entity expansion attacks that exhaust memory. Use a parser with entity-expansion limits.

In every modern XML library, disable DTDs and external entities for untrusted input. Common config: XMLConstants.FEATURE_SECURE_PROCESSING in Java; defusedxml in Python; libxml2 flags XML_PARSE_NOENT off and XML_PARSE_NONET on.

Where XML Wins#

The cases where XML still beats every alternative. Document formats, mixed-content text, and decades-old industry schemas (finance, government, health) are where XML’s verbosity is actually doing useful work that JSON can’t.

  • Mixed content (text with embedded markup), XML’s strength; awkward in JSON.

  • Document formats with rich structure, DOCX, ODT, SVG.

  • Schemas with the deepest tooling support across decades of industry.

  • Industries with regulatory XML standards (finance, government, health).

Where XML Loses#

Why XML stopped being the default. Verbose syntax, ambiguity in modeling (attributes vs. children), thinner library support in modern languages, and the long security history all push toward JSON for new work.

  • Verbosity, closing tags double the byte count vs. JSON.

  • Ambiguity, attributes vs. child elements; readers and writers must agree.

  • Tooling, modern languages have better JSON / YAML support out of the box.

  • Untrusted input, the security history is unforgiving.

Choosing#

The decision tree as a paragraph. For new HTTP APIs, use JSON. For document-shaped content with rich text markup, XML still has the edge. For configuration, prefer YAML or TOML over both. For machine-to-machine wire protocols where size matters, prefer Protocol Buffers or CBOR.

When you find XML in a project, it’s almost always for a reason: an external standard, a long-lived integration, or a document format. Work with it, don’t try to replace it casually.

Workflow#

Extract, parse, filter, save. xmlstarlet is the command-line workhorse; for batch processing or real schemas, Python lxml or Go encoding/xml.

Extract and inspect.

$ xmlstarlet el -a inventory.xml | head -40        # element + attr outline
$ xmlstarlet val -e inventory.xml                  # well-formedness check
$ xmlstarlet sel -t -v 'count(//host)' inventory.xml

Parse and filter with XPath.

# all hosts with role attribute "db"
$ xmlstarlet sel -t -c '//host[@role="db"]' inventory.xml

# project the @name attribute of each match
$ xmlstarlet sel -t -m '//host[@role="db"]' \
    -v '@name' -n inventory.xml

Save.

$ xmlstarlet sel -t -c '//host[@role="db"]' inventory.xml > db-hosts.xml

# in-place update of an attribute
$ xmlstarlet ed -L -u '//host[@name="db1"]/@port' -v 5433 inventory.xml

Python with lxml.

from lxml import etree

tree  = etree.parse("inventory.xml")
hosts = tree.xpath('//host[@role="db"]')
out   = etree.Element("hosts")
for h in hosts: out.append(h)
etree.ElementTree(out).write("db-hosts.xml", pretty_print=True)