Validador XSD
Valide com um XSD. Os dois arquivos ficam no seu navegador.
Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.
Cole o documento à esquerda, o XSD dele à direita e clique em Validar contra o esquema. O libxml2, compilado para WebAssembly, roda a verificação em um Web Worker nesta aba, e cada violação é listada com linha, coluna e um marcador em que você pode clicar para saltar até lá.
Você costuma chegar aqui depois que outra coisa rejeitou o documento: um parceiro devolve cvc-complex-type.2.4.a, uma etapa do build falha num arquivo de configuração, um gateway diz que o payload não bate com o esquema e para por aí. Você tem o XSD e quer o elemento que falhou, não uma cadeia de ferramentas Java.
A diferença é onde isso roda. Navegadores não têm validador de esquema nativo, então as ferramentas gratuitas que oferecem validação XSD enviam os dois arquivos para um servidor; a mais conhecida faz você submeter primeiro o XML e o esquema num segundo formulário. Aqui nenhum dos arquivos sai desta aba, e o motor, cerca de 480 KB comprimidos, é baixado quando você aperta o botão, não ao carregar a página.
Primeiro bem formado, depois válido
São duas verificações, não uma. Boa formação é sintaxe: tags fechadas, aninhamento correto, uma raiz, "e comercial" escapado. Precisa apenas do documento, então roda enquanto você digita. Validade é boa formação mais conformidade com um esquema, o que exige um segundo arquivo, e por isso é uma ação deliberada.
O motor analisa o XSD, compila, analisa o documento e então valida, e cada etapa falha de um jeito diferente. Um documento mal formado nunca é confrontado com um esquema, porque não há nada coerente para verificar. Todo diagnóstico é rotulado como Problema no esquema ou Erro de validade, e uma posição dentro do esquema deliberadamente não é clicável, já que aquele número de linha pertence ao outro painel.
Só XSD 1.0, e o que isso deixa de fora
O libxml2 implementa XSD 1.0. O XSD 1.1 virou Recomendação do W3C em 5 de abril de 2012 e é implementado pelo Apache Xerces-J, pelo Saxon-EE e pelo pacote xmlschema do Python, mas não pelo libxml2, nem pelo XmlSchemaSet do .NET, nem pelo lxml, que embrulha o libxml2. Um esquema que use recursos do 1.1 não vai compilar aqui, e o painel aponta o 1.1 como causa provável em vez de devolver um erro estrutural enigmático.
- xs:assert e a faceta xs:assertion: restrições de coocorrência, em que a validade de um campo depende de outro. O XSD 1.0 não consegue expressá-las de forma alguma.
- xs:alternative, a atribuição condicional de tipo, em que o tipo de um elemento é escolhido por um teste XPath sobre os próprios atributos dele.
- xs:openContent, xs:defaultOpenContent e xs:override, mais os tipos de dado do 1.1 xs:dateTimeStamp, xs:dayTimeDuration e xs:yearMonthDuration. O xs:precisionDecimal, que aparece em um monte de artigos, foi descartado antes da Recomendação final.
- Mais dois limites do próprio libxml2: xs:redefine há muito cai num caminho não implementado, e xs:import não consegue resolver um schemaLocation aqui, porque nada nesta compilação pode ler um arquivo. Achate antes um conjunto de esquemas espalhado por vários documentos.
As falhas que você vai ver de verdade
A ordem causa a maioria. xs:sequence significa que os filhos precisam aparecer na ordem declarada, e essa ordem faz parte do contrato, não é preferência de formatação, então mover o elemento costuma ser a correção. O XSD 1.0 permite xs:all, a alternativa sem ordem, só no topo de um modelo de conteúdo e com cada elemento no máximo uma vez, e é por isso que quase todo esquema real usa uma sequência.
Os namespaces causam o resto. Se o esquema declara targetNamespace="urn:example:orders", todo elemento que ele governa precisa estar nesse namespace; se não declara nenhum, eles precisam estar sem namespace, e acrescentar um xmlns padrão quebra tudo. A armadilha vizinha é o elementFormDefault: ausente, ele vale unqualified, então a raiz é qualificada e os filhos não devem ser.
- cvc-complex-type.2.4.a: apareceu um elemento onde o esquema não permitia. Os nomes entre chaves são os que seriam legais ali, então "One of {qty} is expected" quer dizer que qty era o próximo.
- cvc-complex-type.2.4.b: o modelo de conteúdo não foi satisfeito, geralmente um filho obrigatório faltando no fim.
- cvc-datatype-valid.1.2.1: o texto não é lexicamente válido para o tipo dele. Um elemento vazio declarado xs:int, um dateTime sem segundos, um decimal com separador de milhar.
- cvc-elt.1: nenhuma declaração encontrada para o elemento raiz. Quase sempre um targetNamespace que não bate, e não uma declaração ausente.
- cvc-complex-type.3.2.2: um atributo não é permitido, muitas vezes xsi:type ou xsi:nil sem o namespace XMLSchema-instance declarado.
O que ele nunca faz, e o que isso custa
Ele nunca busca o xsi:schemaLocation. A parte 1 do XSD chama esse atributo de uma dica "sobre a localização física dos documentos de esquema" e diz que um processador "deveria tentar dereferenciá-la" "salvo instrução em contrário, por exemplo da aplicação invocadora". Recusar é conforme, e comum: o SQL Server ignora isso em colunas do tipo xml.
Ele não conseguiria buscar nada nem se quisesse. Toda análise usa XML_PARSE_NO_XXE, NONET e NO_SYS_CATALOG, e esta compilação não registra nenhum carregador de recursos, então entidades externas, um subconjunto DTD externo e uma URL de schemaLocation resolvem todos para nada. XML_PARSE_HUGE nunca é ligado, o que mantém os tetos do libxml2: profundidade de elementos 256, aninhamento de entidades 20 e um limite de amplificação.
Uma fraqueza honesta: a documentação do libxml2 chama o módulo de esquemas dele de implementação parcial da parte 1 do XML Schema, e ele frequentemente reporta menos diagnósticos que o Xerces com as mesmas entradas. Leia a lista como "pelo menos estes" e rode de novo depois de cada correção.
Validando contra um XSD em código
A mesma verificação nas linguagens que consomem XML. Todos os exemplos desligam o acesso externo, porque uma referência de esquema é uma instrução de busca e a maioria dessas bibliotecas a segue por padrão.
// npm i libxml2-wasm, the same engine this page runs.
import { XmlDocument, XsdValidator, ParseOption, XmlValidateError } from 'libxml2-wasm';
// NO_XXE blocks external DTDs and entities. Leaving HUGE unset is what keeps
// libxml2's depth, entity-nesting and amplification limits switched on.
const SAFE = ParseOption.XML_PARSE_NO_XXE | ParseOption.XML_PARSE_NONET;
export function validateXsd(xmlText, xsdText) {
let xsd = null;
let validator = null;
let doc = null;
try {
xsd = XmlDocument.fromString(xsdText, { option: SAFE });
validator = XsdValidator.fromDoc(xsd);
doc = XmlDocument.fromString(xmlText, { option: SAFE });
validator.validate(doc);
return { valid: true, errors: [] };
} catch (err) {
if (err instanceof XmlValidateError) {
// Each detail is { line, col, level, message, xpath }.
return { valid: false, errors: err.details };
}
throw err;
} finally {
// dispose() is mandatory: these are WebAssembly heap allocations.
doc?.dispose();
validator?.dispose();
xsd?.dispose();
}
}# pip install lxml
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False, # no entity substitution
no_network=True, # never fetch a schema or DTD
load_dtd=False,
huge_tree=False, # keep the depth and expansion limits
)
schema = etree.XMLSchema(etree.parse('schema.xsd', parser))
doc = etree.parse('document.xml', parser)
if schema.validate(doc):
print('valid')
else:
for e in schema.error_log:
print(f'{e.line}:{e.column} {e.message}')
# lxml wraps libxml2, so this is XSD 1.0. For xs:assert and xs:alternative use
# the pure-Python implementation instead:
# import xmlschema
# xmlschema.XMLSchema11('schema.xsd').validate('document.xml')import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// With Xerces on the classpath, XSD 1.1 is one string away:
// SchemaFactory.newInstance("http://www.w3.org/XML/XMLSchema/v1.1");
// "file" allows xs:import of a schema next to this one; http is refused.
// Pass "" instead to block every external reference, local ones included.
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
Schema schema = factory.newSchema(new StreamSource(new java.io.File("schema.xsd")));
Validator validator = schema.newValidator();
validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
// Without an ErrorHandler the first error throws and you see one problem.
// With one, Xerces carries on and reports the lot.
validator.setErrorHandler(new DefaultHandler() {
@Override public void error(SAXParseException e) {
System.out.printf("%d:%d %s%n", e.getLineNumber(), e.getColumnNumber(), e.getMessage());
}
@Override public void fatalError(SAXParseException e) throws SAXParseException {
throw e;
}
});
validator.validate(new StreamSource(new java.io.File("document.xml")));using System;
using System.Xml;
using System.Xml.Schema;
var schemaSettings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null, // no xs:import over the network
};
var schemas = new XmlSchemaSet { XmlResolver = null };
using (var schemaReader = XmlReader.Create("schema.xsd", schemaSettings))
{
schemas.Add(null, schemaReader); // null: take targetNamespace from the file
}
var settings = new XmlReaderSettings
{
ValidationType = ValidationType.Schema,
Schemas = schemas,
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
};
// Never follow xsi:schemaLocation from the instance document.
settings.ValidationFlags &= ~XmlSchemaValidationFlags.ProcessSchemaLocation;
// Without a handler the first error throws; with one, validation continues.
settings.ValidationEventHandler += (_, e) =>
Console.WriteLine($"{e.Exception.LineNumber}:{e.Exception.LinePosition} {e.Message}");
using var reader = XmlReader.Create("document.xml", settings);
while (reader.Read()) { } // validation happens as the document is read
// System.Xml.Schema is XSD 1.0, and Microsoft has said it is not moving past it.<?php
// DOMDocument is libxml2, so this is the same engine and the same XSD 1.0.
libxml_use_internal_errors(true);
$doc = new DOMDocument();
if (!$doc->loadXML($source, LIBXML_NONET)) {
fwrite(STDERR, "The document is not well-formed.\n");
exit(1);
}
// schemaValidateSource() takes the XSD as a string if you already have it.
if (!$doc->schemaValidate('schema.xsd')) {
foreach (libxml_get_errors() as $e) {
fprintf(STDERR, "%d:%d %s\n", $e->line, $e->column, trim($e->message));
}
libxml_clear_errors();
exit(1);
}
echo "valid\n";# xmllint ships with libxml2 and is the same engine as this page.
# --nonet stops it fetching anything the document or the schema references.
xmllint --noout --nonet --schema schema.xsd document.xml
# Exit status is 0 when the document is valid, non-zero otherwise, and the
# diagnostics go to stderr in file:line: form.
# A schema split across xs:import files works here, because xmllint reads the
# sibling files from disk. That is the one thing a browser cannot do.
# No widely installed command-line tool does XSD 1.1; for that you need
# Xerces-J or Saxon-EE, driven from code.Repare no padrão do handler nos exemplos de Java e C#: as duas APIs lançam exceção no primeiro erro a menos que você instale um, e é por isso que tanto código de produção reporta um problema de cada vez.
Perguntas frequentes
Meu XML e meu XSD são enviados para algum lugar?
Não. Os dois painéis ficam nesta aba. O libxml2 roda como WebAssembly em um Web Worker aqui, não existe endpoint de upload, e a compilação não tem caminho de rede próprio.
Isso pesa mais aqui do que na maioria das páginas: um esquema nomeia cada campo, cada lista de códigos e cada fronteira interna, e muitas vezes está sob NDA enquanto o documento é só dado de teste. Abra o painel de rede, clique em Validar e veja o motor carregar uma vez e nada mais depois. Os dois painéis ficam no localStorage para que um recarregamento não perca seu trabalho, e nada acima de 300 KB é guardado.
Ele suporta XSD 1.1, ou xs:assert?
Não. O libxml2 é XSD 1.0, então xs:assert, xs:alternative, xs:openContent e xs:override não vão compilar. Em vez de um enigmático "element assert is not expected here", o painel diz que o esquema é XML bem formado mas não um XSD utilizável, e aponta o 1.1 como causa provável.
Para 1.1, use o Xerces-J, que é gratuito e entrega uma SchemaFactory 1.1 mudando uma string de namespace, o Saxon-EE, ou o pacote xmlschema do Python. Nada baseado em navegador faz isso: são todos builds do libxml2.
Meu esquema usa xs:import. Ainda dá para validar aqui?
Só se você achatar antes. Resolver um xs:import significa ler um arquivo ou fazer uma requisição, e esta compilação não registra nenhum carregador de recursos, a mesma decisão que impede a busca de entidades externas, então o esquema não vai compilar.
Ou você cola as definições de tipo importadas no documento contra o qual valida, ou roda o xmllint localmente, onde os arquivos .xsd vizinhos são lidos do disco. A maioria das grandes famílias de esquemas públicos, UBL e FpML entre elas, precisa desse tratamento.
O que quer dizer cvc-complex-type.2.4.a?
Que apareceu um elemento onde o esquema não permitia. A mensagem termina com os nomes que seriam legais ali, entre chaves, então "Invalid content was found starting with element 'item'. One of '{qty}' is expected." quer dizer que qty era o próximo.
Três causas cobrem quase tudo: os filhos estão na ordem errada para uma xs:sequence, de longe a mais comum; o elemento não está declarado de jeito nenhum, geralmente um erro de digitação ou um campo que um lado da integração acrescentou; ou o nome está certo e o namespace está errado, o que a mensagem não consegue mostrar porque imprime o nome local.
Ele vai buscar o esquema indicado em xsi:schemaLocation?
Nunca. A especificação chama esse atributo de dica e deixa a aplicação invocadora instruir o processador a não dereferenciá-lo, então recusar é conforme e não um atalho.
As razões vão além da privacidade: seguir uma URL controlada por um atacante a partir de um documento não confiável é uma primitiva de falsificação de requisição, e amarra o seu resultado ao que um terceiro estiver servindo hoje.
Que tamanho de documento ele consegue verificar?
Vinte megabytes. Carregar um arquivo maior é recusado, porque tudo fica na memória desta página e a validação monta uma árvore inteira mais o esquema compilado.
Para documentos realmente grandes, rode o xmllint localmente: o mesmo código do libxml2, sem o teto do navegador.