diff --git a/docx-core/examples/data_binding.rs b/docx-core/examples/data_binding.rs new file mode 100644 index 0000000..842db3c --- /dev/null +++ b/docx-core/examples/data_binding.rs @@ -0,0 +1,23 @@ +use docx_rs::*; + +pub fn main() -> Result<(), DocxError> { + let path = std::path::Path::new("./output/data_binding.docx"); + let file = std::fs::File::create(&path).unwrap(); + Docx::new() + .add_paragraph( + Paragraph::new() + .add_structured_data_tag( + StructuredDataTag::new().data_binding(DataBinding::new().xpath("/root/item1")), + ) + .add_structured_data_tag( + StructuredDataTag::new().data_binding(DataBinding::new().xpath("/root/item2")), + ), + ) + .add_custom_item( + "06AC5857-5C65-A94A-BCEC-37356A209BC3", + "Hello World!", + ) + .build() + .pack(file)?; + Ok(()) +} diff --git a/docx-core/src/documents/custom_item.rs b/docx-core/src/documents/custom_item.rs index f56fb87..91580a0 100644 --- a/docx-core/src/documents/custom_item.rs +++ b/docx-core/src/documents/custom_item.rs @@ -44,19 +44,17 @@ mod tests { #[test] fn test_custom_xml() { let c = CustomItem::from_str( - r#" - - - - - "#, + r#" + +"#, ) .unwrap(); assert_eq!( c.0.to_string(), - "\n \n \n \n\n \n\n\n" + r#" + +"# ); assert_eq!( serde_json::to_string(&c).unwrap(), diff --git a/docx-core/src/documents/elements/data_binding.rs b/docx-core/src/documents/elements/data_binding.rs new file mode 100644 index 0000000..82ab173 --- /dev/null +++ b/docx-core/src/documents/elements/data_binding.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use crate::documents::*; +use crate::xml_builder::*; + +#[derive(Serialize, Debug, Clone, PartialEq, Default)] +pub struct DataBinding { + pub xpath: Option, + pub prefix_mappings: Option, + pub store_item_id: Option, +} + +impl DataBinding { + pub fn new() -> Self { + Default::default() + } + + pub fn xpath(mut self, xpath: impl Into) -> Self { + self.xpath = Some(xpath.into()); + self + } + + pub fn prefix_mappings(mut self, m: impl Into) -> Self { + self.prefix_mappings = Some(m.into()); + self + } + + pub fn store_item_id(mut self, id: impl Into) -> Self { + self.store_item_id = Some(id.into()); + self + } +} + +impl BuildXML for DataBinding { + fn build(&self) -> Vec { + XMLBuilder::new() + .data_binding( + self.xpath.as_ref(), + self.prefix_mappings.as_ref(), + self.store_item_id.as_ref(), + ) + .build() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[cfg(test)] + use pretty_assertions::assert_eq; + use std::str; + + #[test] + fn test_delete_default() { + let b = DataBinding::new().xpath("root/hello").build(); + assert_eq!( + str::from_utf8(&b).unwrap(), + r#""# + ); + } +} diff --git a/docx-core/src/documents/elements/mod.rs b/docx-core/src/documents/elements/mod.rs index df9cb1f..87d0378 100644 --- a/docx-core/src/documents/elements/mod.rs +++ b/docx-core/src/documents/elements/mod.rs @@ -13,6 +13,7 @@ mod comment; mod comment_extended; mod comment_range_end; mod comment_range_start; +mod data_binding; mod default_tab_stop; mod delete; mod delete_text; @@ -65,6 +66,7 @@ mod section_property; mod shading; mod start; mod structured_data_tag; +mod structured_data_tag_property; mod style; mod sz; mod sz_cs; @@ -114,6 +116,7 @@ pub use comment::*; pub use comment_extended::*; pub use comment_range_end::*; pub use comment_range_start::*; +pub use data_binding::*; pub use default_tab_stop::*; pub use delete::*; pub use delete_text::*; @@ -166,6 +169,7 @@ pub use section_property::*; pub use shading::*; pub use start::*; pub use structured_data_tag::*; +pub use structured_data_tag_property::*; pub use style::*; pub use sz::*; pub use sz_cs::*; diff --git a/docx-core/src/documents/elements/paragraph.rs b/docx-core/src/documents/elements/paragraph.rs index 01439ed..41137e3 100644 --- a/docx-core/src/documents/elements/paragraph.rs +++ b/docx-core/src/documents/elements/paragraph.rs @@ -35,6 +35,7 @@ pub enum ParagraphChild { BookmarkEnd(BookmarkEnd), CommentStart(Box), CommentEnd(CommentRangeEnd), + StructuredDataTag(Box), } impl BuildXML for ParagraphChild { @@ -47,6 +48,7 @@ impl BuildXML for ParagraphChild { ParagraphChild::BookmarkEnd(v) => v.build(), ParagraphChild::CommentStart(v) => v.build(), ParagraphChild::CommentEnd(v) => v.build(), + ParagraphChild::StructuredDataTag(v) => v.build(), } } } @@ -99,6 +101,12 @@ impl Serialize for ParagraphChild { t.serialize_field("data", r)?; t.end() } + ParagraphChild::StructuredDataTag(ref r) => { + let mut t = serializer.serialize_struct("StructuredDataTag", 2)?; + t.serialize_field("type", "structuredDataTag")?; + t.serialize_field("data", r)?; + t.end() + } } } } @@ -122,6 +130,12 @@ impl Paragraph { self } + pub fn add_structured_data_tag(mut self, t: StructuredDataTag) -> Self { + self.children + .push(ParagraphChild::StructuredDataTag(Box::new(t))); + self + } + pub fn add_insert(mut self, insert: Insert) -> Paragraph { self.children.push(ParagraphChild::Insert(insert)); self diff --git a/docx-core/src/documents/elements/structured_data_tag.rs b/docx-core/src/documents/elements/structured_data_tag.rs index f602188..84478a0 100644 --- a/docx-core/src/documents/elements/structured_data_tag.rs +++ b/docx-core/src/documents/elements/structured_data_tag.rs @@ -10,6 +10,7 @@ use crate::xml_builder::*; #[serde(rename_all = "camelCase")] pub struct StructuredDataTag { pub children: Vec, + pub property: StructuredDataTagProperty, pub has_numbering: bool, } @@ -17,6 +18,7 @@ impl Default for StructuredDataTag { fn default() -> Self { Self { children: Vec::new(), + property: StructuredDataTagProperty::new(), has_numbering: false, } } @@ -78,14 +80,18 @@ impl StructuredDataTag { .push(StructuredDataTagChild::Paragraph(Box::new(p))); self } + + pub fn data_binding(mut self, d: DataBinding) -> Self { + self.property = self.property.data_binding(d); + self + } } impl BuildXML for StructuredDataTag { fn build(&self) -> Vec { XMLBuilder::new() .open_structured_tag() - .open_structured_tag_property() - .close() + .add_child(&self.property) .open_structured_tag_content() .add_children(&self.children) .close() @@ -105,13 +111,12 @@ mod tests { #[test] fn test_sdt() { let b = StructuredDataTag::new() + .data_binding(DataBinding::new().xpath("root/hello")) .add_run(Run::new().add_text("Hello")) .build(); assert_eq!( str::from_utf8(&b).unwrap(), - r#" - - Hello + r#"Hello "# ); } diff --git a/docx-core/src/documents/elements/structured_data_tag_property.rs b/docx-core/src/documents/elements/structured_data_tag_property.rs new file mode 100644 index 0000000..8541e1c --- /dev/null +++ b/docx-core/src/documents/elements/structured_data_tag_property.rs @@ -0,0 +1,62 @@ +use serde::Serialize; + +use super::*; +use crate::documents::BuildXML; +use crate::xml_builder::*; + +#[derive(Serialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct StructuredDataTagProperty { + pub run_property: RunProperty, + pub data_binding: Option, +} + +impl Default for StructuredDataTagProperty { + fn default() -> Self { + StructuredDataTagProperty { + run_property: RunProperty::new(), + data_binding: None, + } + } +} + +impl StructuredDataTagProperty { + pub fn new() -> Self { + Default::default() + } + + pub fn data_binding(mut self, d: DataBinding) -> Self { + self.data_binding = Some(d); + self + } +} + +impl BuildXML for StructuredDataTagProperty { + fn build(&self) -> Vec { + XMLBuilder::new() + .open_structured_tag_property() + .add_child(&self.run_property) + .add_optional_child(&self.data_binding) + .close() + .build() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[cfg(test)] + use pretty_assertions::assert_eq; + use std::str; + + #[test] + fn test_default() { + let c = StructuredDataTagProperty::new(); + let b = c.build(); + assert_eq!( + str::from_utf8(&b).unwrap(), + r#""# + ); + } +} diff --git a/docx-core/src/xml_builder/elements.rs b/docx-core/src/xml_builder/elements.rs index 882b97d..83c4a83 100644 --- a/docx-core/src/xml_builder/elements.rs +++ b/docx-core/src/xml_builder/elements.rs @@ -61,6 +61,27 @@ impl XMLBuilder { self.writer.write(text).expect(EXPECT_MESSAGE); self.close() } + + pub(crate) fn data_binding( + mut self, + xpath: Option<&String>, + prefix_mappings: Option<&String>, + store_item_id: Option<&String>, + ) -> Self { + let mut e = XmlEvent::start_element("w:dataBinding"); + if let Some(xpath) = xpath { + e = e.attr("w:xpath", xpath); + } + if let Some(prefix_mappings) = prefix_mappings { + e = e.attr("w:prefixMappings", prefix_mappings); + } + if let Some(store_item_id) = store_item_id { + e = e.attr("w:storeItemID", store_item_id); + } + self.writer.write(e).expect(EXPECT_MESSAGE); + self.close() + } + // i.e. open!(open_run, "w:r"); open!(open_run_property, "w:rPr"); diff --git a/docx-core/src/xml_json/mod.rs b/docx-core/src/xml_json/mod.rs index 2a54cf4..350de50 100644 --- a/docx-core/src/xml_json/mod.rs +++ b/docx-core/src/xml_json/mod.rs @@ -39,7 +39,7 @@ impl fmt::Display for XmlDocument { /// An XML Tag /// -/// For exammple: +/// For example: /// /// ```XML /// @@ -60,12 +60,12 @@ pub struct XmlData { } // Generate indentation -fn indent(size: usize) -> String { - const INDENT: &str = " "; - (0..size) - .map(|_| INDENT) - .fold(String::with_capacity(size * INDENT.len()), |r, s| r + s) -} +// fn indent(size: usize) -> String { +// const INDENT: &str = " "; +// (0..size) +// .map(|_| INDENT) +// .fold(String::with_capacity(size * INDENT.len()), |r, s| r + s) +// } // Get the attributes as a string fn attributes_to_string(attributes: &[(String, String)]) -> String { @@ -88,22 +88,20 @@ fn format(data: &XmlData, depth: usize) -> String { sub }; - let indt = indent(depth); + // let indt = indent(depth); let fmt_data = if let Some(ref d) = data.data { - format!("\n{}{}", indent(depth + 1), d) + format!("\n{}", d) } else { "".to_string() }; format!( - "{}<{}{}>{}{}\n{}\n", - indt, + "<{}{}>{}{}", data.name, attributes_to_string(&data.attributes), fmt_data, sub, - indt, data.name ) } diff --git a/docx-wasm/test/__snapshots__/index.test.js.snap b/docx-wasm/test/__snapshots__/index.test.js.snap index 6d27071..65a32e4 100644 --- a/docx-wasm/test/__snapshots__/index.test.js.snap +++ b/docx-wasm/test/__snapshots__/index.test.js.snap @@ -25315,13 +25315,7 @@ exports[`writer should write customItem 6`] = ` exports[`writer should write customItem 7`] = ` " - - - - - - -" +" `; exports[`writer should write default font 1`] = `