Appearance
Most TEI validation errors fall into four buckets: an element used where the schema forbids it, an xml:id that is duplicated or malformed, a pointer (@ref, @target, @who) that resolves to nothing, and a module that was never enabled. Read the validator's message for the context it names, fix the specific cause, then re-run — and validate against your own ODD-derived schema, not raw tei_all, so the errors that matter actually surface.
How do you read a TEI validation error?
The message tells you more than it seems. A typical Jing/RELAX NG error:
text
edition.xml:142:18: error: element "table" not allowed anywhere;
expected element "p", "list", "head" ...That gives you the line (142), the offending element (table), and the elements that were allowed. Two readings follow: either table is genuinely misplaced, or its module is not enabled so the schema does not know it at all. Always check the named context before assuming the element is wrong.
What does "element not allowed here" really mean?
This is the most common error, and it has two distinct causes:
- Wrong location — the element exists but its parent forbids it. For example,
lbdirectly insidebodyinstead of inside a block. Fix by moving it to a permitted parent. - Module not enabled —
table,figure,appand others live in optional modules. If your customisation omits the module, the element is unknown. Fix by adding themoduleRefto your ODD.
A quick way to tell them apart: if tei_all accepts the document but your project schema rejects it, it is a customisation/module issue, not a placement one.
Why do xml:id and pointer errors happen so often?
Identifiers and references are the second big cluster. The rules are strict:
xml:idmust be unique in the document and a valid XML name — no spaces, no colon, not starting with a digit.- Every pointer (
@ref,@target,@who,@corresp,@next) must resolve to an existing id or valid URI.
Catch both classes in one pass:
bash
# duplicate xml:id values
xmllint --xpath "//@xml:id[. = preceding::*/@xml:id]" doc.xml
# pointers that resolve to nothing
xmllint --xpath \
"//@who[not(//@xml:id = substring-after(.,'#'))]" doc.xmlA leading-digit id (id="1841letter") is invalid; prefix it (l1841letter). Dangling pointers usually mean a typo or a target element you forgot to add.
A triage table for the usual suspects
| Error message (paraphrased) | Likely cause | Fix |
|---|---|---|
| element X not allowed here | wrong parent or module off | move element / add moduleRef |
| duplicate ID value | repeated xml:id | make ids unique |
| ID value is not a name | id starts with digit / has space | prefix or rename |
| IDREF does not resolve | dangling @ref/@target | fix typo or add target |
| required attribute missing | ODD makes attr mandatory | add the attribute |
| unexpected attribute | banned by your customisation | remove or allow in ODD |
Keep this taped to your monitor and most errors resolve in under a minute.
Well-formed versus valid: which check first?
Always fix well-formedness first — a parser cannot validate XML it cannot even parse. Well-formed means tags are closed and properly nested; valid means it also obeys the schema. The order:
bash
# 1. well-formedness only
xmllint --noout doc.xml
# 2. schema validity against your project schema
jing project-schema.rng doc.xml
# 3. extra rules
jing -i schematron-rules.sch doc.xmlAn unclosed tag at line 12 will cascade into dozens of phantom errors below it; fix the first reported syntax error and re-run before touching anything else.
Why validate against your own schema, not tei_all?
tei_all permits almost every element and attribute, so it is a permissive net that lets real problems through. Your ODD-derived schema is where you encode the rules that matter to your project: a required @xml:lang, a banned element, a controlled @type list. Validating against tei_all and declaring victory is the single most common reason inconsistent encoding ships. Generate a RELAX NG plus Schematron pair from your ODD with roma or oxgarage, and validate against that.
What belongs in Schematron rather than the schema?
RELAX NG cannot express every constraint. Push these into Schematron, which TEI ODD lets you embed:
xml
<sch:rule context="tei:date">
<sch:assert test="not(@notBefore) or not(@notAfter)
or @notBefore <= @notAfter">
notBefore must not be later than notAfter.
</sch:assert>
</sch:rule>Use Schematron for cross-attribute checks, conditional requirements (if @cert='low' then a note is required), and uniqueness rules — the defensible, project-specific quality gate.
Key Takeaways
- Read the validator message for line, element and allowed context before guessing.
- "Not allowed here" means either wrong parent or a disabled module —
tei_alltells them apart. xml:idmust be unique and a legal XML name; every pointer must resolve to a real id.- Fix well-formedness before validity; one unclosed tag spawns many false errors.
- Validate against your ODD-derived schema, never raw
tei_all, to catch what matters. - Encode project-specific and cross-attribute rules in embedded Schematron.
Frequently Asked Questions
What causes the 'element X not allowed here' TEI error?
Usually the element is real but in the wrong place, or its module is not enabled in your schema. Check the parent the element appears in and confirm the relevant module is referenced in your ODD; the error names the context where the element was rejected.
Why does my xml:id cause a validation error?
xml:id values must be unique across the whole document and must be valid XML names — no spaces, no leading digit, no colons. Duplicate ids and ids beginning with a number are the two most common offenders.
How do I fix a pointer that does not resolve?
Every reference like @target, @ref, @who or @corresp must point at an existing @xml:id (or valid URI). Run a check that each pointer minus its hash matches a real id, and fix typos or missing target elements.
What is the difference between well-formed and valid in TEI?
Well-formed means the XML syntax is correct (tags closed and nested). Valid means it also obeys your schema's rules about which elements and attributes are allowed where. A document can be well-formed but invalid.
Should I validate against tei_all or my own schema?
Validate against your project's ODD-derived schema, not raw tei_all. tei_all permits almost everything, so it hides errors your project rules would catch, such as a missing required attribute or a banned element.
How do I add custom rules beyond what the schema checks?
Use Schematron for constraints a RELAX NG schema cannot express — uniqueness across attributes, conditional requirements, or value cross-checks. TEI ODD lets you embed Schematron rules directly in your customisation.