A long <xs:complexType> simpleContent extension chain (T0 extends T1 extends T2 ...) is flat in the document, so each of the N complex types re-walked its whole base chain to resolve the simple type its simpleContent derives from (an O(n^3) compile; a 400-link chain took ~23s). The resolved simple type is now memoized by complex-type name (SimpleContentMemo, a reference type surviving the value-type context copies, mirroring GroupParticleMemo); the same chain compiles in ~1.2s, and past the 512 expansion-depth bound it is rejected with a located error as before. Behaviour-preserving (an acyclic resolution is intrinsic to the name; a derivation cycle is rejected elsewhere), verified conformance-neutral over the full W3C XSTS corpus, the determinism differential, and the full suite. The attribute flattening along the chain is inherently quadratic in the bounded length and is tracked.
76 lines
3.8 KiB
Swift
76 lines
3.8 KiB
Swift
private typealias SimpleContentNode = PureXML.Schema.XSDNode
|
|
private typealias SimpleContentType = PureXML.Schema.SimpleType
|
|
private typealias SimpleContentContext = PureXML.Schema.XSDContext
|
|
private typealias SimpleContentBuiltinType = PureXML.Schema.BuiltinType
|
|
|
|
extension PureXML.Schema.XSDParser {
|
|
static func simpleContentType(
|
|
_ node: XSDTree,
|
|
_ context: PureXML.Schema.XSDContext,
|
|
visited: Set<String> = [],
|
|
) -> PureXML.Schema.SimpleType {
|
|
guard let inner = derivation(node) else { return SimpleContentType(base: .string) }
|
|
let rawBase = SimpleContentNode.attribute(inner, "base") ?? "string"
|
|
let baseName = SimpleContentNode.stripPrefix(rawBase)
|
|
let bindings = namespaceBindingsInScope(of: inner, defaultBindings: context.namespaceBindings)
|
|
let uri = SimpleContentNode.referenceNamespace(rawBase, bindings)
|
|
|
|
let baseType = simpleContentBaseType(baseName, uri: uri, context, visited: visited)
|
|
let effectiveType: SimpleContentType
|
|
let inlineType = SimpleContentNode.localName(inner) == "restriction"
|
|
? SimpleContentNode.firstChild(inner, named: "simpleType")
|
|
: nil
|
|
if let inline = inlineType {
|
|
effectiveType = scopedSimpleType(inline, context)
|
|
} else {
|
|
effectiveType = baseType
|
|
}
|
|
var facets = effectiveType.facets
|
|
let declaresEnumeration = SimpleContentNode.elementChildren(inner)
|
|
.contains { SimpleContentNode.localName($0) == "enumeration" }
|
|
if SimpleContentNode.localName(inner) == "restriction", declaresEnumeration {
|
|
facets.enumeration = nil
|
|
}
|
|
PureXML.Schema.XSDSimpleParser.applyFacets(inner, into: &facets)
|
|
return SimpleContentType(base: effectiveType.base, facets: facets, variety: effectiveType.variety, isBuiltinList: effectiveType.isBuiltinList)
|
|
}
|
|
|
|
static func scopedSimpleType(_ node: XSDTree, _ context: PureXML.Schema.XSDContext) -> PureXML.Schema.SimpleType {
|
|
var scoped = context
|
|
scoped.namespaceBindings = namespaceBindingsInScope(of: node, defaultBindings: context.namespaceBindings)
|
|
return PureXML.Schema.XSDSimpleParser.simpleType(node, scoped)
|
|
}
|
|
|
|
/// The simple type a `simpleContent` derivation derives from: an XSD built-in, a
|
|
/// named simple type, or, when the base is another complex type with
|
|
/// simpleContent, that type's own resolved simple type. A cycle in the base
|
|
/// chain falls back to `string`.
|
|
private static func simpleContentBaseType(
|
|
_ baseName: String,
|
|
uri: String?,
|
|
_ context: SimpleContentContext,
|
|
visited: Set<String>,
|
|
) -> SimpleContentType {
|
|
if uri == PureXML.Schema.XSDParser.xsdNamespace {
|
|
return SimpleContentType(base: SimpleContentBuiltinType(rawValue: baseName) ?? .string)
|
|
}
|
|
if let simple = context.simpleTypes[baseName] {
|
|
return simple
|
|
}
|
|
// A complex-type `simpleContent` base resolves to that type's own simple type.
|
|
// Memoize it by name so a long extension chain is walked once, not re-walked while
|
|
// compiling every type on it (the former O(n^3)); the resolution is intrinsic to the
|
|
// name for an acyclic chain, and a derivation cycle is rejected elsewhere.
|
|
if let memoized = context.simpleContentMemo.store[baseName] {
|
|
return memoized
|
|
}
|
|
guard !visited.contains(baseName),
|
|
let complexNode = context.complexTypeNodes[baseName],
|
|
let baseSimpleContent = SimpleContentNode.firstChild(complexNode, named: "simpleContent")
|
|
else { return SimpleContentType(base: .string) }
|
|
let resolved = simpleContentType(baseSimpleContent, context, visited: visited.union([baseName]))
|
|
context.simpleContentMemo.store[baseName] = resolved
|
|
return resolved
|
|
}
|
|
}
|