The scanner records declaration provenance (internal-subset entities, element models, attribute lists); the validator reports the three standalone='yes' dependencies on the external subset: a default that would be supplied, a non-CDATA normalization that would change a value, and whitespace in externally-declared element content. The parser hides externally-declared entities from standalone documents, so referencing one reports as undeclared (WFC: Entity Declared, 2.9). The Sun section is fully clean: not-wf 56/56, valid 28/28, invalid 74/74, no baseline. Closes #134 Closes #124
80 lines
4.0 KiB
Swift
80 lines
4.0 KiB
Swift
/// The standalone validity constraints (2.9): a document declaring
|
|
/// `standalone='yes'` must not depend on markup declarations from the external
|
|
/// subset. Three dependencies are checked per element: an attribute supplied
|
|
/// by an externally-declared default, an attribute whose value changes under
|
|
/// the tokenized normalization an external declaration calls for, and
|
|
/// whitespace in element content whose model is externally declared.
|
|
extension PureXML.Validation.DTD {
|
|
/// Externally-declared attribute defaults and normalization must not
|
|
/// affect a standalone document.
|
|
static var standaloneAttributes: PureXML.Validation.Validation<DTDElement, PureXML.Validation.DTDSchema> {
|
|
.init(description: "Standalone documents do not depend on external attribute declarations") { context in
|
|
let schema = context.document
|
|
guard schema.standalone,
|
|
let declarations = schema.externalAttributes[context.subject.name.description]
|
|
else { return [] }
|
|
return declarations.compactMap { declaration in
|
|
standaloneAttributeViolation(declaration, on: context.subject, at: context.codingPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Whitespace in element content is a dependency on the external subset
|
|
/// when the content model is externally declared (a standalone processor
|
|
/// could not know the whitespace is ignorable).
|
|
static var standaloneElementWhitespace: PureXML.Validation.Validation<DTDElement, PureXML.Validation.DTDSchema> {
|
|
.init(description: "Standalone documents have no whitespace in externally-declared element content") { context in
|
|
let schema = context.document
|
|
let name = context.subject.name.description
|
|
guard schema.standalone, schema.externalElementModels.contains(name),
|
|
case .children = schema.models[name],
|
|
elementContentWhitespace(in: context.subject)
|
|
else { return [] }
|
|
let reason = "standalone document has whitespace in the externally-declared element content of <\(name)>"
|
|
return [DTDFailure(reason: reason, at: context.codingPath)]
|
|
}
|
|
}
|
|
|
|
private static func standaloneAttributeViolation(
|
|
_ declaration: PureXML.Validation.AttributeDeclaration,
|
|
on element: DTDElement,
|
|
at path: DTDPath,
|
|
) -> DTDFailure? {
|
|
let supplied = element.attributes.first {
|
|
$0.name.description == declaration.name || $0.name.localName == declaration.name
|
|
}
|
|
guard let supplied else {
|
|
// Absent attribute with an external default: the default would be
|
|
// supplied from the external subset.
|
|
let hasDefault = switch declaration.defaultDecl {
|
|
case .fixed, .value: true
|
|
case .required, .implied: false
|
|
}
|
|
guard hasDefault else { return nil }
|
|
return DTDFailure(
|
|
reason: "standalone document depends on the externally-declared default for attribute '\(declaration.name)' on <\(element.name.description)>",
|
|
at: path,
|
|
)
|
|
}
|
|
// Present attribute whose externally-declared non-CDATA type calls for
|
|
// normalization the standalone processor would not perform.
|
|
if case .cdata = declaration.type { return nil }
|
|
guard supplied.value != normalize(supplied.value, for: declaration.type) else { return nil }
|
|
return DTDFailure(
|
|
reason: "standalone document depends on the externally-declared normalization of attribute '\(declaration.name)' on <\(element.name.description)>",
|
|
at: path,
|
|
)
|
|
}
|
|
|
|
/// Whether the element directly contains whitespace-only text.
|
|
private static func elementContentWhitespace(in element: DTDElement) -> Bool {
|
|
element.children.contains { child in
|
|
if case let .text(value) = child {
|
|
!value.isEmpty && value.allSatisfy(\.isWhitespace)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|