Comprobador de sintaxis XML
Todos los errores de sintaxis a la vez, con línea, columna y solución.
Todo se ejecuta en esta pestaña. Nada de lo que pegues se sube, se registra ni se envía a ningún sitio. Abre el panel de red y compruébalo.
Pega un documento arriba, o suelta un archivo sobre el editor, y se comprueba contra las reglas de buena formación de XML 1.0 mientras escribes. Cada infracción se lista con su línea, su columna y qué escribir en su lugar, y al pulsar un resultado el cursor se coloca sobre el carácter culpable.
Esta es la comprobación que tiene que pasar antes de que pueda ejecutarse cualquier otra. Un validador de esquemas, un procesador XSLT, una pila SOAP y un navegador se niegan todos a mirar un documento con la sintaxis rota, así que un "premature end of data in tag" salido de tu build es un problema de sintaxis, y por mucho que leas el XSD no lo vas a explicar.
Lo que aquí es distinto es que el escáner no para en el primer fallo. Se recupera y sigue, así que una sola pasada informa del atributo sin comillas de la línea 4, del ampersand suelto de la línea 12 y de la etiqueta que quedó abierta al final. Cada una de sus 37 clases de error lleva su propia redacción y su propia solución, en vez de un único "Syntax Error" para todo. No se sube nada: abre tu panel de red y míralo seguir vacío.
Qué significa realmente "bien formado"
XML 1.0 enumera doce restricciones de buena formación y deja el resto en la gramática. Reducido a comprobaciones que un documento pasa o no pasa, queda esta lista, y aquí se aplica cada una de sus líneas.
- Exactamente un elemento raíz, sin nada fuera de él salvo comentarios, instrucciones de procesamiento y espacio en blanco.
- Cada etiqueta de apertura cerrada por una de cierre cuyo nombre coincida exactamente. Los nombres distinguen mayúsculas, así que </Note> no cierra <note>.
- Elementos anidados, no solapados: <b><i>x</b></i> no es XML, por muy tolerante que haya sido tu analizador de HTML.
- Valores de atributo entrecomillados, y ningún atributo repetido en un mismo elemento.
- Ningún < literal en el texto ni en un valor de atributo, y ningún & suelto fuera de una referencia. Un > es legal en el texto; la secuencia ]]> no lo es.
- Solo las cinco entidades predefinidas —amp, lt, gt, quot y apos— salvo que una DTD declare más. es de HTML y no es una de ellas.
- Ningún carácter fuera del conjunto que XML permite, ningún comentario que contenga --, y una declaración, si está, la primera del archivo y escrita como version, luego encoding, luego standalone.
- Cada prefijo de espacio de nombres vinculado por una declaración xmlns en ámbito. Esa regla viene de Namespaces in XML y no de XML 1.0, y es la que más validadores gratuitos se saltan: uno de los competidores líderes da por válido <ns:root> sin ninguna vinculación en el archivo.
Todos los errores a la vez, y por qué otras herramientas te dan uno
La especificación le dice al procesador que no debe continuar el procesamiento normal tras un error fatal, y por eso DOMParser, Expat y .NET informan del primer problema y paran. Eso es comportamiento correcto y no pereza, y también es la razón de que arreglar un documento grande cueste una ida y vuelta por error.
La recuperación aquí es deliberada. Cuando una etiqueta de cierre no coincide, el escáner busca ese nombre más arriba en la pila de elementos abiertos: si lo encuentra, el error real son los elementos que quedaron abiertos en medio, así que cada uno se informa en su propia línea de apertura en vez de cargarle la culpa a la etiqueta de cierre. Un carácter extraño dentro de un nombre absorbe el token entero, así que amount$="10" produce un mensaje sobre el símbolo del dólar en lugar de cinco sobre el igual y las comillas. Una advertencia: un error estructural temprano hace que todo lo que viene después parezca mal, así que arregla los dos o tres primeros y vuelve a comprobar. El informe se detiene a los 200 problemas, con una nota diciéndolo.
Los fallos que aparecen de verdad
Los errores de sintaxis reales rara vez son exóticos. Cuestan tiempo porque el analizador nombra un síntoma mientras la causa está en otro sitio, o es invisible.
- Una marca de orden de bytes UTF-8 antes de la declaración. Legal, invisible y la causa habitual de "Content is not allowed in prolog". Aquí se informa como lo que es, no como contenido misterioso.
- Una declaración escrita como <?xml encoding="UTF-8" version="1.0"?>, o una que dice windows-1252 mientras los bytes son UTF-8. Lo segundo se avisa como advertencia sobre lo que leerá un analizador a nivel de bytes, porque el archivo ya venía decodificado cuando llegó a esta página.
- , — y unas cincuenta entidades HTML más, nombradas como HTML y no como una entidad indefinida genérica, con la referencia numérica que hay que usar en su lugar.
- Un aviso de PHP o una traza de pila añadidos después de la etiqueta raíz de cierre, que es por lo que ese mensaje te apunta al cuerpo HTTP en crudo.
- Caracteres de control salidos de una columna de base de datos, que XML 1.0 prohíbe en todas partes, CDATA incluido.
- Un prefijo soap:, xsi: o xlink: sin vincular, después de copiar un fragmento de un documento mayor y dejar su declaración xmlns en la raíz original.
Dónde se detiene la comprobación de sintaxis
Esta página responde a una pregunta: si la sintaxis es legal. Nunca se pregunta si un <invoice> puede contener una <line>, ni si falta un elemento obligatorio. Eso necesita un esquema, así que vive en los validadores de XSD y de DTD. Si un sistema ha llamado "inválido" a tu documento, ejecutó una comprobación de esquema, y un resultado verde aquí no lo explicará.
Dos límites que conviene decir. El subconjunto interno de la DTD se lee solo para declaraciones de entidades, así que las declaraciones de elementos y de listas de atributos que haya dentro se analizan de paso pero no se aplican, y las DTD externas se informan pero nunca se descargan. El tamaño está limitado a 20 millones de caracteres, el anidamiento a 512 niveles y la expansión de entidades a 10 millones, porque todo se ejecuta en esta pestaña.
Comprobar la sintaxis XML desde código
La misma comprobación en los lenguajes que más consumen XML, cada uno en la forma segura frente a entidades externas y a la expansión de entidades. Fíjate en lo pocos que informan de más de un error: eso lo dice la especificación, no la biblioteca.
// DOMParser never throws. Given broken input it returns a document whose
// root is <parsererror>, which is why so much code silently accepts XML
// that no other parser would take.
function checkXml(source) {
const doc = new DOMParser().parseFromString(source, 'application/xml');
const error = doc.querySelector('parsererror');
if (!error) return { wellFormed: true, message: null };
// The text is browser-specific. Firefox includes a line and column,
// Chrome's wording differs, and neither exposes them as fields.
return { wellFormed: false, message: error.textContent.trim() };
}
// Browsers do not resolve external entities, so XXE is not reachable here.
// Internal entity expansion is, so cap the input length before parsing:
// if (source.length > 20_000_000) throw new Error('too large');# lxml is the only common Python parser that keeps going after a syntax
# error and hands back the whole list rather than the first entry.
from lxml import etree
parser = etree.XMLParser(
recover=True, # keep parsing so error_log fills up
resolve_entities=False, # do not expand entities
no_network=True, # never fetch an external DTD
load_dtd=False,
huge_tree=False, # keep libxml2's depth and expansion limits
)
etree.fromstring(source.encode('utf-8'), parser)
for entry in parser.error_log:
print(f"line {entry.line}, column {entry.column}: {entry.message}")
if not parser.error_log:
print('well-formed')
# Without recover=True, fromstring raises etree.XMLSyntaxError and
# e.position gives a (line, column) tuple for the first failure only.
# For untrusted input with the standard library, use defusedxml.import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// Drop the next line only if your documents legitimately carry a DOCTYPE.
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
var reader = factory.newSAXParser().getXMLReader();
reader.setErrorHandler(new DefaultHandler() {
@Override public void warning(SAXParseException e) { print("warning", e); }
@Override public void error(SAXParseException e) { print("error", e); }
@Override public void fatalError(SAXParseException e) throws SAXParseException {
print("fatal", e);
throw e; // the specification requires processing to stop here
}
private void print(String kind, SAXParseException e) {
System.err.printf("%s at line %d, column %d: %s%n",
kind, e.getLineNumber(), e.getColumnNumber(), e.getMessage());
}
});
reader.parse(new InputSource(new java.io.StringReader(source)));using System.Xml;
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // no DTD, so no entity expansion
XmlResolver = null, // never fetch anything
MaxCharactersFromEntities = 1024 * 1024,
MaxCharactersInDocument = 20L * 1024 * 1024,
ConformanceLevel = ConformanceLevel.Document,
};
try
{
using var reader = XmlReader.Create(new StringReader(source), settings);
while (reader.Read()) { } // pull every node; syntax errors surface here
Console.WriteLine("well-formed");
}
catch (XmlException e)
{
// LinePosition is the column, 1-based. Reading stops at this point.
Console.Error.WriteLine(
$"line {e.LineNumber}, column {e.LinePosition}: {e.Message}");
}<?php
// libxml recovers internally, so libxml_get_errors() is the closest a PHP
// script gets to a full list rather than a first failure.
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadXML($source, LIBXML_NONET); // NONET blocks external fetches
$errors = libxml_get_errors();
libxml_clear_errors();
foreach ($errors as $e) {
$kind = $e->level === LIBXML_ERR_FATAL ? 'fatal' : 'error';
fprintf(
STDERR,
"%s at line %d, column %d: %s\n",
$kind,
$e->line,
$e->column,
trim($e->message)
);
}
exit($errors ? 1 : 0);# xmllint ships with libxml2 and is already installed on most machines.
# --nonet stops it fetching a DTD the document points at.
xmllint --noout --nonet document.xml
# --recover keeps parsing after a failure, so you see more than the first.
xmllint --noout --nonet --recover document.xml
# Check a whole tree, one file at a time:
find . -name '*.xml' -print0 | xargs -0 -n1 xmllint --noout --nonet
# Exit status: 0 well-formed, 1 unclassified, 3 well-formedness error,
# 4 validation error against a DTD or schema.Todos los ejemplos desactivan algo, y eso es lo esencial, no relleno. La resolución de entidades externas está activada por defecto en Java, y la biblioteca estándar de Python no aplica ningún límite de expansión, así que esos flags son lo que separa una comprobación de sintaxis de una primitiva de lectura de archivos apuntando a tu propio servidor.
Preguntas frecuentes
¿Cómo compruebo la sintaxis XML sin instalar nada?
Pega el documento en el editor de la parte superior de esta página. La comprobación se ejecuta mientras escribes, sin botón que pulsar ni cuenta que crear. También puedes soltar un archivo .xml encima: el archivo se lee con la File API del navegador y nunca se transmite.
Desde un terminal, xmllint forma parte de libxml2 y ya está presente en macOS y en la mayoría de distribuciones Linux: xmllint --noout --nonet tuarchivo.xml no imprime nada cuando la sintaxis es legal. Para en el primer error, que es la diferencia práctica.
¿Se sube mi XML cuando lo compruebo aquí?
No. El escáner es JavaScript ejecutándose en esta pestaña, en un Web Worker. No hay endpoint al que enviar un documento, ni analítica ni script de reporte de errores con acceso al editor.
Eso se puede comprobar, no es una promesa. Abre las herramientas de desarrollo, ve a la pestaña Red, pega un documento y mira: los recursos de esta página se cargan una vez y no sigue nada. Importa porque los documentos que la gente pega en comprobadores de sintaxis son envelopes SOAP con tokens de portador, aserciones SAML y archivos de configuración con cadenas de conexión.
¿Cuál es la diferencia entre un comprobador de sintaxis y un validador XML?
Los dos se usan indistintamente, y ahí empieza la confusión. Una comprobación de sintaxis pregunta si el documento obedece las reglas del propio XML: etiquetas cerradas, anidamiento correcto, una raíz, atributos entrecomillados, ampersands escapados. No necesita nada más que el documento.
La validación en el sentido de la especificación pregunta si el documento cumple un esquema, un XSD o una DTD: un segundo archivo que describe qué elementos pueden aparecer y dónde. No puede ejecutarse hasta que la sintaxis esté limpia, y por eso un "inválido" de una pila de middleware a veces significa error de sintaxis y a veces violación de esquema. Esta página hace la primera comprobación y lo dice; los validadores de XSD y DTD de aquí hacen la segunda, también sin subir nada.
¿Por qué esto informa de cinco errores cuando mi editor informa de uno?
Porque la especificación XML indica a un procesador conforme que pare tras un error fatal, y el analizador que hay detrás de tu editor hace eso. La regla es defendible: una vez hay una etiqueta sin cerrar, el analizador de verdad no sabe qué pretendía decir el resto del documento.
Este escáner se recupera en cambio: resincroniza en los límites de las etiquetas, absorbe entero un token de nombre defectuoso y prefiere culpar a una etiqueta de apertura sin cerrar antes que a la de cierre que la delató. Toma los errores más tempranos como los fiables y vuelve a comprobar, porque una lista larga suele tener un único error estructural cerca del principio que genera casi todo lo demás.
¿Los nombres de etiqueta XML distinguen mayúsculas y minúsculas?
Por completo. <Note> y <note> son elementos distintos, y </Note> no cierra <note>. Esto pilla a quien viene de HTML, donde el analizador pasa los nombres de etiqueta a minúsculas, y es la causa más común de un error de etiquetas no coincidentes en XML escrito a mano.
También se aplica a los nombres de atributo y a los prefijos de espacio de nombres: xmlns:Soap y xmlns:soap son prefijos distintos. Cuando una discrepancia solo difiere en mayúsculas, el mensaje lo dice en vez de informar de un desajuste genérico. Varios resúmenes de "reglas de sintaxis XML" muy copiados afirman que las etiquetas XML no distinguen mayúsculas; se equivocan, y seguirlos produce documentos que cualquier analizador real rechaza.
¿Cuál es el archivo más grande que puede comprobar?
20 millones de caracteres, más o menos 20 MB de ASCII. Un documento de 10 MB se escanea en algo más de un segundo y 1 MB en unos 110 milisegundos, en un Web Worker, así que el editor sigue respondiendo.
Para algo mayor, usa un analizador en flujo en local: xmllint, el iterparse de Python o cualquier analizador SAX comprueban la buena formación sin construir el árbol entero.