bun-docx 0.3.0 → 0.4.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/dist/index.js +462 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17712,9 +17712,72 @@ var init_insert = __esm(() => {
|
|
|
17712
17712
|
init_jsx_dev_runtime();
|
|
17713
17713
|
});
|
|
17714
17714
|
|
|
17715
|
-
// src/cli/
|
|
17716
|
-
|
|
17717
|
-
|
|
17715
|
+
// src/cli/outline/build.ts
|
|
17716
|
+
function buildOutline(doc, options = {}) {
|
|
17717
|
+
const stylePrefix = options.stylePrefix ?? "Heading";
|
|
17718
|
+
const root = {
|
|
17719
|
+
id: "",
|
|
17720
|
+
locator: "",
|
|
17721
|
+
level: 0,
|
|
17722
|
+
style: "",
|
|
17723
|
+
text: "",
|
|
17724
|
+
children: []
|
|
17725
|
+
};
|
|
17726
|
+
const stack = [root];
|
|
17727
|
+
for (const paragraph of headingParagraphs(doc.blocks, stylePrefix)) {
|
|
17728
|
+
const level = headingLevel(paragraph.style, stylePrefix);
|
|
17729
|
+
if (level === null)
|
|
17730
|
+
continue;
|
|
17731
|
+
while ((stack[stack.length - 1]?.level ?? 0) >= level)
|
|
17732
|
+
stack.pop();
|
|
17733
|
+
const parent = stack[stack.length - 1] ?? root;
|
|
17734
|
+
const entry = {
|
|
17735
|
+
id: paragraph.id,
|
|
17736
|
+
locator: paragraph.id,
|
|
17737
|
+
level,
|
|
17738
|
+
style: paragraph.style ?? "",
|
|
17739
|
+
text: paragraphText(paragraph),
|
|
17740
|
+
children: []
|
|
17741
|
+
};
|
|
17742
|
+
parent.children.push(entry);
|
|
17743
|
+
stack.push(entry);
|
|
17744
|
+
}
|
|
17745
|
+
return root.children;
|
|
17746
|
+
}
|
|
17747
|
+
function headingLevel(style, stylePrefix) {
|
|
17748
|
+
if (!style)
|
|
17749
|
+
return null;
|
|
17750
|
+
if (!style.startsWith(stylePrefix))
|
|
17751
|
+
return null;
|
|
17752
|
+
const remainder = style.slice(stylePrefix.length).trim();
|
|
17753
|
+
if (remainder === "")
|
|
17754
|
+
return 1;
|
|
17755
|
+
const parsed = Number(remainder);
|
|
17756
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
17757
|
+
return null;
|
|
17758
|
+
return parsed;
|
|
17759
|
+
}
|
|
17760
|
+
function* headingParagraphs(blocks, stylePrefix) {
|
|
17761
|
+
for (const block of blocks) {
|
|
17762
|
+
if (block.type !== "paragraph")
|
|
17763
|
+
continue;
|
|
17764
|
+
if (headingLevel(block.style, stylePrefix) === null)
|
|
17765
|
+
continue;
|
|
17766
|
+
yield block;
|
|
17767
|
+
}
|
|
17768
|
+
}
|
|
17769
|
+
function paragraphText(paragraph) {
|
|
17770
|
+
let out = "";
|
|
17771
|
+
for (const run19 of paragraph.runs) {
|
|
17772
|
+
if (run19.type === "text")
|
|
17773
|
+
out += run19.text;
|
|
17774
|
+
}
|
|
17775
|
+
return out;
|
|
17776
|
+
}
|
|
17777
|
+
|
|
17778
|
+
// src/cli/outline/index.ts
|
|
17779
|
+
var exports_outline = {};
|
|
17780
|
+
__export(exports_outline, {
|
|
17718
17781
|
run: () => run19
|
|
17719
17782
|
});
|
|
17720
17783
|
import { parseArgs as parseArgs16 } from "util";
|
|
@@ -17725,12 +17788,13 @@ async function run19(args) {
|
|
|
17725
17788
|
args,
|
|
17726
17789
|
allowPositionals: true,
|
|
17727
17790
|
options: {
|
|
17728
|
-
|
|
17791
|
+
"style-prefix": { type: "string" },
|
|
17729
17792
|
help: { type: "boolean", short: "h" }
|
|
17730
17793
|
}
|
|
17731
17794
|
});
|
|
17732
|
-
} catch (
|
|
17733
|
-
|
|
17795
|
+
} catch (parseError) {
|
|
17796
|
+
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
17797
|
+
return fail("USAGE", message, HELP19);
|
|
17734
17798
|
}
|
|
17735
17799
|
if (parsed.values.help) {
|
|
17736
17800
|
await writeStdout(HELP19);
|
|
@@ -17739,6 +17803,77 @@ async function run19(args) {
|
|
|
17739
17803
|
const path = parsed.positionals[0];
|
|
17740
17804
|
if (!path)
|
|
17741
17805
|
return fail("USAGE", "Missing FILE argument", HELP19);
|
|
17806
|
+
const stylePrefix = parsed.values["style-prefix"] ?? "Heading";
|
|
17807
|
+
if (stylePrefix.length === 0) {
|
|
17808
|
+
return fail("USAGE", "--style-prefix cannot be empty");
|
|
17809
|
+
}
|
|
17810
|
+
const view = await openOrFail(path);
|
|
17811
|
+
if (typeof view === "number")
|
|
17812
|
+
return view;
|
|
17813
|
+
const outline = buildOutline(view.doc, { stylePrefix });
|
|
17814
|
+
await respond({
|
|
17815
|
+
ok: true,
|
|
17816
|
+
operation: "outline",
|
|
17817
|
+
path,
|
|
17818
|
+
stylePrefix,
|
|
17819
|
+
outline
|
|
17820
|
+
});
|
|
17821
|
+
return EXIT.OK;
|
|
17822
|
+
}
|
|
17823
|
+
var HELP19 = `docx outline \u2014 list headings as a hierarchical tree
|
|
17824
|
+
|
|
17825
|
+
Usage:
|
|
17826
|
+
docx outline FILE [options]
|
|
17827
|
+
|
|
17828
|
+
Options:
|
|
17829
|
+
--style-prefix S paragraph-style prefix that marks a heading (default: "Heading")
|
|
17830
|
+
-h, --help show this help
|
|
17831
|
+
|
|
17832
|
+
Walks top-level paragraphs whose style starts with the prefix and parses the
|
|
17833
|
+
trailing number as the heading level (e.g. "Heading1" \u2192 1, "Heading 2" \u2192 2).
|
|
17834
|
+
Paragraphs nested inside table cells are skipped \u2014 outlines reflect the
|
|
17835
|
+
document's structural skeleton, not embedded labels. Lower levels nest under
|
|
17836
|
+
higher ones; missing intermediate levels nest directly (H1 \u2192 H3 is fine).
|
|
17837
|
+
|
|
17838
|
+
Output is JSON: an array of entries, each shaped like
|
|
17839
|
+
{ id, locator, level, style, text, children }.
|
|
17840
|
+
|
|
17841
|
+
Examples:
|
|
17842
|
+
docx outline doc.docx
|
|
17843
|
+
docx outline doc.docx --style-prefix "Section"
|
|
17844
|
+
docx outline doc.docx | jq '.outline[].text'
|
|
17845
|
+
`;
|
|
17846
|
+
var init_outline = __esm(() => {
|
|
17847
|
+
init_respond();
|
|
17848
|
+
});
|
|
17849
|
+
|
|
17850
|
+
// src/cli/read/index.ts
|
|
17851
|
+
var exports_read = {};
|
|
17852
|
+
__export(exports_read, {
|
|
17853
|
+
run: () => run20
|
|
17854
|
+
});
|
|
17855
|
+
import { parseArgs as parseArgs17 } from "util";
|
|
17856
|
+
async function run20(args) {
|
|
17857
|
+
let parsed;
|
|
17858
|
+
try {
|
|
17859
|
+
parsed = parseArgs17({
|
|
17860
|
+
args,
|
|
17861
|
+
allowPositionals: true,
|
|
17862
|
+
options: {
|
|
17863
|
+
text: { type: "boolean" },
|
|
17864
|
+
help: { type: "boolean", short: "h" }
|
|
17865
|
+
}
|
|
17866
|
+
});
|
|
17867
|
+
} catch (e) {
|
|
17868
|
+
return fail("USAGE", e instanceof Error ? e.message : String(e), HELP20);
|
|
17869
|
+
}
|
|
17870
|
+
if (parsed.values.help) {
|
|
17871
|
+
await writeStdout(HELP20);
|
|
17872
|
+
return EXIT.OK;
|
|
17873
|
+
}
|
|
17874
|
+
const path = parsed.positionals[0];
|
|
17875
|
+
if (!path)
|
|
17876
|
+
return fail("USAGE", "Missing FILE argument", HELP20);
|
|
17742
17877
|
const view = await openOrFail(path);
|
|
17743
17878
|
if (typeof view === "number")
|
|
17744
17879
|
return view;
|
|
@@ -17746,7 +17881,7 @@ async function run19(args) {
|
|
|
17746
17881
|
await respond(view.doc);
|
|
17747
17882
|
return EXIT.OK;
|
|
17748
17883
|
}
|
|
17749
|
-
var
|
|
17884
|
+
var HELP20 = `docx read \u2014 print AST as JSON
|
|
17750
17885
|
|
|
17751
17886
|
Usage:
|
|
17752
17887
|
docx read FILE [options]
|
|
@@ -17829,14 +17964,14 @@ function rebuildContainer(container, baseOffset, span, replacement, runPropertie
|
|
|
17829
17964
|
if (placed)
|
|
17830
17965
|
return;
|
|
17831
17966
|
placed = true;
|
|
17832
|
-
const
|
|
17967
|
+
const run21 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
|
|
17833
17968
|
runProperties,
|
|
17834
17969
|
text: replacement
|
|
17835
17970
|
}, undefined, false, undefined, this);
|
|
17836
17971
|
newChildren.push(tracked && isParagraph ? /* @__PURE__ */ jsxDEV(Ins, {
|
|
17837
17972
|
meta: mintMeta(tracked),
|
|
17838
|
-
children:
|
|
17839
|
-
}, undefined, false, undefined, this) :
|
|
17973
|
+
children: run21
|
|
17974
|
+
}, undefined, false, undefined, this) : run21);
|
|
17840
17975
|
};
|
|
17841
17976
|
for (const child of container.children) {
|
|
17842
17977
|
if (child.tag === "w:r") {
|
|
@@ -17896,14 +18031,14 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
|
|
|
17896
18031
|
if (placed)
|
|
17897
18032
|
return;
|
|
17898
18033
|
placed = true;
|
|
17899
|
-
const
|
|
18034
|
+
const run21 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
|
|
17900
18035
|
runProperties,
|
|
17901
18036
|
text: replacement
|
|
17902
18037
|
}, undefined, false, undefined, this);
|
|
17903
18038
|
newChildren.push(tracked ? /* @__PURE__ */ jsxDEV(Ins, {
|
|
17904
18039
|
meta: mintMeta(tracked),
|
|
17905
|
-
children:
|
|
17906
|
-
}, undefined, false, undefined, this) :
|
|
18040
|
+
children: run21
|
|
18041
|
+
}, undefined, false, undefined, this) : run21);
|
|
17907
18042
|
};
|
|
17908
18043
|
for (const child of paragraph.children) {
|
|
17909
18044
|
if (child.tag === "w:r") {
|
|
@@ -18027,8 +18162,8 @@ function sumInnerRunLengths(wrapper) {
|
|
|
18027
18162
|
function mintMeta(tracked) {
|
|
18028
18163
|
return { ...tracked.meta, revisionId: tracked.allocator.next() };
|
|
18029
18164
|
}
|
|
18030
|
-
function convertRunTextToDelText(
|
|
18031
|
-
for (const child of
|
|
18165
|
+
function convertRunTextToDelText(run21) {
|
|
18166
|
+
for (const child of run21.children) {
|
|
18032
18167
|
if (child.tag === "w:t")
|
|
18033
18168
|
child.tag = "w:delText";
|
|
18034
18169
|
}
|
|
@@ -18057,13 +18192,13 @@ var init_replace_span = __esm(() => {
|
|
|
18057
18192
|
// src/cli/replace/index.ts
|
|
18058
18193
|
var exports_replace2 = {};
|
|
18059
18194
|
__export(exports_replace2, {
|
|
18060
|
-
run: () =>
|
|
18195
|
+
run: () => run21
|
|
18061
18196
|
});
|
|
18062
|
-
import { parseArgs as
|
|
18063
|
-
async function
|
|
18197
|
+
import { parseArgs as parseArgs18 } from "util";
|
|
18198
|
+
async function run21(args) {
|
|
18064
18199
|
let parsed;
|
|
18065
18200
|
try {
|
|
18066
|
-
parsed =
|
|
18201
|
+
parsed = parseArgs18({
|
|
18067
18202
|
args,
|
|
18068
18203
|
allowPositionals: true,
|
|
18069
18204
|
options: {
|
|
@@ -18078,21 +18213,21 @@ async function run20(args) {
|
|
|
18078
18213
|
});
|
|
18079
18214
|
} catch (parseError) {
|
|
18080
18215
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
18081
|
-
return fail("USAGE", message,
|
|
18216
|
+
return fail("USAGE", message, HELP21);
|
|
18082
18217
|
}
|
|
18083
18218
|
if (parsed.values.help) {
|
|
18084
|
-
await writeStdout(
|
|
18219
|
+
await writeStdout(HELP21);
|
|
18085
18220
|
return EXIT.OK;
|
|
18086
18221
|
}
|
|
18087
18222
|
const path = parsed.positionals[0];
|
|
18088
18223
|
const pattern = parsed.positionals[1];
|
|
18089
18224
|
const replacement = parsed.positionals[2];
|
|
18090
18225
|
if (!path)
|
|
18091
|
-
return fail("USAGE", "Missing FILE argument",
|
|
18226
|
+
return fail("USAGE", "Missing FILE argument", HELP21);
|
|
18092
18227
|
if (pattern == null)
|
|
18093
|
-
return fail("USAGE", "Missing PATTERN argument",
|
|
18228
|
+
return fail("USAGE", "Missing PATTERN argument", HELP21);
|
|
18094
18229
|
if (replacement == null) {
|
|
18095
|
-
return fail("USAGE", "Missing REPLACEMENT argument",
|
|
18230
|
+
return fail("USAGE", "Missing REPLACEMENT argument", HELP21);
|
|
18096
18231
|
}
|
|
18097
18232
|
const ignoreCase = Boolean(parsed.values["ignore-case"]);
|
|
18098
18233
|
const useRegex = Boolean(parsed.values.regex);
|
|
@@ -18194,7 +18329,7 @@ async function run20(args) {
|
|
|
18194
18329
|
});
|
|
18195
18330
|
return EXIT.OK;
|
|
18196
18331
|
}
|
|
18197
|
-
var
|
|
18332
|
+
var HELP21 = `docx replace \u2014 substitute text spans (sed for docx)
|
|
18198
18333
|
|
|
18199
18334
|
Usage:
|
|
18200
18335
|
docx replace FILE PATTERN REPLACEMENT [options]
|
|
@@ -18237,13 +18372,13 @@ var init_replace2 = __esm(() => {
|
|
|
18237
18372
|
// src/cli/track-changes/index.tsx
|
|
18238
18373
|
var exports_track_changes = {};
|
|
18239
18374
|
__export(exports_track_changes, {
|
|
18240
|
-
run: () =>
|
|
18375
|
+
run: () => run22
|
|
18241
18376
|
});
|
|
18242
|
-
import { parseArgs as
|
|
18243
|
-
async function
|
|
18377
|
+
import { parseArgs as parseArgs19 } from "util";
|
|
18378
|
+
async function run22(args) {
|
|
18244
18379
|
let parsed;
|
|
18245
18380
|
try {
|
|
18246
|
-
parsed =
|
|
18381
|
+
parsed = parseArgs19({
|
|
18247
18382
|
args,
|
|
18248
18383
|
allowPositionals: true,
|
|
18249
18384
|
options: {
|
|
@@ -18254,18 +18389,18 @@ async function run21(args) {
|
|
|
18254
18389
|
});
|
|
18255
18390
|
} catch (parseError) {
|
|
18256
18391
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
18257
|
-
return fail("USAGE", message,
|
|
18392
|
+
return fail("USAGE", message, HELP22);
|
|
18258
18393
|
}
|
|
18259
18394
|
if (parsed.values.help) {
|
|
18260
|
-
await writeStdout(
|
|
18395
|
+
await writeStdout(HELP22);
|
|
18261
18396
|
return EXIT.OK;
|
|
18262
18397
|
}
|
|
18263
18398
|
const path = parsed.positionals[0];
|
|
18264
18399
|
if (!path)
|
|
18265
|
-
return fail("USAGE", "Missing FILE argument",
|
|
18400
|
+
return fail("USAGE", "Missing FILE argument", HELP22);
|
|
18266
18401
|
const mode = parsed.positionals[1];
|
|
18267
18402
|
if (mode !== "on" && mode !== "off") {
|
|
18268
|
-
return fail("USAGE", `Expected "on" or "off", got: ${mode ?? "<nothing>"}`,
|
|
18403
|
+
return fail("USAGE", `Expected "on" or "off", got: ${mode ?? "<nothing>"}`, HELP22);
|
|
18269
18404
|
}
|
|
18270
18405
|
const view = await openOrFail(path);
|
|
18271
18406
|
if (typeof view === "number")
|
|
@@ -18317,7 +18452,7 @@ async function run21(args) {
|
|
|
18317
18452
|
});
|
|
18318
18453
|
return EXIT.OK;
|
|
18319
18454
|
}
|
|
18320
|
-
var
|
|
18455
|
+
var HELP22 = `docx track-changes \u2014 toggle the document's tracked-changes mode
|
|
18321
18456
|
|
|
18322
18457
|
Usage:
|
|
18323
18458
|
docx track-changes FILE on|off [options]
|
|
@@ -18344,10 +18479,297 @@ var init_track_changes2 = __esm(() => {
|
|
|
18344
18479
|
init_respond();
|
|
18345
18480
|
init_jsx_dev_runtime();
|
|
18346
18481
|
});
|
|
18482
|
+
|
|
18483
|
+
// src/cli/wc/count.ts
|
|
18484
|
+
function countWords(text) {
|
|
18485
|
+
const matches = text.match(/\S+/g);
|
|
18486
|
+
return matches?.length ?? 0;
|
|
18487
|
+
}
|
|
18488
|
+
function paragraphText2(paragraph) {
|
|
18489
|
+
let out = "";
|
|
18490
|
+
for (const run23 of paragraph.runs) {
|
|
18491
|
+
if (run23.type === "text")
|
|
18492
|
+
out += run23.text;
|
|
18493
|
+
}
|
|
18494
|
+
return out;
|
|
18495
|
+
}
|
|
18496
|
+
function countWordsInBlocks(blocks) {
|
|
18497
|
+
let total = 0;
|
|
18498
|
+
for (const block of blocks) {
|
|
18499
|
+
if (block.type === "paragraph") {
|
|
18500
|
+
total += countWords(paragraphText2(block));
|
|
18501
|
+
} else if (block.type === "table") {
|
|
18502
|
+
for (const row of block.rows) {
|
|
18503
|
+
for (const cell of row.cells) {
|
|
18504
|
+
total += countWordsInBlocks(cell.blocks);
|
|
18505
|
+
}
|
|
18506
|
+
}
|
|
18507
|
+
}
|
|
18508
|
+
}
|
|
18509
|
+
return total;
|
|
18510
|
+
}
|
|
18511
|
+
function findBlockById(blocks, blockId) {
|
|
18512
|
+
for (const block of blocks) {
|
|
18513
|
+
if (block.id === blockId)
|
|
18514
|
+
return block;
|
|
18515
|
+
if (block.type === "table") {
|
|
18516
|
+
for (const row of block.rows) {
|
|
18517
|
+
for (const cell of row.cells) {
|
|
18518
|
+
const inner = findBlockById(cell.blocks, blockId);
|
|
18519
|
+
if (inner)
|
|
18520
|
+
return inner;
|
|
18521
|
+
}
|
|
18522
|
+
}
|
|
18523
|
+
}
|
|
18524
|
+
}
|
|
18525
|
+
return null;
|
|
18526
|
+
}
|
|
18527
|
+
function countWordsInParagraphSpan(paragraph, start, end) {
|
|
18528
|
+
const text = paragraphText2(paragraph);
|
|
18529
|
+
const clampedEnd = Math.min(Math.max(end, 0), text.length);
|
|
18530
|
+
const clampedStart = Math.min(Math.max(start, 0), clampedEnd);
|
|
18531
|
+
return countWords(text.slice(clampedStart, clampedEnd));
|
|
18532
|
+
}
|
|
18533
|
+
function countWordsInRange(paragraphsInOrder, startBlockId, startOffset, endBlockId, endOffset) {
|
|
18534
|
+
const startIndex = paragraphsInOrder.findIndex((p) => p.id === startBlockId);
|
|
18535
|
+
const endIndex = paragraphsInOrder.findIndex((p) => p.id === endBlockId);
|
|
18536
|
+
if (startIndex === -1 || endIndex === -1)
|
|
18537
|
+
return 0;
|
|
18538
|
+
if (endIndex < startIndex)
|
|
18539
|
+
return 0;
|
|
18540
|
+
if (startIndex === endIndex) {
|
|
18541
|
+
const paragraph = paragraphsInOrder[startIndex];
|
|
18542
|
+
if (!paragraph)
|
|
18543
|
+
return 0;
|
|
18544
|
+
return countWordsInParagraphSpan(paragraph, startOffset, endOffset);
|
|
18545
|
+
}
|
|
18546
|
+
let total = 0;
|
|
18547
|
+
const first = paragraphsInOrder[startIndex];
|
|
18548
|
+
if (first) {
|
|
18549
|
+
total += countWordsInParagraphSpan(first, startOffset, paragraphText2(first).length);
|
|
18550
|
+
}
|
|
18551
|
+
for (let index = startIndex + 1;index < endIndex; index++) {
|
|
18552
|
+
const middle = paragraphsInOrder[index];
|
|
18553
|
+
if (middle)
|
|
18554
|
+
total += countWords(paragraphText2(middle));
|
|
18555
|
+
}
|
|
18556
|
+
const last = paragraphsInOrder[endIndex];
|
|
18557
|
+
if (last)
|
|
18558
|
+
total += countWordsInParagraphSpan(last, 0, endOffset);
|
|
18559
|
+
return total;
|
|
18560
|
+
}
|
|
18561
|
+
function flattenParagraphs(blocks) {
|
|
18562
|
+
const out = [];
|
|
18563
|
+
for (const block of blocks) {
|
|
18564
|
+
if (block.type === "paragraph") {
|
|
18565
|
+
out.push(block);
|
|
18566
|
+
continue;
|
|
18567
|
+
}
|
|
18568
|
+
if (block.type === "table") {
|
|
18569
|
+
for (const row of block.rows) {
|
|
18570
|
+
for (const cell of row.cells) {
|
|
18571
|
+
out.push(...flattenParagraphs(cell.blocks));
|
|
18572
|
+
}
|
|
18573
|
+
}
|
|
18574
|
+
}
|
|
18575
|
+
}
|
|
18576
|
+
return out;
|
|
18577
|
+
}
|
|
18578
|
+
|
|
18579
|
+
// src/cli/wc/index.ts
|
|
18580
|
+
var exports_wc = {};
|
|
18581
|
+
__export(exports_wc, {
|
|
18582
|
+
run: () => run23
|
|
18583
|
+
});
|
|
18584
|
+
import { parseArgs as parseArgs20 } from "util";
|
|
18585
|
+
async function run23(args) {
|
|
18586
|
+
let parsed;
|
|
18587
|
+
try {
|
|
18588
|
+
parsed = parseArgs20({
|
|
18589
|
+
args,
|
|
18590
|
+
allowPositionals: true,
|
|
18591
|
+
options: {
|
|
18592
|
+
help: { type: "boolean", short: "h" }
|
|
18593
|
+
}
|
|
18594
|
+
});
|
|
18595
|
+
} catch (parseError) {
|
|
18596
|
+
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
18597
|
+
return fail("USAGE", message, HELP23);
|
|
18598
|
+
}
|
|
18599
|
+
if (parsed.values.help) {
|
|
18600
|
+
await writeStdout(HELP23);
|
|
18601
|
+
return EXIT.OK;
|
|
18602
|
+
}
|
|
18603
|
+
const path = parsed.positionals[0];
|
|
18604
|
+
const locatorInput = parsed.positionals[1];
|
|
18605
|
+
if (!path)
|
|
18606
|
+
return fail("USAGE", "Missing FILE argument", HELP23);
|
|
18607
|
+
const view = await openOrFail(path);
|
|
18608
|
+
if (typeof view === "number")
|
|
18609
|
+
return view;
|
|
18610
|
+
if (!locatorInput) {
|
|
18611
|
+
await respond({
|
|
18612
|
+
ok: true,
|
|
18613
|
+
operation: "wc",
|
|
18614
|
+
path,
|
|
18615
|
+
scope: "document",
|
|
18616
|
+
words: countWordsInBlocks(view.doc.blocks)
|
|
18617
|
+
});
|
|
18618
|
+
return EXIT.OK;
|
|
18619
|
+
}
|
|
18620
|
+
let locator;
|
|
18621
|
+
try {
|
|
18622
|
+
locator = parseLocator(locatorInput);
|
|
18623
|
+
} catch (error) {
|
|
18624
|
+
if (error instanceof LocatorParseError) {
|
|
18625
|
+
return fail("INVALID_LOCATOR", error.message);
|
|
18626
|
+
}
|
|
18627
|
+
throw error;
|
|
18628
|
+
}
|
|
18629
|
+
if (locator.kind === "comment" || locator.kind === "image") {
|
|
18630
|
+
return fail("USAGE", `Locator ${locatorInput} addresses a ${locator.kind}, not text`, "wc accepts paragraph, span, range, table, and cell locators.");
|
|
18631
|
+
}
|
|
18632
|
+
const blocks = view.doc.blocks;
|
|
18633
|
+
if (locator.kind === "block") {
|
|
18634
|
+
const block = findBlockById(blocks, locator.blockId);
|
|
18635
|
+
if (!block) {
|
|
18636
|
+
return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
|
|
18637
|
+
}
|
|
18638
|
+
const words = block.type === "paragraph" ? countWords(paragraphText2(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
|
|
18639
|
+
await respond({
|
|
18640
|
+
ok: true,
|
|
18641
|
+
operation: "wc",
|
|
18642
|
+
path,
|
|
18643
|
+
locator: locatorInput,
|
|
18644
|
+
scope: block.type,
|
|
18645
|
+
words
|
|
18646
|
+
});
|
|
18647
|
+
return EXIT.OK;
|
|
18648
|
+
}
|
|
18649
|
+
if (locator.kind === "blockSpan") {
|
|
18650
|
+
const block = findBlockById(blocks, locator.blockId);
|
|
18651
|
+
if (!block || block.type !== "paragraph") {
|
|
18652
|
+
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.blockId}`);
|
|
18653
|
+
}
|
|
18654
|
+
await respond({
|
|
18655
|
+
ok: true,
|
|
18656
|
+
operation: "wc",
|
|
18657
|
+
path,
|
|
18658
|
+
locator: locatorInput,
|
|
18659
|
+
scope: "paragraphSpan",
|
|
18660
|
+
words: countWordsInParagraphSpan(block, locator.start, locator.end)
|
|
18661
|
+
});
|
|
18662
|
+
return EXIT.OK;
|
|
18663
|
+
}
|
|
18664
|
+
if (locator.kind === "range") {
|
|
18665
|
+
const paragraphs = flattenParagraphs(blocks);
|
|
18666
|
+
const startExists = paragraphs.some((p) => p.id === locator.start.blockId);
|
|
18667
|
+
const endExists = paragraphs.some((p) => p.id === locator.end.blockId);
|
|
18668
|
+
if (!startExists) {
|
|
18669
|
+
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.start.blockId}`);
|
|
18670
|
+
}
|
|
18671
|
+
if (!endExists) {
|
|
18672
|
+
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.end.blockId}`);
|
|
18673
|
+
}
|
|
18674
|
+
await respond({
|
|
18675
|
+
ok: true,
|
|
18676
|
+
operation: "wc",
|
|
18677
|
+
path,
|
|
18678
|
+
locator: locatorInput,
|
|
18679
|
+
scope: "range",
|
|
18680
|
+
words: countWordsInRange(paragraphs, locator.start.blockId, locator.start.offset, locator.end.blockId, locator.end.offset)
|
|
18681
|
+
});
|
|
18682
|
+
return EXIT.OK;
|
|
18683
|
+
}
|
|
18684
|
+
if (locator.kind === "cell") {
|
|
18685
|
+
const cellPath = `${locator.tableId}:r${locator.row}c${locator.col}`;
|
|
18686
|
+
if (!locator.inner) {
|
|
18687
|
+
const cellBlocks = findCellBlocks(blocks, locator);
|
|
18688
|
+
if (!cellBlocks) {
|
|
18689
|
+
return fail("BLOCK_NOT_FOUND", `Cell not found: ${cellPath}`);
|
|
18690
|
+
}
|
|
18691
|
+
await respond({
|
|
18692
|
+
ok: true,
|
|
18693
|
+
operation: "wc",
|
|
18694
|
+
path,
|
|
18695
|
+
locator: locatorInput,
|
|
18696
|
+
scope: "cell",
|
|
18697
|
+
words: countWordsInBlocks(cellBlocks)
|
|
18698
|
+
});
|
|
18699
|
+
return EXIT.OK;
|
|
18700
|
+
}
|
|
18701
|
+
const innerBlockId = locator.inner.kind === "block" ? locator.inner.blockId : locator.inner.kind === "blockSpan" ? locator.inner.blockId : null;
|
|
18702
|
+
if (!innerBlockId) {
|
|
18703
|
+
return fail("USAGE", `Unsupported inner locator for cell: ${locatorInput}`, "Inside a cell, wc accepts pK or pK:S-E only.");
|
|
18704
|
+
}
|
|
18705
|
+
const composedId = `${cellPath}:${innerBlockId}`;
|
|
18706
|
+
const block = findBlockById(blocks, composedId);
|
|
18707
|
+
if (!block || block.type !== "paragraph") {
|
|
18708
|
+
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${composedId}`);
|
|
18709
|
+
}
|
|
18710
|
+
const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText2(block));
|
|
18711
|
+
await respond({
|
|
18712
|
+
ok: true,
|
|
18713
|
+
operation: "wc",
|
|
18714
|
+
path,
|
|
18715
|
+
locator: locatorInput,
|
|
18716
|
+
scope: locator.inner.kind === "blockSpan" ? "paragraphSpan" : "paragraph",
|
|
18717
|
+
words
|
|
18718
|
+
});
|
|
18719
|
+
return EXIT.OK;
|
|
18720
|
+
}
|
|
18721
|
+
return fail("USAGE", `Unsupported locator: ${locatorInput}`);
|
|
18722
|
+
}
|
|
18723
|
+
function findCellBlocks(blocks, locator) {
|
|
18724
|
+
const block = findBlockById(blocks, locator.tableId);
|
|
18725
|
+
if (!block || block.type !== "table")
|
|
18726
|
+
return null;
|
|
18727
|
+
const row = block.rows[locator.row];
|
|
18728
|
+
if (!row)
|
|
18729
|
+
return null;
|
|
18730
|
+
const cell = row.cells[locator.col];
|
|
18731
|
+
if (!cell)
|
|
18732
|
+
return null;
|
|
18733
|
+
return cell.blocks;
|
|
18734
|
+
}
|
|
18735
|
+
var HELP23 = `docx wc \u2014 count words in a document or a locator-addressed slice
|
|
18736
|
+
|
|
18737
|
+
Usage:
|
|
18738
|
+
docx wc FILE [LOCATOR] [options]
|
|
18739
|
+
|
|
18740
|
+
Locators (optional; default: whole document):
|
|
18741
|
+
pN whole paragraph N
|
|
18742
|
+
pN:S-E chars S..E within paragraph N
|
|
18743
|
+
pN:S-pM:E cross-paragraph range
|
|
18744
|
+
tN whole table
|
|
18745
|
+
tN:rRcC whole cell
|
|
18746
|
+
tN:rRcC:pK paragraph K inside that cell
|
|
18747
|
+
tN:rRcC:pK:S-E span within a cell paragraph
|
|
18748
|
+
|
|
18749
|
+
Options:
|
|
18750
|
+
-h, --help show this help
|
|
18751
|
+
|
|
18752
|
+
Counting is whitespace-segmented (\\S+) over the joined paragraph text. Hidden
|
|
18753
|
+
content like images/breaks/tabs contributes no words. Tracked-change deletions
|
|
18754
|
+
are counted alongside insertions because their characters are still part of the
|
|
18755
|
+
on-disk text; accept or reject changes first if you need a delta on the
|
|
18756
|
+
"accepted" view.
|
|
18757
|
+
|
|
18758
|
+
Examples:
|
|
18759
|
+
docx wc doc.docx
|
|
18760
|
+
docx wc doc.docx p3
|
|
18761
|
+
docx wc doc.docx p3:0-120
|
|
18762
|
+
docx wc doc.docx p5:10-p9:42
|
|
18763
|
+
docx wc doc.docx t0:r1c0
|
|
18764
|
+
`;
|
|
18765
|
+
var init_wc = __esm(() => {
|
|
18766
|
+
init_core();
|
|
18767
|
+
init_respond();
|
|
18768
|
+
});
|
|
18347
18769
|
// package.json
|
|
18348
18770
|
var package_default = {
|
|
18349
18771
|
name: "bun-docx",
|
|
18350
|
-
version: "0.
|
|
18772
|
+
version: "0.4.0",
|
|
18351
18773
|
description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
|
|
18352
18774
|
keywords: [
|
|
18353
18775
|
"docx",
|
|
@@ -18422,6 +18844,8 @@ Commands:
|
|
|
18422
18844
|
delete FILE Remove a block or run range
|
|
18423
18845
|
find FILE QUERY Find text spans, return locators
|
|
18424
18846
|
replace FILE PATTERN REPL Substitute text spans (sed for docx)
|
|
18847
|
+
wc FILE [LOCATOR] Count words in the doc or a slice
|
|
18848
|
+
outline FILE List headings as a hierarchical tree
|
|
18425
18849
|
comments \u2026 Add, reply, resolve, delete, list comments
|
|
18426
18850
|
images \u2026 Extract, replace, list images
|
|
18427
18851
|
track-changes FILE on|off Toggle tracked-changes mode
|
|
@@ -18453,9 +18877,11 @@ var COMMANDS = {
|
|
|
18453
18877
|
images: () => Promise.resolve().then(() => (init_images(), exports_images)),
|
|
18454
18878
|
info: () => Promise.resolve().then(() => (init_info(), exports_info)),
|
|
18455
18879
|
insert: () => Promise.resolve().then(() => (init_insert(), exports_insert)),
|
|
18880
|
+
outline: () => Promise.resolve().then(() => (init_outline(), exports_outline)),
|
|
18456
18881
|
read: () => Promise.resolve().then(() => (init_read2(), exports_read)),
|
|
18457
18882
|
replace: () => Promise.resolve().then(() => (init_replace2(), exports_replace2)),
|
|
18458
|
-
"track-changes": () => Promise.resolve().then(() => (init_track_changes2(), exports_track_changes))
|
|
18883
|
+
"track-changes": () => Promise.resolve().then(() => (init_track_changes2(), exports_track_changes)),
|
|
18884
|
+
wc: () => Promise.resolve().then(() => (init_wc(), exports_wc))
|
|
18459
18885
|
};
|
|
18460
18886
|
async function main(argv) {
|
|
18461
18887
|
const args = argv.slice(2);
|