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.
Files changed (3) hide show
  1. package/README.md +34 -3
  2. package/dist/index.js +721 -78
  3. 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 → `![alt](imgN)`
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
- return { schemaVersion: 1, path, properties, blocks, comments };
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 image = readImageFromDrawing(view, child, state);
13941
- if (image)
13942
- return image;
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
- throw new Error(`Cannot emit run of type "${run9.type}" \u2014 image emission lives in the images command.`);
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 (paragraphText) => {
16491
- const haystack = ignoreCase ? paragraphText.toLowerCase() : paragraphText;
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: paragraphText.slice(cursor, cursor + needle.length)
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 (paragraphText) => {
16640
+ return (paragraphText2) => {
16509
16641
  const matches = [];
16510
16642
  regex.lastIndex = 0;
16511
- let result = regex.exec(paragraphText);
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(paragraphText);
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(paragraphText);
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 paragraphText = block.runs.map((run10) => run10.type === "text" ? run10.text : "").join("");
16533
- for (const span of matcher(paragraphText)) {
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 = TextRun | ImageRun | BreakRun | TabRun;
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: ["schemaVersion", "path", "properties", "blocks", "comments"],
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
- function paragraphText(paragraph) {
18504
- let out = "";
18505
- for (const run24 of paragraph.runs) {
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 += `![${alt}](${run25.id})`;
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
- text: { type: "boolean" },
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
- --text Render as human-readable text instead of JSON (NOT YET IMPLEMENTED)
18625
- -h, --help Show this help
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(paragraphText2(block));
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 = paragraphText2(paragraph);
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, paragraphText2(first).length);
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(paragraphText2(middle));
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
- function flattenParagraphs(blocks) {
19370
- const out = [];
19371
- for (const block of blocks) {
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(paragraphText2(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
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(paragraphText2(block));
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.5.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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-docx",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
5
5
  "keywords": [
6
6
  "docx",