XML Tutorial · XML Basics · Lesson 4 of 28

Well-Formed XML

The rules every XML document must follow, what parsers do with mistakes, and how to read parser error messages.

Create a free account to save this page, take notes and track your progress. Sign in

The rules

A document is well-formed when it follows XML's syntax rules. A conforming parser must stop with an error at the first violation; unlike HTML, nothing is silently repaired.

  1. Exactly one root element.
  2. Every start tag is closed, and elements nest properly: <a><b></b></a>, never <a><b></a></b>.
  3. Names match exactly, including case: <Item>…</item> is an error.
  4. Attribute values are quoted, and an attribute appears at most once per element.
  5. < and & in text must be escaped as &lt; and &amp; (see special characters).
  6. The XML declaration, if present, comes first.
  7. The bytes must match the declared encoding (UTF-8 by default).

Well-formed only means "syntactically correct". Valid means it also follows a schema (a DTD or XSD); see the validation section.

Errors in practice

Each broken document below fails at a specific line and column:

What parsers reportTry it yourself
from lxml import etree

broken = {
    "unclosed tag": b"<note><to>Ada</note>",
    "wrong nesting": b"<a><b></a></b>",
    "case mismatch": b"<Item>tea</item>",
    "unquoted attribute": b"<item qty=2>tea</item>",
    "raw ampersand": b"<shop>Fish & Chips</shop>",
    "two roots": b"<a/><b/>",
}
for name, xml in broken.items():
    try:
        etree.fromstring(xml)
    except etree.XMLSyntaxError as e:
        print(f"{name:20} line {e.lineno}, col {e.position[1]}: {e.msg}")

Output

unclosed tag         line 1, col 21: Opening and ending tag mismatch: to line 1 and note, line 1, column 21
wrong nesting        line 1, col 11: Opening and ending tag mismatch: b line 1 and a, line 1, column 11
case mismatch        line 1, col 17: Opening and ending tag mismatch: Item line 1 and item, line 1, column 17
unquoted attribute   line 1, col 11: AttValue: " or ' expected, line 1, column 11
raw ampersand        line 1, col 13: xmlParseEntityRef: no name, line 1, column 13
two roots            line 1, col 5: Extra content at the end of the document, line 1, column 5

Recovering from bad input

Sometimes you must read broken XML from a legacy system. lxml can try to recover, but treat the result with suspicion: data may be lost or misplaced.

Recover mode (use with care)Try it yourself
from lxml import etree

bad = b"<items><item>tea<item>cake</item></items>"
parser = etree.XMLParser(recover=True)
root = etree.fromstring(bad, parser)
print(etree.tostring(root).decode())
print("errors:", len(parser.error_log))

Output

<items><item>tea<item>cake</item></item></items>
errors: 1

Prefer fixing the producer. Never build XML by gluing strings together; use a library that escapes for you (see special characters).

Test yourself

What must a conforming XML parser do when it finds a well-formedness error?

  1. Report a fatal error and stop normal processing
  2. Fix it silently
  3. Skip the element
  4. Convert it to a comment
Show answer

Answer: Report a fatal error and stop normal processing

The XML spec makes well-formedness errors fatal; recovery modes are non-standard extras.

Was this page helpful?