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.
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.
- Exactly one root element.
- Every start tag is closed, and elements nest properly:
<a><b></b></a>, never<a><b></a></b>. - Names match exactly, including case:
<Item>…</item>is an error. - Attribute values are quoted, and an attribute appears at most once per element.
<and&in text must be escaped as<and&(see special characters).- The XML declaration, if present, comes first.
- 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:
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.
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?
Report a fatal error and stop normal processingFix it silentlySkip the elementConvert 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.