#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
mod asset_provider;
mod content_types;
mod document;
mod numbering;
mod package;
mod properties;
mod rels;
mod styles;
mod symbol_fonts;
use std::io::{BufReader, Read, Seek};
use std::path::Path;
pub use docspec_core::EventSource;
pub use asset_provider::DocxAssetProvider;
use docspec_core::{Error, Result};
const _: for<'a> fn(&'a content_types::ContentTypes, &str) -> Option<&'a str> =
content_types::ContentTypes::lookup;
fn _image_rel_fields(r: &rels::ImageRel) -> (&str, bool) {
(&r.target, r.is_external)
}
const _: for<'a> fn(&'a rels::ImageRel) -> (&'a str, bool) = _image_rel_fields;
fn _use_docx_asset_provider_from_path(p: &Path) -> Result<asset_provider::DocxAssetProvider> {
asset_provider::DocxAssetProvider::from_path(p)
}
const _: fn(&Path) -> Result<asset_provider::DocxAssetProvider> =
_use_docx_asset_provider_from_path;
fn _use_docx_asset_provider_from_reader(
r: std::io::Cursor<Vec<u8>>,
) -> Result<asset_provider::DocxAssetProvider> {
asset_provider::DocxAssetProvider::from_reader(r)
}
const _: fn(std::io::Cursor<Vec<u8>>) -> Result<asset_provider::DocxAssetProvider> =
_use_docx_asset_provider_from_reader;
#[derive(Debug)]
pub struct DocxReader {
inner: document::DocumentReader,
}
impl DocxReader {
#[inline]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = std::fs::File::open(path.as_ref()).map_err(Error::from)?;
Self::from_reader(file)
}
#[inline]
pub fn from_reader<R: Read + Seek + Send + 'static>(reader: R) -> Result<Self> {
let (style_list, numbering, hyperlink_map, image_map, _content_types, stream) =
package::open_package(reader)?;
let xml = quick_xml::Reader::from_reader(BufReader::new(stream));
let data = document::DocxData {
style_list,
hyperlink_map,
numbering,
image_map,
};
Ok(Self {
inner: document::DocumentReader::from_xml_reader(xml, data),
})
}
}
impl EventSource for DocxReader {
#[inline]
fn next_event(&mut self) -> Result<Option<docspec_core::Event>> {
self.inner.next_event()
}
}
#[cfg(test)]
#[cfg(not(coverage))]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
#[test]
fn docx_reader_is_send_static() {
fn assert_send_static<T: Send + 'static>() {}
assert_send_static::<DocxReader>();
}
#[test]
fn docx_without_styles_emits_only_paragraphs() {
use std::io::{Cursor, Write as _};
use zip::ZipWriter;
let root_rels = r#"<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>"#;
let document_xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p><w:r><w:t>hi</w:t></w:r></w:p>
</w:body>
</w:document>"#;
let buf = Cursor::new(Vec::new());
let mut writer = ZipWriter::new(buf);
let options = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
writer.start_file("_rels/.rels", options).unwrap();
writer.write_all(root_rels.as_bytes()).unwrap();
writer.start_file("word/document.xml", options).unwrap();
writer.write_all(document_xml.as_bytes()).unwrap();
let zip_bytes = writer.finish().unwrap().into_inner();
let mut reader = DocxReader::from_reader(Cursor::new(zip_bytes)).unwrap();
let mut events = Vec::new();
loop {
match reader.next_event() {
Ok(Some(event)) => events.push(event),
Ok(None) => break,
Err(err) => panic!("unexpected error: {err:?}"),
}
}
assert_eq!(
events,
vec![
docspec_core::Event::StartDocument {
id: None,
language: None,
metadata: None,
},
docspec_core::Event::StartParagraph {
alignment: None,
id: None,
},
docspec_core::Event::Text {
content: "hi".to_string(),
},
docspec_core::Event::EndParagraph,
docspec_core::Event::EndDocument,
]
);
}
}