@beyondwork/docx-react-component 1.0.58 → 1.0.60
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/README.md +2 -2
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +980 -10
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +4 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +72 -42
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/workflow-payload.ts +6 -1
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +159 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +5 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +153 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +230 -0
- package/src/runtime/document-runtime.ts +821 -54
- package/src/runtime/document-search.ts +115 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +108 -10
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +290 -21
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +187 -43
- package/src/ui/editor-runtime-boundary.ts +10 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CommentThread } from "../../review/store/comment-store.ts";
|
|
2
2
|
import { createImportedCommentThread } from "../../review/store/comment-thread.ts";
|
|
3
|
+
import { parseXmlWithOffsets as parseXml } from "./xml-parser.ts";
|
|
4
|
+
import { localName, readStringAttr } from "./xml-attr-helpers.ts";
|
|
3
5
|
|
|
4
6
|
interface XmlElementNode {
|
|
5
7
|
type: "element";
|
|
@@ -293,7 +295,7 @@ function parseCommentDefinitions(
|
|
|
293
295
|
return [];
|
|
294
296
|
}
|
|
295
297
|
|
|
296
|
-
const root = parseXml(commentsXml);
|
|
298
|
+
const root = parseXml(commentsXml) as XmlElementNode;
|
|
297
299
|
const commentsElement = findChildElement(root, "comments");
|
|
298
300
|
const definitions: ImportedCommentDefinition[] = [];
|
|
299
301
|
let order = 0;
|
|
@@ -303,16 +305,16 @@ function parseCommentDefinitions(
|
|
|
303
305
|
continue;
|
|
304
306
|
}
|
|
305
307
|
|
|
306
|
-
const commentId = child
|
|
308
|
+
const commentId = readStringAttr(child, "w:id");
|
|
307
309
|
if (!commentId) {
|
|
308
310
|
continue;
|
|
309
311
|
}
|
|
310
312
|
|
|
311
|
-
const authorId = child
|
|
313
|
+
const authorId = readStringAttr(child, "w:author");
|
|
312
314
|
const createdAt = normalizeImportedTimestamp(
|
|
313
|
-
child
|
|
315
|
+
readStringAttr(child, "w:date"),
|
|
314
316
|
);
|
|
315
|
-
const initials = child
|
|
317
|
+
const initials = readStringAttr(child, "w:initials");
|
|
316
318
|
const paragraphNodes = child.children.filter(
|
|
317
319
|
(node): node is XmlElementNode => node.type === "element" && localName(node.name) === "p",
|
|
318
320
|
);
|
|
@@ -359,7 +361,7 @@ function parseCommentExtensions(xml: string | undefined): Map<string, CommentExt
|
|
|
359
361
|
return new Map();
|
|
360
362
|
}
|
|
361
363
|
|
|
362
|
-
const root = parseXml(xml);
|
|
364
|
+
const root = parseXml(xml) as XmlElementNode;
|
|
363
365
|
const commentsElement = findChildElement(root, "commentsEx");
|
|
364
366
|
const extensions = new Map<string, CommentExtensionRecord>();
|
|
365
367
|
|
|
@@ -393,7 +395,7 @@ function parseCommentDurableIds(xml: string | undefined): Map<string, string> {
|
|
|
393
395
|
return new Map();
|
|
394
396
|
}
|
|
395
397
|
|
|
396
|
-
const root = parseXml(xml);
|
|
398
|
+
const root = parseXml(xml) as XmlElementNode;
|
|
397
399
|
const commentsIdsElement = findChildElement(root, "commentsIds");
|
|
398
400
|
const durableIds = new Map<string, string>();
|
|
399
401
|
|
|
@@ -417,7 +419,7 @@ function parsePeopleAuthors(xml: string | undefined): string[] {
|
|
|
417
419
|
return [];
|
|
418
420
|
}
|
|
419
421
|
|
|
420
|
-
const root = parseXml(xml);
|
|
422
|
+
const root = parseXml(xml) as XmlElementNode;
|
|
421
423
|
const peopleElement = findChildElement(root, "people");
|
|
422
424
|
const authors = new Set<string>();
|
|
423
425
|
|
|
@@ -445,7 +447,7 @@ function extractRootTag(xml: string | undefined, localTagName: string): string |
|
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
function parseCommentAnchors(documentXml: string): Map<string, CommentAnchorBounds> {
|
|
448
|
-
const root = parseXml(documentXml);
|
|
450
|
+
const root = parseXml(documentXml) as XmlElementNode;
|
|
449
451
|
const documentElement = findChildElement(root, "document");
|
|
450
452
|
const bodyElement = findChildElement(documentElement, "body");
|
|
451
453
|
const anchors = new Map<string, CommentAnchorBounds>();
|
|
@@ -562,7 +564,7 @@ function walkInlineNode(
|
|
|
562
564
|
|
|
563
565
|
switch (localName(node.name)) {
|
|
564
566
|
case "commentRangeStart": {
|
|
565
|
-
const commentId = node
|
|
567
|
+
const commentId = readStringAttr(node, "w:id");
|
|
566
568
|
if (commentId) {
|
|
567
569
|
const bounds = ensureCommentAnchor(anchors, commentId);
|
|
568
570
|
bounds.start = getCursor();
|
|
@@ -571,7 +573,7 @@ function walkInlineNode(
|
|
|
571
573
|
return;
|
|
572
574
|
}
|
|
573
575
|
case "commentRangeEnd": {
|
|
574
|
-
const commentId = node
|
|
576
|
+
const commentId = readStringAttr(node, "w:id");
|
|
575
577
|
if (commentId) {
|
|
576
578
|
const bounds = ensureCommentAnchor(anchors, commentId);
|
|
577
579
|
bounds.end = getCursor();
|
|
@@ -580,7 +582,7 @@ function walkInlineNode(
|
|
|
580
582
|
return;
|
|
581
583
|
}
|
|
582
584
|
case "commentReference": {
|
|
583
|
-
const commentId = node
|
|
585
|
+
const commentId = readStringAttr(node, "w:id");
|
|
584
586
|
if (commentId) {
|
|
585
587
|
const bounds = ensureCommentAnchor(anchors, commentId);
|
|
586
588
|
bounds.referenceAt = getCursor();
|
|
@@ -674,103 +676,6 @@ function toDetachedRange(anchor: CommentAnchorBounds): { from: number; to: numbe
|
|
|
674
676
|
};
|
|
675
677
|
}
|
|
676
678
|
|
|
677
|
-
function parseXml(xml: string): XmlElementNode {
|
|
678
|
-
const root: XmlElementNode = {
|
|
679
|
-
type: "element",
|
|
680
|
-
name: "#document",
|
|
681
|
-
attributes: {},
|
|
682
|
-
children: [],
|
|
683
|
-
start: 0,
|
|
684
|
-
end: xml.length,
|
|
685
|
-
};
|
|
686
|
-
const stack: XmlElementNode[] = [root];
|
|
687
|
-
const tokenPattern =
|
|
688
|
-
/<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!DOCTYPE[\s\S]*?>|<!\[CDATA\[[\s\S]*?\]\]>|<[^>]+>|[^<]+/gu;
|
|
689
|
-
|
|
690
|
-
for (const match of xml.matchAll(tokenPattern)) {
|
|
691
|
-
const token = match[0] ?? "";
|
|
692
|
-
const start = match.index ?? 0;
|
|
693
|
-
const end = start + token.length;
|
|
694
|
-
|
|
695
|
-
if (token.startsWith("<?") || token.startsWith("<!DOCTYPE") || token.startsWith("<!--")) {
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
if (token.startsWith("<![CDATA[")) {
|
|
700
|
-
const text = token.slice(9, -3);
|
|
701
|
-
stack[stack.length - 1]?.children.push({
|
|
702
|
-
type: "text",
|
|
703
|
-
text,
|
|
704
|
-
start,
|
|
705
|
-
end,
|
|
706
|
-
});
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (token.startsWith("</")) {
|
|
711
|
-
const node = stack.pop();
|
|
712
|
-
if (!node) {
|
|
713
|
-
throw new Error("Malformed XML: unexpected closing tag.");
|
|
714
|
-
}
|
|
715
|
-
node.end = end;
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
if (token.startsWith("<")) {
|
|
720
|
-
const selfClosing = /\/>$/.test(token);
|
|
721
|
-
const tagBody = token.slice(1, token.length - (selfClosing ? 2 : 1)).trim();
|
|
722
|
-
const { name, attributes } = parseTag(tagBody);
|
|
723
|
-
const node: XmlElementNode = {
|
|
724
|
-
type: "element",
|
|
725
|
-
name,
|
|
726
|
-
attributes,
|
|
727
|
-
children: [],
|
|
728
|
-
start,
|
|
729
|
-
end,
|
|
730
|
-
};
|
|
731
|
-
stack[stack.length - 1]?.children.push(node);
|
|
732
|
-
if (!selfClosing) {
|
|
733
|
-
stack.push(node);
|
|
734
|
-
}
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const text = decodeXmlText(token);
|
|
739
|
-
if (text.length > 0) {
|
|
740
|
-
stack[stack.length - 1]?.children.push({
|
|
741
|
-
type: "text",
|
|
742
|
-
text,
|
|
743
|
-
start,
|
|
744
|
-
end,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (stack.length !== 1) {
|
|
750
|
-
throw new Error("Malformed XML: unclosed tag.");
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
return root;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
|
|
757
|
-
const whitespaceIndex = tagBody.search(/\s/u);
|
|
758
|
-
const name = whitespaceIndex === -1 ? tagBody : tagBody.slice(0, whitespaceIndex);
|
|
759
|
-
const rawAttributes = whitespaceIndex === -1 ? "" : tagBody.slice(whitespaceIndex + 1);
|
|
760
|
-
const attributes: Record<string, string> = {};
|
|
761
|
-
const pattern = /([A-Za-z_][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
762
|
-
|
|
763
|
-
for (const match of rawAttributes.matchAll(pattern)) {
|
|
764
|
-
const key = match[1];
|
|
765
|
-
const value = match[3] ?? match[4] ?? "";
|
|
766
|
-
if (key) {
|
|
767
|
-
attributes[key] = decodeXmlText(value);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
return { name, attributes };
|
|
772
|
-
}
|
|
773
|
-
|
|
774
679
|
function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
|
|
775
680
|
const match = node.children.find(
|
|
776
681
|
(child): child is XmlElementNode =>
|
|
@@ -784,36 +689,3 @@ function findChildElement(node: XmlElementNode, name: string): XmlElementNode {
|
|
|
784
689
|
return match;
|
|
785
690
|
}
|
|
786
691
|
|
|
787
|
-
function localName(name: string): string {
|
|
788
|
-
const index = name.indexOf(":");
|
|
789
|
-
return index === -1 ? name : name.slice(index + 1);
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
function decodeXmlText(text: string): string {
|
|
793
|
-
return text.replace(
|
|
794
|
-
/&(?:#x([0-9A-Fa-f]+)|#([0-9]+)|([A-Za-z]+));/gu,
|
|
795
|
-
(_, hex, dec, named) => {
|
|
796
|
-
if (hex) {
|
|
797
|
-
return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
798
|
-
}
|
|
799
|
-
if (dec) {
|
|
800
|
-
return String.fromCodePoint(Number.parseInt(dec, 10));
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
switch (named) {
|
|
804
|
-
case "amp":
|
|
805
|
-
return "&";
|
|
806
|
-
case "lt":
|
|
807
|
-
return "<";
|
|
808
|
-
case "gt":
|
|
809
|
-
return ">";
|
|
810
|
-
case "quot":
|
|
811
|
-
return "\"";
|
|
812
|
-
case "apos":
|
|
813
|
-
return "'";
|
|
814
|
-
default:
|
|
815
|
-
return `&${named};`;
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
);
|
|
819
|
-
}
|
|
@@ -13,6 +13,8 @@ import type { OpcRelationship } from "./part-manifest.ts";
|
|
|
13
13
|
import { normalizePartPath, resolveRelationshipTarget } from "./part-manifest.ts";
|
|
14
14
|
import { parseChartSpace } from "./chart/parse-chart-space.ts";
|
|
15
15
|
import type { ChartModel } from "./chart/types.ts";
|
|
16
|
+
import { parseXml } from "./xml-parser.ts";
|
|
17
|
+
import { localName } from "./xml-attr-helpers.ts";
|
|
16
18
|
|
|
17
19
|
export interface InlineMediaPart {
|
|
18
20
|
path: string;
|
|
@@ -260,21 +262,7 @@ function isSmartArtUri(uri: string): boolean {
|
|
|
260
262
|
);
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
interface XmlElementNode {
|
|
266
|
-
type: "element";
|
|
267
|
-
name: string;
|
|
268
|
-
attributes: Record<string, string>;
|
|
269
|
-
children: XmlNode[];
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
interface XmlTextNode {
|
|
273
|
-
type: "text";
|
|
274
|
-
text: string;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
type XmlNode = XmlElementNode | XmlTextNode;
|
|
265
|
+
import type { XmlElementNode } from "./xml-element.ts";
|
|
278
266
|
|
|
279
267
|
function findFirstChild(node: XmlElementNode, local: string): XmlElementNode | undefined {
|
|
280
268
|
for (const child of node.children) {
|
|
@@ -295,95 +283,4 @@ function findFirstDescendant(node: XmlElementNode, local: string): XmlElementNod
|
|
|
295
283
|
return undefined;
|
|
296
284
|
}
|
|
297
285
|
|
|
298
|
-
function localName(name: string): string {
|
|
299
|
-
const i = name.indexOf(":");
|
|
300
|
-
return i >= 0 ? name.slice(i + 1) : name;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function parseXml(xml: string): XmlElementNode {
|
|
304
|
-
const root: XmlElementNode = { type: "element", name: "__root__", attributes: {}, children: [] };
|
|
305
|
-
const stack: XmlElementNode[] = [root];
|
|
306
|
-
let cursor = 0;
|
|
307
286
|
|
|
308
|
-
while (cursor < xml.length) {
|
|
309
|
-
if (xml.startsWith("<!--", cursor)) {
|
|
310
|
-
const end = xml.indexOf("-->", cursor);
|
|
311
|
-
cursor = end >= 0 ? end + 3 : xml.length;
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (xml.startsWith("<?", cursor)) {
|
|
315
|
-
const end = xml.indexOf("?>", cursor);
|
|
316
|
-
cursor = end >= 0 ? end + 2 : xml.length;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (xml[cursor] !== "<") {
|
|
320
|
-
const nextTag = xml.indexOf("<", cursor);
|
|
321
|
-
const end = nextTag >= 0 ? nextTag : xml.length;
|
|
322
|
-
const text = xml.slice(cursor, end);
|
|
323
|
-
if (text.length > 0) {
|
|
324
|
-
stack[stack.length - 1]?.children.push({ type: "text", text });
|
|
325
|
-
}
|
|
326
|
-
cursor = end;
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
if (xml[cursor + 1] === "/") {
|
|
330
|
-
const end = xml.indexOf(">", cursor);
|
|
331
|
-
stack.pop();
|
|
332
|
-
cursor = end + 1;
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
const tagEnd = findTagEnd(xml, cursor);
|
|
336
|
-
const tagBody = xml.slice(cursor + 1, tagEnd);
|
|
337
|
-
const selfClosing = /\/\s*$/.test(tagBody);
|
|
338
|
-
const { name, attributes } = parseTag(tagBody.replace(/\/\s*$/, "").trim());
|
|
339
|
-
const element: XmlElementNode = { type: "element", name, attributes, children: [] };
|
|
340
|
-
stack[stack.length - 1]?.children.push(element);
|
|
341
|
-
if (!selfClosing) stack.push(element);
|
|
342
|
-
cursor = tagEnd + 1;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return root;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
|
|
349
|
-
let cursor = 0;
|
|
350
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
|
|
351
|
-
const nameStart = cursor;
|
|
352
|
-
while (cursor < tagBody.length && !/\s/.test(tagBody[cursor] ?? "")) cursor++;
|
|
353
|
-
const name = tagBody.slice(nameStart, cursor);
|
|
354
|
-
const attributes: Record<string, string> = {};
|
|
355
|
-
|
|
356
|
-
while (cursor < tagBody.length) {
|
|
357
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
|
|
358
|
-
if (cursor >= tagBody.length) break;
|
|
359
|
-
const keyStart = cursor;
|
|
360
|
-
while (cursor < tagBody.length && !/[\s=]/.test(tagBody[cursor] ?? "")) cursor++;
|
|
361
|
-
const key = tagBody.slice(keyStart, cursor);
|
|
362
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
|
|
363
|
-
if (tagBody[cursor] !== "=") { attributes[key] = ""; continue; }
|
|
364
|
-
cursor++;
|
|
365
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) cursor++;
|
|
366
|
-
const quote = tagBody[cursor];
|
|
367
|
-
if (quote !== `"` && quote !== `'`) break;
|
|
368
|
-
cursor++;
|
|
369
|
-
const valueStart = cursor;
|
|
370
|
-
while (cursor < tagBody.length && tagBody[cursor] !== quote) cursor++;
|
|
371
|
-
attributes[key] = tagBody.slice(valueStart, cursor);
|
|
372
|
-
cursor++;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return { name, attributes };
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function findTagEnd(xml: string, start: number): number {
|
|
379
|
-
let cursor = start + 1;
|
|
380
|
-
let quote: string | null = null;
|
|
381
|
-
while (cursor < xml.length) {
|
|
382
|
-
const c = xml[cursor];
|
|
383
|
-
if (quote) { if (c === quote) quote = null; cursor++; continue; }
|
|
384
|
-
if (c === `"` || c === `'`) { quote = c; cursor++; continue; }
|
|
385
|
-
if (c === ">") return cursor;
|
|
386
|
-
cursor++;
|
|
387
|
-
}
|
|
388
|
-
return xml.length - 1;
|
|
389
|
-
}
|