bun-docx 0.5.0 → 0.6.1
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 +44 -4
- package/dist/index.js +950 -107
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13780,7 +13780,17 @@ function buildDoc(view, path) {
|
|
|
13780
13780
|
};
|
|
13781
13781
|
const blocks = readBlocks(view, body, state);
|
|
13782
13782
|
const comments = readComments(view, state.commentAnchors);
|
|
13783
|
-
|
|
13783
|
+
const footnotes = readNotes(view.footnotesTree, "w:footnotes", "w:footnote", "fn");
|
|
13784
|
+
const endnotes = readNotes(view.endnotesTree, "w:endnotes", "w:endnote", "en");
|
|
13785
|
+
return {
|
|
13786
|
+
schemaVersion: 1,
|
|
13787
|
+
path,
|
|
13788
|
+
properties,
|
|
13789
|
+
blocks,
|
|
13790
|
+
comments,
|
|
13791
|
+
footnotes,
|
|
13792
|
+
endnotes
|
|
13793
|
+
};
|
|
13784
13794
|
}
|
|
13785
13795
|
function readBlocks(view, body, state) {
|
|
13786
13796
|
const blocks = [];
|
|
@@ -13868,6 +13878,28 @@ function walkRunContainer(context, container, trackedChange, hyperlink) {
|
|
|
13868
13878
|
}
|
|
13869
13879
|
continue;
|
|
13870
13880
|
}
|
|
13881
|
+
if (child.tag === "m:oMath") {
|
|
13882
|
+
const text = collectMathText(child);
|
|
13883
|
+
if (text.length > 0) {
|
|
13884
|
+
context.paragraph.runs.push({
|
|
13885
|
+
type: "equation",
|
|
13886
|
+
text,
|
|
13887
|
+
display: false
|
|
13888
|
+
});
|
|
13889
|
+
}
|
|
13890
|
+
continue;
|
|
13891
|
+
}
|
|
13892
|
+
if (child.tag === "m:oMathPara") {
|
|
13893
|
+
const text = collectMathText(child);
|
|
13894
|
+
if (text.length > 0) {
|
|
13895
|
+
context.paragraph.runs.push({
|
|
13896
|
+
type: "equation",
|
|
13897
|
+
text,
|
|
13898
|
+
display: true
|
|
13899
|
+
});
|
|
13900
|
+
}
|
|
13901
|
+
continue;
|
|
13902
|
+
}
|
|
13871
13903
|
if (child.tag === "w:ins" || child.tag === "w:del") {
|
|
13872
13904
|
const change = {
|
|
13873
13905
|
kind: child.tag === "w:ins" ? "ins" : "del",
|
|
@@ -13937,9 +13969,9 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13937
13969
|
const runProperties = node.findChild("w:rPr");
|
|
13938
13970
|
for (const child of node.children) {
|
|
13939
13971
|
if (child.tag === "w:drawing") {
|
|
13940
|
-
const
|
|
13941
|
-
if (
|
|
13942
|
-
return
|
|
13972
|
+
const drawing = readDrawing(view, child, state);
|
|
13973
|
+
if (drawing)
|
|
13974
|
+
return drawing;
|
|
13943
13975
|
}
|
|
13944
13976
|
if (child.tag === "w:br") {
|
|
13945
13977
|
const kind = child.getAttribute("w:type") ?? "line";
|
|
@@ -13948,6 +13980,16 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13948
13980
|
if (child.tag === "w:tab") {
|
|
13949
13981
|
return { type: "tab" };
|
|
13950
13982
|
}
|
|
13983
|
+
if (child.tag === "w:footnoteReference") {
|
|
13984
|
+
const id = child.getAttribute("w:id");
|
|
13985
|
+
if (id)
|
|
13986
|
+
return { type: "footnoteRef", kind: "footnote", id: `fn${id}` };
|
|
13987
|
+
}
|
|
13988
|
+
if (child.tag === "w:endnoteReference") {
|
|
13989
|
+
const id = child.getAttribute("w:id");
|
|
13990
|
+
if (id)
|
|
13991
|
+
return { type: "footnoteRef", kind: "endnote", id: `en${id}` };
|
|
13992
|
+
}
|
|
13951
13993
|
}
|
|
13952
13994
|
let combinedText = "";
|
|
13953
13995
|
for (const child of node.children) {
|
|
@@ -13968,6 +14010,29 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13968
14010
|
run.hyperlink = hyperlink;
|
|
13969
14011
|
return run;
|
|
13970
14012
|
}
|
|
14013
|
+
function readDrawing(view, drawing, state) {
|
|
14014
|
+
const image = readImageFromDrawing(view, drawing, state);
|
|
14015
|
+
if (image)
|
|
14016
|
+
return image;
|
|
14017
|
+
if (drawing.findDescendant("c:chart"))
|
|
14018
|
+
return { type: "chart", kind: "chart" };
|
|
14019
|
+
if (drawing.findDescendant("dgm:relIds"))
|
|
14020
|
+
return { type: "chart", kind: "smartart" };
|
|
14021
|
+
if (drawing.findDescendant("wps:wsp"))
|
|
14022
|
+
return { type: "chart", kind: "shape" };
|
|
14023
|
+
return { type: "chart", kind: "drawing" };
|
|
14024
|
+
}
|
|
14025
|
+
function collectMathText(node) {
|
|
14026
|
+
let out = "";
|
|
14027
|
+
for (const child of node.children) {
|
|
14028
|
+
if (child.tag === "m:t") {
|
|
14029
|
+
out += child.collectText();
|
|
14030
|
+
continue;
|
|
14031
|
+
}
|
|
14032
|
+
out += collectMathText(child);
|
|
14033
|
+
}
|
|
14034
|
+
return out;
|
|
14035
|
+
}
|
|
13971
14036
|
function applyRunProperties(run, runProperties) {
|
|
13972
14037
|
const colorNode = runProperties.findChild("w:color");
|
|
13973
14038
|
if (colorNode) {
|
|
@@ -14222,6 +14287,26 @@ function readCommentsExtended(view) {
|
|
|
14222
14287
|
}
|
|
14223
14288
|
return out;
|
|
14224
14289
|
}
|
|
14290
|
+
function readNotes(tree, rootTag, itemTag, idPrefix) {
|
|
14291
|
+
if (!tree)
|
|
14292
|
+
return [];
|
|
14293
|
+
const root = XmlNode2.findRoot(tree, rootTag);
|
|
14294
|
+
if (!root)
|
|
14295
|
+
return [];
|
|
14296
|
+
const out = [];
|
|
14297
|
+
for (const child of root.children) {
|
|
14298
|
+
if (child.tag !== itemTag)
|
|
14299
|
+
continue;
|
|
14300
|
+
if (child.getAttribute("w:type"))
|
|
14301
|
+
continue;
|
|
14302
|
+
const numericId = child.getAttribute("w:id");
|
|
14303
|
+
if (numericId == null)
|
|
14304
|
+
continue;
|
|
14305
|
+
const text = child.collectText().replace(/\s+/g, " ").trim();
|
|
14306
|
+
out.push({ id: `${idPrefix}${numericId}`, text });
|
|
14307
|
+
}
|
|
14308
|
+
return out;
|
|
14309
|
+
}
|
|
14225
14310
|
var RELATIONSHIP_NAMESPACE_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", RELATIONSHIP_NAMESPACE_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
14226
14311
|
var init_read = __esm(() => {
|
|
14227
14312
|
init_parser();
|
|
@@ -14237,11 +14322,15 @@ async function openDocView(path) {
|
|
|
14237
14322
|
const commentsExtTree = pkg.hasPart("word/commentsExtended.xml") ? XmlNode2.parse(await pkg.readText("word/commentsExtended.xml")) : undefined;
|
|
14238
14323
|
const corePropertiesTree = pkg.hasPart("docProps/core.xml") ? XmlNode2.parse(await pkg.readText("docProps/core.xml")) : undefined;
|
|
14239
14324
|
const settingsTree = pkg.hasPart("word/settings.xml") ? XmlNode2.parse(await pkg.readText("word/settings.xml")) : undefined;
|
|
14325
|
+
const footnotesTree = pkg.hasPart("word/footnotes.xml") ? XmlNode2.parse(await pkg.readText("word/footnotes.xml")) : undefined;
|
|
14326
|
+
const endnotesTree = pkg.hasPart("word/endnotes.xml") ? XmlNode2.parse(await pkg.readText("word/endnotes.xml")) : undefined;
|
|
14240
14327
|
const view = {
|
|
14241
14328
|
pkg,
|
|
14242
14329
|
documentTree,
|
|
14243
14330
|
commentsTree,
|
|
14244
14331
|
commentsExtTree,
|
|
14332
|
+
footnotesTree,
|
|
14333
|
+
endnotesTree,
|
|
14245
14334
|
relationshipsTree,
|
|
14246
14335
|
contentTypesTree,
|
|
14247
14336
|
corePropertiesTree,
|
|
@@ -14322,6 +14411,49 @@ var init_doc_view = __esm(() => {
|
|
|
14322
14411
|
init_read();
|
|
14323
14412
|
});
|
|
14324
14413
|
|
|
14414
|
+
// src/core/ast/text.ts
|
|
14415
|
+
function paragraphText(paragraph) {
|
|
14416
|
+
let out = "";
|
|
14417
|
+
for (const run of paragraph.runs) {
|
|
14418
|
+
if (run.type === "text")
|
|
14419
|
+
out += run.text;
|
|
14420
|
+
}
|
|
14421
|
+
return out;
|
|
14422
|
+
}
|
|
14423
|
+
function flattenParagraphs(blocks) {
|
|
14424
|
+
const out = [];
|
|
14425
|
+
for (const block of blocks) {
|
|
14426
|
+
if (block.type === "paragraph") {
|
|
14427
|
+
out.push(block);
|
|
14428
|
+
continue;
|
|
14429
|
+
}
|
|
14430
|
+
if (block.type === "table") {
|
|
14431
|
+
for (const row of block.rows) {
|
|
14432
|
+
for (const cell of row.cells) {
|
|
14433
|
+
out.push(...flattenParagraphs(cell.blocks));
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
}
|
|
14437
|
+
}
|
|
14438
|
+
return out;
|
|
14439
|
+
}
|
|
14440
|
+
function findBlockById(blocks, blockId) {
|
|
14441
|
+
for (const block of blocks) {
|
|
14442
|
+
if (block.id === blockId)
|
|
14443
|
+
return block;
|
|
14444
|
+
if (block.type === "table") {
|
|
14445
|
+
for (const row of block.rows) {
|
|
14446
|
+
for (const cell of row.cells) {
|
|
14447
|
+
const inner = findBlockById(cell.blocks, blockId);
|
|
14448
|
+
if (inner)
|
|
14449
|
+
return inner;
|
|
14450
|
+
}
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
}
|
|
14454
|
+
return null;
|
|
14455
|
+
}
|
|
14456
|
+
|
|
14325
14457
|
// src/core/ast/index.ts
|
|
14326
14458
|
var init_ast = __esm(() => {
|
|
14327
14459
|
init_doc_view();
|
|
@@ -14513,8 +14645,6 @@ function resolveAuthor(authorFlag) {
|
|
|
14513
14645
|
return authorFlag;
|
|
14514
14646
|
if (Bun.env.DOCX_AUTHOR)
|
|
14515
14647
|
return Bun.env.DOCX_AUTHOR;
|
|
14516
|
-
if (Bun.env.DOCX_CLI_AUTHOR)
|
|
14517
|
-
return Bun.env.DOCX_CLI_AUTHOR;
|
|
14518
14648
|
return "docx-cli";
|
|
14519
14649
|
}
|
|
14520
14650
|
function resolveDate() {
|
|
@@ -14935,15 +15065,15 @@ function ensureCommentsExtPart(view) {
|
|
|
14935
15065
|
return root;
|
|
14936
15066
|
}
|
|
14937
15067
|
function paragraphTextLength(paragraph) {
|
|
15068
|
+
return sumTextLength(paragraph.children);
|
|
15069
|
+
}
|
|
15070
|
+
function sumTextLength(children) {
|
|
14938
15071
|
let total = 0;
|
|
14939
|
-
for (const child of
|
|
15072
|
+
for (const child of children) {
|
|
14940
15073
|
if (child.tag === "w:r")
|
|
14941
15074
|
total += runTextLength(child);
|
|
14942
|
-
else if (child.tag === "w:ins" || child.tag === "w:del") {
|
|
14943
|
-
|
|
14944
|
-
if (inner.tag === "w:r")
|
|
14945
|
-
total += runTextLength(inner);
|
|
14946
|
-
}
|
|
15075
|
+
else if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15076
|
+
total += sumTextLength(child.children);
|
|
14947
15077
|
}
|
|
14948
15078
|
}
|
|
14949
15079
|
return total;
|
|
@@ -14982,6 +15112,97 @@ function addCommentRangeMarkers(startParagraph, startOffset, endParagraph, endOf
|
|
|
14982
15112
|
}
|
|
14983
15113
|
]);
|
|
14984
15114
|
}
|
|
15115
|
+
function addCommentMarkersAroundRun(paragraph, target, commentId) {
|
|
15116
|
+
function walk(parent) {
|
|
15117
|
+
const children = parent.children;
|
|
15118
|
+
for (let index = 0;index < children.length; index++) {
|
|
15119
|
+
const child = children[index];
|
|
15120
|
+
if (child === target) {
|
|
15121
|
+
children.splice(index, 1, commentRangeStartMarker(commentId), target, commentRangeEndMarker(commentId), commentReferenceRun(commentId));
|
|
15122
|
+
return true;
|
|
15123
|
+
}
|
|
15124
|
+
if (child && (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink")) {
|
|
15125
|
+
if (walk(child))
|
|
15126
|
+
return true;
|
|
15127
|
+
}
|
|
15128
|
+
}
|
|
15129
|
+
return false;
|
|
15130
|
+
}
|
|
15131
|
+
return walk(paragraph);
|
|
15132
|
+
}
|
|
15133
|
+
function findElementOffsetsInParagraph(paragraph, target) {
|
|
15134
|
+
let cursor = 0;
|
|
15135
|
+
let result = null;
|
|
15136
|
+
function walk(children) {
|
|
15137
|
+
for (const child of children) {
|
|
15138
|
+
if (child === target) {
|
|
15139
|
+
const start = cursor;
|
|
15140
|
+
cursor += sumTextLength([child]);
|
|
15141
|
+
result = { start, end: cursor };
|
|
15142
|
+
return true;
|
|
15143
|
+
}
|
|
15144
|
+
if (child.tag === "w:r") {
|
|
15145
|
+
cursor += runTextLength(child);
|
|
15146
|
+
continue;
|
|
15147
|
+
}
|
|
15148
|
+
if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15149
|
+
if (walk(child.children))
|
|
15150
|
+
return true;
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
return false;
|
|
15154
|
+
}
|
|
15155
|
+
walk(paragraph.children);
|
|
15156
|
+
return result;
|
|
15157
|
+
}
|
|
15158
|
+
function findContainingParagraph(documentTree, target) {
|
|
15159
|
+
function walk(node) {
|
|
15160
|
+
if (node.tag === "w:p" && containsNode(node, target))
|
|
15161
|
+
return node;
|
|
15162
|
+
for (const child of node.children) {
|
|
15163
|
+
const found = walk(child);
|
|
15164
|
+
if (found)
|
|
15165
|
+
return found;
|
|
15166
|
+
}
|
|
15167
|
+
return null;
|
|
15168
|
+
}
|
|
15169
|
+
for (const root of documentTree) {
|
|
15170
|
+
const found = walk(root);
|
|
15171
|
+
if (found)
|
|
15172
|
+
return found;
|
|
15173
|
+
}
|
|
15174
|
+
return null;
|
|
15175
|
+
}
|
|
15176
|
+
function containsNode(haystack, needle) {
|
|
15177
|
+
if (haystack === needle)
|
|
15178
|
+
return true;
|
|
15179
|
+
for (const child of haystack.children) {
|
|
15180
|
+
if (containsNode(child, needle))
|
|
15181
|
+
return true;
|
|
15182
|
+
}
|
|
15183
|
+
return false;
|
|
15184
|
+
}
|
|
15185
|
+
function emitAuditComment(view, anchor, options) {
|
|
15186
|
+
const numericId = nextCommentId(view);
|
|
15187
|
+
const paraId = generateParaId();
|
|
15188
|
+
if (anchor.kind === "span") {
|
|
15189
|
+
addCommentMarkersToParagraph(anchor.paragraph, numericId, anchor.span);
|
|
15190
|
+
} else {
|
|
15191
|
+
addCommentMarkersAroundRun(anchor.paragraph, anchor.run, numericId);
|
|
15192
|
+
}
|
|
15193
|
+
const commentsRoot = ensureCommentsPart(view);
|
|
15194
|
+
commentsRoot.children.push(/* @__PURE__ */ jsxDEV(CommentBody, {
|
|
15195
|
+
options: {
|
|
15196
|
+
id: numericId,
|
|
15197
|
+
author: options.author,
|
|
15198
|
+
date: options.date,
|
|
15199
|
+
initials: authorInitials(options.author),
|
|
15200
|
+
paraId,
|
|
15201
|
+
text: options.body
|
|
15202
|
+
}
|
|
15203
|
+
}, undefined, false, undefined, this));
|
|
15204
|
+
return numericId;
|
|
15205
|
+
}
|
|
14985
15206
|
function commentRangeStartMarker(commentId) {
|
|
14986
15207
|
return /* @__PURE__ */ jsxDEV(w.commentRangeStart, {
|
|
14987
15208
|
"w-id": commentId
|
|
@@ -15017,13 +15238,13 @@ function placeMarkersInParagraph(paragraph, markers) {
|
|
|
15017
15238
|
}
|
|
15018
15239
|
const pending = markers.slice();
|
|
15019
15240
|
const state = { offset: 0, placedCount: 0 };
|
|
15020
|
-
paragraph.children = walkAndPlace(paragraph.children, pending,
|
|
15241
|
+
paragraph.children = walkAndPlace(paragraph.children, pending, state);
|
|
15021
15242
|
flushAtCurrentOffset(paragraph.children, pending, state);
|
|
15022
15243
|
if (state.placedCount !== markers.length) {
|
|
15023
15244
|
throw new SpanOutOfRangeError(`Could not place comment markers (placed ${state.placedCount} of ${markers.length})`);
|
|
15024
15245
|
}
|
|
15025
15246
|
}
|
|
15026
|
-
function walkAndPlace(children, pending,
|
|
15247
|
+
function walkAndPlace(children, pending, state) {
|
|
15027
15248
|
const result = [];
|
|
15028
15249
|
for (const child of children) {
|
|
15029
15250
|
if (child.tag === "w:r") {
|
|
@@ -15065,9 +15286,9 @@ function walkAndPlace(children, pending, isParagraphLevel, state) {
|
|
|
15065
15286
|
state.offset = runEnd;
|
|
15066
15287
|
continue;
|
|
15067
15288
|
}
|
|
15068
|
-
if (
|
|
15289
|
+
if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:hyperlink") {
|
|
15069
15290
|
flushAtCurrentOffset(result, pending, state);
|
|
15070
|
-
const innerChildren = walkAndPlace(child.children, pending,
|
|
15291
|
+
const innerChildren = walkAndPlace(child.children, pending, state);
|
|
15071
15292
|
const wrapper = new XmlNode2(child.tag, { ...child.attributes });
|
|
15072
15293
|
wrapper.children = innerChildren;
|
|
15073
15294
|
result.push(wrapper);
|
|
@@ -16042,6 +16263,7 @@ async function run8(args) {
|
|
|
16042
16263
|
allowPositionals: true,
|
|
16043
16264
|
options: {
|
|
16044
16265
|
at: { type: "string" },
|
|
16266
|
+
author: { type: "string" },
|
|
16045
16267
|
output: { type: "string", short: "o" },
|
|
16046
16268
|
"dry-run": { type: "boolean" },
|
|
16047
16269
|
help: { type: "boolean", short: "h" }
|
|
@@ -16087,7 +16309,7 @@ async function run8(args) {
|
|
|
16087
16309
|
if (blockRef.node.tag !== "w:p") {
|
|
16088
16310
|
return fail("TRACKED_CHANGE_CONFLICT", "Tracked deletion of non-paragraph blocks (e.g., tables) is not supported", "Use `docx track-changes off` first, or delete table contents row-by-row.");
|
|
16089
16311
|
}
|
|
16090
|
-
applyTrackedDeletion(view, blockRef.node);
|
|
16312
|
+
applyTrackedDeletion(view, blockRef.node, parsed.values.author);
|
|
16091
16313
|
} else {
|
|
16092
16314
|
blockRef.parent.splice(targetIndex, 1);
|
|
16093
16315
|
}
|
|
@@ -16100,9 +16322,9 @@ async function run8(args) {
|
|
|
16100
16322
|
});
|
|
16101
16323
|
return EXIT.OK;
|
|
16102
16324
|
}
|
|
16103
|
-
function applyTrackedDeletion(view, paragraph) {
|
|
16325
|
+
function applyTrackedDeletion(view, paragraph, authorFlag) {
|
|
16104
16326
|
const allocator = createRevisionAllocator(view);
|
|
16105
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
16327
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
16106
16328
|
const mintMeta = () => ({
|
|
16107
16329
|
...baseMeta,
|
|
16108
16330
|
revisionId: allocator.next()
|
|
@@ -16139,6 +16361,7 @@ Usage:
|
|
|
16139
16361
|
Locator (required):
|
|
16140
16362
|
--at LOCATOR Block to remove (e.g., p3, t0)
|
|
16141
16363
|
|
|
16364
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
16142
16365
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16143
16366
|
--dry-run Print what would be removed; do not write the file
|
|
16144
16367
|
-h, --help Show this help
|
|
@@ -16185,7 +16408,7 @@ function RunElement({ run: run9 }) {
|
|
|
16185
16408
|
children: /* @__PURE__ */ jsxDEV(w.tab, {}, undefined, false, undefined, this)
|
|
16186
16409
|
}, undefined, false, undefined, this);
|
|
16187
16410
|
}
|
|
16188
|
-
|
|
16411
|
+
return null;
|
|
16189
16412
|
}
|
|
16190
16413
|
function TextRunElement({ run: run9 }) {
|
|
16191
16414
|
return /* @__PURE__ */ jsxDEV(w.r, {
|
|
@@ -16291,6 +16514,7 @@ async function run9(args) {
|
|
|
16291
16514
|
color: { type: "string" },
|
|
16292
16515
|
bold: { type: "boolean" },
|
|
16293
16516
|
italic: { type: "boolean" },
|
|
16517
|
+
author: { type: "string" },
|
|
16294
16518
|
output: { type: "string", short: "o" },
|
|
16295
16519
|
"dry-run": { type: "boolean" },
|
|
16296
16520
|
help: { type: "boolean", short: "h" }
|
|
@@ -16378,7 +16602,7 @@ async function run9(args) {
|
|
|
16378
16602
|
return EXIT.OK;
|
|
16379
16603
|
}
|
|
16380
16604
|
if (isTrackChangesEnabled(view)) {
|
|
16381
|
-
applyTrackedEdit(view, blockRef.node, paragraphNode);
|
|
16605
|
+
applyTrackedEdit(view, blockRef.node, paragraphNode, parsed.values.author);
|
|
16382
16606
|
} else {
|
|
16383
16607
|
blockRef.parent.splice(targetIndex, 1, paragraphNode);
|
|
16384
16608
|
}
|
|
@@ -16391,9 +16615,9 @@ async function run9(args) {
|
|
|
16391
16615
|
});
|
|
16392
16616
|
return EXIT.OK;
|
|
16393
16617
|
}
|
|
16394
|
-
function applyTrackedEdit(view, existingParagraph, newParagraph) {
|
|
16618
|
+
function applyTrackedEdit(view, existingParagraph, newParagraph, authorFlag) {
|
|
16395
16619
|
const allocator = createRevisionAllocator(view);
|
|
16396
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
16620
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
16397
16621
|
const mintMeta = () => ({
|
|
16398
16622
|
...baseMeta,
|
|
16399
16623
|
revisionId: allocator.next()
|
|
@@ -16460,6 +16684,7 @@ Run options (only with --text):
|
|
|
16460
16684
|
--bold Bold
|
|
16461
16685
|
--italic Italic
|
|
16462
16686
|
|
|
16687
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
16463
16688
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16464
16689
|
--dry-run Print what would change; do not write the file
|
|
16465
16690
|
-h, --help Show this help
|
|
@@ -16487,15 +16712,15 @@ function literalMatcher(query, ignoreCase) {
|
|
|
16487
16712
|
throw new Error("query cannot be empty");
|
|
16488
16713
|
}
|
|
16489
16714
|
const needle = ignoreCase ? query.toLowerCase() : query;
|
|
16490
|
-
return (
|
|
16491
|
-
const haystack = ignoreCase ?
|
|
16715
|
+
return (paragraphText2) => {
|
|
16716
|
+
const haystack = ignoreCase ? paragraphText2.toLowerCase() : paragraphText2;
|
|
16492
16717
|
const matches = [];
|
|
16493
16718
|
let cursor = haystack.indexOf(needle);
|
|
16494
16719
|
while (cursor !== -1) {
|
|
16495
16720
|
matches.push({
|
|
16496
16721
|
start: cursor,
|
|
16497
16722
|
end: cursor + needle.length,
|
|
16498
|
-
text:
|
|
16723
|
+
text: paragraphText2.slice(cursor, cursor + needle.length)
|
|
16499
16724
|
});
|
|
16500
16725
|
cursor = haystack.indexOf(needle, cursor + needle.length);
|
|
16501
16726
|
}
|
|
@@ -16505,15 +16730,15 @@ function literalMatcher(query, ignoreCase) {
|
|
|
16505
16730
|
function regexMatcher(pattern, ignoreCase) {
|
|
16506
16731
|
const flags = `g${ignoreCase ? "i" : ""}`;
|
|
16507
16732
|
const regex = new RegExp(pattern, flags);
|
|
16508
|
-
return (
|
|
16733
|
+
return (paragraphText2) => {
|
|
16509
16734
|
const matches = [];
|
|
16510
16735
|
regex.lastIndex = 0;
|
|
16511
|
-
let result = regex.exec(
|
|
16736
|
+
let result = regex.exec(paragraphText2);
|
|
16512
16737
|
while (result !== null) {
|
|
16513
16738
|
const matched = result[0];
|
|
16514
16739
|
if (matched.length === 0) {
|
|
16515
16740
|
regex.lastIndex += 1;
|
|
16516
|
-
result = regex.exec(
|
|
16741
|
+
result = regex.exec(paragraphText2);
|
|
16517
16742
|
continue;
|
|
16518
16743
|
}
|
|
16519
16744
|
matches.push({
|
|
@@ -16521,7 +16746,7 @@ function regexMatcher(pattern, ignoreCase) {
|
|
|
16521
16746
|
end: result.index + matched.length,
|
|
16522
16747
|
text: matched
|
|
16523
16748
|
});
|
|
16524
|
-
result = regex.exec(
|
|
16749
|
+
result = regex.exec(paragraphText2);
|
|
16525
16750
|
}
|
|
16526
16751
|
return matches;
|
|
16527
16752
|
};
|
|
@@ -16529,8 +16754,8 @@ function regexMatcher(pattern, ignoreCase) {
|
|
|
16529
16754
|
function collectMatches(blocks, matcher, out) {
|
|
16530
16755
|
for (const block of blocks) {
|
|
16531
16756
|
if (block.type === "paragraph") {
|
|
16532
|
-
const
|
|
16533
|
-
for (const span of matcher(
|
|
16757
|
+
const paragraphText2 = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
|
|
16758
|
+
for (const span of matcher(paragraphText2)) {
|
|
16534
16759
|
const match = {
|
|
16535
16760
|
blockId: block.id,
|
|
16536
16761
|
start: span.start,
|
|
@@ -16801,6 +17026,7 @@ async function run11(args) {
|
|
|
16801
17026
|
options: {
|
|
16802
17027
|
at: { type: "string" },
|
|
16803
17028
|
url: { type: "string" },
|
|
17029
|
+
author: { type: "string" },
|
|
16804
17030
|
output: { type: "string", short: "o" },
|
|
16805
17031
|
"dry-run": { type: "boolean" },
|
|
16806
17032
|
help: { type: "boolean", short: "h" }
|
|
@@ -16869,6 +17095,13 @@ async function run11(args) {
|
|
|
16869
17095
|
throw error;
|
|
16870
17096
|
}
|
|
16871
17097
|
view.hyperlinksByRelationshipId.set(relationshipId, { url });
|
|
17098
|
+
if (isTrackChangesEnabled(view)) {
|
|
17099
|
+
emitAuditComment(view, { kind: "span", paragraph: paragraphRef.node, span: target.span }, {
|
|
17100
|
+
body: `[docx-cli] hyperlink added \u2192 ${url}`,
|
|
17101
|
+
author: resolveAuthor(parsed.values.author),
|
|
17102
|
+
date: resolveDate()
|
|
17103
|
+
});
|
|
17104
|
+
}
|
|
16872
17105
|
await saveDocView(view, outputPath);
|
|
16873
17106
|
await respond({
|
|
16874
17107
|
ok: true,
|
|
@@ -16891,6 +17124,8 @@ Required:
|
|
|
16891
17124
|
--url URL Target URL
|
|
16892
17125
|
|
|
16893
17126
|
Optional:
|
|
17127
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17128
|
+
(default: $DOCX_AUTHOR)
|
|
16894
17129
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16895
17130
|
--dry-run Print what would change; do not write the file
|
|
16896
17131
|
-h, --help Show this help
|
|
@@ -16898,12 +17133,16 @@ Optional:
|
|
|
16898
17133
|
The span must lie inside a single paragraph and must not overlap an existing
|
|
16899
17134
|
hyperlink or a tracked-change wrapper.
|
|
16900
17135
|
|
|
17136
|
+
When track-changes is on, an audit comment is anchored to the wrapped span
|
|
17137
|
+
since OOXML has no native tracked-change form for hyperlink edits.
|
|
17138
|
+
|
|
16901
17139
|
Examples:
|
|
16902
17140
|
docx hyperlinks add doc.docx --at p3:5-20 --url https://example.com
|
|
16903
17141
|
`;
|
|
16904
17142
|
var init_add2 = __esm(() => {
|
|
16905
17143
|
init_core();
|
|
16906
17144
|
init_parser();
|
|
17145
|
+
init_helpers();
|
|
16907
17146
|
init_respond();
|
|
16908
17147
|
init_wrap();
|
|
16909
17148
|
});
|
|
@@ -16922,6 +17161,7 @@ async function run12(args) {
|
|
|
16922
17161
|
allowPositionals: true,
|
|
16923
17162
|
options: {
|
|
16924
17163
|
at: { type: "string" },
|
|
17164
|
+
author: { type: "string" },
|
|
16925
17165
|
output: { type: "string", short: "o" },
|
|
16926
17166
|
"dry-run": { type: "boolean" },
|
|
16927
17167
|
help: { type: "boolean", short: "h" }
|
|
@@ -16966,6 +17206,9 @@ async function run12(args) {
|
|
|
16966
17206
|
if (index === -1) {
|
|
16967
17207
|
return fail("HYPERLINK_NOT_FOUND", `Hyperlink reference is stale (parent does not contain it): ${targetId}`);
|
|
16968
17208
|
}
|
|
17209
|
+
const trackingOn = isTrackChangesEnabled(view);
|
|
17210
|
+
const paragraph = trackingOn ? findContainingParagraph(view.documentTree, reference.node) : null;
|
|
17211
|
+
const offsets = trackingOn && paragraph ? findElementOffsetsInParagraph(paragraph, reference.node) : null;
|
|
16969
17212
|
reference.parent.splice(index, 1, ...reference.node.children);
|
|
16970
17213
|
view.hyperlinkById.delete(targetId);
|
|
16971
17214
|
if (reference.relationshipId) {
|
|
@@ -16975,6 +17218,13 @@ async function run12(args) {
|
|
|
16975
17218
|
view.hyperlinksByRelationshipId.delete(reference.relationshipId);
|
|
16976
17219
|
}
|
|
16977
17220
|
}
|
|
17221
|
+
if (trackingOn && paragraph && offsets) {
|
|
17222
|
+
emitAuditComment(view, { kind: "span", paragraph, span: offsets }, {
|
|
17223
|
+
body: `[docx-cli] hyperlink removed (was: ${oldUrl ?? "(none)"})`,
|
|
17224
|
+
author: resolveAuthor(parsed.values.author),
|
|
17225
|
+
date: resolveDate()
|
|
17226
|
+
});
|
|
17227
|
+
}
|
|
16978
17228
|
await saveDocView(view, outputPath);
|
|
16979
17229
|
await respond({
|
|
16980
17230
|
ok: true,
|
|
@@ -17017,6 +17267,8 @@ Required:
|
|
|
17017
17267
|
--at LINK_ID Existing hyperlink to remove (e.g., link0)
|
|
17018
17268
|
|
|
17019
17269
|
Optional:
|
|
17270
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17271
|
+
(default: $DOCX_AUTHOR)
|
|
17020
17272
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17021
17273
|
--dry-run Print what would change; do not write the file
|
|
17022
17274
|
-h, --help Show this help
|
|
@@ -17025,12 +17277,16 @@ The display text stays in place; only the <w:hyperlink> wrapper is removed.
|
|
|
17025
17277
|
If the underlying relationship is no longer referenced, it is pruned from the
|
|
17026
17278
|
rels file too.
|
|
17027
17279
|
|
|
17280
|
+
When track-changes is on, an audit comment is anchored to the surviving text
|
|
17281
|
+
since OOXML has no native tracked-change form for hyperlink removal.
|
|
17282
|
+
|
|
17028
17283
|
Examples:
|
|
17029
17284
|
docx hyperlinks delete doc.docx --at link0
|
|
17030
17285
|
`;
|
|
17031
17286
|
var init_delete3 = __esm(() => {
|
|
17032
17287
|
init_core();
|
|
17033
17288
|
init_parser();
|
|
17289
|
+
init_helpers();
|
|
17034
17290
|
init_respond();
|
|
17035
17291
|
});
|
|
17036
17292
|
|
|
@@ -17140,6 +17396,7 @@ async function run14(args) {
|
|
|
17140
17396
|
options: {
|
|
17141
17397
|
at: { type: "string" },
|
|
17142
17398
|
with: { type: "string" },
|
|
17399
|
+
author: { type: "string" },
|
|
17143
17400
|
output: { type: "string", short: "o" },
|
|
17144
17401
|
"dry-run": { type: "boolean" },
|
|
17145
17402
|
help: { type: "boolean", short: "h" }
|
|
@@ -17201,6 +17458,17 @@ async function run14(args) {
|
|
|
17201
17458
|
updateRelationshipTarget(relationships, existingId, newUrl);
|
|
17202
17459
|
view.hyperlinksByRelationshipId.set(existingId, { url: newUrl });
|
|
17203
17460
|
}
|
|
17461
|
+
if (isTrackChangesEnabled(view)) {
|
|
17462
|
+
const paragraph = findContainingParagraph(view.documentTree, reference.node);
|
|
17463
|
+
const offsets = paragraph ? findElementOffsetsInParagraph(paragraph, reference.node) : null;
|
|
17464
|
+
if (paragraph && offsets) {
|
|
17465
|
+
emitAuditComment(view, { kind: "span", paragraph, span: offsets }, {
|
|
17466
|
+
body: `[docx-cli] hyperlink target changed: ${oldUrl ?? "(none)"} \u2192 ${newUrl}`,
|
|
17467
|
+
author: resolveAuthor(parsed.values.author),
|
|
17468
|
+
date: resolveDate()
|
|
17469
|
+
});
|
|
17470
|
+
}
|
|
17471
|
+
}
|
|
17204
17472
|
await saveDocView(view, outputPath);
|
|
17205
17473
|
await respond({
|
|
17206
17474
|
ok: true,
|
|
@@ -17249,6 +17517,8 @@ Required:
|
|
|
17249
17517
|
--with URL New target URL
|
|
17250
17518
|
|
|
17251
17519
|
Optional:
|
|
17520
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17521
|
+
(default: $DOCX_AUTHOR)
|
|
17252
17522
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17253
17523
|
--dry-run Print what would change; do not write the file
|
|
17254
17524
|
-h, --help Show this help
|
|
@@ -17257,12 +17527,17 @@ Replaces only the targeted hyperlink. If multiple hyperlinks shared the same
|
|
|
17257
17527
|
underlying relationship, a new relationship is allocated so the others are
|
|
17258
17528
|
unaffected.
|
|
17259
17529
|
|
|
17530
|
+
When track-changes is on, an audit comment is anchored to the affected
|
|
17531
|
+
hyperlink span since OOXML has no native tracked-change form for hyperlink
|
|
17532
|
+
target edits.
|
|
17533
|
+
|
|
17260
17534
|
Examples:
|
|
17261
17535
|
docx hyperlinks replace doc.docx --at link0 --with https://example.com
|
|
17262
17536
|
`;
|
|
17263
17537
|
var init_replace = __esm(() => {
|
|
17264
17538
|
init_core();
|
|
17265
17539
|
init_parser();
|
|
17540
|
+
init_helpers();
|
|
17266
17541
|
init_respond();
|
|
17267
17542
|
});
|
|
17268
17543
|
|
|
@@ -17522,6 +17797,7 @@ async function run18(args) {
|
|
|
17522
17797
|
options: {
|
|
17523
17798
|
at: { type: "string" },
|
|
17524
17799
|
with: { type: "string" },
|
|
17800
|
+
author: { type: "string" },
|
|
17525
17801
|
output: { type: "string", short: "o" },
|
|
17526
17802
|
"dry-run": { type: "boolean" },
|
|
17527
17803
|
help: { type: "boolean", short: "h" }
|
|
@@ -17577,6 +17853,7 @@ async function run18(args) {
|
|
|
17577
17853
|
return EXIT.OK;
|
|
17578
17854
|
}
|
|
17579
17855
|
const bytes = new Uint8Array(await sourceFile.arrayBuffer());
|
|
17856
|
+
const originalMimeType = reference.contentType;
|
|
17580
17857
|
if (newPartName === originalPartName) {
|
|
17581
17858
|
view.pkg.writeBytes(originalPartName, bytes);
|
|
17582
17859
|
} else {
|
|
@@ -17587,6 +17864,18 @@ async function run18(args) {
|
|
|
17587
17864
|
reference.partName = newPartName;
|
|
17588
17865
|
reference.contentType = newMimeType;
|
|
17589
17866
|
}
|
|
17867
|
+
if (isTrackChangesEnabled(view)) {
|
|
17868
|
+
const author = resolveAuthor(parsed.values.author);
|
|
17869
|
+
const date = resolveDate();
|
|
17870
|
+
const body = `[docx-cli] image replaced: ${originalPartName} (${originalMimeType}) \u2192 ${newPartName} (${newMimeType}, ${bytes.length} bytes)`;
|
|
17871
|
+
const drawingRuns = findDrawingRunsForRelationship(view.documentTree, reference.relationshipId);
|
|
17872
|
+
for (const drawingRun of drawingRuns) {
|
|
17873
|
+
const paragraph = findContainingParagraph(view.documentTree, drawingRun);
|
|
17874
|
+
if (!paragraph)
|
|
17875
|
+
continue;
|
|
17876
|
+
emitAuditComment(view, { kind: "run", paragraph, run: drawingRun }, { body, author, date });
|
|
17877
|
+
}
|
|
17878
|
+
}
|
|
17590
17879
|
await saveDocView(view, outputPath);
|
|
17591
17880
|
await respond({
|
|
17592
17881
|
ok: true,
|
|
@@ -17599,6 +17888,33 @@ async function run18(args) {
|
|
|
17599
17888
|
});
|
|
17600
17889
|
return EXIT.OK;
|
|
17601
17890
|
}
|
|
17891
|
+
function findDrawingRunsForRelationship(documentTree, relationshipId) {
|
|
17892
|
+
const matches = [];
|
|
17893
|
+
function walk(node) {
|
|
17894
|
+
if (node.tag === "w:r" && runReferencesImage(node, relationshipId)) {
|
|
17895
|
+
matches.push(node);
|
|
17896
|
+
return;
|
|
17897
|
+
}
|
|
17898
|
+
for (const child of node.children)
|
|
17899
|
+
walk(child);
|
|
17900
|
+
}
|
|
17901
|
+
for (const root of documentTree)
|
|
17902
|
+
walk(root);
|
|
17903
|
+
return matches;
|
|
17904
|
+
}
|
|
17905
|
+
function runReferencesImage(run19, relationshipId) {
|
|
17906
|
+
for (const child of run19.children) {
|
|
17907
|
+
if (child.tag !== "w:drawing")
|
|
17908
|
+
continue;
|
|
17909
|
+
const blip = child.findDescendant("a:blip");
|
|
17910
|
+
if (!blip)
|
|
17911
|
+
continue;
|
|
17912
|
+
const embed = blip.getAttribute("r:embed") ?? blip.getAttribute("r:link");
|
|
17913
|
+
if (embed === relationshipId)
|
|
17914
|
+
return true;
|
|
17915
|
+
}
|
|
17916
|
+
return false;
|
|
17917
|
+
}
|
|
17602
17918
|
function renameExtension(partName, newExtension) {
|
|
17603
17919
|
const dotIndex = partName.lastIndexOf(".");
|
|
17604
17920
|
const slashIndex = partName.lastIndexOf("/");
|
|
@@ -17645,6 +17961,8 @@ Required:
|
|
|
17645
17961
|
--with PATH New image file (any image MIME type)
|
|
17646
17962
|
|
|
17647
17963
|
Optional:
|
|
17964
|
+
--author NAME Author for the audit comment when track-changes is on
|
|
17965
|
+
(default: $DOCX_AUTHOR)
|
|
17648
17966
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17649
17967
|
--dry-run Print what would change; do not write the file
|
|
17650
17968
|
-h, --help Show this help
|
|
@@ -17653,6 +17971,10 @@ If the replacement uses a different format from the original, the part is
|
|
|
17653
17971
|
renamed (extension changes), the relationship Target is rewritten, and
|
|
17654
17972
|
[Content_Types].xml gets a Default entry for the new extension if needed.
|
|
17655
17973
|
|
|
17974
|
+
When track-changes is on, an audit comment is anchored to each drawing that
|
|
17975
|
+
referenced the swapped image since OOXML has no native tracked-change form
|
|
17976
|
+
for image replacement.
|
|
17977
|
+
|
|
17656
17978
|
Examples:
|
|
17657
17979
|
docx images replace doc.docx --at img2 --with ./new-photo.png
|
|
17658
17980
|
docx images replace doc.docx --at img0 --with ./diagram.svg
|
|
@@ -17660,6 +17982,7 @@ Examples:
|
|
|
17660
17982
|
var init_replace2 = __esm(() => {
|
|
17661
17983
|
init_core();
|
|
17662
17984
|
init_parser();
|
|
17985
|
+
init_helpers();
|
|
17663
17986
|
init_respond();
|
|
17664
17987
|
EXTENSION_BY_MIME = {
|
|
17665
17988
|
"image/png": "png",
|
|
@@ -17721,6 +18044,8 @@ var types_default = `export type Doc = {
|
|
|
17721
18044
|
properties: DocProperties;
|
|
17722
18045
|
blocks: Block[];
|
|
17723
18046
|
comments: Comment[];
|
|
18047
|
+
footnotes: Footnote[];
|
|
18048
|
+
endnotes: Footnote[];
|
|
17724
18049
|
};
|
|
17725
18050
|
|
|
17726
18051
|
export type DocProperties = {
|
|
@@ -17760,7 +18085,14 @@ export type SectionBreak = {
|
|
|
17760
18085
|
type: "sectionBreak";
|
|
17761
18086
|
};
|
|
17762
18087
|
|
|
17763
|
-
export type Run =
|
|
18088
|
+
export type Run =
|
|
18089
|
+
| TextRun
|
|
18090
|
+
| ImageRun
|
|
18091
|
+
| BreakRun
|
|
18092
|
+
| TabRun
|
|
18093
|
+
| EquationRun
|
|
18094
|
+
| FootnoteRefRun
|
|
18095
|
+
| ChartRun;
|
|
17764
18096
|
|
|
17765
18097
|
export type TextRun = {
|
|
17766
18098
|
type: "text";
|
|
@@ -17804,6 +18136,37 @@ export type TabRun = {
|
|
|
17804
18136
|
type: "tab";
|
|
17805
18137
|
};
|
|
17806
18138
|
|
|
18139
|
+
/** A math equation surfaced as concatenated <m:t> plaintext. The structural
|
|
18140
|
+
* OOMath markup (subscripts, fractions, etc.) is collapsed to literal characters,
|
|
18141
|
+
* so the rendering is degraded. \`display\` distinguishes inline equations
|
|
18142
|
+
* (<m:oMath>) from block-level display equations (<m:oMathPara>). */
|
|
18143
|
+
export type EquationRun = {
|
|
18144
|
+
type: "equation";
|
|
18145
|
+
text: string;
|
|
18146
|
+
display: boolean;
|
|
18147
|
+
};
|
|
18148
|
+
|
|
18149
|
+
/** Reference to a footnote or endnote whose body lives in Doc.footnotes or
|
|
18150
|
+
* Doc.endnotes. The id matches the footnote/endnote id (\`fn1\`, \`en1\`). */
|
|
18151
|
+
export type FootnoteRefRun = {
|
|
18152
|
+
type: "footnoteRef";
|
|
18153
|
+
kind: "footnote" | "endnote";
|
|
18154
|
+
id: string;
|
|
18155
|
+
};
|
|
18156
|
+
|
|
18157
|
+
/** Placeholder for a non-picture drawing (chart, shape, SmartArt, etc.). The
|
|
18158
|
+
* full content is preserved in the underlying XML; the run is a marker so
|
|
18159
|
+
* callers can know "something visual lives here." */
|
|
18160
|
+
export type ChartRun = {
|
|
18161
|
+
type: "chart";
|
|
18162
|
+
kind: "chart" | "shape" | "smartart" | "drawing";
|
|
18163
|
+
};
|
|
18164
|
+
|
|
18165
|
+
export type Footnote = {
|
|
18166
|
+
id: string;
|
|
18167
|
+
text: string;
|
|
18168
|
+
};
|
|
18169
|
+
|
|
17807
18170
|
export type TrackedChange = {
|
|
17808
18171
|
kind: "ins" | "del";
|
|
17809
18172
|
author: string;
|
|
@@ -17864,10 +18227,10 @@ async function run20(args) {
|
|
|
17864
18227
|
await respond(JSON_SCHEMA);
|
|
17865
18228
|
return EXIT.OK;
|
|
17866
18229
|
}
|
|
17867
|
-
var HELP20 = `docx schema \u2014 print the AST type definitions
|
|
18230
|
+
var HELP20 = `docx info schema \u2014 print the AST type definitions
|
|
17868
18231
|
|
|
17869
18232
|
Usage:
|
|
17870
|
-
docx schema [options]
|
|
18233
|
+
docx info schema [options]
|
|
17871
18234
|
|
|
17872
18235
|
Options:
|
|
17873
18236
|
--json Print as a JSON Schema document (default)
|
|
@@ -17875,8 +18238,8 @@ Options:
|
|
|
17875
18238
|
-h, --help Show this help
|
|
17876
18239
|
|
|
17877
18240
|
Examples:
|
|
17878
|
-
docx schema | jq '.
|
|
17879
|
-
docx schema --ts > ast.d.ts
|
|
18241
|
+
docx info schema | jq '.$defs.Run'
|
|
18242
|
+
docx info schema --ts > ast.d.ts
|
|
17880
18243
|
`, JSON_SCHEMA;
|
|
17881
18244
|
var init_schema = __esm(() => {
|
|
17882
18245
|
init_types();
|
|
@@ -17886,7 +18249,15 @@ var init_schema = __esm(() => {
|
|
|
17886
18249
|
$id: "https://github.com/kklimuk/docx-cli/schema",
|
|
17887
18250
|
title: "docx-cli AST",
|
|
17888
18251
|
type: "object",
|
|
17889
|
-
required: [
|
|
18252
|
+
required: [
|
|
18253
|
+
"schemaVersion",
|
|
18254
|
+
"path",
|
|
18255
|
+
"properties",
|
|
18256
|
+
"blocks",
|
|
18257
|
+
"comments",
|
|
18258
|
+
"footnotes",
|
|
18259
|
+
"endnotes"
|
|
18260
|
+
],
|
|
17890
18261
|
properties: {
|
|
17891
18262
|
schemaVersion: { const: 1 },
|
|
17892
18263
|
path: { type: "string" },
|
|
@@ -17900,7 +18271,9 @@ var init_schema = __esm(() => {
|
|
|
17900
18271
|
}
|
|
17901
18272
|
},
|
|
17902
18273
|
blocks: { type: "array", items: { $ref: "#/$defs/Block" } },
|
|
17903
|
-
comments: { type: "array", items: { $ref: "#/$defs/Comment" } }
|
|
18274
|
+
comments: { type: "array", items: { $ref: "#/$defs/Comment" } },
|
|
18275
|
+
footnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } },
|
|
18276
|
+
endnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } }
|
|
17904
18277
|
},
|
|
17905
18278
|
$defs: {
|
|
17906
18279
|
Block: {
|
|
@@ -17934,7 +18307,10 @@ var init_schema = __esm(() => {
|
|
|
17934
18307
|
{ $ref: "#/$defs/TextRun" },
|
|
17935
18308
|
{ $ref: "#/$defs/ImageRun" },
|
|
17936
18309
|
{ $ref: "#/$defs/BreakRun" },
|
|
17937
|
-
{ $ref: "#/$defs/TabRun" }
|
|
18310
|
+
{ $ref: "#/$defs/TabRun" },
|
|
18311
|
+
{ $ref: "#/$defs/EquationRun" },
|
|
18312
|
+
{ $ref: "#/$defs/FootnoteRefRun" },
|
|
18313
|
+
{ $ref: "#/$defs/ChartRun" }
|
|
17938
18314
|
]
|
|
17939
18315
|
},
|
|
17940
18316
|
TextRun: {
|
|
@@ -18001,6 +18377,40 @@ var init_schema = __esm(() => {
|
|
|
18001
18377
|
required: ["type"],
|
|
18002
18378
|
properties: { type: { const: "tab" } }
|
|
18003
18379
|
},
|
|
18380
|
+
EquationRun: {
|
|
18381
|
+
type: "object",
|
|
18382
|
+
required: ["type", "text", "display"],
|
|
18383
|
+
properties: {
|
|
18384
|
+
type: { const: "equation" },
|
|
18385
|
+
text: { type: "string" },
|
|
18386
|
+
display: { type: "boolean" }
|
|
18387
|
+
}
|
|
18388
|
+
},
|
|
18389
|
+
FootnoteRefRun: {
|
|
18390
|
+
type: "object",
|
|
18391
|
+
required: ["type", "kind", "id"],
|
|
18392
|
+
properties: {
|
|
18393
|
+
type: { const: "footnoteRef" },
|
|
18394
|
+
kind: { enum: ["footnote", "endnote"] },
|
|
18395
|
+
id: { type: "string" }
|
|
18396
|
+
}
|
|
18397
|
+
},
|
|
18398
|
+
ChartRun: {
|
|
18399
|
+
type: "object",
|
|
18400
|
+
required: ["type", "kind"],
|
|
18401
|
+
properties: {
|
|
18402
|
+
type: { const: "chart" },
|
|
18403
|
+
kind: { enum: ["chart", "shape", "smartart", "drawing"] }
|
|
18404
|
+
}
|
|
18405
|
+
},
|
|
18406
|
+
Footnote: {
|
|
18407
|
+
type: "object",
|
|
18408
|
+
required: ["id", "text"],
|
|
18409
|
+
properties: {
|
|
18410
|
+
id: { type: "string" },
|
|
18411
|
+
text: { type: "string" }
|
|
18412
|
+
}
|
|
18413
|
+
},
|
|
18004
18414
|
Table: {
|
|
18005
18415
|
type: "object",
|
|
18006
18416
|
required: ["id", "type", "rows"],
|
|
@@ -18097,14 +18507,18 @@ async function run21(args) {
|
|
|
18097
18507
|
await writeStdout(REFERENCE);
|
|
18098
18508
|
return EXIT.OK;
|
|
18099
18509
|
}
|
|
18100
|
-
var HELP21 = `docx locators \u2014 print the locator grammar reference
|
|
18510
|
+
var HELP21 = `docx info locators \u2014 print the locator grammar reference
|
|
18101
18511
|
|
|
18102
18512
|
Usage:
|
|
18103
|
-
docx locators [options]
|
|
18513
|
+
docx info locators [options]
|
|
18104
18514
|
|
|
18105
18515
|
Options:
|
|
18106
18516
|
--json Print as JSON
|
|
18107
18517
|
-h, --help Show this help
|
|
18518
|
+
|
|
18519
|
+
Examples:
|
|
18520
|
+
docx info locators
|
|
18521
|
+
docx info locators --json | jq '.entityLocators'
|
|
18108
18522
|
`, REFERENCE = `LOCATOR GRAMMAR
|
|
18109
18523
|
|
|
18110
18524
|
Block locators:
|
|
@@ -18237,6 +18651,7 @@ async function run23(args) {
|
|
|
18237
18651
|
bold: { type: "boolean" },
|
|
18238
18652
|
italic: { type: "boolean" },
|
|
18239
18653
|
url: { type: "string" },
|
|
18654
|
+
author: { type: "string" },
|
|
18240
18655
|
output: { type: "string", short: "o" },
|
|
18241
18656
|
"dry-run": { type: "boolean" },
|
|
18242
18657
|
help: { type: "boolean", short: "h" }
|
|
@@ -18325,7 +18740,7 @@ async function run23(args) {
|
|
|
18325
18740
|
}
|
|
18326
18741
|
const insertIndex = after !== undefined ? targetIndex + 1 : targetIndex;
|
|
18327
18742
|
if (isTrackChangesEnabled(view)) {
|
|
18328
|
-
applyTrackedInsertion(paragraphNode, view);
|
|
18743
|
+
applyTrackedInsertion(paragraphNode, view, parsed.values.author);
|
|
18329
18744
|
}
|
|
18330
18745
|
const outputPath = parsed.values.output;
|
|
18331
18746
|
if (parsed.values["dry-run"]) {
|
|
@@ -18374,9 +18789,9 @@ function wrapFirstRunInHyperlink(view, paragraph, url) {
|
|
|
18374
18789
|
}
|
|
18375
18790
|
paragraph.children = newChildren;
|
|
18376
18791
|
}
|
|
18377
|
-
function applyTrackedInsertion(paragraph, view) {
|
|
18792
|
+
function applyTrackedInsertion(paragraph, view, authorFlag) {
|
|
18378
18793
|
const allocator = createRevisionAllocator(view);
|
|
18379
|
-
const baseMeta = { author: resolveAuthor(), date: resolveDate() };
|
|
18794
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
18380
18795
|
const mintMeta = () => ({
|
|
18381
18796
|
...baseMeta,
|
|
18382
18797
|
revisionId: allocator.next()
|
|
@@ -18427,6 +18842,7 @@ Run options (only with --text):
|
|
|
18427
18842
|
--italic Italic
|
|
18428
18843
|
--url URL Wrap the inserted text in a hyperlink to URL
|
|
18429
18844
|
|
|
18845
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
18430
18846
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
18431
18847
|
--dry-run Print what would be inserted; do not write the file
|
|
18432
18848
|
-h, --help Show this help
|
|
@@ -18500,14 +18916,9 @@ function* headingParagraphs(blocks, stylePrefix) {
|
|
|
18500
18916
|
yield block;
|
|
18501
18917
|
}
|
|
18502
18918
|
}
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
if (run24.type === "text")
|
|
18507
|
-
out += run24.text;
|
|
18508
|
-
}
|
|
18509
|
-
return out;
|
|
18510
|
-
}
|
|
18919
|
+
var init_build = __esm(() => {
|
|
18920
|
+
init_core();
|
|
18921
|
+
});
|
|
18511
18922
|
|
|
18512
18923
|
// src/cli/outline/index.ts
|
|
18513
18924
|
var exports_outline = {};
|
|
@@ -18579,6 +18990,425 @@ Examples:
|
|
|
18579
18990
|
`;
|
|
18580
18991
|
var init_outline = __esm(() => {
|
|
18581
18992
|
init_respond();
|
|
18993
|
+
init_build();
|
|
18994
|
+
});
|
|
18995
|
+
|
|
18996
|
+
// src/cli/read/markdown.ts
|
|
18997
|
+
function renderMarkdown(doc, options = {}) {
|
|
18998
|
+
const blocks = sliceBlocks(doc.blocks, options.from, options.to);
|
|
18999
|
+
const commentIndex = options.showComments ? buildCommentIndex(blocks, options) : emptyCommentIndex();
|
|
19000
|
+
const ctx = {
|
|
19001
|
+
options,
|
|
19002
|
+
commentIndex,
|
|
19003
|
+
referencedFootnoteIds: new Set,
|
|
19004
|
+
referencedEndnoteIds: new Set
|
|
19005
|
+
};
|
|
19006
|
+
const parts = [];
|
|
19007
|
+
for (const block of blocks) {
|
|
19008
|
+
const rendered = renderBlock(block, ctx);
|
|
19009
|
+
if (rendered !== null)
|
|
19010
|
+
parts.push(rendered);
|
|
19011
|
+
}
|
|
19012
|
+
const definitions = [];
|
|
19013
|
+
if (options.showComments) {
|
|
19014
|
+
const commentFootnotes = renderCommentFootnotes(commentIndex, doc.comments);
|
|
19015
|
+
if (commentFootnotes.length > 0)
|
|
19016
|
+
definitions.push(commentFootnotes);
|
|
19017
|
+
}
|
|
19018
|
+
const footnoteDefs = renderNoteDefinitions(doc.footnotes, ctx.referencedFootnoteIds);
|
|
19019
|
+
if (footnoteDefs.length > 0)
|
|
19020
|
+
definitions.push(footnoteDefs);
|
|
19021
|
+
const endnoteDefs = renderNoteDefinitions(doc.endnotes, ctx.referencedEndnoteIds);
|
|
19022
|
+
if (endnoteDefs.length > 0)
|
|
19023
|
+
definitions.push(endnoteDefs);
|
|
19024
|
+
if (definitions.length > 0)
|
|
19025
|
+
parts.push(definitions.join(`
|
|
19026
|
+
`));
|
|
19027
|
+
if (parts.length === 0)
|
|
19028
|
+
return "";
|
|
19029
|
+
return `${parts.join(`
|
|
19030
|
+
|
|
19031
|
+
`)}
|
|
19032
|
+
`;
|
|
19033
|
+
}
|
|
19034
|
+
function emptyCommentIndex() {
|
|
19035
|
+
return {
|
|
19036
|
+
endingsByRun: new Map,
|
|
19037
|
+
spanText: new Map,
|
|
19038
|
+
orderedIds: []
|
|
19039
|
+
};
|
|
19040
|
+
}
|
|
19041
|
+
function buildCommentIndex(blocks, options) {
|
|
19042
|
+
const showChanges = options.showChanges ?? false;
|
|
19043
|
+
const lastSlot = new Map;
|
|
19044
|
+
const spanText = new Map;
|
|
19045
|
+
const orderedIds = [];
|
|
19046
|
+
for (const paragraph of flattenParagraphs(blocks)) {
|
|
19047
|
+
paragraph.runs.forEach((run25, index) => {
|
|
19048
|
+
if (run25.type !== "text")
|
|
19049
|
+
return;
|
|
19050
|
+
if (!showChanges && run25.trackedChange?.kind === "del")
|
|
19051
|
+
return;
|
|
19052
|
+
for (const commentId of run25.comments ?? []) {
|
|
19053
|
+
if (!spanText.has(commentId))
|
|
19054
|
+
orderedIds.push(commentId);
|
|
19055
|
+
spanText.set(commentId, (spanText.get(commentId) ?? "") + run25.text);
|
|
19056
|
+
lastSlot.set(commentId, slotKey(paragraph.id, index));
|
|
19057
|
+
}
|
|
19058
|
+
});
|
|
19059
|
+
}
|
|
19060
|
+
const endingsByRun = new Map;
|
|
19061
|
+
for (const commentId of orderedIds) {
|
|
19062
|
+
const slot = lastSlot.get(commentId);
|
|
19063
|
+
if (!slot)
|
|
19064
|
+
continue;
|
|
19065
|
+
const list = endingsByRun.get(slot) ?? [];
|
|
19066
|
+
list.push(commentId);
|
|
19067
|
+
endingsByRun.set(slot, list);
|
|
19068
|
+
}
|
|
19069
|
+
return { endingsByRun, spanText, orderedIds };
|
|
19070
|
+
}
|
|
19071
|
+
function slotKey(paragraphId, runIndex) {
|
|
19072
|
+
return `${paragraphId}#${runIndex}`;
|
|
19073
|
+
}
|
|
19074
|
+
function renderBlock(block, ctx) {
|
|
19075
|
+
if (block.type === "paragraph")
|
|
19076
|
+
return renderParagraph(block, ctx);
|
|
19077
|
+
if (block.type === "table")
|
|
19078
|
+
return renderTable(block, ctx);
|
|
19079
|
+
if (block.type === "sectionBreak")
|
|
19080
|
+
return `--- <!-- ${block.id} -->`;
|
|
19081
|
+
return null;
|
|
19082
|
+
}
|
|
19083
|
+
function renderParagraph(paragraph, ctx) {
|
|
19084
|
+
const body = renderRuns(paragraph.id, paragraph.runs, ctx);
|
|
19085
|
+
if (body.length === 0)
|
|
19086
|
+
return null;
|
|
19087
|
+
const prefix = paragraphPrefix(paragraph);
|
|
19088
|
+
return `${prefix}${body} <!-- ${paragraph.id} -->`;
|
|
19089
|
+
}
|
|
19090
|
+
function paragraphPrefix(paragraph) {
|
|
19091
|
+
const headingLevel2 = headingLevelFor(paragraph.style);
|
|
19092
|
+
if (headingLevel2 !== null)
|
|
19093
|
+
return `${"#".repeat(headingLevel2)} `;
|
|
19094
|
+
if (paragraph.list) {
|
|
19095
|
+
const indent = " ".repeat(paragraph.list.level);
|
|
19096
|
+
return `${indent}- `;
|
|
19097
|
+
}
|
|
19098
|
+
return "";
|
|
19099
|
+
}
|
|
19100
|
+
function headingLevelFor(style) {
|
|
19101
|
+
if (!style)
|
|
19102
|
+
return null;
|
|
19103
|
+
if (!style.startsWith("Heading"))
|
|
19104
|
+
return null;
|
|
19105
|
+
const remainder = style.slice("Heading".length).trim();
|
|
19106
|
+
if (remainder === "")
|
|
19107
|
+
return 1;
|
|
19108
|
+
const parsed = Number(remainder);
|
|
19109
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 6)
|
|
19110
|
+
return null;
|
|
19111
|
+
return parsed;
|
|
19112
|
+
}
|
|
19113
|
+
function renderRuns(paragraphId, runs, ctx) {
|
|
19114
|
+
const showChanges = ctx.options.showChanges ?? false;
|
|
19115
|
+
const visibleEntries = [];
|
|
19116
|
+
runs.forEach((run25, index) => {
|
|
19117
|
+
if (!showChanges && run25.type === "text" && run25.trackedChange?.kind === "del") {
|
|
19118
|
+
return;
|
|
19119
|
+
}
|
|
19120
|
+
visibleEntries.push({ run: run25, originalIndex: index });
|
|
19121
|
+
});
|
|
19122
|
+
let out = "";
|
|
19123
|
+
let cursor = 0;
|
|
19124
|
+
while (cursor < visibleEntries.length) {
|
|
19125
|
+
const entry = visibleEntries[cursor];
|
|
19126
|
+
if (!entry) {
|
|
19127
|
+
cursor++;
|
|
19128
|
+
continue;
|
|
19129
|
+
}
|
|
19130
|
+
const { run: run25 } = entry;
|
|
19131
|
+
if (run25.type === "text") {
|
|
19132
|
+
let lookahead = cursor + 1;
|
|
19133
|
+
while (lookahead < visibleEntries.length) {
|
|
19134
|
+
const next = visibleEntries[lookahead];
|
|
19135
|
+
if (!next || next.run.type !== "text")
|
|
19136
|
+
break;
|
|
19137
|
+
if (!sameDecoration(run25, next.run))
|
|
19138
|
+
break;
|
|
19139
|
+
lookahead++;
|
|
19140
|
+
}
|
|
19141
|
+
const segment = visibleEntries.slice(cursor, lookahead);
|
|
19142
|
+
out += renderTextSegment(segment.map((entry2) => entry2.run), showChanges);
|
|
19143
|
+
out += commentEndingsFor(paragraphId, segment, ctx.commentIndex);
|
|
19144
|
+
cursor = lookahead;
|
|
19145
|
+
continue;
|
|
19146
|
+
}
|
|
19147
|
+
if (run25.type === "image") {
|
|
19148
|
+
const alt = sanitizeAltText(run25.alt ?? run25.id);
|
|
19149
|
+
out += ``;
|
|
19150
|
+
} else if (run25.type === "break") {
|
|
19151
|
+
if (run25.kind === "line")
|
|
19152
|
+
out += "<br>";
|
|
19153
|
+
} else if (run25.type === "tab") {
|
|
19154
|
+
out += "\t";
|
|
19155
|
+
} else if (run25.type === "equation") {
|
|
19156
|
+
const escaped = run25.text.replace(/`/g, "\\`");
|
|
19157
|
+
out += `\`equation: ${escaped}\``;
|
|
19158
|
+
} else if (run25.type === "footnoteRef") {
|
|
19159
|
+
if (run25.kind === "footnote")
|
|
19160
|
+
ctx.referencedFootnoteIds.add(run25.id);
|
|
19161
|
+
else
|
|
19162
|
+
ctx.referencedEndnoteIds.add(run25.id);
|
|
19163
|
+
out += `[^${run25.id}]`;
|
|
19164
|
+
} else if (run25.type === "chart") {
|
|
19165
|
+
out += `\`[${run25.kind}]\``;
|
|
19166
|
+
}
|
|
19167
|
+
cursor++;
|
|
19168
|
+
}
|
|
19169
|
+
return out;
|
|
19170
|
+
}
|
|
19171
|
+
function commentEndingsFor(paragraphId, segment, commentIndex) {
|
|
19172
|
+
if (commentIndex.endingsByRun.size === 0)
|
|
19173
|
+
return "";
|
|
19174
|
+
let out = "";
|
|
19175
|
+
for (const entry of segment) {
|
|
19176
|
+
const ids = commentIndex.endingsByRun.get(slotKey(paragraphId, entry.originalIndex));
|
|
19177
|
+
if (!ids)
|
|
19178
|
+
continue;
|
|
19179
|
+
for (const commentId of ids)
|
|
19180
|
+
out += `[^${commentId}]`;
|
|
19181
|
+
}
|
|
19182
|
+
return out;
|
|
19183
|
+
}
|
|
19184
|
+
function sameDecoration(a2, b) {
|
|
19185
|
+
return (a2.bold ?? false) === (b.bold ?? false) && (a2.italic ?? false) === (b.italic ?? false) && (a2.strike ?? false) === (b.strike ?? false) && (a2.underline ?? "") === (b.underline ?? "") && (a2.color ?? "") === (b.color ?? "") && (a2.highlight ?? "") === (b.highlight ?? "") && a2.hyperlink?.id === b.hyperlink?.id && a2.trackedChange?.kind === b.trackedChange?.kind && sameCommentSet(a2.comments, b.comments);
|
|
19186
|
+
}
|
|
19187
|
+
function sameCommentSet(left, right) {
|
|
19188
|
+
const a2 = left ?? [];
|
|
19189
|
+
const b = right ?? [];
|
|
19190
|
+
if (a2.length !== b.length)
|
|
19191
|
+
return false;
|
|
19192
|
+
for (let i = 0;i < a2.length; i++) {
|
|
19193
|
+
if (a2[i] !== b[i])
|
|
19194
|
+
return false;
|
|
19195
|
+
}
|
|
19196
|
+
return true;
|
|
19197
|
+
}
|
|
19198
|
+
function renderTextSegment(runs, showChanges) {
|
|
19199
|
+
const text = runs.map((run25) => run25.text).join("");
|
|
19200
|
+
if (text.length === 0)
|
|
19201
|
+
return "";
|
|
19202
|
+
const first = runs[0];
|
|
19203
|
+
if (!first)
|
|
19204
|
+
return "";
|
|
19205
|
+
let out = text;
|
|
19206
|
+
if (first.bold)
|
|
19207
|
+
out = `**${out}**`;
|
|
19208
|
+
if (first.italic)
|
|
19209
|
+
out = `*${out}*`;
|
|
19210
|
+
if (first.strike)
|
|
19211
|
+
out = `~~${out}~~`;
|
|
19212
|
+
if (first.underline)
|
|
19213
|
+
out = `<u>${out}</u>`;
|
|
19214
|
+
const color = colorAttrFor(first.color);
|
|
19215
|
+
if (color)
|
|
19216
|
+
out = `<span style="color:${color}">${out}</span>`;
|
|
19217
|
+
const highlight = highlightCssFor(first.highlight);
|
|
19218
|
+
if (highlight)
|
|
19219
|
+
out = `<span style="background-color:${highlight}">${out}</span>`;
|
|
19220
|
+
if (first.hyperlink) {
|
|
19221
|
+
const target = first.hyperlink.url ?? `#${first.hyperlink.anchor ?? ""}`;
|
|
19222
|
+
out = `[${out}](${target})`;
|
|
19223
|
+
}
|
|
19224
|
+
if (showChanges && first.trackedChange) {
|
|
19225
|
+
out = `<${first.trackedChange.kind}>${out}</${first.trackedChange.kind}>`;
|
|
19226
|
+
}
|
|
19227
|
+
return out;
|
|
19228
|
+
}
|
|
19229
|
+
function colorAttrFor(value) {
|
|
19230
|
+
if (!value)
|
|
19231
|
+
return null;
|
|
19232
|
+
const lowered = value.toLowerCase();
|
|
19233
|
+
if (lowered === "auto")
|
|
19234
|
+
return null;
|
|
19235
|
+
if (/^[0-9a-f]{6}$/.test(lowered))
|
|
19236
|
+
return `#${lowered}`;
|
|
19237
|
+
return value;
|
|
19238
|
+
}
|
|
19239
|
+
function highlightCssFor(value) {
|
|
19240
|
+
if (!value)
|
|
19241
|
+
return null;
|
|
19242
|
+
if (value === "none")
|
|
19243
|
+
return null;
|
|
19244
|
+
return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
|
|
19245
|
+
}
|
|
19246
|
+
function renderTable(table, ctx) {
|
|
19247
|
+
if (table.rows.length === 0)
|
|
19248
|
+
return null;
|
|
19249
|
+
const colCount = Math.max(...table.rows.map((row) => row.cells.length));
|
|
19250
|
+
if (colCount === 0)
|
|
19251
|
+
return null;
|
|
19252
|
+
const renderedRows = table.rows.map((row) => {
|
|
19253
|
+
const cells = [];
|
|
19254
|
+
for (let columnIndex = 0;columnIndex < colCount; columnIndex++) {
|
|
19255
|
+
const cell = row.cells[columnIndex];
|
|
19256
|
+
cells.push(cell ? renderCell(cell, ctx) : "");
|
|
19257
|
+
}
|
|
19258
|
+
return cells;
|
|
19259
|
+
});
|
|
19260
|
+
const lines = [];
|
|
19261
|
+
const headerRow = renderedRows[0];
|
|
19262
|
+
if (!headerRow)
|
|
19263
|
+
return null;
|
|
19264
|
+
lines.push(rowToLine(headerRow));
|
|
19265
|
+
lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
|
|
19266
|
+
for (let rowIndex = 1;rowIndex < renderedRows.length; rowIndex++) {
|
|
19267
|
+
const row = renderedRows[rowIndex];
|
|
19268
|
+
if (row)
|
|
19269
|
+
lines.push(rowToLine(row));
|
|
19270
|
+
}
|
|
19271
|
+
return lines.join(`
|
|
19272
|
+
`);
|
|
19273
|
+
}
|
|
19274
|
+
function rowToLine(cells) {
|
|
19275
|
+
return `| ${cells.join(" | ")} |`;
|
|
19276
|
+
}
|
|
19277
|
+
function renderCell(cell, ctx) {
|
|
19278
|
+
const parts = [];
|
|
19279
|
+
for (const block of cell.blocks) {
|
|
19280
|
+
if (block.type === "paragraph") {
|
|
19281
|
+
const body = renderRuns(block.id, block.runs, ctx);
|
|
19282
|
+
if (body.length === 0)
|
|
19283
|
+
continue;
|
|
19284
|
+
parts.push(`${body} <!-- ${block.id} -->`);
|
|
19285
|
+
continue;
|
|
19286
|
+
}
|
|
19287
|
+
if (block.type === "table") {
|
|
19288
|
+
parts.push(renderNestedTable(block, ctx));
|
|
19289
|
+
}
|
|
19290
|
+
}
|
|
19291
|
+
return escapeCell(parts.join("<br>"));
|
|
19292
|
+
}
|
|
19293
|
+
function renderNestedTable(table, ctx) {
|
|
19294
|
+
const rows = table.rows.map((row) => row.cells.map((cell) => renderCell(cell, ctx)).join(" / "));
|
|
19295
|
+
return rows.join(" // ");
|
|
19296
|
+
}
|
|
19297
|
+
function escapeCell(text) {
|
|
19298
|
+
return text.replace(/\|/g, "\\|");
|
|
19299
|
+
}
|
|
19300
|
+
function sanitizeAltText(text) {
|
|
19301
|
+
return text.replace(/[\r\n]+/g, " ").replace(/\]/g, "\\]");
|
|
19302
|
+
}
|
|
19303
|
+
function renderCommentFootnotes(commentIndex, comments) {
|
|
19304
|
+
if (commentIndex.orderedIds.length === 0)
|
|
19305
|
+
return "";
|
|
19306
|
+
const byId = new Map(comments.map((comment) => [comment.id, comment]));
|
|
19307
|
+
const sorted = [...commentIndex.orderedIds].sort(commentIdCompare);
|
|
19308
|
+
const lines = [];
|
|
19309
|
+
for (const commentId of sorted) {
|
|
19310
|
+
const comment = byId.get(commentId);
|
|
19311
|
+
if (!comment)
|
|
19312
|
+
continue;
|
|
19313
|
+
const span = commentIndex.spanText.get(commentId) ?? "";
|
|
19314
|
+
lines.push(formatFootnote(comment, span));
|
|
19315
|
+
}
|
|
19316
|
+
return lines.join(`
|
|
19317
|
+
`);
|
|
19318
|
+
}
|
|
19319
|
+
function renderNoteDefinitions(notes, referenced) {
|
|
19320
|
+
if (referenced.size === 0)
|
|
19321
|
+
return "";
|
|
19322
|
+
const byId = new Map(notes.map((note) => [note.id, note]));
|
|
19323
|
+
const sorted = [...referenced].sort(noteIdCompare);
|
|
19324
|
+
const lines = [];
|
|
19325
|
+
for (const id of sorted) {
|
|
19326
|
+
const note = byId.get(id);
|
|
19327
|
+
const body = (note?.text ?? "").replace(/\s+/g, " ").trim();
|
|
19328
|
+
lines.push(`[^${id}]: ${body}`);
|
|
19329
|
+
}
|
|
19330
|
+
return lines.join(`
|
|
19331
|
+
`);
|
|
19332
|
+
}
|
|
19333
|
+
function noteIdCompare(left, right) {
|
|
19334
|
+
return numericIdCompare(left, right, /(\d+)$/);
|
|
19335
|
+
}
|
|
19336
|
+
function commentIdCompare(left, right) {
|
|
19337
|
+
return numericIdCompare(left, right, /^c(\d+)$/);
|
|
19338
|
+
}
|
|
19339
|
+
function numericIdCompare(left, right, pattern) {
|
|
19340
|
+
const leftMatch = left.match(pattern);
|
|
19341
|
+
const rightMatch = right.match(pattern);
|
|
19342
|
+
if (leftMatch?.[1] && rightMatch?.[1]) {
|
|
19343
|
+
return Number(leftMatch[1]) - Number(rightMatch[1]);
|
|
19344
|
+
}
|
|
19345
|
+
return left.localeCompare(right);
|
|
19346
|
+
}
|
|
19347
|
+
function formatFootnote(comment, spanText) {
|
|
19348
|
+
const quoted = quoteSpan(spanText);
|
|
19349
|
+
const reply = comment.parentId ? ` \u21B3 ${comment.parentId}` : "";
|
|
19350
|
+
const resolved = comment.resolved ? "\u2713 " : "";
|
|
19351
|
+
const body = comment.text.replace(/\s+/g, " ").trim();
|
|
19352
|
+
return `[^${comment.id}]: ${quoted} \u2014 ${resolved}${comment.author} (${comment.date})${reply}: ${body}`;
|
|
19353
|
+
}
|
|
19354
|
+
function quoteSpan(text) {
|
|
19355
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
19356
|
+
const escaped = collapsed.replace(/"/g, "\\\"");
|
|
19357
|
+
return `"${escaped}"`;
|
|
19358
|
+
}
|
|
19359
|
+
function sliceBlocks(blocks, from, to) {
|
|
19360
|
+
if (!from && !to)
|
|
19361
|
+
return blocks;
|
|
19362
|
+
const fromId = from ? blockIdForLocator(from, "from") : null;
|
|
19363
|
+
const toId = to ? blockIdForLocator(to, "to") : null;
|
|
19364
|
+
const fromIndex = fromId ? blocks.findIndex((b) => b.id === fromId) : 0;
|
|
19365
|
+
if (from && fromId && fromIndex === -1) {
|
|
19366
|
+
throw new MarkdownLocatorError(from, `--from ${from} not found at document top level`);
|
|
19367
|
+
}
|
|
19368
|
+
const toIndex = toId ? blocks.findIndex((b) => b.id === toId) : blocks.length - 1;
|
|
19369
|
+
if (to && toId && toIndex === -1) {
|
|
19370
|
+
throw new MarkdownLocatorError(to, `--to ${to} not found at document top level`);
|
|
19371
|
+
}
|
|
19372
|
+
if (toIndex < fromIndex)
|
|
19373
|
+
return [];
|
|
19374
|
+
return blocks.slice(fromIndex, toIndex + 1);
|
|
19375
|
+
}
|
|
19376
|
+
function blockIdForLocator(input, position) {
|
|
19377
|
+
let parsed;
|
|
19378
|
+
try {
|
|
19379
|
+
parsed = parseLocator(input);
|
|
19380
|
+
} catch (err) {
|
|
19381
|
+
if (err instanceof LocatorParseError) {
|
|
19382
|
+
throw new MarkdownLocatorError(input, err.message);
|
|
19383
|
+
}
|
|
19384
|
+
throw err;
|
|
19385
|
+
}
|
|
19386
|
+
switch (parsed.kind) {
|
|
19387
|
+
case "block":
|
|
19388
|
+
return parsed.blockId;
|
|
19389
|
+
case "blockSpan":
|
|
19390
|
+
return parsed.blockId;
|
|
19391
|
+
case "range":
|
|
19392
|
+
return position === "from" ? parsed.start.blockId : parsed.end.blockId;
|
|
19393
|
+
case "cell":
|
|
19394
|
+
return parsed.tableId;
|
|
19395
|
+
case "comment":
|
|
19396
|
+
case "image":
|
|
19397
|
+
case "hyperlink":
|
|
19398
|
+
throw new MarkdownLocatorError(input, `--${position} does not accept a ${parsed.kind} locator \u2014 use a paragraph or table locator`);
|
|
19399
|
+
}
|
|
19400
|
+
}
|
|
19401
|
+
var MarkdownLocatorError;
|
|
19402
|
+
var init_markdown = __esm(() => {
|
|
19403
|
+
init_core();
|
|
19404
|
+
MarkdownLocatorError = class MarkdownLocatorError extends Error {
|
|
19405
|
+
input;
|
|
19406
|
+
constructor(input, message) {
|
|
19407
|
+
super(message);
|
|
19408
|
+
this.input = input;
|
|
19409
|
+
this.name = "MarkdownLocatorError";
|
|
19410
|
+
}
|
|
19411
|
+
};
|
|
18582
19412
|
});
|
|
18583
19413
|
|
|
18584
19414
|
// src/cli/read/index.ts
|
|
@@ -18594,7 +19424,11 @@ async function run25(args) {
|
|
|
18594
19424
|
args,
|
|
18595
19425
|
allowPositionals: true,
|
|
18596
19426
|
options: {
|
|
18597
|
-
|
|
19427
|
+
markdown: { type: "boolean" },
|
|
19428
|
+
from: { type: "string" },
|
|
19429
|
+
to: { type: "string" },
|
|
19430
|
+
changes: { type: "boolean" },
|
|
19431
|
+
comments: { type: "boolean" },
|
|
18598
19432
|
help: { type: "boolean", short: "h" }
|
|
18599
19433
|
}
|
|
18600
19434
|
});
|
|
@@ -18608,29 +19442,72 @@ async function run25(args) {
|
|
|
18608
19442
|
const path = parsed.positionals[0];
|
|
18609
19443
|
if (!path)
|
|
18610
19444
|
return fail("USAGE", "Missing FILE argument", HELP25);
|
|
19445
|
+
const markdown = Boolean(parsed.values.markdown);
|
|
19446
|
+
const from = parsed.values.from;
|
|
19447
|
+
const to = parsed.values.to;
|
|
19448
|
+
const showChanges = Boolean(parsed.values.changes);
|
|
19449
|
+
const showComments = Boolean(parsed.values.comments);
|
|
19450
|
+
if (!markdown && (from || to || showChanges || showComments)) {
|
|
19451
|
+
return fail("USAGE", "--from, --to, --changes, and --comments require --markdown", HELP25);
|
|
19452
|
+
}
|
|
18611
19453
|
const view = await openOrFail(path);
|
|
18612
19454
|
if (typeof view === "number")
|
|
18613
19455
|
return view;
|
|
19456
|
+
if (markdown) {
|
|
19457
|
+
try {
|
|
19458
|
+
const rendered = renderMarkdown(view.doc, {
|
|
19459
|
+
from,
|
|
19460
|
+
to,
|
|
19461
|
+
showChanges,
|
|
19462
|
+
showComments
|
|
19463
|
+
});
|
|
19464
|
+
await writeStdout(rendered);
|
|
19465
|
+
return EXIT.OK;
|
|
19466
|
+
} catch (err) {
|
|
19467
|
+
if (err instanceof MarkdownLocatorError) {
|
|
19468
|
+
return fail("INVALID_LOCATOR", err.message);
|
|
19469
|
+
}
|
|
19470
|
+
throw err;
|
|
19471
|
+
}
|
|
19472
|
+
}
|
|
18614
19473
|
await enrichImageHashes(view);
|
|
18615
19474
|
await respond(view.doc);
|
|
18616
19475
|
return EXIT.OK;
|
|
18617
19476
|
}
|
|
18618
|
-
var HELP25 = `docx read \u2014 print AST as JSON
|
|
19477
|
+
var HELP25 = `docx read \u2014 print AST as JSON, or render document body as Markdown
|
|
18619
19478
|
|
|
18620
19479
|
Usage:
|
|
18621
19480
|
docx read FILE [options]
|
|
18622
19481
|
|
|
18623
19482
|
Options:
|
|
18624
|
-
--
|
|
18625
|
-
|
|
19483
|
+
--markdown Render the body as GitHub-flavored Markdown
|
|
19484
|
+
(instead of JSON). Locators are emitted as
|
|
19485
|
+
<!-- pN --> HTML comments after each paragraph and
|
|
19486
|
+
inside cell content (invisible in rendered view,
|
|
19487
|
+
but parseable from raw markdown).
|
|
19488
|
+
--from LOC Start markdown rendering at top-level block LOC (inclusive)
|
|
19489
|
+
--to LOC End markdown rendering at top-level block LOC (inclusive)
|
|
19490
|
+
Accepts pN, tN, tN:rRcC[:pK[:S-E]], pN:S-E, pN:S-pM:E.
|
|
19491
|
+
Cell/span/range locators collapse to their enclosing
|
|
19492
|
+
top-level block (the table or paragraph).
|
|
19493
|
+
--changes With --markdown, render tracked insertions/deletions as
|
|
19494
|
+
<ins>/<del> instead of producing the accepted view.
|
|
19495
|
+
--comments With --markdown, append [^cN] after each commented span
|
|
19496
|
+
and emit a footnote definition for each comment at the
|
|
19497
|
+
end of the output (author, date, body).
|
|
19498
|
+
-h, --help Show this help
|
|
18626
19499
|
|
|
18627
19500
|
Examples:
|
|
18628
19501
|
docx read input.docx
|
|
19502
|
+
docx read input.docx --markdown
|
|
19503
|
+
docx read input.docx --markdown --from p3 --to p20
|
|
19504
|
+
docx read input.docx --markdown --changes --comments
|
|
18629
19505
|
docx read input.docx | jq '.blocks[] | select(.type == "paragraph")'
|
|
18630
19506
|
`;
|
|
18631
19507
|
var init_read2 = __esm(() => {
|
|
18632
19508
|
init_core();
|
|
18633
19509
|
init_respond();
|
|
19510
|
+
init_markdown();
|
|
18634
19511
|
});
|
|
18635
19512
|
|
|
18636
19513
|
// src/cli/replace/replace-span.tsx
|
|
@@ -19014,6 +19891,7 @@ async function run26(args) {
|
|
|
19014
19891
|
"ignore-case": { type: "boolean" },
|
|
19015
19892
|
all: { type: "boolean" },
|
|
19016
19893
|
limit: { type: "string" },
|
|
19894
|
+
author: { type: "string" },
|
|
19017
19895
|
output: { type: "string", short: "o" },
|
|
19018
19896
|
"dry-run": { type: "boolean" },
|
|
19019
19897
|
help: { type: "boolean", short: "h" }
|
|
@@ -19112,8 +19990,9 @@ async function run26(args) {
|
|
|
19112
19990
|
}
|
|
19113
19991
|
return rightMatch.start - leftMatch.start;
|
|
19114
19992
|
});
|
|
19993
|
+
const authorFlag = parsed.values.author;
|
|
19115
19994
|
const tracked = isTrackChangesEnabled(view) ? {
|
|
19116
|
-
meta: { author: resolveAuthor(), date: resolveDate() },
|
|
19995
|
+
meta: { author: resolveAuthor(authorFlag), date: resolveDate() },
|
|
19117
19996
|
allocator: createRevisionAllocator(view)
|
|
19118
19997
|
} : undefined;
|
|
19119
19998
|
const regexFlags = ignoreCase ? "i" : "";
|
|
@@ -19147,6 +20026,7 @@ Options:
|
|
|
19147
20026
|
--ignore-case case-insensitive match
|
|
19148
20027
|
--all replace every match (default: just the first)
|
|
19149
20028
|
--limit N replace at most N matches (in document order)
|
|
20029
|
+
--author NAME author for tracked changes (default: $DOCX_AUTHOR)
|
|
19150
20030
|
-o, --output PATH write to PATH instead of overwriting FILE
|
|
19151
20031
|
--dry-run report what would change without writing the file
|
|
19152
20032
|
-h, --help show this help
|
|
@@ -19293,19 +20173,11 @@ function countWords(text) {
|
|
|
19293
20173
|
const matches = text.match(/\S+/g);
|
|
19294
20174
|
return matches?.length ?? 0;
|
|
19295
20175
|
}
|
|
19296
|
-
function paragraphText2(paragraph) {
|
|
19297
|
-
let out = "";
|
|
19298
|
-
for (const run28 of paragraph.runs) {
|
|
19299
|
-
if (run28.type === "text")
|
|
19300
|
-
out += run28.text;
|
|
19301
|
-
}
|
|
19302
|
-
return out;
|
|
19303
|
-
}
|
|
19304
20176
|
function countWordsInBlocks(blocks) {
|
|
19305
20177
|
let total = 0;
|
|
19306
20178
|
for (const block of blocks) {
|
|
19307
20179
|
if (block.type === "paragraph") {
|
|
19308
|
-
total += countWords(
|
|
20180
|
+
total += countWords(paragraphText(block));
|
|
19309
20181
|
} else if (block.type === "table") {
|
|
19310
20182
|
for (const row of block.rows) {
|
|
19311
20183
|
for (const cell of row.cells) {
|
|
@@ -19316,24 +20188,8 @@ function countWordsInBlocks(blocks) {
|
|
|
19316
20188
|
}
|
|
19317
20189
|
return total;
|
|
19318
20190
|
}
|
|
19319
|
-
function findBlockById(blocks, blockId) {
|
|
19320
|
-
for (const block of blocks) {
|
|
19321
|
-
if (block.id === blockId)
|
|
19322
|
-
return block;
|
|
19323
|
-
if (block.type === "table") {
|
|
19324
|
-
for (const row of block.rows) {
|
|
19325
|
-
for (const cell of row.cells) {
|
|
19326
|
-
const inner = findBlockById(cell.blocks, blockId);
|
|
19327
|
-
if (inner)
|
|
19328
|
-
return inner;
|
|
19329
|
-
}
|
|
19330
|
-
}
|
|
19331
|
-
}
|
|
19332
|
-
}
|
|
19333
|
-
return null;
|
|
19334
|
-
}
|
|
19335
20191
|
function countWordsInParagraphSpan(paragraph, start, end) {
|
|
19336
|
-
const text =
|
|
20192
|
+
const text = paragraphText(paragraph);
|
|
19337
20193
|
const clampedEnd = Math.min(Math.max(end, 0), text.length);
|
|
19338
20194
|
const clampedStart = Math.min(Math.max(start, 0), clampedEnd);
|
|
19339
20195
|
return countWords(text.slice(clampedStart, clampedEnd));
|
|
@@ -19354,35 +20210,21 @@ function countWordsInRange(paragraphsInOrder, startBlockId, startOffset, endBloc
|
|
|
19354
20210
|
let total = 0;
|
|
19355
20211
|
const first = paragraphsInOrder[startIndex];
|
|
19356
20212
|
if (first) {
|
|
19357
|
-
total += countWordsInParagraphSpan(first, startOffset,
|
|
20213
|
+
total += countWordsInParagraphSpan(first, startOffset, paragraphText(first).length);
|
|
19358
20214
|
}
|
|
19359
20215
|
for (let index = startIndex + 1;index < endIndex; index++) {
|
|
19360
20216
|
const middle = paragraphsInOrder[index];
|
|
19361
20217
|
if (middle)
|
|
19362
|
-
total += countWords(
|
|
20218
|
+
total += countWords(paragraphText(middle));
|
|
19363
20219
|
}
|
|
19364
20220
|
const last = paragraphsInOrder[endIndex];
|
|
19365
20221
|
if (last)
|
|
19366
20222
|
total += countWordsInParagraphSpan(last, 0, endOffset);
|
|
19367
20223
|
return total;
|
|
19368
20224
|
}
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
if (block.type === "paragraph") {
|
|
19373
|
-
out.push(block);
|
|
19374
|
-
continue;
|
|
19375
|
-
}
|
|
19376
|
-
if (block.type === "table") {
|
|
19377
|
-
for (const row of block.rows) {
|
|
19378
|
-
for (const cell of row.cells) {
|
|
19379
|
-
out.push(...flattenParagraphs(cell.blocks));
|
|
19380
|
-
}
|
|
19381
|
-
}
|
|
19382
|
-
}
|
|
19383
|
-
}
|
|
19384
|
-
return out;
|
|
19385
|
-
}
|
|
20225
|
+
var init_count = __esm(() => {
|
|
20226
|
+
init_core();
|
|
20227
|
+
});
|
|
19386
20228
|
|
|
19387
20229
|
// src/cli/wc/index.ts
|
|
19388
20230
|
var exports_wc = {};
|
|
@@ -19443,7 +20285,7 @@ async function run28(args) {
|
|
|
19443
20285
|
if (!block) {
|
|
19444
20286
|
return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
|
|
19445
20287
|
}
|
|
19446
|
-
const words = block.type === "paragraph" ? countWords(
|
|
20288
|
+
const words = block.type === "paragraph" ? countWords(paragraphText(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
|
|
19447
20289
|
await respond({
|
|
19448
20290
|
ok: true,
|
|
19449
20291
|
operation: "wc",
|
|
@@ -19515,7 +20357,7 @@ async function run28(args) {
|
|
|
19515
20357
|
if (!block || block.type !== "paragraph") {
|
|
19516
20358
|
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${composedId}`);
|
|
19517
20359
|
}
|
|
19518
|
-
const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(
|
|
20360
|
+
const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText(block));
|
|
19519
20361
|
await respond({
|
|
19520
20362
|
ok: true,
|
|
19521
20363
|
operation: "wc",
|
|
@@ -19573,11 +20415,12 @@ Examples:
|
|
|
19573
20415
|
var init_wc = __esm(() => {
|
|
19574
20416
|
init_core();
|
|
19575
20417
|
init_respond();
|
|
20418
|
+
init_count();
|
|
19576
20419
|
});
|
|
19577
20420
|
// package.json
|
|
19578
20421
|
var package_default = {
|
|
19579
20422
|
name: "bun-docx",
|
|
19580
|
-
version: "0.
|
|
20423
|
+
version: "0.6.1",
|
|
19581
20424
|
description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
|
|
19582
20425
|
keywords: [
|
|
19583
20426
|
"docx",
|
|
@@ -19656,7 +20499,7 @@ Commands:
|
|
|
19656
20499
|
outline FILE List headings as a hierarchical tree
|
|
19657
20500
|
comments \u2026 Add, reply, resolve, delete, list comments
|
|
19658
20501
|
images \u2026 Extract, replace, list images
|
|
19659
|
-
hyperlinks \u2026
|
|
20502
|
+
hyperlinks \u2026 Add, list, replace, delete hyperlinks
|
|
19660
20503
|
track-changes FILE on|off Toggle tracked-changes mode
|
|
19661
20504
|
info \u2026 Reference material (schema, locators)
|
|
19662
20505
|
|