Commit Graph

7 Commits

Author SHA1 Message Date
380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +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
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
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
b7da5652f3 feat(catalog): systemSuffix/uriSuffix entries and the prefer attribute
Two OASIS catalog gaps from the audit:

- systemSuffix and uriSuffix entries: an identifier ending in the rule's
  suffix maps to its URI, longest suffix winning, tried after exact and
  rewrite matches.
- prefer=public|system on the catalog: a system match always wins, and
  the public identifier is consulted as a fallback only when prefer is
  public or no system identifier was given (the OASIS rule). The default
  stays prefer=public.

xml:base / RFC 3986 relative-URI resolution of entry URIs and the legacy
SGML catalog format remain.

Part of #86
2026-06-08 15:31:10 +02:00
f15786c0a5 feat(parity): C14N 2.0 text trimming, catalog delegation, XPath number formatting
Close the remaining documented parity edges:

- Canonical XML 2.0 TrimTextNodes: an option that strips leading and trailing
  whitespace from each text node (dropping all-whitespace nodes), exposed as
  Options.canonical2.
- XML Catalog delegation and chaining: delegatePublic, delegateSystem,
  delegateURI, and nextCatalog entries are parsed, and resolveSystem/
  resolvePublic/resolveURI gain a loadingCatalog overload that follows the
  longest matching delegate prefix and then each nextCatalog, with a
  visited-set guard against cycles.
- XPath number formatting: the canonical string form now expands any exponent
  in the shortest double rendering to a plain decimal, since XPath 1.0 forbids
  scientific notation.

Closes #70
2026-06-08 09:37:16 +02:00
634270d529 feat(catalog): OASIS XML Catalog resolution
Add the PureXML.Catalog namespace: parse an OASIS XML catalog (public,
system, uri, rewriteSystem, rewriteURI, group/catalog by local name) into a
Resolver mapping public/system identifiers and URI names to URIs, with
longest-prefix rewriting and system-over-public precedence. entityResolver
(loadingURI:) builds a Parsing.EntityResolver that resolves an external
identifier through the catalog and loads the URI via an injected closure, so
a catalog plus loader wires entities and DTDs in while the default keeps XXE
closed.

Closes #57
Closes #58
Closes #27
2026-06-08 06:03:02 +02:00