bun-docx 0.7.0 → 0.8.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/README.md +95 -106
- package/dist/index.js +2815 -931
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13576,10 +13576,11 @@ function isSubtractiveTrackedChangeWrapper(tag) {
|
|
|
13576
13576
|
function runTextLength(run) {
|
|
13577
13577
|
let total = 0;
|
|
13578
13578
|
for (const child of run.children) {
|
|
13579
|
-
if (child.tag === "w:t")
|
|
13579
|
+
if (child.tag === "w:t" || child.tag === "w:delText") {
|
|
13580
13580
|
total += child.collectText().length;
|
|
13581
|
-
else if (SINGLE_CHAR_TAGS.has(child.tag))
|
|
13581
|
+
} else if (SINGLE_CHAR_TAGS.has(child.tag)) {
|
|
13582
13582
|
total += 1;
|
|
13583
|
+
}
|
|
13583
13584
|
}
|
|
13584
13585
|
return total;
|
|
13585
13586
|
}
|
|
@@ -13805,6 +13806,322 @@ var init_package = __esm(() => {
|
|
|
13805
13806
|
};
|
|
13806
13807
|
});
|
|
13807
13808
|
|
|
13809
|
+
// src/core/jsx/index.ts
|
|
13810
|
+
function normalizeChildren(children) {
|
|
13811
|
+
if (children === undefined || children === null)
|
|
13812
|
+
return [];
|
|
13813
|
+
if (Array.isArray(children))
|
|
13814
|
+
return children;
|
|
13815
|
+
return [children];
|
|
13816
|
+
}
|
|
13817
|
+
function namespace(prefix, tags) {
|
|
13818
|
+
const result = {};
|
|
13819
|
+
for (const tagName of tags) {
|
|
13820
|
+
result[tagName] = makeTag(`${prefix}:${tagName}`);
|
|
13821
|
+
}
|
|
13822
|
+
return result;
|
|
13823
|
+
}
|
|
13824
|
+
function makeTag(qualifiedName) {
|
|
13825
|
+
return (props) => {
|
|
13826
|
+
const attributes = {};
|
|
13827
|
+
let childrenProp;
|
|
13828
|
+
if (props) {
|
|
13829
|
+
for (const [key, value] of Object.entries(props)) {
|
|
13830
|
+
if (key === "children") {
|
|
13831
|
+
childrenProp = value;
|
|
13832
|
+
continue;
|
|
13833
|
+
}
|
|
13834
|
+
if (value === false || value == null)
|
|
13835
|
+
continue;
|
|
13836
|
+
attributes[mapAttributeName(key)] = String(value);
|
|
13837
|
+
}
|
|
13838
|
+
}
|
|
13839
|
+
const childNodes = [];
|
|
13840
|
+
flatten(normalizeChildren(childrenProp), childNodes);
|
|
13841
|
+
return new XmlNode2(qualifiedName, attributes, childNodes);
|
|
13842
|
+
};
|
|
13843
|
+
}
|
|
13844
|
+
function flatten(items, out) {
|
|
13845
|
+
for (const item of items) {
|
|
13846
|
+
if (item == null || item === false)
|
|
13847
|
+
continue;
|
|
13848
|
+
if (Array.isArray(item)) {
|
|
13849
|
+
flatten(item, out);
|
|
13850
|
+
continue;
|
|
13851
|
+
}
|
|
13852
|
+
if (item instanceof XmlNode2) {
|
|
13853
|
+
if (item.tag === FRAGMENT_TAG) {
|
|
13854
|
+
for (const child of item.children)
|
|
13855
|
+
out.push(child);
|
|
13856
|
+
continue;
|
|
13857
|
+
}
|
|
13858
|
+
out.push(item);
|
|
13859
|
+
continue;
|
|
13860
|
+
}
|
|
13861
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
13862
|
+
out.push(XmlNode2.textNode(String(item)));
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
}
|
|
13866
|
+
function mapAttributeName(name) {
|
|
13867
|
+
const dashIndex = name.indexOf("-");
|
|
13868
|
+
if (dashIndex === -1)
|
|
13869
|
+
return name;
|
|
13870
|
+
return `${name.slice(0, dashIndex)}:${name.slice(dashIndex + 1)}`;
|
|
13871
|
+
}
|
|
13872
|
+
var FRAGMENT_TAG = "#fragment", W_TAGS, R_TAGS, A_TAGS, WP_TAGS, PIC_TAGS, CP_TAGS, DC_TAGS, DCTERMS_TAGS, W14_TAGS, W15_TAGS, w, r, a, wp, pic, cp, dc, dcterms, w14, w15;
|
|
13873
|
+
var init_jsx = __esm(() => {
|
|
13874
|
+
init_parser();
|
|
13875
|
+
W_TAGS = [
|
|
13876
|
+
"document",
|
|
13877
|
+
"body",
|
|
13878
|
+
"p",
|
|
13879
|
+
"pPr",
|
|
13880
|
+
"pStyle",
|
|
13881
|
+
"jc",
|
|
13882
|
+
"numPr",
|
|
13883
|
+
"ilvl",
|
|
13884
|
+
"numId",
|
|
13885
|
+
"r",
|
|
13886
|
+
"rPr",
|
|
13887
|
+
"rStyle",
|
|
13888
|
+
"t",
|
|
13889
|
+
"b",
|
|
13890
|
+
"i",
|
|
13891
|
+
"u",
|
|
13892
|
+
"strike",
|
|
13893
|
+
"color",
|
|
13894
|
+
"highlight",
|
|
13895
|
+
"sz",
|
|
13896
|
+
"rFonts",
|
|
13897
|
+
"br",
|
|
13898
|
+
"tab",
|
|
13899
|
+
"drawing",
|
|
13900
|
+
"sectPr",
|
|
13901
|
+
"sectPrChange",
|
|
13902
|
+
"cols",
|
|
13903
|
+
"type",
|
|
13904
|
+
"tbl",
|
|
13905
|
+
"tblPr",
|
|
13906
|
+
"tblGrid",
|
|
13907
|
+
"gridCol",
|
|
13908
|
+
"tr",
|
|
13909
|
+
"tc",
|
|
13910
|
+
"tcPr",
|
|
13911
|
+
"ins",
|
|
13912
|
+
"del",
|
|
13913
|
+
"delText",
|
|
13914
|
+
"commentRangeStart",
|
|
13915
|
+
"commentRangeEnd",
|
|
13916
|
+
"commentReference",
|
|
13917
|
+
"comments",
|
|
13918
|
+
"comment",
|
|
13919
|
+
"settings",
|
|
13920
|
+
"trackChanges",
|
|
13921
|
+
"hyperlink"
|
|
13922
|
+
];
|
|
13923
|
+
R_TAGS = ["embed", "link", "id"];
|
|
13924
|
+
A_TAGS = [
|
|
13925
|
+
"graphic",
|
|
13926
|
+
"graphicData",
|
|
13927
|
+
"blip",
|
|
13928
|
+
"xfrm",
|
|
13929
|
+
"off",
|
|
13930
|
+
"ext",
|
|
13931
|
+
"prstGeom",
|
|
13932
|
+
"avLst"
|
|
13933
|
+
];
|
|
13934
|
+
WP_TAGS = [
|
|
13935
|
+
"inline",
|
|
13936
|
+
"anchor",
|
|
13937
|
+
"extent",
|
|
13938
|
+
"effectExtent",
|
|
13939
|
+
"docPr",
|
|
13940
|
+
"cNvGraphicFramePr"
|
|
13941
|
+
];
|
|
13942
|
+
PIC_TAGS = [
|
|
13943
|
+
"pic",
|
|
13944
|
+
"nvPicPr",
|
|
13945
|
+
"cNvPr",
|
|
13946
|
+
"cNvPicPr",
|
|
13947
|
+
"blipFill",
|
|
13948
|
+
"spPr"
|
|
13949
|
+
];
|
|
13950
|
+
CP_TAGS = ["coreProperties", "lastModifiedBy"];
|
|
13951
|
+
DC_TAGS = ["title", "creator", "subject", "description"];
|
|
13952
|
+
DCTERMS_TAGS = ["created", "modified"];
|
|
13953
|
+
W14_TAGS = ["paraId", "textId"];
|
|
13954
|
+
W15_TAGS = ["commentsEx", "commentEx"];
|
|
13955
|
+
w = namespace("w", W_TAGS);
|
|
13956
|
+
r = namespace("r", R_TAGS);
|
|
13957
|
+
a = namespace("a", A_TAGS);
|
|
13958
|
+
wp = namespace("wp", WP_TAGS);
|
|
13959
|
+
pic = namespace("pic", PIC_TAGS);
|
|
13960
|
+
cp = namespace("cp", CP_TAGS);
|
|
13961
|
+
dc = namespace("dc", DC_TAGS);
|
|
13962
|
+
dcterms = namespace("dcterms", DCTERMS_TAGS);
|
|
13963
|
+
w14 = namespace("w14", W14_TAGS);
|
|
13964
|
+
w15 = namespace("w15", W15_TAGS);
|
|
13965
|
+
});
|
|
13966
|
+
|
|
13967
|
+
// src/core/jsx/jsx-runtime.ts
|
|
13968
|
+
function jsx(type, props, _key) {
|
|
13969
|
+
const result = type(props);
|
|
13970
|
+
return result ?? new XmlNode2("#fragment");
|
|
13971
|
+
}
|
|
13972
|
+
var jsxDEV;
|
|
13973
|
+
var init_jsx_runtime = __esm(() => {
|
|
13974
|
+
init_parser();
|
|
13975
|
+
init_jsx();
|
|
13976
|
+
jsxDEV = jsx;
|
|
13977
|
+
});
|
|
13978
|
+
|
|
13979
|
+
// src/core/jsx/jsx-dev-runtime.ts
|
|
13980
|
+
var init_jsx_dev_runtime = __esm(() => {
|
|
13981
|
+
init_jsx_runtime();
|
|
13982
|
+
});
|
|
13983
|
+
|
|
13984
|
+
// src/core/sections.tsx
|
|
13985
|
+
function readSectionProperties(children) {
|
|
13986
|
+
const props = {};
|
|
13987
|
+
for (const child of children) {
|
|
13988
|
+
if (child.tag === "w:cols") {
|
|
13989
|
+
const num = child.getAttribute("w:num");
|
|
13990
|
+
if (num) {
|
|
13991
|
+
const parsed = Number.parseInt(num, 10);
|
|
13992
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
13993
|
+
props.columns = parsed;
|
|
13994
|
+
}
|
|
13995
|
+
continue;
|
|
13996
|
+
}
|
|
13997
|
+
if (child.tag === "w:type") {
|
|
13998
|
+
const value = child.getAttribute("w:val");
|
|
13999
|
+
if (value && isSectionType(value))
|
|
14000
|
+
props.sectionType = value;
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
return props;
|
|
14004
|
+
}
|
|
14005
|
+
function isSectionType(value) {
|
|
14006
|
+
return SECTION_TYPE_ORDER.includes(value);
|
|
14007
|
+
}
|
|
14008
|
+
function SentinelSectionParagraph({
|
|
14009
|
+
columns,
|
|
14010
|
+
sectionType
|
|
14011
|
+
}) {
|
|
14012
|
+
return /* @__PURE__ */ jsxDEV(w.p, {
|
|
14013
|
+
children: /* @__PURE__ */ jsxDEV(w.pPr, {
|
|
14014
|
+
children: /* @__PURE__ */ jsxDEV(w.sectPr, {
|
|
14015
|
+
children: [
|
|
14016
|
+
sectionType && /* @__PURE__ */ jsxDEV(w.type, {
|
|
14017
|
+
"w-val": sectionType
|
|
14018
|
+
}, undefined, false, undefined, this),
|
|
14019
|
+
columns !== undefined && /* @__PURE__ */ jsxDEV(w.cols, {
|
|
14020
|
+
"w-num": String(columns)
|
|
14021
|
+
}, undefined, false, undefined, this)
|
|
14022
|
+
]
|
|
14023
|
+
}, undefined, true, undefined, this)
|
|
14024
|
+
}, undefined, false, undefined, this)
|
|
14025
|
+
}, undefined, false, undefined, this);
|
|
14026
|
+
}
|
|
14027
|
+
function applyColumns(sectPr, columns) {
|
|
14028
|
+
if (columns === undefined)
|
|
14029
|
+
return;
|
|
14030
|
+
const existing = sectPr.findChild("w:cols");
|
|
14031
|
+
if (columns === null) {
|
|
14032
|
+
if (existing) {
|
|
14033
|
+
const index = sectPr.children.indexOf(existing);
|
|
14034
|
+
if (index !== -1)
|
|
14035
|
+
sectPr.children.splice(index, 1);
|
|
14036
|
+
}
|
|
14037
|
+
return;
|
|
14038
|
+
}
|
|
14039
|
+
if (existing) {
|
|
14040
|
+
existing.setAttribute("w:num", String(columns));
|
|
14041
|
+
return;
|
|
14042
|
+
}
|
|
14043
|
+
insertBeforeSectPrChange(sectPr, /* @__PURE__ */ jsxDEV(w.cols, {
|
|
14044
|
+
"w-num": String(columns)
|
|
14045
|
+
}, undefined, false, undefined, this));
|
|
14046
|
+
}
|
|
14047
|
+
function applySectionType(sectPr, sectionType) {
|
|
14048
|
+
if (sectionType === undefined)
|
|
14049
|
+
return;
|
|
14050
|
+
const existing = sectPr.findChild("w:type");
|
|
14051
|
+
if (sectionType === null) {
|
|
14052
|
+
if (existing) {
|
|
14053
|
+
const index = sectPr.children.indexOf(existing);
|
|
14054
|
+
if (index !== -1)
|
|
14055
|
+
sectPr.children.splice(index, 1);
|
|
14056
|
+
}
|
|
14057
|
+
return;
|
|
14058
|
+
}
|
|
14059
|
+
if (existing) {
|
|
14060
|
+
existing.setAttribute("w:val", sectionType);
|
|
14061
|
+
return;
|
|
14062
|
+
}
|
|
14063
|
+
const node = /* @__PURE__ */ jsxDEV(w.type, {
|
|
14064
|
+
"w-val": sectionType
|
|
14065
|
+
}, undefined, false, undefined, this);
|
|
14066
|
+
const colsIndex = sectPr.children.findIndex((child) => child.tag === "w:cols");
|
|
14067
|
+
if (colsIndex !== -1) {
|
|
14068
|
+
sectPr.children.splice(colsIndex, 0, node);
|
|
14069
|
+
return;
|
|
14070
|
+
}
|
|
14071
|
+
insertBeforeSectPrChange(sectPr, node);
|
|
14072
|
+
}
|
|
14073
|
+
function insertBeforeSectPrChange(sectPr, node) {
|
|
14074
|
+
const changeIndex = sectPr.children.findIndex((child) => child.tag === "w:sectPrChange");
|
|
14075
|
+
if (changeIndex === -1)
|
|
14076
|
+
sectPr.children.push(node);
|
|
14077
|
+
else
|
|
14078
|
+
sectPr.children.splice(changeIndex, 0, node);
|
|
14079
|
+
}
|
|
14080
|
+
function removeInlineSectPr(sectPr, parentChildren) {
|
|
14081
|
+
const index = parentChildren.indexOf(sectPr);
|
|
14082
|
+
if (index === -1)
|
|
14083
|
+
return false;
|
|
14084
|
+
parentChildren.splice(index, 1);
|
|
14085
|
+
return true;
|
|
14086
|
+
}
|
|
14087
|
+
function isTrailingSectPr(bodyChildren, parentChildren) {
|
|
14088
|
+
return parentChildren === bodyChildren;
|
|
14089
|
+
}
|
|
14090
|
+
function wrapSectPrChange(sectPr, meta) {
|
|
14091
|
+
const existingChangeIndex = sectPr.children.findIndex((child) => child.tag === "w:sectPrChange");
|
|
14092
|
+
if (existingChangeIndex !== -1) {
|
|
14093
|
+
sectPr.children.splice(existingChangeIndex, 1);
|
|
14094
|
+
}
|
|
14095
|
+
const snapshotChildren = [];
|
|
14096
|
+
for (const child of sectPr.children) {
|
|
14097
|
+
if (child.tag === "w:sectPrChange")
|
|
14098
|
+
continue;
|
|
14099
|
+
snapshotChildren.push(child.clone());
|
|
14100
|
+
}
|
|
14101
|
+
const change = /* @__PURE__ */ jsxDEV(w.sectPrChange, {
|
|
14102
|
+
"w-id": meta.revisionId,
|
|
14103
|
+
"w-author": meta.author,
|
|
14104
|
+
"w-date": meta.date,
|
|
14105
|
+
children: /* @__PURE__ */ jsxDEV(w.sectPr, {
|
|
14106
|
+
children: snapshotChildren
|
|
14107
|
+
}, undefined, false, undefined, this)
|
|
14108
|
+
}, undefined, false, undefined, this);
|
|
14109
|
+
sectPr.children.push(change);
|
|
14110
|
+
return change;
|
|
14111
|
+
}
|
|
14112
|
+
var SECTION_TYPE_ORDER;
|
|
14113
|
+
var init_sections = __esm(() => {
|
|
14114
|
+
init_jsx();
|
|
14115
|
+
init_jsx_dev_runtime();
|
|
14116
|
+
SECTION_TYPE_ORDER = [
|
|
14117
|
+
"continuous",
|
|
14118
|
+
"nextPage",
|
|
14119
|
+
"evenPage",
|
|
14120
|
+
"oddPage",
|
|
14121
|
+
"nextColumn"
|
|
14122
|
+
];
|
|
14123
|
+
});
|
|
14124
|
+
|
|
13808
14125
|
// src/core/ast/sym.ts
|
|
13809
14126
|
function decodeSym(font, charHex) {
|
|
13810
14127
|
const codepoint = Number.parseInt(charHex, 16);
|
|
@@ -14153,6 +14470,14 @@ function readBlocks(view, body, state) {
|
|
|
14153
14470
|
const id = `p${paragraphIndex++}`;
|
|
14154
14471
|
blocks.push(readParagraph(view, child, id, state));
|
|
14155
14472
|
view.blockReferences.set(id, { node: child, parent: body.children });
|
|
14473
|
+
const inlineSectPr = findInlineSectPr(child);
|
|
14474
|
+
if (inlineSectPr) {
|
|
14475
|
+
const sectionId = `s${sectionIndex++}`;
|
|
14476
|
+
blocks.push(buildSectionBreak(sectionId, inlineSectPr.node));
|
|
14477
|
+
view.blockReferences.set(sectionId, inlineSectPr);
|
|
14478
|
+
registerSectPrChange(view, inlineSectPr.node, state, sectionId);
|
|
14479
|
+
}
|
|
14480
|
+
registerParagraphMarkChanges(view, child, state, id);
|
|
14156
14481
|
continue;
|
|
14157
14482
|
}
|
|
14158
14483
|
if (child.tag === "w:tbl") {
|
|
@@ -14163,12 +14488,60 @@ function readBlocks(view, body, state) {
|
|
|
14163
14488
|
}
|
|
14164
14489
|
if (child.tag === "w:sectPr") {
|
|
14165
14490
|
const id = `s${sectionIndex++}`;
|
|
14166
|
-
blocks.push(
|
|
14491
|
+
blocks.push(buildSectionBreak(id, child));
|
|
14167
14492
|
view.blockReferences.set(id, { node: child, parent: body.children });
|
|
14493
|
+
registerSectPrChange(view, child, state, id);
|
|
14168
14494
|
}
|
|
14169
14495
|
}
|
|
14170
14496
|
return blocks;
|
|
14171
14497
|
}
|
|
14498
|
+
function registerParagraphMarkChanges(view, paragraph, state, blockId) {
|
|
14499
|
+
const pPr = paragraph.findChild("w:pPr");
|
|
14500
|
+
if (!pPr)
|
|
14501
|
+
return;
|
|
14502
|
+
const rPr = pPr.findChild("w:rPr");
|
|
14503
|
+
if (!rPr)
|
|
14504
|
+
return;
|
|
14505
|
+
for (const child of rPr.children) {
|
|
14506
|
+
if (child.tag !== "w:ins" && child.tag !== "w:del")
|
|
14507
|
+
continue;
|
|
14508
|
+
const id = `tc${state.trackedChangeIndex++}`;
|
|
14509
|
+
view.trackedChangeReferences.set(id, {
|
|
14510
|
+
node: child,
|
|
14511
|
+
parent: rPr.children,
|
|
14512
|
+
blockId
|
|
14513
|
+
});
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
function registerSectPrChange(view, sectPr, state, blockId) {
|
|
14517
|
+
const change = sectPr.findChild("w:sectPrChange");
|
|
14518
|
+
if (!change)
|
|
14519
|
+
return;
|
|
14520
|
+
const id = `tc${state.trackedChangeIndex++}`;
|
|
14521
|
+
view.trackedChangeReferences.set(id, {
|
|
14522
|
+
node: change,
|
|
14523
|
+
parent: sectPr.children,
|
|
14524
|
+
blockId
|
|
14525
|
+
});
|
|
14526
|
+
}
|
|
14527
|
+
function findInlineSectPr(paragraph) {
|
|
14528
|
+
const pPr = paragraph.findChild("w:pPr");
|
|
14529
|
+
if (!pPr)
|
|
14530
|
+
return;
|
|
14531
|
+
const sectPr = pPr.findChild("w:sectPr");
|
|
14532
|
+
if (!sectPr)
|
|
14533
|
+
return;
|
|
14534
|
+
return { node: sectPr, parent: pPr.children };
|
|
14535
|
+
}
|
|
14536
|
+
function buildSectionBreak(id, sectPr) {
|
|
14537
|
+
const props = readSectionProperties(sectPr.children);
|
|
14538
|
+
const block = { id, type: "sectionBreak" };
|
|
14539
|
+
if (props.columns !== undefined)
|
|
14540
|
+
block.columns = props.columns;
|
|
14541
|
+
if (props.sectionType !== undefined)
|
|
14542
|
+
block.sectionType = props.sectionType;
|
|
14543
|
+
return block;
|
|
14544
|
+
}
|
|
14172
14545
|
function readParagraph(view, node, id, state) {
|
|
14173
14546
|
const paragraph = { id, type: "paragraph", runs: [] };
|
|
14174
14547
|
const paragraphProperties = node.findChild("w:pPr");
|
|
@@ -14707,6 +15080,7 @@ function readNotes(tree, rootTag, itemTag, idPrefix) {
|
|
|
14707
15080
|
var RELATIONSHIP_NAMESPACE_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", RELATIONSHIP_NAMESPACE_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", TRACKED_CHANGE_KIND_BY_TAG;
|
|
14708
15081
|
var init_read = __esm(() => {
|
|
14709
15082
|
init_parser();
|
|
15083
|
+
init_sections();
|
|
14710
15084
|
init_sym();
|
|
14711
15085
|
TRACKED_CHANGE_KIND_BY_TAG = {
|
|
14712
15086
|
"w:ins": "ins",
|
|
@@ -15124,178 +15498,6 @@ var init_track_changes = __esm(() => {
|
|
|
15124
15498
|
init_parser();
|
|
15125
15499
|
});
|
|
15126
15500
|
|
|
15127
|
-
// src/core/jsx/index.ts
|
|
15128
|
-
function normalizeChildren(children) {
|
|
15129
|
-
if (children === undefined || children === null)
|
|
15130
|
-
return [];
|
|
15131
|
-
if (Array.isArray(children))
|
|
15132
|
-
return children;
|
|
15133
|
-
return [children];
|
|
15134
|
-
}
|
|
15135
|
-
function namespace(prefix, tags) {
|
|
15136
|
-
const result = {};
|
|
15137
|
-
for (const tagName of tags) {
|
|
15138
|
-
result[tagName] = makeTag(`${prefix}:${tagName}`);
|
|
15139
|
-
}
|
|
15140
|
-
return result;
|
|
15141
|
-
}
|
|
15142
|
-
function makeTag(qualifiedName) {
|
|
15143
|
-
return (props) => {
|
|
15144
|
-
const attributes = {};
|
|
15145
|
-
let childrenProp;
|
|
15146
|
-
if (props) {
|
|
15147
|
-
for (const [key, value] of Object.entries(props)) {
|
|
15148
|
-
if (key === "children") {
|
|
15149
|
-
childrenProp = value;
|
|
15150
|
-
continue;
|
|
15151
|
-
}
|
|
15152
|
-
if (value === false || value == null)
|
|
15153
|
-
continue;
|
|
15154
|
-
attributes[mapAttributeName(key)] = String(value);
|
|
15155
|
-
}
|
|
15156
|
-
}
|
|
15157
|
-
const childNodes = [];
|
|
15158
|
-
flatten(normalizeChildren(childrenProp), childNodes);
|
|
15159
|
-
return new XmlNode2(qualifiedName, attributes, childNodes);
|
|
15160
|
-
};
|
|
15161
|
-
}
|
|
15162
|
-
function flatten(items, out) {
|
|
15163
|
-
for (const item of items) {
|
|
15164
|
-
if (item == null || item === false)
|
|
15165
|
-
continue;
|
|
15166
|
-
if (Array.isArray(item)) {
|
|
15167
|
-
flatten(item, out);
|
|
15168
|
-
continue;
|
|
15169
|
-
}
|
|
15170
|
-
if (item instanceof XmlNode2) {
|
|
15171
|
-
if (item.tag === FRAGMENT_TAG) {
|
|
15172
|
-
for (const child of item.children)
|
|
15173
|
-
out.push(child);
|
|
15174
|
-
continue;
|
|
15175
|
-
}
|
|
15176
|
-
out.push(item);
|
|
15177
|
-
continue;
|
|
15178
|
-
}
|
|
15179
|
-
if (typeof item === "string" || typeof item === "number") {
|
|
15180
|
-
out.push(XmlNode2.textNode(String(item)));
|
|
15181
|
-
}
|
|
15182
|
-
}
|
|
15183
|
-
}
|
|
15184
|
-
function mapAttributeName(name) {
|
|
15185
|
-
const dashIndex = name.indexOf("-");
|
|
15186
|
-
if (dashIndex === -1)
|
|
15187
|
-
return name;
|
|
15188
|
-
return `${name.slice(0, dashIndex)}:${name.slice(dashIndex + 1)}`;
|
|
15189
|
-
}
|
|
15190
|
-
var FRAGMENT_TAG = "#fragment", W_TAGS, R_TAGS, A_TAGS, WP_TAGS, PIC_TAGS, CP_TAGS, DC_TAGS, DCTERMS_TAGS, W14_TAGS, W15_TAGS, w, r, a, wp, pic, cp, dc, dcterms, w14, w15;
|
|
15191
|
-
var init_jsx = __esm(() => {
|
|
15192
|
-
init_parser();
|
|
15193
|
-
W_TAGS = [
|
|
15194
|
-
"document",
|
|
15195
|
-
"body",
|
|
15196
|
-
"p",
|
|
15197
|
-
"pPr",
|
|
15198
|
-
"pStyle",
|
|
15199
|
-
"jc",
|
|
15200
|
-
"numPr",
|
|
15201
|
-
"ilvl",
|
|
15202
|
-
"numId",
|
|
15203
|
-
"r",
|
|
15204
|
-
"rPr",
|
|
15205
|
-
"rStyle",
|
|
15206
|
-
"t",
|
|
15207
|
-
"b",
|
|
15208
|
-
"i",
|
|
15209
|
-
"u",
|
|
15210
|
-
"strike",
|
|
15211
|
-
"color",
|
|
15212
|
-
"highlight",
|
|
15213
|
-
"sz",
|
|
15214
|
-
"rFonts",
|
|
15215
|
-
"br",
|
|
15216
|
-
"tab",
|
|
15217
|
-
"drawing",
|
|
15218
|
-
"sectPr",
|
|
15219
|
-
"tbl",
|
|
15220
|
-
"tblPr",
|
|
15221
|
-
"tblGrid",
|
|
15222
|
-
"gridCol",
|
|
15223
|
-
"tr",
|
|
15224
|
-
"tc",
|
|
15225
|
-
"tcPr",
|
|
15226
|
-
"ins",
|
|
15227
|
-
"del",
|
|
15228
|
-
"delText",
|
|
15229
|
-
"commentRangeStart",
|
|
15230
|
-
"commentRangeEnd",
|
|
15231
|
-
"commentReference",
|
|
15232
|
-
"comments",
|
|
15233
|
-
"comment",
|
|
15234
|
-
"settings",
|
|
15235
|
-
"trackChanges",
|
|
15236
|
-
"hyperlink"
|
|
15237
|
-
];
|
|
15238
|
-
R_TAGS = ["embed", "link", "id"];
|
|
15239
|
-
A_TAGS = [
|
|
15240
|
-
"graphic",
|
|
15241
|
-
"graphicData",
|
|
15242
|
-
"blip",
|
|
15243
|
-
"xfrm",
|
|
15244
|
-
"off",
|
|
15245
|
-
"ext",
|
|
15246
|
-
"prstGeom",
|
|
15247
|
-
"avLst"
|
|
15248
|
-
];
|
|
15249
|
-
WP_TAGS = [
|
|
15250
|
-
"inline",
|
|
15251
|
-
"anchor",
|
|
15252
|
-
"extent",
|
|
15253
|
-
"effectExtent",
|
|
15254
|
-
"docPr",
|
|
15255
|
-
"cNvGraphicFramePr"
|
|
15256
|
-
];
|
|
15257
|
-
PIC_TAGS = [
|
|
15258
|
-
"pic",
|
|
15259
|
-
"nvPicPr",
|
|
15260
|
-
"cNvPr",
|
|
15261
|
-
"cNvPicPr",
|
|
15262
|
-
"blipFill",
|
|
15263
|
-
"spPr"
|
|
15264
|
-
];
|
|
15265
|
-
CP_TAGS = ["coreProperties", "lastModifiedBy"];
|
|
15266
|
-
DC_TAGS = ["title", "creator", "subject", "description"];
|
|
15267
|
-
DCTERMS_TAGS = ["created", "modified"];
|
|
15268
|
-
W14_TAGS = ["paraId", "textId"];
|
|
15269
|
-
W15_TAGS = ["commentsEx", "commentEx"];
|
|
15270
|
-
w = namespace("w", W_TAGS);
|
|
15271
|
-
r = namespace("r", R_TAGS);
|
|
15272
|
-
a = namespace("a", A_TAGS);
|
|
15273
|
-
wp = namespace("wp", WP_TAGS);
|
|
15274
|
-
pic = namespace("pic", PIC_TAGS);
|
|
15275
|
-
cp = namespace("cp", CP_TAGS);
|
|
15276
|
-
dc = namespace("dc", DC_TAGS);
|
|
15277
|
-
dcterms = namespace("dcterms", DCTERMS_TAGS);
|
|
15278
|
-
w14 = namespace("w14", W14_TAGS);
|
|
15279
|
-
w15 = namespace("w15", W15_TAGS);
|
|
15280
|
-
});
|
|
15281
|
-
|
|
15282
|
-
// src/core/jsx/jsx-runtime.ts
|
|
15283
|
-
function jsx(type, props, _key) {
|
|
15284
|
-
const result = type(props);
|
|
15285
|
-
return result ?? new XmlNode2("#fragment");
|
|
15286
|
-
}
|
|
15287
|
-
var jsxDEV;
|
|
15288
|
-
var init_jsx_runtime = __esm(() => {
|
|
15289
|
-
init_parser();
|
|
15290
|
-
init_jsx();
|
|
15291
|
-
jsxDEV = jsx;
|
|
15292
|
-
});
|
|
15293
|
-
|
|
15294
|
-
// src/core/jsx/jsx-dev-runtime.ts
|
|
15295
|
-
var init_jsx_dev_runtime = __esm(() => {
|
|
15296
|
-
init_jsx_runtime();
|
|
15297
|
-
});
|
|
15298
|
-
|
|
15299
15501
|
// src/core/track-changes/emit.tsx
|
|
15300
15502
|
function Ins({
|
|
15301
15503
|
meta,
|
|
@@ -15349,6 +15551,7 @@ var init_core = __esm(() => {
|
|
|
15349
15551
|
init_package();
|
|
15350
15552
|
init_parser();
|
|
15351
15553
|
init_relationships();
|
|
15554
|
+
init_sections();
|
|
15352
15555
|
init_track_changes();
|
|
15353
15556
|
init_emit();
|
|
15354
15557
|
});
|
|
@@ -15358,6 +15561,14 @@ async function respond(payload) {
|
|
|
15358
15561
|
await Bun.write(Bun.stdout, `${JSON.stringify(payload)}
|
|
15359
15562
|
`);
|
|
15360
15563
|
}
|
|
15564
|
+
function setVerboseAck(verbose) {
|
|
15565
|
+
verboseAck = verbose;
|
|
15566
|
+
}
|
|
15567
|
+
async function respondAck(payload) {
|
|
15568
|
+
if (!verboseAck)
|
|
15569
|
+
return;
|
|
15570
|
+
await respond(payload);
|
|
15571
|
+
}
|
|
15361
15572
|
async function writeStdout(text) {
|
|
15362
15573
|
await Bun.write(Bun.stdout, text);
|
|
15363
15574
|
}
|
|
@@ -15416,7 +15627,7 @@ async function resolveBlockOrFail(view, locator) {
|
|
|
15416
15627
|
throw err;
|
|
15417
15628
|
}
|
|
15418
15629
|
}
|
|
15419
|
-
var EXIT;
|
|
15630
|
+
var EXIT, verboseAck = false;
|
|
15420
15631
|
var init_respond = __esm(() => {
|
|
15421
15632
|
init_core();
|
|
15422
15633
|
EXIT = {
|
|
@@ -15427,86 +15638,291 @@ var init_respond = __esm(() => {
|
|
|
15427
15638
|
};
|
|
15428
15639
|
});
|
|
15429
15640
|
|
|
15430
|
-
// src/
|
|
15431
|
-
function
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
const
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
}
|
|
15450
|
-
function generateParaId() {
|
|
15451
|
-
const bytes = new Uint8Array(4);
|
|
15452
|
-
crypto.getRandomValues(bytes);
|
|
15453
|
-
let hex = "";
|
|
15454
|
-
for (const byte of bytes)
|
|
15455
|
-
hex += byte.toString(16).padStart(2, "0");
|
|
15456
|
-
return hex.toUpperCase();
|
|
15457
|
-
}
|
|
15458
|
-
function authorInitials(author) {
|
|
15459
|
-
const parts = author.trim().split(/\s+/).filter(Boolean);
|
|
15460
|
-
if (parts.length === 0)
|
|
15461
|
-
return "?";
|
|
15462
|
-
const first = parts[0]?.charAt(0) ?? "";
|
|
15463
|
-
const last = parts.length > 1 ? parts[parts.length - 1]?.charAt(0) ?? "" : "";
|
|
15464
|
-
return (first + last).toUpperCase() || "?";
|
|
15465
|
-
}
|
|
15466
|
-
function ensureCommentsPart(view) {
|
|
15467
|
-
if (view.commentsTree) {
|
|
15468
|
-
const existing = XmlNode2.findRoot(view.commentsTree, "w:comments");
|
|
15469
|
-
if (existing)
|
|
15470
|
-
return existing;
|
|
15641
|
+
// src/core/find/index.ts
|
|
15642
|
+
function findTextSpans(doc, query, options = {}) {
|
|
15643
|
+
const view = options.view ?? "accepted";
|
|
15644
|
+
const exact = options.exact ?? false;
|
|
15645
|
+
const useRegex = options.regex ?? false;
|
|
15646
|
+
let effectiveQuery = query;
|
|
15647
|
+
const applied = [];
|
|
15648
|
+
if (!useRegex && !exact) {
|
|
15649
|
+
const norm = normalizeQuery(query);
|
|
15650
|
+
effectiveQuery = norm.normalized;
|
|
15651
|
+
applied.push(...norm.applied);
|
|
15652
|
+
}
|
|
15653
|
+
const matcher = useRegex ? regexMatcher(query, options.ignoreCase ?? false) : literalMatcher(effectiveQuery, options.ignoreCase ?? false, !exact);
|
|
15654
|
+
const out = [];
|
|
15655
|
+
collectMatches(doc.blocks, matcher, view, out);
|
|
15656
|
+
const result = { matches: out };
|
|
15657
|
+
if (applied.length > 0) {
|
|
15658
|
+
result.normalizedQuery = effectiveQuery;
|
|
15659
|
+
result.normalizationApplied = applied;
|
|
15471
15660
|
}
|
|
15472
|
-
|
|
15473
|
-
"xmlns:w": NS_W,
|
|
15474
|
-
"xmlns:w14": NS_W14
|
|
15475
|
-
}, undefined, false, undefined, this);
|
|
15476
|
-
view.commentsTree = [root];
|
|
15477
|
-
registerPart(view.relationshipsTree, view.contentTypesTree, {
|
|
15478
|
-
partName: "word/comments.xml",
|
|
15479
|
-
contentType: COMMENTS_CONTENT_TYPE,
|
|
15480
|
-
relationshipType: COMMENTS_REL_TYPE,
|
|
15481
|
-
target: "comments.xml"
|
|
15482
|
-
});
|
|
15483
|
-
return root;
|
|
15661
|
+
return result;
|
|
15484
15662
|
}
|
|
15485
|
-
function
|
|
15486
|
-
if (
|
|
15487
|
-
|
|
15488
|
-
if (existing)
|
|
15489
|
-
return existing;
|
|
15663
|
+
function literalMatcher(query, ignoreCase, normalize) {
|
|
15664
|
+
if (query.length === 0) {
|
|
15665
|
+
throw new Error("query cannot be empty");
|
|
15490
15666
|
}
|
|
15491
|
-
const
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15667
|
+
const needle = ignoreCase ? query.toLowerCase() : query;
|
|
15668
|
+
return (paragraphText2) => {
|
|
15669
|
+
const canonical = normalize ? normalizeHaystack(paragraphText2) : paragraphText2;
|
|
15670
|
+
const haystack = ignoreCase ? canonical.toLowerCase() : canonical;
|
|
15671
|
+
const matches = [];
|
|
15672
|
+
let cursor = haystack.indexOf(needle);
|
|
15673
|
+
while (cursor !== -1) {
|
|
15674
|
+
matches.push({
|
|
15675
|
+
start: cursor,
|
|
15676
|
+
end: cursor + needle.length,
|
|
15677
|
+
text: paragraphText2.slice(cursor, cursor + needle.length)
|
|
15678
|
+
});
|
|
15679
|
+
cursor = haystack.indexOf(needle, cursor + needle.length);
|
|
15680
|
+
}
|
|
15681
|
+
return matches;
|
|
15682
|
+
};
|
|
15504
15683
|
}
|
|
15505
|
-
function
|
|
15506
|
-
|
|
15684
|
+
function regexMatcher(pattern, ignoreCase) {
|
|
15685
|
+
const flags = `g${ignoreCase ? "i" : ""}`;
|
|
15686
|
+
const regex = new RegExp(pattern, flags);
|
|
15687
|
+
return (paragraphText2) => {
|
|
15688
|
+
const matches = [];
|
|
15689
|
+
regex.lastIndex = 0;
|
|
15690
|
+
let result = regex.exec(paragraphText2);
|
|
15691
|
+
while (result !== null) {
|
|
15692
|
+
const matched = result[0];
|
|
15693
|
+
if (matched.length === 0) {
|
|
15694
|
+
regex.lastIndex += 1;
|
|
15695
|
+
result = regex.exec(paragraphText2);
|
|
15696
|
+
continue;
|
|
15697
|
+
}
|
|
15698
|
+
matches.push({
|
|
15699
|
+
start: result.index,
|
|
15700
|
+
end: result.index + matched.length,
|
|
15701
|
+
text: matched
|
|
15702
|
+
});
|
|
15703
|
+
result = regex.exec(paragraphText2);
|
|
15704
|
+
}
|
|
15705
|
+
return matches;
|
|
15706
|
+
};
|
|
15707
|
+
}
|
|
15708
|
+
function collectMatches(blocks, matcher, view, out) {
|
|
15709
|
+
for (const block of blocks) {
|
|
15710
|
+
if (block.type === "paragraph") {
|
|
15711
|
+
const paragraphText2 = paragraphTextForView(block, view);
|
|
15712
|
+
for (const span of matcher(paragraphText2)) {
|
|
15713
|
+
const match = {
|
|
15714
|
+
blockId: block.id,
|
|
15715
|
+
start: span.start,
|
|
15716
|
+
end: span.end,
|
|
15717
|
+
text: span.text
|
|
15718
|
+
};
|
|
15719
|
+
const overlaps = trackedChangesOverlapping(block, span.start, span.end, view);
|
|
15720
|
+
if (overlaps.length > 0)
|
|
15721
|
+
match.trackedChanges = overlaps;
|
|
15722
|
+
out.push(match);
|
|
15723
|
+
}
|
|
15724
|
+
continue;
|
|
15725
|
+
}
|
|
15726
|
+
if (block.type === "table") {
|
|
15727
|
+
for (const row of block.rows) {
|
|
15728
|
+
for (const cell of row.cells) {
|
|
15729
|
+
collectMatches(cell.blocks, matcher, view, out);
|
|
15730
|
+
}
|
|
15731
|
+
}
|
|
15732
|
+
}
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
function paragraphTextForView(paragraph, view) {
|
|
15736
|
+
let out = "";
|
|
15737
|
+
for (const run of paragraph.runs) {
|
|
15738
|
+
if (run.type !== "text")
|
|
15739
|
+
continue;
|
|
15740
|
+
if (!isRunVisibleInView(run.trackedChange?.kind, view))
|
|
15741
|
+
continue;
|
|
15742
|
+
out += run.text;
|
|
15743
|
+
}
|
|
15744
|
+
return out;
|
|
15745
|
+
}
|
|
15746
|
+
function isRunVisibleInView(kind, view) {
|
|
15747
|
+
if (view === "current")
|
|
15748
|
+
return true;
|
|
15749
|
+
if (view === "accepted")
|
|
15750
|
+
return kind !== "del" && kind !== "moveFrom";
|
|
15751
|
+
return kind !== "ins" && kind !== "moveTo";
|
|
15752
|
+
}
|
|
15753
|
+
function normalizeQuery(query) {
|
|
15754
|
+
const applied = [];
|
|
15755
|
+
let result = query;
|
|
15756
|
+
const stripped = stripBalancedMarkdownEmphasis(result);
|
|
15757
|
+
if (stripped !== result) {
|
|
15758
|
+
applied.push("strip-md-emphasis");
|
|
15759
|
+
result = stripped;
|
|
15760
|
+
}
|
|
15761
|
+
const quoteNormalized = normalizeQuotes(result);
|
|
15762
|
+
if (quoteNormalized !== result) {
|
|
15763
|
+
applied.push("smart-quotes");
|
|
15764
|
+
result = quoteNormalized;
|
|
15765
|
+
}
|
|
15766
|
+
const dashNormalized = normalizeDashes(result);
|
|
15767
|
+
if (dashNormalized !== result) {
|
|
15768
|
+
applied.push("dashes");
|
|
15769
|
+
result = dashNormalized;
|
|
15770
|
+
}
|
|
15771
|
+
return { normalized: result, applied };
|
|
15772
|
+
}
|
|
15773
|
+
function normalizeHaystack(text) {
|
|
15774
|
+
return normalizeDashes(normalizeQuotes(text));
|
|
15775
|
+
}
|
|
15776
|
+
function normalizeQuotes(text) {
|
|
15777
|
+
return text.replace(/[\u2018\u2019]/g, "'").replace(/[\u201C\u201D]/g, '"');
|
|
15778
|
+
}
|
|
15779
|
+
function normalizeDashes(text) {
|
|
15780
|
+
return text.replace(/[\u2013\u2014]/g, "-");
|
|
15781
|
+
}
|
|
15782
|
+
function stripBalancedMarkdownEmphasis(text) {
|
|
15783
|
+
const patterns = [
|
|
15784
|
+
/\*\*(\S(?:.*?\S)?)\*\*/g,
|
|
15785
|
+
/__(\S(?:.*?\S)?)__/g,
|
|
15786
|
+
/`(\S(?:.*?\S)?)`/g,
|
|
15787
|
+
/(?<![A-Za-z0-9_])\*(\S(?:.*?\S)?)\*(?![A-Za-z0-9_])/g,
|
|
15788
|
+
/(?<![A-Za-z0-9_])_(\S(?:.*?\S)?)_(?![A-Za-z0-9_])/g
|
|
15789
|
+
];
|
|
15790
|
+
let result = text;
|
|
15791
|
+
for (const pattern of patterns) {
|
|
15792
|
+
result = result.replace(pattern, "$1");
|
|
15793
|
+
}
|
|
15794
|
+
return result;
|
|
15795
|
+
}
|
|
15796
|
+
function trackedChangesOverlapping(paragraph, start, end, view) {
|
|
15797
|
+
const seen = new Set;
|
|
15798
|
+
const out = [];
|
|
15799
|
+
let offset = 0;
|
|
15800
|
+
for (const run of paragraph.runs) {
|
|
15801
|
+
if (run.type !== "text")
|
|
15802
|
+
continue;
|
|
15803
|
+
if (!isRunVisibleInView(run.trackedChange?.kind, view))
|
|
15804
|
+
continue;
|
|
15805
|
+
const length = run.text.length;
|
|
15806
|
+
const runStart = offset;
|
|
15807
|
+
const runEnd = offset + length;
|
|
15808
|
+
offset = runEnd;
|
|
15809
|
+
if (runEnd <= start || runStart >= end)
|
|
15810
|
+
continue;
|
|
15811
|
+
const change = run.trackedChange;
|
|
15812
|
+
if (!change)
|
|
15813
|
+
continue;
|
|
15814
|
+
if (seen.has(change.id))
|
|
15815
|
+
continue;
|
|
15816
|
+
seen.add(change.id);
|
|
15817
|
+
out.push(change);
|
|
15818
|
+
}
|
|
15819
|
+
return out;
|
|
15820
|
+
}
|
|
15821
|
+
|
|
15822
|
+
// src/cli/comments/helpers.tsx
|
|
15823
|
+
function isWrapperVisibleInView(tag, view) {
|
|
15824
|
+
if (!isRunBearingWrapper(tag))
|
|
15825
|
+
return false;
|
|
15826
|
+
if (view === "current")
|
|
15827
|
+
return true;
|
|
15828
|
+
if (view === "accepted")
|
|
15829
|
+
return tag !== "w:del" && tag !== "w:moveFrom";
|
|
15830
|
+
return tag !== "w:ins" && tag !== "w:moveTo";
|
|
15831
|
+
}
|
|
15832
|
+
function sumVisibleTextLength(children, view) {
|
|
15833
|
+
let total = 0;
|
|
15834
|
+
for (const child of children) {
|
|
15835
|
+
if (child.tag === "w:r") {
|
|
15836
|
+
total += runTextLength(child);
|
|
15837
|
+
continue;
|
|
15838
|
+
}
|
|
15839
|
+
if (isWrapperVisibleInView(child.tag, view)) {
|
|
15840
|
+
total += sumVisibleTextLength(child.children, view);
|
|
15841
|
+
}
|
|
15842
|
+
}
|
|
15843
|
+
return total;
|
|
15844
|
+
}
|
|
15845
|
+
function nextCommentId(view) {
|
|
15846
|
+
if (!view.commentsTree)
|
|
15847
|
+
return "0";
|
|
15848
|
+
const root = XmlNode2.findRoot(view.commentsTree, "w:comments");
|
|
15849
|
+
if (!root)
|
|
15850
|
+
return "0";
|
|
15851
|
+
let highest = -1;
|
|
15852
|
+
for (const child of root.children) {
|
|
15853
|
+
if (child.tag !== "w:comment")
|
|
15854
|
+
continue;
|
|
15855
|
+
const idAttribute = child.getAttribute("w:id");
|
|
15856
|
+
if (idAttribute == null)
|
|
15857
|
+
continue;
|
|
15858
|
+
const numeric = Number(idAttribute);
|
|
15859
|
+
if (Number.isFinite(numeric) && numeric > highest)
|
|
15860
|
+
highest = numeric;
|
|
15861
|
+
}
|
|
15862
|
+
return String(highest + 1);
|
|
15863
|
+
}
|
|
15864
|
+
function generateParaId() {
|
|
15865
|
+
const bytes = new Uint8Array(4);
|
|
15866
|
+
crypto.getRandomValues(bytes);
|
|
15867
|
+
let hex = "";
|
|
15868
|
+
for (const byte of bytes)
|
|
15869
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
15870
|
+
return hex.toUpperCase();
|
|
15871
|
+
}
|
|
15872
|
+
function authorInitials(author) {
|
|
15873
|
+
const parts = author.trim().split(/\s+/).filter(Boolean);
|
|
15874
|
+
if (parts.length === 0)
|
|
15875
|
+
return "?";
|
|
15876
|
+
const first = parts[0]?.charAt(0) ?? "";
|
|
15877
|
+
const last = parts.length > 1 ? parts[parts.length - 1]?.charAt(0) ?? "" : "";
|
|
15878
|
+
return (first + last).toUpperCase() || "?";
|
|
15879
|
+
}
|
|
15880
|
+
function ensureCommentsPart(view) {
|
|
15881
|
+
if (view.commentsTree) {
|
|
15882
|
+
const existing = XmlNode2.findRoot(view.commentsTree, "w:comments");
|
|
15883
|
+
if (existing)
|
|
15884
|
+
return existing;
|
|
15885
|
+
}
|
|
15886
|
+
const root = /* @__PURE__ */ jsxDEV(w.comments, {
|
|
15887
|
+
"xmlns:w": NS_W,
|
|
15888
|
+
"xmlns:w14": NS_W14
|
|
15889
|
+
}, undefined, false, undefined, this);
|
|
15890
|
+
view.commentsTree = [root];
|
|
15891
|
+
registerPart(view.relationshipsTree, view.contentTypesTree, {
|
|
15892
|
+
partName: "word/comments.xml",
|
|
15893
|
+
contentType: COMMENTS_CONTENT_TYPE,
|
|
15894
|
+
relationshipType: COMMENTS_REL_TYPE,
|
|
15895
|
+
target: "comments.xml"
|
|
15896
|
+
});
|
|
15897
|
+
return root;
|
|
15898
|
+
}
|
|
15899
|
+
function ensureCommentsExtPart(view) {
|
|
15900
|
+
if (view.commentsExtTree) {
|
|
15901
|
+
const existing = XmlNode2.findRoot(view.commentsExtTree, "w15:commentsEx");
|
|
15902
|
+
if (existing)
|
|
15903
|
+
return existing;
|
|
15904
|
+
}
|
|
15905
|
+
const root = /* @__PURE__ */ jsxDEV(w15.commentsEx, {
|
|
15906
|
+
"xmlns:w15": NS_W15,
|
|
15907
|
+
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
15908
|
+
"mc:Ignorable": "w15"
|
|
15909
|
+
}, undefined, false, undefined, this);
|
|
15910
|
+
view.commentsExtTree = [root];
|
|
15911
|
+
registerPart(view.relationshipsTree, view.contentTypesTree, {
|
|
15912
|
+
partName: "word/commentsExtended.xml",
|
|
15913
|
+
contentType: COMMENTS_EXT_CONTENT_TYPE,
|
|
15914
|
+
relationshipType: COMMENTS_EXT_REL_TYPE,
|
|
15915
|
+
target: "commentsExtended.xml"
|
|
15916
|
+
});
|
|
15917
|
+
return root;
|
|
15507
15918
|
}
|
|
15508
|
-
function
|
|
15509
|
-
|
|
15919
|
+
function paragraphTextLength(paragraph, view = "current") {
|
|
15920
|
+
if (view === "current")
|
|
15921
|
+
return sumRunBearingTextLength(paragraph.children);
|
|
15922
|
+
return sumVisibleTextLength(paragraph.children, view);
|
|
15923
|
+
}
|
|
15924
|
+
function addCommentMarkersToParagraph(paragraph, commentId, span, view = "current") {
|
|
15925
|
+
const total = paragraphTextLength(paragraph, view);
|
|
15510
15926
|
const range = span ?? { start: 0, end: total };
|
|
15511
15927
|
if (range.start < 0 || range.end > total || range.start > range.end) {
|
|
15512
15928
|
throw new SpanOutOfRangeError(`Span ${range.start}-${range.end} out of paragraph length ${total}`);
|
|
@@ -15518,26 +15934,24 @@ function addCommentMarkersToParagraph(paragraph, commentId, span) {
|
|
|
15518
15934
|
node: commentRangeEndMarker(commentId),
|
|
15519
15935
|
follower: commentReferenceRun(commentId)
|
|
15520
15936
|
}
|
|
15521
|
-
]);
|
|
15937
|
+
], view);
|
|
15522
15938
|
}
|
|
15523
|
-
function addCommentRangeMarkers(startParagraph, startOffset, endParagraph, endOffset, commentId) {
|
|
15939
|
+
function addCommentRangeMarkers(startParagraph, startOffset, endParagraph, endOffset, commentId, view = "current") {
|
|
15524
15940
|
if (startParagraph === endParagraph) {
|
|
15525
15941
|
addCommentMarkersToParagraph(startParagraph, commentId, {
|
|
15526
15942
|
start: startOffset,
|
|
15527
15943
|
end: endOffset
|
|
15528
|
-
});
|
|
15944
|
+
}, view);
|
|
15529
15945
|
return;
|
|
15530
15946
|
}
|
|
15531
|
-
placeMarkersInParagraph(startParagraph, [
|
|
15532
|
-
{ offset: startOffset, node: commentRangeStartMarker(commentId) }
|
|
15533
|
-
]);
|
|
15947
|
+
placeMarkersInParagraph(startParagraph, [{ offset: startOffset, node: commentRangeStartMarker(commentId) }], view);
|
|
15534
15948
|
placeMarkersInParagraph(endParagraph, [
|
|
15535
15949
|
{
|
|
15536
15950
|
offset: endOffset,
|
|
15537
15951
|
node: commentRangeEndMarker(commentId),
|
|
15538
15952
|
follower: commentReferenceRun(commentId)
|
|
15539
15953
|
}
|
|
15540
|
-
]);
|
|
15954
|
+
], view);
|
|
15541
15955
|
}
|
|
15542
15956
|
function addCommentMarkersAroundRun(paragraph, target, commentId) {
|
|
15543
15957
|
function walk(parent) {
|
|
@@ -15654,10 +16068,10 @@ function commentReferenceRun(commentId) {
|
|
|
15654
16068
|
]
|
|
15655
16069
|
}, undefined, true, undefined, this);
|
|
15656
16070
|
}
|
|
15657
|
-
function placeMarkersInParagraph(paragraph, markers) {
|
|
16071
|
+
function placeMarkersInParagraph(paragraph, markers, view = "current") {
|
|
15658
16072
|
if (markers.length === 0)
|
|
15659
16073
|
return;
|
|
15660
|
-
const total = paragraphTextLength(paragraph);
|
|
16074
|
+
const total = paragraphTextLength(paragraph, view);
|
|
15661
16075
|
for (const marker of markers) {
|
|
15662
16076
|
if (marker.offset < 0 || marker.offset > total) {
|
|
15663
16077
|
throw new SpanOutOfRangeError(`Marker offset ${marker.offset} out of paragraph length ${total}`);
|
|
@@ -15665,13 +16079,13 @@ function placeMarkersInParagraph(paragraph, markers) {
|
|
|
15665
16079
|
}
|
|
15666
16080
|
const pending = markers.slice();
|
|
15667
16081
|
const state = { offset: 0, placedCount: 0 };
|
|
15668
|
-
paragraph.children = walkAndPlace(paragraph.children, pending, state);
|
|
16082
|
+
paragraph.children = walkAndPlace(paragraph.children, pending, state, view);
|
|
15669
16083
|
flushAtCurrentOffset(paragraph.children, pending, state);
|
|
15670
16084
|
if (state.placedCount !== markers.length) {
|
|
15671
16085
|
throw new SpanOutOfRangeError(`Could not place comment markers (placed ${state.placedCount} of ${markers.length})`);
|
|
15672
16086
|
}
|
|
15673
16087
|
}
|
|
15674
|
-
function walkAndPlace(children, pending, state) {
|
|
16088
|
+
function walkAndPlace(children, pending, state, view) {
|
|
15675
16089
|
const result = [];
|
|
15676
16090
|
for (const child of children) {
|
|
15677
16091
|
if (child.tag === "w:r") {
|
|
@@ -15714,8 +16128,12 @@ function walkAndPlace(children, pending, state) {
|
|
|
15714
16128
|
continue;
|
|
15715
16129
|
}
|
|
15716
16130
|
if (isRunBearingWrapper(child.tag)) {
|
|
16131
|
+
if (!isWrapperVisibleInView(child.tag, view)) {
|
|
16132
|
+
result.push(child);
|
|
16133
|
+
continue;
|
|
16134
|
+
}
|
|
15717
16135
|
flushAtCurrentOffset(result, pending, state);
|
|
15718
|
-
const innerChildren = walkAndPlace(child.children, pending, state);
|
|
16136
|
+
const innerChildren = walkAndPlace(child.children, pending, state, view);
|
|
15719
16137
|
const wrapper = new XmlNode2(child.tag, { ...child.attributes });
|
|
15720
16138
|
wrapper.children = innerChildren;
|
|
15721
16139
|
result.push(wrapper);
|
|
@@ -15880,10 +16298,16 @@ async function run(args) {
|
|
|
15880
16298
|
allowPositionals: true,
|
|
15881
16299
|
options: {
|
|
15882
16300
|
range: { type: "string" },
|
|
16301
|
+
anchor: { type: "string" },
|
|
16302
|
+
occurrence: { type: "string" },
|
|
15883
16303
|
text: { type: "string" },
|
|
16304
|
+
batch: { type: "string" },
|
|
15884
16305
|
author: { type: "string" },
|
|
16306
|
+
current: { type: "boolean" },
|
|
16307
|
+
baseline: { type: "boolean" },
|
|
15885
16308
|
output: { type: "string", short: "o" },
|
|
15886
16309
|
"dry-run": { type: "boolean" },
|
|
16310
|
+
verbose: { type: "boolean", short: "v" },
|
|
15887
16311
|
help: { type: "boolean", short: "h" }
|
|
15888
16312
|
}
|
|
15889
16313
|
});
|
|
@@ -15895,171 +16319,338 @@ async function run(args) {
|
|
|
15895
16319
|
await writeStdout(HELP);
|
|
15896
16320
|
return EXIT.OK;
|
|
15897
16321
|
}
|
|
16322
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
15898
16323
|
const path = parsed.positionals[0];
|
|
15899
16324
|
if (!path)
|
|
15900
16325
|
return fail("USAGE", "Missing FILE argument", HELP);
|
|
15901
16326
|
const rangeInput = parsed.values.range;
|
|
16327
|
+
const anchorInput = parsed.values.anchor;
|
|
16328
|
+
const batchInput = parsed.values.batch;
|
|
15902
16329
|
const text = parsed.values.text;
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
|
|
15909
|
-
|
|
15910
|
-
|
|
15911
|
-
if (error instanceof LocatorParseError) {
|
|
15912
|
-
return fail("INVALID_LOCATOR", error.message);
|
|
15913
|
-
}
|
|
15914
|
-
throw error;
|
|
16330
|
+
const occurrenceRaw = parsed.values.occurrence;
|
|
16331
|
+
const defaultAuthor = parsed.values.author ?? Bun.env.DOCX_AUTHOR ?? "";
|
|
16332
|
+
const outputPath = parsed.values.output;
|
|
16333
|
+
const dryRun = Boolean(parsed.values["dry-run"]);
|
|
16334
|
+
const wantCurrent = Boolean(parsed.values.current);
|
|
16335
|
+
const wantBaseline = Boolean(parsed.values.baseline);
|
|
16336
|
+
if (wantCurrent && wantBaseline) {
|
|
16337
|
+
return fail("USAGE", "--current and --baseline are mutually exclusive");
|
|
15915
16338
|
}
|
|
15916
|
-
|
|
15917
|
-
|
|
16339
|
+
const findView = wantCurrent ? "current" : wantBaseline ? "baseline" : "accepted";
|
|
16340
|
+
const anchorCount = (rangeInput ? 1 : 0) + (anchorInput ? 1 : 0) + (batchInput ? 1 : 0);
|
|
16341
|
+
if (anchorCount === 0) {
|
|
16342
|
+
return fail("USAGE", "Specify exactly one of --range, --anchor, or --batch", HELP);
|
|
15918
16343
|
}
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
return fail("INVALID_LOCATOR", "comments add supports paragraph locators only (pN, pN:start-end, pN:S-pM:E, or tT:rRcC:pK[:start-end])", "Comments and images are not valid anchors.");
|
|
16344
|
+
if (anchorCount > 1) {
|
|
16345
|
+
return fail("USAGE", "--range, --anchor, and --batch are mutually exclusive", HELP);
|
|
15922
16346
|
}
|
|
15923
|
-
const blockId = target.blockId;
|
|
15924
16347
|
const view = await openOrFail(path);
|
|
15925
16348
|
if (typeof view === "number")
|
|
15926
16349
|
return view;
|
|
15927
|
-
|
|
15928
|
-
if (
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
15934
|
-
|
|
15935
|
-
|
|
15936
|
-
|
|
15937
|
-
|
|
15938
|
-
|
|
15939
|
-
|
|
15940
|
-
|
|
15941
|
-
|
|
15942
|
-
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
16350
|
+
let rawEntries;
|
|
16351
|
+
if (batchInput) {
|
|
16352
|
+
if (text !== undefined || rangeInput || anchorInput || occurrenceRaw) {
|
|
16353
|
+
return fail("USAGE", "--batch reads each entry's range/anchor/text/author from JSONL \u2014 do not pass them on the CLI", HELP);
|
|
16354
|
+
}
|
|
16355
|
+
try {
|
|
16356
|
+
rawEntries = await readJsonlEntries(batchInput);
|
|
16357
|
+
} catch (error) {
|
|
16358
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16359
|
+
return fail("USAGE", `Failed to read batch: ${message}`);
|
|
16360
|
+
}
|
|
16361
|
+
if (rawEntries.length === 0) {
|
|
16362
|
+
return fail("USAGE", "Batch file is empty");
|
|
16363
|
+
}
|
|
16364
|
+
} else {
|
|
16365
|
+
if (!text)
|
|
16366
|
+
return fail("USAGE", "Missing --text TEXT", HELP);
|
|
16367
|
+
const occurrence = occurrenceRaw === undefined ? undefined : Number(occurrenceRaw);
|
|
16368
|
+
if (occurrence !== undefined && (!Number.isInteger(occurrence) || occurrence < 1)) {
|
|
16369
|
+
return fail("USAGE", `--occurrence must be a positive integer (1-indexed), got "${occurrenceRaw}"`);
|
|
16370
|
+
}
|
|
16371
|
+
rawEntries = [
|
|
16372
|
+
{
|
|
16373
|
+
range: rangeInput,
|
|
16374
|
+
anchor: anchorInput,
|
|
16375
|
+
occurrence,
|
|
16376
|
+
text
|
|
16377
|
+
}
|
|
16378
|
+
];
|
|
16379
|
+
}
|
|
16380
|
+
let resolved;
|
|
15948
16381
|
try {
|
|
15949
|
-
|
|
16382
|
+
resolved = rawEntries.map((entry, index) => resolveEntry(view, entry, defaultAuthor, index, batchInput !== undefined, findView));
|
|
15950
16383
|
} catch (error) {
|
|
15951
|
-
if (error instanceof
|
|
15952
|
-
return fail(
|
|
16384
|
+
if (error instanceof EntryError) {
|
|
16385
|
+
return fail(error.code, error.message, error.hint);
|
|
15953
16386
|
}
|
|
15954
16387
|
throw error;
|
|
15955
16388
|
}
|
|
15956
|
-
|
|
15957
|
-
commentsRoot.children.push(/* @__PURE__ */ jsxDEV(CommentBody, {
|
|
15958
|
-
options: {
|
|
15959
|
-
id: numericId,
|
|
15960
|
-
author,
|
|
15961
|
-
date,
|
|
15962
|
-
initials: authorInitials(author),
|
|
15963
|
-
paraId,
|
|
15964
|
-
text
|
|
15965
|
-
}
|
|
15966
|
-
}, undefined, false, undefined, this));
|
|
15967
|
-
await saveDocView(view, outputPath);
|
|
15968
|
-
await respond({
|
|
15969
|
-
ok: true,
|
|
15970
|
-
operation: "comments.add",
|
|
15971
|
-
path: outputPath ?? path,
|
|
15972
|
-
commentId: `c${numericId}`,
|
|
15973
|
-
locator: rangeInput
|
|
15974
|
-
});
|
|
15975
|
-
return EXIT.OK;
|
|
15976
|
-
}
|
|
15977
|
-
async function runCrossBlock(parsed, path, rangeInput, locator, text) {
|
|
15978
|
-
const view = await openOrFail(path);
|
|
15979
|
-
if (typeof view === "number")
|
|
15980
|
-
return view;
|
|
15981
|
-
const startRef = await resolveBlockOrFail(view, locator.start.blockId);
|
|
15982
|
-
if (typeof startRef === "number")
|
|
15983
|
-
return startRef;
|
|
15984
|
-
const endRef = await resolveBlockOrFail(view, locator.end.blockId);
|
|
15985
|
-
if (typeof endRef === "number")
|
|
15986
|
-
return endRef;
|
|
15987
|
-
const author = parsed.values.author ?? Bun.env.DOCX_AUTHOR ?? "";
|
|
15988
|
-
const date = new Date().toISOString();
|
|
15989
|
-
const numericId = nextCommentId(view);
|
|
15990
|
-
const paraId = generateParaId();
|
|
15991
|
-
const outputPath = parsed.values.output;
|
|
15992
|
-
if (parsed.values["dry-run"]) {
|
|
16389
|
+
if (dryRun) {
|
|
15993
16390
|
await respond({
|
|
15994
16391
|
ok: true,
|
|
15995
16392
|
operation: "comments.add",
|
|
15996
16393
|
dryRun: true,
|
|
15997
16394
|
path,
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16395
|
+
...outputPath ? { output: outputPath } : {},
|
|
16396
|
+
batch: resolved.map((entry) => ({
|
|
16397
|
+
locator: entry.locatorString
|
|
16398
|
+
}))
|
|
16001
16399
|
});
|
|
16002
16400
|
return EXIT.OK;
|
|
16003
16401
|
}
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
16402
|
+
const date = new Date().toISOString();
|
|
16403
|
+
const minted = [];
|
|
16404
|
+
for (const entry of resolved) {
|
|
16405
|
+
const numericId = nextCommentId(view);
|
|
16406
|
+
const paraId = generateParaId();
|
|
16407
|
+
try {
|
|
16408
|
+
if (entry.kind === "single") {
|
|
16409
|
+
const paragraphRef = await resolveBlockOrFail(view, entry.blockId);
|
|
16410
|
+
if (typeof paragraphRef === "number") {
|
|
16411
|
+
return paragraphRef;
|
|
16412
|
+
}
|
|
16413
|
+
addCommentMarkersToParagraph(paragraphRef.node, numericId, entry.span, findView);
|
|
16414
|
+
} else {
|
|
16415
|
+
const startRef = await resolveBlockOrFail(view, entry.startBlockId);
|
|
16416
|
+
if (typeof startRef === "number")
|
|
16417
|
+
return startRef;
|
|
16418
|
+
const endRef = await resolveBlockOrFail(view, entry.endBlockId);
|
|
16419
|
+
if (typeof endRef === "number")
|
|
16420
|
+
return endRef;
|
|
16421
|
+
addCommentRangeMarkers(startRef.node, entry.startOffset, endRef.node, entry.endOffset, numericId, findView);
|
|
16422
|
+
}
|
|
16423
|
+
} catch (error) {
|
|
16424
|
+
if (error instanceof SpanOutOfRangeError) {
|
|
16425
|
+
return fail("INVALID_LOCATOR", error.message);
|
|
16426
|
+
}
|
|
16427
|
+
throw error;
|
|
16428
|
+
}
|
|
16429
|
+
const commentsRoot = ensureCommentsPart(view);
|
|
16430
|
+
commentsRoot.children.push(/* @__PURE__ */ jsxDEV(CommentBody, {
|
|
16431
|
+
options: {
|
|
16432
|
+
id: numericId,
|
|
16433
|
+
author: entry.author,
|
|
16434
|
+
date,
|
|
16435
|
+
initials: authorInitials(entry.author),
|
|
16436
|
+
paraId,
|
|
16437
|
+
text: entry.text
|
|
16438
|
+
}
|
|
16439
|
+
}, undefined, false, undefined, this));
|
|
16440
|
+
minted.push({
|
|
16441
|
+
commentId: `c${numericId}`,
|
|
16442
|
+
locator: entry.locatorString
|
|
16443
|
+
});
|
|
16444
|
+
}
|
|
16023
16445
|
await saveDocView(view, outputPath);
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16446
|
+
if (batchInput) {
|
|
16447
|
+
await respond({
|
|
16448
|
+
ok: true,
|
|
16449
|
+
operation: "comments.add",
|
|
16450
|
+
path: outputPath ?? path,
|
|
16451
|
+
batch: minted
|
|
16452
|
+
});
|
|
16453
|
+
} else {
|
|
16454
|
+
const single = minted[0];
|
|
16455
|
+
if (!single) {
|
|
16456
|
+
throw new Error("internal: single-shot path produced no minted entry");
|
|
16457
|
+
}
|
|
16458
|
+
await respondAck({
|
|
16459
|
+
ok: true,
|
|
16460
|
+
operation: "comments.add",
|
|
16461
|
+
path: outputPath ?? path,
|
|
16462
|
+
commentId: single.commentId,
|
|
16463
|
+
locator: single.locator
|
|
16464
|
+
});
|
|
16465
|
+
}
|
|
16031
16466
|
return EXIT.OK;
|
|
16032
16467
|
}
|
|
16033
|
-
|
|
16468
|
+
function resolveEntry(view, raw, defaultAuthor, entryIndex, isBatch, findView) {
|
|
16469
|
+
const labelPrefix = isBatch ? `entry ${entryIndex}: ` : "";
|
|
16470
|
+
const author = raw.author ?? defaultAuthor;
|
|
16471
|
+
const anchorCount = (raw.range ? 1 : 0) + (raw.anchor ? 1 : 0);
|
|
16472
|
+
if (anchorCount === 0) {
|
|
16473
|
+
throw new EntryError("USAGE", `${labelPrefix}must specify either "range" or "anchor"`);
|
|
16474
|
+
}
|
|
16475
|
+
if (anchorCount > 1) {
|
|
16476
|
+
throw new EntryError("USAGE", `${labelPrefix}"range" and "anchor" are mutually exclusive`);
|
|
16477
|
+
}
|
|
16478
|
+
if (typeof raw.text !== "string" || raw.text.length === 0) {
|
|
16479
|
+
throw new EntryError("USAGE", `${labelPrefix}"text" is required`);
|
|
16480
|
+
}
|
|
16481
|
+
if (raw.range) {
|
|
16482
|
+
return resolveLocatorEntry(raw.range, raw.text, author, labelPrefix);
|
|
16483
|
+
}
|
|
16484
|
+
const anchor = raw.anchor;
|
|
16485
|
+
if (!anchor) {
|
|
16486
|
+
throw new EntryError("USAGE", `${labelPrefix}missing anchor`);
|
|
16487
|
+
}
|
|
16488
|
+
const occurrenceExplicit = raw.occurrence !== undefined;
|
|
16489
|
+
const occurrence = raw.occurrence ?? 1;
|
|
16490
|
+
if (!Number.isInteger(occurrence) || occurrence < 1) {
|
|
16491
|
+
throw new EntryError("USAGE", `${labelPrefix}"occurrence" must be a positive integer, got ${JSON.stringify(raw.occurrence)}`);
|
|
16492
|
+
}
|
|
16493
|
+
return resolveAnchorEntry(view, anchor, occurrence, occurrenceExplicit, raw.text, author, labelPrefix, findView);
|
|
16494
|
+
}
|
|
16495
|
+
function resolveLocatorEntry(rangeInput, text, author, labelPrefix) {
|
|
16496
|
+
let locator;
|
|
16497
|
+
try {
|
|
16498
|
+
locator = parseLocator(rangeInput);
|
|
16499
|
+
} catch (error) {
|
|
16500
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16501
|
+
throw new EntryError("INVALID_LOCATOR", `${labelPrefix}${message}`);
|
|
16502
|
+
}
|
|
16503
|
+
if (locator.kind === "range") {
|
|
16504
|
+
return {
|
|
16505
|
+
kind: "range",
|
|
16506
|
+
startBlockId: locator.start.blockId,
|
|
16507
|
+
startOffset: locator.start.offset,
|
|
16508
|
+
endBlockId: locator.end.blockId,
|
|
16509
|
+
endOffset: locator.end.offset,
|
|
16510
|
+
text,
|
|
16511
|
+
author,
|
|
16512
|
+
locatorString: rangeInput
|
|
16513
|
+
};
|
|
16514
|
+
}
|
|
16515
|
+
const target = locatorToBlockTarget(locator);
|
|
16516
|
+
if (!target) {
|
|
16517
|
+
throw new EntryError("INVALID_LOCATOR", `${labelPrefix}comments add supports paragraph locators only (pN, pN:start-end, pN:S-pM:E, or tT:rRcC:pK[:start-end])`, "Comments and images are not valid anchors.");
|
|
16518
|
+
}
|
|
16519
|
+
return {
|
|
16520
|
+
kind: "single",
|
|
16521
|
+
blockId: target.blockId,
|
|
16522
|
+
span: target.span,
|
|
16523
|
+
text,
|
|
16524
|
+
author,
|
|
16525
|
+
locatorString: rangeInput
|
|
16526
|
+
};
|
|
16527
|
+
}
|
|
16528
|
+
function resolveAnchorEntry(view, anchor, occurrence, occurrenceExplicit, text, author, labelPrefix, findView) {
|
|
16529
|
+
const result = findTextSpans(view.doc, anchor, { view: findView });
|
|
16530
|
+
const matches = result.matches;
|
|
16531
|
+
if (matches.length === 0) {
|
|
16532
|
+
throw new EntryError("MATCH_NOT_FOUND", `${labelPrefix}anchor not found: ${JSON.stringify(anchor)}`, result.normalizedQuery ? `Searched as "${result.normalizedQuery}" after normalization (${(result.normalizationApplied ?? []).join(", ")}).` : undefined);
|
|
16533
|
+
}
|
|
16534
|
+
if (matches.length > 1 && !occurrenceExplicit) {
|
|
16535
|
+
throw new EntryError("USAGE", `${labelPrefix}anchor matches ${matches.length} times; pass --occurrence N (1..${matches.length}) to disambiguate`);
|
|
16536
|
+
}
|
|
16537
|
+
if (occurrence > matches.length) {
|
|
16538
|
+
throw new EntryError("MATCH_NOT_FOUND", `${labelPrefix}only ${matches.length} match(es) for anchor; --occurrence ${occurrence} is out of range`);
|
|
16539
|
+
}
|
|
16540
|
+
const match = matches[occurrence - 1];
|
|
16541
|
+
if (!match) {
|
|
16542
|
+
throw new EntryError("MATCH_NOT_FOUND", `${labelPrefix}match index ${occurrence} not found`);
|
|
16543
|
+
}
|
|
16544
|
+
const locatorString = `${match.blockId}:${match.start}-${match.end}`;
|
|
16545
|
+
return {
|
|
16546
|
+
kind: "single",
|
|
16547
|
+
blockId: match.blockId,
|
|
16548
|
+
span: { start: match.start, end: match.end },
|
|
16549
|
+
text,
|
|
16550
|
+
author,
|
|
16551
|
+
locatorString
|
|
16552
|
+
};
|
|
16553
|
+
}
|
|
16554
|
+
async function readJsonlEntries(source) {
|
|
16555
|
+
const raw = source === "-" ? await new Response(Bun.stdin.stream()).text() : await Bun.file(source).text();
|
|
16556
|
+
const entries = [];
|
|
16557
|
+
const lines = raw.split(`
|
|
16558
|
+
`);
|
|
16559
|
+
for (let index = 0;index < lines.length; index++) {
|
|
16560
|
+
const lineRaw = lines[index];
|
|
16561
|
+
if (lineRaw === undefined)
|
|
16562
|
+
continue;
|
|
16563
|
+
const line = lineRaw.trim();
|
|
16564
|
+
if (line.length === 0)
|
|
16565
|
+
continue;
|
|
16566
|
+
let parsed;
|
|
16567
|
+
try {
|
|
16568
|
+
parsed = JSON.parse(line);
|
|
16569
|
+
} catch (error) {
|
|
16570
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16571
|
+
throw new Error(`line ${index + 1}: invalid JSON (${message})`);
|
|
16572
|
+
}
|
|
16573
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16574
|
+
throw new Error(`line ${index + 1}: expected a JSON object`);
|
|
16575
|
+
}
|
|
16576
|
+
entries.push(parsed);
|
|
16577
|
+
}
|
|
16578
|
+
return entries;
|
|
16579
|
+
}
|
|
16580
|
+
var HELP = `docx comments add \u2014 anchor a new comment to a locator or phrase
|
|
16034
16581
|
|
|
16035
16582
|
Usage:
|
|
16036
|
-
docx comments add FILE [options]
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
--
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
--
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16583
|
+
docx comments add FILE --range LOCATOR --text TEXT [options]
|
|
16584
|
+
docx comments add FILE --anchor PHRASE --text TEXT [options]
|
|
16585
|
+
docx comments add FILE --batch FILE.jsonl [options]
|
|
16586
|
+
docx comments add FILE --batch - [options] # read JSONL from stdin
|
|
16587
|
+
|
|
16588
|
+
Anchor (one required, mutually exclusive):
|
|
16589
|
+
--range LOCATOR Where to anchor. Supports:
|
|
16590
|
+
pN whole paragraph
|
|
16591
|
+
pN:S-E chars S..E of pN
|
|
16592
|
+
pN:S-pM:E chars S of pN through char E of pM
|
|
16593
|
+
tT:rRcC:pK whole cell paragraph
|
|
16594
|
+
tT:rRcC:pK:S-E chars S..E of cell paragraph
|
|
16595
|
+
--anchor PHRASE Find the phrase via the same matcher as \`docx find\`
|
|
16596
|
+
(default: accepted view, normalization on). The match
|
|
16597
|
+
is converted to a pN:S-E locator and used as the
|
|
16598
|
+
anchor. Errors if the phrase matches more than once
|
|
16599
|
+
without --occurrence.
|
|
16600
|
+
--batch PATH Read a JSONL file (one entry per line). Each entry is
|
|
16601
|
+
a JSON object with the same shape as a single-shot
|
|
16602
|
+
call: { range | anchor (+ optional occurrence), text,
|
|
16603
|
+
optional author }. Use - for stdin.
|
|
16604
|
+
|
|
16605
|
+
Per-entry fields:
|
|
16606
|
+
--text TEXT Comment body. Required for single-shot; required as
|
|
16607
|
+
a "text" field on every JSONL entry.
|
|
16608
|
+
--occurrence N For --anchor: pick the Nth match (1-indexed,
|
|
16609
|
+
default 1). Errors if N is out of range.
|
|
16610
|
+
--author NAME Author name (default: $DOCX_AUTHOR)
|
|
16611
|
+
|
|
16612
|
+
View (for resolving --range offsets and --anchor matches):
|
|
16613
|
+
--current Resolve offsets in the raw concatenation (with both
|
|
16614
|
+
ins and del text). Use when offsets came from
|
|
16615
|
+
\`docx find --current\` or hand-counted bytes.
|
|
16616
|
+
--baseline Resolve offsets in the pre-change view (skip ins/
|
|
16617
|
+
moveTo).
|
|
16618
|
+
Default: accepted view (skip del/moveFrom) \u2014 matches
|
|
16619
|
+
\`find\`'s default. Mutually exclusive.
|
|
16620
|
+
|
|
16621
|
+
General options:
|
|
16622
|
+
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16623
|
+
--dry-run Print what would be added; do not write the file
|
|
16624
|
+
-v, --verbose Print the success ack JSON (default: silent on success
|
|
16625
|
+
for single; batch always prints the minted ids)
|
|
16626
|
+
-h, --help Show this help
|
|
16052
16627
|
|
|
16053
16628
|
Examples:
|
|
16054
16629
|
docx comments add doc.docx --range p3 --text "Reconsider this paragraph"
|
|
16055
|
-
docx comments add doc.docx --
|
|
16056
|
-
docx comments add doc.docx --
|
|
16057
|
-
|
|
16630
|
+
docx comments add doc.docx --anchor "fatally flawed" --text "Cite source?"
|
|
16631
|
+
docx comments add doc.docx --anchor "TODO" --occurrence 2 --text "Pick up here"
|
|
16632
|
+
docx comments add doc.docx --batch reviews.jsonl
|
|
16633
|
+
cat reviews.jsonl | docx comments add doc.docx --batch -
|
|
16634
|
+
|
|
16635
|
+
Batch JSONL example:
|
|
16636
|
+
{"range": "p3:5-20", "text": "Sharper wording?"}
|
|
16637
|
+
{"anchor": "fatally flawed", "text": "Cite Bianco here.", "author": "Reviewer"}
|
|
16638
|
+
`, EntryError;
|
|
16058
16639
|
var init_add = __esm(() => {
|
|
16059
16640
|
init_core();
|
|
16060
16641
|
init_respond();
|
|
16061
16642
|
init_helpers();
|
|
16062
16643
|
init_jsx_dev_runtime();
|
|
16644
|
+
EntryError = class EntryError extends Error {
|
|
16645
|
+
code;
|
|
16646
|
+
hint;
|
|
16647
|
+
constructor(code, message, hint) {
|
|
16648
|
+
super(message);
|
|
16649
|
+
this.code = code;
|
|
16650
|
+
this.hint = hint;
|
|
16651
|
+
this.name = "EntryError";
|
|
16652
|
+
}
|
|
16653
|
+
};
|
|
16063
16654
|
});
|
|
16064
16655
|
|
|
16065
16656
|
// src/cli/comments/delete.ts
|
|
@@ -16075,9 +16666,11 @@ async function run2(args) {
|
|
|
16075
16666
|
args,
|
|
16076
16667
|
allowPositionals: true,
|
|
16077
16668
|
options: {
|
|
16078
|
-
id: { type: "string" },
|
|
16669
|
+
id: { type: "string", multiple: true },
|
|
16670
|
+
batch: { type: "string" },
|
|
16079
16671
|
output: { type: "string", short: "o" },
|
|
16080
16672
|
"dry-run": { type: "boolean" },
|
|
16673
|
+
verbose: { type: "boolean", short: "v" },
|
|
16081
16674
|
help: { type: "boolean", short: "h" }
|
|
16082
16675
|
}
|
|
16083
16676
|
});
|
|
@@ -16089,20 +16682,49 @@ async function run2(args) {
|
|
|
16089
16682
|
await writeStdout(HELP2);
|
|
16090
16683
|
return EXIT.OK;
|
|
16091
16684
|
}
|
|
16685
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
16092
16686
|
const path = parsed.positionals[0];
|
|
16093
16687
|
if (!path)
|
|
16094
16688
|
return fail("USAGE", "Missing FILE argument", HELP2);
|
|
16095
|
-
const
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16689
|
+
const idsRaw = parsed.values.id ?? [];
|
|
16690
|
+
const batchInput = parsed.values.batch;
|
|
16691
|
+
if (idsRaw.length > 0 && batchInput) {
|
|
16692
|
+
return fail("USAGE", "--id and --batch are mutually exclusive", HELP2);
|
|
16693
|
+
}
|
|
16694
|
+
if (idsRaw.length === 0 && !batchInput) {
|
|
16695
|
+
return fail("USAGE", "Specify --id cN (repeatable) or --batch FILE", HELP2);
|
|
16696
|
+
}
|
|
16697
|
+
let rawIds;
|
|
16698
|
+
if (batchInput) {
|
|
16699
|
+
try {
|
|
16700
|
+
rawIds = await readJsonlIds(batchInput);
|
|
16701
|
+
} catch (error) {
|
|
16702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16703
|
+
return fail("USAGE", `Failed to read batch: ${message}`);
|
|
16704
|
+
}
|
|
16705
|
+
if (rawIds.length === 0) {
|
|
16706
|
+
return fail("USAGE", "Batch file is empty");
|
|
16707
|
+
}
|
|
16708
|
+
} else {
|
|
16709
|
+
rawIds = idsRaw;
|
|
16710
|
+
}
|
|
16711
|
+
const seen = new Set;
|
|
16712
|
+
const ordered = [];
|
|
16713
|
+
for (const raw of rawIds) {
|
|
16714
|
+
const normalized = raw.startsWith("c") ? raw : `c${raw}`;
|
|
16715
|
+
if (seen.has(normalized))
|
|
16716
|
+
continue;
|
|
16717
|
+
seen.add(normalized);
|
|
16718
|
+
ordered.push(normalized);
|
|
16719
|
+
}
|
|
16100
16720
|
const view = await openOrFail(path);
|
|
16101
16721
|
if (typeof view === "number")
|
|
16102
16722
|
return view;
|
|
16103
|
-
const
|
|
16104
|
-
|
|
16105
|
-
|
|
16723
|
+
for (const commentId of ordered) {
|
|
16724
|
+
const numericId = commentId.slice(1);
|
|
16725
|
+
if (!findCommentByNumericId(view, numericId)) {
|
|
16726
|
+
return fail("COMMENT_NOT_FOUND", `Comment not found: ${commentId}`);
|
|
16727
|
+
}
|
|
16106
16728
|
}
|
|
16107
16729
|
const outputPath = parsed.values.output;
|
|
16108
16730
|
if (parsed.values["dry-run"]) {
|
|
@@ -16111,46 +16733,104 @@ async function run2(args) {
|
|
|
16111
16733
|
operation: "comments.delete",
|
|
16112
16734
|
dryRun: true,
|
|
16113
16735
|
path,
|
|
16114
|
-
|
|
16115
|
-
|
|
16736
|
+
...outputPath ? { output: outputPath } : {},
|
|
16737
|
+
batch: ordered.map((commentId) => ({ commentId }))
|
|
16116
16738
|
});
|
|
16117
16739
|
return EXIT.OK;
|
|
16118
16740
|
}
|
|
16119
|
-
const
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
commentReference
|
|
16123
|
-
|
|
16124
|
-
const
|
|
16125
|
-
|
|
16126
|
-
|
|
16741
|
+
for (const commentId of ordered) {
|
|
16742
|
+
const numericId = commentId.slice(1);
|
|
16743
|
+
const commentReference = findCommentByNumericId(view, numericId);
|
|
16744
|
+
if (!commentReference)
|
|
16745
|
+
continue;
|
|
16746
|
+
const paraId = findCommentParaId(view, commentId);
|
|
16747
|
+
const commentIndex = commentReference.parent.indexOf(commentReference.node);
|
|
16748
|
+
if (commentIndex !== -1)
|
|
16749
|
+
commentReference.parent.splice(commentIndex, 1);
|
|
16750
|
+
if (paraId && view.commentsExtTree) {
|
|
16751
|
+
const extRoot = XmlNode2.findRoot(view.commentsExtTree, "w15:commentsEx");
|
|
16752
|
+
if (extRoot) {
|
|
16753
|
+
extRoot.children = extRoot.children.filter((child) => !(child.tag === "w15:commentEx" && child.getAttribute("w15:paraId") === paraId));
|
|
16754
|
+
}
|
|
16127
16755
|
}
|
|
16756
|
+
removeCommentMarkers(view.documentTree, numericId);
|
|
16128
16757
|
}
|
|
16129
|
-
removeCommentMarkers(view.documentTree, numericId);
|
|
16130
16758
|
await saveDocView(view, outputPath);
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16759
|
+
if (batchInput || ordered.length > 1) {
|
|
16760
|
+
await respond({
|
|
16761
|
+
ok: true,
|
|
16762
|
+
operation: "comments.delete",
|
|
16763
|
+
path: outputPath ?? path,
|
|
16764
|
+
batch: ordered.map((commentId) => ({ commentId }))
|
|
16765
|
+
});
|
|
16766
|
+
} else {
|
|
16767
|
+
const single = ordered[0];
|
|
16768
|
+
if (!single) {
|
|
16769
|
+
throw new Error("internal: empty single-shot id list");
|
|
16770
|
+
}
|
|
16771
|
+
await respondAck({
|
|
16772
|
+
ok: true,
|
|
16773
|
+
operation: "comments.delete",
|
|
16774
|
+
path: outputPath ?? path,
|
|
16775
|
+
commentId: single
|
|
16776
|
+
});
|
|
16777
|
+
}
|
|
16137
16778
|
return EXIT.OK;
|
|
16138
16779
|
}
|
|
16139
|
-
|
|
16780
|
+
async function readJsonlIds(source) {
|
|
16781
|
+
const raw = source === "-" ? await new Response(Bun.stdin.stream()).text() : await Bun.file(source).text();
|
|
16782
|
+
const ids = [];
|
|
16783
|
+
const lines = raw.split(`
|
|
16784
|
+
`);
|
|
16785
|
+
for (let index = 0;index < lines.length; index++) {
|
|
16786
|
+
const lineRaw = lines[index];
|
|
16787
|
+
if (lineRaw === undefined)
|
|
16788
|
+
continue;
|
|
16789
|
+
const line = lineRaw.trim();
|
|
16790
|
+
if (line.length === 0)
|
|
16791
|
+
continue;
|
|
16792
|
+
let parsed;
|
|
16793
|
+
try {
|
|
16794
|
+
parsed = JSON.parse(line);
|
|
16795
|
+
} catch (error) {
|
|
16796
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16797
|
+
throw new Error(`line ${index + 1}: invalid JSON (${message})`);
|
|
16798
|
+
}
|
|
16799
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16800
|
+
throw new Error(`line ${index + 1}: expected a JSON object`);
|
|
16801
|
+
}
|
|
16802
|
+
const entry = parsed;
|
|
16803
|
+
if (typeof entry.id !== "string" || entry.id.length === 0) {
|
|
16804
|
+
throw new Error(`line ${index + 1}: missing "id"`);
|
|
16805
|
+
}
|
|
16806
|
+
ids.push(entry.id);
|
|
16807
|
+
}
|
|
16808
|
+
return ids;
|
|
16809
|
+
}
|
|
16810
|
+
var HELP2 = `docx comments delete \u2014 remove one or more comments
|
|
16140
16811
|
|
|
16141
16812
|
Usage:
|
|
16142
|
-
docx comments delete FILE --id cN [options]
|
|
16813
|
+
docx comments delete FILE --id cN [--id cM ...] [options]
|
|
16814
|
+
docx comments delete FILE --batch FILE.jsonl [options]
|
|
16815
|
+
docx comments delete FILE --batch - [options] # JSONL from stdin
|
|
16143
16816
|
|
|
16144
|
-
|
|
16145
|
-
--id ID
|
|
16817
|
+
Anchor (one required, mutually exclusive):
|
|
16818
|
+
--id ID Comment id (e.g., c0). Repeat for multiple ids:
|
|
16819
|
+
--id c1 --id c3 --id c5. All ids are validated against
|
|
16820
|
+
the pre-mutation tree, so the batch is atomic.
|
|
16821
|
+
--batch PATH JSONL with one {"id": "cN"} per line. Use - for stdin.
|
|
16146
16822
|
|
|
16147
16823
|
Optional:
|
|
16148
|
-
-o, --output PATH
|
|
16149
|
-
--dry-run
|
|
16150
|
-
-
|
|
16824
|
+
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16825
|
+
--dry-run Print what would be removed; do not write the file
|
|
16826
|
+
-v, --verbose Print the success ack JSON (default: silent on success
|
|
16827
|
+
for single; batch always prints the removed ids)
|
|
16828
|
+
-h, --help Show this help
|
|
16151
16829
|
|
|
16152
16830
|
Examples:
|
|
16153
16831
|
docx comments delete doc.docx --id c2
|
|
16832
|
+
docx comments delete doc.docx --id c1 --id c3 --id c7
|
|
16833
|
+
docx comments delete doc.docx --batch removals.jsonl
|
|
16154
16834
|
`;
|
|
16155
16835
|
var init_delete = __esm(() => {
|
|
16156
16836
|
init_core();
|
|
@@ -16255,6 +16935,7 @@ async function run4(args) {
|
|
|
16255
16935
|
author: { type: "string" },
|
|
16256
16936
|
output: { type: "string", short: "o" },
|
|
16257
16937
|
"dry-run": { type: "boolean" },
|
|
16938
|
+
verbose: { type: "boolean", short: "v" },
|
|
16258
16939
|
help: { type: "boolean", short: "h" }
|
|
16259
16940
|
}
|
|
16260
16941
|
});
|
|
@@ -16266,6 +16947,7 @@ async function run4(args) {
|
|
|
16266
16947
|
await writeStdout(HELP4);
|
|
16267
16948
|
return EXIT.OK;
|
|
16268
16949
|
}
|
|
16950
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
16269
16951
|
const path = parsed.positionals[0];
|
|
16270
16952
|
if (!path)
|
|
16271
16953
|
return fail("USAGE", "Missing FILE argument", HELP4);
|
|
@@ -16322,7 +17004,7 @@ async function run4(args) {
|
|
|
16322
17004
|
"w15:done": "0"
|
|
16323
17005
|
}));
|
|
16324
17006
|
await saveDocView(view, outputPath);
|
|
16325
|
-
await
|
|
17007
|
+
await respondAck({
|
|
16326
17008
|
ok: true,
|
|
16327
17009
|
operation: "comments.reply",
|
|
16328
17010
|
path: outputPath ?? path,
|
|
@@ -16344,6 +17026,7 @@ Optional:
|
|
|
16344
17026
|
--author NAME Author name (default: $DOCX_AUTHOR)
|
|
16345
17027
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16346
17028
|
--dry-run Print what would be added; do not write the file
|
|
17029
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
16347
17030
|
-h, --help Show this help
|
|
16348
17031
|
|
|
16349
17032
|
Examples:
|
|
@@ -16370,10 +17053,12 @@ async function run5(args) {
|
|
|
16370
17053
|
args,
|
|
16371
17054
|
allowPositionals: true,
|
|
16372
17055
|
options: {
|
|
16373
|
-
id: { type: "string" },
|
|
17056
|
+
id: { type: "string", multiple: true },
|
|
17057
|
+
batch: { type: "string" },
|
|
16374
17058
|
unset: { type: "boolean" },
|
|
16375
17059
|
output: { type: "string", short: "o" },
|
|
16376
17060
|
"dry-run": { type: "boolean" },
|
|
17061
|
+
verbose: { type: "boolean", short: "v" },
|
|
16377
17062
|
help: { type: "boolean", short: "h" }
|
|
16378
17063
|
}
|
|
16379
17064
|
});
|
|
@@ -16385,24 +17070,56 @@ async function run5(args) {
|
|
|
16385
17070
|
await writeStdout(HELP5);
|
|
16386
17071
|
return EXIT.OK;
|
|
16387
17072
|
}
|
|
17073
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
16388
17074
|
const path = parsed.positionals[0];
|
|
16389
17075
|
if (!path)
|
|
16390
17076
|
return fail("USAGE", "Missing FILE argument", HELP5);
|
|
16391
|
-
const
|
|
16392
|
-
|
|
16393
|
-
return fail("USAGE", "Missing --id COMMENT_ID", HELP5);
|
|
16394
|
-
const numericId = idInput.startsWith("c") ? idInput.slice(1) : idInput;
|
|
17077
|
+
const idsRaw = parsed.values.id ?? [];
|
|
17078
|
+
const batchInput = parsed.values.batch;
|
|
16395
17079
|
const resolved = !parsed.values.unset;
|
|
17080
|
+
if (idsRaw.length > 0 && batchInput) {
|
|
17081
|
+
return fail("USAGE", "--id and --batch are mutually exclusive", HELP5);
|
|
17082
|
+
}
|
|
17083
|
+
if (idsRaw.length === 0 && !batchInput) {
|
|
17084
|
+
return fail("USAGE", "Specify --id cN (repeatable) or --batch FILE", HELP5);
|
|
17085
|
+
}
|
|
17086
|
+
let rawIds;
|
|
17087
|
+
if (batchInput) {
|
|
17088
|
+
try {
|
|
17089
|
+
rawIds = await readJsonlIds2(batchInput);
|
|
17090
|
+
} catch (error) {
|
|
17091
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17092
|
+
return fail("USAGE", `Failed to read batch: ${message}`);
|
|
17093
|
+
}
|
|
17094
|
+
if (rawIds.length === 0) {
|
|
17095
|
+
return fail("USAGE", "Batch file is empty");
|
|
17096
|
+
}
|
|
17097
|
+
} else {
|
|
17098
|
+
rawIds = idsRaw;
|
|
17099
|
+
}
|
|
17100
|
+
const seen = new Set;
|
|
17101
|
+
const ordered = [];
|
|
17102
|
+
for (const raw of rawIds) {
|
|
17103
|
+
const normalized = raw.startsWith("c") ? raw : `c${raw}`;
|
|
17104
|
+
if (seen.has(normalized))
|
|
17105
|
+
continue;
|
|
17106
|
+
seen.add(normalized);
|
|
17107
|
+
ordered.push(normalized);
|
|
17108
|
+
}
|
|
16396
17109
|
const view = await openOrFail(path);
|
|
16397
17110
|
if (typeof view === "number")
|
|
16398
17111
|
return view;
|
|
16399
|
-
const
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
17112
|
+
const paraIdByCommentId = new Map;
|
|
17113
|
+
for (const commentId of ordered) {
|
|
17114
|
+
const numericId = commentId.slice(1);
|
|
17115
|
+
if (!findCommentByNumericId(view, numericId)) {
|
|
17116
|
+
return fail("COMMENT_NOT_FOUND", `Comment not found: ${commentId}`);
|
|
17117
|
+
}
|
|
17118
|
+
const paraId = ensureCommentParaId(view, commentId);
|
|
17119
|
+
if (!paraId) {
|
|
17120
|
+
return fail("COMMENT_NOT_FOUND", `Comment ${commentId} could not be assigned a w14:paraId.`);
|
|
17121
|
+
}
|
|
17122
|
+
paraIdByCommentId.set(commentId, paraId);
|
|
16406
17123
|
}
|
|
16407
17124
|
const outputPath = parsed.values.output;
|
|
16408
17125
|
if (parsed.values["dry-run"]) {
|
|
@@ -16411,49 +17128,108 @@ async function run5(args) {
|
|
|
16411
17128
|
operation: "comments.resolve",
|
|
16412
17129
|
dryRun: true,
|
|
16413
17130
|
path,
|
|
16414
|
-
commentId: `c${numericId}`,
|
|
16415
17131
|
resolved,
|
|
16416
|
-
...outputPath ? { output: outputPath } : {}
|
|
17132
|
+
...outputPath ? { output: outputPath } : {},
|
|
17133
|
+
batch: ordered.map((commentId) => ({ commentId, resolved }))
|
|
16417
17134
|
});
|
|
16418
17135
|
return EXIT.OK;
|
|
16419
17136
|
}
|
|
16420
17137
|
const extRoot = ensureCommentsExtPart(view);
|
|
16421
|
-
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
|
|
17138
|
+
for (const commentId of ordered) {
|
|
17139
|
+
const paraId = paraIdByCommentId.get(commentId);
|
|
17140
|
+
if (!paraId) {
|
|
17141
|
+
throw new Error(`internal: missing paraId for ${commentId}`);
|
|
17142
|
+
}
|
|
17143
|
+
let entry = extRoot.children.find((child) => child.tag === "w15:commentEx" && child.getAttribute("w15:paraId") === paraId);
|
|
17144
|
+
if (!entry) {
|
|
17145
|
+
entry = new XmlNode2("w15:commentEx", { "w15:paraId": paraId });
|
|
17146
|
+
extRoot.children.push(entry);
|
|
17147
|
+
}
|
|
17148
|
+
if (resolved)
|
|
17149
|
+
entry.setAttribute("w15:done", "1");
|
|
17150
|
+
else
|
|
17151
|
+
delete entry.attributes["w15:done"];
|
|
16425
17152
|
}
|
|
16426
|
-
if (resolved)
|
|
16427
|
-
entry.setAttribute("w15:done", "1");
|
|
16428
|
-
else
|
|
16429
|
-
delete entry.attributes["w15:done"];
|
|
16430
17153
|
await saveDocView(view, outputPath);
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
|
|
17154
|
+
if (batchInput || ordered.length > 1) {
|
|
17155
|
+
await respond({
|
|
17156
|
+
ok: true,
|
|
17157
|
+
operation: "comments.resolve",
|
|
17158
|
+
path: outputPath ?? path,
|
|
17159
|
+
resolved,
|
|
17160
|
+
batch: ordered.map((commentId) => ({ commentId, resolved }))
|
|
17161
|
+
});
|
|
17162
|
+
} else {
|
|
17163
|
+
const single = ordered[0];
|
|
17164
|
+
if (!single) {
|
|
17165
|
+
throw new Error("internal: empty single-shot id list");
|
|
17166
|
+
}
|
|
17167
|
+
await respondAck({
|
|
17168
|
+
ok: true,
|
|
17169
|
+
operation: "comments.resolve",
|
|
17170
|
+
path: outputPath ?? path,
|
|
17171
|
+
commentId: single,
|
|
17172
|
+
resolved
|
|
17173
|
+
});
|
|
17174
|
+
}
|
|
16438
17175
|
return EXIT.OK;
|
|
16439
17176
|
}
|
|
16440
|
-
|
|
17177
|
+
async function readJsonlIds2(source) {
|
|
17178
|
+
const raw = source === "-" ? await new Response(Bun.stdin.stream()).text() : await Bun.file(source).text();
|
|
17179
|
+
const ids = [];
|
|
17180
|
+
const lines = raw.split(`
|
|
17181
|
+
`);
|
|
17182
|
+
for (let index = 0;index < lines.length; index++) {
|
|
17183
|
+
const lineRaw = lines[index];
|
|
17184
|
+
if (lineRaw === undefined)
|
|
17185
|
+
continue;
|
|
17186
|
+
const line = lineRaw.trim();
|
|
17187
|
+
if (line.length === 0)
|
|
17188
|
+
continue;
|
|
17189
|
+
let parsed;
|
|
17190
|
+
try {
|
|
17191
|
+
parsed = JSON.parse(line);
|
|
17192
|
+
} catch (error) {
|
|
17193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17194
|
+
throw new Error(`line ${index + 1}: invalid JSON (${message})`);
|
|
17195
|
+
}
|
|
17196
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
17197
|
+
throw new Error(`line ${index + 1}: expected a JSON object`);
|
|
17198
|
+
}
|
|
17199
|
+
const entry = parsed;
|
|
17200
|
+
if (typeof entry.id !== "string" || entry.id.length === 0) {
|
|
17201
|
+
throw new Error(`line ${index + 1}: missing "id"`);
|
|
17202
|
+
}
|
|
17203
|
+
ids.push(entry.id);
|
|
17204
|
+
}
|
|
17205
|
+
return ids;
|
|
17206
|
+
}
|
|
17207
|
+
var HELP5 = `docx comments resolve \u2014 mark one or more comments resolved
|
|
16441
17208
|
|
|
16442
17209
|
Usage:
|
|
16443
|
-
docx comments resolve FILE --id cN [options]
|
|
17210
|
+
docx comments resolve FILE --id cN [--id cM ...] [options]
|
|
17211
|
+
docx comments resolve FILE --batch FILE.jsonl [options]
|
|
17212
|
+
docx comments resolve FILE --batch - [options] # JSONL from stdin
|
|
16444
17213
|
|
|
16445
|
-
|
|
16446
|
-
--id ID
|
|
17214
|
+
Anchor (one required, mutually exclusive):
|
|
17215
|
+
--id ID Comment id (e.g., c0). Repeat for multiple ids:
|
|
17216
|
+
--id c1 --id c3 --id c5. All ids are validated against
|
|
17217
|
+
the pre-mutation tree, so the batch is atomic.
|
|
17218
|
+
--batch PATH JSONL with one {"id": "cN"} per line. Use - for stdin.
|
|
16447
17219
|
|
|
16448
17220
|
Optional:
|
|
16449
|
-
--unset
|
|
16450
|
-
|
|
16451
|
-
|
|
16452
|
-
-
|
|
17221
|
+
--unset Mark unresolved instead of resolved (applies to all
|
|
17222
|
+
ids in the batch)
|
|
17223
|
+
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17224
|
+
--dry-run Print what would change; do not write the file
|
|
17225
|
+
-v, --verbose Print the success ack JSON (default: silent on success
|
|
17226
|
+
for single; batch always prints the affected ids)
|
|
17227
|
+
-h, --help Show this help
|
|
16453
17228
|
|
|
16454
17229
|
Examples:
|
|
16455
17230
|
docx comments resolve doc.docx --id c2
|
|
16456
|
-
docx comments resolve doc.docx --id
|
|
17231
|
+
docx comments resolve doc.docx --id c1 --id c3 --unset
|
|
17232
|
+
docx comments resolve doc.docx --batch resolutions.jsonl
|
|
16457
17233
|
`;
|
|
16458
17234
|
var init_resolve2 = __esm(() => {
|
|
16459
17235
|
init_core();
|
|
@@ -16611,6 +17387,7 @@ async function run7(args) {
|
|
|
16611
17387
|
author: { type: "string" },
|
|
16612
17388
|
text: { type: "string" },
|
|
16613
17389
|
force: { type: "boolean" },
|
|
17390
|
+
verbose: { type: "boolean", short: "v" },
|
|
16614
17391
|
help: { type: "boolean", short: "h" }
|
|
16615
17392
|
}
|
|
16616
17393
|
});
|
|
@@ -16621,6 +17398,7 @@ async function run7(args) {
|
|
|
16621
17398
|
await writeStdout(HELP7);
|
|
16622
17399
|
return EXIT.OK;
|
|
16623
17400
|
}
|
|
17401
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
16624
17402
|
const path = parsed.positionals[0];
|
|
16625
17403
|
if (!path) {
|
|
16626
17404
|
return fail("USAGE", "Missing FILE argument", HELP7);
|
|
@@ -16644,7 +17422,7 @@ async function run7(args) {
|
|
|
16644
17422
|
compressionOptions: { level: 6 }
|
|
16645
17423
|
});
|
|
16646
17424
|
await writeAtomic(path, buf);
|
|
16647
|
-
await
|
|
17425
|
+
await respondAck({
|
|
16648
17426
|
ok: true,
|
|
16649
17427
|
operation: "create",
|
|
16650
17428
|
path,
|
|
@@ -16663,6 +17441,7 @@ Options:
|
|
|
16663
17441
|
--author TEXT Document author (default: $DOCX_AUTHOR)
|
|
16664
17442
|
--text TEXT Seed first paragraph with this text
|
|
16665
17443
|
--force Overwrite if file exists
|
|
17444
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
16666
17445
|
-h, --help Show this help
|
|
16667
17446
|
|
|
16668
17447
|
Examples:
|
|
@@ -16683,18 +17462,27 @@ __export(exports_delete2, {
|
|
|
16683
17462
|
});
|
|
16684
17463
|
import { parseArgs as parseArgs7 } from "util";
|
|
16685
17464
|
async function run8(args) {
|
|
17465
|
+
const opts = await parseAndValidateOptions(args);
|
|
17466
|
+
if (typeof opts === "number")
|
|
17467
|
+
return opts;
|
|
17468
|
+
const view = await openOrFail(opts.filePath);
|
|
17469
|
+
if (typeof view === "number")
|
|
17470
|
+
return view;
|
|
17471
|
+
const blockRef = await resolveBlockOrFail(view, opts.locator);
|
|
17472
|
+
if (typeof blockRef === "number")
|
|
17473
|
+
return blockRef;
|
|
17474
|
+
if (blockRef.node.tag === "w:sectPr") {
|
|
17475
|
+
return commitSectionDelete(view, blockRef, opts);
|
|
17476
|
+
}
|
|
17477
|
+
return commitBlockDelete(view, blockRef, opts);
|
|
17478
|
+
}
|
|
17479
|
+
async function parseAndValidateOptions(args) {
|
|
16686
17480
|
let parsed;
|
|
16687
17481
|
try {
|
|
16688
17482
|
parsed = parseArgs7({
|
|
16689
17483
|
args,
|
|
16690
17484
|
allowPositionals: true,
|
|
16691
|
-
options:
|
|
16692
|
-
at: { type: "string" },
|
|
16693
|
-
author: { type: "string" },
|
|
16694
|
-
output: { type: "string", short: "o" },
|
|
16695
|
-
"dry-run": { type: "boolean" },
|
|
16696
|
-
help: { type: "boolean", short: "h" }
|
|
16697
|
-
}
|
|
17485
|
+
options: OPTION_SPEC
|
|
16698
17486
|
});
|
|
16699
17487
|
} catch (parseError) {
|
|
16700
17488
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
@@ -16704,51 +17492,89 @@ async function run8(args) {
|
|
|
16704
17492
|
await writeStdout(HELP8);
|
|
16705
17493
|
return EXIT.OK;
|
|
16706
17494
|
}
|
|
16707
|
-
|
|
16708
|
-
|
|
17495
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
17496
|
+
const filePath = parsed.positionals[0];
|
|
17497
|
+
if (!filePath)
|
|
16709
17498
|
return fail("USAGE", "Missing FILE argument", HELP8);
|
|
16710
|
-
const
|
|
16711
|
-
if (!
|
|
17499
|
+
const locator = parsed.values.at;
|
|
17500
|
+
if (!locator)
|
|
16712
17501
|
return fail("USAGE", "Missing --at LOCATOR", HELP8);
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
|
|
17502
|
+
return {
|
|
17503
|
+
filePath,
|
|
17504
|
+
locator,
|
|
17505
|
+
authorFlag: parsed.values.author,
|
|
17506
|
+
outputPath: parsed.values.output,
|
|
17507
|
+
dryRun: Boolean(parsed.values["dry-run"])
|
|
17508
|
+
};
|
|
17509
|
+
}
|
|
17510
|
+
async function commitSectionDelete(view, blockRef, opts) {
|
|
17511
|
+
const bodyChildren = findBodyChildren(view);
|
|
17512
|
+
if (bodyChildren && isTrailingSectPr(bodyChildren, blockRef.parent)) {
|
|
17513
|
+
return fail("USAGE", "Cannot delete the trailing section break (mandatory in OOXML)", "Use `docx edit --at sN --columns 1` to reset its properties instead.");
|
|
17514
|
+
}
|
|
17515
|
+
if (opts.dryRun)
|
|
17516
|
+
return respondDryRun(opts);
|
|
17517
|
+
const trackingOn = isTrackChangesEnabled(view);
|
|
17518
|
+
const owningParagraph = trackingOn ? findContainingParagraph(view.documentTree, blockRef.node) : null;
|
|
17519
|
+
const anchorRun = owningParagraph?.children.find((child) => child.tag === "w:r") ?? null;
|
|
17520
|
+
removeInlineSectPr(blockRef.node, blockRef.parent);
|
|
17521
|
+
if (trackingOn && owningParagraph && anchorRun) {
|
|
17522
|
+
emitAuditComment(view, { kind: "run", paragraph: owningParagraph, run: anchorRun }, {
|
|
17523
|
+
body: `[docx-cli] section break removed (${opts.locator})`,
|
|
17524
|
+
author: resolveAuthor(opts.authorFlag),
|
|
17525
|
+
date: resolveDate()
|
|
17526
|
+
});
|
|
17527
|
+
}
|
|
17528
|
+
await saveDocView(view, opts.outputPath);
|
|
17529
|
+
return emitDeleteAck(opts);
|
|
17530
|
+
}
|
|
17531
|
+
async function commitBlockDelete(view, blockRef, opts) {
|
|
16719
17532
|
const targetIndex = blockRef.parent.indexOf(blockRef.node);
|
|
16720
17533
|
if (targetIndex === -1) {
|
|
16721
17534
|
return fail("BLOCK_NOT_FOUND", "Block reference is stale (parent does not contain it)");
|
|
16722
17535
|
}
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
await respond({
|
|
16726
|
-
ok: true,
|
|
16727
|
-
operation: "delete",
|
|
16728
|
-
dryRun: true,
|
|
16729
|
-
path,
|
|
16730
|
-
locator: at,
|
|
16731
|
-
...outputPath ? { output: outputPath } : {}
|
|
16732
|
-
});
|
|
16733
|
-
return EXIT.OK;
|
|
16734
|
-
}
|
|
17536
|
+
if (opts.dryRun)
|
|
17537
|
+
return respondDryRun(opts);
|
|
16735
17538
|
if (isTrackChangesEnabled(view)) {
|
|
16736
17539
|
if (blockRef.node.tag !== "w:p") {
|
|
16737
17540
|
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.");
|
|
16738
17541
|
}
|
|
16739
|
-
applyTrackedDeletion(view, blockRef.node,
|
|
17542
|
+
applyTrackedDeletion(view, blockRef.node, opts.authorFlag);
|
|
16740
17543
|
} else {
|
|
16741
17544
|
blockRef.parent.splice(targetIndex, 1);
|
|
16742
17545
|
}
|
|
16743
|
-
await saveDocView(view, outputPath);
|
|
17546
|
+
await saveDocView(view, opts.outputPath);
|
|
17547
|
+
return emitDeleteAck(opts);
|
|
17548
|
+
}
|
|
17549
|
+
async function respondDryRun(opts) {
|
|
16744
17550
|
await respond({
|
|
16745
17551
|
ok: true,
|
|
16746
17552
|
operation: "delete",
|
|
16747
|
-
|
|
16748
|
-
|
|
17553
|
+
dryRun: true,
|
|
17554
|
+
path: opts.filePath,
|
|
17555
|
+
locator: opts.locator,
|
|
17556
|
+
...opts.outputPath ? { output: opts.outputPath } : {}
|
|
17557
|
+
});
|
|
17558
|
+
return EXIT.OK;
|
|
17559
|
+
}
|
|
17560
|
+
async function emitDeleteAck(opts) {
|
|
17561
|
+
await respondAck({
|
|
17562
|
+
ok: true,
|
|
17563
|
+
operation: "delete",
|
|
17564
|
+
path: opts.outputPath ?? opts.filePath,
|
|
17565
|
+
locator: opts.locator
|
|
16749
17566
|
});
|
|
16750
17567
|
return EXIT.OK;
|
|
16751
17568
|
}
|
|
17569
|
+
function findBodyChildren(view) {
|
|
17570
|
+
const root = XmlNode2.findRoot(view.documentTree, "w:document");
|
|
17571
|
+
if (!root)
|
|
17572
|
+
return null;
|
|
17573
|
+
const body = root.findChild("w:body");
|
|
17574
|
+
if (!body)
|
|
17575
|
+
return null;
|
|
17576
|
+
return body.children;
|
|
17577
|
+
}
|
|
16752
17578
|
function applyTrackedDeletion(view, paragraph, authorFlag) {
|
|
16753
17579
|
const allocator = createRevisionAllocator(view);
|
|
16754
17580
|
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
@@ -16786,21 +17612,47 @@ Usage:
|
|
|
16786
17612
|
docx delete FILE [options]
|
|
16787
17613
|
|
|
16788
17614
|
Locator (required):
|
|
16789
|
-
--at LOCATOR Block to remove
|
|
17615
|
+
--at LOCATOR Block to remove
|
|
17616
|
+
pN paragraph (whole block, with all its runs)
|
|
17617
|
+
tN table (entire table)
|
|
17618
|
+
sN inline section break \u2014 strips the <w:sectPr> from
|
|
17619
|
+
its owning paragraph (the paragraph itself stays);
|
|
17620
|
+
rejects the trailing section break (mandatory in OOXML)
|
|
16790
17621
|
|
|
16791
17622
|
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
16792
17623
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
16793
17624
|
--dry-run Print what would be removed; do not write the file
|
|
17625
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
16794
17626
|
-h, --help Show this help
|
|
16795
17627
|
|
|
17628
|
+
Tracked behavior:
|
|
17629
|
+
When tracking is on, paragraph deletion wraps runs in <w:del> and marks
|
|
17630
|
+
the paragraph mark as deleted (accept removes the paragraph by merging it
|
|
17631
|
+
forward). Section deletion under tracking emits a [docx-cli] audit comment
|
|
17632
|
+
on the owning paragraph if it has runs to anchor on; otherwise (sentinel
|
|
17633
|
+
paragraphs from "insert --section" have no runs) the mutation is silent.
|
|
17634
|
+
delete --at tN under tracking is rejected (tracked table-row deletion is
|
|
17635
|
+
not supported).
|
|
17636
|
+
|
|
16796
17637
|
Examples:
|
|
16797
17638
|
docx delete doc.docx --at p3
|
|
16798
17639
|
docx delete doc.docx --at t0
|
|
16799
|
-
|
|
17640
|
+
docx delete doc.docx --at s2
|
|
17641
|
+
`, OPTION_SPEC;
|
|
16800
17642
|
var init_delete2 = __esm(() => {
|
|
16801
17643
|
init_core();
|
|
17644
|
+
init_parser();
|
|
17645
|
+
init_helpers();
|
|
16802
17646
|
init_respond();
|
|
16803
17647
|
init_jsx_dev_runtime();
|
|
17648
|
+
OPTION_SPEC = {
|
|
17649
|
+
at: { type: "string" },
|
|
17650
|
+
author: { type: "string" },
|
|
17651
|
+
output: { type: "string", short: "o" },
|
|
17652
|
+
"dry-run": { type: "boolean" },
|
|
17653
|
+
verbose: { type: "boolean", short: "v" },
|
|
17654
|
+
help: { type: "boolean", short: "h" }
|
|
17655
|
+
};
|
|
16804
17656
|
});
|
|
16805
17657
|
|
|
16806
17658
|
// src/cli/insert/emit.tsx
|
|
@@ -16920,32 +17772,399 @@ var init_emit2 = __esm(() => {
|
|
|
16920
17772
|
];
|
|
16921
17773
|
});
|
|
16922
17774
|
|
|
16923
|
-
// src/cli/edit/
|
|
16924
|
-
|
|
16925
|
-
|
|
16926
|
-
|
|
16927
|
-
|
|
16928
|
-
|
|
16929
|
-
|
|
17775
|
+
// src/cli/edit/preserve-formatting.tsx
|
|
17776
|
+
function extractOldTokens(paragraph) {
|
|
17777
|
+
let fullText = "";
|
|
17778
|
+
const charToRpr = [];
|
|
17779
|
+
function walk(node) {
|
|
17780
|
+
for (const child of node.children) {
|
|
17781
|
+
if (child.tag === "w:r") {
|
|
17782
|
+
const segText = collectRunText(child);
|
|
17783
|
+
if (segText.length === 0)
|
|
17784
|
+
continue;
|
|
17785
|
+
const rPr = child.findChild("w:rPr") ?? null;
|
|
17786
|
+
fullText += segText;
|
|
17787
|
+
for (let index = 0;index < segText.length; index++) {
|
|
17788
|
+
charToRpr.push(rPr);
|
|
17789
|
+
}
|
|
17790
|
+
continue;
|
|
17791
|
+
}
|
|
17792
|
+
if (isAcceptedViewVisibleWrapper(child.tag)) {
|
|
17793
|
+
walk(child);
|
|
17794
|
+
}
|
|
17795
|
+
}
|
|
17796
|
+
}
|
|
17797
|
+
walk(paragraph);
|
|
17798
|
+
const tokenTexts = tokenize(fullText);
|
|
17799
|
+
const tokens = [];
|
|
17800
|
+
let offset = 0;
|
|
17801
|
+
for (const text of tokenTexts) {
|
|
17802
|
+
const rPrSource = charToRpr[offset] ?? null;
|
|
17803
|
+
tokens.push({
|
|
17804
|
+
text,
|
|
17805
|
+
rPr: rPrSource ? rPrSource.clone() : null
|
|
17806
|
+
});
|
|
17807
|
+
offset += text.length;
|
|
17808
|
+
}
|
|
17809
|
+
return tokens;
|
|
17810
|
+
}
|
|
17811
|
+
function isAcceptedViewVisibleWrapper(tag) {
|
|
17812
|
+
if (!isRunBearingWrapper(tag))
|
|
17813
|
+
return false;
|
|
17814
|
+
return tag !== "w:del" && tag !== "w:moveFrom";
|
|
17815
|
+
}
|
|
17816
|
+
function collectRunText(run9) {
|
|
17817
|
+
let out = "";
|
|
17818
|
+
for (const child of run9.children) {
|
|
17819
|
+
if (child.tag === "w:t")
|
|
17820
|
+
out += child.collectText();
|
|
17821
|
+
}
|
|
17822
|
+
return out;
|
|
17823
|
+
}
|
|
17824
|
+
function tokenize(text) {
|
|
17825
|
+
if (text.length === 0)
|
|
17826
|
+
return [];
|
|
17827
|
+
return text.match(/\S+|\s+/g) ?? [];
|
|
17828
|
+
}
|
|
17829
|
+
function diffTokens(oldTokens, newTokens) {
|
|
17830
|
+
const m = oldTokens.length;
|
|
17831
|
+
const n = newTokens.length;
|
|
17832
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
17833
|
+
for (let i2 = 1;i2 <= m; i2++) {
|
|
17834
|
+
for (let j2 = 1;j2 <= n; j2++) {
|
|
17835
|
+
const oldText = oldTokens[i2 - 1]?.text;
|
|
17836
|
+
const newText = newTokens[j2 - 1];
|
|
17837
|
+
const left = dp[i2 - 1] ?? [];
|
|
17838
|
+
const here = dp[i2] ?? [];
|
|
17839
|
+
if (oldText === newText) {
|
|
17840
|
+
here[j2] = (left[j2 - 1] ?? 0) + 1;
|
|
17841
|
+
} else {
|
|
17842
|
+
here[j2] = Math.max(left[j2] ?? 0, here[j2 - 1] ?? 0);
|
|
17843
|
+
}
|
|
17844
|
+
}
|
|
17845
|
+
}
|
|
17846
|
+
const ops = [];
|
|
17847
|
+
let i = m;
|
|
17848
|
+
let j = n;
|
|
17849
|
+
while (i > 0 || j > 0) {
|
|
17850
|
+
const oldToken = i > 0 ? oldTokens[i - 1] : undefined;
|
|
17851
|
+
const newText = j > 0 ? newTokens[j - 1] : undefined;
|
|
17852
|
+
if (i > 0 && j > 0 && oldToken !== undefined && oldToken.text === newText) {
|
|
17853
|
+
ops.unshift({ kind: "keep", old: oldToken });
|
|
17854
|
+
i--;
|
|
17855
|
+
j--;
|
|
17856
|
+
continue;
|
|
17857
|
+
}
|
|
17858
|
+
const upScore = i > 0 ? dp[i - 1]?.[j] ?? 0 : -1;
|
|
17859
|
+
const leftScore = j > 0 ? dp[i]?.[j - 1] ?? 0 : -1;
|
|
17860
|
+
if (j > 0 && newText !== undefined && (i === 0 || leftScore >= upScore)) {
|
|
17861
|
+
ops.unshift({ kind: "insert", text: newText });
|
|
17862
|
+
j--;
|
|
17863
|
+
continue;
|
|
17864
|
+
}
|
|
17865
|
+
if (i > 0 && oldToken !== undefined) {
|
|
17866
|
+
ops.unshift({ kind: "delete", old: oldToken });
|
|
17867
|
+
i--;
|
|
17868
|
+
continue;
|
|
17869
|
+
}
|
|
17870
|
+
break;
|
|
17871
|
+
}
|
|
17872
|
+
return consolidateEditGroups(demoteOrphanWhitespaceKeeps(ops));
|
|
17873
|
+
}
|
|
17874
|
+
function demoteOrphanWhitespaceKeeps(ops) {
|
|
17875
|
+
const out = [];
|
|
17876
|
+
for (let index = 0;index < ops.length; index++) {
|
|
17877
|
+
const op = ops[index];
|
|
17878
|
+
if (!op)
|
|
17879
|
+
continue;
|
|
17880
|
+
if (op.kind !== "keep") {
|
|
17881
|
+
out.push(op);
|
|
17882
|
+
continue;
|
|
17883
|
+
}
|
|
17884
|
+
if (!isWhitespaceOnly(op.old.text)) {
|
|
17885
|
+
out.push(op);
|
|
17886
|
+
continue;
|
|
17887
|
+
}
|
|
17888
|
+
const prev = ops[index - 1];
|
|
17889
|
+
const next = ops[index + 1];
|
|
17890
|
+
const prevIsEdit = prev !== undefined && prev.kind !== "keep";
|
|
17891
|
+
const nextIsEdit = next !== undefined && next.kind !== "keep";
|
|
17892
|
+
if (!prevIsEdit && !nextIsEdit) {
|
|
17893
|
+
out.push(op);
|
|
17894
|
+
continue;
|
|
17895
|
+
}
|
|
17896
|
+
out.push({ kind: "delete", old: op.old });
|
|
17897
|
+
out.push({ kind: "insert", text: op.old.text });
|
|
17898
|
+
}
|
|
17899
|
+
return out;
|
|
17900
|
+
}
|
|
17901
|
+
function consolidateEditGroups(ops) {
|
|
17902
|
+
const out = [];
|
|
17903
|
+
let cursor = 0;
|
|
17904
|
+
while (cursor < ops.length) {
|
|
17905
|
+
const op = ops[cursor];
|
|
17906
|
+
if (!op) {
|
|
17907
|
+
cursor++;
|
|
17908
|
+
continue;
|
|
17909
|
+
}
|
|
17910
|
+
if (op.kind === "keep") {
|
|
17911
|
+
out.push(op);
|
|
17912
|
+
cursor++;
|
|
17913
|
+
continue;
|
|
17914
|
+
}
|
|
17915
|
+
const groupEnd = (() => {
|
|
17916
|
+
let end = cursor;
|
|
17917
|
+
while (end < ops.length) {
|
|
17918
|
+
const candidate = ops[end];
|
|
17919
|
+
if (!candidate || candidate.kind === "keep")
|
|
17920
|
+
break;
|
|
17921
|
+
end++;
|
|
17922
|
+
}
|
|
17923
|
+
return end;
|
|
17924
|
+
})();
|
|
17925
|
+
const deletes = [];
|
|
17926
|
+
const inserts = [];
|
|
17927
|
+
for (let index = cursor;index < groupEnd; index++) {
|
|
17928
|
+
const groupOp = ops[index];
|
|
17929
|
+
if (!groupOp)
|
|
17930
|
+
continue;
|
|
17931
|
+
if (groupOp.kind === "delete")
|
|
17932
|
+
deletes.push(groupOp);
|
|
17933
|
+
else if (groupOp.kind === "insert")
|
|
17934
|
+
inserts.push(groupOp);
|
|
17935
|
+
}
|
|
17936
|
+
out.push(...deletes, ...inserts);
|
|
17937
|
+
cursor = groupEnd;
|
|
17938
|
+
}
|
|
17939
|
+
return out;
|
|
17940
|
+
}
|
|
17941
|
+
function isWhitespaceOnly(text) {
|
|
17942
|
+
return text.length > 0 && /^\s+$/.test(text);
|
|
17943
|
+
}
|
|
17944
|
+
function buildUntrackedRuns(ops) {
|
|
17945
|
+
const flow = [];
|
|
17946
|
+
for (let index = 0;index < ops.length; index++) {
|
|
17947
|
+
const op = ops[index];
|
|
17948
|
+
if (!op)
|
|
17949
|
+
continue;
|
|
17950
|
+
if (op.kind === "delete")
|
|
17951
|
+
continue;
|
|
17952
|
+
if (op.kind === "keep") {
|
|
17953
|
+
flow.push({ text: op.old.text, rPr: cloneOrNull(op.old.rPr) });
|
|
17954
|
+
continue;
|
|
17955
|
+
}
|
|
17956
|
+
const inherited = inheritedRprForInsert(ops, index);
|
|
17957
|
+
flow.push({ text: op.text, rPr: cloneOrNull(inherited) });
|
|
17958
|
+
}
|
|
17959
|
+
return groupAndEmitPlainRuns(flow);
|
|
17960
|
+
}
|
|
17961
|
+
function inheritedRprForInsert(ops, index) {
|
|
17962
|
+
let groupStart = index;
|
|
17963
|
+
while (groupStart > 0) {
|
|
17964
|
+
const prev = ops[groupStart - 1];
|
|
17965
|
+
if (!prev || prev.kind === "keep")
|
|
17966
|
+
break;
|
|
17967
|
+
groupStart--;
|
|
17968
|
+
}
|
|
17969
|
+
let groupEnd = index;
|
|
17970
|
+
while (groupEnd < ops.length - 1) {
|
|
17971
|
+
const next = ops[groupEnd + 1];
|
|
17972
|
+
if (!next || next.kind === "keep")
|
|
17973
|
+
break;
|
|
17974
|
+
groupEnd++;
|
|
17975
|
+
}
|
|
17976
|
+
const deletes = [];
|
|
17977
|
+
let insertOrdinal = 0;
|
|
17978
|
+
let myInsertOrdinal = -1;
|
|
17979
|
+
for (let cursor = groupStart;cursor <= groupEnd; cursor++) {
|
|
17980
|
+
const op = ops[cursor];
|
|
17981
|
+
if (!op)
|
|
17982
|
+
continue;
|
|
17983
|
+
if (op.kind === "delete") {
|
|
17984
|
+
deletes.push(op.old.rPr);
|
|
17985
|
+
continue;
|
|
17986
|
+
}
|
|
17987
|
+
if (op.kind === "insert") {
|
|
17988
|
+
if (cursor === index)
|
|
17989
|
+
myInsertOrdinal = insertOrdinal;
|
|
17990
|
+
insertOrdinal++;
|
|
17991
|
+
}
|
|
17992
|
+
}
|
|
17993
|
+
if (deletes.length > 0 && myInsertOrdinal >= 0) {
|
|
17994
|
+
const paired = myInsertOrdinal < deletes.length ? deletes[myInsertOrdinal] : deletes[deletes.length - 1];
|
|
17995
|
+
if (paired !== undefined)
|
|
17996
|
+
return paired;
|
|
17997
|
+
}
|
|
17998
|
+
for (let cursor = groupStart - 1;cursor >= 0; cursor--) {
|
|
17999
|
+
const op = ops[cursor];
|
|
18000
|
+
if (op?.kind === "keep")
|
|
18001
|
+
return op.old.rPr;
|
|
18002
|
+
}
|
|
18003
|
+
for (let cursor = groupEnd + 1;cursor < ops.length; cursor++) {
|
|
18004
|
+
const op = ops[cursor];
|
|
18005
|
+
if (op?.kind === "keep")
|
|
18006
|
+
return op.old.rPr;
|
|
18007
|
+
}
|
|
18008
|
+
return null;
|
|
18009
|
+
}
|
|
18010
|
+
function buildTrackedRuns(ops, mintMeta) {
|
|
18011
|
+
const grouping = [];
|
|
18012
|
+
for (let index = 0;index < ops.length; index++) {
|
|
18013
|
+
const op = ops[index];
|
|
18014
|
+
if (!op)
|
|
18015
|
+
continue;
|
|
18016
|
+
if (op.kind === "keep") {
|
|
18017
|
+
pushOpTo(grouping, "keep", {
|
|
18018
|
+
text: op.old.text,
|
|
18019
|
+
rPr: cloneOrNull(op.old.rPr)
|
|
18020
|
+
});
|
|
18021
|
+
continue;
|
|
18022
|
+
}
|
|
18023
|
+
if (op.kind === "insert") {
|
|
18024
|
+
const inherited = inheritedRprForInsert(ops, index);
|
|
18025
|
+
pushOpTo(grouping, "insert", {
|
|
18026
|
+
text: op.text,
|
|
18027
|
+
rPr: cloneOrNull(inherited)
|
|
18028
|
+
});
|
|
18029
|
+
continue;
|
|
18030
|
+
}
|
|
18031
|
+
pushOpTo(grouping, "delete", {
|
|
18032
|
+
text: op.old.text,
|
|
18033
|
+
rPr: cloneOrNull(op.old.rPr)
|
|
18034
|
+
});
|
|
18035
|
+
}
|
|
18036
|
+
const out = [];
|
|
18037
|
+
for (const group of grouping) {
|
|
18038
|
+
const runs = group.kind === "delete" ? groupAndEmitDeletedRuns(group.flow) : groupAndEmitPlainRuns(group.flow);
|
|
18039
|
+
if (runs.length === 0)
|
|
18040
|
+
continue;
|
|
18041
|
+
if (group.kind === "keep") {
|
|
18042
|
+
out.push(...runs);
|
|
18043
|
+
} else if (group.kind === "insert") {
|
|
18044
|
+
out.push(/* @__PURE__ */ jsxDEV(Ins, {
|
|
18045
|
+
meta: mintMeta(),
|
|
18046
|
+
children: runs
|
|
18047
|
+
}, undefined, false, undefined, this));
|
|
18048
|
+
} else {
|
|
18049
|
+
out.push(/* @__PURE__ */ jsxDEV(Del, {
|
|
18050
|
+
meta: mintMeta(),
|
|
18051
|
+
children: runs
|
|
18052
|
+
}, undefined, false, undefined, this));
|
|
18053
|
+
}
|
|
18054
|
+
}
|
|
18055
|
+
return out;
|
|
18056
|
+
}
|
|
18057
|
+
function pushOpTo(grouping, kind, entry) {
|
|
18058
|
+
const last = grouping[grouping.length - 1];
|
|
18059
|
+
if (last && last.kind === kind) {
|
|
18060
|
+
last.flow.push(entry);
|
|
18061
|
+
return;
|
|
18062
|
+
}
|
|
18063
|
+
grouping.push({ kind, flow: [entry] });
|
|
18064
|
+
}
|
|
18065
|
+
function groupAndEmitPlainRuns(flow) {
|
|
18066
|
+
const out = [];
|
|
18067
|
+
let current = null;
|
|
18068
|
+
for (const entry of flow) {
|
|
18069
|
+
if (entry.text.length === 0)
|
|
18070
|
+
continue;
|
|
18071
|
+
if (current && rPrEqual(current.rPr, entry.rPr)) {
|
|
18072
|
+
current.text += entry.text;
|
|
18073
|
+
continue;
|
|
18074
|
+
}
|
|
18075
|
+
if (current)
|
|
18076
|
+
out.push(emitPlainRun(current.text, current.rPr));
|
|
18077
|
+
current = { text: entry.text, rPr: entry.rPr };
|
|
18078
|
+
}
|
|
18079
|
+
if (current)
|
|
18080
|
+
out.push(emitPlainRun(current.text, current.rPr));
|
|
18081
|
+
return out;
|
|
18082
|
+
}
|
|
18083
|
+
function groupAndEmitDeletedRuns(flow) {
|
|
18084
|
+
const out = [];
|
|
18085
|
+
let current = null;
|
|
18086
|
+
for (const entry of flow) {
|
|
18087
|
+
if (entry.text.length === 0)
|
|
18088
|
+
continue;
|
|
18089
|
+
if (current && rPrEqual(current.rPr, entry.rPr)) {
|
|
18090
|
+
current.text += entry.text;
|
|
18091
|
+
continue;
|
|
18092
|
+
}
|
|
18093
|
+
if (current)
|
|
18094
|
+
out.push(emitDeletedRun(current.text, current.rPr));
|
|
18095
|
+
current = { text: entry.text, rPr: entry.rPr };
|
|
18096
|
+
}
|
|
18097
|
+
if (current)
|
|
18098
|
+
out.push(emitDeletedRun(current.text, current.rPr));
|
|
18099
|
+
return out;
|
|
18100
|
+
}
|
|
18101
|
+
function emitPlainRun(text, rPr) {
|
|
18102
|
+
return /* @__PURE__ */ jsxDEV(w.r, {
|
|
18103
|
+
children: [
|
|
18104
|
+
rPr,
|
|
18105
|
+
/* @__PURE__ */ jsxDEV(w.t, {
|
|
18106
|
+
"xml:space": "preserve",
|
|
18107
|
+
children: text
|
|
18108
|
+
}, undefined, false, undefined, this)
|
|
18109
|
+
]
|
|
18110
|
+
}, undefined, true, undefined, this);
|
|
18111
|
+
}
|
|
18112
|
+
function emitDeletedRun(text, rPr) {
|
|
18113
|
+
return /* @__PURE__ */ jsxDEV(w.r, {
|
|
18114
|
+
children: [
|
|
18115
|
+
rPr,
|
|
18116
|
+
/* @__PURE__ */ jsxDEV(w.delText, {
|
|
18117
|
+
"xml:space": "preserve",
|
|
18118
|
+
children: text
|
|
18119
|
+
}, undefined, false, undefined, this)
|
|
18120
|
+
]
|
|
18121
|
+
}, undefined, true, undefined, this);
|
|
18122
|
+
}
|
|
18123
|
+
function cloneOrNull(node) {
|
|
18124
|
+
return node ? node.clone() : null;
|
|
18125
|
+
}
|
|
18126
|
+
function rPrEqual(a2, b) {
|
|
18127
|
+
if (a2 === null && b === null)
|
|
18128
|
+
return true;
|
|
18129
|
+
if (a2 === null || b === null)
|
|
18130
|
+
return false;
|
|
18131
|
+
return XmlNode2.serialize([a2]) === XmlNode2.serialize([b]);
|
|
18132
|
+
}
|
|
18133
|
+
var init_preserve_formatting = __esm(() => {
|
|
18134
|
+
init_core();
|
|
18135
|
+
init_jsx();
|
|
18136
|
+
init_parser();
|
|
18137
|
+
init_jsx_dev_runtime();
|
|
18138
|
+
});
|
|
18139
|
+
|
|
18140
|
+
// src/cli/edit/index.tsx
|
|
18141
|
+
var exports_edit = {};
|
|
18142
|
+
__export(exports_edit, {
|
|
18143
|
+
run: () => run9
|
|
18144
|
+
});
|
|
18145
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
18146
|
+
async function run9(args) {
|
|
18147
|
+
const opts = await parseAndValidateOptions2(args);
|
|
18148
|
+
if (typeof opts === "number")
|
|
18149
|
+
return opts;
|
|
18150
|
+
const view = await openOrFail(opts.filePath);
|
|
18151
|
+
if (typeof view === "number")
|
|
18152
|
+
return view;
|
|
18153
|
+
const blockRef = await resolveBlockOrFail(view, opts.locator);
|
|
18154
|
+
if (typeof blockRef === "number")
|
|
18155
|
+
return blockRef;
|
|
18156
|
+
if (opts.spec.kind === "section") {
|
|
18157
|
+
return commitSectionPropertyEdit(view, blockRef, opts.spec, opts);
|
|
18158
|
+
}
|
|
18159
|
+
return commitParagraphReplacement(view, blockRef, opts.spec, opts);
|
|
18160
|
+
}
|
|
18161
|
+
async function parseAndValidateOptions2(args) {
|
|
16930
18162
|
let parsed;
|
|
16931
18163
|
try {
|
|
16932
18164
|
parsed = parseArgs8({
|
|
16933
18165
|
args,
|
|
16934
18166
|
allowPositionals: true,
|
|
16935
|
-
options:
|
|
16936
|
-
at: { type: "string" },
|
|
16937
|
-
text: { type: "string" },
|
|
16938
|
-
runs: { type: "string" },
|
|
16939
|
-
style: { type: "string" },
|
|
16940
|
-
alignment: { type: "string" },
|
|
16941
|
-
color: { type: "string" },
|
|
16942
|
-
bold: { type: "boolean" },
|
|
16943
|
-
italic: { type: "boolean" },
|
|
16944
|
-
author: { type: "string" },
|
|
16945
|
-
output: { type: "string", short: "o" },
|
|
16946
|
-
"dry-run": { type: "boolean" },
|
|
16947
|
-
help: { type: "boolean", short: "h" }
|
|
16948
|
-
}
|
|
18167
|
+
options: OPTION_SPEC2
|
|
16949
18168
|
});
|
|
16950
18169
|
} catch (parseError) {
|
|
16951
18170
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
@@ -16955,90 +18174,255 @@ async function run9(args) {
|
|
|
16955
18174
|
await writeStdout(HELP9);
|
|
16956
18175
|
return EXIT.OK;
|
|
16957
18176
|
}
|
|
16958
|
-
|
|
16959
|
-
|
|
18177
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
18178
|
+
const filePath = parsed.positionals[0];
|
|
18179
|
+
if (!filePath)
|
|
16960
18180
|
return fail("USAGE", "Missing FILE argument", HELP9);
|
|
16961
|
-
const
|
|
16962
|
-
if (!
|
|
18181
|
+
const locator = parsed.values.at;
|
|
18182
|
+
if (!locator)
|
|
16963
18183
|
return fail("USAGE", "Missing --at LOCATOR", HELP9);
|
|
16964
|
-
const
|
|
16965
|
-
|
|
18184
|
+
const paragraphOptions = await parseParagraphOptions(parsed.values);
|
|
18185
|
+
if (typeof paragraphOptions === "number")
|
|
18186
|
+
return paragraphOptions;
|
|
18187
|
+
const isSectionLocator = /^s\d+$/.test(locator);
|
|
18188
|
+
const spec = isSectionLocator ? await validateSectionEdit(parsed.values) : await validateParagraphEdit(parsed.values, paragraphOptions);
|
|
18189
|
+
if (typeof spec === "number")
|
|
18190
|
+
return spec;
|
|
18191
|
+
return {
|
|
18192
|
+
filePath,
|
|
18193
|
+
locator,
|
|
18194
|
+
spec,
|
|
18195
|
+
authorFlag: parsed.values.author,
|
|
18196
|
+
outputPath: parsed.values.output,
|
|
18197
|
+
dryRun: Boolean(parsed.values["dry-run"]),
|
|
18198
|
+
noFormatting: Boolean(parsed.values["no-formatting"])
|
|
18199
|
+
};
|
|
18200
|
+
}
|
|
18201
|
+
async function validateSectionEdit(values) {
|
|
18202
|
+
if (values.text !== undefined || values.runs !== undefined) {
|
|
18203
|
+
return fail("USAGE", "Section locators (sN) take --columns and --type, not --text/--runs", HELP9);
|
|
18204
|
+
}
|
|
18205
|
+
if (values.columns === undefined && values.type === undefined) {
|
|
18206
|
+
return fail("USAGE", "Section edit requires --columns and/or --type", HELP9);
|
|
18207
|
+
}
|
|
18208
|
+
const sectionFlags = await parseSectionFlags(values);
|
|
18209
|
+
if (typeof sectionFlags === "number")
|
|
18210
|
+
return sectionFlags;
|
|
18211
|
+
return { kind: "section", ...sectionFlags };
|
|
18212
|
+
}
|
|
18213
|
+
async function validateParagraphEdit(values, paragraphOptions) {
|
|
18214
|
+
if (values.columns !== undefined || values.type !== undefined) {
|
|
18215
|
+
return fail("USAGE", "--columns and --type require a section locator (sN)", HELP9);
|
|
18216
|
+
}
|
|
18217
|
+
const text = values.text;
|
|
18218
|
+
const runsJson = values.runs;
|
|
16966
18219
|
if (!text && !runsJson) {
|
|
16967
18220
|
return fail("USAGE", "Missing content: pass --text or --runs", HELP9);
|
|
16968
18221
|
}
|
|
16969
18222
|
if (text && runsJson) {
|
|
16970
18223
|
return fail("USAGE", "Pass either --text or --runs, not both", HELP9);
|
|
16971
18224
|
}
|
|
16972
|
-
|
|
16973
|
-
|
|
18225
|
+
if (text !== undefined) {
|
|
18226
|
+
return {
|
|
18227
|
+
kind: "text",
|
|
18228
|
+
text,
|
|
18229
|
+
format: {
|
|
18230
|
+
color: values.color,
|
|
18231
|
+
bold: values.bold,
|
|
18232
|
+
italic: values.italic
|
|
18233
|
+
},
|
|
18234
|
+
paragraphOptions
|
|
18235
|
+
};
|
|
18236
|
+
}
|
|
18237
|
+
const runs = await parseRunsArg(runsJson);
|
|
18238
|
+
if (typeof runs === "number")
|
|
18239
|
+
return runs;
|
|
18240
|
+
return { kind: "runs", runs, paragraphOptions };
|
|
18241
|
+
}
|
|
18242
|
+
async function parseSectionFlags(values) {
|
|
18243
|
+
const out = {};
|
|
18244
|
+
const columnsRaw = values.columns;
|
|
18245
|
+
if (columnsRaw !== undefined) {
|
|
18246
|
+
const columns = Number.parseInt(columnsRaw, 10);
|
|
18247
|
+
if (!Number.isFinite(columns) || columns <= 0) {
|
|
18248
|
+
return fail("USAGE", `--columns must be a positive integer, got "${columnsRaw}"`);
|
|
18249
|
+
}
|
|
18250
|
+
out.columns = columns;
|
|
18251
|
+
}
|
|
18252
|
+
const sectionTypeRaw = values.type;
|
|
18253
|
+
if (sectionTypeRaw !== undefined) {
|
|
18254
|
+
if (!isSectionType(sectionTypeRaw)) {
|
|
18255
|
+
return fail("USAGE", `Invalid --type: ${sectionTypeRaw}`, "Valid values: continuous, nextPage, evenPage, oddPage, nextColumn");
|
|
18256
|
+
}
|
|
18257
|
+
out.sectionType = sectionTypeRaw;
|
|
18258
|
+
}
|
|
18259
|
+
return out;
|
|
18260
|
+
}
|
|
18261
|
+
async function parseRunsArg(json) {
|
|
18262
|
+
let parsed;
|
|
18263
|
+
try {
|
|
18264
|
+
parsed = JSON.parse(json);
|
|
18265
|
+
} catch (jsonError) {
|
|
18266
|
+
const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
18267
|
+
return fail("USAGE", `Invalid --runs JSON: ${message}`);
|
|
18268
|
+
}
|
|
18269
|
+
if (!Array.isArray(parsed)) {
|
|
18270
|
+
return fail("USAGE", "--runs must be a JSON array of Run objects");
|
|
18271
|
+
}
|
|
18272
|
+
return parsed;
|
|
18273
|
+
}
|
|
18274
|
+
async function parseParagraphOptions(values) {
|
|
18275
|
+
const out = {};
|
|
18276
|
+
const styleValue = values.style;
|
|
16974
18277
|
if (styleValue)
|
|
16975
|
-
|
|
16976
|
-
const alignmentValue =
|
|
18278
|
+
out.style = styleValue;
|
|
18279
|
+
const alignmentValue = values.alignment;
|
|
16977
18280
|
if (alignmentValue) {
|
|
16978
18281
|
if (alignmentValue !== "left" && alignmentValue !== "center" && alignmentValue !== "right" && alignmentValue !== "justify") {
|
|
16979
18282
|
return fail("USAGE", `Invalid --alignment: ${alignmentValue}`, "Valid values: left, center, right, justify");
|
|
16980
18283
|
}
|
|
16981
|
-
|
|
18284
|
+
out.alignment = alignmentValue;
|
|
16982
18285
|
}
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
|
|
16988
|
-
return blockRef;
|
|
16989
|
-
let paragraphNode;
|
|
16990
|
-
if (text !== undefined) {
|
|
16991
|
-
const color = parsed.values.color;
|
|
16992
|
-
paragraphNode = /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
16993
|
-
text,
|
|
16994
|
-
...paragraphOptions,
|
|
16995
|
-
...color ? { color } : {},
|
|
16996
|
-
...parsed.values.bold ? { bold: true } : {},
|
|
16997
|
-
...parsed.values.italic ? { italic: true } : {}
|
|
16998
|
-
}, undefined, false, undefined, this);
|
|
16999
|
-
} else {
|
|
17000
|
-
let runsValue;
|
|
17001
|
-
try {
|
|
17002
|
-
runsValue = JSON.parse(runsJson);
|
|
17003
|
-
} catch (jsonError) {
|
|
17004
|
-
const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
17005
|
-
return fail("USAGE", `Invalid --runs JSON: ${message}`);
|
|
17006
|
-
}
|
|
17007
|
-
if (!Array.isArray(runsValue)) {
|
|
17008
|
-
return fail("USAGE", "--runs must be a JSON array of Run objects");
|
|
17009
|
-
}
|
|
17010
|
-
paragraphNode = /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
17011
|
-
runs: runsValue,
|
|
17012
|
-
...paragraphOptions
|
|
17013
|
-
}, undefined, false, undefined, this);
|
|
18286
|
+
return out;
|
|
18287
|
+
}
|
|
18288
|
+
async function commitSectionPropertyEdit(view, blockRef, spec, opts) {
|
|
18289
|
+
if (blockRef.node.tag !== "w:sectPr") {
|
|
18290
|
+
return fail("BLOCK_NOT_FOUND", `Locator ${opts.locator} did not resolve to a section break`);
|
|
17014
18291
|
}
|
|
18292
|
+
if (opts.dryRun)
|
|
18293
|
+
return respondDryRun2(opts);
|
|
18294
|
+
if (isTrackChangesEnabled(view)) {
|
|
18295
|
+
const allocator = createRevisionAllocator(view);
|
|
18296
|
+
const meta = {
|
|
18297
|
+
author: resolveAuthor(opts.authorFlag),
|
|
18298
|
+
date: resolveDate(),
|
|
18299
|
+
revisionId: allocator.next()
|
|
18300
|
+
};
|
|
18301
|
+
wrapSectPrChange(blockRef.node, meta);
|
|
18302
|
+
}
|
|
18303
|
+
applyColumns(blockRef.node, spec.columns);
|
|
18304
|
+
applySectionType(blockRef.node, spec.sectionType);
|
|
18305
|
+
await saveDocView(view, opts.outputPath);
|
|
18306
|
+
return emitEditAck(opts);
|
|
18307
|
+
}
|
|
18308
|
+
async function commitParagraphReplacement(view, blockRef, spec, opts) {
|
|
17015
18309
|
const targetIndex = blockRef.parent.indexOf(blockRef.node);
|
|
17016
18310
|
if (targetIndex === -1) {
|
|
17017
18311
|
return fail("BLOCK_NOT_FOUND", "Block reference is stale (parent does not contain it)");
|
|
17018
18312
|
}
|
|
17019
|
-
|
|
17020
|
-
|
|
17021
|
-
|
|
17022
|
-
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
18313
|
+
if (opts.dryRun)
|
|
18314
|
+
return respondDryRun2(opts);
|
|
18315
|
+
const tracked = isTrackChangesEnabled(view);
|
|
18316
|
+
if (canPreserveFormatting(spec, opts)) {
|
|
18317
|
+
applyFormattingPreservingEdit(view, blockRef.node, spec, opts.authorFlag, tracked);
|
|
18318
|
+
await saveDocView(view, opts.outputPath);
|
|
18319
|
+
return emitEditAck(opts);
|
|
18320
|
+
}
|
|
18321
|
+
const newParagraph = buildReplacementParagraph(spec);
|
|
18322
|
+
if (tracked) {
|
|
18323
|
+
applyTrackedEdit(view, blockRef.node, newParagraph, opts.authorFlag);
|
|
18324
|
+
} else {
|
|
18325
|
+
blockRef.parent.splice(targetIndex, 1, newParagraph);
|
|
17030
18326
|
}
|
|
17031
|
-
|
|
17032
|
-
|
|
18327
|
+
await saveDocView(view, opts.outputPath);
|
|
18328
|
+
return emitEditAck(opts);
|
|
18329
|
+
}
|
|
18330
|
+
function canPreserveFormatting(spec, opts) {
|
|
18331
|
+
if (opts.noFormatting)
|
|
18332
|
+
return false;
|
|
18333
|
+
if (spec.kind !== "text")
|
|
18334
|
+
return false;
|
|
18335
|
+
const format = spec.format;
|
|
18336
|
+
if (format.color || format.bold || format.italic)
|
|
18337
|
+
return false;
|
|
18338
|
+
return true;
|
|
18339
|
+
}
|
|
18340
|
+
function applyFormattingPreservingEdit(view, existingParagraph, spec, authorFlag, tracked) {
|
|
18341
|
+
const oldTokens = extractOldTokens(existingParagraph);
|
|
18342
|
+
const newTokens = tokenize(spec.text);
|
|
18343
|
+
const ops = diffTokens(oldTokens, newTokens);
|
|
18344
|
+
let runChildren;
|
|
18345
|
+
if (tracked) {
|
|
18346
|
+
const allocator = createRevisionAllocator(view);
|
|
18347
|
+
const baseMeta = { author: resolveAuthor(authorFlag), date: resolveDate() };
|
|
18348
|
+
const mintMeta = () => ({
|
|
18349
|
+
...baseMeta,
|
|
18350
|
+
revisionId: allocator.next()
|
|
18351
|
+
});
|
|
18352
|
+
runChildren = buildTrackedRuns(ops, mintMeta);
|
|
17033
18353
|
} else {
|
|
17034
|
-
|
|
18354
|
+
runChildren = buildUntrackedRuns(ops);
|
|
17035
18355
|
}
|
|
17036
|
-
|
|
18356
|
+
const rebuilt = [];
|
|
18357
|
+
for (const child of existingParagraph.children) {
|
|
18358
|
+
if (child.tag === "w:r")
|
|
18359
|
+
continue;
|
|
18360
|
+
if (isRunBearingWrapper(child.tag))
|
|
18361
|
+
continue;
|
|
18362
|
+
rebuilt.push(child);
|
|
18363
|
+
}
|
|
18364
|
+
applyParagraphOptionsInPlace(rebuilt, spec.paragraphOptions);
|
|
18365
|
+
rebuilt.push(...runChildren);
|
|
18366
|
+
existingParagraph.children = rebuilt;
|
|
18367
|
+
}
|
|
18368
|
+
function applyParagraphOptionsInPlace(rebuilt, options) {
|
|
18369
|
+
if (!options.style && !options.alignment)
|
|
18370
|
+
return;
|
|
18371
|
+
let pPr = rebuilt.find((child) => child.tag === "w:pPr");
|
|
18372
|
+
if (!pPr) {
|
|
18373
|
+
pPr = new XmlNode2("w:pPr");
|
|
18374
|
+
rebuilt.unshift(pPr);
|
|
18375
|
+
}
|
|
18376
|
+
if (options.style) {
|
|
18377
|
+
const existingStyle = pPr.findChild("w:pStyle");
|
|
18378
|
+
if (existingStyle) {
|
|
18379
|
+
existingStyle.setAttribute("w:val", options.style);
|
|
18380
|
+
} else {
|
|
18381
|
+
const styleNode = new XmlNode2("w:pStyle", { "w:val": options.style });
|
|
18382
|
+
pPr.children.unshift(styleNode);
|
|
18383
|
+
}
|
|
18384
|
+
}
|
|
18385
|
+
if (options.alignment) {
|
|
18386
|
+
const existingJc = pPr.findChild("w:jc");
|
|
18387
|
+
if (existingJc) {
|
|
18388
|
+
existingJc.setAttribute("w:val", options.alignment);
|
|
18389
|
+
} else {
|
|
18390
|
+
pPr.children.push(new XmlNode2("w:jc", { "w:val": options.alignment }));
|
|
18391
|
+
}
|
|
18392
|
+
}
|
|
18393
|
+
}
|
|
18394
|
+
function buildReplacementParagraph(spec) {
|
|
18395
|
+
if (spec.kind === "text") {
|
|
18396
|
+
return /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
18397
|
+
text: spec.text,
|
|
18398
|
+
...spec.paragraphOptions,
|
|
18399
|
+
...spec.format.color ? { color: spec.format.color } : {},
|
|
18400
|
+
...spec.format.bold ? { bold: true } : {},
|
|
18401
|
+
...spec.format.italic ? { italic: true } : {}
|
|
18402
|
+
}, undefined, false, undefined, this);
|
|
18403
|
+
}
|
|
18404
|
+
return /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
18405
|
+
runs: spec.runs,
|
|
18406
|
+
...spec.paragraphOptions
|
|
18407
|
+
}, undefined, false, undefined, this);
|
|
18408
|
+
}
|
|
18409
|
+
async function respondDryRun2(opts) {
|
|
17037
18410
|
await respond({
|
|
17038
18411
|
ok: true,
|
|
17039
18412
|
operation: "edit",
|
|
17040
|
-
|
|
17041
|
-
|
|
18413
|
+
dryRun: true,
|
|
18414
|
+
path: opts.filePath,
|
|
18415
|
+
locator: opts.locator,
|
|
18416
|
+
...opts.outputPath ? { output: opts.outputPath } : {}
|
|
18417
|
+
});
|
|
18418
|
+
return EXIT.OK;
|
|
18419
|
+
}
|
|
18420
|
+
async function emitEditAck(opts) {
|
|
18421
|
+
await respondAck({
|
|
18422
|
+
ok: true,
|
|
18423
|
+
operation: "edit",
|
|
18424
|
+
path: opts.outputPath ?? opts.filePath,
|
|
18425
|
+
locator: opts.locator
|
|
17042
18426
|
});
|
|
17043
18427
|
return EXIT.OK;
|
|
17044
18428
|
}
|
|
@@ -17052,10 +18436,15 @@ function applyTrackedEdit(view, existingParagraph, newParagraph, authorFlag) {
|
|
|
17052
18436
|
const oldRuns = [];
|
|
17053
18437
|
const oldNonRuns = [];
|
|
17054
18438
|
for (const child of existingParagraph.children) {
|
|
17055
|
-
if (child.tag === "w:r")
|
|
18439
|
+
if (child.tag === "w:r") {
|
|
17056
18440
|
oldRuns.push(child);
|
|
17057
|
-
|
|
17058
|
-
|
|
18441
|
+
continue;
|
|
18442
|
+
}
|
|
18443
|
+
if (isRunBearingWrapper(child.tag)) {
|
|
18444
|
+
collectRunsFromWrapper(child, oldRuns);
|
|
18445
|
+
continue;
|
|
18446
|
+
}
|
|
18447
|
+
oldNonRuns.push(child);
|
|
17059
18448
|
}
|
|
17060
18449
|
let newPPr = null;
|
|
17061
18450
|
const newRuns = [];
|
|
@@ -17090,145 +18479,91 @@ function applyTrackedEdit(view, existingParagraph, newParagraph, authorFlag) {
|
|
|
17090
18479
|
}
|
|
17091
18480
|
existingParagraph.children = rebuilt;
|
|
17092
18481
|
}
|
|
17093
|
-
|
|
18482
|
+
function collectRunsFromWrapper(wrapper, out) {
|
|
18483
|
+
for (const child of wrapper.children) {
|
|
18484
|
+
if (child.tag === "w:r") {
|
|
18485
|
+
out.push(child);
|
|
18486
|
+
continue;
|
|
18487
|
+
}
|
|
18488
|
+
if (isRunBearingWrapper(child.tag)) {
|
|
18489
|
+
collectRunsFromWrapper(child, out);
|
|
18490
|
+
}
|
|
18491
|
+
}
|
|
18492
|
+
}
|
|
18493
|
+
var HELP9 = `docx edit \u2014 replace a paragraph or modify a section at a locator
|
|
17094
18494
|
|
|
17095
18495
|
Usage:
|
|
17096
18496
|
docx edit FILE [options]
|
|
17097
18497
|
|
|
17098
18498
|
Locator (required):
|
|
17099
|
-
--at LOCATOR Block to
|
|
18499
|
+
--at LOCATOR Block to edit (paragraph pN or section sN)
|
|
17100
18500
|
|
|
17101
|
-
|
|
18501
|
+
Paragraph content (one required for paragraph locators):
|
|
17102
18502
|
--text TEXT Replace with a single-run paragraph
|
|
17103
18503
|
--runs JSON Replace with custom runs (Run[] JSON)
|
|
17104
18504
|
|
|
17105
|
-
Paragraph options:
|
|
17106
|
-
--style NAME Paragraph style (e.g., Heading1)
|
|
17107
|
-
--alignment ALIGN left | center | right | justify
|
|
17108
|
-
|
|
17109
|
-
Run options (only with --text):
|
|
17110
|
-
--color HEX Run color, hex (e.g., 800080 for purple)
|
|
17111
|
-
--bold Bold
|
|
17112
|
-
--italic Italic
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
--
|
|
17117
|
-
|
|
17118
|
-
|
|
17119
|
-
|
|
17120
|
-
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17129
|
-
|
|
17130
|
-
|
|
17131
|
-
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
}
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
}
|
|
17157
|
-
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
regex.lastIndex += 1;
|
|
17168
|
-
result = regex.exec(paragraphText2);
|
|
17169
|
-
continue;
|
|
17170
|
-
}
|
|
17171
|
-
matches.push({
|
|
17172
|
-
start: result.index,
|
|
17173
|
-
end: result.index + matched.length,
|
|
17174
|
-
text: matched
|
|
17175
|
-
});
|
|
17176
|
-
result = regex.exec(paragraphText2);
|
|
17177
|
-
}
|
|
17178
|
-
return matches;
|
|
17179
|
-
};
|
|
17180
|
-
}
|
|
17181
|
-
function collectMatches(blocks, matcher, out) {
|
|
17182
|
-
for (const block of blocks) {
|
|
17183
|
-
if (block.type === "paragraph") {
|
|
17184
|
-
const paragraphText2 = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
|
|
17185
|
-
for (const span of matcher(paragraphText2)) {
|
|
17186
|
-
const match = {
|
|
17187
|
-
blockId: block.id,
|
|
17188
|
-
start: span.start,
|
|
17189
|
-
end: span.end,
|
|
17190
|
-
text: span.text
|
|
17191
|
-
};
|
|
17192
|
-
const overlaps = trackedChangesOverlapping(block, span.start, span.end);
|
|
17193
|
-
if (overlaps.length > 0)
|
|
17194
|
-
match.trackedChanges = overlaps;
|
|
17195
|
-
out.push(match);
|
|
17196
|
-
}
|
|
17197
|
-
continue;
|
|
17198
|
-
}
|
|
17199
|
-
if (block.type === "table") {
|
|
17200
|
-
for (const row of block.rows) {
|
|
17201
|
-
for (const cell of row.cells) {
|
|
17202
|
-
collectMatches(cell.blocks, matcher, out);
|
|
17203
|
-
}
|
|
17204
|
-
}
|
|
17205
|
-
}
|
|
17206
|
-
}
|
|
17207
|
-
}
|
|
17208
|
-
function trackedChangesOverlapping(paragraph, start, end) {
|
|
17209
|
-
const seen = new Set;
|
|
17210
|
-
const out = [];
|
|
17211
|
-
let offset = 0;
|
|
17212
|
-
for (const run10 of paragraph.runs) {
|
|
17213
|
-
const length = run10.type === "text" ? run10.text.length : 0;
|
|
17214
|
-
const runStart = offset;
|
|
17215
|
-
const runEnd = offset + length;
|
|
17216
|
-
offset = runEnd;
|
|
17217
|
-
if (run10.type !== "text")
|
|
17218
|
-
continue;
|
|
17219
|
-
if (runEnd <= start || runStart >= end)
|
|
17220
|
-
continue;
|
|
17221
|
-
const change = run10.trackedChange;
|
|
17222
|
-
if (!change)
|
|
17223
|
-
continue;
|
|
17224
|
-
if (seen.has(change.id))
|
|
17225
|
-
continue;
|
|
17226
|
-
seen.add(change.id);
|
|
17227
|
-
out.push(change);
|
|
17228
|
-
}
|
|
17229
|
-
return out;
|
|
17230
|
-
}
|
|
17231
|
-
|
|
18505
|
+
Paragraph options:
|
|
18506
|
+
--style NAME Paragraph style (e.g., Heading1)
|
|
18507
|
+
--alignment ALIGN left | center | right | justify
|
|
18508
|
+
|
|
18509
|
+
Run options (only with --text):
|
|
18510
|
+
--color HEX Run color, hex (e.g., 800080 for purple)
|
|
18511
|
+
--bold Bold
|
|
18512
|
+
--italic Italic
|
|
18513
|
+
|
|
18514
|
+
Section options (for section locators sN):
|
|
18515
|
+
--columns N Number of columns for the targeted section
|
|
18516
|
+
--type T continuous | nextPage | evenPage | oddPage | nextColumn
|
|
18517
|
+
|
|
18518
|
+
Formatting (for --text):
|
|
18519
|
+
By default --text preserves run-level formatting (bold/italic/color/etc.)
|
|
18520
|
+
on words shared between the old and new text via a word-level diff. New
|
|
18521
|
+
words inherit formatting from the nearest unchanged neighbor. Pass
|
|
18522
|
+
--no-formatting to fall back to a single fresh run with no formatting.
|
|
18523
|
+
Passing --color/--bold/--italic also bypasses preservation \u2014 those flags
|
|
18524
|
+
apply uniformly to the new paragraph.
|
|
18525
|
+
|
|
18526
|
+
General options:
|
|
18527
|
+
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
18528
|
+
--no-formatting Replace with a single fresh run; do not preserve rPr
|
|
18529
|
+
on unchanged words
|
|
18530
|
+
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
18531
|
+
--dry-run Print what would change; do not write the file
|
|
18532
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
18533
|
+
-h, --help Show this help
|
|
18534
|
+
|
|
18535
|
+
Examples:
|
|
18536
|
+
docx edit doc.docx --at p3 --text "Replaced." --style Heading2
|
|
18537
|
+
docx edit doc.docx --at p0 --runs '[{"type":"text","text":"X","bold":true}]'
|
|
18538
|
+
docx edit doc.docx --at s0 --columns 2 --type continuous
|
|
18539
|
+
`, OPTION_SPEC2;
|
|
18540
|
+
var init_edit = __esm(() => {
|
|
18541
|
+
init_core();
|
|
18542
|
+
init_parser();
|
|
18543
|
+
init_emit2();
|
|
18544
|
+
init_respond();
|
|
18545
|
+
init_preserve_formatting();
|
|
18546
|
+
init_jsx_dev_runtime();
|
|
18547
|
+
OPTION_SPEC2 = {
|
|
18548
|
+
at: { type: "string" },
|
|
18549
|
+
text: { type: "string" },
|
|
18550
|
+
runs: { type: "string" },
|
|
18551
|
+
columns: { type: "string" },
|
|
18552
|
+
type: { type: "string" },
|
|
18553
|
+
style: { type: "string" },
|
|
18554
|
+
alignment: { type: "string" },
|
|
18555
|
+
color: { type: "string" },
|
|
18556
|
+
bold: { type: "boolean" },
|
|
18557
|
+
italic: { type: "boolean" },
|
|
18558
|
+
author: { type: "string" },
|
|
18559
|
+
"no-formatting": { type: "boolean" },
|
|
18560
|
+
output: { type: "string", short: "o" },
|
|
18561
|
+
"dry-run": { type: "boolean" },
|
|
18562
|
+
verbose: { type: "boolean", short: "v" },
|
|
18563
|
+
help: { type: "boolean", short: "h" }
|
|
18564
|
+
};
|
|
18565
|
+
});
|
|
18566
|
+
|
|
17232
18567
|
// src/cli/find/index.ts
|
|
17233
18568
|
var exports_find = {};
|
|
17234
18569
|
__export(exports_find, {
|
|
@@ -17246,6 +18581,9 @@ async function run10(args) {
|
|
|
17246
18581
|
"ignore-case": { type: "boolean" },
|
|
17247
18582
|
all: { type: "boolean" },
|
|
17248
18583
|
nth: { type: "string" },
|
|
18584
|
+
current: { type: "boolean" },
|
|
18585
|
+
baseline: { type: "boolean" },
|
|
18586
|
+
exact: { type: "boolean" },
|
|
17249
18587
|
help: { type: "boolean", short: "h" }
|
|
17250
18588
|
}
|
|
17251
18589
|
});
|
|
@@ -17266,6 +18604,13 @@ async function run10(args) {
|
|
|
17266
18604
|
const ignoreCase = Boolean(parsed.values["ignore-case"]);
|
|
17267
18605
|
const useRegex = Boolean(parsed.values.regex);
|
|
17268
18606
|
const wantAll = Boolean(parsed.values.all);
|
|
18607
|
+
const wantCurrent = Boolean(parsed.values.current);
|
|
18608
|
+
const wantBaseline = Boolean(parsed.values.baseline);
|
|
18609
|
+
const exact = Boolean(parsed.values.exact);
|
|
18610
|
+
if (wantCurrent && wantBaseline) {
|
|
18611
|
+
return fail("USAGE", "--current and --baseline are mutually exclusive");
|
|
18612
|
+
}
|
|
18613
|
+
const findView = wantCurrent ? "current" : wantBaseline ? "baseline" : "accepted";
|
|
17269
18614
|
const nthRaw = parsed.values.nth;
|
|
17270
18615
|
const nth = nthRaw === undefined ? undefined : Number(nthRaw);
|
|
17271
18616
|
if (nth !== undefined && (!Number.isInteger(nth) || nth < 0)) {
|
|
@@ -17274,16 +18619,19 @@ async function run10(args) {
|
|
|
17274
18619
|
const view = await openOrFail(path);
|
|
17275
18620
|
if (typeof view === "number")
|
|
17276
18621
|
return view;
|
|
17277
|
-
let
|
|
18622
|
+
let result;
|
|
17278
18623
|
try {
|
|
17279
|
-
|
|
18624
|
+
result = findTextSpans(view.doc, query, {
|
|
17280
18625
|
regex: useRegex,
|
|
17281
|
-
ignoreCase
|
|
18626
|
+
ignoreCase,
|
|
18627
|
+
view: findView,
|
|
18628
|
+
exact
|
|
17282
18629
|
});
|
|
17283
18630
|
} catch (matcherError) {
|
|
17284
18631
|
const message = matcherError instanceof Error ? matcherError.message : String(matcherError);
|
|
17285
18632
|
return fail("USAGE", `Invalid query: ${message}`);
|
|
17286
18633
|
}
|
|
18634
|
+
const allMatches = result.matches;
|
|
17287
18635
|
let selected;
|
|
17288
18636
|
if (nth !== undefined) {
|
|
17289
18637
|
const single = allMatches[nth];
|
|
@@ -17303,11 +18651,16 @@ async function run10(args) {
|
|
|
17303
18651
|
query,
|
|
17304
18652
|
regex: useRegex,
|
|
17305
18653
|
ignoreCase,
|
|
18654
|
+
view: findView,
|
|
17306
18655
|
totalMatches: allMatches.length,
|
|
17307
18656
|
matches: selected.map((match) => ({
|
|
17308
18657
|
locator: `${match.blockId}:${match.start}-${match.end}`,
|
|
17309
18658
|
...match
|
|
17310
|
-
}))
|
|
18659
|
+
})),
|
|
18660
|
+
...result.normalizedQuery !== undefined ? {
|
|
18661
|
+
normalizedQuery: result.normalizedQuery,
|
|
18662
|
+
normalizationApplied: result.normalizationApplied
|
|
18663
|
+
} : {}
|
|
17311
18664
|
});
|
|
17312
18665
|
return EXIT.OK;
|
|
17313
18666
|
}
|
|
@@ -17324,6 +18677,12 @@ Options:
|
|
|
17324
18677
|
--ignore-case case-insensitive match
|
|
17325
18678
|
--all return every match (default: just the first)
|
|
17326
18679
|
--nth N return only the Nth match (0-indexed)
|
|
18680
|
+
--current search the raw concatenation (both ins and del text)
|
|
18681
|
+
--baseline search the pre-change text (skip ins/moveTo)
|
|
18682
|
+
default: accepted view (skip del/moveFrom) \u2014 matches
|
|
18683
|
+
"docx read --markdown" / "docx wc" / "docx comments add"
|
|
18684
|
+
--exact disable query normalization (no markdown-emphasis stripping,
|
|
18685
|
+
no smart/straight quote or em/en-dash equivalence)
|
|
17327
18686
|
-h, --help show this help
|
|
17328
18687
|
|
|
17329
18688
|
Within-paragraph matches only \u2014 cross-paragraph ranges aren't supported
|
|
@@ -17331,6 +18690,11 @@ yet. Searches the concatenated text of each paragraph in document order,
|
|
|
17331
18690
|
including paragraphs nested in table cells (locators look like
|
|
17332
18691
|
tT:rRcC:pK:S-E for those).
|
|
17333
18692
|
|
|
18693
|
+
By default the query is normalized: balanced markdown emphasis around
|
|
18694
|
+
non-whitespace (**X**, __X__, *X*, \`X\`) is stripped; smart quotes match
|
|
18695
|
+
straight quotes; em-dash and en-dash match the hyphen. Pass --exact to
|
|
18696
|
+
match the raw query verbatim. --regex is always verbatim.
|
|
18697
|
+
|
|
17334
18698
|
Examples:
|
|
17335
18699
|
docx find doc.docx "fox"
|
|
17336
18700
|
docx find doc.docx "Action Item:" --all
|
|
@@ -17444,6 +18808,7 @@ async function run11(args) {
|
|
|
17444
18808
|
author: { type: "string" },
|
|
17445
18809
|
output: { type: "string", short: "o" },
|
|
17446
18810
|
"dry-run": { type: "boolean" },
|
|
18811
|
+
verbose: { type: "boolean", short: "v" },
|
|
17447
18812
|
help: { type: "boolean", short: "h" }
|
|
17448
18813
|
}
|
|
17449
18814
|
});
|
|
@@ -17455,6 +18820,7 @@ async function run11(args) {
|
|
|
17455
18820
|
await writeStdout(HELP11);
|
|
17456
18821
|
return EXIT.OK;
|
|
17457
18822
|
}
|
|
18823
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
17458
18824
|
const path = parsed.positionals[0];
|
|
17459
18825
|
if (!path)
|
|
17460
18826
|
return fail("USAGE", "Missing FILE argument", HELP11);
|
|
@@ -17518,7 +18884,7 @@ async function run11(args) {
|
|
|
17518
18884
|
});
|
|
17519
18885
|
}
|
|
17520
18886
|
await saveDocView(view, outputPath);
|
|
17521
|
-
await
|
|
18887
|
+
await respondAck({
|
|
17522
18888
|
ok: true,
|
|
17523
18889
|
operation: "hyperlinks.add",
|
|
17524
18890
|
path: outputPath ?? path,
|
|
@@ -17543,6 +18909,7 @@ Optional:
|
|
|
17543
18909
|
(default: $DOCX_AUTHOR)
|
|
17544
18910
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17545
18911
|
--dry-run Print what would change; do not write the file
|
|
18912
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
17546
18913
|
-h, --help Show this help
|
|
17547
18914
|
|
|
17548
18915
|
The span must lie inside a single paragraph and must not overlap an existing
|
|
@@ -17581,6 +18948,7 @@ async function run12(args) {
|
|
|
17581
18948
|
author: { type: "string" },
|
|
17582
18949
|
output: { type: "string", short: "o" },
|
|
17583
18950
|
"dry-run": { type: "boolean" },
|
|
18951
|
+
verbose: { type: "boolean", short: "v" },
|
|
17584
18952
|
help: { type: "boolean", short: "h" }
|
|
17585
18953
|
}
|
|
17586
18954
|
});
|
|
@@ -17592,6 +18960,7 @@ async function run12(args) {
|
|
|
17592
18960
|
await writeStdout(HELP12);
|
|
17593
18961
|
return EXIT.OK;
|
|
17594
18962
|
}
|
|
18963
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
17595
18964
|
const path = parsed.positionals[0];
|
|
17596
18965
|
if (!path)
|
|
17597
18966
|
return fail("USAGE", "Missing FILE argument", HELP12);
|
|
@@ -17643,7 +19012,7 @@ async function run12(args) {
|
|
|
17643
19012
|
});
|
|
17644
19013
|
}
|
|
17645
19014
|
await saveDocView(view, outputPath);
|
|
17646
|
-
await
|
|
19015
|
+
await respondAck({
|
|
17647
19016
|
ok: true,
|
|
17648
19017
|
operation: "hyperlinks.delete",
|
|
17649
19018
|
path: outputPath ?? path,
|
|
@@ -17688,6 +19057,7 @@ Optional:
|
|
|
17688
19057
|
(default: $DOCX_AUTHOR)
|
|
17689
19058
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17690
19059
|
--dry-run Print what would change; do not write the file
|
|
19060
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
17691
19061
|
-h, --help Show this help
|
|
17692
19062
|
|
|
17693
19063
|
The display text stays in place; only the <w:hyperlink> wrapper is removed.
|
|
@@ -17816,6 +19186,7 @@ async function run14(args) {
|
|
|
17816
19186
|
author: { type: "string" },
|
|
17817
19187
|
output: { type: "string", short: "o" },
|
|
17818
19188
|
"dry-run": { type: "boolean" },
|
|
19189
|
+
verbose: { type: "boolean", short: "v" },
|
|
17819
19190
|
help: { type: "boolean", short: "h" }
|
|
17820
19191
|
}
|
|
17821
19192
|
});
|
|
@@ -17827,6 +19198,7 @@ async function run14(args) {
|
|
|
17827
19198
|
await writeStdout(HELP14);
|
|
17828
19199
|
return EXIT.OK;
|
|
17829
19200
|
}
|
|
19201
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
17830
19202
|
const path = parsed.positionals[0];
|
|
17831
19203
|
if (!path)
|
|
17832
19204
|
return fail("USAGE", "Missing FILE argument", HELP14);
|
|
@@ -17887,7 +19259,7 @@ async function run14(args) {
|
|
|
17887
19259
|
}
|
|
17888
19260
|
}
|
|
17889
19261
|
await saveDocView(view, outputPath);
|
|
17890
|
-
await
|
|
19262
|
+
await respondAck({
|
|
17891
19263
|
ok: true,
|
|
17892
19264
|
operation: "hyperlinks.replace",
|
|
17893
19265
|
path: outputPath ?? path,
|
|
@@ -17938,6 +19310,7 @@ Optional:
|
|
|
17938
19310
|
(default: $DOCX_AUTHOR)
|
|
17939
19311
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
17940
19312
|
--dry-run Print what would change; do not write the file
|
|
19313
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
17941
19314
|
-h, --help Show this help
|
|
17942
19315
|
|
|
17943
19316
|
Replaces only the targeted hyperlink. If multiple hyperlinks shared the same
|
|
@@ -18217,6 +19590,7 @@ async function run18(args) {
|
|
|
18217
19590
|
author: { type: "string" },
|
|
18218
19591
|
output: { type: "string", short: "o" },
|
|
18219
19592
|
"dry-run": { type: "boolean" },
|
|
19593
|
+
verbose: { type: "boolean", short: "v" },
|
|
18220
19594
|
help: { type: "boolean", short: "h" }
|
|
18221
19595
|
}
|
|
18222
19596
|
});
|
|
@@ -18228,6 +19602,7 @@ async function run18(args) {
|
|
|
18228
19602
|
await writeStdout(HELP18);
|
|
18229
19603
|
return EXIT.OK;
|
|
18230
19604
|
}
|
|
19605
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
18231
19606
|
const path = parsed.positionals[0];
|
|
18232
19607
|
if (!path)
|
|
18233
19608
|
return fail("USAGE", "Missing FILE argument", HELP18);
|
|
@@ -18294,7 +19669,7 @@ async function run18(args) {
|
|
|
18294
19669
|
}
|
|
18295
19670
|
}
|
|
18296
19671
|
await saveDocView(view, outputPath);
|
|
18297
|
-
await
|
|
19672
|
+
await respondAck({
|
|
18298
19673
|
ok: true,
|
|
18299
19674
|
operation: "images.replace",
|
|
18300
19675
|
path: outputPath ?? path,
|
|
@@ -18382,6 +19757,7 @@ Optional:
|
|
|
18382
19757
|
(default: $DOCX_AUTHOR)
|
|
18383
19758
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
18384
19759
|
--dry-run Print what would change; do not write the file
|
|
19760
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
18385
19761
|
-h, --help Show this help
|
|
18386
19762
|
|
|
18387
19763
|
If the replacement uses a different format from the original, the part is
|
|
@@ -18500,8 +19876,17 @@ export type TableCell = {
|
|
|
18500
19876
|
export type SectionBreak = {
|
|
18501
19877
|
id: string;
|
|
18502
19878
|
type: "sectionBreak";
|
|
19879
|
+
columns?: number;
|
|
19880
|
+
sectionType?: SectionType;
|
|
18503
19881
|
};
|
|
18504
19882
|
|
|
19883
|
+
export type SectionType =
|
|
19884
|
+
| "continuous"
|
|
19885
|
+
| "nextPage"
|
|
19886
|
+
| "evenPage"
|
|
19887
|
+
| "oddPage"
|
|
19888
|
+
| "nextColumn";
|
|
19889
|
+
|
|
18505
19890
|
export type Run =
|
|
18506
19891
|
| TextRun
|
|
18507
19892
|
| ImageRun
|
|
@@ -18598,8 +19983,17 @@ export type TrackedChange = {
|
|
|
18598
19983
|
* of a tracked move. moveFrom behaves like a delete (text leaves this
|
|
18599
19984
|
* location, stored as <w:delText> internally); moveTo behaves like an
|
|
18600
19985
|
* insert (text arrives at this location).
|
|
19986
|
+
* - \`sectPrChange\`: <w:sectPrChange> \u2014 section-property revision marker
|
|
19987
|
+
* embedded inside a <w:sectPr>. Carries a snapshot of the prior section
|
|
19988
|
+
* properties (e.g. columns / type) so accept/reject can drop or restore
|
|
19989
|
+
* them. Has no run text.
|
|
18601
19990
|
*/
|
|
18602
|
-
export type TrackedChangeKind =
|
|
19991
|
+
export type TrackedChangeKind =
|
|
19992
|
+
| "ins"
|
|
19993
|
+
| "del"
|
|
19994
|
+
| "moveFrom"
|
|
19995
|
+
| "moveTo"
|
|
19996
|
+
| "sectPrChange";
|
|
18603
19997
|
|
|
18604
19998
|
export type Comment = {
|
|
18605
19999
|
id: string;
|
|
@@ -18760,7 +20154,9 @@ var init_schema = __esm(() => {
|
|
|
18760
20154
|
required: ["id", "kind", "author", "date", "revisionId"],
|
|
18761
20155
|
properties: {
|
|
18762
20156
|
id: { type: "string" },
|
|
18763
|
-
kind: {
|
|
20157
|
+
kind: {
|
|
20158
|
+
enum: ["ins", "del", "moveFrom", "moveTo"]
|
|
20159
|
+
},
|
|
18764
20160
|
author: { type: "string" },
|
|
18765
20161
|
date: { type: "string" },
|
|
18766
20162
|
revisionId: { type: "string" }
|
|
@@ -18872,7 +20268,11 @@ var init_schema = __esm(() => {
|
|
|
18872
20268
|
required: ["id", "type"],
|
|
18873
20269
|
properties: {
|
|
18874
20270
|
id: { type: "string" },
|
|
18875
|
-
type: { const: "sectionBreak" }
|
|
20271
|
+
type: { const: "sectionBreak" },
|
|
20272
|
+
columns: { type: "number" },
|
|
20273
|
+
sectionType: {
|
|
20274
|
+
enum: ["continuous", "nextPage", "evenPage", "oddPage", "nextColumn"]
|
|
20275
|
+
}
|
|
18876
20276
|
}
|
|
18877
20277
|
},
|
|
18878
20278
|
Comment: {
|
|
@@ -18967,13 +20367,17 @@ Entity locators:
|
|
|
18967
20367
|
imgN Image id (e.g., img2)
|
|
18968
20368
|
linkN Hyperlink id (e.g., link0)
|
|
18969
20369
|
tcN Tracked change id (e.g., tc0) \u2014 the Nth revision wrapper
|
|
18970
|
-
|
|
20370
|
+
in document order. Includes run-level <w:ins>/<w:del>/
|
|
20371
|
+
<w:moveFrom>/<w:moveTo>, section-property revisions
|
|
20372
|
+
<w:sectPrChange>, and paragraph-mark <w:ins>/<w:del>
|
|
20373
|
+
(a self-closing element inside <w:pPr><w:rPr>).
|
|
18971
20374
|
tN:rRcC Cell at row R, column C of table tN
|
|
18972
20375
|
|
|
18973
20376
|
Examples:
|
|
18974
20377
|
p3 -> the entire paragraph p3
|
|
18975
20378
|
p3:5-20 -> characters 5..20 of p3
|
|
18976
20379
|
p3:5-p5:10 -> from char 5 of p3 to char 10 of p5
|
|
20380
|
+
s0 -> first section break (typically the front-matter section)
|
|
18977
20381
|
c1 -> comment c1
|
|
18978
20382
|
img0 -> image img0
|
|
18979
20383
|
link0 -> hyperlink link0
|
|
@@ -19011,7 +20415,11 @@ var init_locators2 = __esm(() => {
|
|
|
19011
20415
|
comment: { syntax: "cN", example: "c1" },
|
|
19012
20416
|
image: { syntax: "imgN", example: "img0" },
|
|
19013
20417
|
hyperlink: { syntax: "linkN", example: "link0" },
|
|
19014
|
-
trackedChange: {
|
|
20418
|
+
trackedChange: {
|
|
20419
|
+
syntax: "tcN",
|
|
20420
|
+
example: "tc0",
|
|
20421
|
+
notes: "Covers run-level <w:ins>/<w:del>/<w:moveFrom>/<w:moveTo>, <w:sectPrChange>, and paragraph-mark <w:ins>/<w:del>."
|
|
20422
|
+
},
|
|
19015
20423
|
cell: { syntax: "tN:rRcC", example: "t0:r1c2" },
|
|
19016
20424
|
nestedCell: { syntax: "tN:rRcC:pK", example: "t0:r1c2:p0" }
|
|
19017
20425
|
},
|
|
@@ -19067,27 +20475,25 @@ __export(exports_insert, {
|
|
|
19067
20475
|
});
|
|
19068
20476
|
import { parseArgs as parseArgs19 } from "util";
|
|
19069
20477
|
async function run23(args) {
|
|
20478
|
+
const opts = await parseAndValidateOptions3(args);
|
|
20479
|
+
if (typeof opts === "number")
|
|
20480
|
+
return opts;
|
|
20481
|
+
const view = await openOrFail(opts.filePath);
|
|
20482
|
+
if (typeof view === "number")
|
|
20483
|
+
return view;
|
|
20484
|
+
const blockRef = await resolveBlockOrFail(view, opts.placement.locator);
|
|
20485
|
+
if (typeof blockRef === "number")
|
|
20486
|
+
return blockRef;
|
|
20487
|
+
const paragraph = buildInsertedParagraph(view, opts.spec, opts.paragraphOptions);
|
|
20488
|
+
return commitInsertedParagraph(view, blockRef, paragraph, opts);
|
|
20489
|
+
}
|
|
20490
|
+
async function parseAndValidateOptions3(args) {
|
|
19070
20491
|
let parsed;
|
|
19071
20492
|
try {
|
|
19072
20493
|
parsed = parseArgs19({
|
|
19073
20494
|
args,
|
|
19074
20495
|
allowPositionals: true,
|
|
19075
|
-
options:
|
|
19076
|
-
after: { type: "string" },
|
|
19077
|
-
before: { type: "string" },
|
|
19078
|
-
text: { type: "string" },
|
|
19079
|
-
runs: { type: "string" },
|
|
19080
|
-
style: { type: "string" },
|
|
19081
|
-
alignment: { type: "string" },
|
|
19082
|
-
color: { type: "string" },
|
|
19083
|
-
bold: { type: "boolean" },
|
|
19084
|
-
italic: { type: "boolean" },
|
|
19085
|
-
url: { type: "string" },
|
|
19086
|
-
author: { type: "string" },
|
|
19087
|
-
output: { type: "string", short: "o" },
|
|
19088
|
-
"dry-run": { type: "boolean" },
|
|
19089
|
-
help: { type: "boolean", short: "h" }
|
|
19090
|
-
}
|
|
20496
|
+
options: OPTION_SPEC3
|
|
19091
20497
|
});
|
|
19092
20498
|
} catch (parseError) {
|
|
19093
20499
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
@@ -19097,104 +20503,200 @@ async function run23(args) {
|
|
|
19097
20503
|
await writeStdout(HELP23);
|
|
19098
20504
|
return EXIT.OK;
|
|
19099
20505
|
}
|
|
19100
|
-
|
|
19101
|
-
|
|
20506
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
20507
|
+
const filePath = parsed.positionals[0];
|
|
20508
|
+
if (!filePath)
|
|
19102
20509
|
return fail("USAGE", "Missing FILE argument", HELP23);
|
|
19103
|
-
const
|
|
19104
|
-
|
|
20510
|
+
const placement = await parseTargetPlacement(parsed.values);
|
|
20511
|
+
if (typeof placement === "number")
|
|
20512
|
+
return placement;
|
|
20513
|
+
const spec = await chooseContentSpec(parsed.values);
|
|
20514
|
+
if (typeof spec === "number")
|
|
20515
|
+
return spec;
|
|
20516
|
+
const paragraphOptions = await parseParagraphOptions2(parsed.values);
|
|
20517
|
+
if (typeof paragraphOptions === "number")
|
|
20518
|
+
return paragraphOptions;
|
|
20519
|
+
return {
|
|
20520
|
+
filePath,
|
|
20521
|
+
placement,
|
|
20522
|
+
spec,
|
|
20523
|
+
paragraphOptions,
|
|
20524
|
+
authorFlag: parsed.values.author,
|
|
20525
|
+
outputPath: parsed.values.output,
|
|
20526
|
+
dryRun: Boolean(parsed.values["dry-run"])
|
|
20527
|
+
};
|
|
20528
|
+
}
|
|
20529
|
+
async function parseTargetPlacement(values) {
|
|
20530
|
+
const after = values.after;
|
|
20531
|
+
const before = values.before;
|
|
19105
20532
|
if (!after && !before) {
|
|
19106
20533
|
return fail("USAGE", "Missing locator: pass --after or --before", HELP23);
|
|
19107
20534
|
}
|
|
19108
20535
|
if (after && before) {
|
|
19109
20536
|
return fail("USAGE", "Pass either --after or --before, not both", HELP23);
|
|
19110
20537
|
}
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
20538
|
+
if (after !== undefined)
|
|
20539
|
+
return { mode: "after", locator: after };
|
|
20540
|
+
return { mode: "before", locator: before };
|
|
20541
|
+
}
|
|
20542
|
+
async function chooseContentSpec(values) {
|
|
20543
|
+
const text = values.text;
|
|
20544
|
+
const runsJson = values.runs;
|
|
20545
|
+
const url = values.url;
|
|
20546
|
+
const pageBreak = values["page-break"] ?? false;
|
|
20547
|
+
const columnBreak = values["column-break"] ?? false;
|
|
20548
|
+
const sectionFlag = values.section ?? false;
|
|
20549
|
+
const columnsRaw = values.columns;
|
|
20550
|
+
const sectionTypeRaw = values.type;
|
|
20551
|
+
const contentFlagCount = (text !== undefined ? 1 : 0) + (runsJson !== undefined ? 1 : 0) + (pageBreak ? 1 : 0) + (columnBreak ? 1 : 0) + (sectionFlag ? 1 : 0);
|
|
20552
|
+
if (contentFlagCount === 0) {
|
|
20553
|
+
return fail("USAGE", "Missing content: pass --text, --runs, --page-break, --column-break, or --section", HELP23);
|
|
20554
|
+
}
|
|
20555
|
+
if (contentFlagCount > 1) {
|
|
20556
|
+
return fail("USAGE", "Pass only one of --text, --runs, --page-break, --column-break, --section", HELP23);
|
|
20557
|
+
}
|
|
20558
|
+
if (url !== undefined && text === undefined) {
|
|
20559
|
+
return fail("USAGE", "--url requires --text", HELP23);
|
|
19116
20560
|
}
|
|
19117
|
-
if (
|
|
19118
|
-
return fail("USAGE", "
|
|
20561
|
+
if ((columnsRaw !== undefined || sectionTypeRaw !== undefined) && !sectionFlag) {
|
|
20562
|
+
return fail("USAGE", "--columns and --type require --section", HELP23);
|
|
19119
20563
|
}
|
|
19120
|
-
if (
|
|
19121
|
-
return
|
|
20564
|
+
if (text !== undefined) {
|
|
20565
|
+
return {
|
|
20566
|
+
kind: "text",
|
|
20567
|
+
text,
|
|
20568
|
+
format: {
|
|
20569
|
+
color: values.color,
|
|
20570
|
+
bold: values.bold,
|
|
20571
|
+
italic: values.italic
|
|
20572
|
+
},
|
|
20573
|
+
...url ? { hyperlinkUrl: url } : {}
|
|
20574
|
+
};
|
|
20575
|
+
}
|
|
20576
|
+
if (runsJson !== undefined) {
|
|
20577
|
+
const runs = await parseRunsArg2(runsJson);
|
|
20578
|
+
if (typeof runs === "number")
|
|
20579
|
+
return runs;
|
|
20580
|
+
return { kind: "runs", runs };
|
|
20581
|
+
}
|
|
20582
|
+
if (pageBreak)
|
|
20583
|
+
return { kind: "break", breakKind: "page" };
|
|
20584
|
+
if (columnBreak)
|
|
20585
|
+
return { kind: "break", breakKind: "column" };
|
|
20586
|
+
const sectionFlags = await parseSectionFlags2(values);
|
|
20587
|
+
if (typeof sectionFlags === "number")
|
|
20588
|
+
return sectionFlags;
|
|
20589
|
+
return { kind: "section", ...sectionFlags };
|
|
20590
|
+
}
|
|
20591
|
+
async function parseRunsArg2(json) {
|
|
20592
|
+
let parsed;
|
|
20593
|
+
try {
|
|
20594
|
+
parsed = JSON.parse(json);
|
|
20595
|
+
} catch (jsonError) {
|
|
20596
|
+
const message = jsonError instanceof Error ? jsonError.message : String(jsonError);
|
|
20597
|
+
return fail("USAGE", `Invalid --runs JSON: ${message}`);
|
|
20598
|
+
}
|
|
20599
|
+
if (!Array.isArray(parsed)) {
|
|
20600
|
+
return fail("USAGE", "--runs must be a JSON array of Run objects");
|
|
20601
|
+
}
|
|
20602
|
+
return parsed;
|
|
20603
|
+
}
|
|
20604
|
+
async function parseSectionFlags2(values) {
|
|
20605
|
+
const out = {};
|
|
20606
|
+
const columnsRaw = values.columns;
|
|
20607
|
+
if (columnsRaw !== undefined) {
|
|
20608
|
+
const columns = Number.parseInt(columnsRaw, 10);
|
|
20609
|
+
if (!Number.isFinite(columns) || columns <= 0) {
|
|
20610
|
+
return fail("USAGE", `--columns must be a positive integer, got "${columnsRaw}"`);
|
|
20611
|
+
}
|
|
20612
|
+
out.columns = columns;
|
|
20613
|
+
}
|
|
20614
|
+
const sectionTypeRaw = values.type;
|
|
20615
|
+
if (sectionTypeRaw !== undefined) {
|
|
20616
|
+
if (!isSectionType(sectionTypeRaw)) {
|
|
20617
|
+
return fail("USAGE", `Invalid --type: ${sectionTypeRaw}`, "Valid values: continuous, nextPage, evenPage, oddPage, nextColumn");
|
|
20618
|
+
}
|
|
20619
|
+
out.sectionType = sectionTypeRaw;
|
|
19122
20620
|
}
|
|
19123
|
-
|
|
19124
|
-
|
|
20621
|
+
return out;
|
|
20622
|
+
}
|
|
20623
|
+
async function parseParagraphOptions2(values) {
|
|
20624
|
+
const out = {};
|
|
20625
|
+
const styleValue = values.style;
|
|
19125
20626
|
if (styleValue)
|
|
19126
|
-
|
|
19127
|
-
const alignmentValue =
|
|
20627
|
+
out.style = styleValue;
|
|
20628
|
+
const alignmentValue = values.alignment;
|
|
19128
20629
|
if (alignmentValue) {
|
|
19129
20630
|
if (alignmentValue !== "left" && alignmentValue !== "center" && alignmentValue !== "right" && alignmentValue !== "justify") {
|
|
19130
20631
|
return fail("USAGE", `Invalid --alignment: ${alignmentValue}`, "Valid values: left, center, right, justify");
|
|
19131
20632
|
}
|
|
19132
|
-
|
|
20633
|
+
out.alignment = alignmentValue;
|
|
19133
20634
|
}
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
|
|
19139
|
-
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
|
|
19149
|
-
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
|
|
19156
|
-
|
|
19157
|
-
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
}
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
}, undefined, false, undefined, this);
|
|
20635
|
+
return out;
|
|
20636
|
+
}
|
|
20637
|
+
function buildInsertedParagraph(view, spec, paragraphOptions) {
|
|
20638
|
+
switch (spec.kind) {
|
|
20639
|
+
case "text":
|
|
20640
|
+
return buildTextParagraph(view, spec, paragraphOptions);
|
|
20641
|
+
case "runs":
|
|
20642
|
+
return /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
20643
|
+
runs: spec.runs,
|
|
20644
|
+
...paragraphOptions
|
|
20645
|
+
}, undefined, false, undefined, this);
|
|
20646
|
+
case "break":
|
|
20647
|
+
return /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
20648
|
+
runs: [{ type: "break", kind: spec.breakKind }],
|
|
20649
|
+
...paragraphOptions
|
|
20650
|
+
}, undefined, false, undefined, this);
|
|
20651
|
+
case "section":
|
|
20652
|
+
return /* @__PURE__ */ jsxDEV(SentinelSectionParagraph, {
|
|
20653
|
+
...spec.columns !== undefined ? { columns: spec.columns } : {},
|
|
20654
|
+
...spec.sectionType ? { sectionType: spec.sectionType } : {}
|
|
20655
|
+
}, undefined, false, undefined, this);
|
|
20656
|
+
}
|
|
20657
|
+
}
|
|
20658
|
+
function buildTextParagraph(view, spec, paragraphOptions) {
|
|
20659
|
+
const paragraphNode = /* @__PURE__ */ jsxDEV(Paragraph, {
|
|
20660
|
+
text: spec.text,
|
|
20661
|
+
...paragraphOptions,
|
|
20662
|
+
...spec.format.color ? { color: spec.format.color } : {},
|
|
20663
|
+
...spec.format.bold ? { bold: true } : {},
|
|
20664
|
+
...spec.format.italic ? { italic: true } : {}
|
|
20665
|
+
}, undefined, false, undefined, this);
|
|
20666
|
+
if (spec.hyperlinkUrl) {
|
|
20667
|
+
wrapFirstRunInHyperlink(view, paragraphNode, spec.hyperlinkUrl);
|
|
19168
20668
|
}
|
|
20669
|
+
return paragraphNode;
|
|
20670
|
+
}
|
|
20671
|
+
async function commitInsertedParagraph(view, blockRef, paragraph, opts) {
|
|
19169
20672
|
const targetIndex = blockRef.parent.indexOf(blockRef.node);
|
|
19170
20673
|
if (targetIndex === -1) {
|
|
19171
20674
|
return fail("BLOCK_NOT_FOUND", "Block reference is stale (parent does not contain it)");
|
|
19172
20675
|
}
|
|
19173
|
-
const insertIndex =
|
|
20676
|
+
const insertIndex = opts.placement.mode === "after" ? targetIndex + 1 : targetIndex;
|
|
19174
20677
|
if (isTrackChangesEnabled(view)) {
|
|
19175
|
-
applyTrackedInsertion(
|
|
20678
|
+
applyTrackedInsertion(paragraph, view, opts.authorFlag);
|
|
19176
20679
|
}
|
|
19177
|
-
|
|
19178
|
-
if (parsed.values["dry-run"]) {
|
|
20680
|
+
if (opts.dryRun) {
|
|
19179
20681
|
await respond({
|
|
19180
20682
|
ok: true,
|
|
19181
20683
|
operation: "insert",
|
|
19182
20684
|
dryRun: true,
|
|
19183
|
-
path,
|
|
19184
|
-
locator:
|
|
19185
|
-
placement:
|
|
19186
|
-
...outputPath ? { output: outputPath } : {}
|
|
20685
|
+
path: opts.filePath,
|
|
20686
|
+
locator: opts.placement.locator,
|
|
20687
|
+
placement: opts.placement.mode,
|
|
20688
|
+
...opts.outputPath ? { output: opts.outputPath } : {}
|
|
19187
20689
|
});
|
|
19188
20690
|
return EXIT.OK;
|
|
19189
20691
|
}
|
|
19190
|
-
blockRef.parent.splice(insertIndex, 0,
|
|
19191
|
-
await saveDocView(view, outputPath);
|
|
19192
|
-
await
|
|
20692
|
+
blockRef.parent.splice(insertIndex, 0, paragraph);
|
|
20693
|
+
await saveDocView(view, opts.outputPath);
|
|
20694
|
+
await respondAck({
|
|
19193
20695
|
ok: true,
|
|
19194
20696
|
operation: "insert",
|
|
19195
|
-
path: outputPath ??
|
|
19196
|
-
locator:
|
|
19197
|
-
placement:
|
|
20697
|
+
path: opts.outputPath ?? opts.filePath,
|
|
20698
|
+
locator: opts.placement.locator,
|
|
20699
|
+
placement: opts.placement.mode
|
|
19198
20700
|
});
|
|
19199
20701
|
return EXIT.OK;
|
|
19200
20702
|
}
|
|
@@ -19263,6 +20765,9 @@ Locator (one required):
|
|
|
19263
20765
|
Content (one required):
|
|
19264
20766
|
--text TEXT Insert a paragraph with this text
|
|
19265
20767
|
--runs JSON Insert a paragraph with custom runs (Run[] JSON)
|
|
20768
|
+
--page-break Insert an empty paragraph containing a page break
|
|
20769
|
+
--column-break Insert an empty paragraph containing a column break
|
|
20770
|
+
--section Insert a section boundary (sentinel paragraph w/ inline sectPr)
|
|
19266
20771
|
|
|
19267
20772
|
Paragraph options:
|
|
19268
20773
|
--style NAME Apply paragraph style (e.g., Heading1)
|
|
@@ -19274,9 +20779,15 @@ Run options (only with --text):
|
|
|
19274
20779
|
--italic Italic
|
|
19275
20780
|
--url URL Wrap the inserted text in a hyperlink to URL
|
|
19276
20781
|
|
|
20782
|
+
Section options (only with --section):
|
|
20783
|
+
--columns N Number of columns for the section ending at this boundary
|
|
20784
|
+
--type T continuous | nextPage | evenPage | oddPage | nextColumn
|
|
20785
|
+
|
|
20786
|
+
General options:
|
|
19277
20787
|
--author NAME Author for tracked changes (default: $DOCX_AUTHOR)
|
|
19278
20788
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
19279
20789
|
--dry-run Print what would be inserted; do not write the file
|
|
20790
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
19280
20791
|
-h, --help Show this help
|
|
19281
20792
|
|
|
19282
20793
|
Examples:
|
|
@@ -19284,7 +20795,9 @@ Examples:
|
|
|
19284
20795
|
docx insert doc.docx --before p0 --text "ALERT" --color CC0000 --bold
|
|
19285
20796
|
docx insert doc.docx --after p2 --runs '[{"type":"text","text":"X","bold":true}]'
|
|
19286
20797
|
docx insert doc.docx --after p3 --text "click here" --url https://example.com
|
|
19287
|
-
|
|
20798
|
+
docx insert doc.docx --after p3 --page-break
|
|
20799
|
+
docx insert doc.docx --after p9 --section --columns 2 --type continuous
|
|
20800
|
+
`, OPTION_SPEC3;
|
|
19288
20801
|
var init_insert = __esm(() => {
|
|
19289
20802
|
init_core();
|
|
19290
20803
|
init_jsx();
|
|
@@ -19292,6 +20805,28 @@ var init_insert = __esm(() => {
|
|
|
19292
20805
|
init_respond();
|
|
19293
20806
|
init_emit2();
|
|
19294
20807
|
init_jsx_dev_runtime();
|
|
20808
|
+
OPTION_SPEC3 = {
|
|
20809
|
+
after: { type: "string" },
|
|
20810
|
+
before: { type: "string" },
|
|
20811
|
+
text: { type: "string" },
|
|
20812
|
+
runs: { type: "string" },
|
|
20813
|
+
"page-break": { type: "boolean" },
|
|
20814
|
+
"column-break": { type: "boolean" },
|
|
20815
|
+
section: { type: "boolean" },
|
|
20816
|
+
columns: { type: "string" },
|
|
20817
|
+
type: { type: "string" },
|
|
20818
|
+
style: { type: "string" },
|
|
20819
|
+
alignment: { type: "string" },
|
|
20820
|
+
color: { type: "string" },
|
|
20821
|
+
bold: { type: "boolean" },
|
|
20822
|
+
italic: { type: "boolean" },
|
|
20823
|
+
url: { type: "string" },
|
|
20824
|
+
author: { type: "string" },
|
|
20825
|
+
output: { type: "string", short: "o" },
|
|
20826
|
+
"dry-run": { type: "boolean" },
|
|
20827
|
+
verbose: { type: "boolean", short: "v" },
|
|
20828
|
+
help: { type: "boolean", short: "h" }
|
|
20829
|
+
};
|
|
19295
20830
|
});
|
|
19296
20831
|
|
|
19297
20832
|
// src/cli/outline/build.ts
|
|
@@ -19485,7 +21020,7 @@ function isRunVisible(run25, view) {
|
|
|
19485
21020
|
return true;
|
|
19486
21021
|
}
|
|
19487
21022
|
function buildCommentIndex(blocks, options) {
|
|
19488
|
-
const view = options.view ?? "
|
|
21023
|
+
const view = options.view ?? "accepted";
|
|
19489
21024
|
const lastSlot = new Map;
|
|
19490
21025
|
const spanText = new Map;
|
|
19491
21026
|
const orderedIds = [];
|
|
@@ -19557,7 +21092,7 @@ function headingLevelFor(style) {
|
|
|
19557
21092
|
return parsed;
|
|
19558
21093
|
}
|
|
19559
21094
|
function renderRuns(paragraphId, runs, ctx) {
|
|
19560
|
-
const view = ctx.options.view ?? "
|
|
21095
|
+
const view = ctx.options.view ?? "accepted";
|
|
19561
21096
|
const visibleEntries = [];
|
|
19562
21097
|
runs.forEach((run25, index) => {
|
|
19563
21098
|
if (run25.type === "text" && !isRunVisible(run25, view))
|
|
@@ -19915,6 +21450,7 @@ async function run25(args) {
|
|
|
19915
21450
|
to: { type: "string" },
|
|
19916
21451
|
accepted: { type: "boolean" },
|
|
19917
21452
|
baseline: { type: "boolean" },
|
|
21453
|
+
current: { type: "boolean" },
|
|
19918
21454
|
comments: { type: "boolean" },
|
|
19919
21455
|
help: { type: "boolean", short: "h" }
|
|
19920
21456
|
}
|
|
@@ -19934,14 +21470,16 @@ async function run25(args) {
|
|
|
19934
21470
|
const to = parsed.values.to;
|
|
19935
21471
|
const accepted = Boolean(parsed.values.accepted);
|
|
19936
21472
|
const baseline = Boolean(parsed.values.baseline);
|
|
21473
|
+
const current = Boolean(parsed.values.current);
|
|
19937
21474
|
const showComments = Boolean(parsed.values.comments);
|
|
19938
|
-
if (!markdown && (from || to || accepted || baseline || showComments)) {
|
|
19939
|
-
return fail("USAGE", "--from, --to, --accepted, --baseline, and --comments require --markdown", HELP25);
|
|
21475
|
+
if (!markdown && (from || to || accepted || baseline || current || showComments)) {
|
|
21476
|
+
return fail("USAGE", "--from, --to, --accepted, --baseline, --current, and --comments require --markdown", HELP25);
|
|
19940
21477
|
}
|
|
19941
|
-
|
|
19942
|
-
|
|
21478
|
+
const viewFlagCount = (accepted ? 1 : 0) + (baseline ? 1 : 0) + (current ? 1 : 0);
|
|
21479
|
+
if (viewFlagCount > 1) {
|
|
21480
|
+
return fail("USAGE", "--accepted, --baseline, and --current are mutually exclusive", HELP25);
|
|
19943
21481
|
}
|
|
19944
|
-
const view =
|
|
21482
|
+
const view = current ? "current" : baseline ? "baseline" : "accepted";
|
|
19945
21483
|
const docView = await openOrFail(path);
|
|
19946
21484
|
if (typeof docView === "number")
|
|
19947
21485
|
return docView;
|
|
@@ -19982,18 +21520,19 @@ Options:
|
|
|
19982
21520
|
Accepts pN, tN, tN:rRcC[:pK[:S-E]], pN:S-E, pN:S-pM:E.
|
|
19983
21521
|
Cell/span/range locators collapse to their enclosing
|
|
19984
21522
|
top-level block (the table or paragraph).
|
|
19985
|
-
--accepted
|
|
19986
|
-
subtractive wrappers (<w:del>, <w:moveFrom>), inline
|
|
21523
|
+
--accepted Default with --markdown: render the post-accept view \u2014
|
|
21524
|
+
drop subtractive wrappers (<w:del>, <w:moveFrom>), inline
|
|
19987
21525
|
additive wrappers (<w:ins>, <w:moveTo>) as plain text,
|
|
19988
|
-
no markers/refs.
|
|
21526
|
+
no markers/refs. Kept as an explicit alias for clarity.
|
|
19989
21527
|
--baseline With --markdown, render the pre-change view: drop
|
|
19990
21528
|
additive wrappers (<w:ins>, <w:moveTo>), inline
|
|
19991
21529
|
subtractive wrappers (<w:del>, <w:moveFrom>) as plain
|
|
19992
|
-
text, no markers/refs.
|
|
19993
|
-
|
|
19994
|
-
{++text++}[^tcN] and subtractive as
|
|
19995
|
-
(CriticMarkup); the [^tcN] footnote
|
|
19996
|
-
(insertion / deletion / moveTo /
|
|
21530
|
+
text, no markers/refs.
|
|
21531
|
+
--current With --markdown, render the raw concatenation: additive
|
|
21532
|
+
wrappers as {++text++}[^tcN] and subtractive as
|
|
21533
|
+
{--text--}[^tcN] (CriticMarkup); the [^tcN] footnote
|
|
21534
|
+
spells out the kind (insertion / deletion / moveTo /
|
|
21535
|
+
moveFrom). Mutually exclusive with --accepted/--baseline.
|
|
19997
21536
|
--comments With --markdown, append [^cN] after each commented span
|
|
19998
21537
|
and emit a footnote definition for each comment at the
|
|
19999
21538
|
end of the output (author, date, body).
|
|
@@ -20014,11 +21553,33 @@ var init_read2 = __esm(() => {
|
|
|
20014
21553
|
});
|
|
20015
21554
|
|
|
20016
21555
|
// src/cli/replace/replace-span.tsx
|
|
20017
|
-
function
|
|
21556
|
+
function isWrapperVisibleInView2(tag, view) {
|
|
21557
|
+
if (!isRunBearingWrapper(tag))
|
|
21558
|
+
return false;
|
|
21559
|
+
if (view === "current")
|
|
21560
|
+
return true;
|
|
21561
|
+
if (view === "accepted")
|
|
21562
|
+
return tag !== "w:del" && tag !== "w:moveFrom";
|
|
21563
|
+
return tag !== "w:ins" && tag !== "w:moveTo";
|
|
21564
|
+
}
|
|
21565
|
+
function sumVisibleTextLength2(children, view) {
|
|
21566
|
+
let total = 0;
|
|
21567
|
+
for (const child of children) {
|
|
21568
|
+
if (child.tag === "w:r") {
|
|
21569
|
+
total += runTextLength(child);
|
|
21570
|
+
continue;
|
|
21571
|
+
}
|
|
21572
|
+
if (isWrapperVisibleInView2(child.tag, view)) {
|
|
21573
|
+
total += sumVisibleTextLength2(child.children, view);
|
|
21574
|
+
}
|
|
21575
|
+
}
|
|
21576
|
+
return total;
|
|
21577
|
+
}
|
|
21578
|
+
function replaceSpanInParagraph(paragraph, span, replacement, tracked, view = "accepted") {
|
|
20018
21579
|
if (span.start > span.end) {
|
|
20019
21580
|
throw new Error(`replaceSpanInParagraph: invalid span ${span.start}-${span.end}`);
|
|
20020
21581
|
}
|
|
20021
|
-
const slots = collectRunSlots(paragraph);
|
|
21582
|
+
const slots = collectRunSlots(paragraph, view);
|
|
20022
21583
|
const overlapping = slots.filter((slot) => slot.offsetBefore + slot.length > span.start && slot.offsetBefore < span.end);
|
|
20023
21584
|
const firstSlot = overlapping[0];
|
|
20024
21585
|
if (!firstSlot) {
|
|
@@ -20033,12 +21594,12 @@ function replaceSpanInParagraph(paragraph, span, replacement, tracked) {
|
|
|
20033
21594
|
const allSameParent = overlapping.every((slot) => slot.parent === firstParent);
|
|
20034
21595
|
if (allSameParent) {
|
|
20035
21596
|
const containerStart = firstParent === paragraph ? 0 : firstSlot.offsetBefore;
|
|
20036
|
-
rebuildContainer(firstParent, containerStart, span, replacement, inheritedProperties, firstParent === paragraph, tracked ?? null);
|
|
21597
|
+
rebuildContainer(firstParent, containerStart, span, replacement, inheritedProperties, firstParent === paragraph, tracked ?? null, view);
|
|
20037
21598
|
return;
|
|
20038
21599
|
}
|
|
20039
|
-
rebuildAcrossBoundaries(paragraph, span, replacement, inheritedProperties, tracked ?? null);
|
|
21600
|
+
rebuildAcrossBoundaries(paragraph, span, replacement, inheritedProperties, tracked ?? null, view);
|
|
20040
21601
|
}
|
|
20041
|
-
function collectRunSlots(paragraph) {
|
|
21602
|
+
function collectRunSlots(paragraph, view) {
|
|
20042
21603
|
const slots = [];
|
|
20043
21604
|
let offset = 0;
|
|
20044
21605
|
function walk(parent, children) {
|
|
@@ -20054,7 +21615,7 @@ function collectRunSlots(paragraph) {
|
|
|
20054
21615
|
offset += length;
|
|
20055
21616
|
continue;
|
|
20056
21617
|
}
|
|
20057
|
-
if (
|
|
21618
|
+
if (isWrapperVisibleInView2(child.tag, view)) {
|
|
20058
21619
|
walk(child, child.children);
|
|
20059
21620
|
}
|
|
20060
21621
|
}
|
|
@@ -20062,7 +21623,7 @@ function collectRunSlots(paragraph) {
|
|
|
20062
21623
|
walk(paragraph, paragraph.children);
|
|
20063
21624
|
return slots;
|
|
20064
21625
|
}
|
|
20065
|
-
function rebuildContainer(container, baseOffset, span, replacement, runProperties, isParagraph, tracked) {
|
|
21626
|
+
function rebuildContainer(container, baseOffset, span, replacement, runProperties, isParagraph, tracked, view) {
|
|
20066
21627
|
const newChildren = [];
|
|
20067
21628
|
let offset = baseOffset;
|
|
20068
21629
|
let placed = false;
|
|
@@ -20113,8 +21674,8 @@ function rebuildContainer(container, baseOffset, span, replacement, runPropertie
|
|
|
20113
21674
|
}
|
|
20114
21675
|
continue;
|
|
20115
21676
|
}
|
|
20116
|
-
if (isParagraph &&
|
|
20117
|
-
offset +=
|
|
21677
|
+
if (isParagraph && isWrapperVisibleInView2(child.tag, view)) {
|
|
21678
|
+
offset += sumVisibleTextLength2(child.children, view);
|
|
20118
21679
|
newChildren.push(child);
|
|
20119
21680
|
continue;
|
|
20120
21681
|
}
|
|
@@ -20124,7 +21685,7 @@ function rebuildContainer(container, baseOffset, span, replacement, runPropertie
|
|
|
20124
21685
|
placeReplacement();
|
|
20125
21686
|
container.children = newChildren;
|
|
20126
21687
|
}
|
|
20127
|
-
function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tracked) {
|
|
21688
|
+
function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tracked, view) {
|
|
20128
21689
|
const newChildren = [];
|
|
20129
21690
|
let offset = 0;
|
|
20130
21691
|
let placed = false;
|
|
@@ -20175,8 +21736,12 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
|
|
|
20175
21736
|
}
|
|
20176
21737
|
continue;
|
|
20177
21738
|
}
|
|
21739
|
+
if ((child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:moveFrom" || child.tag === "w:moveTo") && !isWrapperVisibleInView2(child.tag, view)) {
|
|
21740
|
+
newChildren.push(child);
|
|
21741
|
+
continue;
|
|
21742
|
+
}
|
|
20178
21743
|
if (child.tag === "w:ins" || child.tag === "w:del" || child.tag === "w:moveFrom" || child.tag === "w:moveTo") {
|
|
20179
|
-
const innerLength =
|
|
21744
|
+
const innerLength = sumVisibleTextLength2(child.children, view);
|
|
20180
21745
|
const wrapperStart = offset;
|
|
20181
21746
|
const wrapperEnd = offset + innerLength;
|
|
20182
21747
|
offset = wrapperEnd;
|
|
@@ -20193,7 +21758,7 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
|
|
|
20193
21758
|
continue;
|
|
20194
21759
|
}
|
|
20195
21760
|
if (child.tag === "w:hyperlink") {
|
|
20196
|
-
const innerLength =
|
|
21761
|
+
const innerLength = sumVisibleTextLength2(child.children, view);
|
|
20197
21762
|
const wrapperStart = offset;
|
|
20198
21763
|
const wrapperEnd = offset + innerLength;
|
|
20199
21764
|
offset = wrapperEnd;
|
|
@@ -20206,13 +21771,13 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
|
|
|
20206
21771
|
newChildren.push(child);
|
|
20207
21772
|
continue;
|
|
20208
21773
|
}
|
|
20209
|
-
splitHyperlinkAcrossSpan(child, wrapperStart, span, runProperties, replacement, tracked, newChildren, placeReplacement, () => {
|
|
21774
|
+
splitHyperlinkAcrossSpan(child, wrapperStart, span, runProperties, replacement, tracked, view, newChildren, placeReplacement, () => {
|
|
20210
21775
|
placed = true;
|
|
20211
21776
|
});
|
|
20212
21777
|
continue;
|
|
20213
21778
|
}
|
|
20214
21779
|
if (isRunBearingWrapper(child.tag)) {
|
|
20215
|
-
const innerLength =
|
|
21780
|
+
const innerLength = sumVisibleTextLength2(child.children, view);
|
|
20216
21781
|
const wrapperStart = offset;
|
|
20217
21782
|
const wrapperEnd = offset + innerLength;
|
|
20218
21783
|
offset = wrapperEnd;
|
|
@@ -20328,8 +21893,8 @@ function splitTransparentWrapperAcrossSpan(wrapper, wrapperStart, span, out, pla
|
|
|
20328
21893
|
out.push(postWrapper);
|
|
20329
21894
|
}
|
|
20330
21895
|
}
|
|
20331
|
-
function splitHyperlinkAcrossSpan(wrapper, wrapperStart, span, runProperties, replacement, tracked, out, placeReplacement, markReplacementPlaced) {
|
|
20332
|
-
const wrapperEnd = wrapperStart +
|
|
21896
|
+
function splitHyperlinkAcrossSpan(wrapper, wrapperStart, span, runProperties, replacement, tracked, view, out, placeReplacement, markReplacementPlaced) {
|
|
21897
|
+
const wrapperEnd = wrapperStart + sumVisibleTextLength2(wrapper.children, view);
|
|
20333
21898
|
const startsInside = span.start > wrapperStart && span.start < wrapperEnd;
|
|
20334
21899
|
const preInner = [];
|
|
20335
21900
|
const postInner = [];
|
|
@@ -20431,8 +21996,12 @@ async function run26(args) {
|
|
|
20431
21996
|
all: { type: "boolean" },
|
|
20432
21997
|
limit: { type: "string" },
|
|
20433
21998
|
author: { type: "string" },
|
|
21999
|
+
current: { type: "boolean" },
|
|
22000
|
+
baseline: { type: "boolean" },
|
|
22001
|
+
exact: { type: "boolean" },
|
|
20434
22002
|
output: { type: "string", short: "o" },
|
|
20435
22003
|
"dry-run": { type: "boolean" },
|
|
22004
|
+
verbose: { type: "boolean", short: "v" },
|
|
20436
22005
|
help: { type: "boolean", short: "h" }
|
|
20437
22006
|
}
|
|
20438
22007
|
});
|
|
@@ -20444,6 +22013,7 @@ async function run26(args) {
|
|
|
20444
22013
|
await writeStdout(HELP26);
|
|
20445
22014
|
return EXIT.OK;
|
|
20446
22015
|
}
|
|
22016
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
20447
22017
|
const path = parsed.positionals[0];
|
|
20448
22018
|
const pattern = parsed.positionals[1];
|
|
20449
22019
|
const replacement = parsed.positionals[2];
|
|
@@ -20457,6 +22027,13 @@ async function run26(args) {
|
|
|
20457
22027
|
const ignoreCase = Boolean(parsed.values["ignore-case"]);
|
|
20458
22028
|
const useRegex = Boolean(parsed.values.regex);
|
|
20459
22029
|
const wantAll = Boolean(parsed.values.all);
|
|
22030
|
+
const wantCurrent = Boolean(parsed.values.current);
|
|
22031
|
+
const wantBaseline = Boolean(parsed.values.baseline);
|
|
22032
|
+
const exact = Boolean(parsed.values.exact);
|
|
22033
|
+
if (wantCurrent && wantBaseline) {
|
|
22034
|
+
return fail("USAGE", "--current and --baseline are mutually exclusive");
|
|
22035
|
+
}
|
|
22036
|
+
const findView = wantCurrent ? "current" : wantBaseline ? "baseline" : "accepted";
|
|
20460
22037
|
const limitRaw = parsed.values.limit;
|
|
20461
22038
|
const limit = limitRaw === undefined ? undefined : Number(limitRaw);
|
|
20462
22039
|
if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
|
|
@@ -20465,16 +22042,23 @@ async function run26(args) {
|
|
|
20465
22042
|
const view = await openOrFail(path);
|
|
20466
22043
|
if (typeof view === "number")
|
|
20467
22044
|
return view;
|
|
20468
|
-
let
|
|
22045
|
+
let findResult;
|
|
20469
22046
|
try {
|
|
20470
|
-
|
|
22047
|
+
findResult = findTextSpans(view.doc, pattern, {
|
|
20471
22048
|
regex: useRegex,
|
|
20472
|
-
ignoreCase
|
|
22049
|
+
ignoreCase,
|
|
22050
|
+
view: findView,
|
|
22051
|
+
exact
|
|
20473
22052
|
});
|
|
20474
22053
|
} catch (matcherError) {
|
|
20475
22054
|
const message = matcherError instanceof Error ? matcherError.message : String(matcherError);
|
|
20476
22055
|
return fail("USAGE", `Invalid pattern: ${message}`);
|
|
20477
22056
|
}
|
|
22057
|
+
const allMatches = findResult.matches;
|
|
22058
|
+
const normalizationFields = findResult.normalizedQuery !== undefined ? {
|
|
22059
|
+
normalizedPattern: findResult.normalizedQuery,
|
|
22060
|
+
normalizationApplied: findResult.normalizationApplied
|
|
22061
|
+
} : {};
|
|
20478
22062
|
let selected;
|
|
20479
22063
|
if (limit !== undefined) {
|
|
20480
22064
|
selected = allMatches.slice(0, limit);
|
|
@@ -20501,15 +22085,17 @@ async function run26(args) {
|
|
|
20501
22085
|
replacement,
|
|
20502
22086
|
regex: useRegex,
|
|
20503
22087
|
ignoreCase,
|
|
22088
|
+
view: findView,
|
|
20504
22089
|
totalMatches: allMatches.length,
|
|
20505
22090
|
replaced: selected.length,
|
|
20506
22091
|
matches: matchesPayload,
|
|
22092
|
+
...normalizationFields,
|
|
20507
22093
|
...outputPath ? { output: outputPath } : {}
|
|
20508
22094
|
});
|
|
20509
22095
|
return EXIT.OK;
|
|
20510
22096
|
}
|
|
20511
22097
|
if (selected.length === 0) {
|
|
20512
|
-
await
|
|
22098
|
+
await respondAck({
|
|
20513
22099
|
ok: true,
|
|
20514
22100
|
operation: "replace",
|
|
20515
22101
|
path,
|
|
@@ -20517,9 +22103,11 @@ async function run26(args) {
|
|
|
20517
22103
|
replacement,
|
|
20518
22104
|
regex: useRegex,
|
|
20519
22105
|
ignoreCase,
|
|
22106
|
+
view: findView,
|
|
20520
22107
|
totalMatches: 0,
|
|
20521
22108
|
replaced: 0,
|
|
20522
|
-
matches: []
|
|
22109
|
+
matches: [],
|
|
22110
|
+
...normalizationFields
|
|
20523
22111
|
});
|
|
20524
22112
|
return EXIT.OK;
|
|
20525
22113
|
}
|
|
@@ -20538,10 +22126,10 @@ async function run26(args) {
|
|
|
20538
22126
|
for (const match of reversed) {
|
|
20539
22127
|
const concreteReplacement = useRegex ? match.text.replace(new RegExp(pattern, regexFlags), replacement) : replacement;
|
|
20540
22128
|
const blockRef = resolveBlock(view, match.blockId);
|
|
20541
|
-
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end }, concreteReplacement, tracked);
|
|
22129
|
+
replaceSpanInParagraph(blockRef.node, { start: match.start, end: match.end }, concreteReplacement, tracked, findView);
|
|
20542
22130
|
}
|
|
20543
22131
|
await saveDocView(view, outputPath);
|
|
20544
|
-
await
|
|
22132
|
+
await respondAck({
|
|
20545
22133
|
ok: true,
|
|
20546
22134
|
operation: "replace",
|
|
20547
22135
|
path: outputPath ?? path,
|
|
@@ -20549,9 +22137,11 @@ async function run26(args) {
|
|
|
20549
22137
|
replacement,
|
|
20550
22138
|
regex: useRegex,
|
|
20551
22139
|
ignoreCase,
|
|
22140
|
+
view: findView,
|
|
20552
22141
|
totalMatches: allMatches.length,
|
|
20553
22142
|
replaced: selected.length,
|
|
20554
|
-
matches: matchesPayload
|
|
22143
|
+
matches: matchesPayload,
|
|
22144
|
+
...normalizationFields
|
|
20555
22145
|
});
|
|
20556
22146
|
return EXIT.OK;
|
|
20557
22147
|
}
|
|
@@ -20566,8 +22156,15 @@ Options:
|
|
|
20566
22156
|
--all replace every match (default: just the first)
|
|
20567
22157
|
--limit N replace at most N matches (in document order)
|
|
20568
22158
|
--author NAME author for tracked changes (default: $DOCX_AUTHOR)
|
|
22159
|
+
--current operate on the raw concatenation (both ins and del text)
|
|
22160
|
+
--baseline operate on the pre-change text (skip ins/moveTo)
|
|
22161
|
+
default: accepted view (skip del/moveFrom) \u2014 matches
|
|
22162
|
+
"docx find" / "docx read --markdown"
|
|
22163
|
+
--exact disable pattern normalization (no markdown-emphasis stripping,
|
|
22164
|
+
no smart/straight quote or em/en-dash equivalence)
|
|
20569
22165
|
-o, --output PATH write to PATH instead of overwriting FILE
|
|
20570
22166
|
--dry-run report what would change without writing the file
|
|
22167
|
+
-v, --verbose print the success ack JSON (default: silent on success)
|
|
20571
22168
|
-h, --help show this help
|
|
20572
22169
|
|
|
20573
22170
|
Within-paragraph matches only. Run formatting (rPr) on the surrounding text
|
|
@@ -20576,6 +22173,12 @@ overlaps the matched span. When a single invocation produces multiple
|
|
|
20576
22173
|
replacements in the same paragraph, they're applied in reverse offset order
|
|
20577
22174
|
so earlier offsets don't shift before being applied.
|
|
20578
22175
|
|
|
22176
|
+
By default the PATTERN is normalized: balanced markdown emphasis around
|
|
22177
|
+
non-whitespace (**X**, __X__, *X*, \`X\`) is stripped; smart quotes match
|
|
22178
|
+
straight quotes; em-dash and en-dash match the hyphen. The REPLACEMENT
|
|
22179
|
+
is always literal \u2014 whatever bytes you pass go in as-is. Pass --exact
|
|
22180
|
+
to match the raw pattern verbatim. --regex is always verbatim.
|
|
22181
|
+
|
|
20579
22182
|
With --regex, REPLACEMENT supports JS String.replace substitution syntax:
|
|
20580
22183
|
$1, $2, ... numbered capture groups
|
|
20581
22184
|
$& the matched substring
|
|
@@ -20650,7 +22253,7 @@ async function run27(args) {
|
|
|
20650
22253
|
const kind = trackedChangeKindForTag(reference.node.tag);
|
|
20651
22254
|
if (!kind)
|
|
20652
22255
|
continue;
|
|
20653
|
-
|
|
22256
|
+
const record = {
|
|
20654
22257
|
id,
|
|
20655
22258
|
kind,
|
|
20656
22259
|
author: reference.node.getAttribute("w:author") ?? "",
|
|
@@ -20658,7 +22261,14 @@ async function run27(args) {
|
|
|
20658
22261
|
revisionId: reference.node.getAttribute("w:id") ?? "",
|
|
20659
22262
|
blockId: reference.blockId,
|
|
20660
22263
|
text: ""
|
|
20661
|
-
}
|
|
22264
|
+
};
|
|
22265
|
+
if (kind === "sectPrChange") {
|
|
22266
|
+
const liveSiblings = reference.parent.filter((child) => child !== reference.node);
|
|
22267
|
+
record.current = readSectionProperties(liveSiblings);
|
|
22268
|
+
const snapshot = reference.node.findChild("w:sectPr");
|
|
22269
|
+
record.prior = snapshot ? readSectionProperties(snapshot.children) : {};
|
|
22270
|
+
}
|
|
22271
|
+
byId.set(id, record);
|
|
20662
22272
|
}
|
|
20663
22273
|
const sorted = [...byId.values()].sort((a2, b) => trackedChangeIndex(a2.id) - trackedChangeIndex(b.id));
|
|
20664
22274
|
await respond(sorted);
|
|
@@ -20677,6 +22287,8 @@ function trackedChangeKindForTag(tag) {
|
|
|
20677
22287
|
return "moveFrom";
|
|
20678
22288
|
if (tag === "w:moveTo")
|
|
20679
22289
|
return "moveTo";
|
|
22290
|
+
if (tag === "w:sectPrChange")
|
|
22291
|
+
return "sectPrChange";
|
|
20680
22292
|
return null;
|
|
20681
22293
|
}
|
|
20682
22294
|
var HELP27 = `docx track-changes list \u2014 inventory every revision wrapper
|
|
@@ -20687,18 +22299,28 @@ Usage:
|
|
|
20687
22299
|
Options:
|
|
20688
22300
|
-h, --help Show this help
|
|
20689
22301
|
|
|
20690
|
-
Lists every <w:ins>, <w:del>,
|
|
20691
|
-
|
|
20692
|
-
|
|
22302
|
+
Lists every revision wrapper with stable tcN ids \u2014 run-level <w:ins>, <w:del>,
|
|
22303
|
+
<w:moveFrom>, <w:moveTo>; section-property revisions <w:sectPrChange>; and
|
|
22304
|
+
paragraph-mark <w:ins>/<w:del> markers (a self-closing element inside
|
|
22305
|
+
<w:pPr><w:rPr> that tracks a paragraph break itself). moveFrom/moveTo halves
|
|
22306
|
+
of the same logical move appear as separate entries; their kind tells them
|
|
22307
|
+
apart.
|
|
20693
22308
|
|
|
20694
22309
|
Output: JSON array of { id, kind, author, date, revisionId, blockId, text }
|
|
20695
22310
|
sorted by id (document order). kind is one of: "ins", "del", "moveFrom",
|
|
20696
|
-
"moveTo".
|
|
22311
|
+
"moveTo", "sectPrChange". Paragraph-mark entries have kind "ins"/"del" with
|
|
22312
|
+
text "" \u2014 their blockId is the owning paragraph's pN.
|
|
22313
|
+
|
|
22314
|
+
Section-property revisions (kind="sectPrChange") additionally include
|
|
22315
|
+
{ prior, current } objects with the section's columns/sectionType from
|
|
22316
|
+
before and after the tracked edit, so agents can see the diff without
|
|
22317
|
+
re-reading XML.
|
|
20697
22318
|
|
|
20698
22319
|
Examples:
|
|
20699
22320
|
docx track-changes list doc.docx
|
|
20700
22321
|
docx track-changes list doc.docx | jq '.[] | select(.kind == "del")'
|
|
20701
22322
|
docx track-changes list doc.docx | jq '.[] | select(.kind | test("move"))'
|
|
22323
|
+
docx track-changes list doc.docx | jq '.[] | select(.kind == "sectPrChange") | .prior, .current'
|
|
20702
22324
|
`;
|
|
20703
22325
|
var init_list4 = __esm(() => {
|
|
20704
22326
|
init_core();
|
|
@@ -20714,10 +22336,11 @@ async function runApply(args, verb, help) {
|
|
|
20714
22336
|
args,
|
|
20715
22337
|
allowPositionals: true,
|
|
20716
22338
|
options: {
|
|
20717
|
-
at: { type: "string" },
|
|
22339
|
+
at: { type: "string", multiple: true },
|
|
20718
22340
|
all: { type: "boolean" },
|
|
20719
22341
|
output: { type: "string", short: "o" },
|
|
20720
22342
|
"dry-run": { type: "boolean" },
|
|
22343
|
+
verbose: { type: "boolean", short: "v" },
|
|
20721
22344
|
help: { type: "boolean", short: "h" }
|
|
20722
22345
|
}
|
|
20723
22346
|
});
|
|
@@ -20729,16 +22352,17 @@ async function runApply(args, verb, help) {
|
|
|
20729
22352
|
await writeStdout(help);
|
|
20730
22353
|
return EXIT.OK;
|
|
20731
22354
|
}
|
|
22355
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
20732
22356
|
const path = parsed.positionals[0];
|
|
20733
22357
|
if (!path)
|
|
20734
22358
|
return fail("USAGE", "Missing FILE argument", help);
|
|
20735
|
-
const
|
|
22359
|
+
const atRaw = parsed.values.at;
|
|
20736
22360
|
const all = Boolean(parsed.values.all);
|
|
20737
|
-
if (
|
|
22361
|
+
if (atRaw && atRaw.length > 0 && all) {
|
|
20738
22362
|
return fail("USAGE", "--at and --all are mutually exclusive", help);
|
|
20739
22363
|
}
|
|
20740
|
-
if (!
|
|
20741
|
-
return fail("USAGE", "Specify --at tcN or --all", help);
|
|
22364
|
+
if ((!atRaw || atRaw.length === 0) && !all) {
|
|
22365
|
+
return fail("USAGE", "Specify --at tcN (repeatable) or --all", help);
|
|
20742
22366
|
}
|
|
20743
22367
|
const view = await openOrFail(path);
|
|
20744
22368
|
if (typeof view === "number")
|
|
@@ -20748,16 +22372,33 @@ async function runApply(args, verb, help) {
|
|
|
20748
22372
|
if (all) {
|
|
20749
22373
|
targets = allChanges;
|
|
20750
22374
|
} else {
|
|
20751
|
-
const
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
22375
|
+
const seen = new Set;
|
|
22376
|
+
const ordered = [];
|
|
22377
|
+
for (const id of atRaw ?? []) {
|
|
22378
|
+
if (seen.has(id))
|
|
22379
|
+
continue;
|
|
22380
|
+
seen.add(id);
|
|
22381
|
+
ordered.push(id);
|
|
22382
|
+
}
|
|
22383
|
+
const byId = new Map(allChanges.map((change) => [change.id, change]));
|
|
22384
|
+
targets = [];
|
|
22385
|
+
for (const id of ordered) {
|
|
22386
|
+
const found = byId.get(id);
|
|
22387
|
+
if (!found) {
|
|
22388
|
+
return fail("TRACKED_CHANGE_NOT_FOUND", `Tracked change not found: ${id}`);
|
|
22389
|
+
}
|
|
22390
|
+
targets.push(found);
|
|
22391
|
+
}
|
|
22392
|
+
targets.sort((left, right) => {
|
|
22393
|
+
const leftIndex = allChanges.indexOf(left);
|
|
22394
|
+
const rightIndex = allChanges.indexOf(right);
|
|
22395
|
+
return leftIndex - rightIndex;
|
|
22396
|
+
});
|
|
20756
22397
|
}
|
|
20757
22398
|
const records = targets.map((target) => ({
|
|
20758
22399
|
id: target.id,
|
|
20759
22400
|
kind: target.kind,
|
|
20760
|
-
action: actionFor(target
|
|
22401
|
+
action: actionFor(target, verb),
|
|
20761
22402
|
author: target.author,
|
|
20762
22403
|
date: target.date
|
|
20763
22404
|
}));
|
|
@@ -20775,12 +22416,12 @@ async function runApply(args, verb, help) {
|
|
|
20775
22416
|
}
|
|
20776
22417
|
for (const target of [...targets].reverse()) {
|
|
20777
22418
|
if (verb === "accept")
|
|
20778
|
-
applyAccept(target
|
|
22419
|
+
applyAccept(target);
|
|
20779
22420
|
else
|
|
20780
|
-
applyReject(target
|
|
22421
|
+
applyReject(target);
|
|
20781
22422
|
}
|
|
20782
22423
|
await saveDocView(view, outputPath);
|
|
20783
|
-
await
|
|
22424
|
+
await respondAck({
|
|
20784
22425
|
ok: true,
|
|
20785
22426
|
operation: `track-changes.${verb}`,
|
|
20786
22427
|
path: outputPath ?? path,
|
|
@@ -20790,26 +22431,95 @@ async function runApply(args, verb, help) {
|
|
|
20790
22431
|
}
|
|
20791
22432
|
function collectTrackedChanges(tree) {
|
|
20792
22433
|
const out = [];
|
|
20793
|
-
|
|
20794
|
-
|
|
20795
|
-
|
|
20796
|
-
|
|
20797
|
-
|
|
20798
|
-
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
22434
|
+
const counter = { value: 0 };
|
|
22435
|
+
const documentNode = XmlNode2.findRoot(tree, "w:document");
|
|
22436
|
+
if (!documentNode)
|
|
22437
|
+
return out;
|
|
22438
|
+
const body = documentNode.findChild("w:body");
|
|
22439
|
+
if (!body)
|
|
22440
|
+
return out;
|
|
22441
|
+
visitBlocks(body.children, out, counter);
|
|
22442
|
+
return out;
|
|
22443
|
+
}
|
|
22444
|
+
function visitBlocks(blocks, out, counter) {
|
|
22445
|
+
for (const block of blocks) {
|
|
22446
|
+
if (block.tag === "w:p") {
|
|
22447
|
+
visitParagraph(block, blocks, out, counter);
|
|
22448
|
+
continue;
|
|
22449
|
+
}
|
|
22450
|
+
if (block.tag === "w:tbl") {
|
|
22451
|
+
for (const row of block.findChildren("w:tr")) {
|
|
22452
|
+
for (const cell of row.findChildren("w:tc")) {
|
|
22453
|
+
visitBlocks(cell.children, out, counter);
|
|
22454
|
+
}
|
|
20806
22455
|
}
|
|
20807
|
-
|
|
20808
|
-
|
|
22456
|
+
continue;
|
|
22457
|
+
}
|
|
22458
|
+
if (block.tag === "w:sectPr") {
|
|
22459
|
+
visitSectPrChange(block, out, counter);
|
|
20809
22460
|
}
|
|
20810
22461
|
}
|
|
20811
|
-
|
|
20812
|
-
|
|
22462
|
+
}
|
|
22463
|
+
function visitParagraph(paragraph, paragraphParent, out, counter) {
|
|
22464
|
+
visitRunContainer(paragraph, out, counter);
|
|
22465
|
+
const pPr = paragraph.findChild("w:pPr");
|
|
22466
|
+
if (!pPr)
|
|
22467
|
+
return;
|
|
22468
|
+
const sectPr = pPr.findChild("w:sectPr");
|
|
22469
|
+
if (sectPr)
|
|
22470
|
+
visitSectPrChange(sectPr, out, counter);
|
|
22471
|
+
const rPr = pPr.findChild("w:rPr");
|
|
22472
|
+
if (!rPr)
|
|
22473
|
+
return;
|
|
22474
|
+
for (const child of rPr.children) {
|
|
22475
|
+
const kind = trackedChangeKindForTag2(child.tag);
|
|
22476
|
+
if (kind !== "ins" && kind !== "del")
|
|
22477
|
+
continue;
|
|
22478
|
+
out.push({
|
|
22479
|
+
node: child,
|
|
22480
|
+
parent: rPr.children,
|
|
22481
|
+
kind,
|
|
22482
|
+
id: `tc${counter.value++}`,
|
|
22483
|
+
author: child.getAttribute("w:author") ?? "",
|
|
22484
|
+
date: child.getAttribute("w:date") ?? "",
|
|
22485
|
+
paragraph,
|
|
22486
|
+
paragraphParent
|
|
22487
|
+
});
|
|
22488
|
+
}
|
|
22489
|
+
}
|
|
22490
|
+
function visitRunContainer(container, out, counter) {
|
|
22491
|
+
for (const child of container.children) {
|
|
22492
|
+
if (child.tag === "w:pPr")
|
|
22493
|
+
continue;
|
|
22494
|
+
const kind = trackedChangeKindForTag2(child.tag);
|
|
22495
|
+
if (kind === "ins" || kind === "del" || kind === "moveFrom" || kind === "moveTo") {
|
|
22496
|
+
out.push({
|
|
22497
|
+
node: child,
|
|
22498
|
+
parent: container.children,
|
|
22499
|
+
kind,
|
|
22500
|
+
id: `tc${counter.value++}`,
|
|
22501
|
+
author: child.getAttribute("w:author") ?? "",
|
|
22502
|
+
date: child.getAttribute("w:date") ?? ""
|
|
22503
|
+
});
|
|
22504
|
+
visitRunContainer(child, out, counter);
|
|
22505
|
+
continue;
|
|
22506
|
+
}
|
|
22507
|
+
if (child.children.length > 0)
|
|
22508
|
+
visitRunContainer(child, out, counter);
|
|
22509
|
+
}
|
|
22510
|
+
}
|
|
22511
|
+
function visitSectPrChange(sectPr, out, counter) {
|
|
22512
|
+
const change = sectPr.findChild("w:sectPrChange");
|
|
22513
|
+
if (!change)
|
|
22514
|
+
return;
|
|
22515
|
+
out.push({
|
|
22516
|
+
node: change,
|
|
22517
|
+
parent: sectPr.children,
|
|
22518
|
+
kind: "sectPrChange",
|
|
22519
|
+
id: `tc${counter.value++}`,
|
|
22520
|
+
author: change.getAttribute("w:author") ?? "",
|
|
22521
|
+
date: change.getAttribute("w:date") ?? ""
|
|
22522
|
+
});
|
|
20813
22523
|
}
|
|
20814
22524
|
function trackedChangeKindForTag2(tag) {
|
|
20815
22525
|
if (tag === "w:ins")
|
|
@@ -20820,28 +22530,96 @@ function trackedChangeKindForTag2(tag) {
|
|
|
20820
22530
|
return "moveFrom";
|
|
20821
22531
|
if (tag === "w:moveTo")
|
|
20822
22532
|
return "moveTo";
|
|
22533
|
+
if (tag === "w:sectPrChange")
|
|
22534
|
+
return "sectPrChange";
|
|
20823
22535
|
return null;
|
|
20824
22536
|
}
|
|
20825
|
-
function actionFor(
|
|
20826
|
-
|
|
22537
|
+
function actionFor(target, verb) {
|
|
22538
|
+
if (target.paragraph) {
|
|
22539
|
+
if (target.kind === "ins") {
|
|
22540
|
+
return verb === "accept" ? "delete" : "deleteParagraph";
|
|
22541
|
+
}
|
|
22542
|
+
if (target.kind === "del") {
|
|
22543
|
+
return verb === "accept" ? "merge" : "delete";
|
|
22544
|
+
}
|
|
22545
|
+
}
|
|
22546
|
+
if (target.kind === "sectPrChange") {
|
|
22547
|
+
return verb === "accept" ? "delete" : "restore";
|
|
22548
|
+
}
|
|
22549
|
+
const isAdditive = target.kind === "ins" || target.kind === "moveTo";
|
|
20827
22550
|
if (verb === "accept")
|
|
20828
22551
|
return isAdditive ? "unwrap" : "delete";
|
|
20829
22552
|
return isAdditive ? "delete" : "unwrap";
|
|
20830
22553
|
}
|
|
20831
|
-
function applyAccept(
|
|
20832
|
-
if (
|
|
20833
|
-
|
|
22554
|
+
function applyAccept(target) {
|
|
22555
|
+
if (target.paragraph && target.paragraphParent) {
|
|
22556
|
+
if (target.kind === "ins") {
|
|
22557
|
+
deleteNode(target.node, target.parent);
|
|
22558
|
+
return;
|
|
22559
|
+
}
|
|
22560
|
+
if (target.kind === "del") {
|
|
22561
|
+
deleteNode(target.node, target.parent);
|
|
22562
|
+
mergeParagraphWithNext(target.paragraph, target.paragraphParent);
|
|
22563
|
+
return;
|
|
22564
|
+
}
|
|
22565
|
+
}
|
|
22566
|
+
if (target.node.tag === "w:ins" || target.node.tag === "w:moveTo") {
|
|
22567
|
+
unwrapNode(target.node, target.parent);
|
|
22568
|
+
return;
|
|
22569
|
+
}
|
|
22570
|
+
if (target.node.tag === "w:sectPrChange") {
|
|
22571
|
+
deleteNode(target.node, target.parent);
|
|
22572
|
+
return;
|
|
22573
|
+
}
|
|
22574
|
+
deleteNode(target.node, target.parent);
|
|
22575
|
+
}
|
|
22576
|
+
function applyReject(target) {
|
|
22577
|
+
if (target.paragraph && target.paragraphParent) {
|
|
22578
|
+
if (target.kind === "ins") {
|
|
22579
|
+
const idx = target.paragraphParent.indexOf(target.paragraph);
|
|
22580
|
+
if (idx !== -1)
|
|
22581
|
+
target.paragraphParent.splice(idx, 1);
|
|
22582
|
+
return;
|
|
22583
|
+
}
|
|
22584
|
+
if (target.kind === "del") {
|
|
22585
|
+
deleteNode(target.node, target.parent);
|
|
22586
|
+
return;
|
|
22587
|
+
}
|
|
22588
|
+
}
|
|
22589
|
+
if (target.node.tag === "w:ins" || target.node.tag === "w:moveTo") {
|
|
22590
|
+
deleteNode(target.node, target.parent);
|
|
22591
|
+
return;
|
|
22592
|
+
}
|
|
22593
|
+
if (target.node.tag === "w:sectPrChange") {
|
|
22594
|
+
restoreSectPrSnapshot(target.node, target.parent);
|
|
20834
22595
|
return;
|
|
20835
22596
|
}
|
|
20836
|
-
|
|
22597
|
+
renameDelTextToText(target.node);
|
|
22598
|
+
unwrapNode(target.node, target.parent);
|
|
20837
22599
|
}
|
|
20838
|
-
function
|
|
20839
|
-
|
|
20840
|
-
|
|
22600
|
+
function mergeParagraphWithNext(paragraph, paragraphParent) {
|
|
22601
|
+
const idx = paragraphParent.indexOf(paragraph);
|
|
22602
|
+
if (idx === -1)
|
|
20841
22603
|
return;
|
|
22604
|
+
const next = paragraphParent[idx + 1];
|
|
22605
|
+
if (!next || next.tag !== "w:p")
|
|
22606
|
+
return;
|
|
22607
|
+
const toMove = [];
|
|
22608
|
+
for (const child of next.children) {
|
|
22609
|
+
if (child.tag === "w:pPr")
|
|
22610
|
+
continue;
|
|
22611
|
+
toMove.push(child);
|
|
22612
|
+
}
|
|
22613
|
+
paragraph.children.push(...toMove);
|
|
22614
|
+
paragraphParent.splice(idx + 1, 1);
|
|
22615
|
+
}
|
|
22616
|
+
function restoreSectPrSnapshot(node, parent) {
|
|
22617
|
+
const snapshot = node.findChild("w:sectPr");
|
|
22618
|
+
parent.length = 0;
|
|
22619
|
+
if (snapshot) {
|
|
22620
|
+
for (const child of snapshot.children)
|
|
22621
|
+
parent.push(child);
|
|
20842
22622
|
}
|
|
20843
|
-
renameDelTextToText(node);
|
|
20844
|
-
unwrapNode(node, parent);
|
|
20845
22623
|
}
|
|
20846
22624
|
function unwrapNode(node, parent) {
|
|
20847
22625
|
const index = parent.indexOf(node);
|
|
@@ -20863,6 +22641,7 @@ function renameDelTextToText(node) {
|
|
|
20863
22641
|
}
|
|
20864
22642
|
var init_apply = __esm(() => {
|
|
20865
22643
|
init_core();
|
|
22644
|
+
init_parser();
|
|
20866
22645
|
init_respond();
|
|
20867
22646
|
});
|
|
20868
22647
|
|
|
@@ -20885,19 +22664,33 @@ wrapper \u2014 the content becomes plain runs at this location. Accepting a
|
|
|
20885
22664
|
deletion (<w:del>) or move source (<w:moveFrom>) removes the element and its
|
|
20886
22665
|
<w:delText> entirely (the text disappears for real).
|
|
20887
22666
|
|
|
20888
|
-
|
|
20889
|
-
|
|
20890
|
-
|
|
22667
|
+
Section-property revisions (<w:sectPrChange>): accept drops the prior-state
|
|
22668
|
+
snapshot, leaving the live section properties in place.
|
|
22669
|
+
|
|
22670
|
+
Paragraph-mark trackings (<w:ins>/<w:del> inside <w:pPr><w:rPr>): accepting
|
|
22671
|
+
a paragraph-mark insertion just removes the marker (the inserted paragraph
|
|
22672
|
+
stays as a regular paragraph). Accepting a paragraph-mark deletion merges
|
|
22673
|
+
the owning paragraph with the next paragraph \u2014 the next paragraph's runs
|
|
22674
|
+
are appended to this one and the next paragraph is removed (per ECMA-376
|
|
22675
|
+
\xA717.13.5.4).
|
|
22676
|
+
|
|
22677
|
+
Out of scope: formatting changes (<w:rPrChange>/<w:pPrChange>). These aren't
|
|
22678
|
+
modeled in the AST today and --all silently skips them.
|
|
20891
22679
|
|
|
20892
22680
|
Options:
|
|
20893
|
-
--at tcN Accept a
|
|
22681
|
+
--at tcN Accept a tracked change by id. Repeat for multiple ids
|
|
22682
|
+
(--at tc1 --at tc2 --at tc3) \u2014 all targets are resolved
|
|
22683
|
+
against the pre-mutation tree, so renumbering during the
|
|
22684
|
+
batch is not a concern.
|
|
20894
22685
|
--all Accept every tracked change (mutually exclusive with --at)
|
|
20895
22686
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
20896
22687
|
--dry-run Print what would change; do not write the file
|
|
22688
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
20897
22689
|
-h, --help Show this help
|
|
20898
22690
|
|
|
20899
22691
|
Examples:
|
|
20900
22692
|
docx track-changes accept doc.docx --at tc0
|
|
22693
|
+
docx track-changes accept doc.docx --at tc1 --at tc3 --at tc5
|
|
20901
22694
|
docx track-changes accept doc.docx --all
|
|
20902
22695
|
docx track-changes accept doc.docx --all --dry-run
|
|
20903
22696
|
`;
|
|
@@ -20925,23 +22718,37 @@ Rejecting a deletion (<w:del>) or move source (<w:moveFrom>) unwraps the
|
|
|
20925
22718
|
wrapper and converts <w:delText> back to <w:t>, so the text reappears as
|
|
20926
22719
|
plain runs.
|
|
20927
22720
|
|
|
22721
|
+
Section-property revisions (<w:sectPrChange>): reject restores the prior-state
|
|
22722
|
+
snapshot \u2014 the live section's columns/type are replaced with the values that
|
|
22723
|
+
were in effect before the tracked edit.
|
|
22724
|
+
|
|
22725
|
+
Paragraph-mark trackings (<w:ins>/<w:del> inside <w:pPr><w:rPr>): rejecting
|
|
22726
|
+
a paragraph-mark insertion removes the entire owning paragraph (the inserted
|
|
22727
|
+
break disappears \u2014 for sentinels created by "insert --section" this also
|
|
22728
|
+
removes the section break the sentinel was carrying). Rejecting a
|
|
22729
|
+
paragraph-mark deletion just removes the marker (the paragraph stays).
|
|
22730
|
+
|
|
20928
22731
|
moveFrom and moveTo are processed independently. To fully undo a move, target
|
|
20929
22732
|
both halves (or use --all). The runtime treats them as paired only by their
|
|
20930
22733
|
shared revision id, not by atomic accept/reject.
|
|
20931
22734
|
|
|
20932
|
-
Out of scope:
|
|
20933
|
-
|
|
20934
|
-
the AST today and --all silently skips them.
|
|
22735
|
+
Out of scope: formatting changes (<w:rPrChange>/<w:pPrChange>). These aren't
|
|
22736
|
+
modeled in the AST today and --all silently skips them.
|
|
20935
22737
|
|
|
20936
22738
|
Options:
|
|
20937
|
-
--at tcN Reject a
|
|
22739
|
+
--at tcN Reject a tracked change by id. Repeat for multiple ids
|
|
22740
|
+
(--at tc1 --at tc2 --at tc3) \u2014 all targets are resolved
|
|
22741
|
+
against the pre-mutation tree, so renumbering during the
|
|
22742
|
+
batch is not a concern.
|
|
20938
22743
|
--all Reject every tracked change (mutually exclusive with --at)
|
|
20939
22744
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
20940
22745
|
--dry-run Print what would change; do not write the file
|
|
22746
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
20941
22747
|
-h, --help Show this help
|
|
20942
22748
|
|
|
20943
22749
|
Examples:
|
|
20944
22750
|
docx track-changes reject doc.docx --at tc0
|
|
22751
|
+
docx track-changes reject doc.docx --at tc1 --at tc3 --at tc5
|
|
20945
22752
|
docx track-changes reject doc.docx --all
|
|
20946
22753
|
docx track-changes reject doc.docx --all --dry-run
|
|
20947
22754
|
`;
|
|
@@ -20964,6 +22771,7 @@ async function run30(args) {
|
|
|
20964
22771
|
options: {
|
|
20965
22772
|
output: { type: "string", short: "o" },
|
|
20966
22773
|
"dry-run": { type: "boolean" },
|
|
22774
|
+
verbose: { type: "boolean", short: "v" },
|
|
20967
22775
|
help: { type: "boolean", short: "h" }
|
|
20968
22776
|
}
|
|
20969
22777
|
});
|
|
@@ -20975,6 +22783,7 @@ async function run30(args) {
|
|
|
20975
22783
|
await writeStdout(HELP30);
|
|
20976
22784
|
return EXIT.OK;
|
|
20977
22785
|
}
|
|
22786
|
+
setVerboseAck(Boolean(parsed.values.verbose));
|
|
20978
22787
|
const path = parsed.positionals[0];
|
|
20979
22788
|
if (!path)
|
|
20980
22789
|
return fail("USAGE", "Missing FILE argument", HELP30);
|
|
@@ -21023,7 +22832,7 @@ async function run30(args) {
|
|
|
21023
22832
|
});
|
|
21024
22833
|
}
|
|
21025
22834
|
await saveDocView(view, outputPath);
|
|
21026
|
-
await
|
|
22835
|
+
await respondAck({
|
|
21027
22836
|
ok: true,
|
|
21028
22837
|
operation: "track-changes",
|
|
21029
22838
|
path: outputPath ?? path,
|
|
@@ -21045,6 +22854,7 @@ insert/edit/delete/replace commands also emit <w:ins>/<w:del> markers
|
|
|
21045
22854
|
Options:
|
|
21046
22855
|
-o, --output PATH Write to PATH instead of overwriting FILE
|
|
21047
22856
|
--dry-run Print what would change; do not write the file
|
|
22857
|
+
-v, --verbose Print the success ack JSON (default: silent on success)
|
|
21048
22858
|
-h, --help Show this help
|
|
21049
22859
|
|
|
21050
22860
|
Examples:
|
|
@@ -21100,18 +22910,23 @@ Usage:
|
|
|
21100
22910
|
Verbs:
|
|
21101
22911
|
on Set <w:trackChanges/> in word/settings.xml
|
|
21102
22912
|
off Remove <w:trackChanges/>
|
|
21103
|
-
list Inventory every
|
|
21104
|
-
|
|
21105
|
-
|
|
21106
|
-
|
|
21107
|
-
|
|
21108
|
-
|
|
22913
|
+
list Inventory every revision wrapper (run-level ins/del/move,
|
|
22914
|
+
<w:sectPrChange>, paragraph-mark <w:ins>/<w:del>) with stable
|
|
22915
|
+
tcN ids
|
|
22916
|
+
accept Accept tracked changes \u2014 ins/moveTo unwrap; del/moveFrom are
|
|
22917
|
+
removed; sectPrChange drops its snapshot (live state stays);
|
|
22918
|
+
paragraph-mark del merges with the next paragraph
|
|
22919
|
+
reject Reject tracked changes \u2014 ins/moveTo are removed (and for a
|
|
22920
|
+
paragraph-mark ins the entire paragraph is removed); del/moveFrom
|
|
22921
|
+
unwrap (with <w:delText> \u2192 <w:t> rename); sectPrChange restores
|
|
22922
|
+
its snapshot
|
|
21109
22923
|
|
|
21110
22924
|
When tracking is on, this CLI's insert/edit/delete/replace commands
|
|
21111
|
-
emit <w:ins>/<w:del> markers (attributed via --author or $DOCX_AUTHOR)
|
|
21112
|
-
|
|
21113
|
-
don't emit them
|
|
21114
|
-
|
|
22925
|
+
emit <w:ins>/<w:del> markers (attributed via --author or $DOCX_AUTHOR);
|
|
22926
|
+
edit --at sN under tracking emits <w:sectPrChange>. moveFrom/moveTo are
|
|
22927
|
+
read, listed, and accept/reject independently \u2014 we don't emit them
|
|
22928
|
+
ourselves (Word does that interactively). Accept/reject themselves
|
|
22929
|
+
bypass tracking \u2014 they're doc surgery, not edits.
|
|
21115
22930
|
|
|
21116
22931
|
Run "docx track-changes <verb> --help" for verb-specific help.
|
|
21117
22932
|
`;
|
|
@@ -21121,16 +22936,48 @@ var init_track_changes2 = __esm(() => {
|
|
|
21121
22936
|
|
|
21122
22937
|
// src/cli/wc/count.ts
|
|
21123
22938
|
function textFor(options) {
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
22939
|
+
const view = options.view ?? "accepted";
|
|
22940
|
+
if (view === "current")
|
|
22941
|
+
return paragraphText;
|
|
22942
|
+
if (view === "baseline")
|
|
21127
22943
|
return paragraphTextBaseline;
|
|
21128
|
-
return
|
|
22944
|
+
return paragraphTextAccepted;
|
|
21129
22945
|
}
|
|
21130
22946
|
function countWords(text) {
|
|
21131
22947
|
const matches = text.match(/\S+/g);
|
|
21132
22948
|
return matches?.length ?? 0;
|
|
21133
22949
|
}
|
|
22950
|
+
function countWordsInSection(blocks, targetId, options = {}) {
|
|
22951
|
+
let bucket = [];
|
|
22952
|
+
for (const block of blocks) {
|
|
22953
|
+
if (block.type === "sectionBreak") {
|
|
22954
|
+
if (block.id === targetId) {
|
|
22955
|
+
return countWordsInBlocks(bucket, options);
|
|
22956
|
+
}
|
|
22957
|
+
bucket = [];
|
|
22958
|
+
continue;
|
|
22959
|
+
}
|
|
22960
|
+
bucket.push(block);
|
|
22961
|
+
}
|
|
22962
|
+
return null;
|
|
22963
|
+
}
|
|
22964
|
+
function countSectionsInBlocks(blocks) {
|
|
22965
|
+
let total = 0;
|
|
22966
|
+
for (const block of blocks) {
|
|
22967
|
+
if (block.type === "sectionBreak") {
|
|
22968
|
+
total += 1;
|
|
22969
|
+
continue;
|
|
22970
|
+
}
|
|
22971
|
+
if (block.type === "table") {
|
|
22972
|
+
for (const row of block.rows) {
|
|
22973
|
+
for (const cell of row.cells) {
|
|
22974
|
+
total += countSectionsInBlocks(cell.blocks);
|
|
22975
|
+
}
|
|
22976
|
+
}
|
|
22977
|
+
}
|
|
22978
|
+
}
|
|
22979
|
+
return total;
|
|
22980
|
+
}
|
|
21134
22981
|
function countWordsInBlocks(blocks, options = {}) {
|
|
21135
22982
|
const text = textFor(options);
|
|
21136
22983
|
let total = 0;
|
|
@@ -21201,6 +23048,7 @@ async function run32(args) {
|
|
|
21201
23048
|
options: {
|
|
21202
23049
|
accepted: { type: "boolean" },
|
|
21203
23050
|
baseline: { type: "boolean" },
|
|
23051
|
+
current: { type: "boolean" },
|
|
21204
23052
|
help: { type: "boolean", short: "h" }
|
|
21205
23053
|
}
|
|
21206
23054
|
});
|
|
@@ -21218,10 +23066,12 @@ async function run32(args) {
|
|
|
21218
23066
|
return fail("USAGE", "Missing FILE argument", HELP32);
|
|
21219
23067
|
const wantAccepted = Boolean(parsed.values.accepted);
|
|
21220
23068
|
const wantBaseline = Boolean(parsed.values.baseline);
|
|
21221
|
-
|
|
21222
|
-
|
|
23069
|
+
const wantCurrent = Boolean(parsed.values.current);
|
|
23070
|
+
const viewFlagCount = (wantAccepted ? 1 : 0) + (wantBaseline ? 1 : 0) + (wantCurrent ? 1 : 0);
|
|
23071
|
+
if (viewFlagCount > 1) {
|
|
23072
|
+
return fail("USAGE", "--accepted, --baseline, and --current are mutually exclusive", HELP32);
|
|
21223
23073
|
}
|
|
21224
|
-
const view =
|
|
23074
|
+
const view = wantCurrent ? "current" : wantBaseline ? "baseline" : "accepted";
|
|
21225
23075
|
const pickText = paragraphTextFor(view);
|
|
21226
23076
|
const docView = await openOrFail(path);
|
|
21227
23077
|
if (typeof docView === "number")
|
|
@@ -21233,7 +23083,8 @@ async function run32(args) {
|
|
|
21233
23083
|
path,
|
|
21234
23084
|
scope: "document",
|
|
21235
23085
|
view,
|
|
21236
|
-
words: countWordsInBlocks(docView.doc.blocks, { view })
|
|
23086
|
+
words: countWordsInBlocks(docView.doc.blocks, { view }),
|
|
23087
|
+
sections: countSectionsInBlocks(docView.doc.blocks)
|
|
21237
23088
|
});
|
|
21238
23089
|
return EXIT.OK;
|
|
21239
23090
|
}
|
|
@@ -21255,6 +23106,24 @@ async function run32(args) {
|
|
|
21255
23106
|
if (!block) {
|
|
21256
23107
|
return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
|
|
21257
23108
|
}
|
|
23109
|
+
if (block.type === "sectionBreak") {
|
|
23110
|
+
const sectionWords = countWordsInSection(blocks, locator.blockId, {
|
|
23111
|
+
view
|
|
23112
|
+
});
|
|
23113
|
+
if (sectionWords === null) {
|
|
23114
|
+
return fail("BLOCK_NOT_FOUND", `Section not found: ${locator.blockId}`);
|
|
23115
|
+
}
|
|
23116
|
+
await respond({
|
|
23117
|
+
ok: true,
|
|
23118
|
+
operation: "wc",
|
|
23119
|
+
path,
|
|
23120
|
+
locator: locatorInput,
|
|
23121
|
+
scope: "section",
|
|
23122
|
+
view,
|
|
23123
|
+
words: sectionWords
|
|
23124
|
+
});
|
|
23125
|
+
return EXIT.OK;
|
|
23126
|
+
}
|
|
21258
23127
|
const words = block.type === "paragraph" ? countWords(pickText(block)) : block.type === "table" ? countWordsInBlocks([block], { view }) : 0;
|
|
21259
23128
|
await respond({
|
|
21260
23129
|
ok: true,
|
|
@@ -21379,22 +23248,27 @@ Locators (optional; default: whole document):
|
|
|
21379
23248
|
tN:rRcC whole cell
|
|
21380
23249
|
tN:rRcC:pK paragraph K inside that cell
|
|
21381
23250
|
tN:rRcC:pK:S-E span within a cell paragraph
|
|
23251
|
+
sN section N \u2014 every paragraph and table from the prior section
|
|
23252
|
+
boundary up to and including the paragraph that holds sN's
|
|
23253
|
+
inline sectPr (or to end of body for the trailing section)
|
|
21382
23254
|
|
|
21383
23255
|
Options:
|
|
21384
|
-
--accepted Count the accepted view: skip subtractive
|
|
21385
|
-
(<w:del>, <w:moveFrom>); keep additive
|
|
21386
|
-
(<w:ins>, <w:moveTo>) as plain text. Mirrors
|
|
21387
|
-
"docx read --markdown
|
|
23256
|
+
--accepted Default. Count the accepted view: skip subtractive
|
|
23257
|
+
wrappers (<w:del>, <w:moveFrom>); keep additive
|
|
23258
|
+
wrappers (<w:ins>, <w:moveTo>) as plain text. Mirrors
|
|
23259
|
+
"docx read --markdown" / "docx find" defaults.
|
|
21388
23260
|
--baseline Count the baseline view: skip additive wrappers
|
|
21389
23261
|
(<w:ins>, <w:moveTo>); keep subtractive wrappers
|
|
21390
23262
|
(<w:del>, <w:moveFrom>) as plain text \u2014 i.e., the doc as
|
|
21391
23263
|
it was before any tracked changes were made.
|
|
23264
|
+
--current Count the raw concatenation: every tracked-change
|
|
23265
|
+
wrapper's text counts (everything on disk).
|
|
21392
23266
|
-h, --help show this help
|
|
21393
23267
|
|
|
21394
23268
|
Counting is whitespace-segmented (\\S+) over the joined paragraph text. Hidden
|
|
21395
|
-
content like images/breaks/tabs contributes no words.
|
|
21396
|
-
|
|
21397
|
-
|
|
23269
|
+
content like images/breaks/tabs contributes no words. The three view flags
|
|
23270
|
+
are mutually exclusive; default (no flag) is --accepted, matching what a
|
|
23271
|
+
reader would see if every tracked change were accepted.
|
|
21398
23272
|
|
|
21399
23273
|
Examples:
|
|
21400
23274
|
docx wc doc.docx
|
|
@@ -21402,6 +23276,7 @@ Examples:
|
|
|
21402
23276
|
docx wc doc.docx p3:0-120
|
|
21403
23277
|
docx wc doc.docx p5:10-p9:42
|
|
21404
23278
|
docx wc doc.docx t0:r1c0
|
|
23279
|
+
docx wc doc.docx s2
|
|
21405
23280
|
`;
|
|
21406
23281
|
var init_wc = __esm(() => {
|
|
21407
23282
|
init_core();
|
|
@@ -21411,7 +23286,7 @@ var init_wc = __esm(() => {
|
|
|
21411
23286
|
// package.json
|
|
21412
23287
|
var package_default = {
|
|
21413
23288
|
name: "bun-docx",
|
|
21414
|
-
version: "0.
|
|
23289
|
+
version: "0.8.0",
|
|
21415
23290
|
description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
|
|
21416
23291
|
keywords: [
|
|
21417
23292
|
"docx",
|
|
@@ -21503,12 +23378,21 @@ Environment:
|
|
|
21503
23378
|
|
|
21504
23379
|
Tracked changes:
|
|
21505
23380
|
Toggle: docx track-changes FILE on|off
|
|
21506
|
-
Inventory: docx track-changes list FILE (JSON
|
|
23381
|
+
Inventory: docx track-changes list FILE (JSON array of { id, kind, author,
|
|
23382
|
+
date, revisionId, blockId, text }; sectPrChange entries also
|
|
23383
|
+
include { prior, current } with the section properties on each
|
|
23384
|
+
side of the edit)
|
|
21507
23385
|
Accept: docx track-changes accept FILE (--at tcN | --all)
|
|
21508
23386
|
Reject: docx track-changes reject FILE (--at tcN | --all)
|
|
21509
23387
|
|
|
21510
23388
|
When <w:trackChanges/> is set, insert/edit/delete/replace automatically emit
|
|
21511
23389
|
<w:ins>/<w:del> markers attributed via --author (or $DOCX_AUTHOR, or "docx-cli").
|
|
23390
|
+
edit --at sN under tracking emits <w:sectPrChange> snapshots on the section.
|
|
23391
|
+
|
|
23392
|
+
Accept/reject handle: run-level ins/del/moveFrom/moveTo, sectPrChange
|
|
23393
|
+
(snapshot drop or restore), and paragraph-mark ins/del (accept-del merges
|
|
23394
|
+
with the next paragraph; reject-ins removes the entire owning paragraph).
|
|
23395
|
+
Out of scope: rPrChange / pPrChange formatting revisions.
|
|
21512
23396
|
|
|
21513
23397
|
"docx read --markdown" surfaces them as CriticMarkup:
|
|
21514
23398
|
{++inserted++}[^tcN] {--deleted--}[^tcN]
|