XML (eXtensible Markup Language) è un formato di testo per archiviare e trasmettere dati strutturati sotto forma di albero. Su di esso si basano i feed di prodotto di marketplace e aggregatori, i formati bancari e di interscambio, i file di configurazione e anche i suoi formati derivati: i feed RSS e le sitemap. Questo articolo fa parte della nostra panoramica generale sul parsing di documenti, con il focus proprio su XML.
Come è fatto un XML
Un documento XML è un albero di elementi. Ogni elemento ha un nome (il tag) e può avere un insieme di attributi, un contenuto testuale ed elementi annidati.
<catalog>
<product id="101" available="true">
<name>Macinacaffè</name>
<price currency="EUR">34.90</price>
</product>
<product id="102" available="false">
<name>Bollitore</name>
<price currency="EUR">21.90</price>
</product>
</catalog>La differenza chiave rispetto a JSON è che in XML i dati vivono in due posti contemporaneamente: nel testo degli elementi (<name>Macinacaffè</name>) e negli attributi (id="101"). Va tenuto a mente: in fase di estrazione, a volte interroghi il contenuto del tag, a volte i suoi attributi.
Un tema a parte sono i namespace (spazi dei nomi). Nei formati complessi i tag appaiono come <g:price> o <atom:link>, dove il prefisso è legato a un URI. I parser esigono che il namespace venga considerato nella ricerca dei nodi, altrimenti la selezione restituisce il vuoto — è la causa più frequente del «il parser non trova nulla, anche se i dati sono lì».
Due approcci: albero in memoria e lettura in streaming
Esistono due modi radicalmente diversi di fare il parsing. L’approccio DOM costruisce l’intero albero in memoria e offre una navigazione comoda, anche via XPath: è semplice e va bene per file fino a qualche decina di megabyte. L’approccio in streaming (SAX / iterparse) legge il file per eventi, senza caricarlo per intero, ed è insostituibile con esportazioni da centinaia di megabyte o da gigabyte. Per i compiti tipici basta quasi sempre il DOM; allo streaming si passa quando il file smette di stare in memoria.
Python
La libreria standard include il modulo xml.etree.ElementTree: è sufficiente per i casi semplici e non richiede installazione.
import xml.etree.ElementTree as ET
tree = ET.parse("catalog.xml")
root = tree.getroot()
for product in root.findall("product"):
name = product.find("name").text
price = product.find("price").text
available = product.get("available") # lettura dell'attributo
print(name, price, available)Per il lavoro serio si usa lxml: è più veloce, digerisce meglio l’XML «sporco» e supporta XPath al completo, namespace inclusi.
from lxml import etree
tree = etree.parse("catalog.xml")
# XPath: tutti i prodotti disponibili
for product in tree.xpath("//product[@available='true']"):
name = product.xpath("string(name)")
price = product.xpath("string(price)")
print(name, price)Se i dati sono arrivati da un’API in XML, ma è più comodo trattarli come un dizionario, aiuta la libreria xmltodict, che trasforma l’XML in normali strutture annidate di Python — molto vicino a come elaboreresti un JSON.
JavaScript / Node.js
In Node sono popolari due soluzioni. fast-xml-parser trasforma l’XML in un oggetto senza dipendenze esterne e lavora molto velocemente.
const { XMLParser } = require("fast-xml-parser");
const fs = require("fs");
const xml = fs.readFileSync("catalog.xml", "utf-8");
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@_" });
const data = parser.parse(xml);
for (const product of data.catalog.product) {
console.log(product.name, product.price["#text"], product["@_available"]);
}L’alternativa è xml2js, veterana dell’ecosistema e comoda per l’elaborazione asincrona. Nel browser è disponibile il DOMParser integrato, che permette di percorrere l’XML con gli stessi metodi di un documento HTML.
PHP
Per i documenti semplici è perfetto SimpleXML, integrato nel linguaggio: dà accesso agli elementi come se fossero proprietà di un oggetto.
<?php
$xml = simplexml_load_file("catalog.xml");
foreach ($xml->product as $product) {
$name = (string) $product->name;
$price = (string) $product->price;
$available = (string) $product["available"]; // attributo
echo "$name — $price — $available\n";
}Quando servono i namespace, XPath complessi o l’elaborazione in streaming di file grandi, si passa a DOMDocument e XMLReader, inclusi nella stessa dotazione standard di PHP.
Go
La libreria standard di Go include il pacchetto encoding/xml, che trasforma l’XML direttamente in struct tramite i tag dei campi.
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Catalog struct {
Products []struct {
ID string `xml:"id,attr"`
Available string `xml:"available,attr"`
Name string `xml:"name"`
Price string `xml:"price"`
} `xml:"product"`
}
func main() {
data, _ := os.ReadFile("catalog.xml")
var c Catalog
xml.Unmarshal(data, &c)
for _, p := range c.Products {
fmt.Println(p.Name, p.Price, p.Available)
}
}Errori tipici
Il più delle volte i problemi nascono dalla codifica: se la dichiarazione indica ISO-8859-1 (o windows-1252) e il file viene letto come UTF-8, i caratteri si trasformano in spazzatura — la codifica va lasciata al parser oppure convertita in modo esplicito. La seconda causa per frequenza sono i namespace ignorati, per colpa dei quali le query XPath restituiscono in silenzio un risultato vuoto. La terza è il tentativo di caricare per intero nel DOM un file troppo grande: un’esportazione da gigabyte va letta in streaming. E infine, non fare il parsing dell’XML con le espressioni regolari: il formato è annidato e ricorsivo, e le regex si rompono al primo caso fuori standard — per il testo senza struttura sono giustificate (vedi il parsing di TXT), ma non per l’XML.
Se dalla tua parte c’è uno schema di esportazione non standard, namespace rotti o file da gigabyte — configuriamo il parsing di XML su misura per il tuo formato. E se i dati di origine non arrivano in XML puro, ma nei suoi derivati, guarda i materiali affini: RSS e sitemap.