@formicoidea/labre-core 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
  2. package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
  3. package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
  4. package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
  5. package/dist/_pkgs/affine-block-attachment/index.js +1 -0
  6. package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
  7. package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
  8. package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
  9. package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
  10. package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
  11. package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
  12. package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
  13. package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
  14. package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
  15. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
  16. package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
  17. package/dist/_pkgs/affine-block-frame/index.js +1 -0
  18. package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
  19. package/dist/_pkgs/affine-block-frame/translations.js +16 -1
  20. package/dist/_pkgs/affine-block-frame/view.js +6 -0
  21. package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
  22. package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
  23. package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
  24. package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
  25. package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
  26. package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
  27. package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
  28. package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
  29. package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
  30. package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
  31. package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
  32. package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
  33. package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
  34. package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
  35. package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
  36. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
  37. package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
  38. package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
  39. package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
  40. package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
  41. package/dist/_pkgs/affine-block-image/effects.js +2 -0
  42. package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
  43. package/dist/_pkgs/affine-block-image/image-block.js +2 -0
  44. package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
  45. package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
  46. package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
  47. package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
  48. package/dist/_pkgs/affine-block-image/index.js +1 -0
  49. package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
  50. package/dist/_pkgs/affine-block-image/translations.js +43 -0
  51. package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
  52. package/dist/_pkgs/affine-block-image/utils.js +10 -1
  53. package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
  54. package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
  55. package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
  56. package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
  57. package/dist/_pkgs/affine-block-root/index.js +1 -0
  58. package/dist/_pkgs/affine-block-root/view.js +8 -0
  59. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
  60. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
  61. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
  62. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
  63. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
  64. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
  65. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
  66. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
  67. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
  68. package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
  69. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
  70. package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
  71. package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
  72. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
  73. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
  74. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
  75. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
  76. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
  77. package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
  78. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
  79. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
  80. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
  81. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
  82. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
  83. package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
  84. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
  85. package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
  86. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
  87. package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
  88. package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
  89. package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
  90. package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
  91. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
  92. package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
  93. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
  94. package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
  95. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
  96. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
  97. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
  98. package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
  99. package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
  100. package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
  101. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
  102. package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
  103. package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
  104. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  105. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  106. package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
  107. package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
  108. package/dist/_pkgs/affine-block-surface/view.js +22 -2
  109. package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
  110. package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
  111. package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
  112. package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
  113. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
  114. package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
  115. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
  116. package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
  117. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
  118. package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
  119. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
  120. package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
  121. package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
  122. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
  123. package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
  124. package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
  125. package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
  126. package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
  127. package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
  128. package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
  129. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
  130. package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
  131. package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
  132. package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
  133. package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
  134. package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
  135. package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
  136. package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
  137. package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
  138. package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
  139. package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
  140. package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
  141. package/dist/_pkgs/affine-model/consts/shape.js +5 -0
  142. package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
  143. package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
  144. package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
  145. package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
  146. package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
  147. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
  148. package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
  149. package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
  150. package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
  151. package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
  152. package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
  153. package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
  154. package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
  155. package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
  156. package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
  157. package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
  158. package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
  159. package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
  160. package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
  161. package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
  162. package/dist/_pkgs/affine-model/headless.d.ts +19 -0
  163. package/dist/_pkgs/affine-model/headless.js +19 -0
  164. package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
  165. package/dist/_pkgs/affine-shared/headless.js +10 -0
  166. package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
  167. package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
  168. package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
  169. package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
  170. package/dist/_pkgs/affine-shared/services/index.d.ts +3 -0
  171. package/dist/_pkgs/affine-shared/services/index.js +3 -0
  172. package/dist/_pkgs/affine-shared/services/thread-marks-service.d.ts +69 -0
  173. package/dist/_pkgs/affine-shared/services/thread-marks-service.js +20 -0
  174. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
  175. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
  176. package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
  177. package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
  178. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
  179. package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
  180. package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
  181. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
  182. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
  183. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
  184. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
  185. package/dist/_pkgs/affine-widget-thread-marks/effects.d.ts +1 -0
  186. package/dist/_pkgs/affine-widget-thread-marks/effects.js +4 -0
  187. package/dist/_pkgs/affine-widget-thread-marks/excerpt.d.ts +19 -0
  188. package/dist/_pkgs/affine-widget-thread-marks/excerpt.js +68 -0
  189. package/dist/_pkgs/affine-widget-thread-marks/index.d.ts +5 -0
  190. package/dist/_pkgs/affine-widget-thread-marks/index.js +8 -0
  191. package/dist/_pkgs/affine-widget-thread-marks/toolbar.d.ts +49 -0
  192. package/dist/_pkgs/affine-widget-thread-marks/toolbar.js +108 -0
  193. package/dist/_pkgs/affine-widget-thread-marks/translations.d.ts +9 -0
  194. package/dist/_pkgs/affine-widget-thread-marks/translations.js +11 -0
  195. package/dist/_pkgs/affine-widget-thread-marks/view.d.ts +12 -0
  196. package/dist/_pkgs/affine-widget-thread-marks/view.js +25 -0
  197. package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +51 -0
  198. package/dist/_pkgs/affine-widget-thread-marks/widget.js +214 -0
  199. package/dist/commands.js +5 -2
  200. package/dist/extensions/view.js +2 -0
  201. package/dist/host-panels.d.ts +24 -1
  202. package/dist/host-panels.js +61 -10
  203. package/dist/interchange.d.ts +1 -0
  204. package/dist/interchange.js +1 -0
  205. package/dist/model/headless.d.ts +1 -0
  206. package/dist/model/headless.js +1 -0
  207. package/dist/shared/headless.d.ts +1 -0
  208. package/dist/shared/headless.js +1 -0
  209. package/dist/translations.js +10 -1
  210. package/package.json +13 -1
@@ -1,7 +1,8 @@
1
- import { ShapeStyle, ShapeType, StrokeStyle, TextAlign, } from '../../affine-model/index.js';
2
- import DOMPurify from 'dompurify';
1
+ import { ShapeStyle, ShapeType, StrokeStyle, TextAlign, } from '../../affine-model/headless.js';
3
2
  import { InterchangeImportError, } from './interchange.js';
4
- import { SVG_SKETCH_CURRENT_COLOR, SVG_SKETCH_CURVE, SVG_SKETCH_EMPTY, SVG_SKETCH_EMPTY_BOX, SVG_SKETCH_EMPTY_TEXT, SVG_SKETCH_ERROR_MALFORMED_XML, SVG_SKETCH_ERROR_NOT_SVG, SVG_SKETCH_ERROR_SANITIZED_AWAY, SVG_SKETCH_FLAT_POLYGON, SVG_SKETCH_FONT_UNIT, SVG_SKETCH_HIDDEN_DISPLAY, SVG_SKETCH_HIDDEN_VISIBILITY, SVG_SKETCH_LONE_POINT, SVG_SKETCH_OPACITY, SVG_SKETCH_PAINT_SERVER, SVG_SKETCH_PERCENT_RADIUS, SVG_SKETCH_REMOVED, SVG_SKETCH_REMOVED_ATTRIBUTE, SVG_SKETCH_SKIPPED, SVG_SKETCH_SWITCH, SVG_SKETCH_TRANSFORM, } from './svg-sketch-translations.js';
3
+ import { MAX_SVG_DEPTH, sanitizeSvgNode } from './svg-sanitize.js';
4
+ import { SVG_SKETCH_CURRENT_COLOR, SVG_SKETCH_CURVE, SVG_SKETCH_EMPTY, SVG_SKETCH_EMPTY_BOX, SVG_SKETCH_EMPTY_TEXT, SVG_SKETCH_ERROR_MALFORMED_XML, SVG_SKETCH_ERROR_NOT_SVG, SVG_SKETCH_ERROR_SANITIZED_AWAY, SVG_SKETCH_FLAT_POLYGON, SVG_SKETCH_FONT_UNIT, SVG_SKETCH_HIDDEN_DISPLAY, SVG_SKETCH_HIDDEN_VISIBILITY, SVG_SKETCH_LONE_POINT, SVG_SKETCH_OPACITY, SVG_SKETCH_PAINT_SERVER, SVG_SKETCH_PERCENT_RADIUS, SVG_SKETCH_REMOVED, SVG_SKETCH_REMOVED_ATTRIBUTE, SVG_SKETCH_SKIPPED, SVG_SKETCH_SWITCH, SVG_SKETCH_TOO_DEEP, SVG_SKETCH_TRANSFORM, } from './svg-sketch-translations.js';
5
+ import { readXml } from './xml-reader.js';
5
6
  /**
6
7
  * **SVG → a canvas sketch.** The platform's VISUAL-tier importer, shared by
7
8
  * every framework that declares it (`docs/adr/0012`, P2).
@@ -99,27 +100,14 @@ export const SVG_SKETCH_EXTENSION = '.svg';
99
100
  export const SVG_SKETCH_MIME = 'image/svg+xml';
100
101
  /* ── Small readers ────────────────────────────────────────────────────── */
101
102
  /**
102
- * happy-dom (the vitest environment two of the three packages that test this
103
- * run in) does not implement namespace-aware lookups, so this file walks
104
- * children by hand and compares `localName`, exactly as the BPMN reader does.
105
- * `children` is also skipped in favour of `childNodes`, because the former is
106
- * an `HTMLCollection` whose behaviour on a foreign-namespaced document is one
107
- * more thing that differs between the two environments.
103
+ * The walk reads the sanitizer's tree (`svg-sanitize.ts`), a DOM-shaped view
104
+ * over `readXml` rather than a DOM, so a server reads an `.svg` with no DOM
105
+ * installed (ADR 0012, P3 as amended 2026-10-14). It compares `localName`
106
+ * folded to lower case, as it did when that tree came out of an HTML parser.
108
107
  */
109
- const ELEMENT_NODE = 1;
110
108
  const TEXT_NODE = 3;
111
- const CDATA_NODE = 4;
112
- // `Node` and not `Element`, because only `childNodes` is ever read and one
113
- // caller is DOMPurify's `RETURN_DOM` answer — which its own typed overload
114
- // declares as a `Node`. Widening the parameter is more honest than casting the
115
- // argument at that one call site.
116
109
  function childElements(parent) {
117
- const found = [];
118
- for (const node of Array.from(parent.childNodes)) {
119
- if (node.nodeType === ELEMENT_NODE)
120
- found.push(node);
121
- }
122
- return found;
110
+ return parent.children;
123
111
  }
124
112
  const nameOf = (element) => element.localName?.toLowerCase() ?? '';
125
113
  /** One attribute as a finite number, or the caller's default. */
@@ -614,10 +602,10 @@ function readPolygon(element, frame, paint, sketch) {
614
602
  }
615
603
  const xs = points.map(p => p[0]);
616
604
  const ys = points.map(p => p[1]);
617
- const minX = Math.min(...xs);
618
- const minY = Math.min(...ys);
619
- const w = Math.max(...xs) - minX;
620
- const h = Math.max(...ys) - minY;
605
+ const minX = xs.reduce((a, b) => Math.min(a, b), Infinity);
606
+ const minY = ys.reduce((a, b) => Math.min(a, b), Infinity);
607
+ const w = xs.reduce((a, b) => Math.max(a, b), -Infinity) - minX;
608
+ const h = ys.reduce((a, b) => Math.max(a, b), -Infinity) - minY;
621
609
  if (points.length < 3 || w <= 0 || h <= 0) {
622
610
  if (points.length === 0 || w < 0 || h < 0) {
623
611
  sketch.notes.once('empty-box', EMPTY_BOX_NOTE, SVG_SKETCH_EMPTY_BOX);
@@ -712,13 +700,14 @@ const LINE_HEIGHT_RATIO = 1.2;
712
700
  function readText(element, frame, paint, sketch) {
713
701
  const lines = [];
714
702
  const collapse = (raw) => raw.replace(/\s+/g, ' ').trim();
715
- for (const node of Array.from(element.childNodes)) {
716
- if (node.nodeType === TEXT_NODE || node.nodeType === CDATA_NODE) {
717
- const line = collapse(node.nodeValue ?? '');
703
+ for (const node of element.childNodes) {
704
+ if (!('localName' in node)) {
705
+ // Text and CDATA alike; a comment is not a line.
706
+ const line = node.nodeType === TEXT_NODE ? collapse(node.nodeValue) : '';
718
707
  if (line)
719
708
  lines.push(line);
720
709
  }
721
- else if (node.nodeType === ELEMENT_NODE) {
710
+ else {
722
711
  const child = node;
723
712
  if (nameOf(child) !== 'tspan')
724
713
  continue;
@@ -734,7 +723,9 @@ function readText(element, frame, paint, sketch) {
734
723
  return;
735
724
  }
736
725
  const fontSize = len(frame, paint.fontSize ?? DEFAULT_FONT_SIZE);
737
- const widest = Math.max(...lines.map(line => line.length));
726
+ const widest = lines
727
+ .map(line => line.length)
728
+ .reduce((a, b) => Math.max(a, b), -Infinity);
738
729
  const w = Math.max(widest * fontSize * CHARACTER_WIDTH_RATIO, fontSize);
739
730
  const h = lines.length * fontSize * LINE_HEIGHT_RATIO;
740
731
  // `text-anchor` says which end of the box `x` names, and it INHERITS — a
@@ -771,8 +762,6 @@ const CONTAINERS = new Set(['g', 'a']);
771
762
  * most visible thing this reader does to a mermaid diagram.
772
763
  */
773
764
  const NON_RENDERING = new Set(['title', 'desc', 'metadata']);
774
- /** The sanitizer's own HTML wrapper, which no `.svg` on a disk ever contained. */
775
- const PARSER_SCAFFOLDING = new Set(['html', 'head', 'body']);
776
765
  function walk(parent, frame, inherited, sketch) {
777
766
  for (const element of childElements(parent)) {
778
767
  visit(element, frame, inherited, sketch);
@@ -925,83 +914,66 @@ export function svgFrameOf(element) {
925
914
  /**
926
915
  * The document, sanitized, or an exception naming what is wrong with it.
927
916
  *
928
- * Well-formedness FIRST, because `DOMParser` reports a malformed document as a
929
- * `parsererror` element rather than by throwing — a verdict in the shape of a
930
- * document — and because DOMPurify's parser is the forgiving HTML one, which
931
- * would quietly repair the very thing the caller needs to be told about. So:
932
- * parse for the verdict, reading nothing out of the result but that; sanitize
933
- * the SOURCE; and walk only what came back from the sanitizer.
917
+ * Read with `readXml` in its conformant mode, then filtered by the allow-list
918
+ * in `svg-sanitize.ts`; the walk and every recogniser see only what came back.
919
+ * No DOM is involved at any step, so the same function runs in the editor, in
920
+ * a test and on a server (ADR 0012, P3 as amended 2026-10-14). It replaced
921
+ * `DOMParser` + DOMPurify, which needed a DOM installed before the library was
922
+ * even imported (the P0 import spike measured that trap).
923
+ *
924
+ * Well-formedness first: anything the reader had to recover from is refused,
925
+ * with the reader's first remark as the detail, as `DOMParser` used to refuse
926
+ * it. A `DOCTYPE` is not an error: it is skipped and no entity it declares is
927
+ * ever expanded, so a `&name;` it declares stays those characters.
934
928
  *
935
929
  * The sanitizer is not optional and it is not defensive programming. An `.svg`
936
930
  * is an executable document — `<script>`, `<foreignObject>` with markup, event
937
- * handler attributes — handed over by whoever sent the file. The same
938
- * `USE_PROFILES: { svg: true }` guard is what the edgeless clipboard already
939
- * puts in front of a pasted SVG.
931
+ * handler attributes — handed over by whoever sent the file.
940
932
  *
941
933
  * The first of the two passes {@link parseSvgSketch} is made of, exported so a
942
934
  * framework that recognises part of a picture reads the SAME sanitised tree
943
935
  * the sketch walks (ADR 0032 §2, §7) — never the source.
944
936
  */
945
937
  export function sanitizeSvg(source, notes) {
946
- const doc = new DOMParser().parseFromString(source, 'image/svg+xml');
947
- const error = doc.querySelector('parsererror');
948
- if (error) {
949
- // The FIRST line only. Chromium's `parsererror` is a whole rendered
950
- // document — a heading, the message, then the offending source line with a
951
- // caret under it — and pouring that into a toast makes the one sentence
952
- // that matters unreadable.
953
- const [summary = ''] = (error.textContent ?? '')
954
- .split('\n')
955
- .map(line => line.trim())
956
- .filter(line => line.length > 0);
957
- const detail = summary.slice(0, 200);
938
+ const document = readXml(source, { conformant: true });
939
+ const [complaint] = document.notes;
940
+ if (complaint !== undefined) {
941
+ const detail = complaint.slice(0, 200);
958
942
  throw new InterchangeImportError(`This file is not well-formed XML, so no drawing can be read out of it: ${detail}`, {
959
943
  messageKey: SVG_SKETCH_ERROR_MALFORMED_XML[0],
960
944
  messageParams: { detail },
961
945
  });
962
946
  }
963
- if (nameOf(doc.documentElement) !== 'svg') {
964
- const tag = doc.documentElement?.localName ?? 'nothing';
947
+ if (document.root?.local.toLowerCase() !== 'svg') {
948
+ const tag = document.root?.local ?? 'nothing';
965
949
  throw new InterchangeImportError(`An SVG opens on <svg>; this one opens on <${tag}>.`, { messageKey: SVG_SKETCH_ERROR_NOT_SVG[0], messageParams: { tag } });
966
950
  }
967
- const clean = DOMPurify.sanitize(source, {
968
- USE_PROFILES: { svg: true },
969
- RETURN_DOM: true,
970
- });
951
+ const { root, report } = sanitizeSvgNode(document.root);
971
952
  // …and then say what the sanitizer took, which is NOT the same list as what
972
953
  // the walk below skips. `<use>` and `<foreignObject>` never reach the walk at
973
- // all — they are two of the SVG format's genuine attack surfaces and DOMPurify
974
- // removes them outright — so a drawing built out of symbol instances arrives
975
- // empty. Without this the only remark would be "nothing was recognised",
976
- // which is true and useless. `DOMPurify.removed` is repopulated by each call
977
- // and is read here immediately, before anything else can sanitize.
978
- for (const entry of DOMPurify.removed) {
979
- const removed = entry;
980
- if (removed.element) {
954
+ // all — they are two of the SVG format's genuine attack surfaces — so a
955
+ // drawing built out of symbol instances arrives empty. Without this the only
956
+ // remark would be "nothing was recognised", which is true and useless.
957
+ for (const removal of report.removed) {
958
+ if ('element' in removal) {
981
959
  // The node's OWN spelling, because `foreignObject` is camel-cased in the
982
960
  // format and a remark that called it `foreignobject` would send its
983
961
  // reader looking for something the spec does not have. The dedup key is
984
962
  // folded; the sentence is not.
985
- const name = removed.element.nodeName ?? '';
986
- // The sanitizer parses into an HTML document and reports unwinding its
987
- // own wrapper as a removal. Nobody wrote a `<body>` into an `.svg`, and a
988
- // remark about one would be this reader telling the user about its own
989
- // plumbing.
990
- if (PARSER_SCAFFOLDING.has(name.toLowerCase()))
991
- continue;
963
+ const name = removal.element;
992
964
  notes.once(`removed:${name.toLowerCase()}`, `\`<${name}>\` was removed while sanitizing the file — it is one of the constructs an SVG can carry code in — so nothing it drew was imported.`, SVG_SKETCH_REMOVED, { name }, name);
993
965
  }
994
- else if (removed.attribute) {
966
+ else {
995
967
  // Worded for what this actually is, which is broader than "unsafe": the
996
- // sanitizer's allow-list drops an event handler AND a `requiredFeatures`,
997
- // and calling the second one dangerous would be this reader accusing a
968
+ // allow-list drops an event handler AND a `requiredFeatures`, and
969
+ // calling the second one dangerous would be this reader accusing a
998
970
  // perfectly ordinary file of something.
999
971
  notes.once('removed-attribute', 'Attributes outside the safe SVG drawing vocabulary — event handlers, script URLs, and anything else the sanitizer does not know — were removed before the file was read.', SVG_SKETCH_REMOVED_ATTRIBUTE);
1000
972
  }
1001
973
  }
1002
- // No cast: `RETURN_DOM: true` in the literal selects DOMPurify's typed
1003
- // overload, which answers an `HTMLElement` — the sanitized body wrapper.
1004
- const root = childElements(clean).find(child => nameOf(child) === 'svg');
974
+ if (report.tooDeep) {
975
+ notes.once('too-deep', `Elements nested more than ${MAX_SVG_DEPTH} levels deep were not read.`, SVG_SKETCH_TOO_DEEP, { depth: MAX_SVG_DEPTH });
976
+ }
1005
977
  if (!root) {
1006
978
  throw new InterchangeImportError(SVG_SKETCH_ERROR_SANITIZED_AWAY[1], {
1007
979
  messageKey: SVG_SKETCH_ERROR_SANITIZED_AWAY[0],
@@ -0,0 +1,98 @@
1
+ import { type XmlDocument, type XmlNode, type XmlReadOptions } from './xml-reader.js';
2
+ /**
3
+ * A read-only, DOM-SHAPED view over {@link readXml}'s tree, with namespaces
4
+ * resolved — what the BPMN reader and the SVG sketch walk instead of a
5
+ * `DOMParser` document (ADR 0012, P3 as amended 2026-10-14).
6
+ *
7
+ * Both readers were written against the DOM and read only a small part of it:
8
+ * `localName`, `namespaceURI`, `attributes`, `getAttribute`, `children`,
9
+ * `childNodes`, `textContent`, `parentElement` and a few compound selectors.
10
+ * This class answers exactly that part, with the DOM's names and the DOM's
11
+ * answers for an XML document (case-sensitive names, `null` for a missing
12
+ * attribute or namespace), so the readers kept their logic and lost their
13
+ * global. It is not a DOM: nothing here mutates, and a member a reader does
14
+ * not use does not exist.
15
+ *
16
+ * Built with an explicit stack: the depth of a file is its author's choice.
17
+ */
18
+ export declare const XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
19
+ export declare const XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
20
+ /** One attribute, as the DOM's `Attr` names it. */
21
+ export interface XmlAttribute {
22
+ /** The qualified name as written: `xlink:href`, `fill`. */
23
+ name: string;
24
+ prefix: string | null;
25
+ localName: string;
26
+ /** Resolved; `null` for an unprefixed attribute, as XML namespaces say. */
27
+ namespaceURI: string | null;
28
+ value: string;
29
+ }
30
+ /** Character data or a comment, as the DOM's `Text` / `Comment` show it. */
31
+ export interface XmlCharacterNode {
32
+ /** 3 for text and CDATA, 8 for a comment, 7 for an instruction. */
33
+ nodeType: 3 | 7 | 8;
34
+ nodeValue: string;
35
+ }
36
+ export declare class XmlElement {
37
+ /** The reader's node, for a caller that wants its range or its content. */
38
+ readonly node: XmlNode;
39
+ readonly parentElement: XmlElement | null;
40
+ readonly namespaceURI: string | null;
41
+ readonly attributes: XmlAttribute[];
42
+ readonly nodeType = 1;
43
+ readonly children: XmlElement[];
44
+ readonly childNodes: (XmlElement | XmlCharacterNode)[];
45
+ constructor(
46
+ /** The reader's node, for a caller that wants its range or its content. */
47
+ node: XmlNode, parentElement: XmlElement | null, namespaceURI: string | null, attributes: XmlAttribute[]);
48
+ get localName(): string;
49
+ get prefix(): string | null;
50
+ /** The qualified name as written, which is what the DOM says for XML. */
51
+ get nodeName(): string;
52
+ get tagName(): string;
53
+ get firstElementChild(): XmlElement | null;
54
+ /** Every text and CDATA under this element, in order; comments excluded. */
55
+ get textContent(): string;
56
+ /** By qualified name, exactly as written — the DOM's rule for XML. */
57
+ getAttribute(name: string): string | null;
58
+ hasAttribute(name: string): boolean;
59
+ get classList(): {
60
+ contains(token: string): boolean;
61
+ };
62
+ /** Is `other` this element or one of its descendants? */
63
+ contains(other: XmlElement | null): boolean;
64
+ /** Every descendant (never this element), in document order. */
65
+ descendants(): XmlElement[];
66
+ matches(selectors: string): boolean;
67
+ querySelectorAll(selectors: string): XmlElement[];
68
+ querySelector(selectors: string): XmlElement | null;
69
+ private is;
70
+ }
71
+ /**
72
+ * The namespaces in scope during a depth-first walk, as one stack per prefix:
73
+ * {@link enter} and {@link exit} cost the element's own declarations, and
74
+ * {@link resolve} is one lookup. A copied map per declaring element was
75
+ * quadratic: a root declaring 2 000 prefixes over thousands of
76
+ * `<g xmlns:q="x"/>` took 28 s and 284 MiB (security review, 2026-10-14).
77
+ */
78
+ export declare class XmlNamespaceScope {
79
+ private readonly bound;
80
+ enter(node: XmlNode): void;
81
+ exit(node: XmlNode): void;
82
+ /** `null` when unbound — or undeclared by `xmlns=""` (Namespaces in XML §6.2). */
83
+ resolve(prefix: string): string | null;
84
+ /** The node's attributes, resolved in this scope (entered first). */
85
+ attributesOf(node: XmlNode): XmlAttribute[];
86
+ }
87
+ /**
88
+ * The element view of a node the reader produced, namespaces resolved from the
89
+ * `xmlns` declarations on the way down (`xml` is bound, as XML says it is).
90
+ * An unbound prefix resolves to `null`, which is what a reader then refuses or
91
+ * ignores: this function never guesses a namespace.
92
+ */
93
+ export declare function xmlElementOf(root: XmlNode): XmlElement;
94
+ /** {@link readXml}, then {@link xmlElementOf} on its first root. */
95
+ export declare function readXmlElement(source: string, options?: XmlReadOptions): {
96
+ document: XmlDocument;
97
+ root: XmlElement | undefined;
98
+ };
@@ -0,0 +1,261 @@
1
+ import { readXml, } from './xml-reader.js';
2
+ /**
3
+ * A read-only, DOM-SHAPED view over {@link readXml}'s tree, with namespaces
4
+ * resolved — what the BPMN reader and the SVG sketch walk instead of a
5
+ * `DOMParser` document (ADR 0012, P3 as amended 2026-10-14).
6
+ *
7
+ * Both readers were written against the DOM and read only a small part of it:
8
+ * `localName`, `namespaceURI`, `attributes`, `getAttribute`, `children`,
9
+ * `childNodes`, `textContent`, `parentElement` and a few compound selectors.
10
+ * This class answers exactly that part, with the DOM's names and the DOM's
11
+ * answers for an XML document (case-sensitive names, `null` for a missing
12
+ * attribute or namespace), so the readers kept their logic and lost their
13
+ * global. It is not a DOM: nothing here mutates, and a member a reader does
14
+ * not use does not exist.
15
+ *
16
+ * Built with an explicit stack: the depth of a file is its author's choice.
17
+ */
18
+ export const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
19
+ export const XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/';
20
+ /** `Node.ELEMENT_NODE`, spelled. */
21
+ const ELEMENT_NODE = 1;
22
+ const COMPOUND = /^([A-Za-z][\w-]*)?((?:[.#][\w-]+|\[[\w:-]+(?:="[^"]*")?\])*)$/;
23
+ const PART = /\.([\w-]+)|#([\w-]+)|\[([\w:-]+)(?:="([^"]*)")?\]/g;
24
+ /**
25
+ * A selector list without combinators — the only shape the readers write.
26
+ * Anything else is a programming error in a reader, and says so.
27
+ */
28
+ function parseSelectors(selectors) {
29
+ return selectors.split(',').map(raw => {
30
+ const text = raw.trim();
31
+ const match = COMPOUND.exec(text);
32
+ if (!match || text.length === 0) {
33
+ throw new Error(`XmlElement does not support the selector "${text}"`);
34
+ }
35
+ const compound = { type: match[1], classes: [], attrs: [] };
36
+ for (const part of (match[2] ?? '').matchAll(PART)) {
37
+ if (part[1] !== undefined)
38
+ compound.classes.push(part[1]);
39
+ else if (part[2] !== undefined)
40
+ compound.attrs.push({ name: 'id', value: part[2] });
41
+ else
42
+ compound.attrs.push({ name: part[3], value: part[4] });
43
+ }
44
+ return compound;
45
+ });
46
+ }
47
+ export class XmlElement {
48
+ constructor(
49
+ /** The reader's node, for a caller that wants its range or its content. */
50
+ node, parentElement, namespaceURI, attributes) {
51
+ this.node = node;
52
+ this.parentElement = parentElement;
53
+ this.namespaceURI = namespaceURI;
54
+ this.attributes = attributes;
55
+ this.nodeType = ELEMENT_NODE;
56
+ this.children = [];
57
+ this.childNodes = [];
58
+ }
59
+ get localName() {
60
+ return this.node.local;
61
+ }
62
+ get prefix() {
63
+ return this.node.prefix === '' ? null : this.node.prefix;
64
+ }
65
+ /** The qualified name as written, which is what the DOM says for XML. */
66
+ get nodeName() {
67
+ return this.node.name;
68
+ }
69
+ get tagName() {
70
+ return this.node.name;
71
+ }
72
+ get firstElementChild() {
73
+ return this.children[0] ?? null;
74
+ }
75
+ /** Every text and CDATA under this element, in order; comments excluded. */
76
+ get textContent() {
77
+ const parts = [];
78
+ const stack = [
79
+ ...this.childNodes,
80
+ ].reverse();
81
+ while (stack.length > 0) {
82
+ const next = stack.pop();
83
+ if (next instanceof XmlElement) {
84
+ for (let index = next.childNodes.length - 1; index >= 0; index -= 1) {
85
+ stack.push(next.childNodes[index]);
86
+ }
87
+ }
88
+ else if (next.nodeType === 3) {
89
+ parts.push(next.nodeValue);
90
+ }
91
+ }
92
+ return parts.join('');
93
+ }
94
+ /** By qualified name, exactly as written — the DOM's rule for XML. */
95
+ getAttribute(name) {
96
+ // Own properties only: an attribute named `constructor` is a string, and
97
+ // a missing one is never `Object.prototype`'s.
98
+ return Object.hasOwn(this.node.attrs, name) ? this.node.attrs[name] : null;
99
+ }
100
+ hasAttribute(name) {
101
+ return Object.hasOwn(this.node.attrs, name);
102
+ }
103
+ get classList() {
104
+ const tokens = (this.getAttribute('class') ?? '').split(/\s+/);
105
+ return { contains: token => tokens.includes(token) };
106
+ }
107
+ /** Is `other` this element or one of its descendants? */
108
+ contains(other) {
109
+ for (let at = other; at; at = at.parentElement) {
110
+ if (at === this)
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+ /** Every descendant (never this element), in document order. */
116
+ descendants() {
117
+ const found = [];
118
+ const stack = [...this.children].reverse();
119
+ while (stack.length > 0) {
120
+ const next = stack.pop();
121
+ found.push(next);
122
+ for (let index = next.children.length - 1; index >= 0; index -= 1) {
123
+ stack.push(next.children[index]);
124
+ }
125
+ }
126
+ return found;
127
+ }
128
+ matches(selectors) {
129
+ return parseSelectors(selectors).some(compound => this.is(compound));
130
+ }
131
+ querySelectorAll(selectors) {
132
+ const list = parseSelectors(selectors);
133
+ return this.descendants().filter(element => list.some(compound => element.is(compound)));
134
+ }
135
+ querySelector(selectors) {
136
+ return this.querySelectorAll(selectors)[0] ?? null;
137
+ }
138
+ is(compound) {
139
+ if (compound.type !== undefined && compound.type !== this.localName) {
140
+ return false;
141
+ }
142
+ if (compound.classes.some(token => !this.classList.contains(token))) {
143
+ return false;
144
+ }
145
+ return compound.attrs.every(({ name, value }) => {
146
+ const actual = this.getAttribute(name);
147
+ return actual !== null && (value === undefined || actual === value);
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * The namespaces in scope during a depth-first walk, as one stack per prefix:
153
+ * {@link enter} and {@link exit} cost the element's own declarations, and
154
+ * {@link resolve} is one lookup. A copied map per declaring element was
155
+ * quadratic: a root declaring 2 000 prefixes over thousands of
156
+ * `<g xmlns:q="x"/>` took 28 s and 284 MiB (security review, 2026-10-14).
157
+ */
158
+ export class XmlNamespaceScope {
159
+ constructor() {
160
+ this.bound = new Map([
161
+ ['xml', [XML_NAMESPACE]],
162
+ ]);
163
+ }
164
+ enter(node) {
165
+ for (const [prefix, value] of declarationsOf(node)) {
166
+ const stack = this.bound.get(prefix);
167
+ if (stack)
168
+ stack.push(value);
169
+ else
170
+ this.bound.set(prefix, [value]);
171
+ }
172
+ }
173
+ exit(node) {
174
+ for (const [prefix] of declarationsOf(node))
175
+ this.bound.get(prefix)?.pop();
176
+ }
177
+ /** `null` when unbound — or undeclared by `xmlns=""` (Namespaces in XML §6.2). */
178
+ resolve(prefix) {
179
+ const stack = this.bound.get(prefix);
180
+ return stack?.at(-1) || null;
181
+ }
182
+ /** The node's attributes, resolved in this scope (entered first). */
183
+ attributesOf(node) {
184
+ return Object.entries(node.attrs).map(([name, value]) => {
185
+ const colon = name.indexOf(':');
186
+ const prefix = colon < 0 ? null : name.slice(0, colon);
187
+ const localName = colon < 0 ? name : name.slice(colon + 1);
188
+ let namespaceURI = null;
189
+ if (name === 'xmlns' || prefix === 'xmlns')
190
+ namespaceURI = XMLNS_NAMESPACE;
191
+ else if (prefix !== null)
192
+ namespaceURI = this.resolve(prefix);
193
+ return { name, prefix, localName, namespaceURI, value };
194
+ });
195
+ }
196
+ }
197
+ function* declarationsOf(node) {
198
+ for (const name in node.attrs) {
199
+ if (name === 'xmlns')
200
+ yield ['', node.attrs[name]];
201
+ else if (name.startsWith('xmlns:'))
202
+ yield [name.slice(6), node.attrs[name]];
203
+ }
204
+ }
205
+ /**
206
+ * The element view of a node the reader produced, namespaces resolved from the
207
+ * `xmlns` declarations on the way down (`xml` is bound, as XML says it is).
208
+ * An unbound prefix resolves to `null`, which is what a reader then refuses or
209
+ * ignores: this function never guesses a namespace.
210
+ */
211
+ export function xmlElementOf(root) {
212
+ const scope = new XmlNamespaceScope();
213
+ const make = (node, parent) => {
214
+ scope.enter(node);
215
+ return new XmlElement(node, parent, scope.resolve(node.prefix), scope.attributesOf(node));
216
+ };
217
+ // A node assembled by hand has no `content`; its children are all it has.
218
+ const frameOf = (node, element) => ({
219
+ node,
220
+ element,
221
+ entries: node.content ?? node.children,
222
+ index: 0,
223
+ });
224
+ const top = make(root, null);
225
+ const stack = [frameOf(root, top)];
226
+ while (stack.length > 0) {
227
+ const frame = stack[stack.length - 1];
228
+ if (frame.index >= frame.entries.length) {
229
+ scope.exit(frame.node);
230
+ stack.pop();
231
+ continue;
232
+ }
233
+ const entry = frame.entries[frame.index];
234
+ frame.index += 1;
235
+ const { element } = frame;
236
+ if (!('kind' in entry)) {
237
+ const child = make(entry, element);
238
+ element.children.push(child);
239
+ element.childNodes.push(child);
240
+ stack.push(frameOf(entry, child));
241
+ }
242
+ else if (entry.kind === 'text') {
243
+ element.childNodes.push({ nodeType: 3, nodeValue: entry.value });
244
+ }
245
+ else if (entry.kind === 'comment') {
246
+ element.childNodes.push({ nodeType: 8, nodeValue: entry.value });
247
+ }
248
+ else {
249
+ element.childNodes.push({ nodeType: 7, nodeValue: entry.data });
250
+ }
251
+ }
252
+ return top;
253
+ }
254
+ /** {@link readXml}, then {@link xmlElementOf} on its first root. */
255
+ export function readXmlElement(source, options = {}) {
256
+ const document = readXml(source, options);
257
+ return {
258
+ document,
259
+ root: document.root ? xmlElementOf(document.root) : undefined,
260
+ };
261
+ }