Skip to content

Commit

Permalink
Implement streaming writers (#765)
Browse files Browse the repository at this point in the history
* Make XMLBuilder generic

* Reduce allocations at XmlData display impl

* Implement streaming writers

- Extend BuildXML trait, add the streaming method
- Remove impls for Box<Ty>, as they can be implemented on the trait level
- Rewrite build() methods in chaining style, backed by apply_* helpers
- Remove quite a few allocations, though numbers still produce them
- Add spaces between children nodes, fix tests

* Add rustfmt.toml and format code

* Fix clippy warnings

* Expose the BuildXML trait without displaying its methods in hints
  • Loading branch information
xamgore authored Nov 5, 2024
1 parent 72637a4 commit 238d2bc
Show file tree
Hide file tree
Showing 186 changed files with 3,502 additions and 4,407 deletions.
37 changes: 36 additions & 1 deletion docx-core/src/documents/build_xml.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
use crate::xml_builder::XMLBuilder;
use std::io::Write;

pub trait BuildXML {
fn build(&self) -> Vec<u8>;
/// Write XML to the output stream.
#[doc(hidden)]
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>>;

#[doc(hidden)]
fn build(&self) -> Vec<u8> {
self.build_to(XMLBuilder::new(Vec::new()).into_inner().unwrap())
.expect("should write to buf")
.into_inner()
}
}

impl<'a, T: BuildXML> BuildXML for &'a T {
/// Building XML from `&T` is the same as from `T`.
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
(*self).build_to(stream)
}
}

impl<T: BuildXML> BuildXML for Box<T> {
/// Building XML from `Box<T>` is the same as from `T`.
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
(**self).build_to(stream)
}
}
20 changes: 12 additions & 8 deletions docx-core/src/documents/comments.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::Comment;
use crate::documents::BuildXML;
use crate::xml_builder::*;
use std::io::Write;

use serde::Serialize;

Expand Down Expand Up @@ -29,12 +30,16 @@ impl Comments {
}

impl BuildXML for Comments {
fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new().declaration(Some(true)).open_comments();
for c in &self.comments {
b = b.add_child(c)
}
b.close().build()
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.declaration(Some(true))?
.open_comments()?
.add_children(&self.comments)?
.close()?
.into_inner()
}
}

Expand All @@ -51,8 +56,7 @@ mod tests {
let b = Comments::new().build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:comments xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14" />"#
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:comments xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14" />"#
);
}
}
18 changes: 10 additions & 8 deletions docx-core/src/documents/comments_extended.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::Serialize;
use std::io::Write;

use super::*;
use crate::documents::BuildXML;
Expand All @@ -22,14 +23,15 @@ impl CommentsExtended {
}

impl BuildXML for CommentsExtended {
fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new();
b = b.open_comments_extended();

for c in &self.children {
b = b.add_child(c)
}
b.close().build()
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.open_comments_extended()?
.add_children(&self.children)?
.close()?
.into_inner()
}
}

Expand Down
41 changes: 19 additions & 22 deletions docx-core/src/documents/content_types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::io::Read;
use std::io::{Read, Write};
use xml::reader::{EventReader, XmlEvent};

use crate::documents::BuildXML;
Expand Down Expand Up @@ -153,28 +153,26 @@ impl Default for ContentTypes {
}

impl BuildXML for ContentTypes {
fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new();
let mut b = b
.declaration(None)
.open_types("http://schemas.openxmlformats.org/package/2006/content-types");

b = b
.add_default("png", "image/png")
.add_default("jpeg", "image/jpeg")
.add_default("jpg", "image/jpg")
.add_default("bmp", "image/bmp")
.add_default("gif", "image/gif")
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.declaration(None)?
.open_types("http://schemas.openxmlformats.org/package/2006/content-types")?
.add_default("png", "image/png")?
.add_default("jpeg", "image/jpeg")?
.add_default("jpg", "image/jpg")?
.add_default("bmp", "image/bmp")?
.add_default("gif", "image/gif")?
.add_default(
"rels",
"application/vnd.openxmlformats-package.relationships+xml",
)
.add_default("xml", "application/xml");

for (k, v) in self.types.iter() {
b = b.add_override(k, v);
}
b.close().build()
)?
.add_default("xml", "application/xml")?
.apply_each(self.types.iter(), |(k, v), b| b.add_override(k, v))?
.close()?
.into_inner()
}
}

Expand Down Expand Up @@ -213,8 +211,7 @@ mod tests {

#[test]
fn test_from_xml() {
let xml = r#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"></Override></Types>"#;
let xml = r#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"></Override></Types>"#;
let c = ContentTypes::from_xml(xml.as_bytes()).unwrap();
let mut types = BTreeMap::new();
types.insert(
Expand Down
19 changes: 11 additions & 8 deletions docx-core/src/documents/custom_item.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::documents::BuildXML;
use crate::xml_builder::XMLBuilder;
use crate::{ParseXmlError, XmlDocument};
use serde::ser::SerializeSeq;
use serde::Serialize;
use std::io::Write;
use std::str::FromStr;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -29,8 +31,13 @@ impl Serialize for CustomItem {
}

impl BuildXML for CustomItem {
fn build(&self) -> Vec<u8> {
self.0.to_string().as_bytes().to_vec()
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
let mut b = XMLBuilder::from(stream);
write!(b.inner_mut()?, "{}", self.0)?;
b.into_inner()
}
}

Expand All @@ -44,17 +51,13 @@ mod tests {
#[test]
fn test_custom_xml() {
let c = CustomItem::from_str(
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
<ds:schemaRefs>
<ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#,
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml"><ds:schemaRefs><ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#,
)
.unwrap();

assert_eq!(
c.0.to_string(),
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
<ds:schemaRefs>
<ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml"><ds:schemaRefs><ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#
);
assert_eq!(
serde_json::to_string(&c).unwrap(),
Expand Down
20 changes: 12 additions & 8 deletions docx-core/src/documents/custom_item_property.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::Serialize;
use std::io::Write;

use crate::documents::BuildXML;
use crate::xml_builder::*;
Expand All @@ -15,16 +16,19 @@ impl CustomItemProperty {
}

impl BuildXML for CustomItemProperty {
fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new();
b = b.declaration(Some(false));
b = b
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.declaration(Some(false))?
.open_data_store_item(
"http://schemas.openxmlformats.org/officeDocument/2006/customXml",
&format!("{{{}}}", self.id),
)
.open_data_store_schema_refs()
.close();
b.close().build()
)?
.open_data_store_schema_refs()?
.close()?
.close()?
.into_inner()
}
}
33 changes: 17 additions & 16 deletions docx-core/src/documents/custom_item_rels.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::Serialize;
use std::io::Write;

use crate::documents::BuildXML;
use crate::xml_builder::*;
Expand All @@ -21,21 +22,21 @@ impl CustomItemRels {
}

impl BuildXML for CustomItemRels {
fn build(&self) -> Vec<u8> {
let mut b = XMLBuilder::new();
b = b
.declaration(Some(true))
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships");

for id in 0..self.custom_item_count {
let id = id + 1;
b = b.relationship(
&format!("rId{}", id),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps",
&format!("itemProps{}.xml", id),
)
}

b.close().build()
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.declaration(Some(true))?
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships")?
.apply_each(0..self.custom_item_count, |id, b| {
b.relationship(
&format!("rId{}", id + 1),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps",
&format!("itemProps{}.xml", id + 1),
)
})?
.close()?
.into_inner()
}
}
23 changes: 14 additions & 9 deletions docx-core/src/documents/doc_props/app.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::Serialize;
use std::io::Write;

use crate::documents::BuildXML;
use crate::xml_builder::*;
Expand All @@ -14,13 +15,18 @@ impl AppProps {
}

impl BuildXML for AppProps {
fn build(&self) -> Vec<u8> {
let b = XMLBuilder::new();
let base = b.declaration(Some(true)).open_properties(
"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
);
base.close().build()
fn build_to<W: Write>(
&self,
stream: xml::writer::EventWriter<W>,
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
XMLBuilder::from(stream)
.declaration(Some(true))?
.open_properties(
"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
)?
.close()?
.into_inner()
}
}

Expand All @@ -38,8 +44,7 @@ mod tests {
let b = c.build();
assert_eq!(
str::from_utf8(&b).unwrap(),
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" />"#
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" />"#
);
}
}
Loading

0 comments on commit 238d2bc

Please sign in to comment.