Pourquoi écrivons-nous cet article ? Nous sommes Geonode et nous vendons des proxys à des personnes qui collectent des données, et une grande partie de ces données nous parvient au format XML : plans de site, flux RSS et Atom, réponses SOAP, catalogues de produits. En toute honnêteté, une erreur d’analyse n’est presque jamais due à un problème réseau. Si votre analyseur XML signale une erreur, affichez les 200 premiers caractères de ce que vous avez reçu avant de modifier quoi que ce soit dans votre code. Neuf fois sur dix, il s’agit d’une page d’erreur HTML, et l’analyseur indique à juste titre que vous n’avez pas reçu de données XML.
Le piège de l'erreur : elle n'est pas levée
Le comportement qui prend tout le monde au dépourvu la première fois.
Si vous fournissez un XML mal formé à DOMParser, aucune exception n'est levée. MDN est clair : « l'objet XMLDocument renvoyé contiendra un nœud <parsererror> décrivant l'erreur d'analyse », et l'erreur « peut également être signalée à la console JavaScript du navigateur ».
Vous devez donc effectuer la vérification suivante :
const doc = parser.parseFromString(xmlString, "application/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
throw new Error(`XML parse failed: ${errorNode.textContent}`);
}
Sans cette vérification, un document mal formé produit un objet Document contenant un message d’erreur à la place de vos données. Votre appel querySelectorAll suivant ne renvoie rien, et le symptôme ressemble alors à un problème de sélecteur plutôt qu’à un échec d’analyse.
Enveloppez-le une fois :
function parseXml(text) {
const doc = new DOMParser().parseFromString(text, "application/xml");
const err = doc.querySelector("parsererror");
if (err) {
throw new Error(
`XML parse failed: ${err.textContent.trim()}. ` +
`First 200 chars: ${text.slice(0, 200)}`
);
}
return doc;
}
L’text.slice(0, 200) est la partie qui permet de gagner du temps. Une erreur d’analyse vous indique que l’entrée n’était pas valide ; les 200 premiers caractères vous indiquent qu’il s’agissait d’une page d’erreur HTML.
Dans Node : choisir une bibliothèque
Node ne dispose pas d’analyseur XML intégré ; le choix dépend donc des dépendances. Les chiffres hebdomadaires de téléchargement donnent une idée approximative de l’adoption — ces données proviennent du registre npm, consultées en septembre 2026.
| Bibliothèque | Téléchargements hebdomadaires | Style |
|---|---|---|
sax |
| ~88,9 millions | Streaming, basé sur les événements |
| fast-xml-parser
| ~85,0 millions | Conversion de XML en objets JavaScript simples |
| @xmldom/xmldom
| ~48,7 millions | Implémentation DOM pour Node |
| xml2js
| ~44,5 M | Conversion de XML en objets, API de callback et de promise |
| xpath
| ~12,2 M | Requêtes XPath, s'associe à xmldom |
**fast-xml-parser
** est la solution par défaut la plus pragmatique pour la plupart des cas d'utilisation. Elle convertit le XML en objets JavaScript ordinaires, ce qui signifie que vous naviguez à l'aide de la notation par points plutôt qu'avec les méthodes DOM :
import { XMLParser } from "fast-xml-parser";
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@" });
const obj = parser.parse(xmlString);
console.log(obj.rss.channel.item[0].title);
Il faut toutefois comprendre le compromis suivant : la conversion en objets entraîne une perte d’informations d’une manière bien précise. Un élément qui n’apparaît qu’une seule fois devient un objet ; le même élément apparaissant deux fois devient un tableau. Ainsi, channel.item
est un tableau pour un flux comportant trois éléments et un objet pour un flux n’en comportant qu’un seul — et le code qui s’attend à un tableau ne fonctionnera pas dans le cas d’un flux à élément unique. La plupart des bibliothèques proposent une option permettant de toujours générer des tableaux pour les éléments nommés ; il est judicieux de l’activer pour tout ce que vous comptez parcourir avant que cela ne vous pose problème.
**@xmldom/xmldom
** vous fournit un véritable DOM sous Node, ce qui est important si vous souhaitez que le même code fonctionne dans les deux environnements ou si vous avez besoin d’XPath. Associez-le au package xpath
:
import { DOMParser } from "@xmldom/xmldom";
import xpath from "xpath";
const doc = new DOMParser().parseFromString(xmlString, "text/xml");
const titles = xpath.select("//item/title/text()", doc);
**sax
** est un analyseur en continu qui émet des événements au fur et à mesure de la lecture. C’est la solution idéale pour les documents trop volumineux pour tenir en mémoire — un index de plan de site de plusieurs gigaoctets, une exportation de catalogue en masse — où une approche DOM ne convient tout simplement pas.
**xml2js
** est un outil établi de longue date et largement utilisé. Son API est un peu dépassée mais tout à fait fonctionnelle, et il existe une grande quantité de code existant qui l'utilise.
Les espaces de noms : ce qui perturbe les documents réels
La raison la plus courante pour laquelle un sélecteur qui fonctionnait auparavant ne trouve soudainement plus rien.
De nombreux formats XML réels déclarent des espaces de noms :
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
</urlset>
Cette xmlns
place chaque élément dans un espace de noms par défaut. Dans un analyseur syntaxique prenant en compte les espaces de noms, <loc>
n’est pas simplement loc
— c’est loc
dans l’espace de noms sitemaps, et un simple getElementsByTagName("loc")
risque de ne rien trouver.
Trois façons de gérer cela, par ordre croissant de pertinence.
Utilisez les méthodes prenant en compte les espaces de noms :
const NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
const locs = doc.getElementsByTagNameNS(NS, "loc");
Utilisez un espace de noms générique lorsque le choix de l’espace de noms n’a pas d’importance :
const locs = doc.getElementsByTagNameNS("*", "loc");
Configurez votre bibliothèque pour qu’elle ignore les espaces de noms. La plupart des bibliothèques de mappage d’objets disposent d’une option permettant de supprimer les préfixes d’espace de noms, ce qui produit des clés au format loc
. C’est pratique, mais cela fusionnera silencieusement deux éléments véritablement différents qui partagent par hasard un nom local — ce qui est acceptable pour un plan de site, mais dangereux pour un document mélangeant plusieurs vocabulaires.
Notez que querySelector
se comporte différemment de getElementsByTagNameNS
dans ce cas : les sélecteurs CSS ont leur propre syntaxe d’espace de noms, qui est peu pratique et rarement utilisée ; pour le XML avec espaces de noms, les méthodes NS
ou XPath sont donc plus fiables.
XPath en JavaScript
Disponible dans les navigateurs via document.evaluate, et dans Node via le package xpath avec xmldom.
const result = doc.evaluate(
"//item/title/text()",
doc,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0; i < result.snapshotLength; i++) {
console.log(result.snapshotItem(i).nodeValue);
}
L’API est suffisamment complexe pour que la plupart des utilisateurs l’intègrent une seule fois, puis l’oublient. Ce qui justifie cet effort, c’est que l’XPath permet d’exprimer des choses que les sélecteurs CSS ne peuvent pas : la correspondance sur le contenu textuel, la navigation vers les ancêtres et la logique positionnelle par rapport aux éléments frères.
Deux contraintes. Les navigateurs implémentent XPath 1.0, donc pas de matches(), pas de lower-case(), pas de ends-with(). Et les espaces de noms nécessitent une fonction de résolution — le troisième argument — qui mappe les préfixes aux URI d’espace de noms. Passer null ne fonctionne que pour les documents sans espaces de noms, ce qui exclut la plupart des flux réels.
Pour les documents avec espace de noms dans un navigateur :
const resolver = prefix => ({ sm: "http://www.sitemaps.org/schemas/sitemap/0.9" }[prefix] || null);
const result = doc.evaluate("//sm:loc/text()", doc, resolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
Notez que vous inventez le préfixe — sm ici — quel que soit celui utilisé par le document, car XPath 1.0 ne connaît pas de concept d’espace de noms par défaut.
Conversion de XML en JSON, et ce qui se perd
C'est ce que les gens recherchent le plus souvent, mais il faut bien comprendre que cette conversion n'est pas sans perte.
XML et JSON ont des modèles de données différents. XML comporte des attributs, des éléments, des nœuds de texte, des commentaires, des instructions de traitement, des espaces de noms et un ordre de lecture. JSON comporte des objets, des tableaux, des chaînes de caractères, des nombres, des booléens et la valeur null. Quatre éléments ne survivent pas intacts à cette conversion.
Attributs contre éléments enfants. <item id="1"><name>x</name></item> comporte un attribut et un élément enfant, et JSON ne fait aucune distinction entre les deux. Les bibliothèques gèrent cela en ajoutant un préfixe aux clés d’attributs — généralement @ ou $ — que vous configurez et devez ensuite mémoriser :
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@" });
// { item: { "@id": "1", name: "x" } }
Les éléments répétés sont convertis en tableaux de manière incohérente. Ce point a été abordé plus haut, mais mérite d’être répété car il s’agit du bug le plus courant dans ce domaine : une occurrence donne un objet, deux donnent un tableau. Activez l’option « always array » de la bibliothèque pour chaque élément que vous comptez parcourir.
Le contenu mixte ne dispose d’aucune représentation claire. <p>Hello <b>world</b>!</p> entremêle du texte et des éléments. Une fois convertis en objet, les fragments de texte et leurs positions par rapport à l’élément enfant sont difficiles à représenter, et la plupart des bibliothèques concatènent le texte ou en suppriment des parties. Si votre XML contient du balisage de prose, la conversion en objet n’est pas la bonne approche — conservez le DOM.
L’ordre n’est pas garanti. Les clés des objets JSON n’ont pas d’ordre défini dans le modèle de données ; ainsi, un document où la séquence des éléments a une signification perd cette information. Les tableaux préservent l’ordre ; ce n’est pas le cas des éléments frères portant des noms différents.
Et tout devient une chaîne de caractères, sauf indication contraire. Le XML ne dispose pas de types ; ainsi, l’<price>42.50</price>e est du texte. La plupart des bibliothèques proposent une coercition numérique, ce qui est pratique et transformera volontiers un code produit commençant par un zéro en nombre, ou une chaîne de version en nombre à virgule flottante. Pour tout ce qui est un identifiant plutôt qu’une quantité, désactivez la coercition.
Conseil pratique : la conversion en objets convient aux XML de type « données » — flux, catalogues, configurations, réponses d’API — où les éléments sont des enregistrements et des champs. Privilégiez un DOM pour les XML de type « document », où le balisage est intégré au texte et où la structure véhicule du sens.
Sécurité : XXE et injection
Deux risques distincts, tous deux bien réels.
Traitement des entités externes XML. Le langage XML permet de déclarer des entités faisant référence à des ressources externes, notamment des fichiers locaux et des URL réseau. Un analyseur syntaxique capable de les résoudre peut être amené à lire des fichiers depuis le serveur ou à effectuer des requêtes pour le compte d’un attaquant. Il s’agit d’une catégorie de vulnérabilité classique et toujours courante.
La mesure de protection consiste à désactiver le traitement des entités externes et des DTD dans l’analyseur syntaxique que vous utilisez. DOMParser, le parseur du navigateur, ne résout pas les entités externes ; le navigateur est donc sécurisé par défaut. Les bibliothèques Node varient, et il vaut mieux vérifier plutôt que de partir d’une hypothèse : si vous analysez du XML provenant d’une source non fiable dans Node, vérifiez la gestion des entités par votre parseur avant la mise en production.
Injection lors de la réinsertion dans le DOM. MDN avertit que parseFromString « constitue un “puits d’injection” et un vecteur potentiel d’attaques XSS si l’entrée provient d’un attaquant ». La nuance est importante : avec text/html, « les éléments <script> sont marqués comme non exécutables et les gestionnaires d’événements ne sont pas appelés » — mais les scripts « s’exécuteront si le document analysé est ensuite injecté dans le DOM visible ».
L’analyse est donc sûre ; l’insertion du résultat ne l’est pas. MDN recommande de transmettre des objets TrustedHTML plutôt que des chaînes de caractères, d’imposer des types fiables via la directive CSP require-trusted-types-for, et de nettoyer le contenu à l’aide d’une bibliothèque telle que DOMPurify via une TrustedTypePolicy.
La règle est simple : n’insérez jamais de balisage analysé non fiable dans le DOM actif sans l’avoir nettoyé, et privilégiez textContent plutôt que innerHTML lorsque vous n’avez besoin que du texte.
Modèles pratiques
Analyse d'un flux RSS ou Atom :
const doc = parseXml(await res.text());
const items = [...doc.getElementsByTagNameNS("*", "item")].map(item => ({
title: item.getElementsByTagNameNS("*", "title")[0]?.textContent?.trim(),
link: item.getElementsByTagNameNS("*", "link")[0]?.textContent?.trim(),
date: item.getElementsByTagNameNS("*", "pubDate")[0]?.textContent?.trim(),
}));
L'espace de noms générique prend en charge à la fois RSS et Atom sans ramification, et le chaînage optionnel gère les flux dont certains champs sont manquants — ce qui est le cas de la plupart d'entre eux.
Analyse d'un plan du site, y compris les fichiers d'index :
const doc = parseXml(xml);
const isIndex = doc.documentElement.localName === "sitemapindex";
const locs = [...doc.getElementsByTagNameNS("*", "loc")].map(n => n.textContent.trim());
// if isIndex, these are sitemap URLs to fetch; otherwise they are page URLs
Vérifier l’attribut ``localName`
de l’élémentdocument est le moyen le plus fiable de distinguer les deux, car ils contiennent tous deux des éléments ``<loc>
` et seul l’élément d’encapsulation diffère.
Gestion d’un document volumineux — utilisez un analyseur en continu plutôt que de construire un DOM :
import sax from "sax";
const stream = sax.createStream(true, { trim: true });
let current = null;
stream.on("opentag", node => { if (node.name === "loc") current = ""; });
stream.on("text", t => { if (current !== null) current += t; });
stream.on("closetag", name => { if (name === "loc") { emit(current); current = null; } });
Mémoire constante quelle que soit la taille du document, au prix de l’écriture d’une petite machine à états.
Questions fréquentes
Comment analyser un fichier XML en JavaScript ?
Dans un navigateur, utilisez l’DOMParser intégré : new DOMParser().parseFromString(xml, "application/xml") renvoie un Document que vous pouvez interroger à l’aide des méthodes DOM. Sous Node, il n’existe pas d’analyseur intégré ; vous devez donc en installer un : fast-xml-parser pour la conversion en objet, @xmldom/xmldom pour un véritable DOM.
Pourquoi DOMParser ne lève-t-il pas d’exception en cas d’XML invalide ?
C’est voulu. Au lieu d’une exception, le document renvoyé contient un nœud <parsererror> décrivant l’erreur. Vous devez le vérifier explicitement à l’aide de doc.querySelector("parsererror"), sinon un document mal formé produira silencieusement des résultats de requête vides.
Node.js dispose-t-il d’un analyseur XML intégré ?
Non. Contrairement au JSON, le XML nécessite une bibliothèque externe. Les options les plus couramment utilisées sont fast-xml-parser et xml2js pour la conversion en objets, @xmldom/xmldom pour une implémentation DOM, et sax pour le traitement en continu de documents très volumineux.
Pourquoi mon sélecteur XML ne trouve-t-il rien ?
Généralement à cause des espaces de noms. Un document déclarant xmlns place chaque élément dans cet espace de noms, et un simple getElementsByTagName risque de ne pas correspondre. Utilisez getElementsByTagNameNS avec l’URI de l’espace de noms, ou "*" comme caractère générique, ou configurez votre bibliothèque pour qu’elle ignore les espaces de noms.
Comment utiliser XPath avec du XML en JavaScript ?
Dans les navigateurs, utilisez document.evaluate avec un type « XPathResult » — suffisamment détaillé pour être utilisé une seule fois. Sous Node, utilisez le package xpath associé à @xmldom/xmldom. Notez que les navigateurs implémentent uniquement XPath 1.0, et que les documents avec espace de noms nécessitent une fonction de résolution mappant les préfixes aux URI.
L'analyse syntaxique du XML en JavaScript présente-t-elle un risque de sécurité ?
Deux risques. Le traitement des entités externes XML peut amener un analyseur à lire des fichiers locaux ou à envoyer des requêtes — l’DOMParser du navigateur ne résout pas les entités externes, mais les bibliothèques Node varient et doivent être vérifiées. De plus, l’insertion de balises analysées non fiables dans le DOM actif peut entraîner l’exécution de scripts ; il convient donc de les nettoyer avant insertion.
Comment analyser un très gros fichier XML ?
Utilisez un analyseur en flux continu tel que sax, qui émet des événements au fur et à mesure de la lecture plutôt que de construire un document en mémoire. Cela permet de traiter des fichiers de n’importe quelle taille avec une consommation de mémoire constante, au prix de l’écriture d’une petite machine à états pour suivre votre progression.
Pourquoi mon XML analysé renvoie-t-il parfois un tableau et parfois un objet ?
Parce que les bibliothèques de mappage d’objets ne produisent un tableau que lorsqu’un élément se répète. Un flux comportant trois éléments vous donne un tableau ; le même flux avec un seul élément vous donne un objet. La plupart des bibliothèques proposent une option permettant de toujours produire des tableaux pour les éléments nommés — activez-la pour tout ce que vous itérez.
En conclusion
C’est l’environnement qui détermine en grande partie cela. Dans un navigateur, vous disposez de DOMParser et il n’y a pas de dépendance ; dans Node, vous choisissez une bibliothèque, et ce choix détermine la manière dont vous écrivez tout ce qui suit.
Deux comportements sont à l’origine de la majeure partie du temps perdu. DOMParser signale les échecs à l’aide d’un nœud parsererror au lieu d’une exception ; ainsi, un document mal formé renvoie des résultats vides qui ressemblent à un problème de sélecteur — vérifiez ce nœud et consignez les 200 premiers caractères de l’entrée, car il s’agit généralement d’une page d’erreur HTML. De plus, les espaces de noms empêchent silencieusement la recherche de noms de balises simples précisément sur les documents que vous souhaitez le plus analyser : les plans de site, les flux et les réponses SOAP les déclarent tous.
Au-delà de cela, adaptez l’outil à la taille. Un mappage d’objets pour les documents ordinaires, un véritable DOM lorsque vous avez besoin d’XPath ou de code partagé entre le navigateur et le serveur, et un analyseur en continu lorsque le fichier est trop volumineux pour être chargé en mémoire. Et si la source n’est pas fiable, vérifiez la gestion des entités externes de votre analyseur Node avant la mise en production — cela constitue une classe de vulnérabilité depuis deux décennies et l’est toujours.
