76 lines
3.2 KiB
Swift
76 lines
3.2 KiB
Swift
/// Table-context and foreign-content (SVG/MathML) helpers, split out so
|
|
/// HTMLDocument's own body stays within the type-body length budget.
|
|
extension HTMLDocument {
|
|
/// Tags that close an open `<select>` (the "in select" rule): a nested select,
|
|
/// or an input/keygen/textarea.
|
|
static let closesSelect: Set<String> = ["select", "input", "keygen", "textarea"]
|
|
|
|
/// Table-structural tags that close a select which is inside a table (the "in
|
|
/// select in table" rule).
|
|
private static let closesSelectInTable: Set<String> = ["caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th"]
|
|
|
|
/// The SVG HTML integration points: their content is parsed as HTML.
|
|
private static let svgIntegrationPoints: Set<String> = ["foreignobject", "desc", "title"]
|
|
|
|
/// The open-stack index of a `<select>` that an opening table tag should close
|
|
/// (a select inside a table), or nil when there is none.
|
|
func selectInTableToClose(for name: String) -> Int? {
|
|
guard Self.closesSelectInTable.contains(name),
|
|
let selectIndex = openBody.lastIndex(where: { tagName($0) == "select" }), selectIndex >= 1,
|
|
openBody[..<selectIndex].contains(where: { tagName($0) == "table" }) else { return nil }
|
|
return selectIndex
|
|
}
|
|
|
|
func bodyEnsureTableContext(for name: String) {
|
|
let structural: Set = ["table", "tbody", "thead", "tfoot", "tr"]
|
|
guard let context = openBody.reversed().first(where: { structural.contains(tagName($0)) }).map(tagName) else { return }
|
|
switch name {
|
|
case "tr" where context == "table":
|
|
bodyPushImplied("tbody")
|
|
case "td", "th":
|
|
if context == "table" {
|
|
bodyPushImplied("tbody")
|
|
}
|
|
if context == "table" || ["tbody", "thead", "tfoot"].contains(context) {
|
|
bodyPushImplied("tr")
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func bodyPushImplied(_ name: String) {
|
|
let element = PureXML.Model.TreeNode.element(name)
|
|
openBody.last?.append(element)
|
|
openBody.append(element)
|
|
}
|
|
|
|
/// Restores SVG attribute names to their canonical camel case.
|
|
func adjustedAttributes(_ attributes: [Attribute], namespace: String?) -> [Attribute] {
|
|
guard namespace == ForeignNamespace.svg else { return attributes }
|
|
return attributes.map { attribute in
|
|
guard let adjusted = PureXML.HTML.ForeignNames.svgAttributes[attribute.name.localName.lowercased()] else { return attribute }
|
|
return Attribute(adjusted, attribute.value)
|
|
}
|
|
}
|
|
|
|
/// The foreign-content namespace for a body element: SVG/MathML on entry, the
|
|
/// nearest open foreign ancestor's, or HTML (nil) inside an integration point.
|
|
func bodyForeignNamespace(for name: String) -> String? {
|
|
if name == "svg" {
|
|
return ForeignNamespace.svg
|
|
}
|
|
if name == "math" {
|
|
return ForeignNamespace.mathml
|
|
}
|
|
for node in openBody.reversed() {
|
|
guard let namespace = node.name?.namespaceURI else { continue }
|
|
if Self.svgIntegrationPoints.contains(tagName(node)) {
|
|
return nil
|
|
}
|
|
return namespace
|
|
}
|
|
return nil
|
|
}
|
|
}
|