@stll/folio-core 0.7.0 → 0.9.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/README.md +39 -0
- package/dist/ai-edits/apply.js +18 -4
- package/dist/ai-edits/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/headless.d.ts +31 -4
- package/dist/ai-edits/headless.js +148 -35
- package/dist/ai-edits/index.d.ts +6 -4
- package/dist/ai-edits/index.js +4 -1
- package/dist/ai-edits/scoped-reading.d.ts +9 -0
- package/dist/ai-edits/scoped-reading.js +60 -0
- package/dist/ai-edits/snapshot.js +53 -7
- package/dist/ai-edits/types.d.ts +46 -3
- package/dist/compat/eigenpal.d.ts +8 -3
- package/dist/compat/eigenpal.js +7 -2
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/latestRequestGate.d.ts +13 -0
- package/dist/controller/latestRequestGate.js +16 -0
- package/dist/controller/layoutPipeline.js +88 -10
- package/dist/document-operations.d.ts +10 -3
- package/dist/document-operations.js +29 -14
- package/dist/docx/blockContentParser.js +53 -10
- package/dist/docx/commentParser.js +1 -1
- package/dist/docx/compatibility.d.ts +30 -2
- package/dist/docx/compatibility.js +118 -39
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/encryption/agileDecryption.js +1 -1
- package/dist/docx/encryption/encryptionInfo.js +1 -1
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/fontTableParser.d.ts +6 -0
- package/dist/docx/fontTableParser.js +72 -0
- package/dist/docx/groupDrawingParser.js +7 -2
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/normalizeBaseDirection.js +1 -1
- package/dist/docx/numberingParser.js +2 -0
- package/dist/docx/paragraphParser.js +21 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.d.ts +1 -1
- package/dist/docx/parser.js +14 -9
- package/dist/docx/rezip.js +70 -5
- package/dist/docx/runConsolidator.js +4 -0
- package/dist/docx/runParser.js +19 -1
- package/dist/docx/selectiveSave.js +3 -3
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
- package/dist/docx/serializer/fontTableSerializer.js +26 -0
- package/dist/docx/serializer/headerFooterSerializer.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
- package/dist/docx/serializer/settingsSerializer.js +16 -0
- package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
- package/dist/docx/serializer/stylesSerializer.js +56 -0
- package/dist/docx/serializer/tableSerializer.js +29 -26
- package/dist/docx/serializer/themeSerializer.d.ts +6 -0
- package/dist/docx/serializer/themeSerializer.js +36 -0
- package/dist/docx/settingsParser.js +46 -0
- package/dist/docx/styleParser.js +26 -1
- package/dist/docx/tableParser.js +19 -1
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +146 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
- package/dist/i18n/messages/catalogs.gen.js +5910 -5910
- package/dist/index.d.ts +8 -3
- package/dist/index.js +7 -2
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
- package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
- package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
- package/dist/layout-bridge/engine/hitTest.js +2 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +2 -1
- package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
- package/dist/layout-engine/headerFooterRefs.js +40 -0
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +68 -106
- package/dist/layout-engine/measure/cache.js +11 -3
- package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
- package/dist/layout-engine/measure/index.d.ts +2 -1
- package/dist/layout-engine/measure/index.js +2 -1
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
- package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
- package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
- package/dist/layout-engine/measure/lineBreaks.js +11 -18
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
- package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
- package/dist/layout-engine/measure/measureBlocks.js +138 -86
- package/dist/layout-engine/measure/measureContainer.js +32 -9
- package/dist/layout-engine/measure/measureHelpers.js +5 -2
- package/dist/layout-engine/measure/measureParagraph.js +346 -74
- package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
- package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
- package/dist/layout-engine/measure/measureWorker.js +8 -19
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
- package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
- package/dist/layout-engine/measure/tableCellFloating.js +37 -29
- package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
- package/dist/layout-engine/measure/tableCellFlow.js +73 -0
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
- package/dist/layout-engine/measure/tableCellGrid.js +62 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
- package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +7 -6
- package/dist/layout-engine/paragraphFrame.d.ts +22 -0
- package/dist/layout-engine/paragraphFrame.js +17 -0
- package/dist/layout-engine/paragraphSequence.d.ts +7 -0
- package/dist/layout-engine/paragraphSequence.js +25 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
- package/dist/layout-engine/paragraphSpacing.js +30 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
- package/dist/layout-engine/tableRowBreak.d.ts +5 -2
- package/dist/layout-engine/tableRowBreak.js +62 -29
- package/dist/layout-engine/types.d.ts +54 -12
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
- package/dist/layout-painter/anchoredImagePosition.js +95 -0
- package/dist/layout-painter/borderStroke.d.ts +23 -0
- package/dist/layout-painter/borderStroke.js +39 -0
- package/dist/layout-painter/renderImage.d.ts +5 -4
- package/dist/layout-painter/renderImage.js +18 -4
- package/dist/layout-painter/renderPage.d.ts +2 -27
- package/dist/layout-painter/renderPage.js +15 -101
- package/dist/layout-painter/renderParagraph.js +45 -26
- package/dist/layout-painter/renderTable.d.ts +2 -0
- package/dist/layout-painter/renderTable.js +257 -47
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.d.ts +1 -1
- package/dist/managers/DocumentLoaderManager.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
- package/dist/paged-layout/sectionBlockWidths.js +9 -0
- package/dist/paged-layout/sectionGeometry.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +32 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
- package/dist/prosemirror/conversion/toProseDoc.js +73 -37
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
- package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +6 -1
- package/dist/prosemirror/schema/nodes.d.ts +16 -12
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
- package/dist/prosemirror/utils/tabCalculator.js +12 -10
- package/dist/prosemirror/validation.js +4 -1
- package/dist/redline.js +13 -3
- package/dist/server.d.ts +9 -4
- package/dist/server.js +7 -2
- package/dist/style-sets/extract.d.ts +35 -0
- package/dist/style-sets/extract.js +123 -0
- package/dist/style-sets/stellaStyle.d.ts +13 -0
- package/dist/style-sets/stellaStyle.js +516 -0
- package/dist/style-sets/types.d.ts +31 -0
- package/dist/style-sets/types.js +5 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.d.ts +12 -2
- package/dist/utils/createDocument.js +55 -36
- package/dist/utils/fontResolver.js +6 -6
- package/dist/utils/formatToStyle.js +1 -1
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
- package/dist/utils/paragraphFormattingMerge.js +14 -4
- package/package.json +5 -1
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { isXmlNameBoundary } from "./selectiveXmlPatch.js";
|
|
2
|
+
import { deterministicHexId } from "../utils/hexId.js";
|
|
3
|
+
import { TaggedError } from "better-result";
|
|
4
|
+
import JSZip from "jszip";
|
|
5
|
+
//#region src/docx/ensureParaIds.ts
|
|
6
|
+
/**
|
|
7
|
+
* Headless `w14:paraId` normalization for a `.docx` buffer.
|
|
8
|
+
*
|
|
9
|
+
* Word 2010+ stamps every paragraph with a `w14:paraId`, but Google Docs
|
|
10
|
+
* exports, LibreOffice, python-docx, and docx4j generally do not. Paragraphs
|
|
11
|
+
* without one are invisible to everything that anchors on paraId (block ids,
|
|
12
|
+
* comment threads, AI-edit targeting) and fall back to positional `seq-NNNN`
|
|
13
|
+
* ids that renumber after structural edits. Hosts call {@link ensureParaIds}
|
|
14
|
+
* once at ingest so every stored version has full id coverage before any
|
|
15
|
+
* snapshot or external anchor is created.
|
|
16
|
+
*
|
|
17
|
+
* IDs remain stable in folio and identity-preserving DOCX round-trips.
|
|
18
|
+
* Microsoft Word can establish a new `w14:docId` and replace all paragraph
|
|
19
|
+
* IDs on its first save of a non-Word-produced package; callers that ingest
|
|
20
|
+
* such an externally edited version must normalize it again and cannot assume
|
|
21
|
+
* the pre-save IDs still match.
|
|
22
|
+
*
|
|
23
|
+
* The pass patches part XML in place (string splices, no model round-trip),
|
|
24
|
+
* so documents carrying features folio's parser does not model come back
|
|
25
|
+
* with those features byte-identical. Contract:
|
|
26
|
+
*
|
|
27
|
+
* - Parts covered: `word/document.xml`, `word/header*.xml`, `word/footer*.xml`,
|
|
28
|
+
* `word/footnotes.xml`, `word/endnotes.xml`. Paragraphs nested in table
|
|
29
|
+
* cells and in `mc:Choice` text boxes are plain `<w:p>` elements inside
|
|
30
|
+
* those parts and are covered by the same scan. The comments part mints its
|
|
31
|
+
* own deterministic paraIds at save time (see `commentSerializer`) and is
|
|
32
|
+
* left untouched; its ids still count toward uniqueness.
|
|
33
|
+
* - Paragraphs inside `mc:Fallback` are never modified: Word regenerates the
|
|
34
|
+
* fallback branch (duplicating the `mc:Choice` ids) on save, so stamping or
|
|
35
|
+
* deduplicating there would churn on every Word round-trip.
|
|
36
|
+
* - Existing ids are preserved. Only three cases get a fresh id: a missing
|
|
37
|
+
* `paraId`, the reserved all-zero value (Word reads `00000000` as "no id"),
|
|
38
|
+
* and a duplicate of an id already seen earlier in the scan (first
|
|
39
|
+
* occurrence keeps it — the same rule `ParaIdAllocatorExtension` applies in
|
|
40
|
+
* the editor). `w14:textId` is written alongside a newly minted `paraId`
|
|
41
|
+
* (same value) and never touched otherwise; it is a text-revision marker,
|
|
42
|
+
* not identity.
|
|
43
|
+
* - Fresh ids are deterministic (`deterministicHexId` over document content,
|
|
44
|
+
* part path, and paragraph ordinal), so the pass is a pure function of the
|
|
45
|
+
* input bytes: retrying an ingest produces identical output.
|
|
46
|
+
* - Each patched part's root element gets `xmlns:w14` / `xmlns:mc`
|
|
47
|
+
* declarations and a `mc:Ignorable` listing `w14` when missing — non-Word
|
|
48
|
+
* producers declare neither, and absent `mc:Ignorable` handling is what
|
|
49
|
+
* makes pre-2010 consumers choke on the new attributes.
|
|
50
|
+
* - Idempotent: a document that already has full coverage is returned as the
|
|
51
|
+
* original bytes, untouched (`alreadyComplete: true`).
|
|
52
|
+
* - Digitally signed packages are returned untouched when already complete.
|
|
53
|
+
* When normalization would rewrite the package, it fails unless the caller
|
|
54
|
+
* explicitly allows signature invalidation after warning the user.
|
|
55
|
+
*/
|
|
56
|
+
/** A malformed or unsupported package prevented paragraph-ID normalization. */
|
|
57
|
+
var EnsureParaIdsError = class extends TaggedError("EnsureParaIdsError")() {};
|
|
58
|
+
const W14_NAMESPACE_URI = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
59
|
+
const MC_NAMESPACE_URI = "http://schemas.openxmlformats.org/markup-compatibility/2006";
|
|
60
|
+
const MC_IGNORABLE_W14 = "w14";
|
|
61
|
+
const DIGITAL_SIGNATURE_PART_PREFIX = "_xmlsignatures/";
|
|
62
|
+
/** Word reads an all-zero `w14:paraId` as "no id assigned". */
|
|
63
|
+
const RESERVED_ZERO_ID_PATTERN = /^0*$/u;
|
|
64
|
+
const DOCUMENT_PART = "word/document.xml";
|
|
65
|
+
const TARGET_PART_PATTERNS = [
|
|
66
|
+
/^word\/document\.xml$/u,
|
|
67
|
+
/^word\/header\d*\.xml$/u,
|
|
68
|
+
/^word\/footer\d*\.xml$/u,
|
|
69
|
+
/^word\/footnotes\.xml$/u,
|
|
70
|
+
/^word\/endnotes\.xml$/u
|
|
71
|
+
];
|
|
72
|
+
const PARAGRAPH_OPEN = "<w:p";
|
|
73
|
+
const FALLBACK_OPEN = "<mc:Fallback";
|
|
74
|
+
const FALLBACK_CLOSE = "</mc:Fallback>";
|
|
75
|
+
const OPAQUE_XML_REGIONS = [
|
|
76
|
+
{
|
|
77
|
+
open: "<!--",
|
|
78
|
+
close: "-->"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
open: "<![CDATA[",
|
|
82
|
+
close: "]]>"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
open: "<?",
|
|
86
|
+
close: "?>"
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
/**
|
|
90
|
+
* The parser accepts the `w:` fallback prefix for paraId, and comment parts
|
|
91
|
+
* link replies via `w15:paraId` — all three count toward uniqueness.
|
|
92
|
+
*/
|
|
93
|
+
const ANY_PARA_ID_PATTERN = /\bw(?:14|15)?:paraId=(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/gu;
|
|
94
|
+
const OPEN_TAG_PARA_ID_PATTERN = /\sw(?:14)?:paraId=(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/u;
|
|
95
|
+
const OPEN_TAG_TEXT_ID_PATTERN = /\sw(?:14)?:textId=(?<quote>["'])(?<id>[\s\S]*?)\k<quote>/u;
|
|
96
|
+
const XMLNS_W14_PATTERN = /\sxmlns:w14=(?<quote>["'])(?<value>[\s\S]*?)\k<quote>/u;
|
|
97
|
+
const XMLNS_MC_PATTERN = /\sxmlns:mc=(?<quote>["'])(?<value>[\s\S]*?)\k<quote>/u;
|
|
98
|
+
const MC_IGNORABLE_PATTERN = /\smc:Ignorable=(?<quote>["'])(?<value>[\s\S]*?)\k<quote>/u;
|
|
99
|
+
const applySplices = (xml, edits) => {
|
|
100
|
+
const ordered = [...edits].sort((a, b) => b.start - a.start);
|
|
101
|
+
let result = xml;
|
|
102
|
+
for (const { start, end, text } of ordered) result = result.slice(0, start) + text + result.slice(end);
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
const createEnsureParaIdsError = (message, cause) => new EnsureParaIdsError({
|
|
106
|
+
message,
|
|
107
|
+
...cause === void 0 ? {} : { cause }
|
|
108
|
+
});
|
|
109
|
+
const skipOpaqueXmlRegion = (xml, start, partPath) => {
|
|
110
|
+
for (const { open, close } of OPAQUE_XML_REGIONS) {
|
|
111
|
+
if (!xml.startsWith(open, start)) continue;
|
|
112
|
+
const closeStart = xml.indexOf(close, start + open.length);
|
|
113
|
+
if (closeStart === -1) throw createEnsureParaIdsError(`Unterminated ${open} region in ${partPath}`);
|
|
114
|
+
return closeStart + close.length;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
const attributeValueRange = (match, absoluteOffset, group) => {
|
|
119
|
+
const quote = match.groups["quote"];
|
|
120
|
+
const value = match.groups[group];
|
|
121
|
+
const start = absoluteOffset + match.index + match[0].indexOf(quote) + 1;
|
|
122
|
+
return {
|
|
123
|
+
start,
|
|
124
|
+
end: start + value.length
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
const collectExistingParaIds = (xml, into) => {
|
|
128
|
+
for (const match of xml.matchAll(ANY_PARA_ID_PATTERN)) {
|
|
129
|
+
const id = match.groups["id"];
|
|
130
|
+
if (id.length > 0) into.add(id.toUpperCase());
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const collectFallbackParaIds = (xml, partPath, into) => {
|
|
134
|
+
let fallbackDepth = 0;
|
|
135
|
+
let pos = 0;
|
|
136
|
+
while (pos < xml.length) {
|
|
137
|
+
const tagStart = xml.indexOf("<", pos);
|
|
138
|
+
if (tagStart === -1) return;
|
|
139
|
+
const opaqueEnd = skipOpaqueXmlRegion(xml, tagStart, partPath);
|
|
140
|
+
if (opaqueEnd !== null) {
|
|
141
|
+
pos = opaqueEnd;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (xml.startsWith(FALLBACK_OPEN, tagStart) && isXmlNameBoundary(xml[tagStart + 12])) {
|
|
145
|
+
const tagEnd = xml.indexOf(">", tagStart);
|
|
146
|
+
if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated <mc:Fallback> tag in ${partPath}`);
|
|
147
|
+
if (xml[tagEnd - 1] !== "/") fallbackDepth += 1;
|
|
148
|
+
pos = tagEnd + 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (xml.startsWith(FALLBACK_CLOSE, tagStart)) {
|
|
152
|
+
fallbackDepth = Math.max(0, fallbackDepth - 1);
|
|
153
|
+
pos = tagStart + 14;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (fallbackDepth === 0 || !xml.startsWith(PARAGRAPH_OPEN, tagStart) || !isXmlNameBoundary(xml[tagStart + 4])) {
|
|
157
|
+
pos = tagStart + 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const tagEnd = xml.indexOf(">", tagStart);
|
|
161
|
+
if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated <w:p> tag in ${partPath}`);
|
|
162
|
+
const id = OPEN_TAG_PARA_ID_PATTERN.exec(xml.slice(tagStart, tagEnd + 1))?.groups?.["id"];
|
|
163
|
+
if (id) into.add(id.toUpperCase());
|
|
164
|
+
pos = tagEnd + 1;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Deterministic fresh id for the paragraph at `ordinal` in `partPath`,
|
|
169
|
+
* salted past collisions with any id anywhere in the document.
|
|
170
|
+
*/
|
|
171
|
+
const mintParaId = (context, partPath, ordinal) => {
|
|
172
|
+
const seed = `${context.docKey}:${partPath}:${ordinal}`;
|
|
173
|
+
let id = deterministicHexId(seed);
|
|
174
|
+
for (let salt = 1; context.taken.has(id); salt += 1) id = deterministicHexId(`${seed}:${salt}`);
|
|
175
|
+
context.taken.add(id);
|
|
176
|
+
return id;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* One forward scan over a part: every `<w:p>` open tag outside `mc:Fallback`
|
|
180
|
+
* either keeps its paraId (valid, first occurrence) or gets a splice edit
|
|
181
|
+
* minting / replacing one. `seen` spans parts so the first-occurrence rule is
|
|
182
|
+
* document-wide across the scan order.
|
|
183
|
+
*/
|
|
184
|
+
const scanPart = (xml, partPath, context, seen) => {
|
|
185
|
+
const edits = [];
|
|
186
|
+
let assigned = 0;
|
|
187
|
+
let deduplicated = 0;
|
|
188
|
+
let ordinal = 0;
|
|
189
|
+
let fallbackDepth = 0;
|
|
190
|
+
let pos = 0;
|
|
191
|
+
while (pos < xml.length) {
|
|
192
|
+
const tagStart = xml.indexOf("<", pos);
|
|
193
|
+
if (tagStart === -1) break;
|
|
194
|
+
const opaqueEnd = skipOpaqueXmlRegion(xml, tagStart, partPath);
|
|
195
|
+
if (opaqueEnd !== null) {
|
|
196
|
+
pos = opaqueEnd;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (xml.startsWith(FALLBACK_OPEN, tagStart) && isXmlNameBoundary(xml[tagStart + 12])) {
|
|
200
|
+
const tagEnd = xml.indexOf(">", tagStart);
|
|
201
|
+
if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated <mc:Fallback> tag in ${partPath}`);
|
|
202
|
+
if (xml[tagEnd - 1] !== "/") fallbackDepth += 1;
|
|
203
|
+
pos = tagEnd + 1;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (xml.startsWith(FALLBACK_CLOSE, tagStart)) {
|
|
207
|
+
fallbackDepth = Math.max(0, fallbackDepth - 1);
|
|
208
|
+
pos = tagStart + 14;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!xml.startsWith(PARAGRAPH_OPEN, tagStart) || !isXmlNameBoundary(xml[tagStart + 4])) {
|
|
212
|
+
pos = tagStart + 1;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const tagEnd = xml.indexOf(">", tagStart);
|
|
216
|
+
if (tagEnd === -1) throw createEnsureParaIdsError(`Unterminated <w:p> tag in ${partPath}`);
|
|
217
|
+
pos = tagEnd + 1;
|
|
218
|
+
if (fallbackDepth > 0) continue;
|
|
219
|
+
ordinal += 1;
|
|
220
|
+
const openTag = xml.slice(tagStart, tagEnd + 1);
|
|
221
|
+
const paraIdMatch = OPEN_TAG_PARA_ID_PATTERN.exec(openTag);
|
|
222
|
+
if (!paraIdMatch) {
|
|
223
|
+
const id = mintParaId(context, partPath, ordinal);
|
|
224
|
+
const insertAt = tagStart + 4;
|
|
225
|
+
const textIdMatch = OPEN_TAG_TEXT_ID_PATTERN.exec(openTag);
|
|
226
|
+
if (textIdMatch) {
|
|
227
|
+
edits.push({
|
|
228
|
+
start: insertAt,
|
|
229
|
+
end: insertAt,
|
|
230
|
+
text: ` w14:paraId="${id}"`
|
|
231
|
+
});
|
|
232
|
+
const range = attributeValueRange(textIdMatch, tagStart, "id");
|
|
233
|
+
edits.push({
|
|
234
|
+
...range,
|
|
235
|
+
text: id
|
|
236
|
+
});
|
|
237
|
+
} else edits.push({
|
|
238
|
+
start: insertAt,
|
|
239
|
+
end: insertAt,
|
|
240
|
+
text: ` w14:paraId="${id}" w14:textId="${id}"`
|
|
241
|
+
});
|
|
242
|
+
assigned += 1;
|
|
243
|
+
seen.add(id);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const value = paraIdMatch.groups["id"].toUpperCase();
|
|
247
|
+
const unassigned = RESERVED_ZERO_ID_PATTERN.test(value);
|
|
248
|
+
if (!unassigned && !seen.has(value)) {
|
|
249
|
+
seen.add(value);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const id = mintParaId(context, partPath, ordinal);
|
|
253
|
+
edits.push({
|
|
254
|
+
...attributeValueRange(paraIdMatch, tagStart, "id"),
|
|
255
|
+
text: id
|
|
256
|
+
});
|
|
257
|
+
const textIdMatch = OPEN_TAG_TEXT_ID_PATTERN.exec(openTag);
|
|
258
|
+
if (textIdMatch) edits.push({
|
|
259
|
+
...attributeValueRange(textIdMatch, tagStart, "id"),
|
|
260
|
+
text: id
|
|
261
|
+
});
|
|
262
|
+
else {
|
|
263
|
+
const insertAt = tagStart + 4;
|
|
264
|
+
edits.push({
|
|
265
|
+
start: insertAt,
|
|
266
|
+
end: insertAt,
|
|
267
|
+
text: ` w14:textId="${id}"`
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (unassigned) assigned += 1;
|
|
271
|
+
else deduplicated += 1;
|
|
272
|
+
seen.add(id);
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
edits,
|
|
276
|
+
assigned,
|
|
277
|
+
deduplicated
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* Splice edits ensuring the part's root element declares `xmlns:w14` /
|
|
282
|
+
* `xmlns:mc` and lists `w14` in `mc:Ignorable`, so consumers that predate the
|
|
283
|
+
* 2010 extensions skip the new attributes instead of rejecting the part.
|
|
284
|
+
*/
|
|
285
|
+
const ensureRootNamespaces = (xml, partPath) => {
|
|
286
|
+
let pos = 0;
|
|
287
|
+
let rootStart = -1;
|
|
288
|
+
while (pos < xml.length) {
|
|
289
|
+
const lt = xml.indexOf("<", pos);
|
|
290
|
+
if (lt === -1) break;
|
|
291
|
+
const opaqueEnd = skipOpaqueXmlRegion(xml, lt, partPath);
|
|
292
|
+
if (opaqueEnd !== null) {
|
|
293
|
+
pos = opaqueEnd;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (xml[lt + 1] === "!") {
|
|
297
|
+
const skipTo = xml.indexOf(">", lt);
|
|
298
|
+
if (skipTo === -1) throw createEnsureParaIdsError(`Unterminated prolog in ${partPath}`);
|
|
299
|
+
pos = skipTo + 1;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
rootStart = lt;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (rootStart === -1) throw createEnsureParaIdsError(`No root element in ${partPath}`);
|
|
306
|
+
const rootEnd = xml.indexOf(">", rootStart);
|
|
307
|
+
if (rootEnd === -1) throw createEnsureParaIdsError(`Unterminated root element in ${partPath}`);
|
|
308
|
+
const rootTag = xml.slice(rootStart, rootEnd + 1);
|
|
309
|
+
const edits = [];
|
|
310
|
+
const declarations = [];
|
|
311
|
+
const mcNamespace = XMLNS_MC_PATTERN.exec(rootTag);
|
|
312
|
+
if (mcNamespace) {
|
|
313
|
+
if (mcNamespace.groups["value"] !== MC_NAMESPACE_URI) throw createEnsureParaIdsError(`xmlns:mc has an unexpected namespace URI in ${partPath}`);
|
|
314
|
+
} else declarations.push(` xmlns:mc="${MC_NAMESPACE_URI}"`);
|
|
315
|
+
const w14Namespace = XMLNS_W14_PATTERN.exec(rootTag);
|
|
316
|
+
if (w14Namespace) {
|
|
317
|
+
if (w14Namespace.groups["value"] !== W14_NAMESPACE_URI) throw createEnsureParaIdsError(`xmlns:w14 has an unexpected namespace URI in ${partPath}`);
|
|
318
|
+
} else declarations.push(` xmlns:w14="${W14_NAMESPACE_URI}"`);
|
|
319
|
+
const ignorable = MC_IGNORABLE_PATTERN.exec(rootTag);
|
|
320
|
+
if (ignorable) {
|
|
321
|
+
const tokens = ignorable.groups["value"].split(/\s+/u).filter((token) => token.length > 0);
|
|
322
|
+
if (!tokens.includes(MC_IGNORABLE_W14)) {
|
|
323
|
+
const { end } = attributeValueRange(ignorable, rootStart, "value");
|
|
324
|
+
const text = tokens.length === 0 ? MC_IGNORABLE_W14 : ` ${MC_IGNORABLE_W14}`;
|
|
325
|
+
edits.push({
|
|
326
|
+
start: end,
|
|
327
|
+
end,
|
|
328
|
+
text
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} else declarations.push(` mc:Ignorable="${MC_IGNORABLE_W14}"`);
|
|
332
|
+
if (declarations.length > 0) {
|
|
333
|
+
let nameEnd = rootStart + 1;
|
|
334
|
+
while (nameEnd < xml.length && !isXmlNameBoundary(xml[nameEnd])) nameEnd += 1;
|
|
335
|
+
edits.push({
|
|
336
|
+
start: nameEnd,
|
|
337
|
+
end: nameEnd,
|
|
338
|
+
text: declarations.join("")
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return edits;
|
|
342
|
+
};
|
|
343
|
+
/** OPC part names are case-insensitive; compare lowercased. */
|
|
344
|
+
const isTargetPart = (path) => TARGET_PART_PATTERNS.some((pattern) => pattern.test(path.toLowerCase()));
|
|
345
|
+
const toUint8Array = (docx) => docx instanceof Uint8Array ? docx : new Uint8Array(docx);
|
|
346
|
+
const hasDigitalSignatureParts = (zip) => Object.values(zip.files).some(({ dir, name }) => !dir && name.toLowerCase().startsWith(DIGITAL_SIGNATURE_PART_PREFIX));
|
|
347
|
+
/**
|
|
348
|
+
* Backfill `w14:paraId` on every paragraph of a `.docx` buffer. See the
|
|
349
|
+
* module doc for the exact contract. Throws {@link EnsureParaIdsError} when
|
|
350
|
+
* the buffer is not a WordprocessingML package or a part is malformed.
|
|
351
|
+
*/
|
|
352
|
+
const ensureParaIdsInternal = async (docx, options) => {
|
|
353
|
+
const zip = await JSZip.loadAsync(docx);
|
|
354
|
+
const xmlPartNames = [];
|
|
355
|
+
zip.forEach((relativePath, entry) => {
|
|
356
|
+
const lower = relativePath.toLowerCase();
|
|
357
|
+
if (!entry.dir && lower.startsWith("word/") && lower.endsWith(".xml")) xmlPartNames.push(relativePath);
|
|
358
|
+
});
|
|
359
|
+
const documentPartName = xmlPartNames.find((name) => name.toLowerCase() === DOCUMENT_PART);
|
|
360
|
+
if (documentPartName === void 0) throw createEnsureParaIdsError("word/document.xml not found: not a WordprocessingML package");
|
|
361
|
+
const partTexts = /* @__PURE__ */ new Map();
|
|
362
|
+
for (const name of xmlPartNames) {
|
|
363
|
+
const entry = zip.file(name);
|
|
364
|
+
if (entry) partTexts.set(name, await entry.async("text"));
|
|
365
|
+
}
|
|
366
|
+
const taken = /* @__PURE__ */ new Set();
|
|
367
|
+
for (const text of partTexts.values()) collectExistingParaIds(text, taken);
|
|
368
|
+
const context = {
|
|
369
|
+
taken,
|
|
370
|
+
docKey: deterministicHexId(partTexts.get(documentPartName))
|
|
371
|
+
};
|
|
372
|
+
const targetParts = [documentPartName, ...xmlPartNames.filter((name) => name !== documentPartName && isTargetPart(name)).sort((a, b) => a.localeCompare(b))];
|
|
373
|
+
const seen = /* @__PURE__ */ new Set();
|
|
374
|
+
for (const [partPath, xml] of partTexts) if (isTargetPart(partPath)) collectFallbackParaIds(xml, partPath, seen);
|
|
375
|
+
else collectExistingParaIds(xml, seen);
|
|
376
|
+
const updates = /* @__PURE__ */ new Map();
|
|
377
|
+
let assigned = 0;
|
|
378
|
+
let deduplicated = 0;
|
|
379
|
+
for (const partPath of targetParts) {
|
|
380
|
+
const xml = partTexts.get(partPath);
|
|
381
|
+
const scan = scanPart(xml, partPath, context, seen);
|
|
382
|
+
if (scan.edits.length === 0) continue;
|
|
383
|
+
assigned += scan.assigned;
|
|
384
|
+
deduplicated += scan.deduplicated;
|
|
385
|
+
updates.set(partPath, applySplices(xml, [...scan.edits, ...ensureRootNamespaces(xml, partPath)]));
|
|
386
|
+
}
|
|
387
|
+
if (updates.size === 0) return {
|
|
388
|
+
docx: toUint8Array(docx),
|
|
389
|
+
assigned: 0,
|
|
390
|
+
deduplicated: 0,
|
|
391
|
+
alreadyComplete: true
|
|
392
|
+
};
|
|
393
|
+
if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw createEnsureParaIdsError("Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable.");
|
|
394
|
+
for (const [partPath, content] of updates) zip.file(partPath, content, {
|
|
395
|
+
compression: "DEFLATE",
|
|
396
|
+
compressionOptions: { level: 6 }
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
docx: await zip.generateAsync({
|
|
400
|
+
type: "uint8array",
|
|
401
|
+
compression: "DEFLATE",
|
|
402
|
+
compressionOptions: { level: 6 }
|
|
403
|
+
}),
|
|
404
|
+
assigned,
|
|
405
|
+
deduplicated,
|
|
406
|
+
alreadyComplete: false
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
/**
|
|
410
|
+
* Backfill `w14:paraId` on every paragraph of a `.docx` buffer. All package,
|
|
411
|
+
* XML, and compression failures are surfaced as {@link EnsureParaIdsError}.
|
|
412
|
+
*/
|
|
413
|
+
const ensureParaIds = async (docx, options = {}) => {
|
|
414
|
+
try {
|
|
415
|
+
return await ensureParaIdsInternal(docx, options);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (error instanceof EnsureParaIdsError) throw error;
|
|
418
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
419
|
+
throw createEnsureParaIdsError(`Failed to normalize paragraph IDs: ${message}`, error);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
//#endregion
|
|
423
|
+
export { EnsureParaIdsError, ensureParaIds };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { findChild, findChildren, getAttribute, parseXmlDocument } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/fontTableParser.ts
|
|
3
|
+
const FONT_FAMILIES = [
|
|
4
|
+
"decorative",
|
|
5
|
+
"modern",
|
|
6
|
+
"roman",
|
|
7
|
+
"script",
|
|
8
|
+
"swiss",
|
|
9
|
+
"auto"
|
|
10
|
+
];
|
|
11
|
+
const FONT_PITCHES = [
|
|
12
|
+
"default",
|
|
13
|
+
"fixed",
|
|
14
|
+
"variable"
|
|
15
|
+
];
|
|
16
|
+
const parseFontTable = (xml) => {
|
|
17
|
+
if (!xml) return;
|
|
18
|
+
const root = parseXmlDocument(xml);
|
|
19
|
+
if (!root) return;
|
|
20
|
+
const fonts = [];
|
|
21
|
+
for (const element of findChildren(root, "w", "font")) {
|
|
22
|
+
const font = parseFont(element);
|
|
23
|
+
if (font) fonts.push(font);
|
|
24
|
+
}
|
|
25
|
+
return fonts.length > 0 ? { fonts } : void 0;
|
|
26
|
+
};
|
|
27
|
+
const parseFont = (element) => {
|
|
28
|
+
const name = getAttribute(element, "w", "name");
|
|
29
|
+
if (!name) return;
|
|
30
|
+
const font = { name };
|
|
31
|
+
assignValue(font, "altName", childValue(element, "altName"));
|
|
32
|
+
assignValue(font, "panose1", childValue(element, "panose1"));
|
|
33
|
+
assignValue(font, "charset", childValue(element, "charset"));
|
|
34
|
+
const family = childValue(element, "family");
|
|
35
|
+
if (family && isFontFamily(family)) font.family = family;
|
|
36
|
+
const pitch = childValue(element, "pitch");
|
|
37
|
+
if (pitch && isFontPitch(pitch)) font.pitch = pitch;
|
|
38
|
+
const sig = findChild(element, "w", "sig");
|
|
39
|
+
if (sig) {
|
|
40
|
+
const parsed = {};
|
|
41
|
+
assignSignatureValue(parsed, "usb0", getAttribute(sig, "w", "usb0") ?? void 0);
|
|
42
|
+
assignSignatureValue(parsed, "usb1", getAttribute(sig, "w", "usb1") ?? void 0);
|
|
43
|
+
assignSignatureValue(parsed, "usb2", getAttribute(sig, "w", "usb2") ?? void 0);
|
|
44
|
+
assignSignatureValue(parsed, "usb3", getAttribute(sig, "w", "usb3") ?? void 0);
|
|
45
|
+
assignSignatureValue(parsed, "csb0", getAttribute(sig, "w", "csb0") ?? void 0);
|
|
46
|
+
assignSignatureValue(parsed, "csb1", getAttribute(sig, "w", "csb1") ?? void 0);
|
|
47
|
+
if (Object.values(parsed).some((value) => value !== void 0)) font.sig = parsed;
|
|
48
|
+
}
|
|
49
|
+
assignValue(font, "embedRegular", relationshipId(element, "embedRegular"));
|
|
50
|
+
assignValue(font, "embedBold", relationshipId(element, "embedBold"));
|
|
51
|
+
assignValue(font, "embedItalic", relationshipId(element, "embedItalic"));
|
|
52
|
+
assignValue(font, "embedBoldItalic", relationshipId(element, "embedBoldItalic"));
|
|
53
|
+
return font;
|
|
54
|
+
};
|
|
55
|
+
const childValue = (element, name) => {
|
|
56
|
+
const child = findChild(element, "w", name);
|
|
57
|
+
return child ? getAttribute(child, "w", "val") ?? void 0 : void 0;
|
|
58
|
+
};
|
|
59
|
+
const relationshipId = (element, name) => {
|
|
60
|
+
const child = findChild(element, "w", name);
|
|
61
|
+
return child ? getAttribute(child, "r", "id") ?? void 0 : void 0;
|
|
62
|
+
};
|
|
63
|
+
const assignValue = (font, key, value) => {
|
|
64
|
+
if (value !== void 0) font[key] = value;
|
|
65
|
+
};
|
|
66
|
+
const assignSignatureValue = (signature, key, value) => {
|
|
67
|
+
if (value !== void 0) signature[key] = value;
|
|
68
|
+
};
|
|
69
|
+
const isFontFamily = (value) => FONT_FAMILIES.some((family) => family === value);
|
|
70
|
+
const isFontPitch = (value) => FONT_PITCHES.some((pitch) => pitch === value);
|
|
71
|
+
//#endregion
|
|
72
|
+
export { parseFontTable };
|
|
@@ -37,7 +37,10 @@ const colorFrom = (parent, fallback) => {
|
|
|
37
37
|
const value = getAttribute(parent, "w", "val");
|
|
38
38
|
if (value && HEX_COLOR.test(value)) return value.toUpperCase();
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
if (findChildByLocalName(parent, "noFill")) return "none";
|
|
41
|
+
const srgb = findChildByLocalName(findChildByLocalName(parent, "solidFill"), "srgbClr");
|
|
42
|
+
const referencedSrgb = findChildByLocalName(parent, "srgbClr");
|
|
43
|
+
const value = getAttribute(srgb ?? referencedSrgb, null, "val");
|
|
41
44
|
if (value && HEX_COLOR.test(value)) return value.toUpperCase();
|
|
42
45
|
return fallback;
|
|
43
46
|
};
|
|
@@ -80,9 +83,11 @@ const renderGeometry = (wsp) => {
|
|
|
80
83
|
const { x, y, width, height } = childTransform(wsp);
|
|
81
84
|
if (width <= 0 || height <= 0) return "";
|
|
82
85
|
const spPr = findChildByLocalName(wsp, "spPr");
|
|
86
|
+
const style = findChildByLocalName(wsp, "style");
|
|
87
|
+
const styleStroke = colorFrom(findChildByLocalName(style, "lnRef"), "none");
|
|
83
88
|
const fill = colorFrom(spPr, "none");
|
|
84
89
|
const line = findChildByLocalName(spPr, "ln");
|
|
85
|
-
const stroke = colorFrom(line,
|
|
90
|
+
const stroke = colorFrom(line, styleStroke);
|
|
86
91
|
const strokeWidth = line ? parseNumericAttribute(line, null, "w") ?? DEFAULT_LINE_WIDTH_EMU : 0;
|
|
87
92
|
const paths = findAllDeep(findChildByLocalName(spPr, "custGeom"), "a", "path");
|
|
88
93
|
if (paths.length === 0) return `<rect x="${x}" y="${y}" width="${width}" height="${height}" fill="${fill === "none" ? "none" : `#${fill}`}" stroke="${stroke === "none" ? "none" : `#${stroke}`}" stroke-width="${strokeWidth}"/>`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
|
|
2
2
|
import { parseWatermark } from "./watermarkParser.js";
|
|
3
3
|
import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
|
|
4
|
-
import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
|
|
5
4
|
import { parseBlockContent } from "./blockContentParser.js";
|
|
5
|
+
import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
|
|
6
6
|
//#region src/docx/headerFooterParser.ts
|
|
7
7
|
/**
|
|
8
8
|
* Parse a header XML file (word/header*.xml)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
2
1
|
import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
|
|
2
|
+
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
3
3
|
//#region src/docx/normalizeBaseDirection.ts
|
|
4
4
|
const runText = (run) => {
|
|
5
5
|
let text = "";
|
|
@@ -603,7 +603,9 @@ function computeListRendering(numPr, numbering) {
|
|
|
603
603
|
const markerFont = level.rPr?.fontFamily?.ascii || level.rPr?.fontFamily?.hAnsi;
|
|
604
604
|
if (markerFont) rendering.markerFontFamily = markerFont;
|
|
605
605
|
if (level.rPr?.fontSize) rendering.markerFontSize = level.rPr.fontSize / 2;
|
|
606
|
+
if (level.rPr?.bold !== void 0) rendering.markerBold = level.rPr.bold;
|
|
606
607
|
if (level.rPr?.allCaps) rendering.markerAllCaps = true;
|
|
608
|
+
if (level.lvlJc) rendering.markerAlignment = level.lvlJc;
|
|
607
609
|
if (level.suffix) rendering.markerSuffix = level.suffix;
|
|
608
610
|
if (instance?.abstractNumId !== void 0) rendering.abstractNumId = instance.abstractNumId;
|
|
609
611
|
if (overrideForLevel?.startOverride !== void 0) rendering.startOverride = overrideForLevel.startOverride;
|
|
@@ -110,10 +110,18 @@ function parseTabStops(tabs) {
|
|
|
110
110
|
function parseFrameProperties(framePr) {
|
|
111
111
|
if (!framePr) return;
|
|
112
112
|
const frame = {};
|
|
113
|
+
const dropCap = getAttribute(framePr, "w", "dropCap");
|
|
114
|
+
if (dropCap === "none" || dropCap === "drop" || dropCap === "margin") frame.dropCap = dropCap;
|
|
115
|
+
const lines = parseNumericAttribute(framePr, "w", "lines");
|
|
116
|
+
if (lines !== void 0) frame.lines = lines;
|
|
113
117
|
const w = parseNumericAttribute(framePr, "w", "w");
|
|
114
118
|
if (w !== void 0) frame.width = w;
|
|
115
119
|
const h = parseNumericAttribute(framePr, "w", "h");
|
|
116
120
|
if (h !== void 0) frame.height = h;
|
|
121
|
+
const hSpace = parseNumericAttribute(framePr, "w", "hSpace");
|
|
122
|
+
if (hSpace !== void 0) frame.hSpace = hSpace;
|
|
123
|
+
const vSpace = parseNumericAttribute(framePr, "w", "vSpace");
|
|
124
|
+
if (vSpace !== void 0) frame.vSpace = vSpace;
|
|
117
125
|
const hAnchor = getAttribute(framePr, "w", "hAnchor");
|
|
118
126
|
if (hAnchor === "text" || hAnchor === "margin" || hAnchor === "page") frame.hAnchor = hAnchor;
|
|
119
127
|
const vAnchor = getAttribute(framePr, "w", "vAnchor");
|
|
@@ -156,12 +164,18 @@ function collectFirstParagraphPropertyChildren(pPr) {
|
|
|
156
164
|
case "keepNext":
|
|
157
165
|
children.keepNext ??= child;
|
|
158
166
|
break;
|
|
167
|
+
case "kinsoku":
|
|
168
|
+
children.kinsoku ??= child;
|
|
169
|
+
break;
|
|
159
170
|
case "numPr":
|
|
160
171
|
children.numPr ??= child;
|
|
161
172
|
break;
|
|
162
173
|
case "outlineLvl":
|
|
163
174
|
children.outlineLvl ??= child;
|
|
164
175
|
break;
|
|
176
|
+
case "overflowPunct":
|
|
177
|
+
children.overflowPunct ??= child;
|
|
178
|
+
break;
|
|
165
179
|
case "pageBreakBefore":
|
|
166
180
|
children.pageBreakBefore ??= child;
|
|
167
181
|
break;
|
|
@@ -218,6 +232,10 @@ function parseParagraphProperties(pPr, theme, styles) {
|
|
|
218
232
|
if (!pPr) return;
|
|
219
233
|
const formatting = {};
|
|
220
234
|
const propertyChildren = collectFirstParagraphPropertyChildren(pPr);
|
|
235
|
+
const kinsoku = propertyChildren.kinsoku;
|
|
236
|
+
if (kinsoku) formatting.kinsoku = parseBooleanElement(kinsoku);
|
|
237
|
+
const overflowPunct = propertyChildren.overflowPunct;
|
|
238
|
+
if (overflowPunct) formatting.overflowPunctuation = parseBooleanElement(overflowPunct);
|
|
221
239
|
const jc = propertyChildren.jc;
|
|
222
240
|
if (jc) {
|
|
223
241
|
const val = narrowEnum(getAttribute(jc, "w", "val"), ParagraphAlignmentSchema);
|
|
@@ -517,7 +535,7 @@ function isTrackedChangeWrapperChild(content) {
|
|
|
517
535
|
return content.type === "run" || content.type === "hyperlink";
|
|
518
536
|
}
|
|
519
537
|
function isInlineSdtContent(content) {
|
|
520
|
-
return content.type === "run" || content.type === "hyperlink" || content.type === "simpleField" || content.type === "complexField" || content.type === "inlineSdt" || content.type === "mathEquation";
|
|
538
|
+
return content.type === "run" || content.type === "hyperlink" || content.type === "simpleField" || content.type === "complexField" || content.type === "inlineSdt" || content.type === "insertion" || content.type === "deletion" || content.type === "mathEquation";
|
|
521
539
|
}
|
|
522
540
|
function pushTrackedChangeWrapper({ contents, type, info, content, preserveEmpty = false }) {
|
|
523
541
|
if (content.length === 0 && !preserveEmpty) return;
|
|
@@ -973,7 +991,9 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
|
|
|
973
991
|
const markerFont = level.rPr?.fontFamily?.ascii || level.rPr?.fontFamily?.hAnsi;
|
|
974
992
|
if (markerFont) listRendering.markerFontFamily = markerFont;
|
|
975
993
|
if (level.rPr?.fontSize) listRendering.markerFontSize = level.rPr.fontSize / 2;
|
|
994
|
+
if (level.rPr?.bold !== void 0) listRendering.markerBold = level.rPr.bold;
|
|
976
995
|
if (level.rPr?.allCaps) listRendering.markerAllCaps = true;
|
|
996
|
+
if (level.lvlJc) listRendering.markerAlignment = level.lvlJc;
|
|
977
997
|
if (level.suffix) listRendering.markerSuffix = level.suffix;
|
|
978
998
|
let implicitChildLevelAdvances = 0;
|
|
979
999
|
const foldedMarkerSuffix = [];
|