Der Grund, warum wir diesen Beitrag schreiben: Wir sind Geonode und verkaufen Proxys an Leute, die Daten sammeln, und ein Großteil dieser Daten liegt im XML-Format vor – Sitemaps, RSS- und Atom-Feeds, SOAP-Antworten, Produktkataloge. Um ehrlich zu sein: Ein Parsing-Fehler ist so gut wie nie ein Netzwerkproblem. Wenn Ihr XML-Parser Fehler meldet, geben Sie die ersten 200 Zeichen der empfangenen Daten aus, bevor Sie irgendwelche Änderungen am Code vornehmen. In neun von zehn Fällen handelt es sich um eine HTML-Fehlerseite, und der Parser meldet korrekt, dass Ihnen kein XML gesendet wurde.
Im Browser: DOMParser
Integriert, keine Abhängigkeiten.
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, "application/xml");
const titles = doc.querySelectorAll("item > title");
titles.forEach(t => console.log(t.textContent));
MDN dokumentiert die akzeptierten MIME-Typen als text/html
, text/xml
, application/xml
, application/xhtml+xml
und image/svg+xml
. Es gibt ein Document
zurück, „dessen contentType
-Eigenschaft mit dem angegebenen mimeType
übereinstimmt“, wobei es sich je nach Ihrer Anfrage um ein HTMLDocument
oder ein XMLDocument
handeln kann.
Verwenden Sie „application/xml
“ für XML. Die Übergabe von „text/html
“ ruft den HTML-Parser auf, der in gewisser Weise nachsichtig ist und Ihr Dokument stillschweigend verändert – er berücksichtigt die Groß-/Kleinschreibung von XML nicht und akzeptiert problemlos Dinge, die XML verbietet.
Nach dem Parsen verfügen Sie über ein DOM. Alles, was Sie über die DOM-Durchquerung wissen, gilt auch hier: „querySelector
“, „querySelectorAll
“, „getElementsByTagName
“, „children
“, „textContent
“.
Die Fehlerfalle: Es wird keine Ausnahme ausgelöst
Das Verhalten, das beim ersten Mal jeden überrascht.
Wenn man DOMParser mit fehlerhaftem XML füttert, löst dies keine Ausnahme aus. MDN sagt ausdrücklich: „Das zurückgegebene XMLDocument enthält einen <parsererror>-Knoten, der den Parsing-Fehler beschreibt“, und der Fehler „kann auch an die JavaScript-Konsole des Browsers gemeldet werden“.
Sie müssen also Folgendes prüfen:
const doc = parser.parseFromString(xmlString, "application/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
throw new Error(`XML parse failed: ${errorNode.textContent}`);
}
Ohne diese Prüfung erzeugt ein fehlerhaftes Dokument ein Document-Objekt, das anstelle Ihrer Daten eine Fehlermeldung enthält. Ihr anschließender Aufruf von querySelectorAll liefert nichts zurück, und das Symptom sieht eher nach einem Selektorproblem als nach einem Parsing-Fehler aus.
Umschließen Sie es einmal:
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;
}
Das text.slice(0, 200) ist der Teil, der Zeit spart. Ein Parsing-Fehler sagt Ihnen, dass die Eingabe ungültig war; die ersten 200 Zeichen sagen Ihnen, dass es sich um eine HTML-Fehlerseite handelte.
In Node: Auswahl einer Bibliothek
Node verfügt über keinen integrierten XML-Parser, daher hängt die Wahl von den jeweiligen Abhängigkeiten ab. Die wöchentlichen Downloadzahlen geben einen groben Eindruck von der Verbreitung – diese stammen aus dem npm-Register (Stand: September 2026).
| Bibliothek | Wöchentliche Downloads | Stil |
|---|---|---|
sax |
| ~88,9 Mio. | Streaming, ereignisbasiert |
| fast-xml-parser
| ~85,0 Mio. | XML in einfache JavaScript-Objekte |
| @xmldom/xmldom
| ~48,7 Mio. | DOM-Implementierung für Node |
| xml2js
| ~44,5 Mio. | XML in Objekte, Callback- und Promise-APIs |
| xpath
| ~12,2 Mio. | XPath-Abfragen, in Kombination mit xmldom |
**fast-xml-parser
** ist der pragmatische Standard für die meisten Anwendungen. Es konvertiert XML in gewöhnliche JavaScript-Objekte, was bedeutet, dass man mit Punktnotation statt mit DOM-Methoden navigiert:
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);
Der zu beachtende Kompromiss: Die Objektkonvertierung ist in einer bestimmten Hinsicht verlustbehaftet. Ein Element, das einmal vorkommt, wird zu einem Objekt; dasselbe Element, das zweimal vorkommt, wird zu einem Array. Daher ist channel.item
bei einem Feed mit drei Elementen ein Array und bei einem Feed mit einem Element ein Objekt – und Code, der von einem Array ausgeht, schlägt im Fall eines einzelnen Elements fehl. Die meisten Bibliotheken bieten eine Option, bei der für benannte Elemente immer Arrays erzeugt werden, und es lohnt sich, diese Option für alles zu aktivieren, was Sie durchlaufen wollen, bevor es Ihnen Probleme bereitet.
**@xmldom/xmldom
** stellt Ihnen ein echtes DOM in Node zur Verfügung, was wichtig ist, wenn Sie möchten, dass derselbe Code in beiden Umgebungen funktioniert, oder wenn Sie XPath benötigen. Kombinieren Sie es mit dem Paket „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
** ist ein Streaming-Parser, der beim Einlesen Ereignisse ausgibt. Er ist die Lösung für Dokumente, die zu groß sind, um im Speicher untergebracht zu werden – beispielsweise ein mehrere Gigabyte großer Sitemap-Index oder ein Massenexport eines Katalogs –, bei denen ein DOM-Ansatz einfach nicht in Frage kommt.
**xml2js
** ist seit langem etabliert und weit verbreitet. Seine API ist zwar etwas veraltet, aber durchaus brauchbar, und es gibt eine Vielzahl an bestehendem Code, der darauf zurückgreift.
Namespaces: Der Grund, warum echte Dokumente nicht mehr funktionieren
Der häufigste Grund, warum ein bisher funktionierender Selektor plötzlich keine Ergebnisse mehr liefert.
Viele echte XML-Formate deklarieren Namespaces:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
</urlset>
xmlns
ordnet jedes Element einem Standard-Namespace zu. In einem Namespace-fähigen Parser ist <loc>
nicht einfach loc
– es ist loc
im Namespace „sitemaps“, und ein reines getElementsByTagName("loc")
findet möglicherweise nichts.
Drei Möglichkeiten, damit umzugehen, in aufsteigender Reihenfolge der Korrektheit.
Verwenden Sie die namensraumbewussten Methoden:
const NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
const locs = doc.getElementsByTagNameNS(NS, "loc");
Verwenden Sie einen Platzhalter-Namensraum, wenn es Ihnen egal ist, welcher es ist:
const locs = doc.getElementsByTagNameNS("*", "loc");
Konfigurieren Sie Ihre Bibliothek so, dass sie Namespaces ignoriert. Die meisten Objekt-Mapping-Bibliotheken verfügen über eine Option zum Entfernen von Namespace-Präfixen, wodurch einfache Schlüssel wie loc
erzeugt werden. Das ist praktisch, führt jedoch dazu, dass zwei tatsächlich unterschiedliche Elemente, die zufällig denselben lokalen Namen haben, stillschweigend zusammengeführt werden – für eine Sitemap akzeptabel, für ein Dokument, in dem verschiedene Vokabulare gemischt werden, jedoch gefährlich.
Beachten Sie, dass sich querySelector
hier anders verhält als getElementsByTagNameNS
: CSS-Selektoren haben eine eigene Namespace-Syntax, die umständlich und selten verwendet wird; daher sind für XML mit Namespaces die Methoden NS
oder XPath zuverlässiger.
XPath in JavaScript
Verfügbar in Browsern über document.evaluate und in Node über das Paket „xpath“ mit 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);
}
Die API ist so ausführlich, dass die meisten Nutzer sie einmal einbinden und dann vergessen. Was den Aufwand jedoch lohnenswert macht, ist, dass XPath Dinge ausdrücken kann, die CSS-Selektoren nicht können – das Abgleichen von Textinhalten, das Navigieren zu übergeordneten Elementen und Positionslogik relativ zu Geschwistern.
Zwei Einschränkungen. Browser implementieren XPath 1.0, daher funktionieren weder matches() noch lower-case() noch ends-with(). Und Namespaces erfordern eine Resolver-Funktion – das dritte Argument –, die Präfixe Namespace-URIs zuordnet. Die Übergabe von null funktioniert nur bei Dokumenten ohne Namespaces, was die meisten echten Feeds ausschließt.
Für Dokumente mit Namespace in einem Browser:
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);
Beachten Sie, dass Sie das Präfix – hier sm – selbst festlegen, unabhängig davon, was das Dokument verwendet, da XPath 1.0 kein Konzept eines Standard-Namespace kennt.
Konvertierung von XML in JSON und was dabei verloren geht
Das ist eigentlich das, was die meisten Leute wollen, und man sollte sich bewusst sein, dass die Konvertierung nicht verlustfrei ist.
XML und JSON haben unterschiedliche Datenmodelle. XML verfügt über Attribute, Elemente, Textknoten, Kommentare, Verarbeitungsanweisungen, Namespaces und eine bestimmte Reihenfolge. JSON verfügt über Objekte, Arrays, Zeichenketten, Zahlen, Boolesche Werte und „null“. Vier Dinge überstehen die Umwandlung nicht vollständig.
Attribute versus untergeordnete Elemente. <item id="1"><name>x</name></item> hat ein Attribut und ein untergeordnetes Element, und JSON macht keinen Unterschied zwischen beiden. Bibliotheken lösen dies, indem sie Attributschlüssel mit einem Präfix versehen – üblicherweise @ oder $ –, das Sie konfigurieren und sich dann merken müssen:
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@" });
// { item: { "@id": "1", name: "x" } }
Wiederholte Elemente werden uneinheitlich zu Arrays. Dies wurde oben bereits behandelt und ist eine Wiederholung wert, da es sich um den häufigsten Fehler in diesem Bereich handelt: Ein Vorkommen ergibt ein Objekt, zwei ergeben ein Array. Setzen Sie die Option „always array“ der Bibliothek für jedes Element, das Sie durchlaufen möchten.
Gemischter Inhalt lässt sich nicht sauber darstellen. „<p>Hello <b>world</b>!</p>“ vermischt Text und Elemente. Bei der Konvertierung in ein Objekt lassen sich die Textfragmente und ihre Positionen relativ zum untergeordneten Element nur schwer darstellen, und die meisten Bibliotheken fügen den Text entweder zusammen oder lassen Teile davon weg. Wenn Ihr XML Prosa-Markup enthält, ist die Objektkonvertierung der falsche Ansatz – behalten Sie das DOM bei.
Die Reihenfolge ist nicht garantiert. JSON-Objektschlüssel haben im Datenmodell keine definierte Reihenfolge, sodass ein Dokument, bei dem die Reihenfolge der Elemente von Bedeutung ist, diese Information verliert. Arrays bewahren die Reihenfolge; gleichrangige Elemente mit unterschiedlichen Namen tun dies nicht.
Und alles wird zu einer Zeichenkette, sofern Sie nichts anderes festlegen. XML kennt keine Datentypen, daher ist „<price>42.50</price>“ Text. Die meisten Bibliotheken bieten numerische Typumwandlung an, was praktisch ist und beispielsweise einen Produktcode mit führender Null gerne in eine Zahl und eine Versionszeichenkette in eine Gleitkommazahl umwandelt. Deaktivieren Sie die Typumwandlung für alles, was eher ein Bezeichner als eine Menge ist.
Die praktische Empfehlung: Objektkonvertierung eignet sich für datenorientiertes XML – Feeds, Kataloge, Konfigurationen, API-Antworten –, bei dem Elemente Datensätze und Felder sind. Verwenden Sie ein DOM für dokumentorientiertes XML, bei dem Markup in den Text eingebettet ist und die Struktur Bedeutung trägt.
Sicherheit: XXE und Injektion
Zwei unterschiedliche Risiken, beide real.
Verarbeitung externer XML-Entitäten. In XML können Entitäten deklariert werden, die auf externe Ressourcen verweisen, darunter lokale Dateien und Netzwerk-URLs. Ein Parser, der diese auflöst, kann dazu gebracht werden, Dateien vom Server zu lesen oder im Auftrag eines Angreifers Anfragen zu stellen. Dies ist eine klassische und nach wie vor häufige Schwachstellenklasse.
Abhilfe schafft die Deaktivierung der Verarbeitung externer Entitäten und DTDs in dem von Ihnen verwendeten Parser. Der Browser DOMParser löst keine externen Entitäten auf, sodass der Browser standardmäßig sicher ist. Node-Bibliotheken variieren, und es lohnt sich, dies zu überprüfen, anstatt davon auszugehen – wenn Sie in Node XML aus einer nicht vertrauenswürdigen Quelle parsen, überprüfen Sie vor der Veröffentlichung die Entitätsverarbeitung Ihres Parsers.
Injektion beim erneuten Einfügen in das DOM. MDN warnt davor, dass parseFromString „ein ‚Injection-Sink‘ und ein potenzieller Vektor für XSS-Angriffe ist, wenn die Eingabe von einem Angreifer stammt“. Die Nuance ist entscheidend: Bei „text/html“ werden „<script>-Elemente als nicht ausführbar markiert und Ereignisbehandler nicht aufgerufen“ – Skripte „werden jedoch ausgeführt, wenn das geparste Dokument später in das sichtbare DOM eingefügt wird“.
Das Parsen ist also sicher; das Einfügen des Ergebnisses hingegen nicht. MDN empfiehlt, „TrustedHTML“-Objekte anstelle von Strings zu übergeben, vertrauenswürdige Typen über die CSP-Direktive „require-trusted-types-for“ zu erzwingen und die Daten mit einer Bibliothek wie DOMPurify über „TrustedTypePolicy“ zu bereinigen.
Die einfache Regel lautet: Fügen Sie niemals geparstes, nicht vertrauenswürdiges Markup in das Live-DOM ein, ohne es zuvor zu bereinigen, und bevorzugen Sie „textContent“ gegenüber „innerHTML“, wenn Sie nur den Text benötigen.
Praktische Muster
Parsen eines RSS- oder Atom-Feeds:
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(),
}));
Der Platzhalter-Namespace verarbeitet sowohl RSS als auch Atom ohne Verzweigungen, und die optionale Verkettung behandelt Feeds mit fehlenden Feldern – was bei den meisten der Fall ist.
Parsen einer Sitemap, einschließlich Indexdateien:
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
Die Überprüfung von „localName
“ am Dokumentelement ist der zuverlässige Weg, um die beiden zu unterscheiden, da beide „<loc>
“-Elemente enthalten und sich lediglich der Wrapper unterscheidet.
Umgang mit einem großen Dokument – verwende einen Streaming-Parser, anstatt ein DOM aufzubauen:
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; } });
Konstanter Speicherbedarf unabhängig von der Dokumentgröße, auf Kosten der Erstellung einer kleinen Zustandsmaschine.
Häufig gestellte Fragen
Wie parse ich XML in JavaScript?
Verwenden Sie in einem Browser den integrierten „DOMParser“: new DOMParser().parseFromString(xml, "application/xml") gibt ein „Document“ zurück, das Sie mit DOM-Methoden abfragen können. In Node gibt es keinen integrierten Parser, daher müssen Sie einen installieren – fast-xml-parser für die Objektkonvertierung, @xmldom/xmldom für ein echtes DOM.
Warum löst DOMParser bei ungültigem XML keine Ausnahme aus?
Das ist beabsichtigt. Anstelle einer Ausnahme enthält das zurückgegebene Dokument einen „<parsererror>“-Knoten, der den Fehler beschreibt. Sie müssen explizit mit „doc.querySelector("parsererror")“ darauf prüfen, da ein fehlerhaftes Dokument sonst stillschweigend leere Abfrageergebnisse liefert.
Verfügt Node.js über einen integrierten XML-Parser?
Nein. Im Gegensatz zu JSON erfordert XML eine Abhängigkeit. Die gängigen Optionen sind fast-xml-parser und xml2js für die Konvertierung in Objekte, @xmldom/xmldom für eine DOM-Implementierung und sax für das Streaming sehr großer Dokumente.
Warum findet mein XML-Selektor nichts?
Meistens liegt es an den Namespaces. Ein Dokument, das „xmlns“ deklariert, ordnet jedes Element diesem Namespace zu, und ein einfacher „getElementsByTagName“ passt möglicherweise nicht. Verwenden Sie „getElementsByTagNameNS“ mit der Namespace-URI oder „"*"“ als Platzhalter, oder konfigurieren Sie Ihre Bibliothek so, dass Namespaces ignoriert werden.
Wie verwende ich XPath mit XML in JavaScript?
In Browsern: document.evaluate mit dem Typ „XPathResult“ – ausführlich genug, um einmal umgebrochen zu werden. In Node: das Paket „xpath“ in Kombination mit „@xmldom/xmldom“. Beachten Sie, dass Browser nur XPath 1.0 implementieren und Dokumente mit Namespaces eine Resolver-Funktion benötigen, die Präfixe auf URIs abbildet.
Stellt das Parsen von XML in JavaScript ein Sicherheitsrisiko dar?
Zwei Risiken. Die Verarbeitung externer XML-Entitäten kann dazu führen, dass ein Parser lokale Dateien liest oder Anfragen sendet – der Browser-DOMParser löst keine externen Entitäten auf, aber Node-Bibliotheken variieren und sollten überprüft werden. Und das Einfügen von geparstem, nicht vertrauenswürdigem Markup in das Live-DOM kann zur Ausführung von Skripten führen; daher sollte das Markup vor dem Einfügen bereinigt werden.
Wie parse ich eine sehr große XML-Datei?
Verwenden Sie einen Streaming-Parser wie sax, der beim Einlesen Ereignisse ausgibt, anstatt ein Dokument im Speicher aufzubauen. Dieser verarbeitet Dateien beliebiger Größe mit konstantem Speicherbedarf, allerdings auf Kosten einer kleinen Zustandsmaschine, die den aktuellen Lesefortschritt verfolgt.
Warum liefert mein geparstes XML manchmal ein Array und manchmal ein Objekt?
Weil Objekt-Mapping-Bibliotheken nur dann ein Array erzeugen, wenn sich ein Element wiederholt. Ein Feed mit drei Elementen liefert ein Array; derselbe Feed mit einem Element liefert ein Objekt. Die meisten Bibliotheken verfügen über eine Option, für benannte Elemente immer Arrays zu erzeugen – aktivieren Sie diese Option für alles, was Sie durchlaufen.
Fazit
Die Umgebung entscheidet darüber größtenteils. In einem Browser gibt es „DOMParser“ und keine Abhängigkeiten; in Node wählt man eine Bibliothek aus, und diese Wahl bestimmt, wie man alles, was danach kommt, schreibt.
Zwei Verhaltensweisen sind für den Großteil des Zeitverlusts verantwortlich. „DOMParser“ meldet Fehler mit einem „parsererror“-Knoten anstelle einer Ausnahme, sodass ein fehlerhaftes Dokument leere Ergebnisse liefert, die wie ein Selektorproblem aussehen – überprüfen Sie diesen Knoten und protokollieren Sie die ersten 200 Zeichen der Eingabe, da es sich in der Regel um eine HTML-Fehlerseite handelt. Und Namespaces unterbrechen stillschweigend einfache Tag-Namenssuchen genau bei den Dokumenten, die Sie am liebsten parsen möchten: Sitemaps, Feeds und SOAP-Antworten deklarieren sie alle.
Darüber hinaus sollten Sie das Tool an die Größe anpassen. Objekt-Mapping für gewöhnliche Dokumente, ein echtes DOM, wenn du XPath oder gemeinsamen Browser- und Servercode benötigst, und einen Streaming-Parser, wenn die Datei zu groß ist, um sie vollständig zu laden. Und wenn die Quelle nicht vertrauenswürdig ist, überprüfe vor der Veröffentlichung die Behandlung externer Entitäten durch deinen Node-Parser – dies ist seit zwei Jahrzehnten eine bekannte Schwachstelle und ist es immer noch.
