Skip to content
datahacks.dev
XML

Well Formed Versus Valid XML and the Difference in Practice

Well formed means the syntax parses, valid means it follows a DTD or XSD. Ten broken files and two schemas run through xmllint show what each catches.

6 min read
Summarize in AI
Flat illustration of two options weighed side by side, with the XML logo.

XML has two levels of correctness, and most of the confusion about it comes from mixing them up. A document can be well formed: it follows the grammar of XML itself, so a parser can read it into a tree. And it can be valid: it also follows a set of rules for one particular kind of document, written in a DTD or an XML Schema. Every valid document is well formed, and most well-formed documents are not valid for anything, because nobody has written the rules.

I ran a batch of small broken files through xmllint to see what each level catches, and what neither does.

Well formed: the syntax rules

A parser has to refuse a document that breaks the grammar, because it cannot build a tree from it. The rules are short, and each one has a failure message. Ten files, each broken in one way, with the first line xmllint --noout printed:

Text
unclosed         Opening and ending tag mismatch: b line 1 and a            <a><b>t</a>
two-roots        Extra content at the end of the document                   <a/><b/>
unquoted-attr    AttValue: " or ' expected                                  <a x=1/>
dup-attr         Attribute x redefined                                      <a x="1" x="2"/>
amp              xmlParseEntityRef: no name                                 <a>Tom & Jerry</a>
lt-in-text       StartTag: invalid element name                             <a>1 < 2</a>
case             Opening and ending tag mismatch: B line 1 and b            <a><B>x</b></a>
no-root          Document is empty                                          (empty file)
text-before      Start tag expected, '<' not found                          text<a/>
space-before-decl XML declaration allowed only at the start of the document  (space, then <?xml ...?>)

The rules that these reflect: tags must be closed and nested properly, there is exactly one root element, attribute values are quoted, attribute names are unique within an element, the characters & and < are reserved in text, names are case sensitive, and the XML declaration, if present, is the very first thing in the file with nothing before it. Not one of these errors is about meaning. They are about whether a tree can be built.

That last rule is the one that surprises people most. A single space or a byte order mark in the wrong place breaks a file that looks fine (content is not allowed in prolog explains the usual cause).

Programming-language parsers apply these rules and nothing more. Python's ElementTree and lxml both refused all ten files (ParseError and XMLSyntaxError) and both accepted the good file and the one that breaks the DTD, whatever the elements are called. Browsers and the XML parsers of other languages follow the same specification.

Valid: the rules for one kind of document

A well-formed catalog can still be nonsense for the system that reads it. My example, a book with no author and a price in a currency the shop does not support:

XML
<catalog><book id="b1"><title>T</title><price currency="GBP">1</price></book></catalog>

xmllint --noout printed nothing: it is well formed. The rules for a catalog live in a DTD:

Text
<!ELEMENT book (title, author+, price, tags?)>
<!ATTLIST price currency (USD|EUR) #REQUIRED>

and checking against it:

Text
$ xmllint --noout --dtdvalid catalog.dtd book.xml
element book: validity error : Element book content does not follow the DTD,
  expecting (title , author+ , price , tags?), got (title price)
element price: validity error : Value "GBP" for attribute currency of price is not among the enumerated set
Document book.xml does not validate against catalog.dtd

Two errors: the missing author, and the currency. The good catalog printed valid against DTD. The DTD is the older mechanism and has limits: it knows element order and counts and a few attribute types, but nothing about the content of price beyond "text".

An XML Schema (XSD) is the more expressive tool. It can say that price is a decimal and that the currency is one of two values:

Text
$ xmllint --noout --schema catalog.xsd bad.xml
Element 'price', attribute 'currency': [facet 'enumeration'] The value 'GBP' is not an element of the set {'USD', 'EUR'}.
bad.xml fails to validate

The message is more specific, with the failed rule named (facet 'enumeration'), and an XSD can check types: a price of twelve fails xs:decimal, an id that repeats fails xs:ID uniqueness. Schemas are also longer to write than DTDs. For anything beyond a toy, or when a partner defines the format, the schema is usually supplied and you validate against it.

What each check protects you from

Well-formedness protects the parser. It guarantees that a tree exists. If you are only reading a file to extract a few values, it is often the only check you want.

Validity protects the consumer. It guarantees the structure that the code downstream assumes: the elements it reads are there, in the right number, with values it can interpret. It is a contract, and the right place to enforce a contract is at the border: when you receive a file from a partner, and when you send one.

Neither of them protects against wrong content that the rules allow. A valid price of 0.01 for a book that costs 12.50 passes everything.

A third kind of check: your own code

Neither level knows your business rules. A valid catalog can list a book at a price of zero, an author that is the empty string, or two books with the same title. Those checks belong in code that runs after the parse, or in Schematron-style rules, which XML has and which few teams use. For most data feeds the pragmatic split is: the parser guarantees well-formedness, a schema (when one exists) guarantees structure, and a short function guarantees that the values make sense. Write all three as tests against a folder of sample files, valid and broken, so the day a partner changes the feed you learn which level failed (a cheat sheet of XML syntax rules lists the rules the first level enforces, and what a DTD is and why you rarely need one now covers the second).

Running the checks

In the command line, xmllint does both:

Shell
xmllint --noout file.xml                          # well-formed only
xmllint --noout --dtdvalid file.dtd file.xml      # against an external DTD
xmllint --noout --schema file.xsd file.xml        # against an XSD

Notice the first line: a document that declares a DOCTYPE with an external DTD was not validated by plain xmllint --noout, which only checks syntax (and my deliberately invalid example passed). Validation has to be requested. In code, lxml provides etree.DTD and etree.XMLSchema objects whose validate() returns a boolean and whose error_log explains failures (parsing and writing XML in Python covers the library). You can also look at a document's structure in the XML formatter before you decide what rules to write.

The specifications are short enough to skim: the XML 1.0 recommendation defines well-formedness and validity in its second section, and XML Schema Part 0: Primer is the gentlest introduction to XSD.

When well formed is as far as you need to go

You are reading a file you trust. A config file in your own repository does not need a schema. The parse error is enough.

The format is loose on purpose. HTML-like documents, user-written notes and extensible vocabularies are checked by code that picks out what it understands and ignores the rest.

The consumer is tolerant. If the code reads price and ignores everything it does not know, a schema adds friction without removing a failure.

You validate by other means. A test suite that parses real samples and asserts on the extracted values is a form of validation, and is often more useful than a schema nobody maintains.

Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.