Cada servicio SOAP que has tocado, cada feed RSS que has consumido, cada SVG que has manipulado — todos son XML. JavaScript tiene dos formas integradas de analizarlo en el navegador, y una sólida biblioteca npm para Node.js. La parte difícil no es el análisis en sí; es navegar por el DOM resultante, manejar los espacios de nombres, y no caer en los problemas que atrapan a todos la primera vez. Repasemos los patrones reales.
DOMParser — Analizar XML en el navegador
La API
DOMParser
integrada del navegador convierte una cadena XML en un documento DOM. Usa el tipo MIME 'application/xml'
(no 'text/html') para que el parser aplique reglas XML estrictas:
const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
<library>
<book isbn="978-0-13-110362-7">
<title>The C Programming Language</title>
<authors>
<author>Brian W. Kernighan</author>
<author>Dennis M. Ritchie</author>
</authors>
<year>1988</year>
<price currency="USD">45.99</price>
</book>
<book isbn="978-0-201-63361-0">
<title>The Pragmatic Programmer</title>
<authors>
<author>Andrew Hunt</author>
<author>David Thomas</author>
</authors>
<year>1999</year>
<price currency="USD">52.00</price>
</book>
</library>`;
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, 'application/xml');
// Siempre verificar errores de análisis primero
const parseError = doc.querySelector('parsererror');
if (parseError) {
throw new Error('Fallo en el análisis XML: ' + parseError.textContent);
}
console.log(doc.documentElement.tagName); // libraryJSON.parse() que lanza una excepción,
DOMParser devuelve un documento que contiene un elemento <parsererror> cuando
el análisis falla — no lanza una excepción. Si omites la verificación de error, operarás silenciosamente
en un documento malformado y obtendrás resultados confusos más adelante.Navegar por el DOM — getElementsByTagName vs querySelector
Una vez que tienes un documento analizado, tienes dos APIs principales para encontrar elementos. Ambas funcionan, pero tienen diferentes fortalezas:
// getElementsByTagName — devuelve una HTMLCollection viva
const books = doc.getElementsByTagName('book');
console.log(books.length); // 2
// querySelector / querySelectorAll — sintaxis de selector CSS, devuelve NodeList
const firstTitle = doc.querySelector('title').textContent;
console.log(firstTitle); // The C Programming Language
// Obtener todos los títulos
const titles = [...doc.querySelectorAll('title')].map(el => el.textContent);
console.log(titles);
// ['The C Programming Language', 'The Pragmatic Programmer']
// Leer atributos
const firstBook = doc.querySelector('book');
const isbn = firstBook.getAttribute('isbn');
console.log(isbn); // 978-0-13-110362-7
// Leer el atributo currency desde price
const priceEl = firstBook.querySelector('price');
console.log(priceEl.textContent); // 45.99
console.log(priceEl.getAttribute('currency')); // USDPrefiero querySelector para búsquedas dirigidas — la sintaxis del selector CSS es
familiar y concisa. Usa getElementsByTagName cuando necesites todos los elementos con una
etiqueta dada y quieras una colección viva (aunque en la práctica, un NodeList extendido suele ser más limpio).
Extraer datos estructurados — Un patrón práctico
Así es como mapear un documento XML en un array limpio de objetos JavaScript — el patrón que usarás al consumir una respuesta real de API XML:
function parseLibraryXml(xmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, 'application/xml');
if (doc.querySelector('parsererror')) {
throw new Error('XML inválido');
}
return [...doc.querySelectorAll('book')].map(book => ({
isbn: book.getAttribute('isbn'),
title: book.querySelector('title').textContent.trim(),
authors: [...book.querySelectorAll('author')].map(a => a.textContent.trim()),
year: parseInt(book.querySelector('year').textContent, 10),
price: {
amount: parseFloat(book.querySelector('price').textContent),
currency: book.querySelector('price').getAttribute('currency')
}
}));
}
const books = parseLibraryXml(xmlString);
console.log(books[0].title); // The C Programming Language
console.log(books[0].authors); // ['Brian W. Kernighan', 'Dennis M. Ritchie']
console.log(books[0].price.amount); // 45.99Manejar XML con espacios de nombres
Los espacios de nombres son donde la mayoría de los desarrolladores chocan con una pared. Las respuestas SOAP, los feeds Atom y SVG usan
espacios de nombres XML — y un naive querySelector('body') devolverá null en un documento SOAP
porque el elemento es en realidad soap:Body. Así es cómo manejarlo correctamente:
const soapResponse = `<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:m="http://www.example.com/orders">
<soap:Header/>
<soap:Body>
<m:GetOrderResponse>
<m:OrderId>ORD-5521</m:OrderId>
<m:Status>Shipped</m:Status>
<m:Total currency="EUR">289.50</m:Total>
</m:GetOrderResponse>
</soap:Body>
</soap:Envelope>`;
const parser = new DOMParser();
const doc = parser.parseFromString(soapResponse, 'application/xml');
// Opción 1: getElementsByTagNameNS — URI de espacio de nombres explícito
const SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/';
const ORDER_NS = 'http://www.example.com/orders';
const body = doc.getElementsByTagNameNS(SOAP_NS, 'Body')[0];
const orderId = doc.getElementsByTagNameNS(ORDER_NS, 'OrderId')[0].textContent;
const status = doc.getElementsByTagNameNS(ORDER_NS, 'Status')[0].textContent;
console.log(orderId); // ORD-5521
console.log(status); // Shipped
// Opción 2: XPath con resolvedor de espacio de nombres (más flexible)
function nsResolver(prefix) {
const namespaces = {
soap: 'http://schemas.xmlsoap.org/soap/envelope/',
m: 'http://www.example.com/orders'
};
return namespaces[prefix] || null;
}
const xpathResult = doc.evaluate(
'//m:OrderId',
doc,
nsResolver,
XPathResult.STRING_TYPE,
null
);
console.log(xpathResult.stringValue); // ORD-5521Consultas XPath con evaluate()
XPath es un lenguaje de consulta para documentos XML. El navegador lo expone via
document.evaluate(). Es más potente que los selectores CSS para XML — puedes
consultar por valor de atributo, posición, contenido de texto y ascendencia.
Consulta la documentación XPath de MDN
para la sintaxis completa de expresiones:
// Usando nuestro documento XML de biblioteca anterior
function xpath(doc, expression, contextNode = doc) {
const result = doc.evaluate(
expression,
contextNode,
null, // resolvedor de espacio de nombres — null para XML sin espacio de nombres
XPathResult.ANY_TYPE,
null
);
return result;
}
// Obtener todos los títulos de libros
const titlesResult = xpath(doc, '//book/title');
const titles = [];
let node;
while ((node = titlesResult.iterateNext())) {
titles.push(node.textContent);
}
console.log(titles);
// ['The C Programming Language', 'The Pragmatic Programmer']
// Obtener el libro con un ISBN específico
const bookResult = doc.evaluate(
'//book[@isbn="978-0-13-110362-7"]/title',
doc, null,
XPathResult.STRING_TYPE,
null
);
console.log(bookResult.stringValue); // The C Programming Language
// Obtener libros con precio mayor a $50
const expensiveResult = xpath(doc, '//book[price > 50]/title');
let expensiveNode;
while ((expensiveNode = expensiveResult.iterateNext())) {
console.log(expensiveNode.textContent); // The Pragmatic Programmer
}Node.js — fast-xml-parser (la mejor opción)
Node.js no tiene DOMParser. Tienes dos opciones: usar el enfoque SAX basado en
node:stream
integrado (doloroso), o usar
fast-xml-parser
(la elección correcta para la mayoría de casos de uso). Es rápido, sin dependencias, y devuelve objetos JavaScript planos:
npm install fast-xml-parserimport { XMLParser } from 'fast-xml-parser';
const xmlString = `<?xml version="1.0"?>
<library>
<book isbn="978-0-13-110362-7">
<title>The C Programming Language</title>
<year>1988</year>
<price currency="USD">45.99</price>
</book>
<book isbn="978-0-201-63361-0">
<title>The Pragmatic Programmer</title>
<year>1999</year>
<price currency="USD">52.00</price>
</book>
</library>`;
const parser = new XMLParser({
ignoreAttributes: false, // incluir atributos XML
attributeNamePrefix: '@_', // prefijar atributos para distinguirlos de los elementos
isArray: (tagName) => tagName === 'book' // siempre tratar <book> como array
});
const result = parser.parse(xmlString);
const books = result.library.book;
books.forEach(book => {
console.log(book.title); // The C Programming Language
console.log(book['@_isbn']); // 978-0-13-110362-7
console.log(book.price['#text']); // 45.99
console.log(book.price['@_currency']); // USD
});fast-xml-parser te dará un
objeto para un elemento y un array para muchos. La opción isArray fuerza un comportamiento
de array consistente para etiquetas nombradas — úsala siempre para elementos que sabes que pueden repetirse.Manejo de errores en Node.js
import { XMLParser, XMLValidator } from 'fast-xml-parser';
function parseXmlSafely(xmlString) {
// Validar primero — devuelve true o un objeto de error
const validation = XMLValidator.validate(xmlString);
if (validation !== true) {
throw new Error(`XML inválido: ${validation.err.msg} en línea ${validation.err.line}`);
}
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
return parser.parse(xmlString);
}
try {
const data = parseXmlSafely(xmlString);
console.log(data);
} catch (err) {
console.error('Fallo en el análisis XML:', err.message);
}Herramientas relacionadas
Al trabajar con XML en proyectos JavaScript: Formateador XML para dar formato a respuestas minificadas, Validador XML para verificar la buena formación antes de analizar, Probador XPath XML para experimentar con consultas XPath, y XML a JSON si quieres convertir a una estructura más simple.
Resumen
En el navegador, DOMParser con 'application/xml' es tu opción principal —
recuerda solo verificar parsererror. Para XML con espacios de nombres, usa
getElementsByTagNameNS o XPath con un resolvedor de espacio de nombres. En Node.js,
fast-xml-parser te da objetos JavaScript limpios sin la sobrecarga del DOM.
Los patrones aquí cubren el 95% de los escenarios de análisis XML del mundo real — respuestas SOAP, feeds RSS,
archivos de configuración y más.