linch_docx_rs/document/paragraph/
properties.rs1use crate::error::Result;
4use crate::xml::{get_w_val, RawXmlElement, RawXmlNode};
5use quick_xml::events::{BytesEnd, BytesStart, Event};
6use quick_xml::{Reader, Writer};
7use std::io::BufRead;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum Alignment {
12 Left,
13 Center,
14 Right,
15 Justify,
16 Distribute,
17}
18
19impl Alignment {
20 pub fn from_ooxml(s: &str) -> Option<Self> {
21 match s {
22 "left" | "start" => Some(Self::Left),
23 "center" => Some(Self::Center),
24 "right" | "end" => Some(Self::Right),
25 "both" | "justify" => Some(Self::Justify),
26 "distribute" => Some(Self::Distribute),
27 _ => None,
28 }
29 }
30
31 pub fn as_ooxml(&self) -> &'static str {
32 match self {
33 Self::Left => "left",
34 Self::Center => "center",
35 Self::Right => "right",
36 Self::Justify => "both",
37 Self::Distribute => "distribute",
38 }
39 }
40}
41
42#[derive(Clone, Debug, Default)]
44pub struct Indentation {
45 pub left: Option<i32>,
46 pub right: Option<i32>,
47 pub first_line: Option<i32>,
48 pub hanging: Option<i32>,
49}
50
51#[derive(Clone, Debug, Default)]
53pub struct LineSpacing {
54 pub before: Option<u32>,
56 pub after: Option<u32>,
58 pub line: Option<u32>,
60 pub line_rule: Option<String>,
62}
63
64#[derive(Clone, Debug, Default)]
66pub struct ParagraphProperties {
67 pub style: Option<String>,
69 pub justification: Option<String>,
71 pub num_id: Option<u32>,
73 pub num_level: Option<u32>,
74 pub outline_level: Option<u8>,
76 pub indentation: Option<Indentation>,
78 pub spacing: Option<LineSpacing>,
80 pub keep_next: Option<bool>,
82 pub keep_lines: Option<bool>,
84 pub page_break_before: Option<bool>,
86 pub run_properties: Option<crate::document::RunProperties>,
88 pub unknown_children: Vec<RawXmlNode>,
90}
91
92impl ParagraphProperties {
93 pub fn from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<Self> {
95 let mut props = ParagraphProperties::default();
96 let mut buf = Vec::new();
97
98 loop {
99 match reader.read_event_into(&mut buf)? {
100 Event::Start(e) => {
101 let local = e.name().local_name();
102 match local.as_ref() {
103 b"numPr" => {
104 parse_num_pr(reader, &mut props)?;
105 }
106 b"rPr" => {
107 props.run_properties =
108 Some(crate::document::RunProperties::from_reader(reader)?);
109 }
110 _ => {
111 let raw = RawXmlElement::from_reader(reader, &e)?;
112 props.unknown_children.push(RawXmlNode::Element(raw));
113 }
114 }
115 }
116 Event::Empty(e) => {
117 let local = e.name().local_name();
118 match local.as_ref() {
119 b"pStyle" => props.style = get_w_val(&e),
120 b"jc" => props.justification = get_w_val(&e),
121 b"outlineLvl" => {
122 props.outline_level = get_w_val(&e).and_then(|v| v.parse().ok());
123 }
124 b"ind" => {
125 props.indentation = Some(Indentation {
126 left: crate::xml::get_attr(&e, "w:left")
127 .and_then(|v| v.parse().ok()),
128 right: crate::xml::get_attr(&e, "w:right")
129 .and_then(|v| v.parse().ok()),
130 first_line: crate::xml::get_attr(&e, "w:firstLine")
131 .and_then(|v| v.parse().ok()),
132 hanging: crate::xml::get_attr(&e, "w:hanging")
133 .and_then(|v| v.parse().ok()),
134 });
135 }
136 b"spacing" => {
137 props.spacing = Some(LineSpacing {
138 before: crate::xml::get_attr(&e, "w:before")
139 .and_then(|v| v.parse().ok()),
140 after: crate::xml::get_attr(&e, "w:after")
141 .and_then(|v| v.parse().ok()),
142 line: crate::xml::get_attr(&e, "w:line")
143 .and_then(|v| v.parse().ok()),
144 line_rule: crate::xml::get_attr(&e, "w:lineRule"),
145 });
146 }
147 b"keepNext" => props.keep_next = Some(crate::xml::parse_bool(&e)),
148 b"keepLines" => props.keep_lines = Some(crate::xml::parse_bool(&e)),
149 b"pageBreakBefore" => {
150 props.page_break_before = Some(crate::xml::parse_bool(&e));
151 }
152 _ => {
153 let raw = RawXmlElement {
154 name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
155 attributes: e
156 .attributes()
157 .filter_map(|a| a.ok())
158 .map(|a| {
159 (
160 String::from_utf8_lossy(a.key.as_ref()).to_string(),
161 String::from_utf8_lossy(&a.value).to_string(),
162 )
163 })
164 .collect(),
165 children: Vec::new(),
166 self_closing: true,
167 };
168 props.unknown_children.push(RawXmlNode::Element(raw));
169 }
170 }
171 }
172 Event::End(e) if e.name().local_name().as_ref() == b"pPr" => break,
173 Event::Eof => break,
174 _ => {}
175 }
176 buf.clear();
177 }
178
179 Ok(props)
180 }
181
182 pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
184 let has_content = self.style.is_some()
185 || self.justification.is_some()
186 || self.num_id.is_some()
187 || self.outline_level.is_some()
188 || self.indentation.is_some()
189 || self.spacing.is_some()
190 || self.keep_next.is_some()
191 || self.keep_lines.is_some()
192 || self.page_break_before.is_some()
193 || self.run_properties.is_some()
194 || !self.unknown_children.is_empty();
195
196 if !has_content {
197 return Ok(());
198 }
199
200 writer.write_event(Event::Start(BytesStart::new("w:pPr")))?;
201
202 if let Some(style) = &self.style {
203 let mut elem = BytesStart::new("w:pStyle");
204 elem.push_attribute(("w:val", style.as_str()));
205 writer.write_event(Event::Empty(elem))?;
206 }
207
208 if let Some(true) = self.keep_next {
209 writer.write_event(Event::Empty(BytesStart::new("w:keepNext")))?;
210 }
211 if let Some(true) = self.keep_lines {
212 writer.write_event(Event::Empty(BytesStart::new("w:keepLines")))?;
213 }
214 if let Some(true) = self.page_break_before {
215 writer.write_event(Event::Empty(BytesStart::new("w:pageBreakBefore")))?;
216 }
217
218 if self.num_id.is_some() || self.num_level.is_some() {
219 writer.write_event(Event::Start(BytesStart::new("w:numPr")))?;
220 if let Some(level) = self.num_level {
221 let mut elem = BytesStart::new("w:ilvl");
222 elem.push_attribute(("w:val", level.to_string().as_str()));
223 writer.write_event(Event::Empty(elem))?;
224 }
225 if let Some(num_id) = self.num_id {
226 let mut elem = BytesStart::new("w:numId");
227 elem.push_attribute(("w:val", num_id.to_string().as_str()));
228 writer.write_event(Event::Empty(elem))?;
229 }
230 writer.write_event(Event::End(BytesEnd::new("w:numPr")))?;
231 }
232
233 if let Some(ref sp) = self.spacing {
234 let mut elem = BytesStart::new("w:spacing");
235 if let Some(v) = sp.before {
236 elem.push_attribute(("w:before", v.to_string().as_str()));
237 }
238 if let Some(v) = sp.after {
239 elem.push_attribute(("w:after", v.to_string().as_str()));
240 }
241 if let Some(v) = sp.line {
242 elem.push_attribute(("w:line", v.to_string().as_str()));
243 }
244 if let Some(ref rule) = sp.line_rule {
245 elem.push_attribute(("w:lineRule", rule.as_str()));
246 }
247 writer.write_event(Event::Empty(elem))?;
248 }
249
250 if let Some(ref ind) = self.indentation {
251 let mut elem = BytesStart::new("w:ind");
252 if let Some(v) = ind.left {
253 elem.push_attribute(("w:left", v.to_string().as_str()));
254 }
255 if let Some(v) = ind.right {
256 elem.push_attribute(("w:right", v.to_string().as_str()));
257 }
258 if let Some(v) = ind.first_line {
259 elem.push_attribute(("w:firstLine", v.to_string().as_str()));
260 }
261 if let Some(v) = ind.hanging {
262 elem.push_attribute(("w:hanging", v.to_string().as_str()));
263 }
264 writer.write_event(Event::Empty(elem))?;
265 }
266
267 if let Some(jc) = &self.justification {
268 let mut elem = BytesStart::new("w:jc");
269 elem.push_attribute(("w:val", jc.as_str()));
270 writer.write_event(Event::Empty(elem))?;
271 }
272
273 if let Some(level) = self.outline_level {
274 let mut elem = BytesStart::new("w:outlineLvl");
275 elem.push_attribute(("w:val", level.to_string().as_str()));
276 writer.write_event(Event::Empty(elem))?;
277 }
278
279 if let Some(ref rpr) = self.run_properties {
280 rpr.write_to(writer)?;
281 }
282
283 for child in &self.unknown_children {
284 child.write_to(writer)?;
285 }
286
287 writer.write_event(Event::End(BytesEnd::new("w:pPr")))?;
288 Ok(())
289 }
290}
291
292fn parse_num_pr<R: BufRead>(reader: &mut Reader<R>, props: &mut ParagraphProperties) -> Result<()> {
294 let mut buf = Vec::new();
295
296 loop {
297 match reader.read_event_into(&mut buf)? {
298 Event::Empty(e) => {
299 let local = e.name().local_name();
300 match local.as_ref() {
301 b"numId" => props.num_id = get_w_val(&e).and_then(|v| v.parse().ok()),
302 b"ilvl" => props.num_level = get_w_val(&e).and_then(|v| v.parse().ok()),
303 _ => {}
304 }
305 }
306 Event::End(e) if e.name().local_name().as_ref() == b"numPr" => break,
307 Event::Eof => break,
308 _ => {}
309 }
310 buf.clear();
311 }
312
313 Ok(())
314}