15. XML Parsing with JavaScript

What you will learn

  • How to turn an XML string into a document with DOMParser
  • How to read elements, attributes, and text
  • How to use XPath in JavaScript
  • How to detect errors, and how to show XML data safely

In these examples the XML is written inside the script as a text string, so they run here without any network. Loading XML from a server uses fetch(), as in the Fetch lesson. The result is the same: you get text, and then parse it.

Parsing a string

DOMParser reads text and builds an XML document that you can search, much like the HTML DOM you already know.

Example

<p id="demo"></p>

<script>
  const xmlText = `<library>
    <book id="b1"><title>Learning XML</title><price>12.00</price></book>
    <book id="b2"><title>Learning PHP</title><price>35.50</price></book>
  </library>`;

  const parser = new DOMParser();
  const xml = parser.parseFromString(xmlText, "application/xml");

  const books = xml.getElementsByTagName("book");

  document.getElementById("demo").textContent =
    "Found " + books.length + " books. First title: " +
    books[0].getElementsByTagName("title")[0].textContent;
</script>

Output (after the script runs)

Found 2 books. First title: Learning XML

The second argument, "application/xml", tells the parser to treat the text as XML, not HTML. The same methods you used on web pages work here: getElementsByTagName, querySelector, textContent, and getAttribute.

Checking for errors

Unlike most JavaScript, parseFromString does not throw an error for bad XML. It returns a document that contains a <parsererror> element. Always check for it.

Example

<p id="demo"></p>

<script>
  function parseXml(text) {
    const doc = new DOMParser().parseFromString(text, "application/xml");
    const problem = doc.getElementsByTagName("parsererror");

    if (problem.length > 0) {
      return null;
    }
    return doc;
  }

  const good = parseXml("<note><to>Ali</to></note>");
  const bad = parseXml("<note><to>Ali</note>");

  document.getElementById("demo").textContent =
    "good: " + (good ? "parsed" : "failed") + " | bad: " + (bad ? "parsed" : "failed");
</script>

Output (after the script runs)

good: parsed | bad: failed

Another check is that the root is called parsererror, but searching for the element, as above, works in every browser.

Reading attributes

Example

<p id="demo"></p>

<script>
  const xmlText = `<library>
    <book id="b1" category="web"><title>Learning XML</title></book>
    <book id="b2" category="design"><title>Learning CSS</title></book>
  </library>`;

  const xml = new DOMParser().parseFromString(xmlText, "application/xml");
  const lines = [];

  xml.querySelectorAll("book").forEach(function (book) {
    lines.push(
      book.getAttribute("id") + " (" + book.getAttribute("category") + "): " +
      book.querySelector("title").textContent
    );
  });

  document.getElementById("demo").textContent = lines.join(" | ");
</script>

Output (after the script runs)

b1 (web): Learning XML | b2 (design): Learning CSS

querySelectorAll uses CSS selectors, so it is often the quickest way to find nodes. A missing attribute gives null, and a missing child gives null too, so check before using it if the data might be incomplete.

Turning XML into JavaScript data

It is often easiest to convert the XML into an array of plain objects, and then use the array methods from the earlier lessons.

Example

<p id="demo"></p>

<script>
  const xmlText = `<library>
    <book><title>Learning XML</title><price>12.00</price></book>
    <book><title>Learning PHP</title><price>35.50</price></book>
    <book><title>Learning CSS</title><price>28.00</price></book>
  </library>`;

  const xml = new DOMParser().parseFromString(xmlText, "application/xml");

  const books = Array.from(xml.querySelectorAll("book")).map(function (book) {
    return {
      title: book.querySelector("title").textContent,
      price: Number(book.querySelector("price").textContent)
    };
  });

  const total = books.reduce(function (sum, b) { return sum + b.price; }, 0);
  const cheap = books.filter(function (b) { return b.price < 30; }).map(function (b) { return b.title; });

  document.getElementById("demo").textContent =
    "Total: " + total.toFixed(2) + " | Under 30: " + cheap.join(", ");
</script>

Output (after the script runs)

Total: 75.50 | Under 30: Learning XML, Learning CSS

Text from XML is always a string, so the price is converted with Number().

XPath in JavaScript

document.evaluate() lets you use the XPath expressions from the earlier lesson.

Example

<p id="demo"></p>

<script>
  const xmlText = `<library>
    <book category="web"><title>Learning XML</title><price>12.00</price></book>
    <book category="web"><title>Learning PHP</title><price>35.50</price></book>
    <book category="design"><title>Learning CSS</title><price>28.00</price></book>
  </library>`;

  const xml = new DOMParser().parseFromString(xmlText, "application/xml");

  const result = xml.evaluate(
    "//book[@category='web']/title",
    xml,
    null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
    null
  );

  const titles = [];
  for (let i = 0; i < result.snapshotLength; i++) {
    titles.push(result.snapshotItem(i).textContent);
  }

  const count = xml.evaluate("count(//book)", xml, null, XPathResult.NUMBER_TYPE, null).numberValue;

  document.getElementById("demo").textContent =
    "Web titles: " + titles.join(", ") + " | Total books: " + count;
</script>

Output (after the script runs)

Web titles: Learning XML, Learning PHP | Total books: 3

The result type controls what you get back: a snapshot of nodes, a number, a string, or a true or false value. If the XML has a default namespace, you must supply a namespace resolver, which is an advanced topic. In that case querySelector with the namespace-free approach, or getElementsByTagNameNS, is often simpler.

Showing XML data on a page safely

Data from XML can contain anything, especially when it comes from outside. Build the page with createElement and textContent, never innerHTML.

Example

<ul id="list"></ul>

<script>
  const xmlText = `<library>
    <book><title>Learning XML</title></book>
    <book><title>&lt;b&gt;Tricky&lt;/b&gt; title</title></book>
  </library>`;

  const xml = new DOMParser().parseFromString(xmlText, "application/xml");
  const list = document.getElementById("list");

  xml.querySelectorAll("title").forEach(function (title) {
    const li = document.createElement("li");
    li.textContent = title.textContent;
    list.appendChild(li);
  });
</script>

Output (after the script runs)

  • Learning XML
  • <b>Tricky</b> title

The second title contains tag-like text. Because it is added with textContent, it appears as plain characters and nothing runs.

Building XML in JavaScript

XMLSerializer turns an XML document back into text.

Example

<pre id="demo"></pre>

<script>
  const doc = document.implementation.createDocument("", "note", null);
  const root = doc.documentElement;

  const to = doc.createElement("to");
  to.textContent = "Tom & Jerry";
  root.appendChild(to);

  root.setAttribute("priority", "high");

  document.getElementById("demo").textContent = new XMLSerializer().serializeToString(doc);
</script>

Output (after the script runs)

<note priority="high"><to>Tom &amp; Jerry</to></note>

Notice that the ampersand was turned into &amp; for you. Building XML with the DOM, rather than joining strings, avoids this kind of escaping mistake.

Loading XML from a server

With a real web address, fetch the text and parse it. This code is for reading and copying, since the editor blocks network requests.

Syntax

async function loadBooks() {
  const response = await fetch("https://example.com/books.xml");

  if (!response.ok) {
    throw new Error("Server answered " + response.status);
  }

  const text = await response.text();
  const xml = new DOMParser().parseFromString(text, "application/xml");

  if (xml.getElementsByTagName("parsererror").length > 0) {
    throw new Error("The file is not valid XML");
  }

  return xml;
}

The address is a placeholder. The usual limits apply: the other site must allow your page to read it (CORS), and you must never put secret keys in front-end code.

Common mistakes

  • Using "text/html" instead of "application/xml", so the XML is treated as HTML.
  • Expecting bad XML to throw an error, instead of checking for parsererror.
  • Forgetting that all values are strings.
  • Reading a child element that does not exist, which gives null and then an error.
  • Inserting XML text with innerHTML.
  • Forgetting the namespace when the XML has a default one.

Exercise

Parse the XML, find the member with the highest score, and show Top: Ali (90) using textContent.

Show solution
<p id="demo"></p>

<script>
  const xmlText = `<team>
    <member><name>Sam</name><score>72</score></member>
    <member><name>Ali</name><score>90</score></member>
    <member><name>Mia</name><score>85</score></member>
  </team>`;

  const xml = new DOMParser().parseFromString(xmlText, "application/xml");

  const members = Array.from(xml.querySelectorAll("member")).map(function (m) {
    return {
      name: m.querySelector("name").textContent,
      score: Number(m.querySelector("score").textContent)
    };
  });

  const top = members.reduce(function (best, m) {
    return m.score > best.score ? m : best;
  });

  document.getElementById("demo").textContent = "Top: " + top.name + " (" + top.score + ")";
</script>

Knowledge check

1. Which object turns an XML string into a document?

DOMParser

2. How do you detect bad XML after parseFromString?

Look for a parsererror element, because no exception is thrown.

3. Why use textContent to show XML data?

It treats the data as plain text, so outside data cannot run as HTML or script.

Summary

  • DOMParser with "application/xml" builds a document, and you read it with the DOM methods you already know.
  • Always check for parsererror, convert strings to numbers, and use XPath with evaluate() when helpful.
  • Show data with textContent, and build XML with the DOM and XMLSerializer.
Scroll to Top