docx-rs

repository·main·Indexed 19 days ago

https://github.com/bokuweb/docx-rs

A library for reading and writing .docx files using Rust, WebAssembly, and JavaScript. It supports document generation and parsing across native and web environments, including features for text layout, tables, media, page settings, and advanced elements like footnotes and tracked changes. The project provides the docx-rs crate for Rust and the docx-wasm package for Browser and Node.js applications.

Tokens
11.2K
Snippets
40
Records
49
Agent score
68%

What's inside docx-rs

  1. Supported capabilities of docx-rs

    main

    The Rust API provides comprehensive support for document generation and parsing, including:

    • Text & Layout: Paragraphs, runs, text formatting, borders, tabs, and breaks.
    • Structure: Tables, nested tables, numbering, styles, and theme colors.
    • Media: Inline and floating images.
    • Page Layout: Headers, footers, page settings, and sections.
    • Advanced Features: Hyperlinks, bookmarks, comments, footnotes, and tracked changes.
    • Metadata & Data: Tables of contents, structured data tags, custom properties, and custom XML.

    The WebAssembly (docx-wasm) package supports document generation and DOCX-to-JSON parsing for browser and Node.js applications, though support for the full OOXML specification is not exhaustive.

  2. Install docx-wasm for Browser or Node.js

    main

    To use the WebAssembly version of the library in JavaScript environments, install docx-wasm via pnpm. If you are working in a browser environment and want to save the generated file, you may also need file-saver.

    pnpm add docx-wasm
    
    # Also for browser examples
    pnpm add file-saver
  3. Upgrade the gaugo123/docx-rs fork

    main

    To update this fork with the latest changes from the upstream bokuweb/docx-rs repository, follow these steps:

    1. Fetch and rebase against the upstream main branch:
      git fetch upstream && git rebase upstream/main
    2. Resolve conflicts: Conflicts are only expected in Category B files (edits to upstream-owned files). Refer to the specific file notes to re-apply changes.
    3. Verify the build and tests (using single-threaded tests to avoid race conditions during rebase):
      cargo test -p docx-rs -- --test-threads=1
      cargo build -p docx-rs
    4. Push the updated branch:
      git push --force-with-lease origin feat/theme-color
    5. If using MDtoWord, rebuild the project:
      cargo update -p docx-rs && cargo make ci
    git fetch upstream && git rebase upstream/main
  4. JSON serialization format for images

    main

    When a Docx object is serialized to JSON (e.g., via serde_json), all images—including EMF files—are unified under a single images key.

    • There is no separate imagesEmf key.
    • Each entry in the images array is a tuple where the original bytes are base64-encoded.
    • For EMF files, the preview field in the JSON will be an empty byte array (represented as an empty base64 string).
  5. How document dependencies like comments and hyperlinks are handled

    main

    When reading or writing .docx files, docx-rs must manage dependencies such as comments and external hyperlinks.

    • Comments: The library traverses the document tree (including paragraphs, tables, and Table of Contents) to identify CommentStart nodes. It maps these to a central comments collection. If a document contains comments, the document_rels.has_comments flag is set to true to ensure compatibility with viewers like Word Online.
    • Hyperlinks: For external hyperlinks, the library extracts the relationship ID (rid) and the target path, storing them in the document_rels.hyperlinks collection to maintain valid file relationships.
    • Footnotes: Footnotes are collected as a distinct part of the document structure, separate from the main body text but linked via references.
  6. How image routing works in Docx

    main

    When adding images to a Docx object using add_image, the library performs automatic routing based on file extensions and magic bytes.

    • PNG files: The library generates a PNG preview. The preview field in the resulting image entry will contain the original PNG bytes.
    • EMF files: The library detects EMF files (via .emf extension or magic bytes). EMF files are treated as passthrough; they are stored in the images vector, but the preview field will be empty because no preview is generated for EMF.
    • Fallback: If a file is detected as EMF via magic bytes even if the extension is generic (e.g., .bin), it is still routed to the images vector with an empty preview.
  7. Implement the DocumentTreeVisitor trait to traverse DOCX elements

    main

    The DocumentTreeVisitor trait allows you to implement custom logic for traversing the DOCX document tree. By implementing this trait, you can intercept specific nodes (like paragraphs, runs, or pictures) as the library recurses through the document structure. This is useful for collecting metadata, finding specific elements, or extracting media dependencies.

    Because the trait provides default empty implementations for all methods, you only need to implement the hooks for the specific elements you are interested in.

    use docx_rs::DocumentTreeVisitor;
    // Note: Actual imports depend on your project structure
    
    struct MyVisitor;
    
    impl DocumentTreeVisitor for MyVisitor {
        fn visit_paragraph(&mut self, _paragraph: &mut Paragraph) {
            // Custom logic when a paragraph is encountered
        }
    
        fn visit_run(&mut self, _run: &mut Run) {
            // Custom logic when a run is encountered
        }
    }
  8. How media and relationships are managed during document packaging

    main

    When preparing a .docx package, the library distinguishes between physical media (the actual image bytes) and relationships (the XML pointers within specific parts like the body, headers, or footers).

    • MediaRegistry: A package-global store that ensures physical media bytes are stored only once. It deduplicates identical images using a combination of preferred IDs and content fingerprints (length + CRC32).
    • CollectedPart: A part-local collection of relationships and footnotes. While multiple CollectedPart instances (e.g., one for the body, one for the header) share a single MediaRegistry, they maintain their own unique relationship IDs to comply with the Open Packaging Conventions (OPC).

    This separation allows a single image to be referenced in both the body and the header without duplicating the bytes in the final ZIP file, while still maintaining valid, independent relationship entries for each XML part.

  9. Collect document part data (body, header, or footer)

    main

    To extract relationships and footnotes from a specific part of a document for packaging, use the specialized collection functions. These functions require a mutable reference to a MediaRegistry to handle global media deduplication.

    • Main Document: Use collect_document_part(&mut document, &mut registry) to gather relationships and footnotes from the main body.
    • Headers: Use collect_header_part(&mut header, &mut registry).
    • Footers: Use collect_footer_part(&mut footer, &mut registry).

    Each function returns a CollectedPart containing a list of relationships (mapping IDs to media paths) and footnotes.

    Note: If you only need footnotes without touching media, use collect_document_footnotes(&mut document).

    // Example of collecting data from a document and its header
    let mut registry = MediaRegistry::default();
    
    // Collect from main document
    let body_part = collect_document_part(&mut document, &mut registry);
    
    // Collect from header
    let header_part = collect_header_part(&mut header, &mut registry);
    
    // Finally, extract the physical media for the ZIP package
    let media_files = registry.into_media();
  10. Create and build a DOCX document with the `Docx` struct

    main

    The Docx struct is the primary entrypoint for constructing a .docx file. You can initialize a new document using Docx::new() and use a builder pattern to add content such as paragraphs, tables, styles, headers, footers, and sections.

    To finalize the document, you have two main options:

    1. build(): Consumes the Docx instance and returns an XMLDocx object containing all rendered XML parts in memory. This is useful if you need to manipulate the rendered parts before writing.
    2. pack<W>(writer): Consumes the Docx instance and streams the XML parts directly into a ZIP archive writer. This is more memory-efficient for large documents.

    Note: Adding elements like paragraphs or tables with numbering will automatically update the document relationships to include numberings.xml to ensure compatibility with Word Online.

    // Example workflow
    let docx = Docx::new()
        .add_paragraph(paragraph)
        .add_table(table)
        .styles(my_styles);
    
    // Option 1: Build to memory
    let xml_docx = docx.build();
    
    // Option 2: Stream directly to a file/writer
    let file = std::fs::File::create("output.docx")?;
    let mut zip_writer = zip::ZipWriter::new(file);
    docx.pack(&mut zip_writer)?;
  11. Write a .docx document with Rust

    main

    Use the Docx builder to construct a document. You can use Docx::pack(file) to write the package directly to an output file. If you need access to the rendered XML package before it is archived, use Docx::build().pack(...) instead.

    use docx_rs::*;
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let file = std::fs::File::create("./hello.docx")?;
    
        Docx::new()
            .add_paragraph(Paragraph::new().add_run(Run::new().add_text("Hello")))
            .pack(file)?;
    
        Ok(())
    }