Two ISO Schematron gaps, the most validator-relevant first:
- Dynamic messages: an assert/report message is now a template, so
<value-of select=> renders the string value of its XPath at the context
node and <name> renders the node's name. A finding can report actual
values ("Age 120 must be under 100") instead of static text.
- Phases: <phase id><active pattern=> scopes which patterns run.
validate(_:phase:) honors a requested phase or the schema's
defaultPhase; nil or #ALL runs every pattern.
let variables, abstract rules/extends, diagnostics, and key()/document()
remain (the last depends on XSLT-style function injection).
Part of #80
Deepen DTD processing in the doctype scanner:
- Conditional sections are processed, not skipped: INCLUDE sections have
their declarations scanned, IGNORE sections are discarded, the keyword
may be a parameter-entity reference, and nested sections are handled.
- <!NOTATION> declarations are parsed and stored on DocumentType.
- Unparsed entities (<!ENTITY ... NDATA n>) are parsed and stored with
their notation, distinct from general entities.
- External parameter entities are loaded through the injected resolver so
their declarations expand; the default refusing resolver loads nothing,
keeping XXE closed.
- NOTATION attributes are a distinct type, validated against the declared
notations (value must be listed and the notation declared).
Tracking unparsed entities also lets ENTITY/ENTITIES attribute values be
validated against the declared NDATA entities, closing the gap left when
#73 could only check name syntax.
Closes#72
Complete the DTD attribute handling found in the audit:
- Default and #FIXED attribute values are applied to the parsed tree.
parseApplyingInternalDTDDefaults returns a tree where every element
carries the attributes its <!ATTLIST> defaults, so an editor sees the
same effective attributes a validating processor would.
- NMTOKEN, NMTOKENS, ENTITY, ENTITIES are validated: NMTOKEN(S) must be
XML name tokens, ENTITY/ENTITIES must be XML names. Previously all fell
through to unconstrained CDATA.
- DTD default values are normalized per XML 1.0: character and predefined
entity references are expanded, literal whitespace becomes a space, and
non-CDATA values are collapsed and trimmed.
ENTITY/ENTITIES check name syntax only; that the named entity is a
declared unparsed entity is not verified, since NDATA entity tracking is
not yet modeled.
Closes#73
Slice 5 of #92. The last validator moves onto the framework and the legacy
Issue type is removed, so all validation now flows through one error model.
- ValidationError gains a severity (error by default, warning for advisory
findings), chosen so Schematron's assert/report distinction survives in the
unified type.
- Schematron is a Validation<Node, Void> over the document root; a failed assert
is an error finding, a matched report a warning. Each finding is located at the
context node its rule fired on (built from the element chain, with a sibling
index only when a name repeats), not the document root.
- Schematron.validate returns [ValidationError] and runs through a Validator.
- Validation.Issue and its Severity-based result are deleted; Severity now backs
ValidationError.
Every validation layer (structural, DTD, XSD content and identity, Schematron)
now produces located ValidationError values through the shared framework.
Slice 3 of #92. The DTD validator's imperative walk becomes declared Validation
values over a DTDSchema document:
- DTDSchema is now just the schema (content models + attribute declarations) and
serves as the rules' Document context; its validate/walk methods are gone.
- contentModel and attributeDeclarations are Validation<Element, DTDSchema>, so
every content-model and attribute violation is now located by coding path
("... at path: r/a"). undeclaredElement is added only in strict mode.
- identifierIntegrity is a Validation<Node, DTDSchema> gated to the root, doing
the document-wide ID-uniqueness and IDREF-resolution checks in one pass.
- DTD.validator(strict:) assembles the rule set; validateAgainstInternalDTD runs
it and returns [ValidationError] instead of [Issue].
A new test pins the located result. Schema (XSD) and Schematron validators still
use Issue and migrate in the following slices.
Slice 1 of #92. Introduce the OpenAPIKit-idiom validation framework, replacing
the imperative if-tree structural validator:
- ValidationError (reason + coding path) renders "<reason> at path: root/row/cell";
ValidationErrorCollection gathers every failure and is thrown once.
- Validation<Subject, Document> is the atomic, composable rule value, with the
multi-error and single-error Bool inits (the latter auto-forms "Failed to
satisfy: <description>"). Predicate-gated, type-directed via an erased
AnyValidation that guards an optional from matching a non-optional rule.
- The framework is parameterized over both Subject and Document so the structural,
DTD, XSD, and Schematron layers share one machinery (Document = Void for rules
that need only their subject).
- Validator<Document> walks the node tree, building the coding path, with blank /
default factories and fluent validating / withoutValidating.
- The combinator algebra (&&, ||, comparison lifts, take, lift, unwrap, all) as
free functions returning closures.
The structural duplicate-attribute rule is reborn as Validation<Element, Void>.
Combinators and the erased wrapper are unit-tested as pure functions per the rule.
The legacy Validation.Issue stays only for the not-yet-migrated schema validators
and is retired in a later slice.
Add PureXML.Validation.Schematron: compile a Schematron schema (matching
schema/pattern/rule/assert/report by local name, so the ISO and legacy
namespaces both work) and validate a document. A rule's context XPath
selects the nodes it fires on; each assert/report test is evaluated relative
to the node via the XPath context-node API. A failed assert is an error, a
matched report a warning; within a pattern a node fires the first matching
rule. Adds XPath.Query.nodes(over:) returning matched tree nodes so one tree
can be queried repeatedly.
Closes#52Closes#53Closes#25
Complete the structural DTD validation layer. AttributeType now distinguishes
the ID family; the validator does a second pass after the structural walk
(since an IDREF may point forward to an ID later in the document):
- ID-typed attribute values must be unique across the document.
- IDREF values must each resolve to a declared ID.
- IDREFS values are split on whitespace and each token must resolve.
Reimplemented from the DTD ID/IDREF spec; no upstream code copied. 4 tests; 79
total.
Surface <!ATTLIST> declarations from the parser and validate attributes against
them. Reimplemented from the DTD attribute-list spec; no upstream code copied.
- DocumentType gains attributeLists (raw ATTLIST bodies per element); the
DoctypeScanner captures them and the EventReader plumbs them through.
- AttributeListParser parses a body into AttributeDeclaration values
(AttributeType: cdata or enumeration; AttributeDefault: required/implied/
fixed/value).
- DTDSchema now checks #REQUIRED presence, #FIXED value match (when present),
and enumerated-value membership, in addition to content models.
- Tokenized types (ID/IDREF/NMTOKEN) parse but are not yet value-enforced.
- 5 attribute tests; 75 total.
Surface the parsed <!DOCTYPE> internal subset's <!ELEMENT> declarations from the
parser and validate a tree against them. Reimplemented from the DTD content-model
spec; no upstream code copied.
- DoctypeScanner captures <!ELEMENT name model> declarations; the parser surfaces
them as Parsing.DocumentType (via parseWithDocumentType).
- Validation.ContentModel + ContentModelParser parse a model string (EMPTY, ANY,
(#PCDATA), mixed, and element content with sequence/choice and ?/*/+).
- ContentModelMatcher matches the child element-name sequence against a model as
a regular language (NFA-position matching).
- Validation.DTDSchema.validate reports issues for EMPTY/PCDATA/mixed violations,
element content with stray text, and child sequences that do not match.
- PureXML.validateAgainstInternalDTD(_:) parses and validates in one call.
- ATTLIST validation is out of scope. 9 tests; 70 total.
Root Swift package mirroring PureYAML's structure, rules, and verification
gates. Includes the XML node model (Node, Element, Attribute, QualifiedName),
a working emitter (Serializer) with escaping and pretty/compact options, and
structural validation. The parser surface is stable; parse() raises
notImplemented until the scanner lands, pinned by a test.
- dependency-free, namespaced under PureXML
- portability and style gates carried over (style, namespacing, forbidden
patterns, swiftformat, swiftlint, linux, wasm, windows CI)
- 11 tests passing