Skip to content

XML Hoja de referencia

Extensible Markup Language for structured data exchange.

01

Getting Started

XML Document Structure

XML documents start with an XML declaration. Elements must be properly nested and closed. Attributes provide additional metadata. xmlns declares XML namespaces to avoid naming conflicts.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This is a comment -->
<bookstore xmlns="https://example.com/books">
  <book id="b1" category="fiction">
    <title lang="en">The Great Gatsby</title>
    <author>F. Scott Fitzgerald</author>
    <price currency="USD">12.99</price>
  </book>
  <book id="b2" category="programming">
    <title lang="en">XML Guide</title>
    <author>John Doe</author>
    <price currency="USD">29.99</price>
  </book>
</bookstore>

XML Declaration

The XML declaration is the prolog's first line. 'version' is required (1.0 or 1.1). 'encoding' defaults to UTF-8. 'standalone' tells parsers whether external DTD declarations are needed.

xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<!-- version is required; encoding and standalone are optional -->
<!-- encoding defaults to UTF-8 -->
<!-- standalone="yes" means no external markup declarations -->

<root>
  <child>text</child>
</root>

Comments & Processing Instructions

Comments use <!-- --> and cannot nest or contain '--' inside. Processing Instructions (PIs) like xml-stylesheet pass app-specific info to the parser. PIs use <?target data?>.

xml
<?xml version="1.0"?>
<!-- a comment: ignored by the parser, cannot contain -- -->

<?xml-stylesheet type="text/xsl" href="style.xsl"?>

<root>
  <!-- comments can span
       multiple lines -->
  <item>data</item>
</root>

Well-formed XML

Well-formed means: one root element, proper nesting, all tags closed, attributes quoted, no duplicate attributes. A document MUST be well-formed to be parseable; validity (against a DTD/Schema) is optional.

xml
<!-- WELL-FORMED: single root, properly nested, closed -->
<root>
  <a>
    <b>text</b>
  </a>
</root>

<!-- NOT well-formed: two roots, overlapping tags -->
<!-- <a><b></a></b> -->
<!-- <x></x><y></y>   (no single root) -->

<!-- All attributes must be quoted -->
<item id='1' type="x"/>

XML Prolog & Whitespace

The prolog includes the XML declaration and optional DOCTYPE. xml:space='preserve' tells parsers to keep whitespace; 'default' allows normal handling. Whitespace between elements is generally insignificant.

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root SYSTEM "root.dtd">
<root xml:space="preserve">
  <item>  spaced  text  </item>
  <item xml:space="default">  trimmed  </item>
</root>

CDATA Overview

CDATA sections mark text that the parser should not interpret as markup. Inside CDATA, characters like <, >, and & need no escaping. CDATA cannot be nested and cannot contain the literal ']]>'.

xml
<?xml version="1.0"?>
<root>
  <code><![CDATA[
    if (a < b && c > d) {
      console.log("no escaping needed: < > & ");
    }
  ]]></code>
</root>
02

Elements & Attributes

Elements vs Attributes

Attributes suit metadata (ids, types, flags); elements suit data with structure or repeated values. An attribute appears once per element and cannot hold nested structure. There is no strict rule—consistency matters most.

xml
<!-- Attributes: metadata/identifiers; Elements: data -->
<person id="p1">
  <name>Alice</name>
  <age>30</age>
</person>

<!-- vs. putting everything in attributes -->
<person id="p1" name="Alice" age="30"/>

<!-- Rule of thumb: use elements for data you display,
     attributes for IDs, types, and metadata -->

Empty Elements

An empty element has no content. The self-closing form <tag/> is shorthand for <tag></tag>. The space before '/>' is a style convention for readability and XHTML compatibility.

xml
<!-- Three equivalent empty-element forms -->
<br></br>
<br/>
<br />

<!-- empty element with attributes -->
<img src="logo.png" alt="Logo" width="200" height="100"/>

<!-- self-closing with namespace -->
<xlink:link xmlns:xlink="http://www.w3.org/1999/xlink" href="#sec1"/>

Nested Elements

Elements nest hierarchically and must close in reverse order—they cannot overlap. Nesting expresses structure. Deeply nested trees are valid but can hurt readability and parsing performance.

xml
<library>
  <section name="fiction">
    <book>
      <title>Book A</title>
      <authors>
        <author>Author 1</author>
        <author>Author 2</author>
      </authors>
    </book>
  </section>
</library>

<!-- WRONG: tags must not overlap -->
<!-- <a><b></a></b> -->

Attribute Values & Quotes

Attribute values must be quoted with single or double quotes. Double quotes inside double-quoted values must be escaped as &quot;. An attribute may appear only once per element—use child elements for repeated values.

xml
<!-- Both single and double quotes are allowed -->
<item id="q1" name='quick'/>
<msg text="He said &quot;hi&quot;"/>
<msg text='She said &apos;hi&apos;'/>

<!-- attribute values MUST be quoted -->
<!-- <item id=q1/>  is NOT well-formed -->

<!-- an attribute can appear at most once per element -->
<point x="1" y="2" z="3"/>

Default & Fixed Attributes

DTD attribute declarations support #REQUIRED (must appear), #IMPLIED (optional), #FIXED (constant value), and a literal default. A #FIXED attribute must always equal its declared value or the document is invalid.

xml
<!-- In a DTD, attributes can have defaults -->
<!ATTLIST item
    type    CDATA     "standard"
    status  CDATA     #REQUIRED
    version CDATA     #FIXED "1.0"
    note    CDATA     #IMPLIED>

<item status="active"/>
<!-- 'type' defaults to "standard", 'version' is fixed,
     'note' is optional (#IMPLIED) -->

Element Naming Rules

Element names must start with a letter or underscore and may contain letters, digits, hyphens, underscores, and dots. Names cannot start with 'xml' (any case). XML is case-sensitive, so <Tag> and <tag> differ.

xml
<!-- Names: start with a letter or underscore,
     continue with letters, digits, hyphens, underscores, dots -->
<my-element>ok</my-element>
<my.element>ok</my.element>
<my_element>ok</my_element>
<_private>ok</_private>
<x-1>ok</x-1>

<!-- NOT allowed: starting with a digit or 'xml', containing spaces -->
<!-- <2way/> -->
<!-- <my element/> -->
<!-- <XmlTag/>  (names are case-sensitive; 'xml' prefix is reserved) -->
03

Namespaces

Default Namespace

A default namespace (xmlns=URI) applies to the element where it is declared and all unprefixed descendants. Child elements inherit it unless overridden. Attributes never use the default namespace.

xml
<?xml version="1.0"?>
<table xmlns="http://www.w3.org/1999/xhtml">
  <tr>
    <td>Cell content</td>
    <td>More content</td>
  </tr>
</table>
<!-- All elements without a prefix use the default namespace -->

Namespace Prefix

A namespace prefix is declared with xmlns:prefix=URI and used as prefix:element. The prefix is just an alias—only the URI matters for identity. Prefixes are case-sensitive.

xml
<?xml version="1.0"?>
<root xmlns:bk="https://example.com/books"
      xmlns:au="https://example.com/authors">
  <bk:book bk:id="b1">
    <au:author au:name="Jane Doe"/>
    <bk:title>XML Guide</bk:title>
  </bk:book>
</root>

Multiple Namespaces

A single document can mix many namespaces. Declare each prefix once (usually on the root) and reuse it. Declaring a namespace on an element applies to that element and its descendants.

xml
<?xml version="1.0"?>
<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <m:GetPrice xsi:type="xsd:string"
                xmlns:m="https://example.com/pricing">
      <m:Item>Apples</m:Item>
    </m:GetPrice>
  </soap:Body>
</soap:Envelope>

Namespace Scope

Namespace declarations are scoped to the element they appear on and its descendants. A redeclaration inside a child overrides the parent's prefix mapping for that subtree. The original mapping is restored after the child closes.

xml
<?xml version="1.0"?>
<root xmlns:a="http://a.example">
  <a:item>A</a:item>
  <child xmlns:a="http://b.example">
    <!-- here 'a' is REBOUND to the b.example URI -->
    <a:item>B</a:item>
  </child>
  <!-- outside child, 'a' is back to a.example -->
  <a:item>A again</a:item>
</root>

Common Namespaces

These URIs are well-known identifiers, not URLs that must be fetched. The xsi namespace provides schema-instance attributes (type, nil, schemaLocation). XSD, XSL, SVG, SOAP, Atom, and Dublin Core (dc) each have standard URIs.

xml
<!-- Frequently used namespace URIs -->
xmlns:xsi  = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd  = "http://www.w3.org/2001/XMLSchema"
xmlns:xsl  = "http://www.w3.org/1999/XSL/Transform"
xmlns:svg  = "http://www.w3.org/2000/svg"
xmlns:html = "http://www.w3.org/1999/xhtml"
xmlns:soap = "http://schemas.xmlsoap.org/soap/envelope/"
xmlns:atom = "http://www.w3.org/2005/Atom"
xmlns:dc   = "http://purl.org/dc/elements/1.1/"

Target Namespace in XSD

A schema's targetNamespace is the namespace of the elements it defines. elementFormDefault='qualified' means locally declared elements are in the target namespace. The tns prefix is a convention for 'this namespace'.

xml
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="https://example.com/orders"
           xmlns:tns="https://example.com/orders"
           elementFormDefault="qualified">
  <xs:element name="order" type="tns:OrderType"/>
  <xs:complexType name="OrderType">
    <xs:sequence>
      <xs:element name="item" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>
04

CDATA & Entities

Predefined Entities

XML predefines five entities: &lt; (<), &gt; (>), &amp; (&), &apos; ('), &quot; ("). & must ALWAYS be escaped (even in CDATA-free text); < must be escaped except as a tag start; quotes matter mainly in attributes.

xml
<?xml version="1.0"?>
<root>
  <lt>less than: &lt;</lt>
  <gt>greater than: &gt;</gt>
  <amp>ampersand: &amp;</amp>
  <apos>apostrophe: &apos;</apos>
  <quot>quote: &quot;</quot>
  <!-- &lt; &gt; &amp; &apos; &quot; are the 5 built-in entities -->
</root>

Character References

Character references use a Unicode code point: &#169; (decimal) or &#xA9; (hex) for ©. They work in element text and attribute values and are not affected by encoding. They can represent any Unicode character.

xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <decimal>&#169; 2024 Company</decimal>
  <hex>&#xA9; 2024 Company</hex>
  <euro>Price: &#x20AC;10</euro>
  <emoji>Smile: &#x1F600;</emoji>
</root>
<!-- &#NNN;  decimal code point -->
<!-- &#xHHH; hexadecimal code point -->

CDATA Sections

CDATA sections let you include <, >, and & without escaping—useful for code and markup. CDATA cannot contain ']]>' literally; to embed it, split the section as shown. CDATA is character data, not parsed as elements.

xml
<?xml version="1.0"?>
<root>
  <script><![CDATA[
    function check(a, b) {
      if (a < b && b > 0) {
        return a & b;
      }
    }
  ]]></script>
  <!-- to include ]]> inside CDATA, split it -->
  <data><![CDATA[foo]]]]><![CDATA[>bar]]></data>
</root>

Custom Entities (DTD)

Internal entities are declared in a DTD and expanded wherever referenced. They act like text macros and can reference other entities. Useful for repeated boilerplate. Beware: external/general entities can be an XXE security risk.

xml
<?xml version="1.0"?>
<!DOCTYPE root [
  <!ENTITY company "Acme Corp.">
  <!ENTITY version "2.0">
  <!ENTITY copyright "Copyright © 2024 &company;">
]>
<root>
  <product>&company; Toolkit v&version;</product>
  <footer>&copyright;</footer>
</root>
<!-- Entities are expanded during parsing -->

Escaping in Attributes

Inside attribute values you must escape & as &amp;, and escape the quote character used to delimit the value (&quot; or &apos;). < should also be escaped. Newlines and tabs in attributes are normalized to spaces by the parser.

xml
<?xml version="1.0"?>
<root>
  <link url="https://example.com?a=1&amp;b=2"/>
  <msg text="Say &quot;hello&quot; &amp; smile"/>
  <path value='C:\Users\name'/>
  <data value="&lt;tagged&gt;"/>
</root>

Parameter Entities

Parameter entities (declared with %) are used only inside DTDs to build reusable content models. They are referenced as %name; (with a semicolon). General entities (&name;) are used in document content.

xml
<?xml version="1.0"?>
<!DOCTYPE root [
  <!ENTITY % inline " (#PCDATA | em | strong)* ">
  <!ELEMENT p %inline;>
  <!ELEMENT em %inline;>
  <!ELEMENT strong %inline;>
  <!ELEMENT root (p)*>
]>
<root>
  <p>Hello <em>world</em> and <strong>XML</strong>.</p>
</root>
05

DTD (Document Type Definition)

Internal DTD

An internal DTD is declared inline in the DOCTYPE within square brackets. It defines the structure: which elements exist, their content models, and attributes. Internal DTDs are self-contained but only apply to that one document.

xml
<?xml version="1.0"?>
<!DOCTYPE note [
  <!ELEMENT note (to, from, heading, body)>
  <!ELEMENT to (#PCDATA)>
  <!ELEMENT from (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body (#PCDATA)>
]>
<note>
  <to>Alice</to>
  <from>Bob</from>
  <heading>Reminder</heading>
  <body>Don't forget the meeting</body>
</note>

External DTD

An external DTD lives in a separate .dtd file referenced by SYSTEM (private) or PUBLIC (public identifier + URI). External DTDs let many documents share one definition. SYSTEM 'file.dtd' is the most common form.

xml
<!-- File: note.dtd -->
<!ELEMENT note (to, from, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT body (#PCDATA)>

<!-- File: note.xml -->
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
  <to>Alice</to>
  <from>Bob</from>
  <body>Hello</body>
</note>

Element Declarations

Content models use , (sequence), | (choice), ? (0-1), * (0+), + (1+). #PCDATA is text; mixed content allows text interleaved with elements. EMPTY means no content; ANY disables checking (avoid in production).

xml
<!ELEMENT root (child1, child2*)>
<!-- content model operators -->
<!ELEMENT list (item+)>            <!-- one or more item -->
<!ELEMENT opt (item?)>             <!-- zero or one item   -->
<!ELEMENT any (item1 | item2)*>    <!-- choice, any number  -->
<!ELEMENT text (#PCDATA)>          <!-- parsed character data -->
<!ELEMENT mixed (#PCDATA | em)*>   <!-- mixed content -->
<!ELEMENT empty EMPTY>             <!-- no content -->
<!ELEMENT any ANY>                 <!-- any content -->

Attribute Declarations

ATTLIST declares an attribute's type and default. ID is a unique identifier; IDREF/IDREFS reference IDs (for cross-links). NMTOKEN is a name token. Enumerated types list allowed values. Defaults: #REQUIRED, #IMPLIED, #FIXED, or a literal.

xml
<!ATTLIST book
    id      ID         #REQUIRED
    category CDATA     #IMPLIED
    lang    NMTOKEN    "en"
    status  (draft|final) "draft"
    version CDATA      #FIXED "1.0">

<!-- Types: CDATA, ID, IDREF, IDREFS, NMTOKEN, NMTOKENS,
     NOTATION, enumerated (a|b|c) -->
<!ATTLIST link ref IDREF #REQUIRED>

Entity Declarations

General entities (&name;) expand in content; parameter entities (%name;) expand in the DTD. External parsed entities include other XML; unparsed entities (with NDATA) point to non-XML data referenced via NOTATION. External entities can cause XXE injection.

xml
<!DOCTYPE root [
  <!-- internal general entity -->
  <!ENTITY name "Alice">
  <!-- external parsed entity -->
  <!ENTITY chap1 SYSTEM "chapter1.xml">
  <!-- unparsed entity (binary, e.g. image) -->
  <!ENTITY logo SYSTEM "logo.png" NDATA image>
  <!-- notation declaration for unparsed entities -->
  <!NOTATION image SYSTEM "image/png">
  <!-- parameter entity (DTD-only) -->
  <!ENTITY % block "(p | list)+">
]>
<root>&name;</root>

DTD Validation

Validation checks that a document matches its DTD (element order, allowed attributes, ID uniqueness). Standard DOM parsers like minidom do NOT validate by default—use lxml (Python), xmllint (command line), or a validating parser.

xml
<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "schema.dtd">
<root>
  <item id="x1">valid</item>
</root>

<!-- Validate with Python: -->
<!-- import xml.dom.minidom as m
dom = m.parse("file.xml")
# minidom does NOT validate; use lxml: -->
<!-- from lxml import etree
dtd = etree.DTD("schema.dtd")
tree = etree.parse("file.xml")
print(dtd.validate(tree)) -->
06

XML Schema (XSD)

Schema Structure

An XSD is itself an XML document. The root <schema> declares the XSD namespace and a target namespace for the elements it defines. elementFormDefault='qualified' puts local elements in the target namespace.

xml
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="https://example.com/order"
           xmlns:tns="https://example.com/order"
           elementFormDefault="qualified">

  <xs:element name="order" type="tns:OrderType"/>

  <xs:complexType name="OrderType">
    <xs:sequence>
      <xs:element name="customer" type="xs:string"/>
      <xs:element name="total" type="xs:decimal"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:ID" use="required"/>
  </xs:complexType>
</xs:schema>

Simple Types

A simpleType restricts a base type with facets (pattern, minInclusive, enumeration, length, etc.). Simple types have only text content and attributes—no child elements. They are reusable across the schema.

xml
<xs:simpleType name="emailType">
  <xs:restriction base="xs:string">
    <xs:pattern value="[^@]+@[^@]+.[^@]+"/>
  </xs:restriction>
</xs:simpleType>

<xs:simpleType name="ageType">
  <xs:restriction base="xs:integer">
    <xs:minInclusive value="0"/>
    <xs:maxInclusive value="120"/>
  </xs:restriction>
</xs:simpleType>

<xs:element name="email" type="tns:emailType"/>
<xs:element name="age" type="tns:ageType"/>

Complex Types

A complexType can hold child elements and attributes. sequence enforces order; choice allows one of several; all allows any order (each at most once). Compositors can nest to express rich structures.

xml
<xs:complexType name="AddressType">
  <xs:sequence>
    <xs:element name="street" type="xs:string"/>
    <xs:element name="city" type="xs:string"/>
    <xs:element name="zip" type="xs:string"/>
  </xs:sequence>
  <xs:attribute name="country" type="xs:string" default="US"/>
</xs:complexType>

<!-- Content model indicators: sequence, choice, all -->
<xs:complexType name="ContactType">
  <xs:choice>
    <xs:element name="phone" type="xs:string"/>
    <xs:element name="email" type="xs:string"/>
  </xs:choice>
</xs:complexType>

Built-in Types

XSD provides a rich type hierarchy: strings, numbers (integer, decimal, float), dates/times, boolean, anyURI, ID/IDREF, and QName. Each supports facets like length, pattern, and enumeration for further restriction.

xml
<!-- Common XSD built-in types -->
<xs:element name="count"  type="xs:integer"/>
<xs:element name="price"  type="xs:decimal"/>
<xs:element name="flag"   type="xs:boolean"/>
<xs:element name="name"   type="xs:string"/>
<xs:element name="date"   type="xs:date"/>        <!-- 2024-01-15 -->
<xs:element name="time"   type="xs:time"/>        <!-- 13:45:00 -->
<xs:element name="dt"     type="xs:dateTime"/>    <!-- 2024-01-15T13:45:00 -->
<xs:element name="id"     type="xs:ID"/>
<xs:element name="ref"    type="xs:IDREF"/>
<xs:element name="uri"    type="xs:anyURI"/>

Restrictions (Facets)

Facets constrain simple types: enumeration (allowed values), pattern (regex), length/minLength/maxLength, min/maxInclusive/Exclusive, totalDigits, fractionDigits, and whiteSpace (preserve/replace/collapse).

xml
<xs:simpleType name="ColorType">
  <xs:restriction base="xs:string">
    <xs:enumeration value="red"/>
    <xs:enumeration value="green"/>
    <xs:enumeration value="blue"/>
  </xs:restriction>
</xs:simpleType>

<xs:simpleType name="PasswordType">
  <xs:restriction base="xs:string">
    <xs:minLength value="8"/>
    <xs:maxLength value="32"/>
    <xs:pattern value="[A-Za-z0-9!@#]+"/>
  </xs:restriction>
</xs:simpleType>

Elements & Attributes in XSD

minOccurs/maxOccurs control cardinality (default 1); maxOccurs='unbounded' allows any number. Attribute 'use' is required/optional/prohibited, with an optional default or fixed value. Anonymous complex types inline the definition.

xml
<xs:element name="product">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="name" type="xs:string"
                  minOccurs="1" maxOccurs="1"/>
      <xs:element name="tag" type="xs:string"
                  minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="sku" type="xs:string" use="required"/>
    <xs:attribute name="weight" type="xs:decimal" use="optional"
                  default="0.0"/>
  </xs:complexType>
</xs:element>
07

XPath

Path Expressions

XPath uses path-like expressions. '/' selects from the root; '//' selects descendants at any depth. '*' is a wildcard for any element; '@' selects attributes. An expression starting with '/' is absolute.

xml
<!-- Sample: <bookstore><book><title>..</title></book></bookstore> -->

/bookstore/book/title       <!-- absolute path -->
bookstore/book/title        <!-- relative path -->
//title                     <!-- any title, anywhere -->
/bookstore//title           <!-- title anywhere under bookstore -->
bookstore/*                 <!-- all children of bookstore -->
//@lang                     <!-- all lang attributes -->
//@*                        <!-- all attributes -->

Predicates

Predicates in [ ] filter a node set. Indexing is 1-based. last() and position() refer to the node's position in its context. Predicates can test element text, attributes (@name), or computed values.

xml
//book[1]                  <!-- first book -->
//book[last()]            <!-- last book -->
//book[position() <= 3]   <!-- first three books -->
//book[price > 25]        <!-- books with price > 25 -->
//book[@category='web']   <!-- books in the web category -->
//book[author='Smith']    <!-- books authored by Smith -->
//book[@lang][2]          <!-- 2nd book that has a lang attr -->

Axes

Axes define navigation directions: child (default), descendant, parent (..), ancestor, following-sibling, preceding-sibling, attribute (@), self (.), descendant-or-self (//). Most have shorthand forms used in practice.

xml
child::book            <!-- default axis: children named book -->
descendant::title      <!-- all title descendants -->
parent::*              <!-- the parent element (shorthand: ..) -->
ancestor::section      <!-- all section ancestors -->
following-sibling::p   <!-- p elements after this one -->
preceding::item        <!-- items before this in document order -->
attribute::id          <!-- the id attribute (shorthand: @id) -->
self::node()           <!-- the current node (shorthand: .) -->

Functions

XPath 1.0 has built-in functions: count, sum, string-length, contains, starts-with, normalize-space, name, concat, substring, round, and more. XPath 2.0+ vastly expands the function library and adds typing.

xml
count(//book)                  <!-- number of book elements -->
string-length(//title)        <!-- length of first title's text -->
contains(//name, 'Ali')       <!-- true if name contains 'Ali' -->
starts-with(@id, 'b')         <!-- true if id starts with 'b' -->
normalize-space(//summary)    <!-- collapse whitespace -->
name(//*[1])                  <!-- name of the first element -->
concat(/a, /b)                <!-- concatenate strings -->
sum(//price)                  <!-- sum of all price values -->

Operators

XPath uses = for equality (single =, not ==), and the operators 'and'/'or' for logic (not && / ||). 'div' is division and 'mod' is modulo, since '/' is reserved for paths. '|' computes the union of node sets.

xml
<!-- Comparison: =, !=, <, >, <=, >= -->
//book[price = 29.99]
//book[price < 20]

<!-- Logical: and, or -->
//book[price > 10 and price < 30]
//book[@cat='a' or @cat='b']

<!-- Arithmetic: + - * div mod -->
//item[price * quantity > 100]
5 div 2        <!-- 2.5 -->
5 mod 2        <!-- 1 -->

<!-- Union: | -->
//book | //magazine

XPath Examples

These patterns combine paths, predicates, and functions for real queries. text() selects the text node; min() requires XPath 2.0+. The //* wildcard with a predicate is a common way to find elements by attribute across the whole tree.

xml
<!-- Select all book titles priced over 20 -->
//book[price > 20]/title/text()

<!-- The category of the cheapest book -->
//book[price = min(//book/price)]/@category

<!-- Authors of books in the 'programming' category -->
//book[@category='programming']/author

<!-- Every 2nd item in each list -->
//list/item[position() mod 2 = 0]

<!-- Elements with a specific attribute value -->
//*[@lang='fr']
08

XSLT Transformations

Basic Transformation

An XSLT stylesheet is XML. Templates match nodes with XPath; apply-templates recursively processes selected nodes. value-of extracts text. The match='/' template runs first, on the document root.

xml
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="/">
    <html>
      <body>
        <h2>Book List</h2>
        <xsl:apply-templates select="bookstore/book"/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="book">
    <p><xsl:value-of select="title"/> - <xsl:value-of select="price"/></p>
  </xsl:template>
</xsl:stylesheet>

xsl:template & apply-templates

match templates fire when the processor visits matching nodes; named templates are called explicitly with call-template. apply-templates without a select processes all children, enabling recursive, rule-driven transformations.

xml
<xsl:template match="book">
  <div class="book">
    <xsl:apply-templates select="title"/>
    <xsl:apply-templates select="author"/>
  </div>
</xsl:template>

<xsl:template match="title">
  <h3><xsl:apply-templates/></h3>
</xsl:template>

<!-- named template (like a function) -->
<xsl:template name="divider">
  <hr/>
</xsl:template>

xsl:value-of & xsl:for-each

xsl:for-each iterates a node set, changing the context node. It is often simpler than templates for flat output, but overuse makes stylesheets procedural. Prefer templates and apply-templates for recursive structures.

xml
<xsl:template match="/">
  <ul>
    <xsl:for-each select="bookstore/book">
      <li>
        <xsl:value-of select="title"/>
        (<xsl:value-of select="@category"/>)
      </li>
    </xsl:for-each>
  </ul>
</xsl:template>

<!-- value-of outputs the string value of an expression -->

xsl:if & xsl:choose

xsl:if has no else—use xsl:choose for multi-way branches. The 'test' expression follows XPath boolean rules: a non-empty node-set is true, an empty one is false, non-zero numbers are true.

xml
<xsl:for-each select="bookstore/book">
  <xsl:if test="price > 25">
    <premium><xsl:value-of select="title"/></premium>
  </xsl:if>

  <xsl:choose>
    <xsl:when test="@category='fiction'">F</xsl:when>
    <xsl:when test="@category='programming'">P</xsl:when>
    <xsl:otherwise>?</xsl:otherwise>
  </xsl:choose>
</xsl:for-each>

<!-- test uses XPath; non-empty node-sets and non-zero numbers are true -->

xsl:sort & xsl:variable

xsl:sort reorders the current node set by one or more keys; data-type is 'text' or 'number'. Variables (xsl:variable) are immutable and scoped. Remember to escape < as &lt; inside attribute values in the stylesheet XML.

xml
<xsl:variable name="max-price" select="50"/>

<xsl:for-each select="bookstore/book[price &lt; $max-price]">
  <xsl:sort select="price" data-type="number" order="descending"/>
  <xsl:sort select="title" order="ascending"/>
  <p>
    <xsl:value-of select="title"/>: <xsl:value-of select="price"/>
  </p>
</xsl:for-each>

<!-- Note: in XSLT, < must be written &lt; inside test attributes -->

xsl:apply-templates & modes

Modes let one set of source nodes be processed in different ways (e.g., a table of contents vs. full entries). The mode attribute on both template and apply-templates must match. Without a mode, only no-mode templates apply.

xml
<!-- A template can have a 'mode' for different outputs -->
<xsl:template match="book" mode="list">
  <li><xsl:value-of select="title"/></li>
</xsl:template>

<xsl:template match="book" mode="detail">
  <div><h3><xsl:value-of select="title"/></h3>
       <p><xsl:value-of select="author"/></p></div>
</xsl:template>

<!-- Invoke a specific mode -->
<xsl:apply-templates select="bookstore/book" mode="list"/>
<xsl:apply-templates select="bookstore/book" mode="detail"/>
09

XQuery

FLWOR Expressions

FLWOR is XQuery's core construct: for binds variables to sequence items, let binds computed values, where filters, order by sorts, and return shapes the output. It resembles SQL's SELECT-FROM-WHERE.

xml
for $b in /bookstore/book
let $discount := $b/price * 0.9
where $b/price > 20
order by $b/price descending
return
  <book>
    <title>{ $b/title/text() }</title>
    <salePrice>{ $discount }</salePrice>
  </book>

<!-- FLWOR = For, Let, Where, Order by, Return -->

Path Expressions

XQuery embeds XPath directly. doc('file.xml') loads a document; collection('uri') queries multiple documents. Path expressions return sequences of nodes, which FLWOR expressions can iterate over.

xml
(: XQuery uses XPath for navigation :)
/bookstore/book/title/text()
/bookstore/book[@category='web']
//author[contains(., 'Smith')]

(: Use doc() to open an external document :)
doc('catalog.xml')//book[price < 30]

(: collection() queries a set of documents :)
collection('/db/books')//title

Predicates & Joins

Multiple 'for' clauses form a join (like SQL JOIN). XQuery performs joins by iterating combinations and filtering with where. For large datasets, explicit joins via predicates or keys are more efficient than nested loops.

xml
(: Join books to authors by id :)
for $b in doc('books.xml')//book
for $a in doc('authors.xml')//author
where $b/@author-id = $a/@id
return
  <entry>
    <title>{ $b/title/text() }</title>
    <author>{ $a/name/text() }</author>
  </entry>

(: Equivalent join with a predicate :)
for $b in doc('books.xml')//book
return
  <entry>{ $b/title, $b/@author-id }</entry>

Conditional & Quantified

XQuery has if-then-else (the else is mandatory), quantified expressions (some/every ... satisfies), and typeswitch for branching on node type. Conditions use XPath-style boolean semantics.

xml
(: if-then-else :)
for $b in //book
return
  if ($b/price > 30) then <expensive>{ $b/title }</expensive>
  else <cheap>{ $b/title }</cheap>

(: quantified expressions :)
some $a in //author satisfies contains($a, 'Smith')
every $p in //price satisfies $p > 0

(: typeswitch for type-based dispatch :)
typeswitch($node)
  case element(book) return "book"
  case element(author) return "author"
  default return "other"

Functions

XQuery reuses XPath functions and adds many (string-join, distinct-values, avg, min, max). User functions are declared in the module with parameter and return types, in the local: namespace by convention.

xml
(: Built-in functions :)
concat('a', 'b', 'c')
string-join(//title, ', ')
count(//book)
distinct-values(//book/@category)
avg(//book/price)
max(//book/price)

(: User-defined function :)
declare function local:discount($price as xs:decimal)
  as xs:decimal {
  $price * 0.9
};

local:discount(29.99)

Constructing XML

XQuery builds XML with direct constructors (literal tags with { } for embedded expressions) or computed constructors (element name { ... }). Curly braces evaluate enclosed expressions; attributes can embed values inline.

xml
(: Direct element construction :)
<result>
  <count>{ count(//book) }</count>
  <books>
    { for $b in //book return <book title="{ $b/title/text() }"/> }
  </books>
</result>

(: Computed constructors :)
element result {
  attribute total { count(//book) },
  for $b in //book
  return element book { $b/title/text() }
}

(: Enclosed expressions use { } to embed values -->
10

DOM Parsing

Loading XML (JavaScript)

DOMParser turns an XML string into a DOM tree in the browser; parse errors appear as a <parsererror> element rather than an exception. Node.js has no built-in XML DOM—use xmldom, @xmldom/xmldom, or a streaming parser.

xml
// Browser: parse an XML string
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, "application/xml");

const errorNode = doc.querySelector("parsererror");
if (errorNode) {
  console.error("Parse error:", errorNode.textContent);
}

const books = doc.getElementsByTagName("book");
for (const book of books) {
  console.log(book.getAttribute("id"));
}

// Node.js: use xmldom or fast-xml-parser instead

DOM Traversal

The DOM API exposes documentElement, children, childNodes, and nextSibling for traversal. nodeType 1 is an element, 3 is text, 8 is a comment. document.evaluate runs XPath in browsers (Node needs xpath libraries).

xml
// JavaScript DOM API (also works for XML documents)
const root = doc.documentElement;        // <bookstore>
const firstBook = root.children[0];      // first <book>
const titles = doc.getElementsByTagName("title");

// walk children
let node = root.firstChild;
while (node) {
  if (node.nodeType === 1) {             // Element node
    console.log(node.tagName);
  }
  node = node.nextSibling;
}

// XPath against the DOM
const it = doc.evaluate("//book[@id='b1']", doc, null,
                        XPathResult.ANY_TYPE, null);
const found = it.iterateNext();

Modifying DOM

createElement/setAttribute/appendChild build and attach nodes; textContent sets text. XMLSerializer turns a DOM back into a string. The DOM is live—mutations reflect immediately in any references.

xml
const book = doc.createElement("book");
book.setAttribute("id", "b3");

const title = doc.createElement("title");
title.textContent = "New Book";
book.appendChild(title);

root.appendChild(book);                  // add to tree

// change an existing element
const price = doc.querySelector("book:nth-child(2) price");
price.textContent = "19.99";
price.setAttribute("currency", "EUR");

// serialize back to a string
const xml = new XMLSerializer().serializeToString(doc);

Creating Elements

minidom lets you build a document from scratch: createDocument for the root, createElement/setAttribute/createTextNode for content, appendChild to link. toprettyxml serializes with indentation. minidom is simple but slow for large trees.

xml
// Python: xml.dom.minidom
from xml.dom import minidom

impl = minidom.getDOMImplementation()
doc = impl.createDocument(None, "root", None)
root = doc.documentElement

item = doc.createElement("item")
item.setAttribute("id", "1")
item.appendChild(doc.createTextNode("Hello"))
root.appendChild(item)

print(doc.toprettyxml(encoding="UTF-8").decode())

Java DOM Parsing

Java's JAXP DOM loads the whole document into memory via DocumentBuilder. setNamespaceAware(true) keeps namespace prefixes. The DOM is convenient for random access but memory-heavy for large files—prefer StAX/SAX then.

xml
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;

DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
Document doc = f.newDocumentBuilder().parse(new File("data.xml"));

Element root = doc.getDocumentElement();
NodeList books = root.getElementsByTagName("book");
for (int i = 0; i < books.getLength(); i++) {
  Element b = (Element) books.item(i);
  String id = b.getAttribute("id");
  String title = b.getElementsByTagName("title")
                      .item(0).getTextContent();
  System.out.println(id + ": " + title);
}

lxml (Python)

lxml is a fast, feature-rich Python binding for libxml2. It supports full XPath, XSLT, validation (DTD/XSD/RelaxNG), and incremental parsing. Its ElementPath (find/findall) is a subset of XPath; use xpath() for full XPath.

xml
from lxml import etree

# parse and validate against a schema
tree = etree.parse("data.xml")
root = tree.getroot()

# XPath with namespaces
ns = {"bk": "https://example.com/books"}
for book in root.xpath("//bk:book", namespaces=ns):
    print(book.get("id"), book.find("bk:title", namespaces=ns).text)

# build an element tree
root = etree.Element("root")
child = etree.SubElement(root, "item", id="1")
child.text = "Hello"
print(etree.tostring(root, pretty_print=True, encoding="unicode"))
11

SAX Parsing

SAX Handler (Python)

SAX is event-driven and streaming: the parser calls startElement, characters, and endElement as it reads, never building a full tree. It is memory-efficient for huge files. You must track state (e.g., current element) yourself.

xml
import xml.sax

class BookHandler(xml.sax.ContentHandler):
    def startElement(self, name, attrs):
        if name == "book":
            print("Book id:", attrs.getValue("id"))
        self.current = name

    def characters(self, content):
        if self.current == "title":
            print("Title:", content.strip())

    def endElement(self, name):
        self.current = None

xml.sax.parse("books.xml", BookHandler())

SAX Event Methods

ContentHandler callbacks mark document and element boundaries. characters() may split a single text node into several calls—accumulate into a buffer. startPrefixMapping/endPrefixMapping report namespace declarations.

xml
class Handler(xml.sax.ContentHandler):
    def startDocument(self):
        print("parsing started")

    def endDocument(self):
        print("parsing done")

    def startElement(self, name, attrs):
        # attrs: get via attrs.getValue('id') or attrs['id']
        pass

    def endElement(self, name):
        pass

    def characters(self, text):
        # may be called multiple times for one text node
        pass

    def startPrefixMapping(self, prefix, uri):
        print("namespace:", prefix, uri)

startElement / endElement

Tracking an element path (a stack) is the standard way to know your location during streaming. Buffer characters() output and flush it in endElement, because characters can fire more than once per text node.

xml
class Handler(xml.sax.ContentHandler):
    def __init__(self):
        self.path = []
        self.text = []

    def startElement(self, name, attrs):
        self.path.append(name)
        self.text = []
        if "id" in attrs:
            print("/".join(self.path), "id =", attrs["id"])

    def endElement(self, name):
        if self.path[-1] == "title":
            print("title:", "".join(self.text).strip())
        self.path.pop()

    def characters(self, content):
        self.text.append(content)

Java SAX Parser

Java SAX extends DefaultHandler. startElement receives namespace URI, local name, qualified name, and an Attributes object. characters(ch, start, len) provides a char slice—buffer it because a text node can span multiple calls.

xml
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.*;
import javax.xml.parsers.*;

class BookHandler extends DefaultHandler {
    private StringBuilder buf = new StringBuilder();

    public void startElement(String uri, String local,
                             String qName, Attributes attrs) {
        buf.setLength(0);
        if ("book".equals(qName))
            System.out.println("id=" + attrs.getValue("id"));
    }

    public void characters(char[] ch, int start, int len) {
        buf.append(ch, start, len);
    }

    public void endElement(String uri, String local, String qName) {
        if ("title".equals(qName))
            System.out.println("title=" + buf);
    }
}
// SAXParserFactory.newInstance().newSAXParser().parse(file, new BookHandler());

SAX vs DOM

SAX streams events with constant memory, ideal for huge files and one-pass extraction. DOM loads the whole tree, enabling XPath and random mutation but using memory proportional to file size. Choose based on access pattern and file size.

xml
<!-- SAX: event-driven, streaming, low memory
     DOM:  full tree in memory, random access

     SAX is best for:
       - large files that won't fit in memory
       - read-only filtering / extraction
       - simple state machines over the document

     DOM is best for:
       - random access to many parts of the tree
       - repeated queries / XPath
       - editing and re-serializing -->

<!-- Memory: SAX ~ O(1); DOM ~ O(file size) -->
<!-- Speed:  SAX often faster for one-pass scans -->

Error Handling (SAX)

An ErrorHandler receives warnings, recoverable errors, and fatal errors. By default, fatal errors raise an exception and stop parsing. Implementing error() lets you log non-fatal validation issues without aborting the whole parse.

xml
import xml.sax

class Handler(xml.sax.ContentHandler):
    pass

# ErrorHandler: warning, error, fatalError
class ErrHandler(xml.sax.ErrorHandler):
    def error(self, exception):
        print("ERROR:", exception)
    def fatalError(self, exception):
        print("FATAL:", exception)
        raise exception
    def warning(self, exception):
        print("WARN:", exception)

parser = xml.sax.make_parser()
parser.setContentHandler(Handler())
parser.setErrorHandler(ErrHandler())
parser.parse("data.xml")
12

StAX (Pull Parsing)

StAX Cursor API

StAX is a pull parser: your code pulls the next event with next(), so you control the loop. The cursor API (XMLStreamReader) moves through events one at a time. It is faster and lower-memory than DOM, and more flexible than SAX's push model.

xml
import javax.xml.stream.*;
import java.io.*;

XMLInputFactory factory = XMLInputFactory.newInstance();
// defend against XXE
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

XMLStreamReader r = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (r.hasNext()) {
    int event = r.next();
    if (event == XMLStreamConstants.START_ELEMENT) {
        System.out.println("start: " + r.getLocalName());
    } else if (event == XMLStreamConstants.END_ELEMENT) {
        System.out.println("end: " + r.getLocalName());
    }
}
r.close();

XMLStreamReader Events

Event types include START_ELEMENT, END_ELEMENT, CHARACTERS, COMMENT, PROCESSING_INSTRUCTION, and START/END_DOCUMENT. getText() returns the current text; isWhiteSpace() filters indentation. The cursor advances one event per next() call.

xml
XMLStreamReader r = factory.createXMLStreamReader(in);
while (r.hasNext()) {
    switch (r.next()) {
        case XMLStreamConstants.START_ELEMENT:
            // r.getLocalName(), r.getAttributeValue(i), r.getNamespaceURI()
            break;
        case XMLStreamConstants.END_ELEMENT:
            break;
        case XMLStreamConstants.CHARACTERS:
            if (!r.isWhiteSpace()) {
                String text = r.getText();
            }
            break;
        case XMLStreamConstants.START_DOCUMENT:
            break;
        case XMLStreamConstants.END_DOCUMENT:
            break;
        case XMLStreamConstants.PROCESSING_INSTRUCTION:
            break;
    }
}

Reading Elements & Attributes

getAttributeValue(namespaceURI, localName) reads attributes by name (pass null for no namespace). getElementText() is a convenience that reads the text of a text-only element and advances past its END_ELEMENT—cleaner than buffering characters yourself.

xml
while (r.hasNext()) {
    int event = r.next();
    if (event == XMLStreamConstants.START_ELEMENT
            && "book".equals(r.getLocalName())) {
        String id = r.getAttributeValue(null, "id");
        String category = r.getAttributeValue(null, "category");
        System.out.println("Book " + id + " (" + category + ")");
    }
    if (event == XMLStreamConstants.START_ELEMENT
            && "title".equals(r.getLocalName())) {
        String title = r.getElementText();   // consumes until END_ELEMENT
        System.out.println("Title: " + title);
    }
}

StAX Writer

XMLStreamWriter builds XML sequentially. writeStartDocument, writeStartElement/writeEndElement, writeAttribute, writeCharacters, and writeNamespace must be balanced. Flush/close at the end to ensure the output is complete.

xml
XMLOutputFactory out = XMLOutputFactory.newInstance();
XMLStreamWriter w = out.createXMLStreamWriter(System.out, "UTF-8");

w.writeStartDocument("UTF-8", "1.0");
w.writeStartElement("books");
w.writeNamespace("bk", "https://example.com/books");

w.writeStartElement("bk", "book", "https://example.com/books");
w.writeAttribute("id", "b1");
w.writeStartElement("bk", "title", "https://example.com/books");
w.writeCharacters("XML Guide");
w.writeEndElement();   // title
w.writeEndElement();   // book

w.writeEndElement();   // books
w.writeEndDocument();
w.close();

StAX vs SAX

SAX pushes events to your handler; StAX lets you pull events on demand. StAX's pull model makes it easier to skip subtrees and manage state, and it adds a writer API. Both avoid building a full DOM tree, so they suit large documents.

xml
<!-- StAX: PULL model  (client calls next())
     SAX:  PUSH model  (parser calls your handlers)

     StAX advantages:
       - client controls parsing (easy to skip sections)
       - simpler state management for many tasks
       - supports both reading and writing
       - can pause/resume naturally

     SAX advantages:
       - slightly lower overhead in pure streaming
       - mature, widely available
       - filters/chains via XMLFilter

     Both are streaming and memory-efficient. -->

Event Reader (Iterator API)

The iterator API (XMLEventReader) returns XMLEvent objects you can peek at and consume. It is more object-oriented than the cursor API and easier for filtering or splitting, but slightly heavier due to event object allocation.

xml
import javax.xml.stream.*;
import javax.xml.stream.events.*;

XMLEventReader reader = XMLInputFactory.newInstance()
        .createXMLEventReader(in);

while (reader.hasNext()) {
    XMLEvent e = reader.peek();              // look without consuming
    if (e.isStartElement()) {
        StartElement se = e.asStartElement();
        System.out.println(se.getName());
    }
    XMLEvent consumed = reader.nextEvent();   // consume it
    if (consumed.isEndElement()) {
        System.out.println("closed");
    }
}
reader.close();
13

XML & JSON Conversion

XML to JSON (JavaScript)

fast-xml-parser converts XML to JSON in Node.js. Attributes are prefixed (default @_), and repeated elements become arrays. The library is pure JavaScript and fast, with options to control arrays, numbers, and booleans.

xml
// Node.js with fast-xml-parser
const { XMLParser } = require("fast-xml-parser");

const xml = "<book id='b1'><title>XML</title><price>29.99</price></book>";
const parser = new XMLParser({
  ignoreAttributes: false,
  attributeNamePrefix: "@_",
});

const json = parser.parse(xml);
console.log(json.book["@_id"]);   // b1
console.log(json.book.title);     // XML
console.log(json.book.price);     // 29.99

JSON to XML (JavaScript)

XMLBuilder reverses the conversion. The @_ prefix marks attributes, and nested objects become child elements. Arrays produce repeated elements. XML's single-root rule means the top-level JSON object must map to one root element.

xml
const { XMLBuilder } = require("fast-xml-parser");

const json = {
  book: {
    "@_id": "b1",
    title: "XML Guide",
    price: 29.99,
  },
};

const builder = new XMLBuilder({
  format: true,
  attributeNamePrefix: "@_",
  ignoreAttributes: false,
});

const xml = builder.build(json);
console.log(xml);
// <book id="b1">
//   <title>XML Guide</title>
//   <price>29.99</price>
// </book>

Python xmltodict

xmltodict treats XML like dict/list structures: attributes use '@name', text uses '#text'. parse() reads XML, unparse() writes it. It is great for quick round-trips but loses ordering and some edge cases for complex schemas.

xml
import xmltodict, json

xml = """<book id="b1"><title>XML</title><price>29.99</price></book>"""

# XML -> dict
data = xmltodict.parse(xml)
print(data["book"]["@id"])      # b1
print(data["book"]["title"])    # XML

# dict -> JSON
print(json.dumps(data, indent=2))

# dict -> XML
xml_out = xmltodict.unparse({"book": {"@id": "b2", "title": "New"}})
print(xml_out)

Attribute & Text Handling

Converting XML to JSON forces decisions about attributes (@_prefix), text (#text), and mixed content. Mixed content—text interleaved with child elements—does not map cleanly to JSON and often loses fidelity. Pick conventions and stay consistent.

xml
<!-- XML with mixed attributes and text -->
<product sku="A1" stock="5">Widget<note>sale</note></product>

// fast-xml-parser output:
// {
//   product: {
//     "@_sku": "A1",
//     "@_stock": "5",
//     "#text": "Widget",
//     note: "sale"
//   }
// }

// Round-trip carefully: mixed content (text + elements)
// is hard to represent losslessly in JSON -->

Conversion Challenges

Common conversion pitfalls: single vs. multiple children (array vs. string), element order, namespace prefixes (which are aliases), the single-root constraint, and primitive typing (XML has no native numbers or booleans). Document your conventions.

xml
<!-- 1. Repeated elements -> array -->
<list><item>a</item><item>b</item></list>
// { list: { item: ["a", "b"] } }   (or single string if only one)

<!-- 2. Order is significant in XML but not in JSON objects -->
<a><x/><y/></a>   vs   <a><y/><x/></a>   // may differ

<!-- 3. Namespaces -->
<ns:elem xmlns:ns="http://x"/>   // prefixes are aliases

<!-- 4. Single root: JSON top-level must be one object -->
<!-- 5. Numbers/booleans: XML is all text; typing is heuristic -->

Round-trip Example

Round-tripping (XML -> JSON -> XML) works for well-structured data but can drop comments, processing instructions, CDATA boundaries, and exact whitespace. For lossless editing, use a DOM/tree API instead of JSON conversion.

xml
// Parse, modify, and re-serialize with fast-xml-parser
const { XMLParser, XMLBuilder } = require("fast-xml-parser");

const opts = { ignoreAttributes: false, attributeNamePrefix: "@_" };
const parser = new XMLParser(opts);
const builder = new XMLBuilder({ ...opts, format: true });

const obj = parser.parse(xmlString);
// mutate
obj.book.price = 19.99;
obj.book["@_onSale"] = "true";

const newXml = builder.build(obj);
console.log(newXml);
14

RSS & Atom Feeds

RSS 2.0 Structure

RSS 2.0 has a <rss> root with one <channel>. Required channel fields: title, link, description. Each <item> has title, link, description, guid, pubDate. Dates follow RFC 822 (e.g., 'Wed, 02 Oct 2024 13:00:00 GMT').

xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>My Blog</title>
    <link>https://example.com</link>
    <description>Recent posts</description>
    <language>en-us</language>
    <pubDate>Wed, 02 Oct 2024 13:00:00 GMT</pubDate>
    <item>
      <title>First Post</title>
      <link>https://example.com/1</link>
      <description>A short summary</description>
      <pubDate>Wed, 02 Oct 2024 12:00:00 GMT</pubDate>
      <guid>https://example.com/1</guid>
    </item>
  </channel>
</rss>

RSS Channel & Item

Channel-level fields describe the feed (title, link, description, language, ttl, image). Item fields describe each entry; enclosure supports podcasts/media with a URL, byte length, and MIME type. category can carry a domain attribute.

xml
<channel>
  <title>Site Title</title>
  <link>https://example.com</link>
  <description>What the feed is about</description>
  <category>Technology</category>
  <ttl>60</ttl>                          <!-- cache minutes -->
  <image>
    <url>https://example.com/logo.png</url>
    <title>Site Title</title>
    <link>https://example.com</link>
  </image>
  <item>
    <title>Post Title</title>
    <link>https://example.com/post</link>
    <description>Summary text</description>
    <author>[email protected]</author>
    <category domain="https://example.com/tags">xml</category>
    <enclosure url="https://example.com/p.mp3" length="12345"
               type="audio/mpeg"/>
  </item>
</channel>

Atom Feed

Atom (RFC 4287) uses a <feed> root in the Atom namespace. Required: id, title, updated. Entries need id, title, updated. IDs should be globally unique (URNs or tag: URIs). Dates use RFC 3339 (ISO 8601) with a timezone.

xml
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Example Feed</title>
  <link href="https://example.com/feed.atom" rel="self"/>
  <link href="https://example.com"/>
  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
  <updated>2024-10-02T13:00:00Z</updated>
  <author><name>Jane Doe</name></author>
  <entry>
    <title>Atom Entry</title>
    <id>tag:example.com,2024:/post/1</id>
    <updated>2024-10-02T12:00:00Z</updated>
    <summary>A short summary</summary>
    <content type="html">&lt;p&gt;Full post body&lt;/p&gt;</content>
  </entry>
</feed>

Atom Entry Details

Atom entries support rich content: text, html, or xhtml via the type attribute. link uses rel attributes (alternate, self, enclosure, related). category has term/scheme/label. published is the original date; updated is the last change.

xml
<entry>
  <title type="text">Post Title</title>
  <id>tag:example.com,2024:/posts/42</id>
  <updated>2024-10-02T12:00:00Z</updated>
  <published>2024-10-01T09:00:00Z</published>
  <author>
    <name>Jane Doe</name>
    <email>[email protected]</email>
    <uri>https://example.com/jane</uri>
  </author>
  <link href="https://example.com/posts/42" rel="alternate"/>
  <category term="xml" scheme="https://example.com/tags" label="XML"/>
  <summary type="text">A teaser.</summary>
  <content type="xhtml">
    <div xmlns="http://www.w3.org/1999/xhtml"><p>Body</p></div>
  </content>
</entry>

Common Feed Namespaces

RSS is often extended with modules: Dublin Core (dc:creator, dc:date), content (content:encoded for full HTML), Media RSS (media:content), and the atom:link rel='self' that helps readers find the feed URL. Namespaces let feeds mix vocabularies.

xml
<?xml version="1.0"?>
<rss version="2.0"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:media="http://search.yahoo.com/mrss/"
     xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Feed</title>
    <link>https://example.com</link>
    <description>desc</description>
    <atom:link href="https://example.com/feed.xml" rel="self"
               type="application/rss+xml"/>
    <item>
      <title>Post</title>
      <dc:creator>Jane Doe</dc:creator>
      <content:encoded><![CDATA[<p>HTML body</p>]]></content:encoded>
      <media:content url="https://example.com/img.png" medium="image"/>
    </item>
  </channel>
</rss>

Feed Validation

Use the W3C Feed Validator to check RSS/Atom. Common errors: missing required fields, bad date formats, non-unique IDs, invalid XML (unescaped &), and missing self link. Valid feeds ensure aggregators parse them reliably.

xml
<!-- RSS: dates must be RFC 822, e.g. -->
<pubDate>Wed, 02 Oct 2024 13:00:00 GMT</pubDate>

<!-- Atom: dates must be RFC 3339, e.g. -->
<updated>2024-10-02T13:00:00Z</updated>

<!-- Validate online with https://validator.w3.org/feed/ -->
<!-- Required RSS channel fields: title, link, description -->
<!-- Required Atom feed fields: id, title, updated -->
<!-- GUIDs/IDs should be unique and permanent -->
15

SVG & XML

SVG Document Structure

SVG is an XML vocabulary for vector graphics. The root <svg> declares the SVG namespace and a viewBox for the coordinate system. All graphic elements (rect, circle, text) are XML elements with presentation attributes.

xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="200" height="200" viewBox="0 0 200 200">
  <rect x="10" y="10" width="180" height="180"
        fill="#005FAD" stroke="black" stroke-width="2"/>
  <circle cx="100" cy="100" r="40" fill="white"/>
  <text x="100" y="105" text-anchor="middle">SVG</text>
</svg>
<!-- SVG is an XML application; it must be well-formed -->

SVG Namespace & Imports

SVG elements live in the http://www.w3.org/2000/svg namespace. xlink is used for href references (in SVG 1.1); SVG 2 prefers plain href. defs hold reusable gradients, patterns, and symbols referenced by id via url(#id).

xml
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="1.1">
  <defs>
    <linearGradient id="g1">
      <stop offset="0%" stop-color="red"/>
      <stop offset="100%" stop-color="blue"/>
    </linearGradient>
  </defs>
  <rect width="100" height="100" fill="url(#g1)"/>
</svg>

SVG Shapes

SVG shape elements: rect (with rx/ry for rounded corners), circle, ellipse, line, polyline (open), and polygon (closed). Coordinates are in the user units defined by viewBox. stroke and fill are common presentation attributes.

xml
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
  <rect x="10" y="10" width="80" height="50" rx="10" fill="teal"/>
  <circle cx="150" cy="60" r="40" fill="orange"/>
  <ellipse cx="220" cy="60" rx="50" ry="20" fill="purple"/>
  <line x1="10" y1="120" x2="290" y2="120" stroke="black"/>
  <polyline points="10,180 50,140 90,180 130,140" fill="none" stroke="red"/>
  <polygon points="200,180 250,140 280,200" fill="green"/>
</svg>

SVG Text & Paths

SVG text uses font-family, font-size, font-weight, font-style. tspan allows mixed styling within a text element. The path element's 'd' attribute uses commands: M (move), L (line), C (cubic Bezier), Q (quadratic), A (arc), Z (close path).

xml
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="150">
  <text x="10" y="40" font-family="Arial" font-size="24"
        font-weight="bold" fill="navy">Hello SVG</text>

  <text x="10" y="80">
    <tspan font-size="16">Normal</tspan>
    <tspan font-size="16" font-style="italic"> italic</tspan>
  </text>

  <path d="M10 100 L 100 100 L 55 140 Z"
        fill="none" stroke="black" stroke-width="2"/>
</svg>
<!-- Path commands: M=move, L=line, H/V, C=cubic, Q=quad, A=arc, Z=close -->

SVG Grouping & Reuse

The <g> element groups shapes for shared styling and transforms. <use> references an element by id (href='#id' in SVG 2, xlink:href in 1.1) and clones it at a new position. <symbol> defines reusable graphics that render only via <use>.

xml
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <g id="node" stroke="black" fill="lightblue">
    <circle cx="50" cy="50" r="20"/>
    <circle cx="100" cy="50" r="20"/>
  </g>

  <use href="#node" x="0" y="100"/>
  <use href="#node" x="0" y="150" opacity="0.5"/>

  <symbol id="star" viewBox="0 0 24 24">
    <polygon points="12,2 15,9 22,9 17,14 19,21 12,17 5,21 7,14 2,9 9,9"/>
  </symbol>
  <use href="#star" width="24" height="24"/>
</svg>

SVG Validation

SVG documents can be validated against the W3C DTD or XSD. Common issues: forgetting the SVG namespace, undeclared prefixes, attributes in the wrong namespace, and malformed path data. xmllint is the standard command-line validator.

xml
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect width="100" height="100"/>
</svg>

<!-- Validate with xmllint: -->
<!-- xmllint --noout --valid file.svg        (DTD) -->
<!-- xmllint --noout --schema svg11.xsd file.svg   (XSD) -->

<!-- Common errors: wrong namespace, undeclared prefixes,
     text outside a text element, invalid path data -->
16

SOAP & WSDL

SOAP Envelope

A SOAP message is an XML document with an <Envelope> root in the SOAP namespace. The optional <Header> carries metadata (auth, routing); the required <Body> holds the actual message payload. SOAP 1.1 and 1.2 use different namespaces.

xml
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <auth xmlns="https://example.com/auth">
      <token>abc123</token>
    </auth>
  </soap:Header>
  <soap:Body>
    <m:GetPrice xmlns:m="https://example.com/pricing">
      <m:Item>Apples</m:Item>
    </m:GetPrice>
  </soap:Body>
</soap:Envelope>
<!-- Envelope is the root; Header is optional, Body is required -->

SOAP Header

Headers carry out-of-band info like authentication, addressing (WS-Addressing), and transactions. soap:mustUnderstand='1' tells the receiver it MUST process this header or return a SOAP fault—this enforces contract compliance.

xml
<soap:Header xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <wsa:Action xmlns:wsa="http://www.w3.org/2005/08/addressing">
    http://example.com/GetPrice
  </wsa:Action>
  <wsa:MessageID>urn:uuid:1234</wsa:MessageID>
  <auth xmlns="https://example.com/auth"
        soap:mustUnderstand="1">
    <token>secret</token>
  </auth>
</soap:Header>
<!-- mustUnderstand="1" forces the receiver to process the header -->

SOAP Body & Fault

The Body carries the request or response payload. On error, the Body contains a <Fault> with faultcode (Client/Server), a human-readable faultstring, and optional detail. SOAP 1.2 renames these elements and adds a Code/Reason structure.

xml
<!-- Request body -->
<soap:Body>
  <m:GetPrice xmlns:m="https://example.com/pricing">
    <m:Item>Apples</m:Item>
  </m:GetPrice>
</soap:Body>

<!-- Fault (error response) -->
<soap:Body>
  <soap:Fault>
    <faultcode>soap:Client</faultcode>
    <faultstring>Unknown item: Apples</faultstring>
    <detail>
      <m:Error xmlns:m="https://example.com/pricing">
        <code>404</code>
      </m:Error>
    </detail>
  </soap:Fault>
</soap:Body>

WSDL Structure

WSDL describes a web service: <types> (XSD schemas), <message> (input/output payloads), <portType> (the interface—operations and their messages), <binding> (protocol/encoding), and <service> (endpoint URL). WSDL 2.0 simplifies this to interfaces and bindings.

xml
<?xml version="1.0"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:xs="http://www.w3.org/2001/XMLSchema"
             targetNamespace="https://example.com/pricing">
  <types>
    <xs:schema targetNamespace="https://example.com/pricing">
      <xs:element name="GetPriceRequest" type="xs:string"/>
      <xs:element name="GetPriceResponse" type="xs:decimal"/>
    </xs:schema>
  </types>
  <message name="GetPriceInput">
    <part name="item" element="tns:GetPriceRequest"/>
  </message>
  <portType name="PricingPort">
    <operation name="GetPrice">
      <input message="tns:GetPriceInput"/>
      <output message="tns:GetPriceOutput"/>
    </operation>
  </portType>
</definitions>

WSDL Binding & Service

Binding maps a portType to a concrete protocol (SOAP over HTTP) and style (document/literal is the modern default). The service element lists endpoints (soap:address). Document/literal wraps the payload in a schema-defined element.

xml
<binding name="PricingSOAP" type="tns:PricingPort">
  <soap:binding style="document"
                transport="http://schemas.xmlsoap.org/soap/http"/>
  <operation name="GetPrice">
    <soap:operation soapAction="http://example.com/GetPrice"/>
    <input><soap:body use="literal"/></input>
    <output><soap:body use="literal"/></output>
  </operation>
</binding>

<service name="PricingService">
  <port name="PricingPort" binding="tns:PricingSOAP">
    <soap:address location="https://example.com/pricing"/>
  </port>
</service>
<!-- style: rpc or document; use: encoded or literal -->

SOAP vs REST

SOAP is a protocol with a strict XML envelope, WSDL contract, and WS-* standards (security, transactions); best for enterprise integrations needing reliability and formal contracts. REST is a lightweight HTTP-based style, usually with JSON, that dominates public APIs.

xml
<!-- SOAP: protocol with envelope, strict contract (WSDL),
     built-in error model (Fault), WS-* extensions.
     Heavier, XML-only, common in enterprise/B2B. -->

<!-- REST: architectural style using HTTP verbs (GET/POST/...),
     flexible payloads (often JSON), no built-in contract.
     Lighter, browser-friendly, dominant on the web. -->

<!-- When to use SOAP:
     - you need strict contracts and tooling
     - transactional reliability (WS-ReliableMessaging)
     - standardized security (WS-Security)
     - existing SOAP-based infrastructure -->

<!-- When to use REST:
     - public APIs, mobile/web clients
     - JSON payloads, HTTP caching
     - simpler, faster iteration -->
17

XML Digital Signature

Signature Structure

An XML Signature's root <Signature> contains <SignedInfo> (what is signed and how), <SignatureValue> (the cryptographic signature over canonicalized SignedInfo), optional <KeyInfo>, and <Object>s holding referenced data.

xml
<?xml version="1.0"?>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    <CanonicalizationMethod Algorithm="..."/>
    <SignatureMethod Algorithm="..."/>
    <Reference URI="#data">
      <DigestMethod Algorithm="..."/>
      <DigestValue>...</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>...</SignatureValue>
  <KeyInfo>...</KeyInfo>
  <Object Id="data">signed content</Object>
</Signature>
<!-- A signature = SignedInfo + SignatureValue + optional KeyInfo -->

SignedInfo

SignedInfo specifies the canonicalization (so byte-identical signing survives XML re-serialization), the signature algorithm, and one or more References. Each Reference points to data via URI, lists Transforms, and gives a DigestValue of that data.

xml
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <CanonicalizationMethod
      Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
  <SignatureMethod
      Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
  <Reference URI="">
    <Transforms>
      <Transform Algorithm="...enveloped-signature"/>
    </Transforms>
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    <DigestValue>base64digest==</DigestValue>
  </Reference>
</SignedInfo>

Reference & Transforms

Each Reference identifies what to sign (URI), applies Transforms (canonicalization, XPath filtering, enveloped-signature removal), then hashes with DigestMethod. The enveloped-signature transform excludes the Signature element itself from its own hash.

xml
<Reference URI="#order">
  <Transforms>
    <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
    <XPath xmlns="...">not(ancestor-or-self::ds:Signature)</XPath>
  </Transforms>
  <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
  <DigestValue>abc123base64==</DigestValue>
</Reference>

<!-- Reference types:
     - same-document (URI="#id")
     - external (URI="https://...")
     - detached (URI pointing elsewhere) -->

SignatureValue & KeyInfo

SignatureValue is the base64 result of signing canonicalized SignedInfo with the signer's private key. KeyInfo is OPTIONAL and only hints how to find the verification key (X.509 cert, RSA public key, key name, or retrieval URL)—it must be validated out of band.

xml
<SignatureValue>base64encodedSignature==</SignatureValue>

<KeyInfo>
  <X509Data>
    <X509SubjectName>CN=Example,O=Example Inc</X509SubjectName>
    <X509Certificate>MIID...base64...==</X509Certificate>
  </X509Data>
</KeyInfo>

<!-- Other KeyInfo options: -->
<!--   <KeyName>key-id</KeyName> -->
<!--   <KeyValue><RSAKeyValue>...</RSAKeyValue></KeyValue> -->
<!--   <RetrievalMethod URI="..."/> -->
<!-- KeyInfo is optional and unverified by itself -->

Canonicalization

Canonicalization converts an XML node set into stable bytes so a signature is reproducible despite irrelevant differences (attribute order, CDATA). Exclusive c14n (xml-exc-c14n) is preferred for signatures because it scopes namespaces to the signed subtree.

xml
<!-- Two documents that are logically identical may differ in bytes:
     - attribute order
     - namespace declarations
     - CDATA vs escaping
     - whitespace outside elements
     Canonical XML (c14n) produces a canonical byte form. -->

<CanonicalizationMethod
    Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>

<!-- Exclusive c14n (recommended for signatures): -->
<CanonicalizationMethod
    Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>

<!-- Exclusive form avoids leaking inherited namespace declarations
     into the signed digest, preventing wrapping attacks. -->

Enveloped Signature

An enveloped signature is embedded inside the document it signs (most common for XML). The enveloped-signature transform removes the <Signature> element before hashing, so the signature is not part of its own digest. Detached and enveloping variants also exist.

xml
<?xml version="1.0"?>
<order Id="order">
  <item>Widget</item>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="...exc-c14n#"/>
      <SignatureMethod Algorithm="...rsa-sha256"/>
      <Reference URI="#order">
        <Transforms>
          <Transform Algorithm="...enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="...sha256"/>
        <DigestValue>...</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>...</SignatureValue>
  </Signature>
</order>
<!-- The signature lives INSIDE the signed element -->
19

Naming Conventions

Element Naming

Pick a style and apply it consistently. Lowercase-with-hyphens is common for XML vocabularies (HTML, SVG). Names should be descriptive enough to be unambiguous but concise. Avoid cryptic abbreviations and overly verbose names.

xml
<!-- Good: lowercase, hyphen-separated, descriptive -->
<customer-address>
  <street>123 Main St</street>
  <postal-code>12345</postal-code>
</customer-address>

<!-- Avoid: abbreviations that are unclear -->
<!-- <cust-addr><pc>...</pc></cust-addr> -->

<!-- Avoid: too long -->
<!-- <customerMailingPostalAddressCode> -->

<!-- Be consistent across the whole vocabulary -->

Attribute Naming

Attributes suit metadata (id, type, href, status). Match the element naming style. Booleans carry explicit values. Don't prefix an attribute with its element name (user-id on <user> is redundant). Use 'id' for unique identifiers.

xml
<product sku="A1" unit-price="9.99" in-stock="true">
  <name>Widget</name>
</product>

<!-- Conventions:
     - attributes are metadata: id, type, href, status
     - use lowercase-hyphen style to match elements
     - boolean attributes still carry a value ("true"/"false")
     - avoid repeating the element name: <user user-id="..."/>
       prefer <user id="..."/> -->

<!-- IDs: use 'id' or '<element>-id' for clarity -->

Namespace URIs & Prefixes

Namespace URIs are identifiers, not fetchable URLs, but should be stable and owned (use HTTPS at a domain you control). Prefixes are local aliases—keep them short and conventional. Version the URI when the schema changes incompatibly.

xml
<!-- Namespace URIs should be stable, owned, and permanent -->
xmlns:bk  = "https://example.com/ns/books/2024"
xmlns:auth= "https://example.com/ns/auth"

<!-- Prefix conventions:
     - short and mnemonic: xs, xsi, xsl, svg, html, dc
     - lowercase, no punctuation beyond letters
     - 'tns' commonly means 'this namespace' in XSD
     - avoid 'xml' (reserved) -->

<!-- Version the URI when the vocabulary changes incompatibly:
     .../ns/books/2024  vs  .../ns/books/2025 -->

Case Styles

Common XML case styles: PascalCase (XSD/WSDL, .NET), camelCase (JSON-derived, Java), lowercase-hyphens (HTML/SVG, REST-friendly), snake_case (rare). The crucial rule is consistency within a vocabulary—mixing styles is confusing.

xml
<!-- PascalCase (UpperCamelCase): common in XSD/WSDL, .NET -->
<OrderRequest>
  <CustomerId>123</CustomerId>
</OrderRequest>

<!-- camelCase: common in JSON-derived XML, Java ecosystems -->
<orderRequest>
  <customerId>123</customerId>
</orderRequest>

<!-- lowercase-with-hyphens: HTML5, SVG, REST-friendly -->
<order-request>
  <customer-id>123</customer-id>
</order-request>

<!-- snake_case: rare in XML, used in some Python/SQL ecosystems -->
<order_request><customer_id>123</customer_id></order_request>

Versioning

Version XML vocabularies explicitly: a version attribute on the root is simplest; a versioned namespace URI is most rigorous (incompatible changes get a new URI). Decide what counts as a minor (additive) vs major (breaking) change and document it.

xml
<!-- Versioning strategies -->

<!-- 1. Attribute on the root -->
<catalog version="2.0">...</catalog>

<!-- 2. Versioned namespace URI -->
<catalog xmlns="https://example.com/ns/catalog/v2">...</catalog>

<!-- 3. Versioned root element name -->
<catalog-v2>...</catalog-v2>

<!-- Backward-compatible changes: add optional elements/attributes
     Incompatible changes: bump the version (and/or namespace) -->

<!-- Document the policy: which changes are "minor" vs "major" -->

File & Extension Conventions

Use recognized extensions so tools and humans identify file types. The XML declaration's encoding attribute should match the actual byte encoding; UTF-8 is the safe default. BOMs are discouraged for UTF-8 XML.

xml
<!-- Common file extensions:
     .xml   generic XML
     .xsd   XML Schema
     .xsl   XSLT stylesheet
     .xslt  XSLT (alternate)
     .dtd   Document Type Definition
     .rss   RSS feed
     .atom  Atom feed
     .svg   SVG graphic
     .wsdl  WSDL service description
     .xhtml XHTML document
     .plist Apple property list -->

<!-- XML files are UTF-8 by default; declare encoding if different:
     <?xml version="1.0" encoding="UTF-8"?> -->
20

Best Practices

Well-formedness First

A document must be well-formed before any tool will process it. Validate with a parser early: xmllint --noout file.xml reports the first error. Well-formedness is non-negotiable; validity against a schema is an additional, optional layer.

xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <child>properly nested &amp; escaped</child>
</root>

<!-- Always ensure well-formedness before worrying about validity:
     - exactly one root element
     - all tags closed and properly nested
     - attributes quoted and unique per element
     - & < escaped (and > in some contexts)
     - valid encoding declaration -->

Validation & Schemas

Validate against a schema to catch structural errors. XSD is the most common choice for typed, namespaced vocabularies; Schematron complements it with business rules. xsi:noNamespaceSchemaLocation links a doc to its XSD; use xsi:schemaLocation for namespaced schemas.

xml
<!-- Choose a schema language:
     - DTD:      simple, built-in, limited types
     - XSD:      rich types, namespaces, industry standard
     - RelaxNG:  compact syntax, flexible patterns
     - Schematron: rule-based business constraints -->

<?xml version="1.0"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="schema.xsd">
  <item>valid</item>
</root>

<!-- Validate during development; consider it at runtime for
     untrusted input. -->

Encoding (UTF-8)

UTF-8 is the default and recommended encoding for XML—it handles all Unicode without entities. Always declare the encoding and save the file in that encoding. Avoid BOMs; for special characters you can also use numeric character references.

xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <msg>Café — résumé — 中文 — emoji 🙂</msg>
</root>

<!-- Always declare UTF-8 and save the file as UTF-8.
     Avoid legacy encodings (ISO-8859-1) unless required.
     Do not add a UTF-8 BOM (some parsers mishandle it).
     For non-UTF-8 encodings, declare them accurately. -->

Readability & Indentation

Consistent indentation (2 spaces is common) makes XML readable and diffs clean. Avoid long single lines. Tools like 'xmllint --format' or an XML formatter can normalize whitespace—but be aware that xml:space='preserve' regions must not be reformatted.

xml
<?xml version="1.0"?>
<catalog>
  <book id="b1">
    <title>XML Guide</title>
    <author>Jane Doe</author>
  </book>
  <book id="b2">
    <title>Advanced XML</title>
    <author>John Smith</author>
  </book>
</catalog>

<!-- Use 2-space indentation; one element per line for mixed
     or deep structures. Tools: xmllint --format, prettier. -->

Performance

For large files, prefer streaming parsers (SAX/StAX) over DOM to keep memory low. Disable DTD loading and external entities unless needed. Compile XPath expressions once and reuse them. For internal systems, consider compact binary formats to cut size and parse time.

xml
<!-- Parsing large XML efficiently:
     - use SAX/StAX (streaming) instead of DOM for big files
     - disable DTD/external entities if you don't need them
     - validate once, not on every read
     - reuse parser instances (factory creation is costly)
     - consider binary formats (protobuf, CBOR) for internal traffic -->

<!-- Java StAX with XXE disabled:
     factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); -->

<!-- For repeated XPath, compile once and reuse the expression -->

Security (XXE)

XML External Entity (XXE) attacks abuse DTD external entities to read files, trigger SSRF, or cause denial of service (billion laughs/entity expansion). The strongest defense is disabling DTDs and external entity resolution entirely unless your use case requires them.

xml
<?xml version="1.0"?>
<!DOCTYPE root [
  <!-- XXE: attacker forces the parser to read a local file -->
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>

<!-- Defenses (do ALL that apply):
     1. Disable DTDs entirely if you don't need them.
     2. Disable external entity resolution.
     3. Disable XInclude.
     4. Validate input; limit entity expansion (billion laughs). -->

<!-- Python: defusedxml
     Java:  XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES = false
     PHP:   libxml_disable_entity_loader(true) -->

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.