anytomd 1.2.1 → 1.3.1

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 CHANGED
@@ -16,11 +16,11 @@ A pure Rust tool and library that converts various document formats into Markdow
16
16
 
17
17
  | Format | Extensions | Notes |
18
18
  |--------|-----------|-------|
19
- | DOCX | `.docx` | Headings, tables, lists, bold/italic, hyperlinks, images, text boxes |
20
- | PPTX | `.pptx` | Slides, tables, speaker notes, images, group shapes |
21
- | XLSX | `.xlsx` | Multi-sheet, date/time handling, images |
22
- | XLS | `.xls` | Legacy Excel (via calamine) |
23
- | HTML | `.html`, `.htm` | Full DOM: headings, tables, lists, links, blockquotes, code blocks |
19
+ | DOCX | `.docx` | Headings, tables (incl. merged/layout tables), lists, bold/italic, hyperlinks, images, text boxes |
20
+ | PPTX | `.pptx` | Slides, tables (incl. merged cells), speaker notes, images, group shapes |
21
+ | XLSX | `.xlsx` | Multi-sheet, date/time handling, images, single-pass table rendering |
22
+ | XLS | `.xls` | Legacy Excel (via calamine), single-pass table rendering |
23
+ | HTML | `.html`, `.htm` | Full DOM: headings, tables (incl. `colspan`/`rowspan`), lists, links, blockquotes, code blocks |
24
24
  | CSV | `.csv` | Converted to Markdown tables |
25
25
  | Jupyter Notebook | `.ipynb` | Markdown cells preserved, code cells in fenced blocks with language detection |
26
26
  | JSON | `.json` | Pretty-printed in fenced code blocks |
@@ -33,6 +33,8 @@ A pure Rust tool and library that converts various document formats into Markdow
33
33
 
34
34
  Format is auto-detected from magic bytes and file extension. ZIP-based formats (DOCX/PPTX/XLSX) are distinguished by inspecting internal archive structure.
35
35
 
36
+ Large XLSX/XLS sheets render Markdown and plain text together in one row pass. Workbook parsing still uses memory proportional to the worksheet, but conversion does not build a second full cell matrix before producing output.
37
+
36
38
  ## Conversion Examples
37
39
 
38
40
  ### CSV
@@ -117,6 +119,30 @@ Data Overview
117
119
  > Note: Test multilingual rendering.
118
120
  ```
119
121
 
122
+ ### Tables and merged cells
123
+
124
+ Every table — plain, merged, or used for page layout — becomes a single
125
+ pipe-delimited Markdown table of the table's true column count. Merged cells
126
+ (`gridSpan`/`vMerge` in DOCX, `colspan`/`rowspan` in HTML) are handled so no
127
+ content is lost:
128
+
129
+ - a horizontally spanned cell puts its text in the first column it covers and
130
+ leaves the spanned-over columns empty (empty-fill);
131
+ - a vertically merged (continuation) cell is left blank;
132
+ - a full-width row (a single cell spanning every column) is kept as a normal
133
+ grid row, empty-filled to the table width — it is **not** turned into a heading
134
+ or a `**Label:** value` line.
135
+
136
+ This is deliberately uniform: keeping a merged/layout table as one table (rather
137
+ than splitting it into headings and field lines) gives downstream consumers,
138
+ especially LLMs, a consistent structure. The one exception is a table that
139
+ contains a **nested table** — a nested table cannot live inside a single Markdown
140
+ cell, so such a table is linearized and each cell's text and nested table are
141
+ emitted as standalone blocks.
142
+
143
+ PPTX tables are likewise rendered as Markdown tables; merged cells keep the
144
+ correct column count and the spanning cell's text is not duplicated.
145
+
120
146
  ## Installation
121
147
 
122
148
  ### Rust (Cargo)
@@ -252,9 +278,19 @@ anytomd --plain-text document.docx
252
278
  # Plain text from stdin
253
279
  echo "Name,Age" | anytomd --format csv --plain-text
254
280
 
255
- # Auto image descriptions (just set GEMINI_API_KEY)
281
+ # Extract comments (DOCX/PPTX) into an appended Comments section
282
+ anytomd --extract-comments document.docx
283
+
284
+ # Image descriptions via Gemini (requires GEMINI_API_KEY env var)
256
285
  export GEMINI_API_KEY=your-key
257
- anytomd presentation.pptx
286
+ anytomd --gemini presentation.pptx
287
+
288
+ # Use a specific Gemini model
289
+ anytomd --gemini --gemini-model gemini-2.5-flash-lite presentation.pptx
290
+
291
+ # Resource limits (defaults: 8GiB input, 4GiB images, 16GiB zip)
292
+ anytomd --max-input-size 500MB document.docx
293
+ anytomd --max-zip-size 2GiB archive.xlsx
258
294
  ```
259
295
 
260
296
  ### Exit Codes
@@ -313,6 +349,47 @@ for (filename, bytes) in &result.images {
313
349
  }
314
350
  ```
315
351
 
352
+ ### Extracting Comments (DOCX / PPTX)
353
+
354
+ Setting `extract_comments` appends a `# Comments` section to the end of the
355
+ output (both Markdown and plain text). Each comment records the commenter, the
356
+ comment body, and the source — the commented-on text for DOCX, or the slide
357
+ label for PPTX (whose comments are anchored to a point, not a text span).
358
+ Replies are flattened and marked `(reply)`; the flag is a no-op for other
359
+ formats.
360
+
361
+ ```rust
362
+ use anytomd::{convert_file, ConversionOptions};
363
+
364
+ let options = ConversionOptions {
365
+ extract_comments: true,
366
+ ..Default::default()
367
+ };
368
+ let result = convert_file("document.docx", &options).unwrap();
369
+ println!("{}", result.markdown);
370
+ ```
371
+
372
+ Example appended section:
373
+
374
+ ```markdown
375
+ # Comments
376
+
377
+ ## 1
378
+ - **author**: Jane Smith (2024-01-15T09:30:00Z)
379
+ - **comment**: Please revise this paragraph.
380
+ - **source**: the quick brown fox
381
+ ```
382
+
383
+ Notes:
384
+
385
+ - **DOCX:** commenter identity comes from the comment author and date; the
386
+ source is the commented-on text (collapsed to one line, capped at 200
387
+ characters). Ranges in the body, headers, footers, footnotes, and endnotes
388
+ are all scanned. Threaded replies are detected via `commentsExtended.xml`.
389
+ - **PPTX:** both the legacy (`commentAuthors.xml`) and modern
390
+ (`authors.xml` / threaded) comment schemes are supported. The source is the
391
+ slide label (e.g. `Slide 2: Quarterly Results`).
392
+
316
393
  ### LLM-Based Image Descriptions
317
394
 
318
395
  anytomd can generate alt text for images using any LLM backend via the `ImageDescriber` trait. A built-in Google Gemini implementation is included.
@@ -431,6 +508,7 @@ pub async fn convert_bytes_async(
431
508
  | Field | Type | Default | Description |
432
509
  |-------|------|---------|-------------|
433
510
  | `extract_images` | `bool` | `false` | Extract embedded images into `result.images` |
511
+ | `extract_comments` | `bool` | `false` | Append a `# Comments` section (DOCX/PPTX only) |
434
512
  | `max_total_image_bytes` | `usize` | 50 MB | Hard cap for total extracted image bytes |
435
513
  | `max_input_bytes` | `usize` | 100 MB | Maximum input file size |
436
514
  | `max_uncompressed_zip_bytes` | `usize` | 500 MB | ZIP bomb guard |
package/anytomd_bg.js CHANGED
@@ -216,7 +216,7 @@ export function __wbg_new_typed_893dbec5fe999814(arg0, arg1) {
216
216
  const a = state0.a;
217
217
  state0.a = 0;
218
218
  try {
219
- return wasm_bindgen__convert__closures_____invoke__h2deb7db8ea6922c5(a, state0.b, arg0, arg1);
219
+ return wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined______(a, state0.b, arg0, arg1);
220
220
  } finally {
221
221
  state0.a = a;
222
222
  }
@@ -335,13 +335,13 @@ export function __wbg_value_414b42ce7b3eca22(arg0) {
335
335
  return ret;
336
336
  }
337
337
  export function __wbindgen_cast_0000000000000001(arg0, arg1) {
338
- // Cast intrinsic for `Closure(Closure { dtor_idx: 180, function: Function { arguments: [], shim_idx: 181, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
339
- const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h5f81f6b11f600e0f, wasm_bindgen__convert__closures_____invoke__hf70767533d8a8a0d);
338
+ // Cast intrinsic for `Closure(Closure { dtor_idx: 203, function: Function { arguments: [], shim_idx: 204, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
339
+ const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen_33c3f992c135746b___closure__destroy___dyn_core_9b3796e30d99ddb7___ops__function__FnMut_____Output_______, wasm_bindgen_33c3f992c135746b___convert__closures_____invoke______);
340
340
  return ret;
341
341
  }
342
342
  export function __wbindgen_cast_0000000000000002(arg0, arg1) {
343
- // Cast intrinsic for `Closure(Closure { dtor_idx: 205, function: Function { arguments: [Externref], shim_idx: 206, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
344
- const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hc635dc0582188d21, wasm_bindgen__convert__closures_____invoke__h889c317fc30b5049);
343
+ // Cast intrinsic for `Closure(Closure { dtor_idx: 224, function: Function { arguments: [Externref], shim_idx: 225, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
344
+ const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen_33c3f992c135746b___closure__destroy___dyn_core_9b3796e30d99ddb7___ops__function__FnMut__wasm_bindgen_33c3f992c135746b___JsValue____Output___core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_33c3f992c135746b___JsError___, wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___wasm_bindgen_33c3f992c135746b___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_33c3f992c135746b___JsError__);
345
345
  return ret;
346
346
  }
347
347
  export function __wbindgen_cast_0000000000000003(arg0, arg1) {
@@ -358,19 +358,19 @@ export function __wbindgen_init_externref_table() {
358
358
  table.set(offset + 2, true);
359
359
  table.set(offset + 3, false);
360
360
  }
361
- function wasm_bindgen__convert__closures_____invoke__hf70767533d8a8a0d(arg0, arg1) {
362
- wasm.wasm_bindgen__convert__closures_____invoke__hf70767533d8a8a0d(arg0, arg1);
361
+ function wasm_bindgen_33c3f992c135746b___convert__closures_____invoke______(arg0, arg1) {
362
+ wasm.wasm_bindgen_33c3f992c135746b___convert__closures_____invoke______(arg0, arg1);
363
363
  }
364
364
 
365
- function wasm_bindgen__convert__closures_____invoke__h889c317fc30b5049(arg0, arg1, arg2) {
366
- const ret = wasm.wasm_bindgen__convert__closures_____invoke__h889c317fc30b5049(arg0, arg1, arg2);
365
+ function wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___wasm_bindgen_33c3f992c135746b___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_33c3f992c135746b___JsError__(arg0, arg1, arg2) {
366
+ const ret = wasm.wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___wasm_bindgen_33c3f992c135746b___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_33c3f992c135746b___JsError__(arg0, arg1, arg2);
367
367
  if (ret[1]) {
368
368
  throw takeFromExternrefTable0(ret[0]);
369
369
  }
370
370
  }
371
371
 
372
- function wasm_bindgen__convert__closures_____invoke__h2deb7db8ea6922c5(arg0, arg1, arg2, arg3) {
373
- wasm.wasm_bindgen__convert__closures_____invoke__h2deb7db8ea6922c5(arg0, arg1, arg2, arg3);
372
+ function wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined______(arg0, arg1, arg2, arg3) {
373
+ wasm.wasm_bindgen_33c3f992c135746b___convert__closures_____invoke___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined___js_sys_74fce3adb247d89d___Function_fn_wasm_bindgen_33c3f992c135746b___JsValue_____wasm_bindgen_33c3f992c135746b___sys__Undefined______(arg0, arg1, arg2, arg3);
374
374
  }
375
375
 
376
376
 
package/anytomd_bg.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "anytomd",
3
3
  "type": "module",
4
4
  "description": "Pure Rust library that converts various document formats into Markdown",
5
- "version": "1.2.1",
5
+ "version": "1.3.1",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",