docx_rs/documents/
styles.rs1use serde::Serialize;
2use std::io::Write;
3
4use super::*;
5use crate::documents::BuildXML;
6use crate::types::*;
7use crate::xml_builder::*;
8
9#[derive(Debug, Clone, PartialEq, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Styles {
12 pub doc_defaults: DocDefaults,
13 pub styles: Vec<Style>,
14}
15
16impl Styles {
17 pub fn new() -> Styles {
18 Default::default()
19 }
20
21 pub fn add_style(mut self, style: Style) -> Self {
22 self.styles.push(style);
23 self
24 }
25
26 pub fn default_size(mut self, size: usize) -> Self {
27 self.doc_defaults = self.doc_defaults.size(size);
28 self
29 }
30
31 pub fn default_spacing(mut self, spacing: i32) -> Self {
32 self.doc_defaults = self.doc_defaults.spacing(spacing);
33 self
34 }
35
36 pub fn default_line_spacing(mut self, spacing: LineSpacing) -> Self {
37 self.doc_defaults = self.doc_defaults.line_spacing(spacing);
38 self
39 }
40
41 pub fn default_fonts(mut self, font: RunFonts) -> Self {
42 self.doc_defaults = self.doc_defaults.fonts(font);
43 self
44 }
45
46 pub(crate) fn doc_defaults(mut self, doc_defaults: DocDefaults) -> Self {
47 self.doc_defaults = doc_defaults;
48 self
49 }
50
51 pub fn find_style_by_id(&self, id: &str) -> Option<&Style> {
52 self.styles.iter().find(|s| s.style_id == id)
53 }
54
55 pub fn create_heading_style_map(&self) -> std::collections::HashMap<String, usize> {
56 self.styles
57 .iter()
58 .filter_map(|s| {
59 if s.name.is_heading() {
60 let n = s.name.get_heading_number();
61 n.map(|n| (s.style_id.clone(), n))
62 } else {
63 None
64 }
65 })
66 .collect()
67 }
68}
69
70impl Default for Styles {
71 fn default() -> Self {
72 Self {
73 doc_defaults: DocDefaults::new(),
74 styles: vec![],
75 }
76 }
77}
78
79impl BuildXML for Styles {
80 fn build_to<W: Write>(
81 &self,
82 stream: xml::writer::EventWriter<W>,
83 ) -> xml::writer::Result<xml::writer::EventWriter<W>> {
84 let normal = Style::new("Normal", StyleType::Paragraph).name("Normal");
85 XMLBuilder::from(stream)
86 .open_styles()?
87 .add_child(&self.doc_defaults)?
88 .add_child(&normal)?
89 .add_children(&self.styles)?
90 .close()?
91 .into_inner()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97
98 use super::*;
99 use crate::types::StyleType;
100 #[cfg(test)]
101 use pretty_assertions::assert_eq;
102 use std::str;
103
104 #[test]
105 fn test_style() {
106 let c =
107 Styles::new().add_style(Style::new("Title", StyleType::Paragraph).name("TitleName"));
108 let b = c.build();
109 assert_eq!(
110 str::from_utf8(&b).unwrap(),
111 r#"<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 w15"><w:docDefaults><w:rPrDefault><w:rPr /></w:rPrDefault><w:pPrDefault><w:pPr><w:rPr /></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal" /><w:rPr /><w:pPr><w:rPr /></w:pPr><w:qFormat /></w:style><w:style w:type="paragraph" w:styleId="Title"><w:name w:val="TitleName" /><w:rPr /><w:pPr><w:rPr /></w:pPr><w:qFormat /></w:style></w:styles>"#
112 );
113 }
114
115 #[test]
116 fn test_table_style() {
117 let c = Styles::new().add_style(
118 Style::new("Table", StyleType::Table)
119 .name("Table Style")
120 .table_property(
121 TableProperty::new().set_margins(
122 TableCellMargins::new()
123 .margin_left(108, WidthType::Dxa)
124 .margin_right(108, WidthType::Dxa),
125 ),
126 ),
127 );
128 let b = c.build();
129 assert_eq!(
130 str::from_utf8(&b).unwrap(),
131 r#"<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" mc:Ignorable="w14 w15"><w:docDefaults><w:rPrDefault><w:rPr /></w:rPrDefault><w:pPrDefault><w:pPr><w:rPr /></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal" /><w:rPr /><w:pPr><w:rPr /></w:pPr><w:qFormat /></w:style><w:style w:type="table" w:styleId="Table"><w:name w:val="Table Style" /><w:rPr /><w:pPr><w:rPr /></w:pPr><w:tcPr /><w:tblPr><w:tblW w:w="0" w:type="auto" /><w:jc w:val="left" /><w:tblBorders><w:top w:val="single" w:sz="2" w:space="0" w:color="000000" /><w:left w:val="single" w:sz="2" w:space="0" w:color="000000" /><w:bottom w:val="single" w:sz="2" w:space="0" w:color="000000" /><w:right w:val="single" w:sz="2" w:space="0" w:color="000000" /><w:insideH w:val="single" w:sz="2" w:space="0" w:color="000000" /><w:insideV w:val="single" w:sz="2" w:space="0" w:color="000000" /></w:tblBorders><w:tblCellMar><w:top w:w="0" w:type="dxa" /><w:left w:w="108" w:type="dxa" /><w:bottom w:w="0" w:type="dxa" /><w:right w:w="108" w:type="dxa" /></w:tblCellMar></w:tblPr><w:qFormat /></w:style></w:styles>"#
132 );
133 }
134
135 #[test]
136 fn test_heading_style() {
137 let c = Styles::new().add_style(Style::new("ToC", StyleType::Paragraph).name("heading 3"));
138 let mut m = std::collections::HashMap::new();
139 m.insert("ToC".to_string(), 3);
140 let b = c.create_heading_style_map();
141 assert_eq!(b, m);
142 }
143}