The OASIS/NIST section of the W3C conformance suite runs via a new XMLCONF_ROOT-gated runner driven by the section's own manifest (parsed with PureXML): 247/247 not-wf rejected, all 100 well-formed cases parse, zero deviations. Closing its 24 gaps makes the DTD subset grammar strict: exact PEReference shape, strict declaration names and keywords, NDATA and notation tails, no bare '%' in entity values, case-sensitive balanced conditional sections, junk between declarations rejected, external-subset violations now propagate (with text-declaration support, production 77), and a PI target must be separated from its data. The xmltest baseline tightens to 181/186: CDATA in the internal subset (107) is now rejected, five documented deviations remain. Closes #123
58 lines
2.5 KiB
Swift
58 lines
2.5 KiB
Swift
/// Conditional sections of a DTD subset (`<![INCLUDE[ ... ]]>` /
|
|
/// `<![IGNORE[ ... ]]>`, productions 61-65), split from the scanner body to
|
|
/// keep it under the length caps.
|
|
extension DTDScanner {
|
|
/// Processes a conditional section `<![INCLUDE[ … ]]>` or `<![IGNORE[ … ]]>`.
|
|
/// The keyword may be a parameter-entity reference, so it is parameter-expanded
|
|
/// first; after expansion it must be exactly `INCLUDE` or `IGNORE` (case-
|
|
/// sensitive, production 61). An INCLUDE section's declarations are scanned;
|
|
/// an IGNORE section's body is discarded. Nested sections are handled by
|
|
/// re-scanning the body, and an unterminated section is not well-formed.
|
|
mutating func scanConditionalSection(_ reader: inout Reader, depth: Int, at mark: Mark) throws {
|
|
reader.consume("<![")
|
|
var keywordRaw = ""
|
|
while let character = reader.peek(), character != "[" {
|
|
keywordRaw.append(character)
|
|
reader.advance()
|
|
}
|
|
guard reader.consume("[") else {
|
|
throw ParseError.malformedDeclaration(mark)
|
|
}
|
|
let keyword = expandParameterReferences(keywordRaw).trimmingXMLWhitespace()
|
|
guard keyword == "INCLUDE" || keyword == "IGNORE" else {
|
|
throw ParseError.malformedDeclaration(mark)
|
|
}
|
|
guard let body = readConditionalBody(&reader) else {
|
|
throw ParseError.malformedDeclaration(mark)
|
|
}
|
|
if keyword == "INCLUDE", depth < maxDepth {
|
|
var sub = Reader(body)
|
|
try scanDeclarations(&sub, depth: depth + 1, terminatedByBracket: false, at: mark)
|
|
}
|
|
}
|
|
|
|
/// Reads the body of a conditional section up to its matching `]]>`, keeping
|
|
/// any nested `<![ … ]]>` sections intact so an INCLUDE can re-scan them.
|
|
/// Returns nil when the input ends before the section is balanced.
|
|
mutating func readConditionalBody(_ reader: inout Reader) -> String? {
|
|
var body = ""
|
|
var nesting = 1
|
|
while reader.peek() != nil {
|
|
if reader.matches("<![") {
|
|
nesting += 1
|
|
reader.consume("<![")
|
|
body += "<!["
|
|
} else if reader.matches("]]>") {
|
|
reader.consume("]]>")
|
|
nesting -= 1
|
|
if nesting == 0 { return body }
|
|
body += "]]>"
|
|
} else if let character = reader.peek() {
|
|
body.append(character)
|
|
reader.advance()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|