docx-rs 0.4.21

A .docx file writer with Rust/WebAssembly.
Documentation
use std::collections::VecDeque;
use std::io::{BufReader, Read};

use quick_xml::encoding::Decoder;
use quick_xml::escape::unescape;
use quick_xml::events::{BytesEnd, BytesRef, BytesStart, BytesText, Event};
use quick_xml::Reader;
use quick_xml::XmlVersion;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OwnedName {
    pub local_name: String,
    pub namespace: Option<String>,
    pub prefix: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OwnedAttribute {
    pub name: OwnedName,
    pub value: String,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Namespace {
    mappings: Vec<(String, String)>,
}

impl Namespace {
    pub fn empty() -> Self {
        Self {
            mappings: Vec::new(),
        }
    }
}

impl IntoIterator for Namespace {
    type Item = (String, String);
    type IntoIter = std::vec::IntoIter<(String, String)>;

    fn into_iter(self) -> Self::IntoIter {
        self.mappings.into_iter()
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum XmlEvent {
    StartElement {
        name: OwnedName,
        attributes: Vec<OwnedAttribute>,
        namespace: Namespace,
    },
    EndElement {
        name: OwnedName,
    },
    Characters(String),
    Whitespace(String),
    EndDocument,
}

pub struct EventReader<R: Read> {
    reader: Reader<BufReader<R>>,
    buf: Vec<u8>,
    pending: VecDeque<XmlEvent>,
    finished: bool,
}

impl<R: Read> EventReader<R> {
    pub fn new(reader: R) -> Self {
        let mut reader = Reader::from_reader(BufReader::new(reader));
        {
            let config = reader.config_mut();
            config.trim_text(false);
            config.check_end_names = true;
            config.expand_empty_elements = false;
        }
        Self {
            reader,
            buf: Vec::new(),
            pending: VecDeque::new(),
            finished: false,
        }
    }

    pub fn next_event(&mut self) -> Result<XmlEvent, quick_xml::Error> {
        self.read_next()
    }

    fn read_next(&mut self) -> Result<XmlEvent, quick_xml::Error> {
        if let Some(event) = self.pending.pop_front() {
            return Ok(event);
        }

        loop {
            self.buf.clear();
            match self.reader.read_event_into(&mut self.buf)? {
                Event::Start(element) => {
                    let decoder = self.reader.decoder();
                    let event = Self::build_start_event(element, decoder)?;
                    return Ok(event);
                }
                Event::Empty(element) => {
                    let decoder = self.reader.decoder();
                    let event = Self::build_start_event(element, decoder)?;
                    if let XmlEvent::StartElement { name, .. } = &event {
                        self.pending
                            .push_back(XmlEvent::EndElement { name: name.clone() });
                    }
                    return Ok(event);
                }
                Event::End(element) => {
                    let name = build_name_from_end(&element)?;
                    return Ok(XmlEvent::EndElement { name });
                }
                Event::Text(text) => {
                    let text = decode_text(text)?;
                    return self.read_text_event(text);
                }
                Event::GeneralRef(reference) => {
                    let text = decode_reference(reference)?;
                    return self.read_text_event(text);
                }
                Event::CData(text) => {
                    let decoded = self.reader.decoder().decode(text.as_ref())?.into_owned();
                    return Ok(XmlEvent::Characters(decoded));
                }
                Event::Eof => {
                    self.finished = true;
                    return Ok(XmlEvent::EndDocument);
                }
                Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_) => {
                    // Skip non-structural events
                }
            }
        }
    }

    fn read_text_event(&mut self, mut text: String) -> Result<XmlEvent, quick_xml::Error> {
        loop {
            self.buf.clear();
            match self.reader.read_event_into(&mut self.buf)? {
                Event::Text(next_text) => {
                    text.push_str(&decode_text(next_text)?);
                }
                Event::GeneralRef(reference) => {
                    text.push_str(&decode_reference(reference)?);
                }
                Event::Start(element) => {
                    let decoder = self.reader.decoder();
                    let event = Self::build_start_event(element, decoder)?;
                    self.pending.push_back(event);
                    break;
                }
                Event::Empty(element) => {
                    let decoder = self.reader.decoder();
                    let event = Self::build_start_event(element, decoder)?;
                    let end_event = if let XmlEvent::StartElement { name, .. } = &event {
                        Some(XmlEvent::EndElement { name: name.clone() })
                    } else {
                        None
                    };
                    self.pending.push_back(event);
                    if let Some(end_event) = end_event {
                        self.pending.push_back(end_event);
                    }
                    break;
                }
                Event::End(element) => {
                    let name = build_name_from_end(&element)?;
                    self.pending.push_back(XmlEvent::EndElement { name });
                    break;
                }
                Event::CData(text) => {
                    let decoded = self.reader.decoder().decode(text.as_ref())?.into_owned();
                    self.pending.push_back(XmlEvent::Characters(decoded));
                    break;
                }
                Event::Eof => {
                    self.pending.push_back(XmlEvent::EndDocument);
                    break;
                }
                Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_) => {
                    break;
                }
            }
        }

        if text.chars().all(char::is_whitespace) {
            Ok(XmlEvent::Whitespace(text))
        } else {
            Ok(XmlEvent::Characters(text))
        }
    }

    fn build_start_event(
        element: BytesStart<'_>,
        decoder: Decoder,
    ) -> Result<XmlEvent, quick_xml::Error> {
        let name = build_name_from_start(&element)?;
        let attributes = build_attributes(&element, decoder)?;
        Ok(XmlEvent::StartElement {
            name,
            attributes,
            namespace: Namespace::empty(),
        })
    }
}

impl<R: Read> Iterator for EventReader<R> {
    type Item = Result<XmlEvent, quick_xml::Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.finished {
            return None;
        }
        match self.read_next() {
            Ok(XmlEvent::EndDocument) => {
                self.finished = true;
                Some(Ok(XmlEvent::EndDocument))
            }
            Ok(event) => Some(Ok(event)),
            Err(e) => {
                self.finished = true;
                Some(Err(e))
            }
        }
    }
}

fn build_name_from_start(element: &BytesStart<'_>) -> Result<OwnedName, quick_xml::Error> {
    let name = element.name();
    Ok(split_qname(name.as_ref()))
}

fn build_name_from_end(element: &BytesEnd<'_>) -> Result<OwnedName, quick_xml::Error> {
    let name = element.name();
    Ok(split_qname(name.as_ref()))
}

fn decode_text(text: BytesText<'_>) -> Result<String, quick_xml::Error> {
    Ok(text.xml_content(XmlVersion::Implicit1_0)?.into_owned())
}

fn decode_reference(reference: BytesRef<'_>) -> Result<String, quick_xml::Error> {
    let reference = reference.xml_content(XmlVersion::Implicit1_0)?;
    let escaped = format!("&{reference};");
    Ok(unescape(&escaped)?.into_owned())
}

fn split_qname(raw: &[u8]) -> OwnedName {
    let text = String::from_utf8_lossy(raw).into_owned();
    if let Some(idx) = text.find(':') {
        let prefix = text[..idx].to_string();
        let local = text[idx + 1..].to_string();
        OwnedName {
            local_name: local,
            namespace: None,
            prefix: Some(prefix),
        }
    } else {
        OwnedName {
            local_name: text,
            namespace: None,
            prefix: None,
        }
    }
}

fn build_attributes(
    element: &BytesStart<'_>,
    decoder: Decoder,
) -> Result<Vec<OwnedAttribute>, quick_xml::Error> {
    let mut attributes = Vec::new();
    for attr_result in element.attributes().with_checks(false) {
        let attr = attr_result.map_err(quick_xml::Error::from)?;
        let value = attr
            .decoded_and_normalized_value(XmlVersion::Implicit1_0, decoder)?
            .into_owned();
        let name = split_qname(attr.key.as_ref());
        attributes.push(OwnedAttribute { name, value });
    }
    Ok(attributes)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn decodes_xml_entities_in_text_and_attributes() {
        let xml = r#"<w:t w:val="A &amp; B">A &amp; B</w:t>"#;
        let mut reader = EventReader::new(xml.as_bytes());

        let start = reader.next_event().unwrap();
        match start {
            XmlEvent::StartElement {
                name, attributes, ..
            } => {
                assert_eq!(name.prefix.as_deref(), Some("w"));
                assert_eq!(name.local_name, "t");
                assert_eq!(attributes[0].name.prefix.as_deref(), Some("w"));
                assert_eq!(attributes[0].name.local_name, "val");
                assert_eq!(attributes[0].value, "A & B");
            }
            event => panic!("expected start element, got {event:?}"),
        }

        assert_eq!(
            reader.next_event().unwrap(),
            XmlEvent::Characters("A & B".to_string())
        );
        assert!(matches!(
            reader.next_event().unwrap(),
            XmlEvent::EndElement { name } if name.local_name == "t"
        ));
        assert_eq!(reader.next_event().unwrap(), XmlEvent::EndDocument);
    }
}