feat(html): select content model (optgroup/nested-select/input close) (#83)
This commit is contained in:
parent
936a3900d2
commit
3ebbaff07c
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- HTML5 select content model (#83, stage 10): a `<select>` now follows the in-select rules. `<optgroup>` closes an open `<option>`, a nested `<select>` closes the open one rather than nesting, and an `<input>`/`<keygen>`/`<textarea>` closes the select and is placed after it.
|
||||
- HTML5 frameset documents (#83, stage 8): a `<frameset>` after the head now produces a frameset document whose `frameset` element replaces the body (`<html><head></head><frameset>...`), with nested framesets and `<noframes>` handled, and `<frame>` is now a void element (it no longer wrongly nests). An ordinary document still produces a body.
|
||||
- HTML5 integration points (#83, stage 7): HTML content inside an SVG integration point (`foreignObject`, `desc`, `title`) is now parsed in the HTML namespace rather than SVG, so `<svg><foreignObject><div>` puts the `div` (and its descendants) back in HTML; re-entering `<svg>` inside an integration point switches back to the SVG namespace.
|
||||
- HTML5 table foster-parenting (#83, stage 6): stray flow content inside a table is now moved out rather than nested in the table structure, matching the HTML standard. A non-table element or character data appearing while a `table`/`tbody`/`thead`/`tfoot`/`tr` is the open node is inserted immediately before the table (`<table><b>x</table>` -> `<b>x</b><table></table>`), while well-formed cell content is untouched.
|
||||
|
||||
@ -89,7 +89,7 @@ flowchart TB
|
||||
|
||||
PureXML is a working, dependency-free XML library today: parse, emit, validate,
|
||||
query, and stream documents on macOS, Linux, Windows, and WASM. The test suite
|
||||
currently runs **745 tests in 116 suites** (`swift test`).
|
||||
currently runs **749 tests in 117 suites** (`swift test`).
|
||||
|
||||
### Shipped (libxml2-aligned surface)
|
||||
|
||||
|
||||
@ -223,7 +223,15 @@ final class HTMLDocument {
|
||||
node.name?.localName.lowercased() ?? ""
|
||||
}
|
||||
|
||||
/// Tags that close an open `<select>` (the "in select" rule): a nested select,
|
||||
/// or an input/keygen/textarea.
|
||||
private static let closesSelect: Set<String> = ["select", "input", "keygen", "textarea"]
|
||||
|
||||
private func bodyOpen(_ name: String, _ attributes: [(String, String)], selfClosing: Bool) {
|
||||
if Self.closesSelect.contains(name), let selectIndex = openBody.lastIndex(where: { tagName($0) == "select" }), selectIndex >= 1 {
|
||||
openBody.removeLast(openBody.count - selectIndex)
|
||||
if name == "select" { return } // a nested select just closes the open one
|
||||
}
|
||||
if let closes = PureXML.HTML.Elements.impliedClose[name] {
|
||||
while let top = openBody.last, top !== bodyRoot, closes.contains(tagName(top)) {
|
||||
openBody.removeLast()
|
||||
|
||||
@ -48,6 +48,7 @@ extension PureXML.HTML {
|
||||
"dd": ["dt", "dd"],
|
||||
"p": ["p"],
|
||||
"option": ["option"],
|
||||
"optgroup": ["option", "optgroup"],
|
||||
"tr": ["tr", "td", "th"],
|
||||
"td": ["td", "th"],
|
||||
"th": ["td", "th"],
|
||||
|
||||
32
Tests/HTMLSelectTests.swift
Normal file
32
Tests/HTMLSelectTests.swift
Normal file
@ -0,0 +1,32 @@
|
||||
@testable import PureXML
|
||||
import Testing
|
||||
|
||||
@Suite("HTML5 select content model")
|
||||
struct HTMLSelectTests {
|
||||
private func body(_ html: String) -> String {
|
||||
let full = PureXML.HTML.serialize(PureXML.HTML.parseDocument(html))
|
||||
let wrapper = "<html><head></head><body>"
|
||||
guard full.hasPrefix(wrapper), full.hasSuffix("</body></html>") else { return full }
|
||||
return String(full.dropFirst(wrapper.count).dropLast("</body></html>".count))
|
||||
}
|
||||
|
||||
@Test("Options nest and close one another")
|
||||
func test_options() {
|
||||
#expect(body("<select><option>a<option>b</select>") == "<select><option>a</option><option>b</option></select>")
|
||||
}
|
||||
|
||||
@Test("optgroup closes an open option")
|
||||
func test_optgroup() {
|
||||
#expect(body("<select><option>1<optgroup><option>2</select>") == "<select><option>1</option><optgroup><option>2</option></optgroup></select>")
|
||||
}
|
||||
|
||||
@Test("A nested select closes the open one")
|
||||
func test_nestedSelect() {
|
||||
#expect(body("<select><select>") == "<select></select>")
|
||||
}
|
||||
|
||||
@Test("An input closes the select and is placed after it")
|
||||
func test_inputClosesSelect() {
|
||||
#expect(body("<select><option>a<input></select>") == "<select><option>a</option></select><input>")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user