@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.
- package/dist/_pkgs/affine-block-attachment/asset-picker.d.ts +24 -0
- package/dist/_pkgs/affine-block-attachment/asset-picker.js +87 -0
- package/dist/_pkgs/affine-block-attachment/configs/slash-menu.js +3 -20
- package/dist/_pkgs/affine-block-attachment/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-attachment/index.js +1 -0
- package/dist/_pkgs/affine-block-attachment/utils.d.ts +9 -1
- package/dist/_pkgs/affine-block-attachment/utils.js +9 -0
- package/dist/_pkgs/affine-block-bookmark/adapters/markdown/markdown.js +14 -1
- package/dist/_pkgs/affine-block-bookmark/adapters/notion-html.js +5 -1
- package/dist/_pkgs/affine-block-database/adapters/markdown.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/html.js +5 -3
- package/dist/_pkgs/affine-block-embed/common/adapters/markdown.js +11 -0
- package/dist/_pkgs/affine-block-frame/frame-template.d.ts +58 -0
- package/dist/_pkgs/affine-block-frame/frame-template.js +207 -0
- package/dist/_pkgs/affine-block-frame/frame-toolbar.js +19 -3
- package/dist/_pkgs/affine-block-frame/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-frame/index.js +1 -0
- package/dist/_pkgs/affine-block-frame/translations.d.ts +7 -0
- package/dist/_pkgs/affine-block-frame/translations.js +16 -1
- package/dist/_pkgs/affine-block-frame/view.js +6 -0
- package/dist/_pkgs/affine-block-image/asset-picker.d.ts +63 -0
- package/dist/_pkgs/affine-block-image/asset-picker.js +183 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.d.ts +5 -0
- package/dist/_pkgs/affine-block-image/commands/insert-images.js +8 -8
- package/dist/_pkgs/affine-block-image/components/page-image-block.d.ts +11 -0
- package/dist/_pkgs/affine-block-image/components/page-image-block.js +81 -12
- package/dist/_pkgs/affine-block-image/configs/replace-action.d.ts +9 -0
- package/dist/_pkgs/affine-block-image/configs/replace-action.js +27 -0
- package/dist/_pkgs/affine-block-image/configs/toolbar.js +6 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.d.ts +46 -0
- package/dist/_pkgs/affine-block-image/crop/crop-controller.js +104 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.d.ts +70 -0
- package/dist/_pkgs/affine-block-image/crop/crop-math.js +141 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.d.ts +57 -0
- package/dist/_pkgs/affine-block-image/crop/crop-overlay.js +343 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.d.ts +12 -0
- package/dist/_pkgs/affine-block-image/crop/crop-toolbar.js +78 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.d.ts +16 -0
- package/dist/_pkgs/affine-block-image/crop/write-crop.js +57 -0
- package/dist/_pkgs/affine-block-image/edgeless-clipboard-config.js +4 -1
- package/dist/_pkgs/affine-block-image/effects.js +2 -0
- package/dist/_pkgs/affine-block-image/image-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-block.js +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.d.ts +2 -0
- package/dist/_pkgs/affine-block-image/image-edgeless-block.js +36 -8
- package/dist/_pkgs/affine-block-image/image-resize-manager.js +15 -0
- package/dist/_pkgs/affine-block-image/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-image/index.js +1 -0
- package/dist/_pkgs/affine-block-image/translations.d.ts +15 -0
- package/dist/_pkgs/affine-block-image/translations.js +43 -0
- package/dist/_pkgs/affine-block-image/utils.d.ts +13 -0
- package/dist/_pkgs/affine-block-image/utils.js +10 -1
- package/dist/_pkgs/affine-block-root/clipboard/page-clipboard.js +5 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.d.ts +2 -0
- package/dist/_pkgs/affine-block-root/commands/page-export-commands.js +71 -0
- package/dist/_pkgs/affine-block-root/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-root/index.js +1 -0
- package/dist/_pkgs/affine-block-root/view.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.d.ts +30 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/capability.js +44 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.d.ts +26 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/colors.js +82 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/command.js +91 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.d.ts +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/index.js +3 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.d.ts +50 -0
- package/dist/_pkgs/affine-block-surface/extensions/drawio-export/writer.js +732 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.d.ts +12 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/draw-image-block.js +15 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +7 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +60 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +2 -2
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.d.ts +6 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/marker-version.js +8 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +28 -1
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +66 -6
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +2 -7
- package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +2 -6
- package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/index.js +2 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.d.ts +84 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-export.js +150 -0
- package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +24 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +23 -2
- package/dist/_pkgs/affine-block-surface/extensions/interchange.js +4 -1
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.d.ts +18 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend-role.js +20 -0
- package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +2 -19
- package/dist/_pkgs/affine-block-surface/extensions/legend.js +2 -18
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.d.ts +67 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sanitize.js +235 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch-translations.js +9 -0
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.d.ts +17 -13
- package/dist/_pkgs/affine-block-surface/extensions/svg-sketch.js +52 -80
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.d.ts +98 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-element.js +261 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.d.ts +238 -0
- package/dist/_pkgs/affine-block-surface/extensions/xml-reader.js +570 -0
- package/dist/_pkgs/affine-block-surface/framework-background/def.js +1 -1
- package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
- package/dist/_pkgs/affine-block-surface/index.js +1 -0
- package/dist/_pkgs/affine-block-surface/interchange-headless.d.ts +29 -0
- package/dist/_pkgs/affine-block-surface/interchange-headless.js +32 -0
- package/dist/_pkgs/affine-block-surface/view.js +22 -2
- package/dist/_pkgs/affine-block-table/adapters/utils.js +24 -1
- package/dist/_pkgs/affine-gfx-note/toolbar/note-menu.js +5 -8
- package/dist/_pkgs/affine-gfx-template/contribute.d.ts +14 -4
- package/dist/_pkgs/affine-gfx-template/contribute.js +47 -6
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.d.ts +7 -2
- package/dist/_pkgs/affine-gfx-template/toolbar/builtin-templates.js +14 -40
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.d.ts +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/resolve-name.js +1 -1
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.d.ts +4 -4
- package/dist/_pkgs/affine-gfx-template/toolbar/template-panel.js +9 -9
- package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +2 -4
- package/dist/_pkgs/affine-gfx-template/translations.d.ts +5 -0
- package/dist/_pkgs/affine-gfx-template/translations.js +9 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/inline-delta.js +6 -0
- package/dist/_pkgs/affine-inline-footnote/adapters/markdown/markdown-inline.js +7 -0
- package/dist/_pkgs/affine-inline-link/adapters/html/html-inline.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/html/inline-delta.js +3 -1
- package/dist/_pkgs/affine-inline-link/adapters/markdown/inline-delta.js +4 -1
- package/dist/_pkgs/affine-inline-link/adapters/notion-html/html-inline.js +2 -1
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.d.ts +7 -0
- package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +37 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.d.ts +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/index.js +1 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.d.ts +22 -0
- package/dist/_pkgs/affine-inline-link/link-node/propose-views.js +59 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.d.ts +48 -0
- package/dist/_pkgs/affine-model/blocks/image/image-crop.js +78 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.d.ts +6 -0
- package/dist/_pkgs/affine-model/blocks/image/image-model.js +2 -0
- package/dist/_pkgs/affine-model/blocks/image/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/blocks/image/index.js +1 -0
- package/dist/_pkgs/affine-model/consts/shape.d.ts +2 -0
- package/dist/_pkgs/affine-model/consts/shape.js +5 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.d.ts +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/consts.js +31 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/bpmn/pool.d.ts +0 -25
- package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -25
- package/dist/_pkgs/affine-model/elements/shape/shape.d.ts +0 -2
- package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -2
- package/dist/_pkgs/affine-model/elements/uml/consts.d.ts +133 -0
- package/dist/_pkgs/affine-model/elements/uml/consts.js +107 -0
- package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +1 -79
- package/dist/_pkgs/affine-model/elements/uml/diagram.js +1 -53
- package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +0 -15
- package/dist/_pkgs/affine-model/elements/uml/fragment.js +1 -15
- package/dist/_pkgs/affine-model/elements/uml/index.d.ts +1 -0
- package/dist/_pkgs/affine-model/elements/uml/index.js +1 -0
- package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +0 -20
- package/dist/_pkgs/affine-model/elements/uml/partition.js +1 -20
- package/dist/_pkgs/affine-model/elements/uml/region.d.ts +0 -11
- package/dist/_pkgs/affine-model/elements/uml/region.js +1 -11
- package/dist/_pkgs/affine-model/headless.d.ts +19 -0
- package/dist/_pkgs/affine-model/headless.js +19 -0
- package/dist/_pkgs/affine-shared/headless.d.ts +10 -0
- package/dist/_pkgs/affine-shared/headless.js +10 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.d.ts +63 -0
- package/dist/_pkgs/affine-shared/services/asset-picker-service.js +20 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.d.ts +83 -0
- package/dist/_pkgs/affine-shared/services/frame-template-service.js +17 -0
- package/dist/_pkgs/affine-shared/services/index.d.ts +3 -0
- package/dist/_pkgs/affine-shared/services/index.js +3 -0
- package/dist/_pkgs/affine-shared/services/thread-marks-service.d.ts +69 -0
- package/dist/_pkgs/affine-shared/services/thread-marks-service.js +20 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +6 -0
- package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +10 -0
- package/dist/_pkgs/affine-widget-drag-handle/watchers/drag-event-watcher.js +8 -3
- package/dist/_pkgs/affine-widget-keyboard-toolbar/config.js +4 -16
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/html.js +25 -24
- package/dist/_pkgs/affine-widget-linked-doc/transformers/index.d.ts +1 -1
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +7 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +26 -25
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +23 -0
- package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +14 -0
- package/dist/_pkgs/affine-widget-thread-marks/effects.d.ts +1 -0
- package/dist/_pkgs/affine-widget-thread-marks/effects.js +4 -0
- package/dist/_pkgs/affine-widget-thread-marks/excerpt.d.ts +19 -0
- package/dist/_pkgs/affine-widget-thread-marks/excerpt.js +68 -0
- package/dist/_pkgs/affine-widget-thread-marks/index.d.ts +5 -0
- package/dist/_pkgs/affine-widget-thread-marks/index.js +8 -0
- package/dist/_pkgs/affine-widget-thread-marks/toolbar.d.ts +49 -0
- package/dist/_pkgs/affine-widget-thread-marks/toolbar.js +108 -0
- package/dist/_pkgs/affine-widget-thread-marks/translations.d.ts +9 -0
- package/dist/_pkgs/affine-widget-thread-marks/translations.js +11 -0
- package/dist/_pkgs/affine-widget-thread-marks/view.d.ts +12 -0
- package/dist/_pkgs/affine-widget-thread-marks/view.js +25 -0
- package/dist/_pkgs/affine-widget-thread-marks/widget.d.ts +51 -0
- package/dist/_pkgs/affine-widget-thread-marks/widget.js +214 -0
- package/dist/commands.js +5 -2
- package/dist/extensions/view.js +2 -0
- package/dist/host-panels.d.ts +24 -1
- package/dist/host-panels.js +61 -10
- package/dist/interchange.d.ts +1 -0
- package/dist/interchange.js +1 -0
- package/dist/model/headless.d.ts +1 -0
- package/dist/model/headless.js +1 -0
- package/dist/shared/headless.d.ts +1 -0
- package/dist/shared/headless.js +1 -0
- package/dist/translations.js +10 -1
- package/package.json +13 -1
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tolerant XML READER — the inverse of UML's `xml.ts` writer, and the one
|
|
3
|
+
* thing an importer cannot do without. Read by the XMI and draw.io importers,
|
|
4
|
+
* by BPMN and by the SVG sketch (through `xml-element.ts`), all four servable
|
|
5
|
+
* from the editor-free `./interchange` entry.
|
|
6
|
+
*
|
|
7
|
+
* ## Why not `DOMParser`
|
|
8
|
+
*
|
|
9
|
+
* BPMN and the SVG sketch used to call it, and the P0 import spike measured
|
|
10
|
+
* the cost on a server: a DOM to install (jsdom, 6 MB, behind `vm`), and a
|
|
11
|
+
* BPMN read that grew quadratically on it. An interchange parser is a PURE function of a string (`docs/adr/0012` P3) — the
|
|
12
|
+
* same function an editor command, a unit test and labre-mcp all call — and
|
|
13
|
+
* `DOMParser` is a DOM global that exists in exactly one of those three. A
|
|
14
|
+
* reader that needs `happy-dom` to be loaded is a reader with an environment in
|
|
15
|
+
* its signature.
|
|
16
|
+
*
|
|
17
|
+
* So: two hundred lines of scanner, and no global in sight.
|
|
18
|
+
*
|
|
19
|
+
* ## Tolerant, and what that costs
|
|
20
|
+
*
|
|
21
|
+
* **It never throws.** A file that stops mid-element, a close tag that names
|
|
22
|
+
* the wrong element, an attribute with no quotes, an entity nobody declared —
|
|
23
|
+
* every one of them produces a tree with a NOTE beside it rather than an
|
|
24
|
+
* exception. That is not sloppiness, it is the job: the files this reader is
|
|
25
|
+
* handed were written by Papyrus, StarUML, Enterprise Architect and a dozen
|
|
26
|
+
* scripts, and a user who exported a model from one of them is entitled to get
|
|
27
|
+
* back everything it said even when part of it is malformed. The alternative —
|
|
28
|
+
* one exception for one stray `&` — imports nothing at all.
|
|
29
|
+
*
|
|
30
|
+
* What it does NOT do is pretend to be a conformant parser. There is no DTD, no
|
|
31
|
+
* entity declaration, no namespace RESOLUTION (prefixes are kept exactly as the
|
|
32
|
+
* file spelled them — see {@link XmlNode.prefix}), no attribute-value
|
|
33
|
+
* normalization beyond the character references XML 1.0 §3.3.3 makes
|
|
34
|
+
* mandatory. Every one of those is a feature an XMI file in the wild does not
|
|
35
|
+
* use, and each would be a piece of surface with nothing behind it.
|
|
36
|
+
*
|
|
37
|
+
* ## Verbatim means the source's own bytes
|
|
38
|
+
*
|
|
39
|
+
* Every node records the byte RANGE it was read from ({@link XmlNode.range}),
|
|
40
|
+
* and {@link xmlFragmentOf} hands back that slice of the original string. So a
|
|
41
|
+
* quarantined fragment (ADR 0012 D5) is the file's own text — its comments, its
|
|
42
|
+
* whitespace, its attribute order, its prefixes — and not this reader's opinion
|
|
43
|
+
* of it. {@link serializeXmlNode} re-serializes from the tree instead, and is
|
|
44
|
+
* the fallback for a node assembled rather than read.
|
|
45
|
+
*
|
|
46
|
+
* ## Bounded, because a server reads it too
|
|
47
|
+
*
|
|
48
|
+
* The one exception to "never throws": a document past {@link XML_MAX_NODES}
|
|
49
|
+
* nodes or {@link XML_MAX_CHARACTERS} characters is refused with an
|
|
50
|
+
* {@link InterchangeImportError} carrying a static key, at the node that
|
|
51
|
+
* crosses the line and not after the tree is built. Each node costs about two
|
|
52
|
+
* hundred bytes of heap, and a 1.5 KiB `.drawio` inflates to a million of
|
|
53
|
+
* them (the 2026-10-14 security review: 3.2 s and 223 MiB before the cap). The
|
|
54
|
+
* scan is linear in the source otherwise: an orphan close tag is answered from
|
|
55
|
+
* a count of open names, not by searching the stack.
|
|
56
|
+
*
|
|
57
|
+
* Pure: a string in, a tree out. No `std`, no DOM, no clock.
|
|
58
|
+
*/
|
|
59
|
+
import { InterchangeImportError } from './interchange.js';
|
|
60
|
+
/**
|
|
61
|
+
* Safe for a function with 256 MiB and two seconds: the largest file of the
|
|
62
|
+
* corpus holds a few thousand nodes, a reading at the cap about 40 MiB.
|
|
63
|
+
*/
|
|
64
|
+
export const XML_MAX_NODES = 200_000;
|
|
65
|
+
/** Notes written in full; the rest are counted in one last note. */
|
|
66
|
+
export const XML_MAX_NOTES = 100;
|
|
67
|
+
/** A name as a note quotes it: its first 64 characters. */
|
|
68
|
+
function shown(name) {
|
|
69
|
+
return name.length > 64 ? `${name.slice(0, 64)}…` : name;
|
|
70
|
+
}
|
|
71
|
+
/** 2 MiB of decoded characters, well past any drawing the canvas can show. */
|
|
72
|
+
export const XML_MAX_CHARACTERS = 2 * 1024 * 1024;
|
|
73
|
+
/** The reader's one refusal; static, so a server can hand the key on. */
|
|
74
|
+
export const XML_READ_TOO_LARGE = [
|
|
75
|
+
'com.labre.interchange.xml.too-large',
|
|
76
|
+
'This file holds more than an import accepts, so it was not read.',
|
|
77
|
+
];
|
|
78
|
+
/* ── Entities ─────────────────────────────────────────────────────────── */
|
|
79
|
+
/** The five XML 1.0 §4.6 predefined entities, and nothing else. */
|
|
80
|
+
const NAMED_ENTITIES = {
|
|
81
|
+
amp: '&',
|
|
82
|
+
lt: '<',
|
|
83
|
+
gt: '>',
|
|
84
|
+
quot: '"',
|
|
85
|
+
apos: "'",
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Character data as its characters — the five named entities and any numeric
|
|
89
|
+
* reference.
|
|
90
|
+
*
|
|
91
|
+
* An entity this reader does not know (` `, a DTD-declared one) is left
|
|
92
|
+
* EXACTLY as it was written, unresolved and unannounced. Two reasons, and the
|
|
93
|
+
* second is the one that matters: resolving it would need the DTD the file
|
|
94
|
+
* does not ship, and dropping it would lose a character; leaving the reference
|
|
95
|
+
* standing means a later re-serialization writes back what the file said, which
|
|
96
|
+
* is the promise `xmlFragmentOf` makes and this keeps true for text as well.
|
|
97
|
+
*/
|
|
98
|
+
export function decodeXmlText(value) {
|
|
99
|
+
if (!value.includes('&'))
|
|
100
|
+
return value;
|
|
101
|
+
return value.replaceAll(/&(#x[0-9a-fA-F]+|#\d+|[A-Za-z][\w.-]*);/g, (match, reference) => {
|
|
102
|
+
if (reference.startsWith('#')) {
|
|
103
|
+
const hex = reference[1] === 'x' || reference[1] === 'X';
|
|
104
|
+
const code = Number.parseInt(hex ? reference.slice(2) : reference.slice(1), hex ? 16 : 10);
|
|
105
|
+
// Out of Unicode's range is a reference no character answers, and
|
|
106
|
+
// `String.fromCodePoint` THROWS on one — which is the single place this
|
|
107
|
+
// reader could have raised. It hands back the reference instead.
|
|
108
|
+
return Number.isInteger(code) && code >= 0 && code <= 0x10ffff
|
|
109
|
+
? String.fromCodePoint(code)
|
|
110
|
+
: match;
|
|
111
|
+
}
|
|
112
|
+
return NAMED_ENTITIES[reference] ?? match;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/* ── The scanner ──────────────────────────────────────────────────────── */
|
|
116
|
+
/** A name character, generously: XML's NameChar minus the exotic ranges. */
|
|
117
|
+
const NAME_CHAR = /[\w.:_\-À-]/;
|
|
118
|
+
/** The prefix and the local half of a qualified name. */
|
|
119
|
+
function splitName(name) {
|
|
120
|
+
const colon = name.indexOf(':');
|
|
121
|
+
return colon < 0
|
|
122
|
+
? { prefix: '', local: name }
|
|
123
|
+
: { prefix: name.slice(0, colon), local: name.slice(colon + 1) };
|
|
124
|
+
}
|
|
125
|
+
/** A node with no children and no text, ready to be filled. */
|
|
126
|
+
function nodeOf(name, start) {
|
|
127
|
+
const { prefix, local } = splitName(name);
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
prefix,
|
|
131
|
+
local,
|
|
132
|
+
// No prototype: an attribute may be called `__proto__` or `constructor`.
|
|
133
|
+
attrs: Object.create(null),
|
|
134
|
+
children: [],
|
|
135
|
+
range: [start, start],
|
|
136
|
+
content: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Read a document. Throws only past its size caps; see the header.
|
|
141
|
+
*
|
|
142
|
+
* The scan is a single left-to-right pass with an explicit stack, which is what
|
|
143
|
+
* makes the tolerance cheap: every recovery is "say what is wrong, and carry on
|
|
144
|
+
* from the next `<`", and there is no recursion to unwind. In conformant mode
|
|
145
|
+
* the first note STOPS the read: its callers refuse on any note, and a file
|
|
146
|
+
* built to raise twenty thousand of them must not get to.
|
|
147
|
+
*/
|
|
148
|
+
export function readXml(source, options = {}) {
|
|
149
|
+
const conformant = options.conformant === true;
|
|
150
|
+
const maxNodes = options.maxNodes ?? XML_MAX_NODES;
|
|
151
|
+
const maxCharacters = options.maxCharacters ?? XML_MAX_CHARACTERS;
|
|
152
|
+
const notes = [];
|
|
153
|
+
const roots = [];
|
|
154
|
+
const stack = [];
|
|
155
|
+
/** How many of each name are on `stack`: an orphan close costs O(1). */
|
|
156
|
+
const openNames = new Map();
|
|
157
|
+
/** Character data accumulated for the element currently open. */
|
|
158
|
+
const pending = [];
|
|
159
|
+
let cursor = 0;
|
|
160
|
+
let nodes = 0;
|
|
161
|
+
let spent = 0;
|
|
162
|
+
let stopped = false;
|
|
163
|
+
const spend = (length) => {
|
|
164
|
+
nodes += 1;
|
|
165
|
+
spent += length;
|
|
166
|
+
if (nodes > maxNodes || spent > maxCharacters) {
|
|
167
|
+
throw new InterchangeImportError(XML_READ_TOO_LARGE[1], {
|
|
168
|
+
messageKey: XML_READ_TOO_LARGE[0],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// A note quotes a name the file chose, and 140 KiB of faults used to make
|
|
173
|
+
// 20 000 notes and two billion characters, past what `JSON.stringify` takes
|
|
174
|
+
// (counter-review of 49aa247ce). So: a name is quoted to its first 64
|
|
175
|
+
// characters, and past XML_MAX_NOTES the rest are counted, not written.
|
|
176
|
+
let unlisted = 0;
|
|
177
|
+
const note = (line) => {
|
|
178
|
+
if (stopped)
|
|
179
|
+
return;
|
|
180
|
+
if (notes.length >= XML_MAX_NOTES) {
|
|
181
|
+
unlisted += 1;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
spend(0);
|
|
185
|
+
notes.push(line);
|
|
186
|
+
stopped = conformant;
|
|
187
|
+
};
|
|
188
|
+
const enter = (node) => {
|
|
189
|
+
stack.push(node);
|
|
190
|
+
openNames.set(node.name, (openNames.get(node.name) ?? 0) + 1);
|
|
191
|
+
};
|
|
192
|
+
const leave = () => {
|
|
193
|
+
const node = stack.pop();
|
|
194
|
+
openNames.set(node.name, (openNames.get(node.name) ?? 1) - 1);
|
|
195
|
+
return node;
|
|
196
|
+
};
|
|
197
|
+
/** Character data, kept for {@link XmlNode.text} and in order in `content`. */
|
|
198
|
+
const characters = (value) => {
|
|
199
|
+
if (value.length === 0)
|
|
200
|
+
return;
|
|
201
|
+
spend(value.length);
|
|
202
|
+
pending.push(value);
|
|
203
|
+
stack[stack.length - 1]?.content?.push({ kind: 'text', value });
|
|
204
|
+
};
|
|
205
|
+
/** Attach the text read since the last tag to whatever is open. */
|
|
206
|
+
const flushText = () => {
|
|
207
|
+
if (pending.length === 0)
|
|
208
|
+
return;
|
|
209
|
+
const text = pending.join('');
|
|
210
|
+
pending.length = 0;
|
|
211
|
+
const open = stack[stack.length - 1];
|
|
212
|
+
if (!open)
|
|
213
|
+
return;
|
|
214
|
+
// Whitespace between two indented children is layout, not content, and an
|
|
215
|
+
// element that held only that keeps no `text` at all — which is what lets a
|
|
216
|
+
// caller ask `if (node.text)` and mean "the file wrote something here".
|
|
217
|
+
if (!/\S/.test(text) && open.children.length > 0)
|
|
218
|
+
return;
|
|
219
|
+
if (!/\S/.test(text) && open.text === undefined)
|
|
220
|
+
return;
|
|
221
|
+
open.text = (open.text ?? '') + text;
|
|
222
|
+
};
|
|
223
|
+
const close = (node, end) => {
|
|
224
|
+
node.range[1] = end;
|
|
225
|
+
const parent = stack[stack.length - 1];
|
|
226
|
+
if (parent) {
|
|
227
|
+
parent.children.push(node);
|
|
228
|
+
parent.content?.push(node);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
if (conformant && roots.length === 1) {
|
|
232
|
+
note('This file has more than one top-level element.');
|
|
233
|
+
}
|
|
234
|
+
roots.push(node);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
while (cursor < source.length && !stopped) {
|
|
238
|
+
const open = source.indexOf('<', cursor);
|
|
239
|
+
if (open < 0) {
|
|
240
|
+
characters(decodeXmlText(source.slice(cursor)));
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if (open > cursor)
|
|
244
|
+
characters(decodeXmlText(source.slice(cursor, open)));
|
|
245
|
+
/* ── The four things that are not an element ─────────────────────── */
|
|
246
|
+
if (source.startsWith('<!--', open)) {
|
|
247
|
+
const end = source.indexOf('-->', open + 4);
|
|
248
|
+
if (end < 0) {
|
|
249
|
+
note('A comment in this file is never closed; the rest was read.');
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
const value = source.slice(open + 4, end);
|
|
253
|
+
spend(value.length);
|
|
254
|
+
stack[stack.length - 1]?.content?.push({ kind: 'comment', value });
|
|
255
|
+
cursor = end + 3;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (source.startsWith('<![CDATA[', open)) {
|
|
259
|
+
const end = source.indexOf(']]>', open + 9);
|
|
260
|
+
if (end < 0) {
|
|
261
|
+
note('A CDATA section in this file is never closed.');
|
|
262
|
+
characters(source.slice(open + 9));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
// CDATA is character data with the escaping turned OFF, so it is pushed
|
|
266
|
+
// raw: decoding it would resolve a `&` the author wrote literally.
|
|
267
|
+
characters(source.slice(open + 9, end));
|
|
268
|
+
cursor = end + 3;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (source.startsWith('<?', open)) {
|
|
272
|
+
const end = source.indexOf('?>', open + 2);
|
|
273
|
+
if (end < 0) {
|
|
274
|
+
note('A processing instruction in this file is never closed.');
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const [, target = '', data = ''] = /^(\S*)\s*([\s\S]*)$/.exec(source.slice(open + 2, end)) ?? [];
|
|
278
|
+
spend(target.length + data.length);
|
|
279
|
+
stack[stack.length - 1]?.content?.push({ kind: 'pi', target, data });
|
|
280
|
+
cursor = end + 2;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (source.startsWith('<!', open)) {
|
|
284
|
+
// A DOCTYPE, with or without an internal subset. The subset is skipped
|
|
285
|
+
// whole rather than parsed: this reader declares no entities, so what is
|
|
286
|
+
// in there could only change how a `&name;` it cannot resolve is read.
|
|
287
|
+
let scan = open + 2;
|
|
288
|
+
let depth = 0;
|
|
289
|
+
while (scan < source.length) {
|
|
290
|
+
const char = source[scan];
|
|
291
|
+
if (char === '[')
|
|
292
|
+
depth += 1;
|
|
293
|
+
else if (char === ']')
|
|
294
|
+
depth -= 1;
|
|
295
|
+
else if (char === '>' && depth <= 0)
|
|
296
|
+
break;
|
|
297
|
+
scan += 1;
|
|
298
|
+
}
|
|
299
|
+
cursor = Math.min(scan + 1, source.length);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
/* ── A close tag ─────────────────────────────────────────────────── */
|
|
303
|
+
if (source.startsWith('</', open)) {
|
|
304
|
+
const end = source.indexOf('>', open);
|
|
305
|
+
if (end < 0) {
|
|
306
|
+
note('A close tag in this file is never finished.');
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
flushText();
|
|
310
|
+
const name = source.slice(open + 2, end).trim();
|
|
311
|
+
// Searched only when the name IS open, and then everything above the
|
|
312
|
+
// match is popped: linear overall, where `<a>`×N `</b>`×N was N².
|
|
313
|
+
const depth = openNames.get(name)
|
|
314
|
+
? stack.findLastIndex(node => node.name === name)
|
|
315
|
+
: -1;
|
|
316
|
+
if (depth < 0) {
|
|
317
|
+
note(`</${shown(name)}> closes an element this file never opened; it was ignored.`);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// Everything still open INSIDE the element being closed is closed with
|
|
321
|
+
// it, in order. `<a><b></a>` is a file that lost a tag, not a file with
|
|
322
|
+
// no `<b>` in it, and the tree keeps the `<b>`.
|
|
323
|
+
for (let index = stack.length - 1; index > depth; index -= 1) {
|
|
324
|
+
const orphan = leave();
|
|
325
|
+
note(`<${shown(orphan.name)}> is closed by </${shown(name)}> rather than by a tag of ` +
|
|
326
|
+
`its own; it was read as far as it goes.`);
|
|
327
|
+
close(orphan, open);
|
|
328
|
+
}
|
|
329
|
+
close(leave(), end + 1);
|
|
330
|
+
}
|
|
331
|
+
cursor = end + 1;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
/* ── An open tag ─────────────────────────────────────────────────── */
|
|
335
|
+
let scan = open + 1;
|
|
336
|
+
while (scan < source.length && NAME_CHAR.test(source[scan]))
|
|
337
|
+
scan += 1;
|
|
338
|
+
const name = source.slice(open + 1, scan);
|
|
339
|
+
if (name.length === 0) {
|
|
340
|
+
// A bare `<` in character data — illegal, and every second hand-written
|
|
341
|
+
// file has one. It is kept as the character it is.
|
|
342
|
+
note('A "<" in this file starts no tag; it was read as a plain character.');
|
|
343
|
+
characters('<');
|
|
344
|
+
cursor = open + 1;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
flushText();
|
|
348
|
+
spend(0);
|
|
349
|
+
const node = nodeOf(name, open);
|
|
350
|
+
/* ── Its attributes ──────────────────────────────────────────────── */
|
|
351
|
+
let selfClosing = false;
|
|
352
|
+
let tagEnd = source.length;
|
|
353
|
+
while (scan < source.length && !stopped) {
|
|
354
|
+
while (scan < source.length && /\s/.test(source[scan]))
|
|
355
|
+
scan += 1;
|
|
356
|
+
if (scan >= source.length)
|
|
357
|
+
break;
|
|
358
|
+
if (source.startsWith('/>', scan)) {
|
|
359
|
+
selfClosing = true;
|
|
360
|
+
tagEnd = scan + 2;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
if (source[scan] === '>') {
|
|
364
|
+
tagEnd = scan + 1;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
const attrStart = scan;
|
|
368
|
+
while (scan < source.length && NAME_CHAR.test(source[scan]))
|
|
369
|
+
scan += 1;
|
|
370
|
+
const attrName = source.slice(attrStart, scan);
|
|
371
|
+
if (attrName.length === 0) {
|
|
372
|
+
// Something that is neither a name, a `>` nor whitespace. Skipped one
|
|
373
|
+
// character at a time so the scan cannot stall on it.
|
|
374
|
+
scan += 1;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
while (scan < source.length && /\s/.test(source[scan]))
|
|
378
|
+
scan += 1;
|
|
379
|
+
if (source[scan] !== '=') {
|
|
380
|
+
// A valueless attribute — HTML's habit, not XML's. Recorded as the
|
|
381
|
+
// empty string, which is what every tolerant reader does with one.
|
|
382
|
+
note(`The attribute "${shown(attrName)}" on <${shown(name)}> has no value; it was read ` +
|
|
383
|
+
`as empty.`);
|
|
384
|
+
spend(0);
|
|
385
|
+
node.attrs[attrName] = '';
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
scan += 1;
|
|
389
|
+
while (scan < source.length && /\s/.test(source[scan]))
|
|
390
|
+
scan += 1;
|
|
391
|
+
const quote = source[scan];
|
|
392
|
+
let value = '';
|
|
393
|
+
if (quote === '"' || quote === "'") {
|
|
394
|
+
const end = source.indexOf(quote, scan + 1);
|
|
395
|
+
if (end < 0) {
|
|
396
|
+
note(`The value of "${shown(attrName)}" on <${shown(name)}> is never closed; the ` +
|
|
397
|
+
`rest of the file was read as its value.`);
|
|
398
|
+
value = source.slice(scan + 1);
|
|
399
|
+
scan = source.length;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
value = source.slice(scan + 1, end);
|
|
403
|
+
scan = end + 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
// Unquoted. Read to the next whitespace or tag end, which is the only
|
|
408
|
+
// reading available and the one every tolerant parser agrees on.
|
|
409
|
+
const start = scan;
|
|
410
|
+
while (scan < source.length && !/[\s>/]/.test(source[scan]))
|
|
411
|
+
scan += 1;
|
|
412
|
+
value = source.slice(start, scan);
|
|
413
|
+
note(`The value of "${shown(attrName)}" on <${shown(name)}> is not quoted; it was read ` +
|
|
414
|
+
`up to the next space.`);
|
|
415
|
+
}
|
|
416
|
+
// §3.3.3: literal whitespace normalizes BEFORE references decode, so a
|
|
417
|
+
// ` ` the writer escaped on purpose stays a newline.
|
|
418
|
+
const decoded = decodeXmlText(conformant ? value.replaceAll(/\r\n|[\t\n\r]/g, ' ') : value);
|
|
419
|
+
spend(decoded.length);
|
|
420
|
+
node.attrs[attrName] = decoded;
|
|
421
|
+
}
|
|
422
|
+
if (tagEnd === source.length && !selfClosing && source[scan] !== '>') {
|
|
423
|
+
note(`<${shown(name)}> is never finished; the file stops inside its tag.`);
|
|
424
|
+
}
|
|
425
|
+
if (selfClosing) {
|
|
426
|
+
close(node, tagEnd);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
enter(node);
|
|
430
|
+
}
|
|
431
|
+
cursor = tagEnd;
|
|
432
|
+
}
|
|
433
|
+
flushText();
|
|
434
|
+
// Whatever the file left open. Closed at the end of what there was, so the
|
|
435
|
+
// tree holds every element the file named even when it stops mid-document.
|
|
436
|
+
while (stack.length > 0) {
|
|
437
|
+
const orphan = leave();
|
|
438
|
+
note(`<${shown(orphan.name)}> is never closed; it was read to the end.`);
|
|
439
|
+
close(orphan, source.length);
|
|
440
|
+
}
|
|
441
|
+
if (unlisted > 0) {
|
|
442
|
+
notes.push(`+${unlisted} more problems of the kinds above were not listed.`);
|
|
443
|
+
}
|
|
444
|
+
return { root: roots[0], roots, notes };
|
|
445
|
+
}
|
|
446
|
+
/* ── Getting back out ─────────────────────────────────────────────────── */
|
|
447
|
+
/**
|
|
448
|
+
* The element's own bytes, from the string it was read from.
|
|
449
|
+
*
|
|
450
|
+
* This is what a quarantined fragment is (ADR 0012 D5): the file's text, not a
|
|
451
|
+
* re-rendering of this reader's tree. Comments, attribute order, prefixes and
|
|
452
|
+
* whitespace all survive, because none of them was ever taken apart.
|
|
453
|
+
*/
|
|
454
|
+
export function xmlFragmentOf(source, node) {
|
|
455
|
+
return source.slice(node.range[0], node.range[1]);
|
|
456
|
+
}
|
|
457
|
+
/** The three characters that would otherwise start markup. */
|
|
458
|
+
function escapeText(value) {
|
|
459
|
+
return value
|
|
460
|
+
.replaceAll('&', '&')
|
|
461
|
+
.replaceAll('<', '<')
|
|
462
|
+
.replaceAll('>', '>');
|
|
463
|
+
}
|
|
464
|
+
/** An attribute value — see `xml.ts`, whose escaping this mirrors exactly. */
|
|
465
|
+
function escapeAttr(value) {
|
|
466
|
+
return escapeText(value)
|
|
467
|
+
.replaceAll('"', '"')
|
|
468
|
+
.replaceAll("'", ''')
|
|
469
|
+
.replaceAll('\n', ' ')
|
|
470
|
+
.replaceAll('\r', ' ')
|
|
471
|
+
.replaceAll('\t', '	');
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* A node back as XML, from the TREE rather than from the source.
|
|
475
|
+
*
|
|
476
|
+
* The fallback for a fragment that has no range to slice — one assembled by a
|
|
477
|
+
* caller, or read out of a document this function's caller no longer holds. It
|
|
478
|
+
* is faithful to what the tree records and therefore drops what the tree does
|
|
479
|
+
* not: comments, processing instructions, and the file's own indentation.
|
|
480
|
+
* Prefer {@link xmlFragmentOf} whenever the source string is still in hand.
|
|
481
|
+
*/
|
|
482
|
+
export function serializeXmlNode(node) {
|
|
483
|
+
// A stack of nodes still to open and close tags still to write: a tree as
|
|
484
|
+
// deep as its author chose costs a loop, not the call stack.
|
|
485
|
+
const out = [];
|
|
486
|
+
const stack = [node];
|
|
487
|
+
while (stack.length > 0) {
|
|
488
|
+
const next = stack.pop();
|
|
489
|
+
if (typeof next === 'string') {
|
|
490
|
+
out.push(next);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const attrs = Object.entries(next.attrs)
|
|
494
|
+
.map(([key, value]) => ` ${key}="${escapeAttr(value)}"`)
|
|
495
|
+
.join('');
|
|
496
|
+
const text = next.text === undefined ? '' : escapeText(next.text);
|
|
497
|
+
if (text.length === 0 && next.children.length === 0) {
|
|
498
|
+
out.push(`<${next.name}${attrs}/>`);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
out.push(`<${next.name}${attrs}>`, text);
|
|
502
|
+
stack.push(`</${next.name}>`);
|
|
503
|
+
for (let index = next.children.length - 1; index >= 0; index -= 1) {
|
|
504
|
+
stack.push(next.children[index]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return out.join('');
|
|
508
|
+
}
|
|
509
|
+
/* ── Reading a tree ───────────────────────────────────────────────────── */
|
|
510
|
+
/** Children with this local name, whatever prefix the file gave them. */
|
|
511
|
+
export function xmlChildren(node, local) {
|
|
512
|
+
return node.children.filter(child => child.local === local);
|
|
513
|
+
}
|
|
514
|
+
/** The first child with this local name, if any. */
|
|
515
|
+
export function xmlChild(node, local) {
|
|
516
|
+
return node.children.find(child => child.local === local);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* An attribute by LOCAL name — `xmi:id`, `xmi:ID` and a bare `id` all answer.
|
|
520
|
+
*
|
|
521
|
+
* Prefix-insensitive on purpose, and it is the difference between reading three
|
|
522
|
+
* tools and reading one: the XMI prefix is `xmi` by convention and by nothing
|
|
523
|
+
* else, Enterprise Architect has shipped files under `XMI`, and an
|
|
524
|
+
* attribute-defaulted file drops it altogether. The QUALIFIED name is still in
|
|
525
|
+
* `attrs` for anything that needs it, and {@link xmlFragmentOf} still writes
|
|
526
|
+
* back what the file said.
|
|
527
|
+
*
|
|
528
|
+
* The first match in document order wins, which only matters for a file that
|
|
529
|
+
* spells the same attribute twice under two prefixes.
|
|
530
|
+
*/
|
|
531
|
+
export function xmlAttr(node, local) {
|
|
532
|
+
const direct = node.attrs[local];
|
|
533
|
+
if (direct !== undefined)
|
|
534
|
+
return direct;
|
|
535
|
+
for (const [key, value] of Object.entries(node.attrs)) {
|
|
536
|
+
if (splitName(key).local === local)
|
|
537
|
+
return value;
|
|
538
|
+
}
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
/** Every `xmlns` / `xmlns:*` declaration on this element, by prefix (`''`). */
|
|
542
|
+
export function xmlNamespaces(node) {
|
|
543
|
+
const declarations = {};
|
|
544
|
+
for (const [key, value] of Object.entries(node.attrs)) {
|
|
545
|
+
if (key === 'xmlns')
|
|
546
|
+
declarations[''] = value;
|
|
547
|
+
else if (key.startsWith('xmlns:'))
|
|
548
|
+
declarations[key.slice(6)] = value;
|
|
549
|
+
}
|
|
550
|
+
return declarations;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Every element under `node`, at any depth, in document order.
|
|
554
|
+
*
|
|
555
|
+
* An explicit stack rather than recursion: the depth of a file is whatever its
|
|
556
|
+
* author chose, and a hostile one nested a hundred thousand deep must cost a
|
|
557
|
+
* loop, not the call stack.
|
|
558
|
+
*/
|
|
559
|
+
export function xmlDescendants(node) {
|
|
560
|
+
const found = [];
|
|
561
|
+
const stack = [...node.children].reverse();
|
|
562
|
+
while (stack.length > 0) {
|
|
563
|
+
const next = stack.pop();
|
|
564
|
+
found.push(next);
|
|
565
|
+
for (let index = next.children.length - 1; index >= 0; index -= 1) {
|
|
566
|
+
stack.push(next.children[index]);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return found;
|
|
570
|
+
}
|
|
@@ -4,6 +4,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
|
|
|
4
4
|
export { CanvasElementType } from './element-model/index.js';
|
|
5
5
|
export { CanvasRenderer } from './renderer/canvas-renderer.js';
|
|
6
6
|
export { DomRenderer } from './renderer/dom-renderer.js';
|
|
7
|
+
export { isPainted } from './renderer/paint-predicate.js';
|
|
7
8
|
export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
|
|
8
9
|
export type { ElementRenderer } from './renderer/elements/index.js';
|
|
9
10
|
export * from './renderer/elements/type.js';
|
|
@@ -6,6 +6,7 @@ export { SurfaceElementModel, SurfaceGroupLikeModel, } from './element-model/bas
|
|
|
6
6
|
export { CanvasElementType } from './element-model/index.js';
|
|
7
7
|
export { CanvasRenderer } from './renderer/canvas-renderer.js';
|
|
8
8
|
export { DomRenderer } from './renderer/dom-renderer.js';
|
|
9
|
+
export { isPainted } from './renderer/paint-predicate.js';
|
|
9
10
|
export { ElementHighlightOverlay } from './renderer/element-highlight-overlay.js';
|
|
10
11
|
export * from './renderer/elements/type.js';
|
|
11
12
|
export { Overlay, OverlayIdentifier } from './renderer/overlay.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@labre/affine-block-surface/interchange` — the interchange platform with no
|
|
3
|
+
* editor behind it (ADR 0012, P3 as amended 2026-10-14).
|
|
4
|
+
*
|
|
5
|
+
* The registry's types and helpers, `InterchangeImportError`, the generic SVG
|
|
6
|
+
* reader, the framework-background geometry and the constants a framework's
|
|
7
|
+
* reader shares with the editor. What
|
|
8
|
+
* is NOT here is the point: the graph of this entry imports no Lit, no
|
|
9
|
+
* `@atlaskit/pragmatic-drag-and-drop`, no `@labre/std` runtime and no block —
|
|
10
|
+
* so a server (the labre import function, labre-mcp) loads the readers in a
|
|
11
|
+
* few hundred kilobytes instead of the whole editor.
|
|
12
|
+
*
|
|
13
|
+
* Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`, which
|
|
14
|
+
* bundles this entry and every framework's `./interchange` and fails on any
|
|
15
|
+
* forbidden module in the graph. A new export here is a new edge in that
|
|
16
|
+
* graph: import it from a file that is itself editor-free, never from the
|
|
17
|
+
* package index.
|
|
18
|
+
*/
|
|
19
|
+
export * from './extensions/export-svg/marker-version.js';
|
|
20
|
+
export * from './extensions/interchange.js';
|
|
21
|
+
export * from './extensions/legend-role.js';
|
|
22
|
+
export * from './extensions/svg-sanitize.js';
|
|
23
|
+
export * from './extensions/svg-sketch.js';
|
|
24
|
+
export * from './extensions/svg-sketch-translations.js';
|
|
25
|
+
export * from './extensions/xml-element.js';
|
|
26
|
+
export * from './extensions/xml-reader.js';
|
|
27
|
+
export * from './framework-background/def.js';
|
|
28
|
+
export * from './framework-background/facts.js';
|
|
29
|
+
export * from './framework-background/labels.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@labre/affine-block-surface/interchange` — the interchange platform with no
|
|
3
|
+
* editor behind it (ADR 0012, P3 as amended 2026-10-14).
|
|
4
|
+
*
|
|
5
|
+
* The registry's types and helpers, `InterchangeImportError`, the generic SVG
|
|
6
|
+
* reader, the framework-background geometry and the constants a framework's
|
|
7
|
+
* reader shares with the editor. What
|
|
8
|
+
* is NOT here is the point: the graph of this entry imports no Lit, no
|
|
9
|
+
* `@atlaskit/pragmatic-drag-and-drop`, no `@labre/std` runtime and no block —
|
|
10
|
+
* so a server (the labre import function, labre-mcp) loads the readers in a
|
|
11
|
+
* few hundred kilobytes instead of the whole editor.
|
|
12
|
+
*
|
|
13
|
+
* Held by `interchange-headless.unit.spec.ts` in `packages/affine/all`, which
|
|
14
|
+
* bundles this entry and every framework's `./interchange` and fails on any
|
|
15
|
+
* forbidden module in the graph. A new export here is a new edge in that
|
|
16
|
+
* graph: import it from a file that is itself editor-free, never from the
|
|
17
|
+
* package index.
|
|
18
|
+
*/
|
|
19
|
+
export * from './extensions/export-svg/marker-version.js';
|
|
20
|
+
export * from './extensions/interchange.js';
|
|
21
|
+
export * from './extensions/legend-role.js';
|
|
22
|
+
export * from './extensions/svg-sanitize.js';
|
|
23
|
+
export * from './extensions/svg-sketch.js';
|
|
24
|
+
export * from './extensions/svg-sketch-translations.js';
|
|
25
|
+
export * from './extensions/xml-element.js';
|
|
26
|
+
export * from './extensions/xml-reader.js';
|
|
27
|
+
// The board geometry an exporter and a reader both compute with: a
|
|
28
|
+
// background's declaration, its plot and its instance zones. Pure data and
|
|
29
|
+
// arithmetic, editor-free like the rest.
|
|
30
|
+
export * from './framework-background/def.js';
|
|
31
|
+
export * from './framework-background/facts.js';
|
|
32
|
+
export * from './framework-background/labels.js';
|