bun-docx 0.5.0 → 0.6.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 +34 -3
- package/dist/index.js +721 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ bunx bun-docx read doc.docx
|
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
docx create FILE [--title T] [--author A] [--text "..."]
|
|
34
|
-
docx read FILE
|
|
34
|
+
docx read FILE [--markdown [--from pN] [--to pN] [--changes] [--comments]]
|
|
35
35
|
docx insert FILE --after p3 --text "..." [--style HeadingN] [--color HEX] [--bold] [--italic] [--url URL]
|
|
36
36
|
docx insert FILE --after p3 --runs '[{"type":"text","text":"X","bold":true}]'
|
|
37
37
|
docx edit FILE --at p3 --text "..." | --runs '[...]'
|
|
@@ -64,6 +64,37 @@ Every command has `--help`. Mutating commands accept `--dry-run` and `-o/--outpu
|
|
|
64
64
|
|
|
65
65
|
When `<w:trackChanges/>` is set in the doc (toggle via `docx track-changes FILE on`), `insert`/`edit`/`delete`/`replace` automatically emit `<w:ins>`/`<w:del>` markers attributed to `$DOCX_AUTHOR` (default `docx-cli`). To make a one-off untracked edit, flip the flag off, edit, then flip it back on. `find` results inside tracked-change wrappers carry a `trackedChanges` array so agents can decide what to do with hits in pending insertions/deletions.
|
|
66
66
|
|
|
67
|
+
### Markdown rendering
|
|
68
|
+
|
|
69
|
+
`docx read FILE --markdown` renders the document body as GitHub-flavored Markdown instead of JSON. Useful when you (or an LLM) want to skim a doc quickly without parsing the AST. Each rendered paragraph is followed by an HTML comment with its locator (`<!-- p3 -->`) so the markdown is invisible-pinned: humans see clean prose in a renderer, agents parse the locators from raw text.
|
|
70
|
+
|
|
71
|
+
- Headings → `#`/`##`/`###` based on `style="HeadingN"`
|
|
72
|
+
- Lists → `- ` indented per `level`
|
|
73
|
+
- Bold/italic/strike → `**…**` / `*…*` / `~~…~~`; underline → `<u>…</u>`
|
|
74
|
+
- Run color → `<span style="color:#hex">…</span>`; highlight → `<span style="background-color:NAME">…</span>`
|
|
75
|
+
- Hyperlinks → `[text](url)`
|
|
76
|
+
- Images → ``
|
|
77
|
+
- Tables → GitHub pipe tables; multi-paragraph cells joined with `<br>`; per-cell-paragraph locators inline
|
|
78
|
+
- Section breaks → `---`
|
|
79
|
+
- Equations (`<m:oMath>`/`<m:oMathPara>`) → `` `equation: text` `` (concatenated `<m:t>` plaintext; structure like sub/sup/fractions collapses to literal characters — degraded but readable)
|
|
80
|
+
- Footnotes / endnotes → inline `[^fnN]` / `[^enN]` refs with GFM footnote definitions at end of output
|
|
81
|
+
- Charts / SmartArt / shapes / other non-picture drawings → `` `[chart]` `` / `` `[smartart]` `` / `` `[shape]` `` / `` `[drawing]` `` placeholders
|
|
82
|
+
|
|
83
|
+
`--from LOC` and `--to LOC` slice by top-level block (both inclusive). Accepts paragraph, table, cell, span, and range locators; cell/span/range collapse to their enclosing top-level block. Comment/image/hyperlink locators are rejected.
|
|
84
|
+
|
|
85
|
+
`--changes` renders tracked insertions and deletions as `<ins>`/`<del>` instead of producing the accepted view (which silently drops deletions and inlines insertions).
|
|
86
|
+
|
|
87
|
+
`--comments` appends a GFM footnote reference (`[^cN]`) at the end of each commented span and emits one footnote definition per comment at the end of the output:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
… some commented text[^c0] …
|
|
91
|
+
|
|
92
|
+
[^c0]: "commented span" — Author Name (2024-01-15T...): comment body
|
|
93
|
+
[^c1]: "another span" — Author Name (2024-01-15T...) ↳ c0: reply body
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Footnotes/endnotes (the document's own `<w:footnoteReference>` / `<w:endnoteReference>`) are rendered unconditionally — `[^fn1]` / `[^en1]` inline + `[^fn1]: body` definitions at the end of the output, alongside any `--comments` footnotes. They use `fn`/`en` prefixes so the namespaces don't collide.
|
|
97
|
+
|
|
67
98
|
### Locators
|
|
68
99
|
|
|
69
100
|
```
|
|
@@ -104,7 +135,7 @@ src/
|
|
|
104
135
|
help.ts # top-level --help
|
|
105
136
|
respond.ts # JSON ack / structured error helpers
|
|
106
137
|
create/ # create FILE
|
|
107
|
-
read/ # read FILE
|
|
138
|
+
read/ # read FILE [--markdown ...] (markdown.ts renderer)
|
|
108
139
|
insert/ # insert FILE (uses ./emit Paragraph component)
|
|
109
140
|
edit/ # edit FILE
|
|
110
141
|
delete/ # delete FILE
|
|
@@ -117,7 +148,7 @@ src/
|
|
|
117
148
|
package/ # JSZip open/close, named-part read/write
|
|
118
149
|
parser/ # XmlNode class + parse/serialize + JSX factory
|
|
119
150
|
jsx/ # h, Fragment, namespaces (w, r, a, wp, pic, ...)
|
|
120
|
-
ast/ # types + DocView + XML→AST reader
|
|
151
|
+
ast/ # types + DocView + XML→AST reader (text.ts: shared paragraph helpers)
|
|
121
152
|
locators/ # parse "p3:5-20" + resolve to refs
|
|
122
153
|
tests/
|
|
123
154
|
core/, cli/, integration/
|
package/dist/index.js
CHANGED
|
@@ -13780,7 +13780,17 @@ function buildDoc(view, path) {
|
|
|
13780
13780
|
};
|
|
13781
13781
|
const blocks = readBlocks(view, body, state);
|
|
13782
13782
|
const comments = readComments(view, state.commentAnchors);
|
|
13783
|
-
|
|
13783
|
+
const footnotes = readNotes(view.footnotesTree, "w:footnotes", "w:footnote", "fn");
|
|
13784
|
+
const endnotes = readNotes(view.endnotesTree, "w:endnotes", "w:endnote", "en");
|
|
13785
|
+
return {
|
|
13786
|
+
schemaVersion: 1,
|
|
13787
|
+
path,
|
|
13788
|
+
properties,
|
|
13789
|
+
blocks,
|
|
13790
|
+
comments,
|
|
13791
|
+
footnotes,
|
|
13792
|
+
endnotes
|
|
13793
|
+
};
|
|
13784
13794
|
}
|
|
13785
13795
|
function readBlocks(view, body, state) {
|
|
13786
13796
|
const blocks = [];
|
|
@@ -13868,6 +13878,28 @@ function walkRunContainer(context, container, trackedChange, hyperlink) {
|
|
|
13868
13878
|
}
|
|
13869
13879
|
continue;
|
|
13870
13880
|
}
|
|
13881
|
+
if (child.tag === "m:oMath") {
|
|
13882
|
+
const text = collectMathText(child);
|
|
13883
|
+
if (text.length > 0) {
|
|
13884
|
+
context.paragraph.runs.push({
|
|
13885
|
+
type: "equation",
|
|
13886
|
+
text,
|
|
13887
|
+
display: false
|
|
13888
|
+
});
|
|
13889
|
+
}
|
|
13890
|
+
continue;
|
|
13891
|
+
}
|
|
13892
|
+
if (child.tag === "m:oMathPara") {
|
|
13893
|
+
const text = collectMathText(child);
|
|
13894
|
+
if (text.length > 0) {
|
|
13895
|
+
context.paragraph.runs.push({
|
|
13896
|
+
type: "equation",
|
|
13897
|
+
text,
|
|
13898
|
+
display: true
|
|
13899
|
+
});
|
|
13900
|
+
}
|
|
13901
|
+
continue;
|
|
13902
|
+
}
|
|
13871
13903
|
if (child.tag === "w:ins" || child.tag === "w:del") {
|
|
13872
13904
|
const change = {
|
|
13873
13905
|
kind: child.tag === "w:ins" ? "ins" : "del",
|
|
@@ -13937,9 +13969,9 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13937
13969
|
const runProperties = node.findChild("w:rPr");
|
|
13938
13970
|
for (const child of node.children) {
|
|
13939
13971
|
if (child.tag === "w:drawing") {
|
|
13940
|
-
const
|
|
13941
|
-
if (
|
|
13942
|
-
return
|
|
13972
|
+
const drawing = readDrawing(view, child, state);
|
|
13973
|
+
if (drawing)
|
|
13974
|
+
return drawing;
|
|
13943
13975
|
}
|
|
13944
13976
|
if (child.tag === "w:br") {
|
|
13945
13977
|
const kind = child.getAttribute("w:type") ?? "line";
|
|
@@ -13948,6 +13980,16 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13948
13980
|
if (child.tag === "w:tab") {
|
|
13949
13981
|
return { type: "tab" };
|
|
13950
13982
|
}
|
|
13983
|
+
if (child.tag === "w:footnoteReference") {
|
|
13984
|
+
const id = child.getAttribute("w:id");
|
|
13985
|
+
if (id)
|
|
13986
|
+
return { type: "footnoteRef", kind: "footnote", id: `fn${id}` };
|
|
13987
|
+
}
|
|
13988
|
+
if (child.tag === "w:endnoteReference") {
|
|
13989
|
+
const id = child.getAttribute("w:id");
|
|
13990
|
+
if (id)
|
|
13991
|
+
return { type: "footnoteRef", kind: "endnote", id: `en${id}` };
|
|
13992
|
+
}
|
|
13951
13993
|
}
|
|
13952
13994
|
let combinedText = "";
|
|
13953
13995
|
for (const child of node.children) {
|
|
@@ -13968,6 +14010,29 @@ function readRun(view, node, activeComments, trackedChange, hyperlink, state) {
|
|
|
13968
14010
|
run.hyperlink = hyperlink;
|
|
13969
14011
|
return run;
|
|
13970
14012
|
}
|
|
14013
|
+
function readDrawing(view, drawing, state) {
|
|
14014
|
+
const image = readImageFromDrawing(view, drawing, state);
|
|
14015
|
+
if (image)
|
|
14016
|
+
return image;
|
|
14017
|
+
if (drawing.findDescendant("c:chart"))
|
|
14018
|
+
return { type: "chart", kind: "chart" };
|
|
14019
|
+
if (drawing.findDescendant("dgm:relIds"))
|
|
14020
|
+
return { type: "chart", kind: "smartart" };
|
|
14021
|
+
if (drawing.findDescendant("wps:wsp"))
|
|
14022
|
+
return { type: "chart", kind: "shape" };
|
|
14023
|
+
return { type: "chart", kind: "drawing" };
|
|
14024
|
+
}
|
|
14025
|
+
function collectMathText(node) {
|
|
14026
|
+
let out = "";
|
|
14027
|
+
for (const child of node.children) {
|
|
14028
|
+
if (child.tag === "m:t") {
|
|
14029
|
+
out += child.collectText();
|
|
14030
|
+
continue;
|
|
14031
|
+
}
|
|
14032
|
+
out += collectMathText(child);
|
|
14033
|
+
}
|
|
14034
|
+
return out;
|
|
14035
|
+
}
|
|
13971
14036
|
function applyRunProperties(run, runProperties) {
|
|
13972
14037
|
const colorNode = runProperties.findChild("w:color");
|
|
13973
14038
|
if (colorNode) {
|
|
@@ -14222,6 +14287,26 @@ function readCommentsExtended(view) {
|
|
|
14222
14287
|
}
|
|
14223
14288
|
return out;
|
|
14224
14289
|
}
|
|
14290
|
+
function readNotes(tree, rootTag, itemTag, idPrefix) {
|
|
14291
|
+
if (!tree)
|
|
14292
|
+
return [];
|
|
14293
|
+
const root = XmlNode2.findRoot(tree, rootTag);
|
|
14294
|
+
if (!root)
|
|
14295
|
+
return [];
|
|
14296
|
+
const out = [];
|
|
14297
|
+
for (const child of root.children) {
|
|
14298
|
+
if (child.tag !== itemTag)
|
|
14299
|
+
continue;
|
|
14300
|
+
if (child.getAttribute("w:type"))
|
|
14301
|
+
continue;
|
|
14302
|
+
const numericId = child.getAttribute("w:id");
|
|
14303
|
+
if (numericId == null)
|
|
14304
|
+
continue;
|
|
14305
|
+
const text = child.collectText().replace(/\s+/g, " ").trim();
|
|
14306
|
+
out.push({ id: `${idPrefix}${numericId}`, text });
|
|
14307
|
+
}
|
|
14308
|
+
return out;
|
|
14309
|
+
}
|
|
14225
14310
|
var RELATIONSHIP_NAMESPACE_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", RELATIONSHIP_NAMESPACE_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
14226
14311
|
var init_read = __esm(() => {
|
|
14227
14312
|
init_parser();
|
|
@@ -14237,11 +14322,15 @@ async function openDocView(path) {
|
|
|
14237
14322
|
const commentsExtTree = pkg.hasPart("word/commentsExtended.xml") ? XmlNode2.parse(await pkg.readText("word/commentsExtended.xml")) : undefined;
|
|
14238
14323
|
const corePropertiesTree = pkg.hasPart("docProps/core.xml") ? XmlNode2.parse(await pkg.readText("docProps/core.xml")) : undefined;
|
|
14239
14324
|
const settingsTree = pkg.hasPart("word/settings.xml") ? XmlNode2.parse(await pkg.readText("word/settings.xml")) : undefined;
|
|
14325
|
+
const footnotesTree = pkg.hasPart("word/footnotes.xml") ? XmlNode2.parse(await pkg.readText("word/footnotes.xml")) : undefined;
|
|
14326
|
+
const endnotesTree = pkg.hasPart("word/endnotes.xml") ? XmlNode2.parse(await pkg.readText("word/endnotes.xml")) : undefined;
|
|
14240
14327
|
const view = {
|
|
14241
14328
|
pkg,
|
|
14242
14329
|
documentTree,
|
|
14243
14330
|
commentsTree,
|
|
14244
14331
|
commentsExtTree,
|
|
14332
|
+
footnotesTree,
|
|
14333
|
+
endnotesTree,
|
|
14245
14334
|
relationshipsTree,
|
|
14246
14335
|
contentTypesTree,
|
|
14247
14336
|
corePropertiesTree,
|
|
@@ -14322,6 +14411,49 @@ var init_doc_view = __esm(() => {
|
|
|
14322
14411
|
init_read();
|
|
14323
14412
|
});
|
|
14324
14413
|
|
|
14414
|
+
// src/core/ast/text.ts
|
|
14415
|
+
function paragraphText(paragraph) {
|
|
14416
|
+
let out = "";
|
|
14417
|
+
for (const run of paragraph.runs) {
|
|
14418
|
+
if (run.type === "text")
|
|
14419
|
+
out += run.text;
|
|
14420
|
+
}
|
|
14421
|
+
return out;
|
|
14422
|
+
}
|
|
14423
|
+
function flattenParagraphs(blocks) {
|
|
14424
|
+
const out = [];
|
|
14425
|
+
for (const block of blocks) {
|
|
14426
|
+
if (block.type === "paragraph") {
|
|
14427
|
+
out.push(block);
|
|
14428
|
+
continue;
|
|
14429
|
+
}
|
|
14430
|
+
if (block.type === "table") {
|
|
14431
|
+
for (const row of block.rows) {
|
|
14432
|
+
for (const cell of row.cells) {
|
|
14433
|
+
out.push(...flattenParagraphs(cell.blocks));
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
}
|
|
14437
|
+
}
|
|
14438
|
+
return out;
|
|
14439
|
+
}
|
|
14440
|
+
function findBlockById(blocks, blockId) {
|
|
14441
|
+
for (const block of blocks) {
|
|
14442
|
+
if (block.id === blockId)
|
|
14443
|
+
return block;
|
|
14444
|
+
if (block.type === "table") {
|
|
14445
|
+
for (const row of block.rows) {
|
|
14446
|
+
for (const cell of row.cells) {
|
|
14447
|
+
const inner = findBlockById(cell.blocks, blockId);
|
|
14448
|
+
if (inner)
|
|
14449
|
+
return inner;
|
|
14450
|
+
}
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
}
|
|
14454
|
+
return null;
|
|
14455
|
+
}
|
|
14456
|
+
|
|
14325
14457
|
// src/core/ast/index.ts
|
|
14326
14458
|
var init_ast = __esm(() => {
|
|
14327
14459
|
init_doc_view();
|
|
@@ -16185,7 +16317,7 @@ function RunElement({ run: run9 }) {
|
|
|
16185
16317
|
children: /* @__PURE__ */ jsxDEV(w.tab, {}, undefined, false, undefined, this)
|
|
16186
16318
|
}, undefined, false, undefined, this);
|
|
16187
16319
|
}
|
|
16188
|
-
|
|
16320
|
+
return null;
|
|
16189
16321
|
}
|
|
16190
16322
|
function TextRunElement({ run: run9 }) {
|
|
16191
16323
|
return /* @__PURE__ */ jsxDEV(w.r, {
|
|
@@ -16487,15 +16619,15 @@ function literalMatcher(query, ignoreCase) {
|
|
|
16487
16619
|
throw new Error("query cannot be empty");
|
|
16488
16620
|
}
|
|
16489
16621
|
const needle = ignoreCase ? query.toLowerCase() : query;
|
|
16490
|
-
return (
|
|
16491
|
-
const haystack = ignoreCase ?
|
|
16622
|
+
return (paragraphText2) => {
|
|
16623
|
+
const haystack = ignoreCase ? paragraphText2.toLowerCase() : paragraphText2;
|
|
16492
16624
|
const matches = [];
|
|
16493
16625
|
let cursor = haystack.indexOf(needle);
|
|
16494
16626
|
while (cursor !== -1) {
|
|
16495
16627
|
matches.push({
|
|
16496
16628
|
start: cursor,
|
|
16497
16629
|
end: cursor + needle.length,
|
|
16498
|
-
text:
|
|
16630
|
+
text: paragraphText2.slice(cursor, cursor + needle.length)
|
|
16499
16631
|
});
|
|
16500
16632
|
cursor = haystack.indexOf(needle, cursor + needle.length);
|
|
16501
16633
|
}
|
|
@@ -16505,15 +16637,15 @@ function literalMatcher(query, ignoreCase) {
|
|
|
16505
16637
|
function regexMatcher(pattern, ignoreCase) {
|
|
16506
16638
|
const flags = `g${ignoreCase ? "i" : ""}`;
|
|
16507
16639
|
const regex = new RegExp(pattern, flags);
|
|
16508
|
-
return (
|
|
16640
|
+
return (paragraphText2) => {
|
|
16509
16641
|
const matches = [];
|
|
16510
16642
|
regex.lastIndex = 0;
|
|
16511
|
-
let result = regex.exec(
|
|
16643
|
+
let result = regex.exec(paragraphText2);
|
|
16512
16644
|
while (result !== null) {
|
|
16513
16645
|
const matched = result[0];
|
|
16514
16646
|
if (matched.length === 0) {
|
|
16515
16647
|
regex.lastIndex += 1;
|
|
16516
|
-
result = regex.exec(
|
|
16648
|
+
result = regex.exec(paragraphText2);
|
|
16517
16649
|
continue;
|
|
16518
16650
|
}
|
|
16519
16651
|
matches.push({
|
|
@@ -16521,7 +16653,7 @@ function regexMatcher(pattern, ignoreCase) {
|
|
|
16521
16653
|
end: result.index + matched.length,
|
|
16522
16654
|
text: matched
|
|
16523
16655
|
});
|
|
16524
|
-
result = regex.exec(
|
|
16656
|
+
result = regex.exec(paragraphText2);
|
|
16525
16657
|
}
|
|
16526
16658
|
return matches;
|
|
16527
16659
|
};
|
|
@@ -16529,8 +16661,8 @@ function regexMatcher(pattern, ignoreCase) {
|
|
|
16529
16661
|
function collectMatches(blocks, matcher, out) {
|
|
16530
16662
|
for (const block of blocks) {
|
|
16531
16663
|
if (block.type === "paragraph") {
|
|
16532
|
-
const
|
|
16533
|
-
for (const span of matcher(
|
|
16664
|
+
const paragraphText2 = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
|
|
16665
|
+
for (const span of matcher(paragraphText2)) {
|
|
16534
16666
|
const match = {
|
|
16535
16667
|
blockId: block.id,
|
|
16536
16668
|
start: span.start,
|
|
@@ -17721,6 +17853,8 @@ var types_default = `export type Doc = {
|
|
|
17721
17853
|
properties: DocProperties;
|
|
17722
17854
|
blocks: Block[];
|
|
17723
17855
|
comments: Comment[];
|
|
17856
|
+
footnotes: Footnote[];
|
|
17857
|
+
endnotes: Footnote[];
|
|
17724
17858
|
};
|
|
17725
17859
|
|
|
17726
17860
|
export type DocProperties = {
|
|
@@ -17760,7 +17894,14 @@ export type SectionBreak = {
|
|
|
17760
17894
|
type: "sectionBreak";
|
|
17761
17895
|
};
|
|
17762
17896
|
|
|
17763
|
-
export type Run =
|
|
17897
|
+
export type Run =
|
|
17898
|
+
| TextRun
|
|
17899
|
+
| ImageRun
|
|
17900
|
+
| BreakRun
|
|
17901
|
+
| TabRun
|
|
17902
|
+
| EquationRun
|
|
17903
|
+
| FootnoteRefRun
|
|
17904
|
+
| ChartRun;
|
|
17764
17905
|
|
|
17765
17906
|
export type TextRun = {
|
|
17766
17907
|
type: "text";
|
|
@@ -17804,6 +17945,37 @@ export type TabRun = {
|
|
|
17804
17945
|
type: "tab";
|
|
17805
17946
|
};
|
|
17806
17947
|
|
|
17948
|
+
/** A math equation surfaced as concatenated <m:t> plaintext. The structural
|
|
17949
|
+
* OOMath markup (subscripts, fractions, etc.) is collapsed to literal characters,
|
|
17950
|
+
* so the rendering is degraded. \`display\` distinguishes inline equations
|
|
17951
|
+
* (<m:oMath>) from block-level display equations (<m:oMathPara>). */
|
|
17952
|
+
export type EquationRun = {
|
|
17953
|
+
type: "equation";
|
|
17954
|
+
text: string;
|
|
17955
|
+
display: boolean;
|
|
17956
|
+
};
|
|
17957
|
+
|
|
17958
|
+
/** Reference to a footnote or endnote whose body lives in Doc.footnotes or
|
|
17959
|
+
* Doc.endnotes. The id matches the footnote/endnote id (\`fn1\`, \`en1\`). */
|
|
17960
|
+
export type FootnoteRefRun = {
|
|
17961
|
+
type: "footnoteRef";
|
|
17962
|
+
kind: "footnote" | "endnote";
|
|
17963
|
+
id: string;
|
|
17964
|
+
};
|
|
17965
|
+
|
|
17966
|
+
/** Placeholder for a non-picture drawing (chart, shape, SmartArt, etc.). The
|
|
17967
|
+
* full content is preserved in the underlying XML; the run is a marker so
|
|
17968
|
+
* callers can know "something visual lives here." */
|
|
17969
|
+
export type ChartRun = {
|
|
17970
|
+
type: "chart";
|
|
17971
|
+
kind: "chart" | "shape" | "smartart" | "drawing";
|
|
17972
|
+
};
|
|
17973
|
+
|
|
17974
|
+
export type Footnote = {
|
|
17975
|
+
id: string;
|
|
17976
|
+
text: string;
|
|
17977
|
+
};
|
|
17978
|
+
|
|
17807
17979
|
export type TrackedChange = {
|
|
17808
17980
|
kind: "ins" | "del";
|
|
17809
17981
|
author: string;
|
|
@@ -17886,7 +18058,15 @@ var init_schema = __esm(() => {
|
|
|
17886
18058
|
$id: "https://github.com/kklimuk/docx-cli/schema",
|
|
17887
18059
|
title: "docx-cli AST",
|
|
17888
18060
|
type: "object",
|
|
17889
|
-
required: [
|
|
18061
|
+
required: [
|
|
18062
|
+
"schemaVersion",
|
|
18063
|
+
"path",
|
|
18064
|
+
"properties",
|
|
18065
|
+
"blocks",
|
|
18066
|
+
"comments",
|
|
18067
|
+
"footnotes",
|
|
18068
|
+
"endnotes"
|
|
18069
|
+
],
|
|
17890
18070
|
properties: {
|
|
17891
18071
|
schemaVersion: { const: 1 },
|
|
17892
18072
|
path: { type: "string" },
|
|
@@ -17900,7 +18080,9 @@ var init_schema = __esm(() => {
|
|
|
17900
18080
|
}
|
|
17901
18081
|
},
|
|
17902
18082
|
blocks: { type: "array", items: { $ref: "#/$defs/Block" } },
|
|
17903
|
-
comments: { type: "array", items: { $ref: "#/$defs/Comment" } }
|
|
18083
|
+
comments: { type: "array", items: { $ref: "#/$defs/Comment" } },
|
|
18084
|
+
footnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } },
|
|
18085
|
+
endnotes: { type: "array", items: { $ref: "#/$defs/Footnote" } }
|
|
17904
18086
|
},
|
|
17905
18087
|
$defs: {
|
|
17906
18088
|
Block: {
|
|
@@ -17934,7 +18116,10 @@ var init_schema = __esm(() => {
|
|
|
17934
18116
|
{ $ref: "#/$defs/TextRun" },
|
|
17935
18117
|
{ $ref: "#/$defs/ImageRun" },
|
|
17936
18118
|
{ $ref: "#/$defs/BreakRun" },
|
|
17937
|
-
{ $ref: "#/$defs/TabRun" }
|
|
18119
|
+
{ $ref: "#/$defs/TabRun" },
|
|
18120
|
+
{ $ref: "#/$defs/EquationRun" },
|
|
18121
|
+
{ $ref: "#/$defs/FootnoteRefRun" },
|
|
18122
|
+
{ $ref: "#/$defs/ChartRun" }
|
|
17938
18123
|
]
|
|
17939
18124
|
},
|
|
17940
18125
|
TextRun: {
|
|
@@ -18001,6 +18186,40 @@ var init_schema = __esm(() => {
|
|
|
18001
18186
|
required: ["type"],
|
|
18002
18187
|
properties: { type: { const: "tab" } }
|
|
18003
18188
|
},
|
|
18189
|
+
EquationRun: {
|
|
18190
|
+
type: "object",
|
|
18191
|
+
required: ["type", "text", "display"],
|
|
18192
|
+
properties: {
|
|
18193
|
+
type: { const: "equation" },
|
|
18194
|
+
text: { type: "string" },
|
|
18195
|
+
display: { type: "boolean" }
|
|
18196
|
+
}
|
|
18197
|
+
},
|
|
18198
|
+
FootnoteRefRun: {
|
|
18199
|
+
type: "object",
|
|
18200
|
+
required: ["type", "kind", "id"],
|
|
18201
|
+
properties: {
|
|
18202
|
+
type: { const: "footnoteRef" },
|
|
18203
|
+
kind: { enum: ["footnote", "endnote"] },
|
|
18204
|
+
id: { type: "string" }
|
|
18205
|
+
}
|
|
18206
|
+
},
|
|
18207
|
+
ChartRun: {
|
|
18208
|
+
type: "object",
|
|
18209
|
+
required: ["type", "kind"],
|
|
18210
|
+
properties: {
|
|
18211
|
+
type: { const: "chart" },
|
|
18212
|
+
kind: { enum: ["chart", "shape", "smartart", "drawing"] }
|
|
18213
|
+
}
|
|
18214
|
+
},
|
|
18215
|
+
Footnote: {
|
|
18216
|
+
type: "object",
|
|
18217
|
+
required: ["id", "text"],
|
|
18218
|
+
properties: {
|
|
18219
|
+
id: { type: "string" },
|
|
18220
|
+
text: { type: "string" }
|
|
18221
|
+
}
|
|
18222
|
+
},
|
|
18004
18223
|
Table: {
|
|
18005
18224
|
type: "object",
|
|
18006
18225
|
required: ["id", "type", "rows"],
|
|
@@ -18500,14 +18719,9 @@ function* headingParagraphs(blocks, stylePrefix) {
|
|
|
18500
18719
|
yield block;
|
|
18501
18720
|
}
|
|
18502
18721
|
}
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
|
|
18506
|
-
if (run24.type === "text")
|
|
18507
|
-
out += run24.text;
|
|
18508
|
-
}
|
|
18509
|
-
return out;
|
|
18510
|
-
}
|
|
18722
|
+
var init_build = __esm(() => {
|
|
18723
|
+
init_core();
|
|
18724
|
+
});
|
|
18511
18725
|
|
|
18512
18726
|
// src/cli/outline/index.ts
|
|
18513
18727
|
var exports_outline = {};
|
|
@@ -18579,6 +18793,425 @@ Examples:
|
|
|
18579
18793
|
`;
|
|
18580
18794
|
var init_outline = __esm(() => {
|
|
18581
18795
|
init_respond();
|
|
18796
|
+
init_build();
|
|
18797
|
+
});
|
|
18798
|
+
|
|
18799
|
+
// src/cli/read/markdown.ts
|
|
18800
|
+
function renderMarkdown(doc, options = {}) {
|
|
18801
|
+
const blocks = sliceBlocks(doc.blocks, options.from, options.to);
|
|
18802
|
+
const commentIndex = options.showComments ? buildCommentIndex(blocks, options) : emptyCommentIndex();
|
|
18803
|
+
const ctx = {
|
|
18804
|
+
options,
|
|
18805
|
+
commentIndex,
|
|
18806
|
+
referencedFootnoteIds: new Set,
|
|
18807
|
+
referencedEndnoteIds: new Set
|
|
18808
|
+
};
|
|
18809
|
+
const parts = [];
|
|
18810
|
+
for (const block of blocks) {
|
|
18811
|
+
const rendered = renderBlock(block, ctx);
|
|
18812
|
+
if (rendered !== null)
|
|
18813
|
+
parts.push(rendered);
|
|
18814
|
+
}
|
|
18815
|
+
const definitions = [];
|
|
18816
|
+
if (options.showComments) {
|
|
18817
|
+
const commentFootnotes = renderCommentFootnotes(commentIndex, doc.comments);
|
|
18818
|
+
if (commentFootnotes.length > 0)
|
|
18819
|
+
definitions.push(commentFootnotes);
|
|
18820
|
+
}
|
|
18821
|
+
const footnoteDefs = renderNoteDefinitions(doc.footnotes, ctx.referencedFootnoteIds);
|
|
18822
|
+
if (footnoteDefs.length > 0)
|
|
18823
|
+
definitions.push(footnoteDefs);
|
|
18824
|
+
const endnoteDefs = renderNoteDefinitions(doc.endnotes, ctx.referencedEndnoteIds);
|
|
18825
|
+
if (endnoteDefs.length > 0)
|
|
18826
|
+
definitions.push(endnoteDefs);
|
|
18827
|
+
if (definitions.length > 0)
|
|
18828
|
+
parts.push(definitions.join(`
|
|
18829
|
+
`));
|
|
18830
|
+
if (parts.length === 0)
|
|
18831
|
+
return "";
|
|
18832
|
+
return `${parts.join(`
|
|
18833
|
+
|
|
18834
|
+
`)}
|
|
18835
|
+
`;
|
|
18836
|
+
}
|
|
18837
|
+
function emptyCommentIndex() {
|
|
18838
|
+
return {
|
|
18839
|
+
endingsByRun: new Map,
|
|
18840
|
+
spanText: new Map,
|
|
18841
|
+
orderedIds: []
|
|
18842
|
+
};
|
|
18843
|
+
}
|
|
18844
|
+
function buildCommentIndex(blocks, options) {
|
|
18845
|
+
const showChanges = options.showChanges ?? false;
|
|
18846
|
+
const lastSlot = new Map;
|
|
18847
|
+
const spanText = new Map;
|
|
18848
|
+
const orderedIds = [];
|
|
18849
|
+
for (const paragraph of flattenParagraphs(blocks)) {
|
|
18850
|
+
paragraph.runs.forEach((run25, index) => {
|
|
18851
|
+
if (run25.type !== "text")
|
|
18852
|
+
return;
|
|
18853
|
+
if (!showChanges && run25.trackedChange?.kind === "del")
|
|
18854
|
+
return;
|
|
18855
|
+
for (const commentId of run25.comments ?? []) {
|
|
18856
|
+
if (!spanText.has(commentId))
|
|
18857
|
+
orderedIds.push(commentId);
|
|
18858
|
+
spanText.set(commentId, (spanText.get(commentId) ?? "") + run25.text);
|
|
18859
|
+
lastSlot.set(commentId, slotKey(paragraph.id, index));
|
|
18860
|
+
}
|
|
18861
|
+
});
|
|
18862
|
+
}
|
|
18863
|
+
const endingsByRun = new Map;
|
|
18864
|
+
for (const commentId of orderedIds) {
|
|
18865
|
+
const slot = lastSlot.get(commentId);
|
|
18866
|
+
if (!slot)
|
|
18867
|
+
continue;
|
|
18868
|
+
const list = endingsByRun.get(slot) ?? [];
|
|
18869
|
+
list.push(commentId);
|
|
18870
|
+
endingsByRun.set(slot, list);
|
|
18871
|
+
}
|
|
18872
|
+
return { endingsByRun, spanText, orderedIds };
|
|
18873
|
+
}
|
|
18874
|
+
function slotKey(paragraphId, runIndex) {
|
|
18875
|
+
return `${paragraphId}#${runIndex}`;
|
|
18876
|
+
}
|
|
18877
|
+
function renderBlock(block, ctx) {
|
|
18878
|
+
if (block.type === "paragraph")
|
|
18879
|
+
return renderParagraph(block, ctx);
|
|
18880
|
+
if (block.type === "table")
|
|
18881
|
+
return renderTable(block, ctx);
|
|
18882
|
+
if (block.type === "sectionBreak")
|
|
18883
|
+
return `--- <!-- ${block.id} -->`;
|
|
18884
|
+
return null;
|
|
18885
|
+
}
|
|
18886
|
+
function renderParagraph(paragraph, ctx) {
|
|
18887
|
+
const body = renderRuns(paragraph.id, paragraph.runs, ctx);
|
|
18888
|
+
if (body.length === 0)
|
|
18889
|
+
return null;
|
|
18890
|
+
const prefix = paragraphPrefix(paragraph);
|
|
18891
|
+
return `${prefix}${body} <!-- ${paragraph.id} -->`;
|
|
18892
|
+
}
|
|
18893
|
+
function paragraphPrefix(paragraph) {
|
|
18894
|
+
const headingLevel2 = headingLevelFor(paragraph.style);
|
|
18895
|
+
if (headingLevel2 !== null)
|
|
18896
|
+
return `${"#".repeat(headingLevel2)} `;
|
|
18897
|
+
if (paragraph.list) {
|
|
18898
|
+
const indent = " ".repeat(paragraph.list.level);
|
|
18899
|
+
return `${indent}- `;
|
|
18900
|
+
}
|
|
18901
|
+
return "";
|
|
18902
|
+
}
|
|
18903
|
+
function headingLevelFor(style) {
|
|
18904
|
+
if (!style)
|
|
18905
|
+
return null;
|
|
18906
|
+
if (!style.startsWith("Heading"))
|
|
18907
|
+
return null;
|
|
18908
|
+
const remainder = style.slice("Heading".length).trim();
|
|
18909
|
+
if (remainder === "")
|
|
18910
|
+
return 1;
|
|
18911
|
+
const parsed = Number(remainder);
|
|
18912
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 6)
|
|
18913
|
+
return null;
|
|
18914
|
+
return parsed;
|
|
18915
|
+
}
|
|
18916
|
+
function renderRuns(paragraphId, runs, ctx) {
|
|
18917
|
+
const showChanges = ctx.options.showChanges ?? false;
|
|
18918
|
+
const visibleEntries = [];
|
|
18919
|
+
runs.forEach((run25, index) => {
|
|
18920
|
+
if (!showChanges && run25.type === "text" && run25.trackedChange?.kind === "del") {
|
|
18921
|
+
return;
|
|
18922
|
+
}
|
|
18923
|
+
visibleEntries.push({ run: run25, originalIndex: index });
|
|
18924
|
+
});
|
|
18925
|
+
let out = "";
|
|
18926
|
+
let cursor = 0;
|
|
18927
|
+
while (cursor < visibleEntries.length) {
|
|
18928
|
+
const entry = visibleEntries[cursor];
|
|
18929
|
+
if (!entry) {
|
|
18930
|
+
cursor++;
|
|
18931
|
+
continue;
|
|
18932
|
+
}
|
|
18933
|
+
const { run: run25 } = entry;
|
|
18934
|
+
if (run25.type === "text") {
|
|
18935
|
+
let lookahead = cursor + 1;
|
|
18936
|
+
while (lookahead < visibleEntries.length) {
|
|
18937
|
+
const next = visibleEntries[lookahead];
|
|
18938
|
+
if (!next || next.run.type !== "text")
|
|
18939
|
+
break;
|
|
18940
|
+
if (!sameDecoration(run25, next.run))
|
|
18941
|
+
break;
|
|
18942
|
+
lookahead++;
|
|
18943
|
+
}
|
|
18944
|
+
const segment = visibleEntries.slice(cursor, lookahead);
|
|
18945
|
+
out += renderTextSegment(segment.map((entry2) => entry2.run), showChanges);
|
|
18946
|
+
out += commentEndingsFor(paragraphId, segment, ctx.commentIndex);
|
|
18947
|
+
cursor = lookahead;
|
|
18948
|
+
continue;
|
|
18949
|
+
}
|
|
18950
|
+
if (run25.type === "image") {
|
|
18951
|
+
const alt = sanitizeAltText(run25.alt ?? run25.id);
|
|
18952
|
+
out += ``;
|
|
18953
|
+
} else if (run25.type === "break") {
|
|
18954
|
+
if (run25.kind === "line")
|
|
18955
|
+
out += "<br>";
|
|
18956
|
+
} else if (run25.type === "tab") {
|
|
18957
|
+
out += "\t";
|
|
18958
|
+
} else if (run25.type === "equation") {
|
|
18959
|
+
const escaped = run25.text.replace(/`/g, "\\`");
|
|
18960
|
+
out += `\`equation: ${escaped}\``;
|
|
18961
|
+
} else if (run25.type === "footnoteRef") {
|
|
18962
|
+
if (run25.kind === "footnote")
|
|
18963
|
+
ctx.referencedFootnoteIds.add(run25.id);
|
|
18964
|
+
else
|
|
18965
|
+
ctx.referencedEndnoteIds.add(run25.id);
|
|
18966
|
+
out += `[^${run25.id}]`;
|
|
18967
|
+
} else if (run25.type === "chart") {
|
|
18968
|
+
out += `\`[${run25.kind}]\``;
|
|
18969
|
+
}
|
|
18970
|
+
cursor++;
|
|
18971
|
+
}
|
|
18972
|
+
return out;
|
|
18973
|
+
}
|
|
18974
|
+
function commentEndingsFor(paragraphId, segment, commentIndex) {
|
|
18975
|
+
if (commentIndex.endingsByRun.size === 0)
|
|
18976
|
+
return "";
|
|
18977
|
+
let out = "";
|
|
18978
|
+
for (const entry of segment) {
|
|
18979
|
+
const ids = commentIndex.endingsByRun.get(slotKey(paragraphId, entry.originalIndex));
|
|
18980
|
+
if (!ids)
|
|
18981
|
+
continue;
|
|
18982
|
+
for (const commentId of ids)
|
|
18983
|
+
out += `[^${commentId}]`;
|
|
18984
|
+
}
|
|
18985
|
+
return out;
|
|
18986
|
+
}
|
|
18987
|
+
function sameDecoration(a2, b) {
|
|
18988
|
+
return (a2.bold ?? false) === (b.bold ?? false) && (a2.italic ?? false) === (b.italic ?? false) && (a2.strike ?? false) === (b.strike ?? false) && (a2.underline ?? "") === (b.underline ?? "") && (a2.color ?? "") === (b.color ?? "") && (a2.highlight ?? "") === (b.highlight ?? "") && a2.hyperlink?.id === b.hyperlink?.id && a2.trackedChange?.kind === b.trackedChange?.kind && sameCommentSet(a2.comments, b.comments);
|
|
18989
|
+
}
|
|
18990
|
+
function sameCommentSet(left, right) {
|
|
18991
|
+
const a2 = left ?? [];
|
|
18992
|
+
const b = right ?? [];
|
|
18993
|
+
if (a2.length !== b.length)
|
|
18994
|
+
return false;
|
|
18995
|
+
for (let i = 0;i < a2.length; i++) {
|
|
18996
|
+
if (a2[i] !== b[i])
|
|
18997
|
+
return false;
|
|
18998
|
+
}
|
|
18999
|
+
return true;
|
|
19000
|
+
}
|
|
19001
|
+
function renderTextSegment(runs, showChanges) {
|
|
19002
|
+
const text = runs.map((run25) => run25.text).join("");
|
|
19003
|
+
if (text.length === 0)
|
|
19004
|
+
return "";
|
|
19005
|
+
const first = runs[0];
|
|
19006
|
+
if (!first)
|
|
19007
|
+
return "";
|
|
19008
|
+
let out = text;
|
|
19009
|
+
if (first.bold)
|
|
19010
|
+
out = `**${out}**`;
|
|
19011
|
+
if (first.italic)
|
|
19012
|
+
out = `*${out}*`;
|
|
19013
|
+
if (first.strike)
|
|
19014
|
+
out = `~~${out}~~`;
|
|
19015
|
+
if (first.underline)
|
|
19016
|
+
out = `<u>${out}</u>`;
|
|
19017
|
+
const color = colorAttrFor(first.color);
|
|
19018
|
+
if (color)
|
|
19019
|
+
out = `<span style="color:${color}">${out}</span>`;
|
|
19020
|
+
const highlight = highlightCssFor(first.highlight);
|
|
19021
|
+
if (highlight)
|
|
19022
|
+
out = `<span style="background-color:${highlight}">${out}</span>`;
|
|
19023
|
+
if (first.hyperlink) {
|
|
19024
|
+
const target = first.hyperlink.url ?? `#${first.hyperlink.anchor ?? ""}`;
|
|
19025
|
+
out = `[${out}](${target})`;
|
|
19026
|
+
}
|
|
19027
|
+
if (showChanges && first.trackedChange) {
|
|
19028
|
+
out = `<${first.trackedChange.kind}>${out}</${first.trackedChange.kind}>`;
|
|
19029
|
+
}
|
|
19030
|
+
return out;
|
|
19031
|
+
}
|
|
19032
|
+
function colorAttrFor(value) {
|
|
19033
|
+
if (!value)
|
|
19034
|
+
return null;
|
|
19035
|
+
const lowered = value.toLowerCase();
|
|
19036
|
+
if (lowered === "auto")
|
|
19037
|
+
return null;
|
|
19038
|
+
if (/^[0-9a-f]{6}$/.test(lowered))
|
|
19039
|
+
return `#${lowered}`;
|
|
19040
|
+
return value;
|
|
19041
|
+
}
|
|
19042
|
+
function highlightCssFor(value) {
|
|
19043
|
+
if (!value)
|
|
19044
|
+
return null;
|
|
19045
|
+
if (value === "none")
|
|
19046
|
+
return null;
|
|
19047
|
+
return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
|
|
19048
|
+
}
|
|
19049
|
+
function renderTable(table, ctx) {
|
|
19050
|
+
if (table.rows.length === 0)
|
|
19051
|
+
return null;
|
|
19052
|
+
const colCount = Math.max(...table.rows.map((row) => row.cells.length));
|
|
19053
|
+
if (colCount === 0)
|
|
19054
|
+
return null;
|
|
19055
|
+
const renderedRows = table.rows.map((row) => {
|
|
19056
|
+
const cells = [];
|
|
19057
|
+
for (let columnIndex = 0;columnIndex < colCount; columnIndex++) {
|
|
19058
|
+
const cell = row.cells[columnIndex];
|
|
19059
|
+
cells.push(cell ? renderCell(cell, ctx) : "");
|
|
19060
|
+
}
|
|
19061
|
+
return cells;
|
|
19062
|
+
});
|
|
19063
|
+
const lines = [];
|
|
19064
|
+
const headerRow = renderedRows[0];
|
|
19065
|
+
if (!headerRow)
|
|
19066
|
+
return null;
|
|
19067
|
+
lines.push(rowToLine(headerRow));
|
|
19068
|
+
lines.push(`| ${Array(colCount).fill("---").join(" | ")} |`);
|
|
19069
|
+
for (let rowIndex = 1;rowIndex < renderedRows.length; rowIndex++) {
|
|
19070
|
+
const row = renderedRows[rowIndex];
|
|
19071
|
+
if (row)
|
|
19072
|
+
lines.push(rowToLine(row));
|
|
19073
|
+
}
|
|
19074
|
+
return lines.join(`
|
|
19075
|
+
`);
|
|
19076
|
+
}
|
|
19077
|
+
function rowToLine(cells) {
|
|
19078
|
+
return `| ${cells.join(" | ")} |`;
|
|
19079
|
+
}
|
|
19080
|
+
function renderCell(cell, ctx) {
|
|
19081
|
+
const parts = [];
|
|
19082
|
+
for (const block of cell.blocks) {
|
|
19083
|
+
if (block.type === "paragraph") {
|
|
19084
|
+
const body = renderRuns(block.id, block.runs, ctx);
|
|
19085
|
+
if (body.length === 0)
|
|
19086
|
+
continue;
|
|
19087
|
+
parts.push(`${body} <!-- ${block.id} -->`);
|
|
19088
|
+
continue;
|
|
19089
|
+
}
|
|
19090
|
+
if (block.type === "table") {
|
|
19091
|
+
parts.push(renderNestedTable(block, ctx));
|
|
19092
|
+
}
|
|
19093
|
+
}
|
|
19094
|
+
return escapeCell(parts.join("<br>"));
|
|
19095
|
+
}
|
|
19096
|
+
function renderNestedTable(table, ctx) {
|
|
19097
|
+
const rows = table.rows.map((row) => row.cells.map((cell) => renderCell(cell, ctx)).join(" / "));
|
|
19098
|
+
return rows.join(" // ");
|
|
19099
|
+
}
|
|
19100
|
+
function escapeCell(text) {
|
|
19101
|
+
return text.replace(/\|/g, "\\|");
|
|
19102
|
+
}
|
|
19103
|
+
function sanitizeAltText(text) {
|
|
19104
|
+
return text.replace(/[\r\n]+/g, " ").replace(/\]/g, "\\]");
|
|
19105
|
+
}
|
|
19106
|
+
function renderCommentFootnotes(commentIndex, comments) {
|
|
19107
|
+
if (commentIndex.orderedIds.length === 0)
|
|
19108
|
+
return "";
|
|
19109
|
+
const byId = new Map(comments.map((comment) => [comment.id, comment]));
|
|
19110
|
+
const sorted = [...commentIndex.orderedIds].sort(commentIdCompare);
|
|
19111
|
+
const lines = [];
|
|
19112
|
+
for (const commentId of sorted) {
|
|
19113
|
+
const comment = byId.get(commentId);
|
|
19114
|
+
if (!comment)
|
|
19115
|
+
continue;
|
|
19116
|
+
const span = commentIndex.spanText.get(commentId) ?? "";
|
|
19117
|
+
lines.push(formatFootnote(comment, span));
|
|
19118
|
+
}
|
|
19119
|
+
return lines.join(`
|
|
19120
|
+
`);
|
|
19121
|
+
}
|
|
19122
|
+
function renderNoteDefinitions(notes, referenced) {
|
|
19123
|
+
if (referenced.size === 0)
|
|
19124
|
+
return "";
|
|
19125
|
+
const byId = new Map(notes.map((note) => [note.id, note]));
|
|
19126
|
+
const sorted = [...referenced].sort(noteIdCompare);
|
|
19127
|
+
const lines = [];
|
|
19128
|
+
for (const id of sorted) {
|
|
19129
|
+
const note = byId.get(id);
|
|
19130
|
+
const body = (note?.text ?? "").replace(/\s+/g, " ").trim();
|
|
19131
|
+
lines.push(`[^${id}]: ${body}`);
|
|
19132
|
+
}
|
|
19133
|
+
return lines.join(`
|
|
19134
|
+
`);
|
|
19135
|
+
}
|
|
19136
|
+
function noteIdCompare(left, right) {
|
|
19137
|
+
return numericIdCompare(left, right, /(\d+)$/);
|
|
19138
|
+
}
|
|
19139
|
+
function commentIdCompare(left, right) {
|
|
19140
|
+
return numericIdCompare(left, right, /^c(\d+)$/);
|
|
19141
|
+
}
|
|
19142
|
+
function numericIdCompare(left, right, pattern) {
|
|
19143
|
+
const leftMatch = left.match(pattern);
|
|
19144
|
+
const rightMatch = right.match(pattern);
|
|
19145
|
+
if (leftMatch?.[1] && rightMatch?.[1]) {
|
|
19146
|
+
return Number(leftMatch[1]) - Number(rightMatch[1]);
|
|
19147
|
+
}
|
|
19148
|
+
return left.localeCompare(right);
|
|
19149
|
+
}
|
|
19150
|
+
function formatFootnote(comment, spanText) {
|
|
19151
|
+
const quoted = quoteSpan(spanText);
|
|
19152
|
+
const reply = comment.parentId ? ` \u21B3 ${comment.parentId}` : "";
|
|
19153
|
+
const resolved = comment.resolved ? "\u2713 " : "";
|
|
19154
|
+
const body = comment.text.replace(/\s+/g, " ").trim();
|
|
19155
|
+
return `[^${comment.id}]: ${quoted} \u2014 ${resolved}${comment.author} (${comment.date})${reply}: ${body}`;
|
|
19156
|
+
}
|
|
19157
|
+
function quoteSpan(text) {
|
|
19158
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
19159
|
+
const escaped = collapsed.replace(/"/g, "\\\"");
|
|
19160
|
+
return `"${escaped}"`;
|
|
19161
|
+
}
|
|
19162
|
+
function sliceBlocks(blocks, from, to) {
|
|
19163
|
+
if (!from && !to)
|
|
19164
|
+
return blocks;
|
|
19165
|
+
const fromId = from ? blockIdForLocator(from, "from") : null;
|
|
19166
|
+
const toId = to ? blockIdForLocator(to, "to") : null;
|
|
19167
|
+
const fromIndex = fromId ? blocks.findIndex((b) => b.id === fromId) : 0;
|
|
19168
|
+
if (from && fromId && fromIndex === -1) {
|
|
19169
|
+
throw new MarkdownLocatorError(from, `--from ${from} not found at document top level`);
|
|
19170
|
+
}
|
|
19171
|
+
const toIndex = toId ? blocks.findIndex((b) => b.id === toId) : blocks.length - 1;
|
|
19172
|
+
if (to && toId && toIndex === -1) {
|
|
19173
|
+
throw new MarkdownLocatorError(to, `--to ${to} not found at document top level`);
|
|
19174
|
+
}
|
|
19175
|
+
if (toIndex < fromIndex)
|
|
19176
|
+
return [];
|
|
19177
|
+
return blocks.slice(fromIndex, toIndex + 1);
|
|
19178
|
+
}
|
|
19179
|
+
function blockIdForLocator(input, position) {
|
|
19180
|
+
let parsed;
|
|
19181
|
+
try {
|
|
19182
|
+
parsed = parseLocator(input);
|
|
19183
|
+
} catch (err) {
|
|
19184
|
+
if (err instanceof LocatorParseError) {
|
|
19185
|
+
throw new MarkdownLocatorError(input, err.message);
|
|
19186
|
+
}
|
|
19187
|
+
throw err;
|
|
19188
|
+
}
|
|
19189
|
+
switch (parsed.kind) {
|
|
19190
|
+
case "block":
|
|
19191
|
+
return parsed.blockId;
|
|
19192
|
+
case "blockSpan":
|
|
19193
|
+
return parsed.blockId;
|
|
19194
|
+
case "range":
|
|
19195
|
+
return position === "from" ? parsed.start.blockId : parsed.end.blockId;
|
|
19196
|
+
case "cell":
|
|
19197
|
+
return parsed.tableId;
|
|
19198
|
+
case "comment":
|
|
19199
|
+
case "image":
|
|
19200
|
+
case "hyperlink":
|
|
19201
|
+
throw new MarkdownLocatorError(input, `--${position} does not accept a ${parsed.kind} locator \u2014 use a paragraph or table locator`);
|
|
19202
|
+
}
|
|
19203
|
+
}
|
|
19204
|
+
var MarkdownLocatorError;
|
|
19205
|
+
var init_markdown = __esm(() => {
|
|
19206
|
+
init_core();
|
|
19207
|
+
MarkdownLocatorError = class MarkdownLocatorError extends Error {
|
|
19208
|
+
input;
|
|
19209
|
+
constructor(input, message) {
|
|
19210
|
+
super(message);
|
|
19211
|
+
this.input = input;
|
|
19212
|
+
this.name = "MarkdownLocatorError";
|
|
19213
|
+
}
|
|
19214
|
+
};
|
|
18582
19215
|
});
|
|
18583
19216
|
|
|
18584
19217
|
// src/cli/read/index.ts
|
|
@@ -18594,7 +19227,11 @@ async function run25(args) {
|
|
|
18594
19227
|
args,
|
|
18595
19228
|
allowPositionals: true,
|
|
18596
19229
|
options: {
|
|
18597
|
-
|
|
19230
|
+
markdown: { type: "boolean" },
|
|
19231
|
+
from: { type: "string" },
|
|
19232
|
+
to: { type: "string" },
|
|
19233
|
+
changes: { type: "boolean" },
|
|
19234
|
+
comments: { type: "boolean" },
|
|
18598
19235
|
help: { type: "boolean", short: "h" }
|
|
18599
19236
|
}
|
|
18600
19237
|
});
|
|
@@ -18608,29 +19245,72 @@ async function run25(args) {
|
|
|
18608
19245
|
const path = parsed.positionals[0];
|
|
18609
19246
|
if (!path)
|
|
18610
19247
|
return fail("USAGE", "Missing FILE argument", HELP25);
|
|
19248
|
+
const markdown = Boolean(parsed.values.markdown);
|
|
19249
|
+
const from = parsed.values.from;
|
|
19250
|
+
const to = parsed.values.to;
|
|
19251
|
+
const showChanges = Boolean(parsed.values.changes);
|
|
19252
|
+
const showComments = Boolean(parsed.values.comments);
|
|
19253
|
+
if (!markdown && (from || to || showChanges || showComments)) {
|
|
19254
|
+
return fail("USAGE", "--from, --to, --changes, and --comments require --markdown", HELP25);
|
|
19255
|
+
}
|
|
18611
19256
|
const view = await openOrFail(path);
|
|
18612
19257
|
if (typeof view === "number")
|
|
18613
19258
|
return view;
|
|
19259
|
+
if (markdown) {
|
|
19260
|
+
try {
|
|
19261
|
+
const rendered = renderMarkdown(view.doc, {
|
|
19262
|
+
from,
|
|
19263
|
+
to,
|
|
19264
|
+
showChanges,
|
|
19265
|
+
showComments
|
|
19266
|
+
});
|
|
19267
|
+
await writeStdout(rendered);
|
|
19268
|
+
return EXIT.OK;
|
|
19269
|
+
} catch (err) {
|
|
19270
|
+
if (err instanceof MarkdownLocatorError) {
|
|
19271
|
+
return fail("INVALID_LOCATOR", err.message);
|
|
19272
|
+
}
|
|
19273
|
+
throw err;
|
|
19274
|
+
}
|
|
19275
|
+
}
|
|
18614
19276
|
await enrichImageHashes(view);
|
|
18615
19277
|
await respond(view.doc);
|
|
18616
19278
|
return EXIT.OK;
|
|
18617
19279
|
}
|
|
18618
|
-
var HELP25 = `docx read \u2014 print AST as JSON
|
|
19280
|
+
var HELP25 = `docx read \u2014 print AST as JSON, or render document body as Markdown
|
|
18619
19281
|
|
|
18620
19282
|
Usage:
|
|
18621
19283
|
docx read FILE [options]
|
|
18622
19284
|
|
|
18623
19285
|
Options:
|
|
18624
|
-
--
|
|
18625
|
-
|
|
19286
|
+
--markdown Render the body as GitHub-flavored Markdown
|
|
19287
|
+
(instead of JSON). Locators are emitted as
|
|
19288
|
+
<!-- pN --> HTML comments after each paragraph and
|
|
19289
|
+
inside cell content (invisible in rendered view,
|
|
19290
|
+
but parseable from raw markdown).
|
|
19291
|
+
--from LOC Start markdown rendering at top-level block LOC (inclusive)
|
|
19292
|
+
--to LOC End markdown rendering at top-level block LOC (inclusive)
|
|
19293
|
+
Accepts pN, tN, tN:rRcC[:pK[:S-E]], pN:S-E, pN:S-pM:E.
|
|
19294
|
+
Cell/span/range locators collapse to their enclosing
|
|
19295
|
+
top-level block (the table or paragraph).
|
|
19296
|
+
--changes With --markdown, render tracked insertions/deletions as
|
|
19297
|
+
<ins>/<del> instead of producing the accepted view.
|
|
19298
|
+
--comments With --markdown, append [^cN] after each commented span
|
|
19299
|
+
and emit a footnote definition for each comment at the
|
|
19300
|
+
end of the output (author, date, body).
|
|
19301
|
+
-h, --help Show this help
|
|
18626
19302
|
|
|
18627
19303
|
Examples:
|
|
18628
19304
|
docx read input.docx
|
|
19305
|
+
docx read input.docx --markdown
|
|
19306
|
+
docx read input.docx --markdown --from p3 --to p20
|
|
19307
|
+
docx read input.docx --markdown --changes --comments
|
|
18629
19308
|
docx read input.docx | jq '.blocks[] | select(.type == "paragraph")'
|
|
18630
19309
|
`;
|
|
18631
19310
|
var init_read2 = __esm(() => {
|
|
18632
19311
|
init_core();
|
|
18633
19312
|
init_respond();
|
|
19313
|
+
init_markdown();
|
|
18634
19314
|
});
|
|
18635
19315
|
|
|
18636
19316
|
// src/cli/replace/replace-span.tsx
|
|
@@ -19293,19 +19973,11 @@ function countWords(text) {
|
|
|
19293
19973
|
const matches = text.match(/\S+/g);
|
|
19294
19974
|
return matches?.length ?? 0;
|
|
19295
19975
|
}
|
|
19296
|
-
function paragraphText2(paragraph) {
|
|
19297
|
-
let out = "";
|
|
19298
|
-
for (const run28 of paragraph.runs) {
|
|
19299
|
-
if (run28.type === "text")
|
|
19300
|
-
out += run28.text;
|
|
19301
|
-
}
|
|
19302
|
-
return out;
|
|
19303
|
-
}
|
|
19304
19976
|
function countWordsInBlocks(blocks) {
|
|
19305
19977
|
let total = 0;
|
|
19306
19978
|
for (const block of blocks) {
|
|
19307
19979
|
if (block.type === "paragraph") {
|
|
19308
|
-
total += countWords(
|
|
19980
|
+
total += countWords(paragraphText(block));
|
|
19309
19981
|
} else if (block.type === "table") {
|
|
19310
19982
|
for (const row of block.rows) {
|
|
19311
19983
|
for (const cell of row.cells) {
|
|
@@ -19316,24 +19988,8 @@ function countWordsInBlocks(blocks) {
|
|
|
19316
19988
|
}
|
|
19317
19989
|
return total;
|
|
19318
19990
|
}
|
|
19319
|
-
function findBlockById(blocks, blockId) {
|
|
19320
|
-
for (const block of blocks) {
|
|
19321
|
-
if (block.id === blockId)
|
|
19322
|
-
return block;
|
|
19323
|
-
if (block.type === "table") {
|
|
19324
|
-
for (const row of block.rows) {
|
|
19325
|
-
for (const cell of row.cells) {
|
|
19326
|
-
const inner = findBlockById(cell.blocks, blockId);
|
|
19327
|
-
if (inner)
|
|
19328
|
-
return inner;
|
|
19329
|
-
}
|
|
19330
|
-
}
|
|
19331
|
-
}
|
|
19332
|
-
}
|
|
19333
|
-
return null;
|
|
19334
|
-
}
|
|
19335
19991
|
function countWordsInParagraphSpan(paragraph, start, end) {
|
|
19336
|
-
const text =
|
|
19992
|
+
const text = paragraphText(paragraph);
|
|
19337
19993
|
const clampedEnd = Math.min(Math.max(end, 0), text.length);
|
|
19338
19994
|
const clampedStart = Math.min(Math.max(start, 0), clampedEnd);
|
|
19339
19995
|
return countWords(text.slice(clampedStart, clampedEnd));
|
|
@@ -19354,35 +20010,21 @@ function countWordsInRange(paragraphsInOrder, startBlockId, startOffset, endBloc
|
|
|
19354
20010
|
let total = 0;
|
|
19355
20011
|
const first = paragraphsInOrder[startIndex];
|
|
19356
20012
|
if (first) {
|
|
19357
|
-
total += countWordsInParagraphSpan(first, startOffset,
|
|
20013
|
+
total += countWordsInParagraphSpan(first, startOffset, paragraphText(first).length);
|
|
19358
20014
|
}
|
|
19359
20015
|
for (let index = startIndex + 1;index < endIndex; index++) {
|
|
19360
20016
|
const middle = paragraphsInOrder[index];
|
|
19361
20017
|
if (middle)
|
|
19362
|
-
total += countWords(
|
|
20018
|
+
total += countWords(paragraphText(middle));
|
|
19363
20019
|
}
|
|
19364
20020
|
const last = paragraphsInOrder[endIndex];
|
|
19365
20021
|
if (last)
|
|
19366
20022
|
total += countWordsInParagraphSpan(last, 0, endOffset);
|
|
19367
20023
|
return total;
|
|
19368
20024
|
}
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
if (block.type === "paragraph") {
|
|
19373
|
-
out.push(block);
|
|
19374
|
-
continue;
|
|
19375
|
-
}
|
|
19376
|
-
if (block.type === "table") {
|
|
19377
|
-
for (const row of block.rows) {
|
|
19378
|
-
for (const cell of row.cells) {
|
|
19379
|
-
out.push(...flattenParagraphs(cell.blocks));
|
|
19380
|
-
}
|
|
19381
|
-
}
|
|
19382
|
-
}
|
|
19383
|
-
}
|
|
19384
|
-
return out;
|
|
19385
|
-
}
|
|
20025
|
+
var init_count = __esm(() => {
|
|
20026
|
+
init_core();
|
|
20027
|
+
});
|
|
19386
20028
|
|
|
19387
20029
|
// src/cli/wc/index.ts
|
|
19388
20030
|
var exports_wc = {};
|
|
@@ -19443,7 +20085,7 @@ async function run28(args) {
|
|
|
19443
20085
|
if (!block) {
|
|
19444
20086
|
return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
|
|
19445
20087
|
}
|
|
19446
|
-
const words = block.type === "paragraph" ? countWords(
|
|
20088
|
+
const words = block.type === "paragraph" ? countWords(paragraphText(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
|
|
19447
20089
|
await respond({
|
|
19448
20090
|
ok: true,
|
|
19449
20091
|
operation: "wc",
|
|
@@ -19515,7 +20157,7 @@ async function run28(args) {
|
|
|
19515
20157
|
if (!block || block.type !== "paragraph") {
|
|
19516
20158
|
return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${composedId}`);
|
|
19517
20159
|
}
|
|
19518
|
-
const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(
|
|
20160
|
+
const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText(block));
|
|
19519
20161
|
await respond({
|
|
19520
20162
|
ok: true,
|
|
19521
20163
|
operation: "wc",
|
|
@@ -19573,11 +20215,12 @@ Examples:
|
|
|
19573
20215
|
var init_wc = __esm(() => {
|
|
19574
20216
|
init_core();
|
|
19575
20217
|
init_respond();
|
|
20218
|
+
init_count();
|
|
19576
20219
|
});
|
|
19577
20220
|
// package.json
|
|
19578
20221
|
var package_default = {
|
|
19579
20222
|
name: "bun-docx",
|
|
19580
|
-
version: "0.
|
|
20223
|
+
version: "0.6.0",
|
|
19581
20224
|
description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
|
|
19582
20225
|
keywords: [
|
|
19583
20226
|
"docx",
|