Skip to content

feat(markkit): bytes-in document API with DOCX and spreadsheet conversion - #9

Merged
chaliy merged 4 commits into
mainfrom
claude/project-thread-gtqhrp
Oct 12, 2026
Merged

chaliy merged 4 commits into
mainfrom
claude/project-thread-gtqhrp

Conversation

@chaliy

@chaliy chaliy commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Before: markkit had no way to convert document bytes to Markdown. DOCX went through docx-rs with no limits, and the only office path pulled in the HTTP client stack. cargo deny also failed on newly published advisories.

After:

  • Markkit::extract_document(bytes, DocumentHint) and extract_document_sync convert DOCX, XLSX/XLSM, XLS and ODS to Markdown. They return an Extraction with the title, the detected Format, warnings and stats.
  • The docx and spreadsheet features build without reqwest or hyper, so fetchkit can embed markkit as its Office backend.
  • cargo deny check and cargo audit pass.

How:

  • Format detection. Format::detect, sniff and from_file_name use the content type first, then the file extension, then magic bytes. Detection works with no format feature enabled.

  • DOCX. Read directly with zip and quick-xml; docx-rs is removed. Every part is decompressed with caps and parsed into a depth-limited XML tree.

  • Spreadsheets. Converted with calamine 0.36.

    • XLSX is streamed.
    • XLS and ODS are pre-scanned against max_spreadsheet_cells, so a tiny file cannot force a huge dense-grid allocation.
    • Hidden sheets are skipped by default, and formulas are not evaluated.
  • New MarkkitBuilder limits. max_archive_entries, max_archive_entry_size, max_archive_total_size, max_sheet_rows, max_sheet_cols, max_spreadsheet_cells and include_hidden_sheets.

  • Unsupported formats. PDF, PPTX, legacy .doc and XLSB are detected and return an explicit UnsupportedFormat error.

  • Advisories.

    • Removed pdf-extract, which was never called. This clears lopdf RUSTSEC-2026-0187 and the unmaintained ttf-parser. The pdf feature is now a dependency-free placeholder.
    • Upgraded pyo3 and pyo3-async-runtimes to 0.29 (RUSTSEC-2026-0176/0177).
    • Upgraded tabled in the bench crate to 0.22, which drops the unmaintained proc-macro-error2.
  • Security tests. They cover:

    • zip bombs
    • entry and total size caps
    • grid blow-ups
    • billion laughs
    • XXE
    • deep nesting
    • hidden sheets
    • fuzz-style proptests

    Fixtures are built in memory.

  • CI. A new step tests --no-default-features --features docx,spreadsheet and fails if an HTTP client appears in the dependency tree.

  • Docs. Specs, the threat model (T6, S8–S11), the README and the CHANGELOG are updated.

Tests:

  • cargo test --workspace passes.
  • cargo clippy -D warnings passes for both the default and the all-features builds.
  • The doc build and the release build pass.
  • cargo deny check and cargo audit pass.

Part of the fetchkit document conversion work (everruns/fetchkit#182). markkit will replace fetchkit's in-crate Office converter.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y2vd61x8kbPtmDxze9Q4Gi

chaliy and others added 4 commits October 12, 2026 18:28
…sion

- Markkit::extract_document / extract_document_sync taking DocumentHint
  { content_type, file_name } and returning Extraction { markdown, title,
  format, warnings, stats }; extract_bytes delegates to it.
- Format::detect (content type -> extension -> magic bytes), Format::sniff,
  Format::from_file_name; new Doc/Xls/Ods/Pptx variants, Format is
  non_exhaustive and serde-serializable.
- DOCX via zip + quick-xml (docx-rs dropped): headings, emphasis, links,
  numbering-based lists, tables, line breaks, core.xml title.
- XLSX/XLS/ODS via calamine 0.36: one section + GFM table per sheet,
  row/column caps with truncated_sheet warnings, hidden sheets skipped.
- Safety: bounded ZIP decompression (entry count, per-entry and total
  size), XLS/ODS grid pre-scan against dense-range amplification,
  streaming XLSX cells, XML depth cap, no entity expansion, catch_unwind.
- PDF now returns UnsupportedFormat instead of an empty string.
- docx/spreadsheet build without reqwest; HTML tests/examples gated on
  the html feature. CLI --file uses format detection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2vd61x8kbPtmDxze9Q4Gi
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2vd61x8kbPtmDxze9Q4Gi
… client

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2vd61x8kbPtmDxze9Q4Gi
Remove unused pdf-extract (pdf feature is a placeholder), clearing lopdf
RUSTSEC-2026-0187 and unmaintained ttf-parser. Upgrade pyo3 and
pyo3-async-runtimes to 0.29 (RUSTSEC-2026-0176/0177) and bench tabled to
0.22 (drops unmaintained proc-macro-error2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2vd61x8kbPtmDxze9Q4Gi
@chaliy
chaliy force-pushed the claude/project-thread-gtqhrp branch from 6ecdf1d to 51192e9 Compare October 12, 2026 18:29
@chaliy
chaliy merged commit f82b7b2 into main Oct 12, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant