feat: Impl dataBinding (#383)

* feat: Impl dataBinding

* fix: snaps
main
bokuweb 2021-12-09 19:37:54 +09:00 committed by GitHub
parent 1850ac2018
commit 54b7a4d20c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 213 additions and 32 deletions

View File

@ -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",
"<root><item1>Hello</item1><item2> World!</item2></root>",
)
.build()
.pack(file)?;
Ok(())
}

View File

@ -44,19 +44,17 @@ mod tests {
#[test]
fn test_custom_xml() {
let c = CustomItem::from_str(
r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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: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(),
"<ds:datastoreItem ds:itemID=\"{06AC5857-5C65-A94A-BCEC-37356A209BC3}\" xmlns:ds=\"http://schemas.openxmlformats.org/officeDocument/2006/customXml\">\n <ds:schemaRefs>\n <ds:schemaRef ds:uri=\"https://hoge.com\">\n </ds:schemaRef>\n\n </ds:schemaRefs>\n\n</ds:datastoreItem>\n"
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(),

View File

@ -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<String>,
pub prefix_mappings: Option<String>,
pub store_item_id: Option<String>,
}
impl DataBinding {
pub fn new() -> Self {
Default::default()
}
pub fn xpath(mut self, xpath: impl Into<String>) -> Self {
self.xpath = Some(xpath.into());
self
}
pub fn prefix_mappings(mut self, m: impl Into<String>) -> Self {
self.prefix_mappings = Some(m.into());
self
}
pub fn store_item_id(mut self, id: impl Into<String>) -> Self {
self.store_item_id = Some(id.into());
self
}
}
impl BuildXML for DataBinding {
fn build(&self) -> Vec<u8> {
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#"<w:dataBinding w:xpath="root/hello" />"#
);
}
}

View File

@ -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::*;

View File

@ -35,6 +35,7 @@ pub enum ParagraphChild {
BookmarkEnd(BookmarkEnd),
CommentStart(Box<CommentRangeStart>),
CommentEnd(CommentRangeEnd),
StructuredDataTag(Box<StructuredDataTag>),
}
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

View File

@ -10,6 +10,7 @@ use crate::xml_builder::*;
#[serde(rename_all = "camelCase")]
pub struct StructuredDataTag {
pub children: Vec<StructuredDataTagChild>,
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<u8> {
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#"<w:sdt>
<w:sdtPr />
<w:sdtContent><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:sdtContent>
r#"<w:sdt><w:sdtPr><w:rPr /><w:dataBinding w:xpath="root/hello" /></w:sdtPr><w:sdtContent><w:r><w:rPr /><w:t xml:space="preserve">Hello</w:t></w:r></w:sdtContent>
</w:sdt>"#
);
}

View File

@ -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<DataBinding>,
}
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<u8> {
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#"<w:sdtPr><w:rPr /></w:sdtPr>"#
);
}
}

View File

@ -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. <w:r ... >
open!(open_run, "w:r");
open!(open_run_property, "w:rPr");

View File

@ -39,7 +39,7 @@ impl fmt::Display for XmlDocument {
/// An XML Tag
///
/// For exammple:
/// For example:
///
/// ```XML
/// <foo bar="baz">
@ -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
)
}

View File

@ -25315,13 +25315,7 @@ exports[`writer should write customItem 6`] = `
exports[`writer should write customItem 7`] = `
"<root xmlns=\\"https://example.com\\">
<item name=\\"Cheap Item\\" price=\\"$193.95\\">
</item>
<item name=\\"Expensive Item\\" price=\\"$931.88\\">
</item>
</root>
"
<item name=\\"Cheap Item\\" price=\\"$193.95\\"></item><item name=\\"Expensive Item\\" price=\\"$931.88\\"></item></root>"
`;
exports[`writer should write default font 1`] = `