bun-docx 0.20.2 → 0.20.4
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 +11 -3
- package/dist/index.js +667 -154
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,9 +226,10 @@ size="…in" margins="…in" text-width="…in" -->` note when the page deviates
|
|
|
226
226
|
so the importer drops it (it can't re-inject into the body); full entries are in
|
|
227
227
|
`read --ast` under `headers`/`footers` (`Marginal[]`). Set with `docx
|
|
228
228
|
headers`/`docx footers`.
|
|
229
|
-
- **Track-changes state** rides a head `<!-- docx:track-changes on -->`
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
- **Track-changes state** always rides a head `<!-- docx:track-changes on|off -->`
|
|
230
|
+
line — it's the one read hint that states its default too (an agent shouldn't have
|
|
231
|
+
to infer "off" from a missing line), so you can see whether subsequent edits will be
|
|
232
|
+
redlined without inspecting `settings.xml`.
|
|
232
233
|
Toggle it with `docx track-changes FILE on|off`; the three tracked-change read views
|
|
233
234
|
(`--accepted`/`--current`/`--baseline`) are covered under the review loop below.
|
|
234
235
|
|
|
@@ -245,9 +246,16 @@ docx styles set-default-font FILE "Font Name" [--size N] [--all] # document-wi
|
|
|
245
246
|
docx replace FILE PATTERN REPLACEMENT [--at pN] [--regex] [--ignore-case] [--all] [--limit N] [--current | --baseline] [--exact] [--track] [--dry-run]
|
|
246
247
|
# Keeps the run's formatting (bold/font) and any tabs — the no-rebuild way to fill a
|
|
247
248
|
# formatted/tabbed template line (e.g. "**Org Name**⇥Date"); don't hand-build --runs to refill it.
|
|
249
|
+
# A TAB matches as one character (pattern "City State Zip" fills a tab-separated line), and
|
|
250
|
+
# TAB/NBSP/bullet-glyph variants match their plain equivalents unless --exact.
|
|
248
251
|
# --at pN (or a cell paragraph tT:rRcC:pN) CONFINES the replace to one paragraph — use it when the
|
|
249
252
|
# SAME placeholder repeats across the doc (a résumé's "City, State" in every entry) and you want THE
|
|
250
253
|
# one in a specific paragraph, instead of find → edit --at pN:S-E span surgery. Batch entries take "at" too.
|
|
254
|
+
# MULTI-LINE (editor-style): "\n" in PATTERN matches a line break OR a paragraph boundary
|
|
255
|
+
# (consecutive paragraphs in the body or one table cell; never across a table/section break/cell wall); the REPLACEMENT's own
|
|
256
|
+
# "\n"s then define the result — single-line replacement MERGES the paragraphs (first one's
|
|
257
|
+
# formatting governs), "\n" in the replacement inserts a paragraph mark (splits). Untracked
|
|
258
|
+
# only: refuses under tracking rather than skip the journal. Block ids shift; re-read after.
|
|
251
259
|
|
|
252
260
|
# Batch — apply many changes from ONE read (no re-reading between edits). Keys
|
|
253
261
|
# on each JSONL line mirror the command's flags; all locators address the doc as
|
package/dist/index.js
CHANGED
|
@@ -4513,8 +4513,8 @@ function runTextLength(run) {
|
|
|
4513
4513
|
for (const child of run.children) {
|
|
4514
4514
|
if (child.tag === "w:t" || child.tag === "w:delText") {
|
|
4515
4515
|
total += child.collectText().length;
|
|
4516
|
-
} else
|
|
4517
|
-
total +=
|
|
4516
|
+
} else {
|
|
4517
|
+
total += inlineMarkerWidth(child);
|
|
4518
4518
|
}
|
|
4519
4519
|
}
|
|
4520
4520
|
return total;
|
|
@@ -4539,7 +4539,7 @@ function sliceRun(run, start, end) {
|
|
|
4539
4539
|
offset += text.length;
|
|
4540
4540
|
continue;
|
|
4541
4541
|
}
|
|
4542
|
-
if (
|
|
4542
|
+
if (inlineMarkerWidth(child) === 1) {
|
|
4543
4543
|
if (offset >= start && offset < end) {
|
|
4544
4544
|
sliced.children.push(child.clone());
|
|
4545
4545
|
}
|
|
@@ -4552,6 +4552,23 @@ function sliceRun(run, start, end) {
|
|
|
4552
4552
|
}
|
|
4553
4553
|
return sliced;
|
|
4554
4554
|
}
|
|
4555
|
+
function inlineMarkerWidth(child) {
|
|
4556
|
+
switch (child.tag) {
|
|
4557
|
+
case "w:noBreakHyphen":
|
|
4558
|
+
case "w:softHyphen":
|
|
4559
|
+
case "w:sym":
|
|
4560
|
+
case "w:tab":
|
|
4561
|
+
case "w:ptab":
|
|
4562
|
+
case "w:cr":
|
|
4563
|
+
return 1;
|
|
4564
|
+
case "w:br": {
|
|
4565
|
+
const type = child.getAttribute("w:type");
|
|
4566
|
+
return type === "page" || type === "column" ? 0 : 1;
|
|
4567
|
+
}
|
|
4568
|
+
default:
|
|
4569
|
+
return 0;
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4555
4572
|
function isRunBearingWrapper(tag) {
|
|
4556
4573
|
return RUN_BEARING_WRAPPER_TAGS.has(tag);
|
|
4557
4574
|
}
|
|
@@ -4595,10 +4612,9 @@ function collectInnerRuns(wrapper, out) {
|
|
|
4595
4612
|
}
|
|
4596
4613
|
}
|
|
4597
4614
|
}
|
|
4598
|
-
var
|
|
4615
|
+
var RUN_BEARING_WRAPPER_TAGS;
|
|
4599
4616
|
var init_run_ops = __esm(() => {
|
|
4600
4617
|
init_xml_node();
|
|
4601
|
-
SINGLE_CHAR_TAGS = new Set(["w:noBreakHyphen", "w:softHyphen", "w:sym"]);
|
|
4602
4618
|
RUN_BEARING_WRAPPER_TAGS = new Set([
|
|
4603
4619
|
"w:ins",
|
|
4604
4620
|
"w:del",
|
|
@@ -22986,12 +23002,18 @@ function readRun(document2, node, activeComments, trackedChange, hyperlink, stat
|
|
|
22986
23002
|
if (child2.tag === "w:br" || child2.tag === "w:cr") {
|
|
22987
23003
|
flushText();
|
|
22988
23004
|
const kind = child2.tag === "w:cr" ? "line" : child2.getAttribute("w:type") ?? "line";
|
|
22989
|
-
|
|
23005
|
+
const breakRun = { type: "break", kind };
|
|
23006
|
+
if (trackedChange)
|
|
23007
|
+
breakRun.trackedChange = trackedChange;
|
|
23008
|
+
out.push(breakRun);
|
|
22990
23009
|
continue;
|
|
22991
23010
|
}
|
|
22992
23011
|
if (child2.tag === "w:tab" || child2.tag === "w:ptab") {
|
|
22993
23012
|
flushText();
|
|
22994
|
-
|
|
23013
|
+
const tabRun = { type: "tab" };
|
|
23014
|
+
if (trackedChange)
|
|
23015
|
+
tabRun.trackedChange = trackedChange;
|
|
23016
|
+
out.push(tabRun);
|
|
22995
23017
|
continue;
|
|
22996
23018
|
}
|
|
22997
23019
|
if (child2.tag === "w:footnoteReference") {
|
|
@@ -34790,7 +34812,7 @@ var init_forms = __esm(() => {
|
|
|
34790
34812
|
crossSpan: {
|
|
34791
34813
|
syntax: "pN:S-pM:E",
|
|
34792
34814
|
example: "p3:5-p5:10",
|
|
34793
|
-
summary: "from char S of paragraph N to char E of paragraph M"
|
|
34815
|
+
summary: "from char S of paragraph N to char E of paragraph M (cell paragraphs too \u2014 tN:rRcC:pA:S-tN:rRcC:pB:E, same cell)"
|
|
34794
34816
|
},
|
|
34795
34817
|
cell: {
|
|
34796
34818
|
syntax: "tN:rRcC",
|
|
@@ -34883,6 +34905,21 @@ function parseLocator(input) {
|
|
|
34883
34905
|
if (endnoteMatch) {
|
|
34884
34906
|
return { kind: "endnote", endnoteId: `en${endnoteMatch[1]}` };
|
|
34885
34907
|
}
|
|
34908
|
+
const rangeMatch = trimmed.match(RANGE_RE);
|
|
34909
|
+
if (rangeMatch) {
|
|
34910
|
+
const [, startBlock, startCapture, endBlock, endCapture] = rangeMatch;
|
|
34911
|
+
if (startBlock && endBlock && cellPrefixOf(startBlock) !== cellPrefixOf(endBlock)) {
|
|
34912
|
+
throw new LocatorParseError(input, "range endpoints must share a container \u2014 both body paragraphs, or paragraphs of the SAME table cell");
|
|
34913
|
+
}
|
|
34914
|
+
const startOffset = Number(startCapture);
|
|
34915
|
+
const endOffset = Number(endCapture);
|
|
34916
|
+
validateOffsets(input, startOffset, endOffset, startBlock !== endBlock);
|
|
34917
|
+
return {
|
|
34918
|
+
kind: "range",
|
|
34919
|
+
start: { blockId: startBlock ?? "", offset: startOffset },
|
|
34920
|
+
end: { blockId: endBlock ?? "", offset: endOffset }
|
|
34921
|
+
};
|
|
34922
|
+
}
|
|
34886
34923
|
const cellRangeMatch = trimmed.match(CELL_RANGE_RE);
|
|
34887
34924
|
if (cellRangeMatch) {
|
|
34888
34925
|
const [, tableIndex, startRow, startCol, endRow, endCol] = cellRangeMatch;
|
|
@@ -34924,18 +34961,6 @@ function parseLocator(input) {
|
|
|
34924
34961
|
col: Number(columnIndex)
|
|
34925
34962
|
};
|
|
34926
34963
|
}
|
|
34927
|
-
const rangeMatch = trimmed.match(RANGE_RE);
|
|
34928
|
-
if (rangeMatch) {
|
|
34929
|
-
const [, startBlock, startCapture, endBlock, endCapture] = rangeMatch;
|
|
34930
|
-
const startOffset = Number(startCapture);
|
|
34931
|
-
const endOffset = Number(endCapture);
|
|
34932
|
-
validateOffsets(input, startOffset, endOffset, startBlock !== endBlock);
|
|
34933
|
-
return {
|
|
34934
|
-
kind: "range",
|
|
34935
|
-
start: { blockId: `p${startBlock}`, offset: startOffset },
|
|
34936
|
-
end: { blockId: `p${endBlock}`, offset: endOffset }
|
|
34937
|
-
};
|
|
34938
|
-
}
|
|
34939
34964
|
const blockRangeMatch = trimmed.match(BLOCK_RANGE_RE);
|
|
34940
34965
|
if (blockRangeMatch) {
|
|
34941
34966
|
const [, startIndex, endIndex] = blockRangeMatch;
|
|
@@ -34965,6 +34990,10 @@ function parseLocator(input) {
|
|
|
34965
34990
|
}
|
|
34966
34991
|
throw new LocatorParseError(input, "unrecognized syntax");
|
|
34967
34992
|
}
|
|
34993
|
+
function cellPrefixOf(blockId) {
|
|
34994
|
+
const lastColon = blockId.lastIndexOf(":");
|
|
34995
|
+
return lastColon === -1 ? "" : blockId.slice(0, lastColon);
|
|
34996
|
+
}
|
|
34968
34997
|
function validateOffsets(input, start, end, crossBlock) {
|
|
34969
34998
|
if (start < 0 || end < 0) {
|
|
34970
34999
|
throw new LocatorParseError(input, "offsets must be non-negative");
|
|
@@ -34985,7 +35014,7 @@ var init_parse = __esm(() => {
|
|
|
34985
35014
|
};
|
|
34986
35015
|
BLOCK_RE = /^(p|t|s)(\d+)$/;
|
|
34987
35016
|
SPAN_RE = /^p(\d+):(\d+)-(\d+)$/;
|
|
34988
|
-
RANGE_RE = /^
|
|
35017
|
+
RANGE_RE = /^((?:t\d+:r\d+c\d+:)?p\d+):(\d+)-((?:t\d+:r\d+c\d+:)?p\d+):(\d+)$/;
|
|
34989
35018
|
BLOCK_RANGE_RE = /^p(\d+)-p(\d+)$/;
|
|
34990
35019
|
COMMENT_RE = /^c(\d+)$/;
|
|
34991
35020
|
IMAGE_RE = /^img(\d+)$/;
|
|
@@ -50155,10 +50184,7 @@ function replaceSpanInParagraph(paragraph, span, replacement, tracked, view = "a
|
|
|
50155
50184
|
const overlapping = slots.filter((slot) => slot.offsetBefore + slot.length > span.start && slot.offsetBefore < span.end);
|
|
50156
50185
|
const firstSlot = overlapping[0];
|
|
50157
50186
|
if (!firstSlot) {
|
|
50158
|
-
paragraph.children.push(
|
|
50159
|
-
runProperties: null,
|
|
50160
|
-
text: replacement
|
|
50161
|
-
}, undefined, false, undefined, this));
|
|
50187
|
+
paragraph.children.push(...replacementRuns(null, replacement));
|
|
50162
50188
|
return;
|
|
50163
50189
|
}
|
|
50164
50190
|
const inheritedProperties = firstSlot.run.findChild("w:rPr")?.clone() ?? null;
|
|
@@ -50203,14 +50229,15 @@ function rebuildContainer(container, baseOffset, span, replacement, runPropertie
|
|
|
50203
50229
|
if (placed)
|
|
50204
50230
|
return;
|
|
50205
50231
|
placed = true;
|
|
50206
|
-
const
|
|
50207
|
-
|
|
50208
|
-
|
|
50209
|
-
|
|
50210
|
-
|
|
50211
|
-
|
|
50212
|
-
|
|
50213
|
-
}
|
|
50232
|
+
const runs = replacementRuns(runProperties, replacement);
|
|
50233
|
+
if (tracked && isParagraph) {
|
|
50234
|
+
newChildren.push(/* @__PURE__ */ jsxDEV(Ins, {
|
|
50235
|
+
meta: mintMeta(tracked),
|
|
50236
|
+
children: runs
|
|
50237
|
+
}, undefined, false, undefined, this));
|
|
50238
|
+
return;
|
|
50239
|
+
}
|
|
50240
|
+
newChildren.push(...runs);
|
|
50214
50241
|
};
|
|
50215
50242
|
for (const child2 of container.children) {
|
|
50216
50243
|
if (child2.tag === "w:r") {
|
|
@@ -50265,14 +50292,15 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
|
|
|
50265
50292
|
if (placed)
|
|
50266
50293
|
return;
|
|
50267
50294
|
placed = true;
|
|
50268
|
-
const
|
|
50269
|
-
|
|
50270
|
-
|
|
50271
|
-
|
|
50272
|
-
|
|
50273
|
-
|
|
50274
|
-
|
|
50275
|
-
}
|
|
50295
|
+
const runs = replacementRuns(runProperties, replacement);
|
|
50296
|
+
if (tracked) {
|
|
50297
|
+
newChildren.push(/* @__PURE__ */ jsxDEV(Ins, {
|
|
50298
|
+
meta: mintMeta(tracked),
|
|
50299
|
+
children: runs
|
|
50300
|
+
}, undefined, false, undefined, this));
|
|
50301
|
+
return;
|
|
50302
|
+
}
|
|
50303
|
+
newChildren.push(...runs);
|
|
50276
50304
|
};
|
|
50277
50305
|
for (const child2 of paragraph.children) {
|
|
50278
50306
|
if (child2.tag === "w:r") {
|
|
@@ -50497,14 +50525,15 @@ function splitHyperlinkAcrossSpan(wrapper, wrapperStart, span, runProperties, re
|
|
|
50497
50525
|
}
|
|
50498
50526
|
}
|
|
50499
50527
|
if (startsInside) {
|
|
50500
|
-
const
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
|
|
50506
|
-
|
|
50507
|
-
|
|
50528
|
+
const innerRuns = replacementRuns(runProperties, replacement);
|
|
50529
|
+
if (tracked) {
|
|
50530
|
+
preInner.push(/* @__PURE__ */ jsxDEV(Ins, {
|
|
50531
|
+
meta: mintMeta(tracked),
|
|
50532
|
+
children: innerRuns
|
|
50533
|
+
}, undefined, false, undefined, this));
|
|
50534
|
+
} else {
|
|
50535
|
+
preInner.push(...innerRuns);
|
|
50536
|
+
}
|
|
50508
50537
|
markReplacementPlaced();
|
|
50509
50538
|
}
|
|
50510
50539
|
if (preInner.length > 0) {
|
|
@@ -50529,48 +50558,318 @@ function convertRunTextToDelText(run) {
|
|
|
50529
50558
|
child2.tag = "w:delText";
|
|
50530
50559
|
}
|
|
50531
50560
|
}
|
|
50532
|
-
function
|
|
50533
|
-
|
|
50534
|
-
|
|
50535
|
-
|
|
50536
|
-
|
|
50561
|
+
function replacementRuns(runProperties, text2) {
|
|
50562
|
+
if (text2.length === 0) {
|
|
50563
|
+
return [
|
|
50564
|
+
/* @__PURE__ */ jsxDEV(w.r, {
|
|
50565
|
+
children: [
|
|
50566
|
+
runProperties,
|
|
50567
|
+
/* @__PURE__ */ jsxDEV(w.t, {
|
|
50568
|
+
"xml:space": "preserve"
|
|
50569
|
+
}, undefined, false, undefined, this)
|
|
50570
|
+
]
|
|
50571
|
+
}, undefined, true, undefined, this)
|
|
50572
|
+
];
|
|
50573
|
+
}
|
|
50574
|
+
const runs = textToRunElements(text2);
|
|
50575
|
+
for (const run of runs) {
|
|
50576
|
+
if (runProperties)
|
|
50577
|
+
run.children.unshift(runProperties.clone());
|
|
50578
|
+
}
|
|
50579
|
+
return runs;
|
|
50580
|
+
}
|
|
50581
|
+
var init_replace_span = __esm(() => {
|
|
50582
|
+
init_blocks();
|
|
50583
|
+
init_jsx();
|
|
50584
|
+
init_parser();
|
|
50585
|
+
init_emit2();
|
|
50586
|
+
init_jsx_dev_runtime();
|
|
50587
|
+
});
|
|
50588
|
+
|
|
50589
|
+
// src/core/find/replace-across.tsx
|
|
50590
|
+
function replaceAcrossParagraphs(body, match, replacement, view = "accepted") {
|
|
50591
|
+
const startRef = body.resolveBlock(match.startBlockId);
|
|
50592
|
+
const endRef = body.resolveBlock(match.endBlockId);
|
|
50593
|
+
const { parent, startIndex, endIndex } = body.resolveBlockRange(match.startBlockId, match.endBlockId);
|
|
50594
|
+
const sameNode = startRef.node === endRef.node;
|
|
50595
|
+
const segments = replacement.split(/\r\n|\n/);
|
|
50596
|
+
let head;
|
|
50597
|
+
let tail;
|
|
50598
|
+
let cut;
|
|
50599
|
+
if (sameNode) {
|
|
50600
|
+
const endSplit = splitChildrenAt(startRef.node.children, match.endOffset, view);
|
|
50601
|
+
const startSplit = splitChildrenAt(endSplit.before, match.startOffset, view);
|
|
50602
|
+
head = startSplit.before;
|
|
50603
|
+
tail = endSplit.after;
|
|
50604
|
+
cut = startSplit.after;
|
|
50605
|
+
} else {
|
|
50606
|
+
const startSplit = splitChildrenAt(startRef.node.children, match.startOffset, view);
|
|
50607
|
+
const endSplit = splitChildrenAt(endRef.node.children, match.endOffset, view);
|
|
50608
|
+
head = startSplit.before;
|
|
50609
|
+
tail = endSplit.after;
|
|
50610
|
+
cut = [
|
|
50611
|
+
...startSplit.after,
|
|
50612
|
+
...endSplit.before.filter((child2) => child2.tag !== "w:pPr")
|
|
50613
|
+
];
|
|
50614
|
+
}
|
|
50615
|
+
const inherited = firstVisibleRun(cut, view)?.findChild("w:rPr")?.clone() ?? null;
|
|
50616
|
+
const salvaged = salvageNonContent(cut, view);
|
|
50617
|
+
const segmentRuns = (segment) => segment.length === 0 ? [] : replacementRuns(inherited, segment);
|
|
50618
|
+
const firstSegment = segments[0] ?? "";
|
|
50619
|
+
const startChildren = [...head, ...segmentRuns(firstSegment), ...salvaged];
|
|
50620
|
+
if (segments.length === 1) {
|
|
50621
|
+
startRef.node.children = [...startChildren, ...tail];
|
|
50622
|
+
parent.splice(startIndex + 1, endIndex - startIndex);
|
|
50623
|
+
return;
|
|
50624
|
+
}
|
|
50625
|
+
startRef.node.children = startChildren;
|
|
50626
|
+
const template = paragraphPropertiesTemplate(startRef.node);
|
|
50627
|
+
const middle = segments.slice(1, -1).map((segment) => buildParagraph(template?.clone() ?? null, segmentRuns(segment)));
|
|
50628
|
+
const lastSegment = segments[segments.length - 1] ?? "";
|
|
50629
|
+
if (sameNode) {
|
|
50630
|
+
const finalParagraph = buildParagraph(template?.clone() ?? null, [
|
|
50631
|
+
...segmentRuns(lastSegment),
|
|
50632
|
+
...tail
|
|
50633
|
+
]);
|
|
50634
|
+
parent.splice(startIndex + 1, 0, ...middle, finalParagraph);
|
|
50635
|
+
return;
|
|
50636
|
+
}
|
|
50637
|
+
const endProperties = endRef.node.findChild("w:pPr");
|
|
50638
|
+
endRef.node.children = [
|
|
50639
|
+
...endProperties ? [endProperties] : [],
|
|
50640
|
+
...segmentRuns(lastSegment),
|
|
50641
|
+
...tail
|
|
50642
|
+
];
|
|
50643
|
+
parent.splice(startIndex + 1, endIndex - startIndex - 1, ...middle);
|
|
50644
|
+
}
|
|
50645
|
+
function splitChildrenAt(children, offset2, view) {
|
|
50646
|
+
const before = [];
|
|
50647
|
+
const after = [];
|
|
50648
|
+
let cursor = 0;
|
|
50649
|
+
for (const child2 of children) {
|
|
50650
|
+
if (child2.tag === "w:pPr") {
|
|
50651
|
+
before.push(child2);
|
|
50652
|
+
continue;
|
|
50653
|
+
}
|
|
50654
|
+
if (child2.tag === "w:r") {
|
|
50655
|
+
const length = runTextLength(child2);
|
|
50656
|
+
if (cursor + length <= offset2) {
|
|
50657
|
+
before.push(child2);
|
|
50658
|
+
} else if (cursor >= offset2) {
|
|
50659
|
+
after.push(child2);
|
|
50660
|
+
} else {
|
|
50661
|
+
before.push(sliceRun(child2, 0, offset2 - cursor));
|
|
50662
|
+
after.push(sliceRun(child2, offset2 - cursor, length));
|
|
50663
|
+
}
|
|
50664
|
+
cursor += length;
|
|
50665
|
+
continue;
|
|
50666
|
+
}
|
|
50667
|
+
if (isRunBearingWrapper(child2.tag) && isWrapperVisibleInView2(child2.tag, view)) {
|
|
50668
|
+
const length = sumVisibleTextLength2(child2.children, view);
|
|
50669
|
+
if (cursor + length <= offset2) {
|
|
50670
|
+
before.push(child2);
|
|
50671
|
+
} else if (cursor >= offset2) {
|
|
50672
|
+
after.push(child2);
|
|
50673
|
+
} else {
|
|
50674
|
+
const inner2 = splitChildrenAt(child2.children, offset2 - cursor, view);
|
|
50675
|
+
if (inner2.before.length > 0) {
|
|
50676
|
+
const preWrapper = new XmlNode2(child2.tag, { ...child2.attributes });
|
|
50677
|
+
preWrapper.children = inner2.before;
|
|
50678
|
+
before.push(preWrapper);
|
|
50679
|
+
}
|
|
50680
|
+
if (inner2.after.length > 0) {
|
|
50681
|
+
const postWrapper = new XmlNode2(child2.tag, { ...child2.attributes });
|
|
50682
|
+
postWrapper.children = inner2.after;
|
|
50683
|
+
after.push(postWrapper);
|
|
50684
|
+
}
|
|
50685
|
+
}
|
|
50686
|
+
cursor += length;
|
|
50687
|
+
continue;
|
|
50688
|
+
}
|
|
50689
|
+
(cursor < offset2 ? before : after).push(child2);
|
|
50690
|
+
}
|
|
50691
|
+
return { before, after };
|
|
50692
|
+
}
|
|
50693
|
+
function firstVisibleRun(children, view) {
|
|
50694
|
+
for (const child2 of children) {
|
|
50695
|
+
if (child2.tag === "w:r")
|
|
50696
|
+
return child2;
|
|
50697
|
+
if (isRunBearingWrapper(child2.tag) && isWrapperVisibleInView2(child2.tag, view)) {
|
|
50698
|
+
const nested = firstVisibleRun(child2.children, view);
|
|
50699
|
+
if (nested)
|
|
50700
|
+
return nested;
|
|
50701
|
+
}
|
|
50702
|
+
}
|
|
50703
|
+
return null;
|
|
50704
|
+
}
|
|
50705
|
+
function salvageNonContent(children, view) {
|
|
50706
|
+
const out = [];
|
|
50707
|
+
for (const child2 of children) {
|
|
50708
|
+
if (child2.tag === "w:r" || child2.tag === "w:pPr")
|
|
50709
|
+
continue;
|
|
50710
|
+
if (isRunBearingWrapper(child2.tag)) {
|
|
50711
|
+
if (isWrapperVisibleInView2(child2.tag, view)) {
|
|
50712
|
+
out.push(...salvageNonContent(child2.children, view));
|
|
50713
|
+
} else {
|
|
50714
|
+
out.push(child2);
|
|
50715
|
+
}
|
|
50716
|
+
continue;
|
|
50717
|
+
}
|
|
50718
|
+
out.push(child2);
|
|
50719
|
+
}
|
|
50720
|
+
return out;
|
|
50721
|
+
}
|
|
50722
|
+
function paragraphPropertiesTemplate(paragraph) {
|
|
50723
|
+
const source2 = paragraph.findChild("w:pPr");
|
|
50724
|
+
if (!source2)
|
|
50725
|
+
return null;
|
|
50726
|
+
const clone = source2.clone();
|
|
50727
|
+
clone.children = clone.children.filter((child2) => child2.tag !== "w:sectPr");
|
|
50728
|
+
const markProperties = clone.findChild("w:rPr");
|
|
50729
|
+
if (markProperties) {
|
|
50730
|
+
markProperties.children = markProperties.children.filter((child2) => child2.tag !== "w:ins" && child2.tag !== "w:del");
|
|
50731
|
+
}
|
|
50732
|
+
return clone;
|
|
50733
|
+
}
|
|
50734
|
+
function buildParagraph(paragraphProperties, runs) {
|
|
50735
|
+
return /* @__PURE__ */ jsxDEV(w.p, {
|
|
50537
50736
|
children: [
|
|
50538
|
-
|
|
50539
|
-
|
|
50540
|
-
"xml:space": "preserve",
|
|
50541
|
-
children: text2
|
|
50542
|
-
}, undefined, false, undefined, this)
|
|
50737
|
+
paragraphProperties,
|
|
50738
|
+
runs
|
|
50543
50739
|
]
|
|
50544
50740
|
}, undefined, true, undefined, this);
|
|
50545
50741
|
}
|
|
50546
|
-
var
|
|
50742
|
+
var init_replace_across = __esm(() => {
|
|
50547
50743
|
init_jsx();
|
|
50548
50744
|
init_parser();
|
|
50549
|
-
|
|
50745
|
+
init_replace_span();
|
|
50550
50746
|
init_jsx_dev_runtime();
|
|
50551
50747
|
});
|
|
50552
50748
|
|
|
50553
50749
|
// src/core/find/index.ts
|
|
50554
50750
|
function findTextSpans(doc, query, options = {}) {
|
|
50555
50751
|
const view = options.view ?? "accepted";
|
|
50556
|
-
const
|
|
50557
|
-
const useRegex = options.regex ?? false;
|
|
50558
|
-
let effectiveQuery = query;
|
|
50559
|
-
const applied = [];
|
|
50560
|
-
if (!useRegex && !exact) {
|
|
50561
|
-
const norm = normalizeQuery(query);
|
|
50562
|
-
effectiveQuery = norm.normalized;
|
|
50563
|
-
applied.push(...norm.applied);
|
|
50564
|
-
}
|
|
50565
|
-
const matcher = useRegex ? regexMatcher(query, options.ignoreCase ?? false) : literalMatcher(effectiveQuery, options.ignoreCase ?? false, !exact);
|
|
50752
|
+
const { matcher, ...normalization } = buildMatcher(query, options);
|
|
50566
50753
|
const out = [];
|
|
50567
50754
|
collectMatches(doc.blocks, matcher, view, out);
|
|
50568
|
-
|
|
50569
|
-
|
|
50570
|
-
|
|
50571
|
-
|
|
50755
|
+
return { matches: out, ...normalization };
|
|
50756
|
+
}
|
|
50757
|
+
function buildMatcher(query, options) {
|
|
50758
|
+
const ignoreCase = options.ignoreCase ?? false;
|
|
50759
|
+
if (options.regex)
|
|
50760
|
+
return { matcher: regexMatcher(query, ignoreCase) };
|
|
50761
|
+
if (options.exact) {
|
|
50762
|
+
return { matcher: literalMatcher(query, ignoreCase, false) };
|
|
50763
|
+
}
|
|
50764
|
+
const { normalized, applied } = normalizeQuery(query);
|
|
50765
|
+
const matcher = literalMatcher(normalized, ignoreCase, true);
|
|
50766
|
+
if (applied.length === 0)
|
|
50767
|
+
return { matcher };
|
|
50768
|
+
return {
|
|
50769
|
+
matcher,
|
|
50770
|
+
normalizedQuery: normalized,
|
|
50771
|
+
normalizationApplied: applied
|
|
50772
|
+
};
|
|
50773
|
+
}
|
|
50774
|
+
function findAcrossParagraphs(doc, query, options = {}) {
|
|
50775
|
+
const view = options.view ?? "accepted";
|
|
50776
|
+
const { matcher, ...normalization } = buildMatcher(query, options);
|
|
50777
|
+
const out = [];
|
|
50778
|
+
for (const group of consecutiveParagraphGroups(doc.blocks)) {
|
|
50779
|
+
const entries = [];
|
|
50780
|
+
let flat = "";
|
|
50781
|
+
for (const paragraph of group) {
|
|
50782
|
+
const text2 = paragraphTextForView(paragraph, view);
|
|
50783
|
+
entries.push({
|
|
50784
|
+
blockId: paragraph.id,
|
|
50785
|
+
flatStart: flat.length,
|
|
50786
|
+
length: text2.length
|
|
50787
|
+
});
|
|
50788
|
+
flat += `${text2}
|
|
50789
|
+
`;
|
|
50790
|
+
}
|
|
50791
|
+
flat = flat.slice(0, -1);
|
|
50792
|
+
let entryCursor = 0;
|
|
50793
|
+
for (const span of matcher(flat)) {
|
|
50794
|
+
const start = mapFlatOffset(span.start, entries, entryCursor);
|
|
50795
|
+
const end = mapFlatOffset(span.end, entries, start.entryIndex);
|
|
50796
|
+
entryCursor = end.entryIndex;
|
|
50797
|
+
out.push({
|
|
50798
|
+
startBlockId: start.blockId,
|
|
50799
|
+
startOffset: start.local,
|
|
50800
|
+
endBlockId: end.blockId,
|
|
50801
|
+
endOffset: end.local,
|
|
50802
|
+
text: span.text
|
|
50803
|
+
});
|
|
50804
|
+
}
|
|
50572
50805
|
}
|
|
50573
|
-
return
|
|
50806
|
+
return { matches: out, ...normalization };
|
|
50807
|
+
}
|
|
50808
|
+
function consecutiveParagraphGroups(blocks) {
|
|
50809
|
+
const groups = [];
|
|
50810
|
+
let current = [];
|
|
50811
|
+
const flush = () => {
|
|
50812
|
+
if (current.length > 0) {
|
|
50813
|
+
groups.push(current);
|
|
50814
|
+
current = [];
|
|
50815
|
+
}
|
|
50816
|
+
};
|
|
50817
|
+
for (const block of blocks) {
|
|
50818
|
+
if (block.type === "paragraph") {
|
|
50819
|
+
current.push(block);
|
|
50820
|
+
continue;
|
|
50821
|
+
}
|
|
50822
|
+
flush();
|
|
50823
|
+
if (block.type === "table") {
|
|
50824
|
+
for (const row of block.rows) {
|
|
50825
|
+
for (const cell of row.cells) {
|
|
50826
|
+
groups.push(...consecutiveParagraphGroups(cell.blocks));
|
|
50827
|
+
}
|
|
50828
|
+
}
|
|
50829
|
+
}
|
|
50830
|
+
}
|
|
50831
|
+
flush();
|
|
50832
|
+
return groups;
|
|
50833
|
+
}
|
|
50834
|
+
function mapFlatOffset(offset2, entries, fromIndex) {
|
|
50835
|
+
for (let index = fromIndex;index < entries.length; index++) {
|
|
50836
|
+
const entry = entries[index];
|
|
50837
|
+
if (entry && offset2 <= entry.flatStart + entry.length) {
|
|
50838
|
+
return {
|
|
50839
|
+
blockId: entry.blockId,
|
|
50840
|
+
local: Math.max(0, offset2 - entry.flatStart),
|
|
50841
|
+
entryIndex: index
|
|
50842
|
+
};
|
|
50843
|
+
}
|
|
50844
|
+
}
|
|
50845
|
+
throw new Error(`mapFlatOffset: offset ${offset2} out of range`);
|
|
50846
|
+
}
|
|
50847
|
+
function applyAcrossReplace(doc, spec, { dryRun = false } = {}) {
|
|
50848
|
+
const { matches, ...normalization } = findAcrossParagraphs(doc, spec.pattern, {
|
|
50849
|
+
regex: spec.regex,
|
|
50850
|
+
ignoreCase: spec.ignoreCase,
|
|
50851
|
+
view: spec.view,
|
|
50852
|
+
exact: spec.exact
|
|
50853
|
+
});
|
|
50854
|
+
const replaced = selectMatches(matches, spec);
|
|
50855
|
+
if (!dryRun) {
|
|
50856
|
+
const expand = replacementExpander(spec);
|
|
50857
|
+
for (const match of [...replaced].reverse()) {
|
|
50858
|
+
replaceAcrossParagraphs(doc, match, expand(match.text), spec.view);
|
|
50859
|
+
}
|
|
50860
|
+
}
|
|
50861
|
+
return { totalMatches: matches.length, replaced, ...normalization };
|
|
50862
|
+
}
|
|
50863
|
+
function selectMatches(matches, options) {
|
|
50864
|
+
if (options.limit !== undefined)
|
|
50865
|
+
return matches.slice(0, options.limit);
|
|
50866
|
+
return options.all ? matches : matches.slice(0, 1);
|
|
50867
|
+
}
|
|
50868
|
+
function replacementExpander(spec) {
|
|
50869
|
+
if (!spec.regex)
|
|
50870
|
+
return () => spec.replacement;
|
|
50871
|
+
const regex = new RegExp(spec.pattern, spec.ignoreCase ? "i" : "");
|
|
50872
|
+
return (matchText) => matchText.replace(regex, spec.replacement);
|
|
50574
50873
|
}
|
|
50575
50874
|
function findFormattedSpans(doc, filter, view = "accepted") {
|
|
50576
50875
|
const out = [];
|
|
@@ -50590,6 +50889,7 @@ function findFormattedSpans(doc, filter, view = "accepted") {
|
|
|
50590
50889
|
for (const run of block.runs) {
|
|
50591
50890
|
if (run.type !== "text") {
|
|
50592
50891
|
flush(offset2);
|
|
50892
|
+
offset2 += runViewText(run, view).length;
|
|
50593
50893
|
continue;
|
|
50594
50894
|
}
|
|
50595
50895
|
if (!isRunVisibleInView(run.trackedChange?.kind, view))
|
|
@@ -50694,15 +50994,23 @@ function collectMatches(blocks, matcher, view, out) {
|
|
|
50694
50994
|
}
|
|
50695
50995
|
function paragraphTextForView(paragraph, view) {
|
|
50696
50996
|
let out = "";
|
|
50697
|
-
for (const run of paragraph.runs)
|
|
50698
|
-
|
|
50699
|
-
continue;
|
|
50700
|
-
if (!isRunVisibleInView(run.trackedChange?.kind, view))
|
|
50701
|
-
continue;
|
|
50702
|
-
out += run.text;
|
|
50703
|
-
}
|
|
50997
|
+
for (const run of paragraph.runs)
|
|
50998
|
+
out += runViewText(run, view);
|
|
50704
50999
|
return out;
|
|
50705
51000
|
}
|
|
51001
|
+
function runViewText(run, view) {
|
|
51002
|
+
if (run.type === "text") {
|
|
51003
|
+
return isRunVisibleInView(run.trackedChange?.kind, view) ? run.text : "";
|
|
51004
|
+
}
|
|
51005
|
+
if (run.type === "tab") {
|
|
51006
|
+
return isRunVisibleInView(run.trackedChange?.kind, view) ? "\t" : "";
|
|
51007
|
+
}
|
|
51008
|
+
if (run.type === "break") {
|
|
51009
|
+
return run.kind === "line" && isRunVisibleInView(run.trackedChange?.kind, view) ? `
|
|
51010
|
+
` : "";
|
|
51011
|
+
}
|
|
51012
|
+
return "";
|
|
51013
|
+
}
|
|
50706
51014
|
function isRunVisibleInView(kind, view) {
|
|
50707
51015
|
if (view === "current")
|
|
50708
51016
|
return true;
|
|
@@ -50728,10 +51036,20 @@ function normalizeQuery(query) {
|
|
|
50728
51036
|
applied.push("dashes");
|
|
50729
51037
|
result = dashNormalized;
|
|
50730
51038
|
}
|
|
51039
|
+
const whitespaceNormalized = normalizeWhitespace(result);
|
|
51040
|
+
if (whitespaceNormalized !== result) {
|
|
51041
|
+
applied.push("whitespace");
|
|
51042
|
+
result = whitespaceNormalized;
|
|
51043
|
+
}
|
|
51044
|
+
const bulletNormalized = normalizeBullets(result);
|
|
51045
|
+
if (bulletNormalized !== result) {
|
|
51046
|
+
applied.push("bullets");
|
|
51047
|
+
result = bulletNormalized;
|
|
51048
|
+
}
|
|
50731
51049
|
return { normalized: result, applied };
|
|
50732
51050
|
}
|
|
50733
51051
|
function normalizeHaystack(text2) {
|
|
50734
|
-
return normalizeDashes(normalizeQuotes(text2));
|
|
51052
|
+
return normalizeBullets(normalizeWhitespace(normalizeDashes(normalizeQuotes(text2))));
|
|
50735
51053
|
}
|
|
50736
51054
|
function normalizeQuotes(text2) {
|
|
50737
51055
|
return text2.replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
|
|
@@ -50739,6 +51057,12 @@ function normalizeQuotes(text2) {
|
|
|
50739
51057
|
function normalizeDashes(text2) {
|
|
50740
51058
|
return text2.replace(/[\u2013\u2014]/g, "-");
|
|
50741
51059
|
}
|
|
51060
|
+
function normalizeWhitespace(text2) {
|
|
51061
|
+
return text2.replace(/[\t\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000]/g, " ");
|
|
51062
|
+
}
|
|
51063
|
+
function normalizeBullets(text2) {
|
|
51064
|
+
return text2.replace(/[\u00B7\u2022\u2023\u2219\u25AA\u25E6]/g, "\u2022");
|
|
51065
|
+
}
|
|
50742
51066
|
function stripBalancedMarkdownEmphasis(text2) {
|
|
50743
51067
|
const patterns = [
|
|
50744
51068
|
/\*\*(\S(?:.*?\S)?)\*\*/g,
|
|
@@ -50758,17 +51082,15 @@ function trackedChangesOverlapping(paragraph, start, end, view) {
|
|
|
50758
51082
|
const out = [];
|
|
50759
51083
|
let offset2 = 0;
|
|
50760
51084
|
for (const run of paragraph.runs) {
|
|
50761
|
-
|
|
50762
|
-
|
|
50763
|
-
if (!isRunVisibleInView(run.trackedChange?.kind, view))
|
|
51085
|
+
const length = runViewText(run, view).length;
|
|
51086
|
+
if (length === 0)
|
|
50764
51087
|
continue;
|
|
50765
|
-
const length = run.text.length;
|
|
50766
51088
|
const runStart = offset2;
|
|
50767
51089
|
const runEnd = offset2 + length;
|
|
50768
51090
|
offset2 = runEnd;
|
|
50769
51091
|
if (runEnd <= start || runStart >= end)
|
|
50770
51092
|
continue;
|
|
50771
|
-
const change = run.trackedChange;
|
|
51093
|
+
const change = run.type === "text" || run.type === "tab" || run.type === "break" ? run.trackedChange : undefined;
|
|
50772
51094
|
if (!change)
|
|
50773
51095
|
continue;
|
|
50774
51096
|
if (seen.has(change.id))
|
|
@@ -50780,6 +51102,7 @@ function trackedChangesOverlapping(paragraph, start, end, view) {
|
|
|
50780
51102
|
}
|
|
50781
51103
|
var init_find = __esm(() => {
|
|
50782
51104
|
init_body();
|
|
51105
|
+
init_replace_across();
|
|
50783
51106
|
init_replace_span();
|
|
50784
51107
|
});
|
|
50785
51108
|
|
|
@@ -83138,6 +83461,15 @@ function invalidLineSpacing(raw) {
|
|
|
83138
83461
|
hint: "Use a multiple (1, 1.5, 2), a name (single, double), or an exact point value (e.g. 15pt, or '15pt atLeast')."
|
|
83139
83462
|
};
|
|
83140
83463
|
}
|
|
83464
|
+
function spanLocator(match) {
|
|
83465
|
+
if ("blockId" in match) {
|
|
83466
|
+
return `${match.blockId}:${match.start}-${match.end}`;
|
|
83467
|
+
}
|
|
83468
|
+
if (match.startBlockId === match.endBlockId) {
|
|
83469
|
+
return `${match.startBlockId}:${match.startOffset}-${match.endOffset}`;
|
|
83470
|
+
}
|
|
83471
|
+
return `${match.startBlockId}:${match.startOffset}-${match.endBlockId}:${match.endOffset}`;
|
|
83472
|
+
}
|
|
83141
83473
|
var PAGE_SIZES, RUN_FORMAT_FLAGS, TWIPS_PER_POINT = 20, TWIPS_PER_INCH = 1440, LINE_UNITS_PER_MULTIPLE = 240;
|
|
83142
83474
|
var init_parse_helpers = __esm(() => {
|
|
83143
83475
|
init_core2();
|
|
@@ -87186,7 +87518,7 @@ async function run15(args) {
|
|
|
87186
87518
|
return EXIT2.OK;
|
|
87187
87519
|
}
|
|
87188
87520
|
const path2 = parsed.positionals[0];
|
|
87189
|
-
const query = parsed.positionals[1];
|
|
87521
|
+
const query = decodeInlineEscapes(parsed.positionals[1]);
|
|
87190
87522
|
if (!path2)
|
|
87191
87523
|
return fail("USAGE", "Missing FILE argument", HELP11);
|
|
87192
87524
|
const formatFilter = {};
|
|
@@ -87222,56 +87554,63 @@ async function run15(args) {
|
|
|
87222
87554
|
const document4 = await openOrFail(path2);
|
|
87223
87555
|
if (typeof document4 === "number")
|
|
87224
87556
|
return document4;
|
|
87225
|
-
let
|
|
87226
|
-
|
|
87227
|
-
|
|
87228
|
-
|
|
87229
|
-
|
|
87230
|
-
|
|
87231
|
-
try {
|
|
87232
|
-
result = findTextSpans(document4.body, query, {
|
|
87557
|
+
let matches = [];
|
|
87558
|
+
let normalizationFields = {};
|
|
87559
|
+
try {
|
|
87560
|
+
if (query?.includes(`
|
|
87561
|
+
`)) {
|
|
87562
|
+
const result = findAcrossParagraphs(document4.body, query, {
|
|
87233
87563
|
regex: useRegex,
|
|
87234
87564
|
ignoreCase,
|
|
87235
87565
|
view: findView,
|
|
87236
87566
|
exact
|
|
87237
87567
|
});
|
|
87238
|
-
|
|
87239
|
-
|
|
87240
|
-
|
|
87568
|
+
matches = result.matches.map((match) => ({
|
|
87569
|
+
locator: spanLocator(match),
|
|
87570
|
+
...match
|
|
87571
|
+
}));
|
|
87572
|
+
normalizationFields = normalizationPayload(result);
|
|
87573
|
+
} else if (hasFormatFilter) {
|
|
87574
|
+
matches = findFormattedSpans(document4.body, formatFilter, findView).map((match) => ({ locator: spanLocator(match), ...match }));
|
|
87575
|
+
} else if (query != null) {
|
|
87576
|
+
const result = findTextSpans(document4.body, query, {
|
|
87577
|
+
regex: useRegex,
|
|
87578
|
+
ignoreCase,
|
|
87579
|
+
view: findView,
|
|
87580
|
+
exact
|
|
87581
|
+
});
|
|
87582
|
+
matches = result.matches.map((match) => ({
|
|
87583
|
+
locator: spanLocator(match),
|
|
87584
|
+
...match
|
|
87585
|
+
}));
|
|
87586
|
+
normalizationFields = normalizationPayload(result);
|
|
87241
87587
|
}
|
|
87588
|
+
} catch (matcherError) {
|
|
87589
|
+
const message = matcherError instanceof Error ? matcherError.message : String(matcherError);
|
|
87590
|
+
return fail("USAGE", `Invalid query: ${message}`);
|
|
87242
87591
|
}
|
|
87243
|
-
|
|
87244
|
-
let selected;
|
|
87592
|
+
let selected = matches;
|
|
87245
87593
|
if (nth !== undefined) {
|
|
87246
|
-
const single =
|
|
87594
|
+
const single = matches[nth];
|
|
87247
87595
|
if (!single) {
|
|
87248
|
-
return fail("MATCH_NOT_FOUND", `Only ${
|
|
87596
|
+
return fail("MATCH_NOT_FOUND", `Only ${matches.length} match(es); --nth ${nth} is out of range`);
|
|
87249
87597
|
}
|
|
87250
87598
|
selected = [single];
|
|
87251
|
-
} else {
|
|
87252
|
-
selected = allMatches;
|
|
87253
87599
|
}
|
|
87254
|
-
const matches = selected.map((match) => ({
|
|
87255
|
-
locator: `${match.blockId}:${match.start}-${match.end}`,
|
|
87256
|
-
...match
|
|
87257
|
-
}));
|
|
87258
87600
|
if (parsed.values.json) {
|
|
87259
87601
|
await respond({
|
|
87260
|
-
totalMatches:
|
|
87602
|
+
totalMatches: matches.length,
|
|
87261
87603
|
query,
|
|
87262
87604
|
regex: useRegex,
|
|
87263
87605
|
ignoreCase,
|
|
87264
87606
|
view: findView,
|
|
87265
|
-
matches,
|
|
87266
|
-
...
|
|
87267
|
-
normalizedQuery: result.normalizedQuery,
|
|
87268
|
-
normalizationApplied: result.normalizationApplied
|
|
87269
|
-
} : {}
|
|
87607
|
+
matches: selected,
|
|
87608
|
+
...normalizationFields
|
|
87270
87609
|
});
|
|
87271
87610
|
return EXIT2.OK;
|
|
87272
87611
|
}
|
|
87273
|
-
if (
|
|
87274
|
-
await writeStdout(`${
|
|
87612
|
+
if (selected.length > 0) {
|
|
87613
|
+
await writeStdout(`${selected.map((match) => match.locator).join(`
|
|
87275
87614
|
`)}
|
|
87276
87615
|
`);
|
|
87277
87616
|
} else {
|
|
@@ -87280,6 +87619,12 @@ async function run15(args) {
|
|
|
87280
87619
|
}
|
|
87281
87620
|
return EXIT2.OK;
|
|
87282
87621
|
}
|
|
87622
|
+
function normalizationPayload(result) {
|
|
87623
|
+
return result.normalizedQuery !== undefined ? {
|
|
87624
|
+
normalizedQuery: result.normalizedQuery,
|
|
87625
|
+
normalizationApplied: result.normalizationApplied
|
|
87626
|
+
} : {};
|
|
87627
|
+
}
|
|
87283
87628
|
var HELP11 = `docx find \u2014 locate text spans and return their locators
|
|
87284
87629
|
|
|
87285
87630
|
Usage:
|
|
@@ -87317,15 +87662,21 @@ Options:
|
|
|
87317
87662
|
--json emit the full match objects as JSON (default: bare locators)
|
|
87318
87663
|
-h, --help show this help
|
|
87319
87664
|
|
|
87320
|
-
|
|
87321
|
-
|
|
87322
|
-
|
|
87323
|
-
|
|
87665
|
+
Searches the concatenated text of each paragraph in document order, including
|
|
87666
|
+
paragraphs nested in table cells (locators look like tT:rRcC:pK:S-E for those).
|
|
87667
|
+
A TAB in the document matches as one character, and an in-paragraph line break
|
|
87668
|
+
matches "\\n". A QUERY containing "\\n" may span lines: each "\\n" matches a
|
|
87669
|
+
line break OR a paragraph boundary (consecutive paragraphs in the body or
|
|
87670
|
+
within ONE table cell \u2014 never across a table, section break, or cell wall). A spanning
|
|
87671
|
+
match prints as pA:S-pB:E \u2014 the cross-paragraph range form \`comments add
|
|
87672
|
+
--at\` accepts, so a spanning find pipes straight into a spanning comment.
|
|
87324
87673
|
|
|
87325
87674
|
By default the query is normalized: balanced markdown emphasis around
|
|
87326
87675
|
non-whitespace (**X**, __X__, *X*, \`X\`) is stripped; smart quotes match
|
|
87327
|
-
straight quotes; em-dash and en-dash match the hyphen
|
|
87328
|
-
|
|
87676
|
+
straight quotes; em-dash and en-dash match the hyphen; a TAB or non-breaking/
|
|
87677
|
+
typographic space matches a plain space; bullet glyph variants (\xB7\u2022\u2023\u2219\u25AA\u25E6) match
|
|
87678
|
+
each other. Pass --exact to match the raw query verbatim. --regex is always
|
|
87679
|
+
verbatim. \\n and \\t in the QUERY are decoded to real characters first.
|
|
87329
87680
|
|
|
87330
87681
|
Output:
|
|
87331
87682
|
Default: EVERY matched span locator (e.g. p3:5-8), one per line \u2014 feed them
|
|
@@ -90261,10 +90612,19 @@ export type ImageRun = {
|
|
|
90261
90612
|
export type BreakRun = {
|
|
90262
90613
|
type: "break";
|
|
90263
90614
|
kind: "page" | "line" | "column";
|
|
90615
|
+
/** Set when the break sits inside a tracked-change wrapper (\`<w:ins>\`/
|
|
90616
|
+
* \`<w:del>\`/\u2026). A LINE break is one offset character (it renders as "\\n"),
|
|
90617
|
+
* so \`find\`/\`replace\` must know when to hide it in the accepted/baseline
|
|
90618
|
+
* view, exactly as they do for a deleted \`TextRun\`. */
|
|
90619
|
+
trackedChange?: TrackedChange;
|
|
90264
90620
|
};
|
|
90265
90621
|
|
|
90266
90622
|
export type TabRun = {
|
|
90267
90623
|
type: "tab";
|
|
90624
|
+
/** Set when the tab sits inside a tracked-change wrapper. A tab is one offset
|
|
90625
|
+
* character (it renders as "\\t"), so its view-visibility must track with the
|
|
90626
|
+
* surrounding revision \u2014 see \`BreakRun.trackedChange\`. */
|
|
90627
|
+
trackedChange?: TrackedChange;
|
|
90268
90628
|
};
|
|
90269
90629
|
|
|
90270
90630
|
/** A math equation. \`latex\` is reconstructed by walking the underlying
|
|
@@ -91707,7 +92067,7 @@ function renderMarkdown2(doc, options = {}) {
|
|
|
91707
92067
|
return "";
|
|
91708
92068
|
const baseNote = formatBaseNote(noteBaseline);
|
|
91709
92069
|
const pageNote = formatPageNote(documentGeometry(blocks));
|
|
91710
|
-
const trackNote =
|
|
92070
|
+
const trackNote = formatNote("track-changes", [], [options.trackChangesOn ? "on" : "off"]);
|
|
91711
92071
|
const wrapSummary = formatWrappingTabSummary(ctx.wrappingTabLines);
|
|
91712
92072
|
const headLines = [
|
|
91713
92073
|
baseNote,
|
|
@@ -93106,6 +93466,98 @@ var init_render2 = __esm(() => {
|
|
|
93106
93466
|
};
|
|
93107
93467
|
});
|
|
93108
93468
|
|
|
93469
|
+
// src/cli/replace/across.ts
|
|
93470
|
+
async function runReplaceAcross(path2, pattern, replacement, options) {
|
|
93471
|
+
const document4 = await openOrFail(path2);
|
|
93472
|
+
if (typeof document4 === "number")
|
|
93473
|
+
return document4;
|
|
93474
|
+
if (resolveTracked(document4, options.track)) {
|
|
93475
|
+
return fail("USAGE", ACROSS_TRACKED_MESSAGE, ACROSS_TRACKED_HINT);
|
|
93476
|
+
}
|
|
93477
|
+
let result;
|
|
93478
|
+
try {
|
|
93479
|
+
result = applyAcrossReplace(document4.body, {
|
|
93480
|
+
pattern,
|
|
93481
|
+
replacement,
|
|
93482
|
+
regex: options.regex,
|
|
93483
|
+
ignoreCase: options.ignoreCase,
|
|
93484
|
+
exact: options.exact,
|
|
93485
|
+
all: options.all,
|
|
93486
|
+
...options.limit !== undefined ? { limit: options.limit } : {},
|
|
93487
|
+
view: options.view
|
|
93488
|
+
}, { dryRun: options.dryRun });
|
|
93489
|
+
} catch (matcherError) {
|
|
93490
|
+
const message = matcherError instanceof Error ? matcherError.message : String(matcherError);
|
|
93491
|
+
return fail("USAGE", `Invalid pattern: ${message}`);
|
|
93492
|
+
}
|
|
93493
|
+
const matchesPayload = result.replaced.map((match) => ({
|
|
93494
|
+
locator: spanLocator(match),
|
|
93495
|
+
text: match.text
|
|
93496
|
+
}));
|
|
93497
|
+
const normalizationFields = result.normalizedQuery !== undefined ? {
|
|
93498
|
+
normalizedPattern: result.normalizedQuery,
|
|
93499
|
+
normalizationApplied: result.normalizationApplied
|
|
93500
|
+
} : {};
|
|
93501
|
+
if (options.dryRun) {
|
|
93502
|
+
await respond({
|
|
93503
|
+
operation: "replace",
|
|
93504
|
+
dryRun: true,
|
|
93505
|
+
path: path2,
|
|
93506
|
+
pattern,
|
|
93507
|
+
replacement,
|
|
93508
|
+
regex: options.regex,
|
|
93509
|
+
ignoreCase: options.ignoreCase,
|
|
93510
|
+
view: options.view,
|
|
93511
|
+
crossParagraph: true,
|
|
93512
|
+
totalMatches: result.totalMatches,
|
|
93513
|
+
replaced: result.replaced.length,
|
|
93514
|
+
matches: matchesPayload,
|
|
93515
|
+
...normalizationFields,
|
|
93516
|
+
...options.output ? { output: options.output } : {}
|
|
93517
|
+
});
|
|
93518
|
+
return EXIT2.OK;
|
|
93519
|
+
}
|
|
93520
|
+
if (result.replaced.length === 0) {
|
|
93521
|
+
return await fail("MATCH_NOT_FOUND", `Pattern not found: ${JSON.stringify(pattern)} \u2014 0 occurrences, nothing changed.`, `Each \\n in the pattern matches a line break or the boundary between CONSECUTIVE paragraphs (never across a table, section break, or cell wall). \`docx read ${path2}\` shows the lines \u2014 check the exact text on each side of every \\n.`);
|
|
93522
|
+
}
|
|
93523
|
+
await document4.save(options.output);
|
|
93524
|
+
const hints = [
|
|
93525
|
+
"\u21B3 paragraph structure changed \u2014 block ids shifted; re-read before locator-addressed edits."
|
|
93526
|
+
];
|
|
93527
|
+
const partialHint = partialReplaceHint(result.replaced.length, result.totalMatches);
|
|
93528
|
+
if (partialHint)
|
|
93529
|
+
hints.push(partialHint);
|
|
93530
|
+
await respondAck({
|
|
93531
|
+
ok: true,
|
|
93532
|
+
operation: "replace",
|
|
93533
|
+
path: options.output ?? path2,
|
|
93534
|
+
pattern,
|
|
93535
|
+
replacement,
|
|
93536
|
+
regex: options.regex,
|
|
93537
|
+
ignoreCase: options.ignoreCase,
|
|
93538
|
+
view: options.view,
|
|
93539
|
+
crossParagraph: true,
|
|
93540
|
+
totalMatches: result.totalMatches,
|
|
93541
|
+
replaced: result.replaced.length,
|
|
93542
|
+
matches: matchesPayload,
|
|
93543
|
+
...normalizationFields
|
|
93544
|
+
}, hints.join(`
|
|
93545
|
+
`));
|
|
93546
|
+
return EXIT2.OK;
|
|
93547
|
+
}
|
|
93548
|
+
function partialReplaceHint(replaced, total) {
|
|
93549
|
+
const remaining = total - replaced;
|
|
93550
|
+
if (remaining <= 0)
|
|
93551
|
+
return;
|
|
93552
|
+
return `\u21B3 ${remaining} more match${remaining === 1 ? "" : "es"} left unreplaced (${replaced} of ${total} done) \u2014 pass --all to replace every match, or --limit N for a specific count.`;
|
|
93553
|
+
}
|
|
93554
|
+
var ACROSS_TRACKED_MESSAGE = "a replace that crosses or inserts paragraph boundaries can't be recorded as a tracked change yet (OOXML tracks a merge/split on the paragraph mark, which this path doesn't emit) \u2014 it will NOT run silently untracked", ACROSS_TRACKED_HINT = "Turn tracking off (docx track-changes off FILE) and re-run, or make within-paragraph replaces (no \\n) which track fine.";
|
|
93555
|
+
var init_across = __esm(() => {
|
|
93556
|
+
init_find();
|
|
93557
|
+
init_parse_helpers();
|
|
93558
|
+
init_respond();
|
|
93559
|
+
});
|
|
93560
|
+
|
|
93109
93561
|
// src/cli/replace/scope.ts
|
|
93110
93562
|
function validateScopeShape(at) {
|
|
93111
93563
|
let parsed;
|
|
@@ -93188,6 +93640,36 @@ async function runReplaceBatch(filePath, batchSource, values2) {
|
|
|
93188
93640
|
const spec = specs[index2];
|
|
93189
93641
|
if (!spec)
|
|
93190
93642
|
continue;
|
|
93643
|
+
if (spec.pattern.includes(`
|
|
93644
|
+
`) || spec.replacement.includes(`
|
|
93645
|
+
`)) {
|
|
93646
|
+
if (spec.at !== undefined) {
|
|
93647
|
+
return fail("USAGE", `entry ${index2}: "at" can't scope a multi-line pattern \u2014 it spans paragraph boundaries`);
|
|
93648
|
+
}
|
|
93649
|
+
if (allocator) {
|
|
93650
|
+
return fail("USAGE", `entry ${index2}: ${ACROSS_TRACKED_MESSAGE}`, ACROSS_TRACKED_HINT);
|
|
93651
|
+
}
|
|
93652
|
+
let acrossResult;
|
|
93653
|
+
try {
|
|
93654
|
+
acrossResult = applyAcrossReplace(document4.body, spec);
|
|
93655
|
+
} catch (matcherError) {
|
|
93656
|
+
const message = matcherError instanceof Error ? matcherError.message : String(matcherError);
|
|
93657
|
+
return fail("USAGE", `entry ${index2}: invalid pattern: ${message}`);
|
|
93658
|
+
}
|
|
93659
|
+
results.push({
|
|
93660
|
+
pattern: spec.pattern,
|
|
93661
|
+
replacement: spec.replacement,
|
|
93662
|
+
totalMatches: acrossResult.totalMatches,
|
|
93663
|
+
replaced: acrossResult.replaced.length,
|
|
93664
|
+
matches: acrossResult.replaced.map((match) => ({
|
|
93665
|
+
locator: spanLocator(match),
|
|
93666
|
+
text: match.text
|
|
93667
|
+
}))
|
|
93668
|
+
});
|
|
93669
|
+
if (acrossResult.replaced.length > 0)
|
|
93670
|
+
document4.reread();
|
|
93671
|
+
continue;
|
|
93672
|
+
}
|
|
93191
93673
|
if (spec.at !== undefined) {
|
|
93192
93674
|
try {
|
|
93193
93675
|
document4.body.resolveBlock(spec.at);
|
|
@@ -93208,7 +93690,7 @@ async function runReplaceBatch(filePath, batchSource, values2) {
|
|
|
93208
93690
|
return fail("USAGE", `entry ${index2}: invalid pattern: ${message}`);
|
|
93209
93691
|
}
|
|
93210
93692
|
const all2 = spec.at !== undefined ? matchesInScope(findResult.matches, spec.at) : findResult.matches;
|
|
93211
|
-
const selected =
|
|
93693
|
+
const selected = selectMatches(all2, spec);
|
|
93212
93694
|
const tracked = allocator ? {
|
|
93213
93695
|
meta: {
|
|
93214
93696
|
author: resolveAuthor(spec.author ?? authorFlag),
|
|
@@ -93222,11 +93704,10 @@ async function runReplaceBatch(filePath, batchSource, values2) {
|
|
|
93222
93704
|
}
|
|
93223
93705
|
return right.start - left.start;
|
|
93224
93706
|
});
|
|
93225
|
-
const
|
|
93707
|
+
const expand = replacementExpander(spec);
|
|
93226
93708
|
for (const match of reversed) {
|
|
93227
|
-
const concreteReplacement = spec.regex ? match.text.replace(new RegExp(spec.pattern, regexFlags), spec.replacement) : spec.replacement;
|
|
93228
93709
|
const blockRef = document4.body.resolveBlock(match.blockId);
|
|
93229
|
-
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end },
|
|
93710
|
+
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end }, expand(match.text), tracked, spec.view);
|
|
93230
93711
|
}
|
|
93231
93712
|
results.push({
|
|
93232
93713
|
pattern: spec.pattern,
|
|
@@ -93234,11 +93715,12 @@ async function runReplaceBatch(filePath, batchSource, values2) {
|
|
|
93234
93715
|
totalMatches: all2.length,
|
|
93235
93716
|
replaced: selected.length,
|
|
93236
93717
|
matches: selected.map((match) => ({
|
|
93237
|
-
locator:
|
|
93718
|
+
locator: spanLocator(match),
|
|
93238
93719
|
text: match.text
|
|
93239
93720
|
}))
|
|
93240
93721
|
});
|
|
93241
|
-
|
|
93722
|
+
if (selected.length > 0)
|
|
93723
|
+
document4.reread();
|
|
93242
93724
|
}
|
|
93243
93725
|
if (dryRun) {
|
|
93244
93726
|
await respond({
|
|
@@ -93319,6 +93801,7 @@ var init_batch4 = __esm(() => {
|
|
|
93319
93801
|
init_find();
|
|
93320
93802
|
init_parse_helpers();
|
|
93321
93803
|
init_respond();
|
|
93804
|
+
init_across();
|
|
93322
93805
|
init_scope();
|
|
93323
93806
|
EntryError3 = class EntryError3 extends Error {
|
|
93324
93807
|
code;
|
|
@@ -93372,8 +93855,8 @@ async function run40(args) {
|
|
|
93372
93855
|
}
|
|
93373
93856
|
return runReplaceBatch(path2, batchInput, parsed.values);
|
|
93374
93857
|
}
|
|
93375
|
-
const pattern = parsed.positionals[1];
|
|
93376
|
-
const replacement = parsed.positionals[2];
|
|
93858
|
+
const pattern = decodeInlineEscapes(parsed.positionals[1]);
|
|
93859
|
+
const replacement = decodeInlineEscapes(parsed.positionals[2]);
|
|
93377
93860
|
if (pattern == null)
|
|
93378
93861
|
return fail("USAGE", "Missing PATTERN argument", HELP36);
|
|
93379
93862
|
if (replacement == null) {
|
|
@@ -93392,6 +93875,24 @@ async function run40(args) {
|
|
|
93392
93875
|
if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
|
|
93393
93876
|
return fail("USAGE", `--limit must be a positive integer, got "${limitRaw}"`);
|
|
93394
93877
|
}
|
|
93878
|
+
if (pattern.includes(`
|
|
93879
|
+
`) || replacement.includes(`
|
|
93880
|
+
`)) {
|
|
93881
|
+
if (parsed.values.at !== undefined) {
|
|
93882
|
+
return fail("USAGE", "--at scopes a replace to ONE paragraph, but a \\n-bearing pattern/replacement spans paragraph boundaries \u2014 drop --at, or remove the \\n");
|
|
93883
|
+
}
|
|
93884
|
+
return runReplaceAcross(path2, pattern, replacement, {
|
|
93885
|
+
regex: useRegex,
|
|
93886
|
+
ignoreCase,
|
|
93887
|
+
exact,
|
|
93888
|
+
all: wantAll,
|
|
93889
|
+
...limit !== undefined ? { limit } : {},
|
|
93890
|
+
view: findView,
|
|
93891
|
+
track: Boolean(parsed.values.track),
|
|
93892
|
+
...parsed.values.output !== undefined ? { output: parsed.values.output } : {},
|
|
93893
|
+
dryRun: Boolean(parsed.values["dry-run"])
|
|
93894
|
+
});
|
|
93895
|
+
}
|
|
93395
93896
|
const document4 = await openOrFail(path2);
|
|
93396
93897
|
if (typeof document4 === "number")
|
|
93397
93898
|
return document4;
|
|
@@ -93424,16 +93925,9 @@ async function run40(args) {
|
|
|
93424
93925
|
normalizedPattern: findResult.normalizedQuery,
|
|
93425
93926
|
normalizationApplied: findResult.normalizationApplied
|
|
93426
93927
|
} : {};
|
|
93427
|
-
|
|
93428
|
-
if (limit !== undefined) {
|
|
93429
|
-
selected = allMatches.slice(0, limit);
|
|
93430
|
-
} else if (wantAll) {
|
|
93431
|
-
selected = allMatches;
|
|
93432
|
-
} else {
|
|
93433
|
-
selected = allMatches.slice(0, 1);
|
|
93434
|
-
}
|
|
93928
|
+
const selected = selectMatches(allMatches, { all: wantAll, limit });
|
|
93435
93929
|
const matchesPayload = selected.map((match) => ({
|
|
93436
|
-
locator:
|
|
93930
|
+
locator: spanLocator(match),
|
|
93437
93931
|
blockId: match.blockId,
|
|
93438
93932
|
start: match.start,
|
|
93439
93933
|
end: match.end,
|
|
@@ -93474,15 +93968,18 @@ async function run40(args) {
|
|
|
93474
93968
|
meta: { author: resolveAuthor(authorFlag), date: resolveDate() },
|
|
93475
93969
|
allocator: new TrackChanges(document4).createAllocator()
|
|
93476
93970
|
} : undefined;
|
|
93477
|
-
const
|
|
93971
|
+
const expand = replacementExpander({
|
|
93972
|
+
pattern,
|
|
93973
|
+
replacement,
|
|
93974
|
+
regex: useRegex,
|
|
93975
|
+
ignoreCase
|
|
93976
|
+
});
|
|
93478
93977
|
for (const match of reversed) {
|
|
93479
|
-
const concreteReplacement = useRegex ? match.text.replace(new RegExp(pattern, regexFlags), replacement) : replacement;
|
|
93480
93978
|
const blockRef = document4.body.resolveBlock(match.blockId);
|
|
93481
|
-
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end },
|
|
93979
|
+
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end }, expand(match.text), tracked, findView);
|
|
93482
93980
|
}
|
|
93483
93981
|
await document4.save(outputPath);
|
|
93484
|
-
const
|
|
93485
|
-
const partialHint = remaining > 0 ? `\u21B3 ${remaining} more match${remaining === 1 ? "" : "es"} left unreplaced (${selected.length} of ${allMatches.length} done) \u2014 pass --all to replace every match, or --limit N for a specific count.` : undefined;
|
|
93982
|
+
const partialHint = partialReplaceHint(selected.length, allMatches.length);
|
|
93486
93983
|
await respondAck({
|
|
93487
93984
|
ok: true,
|
|
93488
93985
|
operation: "replace",
|
|
@@ -93534,17 +94031,32 @@ Options:
|
|
|
93534
94031
|
-v, --verbose print the success ack JSON (default: a one-line confirmation)
|
|
93535
94032
|
-h, --help show this help
|
|
93536
94033
|
|
|
93537
|
-
|
|
93538
|
-
|
|
93539
|
-
|
|
93540
|
-
|
|
93541
|
-
|
|
94034
|
+
Run formatting (rPr) on the surrounding text is preserved; the replacement
|
|
94035
|
+
run inherits the rPr of the first run that overlaps the matched span. Tabs
|
|
94036
|
+
and other runs in the paragraph are left in place \u2014 only the matched text
|
|
94037
|
+
changes (a TAB matches as one character, so a pattern typed with spaces fills
|
|
94038
|
+
a tab-separated line). So this is the no-rebuild way to FILL a formatted/
|
|
94039
|
+
tabbed template line: \`replace "Organization Name" "Northwind Robotics"\`
|
|
93542
94040
|
on a "**Organization Name**\u21E5Date" line keeps the bold and the tab; don't
|
|
93543
94041
|
hand-build \`edit --runs\` JSON to refill a line. \`--batch\` fills many at once.
|
|
93544
94042
|
When a single invocation produces multiple replacements in the same paragraph,
|
|
93545
94043
|
they're applied in reverse offset order so earlier offsets don't shift before
|
|
93546
94044
|
being applied.
|
|
93547
94045
|
|
|
94046
|
+
Multi-line (editor-style): \\n and \\t in PATTERN/REPLACEMENT are decoded to
|
|
94047
|
+
real characters. A "\\n" in the PATTERN matches an in-paragraph line break OR
|
|
94048
|
+
the boundary between consecutive paragraphs \u2014 in the body or within one table
|
|
94049
|
+
cell (never across a table, section break, or cell wall); the REPLACEMENT's
|
|
94050
|
+
newlines then define the resulting
|
|
94051
|
+
structure, exactly as if the span were selected in Word and the replacement
|
|
94052
|
+
typed \u2014 a single-line replacement MERGES the spanned paragraphs into one
|
|
94053
|
+
(first paragraph's formatting governs), "\\n"s in the replacement keep/create
|
|
94054
|
+
paragraph marks (so a "\\n" in the replacement splits a paragraph even when
|
|
94055
|
+
the pattern didn't cross one). \`replace FILE "\\n" "" --all\` merges every
|
|
94056
|
+
paragraph pair. Cross-paragraph replaces can't be tracked yet: with tracking
|
|
94057
|
+
on (or --track) they refuse loudly rather than skip the journal \u2014 turn
|
|
94058
|
+
tracking off first. Block ids shift afterward; re-read before more edits.
|
|
94059
|
+
|
|
93548
94060
|
By default the PATTERN is normalized: balanced markdown emphasis around
|
|
93549
94061
|
non-whitespace (**X**, __X__, *X*, \`X\`) is stripped; smart quotes match
|
|
93550
94062
|
straight quotes; em-dash and en-dash match the hyphen. The REPLACEMENT
|
|
@@ -93591,6 +94103,7 @@ var init_replace4 = __esm(() => {
|
|
|
93591
94103
|
init_find();
|
|
93592
94104
|
init_parse_helpers();
|
|
93593
94105
|
init_respond();
|
|
94106
|
+
init_across();
|
|
93594
94107
|
init_batch4();
|
|
93595
94108
|
init_scope();
|
|
93596
94109
|
});
|
|
@@ -98015,7 +98528,7 @@ Examples:
|
|
|
98015
98528
|
// package.json
|
|
98016
98529
|
var package_default = {
|
|
98017
98530
|
name: "bun-docx",
|
|
98018
|
-
version: "0.20.
|
|
98531
|
+
version: "0.20.4",
|
|
98019
98532
|
description: "Read, edit, redline, and comment on Microsoft Word .docx files from the command line \u2014 built for AI agents.",
|
|
98020
98533
|
keywords: [
|
|
98021
98534
|
"docx",
|