Visualizzatore XML
Albero comprimibile e sorgente, uno accanto all’altro.
La struttura del documento compare qui appena incolli qualcosa.
Tutto viene eseguito in questa scheda. Nulla di ciò che incolli viene caricato, registrato o inviato da qualche parte. Apri il pannello di rete e verifica.
Incolla un documento qui sopra e comparirà due volte: il sorgente a sinistra con evidenziazione della sintassi, numeri di riga e piegatura, e un albero comprimibile a destra costruito dalla stessa analisi. Tutti i nodi partono espansi; clicca su un elemento per comprimerlo insieme a tutto ciò che contiene. I due riquadri vengono da un’unica scansione, quindi l’albero e l’elenco degli errori descrivono sempre lo stesso documento.
Una vista ad albero si guadagna il posto quando il documento non l’hai scritto tu. La risposta di un’API di un fornitore la cui documentazione è un PDF, una configurazione da 4.000 righe lasciata da un team precedente, un export che dovrebbe contenere un record e forse ne contiene duecento. La domanda di solito non è dove sia l’errore di sintassi, ma che forma abbia questa cosa e dove viva il valore che ti serve.
Convalida mentre leggi, segnalando ogni problema di buona formazione con riga, colonna e un rimedio suggerito invece di fermarsi al primo. E nulla viene caricato: il parser, l’albero e l’editor girano tutti in questa scheda.
Che cosa mostra l’albero
Ogni elemento è una riga: il nome del tag così com’è scritto, prefisso compreso, seguito dai suoi attributi come coppie nome="valore". Un elemento senza figli è marcato "vuoto", così distingui <status/> da un nodo che hai compresso. Il testo compare come foglia sotto il suo elemento, troncato a 200 caratteri, perché un albero serve alla struttura e un blob base64 da 40 KB reso per intero la seppellirebbe. I commenti compaiono come riga segnaposto.
Due cose deliberatamente non compaiono: le sezioni CDATA e le istruzioni di elaborazione. Per quelle leggi il riquadro del sorgente; mostra il documento esattamente come l’hai incollato ed è sempre l’autorità. L’albero è vero DOM, un nodo comprimibile per elemento, ed è questo che rende la compressione istantanea e il testo selezionabile. È anche il suo costo: un documento con decine di migliaia di elementi impiega più tempo a costruire l’albero che ad analizzare l’XML.
A che cosa serve un albero, e in che cosa è scarso
Un albero risponde alle domande sulla forma più in fretta di quanto farà mai lo scorrimento. Comprimi tutto un livello più sotto e le sezioni di primo livello stanno in una schermata. Comprimi un Header SOAP e il Body sale in cima al riquadro. Guarda le righe ripetute sotto un contenitore e sai se la risposta contiene un record o molti, che è la differenza fra un consumatore che funziona in test e uno che perde dati in produzione. È scarso in tre cose, e dirlo è più utile che fingere il contrario.
- Modificare. L’albero viene rigenerato dal tuo input a ogni tasto, quindi non c’è nulla in cui scrivere. Modifica nel riquadro del sorgente e guarda l’albero ricostruirsi.
- Confrontare. Due alberi affiancati sono un modo peggiore di trovare una differenza rispetto a un diff che prima normalizza la formattazione su entrambi i lati.
- Estrarre. "Ogni prezzo sotto ogni riga d’ordine" è un’espressione XPath, non un esercizio di scorrimento.
Annidamento profondo, e perché i prefissi sono smorzati
L’XML con namespace è difficile da leggere per una ragione che nulla ha a che vedere con la complessità dei namespace. In soap:Body e ns1:PlaceOrder il prefisso è impalcatura: dice a quale vocabolario appartiene il nome e, una volta che lo sai, è rumore su ogni riga successiva. A otto livelli di indentazione, buona parte dei caratteri sullo schermo sono prefissi e due punti.
Perciò l’editor disegna il prefisso e i suoi due punti con opacità ridotta rispetto al nome locale, e soap:Body si legge come Body con un contrassegno attaccato. Nulla è nascosto o alterato; il testo resta selezionabile e si copia intatto. L’eccezione è una dichiarazione: in xmlns:soap il prefisso è il contenuto e non l’impalcatura, quindi resta a piena intensità. Un prefisso usato senza essere mai stato associato viene segnalato come errore, con la dichiarazione da aggiungere: è il risultato tipico di un frammento copiato da un documento più grande.
Trovare un valore
Per una stringa letterale, metti il cursore nel riquadro del sorgente e premi Ctrl-F, o Cmd-F su un Mac. Il pannello di ricerca dell’editor si apre in alto con evidenziazione delle occorrenze e campo di sostituzione, e cerca nel sorgente, che contiene il testo completo di ogni nodo, comprese le parti che l’albero tronca.
Per una domanda strutturale usa XPath. //order[status="failed"]/@id risponde in un passo a ciò che lo scorrimento risolve in venti, e il tester XPath di questo sito valuta XPath 1.0 sul tuo documento con i prefissi raccolti da esso. Un dettaglio spiega quasi tutte le segnalazioni del tipo "la mia espressione non restituisce nulla": XPath 1.0 non ha namespace predefinito. Se la radice dichiara xmlns="urn:example:orders", allora //order non corrisponde a nulla, perché //order significa un elemento order senza namespace. Associa un prefisso e scrivi //o:order, oppure ripiega su //*[local-name()="order"].
Percorrere un documento da codice
Leggere un documento a mano è di solito un passo verso lo scrivere il codice che lo legge ogni giorno. Questi esempi percorrono un documento e ne stampano la struttura, l’equivalente programmatico dell’albero qui sopra, e ognuno analizza in modo sicuro, perché le impostazioni predefinite di Java e Python risolvono le entità esterne.
// 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.xmlLo schema con grep nell’esempio shell è un trucco di conteggio, non un’analisi: conterà volentieri un nome di tag che compare dentro un commento o una sezione CDATA. Tutti gli altri esempi costruiscono prima un albero, che è la differenza fra una risposta approssimativa e una corretta.
Domande frequenti
Il mio XML viene caricato per essere visualizzato?
No. Il parser, l’albero e l’editor sono JavaScript che gira in questa scheda. Nessun componente lato server, nessun analytics con accesso all’editor, nessuno script di terze parti. Apri la scheda Rete e incolla un documento: la pagina carica le proprie risorse una volta e poi tace.
Quel controllo conta qui perché guardare è esattamente ciò che si fa con i dati altrui. Il documento che si incolla in un visualizzatore è di solito una risposta di API reale o un export di produzione, con nomi, numeri di conto o un token in un header. Diversi strumenti ben posizionati su questa ricerca lo mandano a un server, e uno rende pubblici per impostazione predefinita i documenti salvati.
A che cosa serve davvero una vista ad albero?
A rispondere a domande sulla struttura: quali elementi si ripetono, quanto va in profondità l’annidamento, in quale ramo vive un valore, se un elemento facoltativo è presente in questa risposta. Nel testo indentato è difficile da vedere, nell’albero è immediato.
Il flusso che si ripaga è: comprimi tutto un livello sotto la radice, leggi i nomi delle sezioni, espandi quella che ti interessa, ignora le altre 3.000 righe. Se invece la domanda riguarda la sintassi, la metà di pagina che ti serve è il riquadro del sorgente con l’elenco degli errori.
Posso modificare l’XML nell’albero?
No. L’albero viene rigenerato dal tuo input mentre scrivi, quindi non c’è nulla di persistente da modificare. Fai le modifiche nel riquadro del sorgente, a sinistra.
È un limite voluto. Un editor ad albero deve decidere a ogni modifica che cosa fare di spazi, contenuto misto, posizione dei commenti e ordine degli attributi; le risposte prudenti lo rendono scomodo, quelle comode riscrivono in silenzio parti del documento che non hai toccato.
Come trovo un valore preciso?
Per una stringa letterale, metti il cursore nel riquadro del sorgente e premi Ctrl-F, o Cmd-F su un Mac. Il pannello di ricerca si apre in alto con l’evidenziazione, e cerca nel testo completo invece che nel testo troncato mostrato dall’albero.
Per qualsiasi cosa strutturale usa XPath: //order[status="failed"]/@id risponde in un passo. Se un’espressione non restituisce nulla e l’elemento è chiaramente lì, sospetta un namespace predefinito, perché XPath 1.0 non ne ha: //order non corrisponderà a un elemento in urn:example:orders finché non associ un prefisso e scrivi //o:order.
Perché il browser dice "Questa pagina contiene i seguenti errori" invece di mostrare il file?
È il visualizzatore XML integrato di Chrome o Firefox che ti dice che il documento non è ben formato. Entrambi si fermano al primo errore e rendono il documento solo fino a quel punto. Il messaggio viene da libxml2 o Expat ed è formulato per chi ha scritto il parser: "Opening and ending tag mismatch", "Document is empty".
Incolla il documento qui e ottieni tutti i problemi in una volta con riga, colonna e suggerimento, più un albero per la parte che è stata analizzata. Le cause frequenti sono una e commerciale nuda, un download troncato, un byte order mark prima della dichiarazione XML e una pagina di errore HTML servita con un content type XML.
Quanto può essere grande il file da aprire?
Fino a 20 MB, e in pratica l’albero diventa pesante prima dell’analisi. Scansionare un documento da 1 MB richiede circa 110 millisecondi; costruire un nodo comprimibile per elemento richiede di più quando si arriva a decine di migliaia di elementi.
Il tetto esiste perché nulla viene caricato: il documento e il suo albero vivono entrambi nella memoria di questa scheda. Per file più grandi lavora in locale con qualcosa che elabori in streaming, come xmllint --shell per esplorare o iterparse di Python per estrarre.