Skip to main content

Docx

Struct Docx 

Source
pub struct Docx { /* private fields */ }
Expand description

A Word document opened as a template.

from_bytes opens a .docx file, the replace_* methods fill in its placeholders, and to_bytes or write_to give the result. Replacements apply to the main text and to every header, footer, footnote and endnote; see replace_text for how placeholders are matched.

Writing doesn’t consume the document, and a Docx can be cloned, so a template can be opened once and each output filled from a clone of it. Clones share the original file and copy only the parsed parts.

use ciclostile::Docx;

let template = Docx::from_bytes(std::fs::read("letter.docx")?)?;
for name in ["Ada", "Grace"] {
    let mut letter = template.clone();
    letter.replace_text([("{{name}}", name)]);
    std::fs::write(format!("letter-{name}.docx"), letter.to_bytes())?;
}

Implementations§

Source§

impl Docx

Source

pub fn from_bytes(bytes: impl Into<Bytes>) -> Result<Self, InvalidDocx>

Open a .docx file.

bytes can be anything that converts into Bytes without a copy, such as the Vec<u8> that std::fs::read returns or a &'static [u8]. Entries that are copied to the output are not copied in memory: the output shares them with bytes.

Only Transitional Office Open XML is supported, the format Word and LibreOffice write, with XML parts in UTF-8: Strict Office Open XML documents, encrypted (password-protected) documents and legacy .doc files are rejected. Templates (.dotx, .dotm) and macro-enabled documents (.docm) open too, and keep their content type once filled, so save them with the same kind of extension: Word refuses to open a .dotx saved as .docx. To produce a .docx, start from a .docx.

Only the parts the replacements need are decompressed and parsed: the content types, the relationships, the main document part and its headers, footers, footnotes and endnotes, the document settings, plus the comments and the glossary document, if any, to collect the ids of their drawings. Other entries, such as images, are copied without ever being decompressed.

§Limits

The parts read may decompress to 64 MiB in total and hold 4 million nodes (elements, runs of text, comments) together. Their elements may nest 512 levels deep, under at most 128 namespace declarations, with element and attribute names of at most 256 bytes. Input beyond these limits is rejected, so opening a document takes at most about 650 MB of memory, whatever the input.

§Errors

Returns InvalidDocx::Zip when bytes is not a ZIP archive that can be read and written back, or when a part that must be parsed can’t be decompressed (including when the parts read add up to more than 64 MiB); InvalidDocx::MissingPart when [Content_Types].xml, _rels/.rels, the main document part or a story part it refers to is missing; InvalidDocx::Xml when one of the parts read is malformed, or when they hold more than 4,000,000 nodes together; and InvalidDocx::NotWordprocessingMl when the file is not a Transitional WordprocessingML document, such as a spreadsheet, a presentation or a Strict OOXML document.

Source

pub fn replace_text<I, K, V>(&mut self, replacements: I) -> usize
where I: IntoIterator<Item = (K, V)>, K: AsRef<str>, V: AsRef<str>,

Replace every occurrence of each key of replacements with its value, and return the number of replacements made.

The keys are placeholders: plain text, such as {{name}}, matched exactly and case-sensitively in the text of each paragraph, as Word displays it. Word often stores text typed in one go as several runs - after a formatting change, a spelling check or an edit made on another day - so {{name}} may be stored as {{, name and }} with markup in between: it still matches. A placeholder never spans two paragraphs, nor anything in a paragraph that isn’t text: one interrupted by a tab, a line break, a picture, an equation, a footnote reference or a field is left as it is. Text deleted with tracked changes isn’t matched.

The main text is searched, including its tables, text boxes and content controls, and so are headers, footers, footnotes and endnotes. Other parts, such as comments, are left alone. Word stores each text box twice, as DrawingML and as VML for older readers, so a placeholder in a text box counts twice.

All pairs are applied in a single pass over the text as it was before the call: text inserted for a key is never searched again, for that key or another one, so replacing {{a}} with {{b}} and {{b}} with x in the same call turns {{a}} into {{b}}. Where two keys start at the same place, the longer one wins. Empty keys are ignored, and a key given more than once only uses its first value.

A value takes the formatting of the first character of the placeholder it replaces. In values, line breaks (\n, \r\n or \r) become line breaks in the paragraph and tabs become tab characters. Characters that XML can’t represent (most control characters) are dropped.

Each occurrence gets its own copy of its value, and the template decides how many occurrences there are, so a small template can make a large document. With untrusted templates, bound their size or the number of placeholders they hold.

Pairs can come from any collection of string-like pairs, such as an array of tuples, a Vec<(String, String)> or a map. A &Vec<(String, String)> yields references to tuples instead: pass pairs.iter().map(|(k, v)| (k, v)), or the Vec itself.

use std::collections::HashMap;

use ciclostile::Docx;

let mut docx = Docx::from_bytes(std::fs::read("template.docx")?)?;
let values = HashMap::from([
    ("{{name}}".to_owned(), "Ada Lovelace".to_owned()),
    ("{{address}}".to_owned(), "12 St James's Square\nLondon".to_owned()),
]);
let replaced = docx.replace_text(&values);
println!("{replaced} placeholders replaced");
Source

pub fn replace_with_image(&mut self, placeholder: &str, image: &Image) -> usize

Replace every occurrence of placeholder with image, and return the number of replacements made.

The placeholder is matched like a key of replace_text. The image takes its place inline, as part of the line of text, at the size set on image. Its file is added to the document once per call, however many placeholders it replaces. An empty placeholder matches nothing.

Each occurrence gets a drawing of its own, so a small template can make a large document: 200,000 image placeholders in a 2.4 KB .docx take 830 MB of memory to fill. With untrusted templates, bound their size or the number of placeholders they hold. An occurrence nested so deep, or under so many namespace declarations, that its drawing would exceed the limits of from_bytes is left as it is, and not counted.

When no placeholder is found, the document is left unchanged.

Source

pub fn replace_with_table( &mut self, placeholder: &str, table: &Table, ) -> Result<usize, InvalidPlaceholder>

Replace every paragraph that holds only placeholder with table, and return the number of paragraphs replaced.

The placeholder is matched like a key of replace_text, and must be the only content of its paragraph: the two are compared with the whitespace around each trimmed, non-breaking spaces included. The table takes the paragraph’s place, and keeps its bookmarks and comment anchors. Empty paragraphs are kept or added where Word requires them: after the table when the placeholder’s paragraph ended a section or isn’t followed by another paragraph (a table cell, for one, must end with a paragraph), and before it when it would follow another table (adjacent tables would merge). An empty placeholder matches nothing.

Each paragraph replaced gets a copy of the whole table (see Table for how many cells it has), so a small template can make a large document. With untrusted templates, bound their size or the number of placeholders they hold. A paragraph nested so deep, or under so many namespace declarations, that the table would exceed the limits of from_bytes is left as it is, and not counted.

§Errors

Returns InvalidPlaceholder when placeholder occurs in a paragraph that holds anything else. The document is then left unchanged: no paragraph is replaced, not even those that qualify.

Source

pub fn update_fields_on_open(&mut self)

Make Word offer to update the document’s fields when it opens the file, so that tables of contents and page references show the pages of the filled document.

Nothing else needs to be laid out again: a .docx file stores no pages. Word and LibreOffice paginate a document when they open it, so longer text simply pushes what follows onto the next pages. Only the cached results of fields depend on the layout: a .docx file stores the result each field had when it was last saved, so a table of contents or a page reference keeps the page numbers saved in the template, even where the replacements moved text to other pages, and Word only refreshes them when the fields are updated (F9).

This sets w:updateFields in the document settings (ECMA-376 Part 1, §17.15.1.90), creating the settings part if the document has none. Word then asks the reader, when opening the file, whether to update the fields: the reader has to accept the prompt, and Word then updates every field with its own layout. LibreOffice ignores the setting: there, update the fields with Tools > Update > Update All.

Calling it again changes nothing.

use ciclostile::Docx;

let mut docx = Docx::from_bytes(std::fs::read("report.docx")?)?;
docx.replace_text([("{{findings}}", "A long text that moves the conclusion.")]);
docx.update_fields_on_open();
std::fs::write("filled.docx", docx.to_bytes())?;
Source

pub fn write_to<W: Write + ?Sized>(&self, writer: &mut W) -> Result<()>

Write the document as a .docx file to writer.

Parts that no replacement modified are never re-serialized: their compressed entries are copied byte for byte, and uncompressed entries keep identical content but come out compressed. Modified parts keep the exact bytes of all the XML the replacements didn’t touch: attribute order, namespace prefixes, whitespace and comments included. Writing doesn’t change the document, which can be written again or modified further.

writer receives many small writes: wrap a file or a socket in a BufWriter, and flush it once write_to returns, so that an error from it isn’t lost when it is dropped.

use std::{
    fs::File,
    io::{BufWriter, Write},
};

let mut out = BufWriter::new(File::create("output.docx")?);
docx.write_to(&mut out)?;
out.flush()?;
§Errors

Returns the io::Error of writer when writing to it fails; writer may then hold part of the archive.

Source

pub fn to_bytes(&self) -> Bytes

Return the document as the content of a .docx file.

The same as write_to into a Vec<u8>, which can’t fail, and which the returned Bytes takes over without a copy.

Trait Implementations§

Source§

impl Clone for Docx

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Docx

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Docx

§

impl RefUnwindSafe for Docx

§

impl Send for Docx

§

impl Sync for Docx

§

impl Unpin for Docx

§

impl UnsafeUnpin for Docx

§

impl UnwindSafe for Docx

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.