@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.
- package/AGENTS.md +176 -0
- package/ARCHITECTURE.md +121 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/adapters/config.js +43 -0
- package/adapters/logger.js +89 -0
- package/adapters/xml-adapter.js +74 -0
- package/core/list-targeting.js +398 -0
- package/core/ooxml-identifiers.js +15 -0
- package/core/paragraph-offset-policy.js +50 -0
- package/core/paragraph-targeting.js +501 -0
- package/core/table-targeting.js +233 -0
- package/core/types.js +204 -0
- package/core/xml-query.js +99 -0
- package/dist/docx-redline-js.esm.js +8801 -0
- package/dist/docx-redline-js.esm.js.map +7 -0
- package/dist/docx-redline-js.esm.min.js +195 -0
- package/dist/docx-redline-js.esm.min.js.map +7 -0
- package/engine/format-application.js +358 -0
- package/engine/format-extraction.js +232 -0
- package/engine/format-paragraph-targeting.js +208 -0
- package/engine/format-span-application.js +178 -0
- package/engine/formatting-removal.js +330 -0
- package/engine/oxml-engine.js +279 -0
- package/engine/reconstruction-mapper.js +270 -0
- package/engine/reconstruction-mode.js +38 -0
- package/engine/reconstruction-writer.js +276 -0
- package/engine/rpr-helpers.js +194 -0
- package/engine/run-builders.js +235 -0
- package/engine/surgical-mode.js +520 -0
- package/engine/table-cell-context.js +151 -0
- package/engine/table-mode.js +172 -0
- package/index.js +308 -0
- package/orchestration/list-markdown.js +141 -0
- package/orchestration/list-parsing.js +73 -0
- package/orchestration/list-structural-fallback.js +530 -0
- package/orchestration/redline-operation-converter.js +141 -0
- package/orchestration/route-plan.js +160 -0
- package/package.json +76 -0
- package/pipeline/content-analysis.js +107 -0
- package/pipeline/diff-engine.js +204 -0
- package/pipeline/ingestion-export.js +255 -0
- package/pipeline/ingestion-paragraph.js +351 -0
- package/pipeline/ingestion-table.js +169 -0
- package/pipeline/ingestion-xml.js +39 -0
- package/pipeline/ingestion.js +8 -0
- package/pipeline/list-generation.js +280 -0
- package/pipeline/list-markers.js +77 -0
- package/pipeline/markdown-processor.js +160 -0
- package/pipeline/patching.js +408 -0
- package/pipeline/pipeline.js +326 -0
- package/pipeline/serialization.js +395 -0
- package/services/browser-demo-prompt-context.js +345 -0
- package/services/comment-builders.js +60 -0
- package/services/comment-engine.js +248 -0
- package/services/comment-locator.js +197 -0
- package/services/comment-package.js +113 -0
- package/services/numbering-helpers.js +416 -0
- package/services/numbering-service.js +290 -0
- package/services/package-builder.js +147 -0
- package/services/standalone-docx-plumbing.js +443 -0
- package/services/standalone-operation-runner.js +1169 -0
- package/services/table-reconciliation.js +344 -0
- package/standalone.js +5 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OOXML Reconciliation Pipeline - Serialization
|
|
3
|
+
*
|
|
4
|
+
* Converts patched run model back to OOXML with track changes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { RunKind, escapeXml, createRevisionMetadata } from '../core/types.js';
|
|
8
|
+
import { getApplicableFormatHints } from './markdown-processor.js';
|
|
9
|
+
import { serializeXml } from '../adapters/xml-adapter.js';
|
|
10
|
+
import { warn } from '../adapters/logger.js';
|
|
11
|
+
import { buildDocumentFragmentPackage } from '../services/package-builder.js';
|
|
12
|
+
import { getDefaultAuthor } from '../adapters/config.js';
|
|
13
|
+
|
|
14
|
+
const XMLNS_ATTR_REGEX = /\s+xmlns:[^=]+="[^"]*"/g;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Serializes a patched run model to OOXML.
|
|
18
|
+
*
|
|
19
|
+
* @param {import('../core/types.js').RunEntry[]} patchedModel - The patched run model
|
|
20
|
+
* @param {Element|null} pPr - Paragraph properties element
|
|
21
|
+
* @param {import('../core/types.js').FormatHint[]} [formatHints=[]] - Format hints
|
|
22
|
+
* @param {import('../core/types.js').SerializationOptions} [options={}] - Serialization options
|
|
23
|
+
* @returns {string} OOXML paragraph string (WITHOUT namespace - added by wrapper)
|
|
24
|
+
*/
|
|
25
|
+
export function serializeToOoxml(patchedModel, pPr, formatHints = [], options = {}) {
|
|
26
|
+
const serializationOptions = normalizeSerializationOptions(options);
|
|
27
|
+
const { author, generateRedlines } = serializationOptions;
|
|
28
|
+
const paragraphs = [];
|
|
29
|
+
let currentPPrXml = '';
|
|
30
|
+
let currentPPrElement = null;
|
|
31
|
+
let currentRuns = [];
|
|
32
|
+
|
|
33
|
+
// Helper to flush accumulated runs into a paragraph
|
|
34
|
+
function flushParagraph() {
|
|
35
|
+
if (currentRuns.length > 0 || paragraphs.length === 0) {
|
|
36
|
+
// Build paragraph properties - handle both string and DOM element
|
|
37
|
+
let pPrContent = '';
|
|
38
|
+
if (currentPPrXml) {
|
|
39
|
+
pPrContent = stripNamespaceDeclarations(currentPPrXml);
|
|
40
|
+
} else if (currentPPrElement) {
|
|
41
|
+
currentPPrXml = stripNamespaceDeclarations(serializeXml(currentPPrElement));
|
|
42
|
+
pPrContent = currentPPrXml;
|
|
43
|
+
} else if (pPr) {
|
|
44
|
+
// Fallback to legacy pPr if no PARAGRAPH_START was seen
|
|
45
|
+
if (typeof pPr === 'string') {
|
|
46
|
+
pPrContent = pPr;
|
|
47
|
+
} else {
|
|
48
|
+
pPrContent = serializeXml(pPr);
|
|
49
|
+
}
|
|
50
|
+
pPrContent = stripNamespaceDeclarations(pPrContent);
|
|
51
|
+
}
|
|
52
|
+
paragraphs.push(`<w:p>${pPrContent}${currentRuns.join('')}</w:p>`);
|
|
53
|
+
currentRuns = [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const item of patchedModel) {
|
|
58
|
+
switch (item.kind) {
|
|
59
|
+
case RunKind.PARAGRAPH_START:
|
|
60
|
+
// Flush previous paragraph before starting a new one
|
|
61
|
+
if (currentRuns.length > 0 || paragraphs.length > 0) {
|
|
62
|
+
flushParagraph();
|
|
63
|
+
}
|
|
64
|
+
currentPPrXml = item.pPrXml || '';
|
|
65
|
+
currentPPrElement = item.pPrElement || null;
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case RunKind.TEXT:
|
|
69
|
+
currentRuns.push(buildRunXmlWithHints(item, formatHints, serializationOptions));
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case RunKind.DELETION:
|
|
73
|
+
if (generateRedlines) {
|
|
74
|
+
currentRuns.push(buildDeletionXml(item, serializationOptions));
|
|
75
|
+
}
|
|
76
|
+
// If redlines are disabled, we simply omit the deleted content
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case RunKind.INSERTION:
|
|
80
|
+
if (generateRedlines) {
|
|
81
|
+
currentRuns.push(buildInsertionXml(item, formatHints, serializationOptions));
|
|
82
|
+
} else {
|
|
83
|
+
// Treat insertion as a normal run when redlines are disabled
|
|
84
|
+
currentRuns.push(buildRunXmlWithHints(item, formatHints, serializationOptions));
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case RunKind.BOOKMARK:
|
|
89
|
+
case RunKind.HYPERLINK:
|
|
90
|
+
// Pass through original XML - but strip any namespace declarations
|
|
91
|
+
if (item.nodeXml) {
|
|
92
|
+
currentRuns.push(stripNamespaceDeclarations(item.nodeXml));
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case RunKind.CONTAINER_START:
|
|
97
|
+
if (item.containerKind === 'sdt') {
|
|
98
|
+
currentRuns.push(`<w:sdt>${item.propertiesXml}<w:sdtContent>`);
|
|
99
|
+
} else if (item.containerKind === 'smartTag') {
|
|
100
|
+
currentRuns.push(`<w:smartTag ${item.propertiesXml}>`);
|
|
101
|
+
} else if (item.containerKind === 'hyperlink') {
|
|
102
|
+
const props = JSON.parse(item.propertiesXml);
|
|
103
|
+
const rIdAttr = props.rId ? ` r:id="${props.rId}"` : '';
|
|
104
|
+
const anchorAttr = props.anchor ? ` w:anchor="${props.anchor}"` : '';
|
|
105
|
+
currentRuns.push(`<w:hyperlink${rIdAttr}${anchorAttr}>`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case RunKind.CONTAINER_END:
|
|
110
|
+
if (item.containerKind === 'sdt') {
|
|
111
|
+
currentRuns.push(`</w:sdtContent></w:sdt>`);
|
|
112
|
+
} else if (item.containerKind === 'smartTag') {
|
|
113
|
+
currentRuns.push(`</w:smartTag>`);
|
|
114
|
+
} else if (item.containerKind === 'hyperlink') {
|
|
115
|
+
currentRuns.push(`</w:hyperlink>`);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
default:
|
|
120
|
+
warn('Unknown run kind:', item.kind);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Flush final paragraph
|
|
125
|
+
flushParagraph();
|
|
126
|
+
|
|
127
|
+
// Return all paragraphs WITHOUT namespace - wrapper will add it
|
|
128
|
+
return paragraphs.join('');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Normalizes serialization options to a single object contract.
|
|
133
|
+
*
|
|
134
|
+
* @param {import('../core/types.js').SerializationOptions|string|undefined|null} options - Raw options
|
|
135
|
+
* @returns {import('../core/types.js').SerializationOptions}
|
|
136
|
+
*/
|
|
137
|
+
function normalizeSerializationOptions(options) {
|
|
138
|
+
// Backward compatibility: accept legacy `font` string signature.
|
|
139
|
+
if (typeof options === 'string') {
|
|
140
|
+
return {
|
|
141
|
+
author: getDefaultAuthor(),
|
|
142
|
+
generateRedlines: true,
|
|
143
|
+
font: options
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const normalized = options && typeof options === 'object' ? options : {};
|
|
148
|
+
const resolvedAuthor = typeof normalized.author === 'string' && normalized.author.trim()
|
|
149
|
+
? normalized.author.trim()
|
|
150
|
+
: getDefaultAuthor();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
author: resolvedAuthor,
|
|
154
|
+
generateRedlines: normalized.generateRedlines ?? true,
|
|
155
|
+
font: normalized.font ?? null
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Builds a run XML element, applying format hints if applicable.
|
|
161
|
+
*
|
|
162
|
+
* @param {import('../core/types.js').RunEntry} item - Run entry
|
|
163
|
+
* @param {import('../core/types.js').FormatHint[]} formatHints - Format hints
|
|
164
|
+
* @param {import('../core/types.js').SerializationOptions} options - Serialization options
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
function buildRunXmlWithHints(item, formatHints, options = {}) {
|
|
168
|
+
const applicableHints = getApplicableFormatHints(formatHints, item.startOffset, item.endOffset);
|
|
169
|
+
const font = options?.font ?? null;
|
|
170
|
+
let cleanRPr = item.rPrXml ? stripNamespaceDeclarations(item.rPrXml) : '';
|
|
171
|
+
|
|
172
|
+
if (font) {
|
|
173
|
+
cleanRPr = applyFont(cleanRPr, font);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (applicableHints.length === 0) {
|
|
177
|
+
// No formatting changes - use original rPr (strip namespace)
|
|
178
|
+
return buildSimpleRun(item.text, cleanRPr);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Split the run text at format boundaries and apply hints
|
|
182
|
+
const runs = [];
|
|
183
|
+
let pos = 0;
|
|
184
|
+
const text = item.text;
|
|
185
|
+
const baseOffset = item.startOffset;
|
|
186
|
+
|
|
187
|
+
for (const hint of applicableHints) {
|
|
188
|
+
const localStart = Math.max(0, hint.start - baseOffset);
|
|
189
|
+
const localEnd = Math.min(text.length, hint.end - baseOffset);
|
|
190
|
+
|
|
191
|
+
// Text before the hint
|
|
192
|
+
if (localStart > pos) {
|
|
193
|
+
runs.push(buildSimpleRun(text.slice(pos, localStart), cleanRPr));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Formatted text
|
|
197
|
+
const formattedRPr = injectFormatting(cleanRPr, hint.format);
|
|
198
|
+
runs.push(buildSimpleRun(text.slice(localStart, localEnd), formattedRPr));
|
|
199
|
+
pos = localEnd;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Remaining text after last hint
|
|
203
|
+
if (pos < text.length) {
|
|
204
|
+
runs.push(buildSimpleRun(text.slice(pos), cleanRPr));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return runs.join('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Builds a simple w:r element.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} text - Text content
|
|
214
|
+
* @param {string} rPrXml - Run properties XML
|
|
215
|
+
* @returns {string}
|
|
216
|
+
*/
|
|
217
|
+
function buildSimpleRun(text, rPrXml) {
|
|
218
|
+
if (!text) return '';
|
|
219
|
+
const rPr = rPrXml || '';
|
|
220
|
+
return `<w:r>${rPr}<w:t xml:space="preserve">${escapeXml(text)}</w:t></w:r>`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Builds a deletion (w:del) element.
|
|
225
|
+
*
|
|
226
|
+
* @param {import('../core/types.js').RunEntry} item - Deletion entry
|
|
227
|
+
* @param {import('../core/types.js').SerializationOptions} options - Serialization options
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
function buildDeletionXml(item, options = {}) {
|
|
231
|
+
const metadata = createRevisionMetadata(options.author ?? getDefaultAuthor());
|
|
232
|
+
const font = options.font ?? null;
|
|
233
|
+
let rPr = item.rPrXml ? stripNamespaceDeclarations(item.rPrXml) : '';
|
|
234
|
+
|
|
235
|
+
if (font) {
|
|
236
|
+
rPr = applyFont(rPr, font);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return `<w:del w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}">` +
|
|
240
|
+
`<w:r>${rPr}<w:delText xml:space="preserve">${escapeXml(item.text)}</w:delText></w:r>` +
|
|
241
|
+
`</w:del>`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Builds an insertion (w:ins) element.
|
|
246
|
+
*
|
|
247
|
+
* @param {import('../core/types.js').RunEntry} item - Insertion entry
|
|
248
|
+
* @param {import('../core/types.js').FormatHint[]} formatHints - Format hints
|
|
249
|
+
* @param {import('../core/types.js').SerializationOptions} options - Serialization options
|
|
250
|
+
* @returns {string}
|
|
251
|
+
*/
|
|
252
|
+
function buildInsertionXml(item, formatHints, options = {}) {
|
|
253
|
+
const metadata = createRevisionMetadata(options.author ?? getDefaultAuthor());
|
|
254
|
+
const font = options.font ?? null;
|
|
255
|
+
|
|
256
|
+
// Build the inner run content with format hints
|
|
257
|
+
const applicableHints = getApplicableFormatHints(formatHints, item.startOffset, item.endOffset);
|
|
258
|
+
let innerContent = '';
|
|
259
|
+
let cleanRPr = item.rPrXml ? stripNamespaceDeclarations(item.rPrXml) : '';
|
|
260
|
+
|
|
261
|
+
if (font) {
|
|
262
|
+
cleanRPr = applyFont(cleanRPr, font);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (applicableHints.length === 0) {
|
|
266
|
+
innerContent = buildSimpleRun(item.text, cleanRPr);
|
|
267
|
+
} else {
|
|
268
|
+
// Apply format hints
|
|
269
|
+
let pos = 0;
|
|
270
|
+
const text = item.text;
|
|
271
|
+
const baseOffset = item.startOffset;
|
|
272
|
+
|
|
273
|
+
for (const hint of applicableHints) {
|
|
274
|
+
const localStart = Math.max(0, hint.start - baseOffset);
|
|
275
|
+
const localEnd = Math.min(text.length, hint.end - baseOffset);
|
|
276
|
+
|
|
277
|
+
if (localStart > pos) {
|
|
278
|
+
innerContent += buildSimpleRun(text.slice(pos, localStart), cleanRPr);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const formattedRPr = injectFormatting(cleanRPr, hint.format);
|
|
282
|
+
innerContent += buildSimpleRun(text.slice(localStart, localEnd), formattedRPr);
|
|
283
|
+
pos = localEnd;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (pos < text.length) {
|
|
287
|
+
innerContent += buildSimpleRun(text.slice(pos), cleanRPr);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return `<w:ins w:id="${metadata.id}" w:author="${escapeXml(metadata.author)}" w:date="${metadata.date}">` +
|
|
292
|
+
innerContent +
|
|
293
|
+
`</w:ins>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Applies a font to run properties XML.
|
|
298
|
+
*
|
|
299
|
+
* @param {string} baseRPrXml - Base run properties
|
|
300
|
+
* @param {string} font - Font name
|
|
301
|
+
* @returns {string}
|
|
302
|
+
*/
|
|
303
|
+
function applyFont(baseRPrXml, font) {
|
|
304
|
+
if (!font) return baseRPrXml;
|
|
305
|
+
|
|
306
|
+
// Extract existing content from rPr
|
|
307
|
+
let content = '';
|
|
308
|
+
if (baseRPrXml) {
|
|
309
|
+
content = baseRPrXml.replace(/<\/?w:rPr[^>]*>/g, '');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Replace or add rFonts
|
|
313
|
+
if (content.includes('<w:rFonts')) {
|
|
314
|
+
content = content.replace(/<w:rFonts[^>]*\/>/, `<w:rFonts w:ascii="${font}" w:hAnsi="${font}"/>`);
|
|
315
|
+
} else {
|
|
316
|
+
content = `<w:rFonts w:ascii="${font}" w:hAnsi="${font}"/>` + content;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return `<w:rPr>${content}</w:rPr>`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Injects formatting into run properties XML.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} baseRPrXml - Base run properties
|
|
326
|
+
* @param {Object} format - Format flags (bold, italic, underline, strikethrough)
|
|
327
|
+
* @returns {string}
|
|
328
|
+
*/
|
|
329
|
+
function injectFormatting(baseRPrXml, format) {
|
|
330
|
+
if (!format || Object.keys(format).length === 0) {
|
|
331
|
+
return baseRPrXml;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Extract existing content from rPr
|
|
335
|
+
let content = '';
|
|
336
|
+
if (baseRPrXml) {
|
|
337
|
+
content = baseRPrXml.replace(/<\/?w:rPr[^>]*>/g, '');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Add new formatting elements
|
|
341
|
+
if (format.bold && !content.includes('<w:b')) {
|
|
342
|
+
content = '<w:b/>' + content;
|
|
343
|
+
}
|
|
344
|
+
if (format.italic && !content.includes('<w:i')) {
|
|
345
|
+
content = '<w:i/>' + content;
|
|
346
|
+
}
|
|
347
|
+
if (format.underline && !content.includes('<w:u')) {
|
|
348
|
+
content = '<w:u w:val="single"/>' + content;
|
|
349
|
+
}
|
|
350
|
+
if (format.strikethrough && !content.includes('<w:strike')) {
|
|
351
|
+
content = '<w:strike/>' + content;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return `<w:rPr>${content}</w:rPr>`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Wraps OOXML paragraph content for Word's insertOoxml API.
|
|
359
|
+
* Must include both the document part AND the relationships part.
|
|
360
|
+
*
|
|
361
|
+
* @param {string} paragraphXml - The paragraph XML (without namespace declarations)
|
|
362
|
+
* @param {import('../core/types.js').DocumentFragmentOptions|boolean} [options={}] - Fragment options
|
|
363
|
+
* @returns {string} Complete OOXML package for insertOoxml
|
|
364
|
+
*/
|
|
365
|
+
export function wrapInDocumentFragment(paragraphXml, options = {}) {
|
|
366
|
+
const normalizedOptions = normalizeFragmentOptions(options);
|
|
367
|
+
return buildDocumentFragmentPackage(paragraphXml, normalizedOptions);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Normalizes wrapper options to object form.
|
|
372
|
+
*
|
|
373
|
+
* @param {import('../core/types.js').DocumentFragmentOptions|boolean|undefined|null} options - Raw options
|
|
374
|
+
* @returns {import('../core/types.js').DocumentFragmentOptions}
|
|
375
|
+
*/
|
|
376
|
+
function normalizeFragmentOptions(options) {
|
|
377
|
+
if (typeof options === 'boolean') {
|
|
378
|
+
return { includeNumbering: options };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (!options || typeof options !== 'object') {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
includeNumbering: options.includeNumbering ?? false,
|
|
387
|
+
numberingXml: options.numberingXml ?? null,
|
|
388
|
+
appendTrailingParagraph: options.appendTrailingParagraph ?? true
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function stripNamespaceDeclarations(xml) {
|
|
393
|
+
return xml ? xml.replace(XMLNS_ATTR_REGEX, '') : '';
|
|
394
|
+
}
|
|
395
|
+
|