PureXML/Tests/CatalogSuffixPreferTests.swift
Mihaela Mihaljevic 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

71 lines
3.1 KiB
Swift

@testable import PureXML
import Testing
@Suite("Catalog suffix entries and prefer attribute")
struct CatalogSuffixPreferTests {
@Test("systemSuffix resolves by longest matching suffix")
func test_systemSuffix() throws {
let catalog = """
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<systemSuffix systemIdSuffix=".dtd" uri="generic.dtd"/>
<systemSuffix systemIdSuffix="docbook.dtd" uri="docbook.dtd"/>
</catalog>
"""
let resolver = try PureXML.Catalog.Resolver(catalog)
#expect(resolver.resolveSystem("http://x/foo.dtd") == "generic.dtd")
// The longer matching suffix wins.
#expect(resolver.resolveSystem("http://x/docbook.dtd") == "docbook.dtd")
#expect(resolver.resolveSystem("http://x/foo.xsd") == nil)
}
@Test("uriSuffix resolves a URI name by suffix")
func test_uriSuffix() throws {
let catalog = """
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<uriSuffix uriSuffix=".xsd" uri="schema.xsd"/>
</catalog>
"""
let resolver = try PureXML.Catalog.Resolver(catalog)
#expect(resolver.resolveURI("http://x/types.xsd") == "schema.xsd")
#expect(resolver.resolveURI("http://x/types.dtd") == nil)
}
@Test("An exact system entry still wins over a suffix")
func test_exactBeatsSuffix() throws {
let catalog = """
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<system systemId="http://x/foo.dtd" uri="exact.dtd"/>
<systemSuffix systemIdSuffix=".dtd" uri="generic.dtd"/>
</catalog>
"""
let resolver = try PureXML.Catalog.Resolver(catalog)
#expect(resolver.resolveSystem("http://x/foo.dtd") == "exact.dtd")
}
@Test("prefer=public falls back to a public entry when the system id is unmatched")
func test_preferPublic() throws {
let catalog = """
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="public">
<public publicId="-//X//DTD//EN" uri="bypublic.dtd"/>
</catalog>
"""
let resolver = try PureXML.Catalog.Resolver(catalog)
let resolved = resolver.resolveExternalIdentifier(publicID: "-//X//DTD//EN", systemID: "http://x/unmatched.dtd")
#expect(resolved == "bypublic.dtd")
}
@Test("prefer=system does not fall back to public when a system id is present")
func test_preferSystem() throws {
let catalog = """
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog" prefer="system">
<public publicId="-//X//DTD//EN" uri="bypublic.dtd"/>
</catalog>
"""
let resolver = try PureXML.Catalog.Resolver(catalog)
// A system id is present but unmatched, and prefer=system, so no fallback.
#expect(resolver.resolveExternalIdentifier(publicID: "-//X//DTD//EN", systemID: "http://x/unmatched.dtd") == nil)
// With no system id, the public entry is still used.
#expect(resolver.resolveExternalIdentifier(publicID: "-//X//DTD//EN", systemID: nil) == "bypublic.dtd")
}
}