@ansonlai/docx-redline-js 0.1.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 (64) hide show
  1. package/AGENTS.md +176 -0
  2. package/ARCHITECTURE.md +121 -0
  3. package/LICENSE +21 -0
  4. package/README.md +177 -0
  5. package/adapters/config.js +43 -0
  6. package/adapters/logger.js +89 -0
  7. package/adapters/xml-adapter.js +74 -0
  8. package/core/list-targeting.js +398 -0
  9. package/core/ooxml-identifiers.js +15 -0
  10. package/core/paragraph-offset-policy.js +50 -0
  11. package/core/paragraph-targeting.js +501 -0
  12. package/core/table-targeting.js +233 -0
  13. package/core/types.js +204 -0
  14. package/core/xml-query.js +99 -0
  15. package/dist/docx-redline-js.esm.js +8801 -0
  16. package/dist/docx-redline-js.esm.js.map +7 -0
  17. package/dist/docx-redline-js.esm.min.js +195 -0
  18. package/dist/docx-redline-js.esm.min.js.map +7 -0
  19. package/engine/format-application.js +358 -0
  20. package/engine/format-extraction.js +232 -0
  21. package/engine/format-paragraph-targeting.js +208 -0
  22. package/engine/format-span-application.js +178 -0
  23. package/engine/formatting-removal.js +330 -0
  24. package/engine/oxml-engine.js +279 -0
  25. package/engine/reconstruction-mapper.js +270 -0
  26. package/engine/reconstruction-mode.js +38 -0
  27. package/engine/reconstruction-writer.js +276 -0
  28. package/engine/rpr-helpers.js +194 -0
  29. package/engine/run-builders.js +235 -0
  30. package/engine/surgical-mode.js +520 -0
  31. package/engine/table-cell-context.js +151 -0
  32. package/engine/table-mode.js +172 -0
  33. package/index.js +308 -0
  34. package/orchestration/list-markdown.js +141 -0
  35. package/orchestration/list-parsing.js +73 -0
  36. package/orchestration/list-structural-fallback.js +530 -0
  37. package/orchestration/redline-operation-converter.js +141 -0
  38. package/orchestration/route-plan.js +160 -0
  39. package/package.json +76 -0
  40. package/pipeline/content-analysis.js +107 -0
  41. package/pipeline/diff-engine.js +204 -0
  42. package/pipeline/ingestion-export.js +255 -0
  43. package/pipeline/ingestion-paragraph.js +351 -0
  44. package/pipeline/ingestion-table.js +169 -0
  45. package/pipeline/ingestion-xml.js +39 -0
  46. package/pipeline/ingestion.js +8 -0
  47. package/pipeline/list-generation.js +280 -0
  48. package/pipeline/list-markers.js +77 -0
  49. package/pipeline/markdown-processor.js +160 -0
  50. package/pipeline/patching.js +408 -0
  51. package/pipeline/pipeline.js +326 -0
  52. package/pipeline/serialization.js +395 -0
  53. package/services/browser-demo-prompt-context.js +345 -0
  54. package/services/comment-builders.js +60 -0
  55. package/services/comment-engine.js +248 -0
  56. package/services/comment-locator.js +197 -0
  57. package/services/comment-package.js +113 -0
  58. package/services/numbering-helpers.js +416 -0
  59. package/services/numbering-service.js +290 -0
  60. package/services/package-builder.js +147 -0
  61. package/services/standalone-docx-plumbing.js +443 -0
  62. package/services/standalone-operation-runner.js +1169 -0
  63. package/services/table-reconciliation.js +344 -0
  64. package/standalone.js +5 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * OOXML Reconciliation Pipeline - Numbering Service
3
+ *
4
+ * Manages list numbering to ensure continuation and consistent formatting.
5
+ */
6
+
7
+ import { NS_W, NumberFormat, NumberSuffix } from '../core/types.js';
8
+
9
+ export class NumberingService {
10
+ constructor() {
11
+ this.contextMap = new Map(); // Cache for numIds found in the document
12
+ this.nextNumId = 1000;
13
+ this.customConfigs = []; // Track custom configs needed for current run
14
+ }
15
+
16
+ /**
17
+ * Preserves an existing numId for a given format signature.
18
+ *
19
+ * @param {string} signature - Format signature (e.g., 'bullet' or 'decimal')
20
+ * @param {string} numId - Existing numId from Word
21
+ */
22
+ registerExistingNumId(signature, numId) {
23
+ this.contextMap.set(signature, numId);
24
+ }
25
+
26
+ /**
27
+ * Resolves the best numId to use for a requested list format.
28
+ *
29
+ * @param {Object} formatConfig - Requested format (type, depth)
30
+ * @param {Object} existingContext - Context from adjacent paragraph
31
+ * @returns {string} The numId to use
32
+ */
33
+ getOrCreateNumId(formatConfig, existingContext = null) {
34
+ const requestedType = formatConfig.type || NumberFormat.BULLET;
35
+
36
+ // Priority 1: Use existing context if it matches the requested type
37
+ if (existingContext && existingContext.numId) {
38
+ if (existingContext.type === requestedType || existingContext.type === 'unknown') {
39
+ return existingContext.numId;
40
+ }
41
+ }
42
+
43
+ // Priority 2: Use cached numId for this format
44
+ if (this.contextMap.has(requestedType)) {
45
+ return this.contextMap.get(requestedType);
46
+ }
47
+
48
+ // Priority 3: Special Handling for Outline (recursive 1.1.1)
49
+ if (requestedType === NumberFormat.OUTLINE) {
50
+ // Use our new predefined Outline scheme (numId 3)
51
+ return '3';
52
+ }
53
+
54
+ // Priority 4: Fallback based on type
55
+ if (requestedType === NumberFormat.DECIMAL) return '2';
56
+ if (requestedType === NumberFormat.BULLET) return '1';
57
+
58
+ // Priority 5: Handle specialized Alpha/Roman at Level 0 (or if context doesn't match)
59
+ // If we are at Level 0 and want something other than Decimal, we need a custom config
60
+ const ilvl = existingContext ? parseInt(existingContext.ilvl || '0') : 0;
61
+ if (ilvl === 0 && requestedType !== NumberFormat.DECIMAL && requestedType !== NumberFormat.BULLET) {
62
+ // Find or create a custom config for this specific format
63
+ const signature = `custom_${requestedType}`;
64
+ if (this.contextMap.has(signature)) {
65
+ return this.contextMap.get(signature);
66
+ }
67
+
68
+ const newNumId = String(this.nextNumId++);
69
+ this.customConfigs.push({
70
+ numId: newNumId,
71
+ levels: [
72
+ { format: requestedType, suffix: formatConfig.suffix || NumberSuffix.PERIOD }
73
+ ]
74
+ });
75
+ this.contextMap.set(signature, newNumId);
76
+ return newNumId;
77
+ }
78
+
79
+ // Default fallback to NumId 2 (Legal/Nested)
80
+ return '2';
81
+ }
82
+
83
+ /**
84
+ * Detects numbering format from a string marker (e.g. "1.", "(a)", "i.", "1.1.")
85
+ *
86
+ * @param {string} marker - The marker text
87
+ * @returns {Object} { format, suffix, depth }
88
+ */
89
+ detectNumberingFormat(marker) {
90
+ const m = (marker || '').trim();
91
+ if (!m) return { format: NumberFormat.BULLET, suffix: NumberSuffix.NONE, depth: 0 };
92
+
93
+ // Bullet
94
+ if (/^[-*•]$/.test(m)) {
95
+ return { format: NumberFormat.BULLET, suffix: NumberSuffix.NONE, depth: 0 };
96
+ }
97
+
98
+ // Hierarchical outline: 1.1.2 or 4.1.2.3
99
+ const outlineMatch = m.match(/^(\d+(?:\.\d+)+)\.?$/);
100
+ if (outlineMatch) {
101
+ const depth = outlineMatch[1].split('.').length - 1;
102
+ return { format: NumberFormat.OUTLINE, suffix: NumberSuffix.PERIOD, depth };
103
+ }
104
+
105
+ // Parenthesized formats: (a), (i), (1)
106
+ if (/^\([a-z]\)$/.test(m)) {
107
+ return { format: NumberFormat.LOWER_ALPHA, suffix: NumberSuffix.PAREN_BOTH, depth: 0 };
108
+ }
109
+ if (/^\([ivxlc]+\)$/i.test(m)) {
110
+ const isLower = m === m.toLowerCase();
111
+ return { format: isLower ? NumberFormat.LOWER_ROMAN : NumberFormat.UPPER_ROMAN, suffix: NumberSuffix.PAREN_BOTH, depth: 0 };
112
+ }
113
+ if (/^\(\d+\)$/.test(m)) {
114
+ return { format: NumberFormat.DECIMAL, suffix: NumberSuffix.PAREN_BOTH, depth: 0 };
115
+ }
116
+
117
+ // Standard formats with period: 1., a., A., i., I.
118
+ if (/^\d+\.$/.test(m)) {
119
+ return { format: NumberFormat.DECIMAL, suffix: NumberSuffix.PERIOD, depth: 0 };
120
+ }
121
+ if (/^[a-z]\.$/.test(m)) {
122
+ return { format: NumberFormat.LOWER_ALPHA, suffix: NumberSuffix.PERIOD, depth: 0 };
123
+ }
124
+ if (/^[A-Z]\.$/.test(m)) {
125
+ return { format: NumberFormat.UPPER_ALPHA, suffix: NumberSuffix.PERIOD, depth: 0 };
126
+ }
127
+ if (/^[ivxlc]+\.$/i.test(m)) {
128
+ const isLower = m === m.toLowerCase();
129
+ return { format: isLower ? NumberFormat.LOWER_ROMAN : NumberFormat.UPPER_ROMAN, suffix: NumberSuffix.PERIOD, depth: 0 };
130
+ }
131
+
132
+ // Default to decimal if it looks like a number
133
+ if (/^\d+/.test(m)) return { format: NumberFormat.DECIMAL, suffix: NumberSuffix.PERIOD, depth: 0 };
134
+
135
+ return { format: NumberFormat.BULLET, suffix: NumberSuffix.NONE, depth: 0 };
136
+ }
137
+
138
+ /**
139
+ * Maps internal format to OOXML numFmt string
140
+ */
141
+ formatToOoxmlNumFmt(format) {
142
+ const map = {
143
+ [NumberFormat.DECIMAL]: 'decimal',
144
+ [NumberFormat.LOWER_ALPHA]: 'lowerLetter',
145
+ [NumberFormat.UPPER_ALPHA]: 'upperLetter',
146
+ [NumberFormat.LOWER_ROMAN]: 'lowerRoman',
147
+ [NumberFormat.UPPER_ROMAN]: 'upperRoman',
148
+ [NumberFormat.BULLET]: 'bullet',
149
+ [NumberFormat.OUTLINE]: 'decimal'
150
+ };
151
+ return map[format] || 'decimal';
152
+ }
153
+
154
+ /**
155
+ * Maps levels and suffix to OOXML lvlText
156
+ */
157
+ suffixToOoxmlLevelText(format, suffix, ilvl = 0) {
158
+ if (format === NumberFormat.BULLET) return '•';
159
+
160
+ // Placeholder for current level
161
+ const num = `%${ilvl + 1}`;
162
+
163
+ if (format === NumberFormat.OUTLINE) {
164
+ // Outline %1.%2.%3.
165
+ return Array(ilvl + 1).fill(0).map((_, i) => `%${i + 1}`).join('.') + '.';
166
+ }
167
+
168
+ switch (suffix) {
169
+ case NumberSuffix.PERIOD: return `${num}.`;
170
+ case NumberSuffix.PAREN_RIGHT: return `${num})`;
171
+ case NumberSuffix.PAREN_BOTH: return `(${num})`;
172
+ default: return num;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Builds paragraph properties for a list item.
178
+ *
179
+ * @param {string} numId - The numId
180
+ * @param {number} ilvl - Indentation level
181
+ * @returns {string} Serialized w:pPr XML
182
+ */
183
+ buildListPPr(numId, ilvl, options = {}) {
184
+ const includeListParagraphStyle = options.includeListParagraphStyle === true;
185
+ const styleXml = includeListParagraphStyle ? '\n <w:pStyle w:val="ListParagraph"/>' : '';
186
+ return `
187
+ <w:pPr>${styleXml}
188
+ <w:numPr>
189
+ <w:ilvl w:val="${ilvl}"/>
190
+ <w:numId w:val="${numId}"/>
191
+ </w:numPr>
192
+ </w:pPr>
193
+ `;
194
+ }
195
+
196
+ /**
197
+ * Generates a full w:numbering XML block including custom legal schemes.
198
+ *
199
+ * @param {Array} externalConfigs - Optional array of { numId, levels: [{ format, suffix }] }
200
+ * @returns {string} w:numbering XML
201
+ */
202
+ generateNumberingXml(externalConfigs = []) {
203
+ // Merge internal customConfigs with external ones
204
+ const allCustomConfigs = [...this.customConfigs, ...externalConfigs];
205
+
206
+ // Default Bullet (numId 1)
207
+ let abstractNum0 = `
208
+ <w:abstractNum w:abstractNumId="0">
209
+ <w:multiLevelType w:val="hybridMultilevel"/>
210
+ ${[0, 1, 2, 3, 4, 5, 6, 7, 8].map(lvl => `
211
+ <w:lvl w:ilvl="${lvl}">
212
+ <w:start w:val="1"/>
213
+ <w:numFmt w:val="${lvl % 3 === 0 ? 'bullet' : lvl % 3 === 1 ? 'circle' : 'square'}"/>
214
+ <w:lvlText w:val="${lvl % 3 === 0 ? '•' : lvl % 3 === 1 ? '○' : '■'}"/>
215
+ <w:lvlJc w:val="left"/>
216
+ <w:pPr><w:ind w:left="${720 * (lvl + 1)}" w:hanging="360"/></w:pPr>
217
+ </w:lvl>`).join('')}
218
+ </w:abstractNum>`;
219
+
220
+ // Default Numbered (numId 2) - US/Legal Style
221
+ // Level 0: 1.
222
+ // Level 1: (a)
223
+ // Level 2: (i)
224
+ // Level 3: (1)
225
+ // Level 4: (a) - repeating with different indent...
226
+ let abstractNum1 = `
227
+ <w:abstractNum w:abstractNumId="1">
228
+ <w:multiLevelType w:val="multilevel"/>
229
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl>
230
+ <w:lvl w:ilvl="1"><w:start w:val="1"/><w:numFmt w:val="lowerLetter"/><w:lvlText w:val="(%2)"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr></w:lvl>
231
+ <w:lvl w:ilvl="2"><w:start w:val="1"/><w:numFmt w:val="lowerRoman"/><w:lvlText w:val="(%3)"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr></w:lvl>
232
+ <w:lvl w:ilvl="3"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="(%4)"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="2880" w:hanging="360"/></w:pPr></w:lvl>
233
+ <w:lvl w:ilvl="4"><w:start w:val="1"/><w:numFmt w:val="lowerLetter"/><w:lvlText w:val="%5."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="3600" w:hanging="360"/></w:pPr></w:lvl>
234
+ </w:abstractNum>`;
235
+
236
+ // Outline Numbered (numId 3) - 1 / 1.1 / 1.1.1
237
+ let abstractNum2 = `
238
+ <w:abstractNum w:abstractNumId="2">
239
+ <w:multiLevelType w:val="multilevel"/>
240
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl>
241
+ <w:lvl w:ilvl="1"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1.%2"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="1440" w:hanging="360"/></w:pPr></w:lvl>
242
+ <w:lvl w:ilvl="2"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1.%2.%3"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="2160" w:hanging="360"/></w:pPr></w:lvl>
243
+ <w:lvl w:ilvl="3"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1.%2.%3.%4"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="2880" w:hanging="360"/></w:pPr></w:lvl>
244
+ <w:lvl w:ilvl="4"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1.%2.%3.%4.%5"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="3600" w:hanging="360"/></w:pPr></w:lvl>
245
+ </w:abstractNum>`;
246
+
247
+ // Handle custom configurations (e.g. outline 1.1.1)
248
+ let customAbstractNums = '';
249
+ let customNums = '';
250
+
251
+ allCustomConfigs.forEach((config, idx) => {
252
+ const absId = 10 + idx;
253
+ customAbstractNums += `
254
+ <w:abstractNum w:abstractNumId="${absId}">
255
+ <w:multiLevelType w:val="multilevel"/>
256
+ ${config.levels.map((l, ilvl) => `
257
+ <w:lvl w:ilvl="${ilvl}">
258
+ <w:start w:val="1"/>
259
+ <w:numFmt w:val="${this.formatToOoxmlNumFmt(l.format)}"/>
260
+ <w:lvlText w:val="${this.suffixToOoxmlLevelText(l.format, l.suffix, ilvl)}"/>
261
+ <w:lvlJc w:val="left"/>
262
+ <w:pPr><w:ind w:left="${720 * (ilvl + 1)}" w:hanging="360"/></w:pPr>
263
+ </w:lvl>`).join('')}
264
+ </w:abstractNum>`;
265
+
266
+ customNums += `
267
+ <w:num w:numId="${config.numId}">
268
+ <w:abstractNumId w:val="${absId}"/>
269
+ </w:num>`;
270
+ });
271
+
272
+ return `
273
+ <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
274
+ ${abstractNum0}
275
+ ${abstractNum1}
276
+ ${abstractNum2}
277
+ ${customAbstractNums}
278
+ <w:num w:numId="1">
279
+ <w:abstractNumId w:val="0"/>
280
+ </w:num>
281
+ <w:num w:numId="2">
282
+ <w:abstractNumId w:val="1"/>
283
+ </w:num>
284
+ <w:num w:numId="3">
285
+ <w:abstractNumId w:val="2"/>
286
+ </w:num>
287
+ ${customNums}
288
+ </w:numbering>`;
289
+ }
290
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Shared OOXML package builders.
3
+ *
4
+ * Centralizes `pkg:package` construction used by pipeline/engine/services.
5
+ */
6
+
7
+ const NS_W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
8
+ const NS_R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
9
+ const NS_PKG = 'http://schemas.microsoft.com/office/2006/xmlPackage';
10
+ const NS_REL = 'http://schemas.openxmlformats.org/package/2006/relationships';
11
+
12
+ const REL_OFFICE_DOCUMENT = '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>';
13
+ const REL_NUMBERING = '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>';
14
+ const REL_COMMENTS = '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="comments.xml"/>';
15
+
16
+ const DEFAULT_NUMBERING_XML = `
17
+ <w:numbering xmlns:w="${NS_W}">
18
+ <w:abstractNum w:abstractNumId="0">
19
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="bullet"/><w:lvlText w:val="&#8226;"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl>
20
+ </w:abstractNum>
21
+ <w:abstractNum w:abstractNumId="1">
22
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr></w:lvl>
23
+ </w:abstractNum>
24
+ <w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num>
25
+ <w:num w:numId="2"><w:abstractNumId w:val="1"/></w:num>
26
+ </w:numbering>`.trim();
27
+
28
+ function stripXmlDeclaration(xml) {
29
+ if (!xml) return '';
30
+ return xml.replace(/<\?xml[^>]*\?>/g, '');
31
+ }
32
+
33
+ function buildWordDocument(bodyXml, includeRelationshipsNamespace = true) {
34
+ const rNs = includeRelationshipsNamespace ? ` xmlns:r="${NS_R}"` : '';
35
+ return `<w:document xmlns:w="${NS_W}"${rNs}><w:body>${bodyXml}</w:body></w:document>`;
36
+ }
37
+
38
+ function buildCommentsPart(commentsXml) {
39
+ return `
40
+ <pkg:part pkg:name="/word/comments.xml" pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml">
41
+ <pkg:xmlData>
42
+ ${commentsXml}
43
+ </pkg:xmlData>
44
+ </pkg:part>`;
45
+ }
46
+
47
+ function buildNumberingPart(numberingXml) {
48
+ return `
49
+ <pkg:part pkg:name="/word/numbering.xml" pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml">
50
+ <pkg:xmlData>
51
+ ${stripXmlDeclaration(numberingXml)}
52
+ </pkg:xmlData>
53
+ </pkg:part>`;
54
+ }
55
+
56
+ function buildPackage(documentXml, documentRelationshipsXml = '', extraPartsXml = '') {
57
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
58
+ <pkg:package xmlns:pkg="${NS_PKG}">
59
+ <pkg:part pkg:name="/_rels/.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml">
60
+ <pkg:xmlData>
61
+ <Relationships xmlns="${NS_REL}">
62
+ ${REL_OFFICE_DOCUMENT}
63
+ </Relationships>
64
+ </pkg:xmlData>
65
+ </pkg:part>
66
+ <pkg:part pkg:name="/word/_rels/document.xml.rels" pkg:contentType="application/vnd.openxmlformats-package.relationships+xml">
67
+ <pkg:xmlData>
68
+ <Relationships xmlns="${NS_REL}">
69
+ ${documentRelationshipsXml}
70
+ </Relationships>
71
+ </pkg:xmlData>
72
+ </pkg:part>${extraPartsXml}
73
+ <pkg:part pkg:name="/word/document.xml" pkg:contentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml">
74
+ <pkg:xmlData>
75
+ ${documentXml}
76
+ </pkg:xmlData>
77
+ </pkg:part>
78
+ </pkg:package>`;
79
+ }
80
+
81
+ /**
82
+ * Builds a package for paragraph/document-fragment insertion.
83
+ *
84
+ * @param {string} paragraphXml - Paragraph OOXML content
85
+ * @param {import('../core/types.js').DocumentFragmentOptions} [options={}] - Packaging options
86
+ * @returns {string}
87
+ */
88
+ export function buildDocumentFragmentPackage(paragraphXml, options = {}) {
89
+ const {
90
+ includeNumbering = false,
91
+ numberingXml = null,
92
+ appendTrailingParagraph = true
93
+ } = options;
94
+
95
+ const bodyXml = appendTrailingParagraph
96
+ ? `${paragraphXml}<w:p><w:pPr></w:pPr></w:p>`
97
+ : paragraphXml;
98
+
99
+ const documentXml = buildWordDocument(bodyXml, true);
100
+
101
+ let relationshipsXml = '';
102
+ let extraPartsXml = '';
103
+
104
+ if (numberingXml) {
105
+ relationshipsXml = REL_NUMBERING;
106
+ extraPartsXml += buildNumberingPart(numberingXml);
107
+ } else if (includeNumbering) {
108
+ relationshipsXml = REL_NUMBERING;
109
+ extraPartsXml += buildNumberingPart(DEFAULT_NUMBERING_XML);
110
+ }
111
+
112
+ return buildPackage(documentXml, relationshipsXml, extraPartsXml);
113
+ }
114
+
115
+ /**
116
+ * Builds a minimal package containing only paragraph XML in `word/document.xml`.
117
+ *
118
+ * @param {string} paragraphXml - Paragraph OOXML content
119
+ * @returns {string}
120
+ */
121
+ export function buildParagraphOnlyPackage(paragraphXml) {
122
+ const documentXml = buildWordDocument(paragraphXml, true);
123
+ return buildPackage(documentXml);
124
+ }
125
+
126
+ /**
127
+ * Builds a minimal paragraph package with comments relationship + part.
128
+ *
129
+ * @param {string} paragraphXml - Paragraph OOXML content
130
+ * @param {string} commentsXml - Comments part content
131
+ * @returns {string}
132
+ */
133
+ export function buildParagraphCommentsPackage(paragraphXml, commentsXml) {
134
+ const documentXml = buildWordDocument(paragraphXml, false);
135
+ return buildPackage(documentXml, REL_COMMENTS, buildCommentsPart(commentsXml));
136
+ }
137
+
138
+ /**
139
+ * Builds a package around caller-provided document XML, adding comments part/relationship.
140
+ *
141
+ * @param {string} documentXml - Document XML payload for `word/document.xml`
142
+ * @param {string} commentsXml - Comments part content
143
+ * @returns {string}
144
+ */
145
+ export function buildDocumentCommentsPackage(documentXml, commentsXml) {
146
+ return buildPackage(documentXml, REL_COMMENTS, buildCommentsPart(commentsXml));
147
+ }