Parse modes and binding

pyxsd does two jobs at once: it validates a document against its schema, and it maps the document into Python objects. Those jobs can pull in opposite directions. A report you can trust wants every problem reported; a mapping tool wants usable objects even when a document is a little off.

Parse modes resolve that tension by separating the two:

The validation report is always strict. A parse mode only changes what value is bound into the tree.

No mode suppresses an issue. PyXSD.report looks the same in strict and lax mode; what differs is whether an invalid or unrecognized piece of the document is dropped or kept in a best-effort form.

Using a mode

Pass a preset to PyXSD:

from pyxsd import PyXSD, ParseModes

app = PyXSD("document.xml", xsdFile="schema.xsd", mode=ParseModes.LAX)

or from the CLI:

$ pyxsd -i document.xml -s schema.xsd --mode lax -o out.xml
$ pyxsd -i document.xml -s schema.xsd --namespaces strict -o out.xml

mode accepts any BindingPolicy, so a preset is just a convenient starting point:

from pyxsd import BindingPolicy

mode = ParseModes.LAX.replace(whitespace="compat")
app = PyXSD("document.xml", xsdFile="schema.xsd", mode=mode)

Presets

Preset

invalid_value

unresolved_type

undeclared_content

whitespace

namespaces

ParseModes.STRICT (default)

drop

error

error

xsd

legacy

ParseModes.LAX

raw

generic

generic

xsd

legacy

ParseModes.NAMESPACED

drop

error

error

xsd

strict

Policy fields

Field

Values

Effect

invalid_value

"drop" | "raw"

drop binds None for a value that fails lexical validation (the problem is reported). raw binds the original text as a plain string, so no data is lost.

unresolved_type

"error" | "generic"

An element whose declared type cannot be resolved is reported in both cases; generic also binds the subtree through the wildcard pass-through path instead of dropping it.

undeclared_content

"error" | "generic"

An element that no content-model particle accepts is reported in both cases; generic also binds it so unrecognized markup survives.

whitespace

"xsd" | "compat"

xsd applies XSD 1.0 whitespace processing (space, tab, CR, LF only). compat additionally folds other Unicode whitespace (for example NBSP), matching Python’s own str.strip.

namespaces

"legacy" | "strict"

legacy keys schema components and instance nodes by local name, the historical no-namespace behavior. strict performs namespace-aware validation: expanded-name component identity, form-default-aware instance matching, prefixed type/ref/xsi:type resolution, cross-namespace imports, wildcard namespace + processContents enforcement, and QName value-space identity. See ParseModes.NAMESPACED.

Namespaces

Namespace handling is a policy field, so it composes with the other choices:

# namespace-aware validation, but keep binding lenient
mode = ParseModes.LAX.replace(namespaces="strict")
app = PyXSD("document.xml", xsdFile="schema.xsd", mode=mode)

The default remains legacy so existing callers and output are unchanged. In legacy mode a prefixed name is reduced to its local part and namespaces are ignored during instance matching. In strict mode pyxsd records the in-scope namespace bindings while parsing, uses expanded names for component identity and instance matching, and honors elementFormDefault / attributeFormDefault. A namespace-only xs:import can be satisfied with the PyXSD(..., namespace_schemas=...) mapping or a schemaLocation.

See examples/docx/ for the binding side of a namespaced document and the namespaces/ cases in the conformance corpus for the validation side.

Why not just loosen the report?

Because the report is the contract. Code that checks app.report.has_errors must not silently pass just because a caller wanted lenient binding. Keeping reporting strict means one run of pyxsd answers both questions — “is this valid?” and “what can I use?” — without the answer to the first depending on the answer to the second.

Extending the policy

BindingPolicy is a frozen dataclass and ParseModes is a namespace of named instances, so new fields and presets can be added without changing any call signature. New call sites should read the policy off the generated class (getattr(cls, "_parseMode_", ParseModes.STRICT)), the same channel already used for _contentModel_ and _derivation_.

A worked example is examples/docx/, which parses a deliberately messy document.xml subset under ParseModes.LAX and renders Markdown while the report still lists every problem.