Entities and Subsets

Entities and Internal versus External Subsets

A DOCTYPE may name an external subset (SYSTEM "file.dtd", or PUBLIC plus a URI) and carry an internal subset in brackets, which is read first and wins. General entities (&shop;) are content macros, internal (a literal) or external (a file). Parameter entities (%text;) are macros for the DTD itself; in the internal subset they may be referenced only between declarations, never inside one, so modular DTDs keep such references in external files:

General, external and parameter entitiesShell
echo 'Returns are free within 30 days.' > returns.txt
echo '<!ELEMENT notice %text;>' > common.dtd
cat > notice.xml <<'XML'
<?xml version="1.0"?>
<!DOCTYPE notice [
  <!ENTITY shop "BookNest">
  <!ENTITY returns SYSTEM "returns.txt">
  <!ENTITY % text "(#PCDATA)">
  <!ENTITY % common SYSTEM "common.dtd">
  %common;
]>
<notice>&shop; ships within 48 hours. &returns;</notice>
XML
xmllint --noent --valid notice.xml | tail -n 2
Output
<notice>BookNest ships within 48 hours. Returns are free within 30 days.
</notice>

--noent expands both entities (the file's newline included). Moving the ELEMENT declaration into the internal subset puts %text; inside a declaration, and xmllint 3,427 stops with "xmlParseElementDecl: 'EMPTY', 'ANY' or '(' expected". Never expand entities in untrusted feeds: they power XXE attacks and the "billion laughs" bomb (XML Security).