XML Tutorial · Validation · Lesson 8 of 28

DTD: Document Type Definitions

Declaring elements, attributes and entities in a DTD, validating documents against it, and the limits of DTDs.

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

What a DTD declares

A DTD (Document Type Definition) is XML's original schema language, inherited from SGML. It says which elements may appear, in what order, and which attributes they take:

<!DOCTYPE note [
  <!ELEMENT note (to, from, heading?, body)>   <!-- sequence; ? = optional -->
  <!ELEMENT to (#PCDATA)>                       <!-- text only -->
  <!ELEMENT from (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body (#PCDATA | em)*>               <!-- mixed content -->
  <!ELEMENT em (#PCDATA)>
  <!ATTLIST note priority (low | normal | high) "normal"
                 id ID #REQUIRED>
  <!ENTITY company "ReadyCamel Ltd">
]>
Syntax Meaning
(a, b) a then b
(a | b) a or b
a? a* a+ optional, zero or more, one or more
#PCDATA parsed text
EMPTY / ANY no content / anything
#REQUIRED #IMPLIED "default" attribute must appear / is optional / has a default
ID IDREF CDATA (a|b) unique id, reference to an id, any text, enumeration

Validating

Validate against a DTDTry it yourself
from io import StringIO
from lxml import etree

dtd = etree.DTD(StringIO("""
<!ELEMENT note (to, from, heading?, body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
<!ATTLIST note priority (low | normal | high) "normal">
"""))

docs = {
    "valid": b"<note><to>Ada</to><from>Alan</from><body>Lunch?</body></note>",
    "wrong order": b"<note><from>Alan</from><to>Ada</to><body>Lunch?</body></note>",
    "bad attribute": b"<note priority='urgent'><to>Ada</to><from>Alan</from><body>Hi</body></note>",
}
for name, xml in docs.items():
    doc = etree.fromstring(xml)
    ok = dtd.validate(doc)
    print(f"{name}: {'valid' if ok else 'invalid'}")
    for err in dtd.error_log.filter_from_errors():
        print("   ", err.message)
# Defaults apply when the document carries its DTD and the parser is asked to fill them in
parser = etree.XMLParser(attribute_defaults=True)
note = etree.fromstring(b'<!DOCTYPE note [<!ATTLIST note priority (low|normal|high) "normal">]><note/>', parser)
print("default filled in:", note.get("priority"))

Output

valid: valid
wrong order: invalid
    Element note content does not follow the DTD, expecting (to , from , heading? , body), got (from to body)
bad attribute: invalid
    Value "urgent" for attribute priority of note is not among the enumerated set
default filled in: normal

Limits of DTDs

  • No data types: <price> can't be declared numeric, and dates are just text.
  • No namespace support, and the syntax isn't XML.
  • Entities in DTDs are the root of XXE and "billion laughs" attacks (see XML security), so most systems disable DTD processing for untrusted input.

Today DTDs mostly appear in older standards (DocBook, XHTML 1.0) and to define named entities. New projects use XML Schema or RELAX NG.

Test yourself

Which DTD content model means 'one or more item elements'?

  1. (item+)
  2. (item*)
  3. (item?)
  4. (item)
Show answer

Answer: (item+)

  • is one or more, * zero or more, ? optional.

Was this page helpful?