@stll/folio-core 0.54.2 → 0.55.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 (199) hide show
  1. package/dist/ai-edits/apply.js +22 -1
  2. package/dist/ai-edits/headless.js +1 -0
  3. package/dist/compare/content-types.d.ts +3 -15
  4. package/dist/controller/canonicalComposition.d.ts +36 -0
  5. package/dist/controller/canonicalComposition.js +168 -0
  6. package/dist/controller/canonicalInput.d.ts +45 -0
  7. package/dist/controller/canonicalInput.js +338 -0
  8. package/dist/controller/canonicalOperations.d.ts +22 -0
  9. package/dist/controller/canonicalOperations.js +85 -0
  10. package/dist/controller/canonicalReview.d.ts +12 -0
  11. package/dist/controller/canonicalReview.js +19 -0
  12. package/dist/controller/canonicalSession.d.ts +149 -0
  13. package/dist/controller/canonicalSession.js +1038 -0
  14. package/dist/controller/canonicalStoryEditor.d.ts +34 -0
  15. package/dist/controller/canonicalStoryEditor.js +82 -0
  16. package/dist/controller/canonicalStructure.d.ts +16 -0
  17. package/dist/controller/canonicalStructure.js +269 -0
  18. package/dist/controller/folioEditor.d.ts +1 -1
  19. package/dist/controller/folioEditor.js +9 -0
  20. package/dist/controller/headerFooterEditorManager.d.ts +4 -0
  21. package/dist/controller/headerFooterEditorManager.js +71 -3
  22. package/dist/controller/hiddenEditorApi.d.ts +35 -0
  23. package/dist/controller/hiddenEditorApi.js +25 -8
  24. package/dist/controller/hiddenEditorManager.d.ts +11 -2
  25. package/dist/controller/hiddenEditorManager.js +456 -30
  26. package/dist/controller/layoutPipeline.d.ts +1 -1
  27. package/dist/controller/layoutPipeline.js +2 -0
  28. package/dist/controller/noteEditorManager.d.ts +4 -0
  29. package/dist/controller/noteEditorManager.js +113 -15
  30. package/dist/docx/attributeRemainder.js +1 -0
  31. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  32. package/dist/docx/canonicalSessionInput.js +27 -0
  33. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  34. package/dist/docx/canonicalStoryRepack.js +41 -0
  35. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  36. package/dist/docx/contentTypeRegistry.js +64 -0
  37. package/dist/docx/ensureParaIds.d.ts +7 -1
  38. package/dist/docx/ensureParaIds.js +163 -211
  39. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  40. package/dist/docx/foldedListNumberFields.js +267 -0
  41. package/dist/docx/headerFooterVerbatim.d.ts +14 -1
  42. package/dist/docx/headerFooterVerbatim.js +95 -2
  43. package/dist/docx/noteIds.d.ts +5 -0
  44. package/dist/docx/noteIds.js +6 -2
  45. package/dist/docx/noteReferenceMark.d.ts +3 -1
  46. package/dist/docx/noteReferenceMark.js +15 -14
  47. package/dist/docx/numberingParser.d.ts +3 -1
  48. package/dist/docx/numberingParser.js +17 -3
  49. package/dist/docx/packageParts.d.ts +3 -1
  50. package/dist/docx/packageParts.js +65 -16
  51. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  52. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  53. package/dist/docx/paragraphParser.js +101 -53
  54. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  55. package/dist/docx/paragraphPropertySource.js +76 -22
  56. package/dist/docx/parser.js +2 -1
  57. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  58. package/dist/docx/reviewHistoryNamespace.js +4 -0
  59. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  60. package/dist/docx/reviewResolutionProvenance.js +153 -0
  61. package/dist/docx/rezip.d.ts +7 -3
  62. package/dist/docx/rezip.js +107 -45
  63. package/dist/docx/runConsolidator.d.ts +2 -58
  64. package/dist/docx/runConsolidator.js +3 -175
  65. package/dist/docx/runParser.d.ts +2 -1
  66. package/dist/docx/runParser.js +156 -116
  67. package/dist/docx/saveDiagnostics.d.ts +11 -0
  68. package/dist/docx/saveDiagnostics.js +0 -0
  69. package/dist/docx/selectiveSave.d.ts +2 -1
  70. package/dist/docx/selectiveSave.js +4 -1
  71. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  72. package/dist/docx/selectiveXmlPatch.js +74 -2
  73. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  74. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  75. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  76. package/dist/docx/serializer/noteSerializer.js +24 -8
  77. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  78. package/dist/docx/serializer/partNamespaces.js +2 -1
  79. package/dist/docx/serializer/runSerializer.js +3 -1
  80. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  81. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  82. package/dist/docx/settingsParser.d.ts +1 -2
  83. package/dist/docx/settingsParser.js +5 -6
  84. package/dist/docx/storyBlockReplay.d.ts +11 -0
  85. package/dist/docx/storyBlockReplay.js +367 -0
  86. package/dist/docx/streamingXmlParser.d.ts +22 -1
  87. package/dist/docx/streamingXmlParser.js +49 -3
  88. package/dist/docx/structuralXmlPatch.js +8 -1
  89. package/dist/docx/verbatimCapture.d.ts +3 -1
  90. package/dist/docx/verbatimCapture.js +49 -6
  91. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  92. package/dist/i18n/messages/ar.gen.js +656 -0
  93. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  94. package/dist/i18n/messages/cs.gen.js +656 -0
  95. package/dist/i18n/messages/de.gen.d.ts +5 -0
  96. package/dist/i18n/messages/de.gen.js +656 -0
  97. package/dist/i18n/messages/en.gen.d.ts +5 -0
  98. package/dist/i18n/messages/en.gen.js +656 -0
  99. package/dist/i18n/messages/es.gen.d.ts +5 -0
  100. package/dist/i18n/messages/es.gen.js +656 -0
  101. package/dist/i18n/messages/et.gen.d.ts +5 -0
  102. package/dist/i18n/messages/et.gen.js +656 -0
  103. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  104. package/dist/i18n/messages/fr.gen.js +656 -0
  105. package/dist/i18n/messages/he.gen.d.ts +5 -0
  106. package/dist/i18n/messages/he.gen.js +656 -0
  107. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  108. package/dist/i18n/messages/hi.gen.js +656 -0
  109. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  110. package/dist/i18n/messages/hu.gen.js +656 -0
  111. package/dist/i18n/messages/locales.d.ts +5 -0
  112. package/dist/i18n/messages/locales.js +22 -0
  113. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  114. package/dist/i18n/messages/lt.gen.js +656 -0
  115. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  116. package/dist/i18n/messages/lv.gen.js +656 -0
  117. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  118. package/dist/i18n/messages/pl.gen.js +656 -0
  119. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  120. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  121. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  122. package/dist/i18n/messages/sk.gen.js +656 -0
  123. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  124. package/dist/i18n/messages/tr.gen.js +656 -0
  125. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  126. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  127. package/dist/i18n/messages.d.ts +2 -3
  128. package/dist/i18n/messages.js +1 -19
  129. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  130. package/dist/internal/noteMarkerAttrs.js +16 -0
  131. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  132. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  133. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  134. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  135. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  136. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  137. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  138. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  139. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  140. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  141. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  142. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  143. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  144. package/dist/managers/DocumentLoaderManager.js +8 -1
  145. package/dist/markdown/renderBlock.js +15 -1
  146. package/dist/markdown/renderParagraph.js +9 -13
  147. package/dist/markdown/renderRuns.js +3 -2
  148. package/dist/markdown/renderTable.js +2 -0
  149. package/dist/markdown/types.d.ts +3 -9
  150. package/dist/prosemirror/attrs/index.d.ts +2 -12
  151. package/dist/prosemirror/attrs/index.js +22 -48
  152. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  153. package/dist/prosemirror/canonicalCommands.d.ts +97 -0
  154. package/dist/prosemirror/canonicalCommands.js +28 -0
  155. package/dist/prosemirror/commands/pageBreak.js +15 -1
  156. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  157. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  158. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  159. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  160. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
  161. package/dist/prosemirror/conversion/toProseDoc.js +51 -11
  162. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  163. package/dist/prosemirror/executeEditorCommand.js +26 -0
  164. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  165. package/dist/prosemirror/extensions/core/ParagraphExtension.js +50 -19
  166. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  167. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  168. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  169. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  170. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  171. package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
  172. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  173. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  174. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  175. package/dist/prosemirror/listMarker.d.ts +1 -0
  176. package/dist/prosemirror/listMarker.js +16 -7
  177. package/dist/prosemirror/listNumbering.js +26 -5
  178. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  179. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  180. package/dist/prosemirror/markupViewNotes.js +3 -2
  181. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  182. package/dist/prosemirror/noteReferenceReview.js +5 -3
  183. package/dist/prosemirror/plugins/suggestionMode.js +50 -2
  184. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  185. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  186. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  187. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  188. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  189. package/dist/prosemirror/schema/marks.d.ts +2 -0
  190. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  191. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  192. package/dist/prosemirror/textInput.js +5 -1
  193. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  194. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  195. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  196. package/dist/prosemirror/validation.js +4 -0
  197. package/dist/types/content.d.ts +2 -2
  198. package/dist/utils/createDocument.js +2 -2
  199. package/package.json +14 -2
@@ -0,0 +1,367 @@
1
+ import { canonicalJson } from "../utils/canonicalJson.js";
2
+ import { spliceXml } from "./selectiveXmlPatch.js";
3
+ import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "./streamingXmlParser.js";
4
+ import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
5
+ import { captureSourceProfileXml, isSafeCapturedXmlDocument } from "./verbatimCapture.js";
6
+ import { WORDPROCESSINGML_NAMESPACE_URIS, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
7
+ import { escapeXmlAttribute } from "@stll/docx-core";
8
+ import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
9
+ //#region src/docx/storyBlockReplay.ts
10
+ const BLOCK_ELEMENT_NAMES = {
11
+ paragraph: "p",
12
+ table: "tbl",
13
+ blockSdt: "sdt",
14
+ blockCustomXml: "customXml",
15
+ preservedBlock: null,
16
+ bookmarkStart: "bookmarkStart",
17
+ bookmarkEnd: "bookmarkEnd"
18
+ };
19
+ const modelMatchesElement = ({ value, element, mode }) => {
20
+ if (value === void 0) return false;
21
+ if (mode === "source" && getLocalName(element.name) === "AlternateContent" && getNamespaceUri(element) === "http://schemas.openxmlformats.org/markup-compatibility/2006" && (value.type === "paragraph" || value.type === "table")) return true;
22
+ const name = BLOCK_ELEMENT_NAMES[value.type];
23
+ if (name !== null) return getLocalName(element.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
24
+ if (value.type !== "preservedBlock") return false;
25
+ const parsed = parseStreamingXmlWithSourceRanges(value.xml, element.namespaceScope);
26
+ if (parsed.status !== "parsed") return false;
27
+ const roots = getChildElements(parsed.value);
28
+ const root = roots.at(0);
29
+ const normalize = (uri) => uri === void 0 ? void 0 : TRANSITIONAL_NAMESPACE_BY_STRICT_URI.get(uri) ?? uri;
30
+ return roots.length === 1 && root !== void 0 && getLocalName(root.name) === getLocalName(element.name) && normalize(getNamespaceUri(root)) === normalize(getNamespaceUri(element));
31
+ };
32
+ const XML_TOKEN = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<(?:"[^"]*"|'[^']*'|[^'">])*>/gu;
33
+ const XML_ATTRIBUTE = /([^\s=<>/]+)(\s*=\s*)(["'])(.*?)\3/gsu;
34
+ const readStory = (xml, scope) => {
35
+ const parsed = parseStreamingXmlWithSourceRanges(xml, scope);
36
+ if (parsed.status !== "parsed") return null;
37
+ const roots = getChildElements(parsed.value);
38
+ const root = roots.at(0);
39
+ if (roots.length !== 1 || !root || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(root) ?? "")) return null;
40
+ if (root.elements?.some((child) => child.type === "text" && String(child.text ?? "").trim() !== "")) return null;
41
+ const children = getChildElements(root);
42
+ const blocks = [];
43
+ for (const element of children) {
44
+ const range = getXmlSourceRange(element);
45
+ if (!range) return null;
46
+ blocks.push({
47
+ element,
48
+ ...range
49
+ });
50
+ }
51
+ const range = getXmlSourceRange(root);
52
+ return range ? {
53
+ root,
54
+ blocks,
55
+ range
56
+ } : null;
57
+ };
58
+ const effectiveBindings = (element) => {
59
+ const bindings = /* @__PURE__ */ new Map();
60
+ let scope = element.namespaceScope;
61
+ while (scope) {
62
+ for (const [prefix, uri] of scope.bindings) if (!bindings.has(prefix)) bindings.set(prefix, uri);
63
+ scope = scope.parent;
64
+ }
65
+ return bindings;
66
+ };
67
+ const generatedFragment = ({ xml, element, sourceNamespace, generatedRoot }) => {
68
+ const strict = TRANSITIONAL_NAMESPACE_BY_STRICT_URI.has(sourceNamespace);
69
+ const first = [...xml.matchAll(XML_TOKEN)].at(0);
70
+ if (!first || first.index !== 0 || first[0].startsWith("<!") || first[0].startsWith("<?")) return null;
71
+ const opening = first[0];
72
+ const additions = [];
73
+ const bindings = effectiveBindings(element);
74
+ const markupCompatibilityUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";
75
+ const ignorableUris = /* @__PURE__ */ new Set([markupCompatibilityUri]);
76
+ const inheritedIgnorable = generatedRoot === void 0 ? null : getAttributeByNamespaceUri(generatedRoot, ignorableUris, "Ignorable");
77
+ const localIgnorable = getAttributeByNamespaceUri(element, ignorableUris, "Ignorable");
78
+ const ignorable = [...new Set(`${inheritedIgnorable ?? ""} ${localIgnorable ?? ""}`.trim().split(/\s+/u).filter(Boolean))];
79
+ let ignorableAttribute;
80
+ if (ignorable.length > 0) {
81
+ const localAttribute = Object.keys(element.attributes ?? {}).find((name) => {
82
+ const colon = name.indexOf(":");
83
+ return colon > 0 && getLocalName(name) === "Ignorable" && bindings.get(name.slice(0, colon)) === markupCompatibilityUri;
84
+ });
85
+ const compatibilityPrefix = localAttribute?.slice(0, localAttribute.indexOf(":")) ?? [...bindings].find(([prefix, uri]) => prefix !== "" && uri === markupCompatibilityUri)?.[0];
86
+ if (compatibilityPrefix === void 0 || ignorable.some((prefix) => !bindings.has(prefix))) return null;
87
+ ignorableAttribute = `${compatibilityPrefix}:Ignorable`;
88
+ if (localIgnorable === null) additions.push(` ${ignorableAttribute}="${escapeXmlAttribute(ignorable.join(" "))}"`);
89
+ }
90
+ for (const [prefix, uri] of bindings) {
91
+ const name = prefix === "" ? "xmlns" : `xmlns:${prefix}`;
92
+ if (element.attributes?.[name] !== void 0) continue;
93
+ additions.push(` ${name}="${escapeXmlAttribute(uri)}"`);
94
+ }
95
+ let replacedOpening = opening.replace(XML_ATTRIBUTE, (match, name, assignment, quote) => name === ignorableAttribute ? `${name}${assignment}${quote}${escapeXmlAttribute(ignorable.join(" "))}${quote}` : match);
96
+ const close = opening.endsWith("/>") ? replacedOpening.length - 2 : replacedOpening.length - 1;
97
+ replacedOpening = replacedOpening.slice(0, close) + additions.join("") + replacedOpening.slice(close);
98
+ const materialized = replacedOpening + xml.slice(opening.length);
99
+ if (!isSafeCapturedXmlDocument(materialized)) return null;
100
+ if (!strict) return materialized;
101
+ const parsed = parseStreamingXmlWithSourceRanges(materialized);
102
+ if (parsed.status !== "parsed") return null;
103
+ const root = getChildElements(parsed.value).at(0);
104
+ return root === void 0 ? null : captureSourceProfileXml(root, DOCX_CONFORMANCE_CLASSES.STRICT);
105
+ };
106
+ const paragraphIdentity = (value) => value.type === "paragraph" ? value.paraId?.toUpperCase() : void 0;
107
+ const addCandidate = ({ map, key, index }) => {
108
+ const queue = map.get(key);
109
+ if (queue) queue.indices.push(index);
110
+ else map.set(key, {
111
+ indices: [index],
112
+ cursor: 0
113
+ });
114
+ };
115
+ const takeCandidate = (queue, used) => {
116
+ if (!queue) return void 0;
117
+ while (queue.cursor < queue.indices.length) {
118
+ const candidate = queue.indices[queue.cursor];
119
+ queue.cursor += 1;
120
+ if (candidate !== void 0 && !used.has(candidate)) return candidate;
121
+ }
122
+ };
123
+ /** Replace only mapped block ranges, retaining authored root syntax and every intervening gap. */
124
+ const replayBlocks = ({ sourceXml, baselineContent, currentContent, serializedXml }, scope) => {
125
+ const source = readStory(sourceXml, scope?.source);
126
+ const generated = readStory(serializedXml, scope?.generated);
127
+ if (!source || !generated || getLocalName(source.root.name) !== getLocalName(generated.root.name) || source.blocks.length !== baselineContent.length || generated.blocks.length !== currentContent.length) return null;
128
+ if (source.blocks.some((block, index) => !modelMatchesElement({
129
+ value: baselineContent[index],
130
+ element: block.element,
131
+ mode: "source"
132
+ })) || generated.blocks.some((block, index) => !modelMatchesElement({
133
+ value: currentContent[index],
134
+ element: block.element,
135
+ mode: "generated"
136
+ }))) return null;
137
+ const generatedRoot = scope?.generatedRoot ?? generated.root;
138
+ const fingerprints = baselineContent.map(canonicalJson);
139
+ const identities = /* @__PURE__ */ new Map();
140
+ const byFingerprint = /* @__PURE__ */ new Map();
141
+ for (const [index, block] of baselineContent.entries()) {
142
+ const fingerprint = fingerprints[index];
143
+ if (fingerprint === void 0) return null;
144
+ addCandidate({
145
+ map: byFingerprint,
146
+ key: fingerprint,
147
+ index
148
+ });
149
+ const identity = paragraphIdentity(block);
150
+ if (identity !== void 0) addCandidate({
151
+ map: identities,
152
+ key: identity,
153
+ index
154
+ });
155
+ }
156
+ const used = /* @__PURE__ */ new Set();
157
+ const fragments = [];
158
+ for (const [index, block] of currentContent.entries()) {
159
+ const fingerprint = canonicalJson(block);
160
+ const identity = paragraphIdentity(block);
161
+ let originalIndex = takeCandidate(identity === void 0 ? void 0 : identities.get(identity), used) ?? takeCandidate(byFingerprint.get(fingerprint), used);
162
+ if (originalIndex === void 0 && (block.type === "table" || block.type === "blockSdt" || block.type === "blockCustomXml") && !used.has(index)) {
163
+ if (baselineContent[index]?.type === block.type) originalIndex = index;
164
+ }
165
+ if (originalIndex !== void 0) used.add(originalIndex);
166
+ const original = originalIndex === void 0 ? void 0 : source.blocks[originalIndex];
167
+ if (originalIndex !== void 0 && original && fingerprints[originalIndex] === fingerprint) {
168
+ fragments.push(sourceXml.slice(original.start, original.end));
169
+ continue;
170
+ }
171
+ const replacement = generated.blocks[index];
172
+ if (!replacement) return null;
173
+ if (original && originalIndex !== void 0) {
174
+ const nested = replayNestedBlock({
175
+ sourceXml,
176
+ serializedXml,
177
+ sourceElement: original.element,
178
+ generatedElement: replacement.element,
179
+ baseline: baselineContent[originalIndex],
180
+ current: block,
181
+ generatedRoot
182
+ });
183
+ if (nested !== null) {
184
+ fragments.push(nested);
185
+ continue;
186
+ }
187
+ }
188
+ const fragment = generatedFragment({
189
+ xml: serializedXml.slice(replacement.start, replacement.end),
190
+ element: replacement.element,
191
+ sourceNamespace: getNamespaceUri(source.root) ?? "",
192
+ generatedRoot
193
+ });
194
+ if (fragment === null) return null;
195
+ fragments.push(fragment);
196
+ }
197
+ const splices = source.blocks.map((block, index) => ({
198
+ start: block.start,
199
+ end: block.end,
200
+ newXml: index === source.blocks.length - 1 ? fragments.slice(index).join("") : fragments[index] ?? ""
201
+ }));
202
+ if (source.blocks.length === 0 && fragments.length > 0) {
203
+ const opening = [...sourceXml.slice(source.range.start, source.range.end).matchAll(XML_TOKEN)].at(0)?.[0];
204
+ if (!opening || !source.root.name) return null;
205
+ if (opening.endsWith("/>")) splices.push({
206
+ start: source.range.start,
207
+ end: source.range.end,
208
+ newXml: `${opening.slice(0, -2)}>${fragments.join("")}</${source.root.name}>`
209
+ });
210
+ else {
211
+ const close = sourceXml.lastIndexOf("</", source.range.end);
212
+ if (close < source.range.start) return null;
213
+ splices.push({
214
+ start: close,
215
+ end: close,
216
+ newXml: fragments.join("")
217
+ });
218
+ }
219
+ }
220
+ return spliceXml(sourceXml, splices);
221
+ };
222
+ const buildStoryBlockReplay = (options) => replayBlocks(options);
223
+ const withoutChildren = (value, key) => Object.fromEntries(Object.entries(value).filter(([name]) => name !== key));
224
+ const wordChildren = (element, name) => getChildElements(element).filter((child) => getLocalName(child.name) === name && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? ""));
225
+ const replayNestedBlock = ({ sourceXml, serializedXml, sourceElement, generatedElement, baseline, current, generatedRoot }) => {
226
+ if (baseline === void 0 || baseline.type !== current.type) return null;
227
+ const sourceRange = getXmlSourceRange(sourceElement);
228
+ if (!sourceRange) return null;
229
+ const splices = [];
230
+ const nestedContent = (sourceContainer, generatedContainer, before, after) => {
231
+ const sourceContentRange = getXmlSourceRange(sourceContainer);
232
+ const generatedContentRange = getXmlSourceRange(generatedContainer);
233
+ if (!sourceContentRange || !generatedContentRange) return false;
234
+ const patched = replayBlocks({
235
+ sourceXml: sourceXml.slice(sourceContentRange.start, sourceContentRange.end),
236
+ serializedXml: serializedXml.slice(generatedContentRange.start, generatedContentRange.end),
237
+ baselineContent: before,
238
+ currentContent: after
239
+ }, {
240
+ source: sourceContainer.namespaceScope,
241
+ generated: generatedContainer.namespaceScope,
242
+ generatedRoot
243
+ });
244
+ if (patched === null) return false;
245
+ splices.push({
246
+ start: sourceContentRange.start - sourceRange.start,
247
+ end: sourceContentRange.end - sourceRange.start,
248
+ newXml: patched
249
+ });
250
+ return true;
251
+ };
252
+ switch (current.type) {
253
+ case "blockSdt":
254
+ case "blockCustomXml": {
255
+ if (baseline.type !== "blockSdt" && baseline.type !== "blockCustomXml" || canonicalJson(withoutChildren(baseline, "content")) !== canonicalJson(withoutChildren(current, "content"))) return null;
256
+ if (current.type === "blockCustomXml") {
257
+ if (!nestedContent(sourceElement, generatedElement, baseline.content, current.content)) return null;
258
+ break;
259
+ }
260
+ const sourceChildren = wordChildren(sourceElement, "sdtContent");
261
+ const generatedChildren = wordChildren(generatedElement, "sdtContent");
262
+ const sourceContainer = sourceChildren.at(0);
263
+ const generatedContainer = generatedChildren.at(0);
264
+ if (sourceChildren.length !== 1 || generatedChildren.length !== 1 || !sourceContainer || !generatedContainer || !nestedContent(sourceContainer, generatedContainer, baseline.content, current.content)) return null;
265
+ break;
266
+ }
267
+ case "table": {
268
+ if (baseline.type !== "table" || baseline.rows.length !== current.rows.length || canonicalJson(withoutChildren(baseline, "rows")) !== canonicalJson(withoutChildren(current, "rows"))) return null;
269
+ const sourceRows = wordChildren(sourceElement, "tr");
270
+ const generatedRows = wordChildren(generatedElement, "tr");
271
+ if (sourceRows.length !== current.rows.length || generatedRows.length !== current.rows.length) return null;
272
+ for (const [rowIndex, row] of current.rows.entries()) {
273
+ const beforeRow = baseline.rows[rowIndex];
274
+ const sourceRow = sourceRows[rowIndex];
275
+ const generatedRow = generatedRows[rowIndex];
276
+ if (beforeRow === void 0 || beforeRow.cells.length !== row.cells.length || canonicalJson(withoutChildren(beforeRow, "cells")) !== canonicalJson(withoutChildren(row, "cells")) || !sourceRow || !generatedRow) return null;
277
+ const sourceCells = wordChildren(sourceRow, "tc");
278
+ const generatedCells = wordChildren(generatedRow, "tc");
279
+ if (sourceCells.length !== row.cells.length || generatedCells.length !== row.cells.length) return null;
280
+ for (const [cellIndex, cell] of row.cells.entries()) {
281
+ const beforeCell = beforeRow.cells[cellIndex];
282
+ const sourceCell = sourceCells[cellIndex];
283
+ const generatedCell = generatedCells[cellIndex];
284
+ if (beforeCell === void 0 || canonicalJson(withoutChildren(beforeCell, "content")) !== canonicalJson(withoutChildren(cell, "content")) || !sourceCell || !generatedCell) return null;
285
+ if (!replayCellContent({
286
+ sourceXml,
287
+ serializedXml,
288
+ sourceCell,
289
+ generatedCell,
290
+ before: beforeCell.content,
291
+ after: cell.content,
292
+ sourceRangeStart: sourceRange.start,
293
+ splices,
294
+ generatedRoot
295
+ })) return null;
296
+ }
297
+ }
298
+ break;
299
+ }
300
+ case "paragraph":
301
+ case "preservedBlock":
302
+ case "bookmarkStart":
303
+ case "bookmarkEnd": return null;
304
+ default: return current;
305
+ }
306
+ return spliceXml(sourceXml.slice(sourceRange.start, sourceRange.end), splices);
307
+ };
308
+ const replayCellContent = ({ sourceXml, serializedXml, sourceCell, generatedCell, before, after, sourceRangeStart, splices, generatedRoot }) => {
309
+ const sourceRange = getXmlSourceRange(sourceCell);
310
+ const generatedRange = getXmlSourceRange(generatedCell);
311
+ if (!sourceRange || !generatedRange) return false;
312
+ const sourceProperties = wordChildren(sourceCell, "tcPr");
313
+ const generatedProperties = wordChildren(generatedCell, "tcPr");
314
+ if (sourceProperties.length > 1 || generatedProperties.length > 1) return false;
315
+ const sourceProperty = sourceProperties.at(0);
316
+ const generatedProperty = generatedProperties.at(0);
317
+ const sourceView = sourceXml.slice(sourceRange.start, sourceRange.end);
318
+ let generatedView = serializedXml.slice(generatedRange.start, generatedRange.end);
319
+ const baselineContent = [...before];
320
+ const currentContent = [...after];
321
+ if (sourceProperty !== void 0) {
322
+ if (getChildElements(sourceCell).at(0) !== sourceProperty || !generatedProperty || getChildElements(generatedCell).at(0) !== generatedProperty) return false;
323
+ const range = getXmlSourceRange(sourceProperty);
324
+ if (!range) return false;
325
+ const propertyXml = generatedFragment({
326
+ xml: sourceXml.slice(range.start, range.end),
327
+ element: sourceProperty,
328
+ sourceNamespace: getNamespaceUri(sourceCell) ?? ""
329
+ });
330
+ if (propertyXml === null) return false;
331
+ const property = {
332
+ type: "preservedBlock",
333
+ xml: propertyXml
334
+ };
335
+ baselineContent.unshift(property);
336
+ currentContent.unshift(property);
337
+ } else if (generatedProperty !== void 0) {
338
+ const range = getXmlSourceRange(generatedProperty);
339
+ if (!range) return false;
340
+ const stripped = spliceXml(generatedView, [{
341
+ start: range.start - generatedRange.start,
342
+ end: range.end - generatedRange.start,
343
+ newXml: ""
344
+ }]);
345
+ if (stripped === null) return false;
346
+ generatedView = stripped;
347
+ }
348
+ const patched = replayBlocks({
349
+ sourceXml: sourceView,
350
+ serializedXml: generatedView,
351
+ baselineContent,
352
+ currentContent
353
+ }, {
354
+ source: sourceCell.namespaceScope,
355
+ generated: generatedCell.namespaceScope,
356
+ generatedRoot
357
+ });
358
+ if (patched === null) return false;
359
+ splices.push({
360
+ start: sourceRange.start - sourceRangeStart,
361
+ end: sourceRange.end - sourceRangeStart,
362
+ newXml: patched
363
+ });
364
+ return true;
365
+ };
366
+ //#endregion
367
+ export { buildStoryBlockReplay };
@@ -6,6 +6,12 @@ type ParseXmlResult = {
6
6
  } | {
7
7
  status: "unsupported";
8
8
  };
9
+ type XmlSourceRange = {
10
+ readonly start: number;
11
+ readonly end: number;
12
+ };
13
+ /** Exact element source ranges, available only from the opt-in source-range reader. */
14
+ declare const getXmlSourceRange: (element: XmlElement) => XmlSourceRange | undefined;
9
15
  /**
10
16
  * Parse ordinary OOXML into Folio's existing element representation in one
11
17
  * pass. Unsupported or malformed constructs return a sentinel so callers can
@@ -17,11 +23,20 @@ type AttributeValueSpan = {
17
23
  };
18
24
  type OpenTagVisitor = (element: XmlElement, attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>) => ReadonlyMap<string, string> | null;
19
25
  type OpenTagScanVisitor = (...args: Parameters<OpenTagVisitor>) => null;
26
+ type ElementScanOptions = {
27
+ element: XmlElement;
28
+ attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>;
29
+ nameEnd: number;
30
+ parent: XmlElement | undefined;
31
+ };
32
+ type ElementScanVisitor = (options: ElementScanOptions) => void;
20
33
  /**
21
34
  * @param inheritedNamespaceScope the bindings in scope around `xml`, for a
22
35
  * fragment captured out of a part whose root declared them.
23
36
  */
24
37
  declare const parseStreamingXml: (xml: string, inheritedNamespaceScope?: XmlNamespaceScope) => ParseXmlResult;
38
+ /** Retain source spans for every element without changing the parsed namespace tree. */
39
+ declare const parseStreamingXmlWithSourceRanges: (xml: string, inheritedNamespaceScope?: XmlNamespaceScope) => ParseXmlResult;
25
40
  /** Retain the body tree while collecting identities for import repair. */
26
41
  declare const parseStreamingXmlWithIdentityVisitor: (xml: string, visitOpenTag: OpenTagScanVisitor) => ParseXmlResult;
27
42
  /** Rewrite selected decimal attribute values while preserving every other source byte. */
@@ -37,5 +52,11 @@ declare const scanStreamingXmlNumericIdAttributes: (xml: string, visitOpenTag: O
37
52
  } | {
38
53
  status: "scanned";
39
54
  };
55
+ /** Visit namespace-resolved start tags and source spans without building a tree. */
56
+ declare const scanStreamingXmlElements: (xml: string, visitElement: ElementScanVisitor) => {
57
+ status: "unsupported";
58
+ } | {
59
+ status: "scanned";
60
+ };
40
61
  //#endregion
41
- export { parseStreamingXml, parseStreamingXmlWithIdentityVisitor, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlNumericIdAttributes };
62
+ export { getXmlSourceRange, parseStreamingXml, parseStreamingXmlWithIdentityVisitor, parseStreamingXmlWithSourceRanges, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlElements, scanStreamingXmlNumericIdAttributes };
@@ -2,6 +2,9 @@ import { EMPTY_NAMESPACE_SCOPE, attachXmlNamespaceContext } from "./xmlNamespace
2
2
  import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
3
3
  import { isOoxmlNumericIdAttributeName } from "@stll/docx-core";
4
4
  //#region src/docx/streamingXmlParser.ts
5
+ const sourceRanges = /* @__PURE__ */ new WeakMap();
6
+ /** Exact element source ranges, available only from the opt-in source-range reader. */
7
+ const getXmlSourceRange = (element) => sourceRanges.get(element);
5
8
  const BUILT_IN_ENTITIES = {
6
9
  amp: "&",
7
10
  apos: "'",
@@ -14,6 +17,7 @@ const parseStreamingXmlInternal = (options) => {
14
17
  const visitOpenTag = options.visitOpenTag;
15
18
  let spanMode = "none";
16
19
  if (visitOpenTag !== void 0) spanMode = options.mode === "tree" ? "identities" : "all";
20
+ if (options.visitElement !== void 0) spanMode = "all";
17
21
  const openTagOptions = {
18
22
  xml,
19
23
  start: 0,
@@ -67,6 +71,10 @@ const parseStreamingXmlInternal = (options) => {
67
71
  const name = xml.slice(open + 2, close).trim();
68
72
  const frame = stack.pop();
69
73
  if (!frame || frame.name !== name) return { status: "unsupported" };
74
+ if (frame.start !== void 0) sourceRanges.set(frame.element, {
75
+ start: frame.start,
76
+ end: close + 1
77
+ });
70
78
  cursor = close + 1;
71
79
  mergeAdjacentText = false;
72
80
  continue;
@@ -79,6 +87,12 @@ const parseStreamingXmlInternal = (options) => {
79
87
  const scope = parent?.namespaceScope ?? inheritedNamespaceScope;
80
88
  if (options.mode === "attributes" && options.retainAttribute !== void 0 && parsedTag.element.attributes === void 0) parsedTag.element.namespaceScope = scope;
81
89
  else attachXmlNamespaceContext(parsedTag.element, scope);
90
+ options.visitElement?.({
91
+ element: parsedTag.element,
92
+ attributeValueSpans: parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map(),
93
+ nameEnd: parsedTag.nameEnd,
94
+ parent
95
+ });
82
96
  const rewritten = (options.mode === "tree" ? parsedTag.attributeValueSpans !== void 0 : options.retainAttribute === void 0 || parsedTag.element.attributes !== void 0) ? visitOpenTag?.(parsedTag.element, parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map()) : null;
83
97
  if (rewritten) for (const [attributeName, value] of rewritten) {
84
98
  const span = parsedTag.attributeValueSpans?.get(attributeName);
@@ -91,11 +105,18 @@ const parseStreamingXmlInternal = (options) => {
91
105
  if (options.mode === "tree") appendElement(parent ?? root, parsedTag.element);
92
106
  if (!parsedTag.selfClosing) {
93
107
  if (stack.length >= FOLIO_XML_RESOURCE_LIMITS.maxDepth) return { status: "unsupported" };
94
- stack.push({
108
+ stack.push(options.mode === "tree" && options.sourceRanges === "tracked" ? {
109
+ element: parsedTag.element,
110
+ name: parsedTag.name,
111
+ start: open
112
+ } : {
95
113
  element: parsedTag.element,
96
114
  name: parsedTag.name
97
115
  });
98
- }
116
+ } else if (options.mode === "tree" && options.sourceRanges === "tracked") sourceRanges.set(parsedTag.element, {
117
+ start: open,
118
+ end: close + 1
119
+ });
99
120
  cursor = close + 1;
100
121
  mergeAdjacentText = false;
101
122
  }
@@ -121,6 +142,19 @@ const parseStreamingXml = (xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE)
121
142
  value: parsed.value
122
143
  } : { status: "unsupported" };
123
144
  };
145
+ /** Retain source spans for every element without changing the parsed namespace tree. */
146
+ const parseStreamingXmlWithSourceRanges = (xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
147
+ const parsed = parseStreamingXmlInternal({
148
+ xml,
149
+ mode: "tree",
150
+ inheritedNamespaceScope,
151
+ sourceRanges: "tracked"
152
+ });
153
+ return parsed.status === "parsed" ? {
154
+ status: "parsed",
155
+ value: parsed.value
156
+ } : { status: "unsupported" };
157
+ };
124
158
  /** Retain the body tree while collecting identities for import repair. */
125
159
  const parseStreamingXmlWithIdentityVisitor = (xml, visitOpenTag) => {
126
160
  const parsed = parseStreamingXmlInternal({
@@ -170,12 +204,23 @@ const scanStreamingXmlNumericIdAttributes = (xml, visitOpenTag) => {
170
204
  });
171
205
  return parsed.status === "parsed" ? { status: "scanned" } : parsed;
172
206
  };
207
+ /** Visit namespace-resolved start tags and source spans without building a tree. */
208
+ const scanStreamingXmlElements = (xml, visitElement) => {
209
+ const parsed = parseStreamingXmlInternal({
210
+ xml,
211
+ mode: "attributes",
212
+ visitOpenTag: () => null,
213
+ visitElement
214
+ });
215
+ return parsed.status === "parsed" ? { status: "scanned" } : parsed;
216
+ };
173
217
  const parseOpenTag = ({ xml, start, close, spanMode, retainAttribute }) => {
174
218
  let cursor = skipWhitespace(xml, start, close);
175
219
  const nameStart = cursor;
176
220
  cursor = scanName(xml, cursor, close);
177
221
  if (cursor === nameStart) return { status: "unsupported" };
178
222
  const name = xml.slice(nameStart, cursor);
223
+ const nameEnd = cursor;
179
224
  if (isUnsafePropertyName(name)) return { status: "unsupported" };
180
225
  let attributes;
181
226
  let attributeValueSpans;
@@ -232,6 +277,7 @@ const parseOpenTag = ({ xml, start, close, spanMode, retainAttribute }) => {
232
277
  status: "parsed",
233
278
  element,
234
279
  name,
280
+ nameEnd,
235
281
  selfClosing,
236
282
  attributeValueSpans
237
283
  };
@@ -358,4 +404,4 @@ const isUnsafePropertyName = (name) => {
358
404
  }
359
405
  };
360
406
  //#endregion
361
- export { parseStreamingXml, parseStreamingXmlWithIdentityVisitor, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlNumericIdAttributes };
407
+ export { getXmlSourceRange, parseStreamingXml, parseStreamingXmlWithIdentityVisitor, parseStreamingXmlWithSourceRanges, rewriteStreamingXmlDecimalAttributes, scanStreamingXmlElements, scanStreamingXmlNumericIdAttributes };
@@ -1,7 +1,7 @@
1
1
  import { canonicalJson } from "../utils/canonicalJson.js";
2
2
  import { parseDocumentBody } from "./documentParser.js";
3
3
  import { paraIdAttribute } from "./paraIdAttribute.js";
4
- import { spliceXml } from "./selectiveXmlPatch.js";
4
+ import { spliceSupportsRootNamespaces, spliceXml } from "./selectiveXmlPatch.js";
5
5
  import { readRootNamespaceBindings, serializePartElement } from "./serializer/partNamespaces.js";
6
6
  import { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
7
7
  //#region src/docx/structuralXmlPatch.ts
@@ -203,6 +203,13 @@ const buildStructuralDocumentPatch = ({ originalXml, serializedXml, changedIds }
203
203
  end: source.bodyEnd,
204
204
  newXml: pending.join("")
205
205
  });
206
+ if (!spliceSupportsRootNamespaces({
207
+ originalXml,
208
+ serializedXml,
209
+ fragments: splices.map(({ newXml }) => newXml).filter(Boolean),
210
+ originalRoot: source.root,
211
+ serializedRoot: current.root
212
+ })) return null;
206
213
  return spliceXml(originalXml, splices);
207
214
  };
208
215
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { document_d_exports } from "../types/document.js";
1
2
  import { XmlElement, XmlNamespaceScope } from "./xmlParser.js";
2
3
  //#region src/docx/verbatimCapture.d.ts
3
4
  declare const UntranslatableStrictNamespaceError_base: import("better-result").TaggedErrorClass<"UntranslatableStrictNamespaceError">;
@@ -29,5 +30,6 @@ declare const isSafeCapturedXmlDocument: (xml: string) => boolean;
29
30
  * must therefore depend on nothing but the element they are given.
30
31
  */
31
32
  declare const createCapturedXmlSanitizer: (options: SanitizeCapturedXmlOptions) => ((xml: string | undefined) => string | null);
33
+ declare const captureSourceProfileXml: (element: XmlElement, conformance: document_d_exports.DocxConformanceClass | undefined) => string;
32
34
  //#endregion
33
- export { UntranslatableStrictNamespaceError, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
35
+ export { UntranslatableStrictNamespaceError, captureSourceProfileXml, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
@@ -1,10 +1,11 @@
1
1
  import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
2
2
  import { NUMBERS_PER_PERCENT, isStrictNamespaceUri, percentageSpelling, toTransitionalNamespaceUri, transitionalSlotEncoding } from "./transitionalSpelling.js";
3
3
  import { roundHalfAwayFromZero, universalMeasureAs } from "./universalMeasure.js";
4
- import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, elementToXml, getChildElements, getLocalName, getNamespacePrefix, parseXml } from "./xmlParser.js";
4
+ import { NAMESPACES, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, elementToXml, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespacePrefix, parseXml, resolveAttributeNamespaceUri } from "./xmlParser.js";
5
5
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
6
6
  import { getDocxXmlSafetyIssue } from "./xmlSafety.js";
7
- import { TaggedError } from "better-result";
7
+ import { TaggedError, panic } from "better-result";
8
+ import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
8
9
  //#region src/docx/verbatimCapture.ts
9
10
  /**
10
11
  * The one way to turn parsed markup into a string a rebuilt part replays.
@@ -75,14 +76,22 @@ const originOf = (element, inherited) => {
75
76
  });
76
77
  return TRANSITIONAL_URIS.has(uri) ? "transitional" : inherited;
77
78
  };
78
- const convertAttributes = (element, namespaceUri, localName, convertValues) => {
79
+ /** A same-local-name extension attribute is not a slot in the element's vocabulary. */
80
+ const attributeSlotEncoding = (element, name) => {
81
+ if (getNamespacePrefix(name) !== null) {
82
+ const attributeUri = resolveAttributeNamespaceUri(element, name);
83
+ if (attributeUri === void 0 || element.namespaceUri === void 0 || toTransitionalNamespaceUri(attributeUri) !== toTransitionalNamespaceUri(element.namespaceUri)) return void 0;
84
+ }
85
+ return transitionalSlotEncoding(element.namespaceUri, getLocalName(element.name), getLocalName(name));
86
+ };
87
+ const convertAttributes = (element, namespaceUri, convertValues) => {
79
88
  const attributes = element.attributes ?? {};
80
89
  let converted;
81
90
  for (const [name, raw] of Object.entries(attributes)) {
82
91
  if (typeof raw !== "string") continue;
83
92
  let next = raw;
84
93
  if (name === "xmlns" || name.startsWith("xmlns:")) next = toTransitionalNamespaceUri(raw);
85
- else if (convertValues) next = transitionalValue(raw, namespaceUri, transitionalSlotEncoding(element.namespaceUri, localName, getLocalName(name)));
94
+ else if (convertValues) next = transitionalValue(raw, namespaceUri, attributeSlotEncoding(element, name));
86
95
  if (next === raw) continue;
87
96
  converted ??= { ...attributes };
88
97
  converted[name] = next;
@@ -99,7 +108,7 @@ const toTransitional = (element, inherited) => {
99
108
  const namespaceUri = element.namespaceUri === void 0 ? "" : toTransitionalNamespaceUri(element.namespaceUri);
100
109
  const localName = getLocalName(element.name);
101
110
  const textEncoding = transitionalSlotEncoding(element.namespaceUri, localName);
102
- const attributes = element.attributes ? convertAttributes(element, namespaceUri, localName, origin === "strict") : void 0;
111
+ const attributes = element.attributes ? convertAttributes(element, namespaceUri, origin === "strict") : void 0;
103
112
  let elements;
104
113
  for (const [index, child] of (element.elements ?? []).entries()) {
105
114
  const next = origin === "strict" && child.type === "text" && typeof child.text === "string" ? childWithValue(child, transitionalValue(child.text, namespaceUri, textEncoding)) : toTransitional(child, origin);
@@ -363,5 +372,39 @@ const createCapturedXmlSanitizer = (options) => {
363
372
  return sanitized;
364
373
  };
365
374
  };
375
+ const STRICT_BY_TRANSITIONAL = new Map([...TRANSITIONAL_NAMESPACE_BY_STRICT_URI].map(([strict, transitional]) => [transitional, strict]));
376
+ const strictPercentage = (value, unit) => /^-?\d+(?:\.\d+)?$/u.test(value) ? `${Number(value) / NUMBERS_PER_PERCENT[unit]}%` : value;
377
+ /** Translate generated markup by namespace/slot, never by a user-text replacement. */
378
+ const strictElement = (element) => {
379
+ const localName = getLocalName(element.name);
380
+ for (const [name, value] of Object.entries(element.attributes ?? {})) {
381
+ if (typeof value !== "string") continue;
382
+ if (name === "xmlns" || name.startsWith("xmlns:")) {
383
+ element.attributes[name] = STRICT_BY_TRANSITIONAL.get(value) ?? value;
384
+ continue;
385
+ }
386
+ if (localName === "graphicData" && toTransitionalNamespaceUri(element.namespaceUri ?? "") === NAMESPACES.a && name === "uri") {
387
+ element.attributes[name] = STRICT_BY_TRANSITIONAL.get(value) ?? value;
388
+ continue;
389
+ }
390
+ const encoding = attributeSlotEncoding(element, name);
391
+ if (encoding?.percent && (!encoding.measure || getAttributeByNamespaceUri(element, WORDPROCESSINGML_NAMESPACE_URIS, "type") === "pct")) element.attributes[name] = strictPercentage(value, encoding.percent);
392
+ }
393
+ const encoding = transitionalSlotEncoding(element.namespaceUri, localName);
394
+ for (const child of element.elements ?? []) if (child.type === "element") strictElement(child);
395
+ else if (child.type === "text" && typeof child.text === "string" && encoding?.percent) child.text = strictPercentage(child.text, encoding.percent);
396
+ };
397
+ /** Serialize a generated, scope-bound block in the source part's profile. */
398
+ const cloneSourceProfileTree = (element) => cloneElement(element, {
399
+ ...element.attributes === void 0 ? {} : { attributes: { ...element.attributes } },
400
+ ...element.elements === void 0 ? {} : { elements: element.elements.map(cloneSourceProfileTree) }
401
+ });
402
+ const captureSourceProfileXml = (element, conformance) => {
403
+ const owned = cloneSourceProfileTree(element);
404
+ if (conformance === DOCX_CONFORMANCE_CLASSES.STRICT) strictElement(owned);
405
+ const xml = elementToXml(owned);
406
+ if (!isSafeCapturedXmlDocument(xml)) panic("Generated source-profile markup is unsafe.");
407
+ return xml;
408
+ };
366
409
  //#endregion
367
- export { UntranslatableStrictNamespaceError, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
410
+ export { UntranslatableStrictNamespaceError, captureSourceProfileXml, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
@@ -0,0 +1,5 @@
1
+ import { FolioMessages } from "../messages.js";
2
+ //#region src/i18n/messages/ar.gen.d.ts
3
+ declare const messages: FolioMessages;
4
+ //#endregion
5
+ export { messages as default, messages };