feat(html): in-select-in-table closes select on table tags (#83)
This commit is contained in:
parent
7e56265ed2
commit
a5b032e9dc
@ -18,7 +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 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. In the "in select in table" case, a table-structural tag (`caption`/`table`/`tbody`/`tfoot`/`thead`/`tr`/`td`/`th`) closes a select that is inside a table before being processed.
|
||||
- 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 **749 tests in 117 suites** (`swift test`).
|
||||
currently runs **750 tests in 117 suites** (`swift test`).
|
||||
|
||||
### Shipped (libxml2-aligned surface)
|
||||
|
||||
|
||||
@ -227,11 +227,27 @@ final class HTMLDocument {
|
||||
/// or an input/keygen/textarea.
|
||||
private 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 open-stack index of a `<select>` that an opening table tag should close
|
||||
/// (a select inside a table), or nil when there is none.
|
||||
private 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
|
||||
}
|
||||
|
||||
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 selectIndex = selectInTableToClose(for: name) {
|
||||
openBody.removeLast(openBody.count - selectIndex)
|
||||
}
|
||||
if let closes = PureXML.HTML.Elements.impliedClose[name] {
|
||||
while let top = openBody.last, top !== bodyRoot, closes.contains(tagName(top)) {
|
||||
openBody.removeLast()
|
||||
|
||||
@ -29,4 +29,10 @@ struct HTMLSelectTests {
|
||||
func test_inputClosesSelect() {
|
||||
#expect(body("<select><option>a<input></select>") == "<select><option>a</option></select><input>")
|
||||
}
|
||||
|
||||
@Test("A table cell closes a select that is inside a table")
|
||||
func test_selectInTable() {
|
||||
let expected = "<table><tbody><tr><td>x<select><option>a</option></select></td><td>y</td></tr></tbody></table>"
|
||||
#expect(body("<table><tr><td>x<select><option>a<td>y</table>") == expected)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user