From e9dd76f49837519f4bde50b262c22f933379d6a8 Mon Sep 17 00:00:00 2001 From: VAN BOSSUYT Nicolas Date: Mon, 2 Sep 2024 15:44:38 +0200 Subject: [PATCH] vaev: Update from upstream. --- src/apps/hideo-browser/app.cpp | 4 +- src/apps/hideo-browser/app.h | 4 +- src/apps/hideo-browser/manifest.json | 2 - src/srvs/grund-bus/bus.h | 3 +- src/web/diagrams.tldr | 2349 ++++++++--------- src/web/vaev-base/font.h | 7 +- src/web/vaev-devtools/app.cpp | 18 +- src/web/vaev-devtools/app.h | 4 +- src/web/vaev-devtools/manifest.json | 2 - src/web/vaev-dom/_mod.h | 8 - src/web/vaev-dom/attr.h | 29 - src/web/vaev-dom/character-data.h | 38 - src/web/vaev-dom/comment.h | 18 - src/web/vaev-dom/document-type.h | 30 - src/web/vaev-dom/document.h | 24 - src/web/vaev-dom/element.h | 86 - src/web/vaev-dom/manifest.json | 10 - src/web/vaev-dom/node.h | 160 -- src/web/vaev-dom/tests/manifest.json | 15 - src/web/vaev-dom/tests/test-node.cpp | 11 - src/web/vaev-dom/text.h | 18 - src/web/vaev-dom/token-list.h | 54 - src/web/vaev-driver/fetcher.cpp | 14 +- src/web/vaev-driver/fetcher.h | 4 +- src/web/vaev-driver/manifest.json | 3 +- src/web/vaev-driver/render.cpp | 24 +- src/web/vaev-driver/render.h | 6 +- src/web/vaev-html/_mod.h | 5 - src/web/vaev-html/cli/main.cpp | 60 - src/web/vaev-html/defs/tags.inc | 112 - src/web/vaev-html/lexer.h | 185 -- src/web/vaev-html/manifest.json | 13 - src/web/vaev-html/parser.cpp | 1100 -------- src/web/vaev-html/parser.h | 94 - src/web/vaev-html/tags.cpp | 50 - src/web/vaev-html/tags.h | 29 - src/web/vaev-html/tests/manifest.json | 15 - src/web/vaev-layout/frag.cpp | 16 +- src/web/vaev-layout/frag.h | 6 +- src/web/vaev-layout/manifest.json | 2 +- src/web/vaev-markup/cli/main.cpp | 78 + .../cli/manifest.json | 6 +- .../defs/fetch-html-entities.py} | 0 .../defs/namespaces.inc | 0 .../defs/ns-html-attr-names.inc} | 0 .../defs/ns-html-entities.inc} | 0 .../defs/ns-html-special.inc} | 0 .../defs/ns-html-states.inc} | 0 .../defs/ns-html-tag-names.inc} | 0 .../defs/ns-mathml-attr-names.inc} | 0 .../defs/ns-mathml-tag-names.inc} | 0 .../defs/ns-svg-attr-names.inc} | 0 .../defs/ns-svg-tag-names.inc} | 0 src/web/vaev-markup/dom.h | 411 +++ .../lexer.cpp => vaev-markup/html.cpp} | 1329 +++++++++- src/web/vaev-markup/html.h | 263 ++ src/web/vaev-markup/manifest.json | 10 + src/web/vaev-markup/markup.h | 5 + src/web/vaev-markup/tags.cpp | 148 ++ src/web/{vaev-base => vaev-markup}/tags.h | 99 +- .../tests/manifest.json | 0 .../tests/test-xml-parser.cpp} | 109 +- .../parser.cpp => vaev-markup/xml.cpp} | 74 +- src/web/vaev-markup/xml.h | 63 + src/web/vaev-mathml/manifest.json | 10 - src/web/vaev-mathml/tags.cpp | 50 - src/web/vaev-mathml/tags.h | 24 - src/web/vaev-paint/box.h | 2 - src/web/vaev-style/computer.cpp | 4 +- src/web/vaev-style/computer.h | 6 +- src/web/vaev-style/manifest.json | 4 +- src/web/vaev-style/rules.h | 2 +- src/web/vaev-style/select.cpp | 34 +- src/web/vaev-style/select.h | 6 +- src/web/vaev-style/tests/test-select-spec.cpp | 2 +- src/web/vaev-svg/manifest.json | 10 - src/web/vaev-svg/tags.cpp | 50 - src/web/vaev-svg/tags.h | 24 - src/web/vaev-view/inspect.cpp | 31 +- src/web/vaev-view/inspect.h | 4 +- src/web/vaev-view/view.cpp | 8 +- src/web/vaev-view/view.h | 4 +- src/web/vaev-xml/_mod.h | 3 - src/web/vaev-xml/cli/main.cpp | 39 - src/web/vaev-xml/cli/manifest.json | 11 - src/web/vaev-xml/manifest.json | 10 - src/web/vaev-xml/parser.h | 67 - 87 files changed, 3542 insertions(+), 4090 deletions(-) delete mode 100644 src/web/vaev-dom/_mod.h delete mode 100644 src/web/vaev-dom/attr.h delete mode 100644 src/web/vaev-dom/character-data.h delete mode 100644 src/web/vaev-dom/comment.h delete mode 100644 src/web/vaev-dom/document-type.h delete mode 100644 src/web/vaev-dom/document.h delete mode 100644 src/web/vaev-dom/element.h delete mode 100644 src/web/vaev-dom/manifest.json delete mode 100644 src/web/vaev-dom/node.h delete mode 100644 src/web/vaev-dom/tests/manifest.json delete mode 100644 src/web/vaev-dom/tests/test-node.cpp delete mode 100644 src/web/vaev-dom/text.h delete mode 100644 src/web/vaev-dom/token-list.h delete mode 100644 src/web/vaev-html/_mod.h delete mode 100644 src/web/vaev-html/cli/main.cpp delete mode 100644 src/web/vaev-html/defs/tags.inc delete mode 100644 src/web/vaev-html/lexer.h delete mode 100644 src/web/vaev-html/manifest.json delete mode 100644 src/web/vaev-html/parser.cpp delete mode 100644 src/web/vaev-html/parser.h delete mode 100644 src/web/vaev-html/tags.cpp delete mode 100644 src/web/vaev-html/tags.h delete mode 100644 src/web/vaev-html/tests/manifest.json create mode 100644 src/web/vaev-markup/cli/main.cpp rename src/web/{vaev-html => vaev-markup}/cli/manifest.json (64%) rename src/web/{vaev-html/defs/fetchEntities.py => vaev-markup/defs/fetch-html-entities.py} (100%) rename src/web/{vaev-base => vaev-markup}/defs/namespaces.inc (100%) rename src/web/{vaev-html/defs/attr-names.inc => vaev-markup/defs/ns-html-attr-names.inc} (100%) rename src/web/{vaev-html/defs/entities.inc => vaev-markup/defs/ns-html-entities.inc} (100%) rename src/web/{vaev-html/defs/special.inc => vaev-markup/defs/ns-html-special.inc} (100%) rename src/web/{vaev-html/defs/states.inc => vaev-markup/defs/ns-html-states.inc} (100%) rename src/web/{vaev-html/defs/tag-names.inc => vaev-markup/defs/ns-html-tag-names.inc} (100%) rename src/web/{vaev-mathml/defs/attr-names.inc => vaev-markup/defs/ns-mathml-attr-names.inc} (100%) rename src/web/{vaev-mathml/defs/tag-names.inc => vaev-markup/defs/ns-mathml-tag-names.inc} (100%) rename src/web/{vaev-svg/defs/attr-names.inc => vaev-markup/defs/ns-svg-attr-names.inc} (100%) rename src/web/{vaev-svg/defs/tag-names.inc => vaev-markup/defs/ns-svg-tag-names.inc} (100%) create mode 100644 src/web/vaev-markup/dom.h rename src/web/{vaev-html/lexer.cpp => vaev-markup/html.cpp} (72%) create mode 100644 src/web/vaev-markup/html.h create mode 100644 src/web/vaev-markup/manifest.json create mode 100644 src/web/vaev-markup/markup.h create mode 100644 src/web/vaev-markup/tags.cpp rename src/web/{vaev-base => vaev-markup}/tags.h (76%) rename src/web/{vaev-xml => vaev-markup}/tests/manifest.json (100%) rename src/web/{vaev-xml/tests/test-parser.cpp => vaev-markup/tests/test-xml-parser.cpp} (53%) rename src/web/{vaev-xml/parser.cpp => vaev-markup/xml.cpp} (87%) create mode 100644 src/web/vaev-markup/xml.h delete mode 100644 src/web/vaev-mathml/manifest.json delete mode 100644 src/web/vaev-mathml/tags.cpp delete mode 100644 src/web/vaev-mathml/tags.h delete mode 100644 src/web/vaev-svg/manifest.json delete mode 100644 src/web/vaev-svg/tags.cpp delete mode 100644 src/web/vaev-svg/tags.h delete mode 100644 src/web/vaev-xml/_mod.h delete mode 100644 src/web/vaev-xml/cli/main.cpp delete mode 100644 src/web/vaev-xml/cli/manifest.json delete mode 100644 src/web/vaev-xml/manifest.json delete mode 100644 src/web/vaev-xml/parser.h diff --git a/src/apps/hideo-browser/app.cpp b/src/apps/hideo-browser/app.cpp index 5f0bbea1fc..e89c61cce8 100644 --- a/src/apps/hideo-browser/app.cpp +++ b/src/apps/hideo-browser/app.cpp @@ -23,7 +23,7 @@ enum struct SidePanel { struct State { Mime::Url url; - Res> dom; + Res> dom; SidePanel sidePanel = SidePanel::CLOSE; bool canGoBack() const { @@ -187,7 +187,7 @@ Ui::Child appContent(State const &s) { ); } -Ui::Child app(Mime::Url url, Res> dom) { +Ui::Child app(Mime::Url url, Res> dom) { return Ui::reducer( { url, diff --git a/src/apps/hideo-browser/app.h b/src/apps/hideo-browser/app.h index a970a64071..9f284e6aef 100644 --- a/src/apps/hideo-browser/app.h +++ b/src/apps/hideo-browser/app.h @@ -1,10 +1,10 @@ #pragma once #include -#include +#include namespace Hideo::Browser { -Ui::Child app(Mime::Url url, Res> dom); +Ui::Child app(Mime::Url url, Res> dom); } // namespace Hideo::Browser diff --git a/src/apps/hideo-browser/manifest.json b/src/apps/hideo-browser/manifest.json index 1fd36dd493..a0f2983384 100644 --- a/src/apps/hideo-browser/manifest.json +++ b/src/apps/hideo-browser/manifest.json @@ -5,8 +5,6 @@ "description": "Browse the web", "requires": [ "hideo-base", - "vaev-html", - "vaev-xml", "vaev-view" ] } diff --git a/src/srvs/grund-bus/bus.h b/src/srvs/grund-bus/bus.h index 50d63a9c40..7bf6f4f740 100644 --- a/src/srvs/grund-bus/bus.h +++ b/src/srvs/grund-bus/bus.h @@ -44,9 +44,8 @@ struct Bus { } Res<> run() { - for (auto &service : _services) { + for (auto &service : _services) try$(service->activate(_context)); - } logDebug("running system event loop"); while (true) { diff --git a/src/web/diagrams.tldr b/src/web/diagrams.tldr index 54e7afa686..953c0ae233 100644 --- a/src/web/diagrams.tldr +++ b/src/web/diagrams.tldr @@ -14,8 +14,8 @@ "com.tldraw.pointer": 1, "com.tldraw.shape": 4, "com.tldraw.asset.bookmark": 2, - "com.tldraw.asset.image": 4, - "com.tldraw.asset.video": 4, + "com.tldraw.asset.image": 5, + "com.tldraw.asset.video": 5, "com.tldraw.shape.group": 0, "com.tldraw.shape.text": 2, "com.tldraw.shape.bookmark": 2, @@ -43,9 +43,9 @@ { "id": "pointer:pointer", "typeName": "pointer", - "x": 444.6895122278537, - "y": 207.97377066617366, - "lastActivityTimestamp": 1721221241896, + "x": 1442.446213860517, + "y": 412.7936751330606, + "lastActivityTimestamp": 1725284503221, "meta": {} }, { @@ -56,9 +56,9 @@ "typeName": "page" }, { - "x": 1583.6001423862344, - "y": 385.34328254932086, - "z": 0.2792700295124377, + "x": 861.3833280887666, + "y": 445.0784933892799, + "z": 0.5634184278404026, "meta": {}, "id": "camera:page:page", "typeName": "camera" @@ -85,7 +85,7 @@ "tldraw:dash": "solid", "tldraw:fill": "none", "tldraw:font": "draw", - "tldraw:color": "black", + "tldraw:color": "light-green", "tldraw:arrowheadStart": "none", "tldraw:size": "m", "tldraw:horizontalAlign": "middle", @@ -104,8 +104,8 @@ "screenBounds": { "x": 0, "y": 0, - "w": 1540, - "h": 961 + "w": 2048, + "h": 1304.3199462890625 }, "insets": [ false, @@ -123,43 +123,18 @@ "devicePixelRatio": 2, "isCoarsePointer": false, "isHoveringCanvas": true, - "openMenus": [], + "openMenus": [ + "pages", + "page-menu" + ], "isChangingStyle": false, "isReadonly": false, "meta": {}, "duplicateProps": null, "id": "instance:instance", - "currentPageId": "page:cLMgWOZR-5aG2IfUReA7h", + "currentPageId": "page:yFpBFbmu_D1H7HSkcTIo4", "typeName": "instance" }, - { - "meta": {}, - "id": "page:9JoVeHp6Rv32QY2wIggiS", - "name": "Typed CSSOM", - "index": "a2", - "typeName": "page" - }, - { - "x": -483.4747179318439, - "y": 9.42658340648046, - "z": 0.5350180439746345, - "meta": {}, - "id": "camera:page:9JoVeHp6Rv32QY2wIggiS", - "typeName": "camera" - }, - { - "editingShapeId": null, - "croppingShapeId": null, - "selectedShapeIds": [], - "hoveredShapeId": null, - "erasingShapeIds": [], - "hintingShapeIds": [], - "focusedGroupId": null, - "meta": {}, - "id": "instance_page_state:page:9JoVeHp6Rv32QY2wIggiS", - "pageId": "page:9JoVeHp6Rv32QY2wIggiS", - "typeName": "instance_page_state" - }, { "meta": {}, "id": "page:yFpBFbmu_D1H7HSkcTIo4", @@ -193,7 +168,7 @@ "shape:NP88oWBNN5SGNWmsHn6zF", "shape:Wdk-RU6d3NV43N6T_BxHN" ], - "hoveredShapeId": null, + "hoveredShapeId": "shape:MZ7DQ8pxij5FnpqaYBCup", "erasingShapeIds": [], "hintingShapeIds": [], "focusedGroupId": null, @@ -298,7 +273,8 @@ "terminal": "start", "normalizedAnchor": { "x": 0.6837606837606839, - "y": 0.11206896551724138 + "y": 0.11206896551724138, + "z": 1 }, "isExact": false, "isPrecise": false @@ -315,7 +291,8 @@ "terminal": "end", "normalizedAnchor": { "x": 0.5, - "y": 0.5 + "y": 0.5, + "z": 1 }, "isExact": false, "isPrecise": true @@ -1199,7 +1176,8 @@ "terminal": "end", "normalizedAnchor": { "x": 0.20723537946656492, - "y": 0.4607591069476337 + "y": 0.4607591069476337, + "z": 1 }, "isExact": false, "isPrecise": true @@ -1216,7 +1194,8 @@ "terminal": "start", "normalizedAnchor": { "x": 0.5491157213469069, - "y": 0.4004142793614268 + "y": 0.4004142793614268, + "z": 1 }, "isExact": false, "isPrecise": false @@ -1335,7 +1314,8 @@ "isExact": false, "normalizedAnchor": { "x": 0.10534148876136999, - "y": 0.6046655871939904 + "y": 0.6046655871939904, + "z": 1 }, "terminal": "end" }, @@ -1352,7 +1332,8 @@ "isExact": false, "normalizedAnchor": { "x": 0.8584681707585367, - "y": 0.5627625655983048 + "y": 0.5627625655983048, + "z": 1 }, "terminal": "start" }, @@ -1369,7 +1350,8 @@ "isExact": false, "normalizedAnchor": { "x": 0.43174017587945107, - "y": 0.8245993711748544 + "y": 0.8245993711748544, + "z": 1 }, "terminal": "end" }, @@ -1386,7 +1368,8 @@ "isExact": false, "normalizedAnchor": { "x": 0.8834187493478599, - "y": 0.6923507151295232 + "y": 0.6923507151295232, + "z": 1 }, "terminal": "start" }, @@ -1403,7 +1386,8 @@ "isExact": false, "normalizedAnchor": { "x": 0.8786293052537725, - "y": 0.6520849346253322 + "y": 0.6520849346253322, + "z": 1 }, "terminal": "start" }, @@ -1420,208 +1404,1008 @@ "isExact": false, "normalizedAnchor": { "x": 0.7631214244850333, - "y": 0.5005124078992592 + "y": 0.5005124078992592, + "z": 1 }, "terminal": "end" }, "typeName": "binding" }, { - "x": 355.00000000000006, - "y": 359, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:nrxuBj1xZhF6VyXbS8WlE", - "type": "geo", + "id": "binding:lJnDuPFjHutet1SSprDXd", + "type": "arrow", + "fromId": "shape:8WI-nQbALUyyjZTO7gp6U", + "toId": "shape:ecHhio1cnohS_PMiPQxyh", "props": { - "w": 117, - "h": 116, - "geo": "rectangle", - "color": "light-green", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "CSSOM\n(XL)", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "start" }, - "parentId": "page:page", - "index": "a1", - "typeName": "shape" + "typeName": "binding" }, { - "x": 696, - "y": 368, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:1dITpQOo_B9stNjYL7aN8", - "type": "geo", + "id": "binding:bpf2rxbj6HVKt153F3z_X", + "type": "arrow", + "fromId": "shape:8WI-nQbALUyyjZTO7gp6U", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", "props": { - "w": 133, - "h": 114, - "geo": "rectangle", - "color": "red", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "Selector Tree\n(l)", - "align": "middle", - "verticalAlign": "middle", - "growY": 7.0859375, - "url": "", - "scale": 1 + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5561332169107038, + "y": 0.5472406402386196 + }, + "terminal": "end" }, - "parentId": "page:page", - "index": "aI", - "typeName": "shape" + "typeName": "binding" }, { - "x": -227.87526983388727, - "y": 899.6668663111252, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:-2z6KOuce7gsB-nDKqSn7", - "type": "geo", + "id": "binding:NoT11eXQVEzi8hlua3mlA", + "type": "arrow", + "fromId": "shape:DdiuaCXDjbcLLGLZNoMqv", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", "props": { - "w": 117, - "h": 116, - "geo": "rectangle", - "color": "light-green", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "DOM\n(L)", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5089558337962136, + "y": 0.6477328859908337 + }, + "terminal": "start" }, - "parentId": "page:page", - "index": "a4", - "typeName": "shape" + "typeName": "binding" }, { - "x": 1021.5555902532456, - "y": 392.9388284241478, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:94xqKqCmSzIWfYgclDAvp", - "type": "geo", + "id": "binding:DmBs8tFbmOcICCuMHfIBD", + "type": "arrow", + "fromId": "shape:DdiuaCXDjbcLLGLZNoMqv", + "toId": "shape:oh8JlJtUJgIaRmNRnclCS", "props": { - "w": 235, - "h": 95.0625, - "geo": "rectangle", - "color": "light-green", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "Computed Styles\n(s)", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5443500720874996, + "y": 0.4532523077853807 + }, + "terminal": "end" }, - "parentId": "page:page", - "index": "aJ", - "typeName": "shape" + "typeName": "binding" }, { - "x": 1523.4422532320477, - "y": 408.52897904218537, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:-otjkeBARBJfbm_74wDNC", - "type": "geo", + "id": "binding:A2kLdTH6yVYYnZE7byBtB", + "type": "arrow", + "fromId": "shape:mYo-pOT1oh9enmMr2psmi", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", "props": { - "w": 235, - "h": 95.0625, - "geo": "rectangle", - "color": "orange", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "Layout Tree\n(xxl)", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.4890255216146404, + "y": 0.5863690984939004 + }, + "terminal": "start" }, - "parentId": "page:page", - "index": "aK", - "typeName": "shape" + "typeName": "binding" }, { - "x": 1875.2788769826857, - "y": 405.92006676874075, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:nAhGeGnoMrL03qcC5HDTm", - "type": "geo", + "id": "binding:BSG0ZT7sckMJ65LneME9L", + "type": "arrow", + "fromId": "shape:mYo-pOT1oh9enmMr2psmi", + "toId": "shape:o38XXNIz6srzWv3V1RAkr", "props": { - "w": 235, - "h": 95.0625, - "geo": "rectangle", - "color": "orange", - "labelColor": "black", - "fill": "solid", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "Paint Tree\n(s)", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.3299203470025974, + "y": 0.2723750654574844 + }, + "terminal": "end" }, - "parentId": "page:page", - "index": "aL", - "typeName": "shape" + "typeName": "binding" }, { - "x": 522, - "y": 436, - "rotation": 0, - "isLocked": false, - "opacity": 1, "meta": {}, - "id": "shape:1muTEo_OPEbzOUFTB_F-C", + "id": "binding:JxxLT_MXxXz6zTy9GtJ3H", "type": "arrow", - "parentId": "page:page", - "index": "aIV", + "fromId": "shape:afaAzU_Tn-c8qDcYljnqO", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", "props": { - "dash": "draw", - "size": "m", + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.843013003290062, + "y": 0.20739313816204996 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:gELv0nt81VpPLQV_Oi2JQ", + "type": "arrow", + "fromId": "shape:afaAzU_Tn-c8qDcYljnqO", + "toId": "shape:_CieIgMkJDirDgo47t6cV", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.40008127144545813, + "y": 0.17464425266110603 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:8WvAiE5qsEofzqiotzTX9", + "type": "arrow", + "fromId": "shape:Lv1CwLUA6kNw3mHULpWHT", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.8077758522483165, + "y": 0.5017210279941781 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:MbySwyvFqgsa4p9PKvJGp", + "type": "arrow", + "fromId": "shape:Lv1CwLUA6kNw3mHULpWHT", + "toId": "shape:pTgs1G8uwylUD4S6uHv6G", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.69801598916745, + "y": 0.4511792344250594 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:fgq4be0GLEggvabcAAIfG", + "type": "arrow", + "fromId": "shape:QC8jmmtoDTOiM6q5OTqs5", + "toId": "shape:dGvyydmW1DDJ8yfbMUc02", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.844045429371539, + "y": 0.6164446307058363 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:oJQ84DJuW7xykod2wik1W", + "type": "arrow", + "fromId": "shape:QC8jmmtoDTOiM6q5OTqs5", + "toId": "shape:S646siSusBSnG4qkkffxR", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.4419794367015968, + "y": 0.318235169740979 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:vYAV7WRlkYKOnfGr4I_Kj", + "type": "arrow", + "fromId": "shape:zFte5crnymTOho0UfCz5i", + "toId": "shape:Fiz3OfZZBu0ofvlMoMwSu", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.31233765434314203, + "y": 0.3013522973053976 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:xFvTv77Q2zvnlTn4i8x-p", + "type": "arrow", + "fromId": "shape:W88g3vmrlJ-9GGjk4wbbJ", + "toId": "shape:n6Qg45IYgl4TOJ0-QruZE", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.3518405669132706, + "y": 0.0427513976704394 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:WvcjxXqoIEeX0pPRiqayS", + "type": "arrow", + "fromId": "shape:zFte5crnymTOho0UfCz5i", + "toId": "shape:ecHhio1cnohS_PMiPQxyh", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:2q6BGhJ6JumP5hnn9DIPi", + "type": "arrow", + "fromId": "shape:W88g3vmrlJ-9GGjk4wbbJ", + "toId": "shape:ecHhio1cnohS_PMiPQxyh", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:qxU9BeXbWhg8zCF9gbLjV", + "type": "arrow", + "fromId": "shape:doy8IMjVjJJQZGmFe0kZ1", + "toId": "shape:HKUtHD0o5eDKknEGx4JPv", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.574016623512874, + "y": 0.7701465928132566 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:JbNzXWHltmfBQW33856fG", + "type": "arrow", + "fromId": "shape:KD0hds0ZwCjhSvkTX7P8t", + "toId": "shape:Ecdwq81oAVoThUpg4v1Ln", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.4652117208020428, + "y": 0.09439141344800922 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:ZmLrtSfWIrFZzEu1R_KT8", + "type": "arrow", + "fromId": "shape:jC7QLihbKs1mfvgFWA2Z8", + "toId": "shape:Ecdwq81oAVoThUpg4v1Ln", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.4722587451874189, + "y": 0.8356319033558031 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:CnzqqukUfFtTt9DK1XxRF", + "type": "arrow", + "fromId": "shape:jC7QLihbKs1mfvgFWA2Z8", + "toId": "shape:tq1Ith_lkEnL2-kW2dvsj", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:JJ-eLrbNtiwOqSHHLk1Es", + "type": "arrow", + "fromId": "shape:sCD2qEebJRAyPOFQX2d5k", + "toId": "shape:tq1Ith_lkEnL2-kW2dvsj", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5116610144523434, + "y": 0.7262933082911631 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:ikGCOMX0L7jtb_9rA1-Ra", + "type": "arrow", + "fromId": "shape:Gj1Qoj1bG1s_gaV69btBJ", + "toId": "shape:J9dbYGfcRvuZHx7Bxc3xl", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.4760527587202259, + "y": 0.6455333446891199 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:mXfWVgHVP5JiCJbmWeong", + "type": "arrow", + "fromId": "shape:Gj1Qoj1bG1s_gaV69btBJ", + "toId": "shape:HU-nJuXyu6X8DaKCc8BoY", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:AzfUbCfcG5iqxFDo-MXZY", + "type": "arrow", + "fromId": "shape:VmExTuRjs76QkI9wwL1RO", + "toId": "shape:HU-nJuXyu6X8DaKCc8BoY", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5179630709492152, + "y": 0.6824667702970337 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:YE-q8CQJYmcYDxCfd6F9G", + "type": "arrow", + "fromId": "shape:VmExTuRjs76QkI9wwL1RO", + "toId": "shape:bf3C_BqpKXzs-zippKcxr", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:fwIkBKkePsI5vbPpDHJZO", + "type": "arrow", + "fromId": "shape:xNsVlSiZuIU5pQtdde5Rq", + "toId": "shape:BRThI9G1s7Sp0kedeCn74", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:DhhAmqQFhWzchUkLsv97-", + "type": "arrow", + "fromId": "shape:xNsVlSiZuIU5pQtdde5Rq", + "toId": "shape:bf3C_BqpKXzs-zippKcxr", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.4958898192382167, + "y": 0.8242834309090417 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:ojPmKvYhPX4TvTHoBwWBA", + "type": "arrow", + "fromId": "shape:Md5Vq1ca1INzCSN_oG-4A", + "toId": "shape:0DBbCVAgZj3VQkyOQt3j4", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.48799936790510706, + "y": 0.868449774722568 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:M1oSsKDILhZfK8QNd-M61", + "type": "arrow", + "fromId": "shape:sCD2qEebJRAyPOFQX2d5k", + "toId": "shape:9wPLWMO1IxLjBBdYkpp__", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5140813380182341, + "y": 0.28378180860738755 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:z6vQHBrMyI5FzuxZ7JnHS", + "type": "arrow", + "fromId": "shape:Md5Vq1ca1INzCSN_oG-4A", + "toId": "shape:mX8pZtoVXOH6ajZy8_LL1", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.49823159150083823, + "y": 0.17586445771439593 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:Ora_fAusrHY--DeXjTENr", + "type": "arrow", + "fromId": "shape:KD0hds0ZwCjhSvkTX7P8t", + "toId": "shape:mX8pZtoVXOH6ajZy8_LL1", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.49533140760811983, + "y": 0.5292825728437602 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:xbQqYLvVVM8j0i7i2I9nj", + "type": "arrow", + "fromId": "shape:UN7a8DRY6_VnMXioyug2A", + "toId": "shape:RtLQKw5Y25Ckgzs_12MIz", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5787901989035433, + "y": 0.5950710338394019 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:fu4K6Uvsi7vlU4M33wxCG", + "type": "arrow", + "fromId": "shape:UN7a8DRY6_VnMXioyug2A", + "toId": "shape:ORVVQay1UdQjPeuDnp2oL", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:x1WGSWzI7xq11qk2nuIl0", + "type": "arrow", + "fromId": "shape:skO73nWViwgr-18kdcHn8", + "toId": "shape:ORVVQay1UdQjPeuDnp2oL", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.5029034045745147, + "y": 0.6467943717311037 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:dBRz2WEcCak_RR8GbW5Oz", + "type": "arrow", + "fromId": "shape:skO73nWViwgr-18kdcHn8", + "toId": "shape:RyW9ZWWbh7SVHiIxsO-dl", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.499108687447034, + "y": 0.12770651360001972 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:sEdDvKUXBr7dHBzZ_iznZ", + "type": "arrow", + "fromId": "shape:np4oAZr2mFP6bFbu6htVh", + "toId": "shape:RyW9ZWWbh7SVHiIxsO-dl", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.49616353385555617, + "y": 0.5856775946723972 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:NCMVffz12MjB04jdLkYfd", + "type": "arrow", + "fromId": "shape:np4oAZr2mFP6bFbu6htVh", + "toId": "shape:yB293XsUERo2ab2_dRRV-", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.49923518974015024, + "y": 0.020875795162211355 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:ihWrs7xas_sZ7EjT4O0x-", + "type": "arrow", + "fromId": "shape:AWLLnXrzeeDMgnWsINHvL", + "toId": "shape:yB293XsUERo2ab2_dRRV-", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.4972722140255442, + "y": 0.667008430006586 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:-O65N2T6FrIf1tonzRja-", + "type": "arrow", + "fromId": "shape:AWLLnXrzeeDMgnWsINHvL", + "toId": "shape:a64GoYkJPgbwlPULKA-Cs", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:JH_WSZpZ69n6dBryEtW7R", + "type": "arrow", + "fromId": "shape:yr38U4dQJQugvcvfHfjc8", + "toId": "shape:a64GoYkJPgbwlPULKA-Cs", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.29843775053519317, + "y": 0.6251327263479867 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:AqGKSHrqaiBXwkIcqfQIU", + "type": "arrow", + "fromId": "shape:yr38U4dQJQugvcvfHfjc8", + "toId": "shape:L7ULcorXeep4XH9TGrZv_", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:GTWb6tnKE7_FiBUVwOBYd", + "type": "arrow", + "fromId": "shape:rSQeh5USL7kQ3QjxQWRhl", + "toId": "shape:a64GoYkJPgbwlPULKA-Cs", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.7880688330785064, + "y": 0.6998676393435734 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:FJKnf2iPRAqF9Ldh4dwT9", + "type": "arrow", + "fromId": "shape:rSQeh5USL7kQ3QjxQWRhl", + "toId": "shape:ORVVQay1UdQjPeuDnp2oL", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.9694836985474617, + "y": 0.6410267288629806 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:vU1X5YLefXjGbTzb3Wczs", + "type": "arrow", + "fromId": "shape:hfjqi6Yo3NzVwE2SXt4bR", + "toId": "shape:y2RsMDHaIh9gTuySjA4fG", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.4601487269146303, + "y": 0.8188462743313013 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:GgxNBIhwre5qMdfRrer_y", + "type": "arrow", + "fromId": "shape:doy8IMjVjJJQZGmFe0kZ1", + "toId": "shape:y2RsMDHaIh9gTuySjA4fG", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.4844310530899227, + "y": 0.23580232278272176 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:8IIBiqmkLaQBPTON_JqpM", + "type": "arrow", + "fromId": "shape:hPvjZJEYajguqQm0IJnfl", + "toId": "shape:R9lx-6kRc2EU_MHzFwIhl", + "props": { + "isPrecise": false, + "isExact": false, + "normalizedAnchor": { + "x": 0.43477751250709834, + "y": 0.6153148293131357 + }, + "terminal": "start" + }, + "typeName": "binding" + }, + { + "meta": {}, + "id": "binding:gr7fOwtrtp-s44oOITJ4o", + "type": "arrow", + "fromId": "shape:hPvjZJEYajguqQm0IJnfl", + "toId": "shape:J9dbYGfcRvuZHx7Bxc3xl", + "props": { + "isPrecise": true, + "isExact": false, + "normalizedAnchor": { + "x": 0.5, + "y": 0.5 + }, + "terminal": "end" + }, + "typeName": "binding" + }, + { + "x": 355.00000000000006, + "y": 359, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:nrxuBj1xZhF6VyXbS8WlE", + "type": "geo", + "props": { + "w": 117, + "h": 116, + "geo": "rectangle", + "color": "light-green", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "CSSOM\n(XL)", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "a1", + "typeName": "shape" + }, + { + "x": 696, + "y": 368, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:1dITpQOo_B9stNjYL7aN8", + "type": "geo", + "props": { + "w": 133, + "h": 114, + "geo": "rectangle", + "color": "red", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "Selector Tree\n(l)", + "align": "middle", + "verticalAlign": "middle", + "growY": 7.0859375, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aI", + "typeName": "shape" + }, + { + "x": -19.6973507438164, + "y": 893.7906339386939, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:-2z6KOuce7gsB-nDKqSn7", + "type": "geo", + "props": { + "w": 109.8753574372225, + "h": 108.93625181810094, + "geo": "rectangle", + "color": "light-green", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "DOM\n(L)", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "a4", + "typeName": "shape" + }, + { + "x": 1021.5555902532456, + "y": 392.9388284241478, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:94xqKqCmSzIWfYgclDAvp", + "type": "geo", + "props": { + "w": 235, + "h": 95.0625, + "geo": "rectangle", + "color": "light-green", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "Computed Styles\n(s)", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aJ", + "typeName": "shape" + }, + { + "x": 1523.4422532320477, + "y": 408.52897904218537, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:-otjkeBARBJfbm_74wDNC", + "type": "geo", + "props": { + "w": 235, + "h": 95.0625, + "geo": "rectangle", + "color": "orange", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "Layout Tree\n(xxl)", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aK", + "typeName": "shape" + }, + { + "x": 1875.2788769826857, + "y": 405.92006676874075, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:nAhGeGnoMrL03qcC5HDTm", + "type": "geo", + "props": { + "w": 235, + "h": 95.0625, + "geo": "rectangle", + "color": "yellow", + "labelColor": "black", + "fill": "solid", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "Paint Tree\n(s)", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aL", + "typeName": "shape" + }, + { + "x": 522, + "y": 436, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:1muTEo_OPEbzOUFTB_F-C", + "type": "arrow", + "parentId": "page:page", + "index": "aIV", + "props": { + "dash": "draw", + "size": "m", "fill": "semi", - "color": "yellow", + "color": "light-green", "labelColor": "black", "bend": 0, "start": { @@ -1656,7 +2440,7 @@ "dash": "draw", "size": "m", "fill": "semi", - "color": "yellow", + "color": "light-green", "labelColor": "black", "bend": 0, "start": { @@ -1689,7 +2473,7 @@ "w": 48, "h": 48, "geo": "diamond", - "color": "yellow", + "color": "light-green", "labelColor": "black", "fill": "solid", "dash": "draw", @@ -1721,7 +2505,7 @@ "dash": "draw", "size": "m", "fill": "semi", - "color": "yellow", + "color": "light-green", "labelColor": "black", "bend": 0, "start": { @@ -1742,8 +2526,8 @@ "typeName": "shape" }, { - "x": -326.6873748898789, - "y": 885.0333078516196, + "x": -112.49235383912821, + "y": 880.0481769616283, "rotation": 0, "isLocked": false, "opacity": 1, @@ -1756,9 +2540,9 @@ "dash": "draw", "size": "m", "fill": "semi", - "color": "yellow", + "color": "light-green", "labelColor": "black", - "bend": 214.992956982522, + "bend": 201.9010939738462, "start": { "x": 0, "y": 0 @@ -1847,8 +2631,8 @@ "typeName": "shape" }, { - "x": -459.8277655755331, - "y": 330.9884483391678, + "x": -459.0851606262518, + "y": 333.4392826498819, "rotation": 0, "isLocked": false, "opacity": 1, @@ -1877,8 +2661,8 @@ "typeName": "shape" }, { - "x": 15.946278022183662, - "y": 400.133189554349, + "x": 16.68888297146492, + "y": 402.58402386506305, "rotation": 0, "isLocked": false, "opacity": 1, @@ -1912,8 +2696,8 @@ "typeName": "shape" }, { - "x": -867.0918896643735, - "y": 744.4088986183713, + "x": -619.989270262516, + "y": 747.987004065035, "rotation": 0, "isLocked": false, "opacity": 1, @@ -1921,8 +2705,8 @@ "id": "shape:an-mRELJ9bpTLDdVzJOoU", "type": "geo", "props": { - "w": 116.99999999999999, - "h": 116, + "w": 109.87535743722249, + "h": 108.93625181810094, "geo": "rectangle", "color": "black", "labelColor": "black", @@ -1942,8 +2726,8 @@ "typeName": "shape" }, { - "x": -1454.4066460597212, - "y": 916.6269389761577, + "x": -1171.5398581863974, + "y": 909.7179334791358, "rotation": 0, "isLocked": false, "opacity": 1, @@ -1958,7 +2742,7 @@ "fill": "semi", "color": "yellow", "labelColor": "black", - "bend": -43.01096212250768, + "bend": -40.391836213071535, "start": { "x": 0, "y": 0 @@ -2019,7 +2803,7 @@ "w": 235, "h": 95.0625, "geo": "rectangle", - "color": "orange", + "color": "light-green", "labelColor": "black", "fill": "solid", "dash": "draw", @@ -2086,7 +2870,7 @@ "dash": "draw", "size": "m", "fill": "semi", - "color": "orange", + "color": "light-green", "labelColor": "black", "bend": 0, "start": { @@ -4355,8 +5139,8 @@ "typeName": "shape" }, { - "x": -841.5250142006614, - "y": 1067.437238467542, + "x": -619.2905978411872, + "y": 891.2395225390601, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4364,8 +5148,8 @@ "id": "shape:VYZBJu0cDI8xSeEU6ZZNk", "type": "geo", "props": { - "w": 116.99999999999999, - "h": 116, + "w": 109.87535743722249, + "h": 108.93625181810094, "geo": "rectangle", "color": "black", "labelColor": "black", @@ -4385,8 +5169,8 @@ "typeName": "shape" }, { - "x": -1442.0735685658142, - "y": 1105.9824965703203, + "x": -1159.9577958108075, + "y": 1087.54280162771, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4401,7 +5185,7 @@ "fill": "semi", "color": "light-green", "labelColor": "black", - "bend": 24.71946758364598, + "bend": 23.214190909495187, "start": { "x": 93.97080772574304, "y": -13.308894492458649 @@ -4443,38 +5227,8 @@ "typeName": "shape" }, { - "x": -898.2268619420303, - "y": 957.9345855463699, - "rotation": 0, - "isLocked": false, - "opacity": 1, - "meta": {}, - "id": "shape:pZBbPDQfDw1nfE6mx2aqj", - "type": "geo", - "props": { - "w": 338.9389605855979, - "h": 904.4091312547383, - "geo": "rectangle", - "color": "grey", - "labelColor": "black", - "fill": "none", - "dash": "dotted", - "size": "m", - "font": "draw", - "text": "vaev-xml", - "align": "start", - "verticalAlign": "start", - "growY": 0, - "url": "", - "scale": 1 - }, - "parentId": "page:page", - "index": "a0", - "typeName": "shape" - }, - { - "x": -1139.9761678146037, - "y": 654.9208360376607, + "x": -662.4187445099511, + "y": 663.948261651188, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4482,8 +5236,8 @@ "id": "shape:14VBnqOJu6HY8sXCmaeuI", "type": "geo", "props": { - "w": 627.339581082575, - "h": 1265.831300908972, + "w": 806.135180174703, + "h": 654.5173297846745, "geo": "rectangle", "color": "grey", "labelColor": "black", @@ -4491,7 +5245,7 @@ "dash": "dotted", "size": "l", "font": "draw", - "text": "vaev-html", + "text": "vaev-markup", "align": "start", "verticalAlign": "start", "growY": 0, @@ -4503,8 +5257,8 @@ "typeName": "shape" }, { - "x": -485.3274008272995, - "y": 202.12009983333627, + "x": -484.58479587801827, + "y": 204.57093414405034, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4533,8 +5287,8 @@ "typeName": "shape" }, { - "x": -47.45754492492853, - "y": 349.831991469169, + "x": -46.71493997564727, + "y": 352.28282577988307, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4688,38 +5442,8 @@ "typeName": "shape" }, { - "x": -332.5683336706928, - "y": 710.6350389741426, - "rotation": 0, - "isLocked": false, - "opacity": 1, - "meta": {}, - "id": "shape:Gh_xFUoGkWjAQRzxmdGXB", - "type": "geo", - "props": { - "w": 350.0798620360612, - "h": 421.9652387733921, - "geo": "rectangle", - "color": "grey", - "labelColor": "black", - "fill": "none", - "dash": "dotted", - "size": "l", - "font": "draw", - "text": "vaev-dom", - "align": "start", - "verticalAlign": "start", - "growY": 0, - "url": "", - "scale": 1 - }, - "parentId": "page:page", - "index": "ag", - "typeName": "shape" - }, - { - "x": -854.777613742632, - "y": 1337.8353483258732, + "x": -617.9891561038279, + "y": 1022.7513536391698, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4727,8 +5451,8 @@ "id": "shape:3QllRh6OEnlEicjbnbzc5", "type": "geo", "props": { - "w": 116.99999999999999, - "h": 116, + "w": 109.87535743722249, + "h": 108.93625181810094, "geo": "rectangle", "color": "black", "labelColor": "black", @@ -4748,8 +5472,8 @@ "typeName": "shape" }, { - "x": -1442.0735685658142, - "y": 1105.9824965703203, + "x": -1159.9577958108075, + "y": 1087.54280162771, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4764,7 +5488,7 @@ "fill": "semi", "color": "light-green", "labelColor": "black", - "bend": 132.5782394121563, + "bend": 57.06373307913024, "start": { "x": 93.85582761715307, "y": 68.39422682824534 @@ -4783,68 +5507,8 @@ "typeName": "shape" }, { - "x": -1121.8172282110404, - "y": 1263.84057572848, - "rotation": 0, - "isLocked": false, - "opacity": 1, - "meta": {}, - "id": "shape:R6wTklHfkicy44pZW-JgU", - "type": "geo", - "props": { - "w": 585.9949461580503, - "h": 297.98896946391295, - "geo": "rectangle", - "color": "light-blue", - "labelColor": "black", - "fill": "solid", - "dash": "dotted", - "size": "l", - "font": "draw", - "text": "vaev-svg", - "align": "start", - "verticalAlign": "start", - "growY": 0, - "url": "", - "scale": 1 - }, - "parentId": "page:page", - "index": "Zu", - "typeName": "shape" - }, - { - "x": -1119.356733931993, - "y": 1594.999111537917, - "rotation": 0, - "isLocked": false, - "opacity": 1, - "meta": {}, - "id": "shape:C-IDrsy71iwbZxslvN6oR", - "type": "geo", - "props": { - "w": 582.6819440926066, - "h": 297.98896946391295, - "geo": "rectangle", - "color": "light-blue", - "labelColor": "black", - "fill": "solid", - "dash": "dotted", - "size": "l", - "font": "draw", - "text": "vaev-mathml", - "align": "start", - "verticalAlign": "start", - "growY": 0, - "url": "", - "scale": 1 - }, - "parentId": "page:page", - "index": "Zv", - "typeName": "shape" - }, - { - "x": -851.9102595608106, - "y": 1711.154054197016, + "x": -618.0136698720536, + "y": 1151.485004216612, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4852,8 +5516,8 @@ "id": "shape:iWe1N5lV3hbdYDeBb4y5d", "type": "geo", "props": { - "w": 136.00682015207644, - "h": 116, + "w": 127.72476904367039, + "h": 108.93625181810094, "geo": "rectangle", "color": "black", "labelColor": "black", @@ -4873,8 +5537,8 @@ "typeName": "shape" }, { - "x": -1442.0735685658142, - "y": 1105.9824965703203, + "x": -1159.9577958108075, + "y": 1087.54280162771, "rotation": 0, "isLocked": false, "opacity": 1, @@ -4889,7 +5553,7 @@ "fill": "semi", "color": "light-green", "labelColor": "black", - "bend": 141.43714123979467, + "bend": 112.95893552244387, "start": { "x": 110.77010715606787, "y": 533.8398734895063 @@ -4908,7 +5572,7 @@ "typeName": "shape" }, { - "x": -1181.21806645294, + "x": -730.1185367277258, "y": 56.29693420602359, "rotation": 0, "isLocked": false, @@ -4917,8 +5581,8 @@ "id": "shape:cS98SGX8t8qfXn26oXLy6", "type": "geo", "props": { - "w": 3760.7096378771694, - "h": 1909.0904763509068, + "w": 3309.610108151955, + "h": 1310.5788121213045, "geo": "rectangle", "color": "grey", "labelColor": "black", @@ -4958,8 +5622,8 @@ "y": -41.33856329449054 }, "end": { - "x": 5.266898028821743e-13, - "y": 1965.1520404012135 + "x": 1.2025935802739696e-12, + "y": 1379.4645446903926 }, "arrowheadStart": "none", "arrowheadEnd": "none", @@ -4993,8 +5657,8 @@ "y": -49.26958775614139 }, "end": { - "x": 1.1405321131974233e-12, - "y": 1987.0855403344663 + "x": 1.2080336730946328e-12, + "y": 1414.1551550165113 }, "arrowheadStart": "none", "arrowheadEnd": "none", @@ -5008,8 +5672,8 @@ "typeName": "shape" }, { - "x": -730.5254059432987, - "y": 77.99852297160118, + "x": -488.56254750859637, + "y": 70.94315267748954, "rotation": 0, "isLocked": false, "opacity": 1, @@ -5076,36 +5740,6 @@ "index": "as", "typeName": "shape" }, - { - "meta": {}, - "id": "page:cLMgWOZR-5aG2IfUReA7h", - "name": "Layout Algorithm", - "index": "a4", - "typeName": "page" - }, - { - "x": 591.1525447671376, - "y": 509.1375355774629, - "z": 0.5345665960951396, - "meta": {}, - "id": "camera:page:cLMgWOZR-5aG2IfUReA7h", - "typeName": "camera" - }, - { - "editingShapeId": "shape:ecHhio1cnohS_PMiPQxyh", - "croppingShapeId": null, - "selectedShapeIds": [ - "shape:ecHhio1cnohS_PMiPQxyh" - ], - "hoveredShapeId": null, - "erasingShapeIds": [], - "hintingShapeIds": [], - "focusedGroupId": null, - "meta": {}, - "id": "instance_page_state:page:cLMgWOZR-5aG2IfUReA7h", - "pageId": "page:cLMgWOZR-5aG2IfUReA7h", - "typeName": "instance_page_state" - }, { "x": 124.53447175356456, "y": -220.32425147549904, @@ -5381,40 +6015,6 @@ "index": "a2V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:lJnDuPFjHutet1SSprDXd", - "type": "arrow", - "fromId": "shape:8WI-nQbALUyyjZTO7gp6U", - "toId": "shape:ecHhio1cnohS_PMiPQxyh", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:bpf2rxbj6HVKt153F3z_X", - "type": "arrow", - "fromId": "shape:8WI-nQbALUyyjZTO7gp6U", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5561332169107038, - "y": 0.5472406402386196 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 103.18376961558795, "y": 389.7058227755289, @@ -5480,40 +6080,6 @@ "index": "a3V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:NoT11eXQVEzi8hlua3mlA", - "type": "arrow", - "fromId": "shape:DdiuaCXDjbcLLGLZNoMqv", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5089558337962136, - "y": 0.6477328859908337 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:DmBs8tFbmOcICCuMHfIBD", - "type": "arrow", - "fromId": "shape:DdiuaCXDjbcLLGLZNoMqv", - "toId": "shape:oh8JlJtUJgIaRmNRnclCS", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5443500720874996, - "y": 0.4532523077853807 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": -60.11212642625293, "y": 221.9597060063295, @@ -5547,41 +6113,7 @@ }, "parentId": "page:cLMgWOZR-5aG2IfUReA7h", "index": "a6V", - "typeName": "shape" - }, - { - "meta": {}, - "id": "binding:A2kLdTH6yVYYnZE7byBtB", - "type": "arrow", - "fromId": "shape:mYo-pOT1oh9enmMr2psmi", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.4890255216146404, - "y": 0.5863690984939004 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:BSG0ZT7sckMJ65LneME9L", - "type": "arrow", - "fromId": "shape:mYo-pOT1oh9enmMr2psmi", - "toId": "shape:o38XXNIz6srzWv3V1RAkr", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.3299203470025974, - "y": 0.2723750654574844 - }, - "terminal": "end" - }, - "typeName": "binding" + "typeName": "shape" }, { "x": -1.1096036016816697, @@ -5618,40 +6150,6 @@ "index": "a4V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:JxxLT_MXxXz6zTy9GtJ3H", - "type": "arrow", - "fromId": "shape:afaAzU_Tn-c8qDcYljnqO", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.843013003290062, - "y": 0.20739313816204996 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:gELv0nt81VpPLQV_Oi2JQ", - "type": "arrow", - "fromId": "shape:afaAzU_Tn-c8qDcYljnqO", - "toId": "shape:_CieIgMkJDirDgo47t6cV", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.40008127144545813, - "y": 0.17464425266110603 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": -6.982920925710118, "y": 216.7373168443271, @@ -5687,40 +6185,6 @@ "index": "a5V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:8WvAiE5qsEofzqiotzTX9", - "type": "arrow", - "fromId": "shape:Lv1CwLUA6kNw3mHULpWHT", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.8077758522483165, - "y": 0.5017210279941781 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:MbySwyvFqgsa4p9PKvJGp", - "type": "arrow", - "fromId": "shape:Lv1CwLUA6kNw3mHULpWHT", - "toId": "shape:pTgs1G8uwylUD4S6uHv6G", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.69801598916745, - "y": 0.4511792344250594 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": -0.9375191450542388, "y": 223.8152253647487, @@ -5756,40 +6220,6 @@ "index": "a7V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:fgq4be0GLEggvabcAAIfG", - "type": "arrow", - "fromId": "shape:QC8jmmtoDTOiM6q5OTqs5", - "toId": "shape:dGvyydmW1DDJ8yfbMUc02", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.844045429371539, - "y": 0.6164446307058363 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:oJQ84DJuW7xykod2wik1W", - "type": "arrow", - "fromId": "shape:QC8jmmtoDTOiM6q5OTqs5", - "toId": "shape:S646siSusBSnG4qkkffxR", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.4419794367015968, - "y": 0.318235169740979 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 211.99461457754364, "y": 97.94039225179233, @@ -5825,23 +6255,6 @@ "index": "aA", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:vYAV7WRlkYKOnfGr4I_Kj", - "type": "arrow", - "fromId": "shape:zFte5crnymTOho0UfCz5i", - "toId": "shape:Fiz3OfZZBu0ofvlMoMwSu", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.31233765434314203, - "y": 0.3013522973053976 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 264.1730885423424, "y": 94.71696203639368, @@ -5877,57 +6290,6 @@ "index": "a8V", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:xFvTv77Q2zvnlTn4i8x-p", - "type": "arrow", - "fromId": "shape:W88g3vmrlJ-9GGjk4wbbJ", - "toId": "shape:n6Qg45IYgl4TOJ0-QruZE", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.3518405669132706, - "y": 0.0427513976704394 - }, - "terminal": "end" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:WvcjxXqoIEeX0pPRiqayS", - "type": "arrow", - "fromId": "shape:zFte5crnymTOho0UfCz5i", - "toId": "shape:ecHhio1cnohS_PMiPQxyh", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:2q6BGhJ6JumP5hnn9DIPi", - "type": "arrow", - "fromId": "shape:W88g3vmrlJ-9GGjk4wbbJ", - "toId": "shape:ecHhio1cnohS_PMiPQxyh", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 961.1225913124143, "y": -25.557432657785313, @@ -6148,23 +6510,6 @@ "index": "aFV", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:qxU9BeXbWhg8zCF9gbLjV", - "type": "arrow", - "fromId": "shape:doy8IMjVjJJQZGmFe0kZ1", - "toId": "shape:HKUtHD0o5eDKknEGx4JPv", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.574016623512874, - "y": 0.7701465928132566 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 2489.1540278117054, "y": 107.32939010631952, @@ -6325,23 +6670,6 @@ "index": "aKV", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:JbNzXWHltmfBQW33856fG", - "type": "arrow", - "fromId": "shape:KD0hds0ZwCjhSvkTX7P8t", - "toId": "shape:Ecdwq81oAVoThUpg4v1Ln", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.4652117208020428, - "y": 0.09439141344800922 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 2634.002912556305, "y": 323.5308445283937, @@ -6355,61 +6683,27 @@ "dash": "solid", "size": "m", "fill": "none", - "color": "black", - "labelColor": "black", - "bend": 0, - "start": { - "x": 0, - "y": 0 - }, - "end": { - "x": 2, - "y": 0 - }, - "arrowheadStart": "none", - "arrowheadEnd": "triangle", - "text": "", - "labelPosition": 0.5, - "font": "draw", - "scale": 1 - }, - "parentId": "page:cLMgWOZR-5aG2IfUReA7h", - "index": "aM", - "typeName": "shape" - }, - { - "meta": {}, - "id": "binding:ZmLrtSfWIrFZzEu1R_KT8", - "type": "arrow", - "fromId": "shape:jC7QLihbKs1mfvgFWA2Z8", - "toId": "shape:Ecdwq81oAVoThUpg4v1Ln", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.4722587451874189, - "y": 0.8356319033558031 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:CnzqqukUfFtTt9DK1XxRF", - "type": "arrow", - "fromId": "shape:jC7QLihbKs1mfvgFWA2Z8", - "toId": "shape:tq1Ith_lkEnL2-kW2dvsj", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 + "color": "black", + "labelColor": "black", + "bend": 0, + "start": { + "x": 0, + "y": 0 }, - "terminal": "end" + "end": { + "x": 2, + "y": 0 + }, + "arrowheadStart": "none", + "arrowheadEnd": "triangle", + "text": "", + "labelPosition": 0.5, + "font": "draw", + "scale": 1 }, - "typeName": "binding" + "parentId": "page:cLMgWOZR-5aG2IfUReA7h", + "index": "aM", + "typeName": "shape" }, { "x": 2638.52082510494, @@ -6446,23 +6740,6 @@ "index": "aN", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:JJ-eLrbNtiwOqSHHLk1Es", - "type": "arrow", - "fromId": "shape:sCD2qEebJRAyPOFQX2d5k", - "toId": "shape:tq1Ith_lkEnL2-kW2dvsj", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5116610144523434, - "y": 0.7262933082911631 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 936.0019509804332, "y": 295.6511566485277, @@ -6528,23 +6805,6 @@ "index": "aR", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:ikGCOMX0L7jtb_9rA1-Ra", - "type": "arrow", - "fromId": "shape:Gj1Qoj1bG1s_gaV69btBJ", - "toId": "shape:J9dbYGfcRvuZHx7Bxc3xl", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.4760527587202259, - "y": 0.6455333446891199 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 932.9979959681885, "y": 458.80382780871685, @@ -6575,23 +6835,6 @@ "index": "aQ", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:mXfWVgHVP5JiCJbmWeong", - "type": "arrow", - "fromId": "shape:Gj1Qoj1bG1s_gaV69btBJ", - "toId": "shape:HU-nJuXyu6X8DaKCc8BoY", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1119.3459562686608, "y": 502.88685629632835, @@ -6657,40 +6900,6 @@ "index": "aT", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:AzfUbCfcG5iqxFDo-MXZY", - "type": "arrow", - "fromId": "shape:VmExTuRjs76QkI9wwL1RO", - "toId": "shape:HU-nJuXyu6X8DaKCc8BoY", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5179630709492152, - "y": 0.6824667702970337 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:YE-q8CQJYmcYDxCfd6F9G", - "type": "arrow", - "fromId": "shape:VmExTuRjs76QkI9wwL1RO", - "toId": "shape:bf3C_BqpKXzs-zippKcxr", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1115.111254639749, "y": 644.4868793589561, @@ -6756,40 +6965,6 @@ "index": "aW", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:fwIkBKkePsI5vbPpDHJZO", - "type": "arrow", - "fromId": "shape:xNsVlSiZuIU5pQtdde5Rq", - "toId": "shape:BRThI9G1s7Sp0kedeCn74", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:DhhAmqQFhWzchUkLsv97-", - "type": "arrow", - "fromId": "shape:xNsVlSiZuIU5pQtdde5Rq", - "toId": "shape:bf3C_BqpKXzs-zippKcxr", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.4958898192382167, - "y": 0.8242834309090417 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 1435.9203712463927, "y": -169.00364004756034, @@ -6820,23 +6995,6 @@ "index": "aY", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:ojPmKvYhPX4TvTHoBwWBA", - "type": "arrow", - "fromId": "shape:Md5Vq1ca1INzCSN_oG-4A", - "toId": "shape:0DBbCVAgZj3VQkyOQt3j4", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.48799936790510706, - "y": 0.868449774722568 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 1812.7873004078715, "y": -60.482805590257044, @@ -6867,23 +7025,6 @@ "index": "aZ", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:M1oSsKDILhZfK8QNd-M61", - "type": "arrow", - "fromId": "shape:sCD2qEebJRAyPOFQX2d5k", - "toId": "shape:9wPLWMO1IxLjBBdYkpp__", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5140813380182341, - "y": 0.28378180860738755 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1805.328302602412, "y": 754.3802368673237, @@ -6914,40 +7055,6 @@ "index": "aa", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:z6vQHBrMyI5FzuxZ7JnHS", - "type": "arrow", - "fromId": "shape:Md5Vq1ca1INzCSN_oG-4A", - "toId": "shape:mX8pZtoVXOH6ajZy8_LL1", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.49823159150083823, - "y": 0.17586445771439593 - }, - "terminal": "end" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:Ora_fAusrHY--DeXjTENr", - "type": "arrow", - "fromId": "shape:KD0hds0ZwCjhSvkTX7P8t", - "toId": "shape:mX8pZtoVXOH6ajZy8_LL1", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.49533140760811983, - "y": 0.5292825728437602 - }, - "terminal": "start" - }, - "typeName": "binding" - }, { "x": 1664.6397096339265, "y": 226.06605774258804, @@ -7084,58 +7191,24 @@ "color": "black", "labelColor": "black", "bend": 0, - "start": { - "x": 0, - "y": 0 - }, - "end": { - "x": 2, - "y": 0 - }, - "arrowheadStart": "none", - "arrowheadEnd": "triangle", - "text": "", - "labelPosition": 0.5, - "font": "draw", - "scale": 1 - }, - "parentId": "page:cLMgWOZR-5aG2IfUReA7h", - "index": "adV", - "typeName": "shape" - }, - { - "meta": {}, - "id": "binding:xbQqYLvVVM8j0i7i2I9nj", - "type": "arrow", - "fromId": "shape:UN7a8DRY6_VnMXioyug2A", - "toId": "shape:RtLQKw5Y25Ckgzs_12MIz", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5787901989035433, - "y": 0.5950710338394019 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:fu4K6Uvsi7vlU4M33wxCG", - "type": "arrow", - "fromId": "shape:UN7a8DRY6_VnMXioyug2A", - "toId": "shape:ORVVQay1UdQjPeuDnp2oL", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 + "start": { + "x": 0, + "y": 0 }, - "terminal": "end" + "end": { + "x": 2, + "y": 0 + }, + "arrowheadStart": "none", + "arrowheadEnd": "triangle", + "text": "", + "labelPosition": 0.5, + "font": "draw", + "scale": 1 }, - "typeName": "binding" + "parentId": "page:cLMgWOZR-5aG2IfUReA7h", + "index": "adV", + "typeName": "shape" }, { "x": 1857.7939654764798, @@ -7172,40 +7245,6 @@ "index": "adG", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:x1WGSWzI7xq11qk2nuIl0", - "type": "arrow", - "fromId": "shape:skO73nWViwgr-18kdcHn8", - "toId": "shape:ORVVQay1UdQjPeuDnp2oL", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.5029034045745147, - "y": 0.6467943717311037 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:dBRz2WEcCak_RR8GbW5Oz", - "type": "arrow", - "fromId": "shape:skO73nWViwgr-18kdcHn8", - "toId": "shape:RyW9ZWWbh7SVHiIxsO-dl", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.499108687447034, - "y": 0.12770651360001972 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1856.6832526030282, "y": 268.36147583855995, @@ -7241,40 +7280,6 @@ "index": "acV", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:sEdDvKUXBr7dHBzZ_iznZ", - "type": "arrow", - "fromId": "shape:np4oAZr2mFP6bFbu6htVh", - "toId": "shape:RyW9ZWWbh7SVHiIxsO-dl", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.49616353385555617, - "y": 0.5856775946723972 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:NCMVffz12MjB04jdLkYfd", - "type": "arrow", - "fromId": "shape:np4oAZr2mFP6bFbu6htVh", - "toId": "shape:yB293XsUERo2ab2_dRRV-", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.49923518974015024, - "y": 0.020875795162211355 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1856.317886526235, "y": 402.67004566775654, @@ -7310,40 +7315,6 @@ "index": "af", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:ihWrs7xas_sZ7EjT4O0x-", - "type": "arrow", - "fromId": "shape:AWLLnXrzeeDMgnWsINHvL", - "toId": "shape:yB293XsUERo2ab2_dRRV-", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.4972722140255442, - "y": 0.667008430006586 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:-O65N2T6FrIf1tonzRja-", - "type": "arrow", - "fromId": "shape:AWLLnXrzeeDMgnWsINHvL", - "toId": "shape:a64GoYkJPgbwlPULKA-Cs", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1839.6718080675348, "y": 575.2251364156699, @@ -7379,40 +7350,6 @@ "index": "ag", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:JH_WSZpZ69n6dBryEtW7R", - "type": "arrow", - "fromId": "shape:yr38U4dQJQugvcvfHfjc8", - "toId": "shape:a64GoYkJPgbwlPULKA-Cs", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.29843775053519317, - "y": 0.6251327263479867 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:AqGKSHrqaiBXwkIcqfQIU", - "type": "arrow", - "fromId": "shape:yr38U4dQJQugvcvfHfjc8", - "toId": "shape:L7ULcorXeep4XH9TGrZv_", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1869.9241192260154, "y": 556.0945686347758, @@ -7448,40 +7385,6 @@ "index": "ah", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:GTWb6tnKE7_FiBUVwOBYd", - "type": "arrow", - "fromId": "shape:rSQeh5USL7kQ3QjxQWRhl", - "toId": "shape:a64GoYkJPgbwlPULKA-Cs", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.7880688330785064, - "y": 0.6998676393435734 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:FJKnf2iPRAqF9Ldh4dwT9", - "type": "arrow", - "fromId": "shape:rSQeh5USL7kQ3QjxQWRhl", - "toId": "shape:ORVVQay1UdQjPeuDnp2oL", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.9694836985474617, - "y": 0.6410267288629806 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 1441.9416041919446, "y": 539.2742275542272, @@ -7505,40 +7408,6 @@ "index": "ai", "typeName": "shape" }, - { - "meta": {}, - "id": "binding:vU1X5YLefXjGbTzb3Wczs", - "type": "arrow", - "fromId": "shape:hfjqi6Yo3NzVwE2SXt4bR", - "toId": "shape:y2RsMDHaIh9gTuySjA4fG", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.4601487269146303, - "y": 0.8188462743313013 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:GgxNBIhwre5qMdfRrer_y", - "type": "arrow", - "fromId": "shape:doy8IMjVjJJQZGmFe0kZ1", - "toId": "shape:y2RsMDHaIh9gTuySjA4fG", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.4844310530899227, - "y": 0.23580232278272176 - }, - "terminal": "end" - }, - "typeName": "binding" - }, { "x": 961.0641327401275, "y": 116.32152228256737, @@ -7603,40 +7472,6 @@ "parentId": "page:cLMgWOZR-5aG2IfUReA7h", "index": "ak", "typeName": "shape" - }, - { - "meta": {}, - "id": "binding:8IIBiqmkLaQBPTON_JqpM", - "type": "arrow", - "fromId": "shape:hPvjZJEYajguqQm0IJnfl", - "toId": "shape:R9lx-6kRc2EU_MHzFwIhl", - "props": { - "isPrecise": false, - "isExact": false, - "normalizedAnchor": { - "x": 0.43477751250709834, - "y": 0.6153148293131357 - }, - "terminal": "start" - }, - "typeName": "binding" - }, - { - "meta": {}, - "id": "binding:gr7fOwtrtp-s44oOITJ4o", - "type": "arrow", - "fromId": "shape:hPvjZJEYajguqQm0IJnfl", - "toId": "shape:J9dbYGfcRvuZHx7Bxc3xl", - "props": { - "isPrecise": true, - "isExact": false, - "normalizedAnchor": { - "x": 0.5, - "y": 0.5 - }, - "terminal": "end" - }, - "typeName": "binding" } ] } \ No newline at end of file diff --git a/src/web/vaev-base/font.h b/src/web/vaev-base/font.h index 04919eb6f3..7d6062e235 100644 --- a/src/web/vaev-base/font.h +++ b/src/web/vaev-base/font.h @@ -1,9 +1,10 @@ #pragma once #include -#include -#include -#include + +#include "angle.h" +#include "length.h" +#include "percent.h" namespace Vaev { diff --git a/src/web/vaev-devtools/app.cpp b/src/web/vaev-devtools/app.cpp index 0d192eb17e..02ac014f81 100644 --- a/src/web/vaev-devtools/app.cpp +++ b/src/web/vaev-devtools/app.cpp @@ -23,7 +23,7 @@ enum struct SidePanel { struct State { Mime::Url url; - Res> dom; + Res> dom; SidePanel sidePanel = SidePanel::CLOSE; bool canGoBack() const { @@ -88,7 +88,8 @@ Ui::Child addressBar(Mime::Url const &url) { 0, Math::Align::CENTER, Ui::text("{}", url), - Ui::grow(NONE) + Ui::grow(NONE), + Kr::contextMenuIcon(Model::bind(), Mdi::REFRESH) ) | Ui::box({ .padding = {12, 0, 0, 0}, @@ -170,7 +171,7 @@ Ui::Child appContent(State const &s) { ); } -Ui::Child app(Mime::Url url, Res> dom) { +Ui::Child app(Mime::Url url, Res> dom) { return Ui::reducer( { url, @@ -186,10 +187,13 @@ Ui::Child app(Mime::Url url, Res> dom) { Ui::ButtonStyle::subtle(), Mdi::SURFING ), - addressBar(s.url) | Ui::grow(), Ui::button(Model::bind(), Ui::ButtonStyle::subtle(), Mdi::REFRESH), Ui::button([&](Ui::Node &n) { - Ui::showPopover(n, n.bound().bottomEnd(), mainMenu(s)); - }, - Ui::ButtonStyle::subtle(), Mdi::DOTS_HORIZONTAL), + addressBar(s.url) | Ui::grow(), + Ui::button( + [&](Ui::Node &n) { + Ui::showPopover(n, n.bound().bottomEnd(), mainMenu(s)); + }, + Ui::ButtonStyle::subtle(), Mdi::DOTS_HORIZONTAL + ), Hideo::controls() ) | Ui::dragRegion(), appContent(s) | Ui::grow() diff --git a/src/web/vaev-devtools/app.h b/src/web/vaev-devtools/app.h index a970a64071..9f284e6aef 100644 --- a/src/web/vaev-devtools/app.h +++ b/src/web/vaev-devtools/app.h @@ -1,10 +1,10 @@ #pragma once #include -#include +#include namespace Hideo::Browser { -Ui::Child app(Mime::Url url, Res> dom); +Ui::Child app(Mime::Url url, Res> dom); } // namespace Hideo::Browser diff --git a/src/web/vaev-devtools/manifest.json b/src/web/vaev-devtools/manifest.json index cb70d19f95..49d3989f8f 100644 --- a/src/web/vaev-devtools/manifest.json +++ b/src/web/vaev-devtools/manifest.json @@ -5,8 +5,6 @@ "type": "exe", "requires": [ "hideo-base", - "vaev-html", - "vaev-xml", "vaev-view" ], "provides": [ diff --git a/src/web/vaev-dom/_mod.h b/src/web/vaev-dom/_mod.h deleted file mode 100644 index e0a661f00f..0000000000 --- a/src/web/vaev-dom/_mod.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include "comment.h" -#include "document-type.h" -#include "document.h" -#include "element.h" -#include "node.h" -#include "text.h" diff --git a/src/web/vaev-dom/attr.h b/src/web/vaev-dom/attr.h deleted file mode 100644 index f7fa839655..0000000000 --- a/src/web/vaev-dom/attr.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "node.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#interface-attr -struct Attr : public Node { - AttrName name; - String value; - - Attr(AttrName name, String value) - : name(name), value(value) { - } - - NodeType nodeType() const override { - return NodeType::ATTRIBUTE; - } - - void _repr(Io::Emit &e) const override { - e(" namespaceURI={#}", name.ns.url()); - e(" prefix={#}", name.ns.name()); - e(" localName={#} value={#}", name.name(), value); - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/character-data.h b/src/web/vaev-dom/character-data.h deleted file mode 100644 index 78923a401b..0000000000 --- a/src/web/vaev-dom/character-data.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "node.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#interface-characterdata -struct CharacterData : public Dom::Node { - String data; - - CharacterData(String data) - : data(data) { - } - - void appendData(String const &data) { - // HACK: This is not efficient and pretty slow, - // but it's good enough for now. - StringBuilder sb; - sb.append(this->data); - sb.append(data); - this->data = sb.take(); - } - - void appendData(Rune rune) { - // HACK: This is not efficient and pretty slow, - // but it's good enough for now. - StringBuilder sb; - sb.append(this->data); - sb.append(rune); - this->data = sb.take(); - } - - void _repr(Io::Emit &e) const override { - e(" data={#}", this->data); - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/comment.h b/src/web/vaev-dom/comment.h deleted file mode 100644 index c2237008f3..0000000000 --- a/src/web/vaev-dom/comment.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "character-data.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#interface-comment -struct Comment : public CharacterData { - using CharacterData::CharacterData; - - static constexpr auto TYPE = NodeType::COMMENT; - - NodeType nodeType() const override { - return TYPE; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/document-type.h b/src/web/vaev-dom/document-type.h deleted file mode 100644 index c7d483694a..0000000000 --- a/src/web/vaev-dom/document-type.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "node.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#interface-documenttype -struct DocumentType : public Node { - static constexpr auto TYPE = NodeType::DOCUMENT_TYPE; - - String name; - String publicId; - String systemId; - - DocumentType() = default; - - DocumentType(String name, String publicId, String systemId) - : name(name), publicId(publicId), systemId(systemId) { - } - - NodeType nodeType() const override { - return TYPE; - } - - void _repr(Io::Emit &e) const override { - e(" name={#} publicId={#} systemId={#}", this->name, this->publicId, this->systemId); - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/document.h b/src/web/vaev-dom/document.h deleted file mode 100644 index a3fad69710..0000000000 --- a/src/web/vaev-dom/document.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "node.h" - -namespace Vaev::Dom { - -enum struct QuirkMode { - NO, - LIMITED, - YES -}; - -// https://dom.spec.whatwg.org/#interface-document -struct Document : public Node { - static constexpr auto TYPE = NodeType::DOCUMENT; - - QuirkMode quirkMode{QuirkMode::NO}; - - NodeType nodeType() const override { - return TYPE; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/element.h b/src/web/vaev-dom/element.h deleted file mode 100644 index 4f1ea4a7f2..0000000000 --- a/src/web/vaev-dom/element.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "attr.h" -#include "node.h" -#include "text.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#interface-element -struct Element : public Node { - static constexpr auto TYPE = NodeType::ELEMENT; - - Opt id() const { - return this->getAttribute(Html::ID_ATTR); - } - - TagName tagName; - // NOSPEC: Should be a NamedNodeMap - Map> attributes; - TokenList classList; - - Element(TagName tagName) - : tagName(tagName) { - } - - NodeType nodeType() const override { - return TYPE; - } - - String textContent() const { - String builder; - if (_children.len() == 0) - return ""s; - - if (_children.len() > 1) - panic("textContent is not implemented for elements with multiple children"); - - auto const &child = *_children[0]; - if (auto *text = child.is()) { - return text->data; - } - - panic("textContent is not implemented for elements with children other than text nodes"); - } - - void _repr(Io::Emit &e) const override { - e(" tagName={#}", this->tagName); - if (this->attributes.len()) { - e.indentNewline(); - for (auto const &[name, attr] : this->attributes.iter()) { - attr->repr(e); - } - e.deindent(); - } - } - - void setAttribute(AttrName name, String value) { - if (name == Html::CLASS_ATTR) { - for (auto class_ : iterSplit(value, ' ')) { - this->classList.add(class_); - } - return; - } - auto attr = makeStrong(name, value); - this->attributes.put(name, attr); - } - - bool hasAttribute(AttrName name) const { - return this->attributes.tryGet(name) != NONE; - } - - Opt getAttribute(AttrName name) const { - auto attr = this->attributes.tryGet(name); - if (attr == NONE) - return NONE; - return (*attr)->value; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/manifest.json b/src/web/vaev-dom/manifest.json deleted file mode 100644 index 243c9e808f..0000000000 --- a/src/web/vaev-dom/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-dom", - "type": "lib", - "description": "Implementation of the HTML standard (https://html.spec.whatwg.org/multipage/)", - "requires": [ - "karm-logger", - "vaev-base" - ] -} diff --git a/src/web/vaev-dom/node.h b/src/web/vaev-dom/node.h deleted file mode 100644 index 375adce81b..0000000000 --- a/src/web/vaev-dom/node.h +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace Vaev::Dom { - -#define FOREACH_NODE_TYPE(TYPE) \ - TYPE(ELEMENT, 1) \ - TYPE(ATTRIBUTE, 2) \ - TYPE(TEXT, 3) \ - TYPE(CDATA_SECTION, 4) \ - TYPE(PROCESSING_INSTRUCTION, 7) \ - TYPE(COMMENT, 8) \ - TYPE(DOCUMENT, 9) \ - TYPE(DOCUMENT_TYPE, 10) \ - TYPE(DOCUMENT_FRAGMENT, 11) - -enum struct NodeType { -#define ITER(NAME, VALUE) NAME = VALUE, - FOREACH_NODE_TYPE(ITER) -#undef ITER - _LEN, -}; - -// https://dom.spec.whatwg.org/#interface-node -struct Node : - Meta::Static { - - Node *_parent = nullptr; - Vec> _children; - - virtual ~Node() = default; - - virtual NodeType nodeType() const = 0; - - template - T *is() { - return nodeType() == T::TYPE ? static_cast(this) : nullptr; - } - - template - T const *is() const { - return nodeType() == T::TYPE ? static_cast(this) : nullptr; - } - - // MARK: Parent - - Node &parentNode() { - if (not _parent) - panic("node has no parent"); - return *_parent; - } - - Node const &parentNode() const { - if (not _parent) - panic("node has no parent"); - return *_parent; - } - - bool hasParent() const { - return _parent != nullptr; - } - - usize _parentIndex() const { - if (not _parent) - panic("node has no parent"); - return indexOf(parentNode()._children, *this).unwrap(); - } - - void _detachParent() { - if (_parent) { - _parent->_children.removeAt(_parentIndex()); - _parent = nullptr; - } - } - - // MARK: Children - - bool hasChildren() const { - return _children.len() > 0; - } - - Strong firstChild() { - if (not _children.len()) - panic("node has no children"); - return first(_children); - } - - Strong lastChild() { - if (not _children.len()) - panic("node has no children"); - return last(_children); - } - - void appendChild(Strong child) { - child->_detachParent(); - _children.pushBack(child); - child->_parent = this; - } - - void removeChild(Strong child) { - if (child->_parent != this) - panic("node is not a child"); - child->_detachParent(); - } - - Slice> children() const { - return _children; - } - - // MARK: Siblings - - Strong previousSibling() const { - usize index = _parentIndex(); - return parentNode()._children[index - 1]; - } - - bool hasPreviousSibling() const { - return _parentIndex() > 0; - } - - Strong nextSibling() { - usize index = _parentIndex(); - return parentNode()._children[index + 1]; - } - - bool hasNextSibling() const { - return _parentIndex() < parentNode()._children.len() - 1; - } - - virtual void _repr(Io::Emit &) const {} - - void repr(Io::Emit &e) const { - e("({}", nodeType()); - _repr(e); - if (_children.len() > 0) { - e.indentNewline(); - for (auto &child : _children) { - child->repr(e); - } - e.deindent(); - } - e(")\n"); - } - - // MARK: Operators - - bool operator==(Node const &other) const { - return this == &other; - } - - auto operator<=>(Node const &other) const { - return this <=> &other; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/tests/manifest.json b/src/web/vaev-dom/tests/manifest.json deleted file mode 100644 index cdbd9c4e22..0000000000 --- a/src/web/vaev-dom/tests/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-dom.tests", - "type": "lib", - "props": { - "cpp-excluded": true - }, - "requires": [ - "vaev-dom", - "karm-test" - ], - "injects": [ - "__tests__" - ] -} diff --git a/src/web/vaev-dom/tests/test-node.cpp b/src/web/vaev-dom/tests/test-node.cpp deleted file mode 100644 index d753a8678f..0000000000 --- a/src/web/vaev-dom/tests/test-node.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include - -namespace Vaev::Dom::Tests { - -test$("dom-tree") { - auto doc = makeStrong(); - return Ok(); -} - -} // namespace Vaev::Dom::Tests diff --git a/src/web/vaev-dom/text.h b/src/web/vaev-dom/text.h deleted file mode 100644 index 9fe3a1cb97..0000000000 --- a/src/web/vaev-dom/text.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "character-data.h" - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#text -struct Text : public CharacterData { - static constexpr auto TYPE = NodeType::TEXT; - - using CharacterData::CharacterData; - - NodeType nodeType() const override { - return TYPE; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-dom/token-list.h b/src/web/vaev-dom/token-list.h deleted file mode 100644 index 5dfd22a0f7..0000000000 --- a/src/web/vaev-dom/token-list.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include - -namespace Vaev::Dom { - -// https://dom.spec.whatwg.org/#domtokenlist -struct TokenList { - Vec _tokens; - - usize length() const { - return _tokens.len(); - } - - Opt item(usize index) const { - if (index >= _tokens.len()) - return NONE; - return _tokens[index]; - } - - bool contains(Str token) const { - return _tokens.contains(token); - } - - void add(Str token) { - if (not _tokens.contains(token)) - _tokens.pushBack(token); - } - - void remove(Str token) { - _tokens.removeAll(token); - } - - bool toggle(Str token) { - if (_tokens.contains(token)) { - _tokens.removeAll(token); - return false; - } else { - _tokens.pushBack(token); - return true; - } - } - - bool replace(Str oldToken, Str newToken) { - if (not _tokens.contains(oldToken)) - return false; - _tokens.removeAll(oldToken); - _tokens.pushBack(newToken); - return true; - } -}; - -} // namespace Vaev::Dom diff --git a/src/web/vaev-driver/fetcher.cpp b/src/web/vaev-driver/fetcher.cpp index 28198bacfe..27e78e095a 100644 --- a/src/web/vaev-driver/fetcher.cpp +++ b/src/web/vaev-driver/fetcher.cpp @@ -1,14 +1,14 @@ #include #include -#include +#include +#include #include -#include #include "fetcher.h" namespace Vaev::Driver { -Res> fetchDocument(Mime::Url url) { +Res> fetchDocument(Mime::Url url) { logInfo("fetching: {}", url); if (url.scheme == "about") { @@ -26,24 +26,24 @@ Res> fetchDocument(Mime::Url url) { if (not mime.has()) return Error::invalidInput("cannot determine MIME type"); - auto dom = makeStrong(); + auto dom = makeStrong(); auto file = try$(Sys::File::open(url)); auto buf = try$(Io::readAllUtf8(file)); if (mime->is("text/html"_mime)) { - Html::Parser parser{dom}; + Markup::HtmlParser parser{dom}; parser.write(buf); return Ok(dom); } else if (mime->is("application/xhtml+xml"_mime)) { Io::SScan scan{buf}; - Xml::Parser parser; + Markup::XmlParser parser; dom = try$(parser.parse(scan, HTML)); return Ok(dom); } else if (mime->is("image/svg+xml"_mime)) { Io::SScan scan{buf}; - Xml::Parser parser; + Markup::XmlParser parser; dom = try$(parser.parse(scan, SVG)); return Ok(dom); diff --git a/src/web/vaev-driver/fetcher.h b/src/web/vaev-driver/fetcher.h index 7cf5eae792..5f400d21c9 100644 --- a/src/web/vaev-driver/fetcher.h +++ b/src/web/vaev-driver/fetcher.h @@ -1,13 +1,13 @@ #pragma once #include -#include +#include #include namespace Vaev::Driver { Res fetchStylesheet(Mime::Url url); -Res> fetchDocument(Mime::Url url); +Res> fetchDocument(Mime::Url url); } // namespace Vaev::Driver diff --git a/src/web/vaev-driver/manifest.json b/src/web/vaev-driver/manifest.json index 1f95f5eabf..8fda3c85f4 100644 --- a/src/web/vaev-driver/manifest.json +++ b/src/web/vaev-driver/manifest.json @@ -5,8 +5,7 @@ "description": "Drive Vaev web content", "requires": [ "vaev-layout", - "vaev-html", - "vaev-xml", + "vaev-markup", "karm-mime", "karm-sys" ] diff --git a/src/web/vaev-driver/render.cpp b/src/web/vaev-driver/render.cpp index 9529af0c1a..3c5743965e 100644 --- a/src/web/vaev-driver/render.cpp +++ b/src/web/vaev-driver/render.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include #include #include #include @@ -10,8 +10,10 @@ namespace Vaev::Driver { -static void _collectStyle(Dom::Node const &node, Style::StyleBook &sb) { - auto *el = node.is(); +static constexpr bool DEBUG_RENDER = false; + +static void _collectStyle(Markup::Node const &node, Style::StyleBook &sb) { + auto *el = node.is(); if (el and el->tagName == Html::STYLE) { auto text = el->textContent(); Io::SScan textScan{text}; @@ -46,7 +48,7 @@ static void _collectStyle(Dom::Node const &node, Style::StyleBook &sb) { } } -RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px viewport) { +RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2Px viewport) { Style::StyleBook stylebook; stylebook.add( fetchStylesheet("bundle://vaev-view/user-agent.css"_url) @@ -56,7 +58,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px auto start = Sys::now(); _collectStyle(dom, stylebook); auto elapsed = Sys::now() - start; - logDebug("style collection time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "style collection time: {}", elapsed); start = Sys::now(); @@ -70,7 +72,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px elapsed = Sys::now() - start; - logDebug("layout tree build time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "layout tree build time: {}", elapsed); start = Sys::now(); @@ -78,7 +80,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px elapsed = Sys::now() - start; - logDebug("layout tree measure time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "layout tree measure time: {}", elapsed); start = Sys::now(); @@ -96,7 +98,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px auto paintRoot = makeStrong(); elapsed = Sys::now() - start; - logDebug("layout tree layout time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "layout tree layout time: {}", elapsed); auto paintStart = Sys::now(); @@ -104,7 +106,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px paintRoot->prepare(); elapsed = Sys::now() - paintStart; - logDebug("layout tree paint time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "layout tree paint time: {}", elapsed); return { makeStrong(std::move(tree.root)), @@ -112,7 +114,7 @@ RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px }; } -RenderResult render(Dom::Document &dom, Style::Media const &media, Print::PaperStock paper) { +RenderResult render(Markup::Document &dom, Style::Media const &media, Print::PaperStock paper) { Style::StyleBook stylebook; stylebook.add( fetchStylesheet("bundle://vaev-view/user-agent.css"_url) @@ -122,7 +124,7 @@ RenderResult render(Dom::Document &dom, Style::Media const &media, Print::PaperS auto start = Sys::now(); _collectStyle(dom, stylebook); auto elapsed = Sys::now() - start; - logDebug("style collection time: {}", elapsed); + logDebugIf(DEBUG_RENDER, "style collection time: {}", elapsed); Style::Computer computer{media, stylebook}; diff --git a/src/web/vaev-driver/render.h b/src/web/vaev-driver/render.h index 9c429fb8da..ceb4d40503 100644 --- a/src/web/vaev-driver/render.h +++ b/src/web/vaev-driver/render.h @@ -1,8 +1,8 @@ #pragma once #include -#include #include +#include #include #include @@ -13,8 +13,8 @@ struct RenderResult { Strong paint; }; -RenderResult render(Dom::Document const &dom, Style::Media const &media, Vec2Px viewport); +RenderResult render(Markup::Document const &dom, Style::Media const &media, Vec2Px viewport); -RenderResult render(Dom::Document &dom, Style::Media const &media, Print::PaperStock paper); +RenderResult render(Markup::Document &dom, Style::Media const &media, Print::PaperStock paper); } // namespace Vaev::Driver diff --git a/src/web/vaev-html/_mod.h b/src/web/vaev-html/_mod.h deleted file mode 100644 index 4f02b0d304..0000000000 --- a/src/web/vaev-html/_mod.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include "lexer.h" -#include "parser.h" -#include "tags.h" diff --git a/src/web/vaev-html/cli/main.cpp b/src/web/vaev-html/cli/main.cpp deleted file mode 100644 index 15b7d60396..0000000000 --- a/src/web/vaev-html/cli/main.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 2) { - Sys::errln("usage: vaev-html.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - auto url = co_try$(Mime::parseUrlOrPath(args[1])); - - if (verb == "dump-dom") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - auto doc = makeStrong(); - Vaev::Html::Parser parser{doc}; - parser.write(buf); - Sys::println("{}", doc); - - co_return Ok(); - } else if (verb == "dump-tokens") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - - Vec tokens; - - struct VecSink : public Vaev::Html::Sink { - Vec &tokens; - - VecSink(Vec &tokens) - : tokens(tokens) { - } - - void accept(Vaev::Html::Token const &token) override { - tokens.pushBack(token); - } - }; - - VecSink sink{tokens}; - Vaev::Html::Lexer lexer{}; - lexer.bind(sink); - - for (auto r : iterRunes(buf)) - lexer.consume(r); - - for (auto &t : tokens) - Sys::println("{}", t); - - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: dump-dom, dump-tokens)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-html/defs/tags.inc b/src/web/vaev-html/defs/tags.inc deleted file mode 100644 index 4c4cac726d..0000000000 --- a/src/web/vaev-html/defs/tags.inc +++ /dev/null @@ -1,112 +0,0 @@ -TAG(a) -TAG(abbr) -TAG(address) -TAG(area) -TAG(article) -TAG(aside) -TAG(audio) -TAG(b) -TAG(base) -TAG(bdi) -TAG(bdo) -TAG(blockquote) -TAG(body) -TAG(br) -TAG(button) -TAG(canvas) -TAG(caption) -TAG(cite) -TAG(code) -TAG(col) -TAG(colgroup) -TAG(data) -TAG(datalist) -TAG(dd) -TAG(del) -TAG(details) -TAG(dfn) -TAG(dialog) -TAG(div) -TAG(dl) -TAG(dt) -TAG(em) -TAG(embed) -TAG(fieldset) -TAG(figcaption) -TAG(figure) -TAG(footer) -TAG(form) -TAG(h1) -TAG(h2) -TAG(h3) -TAG(h4) -TAG(h5) -TAG(h6) -TAG(head) -TAG(header) -TAG(hgroup) -TAG(hr) -TAG(html) -TAG(i) -TAG(iframe) -TAG(img) -TAG(input) -TAG(ins) -TAG(kbd) -TAG(label) -TAG(legend) -TAG(li) -TAG(link) -TAG(main) -TAG(map) -TAG(mark) -TAG(menu) -TAG(meta) -TAG(meter) -TAG(nav) -TAG(noscript) -TAG(object) -TAG(ol) -TAG(optgroup) -TAG(option) -TAG(output) -TAG(p) -TAG(picture) -TAG(pre) -TAG(progress) -TAG(q) -TAG(rp) -TAG(rt) -TAG(ruby) -TAG(s) -TAG(samp) -TAG(script) -TAG(search) -TAG(section) -TAG(select) -TAG(slot) -TAG(small) -TAG(source) -TAG(span) -TAG(strong) -TAG(style) -TAG(sub) -TAG(summary) -TAG(sup) -TAG(table) -TAG(tbody) -TAG(td) -TAG(template) -TAG(textarea) -TAG(tfoot) -TAG(th) -TAG(thead) -TAG(time) -TAG(title) -TAG(tr) -TAG(track) -TAG(u) -TAG(ul) -TAG(var) -TAG(video) -TAG(wbr) diff --git a/src/web/vaev-html/lexer.h b/src/web/vaev-html/lexer.h deleted file mode 100644 index 754b6592d8..0000000000 --- a/src/web/vaev-html/lexer.h +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace Vaev::Html { - -#define FOREACH_TOKEN(TOKEN) \ - TOKEN(NIL) \ - TOKEN(DOCTYPE) \ - TOKEN(START_TAG) \ - TOKEN(END_TAG) \ - TOKEN(COMMENT) \ - TOKEN(CHARACTER) \ - TOKEN(END_OF_FILE) - -struct Token { - enum Type { - -#define ITER(NAME) NAME, - FOREACH_TOKEN(ITER) -#undef ITER - - _LEN, - }; - - struct Attr { - String name{}; - String value{}; - }; - - Type type = NIL; - String name; - Rune rune = '\0'; - String data; - String publicIdent; - String systemIdent; - Vec attrs; - bool forceQuirks{false}; - bool selfClosing{false}; -}; - -struct Sink { - virtual ~Sink() = default; - virtual void accept(Token const &token) = 0; -}; - -struct Lexer { - enum struct State { -#define STATE(NAME) NAME, -#include "defs/states.inc" -#undef STATE - - _LEN, - }; - - using enum State; - - State _state = State::DATA; - State _returnState = State::NIL; - - Opt _token; - Opt _last; - Sink *_sink = nullptr; - - Rune _currChar = 0; - StringBuilder _builder; - StringBuilder _temp; - - Token &_begin(Token::Type type) { - _token = Token{}; - _token->type = type; - return *_token; - } - - Token &_ensure() { - if (not _token) - panic("unexpected-token"); - return *_token; - } - - Token &_ensure(Token::Type type) { - auto &token = _ensure(); - if (token.type != type) - panic("unexpected-token"); - return token; - } - - void _emit() { - if (not _sink) - panic("no sink"); - _sink->accept(_ensure()); - _last = std::move(_token); - } - - void _emit(Rune rune) { - _begin(Token::CHARACTER).rune = rune; - _emit(); - } - - void _beginAttribute() { - _ensure().attrs.emplaceBack(); - } - - Token::Attr &_lastAttr() { - auto &token = _ensure(); - if (token.attrs.len() == 0) - panic("_beginAttribute miss match"); - return last(token.attrs); - } - - void _reconsumeIn(State state, Rune rune) { - _switchTo(state); - consume(rune); - } - - void _switchTo(State state) { - _state = state; - } - - void _raise(Str msg); - - bool _isAppropriateEndTagToken() { - if (not _last or not _token) - return false; - return _last->name == _token->name; - } - - void _flushCodePointsConsumedAsACharacterReference() { - debug("flushing code points consumed as a character reference"); - } - - void bind(Sink &sink) { - if (_sink) - panic("sink already bound"); - _sink = &sink; - } - - void consume(Rune rune, bool isEof = false); -}; - -#undef FOREACH_TOKEN - -} // namespace Vaev::Html - -template <> -struct Karm::Io::Formatter { - Res format(Io::TextWriter &writer, Vaev::Html::Token const &val) { - usize written = try$(Io::format(writer, "({}", val.type)); - - if (val.name) - written += try$(Io::format(writer, " name={}", val.name)); - - if (val.rune) - written += try$(Io::format(writer, " rune='{#c}'", val.rune)); - - if (val.data) - written += try$(Io::format(writer, " data='{}'", val.data)); - - if (val.publicIdent) - written += try$(Io::format(writer, " publicIdent='{}'", val.publicIdent)); - - if (val.systemIdent) - written += try$(Io::format(writer, " systemIdent='{}'", val.systemIdent)); - - if (val.attrs.len() > 0) - for (auto &attr : val.attrs) { - written += try$(Io::format(writer, " attr:{}='{}'", attr.name, attr.value)); - } - - if (val.forceQuirks) - written += try$(Io::format(writer, " forceQuirks")); - - if (val.selfClosing) - written += try$(Io::format(writer, " selfClosing")); - - written += try$(writer.writeRune(')')); - return Ok(written); - } -}; diff --git a/src/web/vaev-html/manifest.json b/src/web/vaev-html/manifest.json deleted file mode 100644 index 018fc0d747..0000000000 --- a/src/web/vaev-html/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-html", - "type": "lib", - "description": "Implementation of the HTML standard (https://html.spec.whatwg.org/multipage/)", - "requires": [ - "karm-logger", - "vaev-base", - "vaev-svg", - "vaev-mathml", - "vaev-dom" - ] -} diff --git a/src/web/vaev-html/parser.cpp b/src/web/vaev-html/parser.cpp deleted file mode 100644 index 1171534cef..0000000000 --- a/src/web/vaev-html/parser.cpp +++ /dev/null @@ -1,1100 +0,0 @@ -#include -#include -#include -#include - -#include "parser.h" -#include "tags.h" - -namespace Vaev::Html { - -// 13.2.2 MARK: Parse errors -// https://html.spec.whatwg.org/multipage/parsing.html#parse-errors - -void Parser::_raise(Str msg) { - logError("{}: {}", _insertionMode, msg); -} - -// 13.2.4.2 MARK: The stack of open elements -// https://html.spec.whatwg.org/multipage/parsing.html#the-stack-of-open-elements - -// https://html.spec.whatwg.org/multipage/parsing.html#special -bool isSpecial(TagName const &tag) { - static constexpr Array SPECIAL{ -#define SPECIAL(NAME) NAME, -#include "defs/special.inc" -#undef SPECIAL - }; - - return contains(SPECIAL, tag); -} - -// 13.2.4.3 MARK: The list of active formatting elements -// https://html.spec.whatwg.org/multipage/parsing.html#list-of-active-formatting-elements - -void reconstructActiveFormattingElements(Parser &) { - // TODO -} - -// 13.2.5 MARK: Tokenization -// https://html.spec.whatwg.org/multipage/parsing.html#tokenization - -// https://html.spec.whatwg.org/multipage/parsing.html#acknowledge-self-closing-flag -void acknowledgeSelfClosingFlag(Token const &) { - logDebug("acknowledgeSelfClosingFlag not implemented"); -} - -// 13.2.6 MARK: Tree construction -// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction - -// 13.2.6.1 MARK: Creating and inserting nodes -// https://html.spec.whatwg.org/multipage/parsing.html#creating-and-inserting-nodes - -struct AdjustedInsertionLocation { - Strong parent; - - // https://html.spec.whatwg.org/multipage/parsing.html#insert-an-element-at-the-adjusted-insertion-location - void insert(Strong node) { - // NOSPEC - parent->appendChild(node); - } - - Opt> lastChild() { - // NOSPEC - if (not parent->hasChildren()) - return NONE; - return parent->lastChild(); - } -}; - -// https://html.spec.whatwg.org/multipage/parsing.html#appropriate-place-for-inserting-a-node -AdjustedInsertionLocation apropriatePlaceForInsertingANode(Parser &b, Opt> overrideTarget = NONE) { - // 1. If there was an override target specified, then let target be - // the override target. - // - // Otherwise, let target be the current node. - auto target = overrideTarget - ? *overrideTarget - : last(b._openElements); - - // 2. Determine the adjusted insertion location using the first - // matching steps from the following list: - - // If foster parenting is enabled and target is a table, tbody, tfoot, thead, or tr element - - // NOTE: Foster parenting happens when content is misnested in tables. - - // 1. Let last template be the last template element in the stack of open elements, if any. - - // 2. Let last table be the last table element in the stack of open elements, if any. - - // 3. If there is a last template and either there is no last table, - // or there is one, but last template is lower (more recently added) - // than last table in the stack of open elements, - // then: let adjusted insertion location be inside last template's - // template contents, after its last child (if any), and abort these steps. - - // 4. If there is no last table, then let adjusted insertion location be - // inside the first element in the stack of open elements (the html element), - // after its last child (if any), and abort these steps. (fragment case) - - // 5. If last table has a parent node, then let adjusted insertion location - // be inside last table's parent node, immediately before last table, - // and abort these steps. - - // 6. Let previous element be the element immediately above last table - // in the stack of open elements. - - // 7. Let adjusted insertion location be inside previous element, - // after its last child (if any). - - // Otherwise: Let adjusted insertion location be inside target, - // after its last child (if any). - - // 3. If the adjusted insertion location is inside a template element, - // let it instead be inside the template element's template contents, - // after its last child (if any). - - // 4. Return the adjusted insertion location. - return {target}; -} - -// https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token -Strong createElementFor(Token const &t, Ns ns) { - // NOSPEC: Keep it simple for the POC - - // 1. If the active speculative HTML parser is not null, then return the - // result of creating a speculative mock element given given namespace, - // the tag name of the given token, and the attributes of the given token. - - // 2. Otherwise, optionally create a speculative mock element given given - // namespace, the tag name of the given token, and the attributes of - // the given token. - - // 3. Let document be intended parent's node document. - - // 4. Let local name be the tag name of the token - - // 5. Let is be the value of the "is" attribute in the given token, if - // such an attribute exists, or null otherwise. - - // 6. Let definition be the result of looking up a custom element - // definition given document, given namespace, local name, and is. - - // 7. If definition is non-null and the parser was not created as part - // of the HTML fragment parsing algorithm, then let will execute - // script be true. Otherwise, let it be false. - - // NOSPEC: We don't support scripting so we don't need to worry about this - bool willExecuteScript = false; - - // 8. If will execute script is true, then: - - if (willExecuteScript) { - // 1. Increment document's throw-on-dynamic-markup-insertion counter. - - // 2. If the JavaScript execution context stack is empty, - // then perform a microtask checkpoint. - - // 3. Push a new element queue onto document's relevant agent's - // custom element reactions stack. - } - - // 9. Let element be the result of creating an element given document, - // localName, given namespace, null, and is. If will execute script - // is true, set the synchronous custom elements flag; otherwise, - // leave it unset. - auto el = makeStrong(TagName::make(t.name, ns)); - - // 10. Append each attribute in the given token to element. - for (auto &[name, value] : t.attrs) { - el->setAttribute(AttrName::make(name, ns), value); - } - - // 11. If will execute script is true, then: - if (willExecuteScript) { - // 1. Let queue be the result of popping from document's relevant - // agent's custom element reactions stack. (This will be the - // same element queue as was pushed above.) - - // 2. Invoke custom element reactions in queue. - - // 3. Decrement document's throw-on-dynamic-markup-insertion counter. - } - - // 12. If element has an xmlns attribute in the XMLNS namespace whose - // value is not exactly the same as the element's namespace, that - // is a parse error. Similarly, if element has an xmlns:xlink - // attribute in the XMLNS namespace whose value is not the XLink - // Namespace, that is a parse error. - - // 13. If element is a resettable element, invoke its reset algorithm. - // (This initializes the element's value and checkedness based on the element's attributes.) - - // 14. If element is a form-associated element and not a form-associated - // custom element, the form element pointer is not null, there is no - // template element on the stack of open elements, element is either - // not listed or doesn't have a form attribute, and the intended parent - // is in the same tree as the element pointed to by the form element pointer, - - // Then associate element with the form element pointed to by the form - // element pointer and set element's parser inserted flag. - - // 15. Return element. - return el; -} - -// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-foreign-element - -static Strong insertAForeignElement(Parser &b, Token const &t, Ns ns, bool onlyAddToElementStack = false) { - // 1. Let the adjusted insertion location be the appropriate place for inserting a node. - auto location = apropriatePlaceForInsertingANode(b); - - // 2. Let element be the result of creating an element for the token in the - // given namespace, with the intended parent being the element in which the - // adjusted insertion location finds itself. - auto el = createElementFor(t, ns); - - // 3. If onlyAddToElementStack is false, then run insert an element at the adjusted insertion location with element. - if (not onlyAddToElementStack) { - location.insert(el); - } - - // 4. Push element onto the stack of open elements so that it is the new current node. - b._openElements.pushBack(el); - - // 5. Return element. - return el; -} - -// https://html.spec.whatwg.org/multipage/parsing.html#insert-an-html-element -static Strong insertHtmlElement(Parser &b, Token const &t) { - return insertAForeignElement(b, t, Vaev::HTML, false); -} - -// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-character -static void insertACharacter(Parser &b, Rune c) { - // 2. Let the adjusted insertion location be the appropriate place for inserting a node. - auto location = apropriatePlaceForInsertingANode(b); - - // 3. If the adjusted insertion location is inside a Document node, then ignore the token. - if (location.parent->nodeType() == Dom::NodeType::DOCUMENT) - return; - - // 4. If there is a Text node immediately before the adjusted insertion - // location, then append data to that Text node's data. - auto lastChild = location.lastChild(); - if (lastChild and (*lastChild)->nodeType() == Dom::NodeType::TEXT) { - auto text = (*(*lastChild).cast()); - text->appendData(c); - } - - // Otherwise, create a new Text node whose data is data and whose node - // document is the same as that of the element in which the - // adjusted insertion location finds itself, and insert the - // newly created node at the adjusted insertion location. - else { - auto text = makeStrong(""s); - text->appendData(c); - - location.insert(text); - } -} - -static void insertACharacter(Parser &b, Token const &t) { - // 1. Let data be the characters passed to the algorithm, or, if no characters were explicitly specified, the character of the character token being processed. - insertACharacter(b, t.rune); -} - -// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-comment -static void insertAComment(Parser &b, Token const &t) { - // 1. Let data be the data given in the comment token being processed. - - // 2. If position was specified, then let the adjusted insertion - // location be position. Otherwise, let adjusted insertion location - // be the appropriate place for inserting a node. - - // TODO: If position was - auto location = apropriatePlaceForInsertingANode(b); - - // 3. Create a Comment node whose data attribute is set to data and - // whose node document is the same as that of the node in which - // the adjusted insertion location finds itself. - auto comment = makeStrong(t.data); - - // 4. Insert the newly created node at the adjusted insertion location. - location.insert(comment); -} - -// 13.2.6.2 MARK: Parsing elements that contain only text -// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text - -static void parseRawTextElement(Parser &b, Token const &t) { - insertHtmlElement(b, t); - b._lexer._switchTo(Lexer::RAWTEXT); - b._originalInsertionMode = b._insertionMode; - b._switchTo(Parser::Mode::TEXT); -} - -static void parseRcDataElement(Parser &b, Token const &t) { - insertHtmlElement(b, t); - b._lexer._switchTo(Lexer::RCDATA); - b._originalInsertionMode = b._insertionMode; - b._switchTo(Parser::Mode::TEXT); -} - -// 13.2.6.3 MARK: Closing elements that have implied end tags -// https://html.spec.whatwg.org/multipage/parsing.html#generate-implied-end-tags -static constexpr Array IMPLIED_END_TAGS = { - Html::DD, Html::DT, Html::LI, Html::OPTION, Html::OPTGROUP, Html::P, Html::RB, Html::RP, Html::RT, Html::RTC -}; - -static void generateImpliedEndTags(Parser &b, Str except = ""s) { - while (contains(IMPLIED_END_TAGS, last(b._openElements)->tagName) and - last(b._openElements)->tagName.name() != except) { - b._openElements.popBack(); - } -} - -// 13.2.6.4 MARK: The rules for parsing tokens in HTML content - -// 13.2.6.4.1 MARK: The "initial" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#the-initial-insertion-mode - -static Dom::QuirkMode _whichQuirkMode(Token const &) { - // NOSPEC: We assume no quirk mode - return Dom::QuirkMode::NO; -} - -void Parser::_handleInitialMode(Token const &t) { - // A character token that is one of U+0009 CHARACTER TABULATION, - // U+000A LINE FEED (LF), U+000C FORM FEED (FF), - // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE - if (t.type == Token::CHARACTER and - (t.rune == '\t' or - t.rune == '\n' or - t.rune == '\f' or - t.rune == ' ')) { - // ignore - } - - // A comment token - else if (t.type == Token::COMMENT) { - _document->appendChild(makeStrong(t.data)); - } - - // A DOCTYPE token - else if (t.type == Token::DOCTYPE) { - _document->appendChild(makeStrong( - t.name, - t.publicIdent, - t.systemIdent - )); - _document->quirkMode = _whichQuirkMode(t); - _switchTo(Mode::BEFORE_HTML); - } - - // Anything else - else { - _raise(); - _switchTo(Mode::BEFORE_HTML); - accept(t); - } -} - -// 13.2.6.4.2 MARK: The "before html" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#the-before-html-insertion-mode -void Parser::_handleBeforeHtml(Token const &t) { - // A DOCTYPE token - if (t.type == Token::DOCTYPE) { - // ignore - _raise(); - } - - // A comment token - else if (t.type == Token::COMMENT) { - _document->appendChild(makeStrong(t.data)); - } - - // A character token that is one of U+0009 CHARACTER TABULATION, - // U+000A LINE FEED (LF), U+000C FORM FEED (FF), - // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE - if (t.type == Token::CHARACTER and - (t.rune == '\t' or - t.rune == '\n' or - t.rune == '\f' or - t.rune == ' ')) { - // ignore - } - - // A start tag whose tag name is "html" - else if (t.type == Token::START_TAG and t.name == "html") { - auto el = createElementFor(t, Vaev::HTML); - _document->appendChild(el); - _openElements.pushBack(el); - _switchTo(Mode::BEFORE_HEAD); - } - - // Any other end tag - else if (t.type == Token::END_TAG and not(t.name == "head" or t.name == "body" or t.name == "html" or t.name == "br")) { - // ignore - _raise(); - } - - // An end tag whose tag name is one of: "head", "body", "html", "br" - // Anything else - else { - auto el = makeStrong(Html::HTML); - _document->appendChild(el); - _openElements.pushBack(el); - _switchTo(Mode::BEFORE_HEAD); - accept(t); - } -} - -// 13.2.6.4.3 MARK: The "before head" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#the-before-head-insertion-mode -void Parser::_handleBeforeHead(Token const &t) { - // A character token that is one of U+0009 CHARACTER TABULATION, - // U+000A LINE FEED (LF), U+000C FORM FEED (FF), - // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE - if (t.type == Token::CHARACTER and - (t.rune == '\t' or - t.rune == '\n' or - t.rune == '\f' or - t.rune == ' ')) { - // Ignore the token. - } - - // A comment token - else if (t.type == Token::COMMENT) { - // Insert a comment. - insertAComment(*this, t); - } - - // A comment token - else if (t.type == Token::DOCTYPE) { - // Parse error. Ignore the token. - _raise(); - } - - // A start tag whose tag name is "html" - else if (t.type == Token::START_TAG and t.name == "html") { - // Process the token using the rules for the "in body" insertion mode. - _acceptIn(Mode::IN_BODY, t); - } - - // A start tag whose tag name is "head" - else if (t.type == Token::START_TAG and t.name == "head") { - _headElement = insertHtmlElement(*this, t); - _switchTo(Mode::IN_HEAD); - } - - // Anything else - else if (t.type == Token::END_TAG and not(t.name == "head" or t.name == "body" or t.name == "html" or t.name == "br")) { - // ignore - _raise(); - } - - // An end tag whose tag name is one of: "head", "body", "html", "br" - // Anything else - else { - Token headToken; - headToken.type = Token::START_TAG; - headToken.name = String{"head"}; - _headElement = insertHtmlElement(*this, headToken); - _switchTo(Mode::IN_HEAD); - accept(t); - } -} - -// 13.2.6.4.4 MARK: The "in head" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead -void Parser::_handleInHead(Token const &t) { - auto anythingElse = [&] { - _openElements.popBack(); - _switchTo(Mode::AFTER_HEAD); - accept(t); - }; - - // A character token that is one of U+0009 CHARACTER TABULATION, - // U+000A LINE FEED (LF), U+000C FORM FEED (FF), - // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE - if (t.type == Token::CHARACTER and - (t.rune == '\t' or - t.rune == '\n' or - t.rune == '\f' or - t.rune == ' ')) { - insertACharacter(*this, t); - } - - // A comment token - else if (t.type == Token::COMMENT) { - insertAComment(*this, t); - } - - // A DOCTYPE token - else if (t.type == Token::DOCTYPE) { - _raise(); - } - - // A start tag whose tag name is "html" - else if (t.type == Token::START_TAG and (t.name == "html")) { - _acceptIn(Mode::IN_BODY, t); - } - - // A start tag whose tag name is one of: "base", "basefont", "bgsound", "link" - else if (t.type == Token::START_TAG and (t.name == "base" or t.name == "basefont" or t.name == "bgsound" or t.name == "link")) { - insertHtmlElement(*this, t); - _openElements.popBack(); - // TODO: Acknowledge the token's self-closing flag, if it is set. - } - - // A start tag whose tag name is "meta" - else if (t.type == Token::START_TAG and (t.name == "meta")) { - insertHtmlElement(*this, t); - _openElements.popBack(); - // TODO: Acknowledge the token's self-closing flag, if it is set. - - // TODO: Handle handle speculative parsing - } - - // A start tag whose tag name is "title" - else if (t.type == Token::START_TAG and (t.name == "title")) { - parseRcDataElement(*this, t); - } - - // A start tag whose tag name is "noscript", if the scripting flag is enabled - // A start tag whose tag name is one of: "noframes", "style" - else if (t.type == Token::START_TAG and ((t.name == "noscript" and _scriptingEnabled) or t.name == "noframe" or t.name == "style")) { - parseRawTextElement(*this, t); - } - - // A start tag whose tag name is "noscript", if the scripting flag is disabled - else if (t.type == Token::START_TAG and (t.name == "noscript" and not _scriptingEnabled)) { - insertHtmlElement(*this, t); - _switchTo(Mode::IN_HEAD_NOSCRIPT); - } - - // A start tag whose tag name is "script" - else if (t.type == Token::START_TAG and (t.name == "script")) { - // 1. Let the adjusted insertion location be the appropriate place for inserting a node. - auto localtion = apropriatePlaceForInsertingANode(*this); - - // 2. Create an element for the token in the HTML namespace, with - // the intended parent being the element in which the adjusted - // insertion location finds itself. - auto el = createElementFor(t, Vaev::HTML); - - // 3. Set the element's parser document to the Document, and set - // the element's force async to false. - - // NOSPEC: We don't support async scripts - - // NOTE: This ensures that, if the script is external, - // any document.write() calls in the script will execute - // in-line, instead of blowing the document away, as would - // happen in most other cases. It also prevents the script - // from executing until the end tag is seen. - - // 4. If the parser was created as part of the HTML fragment - // parsing algorithm, then set the script element's already - // started to true. (fragment case) - - // NOSPEC: We don't support fragments - - // 5. If the parser was invoked via the document.write() or - // document.writeln() methods, then optionally set the script - // element's already started to true. (For example, the user - // agent might use this clause to prevent execution of - // cross-origin scripts inserted via document.write() under - // slow network conditions, or when the page has already taken - // a long time to load.) - - // NOSPEC: We don't support document.write() - - // 6. Insert the newly created element at the adjusted insertion location. - localtion.insert(el); - - // 7. Push the element onto the stack of open elements so that it is the new current node. - _openElements.pushBack(el); - - // 8. Switch the tokenizer to the script data state. - _lexer._switchTo(Lexer::SCRIPT_DATA); - - // 9. Let the original insertion mode be the current insertion mode. - _originalInsertionMode = _insertionMode; - - // 10. Switch the insertion mode to "text". - _switchTo(Mode::TEXT); - } else if (t.type == Token::END_TAG and (t.name == "head")) { - _openElements.popBack(); - _switchTo(Mode::AFTER_HEAD); - } else if (t.type == Token::END_TAG and (t.name == "body" or t.name == "html" or t.name == "br")) { - anythingElse(); - } else if (t.type == Token::START_TAG and (t.name == "template")) { - // NOSPEC: We don't support templates - } else if (t.type == Token::END_TAG and (t.name == "template")) { - // NOSPEC: We don't support templates - } else if ((t.type == Token::START_TAG and (t.name == "head")) or t.type == Token::END_TAG) { - // ignore - _raise(); - } else { - anythingElse(); - } -} - -// 13.2.6.4.5 MARK: The "in head noscript" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inheadnoscript -void Parser::_handleInHeadNoScript(Token const &t) { - auto anythingElse = [&] { - _raise(); - _openElements.popBack(); - _switchTo(Mode::IN_HEAD); - accept(t); - }; - - // A DOCTYPE token - if (t.type == Token::DOCTYPE) { - _raise(); - } - - // A start tag whose tag name is "html" - else if (t.type == Token::START_TAG and (t.name == "html")) { - _acceptIn(Mode::IN_BODY, t); - } - - // An end tag whose tag name is "noscript" - else if (t.type == Token::END_TAG and (t.name == "noscript")) { - _openElements.popBack(); - _switchTo(Mode::IN_HEAD); - } - - // A character token that is one of - // - U+0009 CHARACTER TABULATION, - // - U+000A LINE FEED (LF), - // - U+000C FORM FEED (FF), - // - U+000D CARRIAGE RETURN (CR), - // - U+0020 SPACE - // A comment token - // A start tag whose tag name is one of: "basefont", "bgsound", "link", "meta", "noframes", "style" - else if ( - (t.type == Token::CHARACTER and - (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == ' ')) or - t.type == Token::COMMENT or - (t.type == Token::START_TAG and - (t.name == "basefont" or t.name == "bgsound" or t.name == "link" or t.name == "meta" or t.name == "noframes" or t.name == "style")) - ) { - _acceptIn(Mode::IN_HEAD, t); - } - - // An end tag whose tag name is "br" - else if (t.type == Token::END_TAG and (t.name == "br")) { - anythingElse(); - } - - // A start tag whose tag name is one of: "head", "noscript" - // Any other end tag - else if ( - (t.type == Token::START_TAG and (t.name == "head" or t.name == "noscript")) or - t.type == Token::END_TAG - ) { - // ignore - _raise(); - } - - // Anything else - else { - anythingElse(); - } -} - -// 13.2.6.4.6 MARK: The "after head" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#the-after-head-insertion-mode -void Parser::_handleAfterHead(Token const &t) { - auto anythingElse = [&] { - Token bodyToken; - bodyToken.type = Token::START_TAG; - bodyToken.name = String{"body"}; - insertHtmlElement(*this, bodyToken); - _switchTo(Mode::IN_BODY); - accept(t); - }; - - // A character token that is one of - // - U+0009 CHARACTER TABULATION, - // - U+000A LINE FEED (LF), - // - U+000C FORM FEED (FF), - // - U+000D CARRIAGE RETURN (CR) - // - U+0020 SPACE - if (t.type == Token::CHARACTER and - (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == '\r' or t.rune == ' ')) { - insertACharacter(*this, t.rune); - } - - // A comment token - else if (t.type == Token::COMMENT) { - insertAComment(*this, t); - } - - // A DOCTYPE token - else if (t.type == Token::DOCTYPE) { - _raise(); - } - - // A start tag whose tag name is "html" - else if (t.type == Token::START_TAG and (t.name == "html")) { - _acceptIn(Mode::IN_BODY, t); - } - - // A start tag whose tag name is "body" - else if (t.type == Token::START_TAG and (t.name == "body")) { - insertHtmlElement(*this, t); - _framesetOk = false; - _switchTo(Mode::IN_BODY); - } - - // A start tag whose tag name is "frameset" - else if (t.type == Token::START_TAG and (t.name == "frameset")) { - insertHtmlElement(*this, t); - _switchTo(Mode::IN_FRAMESET); - } - - // A start tag whose tag name is one of: - // "base", "basefont", "bgsound", "link", "meta", - // "noframes", "script", "style", "template", "title" - else if ( - t.type == Token::START_TAG and - (t.name == "base" or t.name == "basefont" or - t.name == "bgsound" or t.name == "link" or - t.name == "meta" or t.name == "noframes" or - t.name == "script" or t.name == "style" or - t.name == "template" or t.name == "title") - ) { - _raise(); - _openElements.pushBack(*_headElement); - _acceptIn(Mode::IN_HEAD, t); - _openElements.removeAll(*_headElement); - } - - // An end tag whose tag name is "template" - else if (t.type == Token::END_TAG and (t.name == "template")) { - _acceptIn(Mode::IN_HEAD, t); - } - - // An end tag whose tag name is one of: "body", "html", "br" - else if (t.type == Token::END_TAG and (t.name == "body" or t.name == "html" or t.name == "br")) { - anythingElse(); - } - - // A start tag whose tag name is "head" - else if (t.type == Token::END_TAG or (t.type == Token::START_TAG and t.name == "head")) { - // ignore - _raise(); - } - - // Anything else - else { - anythingElse(); - } -} - -// 13.2.6.4.7 MARK: The "in body" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody -void Parser::_handleInBody(Token const &t) { - // A character token that is U+0000 NULL - if (t.type == Token::CHARACTER and t.rune == '\0') { - _raise(); - } - - // A character token that is one of - // - U+0009 CHARACTER TABULATION - // - U+000A LINE FEED (LF) - // - U+000C FORM FEED (FF) - // - U+000D CARRIAGE RETURN (CR) - // - U+0020 SPACE - else if (t.type == Token::CHARACTER and (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == '\r' or t.rune == ' ')) { - reconstructActiveFormattingElements(*this); - insertACharacter(*this, t); - } - - // Any other character token - else if (t.type == Token::CHARACTER) { - reconstructActiveFormattingElements(*this); - insertACharacter(*this, t); - _framesetOk = false; - } - - // A comment token - else if (t.type == Token::COMMENT) { - insertAComment(*this, t); - } - - // A DOCTYPE token - else if (t.type == Token::DOCTYPE) { - _raise(); - } - - // TODO: A start tag whose tag name is "html" - - // TODO: A start tag whose tag name is one of: "base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "template", "title" - // An end tag whose tag name is "template" - - // TODO: A start tag whose tag name is "body" - - // TODO: A start tag whose tag name is "frameset" - - // TODO: An end-of-file token - - // TODO: An end tag whose tag name is "body" - - // TODO: An end tag whose tag name is "html" - - // TODO: A start tag whose tag name is one of: - // "address", "article", "aside", "blockquote", "center", - // "details", "dialog", "dir", "div", "dl", "fieldset", - // "figcaption", "figure", "footer", "header", "hgroup", - // "main", "menu", "nav", "ol", "p", "search", "section", - // "summary", "ul" - - // TODO: A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" - - // TODO: A start tag whose tag name is one of: "pre", "listing" - - // TODO: A start tag whose tag name is "form" - - // TODO: A start tag whose tag name is "li" - - // TODO: A start tag whose tag name is one of: "dd", "dt" - - // TODO: A start tag whose tag name is "plaintext" - - // TODO: A start tag whose tag name is "button" - - // TODO: An end tag whose tag name is one of: "address", "article", "aside", "blockquote", "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre", "search", "section", "summary", "ul" - - // TODO: An end tag whose tag name is "form" - - // TODO: An end tag whose tag name is "p" - - // TODO: An end tag whose tag name is "li" - - // TODO: An end tag whose tag name is one of: "dd", "dt" - - // TODO: An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" - - // TODO: An end tag whose tag name is "sarcasm" - // This state machine is not equipped to handle sarcasm - // So we just ignore it - - // TODO: A start tag whose tag name is "a" - - // TODO: A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u" - - // TODO: A start tag whose tag name is "nobr" - - // TODO: An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" - - // TODO: A start tag whose tag name is one of: "applet", "marquee", "object" - - // TODO: An end tag token whose tag name is one of: "applet", "marquee", "object" - - // TODO: A start tag whose tag name is "table" - - // TODO: An end tag whose tag name is "br" - - // TODO: A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr" - - // TODO: A start tag whose tag name is "input" - - // TODO: A start tag whose tag name is one of: "param", "source", "track" - - // TODO: A start tag whose tag name is "hr" - - // TODO: A start tag whose tag name is "image" - - // TODO: A start tag whose tag name is "textarea" - - // TODO: A start tag whose tag name is "xmp" - - // TODO: A start tag whose tag name is "iframe" - - // TODO: A start tag whose tag name is "noembed" - // A start tag whose tag name is "noscript", if the scripting flag is enabled - - // TODO: A start tag whose tag name is "select" - - // TODO: A start tag whose tag name is one of: "optgroup", "option" - - // TODO: A start tag whose tag name is one of: "rb", "rtc" - - // TODO: A start tag whose tag name is one of: "rp", "rt" - - // TODO: A start tag whose tag name is "math" - - // TODO: A start tag whose tag name is "svg" - - // TODO: A start tag whose tag name is one of: "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr" - - else if (t.type == Token::START_TAG) { - reconstructActiveFormattingElements(*this); - insertHtmlElement(*this, t); - } - - // TODO: Any other end tag - else if (t.type == Token::END_TAG) { - // loop: - while (true) { - // 1. Initialize node to be the current node (the bottommost node of the stack). - auto node = last(_openElements); - - // 2. Loop: If node is an HTML element with the same tag name as the token, then: - if (node->tagName.name() == t.name) { - // 1. Generate implied end tags, except for HTML elements with the same tag name as the token. - generateImpliedEndTags(*this, t.name); - - // 2. If node is not the current node, then this is a parse error. - if (node != last(_openElements)) { - _raise(); - } - - // 3. Pop all the nodes from the current node up to node, including node, then stop these steps - while (last(_openElements) != node) { - _openElements.popBack(); - } - _openElements.popBack(); - break; - } - - // 3. Otherwise, if node is in the special category, - // then this is a parse error; ignore the token, and return. - - // 4. Set node to the previous entry in the stack of open elements. - - // 5. Return to the step labeled loop. - } - } -} - -// 13.2.6.4.8 MARK: The "text" insertion mode -// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incdata -void Parser::_handleText(Token const &t) { - // A character token - if (t.type == Token::CHARACTER) { - insertACharacter( - *this, - t.rune == '\0' - ? 0xFFFD - : t.rune - ); - } - - else if (t.type == Token::END_OF_FILE) { - _raise(); - - // TODO: If the current node is a script element, then set its already started to true. - - _openElements.popBack(); - _switchTo(_originalInsertionMode); - } - - // An end tag whose tag name is "script" - // else if (t.type == Token::END_TAG and t.name == "script") { - // } - // NOSPEC: We handle script end tags like any other end tag - - // Any other end tag - else if (t.type == Token::END_TAG) { - this->_openElements.popBack(); - _switchTo(_originalInsertionMode); - } - - // FIXME: Implement the rest of the rules -} - -void Parser::_switchTo(Mode mode) { - _insertionMode = mode; -} - -void Parser::_acceptIn(Mode mode, Token const &t) { - logDebug("Parsing {} in {}", t, mode); - - switch (mode) { - - case Mode::INITIAL: - _handleInitialMode(t); - break; - - case Mode::BEFORE_HTML: - _handleBeforeHtml(t); - break; - - case Mode::BEFORE_HEAD: - _handleBeforeHead(t); - break; - - case Mode::IN_HEAD: - _handleInHead(t); - break; - - case Mode::IN_HEAD_NOSCRIPT: - _handleInHeadNoScript(t); - break; - - case Mode::AFTER_HEAD: - _handleAfterHead(t); - break; - - case Mode::IN_BODY: - _handleInBody(t); - break; - - case Mode::TEXT: - _handleText(t); - break; - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intable - case Mode::IN_TABLE: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intabletext - case Mode::IN_TABLE_TEXT: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incaption - case Mode::IN_CAPTION: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incolumngroup - case Mode::IN_COLUMN_GROUP: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intablebody - case Mode::IN_TABLE_BODY: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inrow - case Mode::IN_ROW: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incell - case Mode::IN_CELL: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect - case Mode::IN_SELECT: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselectintable - case Mode::IN_SELECT_IN_TABLE: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intemplate - case Mode::IN_TEMPLATE: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-body-insertion-mode - case Mode::AFTER_BODY: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-in-frameset-insertion-mode - case Mode::IN_FRAMESET: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-frameset-insertion-mode - case Mode::AFTER_FRAMESET: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-after-body-insertion-mode - case Mode::AFTER_AFTER_BODY: { - break; - } - - // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-after-frameset-insertion-mode - case Mode::AFTER_AFTER_FRAMESET: { - break; - } - - default: - break; - } -} - -void Parser::accept(Token const &t) { - _acceptIn(_insertionMode, t); -} - -} // namespace Vaev::Html diff --git a/src/web/vaev-html/parser.h b/src/web/vaev-html/parser.h deleted file mode 100644 index 8555c216be..0000000000 --- a/src/web/vaev-html/parser.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "lexer.h" - -namespace Vaev::Html { - -#define FOREACH_INSERTION_MODE(MODE) \ - MODE(INITIAL) \ - MODE(BEFORE_HTML) \ - MODE(BEFORE_HEAD) \ - MODE(IN_HEAD) \ - MODE(IN_HEAD_NOSCRIPT) \ - MODE(AFTER_HEAD) \ - MODE(IN_BODY) \ - MODE(TEXT) \ - MODE(IN_TABLE) \ - MODE(IN_TABLE_TEXT) \ - MODE(IN_CAPTION) \ - MODE(IN_COLUMN_GROUP) \ - MODE(IN_TABLE_BODY) \ - MODE(IN_ROW) \ - MODE(IN_CELL) \ - MODE(IN_SELECT) \ - MODE(IN_SELECT_IN_TABLE) \ - MODE(IN_TEMPLATE) \ - MODE(AFTER_BODY) \ - MODE(IN_FRAMESET) \ - MODE(AFTER_FRAMESET) \ - MODE(AFTER_AFTER_BODY) \ - MODE(AFTER_AFTER_FRAMESET) - -struct Parser : public Sink { - enum struct Mode { -#define ITER(NAME) NAME, - FOREACH_INSERTION_MODE(ITER) -#undef ITER - - _LEN, - }; - - bool _scriptingEnabled = false; - bool _framesetOk = true; - - Mode _insertionMode = Mode::INITIAL; - Mode _originalInsertionMode = Mode::INITIAL; - - Lexer _lexer; - Strong _document; - Vec> _openElements; - Opt> _headElement; - Opt> _formElement; - - Parser(Strong document) - : _document(document) { - _lexer.bind(*this); - } - - void _raise(Str msg = "parse-error"); - - void _handleInitialMode(Token const &t); - - void _handleBeforeHtml(Token const &t); - - void _handleBeforeHead(Token const &t); - - void _handleInHead(Token const &t); - - void _handleInHeadNoScript(Token const &t); - - void _handleAfterHead(Token const &t); - - void _handleInBody(Token const &t); - - void _handleText(Token const &t); - - void _switchTo(Mode mode); - - void _acceptIn(Mode mode, Token const &t); - - void accept(Token const &t) override; - - void write(Str str) { - for (auto r : iterRunes(str)) - _lexer.consume(r); - } -}; - -#undef FOREACH_INSERTION_MODE - -} // namespace Vaev::Html diff --git a/src/web/vaev-html/tags.cpp b/src/web/vaev-html/tags.cpp deleted file mode 100644 index 9cf148eed0..0000000000 --- a/src/web/vaev-html/tags.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "tags.h" - -namespace Vaev::Html { - -Str _tagName(TagId id) { - switch (id) { -#define TAG(IDENT, NAME) \ - case TagId::IDENT: \ - return #NAME; -#include "defs/tag-names.inc" -#undef TAG - default: - return "unknown"; - } -} - -Str _attrName(AttrId id) { - switch (id) { -#define ATTR(IDENT, NAME) \ - case AttrId::IDENT: \ - return #NAME; -#include "defs/attr-names.inc" -#undef ATTR - default: - return "unknown"; - } -} - -Opt _tagId(Str name) { -#define TAG(IDENT, NAME) \ - if (name == #NAME) \ - return TagId::IDENT; -#include "defs/tag-names.inc" -#undef TAG - - return NONE; -} - -Opt _attrId(Str name) { -#define ATTR(IDENT, NAME) \ - if (name == #NAME) \ - return AttrId::IDENT; - -#include "defs/attr-names.inc" -#undef ATTR - - return NONE; -} - -} // namespace Vaev::Html diff --git a/src/web/vaev-html/tags.h b/src/web/vaev-html/tags.h deleted file mode 100644 index 4d497f94aa..0000000000 --- a/src/web/vaev-html/tags.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -namespace Vaev::Html { - -enum struct TagId : u16 { -#define TAG(IDENT, _) IDENT, -#include "defs/tag-names.inc" -#undef TAG -}; - -enum struct AttrId : u16 { -#define ATTR(IDENT, _) IDENT, -#include "defs/attr-names.inc" -#undef ATTR -}; - -#define TAG(IDENT, _) \ - inline constexpr TagName IDENT = TagId::IDENT; -#include "defs/tag-names.inc" -#undef TAG - -#define ATTR(IDENT, _) \ - inline constexpr AttrName IDENT##_ATTR = AttrId::IDENT; -#include "defs/attr-names.inc" -#undef ATTR - -} // namespace Vaev::Html diff --git a/src/web/vaev-html/tests/manifest.json b/src/web/vaev-html/tests/manifest.json deleted file mode 100644 index 1fbb6fbce2..0000000000 --- a/src/web/vaev-html/tests/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-html.tests", - "type": "lib", - "props": { - "cpp-excluded": true - }, - "requires": [ - "vaev-html", - "karm-test" - ], - "injects": [ - "__tests__" - ] -} diff --git a/src/web/vaev-layout/frag.cpp b/src/web/vaev-layout/frag.cpp index 4782f09935..3d6d7217e8 100644 --- a/src/web/vaev-layout/frag.cpp +++ b/src/web/vaev-layout/frag.cpp @@ -67,13 +67,13 @@ static Strong regularFontface() { return *_regularFontface; } -static void buildChildren(Style::Computer &c, Vec> const &children, Frag &parent) { +static void buildChildren(Style::Computer &c, Vec> const &children, Frag &parent) { for (auto &child : children) { build(c, *child, parent); } } -static void buildElement(Style::Computer &c, Dom::Element const &el, Frag &parent) { +static void buildElement(Style::Computer &c, Markup::Element const &el, Frag &parent) { auto style = c.computeFor(*parent.style, el); auto font = Text::Font{regularFontface(), 16}; @@ -98,7 +98,7 @@ static void buildElement(Style::Computer &c, Dom::Element const &el, Frag &paren parent.add(std::move(frag)); } -static void buildRun(Style::Computer &, Dom::Text const &node, Frag &parent) { +static void buildRun(Style::Computer &, Markup::Text const &node, Frag &parent) { auto style = makeStrong(Style::Computed::initial()); style->inherit(*parent.style); @@ -118,17 +118,17 @@ static void buildRun(Style::Computer &, Dom::Text const &node, Frag &parent) { parent.add({style, font, run}); } -void build(Style::Computer &c, Dom::Node const &node, Frag &parent) { - if (auto *el = node.is()) { +void build(Style::Computer &c, Markup::Node const &node, Frag &parent) { + if (auto *el = node.is()) { buildElement(c, *el, parent); - } else if (auto *text = node.is()) { + } else if (auto *text = node.is()) { buildRun(c, *text, parent); - } else if (auto *doc = node.is()) { + } else if (auto *doc = node.is()) { buildChildren(c, doc->children(), parent); } } -Frag build(Style::Computer &c, Dom::Document const &doc) { +Frag build(Style::Computer &c, Markup::Document const &doc) { auto font = Text::Font{regularFontface(), 16}; Frag root = {makeStrong(Style::Computed::initial()), font}; build(c, doc, root); diff --git a/src/web/vaev-layout/frag.h b/src/web/vaev-layout/frag.h index 395c4d5fb1..abed34a0a4 100644 --- a/src/web/vaev-layout/frag.h +++ b/src/web/vaev-layout/frag.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -45,9 +45,9 @@ struct Tree { // MARK: Build ----------------------------------------------------------------- -void build(Style::Computer &c, Dom::Node const &n, Frag &parent); +void build(Style::Computer &c, Markup::Node const &n, Frag &parent); -Frag build(Style::Computer &c, Dom::Document const &doc); +Frag build(Style::Computer &c, Markup::Document const &doc); // MARK: Layout ---------------------------------------------------------------- diff --git a/src/web/vaev-layout/manifest.json b/src/web/vaev-layout/manifest.json index f88dce4c63..2bf6a41aef 100644 --- a/src/web/vaev-layout/manifest.json +++ b/src/web/vaev-layout/manifest.json @@ -7,7 +7,7 @@ "karm-math", "karm-gfx", "karm-text", - "vaev-dom", + "vaev-markup", "vaev-style", "vaev-paint" ] diff --git a/src/web/vaev-markup/cli/main.cpp b/src/web/vaev-markup/cli/main.cpp new file mode 100644 index 0000000000..0b67f7e34a --- /dev/null +++ b/src/web/vaev-markup/cli/main.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include + +Async::Task<> entryPointAsync(Sys::Context &ctx) { + auto args = Sys::useArgs(ctx); + + if (args.len() != 2) { + Sys::errln("usage: vaev-markup.cli \n"); + co_return Error::invalidInput(); + } + + auto verb = args[0]; + auto url = co_try$(Mime::parseUrlOrPath(args[1])); + + if (verb == "html-dump-dom") { + auto file = co_try$(Sys::File::open(url)); + auto buf = co_try$(Io::readAllUtf8(file)); + auto doc = makeStrong(); + Vaev::Markup::HtmlParser parser{doc}; + parser.write(buf); + Sys::println("{}", doc); + + co_return Ok(); + } else if (verb == "html-dump-tokens") { + auto file = co_try$(Sys::File::open(url)); + auto buf = co_try$(Io::readAllUtf8(file)); + + Vec tokens; + + struct VecSink : public Vaev::Markup::HtmlSink { + Vec &tokens; + + VecSink(Vec &tokens) + : tokens(tokens) { + } + + void accept(Vaev::Markup::HtmlToken const &token) override { + tokens.pushBack(token); + } + }; + + VecSink sink{tokens}; + Vaev::Markup::HtmlLexer lexer{}; + lexer.bind(sink); + + for (auto r : iterRunes(buf)) + lexer.consume(r); + + for (auto &t : tokens) + Sys::println("{}", t); + + co_return Ok(); + } else if (verb == "xml-dump-dom") { + auto file = co_try$(Sys::File::open(url)); + auto buf = co_try$(Io::readAllUtf8(file)); + + auto start = Sys::now(); + + Vaev::Markup::XmlParser parser{}; + Io::SScan s{buf}; + auto res = parser.parse(s, Vaev::HTML); + + auto elapsed = Sys::now() - start; + logInfo("parsed in {}ms", elapsed.toUSecs() / 1000.0); + + Sys::println("{}", res); + + co_return Ok(); + } else { + Sys::errln("unknown verb: {} (expected: html-dump-dom, html-dump-tokens, xml-dump-dom)\n", verb); + co_return Error::invalidInput(); + } +} diff --git a/src/web/vaev-html/cli/manifest.json b/src/web/vaev-markup/cli/manifest.json similarity index 64% rename from src/web/vaev-html/cli/manifest.json rename to src/web/vaev-markup/cli/manifest.json index 783f2e8322..6b14b5a84a 100644 --- a/src/web/vaev-html/cli/manifest.json +++ b/src/web/vaev-markup/cli/manifest.json @@ -1,10 +1,10 @@ { "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-html.cli", + "id": "vaev-markup.cli", "type": "exe", - "description": "HTML CLI", + "description": "Markup CLI", "requires": [ "karm-sys", - "vaev-html" + "vaev-markup" ] } diff --git a/src/web/vaev-html/defs/fetchEntities.py b/src/web/vaev-markup/defs/fetch-html-entities.py similarity index 100% rename from src/web/vaev-html/defs/fetchEntities.py rename to src/web/vaev-markup/defs/fetch-html-entities.py diff --git a/src/web/vaev-base/defs/namespaces.inc b/src/web/vaev-markup/defs/namespaces.inc similarity index 100% rename from src/web/vaev-base/defs/namespaces.inc rename to src/web/vaev-markup/defs/namespaces.inc diff --git a/src/web/vaev-html/defs/attr-names.inc b/src/web/vaev-markup/defs/ns-html-attr-names.inc similarity index 100% rename from src/web/vaev-html/defs/attr-names.inc rename to src/web/vaev-markup/defs/ns-html-attr-names.inc diff --git a/src/web/vaev-html/defs/entities.inc b/src/web/vaev-markup/defs/ns-html-entities.inc similarity index 100% rename from src/web/vaev-html/defs/entities.inc rename to src/web/vaev-markup/defs/ns-html-entities.inc diff --git a/src/web/vaev-html/defs/special.inc b/src/web/vaev-markup/defs/ns-html-special.inc similarity index 100% rename from src/web/vaev-html/defs/special.inc rename to src/web/vaev-markup/defs/ns-html-special.inc diff --git a/src/web/vaev-html/defs/states.inc b/src/web/vaev-markup/defs/ns-html-states.inc similarity index 100% rename from src/web/vaev-html/defs/states.inc rename to src/web/vaev-markup/defs/ns-html-states.inc diff --git a/src/web/vaev-html/defs/tag-names.inc b/src/web/vaev-markup/defs/ns-html-tag-names.inc similarity index 100% rename from src/web/vaev-html/defs/tag-names.inc rename to src/web/vaev-markup/defs/ns-html-tag-names.inc diff --git a/src/web/vaev-mathml/defs/attr-names.inc b/src/web/vaev-markup/defs/ns-mathml-attr-names.inc similarity index 100% rename from src/web/vaev-mathml/defs/attr-names.inc rename to src/web/vaev-markup/defs/ns-mathml-attr-names.inc diff --git a/src/web/vaev-mathml/defs/tag-names.inc b/src/web/vaev-markup/defs/ns-mathml-tag-names.inc similarity index 100% rename from src/web/vaev-mathml/defs/tag-names.inc rename to src/web/vaev-markup/defs/ns-mathml-tag-names.inc diff --git a/src/web/vaev-svg/defs/attr-names.inc b/src/web/vaev-markup/defs/ns-svg-attr-names.inc similarity index 100% rename from src/web/vaev-svg/defs/attr-names.inc rename to src/web/vaev-markup/defs/ns-svg-attr-names.inc diff --git a/src/web/vaev-svg/defs/tag-names.inc b/src/web/vaev-markup/defs/ns-svg-tag-names.inc similarity index 100% rename from src/web/vaev-svg/defs/tag-names.inc rename to src/web/vaev-markup/defs/ns-svg-tag-names.inc diff --git a/src/web/vaev-markup/dom.h b/src/web/vaev-markup/dom.h new file mode 100644 index 0000000000..dd72ef490b --- /dev/null +++ b/src/web/vaev-markup/dom.h @@ -0,0 +1,411 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "tags.h" + +namespace Vaev::Markup { + +// MARK: Node ------------------------------------------------------------------ + +#define FOREACH_NODE_TYPE(TYPE) \ + TYPE(ELEMENT, 1) \ + TYPE(ATTRIBUTE, 2) \ + TYPE(TEXT, 3) \ + TYPE(CDATA_SECTION, 4) \ + TYPE(PROCESSING_INSTRUCTION, 7) \ + TYPE(COMMENT, 8) \ + TYPE(DOCUMENT, 9) \ + TYPE(DOCUMENT_TYPE, 10) \ + TYPE(DOCUMENT_FRAGMENT, 11) + +enum struct NodeType { +#define ITER(NAME, VALUE) NAME = VALUE, + FOREACH_NODE_TYPE(ITER) +#undef ITER + _LEN, +}; + +// https://dom.spec.whatwg.org/#interface-node +struct Node : + Meta::Static { + + Node *_parent = nullptr; + Vec> _children; + + virtual ~Node() = default; + + virtual NodeType nodeType() const = 0; + + template + T *is() { + return nodeType() == T::TYPE ? static_cast(this) : nullptr; + } + + template + T const *is() const { + return nodeType() == T::TYPE ? static_cast(this) : nullptr; + } + + // MARK: Parent + + Node &parentNode() { + if (not _parent) + panic("node has no parent"); + return *_parent; + } + + Node const &parentNode() const { + if (not _parent) + panic("node has no parent"); + return *_parent; + } + + bool hasParent() const { + return _parent != nullptr; + } + + usize _parentIndex() const { + if (not _parent) + panic("node has no parent"); + return indexOf(parentNode()._children, *this).unwrap(); + } + + void _detachParent() { + if (_parent) { + _parent->_children.removeAt(_parentIndex()); + _parent = nullptr; + } + } + + // MARK: Children + + bool hasChildren() const { + return _children.len() > 0; + } + + Strong firstChild() { + if (not _children.len()) + panic("node has no children"); + return first(_children); + } + + Strong lastChild() { + if (not _children.len()) + panic("node has no children"); + return last(_children); + } + + void appendChild(Strong child) { + child->_detachParent(); + _children.pushBack(child); + child->_parent = this; + } + + void removeChild(Strong child) { + if (child->_parent != this) + panic("node is not a child"); + child->_detachParent(); + } + + Slice> children() const { + return _children; + } + + // MARK: Siblings + + Strong previousSibling() const { + usize index = _parentIndex(); + return parentNode()._children[index - 1]; + } + + bool hasPreviousSibling() const { + return _parentIndex() > 0; + } + + Strong nextSibling() { + usize index = _parentIndex(); + return parentNode()._children[index + 1]; + } + + bool hasNextSibling() const { + return _parentIndex() < parentNode()._children.len() - 1; + } + + virtual void _repr(Io::Emit &) const {} + + void repr(Io::Emit &e) const { + e("({}", nodeType()); + _repr(e); + if (_children.len() > 0) { + e.indentNewline(); + for (auto &child : _children) { + child->repr(e); + } + e.deindent(); + } + e(")\n"); + } + + // MARK: Operators + + bool operator==(Node const &other) const { + return this == &other; + } + + auto operator<=>(Node const &other) const { + return this <=> &other; + } +}; + +// MARK: Document -------------------------------------------------------------- + +enum struct QuirkMode { + NO, + LIMITED, + YES +}; + +// https://dom.spec.whatwg.org/#interface-document +struct Document : public Node { + static constexpr auto TYPE = NodeType::DOCUMENT; + + QuirkMode quirkMode{QuirkMode::NO}; + + NodeType nodeType() const override { + return TYPE; + } +}; + +// MARK: DocumentType ---------------------------------------------------------- + +// https://dom.spec.whatwg.org/#interface-documenttype +struct DocumentType : public Node { + static constexpr auto TYPE = NodeType::DOCUMENT_TYPE; + + String name; + String publicId; + String systemId; + + DocumentType() = default; + + DocumentType(String name, String publicId, String systemId) + : name(name), publicId(publicId), systemId(systemId) { + } + + NodeType nodeType() const override { + return TYPE; + } + + void _repr(Io::Emit &e) const override { + e(" name={#} publicId={#} systemId={#}", this->name, this->publicId, this->systemId); + } +}; + +// MARK: CharacterData --------------------------------------------------------- + +// https://dom.spec.whatwg.org/#interface-characterdata +struct CharacterData : public Node { + String data; + + CharacterData(String data) + : data(data) { + } + + void appendData(String const &data) { + // HACK: This is not efficient and pretty slow, + // but it's good enough for now. + StringBuilder sb; + sb.append(this->data); + sb.append(data); + this->data = sb.take(); + } + + void appendData(Rune rune) { + // HACK: This is not efficient and pretty slow, + // but it's good enough for now. + StringBuilder sb; + sb.append(this->data); + sb.append(rune); + this->data = sb.take(); + } + + void _repr(Io::Emit &e) const override { + e(" data={#}", this->data); + } +}; + +// MARK: Text ------------------------------------------------------------------ + +// https://dom.spec.whatwg.org/#text +struct Text : public CharacterData { + static constexpr auto TYPE = NodeType::TEXT; + + using CharacterData::CharacterData; + + NodeType nodeType() const override { + return TYPE; + } +}; + +// MARK: Attrs ----------------------------------------------------------------- + +// https://dom.spec.whatwg.org/#interface-attr + +struct Attr : public Node { + AttrName name; + String value; + + Attr(AttrName name, String value) + : name(name), value(value) { + } + + NodeType nodeType() const override { + return NodeType::ATTRIBUTE; + } + + void _repr(Io::Emit &e) const override { + e(" namespaceURI={#}", name.ns.url()); + e(" prefix={#}", name.ns.name()); + e(" localName={#} value={#}", name.name(), value); + } +}; + +// MARK: TokenList ------------------------------------------------------------ + +// https://dom.spec.whatwg.org/#domtokenlist +struct TokenList { + Vec _tokens; + + usize length() const { + return _tokens.len(); + } + + Opt item(usize index) const { + if (index >= _tokens.len()) + return NONE; + return _tokens[index]; + } + + bool contains(Str token) const { + return _tokens.contains(token); + } + + void add(Str token) { + if (not _tokens.contains(token)) + _tokens.pushBack(token); + } + + void remove(Str token) { + _tokens.removeAll(token); + } + + bool toggle(Str token) { + if (_tokens.contains(token)) { + _tokens.removeAll(token); + return false; + } + _tokens.pushBack(token); + return true; + } + + bool replace(Str oldToken, Str newToken) { + if (not _tokens.contains(oldToken)) + return false; + _tokens.removeAll(oldToken); + _tokens.pushBack(newToken); + return true; + } +}; + +// MARK: Element --------------------------------------------------------------- + +// https://dom.spec.whatwg.org/#interface-element +struct Element : public Node { + static constexpr auto TYPE = NodeType::ELEMENT; + + Opt id() const { + return this->getAttribute(Html::ID_ATTR); + } + + TagName tagName; + // NOSPEC: Should be a NamedNodeMap + Map> attributes; + TokenList classList; + + Element(TagName tagName) + : tagName(tagName) { + } + + NodeType nodeType() const override { + return TYPE; + } + + String textContent() const { + String builder; + if (_children.len() == 0) + return ""s; + + if (_children.len() > 1) + panic("textContent is not implemented for elements with multiple children"); + + auto const &child = *_children[0]; + if (auto *text = child.is()) { + return text->data; + } + + panic("textContent is not implemented for elements with children other than text nodes"); + } + + void _repr(Io::Emit &e) const override { + e(" tagName={#}", this->tagName); + if (this->attributes.len()) { + e.indentNewline(); + for (auto const &[name, attr] : this->attributes.iter()) { + attr->repr(e); + } + e.deindent(); + } + } + + void setAttribute(AttrName name, String value) { + if (name == Html::CLASS_ATTR) { + for (auto class_ : iterSplit(value, ' ')) { + this->classList.add(class_); + } + return; + } + auto attr = makeStrong(name, value); + this->attributes.put(name, attr); + } + + bool hasAttribute(AttrName name) const { + return this->attributes.tryGet(name) != NONE; + } + + Opt getAttribute(AttrName name) const { + auto attr = this->attributes.tryGet(name); + if (attr == NONE) + return NONE; + return (*attr)->value; + } +}; + +// MARK: Comment --------------------------------------------------------------- + +// https://dom.spec.whatwg.org/#interface-comment +struct Comment : public CharacterData { + using CharacterData::CharacterData; + + static constexpr auto TYPE = NodeType::COMMENT; + + NodeType nodeType() const override { + return TYPE; + } +}; + +} // namespace Vaev::Markup diff --git a/src/web/vaev-html/lexer.cpp b/src/web/vaev-markup/html.cpp similarity index 72% rename from src/web/vaev-html/lexer.cpp rename to src/web/vaev-markup/html.cpp index ded79a3438..39ba377795 100644 --- a/src/web/vaev-html/lexer.cpp +++ b/src/web/vaev-markup/html.cpp @@ -1,9 +1,11 @@ #include #include -#include "lexer.h" +#include "html.h" -namespace Vaev::Html { +namespace Vaev::Markup { + +// MARK: Lexer ----------------------------------------------------------------- struct Entity { Str name; @@ -17,15 +19,15 @@ struct Entity { [[maybe_unused]] static Array const ENTITIES = { #define ENTITY(NAME, ...) \ Entity{#NAME, (Rune[]){__VA_ARGS__ __VA_OPT__(, ) 0}}, -#include "defs/entities.inc" +#include "defs/ns-html-entities.inc" #undef ENTITY }; -void Lexer::_raise(Str msg) { +void HtmlLexer::_raise(Str msg) { logError("{}: {}", _state, msg); } -void Lexer::consume(Rune rune, bool isEof) { +void HtmlLexer::consume(Rune rune, bool isEof) { // logDebug("Lexing '{#c}' {#x} in {}", rune, rune, _state); switch (_state) { @@ -59,7 +61,7 @@ void Lexer::consume(Rune rune, bool isEof) { // EOF // Emit an end-of-file token. else if (isEof) { - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -100,7 +102,7 @@ void Lexer::consume(Rune rune, bool isEof) { // EOF // Emit an end-of-file token. else if (isEof) { - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -134,7 +136,7 @@ void Lexer::consume(Rune rune, bool isEof) { // EOF // Emit an end-of-file token. else if (isEof) { - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -168,7 +170,7 @@ void Lexer::consume(Rune rune, bool isEof) { // EOF // Emit an end-of-file token. else if (isEof) { - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -196,7 +198,7 @@ void Lexer::consume(Rune rune, bool isEof) { // EOF // Emit an end-of-file token. else if (isEof) { - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -229,7 +231,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new start tag token, set its tag name to the empty // string. Reconsume in the tag name state. else if (isAsciiAlpha(rune)) { - _begin(Token::START_TAG); + _begin(HtmlToken::START_TAG); _reconsumeIn(State::TAG_NAME, rune); } @@ -239,7 +241,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus comment state. else if (rune == '?') { _raise("unexpected-question-mark-instead-of-tag-name"); - _begin(Token::COMMENT); + _begin(HtmlToken::COMMENT); _reconsumeIn(State::BOGUS_COMMENT, rune); } @@ -249,7 +251,7 @@ void Lexer::consume(Rune rune, bool isEof) { else if (isEof) { _raise("eof-before-tag-name"); _emit('<'); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -274,7 +276,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new end tag token, set its tag name to the empty string. // Reconsume in the tag name state. if (isAsciiAlpha(rune)) { - _begin(Token::END_TAG); + _begin(HtmlToken::END_TAG); _reconsumeIn(State::TAG_NAME, rune); } @@ -294,7 +296,7 @@ void Lexer::consume(Rune rune, bool isEof) { _raise("eof-before-tag-name"); _emit('<'); _emit('/'); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -304,7 +306,7 @@ void Lexer::consume(Rune rune, bool isEof) { // in the bogus comment state. else { _raise("invalid-first-character-of-tag-name"); - _begin(Token::COMMENT); + _begin(HtmlToken::COMMENT); _reconsumeIn(State::BOGUS_COMMENT, rune); } @@ -361,7 +363,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -406,7 +408,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new end tag token, set its tag name to the empty string. // Reconsume in the RCDATA end tag name state. if (isAsciiAlpha(rune)) { - _begin(Token::END_TAG); + _begin(HtmlToken::END_TAG); _reconsumeIn(State::RCDATA_END_TAG_NAME, rune); } @@ -521,7 +523,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new end tag token, set its tag name to the empty string. // Reconsume in the RAWTEXT end tag name state. if (isAsciiAlpha(rune)) { - _begin(Token::END_TAG); + _begin(HtmlToken::END_TAG); _reconsumeIn(State::RAWTEXT_END_TAG_NAME, rune); } @@ -644,7 +646,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new end tag token, set its tag name to the empty string. // Reconsume in the script data end tag name state. if (isAsciiAlpha(rune)) { - _begin(Token::END_TAG); + _begin(HtmlToken::END_TAG); _reconsumeIn(State::SCRIPT_DATA_END_TAG_NAME, rune); } @@ -803,7 +805,7 @@ void Lexer::consume(Rune rune, bool isEof) { // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -845,42 +847,12 @@ void Lexer::consume(Rune rune, bool isEof) { } // EOF - // This is an eof-in-script-html-comment-like-text parse error. Emit - // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } - // Anything else - // Switch to the script data escaped state. Emit the current input - // character as a character token. - else { - _switchTo(State::SCRIPT_DATA_ESCAPED); - _emit(rune); - } - - break; - } - - case State::SCRIPT_DATA_ESCAPED_DASH_DASH: { - // 13.2.5.22 MARK: Script data escaped dash dash state - // Consume the next input character: - - // U+002D HYPHEN-MINUS (-) - // Emit a U+002D HYPHEN-MINUS character token. - if (rune == '-') { - _emit('-'); - } - - // U+003C LESS-THAN SIGN (<) - // Switch to the script data escaped less-than sign state. - else if (rune == '<') { - _switchTo(State::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN); - } - - // U+003E GREATER-THAN SIGN (>) // Switch to the script data state. Emit a U+003E GREATER-THAN SIGN // character token. else if (rune == '>') { @@ -903,7 +875,7 @@ void Lexer::consume(Rune rune, bool isEof) { // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -959,7 +931,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new end tag token, set its tag name to the empty string. // Reconsume in the script data escaped end tag name state. if (isAsciiAlpha(rune)) { - _begin(Token::END_TAG); + _begin(HtmlToken::END_TAG); _reconsumeIn(State::SCRIPT_DATA_ESCAPED_END_TAG_NAME, rune); } @@ -1125,7 +1097,7 @@ void Lexer::consume(Rune rune, bool isEof) { // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1173,7 +1145,7 @@ void Lexer::consume(Rune rune, bool isEof) { // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1229,7 +1201,7 @@ void Lexer::consume(Rune rune, bool isEof) { // an end-of-file token. else if (isEof) { _raise("eof-in-script-html-comment-like-text"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1468,7 +1440,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1558,7 +1530,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1603,7 +1575,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1670,7 +1642,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1714,7 +1686,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1746,7 +1718,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-tag parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-tag"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1767,7 +1739,7 @@ void Lexer::consume(Rune rune, bool isEof) { // U+003E GREATER-THAN SIGN (>) // Switch to the data state. Emit the current comment token. if (rune == '>') { - _ensure(Token::COMMENT).data = _builder.take(); + _ensure(HtmlToken::COMMENT).data = _builder.take(); _switchTo(State::DATA); _emit(); } @@ -1776,7 +1748,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit the comment. Emit an end-of-file token. else if (isEof) { _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1810,7 +1782,7 @@ void Lexer::consume(Rune rune, bool isEof) { break; _temp.clear(); - _begin(Token::COMMENT); + _begin(HtmlToken::COMMENT); _switchTo(State::COMMENT_START); } @@ -1849,7 +1821,7 @@ void Lexer::consume(Rune rune, bool isEof) { else { _temp.clear(); _raise("incorrectly-opened-comment"); - _begin(Token::COMMENT); + _begin(HtmlToken::COMMENT); _reconsumeIn(State::BOGUS_COMMENT, rune); } break; @@ -1908,7 +1880,7 @@ void Lexer::consume(Rune rune, bool isEof) { else if (isEof) { _raise("eof-in-comment"); _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -1956,7 +1928,7 @@ void Lexer::consume(Rune rune, bool isEof) { _raise("eof-in-comment"); _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2072,7 +2044,7 @@ void Lexer::consume(Rune rune, bool isEof) { else if (isEof) { _raise("eof-in-comment"); _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2117,7 +2089,7 @@ void Lexer::consume(Rune rune, bool isEof) { else if (isEof) { _raise("eof-in-comment"); _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2163,7 +2135,7 @@ void Lexer::consume(Rune rune, bool isEof) { else if (isEof) { _raise("eof-in-comment"); _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2206,10 +2178,10 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _begin(Token::DOCTYPE); - _ensure(Token::DOCTYPE).forceQuirks = true; + _begin(HtmlToken::DOCTYPE); + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2242,7 +2214,7 @@ void Lexer::consume(Rune rune, bool isEof) { // version of the current input character (add 0x0020 to the // character's code point). Switch to the DOCTYPE name state. else if (isAsciiUpper(rune)) { - _begin(Token::DOCTYPE); + _begin(HtmlToken::DOCTYPE); _builder.append(toAsciiLower(rune)); _switchTo(State::DOCTYPE_NAME); } @@ -2253,7 +2225,7 @@ void Lexer::consume(Rune rune, bool isEof) { // CHARACTER character. Switch to the DOCTYPE name state. else if (rune == 0) { _raise("unexpected-null-character"); - _begin(Token::DOCTYPE); + _begin(HtmlToken::DOCTYPE); _builder.append(0xFFFD); _switchTo(State::DOCTYPE_NAME); } @@ -2264,8 +2236,8 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit the current token. else if (rune == '>') { _raise("missing-doctype-name"); - _begin(Token::DOCTYPE); - _ensure(Token::DOCTYPE).forceQuirks = true; + _begin(HtmlToken::DOCTYPE); + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2276,10 +2248,10 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _begin(Token::DOCTYPE); - _ensure(Token::DOCTYPE).forceQuirks = true; + _begin(HtmlToken::DOCTYPE); + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2287,7 +2259,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Create a new DOCTYPE token. Set the token's name to the current // input character. Switch to the DOCTYPE name state. else { - _begin(Token::DOCTYPE); + _begin(HtmlToken::DOCTYPE); _builder.append(rune); _switchTo(State::DOCTYPE_NAME); } @@ -2305,14 +2277,14 @@ void Lexer::consume(Rune rune, bool isEof) { // U+0020 SPACE // Switch to the after DOCTYPE name state. if (rune == '\t' or rune == '\n' or rune == '\f' or rune == ' ') { - _ensure(Token::DOCTYPE).name = _builder.take(); + _ensure(HtmlToken::DOCTYPE).name = _builder.take(); _switchTo(State::AFTER_DOCTYPE_NAME); } // U+003E GREATER-THAN SIGN (>) // Switch to the data state. Emit the current DOCTYPE token. else if (rune == '>') { - _ensure(Token::DOCTYPE).name = _builder.take(); + _ensure(HtmlToken::DOCTYPE).name = _builder.take(); _switchTo(State::DATA); _emit(); } @@ -2340,9 +2312,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2382,9 +2354,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2411,7 +2383,7 @@ void Lexer::consume(Rune rune, bool isEof) { // the bogus DOCTYPE state. else { _raise("invalid-character-sequence-after-doctype-name"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2457,7 +2429,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("missing-doctype-public-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2468,9 +2440,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2480,7 +2452,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-public-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2522,7 +2494,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("missing-doctype-public-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2533,9 +2505,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2545,7 +2517,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-public-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2559,7 +2531,7 @@ void Lexer::consume(Rune rune, bool isEof) { // U+0022 QUOTATION MARK (") // Switch to the after DOCTYPE public identifier state. if (rune == '"') { - _ensure(Token::DOCTYPE).publicIdent = _builder.take(); + _ensure(HtmlToken::DOCTYPE).publicIdent = _builder.take(); _switchTo(State::AFTER_DOCTYPE_PUBLIC_IDENTIFIER); } @@ -2578,8 +2550,8 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("abrupt-doctype-public-identifier"); - _ensure(Token::DOCTYPE).publicIdent = _builder.take(); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).publicIdent = _builder.take(); + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2590,9 +2562,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2613,7 +2585,7 @@ void Lexer::consume(Rune rune, bool isEof) { // U+0027 APOSTROPHE (') // Switch to the after DOCTYPE public identifier state. if (rune == '\'') { - _ensure(Token::DOCTYPE).publicIdent = _builder.take(); + _ensure(HtmlToken::DOCTYPE).publicIdent = _builder.take(); _switchTo(State::AFTER_DOCTYPE_PUBLIC_IDENTIFIER); } @@ -2632,8 +2604,8 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("abrupt-doctype-public-identifier"); - _ensure(Token::DOCTYPE).publicIdent = _builder.take(); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).publicIdent = _builder.take(); + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2644,9 +2616,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2708,9 +2680,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2720,7 +2692,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2769,9 +2741,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2781,7 +2753,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2827,7 +2799,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("missing-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2838,9 +2810,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2850,7 +2822,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2892,7 +2864,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("missing-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2903,9 +2875,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2915,7 +2887,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Reconsume in the bogus DOCTYPE state. else { _raise("missing-quote-before-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _reconsumeIn(State::BOGUS_DOCTYPE, rune); } @@ -2947,7 +2919,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("abrupt-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -2958,9 +2930,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -2999,7 +2971,7 @@ void Lexer::consume(Rune rune, bool isEof) { // data state. Emit the current DOCTYPE token. else if (rune == '>') { _raise("abrupt-doctype-system-identifier"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _switchTo(State::DATA); _emit(); } @@ -3010,9 +2982,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -3052,9 +3024,9 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit an end-of-file token. else if (isEof) { _raise("eof-in-doctype"); - _ensure(Token::DOCTYPE).forceQuirks = true; + _ensure(HtmlToken::DOCTYPE).forceQuirks = true; _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -3093,7 +3065,7 @@ void Lexer::consume(Rune rune, bool isEof) { // Emit the DOCTYPE token. Emit an end-of-file token. else if (isEof) { _emit(); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -3120,7 +3092,7 @@ void Lexer::consume(Rune rune, bool isEof) { // This is an eof-in-cdata parse error. Emit an end-of-file token. else if (isEof) { _raise("eof-in-cdata"); - _begin(Token::END_OF_FILE); + _begin(HtmlToken::END_OF_FILE); _emit(); } @@ -3573,4 +3545,1095 @@ void Lexer::consume(Rune rune, bool isEof) { } } -} // namespace Vaev::Html +// MARK: Parser ---------------------------------------------------------------- + +// 13.2.2 MARK: Parse errors +// https://html.spec.whatwg.org/multipage/parsing.html#parse-errors + +void HtmlParser::_raise(Str msg) { + logError("{}: {}", _insertionMode, msg); +} + +// 13.2.4.2 MARK: The stack of open elements +// https://html.spec.whatwg.org/multipage/parsing.html#the-stack-of-open-elements + +// https://html.spec.whatwg.org/multipage/parsing.html#special +bool isSpecial(TagName const &tag) { + static constexpr Array SPECIAL{ +#define SPECIAL(NAME) NAME, +#include "defs/ns-html-special.inc" +#undef SPECIAL + }; + + return contains(SPECIAL, tag); +} + +// 13.2.4.3 MARK: The list of active formatting elements +// https://html.spec.whatwg.org/multipage/parsing.html#list-of-active-formatting-elements + +void reconstructActiveFormattingElements(HtmlParser &) { + // TODO +} + +// 13.2.5 MARK: Tokenization +// https://html.spec.whatwg.org/multipage/parsing.html#tokenization + +// https://html.spec.whatwg.org/multipage/parsing.html#acknowledge-self-closing-flag +void acknowledgeSelfClosingFlag(HtmlToken const &) { + logDebug("acknowledgeSelfClosingFlag not implemented"); +} + +// 13.2.6 MARK: Tree construction +// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction + +// 13.2.6.1 MARK: Creating and inserting nodes +// https://html.spec.whatwg.org/multipage/parsing.html#creating-and-inserting-nodes + +struct AdjustedInsertionLocation { + Strong parent; + + // https://html.spec.whatwg.org/multipage/parsing.html#insert-an-element-at-the-adjusted-insertion-location + void insert(Strong node) { + // NOSPEC + parent->appendChild(node); + } + + Opt> lastChild() { + // NOSPEC + if (not parent->hasChildren()) + return NONE; + return parent->lastChild(); + } +}; + +// https://html.spec.whatwg.org/multipage/parsing.html#appropriate-place-for-inserting-a-node +AdjustedInsertionLocation apropriatePlaceForInsertingANode(HtmlParser &b, Opt> overrideTarget = NONE) { + // 1. If there was an override target specified, then let target be + // the override target. + // + // Otherwise, let target be the current node. + auto target = overrideTarget + ? *overrideTarget + : last(b._openElements); + + // 2. Determine the adjusted insertion location using the first + // matching steps from the following list: + + // If foster parenting is enabled and target is a table, tbody, tfoot, thead, or tr element + + // NOTE: Foster parenting happens when content is misnested in tables. + + // 1. Let last template be the last template element in the stack of open elements, if any. + + // 2. Let last table be the last table element in the stack of open elements, if any. + + // 3. If there is a last template and either there is no last table, + // or there is one, but last template is lower (more recently added) + // than last table in the stack of open elements, + // then: let adjusted insertion location be inside last template's + // template contents, after its last child (if any), and abort these steps. + + // 4. If there is no last table, then let adjusted insertion location be + // inside the first element in the stack of open elements (the html element), + // after its last child (if any), and abort these steps. (fragment case) + + // 5. If last table has a parent node, then let adjusted insertion location + // be inside last table's parent node, immediately before last table, + // and abort these steps. + + // 6. Let previous element be the element immediately above last table + // in the stack of open elements. + + // 7. Let adjusted insertion location be inside previous element, + // after its last child (if any). + + // Otherwise: Let adjusted insertion location be inside target, + // after its last child (if any). + + // 3. If the adjusted insertion location is inside a template element, + // let it instead be inside the template element's template contents, + // after its last child (if any). + + // 4. Return the adjusted insertion location. + return {target}; +} + +// https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token +Strong createElementFor(HtmlToken const &t, Ns ns) { + // NOSPEC: Keep it simple for the POC + + // 1. If the active speculative HTML parser is not null, then return the + // result of creating a speculative mock element given given namespace, + // the tag name of the given token, and the attributes of the given token. + + // 2. Otherwise, optionally create a speculative mock element given given + // namespace, the tag name of the given token, and the attributes of + // the given token. + + // 3. Let document be intended parent's node document. + + // 4. Let local name be the tag name of the token + + // 5. Let is be the value of the "is" attribute in the given token, if + // such an attribute exists, or null otherwise. + + // 6. Let definition be the result of looking up a custom element + // definition given document, given namespace, local name, and is. + + // 7. If definition is non-null and the parser was not created as part + // of the HTML fragment parsing algorithm, then let will execute + // script be true. Otherwise, let it be false. + + // NOSPEC: We don't support scripting so we don't need to worry about this + bool willExecuteScript = false; + + // 8. If will execute script is true, then: + + if (willExecuteScript) { + // 1. Increment document's throw-on-dynamic-markup-insertion counter. + + // 2. If the JavaScript execution context stack is empty, + // then perform a microtask checkpoint. + + // 3. Push a new element queue onto document's relevant agent's + // custom element reactions stack. + } + + // 9. Let element be the result of creating an element given document, + // localName, given namespace, null, and is. If will execute script + // is true, set the synchronous custom elements flag; otherwise, + // leave it unset. + auto el = makeStrong(TagName::make(t.name, ns)); + + // 10. Append each attribute in the given token to element. + for (auto &[name, value] : t.attrs) { + el->setAttribute(AttrName::make(name, ns), value); + } + + // 11. If will execute script is true, then: + if (willExecuteScript) { + // 1. Let queue be the result of popping from document's relevant + // agent's custom element reactions stack. (This will be the + // same element queue as was pushed above.) + + // 2. Invoke custom element reactions in queue. + + // 3. Decrement document's throw-on-dynamic-markup-insertion counter. + } + + // 12. If element has an xmlns attribute in the XMLNS namespace whose + // value is not exactly the same as the element's namespace, that + // is a parse error. Similarly, if element has an xmlns:xlink + // attribute in the XMLNS namespace whose value is not the XLink + // Namespace, that is a parse error. + + // 13. If element is a resettable element, invoke its reset algorithm. + // (This initializes the element's value and checkedness based on the element's attributes.) + + // 14. If element is a form-associated element and not a form-associated + // custom element, the form element pointer is not null, there is no + // template element on the stack of open elements, element is either + // not listed or doesn't have a form attribute, and the intended parent + // is in the same tree as the element pointed to by the form element pointer, + + // Then associate element with the form element pointed to by the form + // element pointer and set element's parser inserted flag. + + // 15. Return element. + return el; +} + +// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-foreign-element + +static Strong insertAForeignElement(HtmlParser &b, HtmlToken const &t, Ns ns, bool onlyAddToElementStack = false) { + // 1. Let the adjusted insertion location be the appropriate place for inserting a node. + auto location = apropriatePlaceForInsertingANode(b); + + // 2. Let element be the result of creating an element for the token in the + // given namespace, with the intended parent being the element in which the + // adjusted insertion location finds itself. + auto el = createElementFor(t, ns); + + // 3. If onlyAddToElementStack is false, then run insert an element at the adjusted insertion location with element. + if (not onlyAddToElementStack) { + location.insert(el); + } + + // 4. Push element onto the stack of open elements so that it is the new current node. + b._openElements.pushBack(el); + + // 5. Return element. + return el; +} + +// https://html.spec.whatwg.org/multipage/parsing.html#insert-an-html-element +static Strong insertHtmlElement(HtmlParser &b, HtmlToken const &t) { + return insertAForeignElement(b, t, Vaev::HTML, false); +} + +// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-character +static void insertACharacter(HtmlParser &b, Rune c) { + // 2. Let the adjusted insertion location be the appropriate place for inserting a node. + auto location = apropriatePlaceForInsertingANode(b); + + // 3. If the adjusted insertion location is inside a Document node, then ignore the token. + if (location.parent->nodeType() == NodeType::DOCUMENT) + return; + + // 4. If there is a Text node immediately before the adjusted insertion + // location, then append data to that Text node's data. + auto lastChild = location.lastChild(); + if (lastChild and (*lastChild)->nodeType() == NodeType::TEXT) { + auto text = (*(*lastChild).cast()); + text->appendData(c); + } + + // Otherwise, create a new Text node whose data is data and whose node + // document is the same as that of the element in which the + // adjusted insertion location finds itself, and insert the + // newly created node at the adjusted insertion location. + else { + auto text = makeStrong(""s); + text->appendData(c); + + location.insert(text); + } +} + +static void insertACharacter(HtmlParser &b, HtmlToken const &t) { + // 1. Let data be the characters passed to the algorithm, or, if no characters were explicitly specified, the character of the character token being processed. + insertACharacter(b, t.rune); +} + +// https://html.spec.whatwg.org/multipage/parsing.html#insert-a-comment +static void insertAComment(HtmlParser &b, HtmlToken const &t) { + // 1. Let data be the data given in the comment token being processed. + + // 2. If position was specified, then let the adjusted insertion + // location be position. Otherwise, let adjusted insertion location + // be the appropriate place for inserting a node. + + // TODO: If position was + auto location = apropriatePlaceForInsertingANode(b); + + // 3. Create a Comment node whose data attribute is set to data and + // whose node document is the same as that of the node in which + // the adjusted insertion location finds itself. + auto comment = makeStrong(t.data); + + // 4. Insert the newly created node at the adjusted insertion location. + location.insert(comment); +} + +// 13.2.6.2 MARK: Parsing elements that contain only text +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text + +static void parseRawTextElement(HtmlParser &b, HtmlToken const &t) { + insertHtmlElement(b, t); + b._lexer._switchTo(HtmlLexer::RAWTEXT); + b._originalInsertionMode = b._insertionMode; + b._switchTo(HtmlParser::Mode::TEXT); +} + +static void parseRcDataElement(HtmlParser &b, HtmlToken const &t) { + insertHtmlElement(b, t); + b._lexer._switchTo(HtmlLexer::RCDATA); + b._originalInsertionMode = b._insertionMode; + b._switchTo(HtmlParser::Mode::TEXT); +} + +// 13.2.6.3 MARK: Closing elements that have implied end tags +// https://html.spec.whatwg.org/multipage/parsing.html#generate-implied-end-tags +static constexpr Array IMPLIED_END_TAGS = { + Html::DD, Html::DT, Html::LI, Html::OPTION, Html::OPTGROUP, Html::P, Html::RB, Html::RP, Html::RT, Html::RTC +}; + +static void generateImpliedEndTags(HtmlParser &b, Str except = ""s) { + while (contains(IMPLIED_END_TAGS, last(b._openElements)->tagName) and + last(b._openElements)->tagName.name() != except) { + b._openElements.popBack(); + } +} + +// 13.2.6.4 MARK: The rules for parsing tokens in HTML content + +// 13.2.6.4.1 MARK: The "initial" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#the-initial-insertion-mode + +static QuirkMode _whichQuirkMode(HtmlToken const &) { + // NOSPEC: We assume no quirk mode + return QuirkMode::NO; +} + +void HtmlParser::_handleInitialMode(HtmlToken const &t) { + // A character token that is one of U+0009 CHARACTER TABULATION, + // U+000A LINE FEED (LF), U+000C FORM FEED (FF), + // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + if (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or + t.rune == '\n' or + t.rune == '\f' or + t.rune == ' ')) { + // ignore + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + _document->appendChild(makeStrong(t.data)); + } + + // A DOCTYPE token + else if (t.type == HtmlToken::DOCTYPE) { + _document->appendChild(makeStrong( + t.name, + t.publicIdent, + t.systemIdent + )); + _document->quirkMode = _whichQuirkMode(t); + _switchTo(Mode::BEFORE_HTML); + } + + // Anything else + else { + _raise(); + _switchTo(Mode::BEFORE_HTML); + accept(t); + } +} + +// 13.2.6.4.2 MARK: The "before html" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#the-before-html-insertion-mode +void HtmlParser::_handleBeforeHtml(HtmlToken const &t) { + // A DOCTYPE token + if (t.type == HtmlToken::DOCTYPE) { + // ignore + _raise(); + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + _document->appendChild(makeStrong(t.data)); + } + + // A character token that is one of U+0009 CHARACTER TABULATION, + // U+000A LINE FEED (LF), U+000C FORM FEED (FF), + // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + if (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or + t.rune == '\n' or + t.rune == '\f' or + t.rune == ' ')) { + // ignore + } + + // A start tag whose tag name is "html" + else if (t.type == HtmlToken::START_TAG and t.name == "html") { + auto el = createElementFor(t, Vaev::HTML); + _document->appendChild(el); + _openElements.pushBack(el); + _switchTo(Mode::BEFORE_HEAD); + } + + // Any other end tag + else if (t.type == HtmlToken::END_TAG and not(t.name == "head" or t.name == "body" or t.name == "html" or t.name == "br")) { + // ignore + _raise(); + } + + // An end tag whose tag name is one of: "head", "body", "html", "br" + // Anything else + else { + auto el = makeStrong(Html::HTML); + _document->appendChild(el); + _openElements.pushBack(el); + _switchTo(Mode::BEFORE_HEAD); + accept(t); + } +} + +// 13.2.6.4.3 MARK: The "before head" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#the-before-head-insertion-mode +void HtmlParser::_handleBeforeHead(HtmlToken const &t) { + // A character token that is one of U+0009 CHARACTER TABULATION, + // U+000A LINE FEED (LF), U+000C FORM FEED (FF), + // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + if (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or + t.rune == '\n' or + t.rune == '\f' or + t.rune == ' ')) { + // Ignore the token. + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + // Insert a comment. + insertAComment(*this, t); + } + + // A comment token + else if (t.type == HtmlToken::DOCTYPE) { + // Parse error. Ignore the token. + _raise(); + } + + // A start tag whose tag name is "html" + else if (t.type == HtmlToken::START_TAG and t.name == "html") { + // Process the token using the rules for the "in body" insertion mode. + _acceptIn(Mode::IN_BODY, t); + } + + // A start tag whose tag name is "head" + else if (t.type == HtmlToken::START_TAG and t.name == "head") { + _headElement = insertHtmlElement(*this, t); + _switchTo(Mode::IN_HEAD); + } + + // Anything else + else if (t.type == HtmlToken::END_TAG and not(t.name == "head" or t.name == "body" or t.name == "html" or t.name == "br")) { + // ignore + _raise(); + } + + // An end tag whose tag name is one of: "head", "body", "html", "br" + // Anything else + else { + HtmlToken headToken; + headToken.type = HtmlToken::START_TAG; + headToken.name = String{"head"}; + _headElement = insertHtmlElement(*this, headToken); + _switchTo(Mode::IN_HEAD); + accept(t); + } +} + +// 13.2.6.4.4 MARK: The "in head" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead +void HtmlParser::_handleInHead(HtmlToken const &t) { + auto anythingElse = [&] { + _openElements.popBack(); + _switchTo(Mode::AFTER_HEAD); + accept(t); + }; + + // A character token that is one of U+0009 CHARACTER TABULATION, + // U+000A LINE FEED (LF), U+000C FORM FEED (FF), + // U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + if (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or + t.rune == '\n' or + t.rune == '\f' or + t.rune == ' ')) { + insertACharacter(*this, t); + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + insertAComment(*this, t); + } + + // A DOCTYPE token + else if (t.type == HtmlToken::DOCTYPE) { + _raise(); + } + + // A start tag whose tag name is "html" + else if (t.type == HtmlToken::START_TAG and (t.name == "html")) { + _acceptIn(Mode::IN_BODY, t); + } + + // A start tag whose tag name is one of: "base", "basefont", "bgsound", "link" + else if (t.type == HtmlToken::START_TAG and (t.name == "base" or t.name == "basefont" or t.name == "bgsound" or t.name == "link")) { + insertHtmlElement(*this, t); + _openElements.popBack(); + // TODO: Acknowledge the token's self-closing flag, if it is set. + } + + // A start tag whose tag name is "meta" + else if (t.type == HtmlToken::START_TAG and (t.name == "meta")) { + insertHtmlElement(*this, t); + _openElements.popBack(); + // TODO: Acknowledge the token's self-closing flag, if it is set. + + // TODO: Handle handle speculative parsing + } + + // A start tag whose tag name is "title" + else if (t.type == HtmlToken::START_TAG and (t.name == "title")) { + parseRcDataElement(*this, t); + } + + // A start tag whose tag name is "noscript", if the scripting flag is enabled + // A start tag whose tag name is one of: "noframes", "style" + else if (t.type == HtmlToken::START_TAG and ((t.name == "noscript" and _scriptingEnabled) or t.name == "noframe" or t.name == "style")) { + parseRawTextElement(*this, t); + } + + // A start tag whose tag name is "noscript", if the scripting flag is disabled + else if (t.type == HtmlToken::START_TAG and (t.name == "noscript" and not _scriptingEnabled)) { + insertHtmlElement(*this, t); + _switchTo(Mode::IN_HEAD_NOSCRIPT); + } + + // A start tag whose tag name is "script" + else if (t.type == HtmlToken::START_TAG and (t.name == "script")) { + // 1. Let the adjusted insertion location be the appropriate place for inserting a node. + auto localtion = apropriatePlaceForInsertingANode(*this); + + // 2. Create an element for the token in the HTML namespace, with + // the intended parent being the element in which the adjusted + // insertion location finds itself. + auto el = createElementFor(t, Vaev::HTML); + + // 3. Set the element's parser document to the Document, and set + // the element's force async to false. + + // NOSPEC: We don't support async scripts + + // NOTE: This ensures that, if the script is external, + // any document.write() calls in the script will execute + // in-line, instead of blowing the document away, as would + // happen in most other cases. It also prevents the script + // from executing until the end tag is seen. + + // 4. If the parser was created as part of the HTML fragment + // parsing algorithm, then set the script element's already + // started to true. (fragment case) + + // NOSPEC: We don't support fragments + + // 5. If the parser was invoked via the document.write() or + // document.writeln() methods, then optionally set the script + // element's already started to true. (For example, the user + // agent might use this clause to prevent execution of + // cross-origin scripts inserted via document.write() under + // slow network conditions, or when the page has already taken + // a long time to load.) + + // NOSPEC: We don't support document.write() + + // 6. Insert the newly created element at the adjusted insertion location. + localtion.insert(el); + + // 7. Push the element onto the stack of open elements so that it is the new current node. + _openElements.pushBack(el); + + // 8. Switch the tokenizer to the script data state. + _lexer._switchTo(HtmlLexer::SCRIPT_DATA); + + // 9. Let the original insertion mode be the current insertion mode. + _originalInsertionMode = _insertionMode; + + // 10. Switch the insertion mode to "text". + _switchTo(Mode::TEXT); + } else if (t.type == HtmlToken::END_TAG and (t.name == "head")) { + _openElements.popBack(); + _switchTo(Mode::AFTER_HEAD); + } else if (t.type == HtmlToken::END_TAG and (t.name == "body" or t.name == "html" or t.name == "br")) { + anythingElse(); + } else if (t.type == HtmlToken::START_TAG and (t.name == "template")) { + // NOSPEC: We don't support templates + } else if (t.type == HtmlToken::END_TAG and (t.name == "template")) { + // NOSPEC: We don't support templates + } else if ((t.type == HtmlToken::START_TAG and (t.name == "head")) or t.type == HtmlToken::END_TAG) { + // ignore + _raise(); + } else { + anythingElse(); + } +} + +// 13.2.6.4.5 MARK: The "in head noscript" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inheadnoscript +void HtmlParser::_handleInHeadNoScript(HtmlToken const &t) { + auto anythingElse = [&] { + _raise(); + _openElements.popBack(); + _switchTo(Mode::IN_HEAD); + accept(t); + }; + + // A DOCTYPE token + if (t.type == HtmlToken::DOCTYPE) { + _raise(); + } + + // A start tag whose tag name is "html" + else if (t.type == HtmlToken::START_TAG and (t.name == "html")) { + _acceptIn(Mode::IN_BODY, t); + } + + // An end tag whose tag name is "noscript" + else if (t.type == HtmlToken::END_TAG and (t.name == "noscript")) { + _openElements.popBack(); + _switchTo(Mode::IN_HEAD); + } + + // A character token that is one of + // - U+0009 CHARACTER TABULATION, + // - U+000A LINE FEED (LF), + // - U+000C FORM FEED (FF), + // - U+000D CARRIAGE RETURN (CR), + // - U+0020 SPACE + // A comment token + // A start tag whose tag name is one of: "basefont", "bgsound", "link", "meta", "noframes", "style" + else if ( + (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == ' ')) or + t.type == HtmlToken::COMMENT or + (t.type == HtmlToken::START_TAG and + (t.name == "basefont" or t.name == "bgsound" or t.name == "link" or t.name == "meta" or t.name == "noframes" or t.name == "style")) + ) { + _acceptIn(Mode::IN_HEAD, t); + } + + // An end tag whose tag name is "br" + else if (t.type == HtmlToken::END_TAG and (t.name == "br")) { + anythingElse(); + } + + // A start tag whose tag name is one of: "head", "noscript" + // Any other end tag + else if ( + (t.type == HtmlToken::START_TAG and (t.name == "head" or t.name == "noscript")) or + t.type == HtmlToken::END_TAG + ) { + // ignore + _raise(); + } + + // Anything else + else { + anythingElse(); + } +} + +// 13.2.6.4.6 MARK: The "after head" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#the-after-head-insertion-mode +void HtmlParser::_handleAfterHead(HtmlToken const &t) { + auto anythingElse = [&] { + HtmlToken bodyToken; + bodyToken.type = HtmlToken::START_TAG; + bodyToken.name = String{"body"}; + insertHtmlElement(*this, bodyToken); + _switchTo(Mode::IN_BODY); + accept(t); + }; + + // A character token that is one of + // - U+0009 CHARACTER TABULATION, + // - U+000A LINE FEED (LF), + // - U+000C FORM FEED (FF), + // - U+000D CARRIAGE RETURN (CR) + // - U+0020 SPACE + if (t.type == HtmlToken::CHARACTER and + (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == '\r' or t.rune == ' ')) { + insertACharacter(*this, t.rune); + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + insertAComment(*this, t); + } + + // A DOCTYPE token + else if (t.type == HtmlToken::DOCTYPE) { + _raise(); + } + + // A start tag whose tag name is "html" + else if (t.type == HtmlToken::START_TAG and (t.name == "html")) { + _acceptIn(Mode::IN_BODY, t); + } + + // A start tag whose tag name is "body" + else if (t.type == HtmlToken::START_TAG and (t.name == "body")) { + insertHtmlElement(*this, t); + _framesetOk = false; + _switchTo(Mode::IN_BODY); + } + + // A start tag whose tag name is "frameset" + else if (t.type == HtmlToken::START_TAG and (t.name == "frameset")) { + insertHtmlElement(*this, t); + _switchTo(Mode::IN_FRAMESET); + } + + // A start tag whose tag name is one of: + // "base", "basefont", "bgsound", "link", "meta", + // "noframes", "script", "style", "template", "title" + else if ( + t.type == HtmlToken::START_TAG and + (t.name == "base" or t.name == "basefont" or + t.name == "bgsound" or t.name == "link" or + t.name == "meta" or t.name == "noframes" or + t.name == "script" or t.name == "style" or + t.name == "template" or t.name == "title") + ) { + _raise(); + _openElements.pushBack(*_headElement); + _acceptIn(Mode::IN_HEAD, t); + _openElements.removeAll(*_headElement); + } + + // An end tag whose tag name is "template" + else if (t.type == HtmlToken::END_TAG and (t.name == "template")) { + _acceptIn(Mode::IN_HEAD, t); + } + + // An end tag whose tag name is one of: "body", "html", "br" + else if (t.type == HtmlToken::END_TAG and (t.name == "body" or t.name == "html" or t.name == "br")) { + anythingElse(); + } + + // A start tag whose tag name is "head" + else if (t.type == HtmlToken::END_TAG or (t.type == HtmlToken::START_TAG and t.name == "head")) { + // ignore + _raise(); + } + + // Anything else + else { + anythingElse(); + } +} + +// 13.2.6.4.7 MARK: The "in body" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody +void HtmlParser::_handleInBody(HtmlToken const &t) { + // A character token that is U+0000 NULL + if (t.type == HtmlToken::CHARACTER and t.rune == '\0') { + _raise(); + } + + // A character token that is one of + // - U+0009 CHARACTER TABULATION + // - U+000A LINE FEED (LF) + // - U+000C FORM FEED (FF) + // - U+000D CARRIAGE RETURN (CR) + // - U+0020 SPACE + else if (t.type == HtmlToken::CHARACTER and (t.rune == '\t' or t.rune == '\n' or t.rune == '\f' or t.rune == '\r' or t.rune == ' ')) { + reconstructActiveFormattingElements(*this); + insertACharacter(*this, t); + } + + // Any other character token + else if (t.type == HtmlToken::CHARACTER) { + reconstructActiveFormattingElements(*this); + insertACharacter(*this, t); + _framesetOk = false; + } + + // A comment token + else if (t.type == HtmlToken::COMMENT) { + insertAComment(*this, t); + } + + // A DOCTYPE token + else if (t.type == HtmlToken::DOCTYPE) { + _raise(); + } + + // TODO: A start tag whose tag name is "html" + + // TODO: A start tag whose tag name is one of: "base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "template", "title" + // An end tag whose tag name is "template" + + // TODO: A start tag whose tag name is "body" + + // TODO: A start tag whose tag name is "frameset" + + // TODO: An end-of-file token + + // TODO: An end tag whose tag name is "body" + + // TODO: An end tag whose tag name is "html" + + // TODO: A start tag whose tag name is one of: + // "address", "article", "aside", "blockquote", "center", + // "details", "dialog", "dir", "div", "dl", "fieldset", + // "figcaption", "figure", "footer", "header", "hgroup", + // "main", "menu", "nav", "ol", "p", "search", "section", + // "summary", "ul" + + // TODO: A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + + // TODO: A start tag whose tag name is one of: "pre", "listing" + + // TODO: A start tag whose tag name is "form" + + // TODO: A start tag whose tag name is "li" + + // TODO: A start tag whose tag name is one of: "dd", "dt" + + // TODO: A start tag whose tag name is "plaintext" + + // TODO: A start tag whose tag name is "button" + + // TODO: An end tag whose tag name is one of: "address", "article", "aside", "blockquote", "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "main", "menu", "nav", "ol", "pre", "search", "section", "summary", "ul" + + // TODO: An end tag whose tag name is "form" + + // TODO: An end tag whose tag name is "p" + + // TODO: An end tag whose tag name is "li" + + // TODO: An end tag whose tag name is one of: "dd", "dt" + + // TODO: An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + + // TODO: An end tag whose tag name is "sarcasm" + // This state machine is not equipped to handle sarcasm + // So we just ignore it + + // TODO: A start tag whose tag name is "a" + + // TODO: A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u" + + // TODO: A start tag whose tag name is "nobr" + + // TODO: An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" + + // TODO: A start tag whose tag name is one of: "applet", "marquee", "object" + + // TODO: An end tag token whose tag name is one of: "applet", "marquee", "object" + + // TODO: A start tag whose tag name is "table" + + // TODO: An end tag whose tag name is "br" + + // TODO: A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr" + + // TODO: A start tag whose tag name is "input" + + // TODO: A start tag whose tag name is one of: "param", "source", "track" + + // TODO: A start tag whose tag name is "hr" + + // TODO: A start tag whose tag name is "image" + + // TODO: A start tag whose tag name is "textarea" + + // TODO: A start tag whose tag name is "xmp" + + // TODO: A start tag whose tag name is "iframe" + + // TODO: A start tag whose tag name is "noembed" + // A start tag whose tag name is "noscript", if the scripting flag is enabled + + // TODO: A start tag whose tag name is "select" + + // TODO: A start tag whose tag name is one of: "optgroup", "option" + + // TODO: A start tag whose tag name is one of: "rb", "rtc" + + // TODO: A start tag whose tag name is one of: "rp", "rt" + + // TODO: A start tag whose tag name is "math" + + // TODO: A start tag whose tag name is "svg" + + // TODO: A start tag whose tag name is one of: "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr" + + else if (t.type == HtmlToken::START_TAG) { + reconstructActiveFormattingElements(*this); + insertHtmlElement(*this, t); + } + + // TODO: Any other end tag + else if (t.type == HtmlToken::END_TAG) { + // loop: + while (true) { + // 1. Initialize node to be the current node (the bottommost node of the stack). + auto node = last(_openElements); + + // 2. Loop: If node is an HTML element with the same tag name as the token, then: + if (node->tagName.name() == t.name) { + // 1. Generate implied end tags, except for HTML elements with the same tag name as the token. + generateImpliedEndTags(*this, t.name); + + // 2. If node is not the current node, then this is a parse error. + if (node != last(_openElements)) { + _raise(); + } + + // 3. Pop all the nodes from the current node up to node, including node, then stop these steps + while (last(_openElements) != node) { + _openElements.popBack(); + } + _openElements.popBack(); + break; + } + + // 3. Otherwise, if node is in the special category, + // then this is a parse error; ignore the token, and return. + + // 4. Set node to the previous entry in the stack of open elements. + + // 5. Return to the step labeled loop. + } + } +} + +// 13.2.6.4.8 MARK: The "text" insertion mode +// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incdata +void HtmlParser::_handleText(HtmlToken const &t) { + // A character token + if (t.type == HtmlToken::CHARACTER) { + insertACharacter( + *this, + t.rune == '\0' + ? 0xFFFD + : t.rune + ); + } + + else if (t.type == HtmlToken::END_OF_FILE) { + _raise(); + + // TODO: If the current node is a script element, then set its already started to true. + + _openElements.popBack(); + _switchTo(_originalInsertionMode); + } + + // An end tag whose tag name is "script" + // else if (t.type == Token::END_TAG and t.name == "script") { + // } + // NOSPEC: We handle script end tags like any other end tag + + // Any other end tag + else if (t.type == HtmlToken::END_TAG) { + this->_openElements.popBack(); + _switchTo(_originalInsertionMode); + } + + // FIXME: Implement the rest of the rules +} + +void HtmlParser::_switchTo(Mode mode) { + _insertionMode = mode; +} + +void HtmlParser::_acceptIn(Mode mode, HtmlToken const &t) { + logDebug("Parsing {} in {}", t, mode); + + switch (mode) { + + case Mode::INITIAL: + _handleInitialMode(t); + break; + + case Mode::BEFORE_HTML: + _handleBeforeHtml(t); + break; + + case Mode::BEFORE_HEAD: + _handleBeforeHead(t); + break; + + case Mode::IN_HEAD: + _handleInHead(t); + break; + + case Mode::IN_HEAD_NOSCRIPT: + _handleInHeadNoScript(t); + break; + + case Mode::AFTER_HEAD: + _handleAfterHead(t); + break; + + case Mode::IN_BODY: + _handleInBody(t); + break; + + case Mode::TEXT: + _handleText(t); + break; + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intable + case Mode::IN_TABLE: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intabletext + case Mode::IN_TABLE_TEXT: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incaption + case Mode::IN_CAPTION: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incolumngroup + case Mode::IN_COLUMN_GROUP: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intablebody + case Mode::IN_TABLE_BODY: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inrow + case Mode::IN_ROW: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-incell + case Mode::IN_CELL: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect + case Mode::IN_SELECT: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselectintable + case Mode::IN_SELECT_IN_TABLE: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-intemplate + case Mode::IN_TEMPLATE: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-body-insertion-mode + case Mode::AFTER_BODY: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-in-frameset-insertion-mode + case Mode::IN_FRAMESET: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-frameset-insertion-mode + case Mode::AFTER_FRAMESET: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-after-body-insertion-mode + case Mode::AFTER_AFTER_BODY: { + break; + } + + // TODO: https://html.spec.whatwg.org/multipage/parsing.html#the-after-after-frameset-insertion-mode + case Mode::AFTER_AFTER_FRAMESET: { + break; + } + + default: + break; + } +} + +void HtmlParser::accept(HtmlToken const &t) { + _acceptIn(_insertionMode, t); +} + +} // namespace Vaev::Markup diff --git a/src/web/vaev-markup/html.h b/src/web/vaev-markup/html.h new file mode 100644 index 0000000000..734ae7cb15 --- /dev/null +++ b/src/web/vaev-markup/html.h @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "dom.h" + +namespace Vaev::Markup { + +// MARK: Lexer ----------------------------------------------------------------- + +#define FOREACH_TOKEN(TOKEN) \ + TOKEN(NIL) \ + TOKEN(DOCTYPE) \ + TOKEN(START_TAG) \ + TOKEN(END_TAG) \ + TOKEN(COMMENT) \ + TOKEN(CHARACTER) \ + TOKEN(END_OF_FILE) + +struct HtmlToken { + enum Type { + +#define ITER(NAME) NAME, + FOREACH_TOKEN(ITER) +#undef ITER + + _LEN, + }; + + struct Attr { + String name{}; + String value{}; + }; + + Type type = NIL; + String name; + Rune rune = '\0'; + String data; + String publicIdent; + String systemIdent; + Vec attrs; + bool forceQuirks{false}; + bool selfClosing{false}; + + void repr(Io::Emit &e) const { + e("type={}", type); + if (name) + e(" name={}", name); + if (rune) + e(" rune={#c}", rune); + if (data) + e(" data={}", data); + if (publicIdent) + e(" publicIdent={}", publicIdent); + if (systemIdent) + e(" systemIdent={}", systemIdent); + if (attrs.len() > 0) { + e.indentNewline(); + for (auto &attr : attrs) { + e(" attr:{}={}", attr.name, attr.value); + } + e.deindent(); + } + if (forceQuirks) + e(" forceQuirks"); + if (selfClosing) + e(" selfClosing"); + } +}; + +struct HtmlSink { + virtual ~HtmlSink() = default; + virtual void accept(HtmlToken const &token) = 0; +}; + +struct HtmlLexer { + enum struct State { +#define STATE(NAME) NAME, +#include "defs/ns-html-states.inc" +#undef STATE + + _LEN, + }; + + using enum State; + + State _state = State::DATA; + State _returnState = State::NIL; + + Opt _token; + Opt _last; + HtmlSink *_sink = nullptr; + + Rune _currChar = 0; + StringBuilder _builder; + StringBuilder _temp; + + HtmlToken &_begin(HtmlToken::Type type) { + _token = HtmlToken{}; + _token->type = type; + return *_token; + } + + HtmlToken &_ensure() { + if (not _token) + panic("unexpected-token"); + return *_token; + } + + HtmlToken &_ensure(HtmlToken::Type type) { + auto &token = _ensure(); + if (token.type != type) + panic("unexpected-token"); + return token; + } + + void _emit() { + if (not _sink) + panic("no sink"); + _sink->accept(_ensure()); + _last = std::move(_token); + } + + void _emit(Rune rune) { + _begin(HtmlToken::CHARACTER).rune = rune; + _emit(); + } + + void _beginAttribute() { + _ensure().attrs.emplaceBack(); + } + + HtmlToken::Attr &_lastAttr() { + auto &token = _ensure(); + if (token.attrs.len() == 0) + panic("_beginAttribute miss match"); + return last(token.attrs); + } + + void _reconsumeIn(State state, Rune rune) { + _switchTo(state); + consume(rune); + } + + void _switchTo(State state) { + _state = state; + } + + void _raise(Str msg); + + bool _isAppropriateEndTagToken() { + if (not _last or not _token) + return false; + return _last->name == _token->name; + } + + void _flushCodePointsConsumedAsACharacterReference() { + debug("flushing code points consumed as a character reference"); + } + + void bind(HtmlSink &sink) { + if (_sink) + panic("sink already bound"); + _sink = &sink; + } + + void consume(Rune rune, bool isEof = false); +}; + +#undef FOREACH_TOKEN + +// MARK: Parser ---------------------------------------------------------------- + +#define FOREACH_INSERTION_MODE(MODE) \ + MODE(INITIAL) \ + MODE(BEFORE_HTML) \ + MODE(BEFORE_HEAD) \ + MODE(IN_HEAD) \ + MODE(IN_HEAD_NOSCRIPT) \ + MODE(AFTER_HEAD) \ + MODE(IN_BODY) \ + MODE(TEXT) \ + MODE(IN_TABLE) \ + MODE(IN_TABLE_TEXT) \ + MODE(IN_CAPTION) \ + MODE(IN_COLUMN_GROUP) \ + MODE(IN_TABLE_BODY) \ + MODE(IN_ROW) \ + MODE(IN_CELL) \ + MODE(IN_SELECT) \ + MODE(IN_SELECT_IN_TABLE) \ + MODE(IN_TEMPLATE) \ + MODE(AFTER_BODY) \ + MODE(IN_FRAMESET) \ + MODE(AFTER_FRAMESET) \ + MODE(AFTER_AFTER_BODY) \ + MODE(AFTER_AFTER_FRAMESET) + +struct HtmlParser : public HtmlSink { + enum struct Mode { +#define ITER(NAME) NAME, + FOREACH_INSERTION_MODE(ITER) +#undef ITER + + _LEN, + }; + + bool _scriptingEnabled = false; + bool _framesetOk = true; + + Mode _insertionMode = Mode::INITIAL; + Mode _originalInsertionMode = Mode::INITIAL; + + HtmlLexer _lexer; + Strong _document; + Vec> _openElements; + Opt> _headElement; + Opt> _formElement; + + HtmlParser(Strong document) + : _document(document) { + _lexer.bind(*this); + } + + void _raise(Str msg = "parse-error"); + + void _handleInitialMode(HtmlToken const &t); + + void _handleBeforeHtml(HtmlToken const &t); + + void _handleBeforeHead(HtmlToken const &t); + + void _handleInHead(HtmlToken const &t); + + void _handleInHeadNoScript(HtmlToken const &t); + + void _handleAfterHead(HtmlToken const &t); + + void _handleInBody(HtmlToken const &t); + + void _handleText(HtmlToken const &t); + + void _switchTo(Mode mode); + + void _acceptIn(Mode mode, HtmlToken const &t); + + void accept(HtmlToken const &t) override; + + void write(Str str) { + for (auto r : iterRunes(str)) + _lexer.consume(r); + } +}; + +#undef FOREACH_INSERTION_MODE + +} // namespace Vaev::Markup diff --git a/src/web/vaev-markup/manifest.json b/src/web/vaev-markup/manifest.json new file mode 100644 index 0000000000..616c112c3c --- /dev/null +++ b/src/web/vaev-markup/manifest.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", + "id": "vaev-markup", + "type": "lib", + "description": "Vaev's markup engine", + "requires": [ + "karm-io", + "karm-logger" + ] +} diff --git a/src/web/vaev-markup/markup.h b/src/web/vaev-markup/markup.h new file mode 100644 index 0000000000..2725782877 --- /dev/null +++ b/src/web/vaev-markup/markup.h @@ -0,0 +1,5 @@ +#pragma once + +namespace Vaev::Markupo { + +} // namespace Vaev::Markupo diff --git a/src/web/vaev-markup/tags.cpp b/src/web/vaev-markup/tags.cpp new file mode 100644 index 0000000000..6a4ec89fda --- /dev/null +++ b/src/web/vaev-markup/tags.cpp @@ -0,0 +1,148 @@ +#include "tags.h" + +namespace Vaev::Html { + +Str _tagName(TagId id) { + switch (id) { +#define TAG(IDENT, NAME) \ + case TagId::IDENT: \ + return #NAME; +#include "defs/ns-html-tag-names.inc" +#undef TAG + default: + return "unknown"; + } +} + +Str _attrName(AttrId id) { + switch (id) { +#define ATTR(IDENT, NAME) \ + case AttrId::IDENT: \ + return #NAME; +#include "defs/ns-html-attr-names.inc" +#undef ATTR + default: + return "unknown"; + } +} + +Opt _tagId(Str name) { +#define TAG(IDENT, NAME) \ + if (name == #NAME) \ + return TagId::IDENT; +#include "defs/ns-html-tag-names.inc" +#undef TAG + + return NONE; +} + +Opt _attrId(Str name) { +#define ATTR(IDENT, NAME) \ + if (name == #NAME) \ + return AttrId::IDENT; + +#include "defs/ns-html-attr-names.inc" +#undef ATTR + + return NONE; +} + +} // namespace Vaev::Html + +namespace Vaev::MathMl { + +Str _tagName(TagId id) { + switch (id) { +#define TAG(IDENT, NAME) \ + case TagId::IDENT: \ + return #NAME; +#include "defs/ns-mathml-tag-names.inc" +#undef TAG + default: + return "unknown"; + } +} + +Str _attrName(AttrId id) { + switch (id) { +#define ATTR(IDENT, NAME) \ + case AttrId::IDENT: \ + return #NAME; +#include "defs/ns-mathml-attr-names.inc" +#undef ATTR + default: + return "unknown"; + } +} + +Opt _tagId(Str name) { +#define TAG(IDENT, NAME) \ + if (name == #NAME) \ + return TagId::IDENT; +#include "defs/ns-mathml-tag-names.inc" +#undef TAG + + return NONE; +} + +Opt _attrId(Str name) { +#define ATTR(IDENT, NAME) \ + if (name == #NAME) \ + return AttrId::IDENT; + +#include "defs/ns-mathml-attr-names.inc" +#undef ATTR + + return NONE; +} + +} // namespace Vaev::MathMl + +namespace Vaev::Svg { + +Str _tagName(TagId id) { + switch (id) { +#define TAG(IDENT, NAME) \ + case TagId::IDENT: \ + return #NAME; +#include "defs/ns-svg-tag-names.inc" +#undef TAG + default: + return "unknown"; + } +} + +Str _attrName(AttrId id) { + switch (id) { +#define ATTR(IDENT, NAME) \ + case AttrId::IDENT: \ + return #NAME; +#include "defs/ns-svg-attr-names.inc" +#undef ATTR + default: + return "unknown"; + } +} + +Opt _tagId(Str name) { +#define TAG(IDENT, NAME) \ + if (name == #NAME) \ + return TagId::IDENT; +#include "defs/ns-svg-tag-names.inc" +#undef TAG + + return NONE; +} + +Opt _attrId(Str name) { +#define ATTR(IDENT, NAME) \ + if (name == #NAME) \ + return AttrId::IDENT; + +#include "defs/ns-svg-attr-names.inc" +#undef ATTR + + return NONE; +} + +} // namespace Vaev::Svg diff --git a/src/web/vaev-base/tags.h b/src/web/vaev-markup/tags.h similarity index 76% rename from src/web/vaev-base/tags.h rename to src/web/vaev-markup/tags.h index ade18d2ab8..c750c2c58d 100644 --- a/src/web/vaev-base/tags.h +++ b/src/web/vaev-markup/tags.h @@ -1,15 +1,23 @@ #pragma once #include -#include +#include namespace Vaev { namespace Html { -enum struct TagId : u16; +enum struct TagId : u16 { +#define TAG(IDENT, _) IDENT, +#include "defs/ns-html-tag-names.inc" +#undef TAG +}; -enum struct AttrId : u16; +enum struct AttrId : u16 { +#define ATTR(IDENT, _) IDENT, +#include "defs/ns-html-attr-names.inc" +#undef ATTR +}; Str _tagName(TagId id); @@ -23,9 +31,17 @@ Opt _attrId(Str name); namespace Svg { -enum struct TagId : u16; +enum struct TagId : u16 { +#define TAG(IDENT, _) IDENT, +#include "defs/ns-svg-tag-names.inc" +#undef TAG +}; -enum struct AttrId : u16; +enum struct AttrId : u16 { +#define ATTR(IDENT, _) IDENT, +#include "defs/ns-svg-attr-names.inc" +#undef ATTR +}; Str _tagName(TagId id); @@ -39,9 +55,17 @@ Opt _attrId(Str name); namespace MathMl { -enum struct TagId : u16; +enum struct TagId : u16 { +#define TAG(IDENT, _) IDENT, +#include "defs/ns-mathml-tag-names.inc" +#undef TAG +}; -enum struct AttrId : u16; +enum struct AttrId : u16 { +#define ATTR(IDENT, _) IDENT, +#include "defs/ns-mathml-attr-names.inc" +#undef ATTR +}; Str _tagName(TagId id); @@ -98,6 +122,10 @@ struct Ns { constexpr bool operator==(Ns const &other) const { return _id == other._id; } + + void repr(Io::Emit &e) const { + e("{}", name()); + } }; #define NAMESPACE(IDENT, URL) \ @@ -163,6 +191,10 @@ struct TagName { } constexpr bool operator==(TagName const &other) const = default; + + void repr(Io::Emit &e) const { + e("{}", name()); + } }; struct AttrName { @@ -223,27 +255,42 @@ struct AttrName { } constexpr bool operator==(AttrName const &other) const = default; -}; - -} // namespace Vaev -template <> -struct Karm::Io::Formatter { - Res format(Io::TextWriter &writer, Vaev::Ns const &val) { - return writer.writeStr(val.name()); + void repr(Io::Emit &e) const { + e("{}", name()); } }; -template <> -struct Karm::Io::Formatter { - Res format(Io::TextWriter &writer, Vaev::TagName const &val) { - return Io::format(writer, "{}:{}", val.ns, val.name()); - } -}; +namespace Html { -template <> -struct Karm::Io::Formatter { - Res format(Io::TextWriter &writer, Vaev::AttrName const &val) { - return Io::format(writer, "{}:{}", val.ns, val.name()); - } -}; +#define TAG(IDENT, _) \ + inline constexpr TagName IDENT = TagId::IDENT; +#include "defs/ns-html-tag-names.inc" +#undef TAG + +#define ATTR(IDENT, _) \ + inline constexpr AttrName IDENT##_ATTR = AttrId::IDENT; +#include "defs/ns-html-attr-names.inc" +#undef ATTR + +} // namespace Html + +namespace MathMl { + +#define TAG(IDENT, _) \ + inline constexpr TagName IDENT = TagId::IDENT; +#include "defs/ns-mathml-tag-names.inc" +#undef TAG + +} // namespace MathMl + +namespace Svg { + +#define TAG(IDENT, _) \ + inline constexpr TagName IDENT = TagId::IDENT; +#include "defs/ns-svg-tag-names.inc" +#undef TAG + +} // namespace Svg + +} // namespace Vaev diff --git a/src/web/vaev-xml/tests/manifest.json b/src/web/vaev-markup/tests/manifest.json similarity index 100% rename from src/web/vaev-xml/tests/manifest.json rename to src/web/vaev-markup/tests/manifest.json diff --git a/src/web/vaev-xml/tests/test-parser.cpp b/src/web/vaev-markup/tests/test-xml-parser.cpp similarity index 53% rename from src/web/vaev-xml/tests/test-parser.cpp rename to src/web/vaev-markup/tests/test-xml-parser.cpp index 9eb98989a8..3b9e8c0143 100644 --- a/src/web/vaev-xml/tests/test-parser.cpp +++ b/src/web/vaev-markup/tests/test-xml-parser.cpp @@ -1,40 +1,39 @@ #include -#include -#include +#include -namespace Vaev::Xml::Tests { +namespace Vaev::Markup::Tests { test$("parse-empty-document") { auto s = Io::SScan(""s); - auto p = Parser(); + XmlParser p{}; expect$(not p.parse(s, Vaev::HTML)); // An empty document is invalid return Ok(); } test$("parse-open-close-tag") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); expect$(doc->hasChildren()); auto first = doc->firstChild(); - expect$(first->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(first.cast())->tagName == Html::HTML); + expect$(first->nodeType() == NodeType::ELEMENT); + expect$(try$(first.cast())->tagName == Html::HTML); return Ok(); } test$("parse-empty-tag") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; try$(p.parse(s, Vaev::HTML)); return Ok(); } test$("parse-attr") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasAttribute(Html::LANG_ATTR)); expect$(el->getAttribute(Html::LANG_ATTR) == "en"); return Ok(); @@ -42,128 +41,128 @@ test$("parse-attr") { test$("parse-text") { auto s = Io::SScan("text"); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto text = el->firstChild(); - expect$(text->nodeType() == Dom::NodeType::TEXT); - expect$(try$(text.cast())->data == "text"); + expect$(text->nodeType() == NodeType::TEXT); + expect$(try$(text.cast())->data == "text"); return Ok(); } test$("parse-text-before-tag") { auto s = Io::SScan("text
"); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto text = el->firstChild(); - expect$(text->nodeType() == Dom::NodeType::TEXT); - expect$(try$(text.cast())->data == "text"); + expect$(text->nodeType() == NodeType::TEXT); + expect$(try$(text.cast())->data == "text"); auto div = text->nextSibling(); - expect$(div->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(div.cast())->tagName == Html::DIV); + expect$(div->nodeType() == NodeType::ELEMENT); + expect$(try$(div.cast())->tagName == Html::DIV); return Ok(); } test$("parse-text-after-tag") { auto s = Io::SScan("
text"); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto div = el->firstChild(); - expect$(div->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(div.cast())->tagName == Html::DIV); + expect$(div->nodeType() == NodeType::ELEMENT); + expect$(try$(div.cast())->tagName == Html::DIV); auto text = div->nextSibling(); - expect$(text->nodeType() == Dom::NodeType::TEXT); - expect$(try$(text.cast())->data == "text"); + expect$(text->nodeType() == NodeType::TEXT); + expect$(try$(text.cast())->data == "text"); return Ok(); } test$("parse-text-between-tags") { auto s = Io::SScan("
text
"); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto div1 = el->firstChild(); - expect$(div1->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(div1.cast())->tagName == Html::DIV); + expect$(div1->nodeType() == NodeType::ELEMENT); + expect$(try$(div1.cast())->tagName == Html::DIV); auto text = div1->nextSibling(); - expect$(text->nodeType() == Dom::NodeType::TEXT); - expect$(try$(text.cast())->data == "text"); + expect$(text->nodeType() == NodeType::TEXT); + expect$(try$(text.cast())->data == "text"); auto div2 = text->nextSibling(); - expect$(div2->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(div2.cast())->tagName == Html::DIV); + expect$(div2->nodeType() == NodeType::ELEMENT); + expect$(try$(div2.cast())->tagName == Html::DIV); return Ok(); } test$("parse-text-between-tags-and-before") { auto s = Io::SScan("test2
text
"); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto text1 = el->firstChild(); - expect$(text1->nodeType() == Dom::NodeType::TEXT); - expectEq$(try$(text1.cast())->data, "test2"); + expect$(text1->nodeType() == NodeType::TEXT); + expectEq$(try$(text1.cast())->data, "test2"); auto div = text1->nextSibling(); - expect$(div->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(div.cast())->tagName == Html::DIV); + expect$(div->nodeType() == NodeType::ELEMENT); + expect$(try$(div.cast())->tagName == Html::DIV); auto text2 = div->firstChild(); - expect$(text2->nodeType() == Dom::NodeType::TEXT); - expectEq$(try$(text2.cast())->data, "text"); + expect$(text2->nodeType() == NodeType::TEXT); + expectEq$(try$(text2.cast())->data, "text"); return Ok(); } test$("parse-nested-tags") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto head = el->firstChild(); - expect$(head->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(head.cast())->tagName == Html::HEAD); + expect$(head->nodeType() == NodeType::ELEMENT); + expect$(try$(head.cast())->tagName == Html::HEAD); auto body = head->nextSibling(); - expect$(body->nodeType() == Dom::NodeType::ELEMENT); - expect$(try$(body.cast())->tagName == Html::BODY); + expect$(body->nodeType() == NodeType::ELEMENT); + expect$(try$(body.cast())->tagName == Html::BODY); return Ok(); } test$("parse-comment") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto el = try$(first.cast()); + auto el = try$(first.cast()); expect$(el->hasChildren()); auto comment = el->firstChild(); - expect$(comment->nodeType() == Dom::NodeType::COMMENT); - expect$(try$(comment.cast())->data == " comment "); + expect$(comment->nodeType() == NodeType::COMMENT); + expect$(try$(comment.cast())->data == " comment "); return Ok(); } test$("parse-doctype") { auto s = Io::SScan(""); - auto p = Parser(); + XmlParser p{}; auto doc = try$(p.parse(s, Vaev::HTML)); auto first = doc->firstChild(); - auto doctype = try$(first.cast()); + auto doctype = try$(first.cast()); expect$(doctype->name == "html"); return Ok(); } -} // namespace Vaev::Xml::Tests +} // namespace Vaev::Markup::Tests diff --git a/src/web/vaev-xml/parser.cpp b/src/web/vaev-markup/xml.cpp similarity index 87% rename from src/web/vaev-xml/parser.cpp rename to src/web/vaev-markup/xml.cpp index a82f9b055d..77cba2856c 100644 --- a/src/web/vaev-xml/parser.cpp +++ b/src/web/vaev-markup/xml.cpp @@ -1,17 +1,17 @@ #include #include -#include "parser.h" +#include "xml.h" -namespace Vaev::Xml { +namespace Vaev::Markup { // 2 MARK: Documents // https://www.w3.org/TR/xml/#sec-documents -Res> Parser::parse(Io::SScan &s, Ns ns) { +Res> XmlParser::parse(Io::SScan &s, Ns ns) { // document :: = prolog element Misc * - auto doc = makeStrong(); + auto doc = makeStrong(); try$(_parseProlog(s, *doc)); doc->appendChild(try$(_parseElement(s, ns))); while (_parseMisc(s, *doc)) @@ -83,7 +83,7 @@ static constexpr auto RE_NAME_CHAR = RE_NAME_START_CHAR & Re::zeroOrMore(Re::cty static constexpr auto RE_NAME = RE_NAME_START_CHAR & Re::zeroOrMore(RE_NAME_CHAR); -Res<> Parser::_parseS(Io::SScan &s) { +Res<> XmlParser::_parseS(Io::SScan &s) { // S ::= (#x20 | #x9 | #xD | #xA)+ s.eat(Re::oneOrMore(RE_S)); @@ -91,7 +91,7 @@ Res<> Parser::_parseS(Io::SScan &s) { return Ok(); } -Res Parser::_parseName(Io::SScan &s) { +Res XmlParser::_parseName(Io::SScan &s) { // Name ::= NameStartChar (NameChar)* auto name = s.token(RE_NAME); @@ -105,7 +105,7 @@ Res Parser::_parseName(Io::SScan &s) { static constexpr auto RE_CHARDATA = Re::negate(Re::single('<', '&')); -Res<> Parser::_parseCharData(Io::SScan &s, StringBuilder &sb) { +Res<> XmlParser::_parseCharData(Io::SScan &s, StringBuilder &sb) { // CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) bool any = false; @@ -131,7 +131,7 @@ Res<> Parser::_parseCharData(Io::SScan &s, StringBuilder &sb) { static constexpr auto RE_COMMENT_START = ""_re; -Res> Parser::_parseComment(Io::SScan &s) { +Res> XmlParser::_parseComment(Io::SScan &s) { // Comment ::= '' auto rollback = s.rollbackPoint(); @@ -151,7 +151,7 @@ Res> Parser::_parseComment(Io::SScan &s) { return Error::invalidData("expected '-->'"); rollback.disarm(); - return Ok(makeStrong(sb.take())); + return Ok(makeStrong(sb.take())); } // 2.6 MARK: Processing Instructions @@ -160,7 +160,7 @@ Res> Parser::_parseComment(Io::SScan &s) { static constexpr auto RE_PI_START = ""_re; -Res<> Parser::_parsePi(Io::SScan &s) { +Res<> XmlParser::_parsePi(Io::SScan &s) { // PI ::= '' Char*)))? '?> auto rollback = s.rollbackPoint(); @@ -182,7 +182,7 @@ Res<> Parser::_parsePi(Io::SScan &s) { return Ok(); } -Res<> Parser::_parsePiTarget(Io::SScan &s) { +Res<> XmlParser::_parsePiTarget(Io::SScan &s) { // PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) auto name = try$(_parseName(s)); @@ -194,7 +194,7 @@ Res<> Parser::_parsePiTarget(Io::SScan &s) { // 2.7 MARK: CDATA Sections // https://www.w3.org/TR/xml/#sec-cdata-sect -Res<> Parser::_parseCDSect(Io::SScan &s, StringBuilder &sb) { +Res<> XmlParser::_parseCDSect(Io::SScan &s, StringBuilder &sb) { // CDStart ::= '' Char*)) // CDEnd ::= ']]>' @@ -219,7 +219,7 @@ Res<> Parser::_parseCDSect(Io::SScan &s, StringBuilder &sb) { static constexpr auto RE_XML_DECL_START = " Parser::_parseVersionInfo(Io::SScan &s) { +Res<> XmlParser::_parseVersionInfo(Io::SScan &s) { // versionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"') try$(_parseS(s)); @@ -230,7 +230,7 @@ Res<> Parser::_parseVersionInfo(Io::SScan &s) { return Ok(); } -Res<> Parser::_parseXmlDecl(Io::SScan &s) { +Res<> XmlParser::_parseXmlDecl(Io::SScan &s) { // XMLDecl ::= '' if (not s.skip(RE_XML_DECL_START)) @@ -241,7 +241,7 @@ Res<> Parser::_parseXmlDecl(Io::SScan &s) { return Ok(); } -Res<> Parser::_parseMisc(Io::SScan &s, Dom::Node &parent) { +Res<> XmlParser::_parseMisc(Io::SScan &s, Node &parent) { // Misc ::= Comment | PI | S auto rollback = s.rollbackPoint(); @@ -260,12 +260,12 @@ Res<> Parser::_parseMisc(Io::SScan &s, Dom::Node &parent) { return Ok(); } -Res<> Parser::_parseProlog(Io::SScan &s, Dom::Node &parent) { +Res<> XmlParser::_parseProlog(Io::SScan &s, Node &parent) { // prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? auto rollback = s.rollbackPoint(); if (s.match(RE_XML_DECL_START) != Match::NO) - try$(Parser::_parseXmlDecl(s)); + try$(_parseXmlDecl(s)); while (_parseMisc(s, parent) and not s.ended()) ; @@ -282,14 +282,14 @@ Res<> Parser::_parseProlog(Io::SScan &s, Dom::Node &parent) { static constexpr auto RE_DOCTYPE_START = "> Parser::_parseDoctype(Io::SScan &s) { +Res> XmlParser::_parseDoctype(Io::SScan &s) { // doctypedecl ::= '' auto rollback = s.rollbackPoint(); if (not s.skip(RE_DOCTYPE_START)) return Error::invalidData("expected '(); + auto docType = makeStrong(); try$(_parseS(s)); @@ -312,7 +312,7 @@ Res> Parser::_parseDoctype(Io::SScan &s) { // 3 MARK: Logical Structures // https://www.w3.org/TR/xml/#sec-logical-struct -Res> Parser::_parseElement(Io::SScan &s, Ns ns) { +Res> XmlParser::_parseElement(Io::SScan &s, Ns ns) { // element ::= EmptyElemTag | STag content ETag auto rollback = s.rollbackPoint(); @@ -337,7 +337,7 @@ Res> Parser::_parseElement(Io::SScan &s, Ns ns) { // 3.1 MARK: Start-Tags, End-Tags, and Empty-Element Tags // https://www.w3.org/TR/xml/#sec-starttags -Res> Parser::_parseStartTag(Io::SScan &s, Ns ns) { +Res> XmlParser::_parseStartTag(Io::SScan &s, Ns ns) { // STag ::= '<' Name (S Attribute)* S? '>' auto rollback = s.rollbackPoint(); @@ -346,7 +346,7 @@ Res> Parser::_parseStartTag(Io::SScan &s, Ns ns) { auto name = try$(_parseName(s)); - auto el = makeStrong(TagName::make(name, ns)); + auto el = makeStrong(TagName::make(name, ns)); try$(_parseS(s)); @@ -359,7 +359,7 @@ Res> Parser::_parseStartTag(Io::SScan &s, Ns ns) { return Ok(el); } -Res<> Parser::_parseAttribute(Io::SScan &s, Ns ns, Dom::Element &el) { +Res<> XmlParser::_parseAttribute(Io::SScan &s, Ns ns, Element &el) { // Attribute ::= Name Eq AttValue auto rollback = s.rollbackPoint(); @@ -377,7 +377,7 @@ Res<> Parser::_parseAttribute(Io::SScan &s, Ns ns, Dom::Element &el) { return Ok(); } -Res Parser::_parseAttValue(Io::SScan &s) { +Res XmlParser::_parseAttValue(Io::SScan &s) { // AttValue ::= '"' ([^<&"] | Reference)* '"' // | "'" ([^<&'] | Reference)* "'" @@ -406,7 +406,7 @@ Res Parser::_parseAttValue(Io::SScan &s) { return Ok(sb.take()); } -Res<> Parser::_parseEndTag(Io::SScan &s, Dom::Element &el) { +Res<> XmlParser::_parseEndTag(Io::SScan &s, Element &el) { // '' auto rollback = s.rollbackPoint(); @@ -427,7 +427,7 @@ Res<> Parser::_parseEndTag(Io::SScan &s, Dom::Element &el) { return Ok(); } -Res<> Parser::_parseContentItem(Io::SScan &s, Ns ns, Dom::Element &el) { +Res<> XmlParser::_parseContentItem(Io::SScan &s, Ns ns, Element &el) { // (element | Reference | CDSect | PI | Comment) if (auto r = _parseElement(s, ns)) { @@ -444,7 +444,7 @@ Res<> Parser::_parseContentItem(Io::SScan &s, Ns ns, Dom::Element &el) { } } -Res<> Parser::_parseContent(Io::SScan &s, Ns ns, Dom::Element &el) { +Res<> XmlParser::_parseContent(Io::SScan &s, Ns ns, Element &el) { // content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)* try$(_parseText(s, el)); @@ -454,7 +454,7 @@ Res<> Parser::_parseContent(Io::SScan &s, Ns ns, Dom::Element &el) { return Ok(); } -Res<> Parser::_parseTextItem(Io::SScan &s, StringBuilder &sb) { +Res<> XmlParser::_parseTextItem(Io::SScan &s, StringBuilder &sb) { if (_parseCharData(s, sb)) { return Ok(); } else if (_parseCDSect(s, sb)) { @@ -467,19 +467,19 @@ Res<> Parser::_parseTextItem(Io::SScan &s, StringBuilder &sb) { } } -Res<> Parser::_parseText(Io::SScan &s, Dom::Element &el) { +Res<> XmlParser::_parseText(Io::SScan &s, Element &el) { StringBuilder sb; while (_parseTextItem(s, sb)) ; auto te = sb.take(); if (te) - el.appendChild(makeStrong(te)); + el.appendChild(makeStrong(te)); return Ok(); } -Res> Parser::_parseEmptyElementTag(Io::SScan &s, Ns ns) { +Res> XmlParser::_parseEmptyElementTag(Io::SScan &s, Ns ns) { // EmptyElemTag ::= '<' Name (S Attribute)* S? '/>' auto rollback = s.rollbackPoint(); @@ -488,7 +488,7 @@ Res> Parser::_parseEmptyElementTag(Io::SScan &s, Ns ns) { auto name = try$(_parseName(s)); - auto el = makeStrong(TagName::make(name, ns)); + auto el = makeStrong(TagName::make(name, ns)); try$(_parseS(s)); while (not s.skip("/>"_re) and not s.ended()) { try$(_parseAttribute(s, ns, *el)); @@ -502,7 +502,7 @@ Res> Parser::_parseEmptyElementTag(Io::SScan &s, Ns ns) { // 4.1 MARK: Character and Entity References // https://www.w3.org/TR/xml/#NT-CharRef -Res Parser::_parseCharRef(Io::SScan &s) { +Res XmlParser::_parseCharRef(Io::SScan &s) { // CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' auto rollback = s.rollbackPoint(); @@ -531,7 +531,7 @@ Res Parser::_parseCharRef(Io::SScan &s) { return Ok(r); } -Res Parser::_parseEntityRef(Io::SScan &s) { +Res XmlParser::_parseEntityRef(Io::SScan &s) { // EntityRef ::= '&' Name ';' auto rollback = s.rollbackPoint(); @@ -560,7 +560,7 @@ Res Parser::_parseEntityRef(Io::SScan &s) { return Error::invalidData("unknown entity reference"); } -Res Parser::_parseReference(Io::SScan &s) { +Res XmlParser::_parseReference(Io::SScan &s) { // Reference ::= EntityRef | CharRef if (auto r = _parseCharRef(s)) @@ -574,7 +574,7 @@ Res Parser::_parseReference(Io::SScan &s) { // 4.2 MARK: Entity Declarations // https://www.w3.org/TR/xml/#sec-entity-decl -Res<> Parser::_parseExternalId(Io::SScan &s, Dom::DocumentType &docType) { +Res<> XmlParser::_parseExternalId(Io::SScan &s, DocumentType &docType) { // ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral auto rollback = s.rollbackPoint(); @@ -598,4 +598,4 @@ Res<> Parser::_parseExternalId(Io::SScan &s, Dom::DocumentType &docType) { } } -} // namespace Vaev::Xml +} // namespace Vaev::Markup diff --git a/src/web/vaev-markup/xml.h b/src/web/vaev-markup/xml.h new file mode 100644 index 0000000000..4dbc1d0958 --- /dev/null +++ b/src/web/vaev-markup/xml.h @@ -0,0 +1,63 @@ +#pragma once + +#include "dom.h" + +namespace Vaev::Markup { + +struct XmlParser { + Res> parse(Io::SScan &s, Ns ns); + + Res<> _parseS(Io::SScan &s); + + Res _parseName(Io::SScan &s); + + Res<> _parseCharData(Io::SScan &s, StringBuilder &sb); + + Res> _parseComment(Io::SScan &s); + + Res<> _parsePi(Io::SScan &s); + + Res<> _parsePiTarget(Io::SScan &s); + + Res<> _parseCDSect(Io::SScan &s, StringBuilder &sb); + + Res<> _parseVersionInfo(Io::SScan &s); + + Res<> _parseXmlDecl(Io::SScan &s); + + Res<> _parseMisc(Io::SScan &s, Node &parent); + + Res<> _parseProlog(Io::SScan &s, Node &parent); + + Res> _parseDoctype(Io::SScan &s); + + Res> _parseElement(Io::SScan &s, Ns ns); + + Res> _parseStartTag(Io::SScan &s, Ns ns); + + Res<> _parseAttribute(Io::SScan &s, Ns ns, Element &el); + + Res _parseAttValue(Io::SScan &s); + + Res<> _parseEndTag(Io::SScan &s, Element &el); + + Res<> _parseContentItem(Io::SScan &s, Ns ns, Element &el); + + Res<> _parseContent(Io::SScan &s, Ns ns, Element &el); + + Res<> _parseTextItem(Io::SScan &s, StringBuilder &sb); + + Res<> _parseText(Io::SScan &s, Element &el); + + Res> _parseEmptyElementTag(Io::SScan &s, Ns ns); + + Res _parseCharRef(Io::SScan &s); + + Res _parseEntityRef(Io::SScan &s); + + Res _parseReference(Io::SScan &s); + + Res<> _parseExternalId(Io::SScan &s, DocumentType &docType); +}; + +} // namespace Vaev::Markup diff --git a/src/web/vaev-mathml/manifest.json b/src/web/vaev-mathml/manifest.json deleted file mode 100644 index 98985c2651..0000000000 --- a/src/web/vaev-mathml/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-mathml", - "type": "lib", - "description": "Implementation of the MathML standard (https://w3c.github.io/mathml-core/)", - "requires": [ - "karm-base", - "vaev-base" - ] -} diff --git a/src/web/vaev-mathml/tags.cpp b/src/web/vaev-mathml/tags.cpp deleted file mode 100644 index 58cb965e25..0000000000 --- a/src/web/vaev-mathml/tags.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "tags.h" - -namespace Vaev::MathMl { - -Str _tagName(TagId id) { - switch (id) { -#define TAG(IDENT, NAME) \ - case TagId::IDENT: \ - return #NAME; -#include "defs/tag-names.inc" -#undef TAG - default: - return "unknown"; - } -} - -Str _attrName(AttrId id) { - switch (id) { -#define ATTR(IDENT, NAME) \ - case AttrId::IDENT: \ - return #NAME; -#include "defs/attr-names.inc" -#undef ATTR - default: - return "unknown"; - } -} - -Opt _tagId(Str name) { -#define TAG(IDENT, NAME) \ - if (name == #NAME) \ - return TagId::IDENT; -#include "defs/tag-names.inc" -#undef TAG - - return NONE; -} - -Opt _attrId(Str name) { -#define ATTR(IDENT, NAME) \ - if (name == #NAME) \ - return AttrId::IDENT; - -#include "defs/attr-names.inc" -#undef ATTR - - return NONE; -} - -} // namespace Vaev::MathMl diff --git a/src/web/vaev-mathml/tags.h b/src/web/vaev-mathml/tags.h deleted file mode 100644 index 501a8875c5..0000000000 --- a/src/web/vaev-mathml/tags.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -namespace Vaev::MathMl { - -enum struct TagId : u16 { -#define TAG(IDENT, _) IDENT, -#include "defs/tag-names.inc" -#undef TAG -}; - -enum struct AttrId : u16 { -#define ATTR(IDENT, _) IDENT, -#include "defs/attr-names.inc" -#undef ATTR -}; - -#define TAG(IDENT, _) \ - inline constexpr TagName IDENT = TagId::IDENT; -#include "defs/tag-names.inc" -#undef TAG - -} // namespace Vaev::MathMl diff --git a/src/web/vaev-paint/box.h b/src/web/vaev-paint/box.h index ce46c36852..b3018ad481 100644 --- a/src/web/vaev-paint/box.h +++ b/src/web/vaev-paint/box.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "base.h" namespace Vaev::Paint { diff --git a/src/web/vaev-style/computer.cpp b/src/web/vaev-style/computer.cpp index 5457eec549..09e0f219cc 100644 --- a/src/web/vaev-style/computer.cpp +++ b/src/web/vaev-style/computer.cpp @@ -13,7 +13,7 @@ Computed const &Computed::initial() { return computed; } -void Computer::_evalRule(Rule const &rule, Dom::Element const &el, MatchingRules &matches) { +void Computer::_evalRule(Rule const &rule, Markup::Element const &el, MatchingRules &matches) { rule.visit(Visitor{ [&](StyleRule const &r) { if (r.match(el)) @@ -30,7 +30,7 @@ void Computer::_evalRule(Rule const &rule, Dom::Element const &el, MatchingRules }); } -Strong Computer::computeFor(Computed const &parent, Dom::Element const &el) { +Strong Computer::computeFor(Computed const &parent, Markup::Element const &el) { MatchingRules matchingRules; // Collect matching styles rules diff --git a/src/web/vaev-style/computer.h b/src/web/vaev-style/computer.h index 9076b88db2..87c9d8952b 100644 --- a/src/web/vaev-style/computer.h +++ b/src/web/vaev-style/computer.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "computed.h" #include "stylesheet.h" @@ -13,9 +13,9 @@ struct Computer { using MatchingRules = Vec; - void _evalRule(Rule const &rule, Dom::Element const &el, MatchingRules &matches); + void _evalRule(Rule const &rule, Markup::Element const &el, MatchingRules &matches); - Strong computeFor(Computed const &parent, Dom::Element const &el); + Strong computeFor(Computed const &parent, Markup::Element const &el); }; } // namespace Vaev::Style diff --git a/src/web/vaev-style/manifest.json b/src/web/vaev-style/manifest.json index 9b1de4f7bb..d75528c35c 100644 --- a/src/web/vaev-style/manifest.json +++ b/src/web/vaev-style/manifest.json @@ -6,8 +6,6 @@ "requires": [ "vaev-base", "vaev-css", - "vaev-html", - "vaev-svg", - "vaev-mathml" + "vaev-markup" ] } diff --git a/src/web/vaev-style/rules.h b/src/web/vaev-style/rules.h index ff52f7cb13..72bac63a1d 100644 --- a/src/web/vaev-style/rules.h +++ b/src/web/vaev-style/rules.h @@ -16,7 +16,7 @@ struct StyleRule { void repr(Io::Emit &e) const; - bool match(Dom::Element const &el) const { + bool match(Markup::Element const &el) const { return selector.match(el); } diff --git a/src/web/vaev-style/select.cpp b/src/web/vaev-style/select.cpp index 3954f79860..c4bd69382c 100644 --- a/src/web/vaev-style/select.cpp +++ b/src/web/vaev-style/select.cpp @@ -52,11 +52,11 @@ Spec spec(Selector const &s) { // MARK: Selector Matching ----------------------------------------------------- // https://www.w3.org/TR/selectors-4/#descendant-combinators -static bool _matchDescendant(Selector const &s, Dom::Element const &e) { - Dom::Node const *curr = &e; +static bool _matchDescendant(Selector const &s, Markup::Element const &e) { + Markup::Node const *curr = &e; while (curr->hasParent()) { auto &parent = curr->parentNode(); - if (auto *el = parent.is()) + if (auto *el = parent.is()) if (s.match(*el)) return true; curr = &parent; @@ -65,33 +65,33 @@ static bool _matchDescendant(Selector const &s, Dom::Element const &e) { } // https://www.w3.org/TR/selectors-4/#child-combinators -static bool _matchChild(Selector const &s, Dom::Element const &e) { +static bool _matchChild(Selector const &s, Markup::Element const &e) { if (not e.hasParent()) return false; auto &parent = e.parentNode(); - if (auto *el = parent.is()) + if (auto *el = parent.is()) return s.match(*el); return false; } // https://www.w3.org/TR/selectors-4/#adjacent-sibling-combinators -static bool _matchAdjacent(Selector const &s, Dom::Element const &e) { +static bool _matchAdjacent(Selector const &s, Markup::Element const &e) { if (not e.hasPreviousSibling()) return false; auto prev = e.previousSibling(); - if (auto *el = prev.is()) + if (auto *el = prev.is()) return s.match(*el); return false; } // https://www.w3.org/TR/selectors-4/#general-sibling-combinators -static bool _matchSubsequent(Selector const &s, Dom::Element const &e) { - Dom::Node const *curr = &e; +static bool _matchSubsequent(Selector const &s, Markup::Element const &e) { + Markup::Node const *curr = &e; while (curr->hasPreviousSibling()) { auto prev = curr->previousSibling(); - if (auto *el = prev.is()) + if (auto *el = prev.is()) if (s.match(*el)) return true; curr = &prev.unwrap(); @@ -99,7 +99,7 @@ static bool _matchSubsequent(Selector const &s, Dom::Element const &e) { return false; } -static bool _match(Infix const &s, Dom::Element const &e) { +static bool _match(Infix const &s, Markup::Element const &e) { if (not s.lhs->match(e)) return false; @@ -122,7 +122,7 @@ static bool _match(Infix const &s, Dom::Element const &e) { } } -static bool _match(Nfix const &s, Dom::Element const &el) { +static bool _match(Nfix const &s, Markup::Element const &el) { switch (s.type) { case Nfix::AND: for (auto &inner : s.inners) @@ -155,27 +155,27 @@ static bool _match(Nfix const &s, Dom::Element const &el) { // 5.1. Type (tag name) selector // https://www.w3.org/TR/selectors-4/#type -static bool _match(TypeSelector const &s, Dom::Element const &el) { +static bool _match(TypeSelector const &s, Markup::Element const &el) { return el.tagName == s.type; } -static bool _match(IdSelector const &s, Dom::Element const &el) { +static bool _match(IdSelector const &s, Markup::Element const &el) { return el.id() == s.id; } -static bool _match(ClassSelector const &s, Dom::Element const &el) { +static bool _match(ClassSelector const &s, Markup::Element const &el) { return el.classList.contains(s.class_); } // 5.2. Universal selector // https://www.w3.org/TR/selectors-4/#the-universal-selector -static bool _match(UniversalSelector const &, Dom::Element const &) { +static bool _match(UniversalSelector const &, Markup::Element const &) { return true; } // MARK: Selector -------------------------------------------------------------- -bool Selector::match(Dom::Element const &el) const { +bool Selector::match(Markup::Element const &el) const { return visit( [&](auto const &s) { if constexpr (requires { _match(s, el); }) diff --git a/src/web/vaev-style/select.h b/src/web/vaev-style/select.h index 0d79ba7156..691c9ecdae 100644 --- a/src/web/vaev-style/select.h +++ b/src/web/vaev-style/select.h @@ -3,8 +3,8 @@ #include #include #include -#include -#include +#include +#include namespace Vaev::Style { @@ -349,7 +349,7 @@ struct Selector : public _Selector { }); } - bool match(Dom::Element const &el) const; + bool match(Markup::Element const &el) const; bool operator==(Selector const &) const = default; diff --git a/src/web/vaev-style/tests/test-select-spec.cpp b/src/web/vaev-style/tests/test-select-spec.cpp index 86726de22c..14c8fdce9b 100644 --- a/src/web/vaev-style/tests/test-select-spec.cpp +++ b/src/web/vaev-style/tests/test-select-spec.cpp @@ -5,7 +5,7 @@ namespace Vaev::Style::Tests { test$("select-class-spec") { Selector sel = ClassSelector{"foo"s}; - auto el = makeStrong(Html::DIV); + auto el = makeStrong(Html::DIV); el->classList.add("foo"); expect$(sel.match(*el)); return Ok(); diff --git a/src/web/vaev-svg/manifest.json b/src/web/vaev-svg/manifest.json deleted file mode 100644 index deb26ff64b..0000000000 --- a/src/web/vaev-svg/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-svg", - "type": "lib", - "description": "Implementation of the SVG standard (https://www.w3.org/TR/SVG2)", - "requires": [ - "karm-base", - "vaev-base" - ] -} diff --git a/src/web/vaev-svg/tags.cpp b/src/web/vaev-svg/tags.cpp deleted file mode 100644 index 068e18f1c3..0000000000 --- a/src/web/vaev-svg/tags.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "tags.h" - -namespace Vaev::Svg { - -Str _tagName(TagId id) { - switch (id) { -#define TAG(IDENT, NAME) \ - case TagId::IDENT: \ - return #NAME; -#include "defs/tag-names.inc" -#undef TAG - default: - return "unknown"; - } -} - -Str _attrName(AttrId id) { - switch (id) { -#define ATTR(IDENT, NAME) \ - case AttrId::IDENT: \ - return #NAME; -#include "defs/attr-names.inc" -#undef ATTR - default: - return "unknown"; - } -} - -Opt _tagId(Str name) { -#define TAG(IDENT, NAME) \ - if (name == #NAME) \ - return TagId::IDENT; -#include "defs/tag-names.inc" -#undef TAG - - return NONE; -} - -Opt _attrId(Str name) { -#define ATTR(IDENT, NAME) \ - if (name == #NAME) \ - return AttrId::IDENT; - -#include "defs/attr-names.inc" -#undef ATTR - - return NONE; -} - -} // namespace Vaev::Svg diff --git a/src/web/vaev-svg/tags.h b/src/web/vaev-svg/tags.h deleted file mode 100644 index f881e9f6ce..0000000000 --- a/src/web/vaev-svg/tags.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -namespace Vaev::Svg { - -enum struct TagId : u16 { -#define TAG(IDENT, _) IDENT, -#include -#undef TAG -}; - -enum struct AttrId : u16 { -#define ATTR(IDENT, _) IDENT, -#include -#undef ATTR -}; - -#define TAG(IDENT, _) \ - inline constexpr TagName IDENT = TagId::IDENT; -#include -#undef TAG - -} // namespace Vaev::Svg diff --git a/src/web/vaev-view/inspect.cpp b/src/web/vaev-view/inspect.cpp index 143cdd3299..8695eee050 100644 --- a/src/web/vaev-view/inspect.cpp +++ b/src/web/vaev-view/inspect.cpp @@ -3,10 +3,7 @@ #include #include #include -#include -#include -#include -#include +#include #include "inspect.h" @@ -32,44 +29,44 @@ auto idented(isize ident) { }; } -Ui::Child elementStartTag(Dom::Element const &el) { +Ui::Child elementStartTag(Markup::Element const &el) { return Ui::text( Ui::TextStyles::codeSmall().withColor(Ui::ACCENT500), "<{}>", el.tagName ); } -Ui::Child elementEndTag(Dom::Element const &el) { +Ui::Child elementEndTag(Markup::Element const &el) { return Ui::text( Ui::TextStyles::codeSmall().withColor(Ui::ACCENT500), "", el.tagName ); } -Ui::Child itemHeader(Dom::Node const &n) { - if (n.is()) { +Ui::Child itemHeader(Markup::Node const &n) { + if (n.is()) { return Ui::codeMedium("#document"); - } else if (n.is()) { + } else if (n.is()) { return Ui::codeMedium("#document-type"); - } else if (auto *tx = n.is()) { + } else if (auto *tx = n.is()) { return Ui::codeMedium("#text {#}", tx->data); - } else if (auto *el = n.is()) { + } else if (auto *el = n.is()) { return Ui::hflow(n.children().len() ? Ui::icon(Mdi::CHEVRON_DOWN) : Ui::empty(), elementStartTag(*el)); - } else if (auto *c = n.is()) { + } else if (auto *c = n.is()) { return Ui::codeMedium(Gfx::GREEN, "", c->data); } else { unreachable(); } } -Ui::Child itemFooter(Dom::Node const &n, isize ident) { - if (auto *el = n.is()) { +Ui::Child itemFooter(Markup::Node const &n, isize ident) { + if (auto *el = n.is()) { return Ui::hflow(n.children().len() ? guide() : Ui::empty(), elementEndTag(*el)) | idented(ident); } return Ui::empty(); } -Ui::Child item(Dom::Node const &n, isize ident) { +Ui::Child item(Markup::Node const &n, isize ident) { return Ui::button( Ui::NOP, Ui::ButtonStyle::subtle(), @@ -77,7 +74,7 @@ Ui::Child item(Dom::Node const &n, isize ident) { ); } -Ui::Child node(Dom::Node const &n, isize ident = 0) { +Ui::Child node(Markup::Node const &n, isize ident = 0) { Ui::Children children{item(n, ident)}; for (auto &c : n.children()) { children.pushBack(node(*c, ident + 1)); @@ -86,7 +83,7 @@ Ui::Child node(Dom::Node const &n, isize ident = 0) { return Ui::vflow(children); } -Ui::Child inspect(Strong dom) { +Ui::Child inspect(Strong dom) { return node(*dom); } diff --git a/src/web/vaev-view/inspect.h b/src/web/vaev-view/inspect.h index c426c59725..8561c87751 100644 --- a/src/web/vaev-view/inspect.h +++ b/src/web/vaev-view/inspect.h @@ -1,10 +1,10 @@ #pragma once #include -#include +#include namespace Vaev::View { -Ui::Child inspect(Strong dom); +Ui::Child inspect(Strong dom); } // namespace Vaev::View diff --git a/src/web/vaev-view/view.cpp b/src/web/vaev-view/view.cpp index 81b4176a9f..26c798373e 100644 --- a/src/web/vaev-view/view.cpp +++ b/src/web/vaev-view/view.cpp @@ -6,10 +6,10 @@ namespace Vaev::View { struct View : public Ui::View { - Strong _dom; + Strong _dom; Opt _renderResult; - View(Strong dom) : _dom(dom) {} + View(Strong dom) : _dom(dom) {} Style::Media _constructMedia(Math::Vec2i viewport) { return { @@ -85,8 +85,6 @@ struct View : public Ui::View { auto media = _constructMedia(size); auto [layout, _] = Driver::render(*_dom, media, size.cast()); - logDebug("Size: {}", layout->layout.borderBox()); - return { layout->layout.borderBox().width.cast(), layout->layout.borderBox().height.cast(), @@ -94,7 +92,7 @@ struct View : public Ui::View { } }; -Ui::Child view(Strong dom) { +Ui::Child view(Strong dom) { return makeStrong(dom); } diff --git a/src/web/vaev-view/view.h b/src/web/vaev-view/view.h index 2845d59f5c..dabb6ee5e2 100644 --- a/src/web/vaev-view/view.h +++ b/src/web/vaev-view/view.h @@ -1,10 +1,10 @@ #pragma once #include -#include +#include namespace Vaev::View { -Ui::Child view(Strong dom); +Ui::Child view(Strong dom); } // namespace Vaev::View diff --git a/src/web/vaev-xml/_mod.h b/src/web/vaev-xml/_mod.h deleted file mode 100644 index 25a6c3a40c..0000000000 --- a/src/web/vaev-xml/_mod.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "parser.h" diff --git a/src/web/vaev-xml/cli/main.cpp b/src/web/vaev-xml/cli/main.cpp deleted file mode 100644 index cfec21420f..0000000000 --- a/src/web/vaev-xml/cli/main.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 2) { - Sys::errln("usage: vaev-html.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - auto url = co_try$(Mime::parseUrlOrPath(args[1])); - - if (verb == "dump-dom") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - - auto start = Sys::now(); - - Vaev::Xml::Parser parser{}; - Io::SScan s{buf}; - auto res = parser.parse(s, Vaev::HTML); - - auto elapsed = Sys::now() - start; - logInfo("parsed in {}ms", elapsed.toUSecs() / 1000.0); - - Sys::println("{}", res); - - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: dump-dom, dump-tokens)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-xml/cli/manifest.json b/src/web/vaev-xml/cli/manifest.json deleted file mode 100644 index 23dd761be6..0000000000 --- a/src/web/vaev-xml/cli/manifest.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-xml.cli", - "type": "exe", - "description": "XML CLI", - "requires": [ - "karm-sys", - "vaev-xml", - "vaev-html" - ] -} diff --git a/src/web/vaev-xml/manifest.json b/src/web/vaev-xml/manifest.json deleted file mode 100644 index 4845e4e124..0000000000 --- a/src/web/vaev-xml/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-xml", - "type": "lib", - "description": "Implementation of the XML standard (https://www.w3.org/TR/xml)", - "requires": [ - "karm-base", - "vaev-dom" - ] -} diff --git a/src/web/vaev-xml/parser.h b/src/web/vaev-xml/parser.h deleted file mode 100644 index a351b606a9..0000000000 --- a/src/web/vaev-xml/parser.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace Vaev::Xml { - -struct Parser { - Res> parse(Io::SScan &s, Ns ns); - - Res<> _parseS(Io::SScan &s); - - Res _parseName(Io::SScan &s); - - Res<> _parseCharData(Io::SScan &s, StringBuilder &sb); - - Res> _parseComment(Io::SScan &s); - - Res<> _parsePi(Io::SScan &s); - - Res<> _parsePiTarget(Io::SScan &s); - - Res<> _parseCDSect(Io::SScan &s, StringBuilder &sb); - - Res<> _parseVersionInfo(Io::SScan &s); - - Res<> _parseXmlDecl(Io::SScan &s); - - Res<> _parseMisc(Io::SScan &s, Dom::Node &parent); - - Res<> _parseProlog(Io::SScan &s, Dom::Node &parent); - - Res> _parseDoctype(Io::SScan &s); - - Res> _parseElement(Io::SScan &s, Ns ns); - - Res> _parseStartTag(Io::SScan &s, Ns ns); - - Res<> _parseAttribute(Io::SScan &s, Ns ns, Dom::Element &el); - - Res _parseAttValue(Io::SScan &s); - - Res<> _parseEndTag(Io::SScan &s, Dom::Element &el); - - Res<> _parseContentItem(Io::SScan &s, Ns ns, Dom::Element &el); - - Res<> _parseContent(Io::SScan &s, Ns ns, Dom::Element &el); - - Res<> _parseTextItem(Io::SScan &s, StringBuilder &sb); - - Res<> _parseText(Io::SScan &s, Dom::Element &el); - - Res> _parseEmptyElementTag(Io::SScan &s, Ns ns); - - Res _parseCharRef(Io::SScan &s); - - Res _parseEntityRef(Io::SScan &s); - - Res _parseReference(Io::SScan &s); - - Res<> _parseExternalId(Io::SScan &s, Dom::DocumentType &docType); -}; - -} // namespace Vaev::Xml