Visualizador XML

Árvore recolhível e código-fonte, lado a lado.

Entrada
ÁrvoreClique em um nó para recolhê-lo

A estrutura do documento aparece aqui assim que você colar algo.

AguardandoCole um documento para verificá-lo. A validação roda enquanto você digita.

Tudo roda nesta aba. Nada do que você colar é enviado, registrado ou transmitido para lugar nenhum. Abra o painel de rede e confira.

Cole um documento acima e ele aparece duas vezes: o código à esquerda com destaque de sintaxe, numeração de linhas e dobra, e uma árvore recolhível à direita construída a partir da mesma análise. Todos os nós começam expandidos; clique num elemento para recolhê-lo junto com tudo o que está abaixo. Os dois painéis vêm de uma única varredura, então a árvore e a lista de erros descrevem sempre o mesmo documento.

Uma visão em árvore ganha o seu lugar quando não foi você quem escreveu o documento. Uma resposta de API de um fornecedor cuja documentação é um PDF, uma configuração de 4.000 linhas deixada por um time anterior, uma exportação que deveria ter um registro e talvez tenha duzentos. A pergunta normalmente não é onde está o erro de sintaxe, e sim que forma isso tem e onde mora o valor de que você precisa.

Ele valida enquanto você lê, reportando cada problema de boa formação com linha, coluna e uma correção sugerida em vez de parar no primeiro. E nada é enviado: o analisador, a árvore e o editor rodam todos nesta aba.

O que a árvore mostra

Cada elemento é uma linha: o nome da tag como está escrito, prefixo incluído, seguido dos atributos como pares nome="valor". Um elemento sem filhos é marcado como "vazio", então dá para distinguir <status/> de um nó que você recolheu. O texto aparece como folha sob o elemento, truncado em 200 caracteres, porque a árvore serve à estrutura e um blob base64 de 40 KB renderizado inteiro a enterraria. Comentários aparecem como uma linha de marcador.

Duas coisas deliberadamente não aparecem: seções CDATA e instruções de processamento. Para essas, leia o painel do código; ele mostra o documento exatamente como você colou e é sempre a autoridade. A árvore é DOM de verdade, um nó recolhível por elemento, e é isso que faz o recolhimento ser instantâneo e o texto selecionável. É também o custo: um documento com dezenas de milhares de elementos leva mais tempo montando a árvore do que analisando o XML.

Para que uma árvore é boa, e no que ela é ruim

Uma árvore responde perguntas de forma mais rápido do que a rolagem jamais responderá. Recolha tudo um nível abaixo e as seções de primeiro nível cabem numa tela. Recolha um Header SOAP e o Body sobe para o topo do painel. Olhe as linhas repetidas sob um contêiner e você sabe se a resposta traz um registro ou muitos, que é a diferença entre um consumidor que funciona em teste e um que perde dados em produção. Ela é ruim em três coisas, e dizer isso é mais útil do que fingir o contrário.

  • Editar. A árvore é regenerada a partir da sua entrada a cada tecla, então não há onde digitar. Edite no painel do código e veja a árvore se reconstruir.
  • Comparar. Duas árvores lado a lado são um jeito pior de achar uma diferença do que um diff que normaliza a formatação dos dois lados primeiro.
  • Extrair. "Todo preço de todo item de linha" é uma expressão XPath, não um exercício de rolagem.

Aninhamento profundo, e por que os prefixos ficam apagados

XML com namespaces é difícil de ler por uma razão que nada tem a ver com namespaces serem complicados. Em soap:Body e ns1:PlaceOrder o prefixo é andaime: ele diz a que vocabulário o nome pertence, e depois que você sabe isso, vira ruído em cada linha seguinte. Com oito níveis de indentação, boa parte dos caracteres na tela são prefixos e dois-pontos.

Por isso o editor desenha o prefixo e o dois-pontos com opacidade menor ao lado do nome local, e soap:Body se lê como Body com um marcador colado. Nada é escondido ou alterado; o texto continua selecionável e é copiado intacto. A exceção é uma declaração: em xmlns:soap o prefixo é o conteúdo, não o andaime, então ele fica com intensidade cheia. Um prefixo usado sem nunca ter sido vinculado é reportado como erro, junto com a declaração que falta, o que costuma ser o resultado de copiar um trecho de um documento maior.

Encontrar um valor

Para uma string literal, ponha o cursor no painel do código e aperte Ctrl-F, ou Cmd-F num Mac. O painel de busca do editor abre no topo com destaque das ocorrências e um campo de substituição, e ele procura no código, que tem o texto completo de cada nó, inclusive as partes que a árvore trunca.

Para uma pergunta estrutural, use XPath. //order[status="failed"]/@id responde em um passo o que a rolagem responde em vinte, e o testador de XPath deste site avalia XPath 1.0 no seu documento com os prefixos colhidos dele. Um detalhe explica a maior parte dos relatos de "minha expressão não retorna nada": XPath 1.0 não tem namespace padrão. Se a raiz declara xmlns="urn:example:orders", então //order não casa com nada, porque //order significa um elemento order sem namespace. Vincule um prefixo e escreva //o:order, ou recorra a //*[local-name()="order"].

Percorrendo um documento em código

Ler um documento na mão costuma ser um passo rumo a escrever o código que o lê todos os dias. Estes exemplos percorrem um documento e imprimem a estrutura dele, o equivalente programático da árvore acima, e cada um faz o parsing de forma segura, porque os padrões de Java e Python resolvem entidades externas.

// Print the shape of a document: element paths with a count, which is
// usually more useful than the tree itself when you are writing a consumer.
function outline(source) {
  const doc = new DOMParser().parseFromString(source, 'application/xml');
  const error = doc.querySelector('parsererror');
  if (error) throw new Error(error.textContent.trim());

  const counts = new Map();
  const walk = (el, path) => {
    const here = path + '/' + el.nodeName;
    counts.set(here, (counts.get(here) ?? 0) + 1);
    for (const child of el.children) walk(child, here);
  };
  walk(doc.documentElement, '');

  for (const [path, n] of [...counts].sort()) {
    console.log(n.toString().padStart(6), path);
  }
}

// nodeName keeps the prefix as written. For namespace-aware work use
// el.localName with el.namespaceURI, since the prefix is arbitrary and the
// URI is what identifies the vocabulary.
# iterparse reads the document as a stream and lets you drop each element
# once you are done with it, so memory stays flat on files far larger than a
# browser will open.
from defusedxml.ElementTree import iterparse
from collections import Counter

counts = Counter()
stack = []

for event, el in iterparse('document.xml', events=('start', 'end')):
    if event == 'start':
        stack.append(el.tag)
        counts['/'.join(stack)] += 1
    else:
        stack.pop()
        el.clear()          # release the subtree; this is the whole point

for path, n in sorted(counts.items()):
    print(f'{n:6d}  {path}')

# ElementTree reports tags as '{namespace-uri}local' rather than 'prefix:local'
# because the prefix is arbitrary. Split on '}' when you want the local name.
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;

public final class Outline {

    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);                 // off by default
        dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        dbf.setXIncludeAware(false);

        Document doc = dbf.newDocumentBuilder().parse(new java.io.File(args[0]));
        print(doc.getDocumentElement(), 0);
    }

    static void print(Node node, int depth) {
        if (node.getNodeType() != Node.ELEMENT_NODE) return;
        Element el = (Element) node;

        StringBuilder line = new StringBuilder("  ".repeat(depth));
        line.append(el.getNodeName());
        NamedNodeMap attrs = el.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            Node a = attrs.item(i);
            line.append(' ').append(a.getNodeName())
                .append("=\"").append(a.getNodeValue()).append('"');
        }
        System.out.println(line);

        NodeList kids = el.getChildNodes();
        for (int i = 0; i < kids.getLength(); i++) print(kids.item(i), depth + 1);
    }
}
using System.Xml;
using System.Xml.Linq;

// XDocument.Load uses a reader with DtdProcessing.Prohibit by default, so
// external entities are never fetched. Construct the reader yourself when
// you want that guarantee stated in the code rather than in the docs.
var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null,
    MaxCharactersInDocument = 20L * 1024 * 1024,
};

using var reader = XmlReader.Create("document.xml", settings);
var doc = XDocument.Load(reader);

// One line per distinct element path, with how often it occurs.
var paths = doc.Descendants()
    .GroupBy(e => string.Join(
        "/",
        e.AncestorsAndSelf().Reverse().Select(a => a.Name.LocalName)))
    .OrderBy(g => g.Key);

foreach (var group in paths)
{
    Console.WriteLine(group.Count().ToString().PadLeft(6) + "  " + group.Key);
}
# xmllint has an interactive tree browser almost nobody knows about. It gives
# you ls, cd, cat, du and pwd over the document, with XPath as the path
# syntax: the terminal equivalent of the tree on this page.
xmllint --nonet --shell document.xml
#   / > du                      show the subtree shape
#   / > cd //order[1]           navigate by XPath
#   / > ls                      list children of the current node
#   / > cat status              print a node
#   / > exit

# Non-interactive: tag counts, a rough outline of an unfamiliar document.
xmllint --nonet --format document.xml |
  grep -o '<[a-zA-Z_][^ />]*' |
  sort | uniq -c | sort -rn | head -30

# One value, straight out:
xmllint --nonet --xpath 'string(//order[1]/status)' document.xml

O esboço com grep no exemplo de shell é um truque de contagem, não um parsing: ele conta alegremente um nome de tag que apareça dentro de um comentário ou de uma seção CDATA. Todos os outros exemplos montam uma árvore primeiro, que é a diferença entre uma resposta aproximada e uma correta.

Perguntas frequentes

Meu XML é enviado para ser visualizado?

Não. O analisador, a árvore e o editor são JavaScript rodando nesta aba. Não há componente de servidor, nem analytics com acesso ao editor, nem script de terceiros. Abra a aba Rede e cole um documento: a página carrega os próprios recursos uma vez e depois fica quieta.

Essa checagem importa aqui porque visualizar é justamente o que se faz com dados dos outros. O documento que você cola num visualizador costuma ser uma resposta de API real ou uma exportação de produção, com nomes, números de conta ou um token num cabeçalho. Várias ferramentas bem posicionadas nesta busca mandam isso para um servidor, e uma delas deixa documentos salvos públicos por padrão.

Para que serve mesmo uma visão em árvore?

Para responder perguntas sobre estrutura: quais elementos se repetem, quão fundo vai o aninhamento, em que ramo mora um valor, se um elemento opcional está presente nesta resposta. Isso é difícil de ver em texto indentado e imediato numa árvore.

O fluxo que se paga é: recolha tudo um nível abaixo da raiz, leia os nomes das seções, expanda a que interessa, ignore as outras 3.000 linhas. Se a pergunta for de sintaxe, o painel do código e a lista de erros são a metade da página que você quer.

Posso editar o XML na árvore?

Não. A árvore é regenerada a partir da sua entrada conforme você digita, então não há nada persistente para editar. Faça as mudanças no painel do código, à esquerda.

Esse é um limite deliberado. Um editor de árvore precisa decidir, a cada mudança, o que fazer com espaço em branco, conteúdo misto, posição de comentários e ordem dos atributos, e as respostas seguras o deixam desajeitado enquanto as convenientes reescrevem em silêncio partes do documento que você não tocou.

Como encontro um valor específico?

Para uma string literal, ponha o cursor no painel do código e aperte Ctrl-F, ou Cmd-F num Mac. O painel de busca abre no topo com destaque das ocorrências e procura no texto completo, e não no texto truncado que a árvore exibe.

Para qualquer coisa estrutural, use XPath: //order[status="failed"]/@id responde em um passo. Se uma expressão não retorna nada e o elemento claramente está ali, desconfie de um namespace padrão, porque XPath 1.0 não tem nenhum: //order não vai casar com um elemento em urn:example:orders até você vincular um prefixo e escrever //o:order.

Por que meu navegador diz "Esta página contém os seguintes erros" em vez de mostrar o arquivo?

Esse é o visualizador de XML embutido do Chrome ou do Firefox dizendo que o documento não está bem formado. Os dois param no primeiro erro e renderizam o documento só até ali. A mensagem vem do libxml2 ou do Expat e é escrita para quem escreveu o analisador: "Opening and ending tag mismatch", "Document is empty".

Cole o documento aqui e você recebe todos os problemas de uma vez com linha, coluna e sugestão, mais uma árvore para a parte que foi analisada. As causas frequentes são um & solto, um download truncado, uma marca de ordem de bytes antes da declaração XML e uma página de erro HTML entregue com content type de XML.

Que tamanho de arquivo ele consegue abrir?

Até 20 MB, e na prática a árvore fica pesada antes do parsing. Varrer um documento de 1 MB leva cerca de 110 milissegundos; montar um nó recolhível por elemento leva mais tempo quando se chega a dezenas de milhares de elementos.

O teto existe porque nada é enviado: o documento e a árvore dele vivem os dois na memória desta aba. Para arquivos maiores, trabalhe localmente com algo que processe em streaming, como xmllint --shell para navegar ou o iterparse do Python para extrair.

Ferramentas relacionadas

Leitura complementar