easydoc-reader 0.1.0

DOCX/DOC document reader for easydoc-rust
Documentation

easydoc-reader

SAX-streaming DOCX/DOC reader with O(1) memory footprint

Crates.io docs.rs MSRV License

English | 简体中文

Overview · Capabilities · Architecture · Quick Start · Security · API · Upstream · Quality


Status: alpha pre-release (latest on crates.io) MSRV: Rust 1.88 Edition: 2024 Resolver: 3 Maturity: Alpha -- public API may change Last verified: 2026-08-11

1. Overview

easydoc-reader is a Rust crate for streaming DOCX (and legacy DOC) document reading, designed for O(1) memory usage regardless of document size. It is part of the easydoc-rust workspace and corresponds to the read layer of Java EasyExcel (com.alibaba.excel).

Dimension Value
Crate easydoc-reader
Status Alpha pre-release (latest on crates.io)
MSRV / Edition 1.88 / 2024
Unsafe policy forbid (workspace lint)
License Apache-2.0

1.1 What It Is

  • A SAX-style streaming DOCX reader built on quick-xml for O(1) memory parsing.
  • Extracts paragraphs, headings, tables (with merge), images (binary), multi-level lists, hyperlinks, nested tables, and OMML math formulas.
  • Provides four view modes (Plain, Annotated, Outline, Stats) for LLM-friendly document analysis.
  • Includes SSRF, ZIP bomb, and Zip Slip security guards.

1.2 What It Is Not

  • Not a DOCX editor -- use easydoc-writer for writing.
  • Not a Markdown converter -- use easydoc-markdown for conversion.
  • Not a 1:1 port of any single Java class; it combines concepts from multiple EasyExcel reader components.
  • Legacy DOC support depends on office_oxide boundaries and is not equivalent to DOCX coverage.

2. Capabilities

2.1 Document Format Support Matrix

Element DOCX Read DOC Read Evidence
Paragraphs Stable Partial sax.rs tests
Headings (H1-H6) Stable Partial sax.rs tests
Tables (with column/row merge) Stable Partial sax.rs tests
Images (binary extraction) Stable N/A image.rs tests
Lists (ordered / unordered, multi-level nesting) Stable N/A sax.rs + numbering.rs tests
Hyperlinks (URL resolution + SSRF check) Stable N/A sax.rs + security.rs tests
Nested tables Stable N/A sax.rs tests
OMML math formulas Stable N/A sax.rs tests
Page / column breaks Stable N/A sax.rs tests
Text styles (bold / italic / strikethrough) Stable N/A sax.rs tests

2.2 Status Definitions

Status Definition
Stable Public API, tests, and documentation complete
Partial Only explicitly listed subset available
N/A Not available for this format

2.3 View Modes

Mode Purpose Output
Plain Bare text extraction Paragraphs joined by newlines
Annotated Structural markers [段落 3], [表格 2: 3行x4列]
Outline Headings only Markdown-style # / ##
Stats Aggregate counts Paragraph / table / image / word counts

3. Architecture

DOCX file (ZIP archive)
        │
        ▼
ZIP validation (bomb / Zip Slip / entry limits)
        │
        ▼
word/document.xml extraction
        │
        ▼
quick-xml SAX parser (O(1) memory)
        │
        ├──► DocumentEvent stream (EventSink)
        └──► DocumentBlock tree (read_blocks)
        │
        ▼
ViewMode rendering (Plain / Annotated / Outline / Stats)

3.1 Crate Dependencies

flowchart TB
    USER["Application"] --> READER["easydoc-reader"]
    READER --> CORE["easydoc-core"]
    READER --> OXIDE["office_oxide"]
    READER --> QXML["quick-xml"]
    READER --> ZIP["zip"]

3.2 Key Types

Type Role
DocxSaxReader<R> Streaming SAX reader; generic over Read
DocReadBuilder Fluent builder for table extraction (do_read)
EventSink Trait for receiving DocumentEvent stream
ViewMode Enum selecting output format
SecurityPolicy Combined SSRF + ZIP limits guard
Numbering Parsed word/numbering.xml for list detection

4. Quick Start

4.1 Installation

[dependencies]
easydoc-reader = "0.1.0-alpha"

4.2 Streaming Read (Event-based)

use std::path::Path;
use easydoc_reader::DocxSaxReader;
use easydoc_core::ContentCollector;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut reader = DocxSaxReader::from_path(Path::new("report.docx"))?;
    let mut collector = ContentCollector::new();
    reader.read_events(&mut collector)?;
    let content = collector.into_content();

    for block in &content.blocks {
        println!("{:?}", block);
    }
    Ok(())
}

4.3 Block-based Read (with Math)

use std::path::Path;
use easydoc_reader::DocxSaxReader;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut reader = DocxSaxReader::from_path(Path::new("report.docx"))?;
    let blocks = reader.read_blocks()?;

    for block in &blocks {
        println!("{:?}", block);
    }
    Ok(())
}

4.4 View Mode Rendering

use std::path::Path;
use easydoc_reader::{DocxSaxReader, ViewMode, render_view};
use easydoc_core::ContentCollector;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut reader = DocxSaxReader::from_path(Path::new("report.docx"))?;
    let mut collector = ContentCollector::new();
    reader.read_events(&mut collector)?;
    let content = collector.into_content();

    let outline = render_view(&content, &ViewMode::Outline { max_level: 3 })?;
    println!("{}", outline);
    Ok(())
}

4.5 Typed Table Extraction

use easydoc_reader::DocReadBuilder;
use easydoc_core::DocxRow;

#[derive(Debug, DocxRow)]
struct Employee {
    #[easydoc(name = "Name")]
    name: String,
    #[easydoc(name = "Age")]
    age: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let employees: Vec<Employee> = DocReadBuilder::new("staff.docx").do_read()?;
    for emp in &employees {
        println!("{}: {}", emp.name, emp.age);
    }
    Ok(())
}

5. Security

5.1 Security Guards

Guard Default Protection
SsrfGuard Conservative Blocks private IPs, localhost, link-local; DNS resolution enabled
PackageLimits 100 MB total, 50 MB/entry, 100x ratio, 10k entries ZIP bomb and element explosion prevention
Zip Slip Always Rejects .. and absolute paths in ZIP entries

5.2 SSRF Protection Details

The SsrfGuard validates all hyperlinks extracted from DOCX documents:

  • Allowed schemes: http, https, mailto
  • Blocked hosts: localhost
  • Blocked IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 100.64.0.0/10, 0.0.0.0/8
  • Blocked IPv6: loopback, unspecified, unique-local (fc00::/7), link-local (fe80::/10), multicast (ff00::/8)
use easydoc_reader::security::SsrfGuard;

let guard = SsrfGuard::new();
assert!(guard.check_url("https://example.com").is_ok());
assert!(guard.check_url("http://127.0.0.1/admin").is_err());

5.3 Custom Security Policy

use easydoc_reader::security::SecurityPolicy;
use easydoc_reader::DocxSaxReader;
use std::path::Path;

let policy = SecurityPolicy::permissive(); // trusted input only
let reader = DocxSaxReader::from_path_with_security(
    Path::new("trusted.docx"),
    policy,
)?;

6. API Reference

6.1 Core API

Function / Type Purpose
DocxSaxReader::from_path(path) Open DOCX with default security
DocxSaxReader::from_path_with_security(path, policy) Open with custom security
DocxSaxReader::from_reader(reader) Wrap raw XML Read source
reader.read_events(sink) Stream DocumentEvents to EventSink
reader.read_blocks() Collect all blocks including Math
render_view(content, mode) Render DocumentContent as text
read_document(path) Convenience: read entire document
read_tables(path) Convenience: extract all tables
read_text(path) Convenience: extract plain text
DocReadBuilder::new(path).do_read::<T>() Typed table extraction

6.2 Error Model

Error Variant Scenario Source
DocError::Format XML parse failure, security violation quick-xml, security guards
DocError::Zip ZIP entry not found or corrupt zip crate
DocError::Io File I/O failure std::io::Error

7. Upstream Compatibility

7.1 Java EasyExcel Mapping

This crate corresponds to the read layer of Java EasyExcel. The design draws from multiple upstream components:

Upstream Component Rust Equivalent Notes
XlsxSaxAnalyser (concept) DocxSaxReader SAX streaming pattern adapted for DOCX
ExcelReader DocReadBuilder Fluent builder for typed reads
ReadListener EventSink Event callback interface
Upstream Capability Rust Status Evidence
Streaming read Stable DocxSaxReader tests
Typed row extraction Stable DocReadBuilder.do_read()
Event-based callbacks Stable EventSink trait

7.2 Differences from Java

  • No reflection: Rust uses derive macros (DocxRow) instead of Java reflection for typed extraction.
  • No partial sheet read: DOCX has no sheet concept; full document is streamed.
  • Math formulas: read_blocks() returns DocumentBlock::Math with raw OMML XML; read_events() drops math (no DocumentEvent::Math variant).

8. Quality & Testing

8.1 Unsafe Policy

This crate uses #![deny(unsafe_code)]. The workspace enforces unsafe_code = "forbid" via [workspace.lints.rust].

8.2 Test Categories

Category Scope Tool
Unit tests SAX parser, image, numbering, security, view modes cargo test
Security tests SSRF guard, ZIP bomb, Zip Slip, compression ratio cargo test
Property tests Input boundary validation proptest

8.3 Building & Testing

cargo check -p easydoc-reader
cargo test -p easydoc-reader
cargo clippy -p easydoc-reader -- -D warnings
cargo doc -p easydoc-reader --no-deps

Back to top · docs.rs · crates.io · Issues