feat(relaxng): skip RNC >> follow-annotations, completing #79
A >> follow-annotation after a pattern (a foreign element name with optional [ ... ] content) is skipped, chaining and combining with leading annotations; neither carries schema semantics. Namespace declarations inside annotations were already dropped by the annotation skipper, so the RNC compact syntax is now fully covered.
This commit is contained in:
parent
45001f3422
commit
dbdf98e231
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- RELAX NG compact `>>` follow-annotations (#79), completing the RNC syntax. A `>>` annotation after a pattern (a foreign element name with optional `[ … ]` content) is skipped, chaining and combining with leading `[ … ]` annotations; both carry no schema semantics. (Namespace declarations inside annotations are already dropped by the annotation skipper.)
|
||||
- XSLT `document()` fragments and base-URI resolution (#82), completing the XSLT function/output audit. A `#fragment` selects a subset of the loaded document via XPointer, a node-set first argument unions the documents named by each node, and a relative URI is resolved against a caller-supplied `baseURI` (also applied to `xsl:include`/`xsl:import`).
|
||||
- XSLT result-tree fragments usable as node-sets (#82). An `xsl:variable` with a body now binds a queryable document fragment instead of a plain string, so `count($rtf/*)`, `$rtf/child`, and `xsl:for-each select="$rtf/..."` work; it still has its concatenated text as a string value.
|
||||
- XSLT `xsl:output cdata-section-elements` (#82). The text content of the named result elements is emitted in `<![CDATA[…]]>` sections instead of escaped, leaving nested elements and other elements unaffected.
|
||||
|
||||
@ -192,14 +192,28 @@ extension RNCParser {
|
||||
|
||||
private func parseParticle() -> Pattern {
|
||||
let primary = parsePrimary()
|
||||
let particle: Pattern
|
||||
switch peek() {
|
||||
case .symbol("?"): advance()
|
||||
return .choice(primary, .empty)
|
||||
particle = .choice(primary, .empty)
|
||||
case .symbol("*"): advance()
|
||||
return .choice(.oneOrMore(primary), .empty)
|
||||
particle = .choice(.oneOrMore(primary), .empty)
|
||||
case .symbol("+"): advance()
|
||||
return .oneOrMore(primary)
|
||||
default: return primary
|
||||
particle = .oneOrMore(primary)
|
||||
default: particle = primary
|
||||
}
|
||||
skipFollowAnnotations()
|
||||
return particle
|
||||
}
|
||||
|
||||
/// Skips `>>` follow-annotations after a pattern: each is a foreign element
|
||||
/// name with optional `[ … ]` content, carrying no schema semantics.
|
||||
private func skipFollowAnnotations() {
|
||||
while peek() == .symbol(">"), peek(1) == .symbol(">") {
|
||||
advance()
|
||||
advance()
|
||||
if case .word = peek() { advance() }
|
||||
skipAnnotation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
38
Tests/RelaxNGCompactFollowAnnotationTests.swift
Normal file
38
Tests/RelaxNGCompactFollowAnnotationTests.swift
Normal file
@ -0,0 +1,38 @@
|
||||
@testable import PureXML
|
||||
import Testing
|
||||
|
||||
@Suite("RELAX NG compact >> follow-annotations")
|
||||
struct RelaxNGCompactFollowAnnotationTests {
|
||||
private func valid(_ rnc: String, _ xml: String) throws -> Bool {
|
||||
try PureXML.Schema.RelaxNG(compact: rnc).validate(xml)
|
||||
}
|
||||
|
||||
@Test("A follow-annotation after an element is ignored")
|
||||
func test_afterElement() throws {
|
||||
let rnc = "element root { element a { empty } >> a:documentation [ \"a child\" ] }"
|
||||
#expect(try valid(rnc, "<root><a/></root>"))
|
||||
#expect(try !valid(rnc, "<root><b/></root>"))
|
||||
}
|
||||
|
||||
@Test("A follow-annotation on a define's pattern is ignored")
|
||||
func test_onDefine() throws {
|
||||
let rnc = """
|
||||
start = element root { ref-a }
|
||||
ref-a = element a { empty } >> a:doc [ "the a element" ]
|
||||
"""
|
||||
#expect(try valid(rnc, "<root><a/></root>"))
|
||||
}
|
||||
|
||||
@Test("Follow-annotations chain after a particle")
|
||||
func test_chained() throws {
|
||||
let rnc = "element root { element a { empty } >> a:x [ \"1\" ] >> a:y [ \"2\" ], element b { empty } }"
|
||||
#expect(try valid(rnc, "<root><a/><b/></root>"))
|
||||
#expect(try !valid(rnc, "<root><a/></root>"))
|
||||
}
|
||||
|
||||
@Test("A leading and a follow annotation can both appear")
|
||||
func test_leadingAndFollow() throws {
|
||||
let rnc = "element root { [ a:doc [ \"lead\" ] ] element a { empty } >> a:doc [ \"follow\" ] }"
|
||||
#expect(try valid(rnc, "<root><a/></root>"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user