XML-Viewer

Klappbarer Baum und Quelltext nebeneinander.

Eingabe
BaumAuf einen Knoten klicken, um ihn einzuklappen

Die Dokumentstruktur erscheint hier, sobald Sie etwas einfügen.

WartetFügen Sie ein Dokument ein. Die Prüfung läuft während der Eingabe.

Alles läuft in diesem Tab. Nichts, was Sie einfügen, wird hochgeladen, protokolliert oder irgendwohin gesendet. Öffnen Sie Ihr Netzwerkpanel und prüfen Sie es.

Fügen Sie oben ein Dokument ein, und es erscheint zweimal: links der Quelltext mit Syntaxhervorhebung, Zeilennummern und Faltung, rechts ein klappbarer Baum, der aus demselben Parse-Vorgang stammt. Alle Knoten starten aufgeklappt; ein Klick auf ein Element klappt es samt allem darunter zu. Beide Bereiche kommen aus einem einzigen Durchlauf, Baum und Fehlerliste beschreiben also immer dasselbe Dokument.

Eine Baumansicht verdient ihren Platz, wenn Sie das Dokument nicht selbst geschrieben haben. Eine API-Antwort eines Anbieters, dessen Dokumentation ein PDF ist, eine 4.000-zeilige Konfiguration, die ein früheres Team hinterlassen hat, ein Export, der einen Datensatz enthalten sollte und vielleicht zweihundert enthält. Die Frage ist meist nicht, wo der Syntaxfehler steckt, sondern welche Form dieses Ding hat und wo der Wert liegt, den Sie brauchen.

Es prüft, während Sie lesen, und meldet jedes Wohlgeformtheitsproblem mit Zeile, Spalte und einem Lösungsvorschlag, statt beim ersten aufzuhören. Und nichts wird hochgeladen: Parser, Baum und Editor laufen alle in diesem Tab.

Was der Baum zeigt

Jedes Element ist eine Zeile: der Tag-Name so, wie er dasteht, samt Präfix, gefolgt von seinen Attributen als name="wert"-Paare. Ein Element ohne Kinder wird als „leer“ markiert, Sie unterscheiden also <status/> von einem Knoten, den Sie zugeklappt haben. Text erscheint als Blatt unter seinem Element, nach 200 Zeichen abgeschnitten, denn ein Baum dient der Struktur, und ein vollständig gerendertes 40-KB-Base64-Blob würde sie begraben. Kommentare erscheinen als Platzhalterzeile.

Zwei Dinge erscheinen bewusst nicht: CDATA-Abschnitte und Verarbeitungsanweisungen. Dafür lesen Sie den Quelltextbereich; er zeigt das Dokument genau so, wie Sie es eingefügt haben, und ist immer die maßgebliche Instanz. Der Baum ist echtes DOM, ein klappbarer Knoten je Element – deshalb ist das Zuklappen sofort da und der Text markierbar. Das ist zugleich der Preis: Bei Zehntausenden Elementen dauert der Aufbau des Baums länger als das Parsen des XML.

Wofür ein Baum taugt und wofür nicht

Ein Baum beantwortet Formfragen schneller, als Scrollen es je könnte. Klappen Sie alles eine Ebene tiefer zu, und die obersten Abschnitte passen auf einen Bildschirm. Klappen Sie einen SOAP-Header zu, und der Body rückt an den Anfang. Sehen Sie sich die wiederholten Zeilen unter einem Container an, und Sie wissen, ob die Antwort einen Datensatz enthält oder viele – der Unterschied zwischen einem Konsumenten, der im Test funktioniert, und einem, der in Produktion Daten verliert. Drei Dinge kann er schlecht, und das zu sagen ist nützlicher, als das Gegenteil zu behaupten.

  • Bearbeiten. Der Baum wird bei jedem Tastendruck aus Ihrer Eingabe neu erzeugt, es gibt also nichts, in das man tippen könnte. Bearbeiten Sie im Quelltextbereich und sehen Sie dem Baum beim Neuaufbau zu.
  • Vergleichen. Zwei Bäume nebeneinander sind ein schlechterer Weg, einen Unterschied zu finden, als ein Diff, der zuerst auf beiden Seiten die Formatierung normalisiert.
  • Extrahieren. „Jeder Preis unter jeder Position“ ist ein XPath-Ausdruck, keine Scroll-Übung.

Tiefe Verschachtelung, und warum Präfixe blasser sind

XML mit Namensräumen ist aus einem Grund schwer zu lesen, der nichts damit zu tun hat, dass Namensräume kompliziert wären. In soap:Body und ns1:PlaceOrder ist das Präfix Gerüst: Es sagt, zu welchem Vokabular der Name gehört, und wenn man das einmal weiß, ist es in jeder weiteren Zeile Rauschen. Bei acht Einrückungsebenen ist ein großer Teil der Zeichen auf dem Bildschirm Präfix und Doppelpunkt.

Der Editor zeichnet Präfix und Doppelpunkt daher mit geringerer Deckkraft neben dem lokalen Namen, und soap:Body liest sich als Body mit angehängter Markierung. Nichts wird versteckt oder verändert; der Text bleibt markierbar und wird unverändert kopiert. Die Ausnahme ist eine Deklaration: In xmlns:soap ist das Präfix die Nutzlast und nicht das Gerüst, es bleibt also in voller Stärke. Ein Präfix, das benutzt, aber nie gebunden wurde, wird als Fehler gemeldet, samt der Deklaration, die fehlt – das übliche Ergebnis, wenn ein Fragment aus einem größeren Dokument kopiert wurde.

Einen Wert finden

Für eine wörtliche Zeichenkette setzen Sie den Cursor in den Quelltextbereich und drücken Strg-F, auf dem Mac Cmd-F. Das Suchpanel des Editors öffnet sich oben, mit Trefferhervorhebung und Ersetzen-Feld, und es durchsucht den Quelltext, der den vollständigen Text jedes Knotens enthält – auch die Teile, die der Baum abschneidet.

Für eine strukturelle Frage nehmen Sie XPath. //order[status="failed"]/@id beantwortet in einem Schritt, wofür Scrollen zwanzig braucht, und der XPath-Tester auf dieser Seite wertet XPath 1.0 gegen Ihr Dokument aus, mit den daraus geernteten Präfixen. Ein Detail erklärt die meisten Meldungen der Sorte „mein Ausdruck liefert nichts“: XPath 1.0 kennt keinen Standard-Namensraum. Deklariert die Wurzel xmlns="urn:example:orders", trifft //order auf nichts zu, denn //order meint ein order-Element in keinem Namensraum. Binden Sie ein Präfix und schreiben Sie //o:order, oder weichen Sie auf //*[local-name()="order"] aus.

Ein Dokument in Code durchlaufen

Ein Dokument von Hand zu lesen ist meist ein Schritt hin zu dem Code, der es täglich liest. Diese Beispiele durchlaufen ein Dokument und geben seine Struktur aus – das programmatische Gegenstück zum Baum oben – und jedes parst sicher, weil die Standardeinstellungen in Java und Python externe Entities auflösen.

// 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

Die grep-Gliederung im Shell-Beispiel ist ein Zähltrick, kein Parse: Sie zählt bereitwillig einen Tag-Namen mit, der in einem Kommentar oder einem CDATA-Abschnitt steht. Jedes andere Beispiel baut zuerst einen Baum – das ist der Unterschied zwischen einer groben und einer richtigen Antwort.

Häufige Fragen

Wird mein XML zum Anzeigen hochgeladen?

Nein. Parser, Baum und Editor sind JavaScript, das in diesem Tab läuft. Kein serverseitiger Teil, keine Analytics mit Zugriff auf den Editor, kein Skript Dritter. Öffnen Sie den Netzwerk-Tab und fügen Sie ein Dokument ein: Die Seite lädt ihre eigenen Assets einmal und wird dann still.

Diese Prüfung zählt hier, weil Ansehen genau das ist, was man mit fremden Daten tut. Das Dokument, das man in einen Viewer einfügt, ist meist eine echte API-Antwort oder ein Produktionsexport, samt Namen, Kontonummern oder einem Token im Header. Mehrere Werkzeuge, die für diese Suche ranken, schicken es an einen Server, und eines macht gespeicherte Dokumente standardmäßig öffentlich.

Wofür ist eine Baumansicht eigentlich gut?

Um Fragen zur Struktur zu beantworten: welche Elemente sich wiederholen, wie tief die Verschachtelung geht, in welchem Zweig ein Wert liegt, ob ein optionales Element in dieser Antwort vorhanden ist. Im eingerückten Text ist das schwer zu sehen, im Baum sofort.

Der Ablauf, der sich bezahlt macht: alles eine Ebene unter der Wurzel zuklappen, die Abschnittsnamen lesen, den interessanten aufklappen, die anderen 3.000 Zeilen ignorieren. Geht es dagegen um Syntax, sind Quelltextbereich und Fehlerliste die Hälfte der Seite, die Sie wollen.

Kann ich das XML im Baum bearbeiten?

Nein. Der Baum wird beim Tippen aus Ihrer Eingabe neu erzeugt, es gibt also nichts Dauerhaftes zu bearbeiten. Nehmen Sie Änderungen im Quelltextbereich links vor.

Das ist eine bewusste Grenze. Ein Baumeditor muss bei jeder Änderung entscheiden, was mit Leerraum, gemischtem Inhalt, Kommentarplatzierung und Attributreihenfolge geschieht; die sicheren Antworten machen ihn umständlich, die bequemen schreiben still Teile des Dokuments um, die Sie nicht angefasst haben.

Wie finde ich einen bestimmten Wert?

Für eine wörtliche Zeichenkette setzen Sie den Cursor in den Quelltextbereich und drücken Strg-F, auf dem Mac Cmd-F. Das Suchpanel öffnet sich oben mit Trefferhervorhebung und durchsucht den vollständigen Text statt des gekürzten, den der Baum anzeigt.

Für alles Strukturelle nehmen Sie XPath: //order[status="failed"]/@id beantwortet es in einem Schritt. Liefert ein Ausdruck nichts, obwohl das Element sichtbar da ist, verdächtigen Sie einen Standard-Namensraum: XPath 1.0 hat keinen, und //order trifft erst dann auf ein Element in urn:example:orders zu, wenn Sie ein Präfix binden und //o:order schreiben.

Warum sagt mein Browser „Diese Seite enthält folgende Fehler“, statt die Datei anzuzeigen?

Das ist der eingebaute XML-Viewer von Chrome oder Firefox, der Ihnen sagt, dass das Dokument nicht wohlgeformt ist. Beide hören beim ersten Fehler auf und zeigen das Dokument nur bis dorthin. Die Meldung stammt von libxml2 oder Expat und ist für den formuliert, der den Parser geschrieben hat: „Opening and ending tag mismatch“, „Document is empty“.

Fügen Sie das Dokument hier ein, und Sie bekommen alle Probleme auf einmal mit Zeile, Spalte und Vorschlag, dazu einen Baum für den Teil, der geparst werden konnte. Häufige Ursachen: ein rohes Und-Zeichen, ein abgebrochener Download, eine Byte-Order-Mark vor der XML-Deklaration und eine HTML-Fehlerseite, die mit einem XML-Content-Type ausgeliefert wurde.

Wie große Dateien kann es öffnen?

Bis 20 MB, und in der Praxis wird der Baum schwer, bevor das Parsen es wird. Ein 1-MB-Dokument zu scannen dauert rund 110 Millisekunden; je einen klappbaren Knoten pro Element zu bauen dauert länger, sobald man bei Zehntausenden Elementen ist.

Die Grenze existiert, weil nichts hochgeladen wird: Dokument und Baum liegen beide im Speicher dieses Tabs. Für größere Dateien arbeiten Sie lokal mit etwas, das streamt, etwa xmllint --shell zum Stöbern oder Pythons iterparse zum Extrahieren.

Verwandte Werkzeuge

Zum Weiterlesen