Skip to content
TEI & XML Encoding

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:

  1. Wrong location — the element exists but its parent forbids it. For example, lb directly inside body instead of inside a block. Fix by moving it to a permitted parent.
  2. Module not enabledtable, figure, app and others live in optional modules. If your customisation omits the module, the element is unknown. Fix by adding the moduleRef to 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:id must 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.xml

A 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 causeFix
element X not allowed herewrong parent or module offmove element / add moduleRef
duplicate ID valuerepeated xml:idmake ids unique
ID value is not a nameid starts with digit / has spaceprefix or rename
IDREF does not resolvedangling @ref/@targetfix typo or add target
required attribute missingODD makes attr mandatoryadd the attribute
unexpected attributebanned by your customisationremove 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.xml

An 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 &lt;= @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_all tells them apart.
  • xml:id must 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.