Each pattern facet value is now compiled when the schema is, instead of only
lazily at instance time (try?) where an unparseable pattern was silently
ignored. A structurally malformed pattern (unbalanced parentheses, a quantifier
with nothing to repeat) is rejected.
The check is conservative against over-rejection: it rejects only the
unambiguous structural RegexError kinds (.unbalanced, .danglingQuantifier) and
tolerates the kinds that fire on otherwise-valid patterns the engine cannot
fully handle (.empty for the empty pattern, .unsupported for an untabulated
\p{Is...} block such as a surrogate or historic block, .badQuantifier for the
lenient {,m} form, and .badEscape/.badClass which could fire on valid character-
class subtraction). So a{2,1} and a trailing backslash remain accepted (an
under-rejection), but no valid schema is rejected.
XSTS: invalid schemas accepted 1129 to 1092 (-37); valid schemas rejected held
at 72; instance buckets unchanged.
56 lines
2.0 KiB
Swift
56 lines
2.0 KiB
Swift
@testable import PureXML
|
|
import Testing
|
|
|
|
/// A `pattern` facet value must be a valid XSD regular expression. The pattern
|
|
/// was only compiled lazily at instance time (`try?`), so an unparseable one was
|
|
/// silently ignored and the schema accepted. Compile-time validation rejects the
|
|
/// unambiguous structural errors (unbalanced parentheses, a quantifier with
|
|
/// nothing to repeat) while tolerating constructs the engine merely does not
|
|
/// support on an otherwise-valid pattern (an untabulated `\p{Is...}` block, the
|
|
/// empty pattern, the lenient `{,m}` quantifier), so no valid schema is rejected.
|
|
@Suite("Pattern facet regex validity")
|
|
struct SchemaPatternFacetTests {
|
|
private func compile(_ pattern: String) throws {
|
|
_ = try PureXML.Schema.Document("""
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="v">
|
|
<xs:simpleType>
|
|
<xs:restriction base="xs:string"><xs:pattern value="\(pattern)"/></xs:restriction>
|
|
</xs:simpleType>
|
|
</xs:element>
|
|
</xs:schema>
|
|
""")
|
|
}
|
|
|
|
private func rejects(_ pattern: String) -> Bool {
|
|
do { try compile(pattern)
|
|
return false
|
|
} catch { return true }
|
|
}
|
|
|
|
@Test("a structurally malformed pattern is rejected")
|
|
func test_malformed() {
|
|
#expect(rejects("((a)"))
|
|
#expect(rejects("(a))"))
|
|
#expect(rejects("?a"))
|
|
#expect(rejects("*a"))
|
|
#expect(rejects("+a"))
|
|
}
|
|
|
|
@Test("a valid pattern compiles")
|
|
func test_valid() throws {
|
|
try compile("[a-z]+")
|
|
try compile("[0-9]{2,4}")
|
|
try compile("(ab)*c?")
|
|
}
|
|
|
|
@Test("a construct the engine does not support is not treated as a schema error")
|
|
func test_engineGapTolerated() throws {
|
|
// Empty pattern (valid: matches the empty string), an untabulated Unicode
|
|
// block escape, and the lenient `{,m}` quantifier are all accepted.
|
|
try compile("")
|
|
try compile(#"\p{IsHighSurrogates}"#)
|
|
try compile("[0-9]{,5}")
|
|
}
|
|
}
|