XML Viewer
Collapsible tree and source, side by side.
The document structure appears here once you paste something.
Everything runs in this tab. Nothing you paste is uploaded, logged or sent anywhere. Open your network panel and check.
Paste a document above and it appears twice: the source on the left with syntax highlighting, line numbers and folding, and a collapsible tree on the right built from the same parse. Every node starts expanded; click an element to collapse it and everything beneath it. Both panes come from one scan, so the tree and the error list always describe the same document.
A tree view earns its place when you did not write the document. An API response from a vendor whose documentation is a PDF, a 4,000-line config a previous team left behind, an export supposed to hold one record that might hold two hundred. The question is not usually where the syntax error is, it is what shape this thing has and where the value you need lives.
It validates while you read, reporting every well-formedness problem with a line, a column and a suggested fix rather than stopping at the first. And nothing is uploaded: the parser, the tree and the editor all run in this tab.
What the tree shows
Each element is one row: the tag name as written, prefix included, followed by its attributes as name="value" pairs. An element with no children is marked "empty", so you can tell <status/> apart from a node you collapsed. Text appears as a leaf under its element, truncated at 200 characters, because a tree is for structure and a 40 KB base64 blob rendered in full would bury it. Comments show as a placeholder row.
Two things deliberately do not appear: CDATA sections and processing instructions. Read the source pane for those; it shows the document exactly as you pasted it and is always the authority. The tree is real DOM, one collapsible node per element, which is what makes collapsing instant and text selectable. It is also the cost: a document with tens of thousands of elements spends longer building the tree than parsing the XML.
What a tree is good for, and what it is bad at
A tree answers shape questions faster than scrolling ever will. Collapse everything one level down and the top-level sections fit on one screen. Collapse a SOAP Header and the Body moves to the top of the pane. Look at the repeated rows under a container and you know whether the response holds one record or many, which is the difference between a consumer that works in testing and one that drops data in production. It is bad at three things, and saying so is more useful than pretending otherwise.
- Editing. The tree is regenerated from your input on every keystroke, so there is nothing to type into. Edit in the source pane and watch the tree rebuild.
- Comparing. Two trees side by side is a worse way to find a difference than a diff that normalises formatting on both sides first.
- Extracting. "Every price under every line item" is an XPath expression, not a scrolling exercise.
Deep nesting, and why prefixes are dimmed
Namespaced XML is hard to read for a reason unrelated to namespaces being complicated. In soap:Body and ns1:PlaceOrder the prefix is scaffolding: it says which vocabulary the name belongs to, and once you know that it is noise on every subsequent line. At eight levels of indentation, a large share of the characters on screen are prefixes and colons.
So the editor renders the prefix and its colon at reduced opacity against the local name, and soap:Body reads as Body with a marker attached. Nothing is hidden or altered; the text is still selectable and copies intact. The exception is a declaration, where in xmlns:soap the prefix is the payload rather than the scaffolding, so it stays at full strength. A prefix used without ever being bound is reported as an error with the declaration you need to add, which is the usual result of copying a fragment out of a larger document.
Finding a value
For a literal string, put the cursor in the source pane and press Ctrl-F, or Cmd-F on a Mac. The editor's search panel opens at the top with match highlighting and a replace field, and it searches the source, which holds the full text of every node including the parts the tree truncates.
For a structural question, use XPath. //order[status="failed"]/@id answers in one step what scrolling answers in twenty, and the XPath tester on this site evaluates XPath 1.0 against your document with the prefixes harvested from it. One detail accounts for most "my expression returns nothing" reports: XPath 1.0 has no default namespace. If the root declares xmlns="urn:example:orders", then //order matches nothing, because //order means an order element in no namespace. Bind a prefix and write //o:order, or fall back to //*[local-name()="order"].
Walking a document in code
Reading a document by hand is usually a step towards writing the code that reads it every day. These samples walk a document and print its structure, the programmatic equivalent of the tree above, and each parses safely because the defaults in Java and Python resolve external entities.
// 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.xmlThe grep outline in the shell sample is a counting trick, not a parse: it will happily count a tag name that appears inside a comment or a CDATA section. Every other sample builds a tree first, which is the difference between a rough answer and a correct one.
Common questions
Is my XML uploaded to view it?
No. The parser, the tree and the editor are JavaScript running in this tab. There is no server-side component, no analytics with access to the editor and no third-party script. Open the Network tab and paste a document: the page loads its own assets once and then goes quiet.
That check matters here because viewing is what you do with other people's data. The document you paste into a viewer is usually a live API response or a production export, complete with names, account numbers or a token in a header. Several tools ranking for this search send it to a server, and one makes saved documents public by default.
What is a tree view actually for?
Answering questions about structure: which elements repeat, how deep the nesting goes, which branch a value lives in, whether an optional element is present in this response. Those are hard to see in indented text and immediate in a tree.
The workflow that pays for itself is: collapse everything one level below the root, read the section names, expand the one you care about, ignore the other 3,000 lines. If the question is about syntax instead, the source pane and the error list are the half of the page you want.
Can I edit the XML in the tree?
No. The tree is regenerated from your input as you type, so there is nothing persistent to edit. Make changes in the source pane on the left.
This is a deliberate limit. A tree editor has to decide what to do about whitespace, mixed content, comment placement and attribute order on every change, and the safe answers make it clumsy while the convenient ones quietly rewrite parts of the document you did not touch.
How do I find a specific value?
For a literal string, put the cursor in the source pane and press Ctrl-F, or Cmd-F on a Mac. The search panel opens at the top with match highlighting, and it searches the full text rather than the truncated text the tree displays.
For anything structural, use XPath: //order[status="failed"]/@id answers in one step. If an expression returns nothing and the element is clearly there, suspect a default namespace, because XPath 1.0 has none: //order will not match an element in urn:example:orders until you bind a prefix and write //o:order.
Why does my browser say "This page contains the following error(s)" instead of showing the file?
That is Chrome or Firefox's built-in XML viewer telling you the document is not well-formed. Both stop at the first error and render the document only up to that point. The message comes from libxml2 or Expat and is phrased for whoever wrote the parser: "Opening and ending tag mismatch", "Document is empty".
Paste the document here and you get every problem at once with a line, a column and a suggestion, plus a tree for the part that did parse. The frequent causes are a raw ampersand, a truncated download, a byte-order mark before the XML declaration, and an HTML error page delivered with an XML content type.
How big a file can it open?
Up to 20 MB, and in practice the tree gets heavy before the parse does. Scanning a 1 MB document takes around 110 milliseconds; building one collapsible node per element takes longer once you are into tens of thousands of elements.
The ceiling exists because nothing is uploaded: the document and its tree both live in this tab's memory. For larger files, work locally with something that streams, such as xmllint --shell for browsing or Python's iterparse for extraction.