1use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
4use quick_xml::{Reader, Writer};
5
6use crate::error::{OxmlError, Result};
7use crate::namespace::{W_NS, matches_local_name};
8use crate::numbering::{parse_scoped_ppr, word_prefixes_at};
9use crate::properties::{CT_PPr, CT_RPr, is_word_element};
10use crate::raw_xml::capture_element;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum StyleType {
15 Paragraph,
16 Character,
17 Table,
18 Numbering,
19}
20
21impl StyleType {
22 pub fn from_str(s: &str) -> Result<Self> {
23 match s {
24 "paragraph" => Ok(StyleType::Paragraph),
25 "character" => Ok(StyleType::Character),
26 "table" => Ok(StyleType::Table),
27 "numbering" => Ok(StyleType::Numbering),
28 _ => Err(OxmlError::InvalidValue(format!("invalid style type: {s}"))),
29 }
30 }
31
32 pub fn to_str(self) -> &'static str {
33 match self {
34 StyleType::Paragraph => "paragraph",
35 StyleType::Character => "character",
36 StyleType::Table => "table",
37 StyleType::Numbering => "numbering",
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
44#[allow(non_snake_case)]
45pub struct CT_Style {
46 pub style_id: String,
47 pub style_type: StyleType,
48 pub name: Option<String>,
49 pub based_on: Option<String>,
50 pub next_style: Option<String>,
51 pub is_default: bool,
52 pub ppr: Option<CT_PPr>,
53 pub rpr: Option<CT_RPr>,
54}
55
56#[allow(non_snake_case)]
57impl CT_Style {
58 pub fn from_xml(reader: &mut Reader<&[u8]>, attrs: &BytesStart) -> Result<Self> {
59 let prefixes = word_prefixes_at(attrs, &["w".to_string()])?;
60 Self::from_xml_with_prefixes(reader, attrs, &prefixes)
61 }
62
63 fn from_xml_with_prefixes(
64 reader: &mut Reader<&[u8]>,
65 attrs: &BytesStart,
66 word_prefixes: &[String],
67 ) -> Result<Self> {
68 let mut style_id = String::new();
69 let mut style_type = StyleType::Paragraph;
70 let mut is_default = false;
71
72 for attr in attrs.attributes() {
73 let attr = attr?;
74 let key = attr.key.as_ref();
75 if matches_local_name(key, b"styleId") {
76 style_id = std::str::from_utf8(&attr.value)?.to_string();
77 } else if matches_local_name(key, b"type") {
78 style_type = StyleType::from_str(std::str::from_utf8(&attr.value)?)?;
79 } else if matches_local_name(key, b"default") {
80 is_default = std::str::from_utf8(&attr.value)? == "1"
81 || std::str::from_utf8(&attr.value)? == "true";
82 }
83 }
84
85 let mut name = None;
86 let mut based_on = None;
87 let mut next_style = None;
88 let mut ppr = None;
89 let mut rpr = None;
90 let mut buf = Vec::new();
91
92 loop {
93 match reader.read_event_into(&mut buf) {
94 Ok(Event::Empty(ref e)) => {
95 let ename = e.name();
96 if matches_local_name(ename.as_ref(), b"name") {
97 name = get_val_attr(e)?;
98 } else if matches_local_name(ename.as_ref(), b"basedOn") {
99 based_on = get_val_attr(e)?;
100 } else if matches_local_name(ename.as_ref(), b"next") {
101 next_style = get_val_attr(e)?;
102 }
103 }
104 Ok(Event::Start(ref e)) => {
105 let ename = e.name();
106 let prefixes = word_prefixes_at(e, word_prefixes)?;
107 if is_word_element(ename.as_ref(), b"pPr", &prefixes) {
108 let raw = capture_element(reader, e)?;
109 ppr = Some(parse_scoped_ppr(&raw, &prefixes)?);
110 } else if matches_local_name(ename.as_ref(), b"rPr") {
111 rpr = Some(CT_RPr::from_xml(reader)?);
112 } else {
113 reader.read_to_end_into(ename, &mut Vec::new())?;
114 }
115 }
116 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"style") => {
117 break;
118 }
119 Ok(Event::Eof) => break,
120 Err(e) => return Err(e.into()),
121 _ => {}
122 }
123 buf.clear();
124 }
125
126 Ok(CT_Style {
127 style_id,
128 style_type,
129 name,
130 based_on,
131 next_style,
132 is_default,
133 ppr,
134 rpr,
135 })
136 }
137
138 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
139 let mut e = BytesStart::new("w:style");
140 e.push_attribute(("w:type", self.style_type.to_str()));
141 e.push_attribute(("w:styleId", self.style_id.as_str()));
142 if self.is_default {
143 e.push_attribute(("w:default", "1"));
144 }
145 writer.write_event(Event::Start(e))?;
146
147 if let Some(ref name) = self.name {
148 let mut ne = BytesStart::new("w:name");
149 ne.push_attribute(("w:val", name.as_str()));
150 writer.write_event(Event::Empty(ne))?;
151 }
152
153 if let Some(ref based_on) = self.based_on {
154 let mut be = BytesStart::new("w:basedOn");
155 be.push_attribute(("w:val", based_on.as_str()));
156 writer.write_event(Event::Empty(be))?;
157 }
158
159 if let Some(ref next) = self.next_style {
160 let mut ne = BytesStart::new("w:next");
161 ne.push_attribute(("w:val", next.as_str()));
162 writer.write_event(Event::Empty(ne))?;
163 }
164
165 if let Some(ref ppr) = self.ppr {
166 ppr.to_xml(writer)?;
167 }
168 if let Some(ref rpr) = self.rpr {
169 rpr.to_xml(writer)?;
170 }
171
172 writer.write_event(Event::End(BytesEnd::new("w:style")))?;
173 Ok(())
174 }
175}
176
177#[derive(Debug, Clone, Default, PartialEq)]
179#[allow(non_snake_case)]
180pub struct CT_DocDefaults {
181 pub rpr: Option<CT_RPr>,
182 pub ppr: Option<CT_PPr>,
183}
184
185#[allow(non_snake_case)]
186impl CT_DocDefaults {
187 pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
188 Self::from_xml_with_prefixes(reader, &["w".to_string()])
189 }
190
191 fn from_xml_with_prefixes(
192 reader: &mut Reader<&[u8]>,
193 word_prefixes: &[String],
194 ) -> Result<Self> {
195 let mut defaults = CT_DocDefaults::default();
196 let mut buf = Vec::new();
197
198 loop {
199 match reader.read_event_into(&mut buf) {
200 Ok(Event::Start(ref e)) => {
201 let name = e.name();
202 let prefixes = word_prefixes_at(e, word_prefixes)?;
203 if matches_local_name(name.as_ref(), b"rPrDefault") {
204 defaults.rpr = Self::parse_pr_default(reader, b"rPrDefault")?;
206 } else if matches_local_name(name.as_ref(), b"pPrDefault") {
207 defaults.ppr = Self::parse_ppr_default(reader, &prefixes)?;
208 } else {
209 reader.read_to_end_into(name, &mut Vec::new())?;
210 }
211 }
212 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"docDefaults") => {
213 break;
214 }
215 Ok(Event::Eof) => break,
216 Err(e) => return Err(e.into()),
217 _ => {}
218 }
219 buf.clear();
220 }
221
222 Ok(defaults)
223 }
224
225 fn parse_pr_default(reader: &mut Reader<&[u8]>, end_tag: &[u8]) -> Result<Option<CT_RPr>> {
226 let mut rpr = None;
227 let mut buf = Vec::new();
228
229 loop {
230 match reader.read_event_into(&mut buf) {
231 Ok(Event::Start(ref e)) => {
232 let name = e.name();
233 if matches_local_name(name.as_ref(), b"rPr") {
234 rpr = Some(CT_RPr::from_xml(reader)?);
235 } else {
236 reader.read_to_end_into(name, &mut Vec::new())?;
237 }
238 }
239 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), end_tag) => {
240 break;
241 }
242 Ok(Event::Eof) => break,
243 Err(e) => return Err(e.into()),
244 _ => {}
245 }
246 buf.clear();
247 }
248
249 Ok(rpr)
250 }
251
252 fn parse_ppr_default(
253 reader: &mut Reader<&[u8]>,
254 word_prefixes: &[String],
255 ) -> Result<Option<CT_PPr>> {
256 let mut ppr = None;
257 let mut buf = Vec::new();
258
259 loop {
260 match reader.read_event_into(&mut buf) {
261 Ok(Event::Start(ref e)) => {
262 let name = e.name();
263 let prefixes = word_prefixes_at(e, word_prefixes)?;
264 if is_word_element(name.as_ref(), b"pPr", &prefixes) {
265 let raw = capture_element(reader, e)?;
266 ppr = Some(parse_scoped_ppr(&raw, &prefixes)?);
267 } else {
268 reader.read_to_end_into(name, &mut Vec::new())?;
269 }
270 }
271 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"pPrDefault") => {
272 break;
273 }
274 Ok(Event::Eof) => break,
275 Err(e) => return Err(e.into()),
276 _ => {}
277 }
278 buf.clear();
279 }
280
281 Ok(ppr)
282 }
283
284 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
285 writer.write_event(Event::Start(BytesStart::new("w:docDefaults")))?;
286
287 if let Some(ref rpr) = self.rpr {
288 writer.write_event(Event::Start(BytesStart::new("w:rPrDefault")))?;
289 rpr.to_xml(writer)?;
290 writer.write_event(Event::End(BytesEnd::new("w:rPrDefault")))?;
291 }
292
293 if let Some(ref ppr) = self.ppr {
294 writer.write_event(Event::Start(BytesStart::new("w:pPrDefault")))?;
295 ppr.to_xml(writer)?;
296 writer.write_event(Event::End(BytesEnd::new("w:pPrDefault")))?;
297 }
298
299 writer.write_event(Event::End(BytesEnd::new("w:docDefaults")))?;
300 Ok(())
301 }
302}
303
304#[derive(Debug, Clone, PartialEq)]
306#[allow(non_snake_case)]
307pub struct CT_Styles {
308 pub doc_defaults: Option<CT_DocDefaults>,
309 pub styles: Vec<CT_Style>,
310}
311
312#[allow(non_snake_case)]
313impl CT_Styles {
314 pub fn new() -> Self {
315 CT_Styles {
316 doc_defaults: None,
317 styles: Vec::new(),
318 }
319 }
320
321 pub fn from_xml(xml: &[u8]) -> Result<Self> {
323 let mut reader = Reader::from_reader(xml);
324 reader.config_mut().trim_text(true);
325
326 let mut doc_defaults = None;
327 let mut styles = Vec::new();
328 let mut buf = Vec::new();
329 let mut word_prefixes = Vec::new();
330
331 loop {
332 match reader.read_event_into(&mut buf) {
333 Ok(Event::Start(ref e)) => {
334 let name = e.name();
335 let prefixes = word_prefixes_at(e, &word_prefixes)?;
336 if matches_local_name(name.as_ref(), b"docDefaults") {
337 doc_defaults = Some(CT_DocDefaults::from_xml_with_prefixes(
338 &mut reader,
339 &prefixes,
340 )?);
341 } else if matches_local_name(name.as_ref(), b"style") {
342 styles.push(CT_Style::from_xml_with_prefixes(&mut reader, e, &prefixes)?);
343 } else if matches_local_name(name.as_ref(), b"styles") {
344 word_prefixes = prefixes;
346 } else {
347 reader.read_to_end_into(name, &mut Vec::new())?;
348 }
349 }
350 Ok(Event::Eof) => break,
351 Err(e) => return Err(e.into()),
352 _ => {}
353 }
354 buf.clear();
355 }
356
357 Ok(CT_Styles {
358 doc_defaults,
359 styles,
360 })
361 }
362
363 pub fn to_xml(&self) -> Result<Vec<u8>> {
365 let mut writer = Writer::new_with_indent(Vec::new(), b' ', 2);
366
367 writer.write_event(Event::Decl(BytesDecl::new(
368 "1.0",
369 Some("UTF-8"),
370 Some("yes"),
371 )))?;
372
373 let mut styles_start = BytesStart::new("w:styles");
374 styles_start.push_attribute(("xmlns:w", W_NS));
375 styles_start.push_attribute((
376 "xmlns:r",
377 "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
378 ));
379 writer.write_event(Event::Start(styles_start))?;
380
381 if let Some(ref defaults) = self.doc_defaults {
382 defaults.to_xml(&mut writer)?;
383 }
384
385 for style in &self.styles {
386 style.to_xml(&mut writer)?;
387 }
388
389 writer.write_event(Event::End(BytesEnd::new("w:styles")))?;
390
391 Ok(writer.into_inner())
392 }
393
394 pub fn get_by_id(&self, style_id: &str) -> Option<&CT_Style> {
396 self.styles.iter().find(|s| s.style_id == style_id)
397 }
398
399 pub fn get_default(&self, style_type: StyleType) -> Option<&CT_Style> {
401 self.styles
402 .iter()
403 .find(|s| s.style_type == style_type && s.is_default)
404 }
405
406 pub fn new_default() -> Self {
408 use crate::units::HalfPoint;
409
410 let normal = CT_Style {
411 style_id: "Normal".to_string(),
412 style_type: StyleType::Paragraph,
413 name: Some("Normal".to_string()),
414 based_on: None,
415 next_style: None,
416 is_default: true,
417 ppr: None,
418 rpr: None,
419 };
420
421 let heading1 = CT_Style {
422 style_id: "Heading1".to_string(),
423 style_type: StyleType::Paragraph,
424 name: Some("heading 1".to_string()),
425 based_on: Some("Normal".to_string()),
426 next_style: Some("Normal".to_string()),
427 is_default: false,
428 ppr: Some(CT_PPr {
429 keep_next: Some(true),
430 keep_lines: Some(true),
431 space_before: Some(crate::units::Twips(240)),
432 space_after: Some(crate::units::Twips(0)),
433 ..Default::default()
434 }),
435 rpr: Some(CT_RPr {
436 sz: Some(HalfPoint(32)),
437 sz_cs: Some(HalfPoint(32)),
438 bold: Some(true),
439 bold_cs: Some(true),
440 color: Some("2F5496".to_string()),
441 ..Default::default()
442 }),
443 };
444
445 let doc_defaults = CT_DocDefaults {
446 rpr: Some(CT_RPr {
447 font_ascii: Some("Calibri".to_string()),
448 font_hansi: Some("Calibri".to_string()),
449 font_east_asia: Some("Calibri".to_string()),
450 font_cs: Some("Times New Roman".to_string()),
451 sz: Some(HalfPoint(22)),
452 sz_cs: Some(HalfPoint(22)),
453 ..Default::default()
454 }),
455 ppr: Some(CT_PPr {
456 space_after: Some(crate::units::Twips(160)),
457 line_spacing: Some(crate::units::Twips(259)),
458 line_rule: Some("auto".to_string()),
459 ..Default::default()
460 }),
461 };
462
463 CT_Styles {
464 doc_defaults: Some(doc_defaults),
465 styles: vec![normal, heading1],
466 }
467 }
468}
469
470impl Default for CT_Styles {
471 fn default() -> Self {
472 Self::new()
473 }
474}
475
476fn get_val_attr(e: &BytesStart) -> Result<Option<String>> {
478 for attr in e.attributes() {
479 let attr = attr?;
480 if matches_local_name(attr.key.as_ref(), b"val") {
481 return Ok(Some(std::str::from_utf8(&attr.value)?.to_string()));
482 }
483 }
484 Ok(None)
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
492 fn round_trip_styles() {
493 let styles = CT_Styles::new_default();
494 let xml = styles.to_xml().unwrap();
495 let parsed = CT_Styles::from_xml(&xml).unwrap();
496
497 assert_eq!(parsed.styles.len(), 2);
498 assert!(parsed.doc_defaults.is_some());
499
500 let normal = parsed.get_by_id("Normal").unwrap();
501 assert_eq!(normal.name, Some("Normal".to_string()));
502 assert!(normal.is_default);
503
504 let h1 = parsed.get_by_id("Heading1").unwrap();
505 assert_eq!(h1.based_on, Some("Normal".to_string()));
506 }
507
508 #[test]
509 fn find_default_style() {
510 let styles = CT_Styles::new_default();
511 let default_para = styles.get_default(StyleType::Paragraph).unwrap();
512 assert_eq!(default_para.style_id, "Normal");
513 }
514
515 #[test]
516 fn aliased_style_paragraph_properties_use_ancestor_namespace_scope() {
517 let xml = format!(
518 r#"<q:styles xmlns:q="{W_NS}" xmlns:ext="urn:producer"><q:style q:type="paragraph" q:styleId="Alias"><q:pPr><ext:jc ext:val="right"/><q:jc q:val="center"/></q:pPr></q:style></q:styles>"#
519 );
520 let parsed = CT_Styles::from_xml(xml.as_bytes()).unwrap();
521 let ppr = parsed.styles[0].ppr.as_ref().unwrap();
522 assert_eq!(ppr.jc, Some(crate::shared::ST_Jc::Center));
523 }
524
525 #[test]
526 fn direct_style_parser_uses_supplied_start_ancestor_scope() {
527 let xml = format!(
528 r#"<outer xmlns:ext="urn:producer"><q:style xmlns:q="{W_NS}" q:type="paragraph" q:styleId="Direct"><ext:pPr><ext:jc ext:val="right"/></ext:pPr><q:pPr><ext:jc ext:val="right"/><q:jc q:val="center"/></q:pPr></q:style></outer>"#
529 );
530 let mut reader = Reader::from_str(&xml);
531 let mut buf = Vec::new();
532 let parsed = loop {
533 match reader.read_event_into(&mut buf) {
534 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
535 break CT_Style::from_xml(&mut reader, element).unwrap();
536 }
537 Ok(Event::Eof) => panic!("missing style"),
538 event => {
539 event.unwrap();
540 }
541 }
542 buf.clear();
543 };
544 assert_eq!(
545 parsed.ppr.as_ref().unwrap().jc,
546 Some(crate::shared::ST_Jc::Center)
547 );
548 }
549
550 #[test]
551 fn direct_style_parser_does_not_promote_foreign_start_prefix() {
552 let xml = r#"<outer><ext:style xmlns:ext="urn:producer" ext:type="paragraph" ext:styleId="Foreign"><ext:pPr><ext:jc ext:val="right"/></ext:pPr></ext:style></outer>"#;
553 let mut reader = Reader::from_str(xml);
554 let mut buf = Vec::new();
555 let parsed = loop {
556 match reader.read_event_into(&mut buf) {
557 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
558 break CT_Style::from_xml(&mut reader, element).unwrap();
559 }
560 Ok(Event::Eof) => panic!("missing style"),
561 event => {
562 event.unwrap();
563 }
564 }
565 buf.clear();
566 };
567 assert!(parsed.ppr.is_none());
568 }
569
570 #[test]
571 fn direct_style_parser_accepts_default_word_namespace() {
572 let xml = format!(
573 r#"<outer xmlns:ext="urn:producer"><style xmlns="{W_NS}" xmlns:w="{W_NS}" w:type="paragraph" w:styleId="Direct"><ext:pPr><ext:jc ext:val="right"/></ext:pPr><pPr><ext:jc ext:val="right"/><jc w:val="center"/></pPr></style></outer>"#
574 );
575 let mut reader = Reader::from_str(&xml);
576 let mut buf = Vec::new();
577 let parsed = loop {
578 match reader.read_event_into(&mut buf) {
579 Ok(Event::Start(ref element)) if element.local_name().as_ref() == b"style" => {
580 break CT_Style::from_xml(&mut reader, element).unwrap();
581 }
582 Ok(Event::Eof) => panic!("missing style"),
583 event => {
584 event.unwrap();
585 }
586 }
587 buf.clear();
588 };
589 assert_eq!(
590 parsed.ppr.as_ref().unwrap().jc,
591 Some(crate::shared::ST_Jc::Center)
592 );
593 }
594}