Skip to content

Commit 238d2bc

Browse files
authored
Implement streaming writers (#765)
* 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
1 parent 72637a4 commit 238d2bc

File tree

186 files changed

+3502
-4407
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

186 files changed

+3502
-4407
lines changed

docx-core/src/documents/build_xml.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1+
use crate::xml_builder::XMLBuilder;
2+
use std::io::Write;
3+
14
pub trait BuildXML {
2-
fn build(&self) -> Vec<u8>;
5+
/// Write XML to the output stream.
6+
#[doc(hidden)]
7+
fn build_to<W: Write>(
8+
&self,
9+
stream: xml::writer::EventWriter<W>,
10+
) -> xml::writer::Result<xml::writer::EventWriter<W>>;
11+
12+
#[doc(hidden)]
13+
fn build(&self) -> Vec<u8> {
14+
self.build_to(XMLBuilder::new(Vec::new()).into_inner().unwrap())
15+
.expect("should write to buf")
16+
.into_inner()
17+
}
18+
}
19+
20+
impl<'a, T: BuildXML> BuildXML for &'a T {
21+
/// Building XML from `&T` is the same as from `T`.
22+
fn build_to<W: Write>(
23+
&self,
24+
stream: xml::writer::EventWriter<W>,
25+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
26+
(*self).build_to(stream)
27+
}
28+
}
29+
30+
impl<T: BuildXML> BuildXML for Box<T> {
31+
/// Building XML from `Box<T>` is the same as from `T`.
32+
fn build_to<W: Write>(
33+
&self,
34+
stream: xml::writer::EventWriter<W>,
35+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
36+
(**self).build_to(stream)
37+
}
338
}

docx-core/src/documents/comments.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::Comment;
22
use crate::documents::BuildXML;
33
use crate::xml_builder::*;
4+
use std::io::Write;
45

56
use serde::Serialize;
67

@@ -29,12 +30,16 @@ impl Comments {
2930
}
3031

3132
impl BuildXML for Comments {
32-
fn build(&self) -> Vec<u8> {
33-
let mut b = XMLBuilder::new().declaration(Some(true)).open_comments();
34-
for c in &self.comments {
35-
b = b.add_child(c)
36-
}
37-
b.close().build()
33+
fn build_to<W: Write>(
34+
&self,
35+
stream: xml::writer::EventWriter<W>,
36+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
37+
XMLBuilder::from(stream)
38+
.declaration(Some(true))?
39+
.open_comments()?
40+
.add_children(&self.comments)?
41+
.close()?
42+
.into_inner()
3843
}
3944
}
4045

@@ -51,8 +56,7 @@ mod tests {
5156
let b = Comments::new().build();
5257
assert_eq!(
5358
str::from_utf8(&b).unwrap(),
54-
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
55-
<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" />"#
59+
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" />"#
5660
);
5761
}
5862
}

docx-core/src/documents/comments_extended.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::Serialize;
2+
use std::io::Write;
23

34
use super::*;
45
use crate::documents::BuildXML;
@@ -22,14 +23,15 @@ impl CommentsExtended {
2223
}
2324

2425
impl BuildXML for CommentsExtended {
25-
fn build(&self) -> Vec<u8> {
26-
let mut b = XMLBuilder::new();
27-
b = b.open_comments_extended();
28-
29-
for c in &self.children {
30-
b = b.add_child(c)
31-
}
32-
b.close().build()
26+
fn build_to<W: Write>(
27+
&self,
28+
stream: xml::writer::EventWriter<W>,
29+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
30+
XMLBuilder::from(stream)
31+
.open_comments_extended()?
32+
.add_children(&self.children)?
33+
.close()?
34+
.into_inner()
3335
}
3436
}
3537

docx-core/src/documents/content_types.rs

+19-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use serde::{Deserialize, Serialize};
22
use std::collections::BTreeMap;
3-
use std::io::Read;
3+
use std::io::{Read, Write};
44
use xml::reader::{EventReader, XmlEvent};
55

66
use crate::documents::BuildXML;
@@ -153,28 +153,26 @@ impl Default for ContentTypes {
153153
}
154154

155155
impl BuildXML for ContentTypes {
156-
fn build(&self) -> Vec<u8> {
157-
let b = XMLBuilder::new();
158-
let mut b = b
159-
.declaration(None)
160-
.open_types("http://schemas.openxmlformats.org/package/2006/content-types");
161-
162-
b = b
163-
.add_default("png", "image/png")
164-
.add_default("jpeg", "image/jpeg")
165-
.add_default("jpg", "image/jpg")
166-
.add_default("bmp", "image/bmp")
167-
.add_default("gif", "image/gif")
156+
fn build_to<W: Write>(
157+
&self,
158+
stream: xml::writer::EventWriter<W>,
159+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
160+
XMLBuilder::from(stream)
161+
.declaration(None)?
162+
.open_types("http://schemas.openxmlformats.org/package/2006/content-types")?
163+
.add_default("png", "image/png")?
164+
.add_default("jpeg", "image/jpeg")?
165+
.add_default("jpg", "image/jpg")?
166+
.add_default("bmp", "image/bmp")?
167+
.add_default("gif", "image/gif")?
168168
.add_default(
169169
"rels",
170170
"application/vnd.openxmlformats-package.relationships+xml",
171-
)
172-
.add_default("xml", "application/xml");
173-
174-
for (k, v) in self.types.iter() {
175-
b = b.add_override(k, v);
176-
}
177-
b.close().build()
171+
)?
172+
.add_default("xml", "application/xml")?
173+
.apply_each(self.types.iter(), |(k, v), b| b.add_override(k, v))?
174+
.close()?
175+
.into_inner()
178176
}
179177
}
180178

@@ -213,8 +211,7 @@ mod tests {
213211

214212
#[test]
215213
fn test_from_xml() {
216-
let xml = r#"<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
217-
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"></Override></Types>"#;
214+
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>"#;
218215
let c = ContentTypes::from_xml(xml.as_bytes()).unwrap();
219216
let mut types = BTreeMap::new();
220217
types.insert(

docx-core/src/documents/custom_item.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::documents::BuildXML;
2+
use crate::xml_builder::XMLBuilder;
23
use crate::{ParseXmlError, XmlDocument};
34
use serde::ser::SerializeSeq;
45
use serde::Serialize;
6+
use std::io::Write;
57
use std::str::FromStr;
68

79
#[derive(Debug, Clone)]
@@ -29,8 +31,13 @@ impl Serialize for CustomItem {
2931
}
3032

3133
impl BuildXML for CustomItem {
32-
fn build(&self) -> Vec<u8> {
33-
self.0.to_string().as_bytes().to_vec()
34+
fn build_to<W: Write>(
35+
&self,
36+
stream: xml::writer::EventWriter<W>,
37+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
38+
let mut b = XMLBuilder::from(stream);
39+
write!(b.inner_mut()?, "{}", self.0)?;
40+
b.into_inner()
3441
}
3542
}
3643

@@ -44,17 +51,13 @@ mod tests {
4451
#[test]
4552
fn test_custom_xml() {
4653
let c = CustomItem::from_str(
47-
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
48-
<ds:schemaRefs>
49-
<ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#,
54+
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>"#,
5055
)
5156
.unwrap();
5257

5358
assert_eq!(
5459
c.0.to_string(),
55-
r#"<ds:datastoreItem ds:itemID="{06AC5857-5C65-A94A-BCEC-37356A209BC3}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml">
56-
<ds:schemaRefs>
57-
<ds:schemaRef ds:uri="https://hoge.com"></ds:schemaRef></ds:schemaRefs></ds:datastoreItem>"#
60+
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>"#
5861
);
5962
assert_eq!(
6063
serde_json::to_string(&c).unwrap(),

docx-core/src/documents/custom_item_property.rs

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::Serialize;
2+
use std::io::Write;
23

34
use crate::documents::BuildXML;
45
use crate::xml_builder::*;
@@ -15,16 +16,19 @@ impl CustomItemProperty {
1516
}
1617

1718
impl BuildXML for CustomItemProperty {
18-
fn build(&self) -> Vec<u8> {
19-
let mut b = XMLBuilder::new();
20-
b = b.declaration(Some(false));
21-
b = b
19+
fn build_to<W: Write>(
20+
&self,
21+
stream: xml::writer::EventWriter<W>,
22+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
23+
XMLBuilder::from(stream)
24+
.declaration(Some(false))?
2225
.open_data_store_item(
2326
"http://schemas.openxmlformats.org/officeDocument/2006/customXml",
2427
&format!("{{{}}}", self.id),
25-
)
26-
.open_data_store_schema_refs()
27-
.close();
28-
b.close().build()
28+
)?
29+
.open_data_store_schema_refs()?
30+
.close()?
31+
.close()?
32+
.into_inner()
2933
}
3034
}
+17-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::Serialize;
2+
use std::io::Write;
23

34
use crate::documents::BuildXML;
45
use crate::xml_builder::*;
@@ -21,21 +22,21 @@ impl CustomItemRels {
2122
}
2223

2324
impl BuildXML for CustomItemRels {
24-
fn build(&self) -> Vec<u8> {
25-
let mut b = XMLBuilder::new();
26-
b = b
27-
.declaration(Some(true))
28-
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships");
29-
30-
for id in 0..self.custom_item_count {
31-
let id = id + 1;
32-
b = b.relationship(
33-
&format!("rId{}", id),
34-
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps",
35-
&format!("itemProps{}.xml", id),
36-
)
37-
}
38-
39-
b.close().build()
25+
fn build_to<W: Write>(
26+
&self,
27+
stream: xml::writer::EventWriter<W>,
28+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
29+
XMLBuilder::from(stream)
30+
.declaration(Some(true))?
31+
.open_relationships("http://schemas.openxmlformats.org/package/2006/relationships")?
32+
.apply_each(0..self.custom_item_count, |id, b| {
33+
b.relationship(
34+
&format!("rId{}", id + 1),
35+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps",
36+
&format!("itemProps{}.xml", id + 1),
37+
)
38+
})?
39+
.close()?
40+
.into_inner()
4041
}
4142
}

docx-core/src/documents/doc_props/app.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::Serialize;
2+
use std::io::Write;
23

34
use crate::documents::BuildXML;
45
use crate::xml_builder::*;
@@ -14,13 +15,18 @@ impl AppProps {
1415
}
1516

1617
impl BuildXML for AppProps {
17-
fn build(&self) -> Vec<u8> {
18-
let b = XMLBuilder::new();
19-
let base = b.declaration(Some(true)).open_properties(
20-
"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
21-
"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
22-
);
23-
base.close().build()
18+
fn build_to<W: Write>(
19+
&self,
20+
stream: xml::writer::EventWriter<W>,
21+
) -> xml::writer::Result<xml::writer::EventWriter<W>> {
22+
XMLBuilder::from(stream)
23+
.declaration(Some(true))?
24+
.open_properties(
25+
"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
26+
"http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
27+
)?
28+
.close()?
29+
.into_inner()
2430
}
2531
}
2632

@@ -38,8 +44,7 @@ mod tests {
3844
let b = c.build();
3945
assert_eq!(
4046
str::from_utf8(&b).unwrap(),
41-
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
42-
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" />"#
47+
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" />"#
4348
);
4449
}
4550
}

0 commit comments

Comments
 (0)