Commit Graph

197 Commits

Author SHA1 Message Date
2cc434abfd Merge: RELAX NG datatype-mismatch detail 2026-06-09 01:09:30 +02:00
3be6832c23 feat(relaxng): quote the offending value and name the datatype in text errors
A RELAX NG text/datatype mismatch now reports the rich datatype reason
(quoting the value and naming the type, e.g. 'abc' is not a valid
integer) and a value mismatch quotes both the actual text and the
required literal, instead of a generic "text content is not valid". The
detail is peeled from the content pattern at the failure point through
choice, group, list, and ref.
2026-06-09 01:09:29 +02:00
3021615b66 docs: refresh test count to 600 2026-06-09 00:59:26 +02:00
cc4a40314c Merge: DTD located content-model errors 2026-06-09 00:59:26 +02:00
fb31033ce8 feat(dtd): located content-model errors with recovery hints
An element whose children break its content model now names each stray
child outside the model's alphabet individually, and reports a pure
order/count violation once with the allowed elements as a hint, instead
of a single opaque "do not match its content model". Adds an alphabet
extractor to the content-model matcher. This brings DTD to the same
located standard as the XSD, RELAX NG, and Schematron validators, so all
four schema languages give editor-grade diagnostics.
2026-06-09 00:59:25 +02:00
3e3ca0f971 docs: refresh test count to 596 2026-06-09 00:55:43 +02:00
489ff498fa Merge: XSD located content-model errors 2026-06-09 00:55:18 +02:00
ea5387db90 feat(xsd): located, recovering content-model errors
A content-model violation is now placed at the offending child with a
recovery hint naming what was expected, instead of one opaque "content
does not match the content model" per element. The sequence/choice
automaton is walked child by child via its follow-set to flag the first
child the model rejects, or the incomplete content at the end; an xs:all
group recovers past each stray child and reports every missing required
member. Well-placed children are still validated for their own content,
so an editor sees every problem at once. Brings XSD to the same located,
recovering standard as the RELAX NG, DTD, and Schematron validators.
2026-06-09 00:55:17 +02:00
bbd293521a docs: refresh test count to 592 2026-06-09 00:41:31 +02:00
e8af2563dd Merge: RELAX NG located, recovering errors (#79) 2026-06-09 00:40:56 +02:00
ae271d65f6 feat(relaxng): located, recovering validation errors for editor use
Adds RelaxNG.errors(in:) reporting every way a document fails the schema
as individual located errors with coding paths, recovering past each
failure so all of a faulty document's problems surface at once instead of
only the first. Errors carry recovery hints naming the element types
accepted at the failure point, and distinguish unexpected elements,
invalid or missing attributes, invalid text/datatype content, and missing
required content. RelaxNG.validation() exposes the schema as a
Validation<Node, Void>, composing with the same framework as the XSD,
DTD, and Schematron rules. The boolean derivative engine remains the
authority on validity; the diagnostic walk only places and explains the
failures, recovering each step so it never cascades. Splits the engine
and the diagnostics into separate files.
2026-06-09 00:40:56 +02:00
5eeb29d10a docs: move closed catalog #86 to done in roadmap, refresh test count 2026-06-08 18:06:13 +02:00
346eb66f45 Merge: catalog group-level prefer (#86) 2026-06-08 18:05:04 +02:00
2414dbc3bc feat(catalog): honor group-level prefer override
A prefer attribute on a <group> (or nested <catalog>) overrides the
catalog-wide preference for the <public> entries within it, inherited
through nested groups. The resolver records each public entry's effective
preference and consults it when falling back from an unmatched system
identifier, so two entries in one catalog can prefer differently. A system
match still always wins.
2026-06-08 18:05:03 +02:00
cc44800a4a Merge: Schematron abstract patterns (#80) 2026-06-08 17:58:33 +02:00
d7a40b9a7a feat(schematron): instantiate abstract patterns with is-a/param
A <pattern abstract="true"> is a template whose rule queries carry $name
references; a <pattern is-a="…"> with <param> children instantiates it,
substituting each parameter into the template's context, test, <let>
value, and <value-of select=> before the XPath is compiled. The
substitution is a single left-to-right pass, so a substituted value is
never rescanned and a short name never captures a longer one; $name
references that are not parameters stay as written. The template itself
contributes no rules.
2026-06-08 17:58:33 +02:00
873aed7025 Merge: changelog-touched gate (verification.md) 2026-06-08 17:49:22 +02:00
e06a29ae1c chore(ci): add the changelog-touched gate verification.md requires
verification.md listed scripts/check-changelog-touched.sh in the local
gate, but the script did not exist (exit 127). Adds it: when a change set
modifies Sources/*.swift it must also update CHANGELOG.md, comparing the
branch against its base plus the working tree, no-op on a clean base, with
a CHANGELOG_SKIP escape hatch for refactor-only changes. Wires it into
check-all.sh alongside the other shell gates.
2026-06-08 17:49:21 +02:00
0171697c37 Merge: changelog and readme for #79 2026-06-08 17:45:42 +02:00
e0e498c4bb docs: record #79 RELAX NG work in changelog and refresh test count
Adds the Unreleased changelog entry for the datatypeLibrary resolution,
value-space <value> comparison, nsName subtraction, and compact-syntax
annotations/div/concatenation work, and updates the README suite count to
573 tests in 84 suites.
2026-06-08 17:45:42 +02:00
eff530bde7 Merge: RNC string concatenation (#79) 2026-06-08 17:38:59 +02:00
9845e1cd9d feat(relaxng): fold RNC ~ string concatenation
A string literal split with ~ ("a" ~ "b") is the single literal "ab",
the way long values and namespace URIs are wrapped across lines. The fold
applies wherever a string literal is read: value literals and the URIs of
namespace/datatypes/include/external declarations.
2026-06-08 17:38:58 +02:00
79f424321c feat(relaxng): compare typed <value> in the datatype value space
A <value> pattern now carries its datatype, so matching compares the
instance and the literal in that type's value space rather than purely
lexically. Numeric and date/time types compare by value (1 equals 01,
1.5 equals 1.50); booleans treat 1/true and 0/false as equal; string and
token compare by their whitespace-normalized lexical form, as before.
Untyped values default to the built-in token type, and a bare string
literal in the compact syntax is token-typed. Durations and binary types
fall under the lexical rule.
2026-06-08 17:36:16 +02:00
c1e181db50 Merge: datatypeLibrary resolution and nsName subtraction (#79) 2026-06-08 17:21:54 +02:00
147d776d21 feat(relaxng): resolve datatypeLibrary and nsName subtraction
Adds a datatypeLibrary-aware datatype resolver to the XML-syntax parser:
the in-scope library is inherited from the nearest ancestor, the default
(empty) library defines only string and token, and the W3C XML Schema
library defines the full built-in set. A type the in-scope library does
not define is an unknown datatype, so the <data>/<value> pattern matches
nothing rather than silently validating.

Adds an nsNameExcept name-class case so nsName with an <except> child
(XML syntax) and prefix:* - name subtraction (compact syntax) narrow a
namespace wildcard. The compact lexer splits prefix:* into a trailing-
colon word plus a star symbol, which the parser now recognizes.

Existing RELAX NG facet tests declared XSD types without the library and
relied on lenient resolution; they now declare the XSD datatypeLibrary,
matching what a conformant processor requires.
2026-06-08 17:21:49 +02:00
704e5e2161 Merge: RNC annotations and div grouping (#79) 2026-06-08 17:12:39 +02:00
e24004dd2a feat(relaxng): handle RNC annotations and div grouping
The compact-syntax parser now skips [ ... ] documentation/grammar
annotations (balanced over nesting) wherever they may appear, before a
define or before a pattern, and treats div { ... } as transparent
grammar grouping that contributes its defines and start directly. A
define literally named div still parses when assigned. Splits the
name-class and token helpers into a companion file to stay within the
type and file length budget.
2026-06-08 17:12:35 +02:00
434d0d8882 Merge epic/86-sgml-catalog: SGML catalog format
Part of #86
2026-06-08 17:03:07 +02:00
3a1ecb503a feat(catalog): SGML Open (TR 9401) catalog format
Resolver(sgml:) parses the legacy SGML catalog format: PUBLIC, SYSTEM,
DELEGATE, CATALOG, BASE, and OVERRIDE entries with -- comment --
delimiters. Replacement URIs resolve against BASE and the supplied
baseURI; OVERRIDE YES/NO maps to the prefer-public fallback. Unknown
keywords are skipped tolerantly. Feeds the same Resolver as the XML
catalog.

Part of #86
2026-06-08 17:03:06 +02:00
2dfa647111 Merge epic/79-rnc-nameclass-subtraction: name-class subtraction
Part of #79
2026-06-08 16:58:24 +02:00
3887b33e4e feat(relaxng): compact name-class subtraction (* - name)
The compact-syntax name class * - nameClass now parses as anyName except
the subtracted class (the model already had anyNameExcept). ns:* - name
would need an nsName-except model case and is left as a refinement.

Part of #79
2026-06-08 16:58:24 +02:00
ab4a8661f5 Merge epic/79-rnc-param-blocks: compact datatype parameter blocks
Part of #79
2026-06-08 16:55:57 +02:00
8438d94b44 feat(relaxng): compact-syntax datatype parameter blocks
The RNC datatype parameter block xsd:int { minInclusive = "5" } is now
parsed into facets (previously skipped), matching the XML-syntax <param>
support. The facet mapping is shared by both parsers through a new
RelaxNGFacets helper.

Part of #79
2026-06-08 16:55:57 +02:00
768d7b1134 Merge epic/80-schematron-scoped-let: schema- and pattern-level let
Part of #80
2026-06-08 16:52:17 +02:00
b3d535c77e feat(schematron): schema- and pattern-level let
<let> may now appear at schema and pattern level, not just in a rule.
Schema lets are evaluated once at the document root; pattern lets extend
them (also at the root); rule lets extend those at each context node. A
narrower-scope binding overrides a wider one of the same name.

Part of #80
2026-06-08 16:52:16 +02:00
7a48b6c9e6 Merge epic/79-relaxng-data-params: data param facets
Part of #79
2026-06-08 16:48:26 +02:00
28cbed0a70 feat(relaxng): <param> facets under <data>
A <data type=> pattern now carries a SimpleType (base + facets) rather
than a bare built-in, and its <param name=>value</param> children
populate the facets (minLength, maxLength, length, pattern, min/max
inclusive/exclusive, total/fraction digits), reusing the XSD datatype
facet validation. So a constrained datatype like xsd:string with
minLength rejects too-short values.

datatypeLibrary resolution, typed <value> comparison, and the RNC
datatype parameter blocks remain.

Part of #79
2026-06-08 16:48:24 +02:00
c29bf69f07 Merge epic/101-validator-decomposition: scope decision + category isolation tests
Closes #101
2026-06-08 16:41:50 +02:00
50a6afb3fc docs(validation): document the XSD validator scope decision; isolation-test each category
The validation framework already conforms to the OpenAPIKit idiom: named
composable Validation values, a Validator with blank/validating/
withoutValidating, located errors, parameterized over both subject and
document. The DTD layer is already four named rules.

XSD content validation stays a single recursive Validation
(contentValidity) rather than a rule per constraint, because XSD is a
recursive type system: a child's type is resolved from its parent's
content model and position, then rewritten by xsi:type and the derivation
controls mid-walk, so the constraint categories are interdependent and
cannot be split into independent rules without changing semantics or
duplicating the recursion. That is the sanctioned multi-error form.

To keep each category verifiable on its own (the idiom's testability),
add XSDCategoryTests exercising ComplexValidator directly per category
(attributes, content, nillable, fixed); derivation, wildcards, and
namespaces are already isolation-tested through crafted schema strings.

Closes #101
2026-06-08 16:41:50 +02:00
e6c1c69f1c Merge epic/91-serialization-toggles: cdataAsText and asciiOnly
Closes #91
2026-06-08 16:34:58 +02:00
e2ba6a7359 feat(emitting): cdataAsText and asciiOnly save options
- cdataAsText emits a CDATA section's content as escaped text instead of
  a <![CDATA[ ... ]]> section.
- asciiOnly escapes every non-ASCII character as a numeric character
  reference, so the output is pure ASCII (libxml2's character-reference
  encoding).

Both apply to the Serializer and the incremental Writer through the
shared Escaping rules.

Closes #91
2026-06-08 16:34:57 +02:00
85c7bded45 Merge epic/80-schematron-diagnostics: diagnostics
Part of #80
2026-06-08 16:30:15 +02:00
a76e9b3da3 feat(schematron): diagnostics
An assert/report may reference <diagnostic> messages by id through its
diagnostics= attribute; each referenced diagnostic is rendered (with the
same value-of/name/let substitution) and appended to the finding for
extra per-failure detail. <diagnostics> is collected schema-wide.

Schema/pattern-level let, abstract patterns, and key()/document() remain.

Part of #80
2026-06-08 16:30:13 +02:00
27d8eb5e16 Merge epic/80-schematron-abstract: abstract rules and extends
Part of #80
2026-06-08 16:26:30 +02:00
7e04f6056c feat(schematron): abstract rules and extends
A <rule abstract="true" id="x"> is a template that does not fire on its
own; a concrete rule pulls in its let bindings and assertions with
<extends rule="x"/>, prepended so the concrete rule extends rather than
replaces them.

Part of #80
2026-06-08 16:26:30 +02:00
c21419fe7d Merge epic/80-schematron-let: rule-level let variables
Part of #80
2026-06-08 16:23:27 +02:00
d0671e40ba feat(schematron): rule-level let variable bindings
<rule> may now declare <let name= value=> bindings, evaluated at each
context node in order (so a later binding can reference an earlier one
through $name). The bindings are available to the rule's assert/report
tests and to <value-of select="$name"> in messages, using the XPath
engine's existing variable support.

Schema- and pattern-level let, abstract rules/extends, diagnostics, and
key()/document() remain.

Part of #80
2026-06-08 16:23:26 +02:00
c7802ed9d0 Merge epic/86-catalog-xmlbase: xml:base and RFC 3986 resolution
Part of #86
2026-06-08 16:19:45 +02:00
502aa26438 feat(catalog): xml:base and RFC 3986 relative-URI resolution of entry URIs
Catalog replacement URIs (uri, rewritePrefix, delegate/next catalog) are
now resolved against the catalog's base URI and any in-scope xml:base,
reusing the existing RFC 3986 URIReference resolver. Resolver(_:baseURI:)
supplies the catalog's own location; xml:base on the catalog/group/entry
elements scopes nested entries. An empty base leaves relative URIs as
written, so catalogs without a base behave exactly as before.

The legacy SGML catalog format and group-level prefer override remain.

Part of #86
2026-06-08 16:19:45 +02:00
293625d41d docs(readme): chain roadmap nodes with directed edges for reliable vertical layout
Invisible ~~~ links did not reliably rank nodes vertically in GitHub's
Mermaid renderer, leaving the shipped nodes spread horizontally. Chain
every node with --> so flowchart TB stacks them in a single top-to-bottom
column.
2026-06-08 16:12:49 +02:00