1use std::io::Write;
2
3use oxml_core::OxmlError;
4use oxml_core::raw_xml::{capture_element, capture_empty_element};
5use oxml_core::xml::{local_name, matches_local_name};
6use quick_xml::events::{BytesEnd, BytesStart, Event};
7use quick_xml::{Reader, Writer, XmlVersion};
8
9use crate::namespace::reject_conflicting_a_prefix;
10use crate::order::OrderedRawChildren;
11
12use super::body::{Result, TextError, missing_end};
13use super::paragraph::CT_TextParagraphProperties;
14
15#[allow(non_camel_case_types)]
17#[derive(Clone, Debug, Default, Eq, PartialEq)]
18pub struct CT_TextListStyle {
19 pub default_paragraph_properties: Option<CT_TextParagraphProperties>,
20 pub level1: Option<CT_TextParagraphProperties>,
21 pub level2: Option<CT_TextParagraphProperties>,
22 pub level3: Option<CT_TextParagraphProperties>,
23 pub level4: Option<CT_TextParagraphProperties>,
24 pub level5: Option<CT_TextParagraphProperties>,
25 pub level6: Option<CT_TextParagraphProperties>,
26 pub level7: Option<CT_TextParagraphProperties>,
27 pub level8: Option<CT_TextParagraphProperties>,
28 pub level9: Option<CT_TextParagraphProperties>,
29 raw_attributes: Vec<(String, String)>,
30 raw_children: OrderedRawChildren,
31}
32
33impl CT_TextListStyle {
34 pub fn from_xml(xml: &[u8]) -> Result<Self> {
37 let mut reader = Reader::from_reader(xml);
38 let mut buffer = Vec::new();
39 loop {
40 match reader
41 .read_event_into(&mut buffer)
42 .map_err(OxmlError::from)?
43 {
44 Event::Start(element) if is_list_style_root(element.name().as_ref()) => {
45 reject_conflicting_a_prefix(&element)?;
46 return Self::from_element(&mut reader, &element);
47 }
48 Event::Empty(element) if is_list_style_root(element.name().as_ref()) => {
49 reject_conflicting_a_prefix(&element)?;
50 return Self::from_start(&element);
51 }
52 Event::Start(element) | Event::Empty(element) => {
53 return Err(unexpected(&element));
54 }
55 Event::Eof => {
56 return Err(TextError::Xml(OxmlError::MissingElement(
57 "DrawingML list style".to_owned(),
58 )));
59 }
60 _ => {}
61 }
62 buffer.clear();
63 }
64 }
65
66 fn from_start(start: &BytesStart<'_>) -> Result<Self> {
67 Ok(Self {
68 raw_attributes: capture_raw_attributes(start)?,
69 ..Self::default()
70 })
71 }
72
73 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
74 let mut style = Self::from_start(start)?;
75 let root_name = local_name(start.name().as_ref()).to_vec();
76 let mut boundary = 0;
77 let mut buffer = Vec::new();
78 loop {
79 match reader
80 .read_event_into(&mut buffer)
81 .map_err(OxmlError::from)?
82 {
83 Event::Start(element) => {
84 let name = local_name(element.name().as_ref()).to_vec();
85 if name == b"defPPr" || list_level(&name)?.is_some() {
86 reject_conflicting_a_prefix(&element)?;
87 }
88 let raw = capture_element(reader, &element)?;
89 style.capture_child(&name, raw, &mut boundary)?;
90 }
91 Event::Empty(element) => {
92 let name = local_name(element.name().as_ref()).to_vec();
93 if name == b"defPPr" || list_level(&name)?.is_some() {
94 reject_conflicting_a_prefix(&element)?;
95 }
96 let raw = capture_empty_element(&element)?;
97 style.capture_child(&name, raw, &mut boundary)?;
98 }
99 Event::End(element)
100 if local_name(element.name().as_ref()) == root_name.as_slice() =>
101 {
102 return Ok(style);
103 }
104 Event::Eof => {
105 return Err(missing_end(
106 std::str::from_utf8(&root_name).unwrap_or("list style"),
107 ));
108 }
109 _ => {}
110 }
111 buffer.clear();
112 }
113 }
114
115 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
116 if name == b"defPPr" {
117 if self.default_paragraph_properties.is_some() {
118 return Err(TextError::DuplicateElement("defPPr".to_owned()));
119 }
120 self.default_paragraph_properties = Some(CT_TextParagraphProperties::from_xml(&raw)?);
121 *boundary = (*boundary).max(1);
122 return Ok(());
123 }
124 if let Some(level) = list_level(name)? {
125 if self.level(level).is_some() {
126 return Err(TextError::DuplicateElement(
127 String::from_utf8_lossy(name).into_owned(),
128 ));
129 }
130 self.set_level(level, CT_TextParagraphProperties::from_xml(&raw)?);
131 *boundary = (*boundary).max(level + 1);
132 return Ok(());
133 }
134
135 let schema_boundary = match name {
136 b"extLst" => 10,
137 _ => *boundary,
138 };
139 self.raw_children
140 .push((*boundary).max(schema_boundary), raw);
141 *boundary = (*boundary).max(schema_boundary);
142 Ok(())
143 }
144
145 pub fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
147 self.write_xml_as(writer, "a:lstStyle")
148 }
149
150 pub fn write_xml_as<W: Write>(&self, writer: &mut Writer<W>, tag: &str) -> Result<()> {
152 let mut start = BytesStart::new(tag);
153 push_raw_attributes(&mut start, &self.raw_attributes);
154 let has_modelled = self.default_paragraph_properties.is_some()
155 || (1..=9).any(|level| self.level(level).is_some());
156 if !has_modelled && self.raw_children.is_empty() {
157 return write_empty(writer, start);
158 }
159
160 write_start(writer, start)?;
161 emit_raw(writer, self.raw_children.at(0))?;
162 if let Some(properties) = &self.default_paragraph_properties {
163 properties.write_xml(writer, "a:defPPr")?;
164 }
165 for level in 1..=9 {
166 emit_raw(writer, self.raw_children.at(level))?;
167 if let Some(properties) = self.level(level) {
168 let tag = level_tag(level)
169 .ok_or_else(|| TextError::UnexpectedElement(format!("list level {level}")))?;
170 properties.write_xml(writer, tag)?;
171 }
172 }
173 emit_raw(writer, self.raw_children.at(10))?;
174 write_end(writer, tag)
175 }
176
177 pub fn to_xml(&self) -> Result<Vec<u8>> {
179 let mut writer = Writer::new(Vec::new());
180 self.write_xml(&mut writer)?;
181 Ok(writer.into_inner())
182 }
183
184 pub fn level(&self, level: usize) -> Option<&CT_TextParagraphProperties> {
186 match level {
187 1 => self.level1.as_ref(),
188 2 => self.level2.as_ref(),
189 3 => self.level3.as_ref(),
190 4 => self.level4.as_ref(),
191 5 => self.level5.as_ref(),
192 6 => self.level6.as_ref(),
193 7 => self.level7.as_ref(),
194 8 => self.level8.as_ref(),
195 9 => self.level9.as_ref(),
196 _ => None,
197 }
198 }
199
200 pub fn raw_children(&self) -> &OrderedRawChildren {
201 &self.raw_children
202 }
203
204 fn set_level(&mut self, level: usize, properties: CT_TextParagraphProperties) {
205 let slot = match level {
206 1 => &mut self.level1,
207 2 => &mut self.level2,
208 3 => &mut self.level3,
209 4 => &mut self.level4,
210 5 => &mut self.level5,
211 6 => &mut self.level6,
212 7 => &mut self.level7,
213 8 => &mut self.level8,
214 9 => &mut self.level9,
215 _ => return,
216 };
217 *slot = Some(properties);
218 }
219}
220
221fn is_list_style_root(name: &[u8]) -> bool {
222 matches_local_name(name, b"lstStyle")
223 || matches_local_name(name, b"defaultTextStyle")
224 || matches_local_name(name, b"titleStyle")
225 || matches_local_name(name, b"bodyStyle")
226 || matches_local_name(name, b"otherStyle")
227 || matches_local_name(name, b"notesStyle")
228}
229
230fn list_level(name: &[u8]) -> Result<Option<usize>> {
231 let Some(number) = name
232 .strip_prefix(b"lvl")
233 .and_then(|name| name.strip_suffix(b"pPr"))
234 else {
235 return Ok(None);
236 };
237 if !number.is_empty() && number.iter().all(u8::is_ascii_digit) {
238 if number.len() == 1 && (b'1'..=b'9').contains(&number[0]) {
239 return Ok(Some(usize::from(number[0] - b'0')));
240 }
241 return Err(TextError::UnexpectedElement(
242 String::from_utf8_lossy(name).into_owned(),
243 ));
244 }
245 Ok(None)
246}
247
248fn level_tag(level: usize) -> Option<&'static str> {
249 match level {
250 1 => Some("a:lvl1pPr"),
251 2 => Some("a:lvl2pPr"),
252 3 => Some("a:lvl3pPr"),
253 4 => Some("a:lvl4pPr"),
254 5 => Some("a:lvl5pPr"),
255 6 => Some("a:lvl6pPr"),
256 7 => Some("a:lvl7pPr"),
257 8 => Some("a:lvl8pPr"),
258 9 => Some("a:lvl9pPr"),
259 _ => None,
260 }
261}
262
263fn capture_raw_attributes(start: &BytesStart<'_>) -> Result<Vec<(String, String)>> {
264 let mut raw = Vec::new();
265 for attribute in start.attributes() {
266 let attribute = attribute.map_err(OxmlError::from)?;
267 let name = std::str::from_utf8(attribute.key.as_ref())
268 .map_err(OxmlError::from)?
269 .to_owned();
270 let value = attribute
271 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())
272 .map_err(OxmlError::from)?
273 .into_owned();
274 raw.push((name, value));
275 }
276 Ok(raw)
277}
278
279fn push_raw_attributes(start: &mut BytesStart<'_>, attributes: &[(String, String)]) {
280 for (name, value) in attributes {
281 start.push_attribute((name.as_str(), value.as_str()));
282 }
283}
284
285fn emit_raw<'a, W: Write>(
286 writer: &mut Writer<W>,
287 children: impl Iterator<Item = &'a [u8]>,
288) -> Result<()> {
289 for child in children {
290 writer.get_mut().write_all(child).map_err(OxmlError::from)?;
291 }
292 Ok(())
293}
294
295fn write_start<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
296 writer
297 .write_event(Event::Start(start))
298 .map_err(OxmlError::from)?;
299 Ok(())
300}
301
302fn write_empty<W: Write>(writer: &mut Writer<W>, start: BytesStart<'_>) -> Result<()> {
303 writer
304 .write_event(Event::Empty(start))
305 .map_err(OxmlError::from)?;
306 Ok(())
307}
308
309fn write_end<W: Write>(writer: &mut Writer<W>, tag: &str) -> Result<()> {
310 writer
311 .write_event(Event::End(BytesEnd::new(tag)))
312 .map_err(OxmlError::from)?;
313 Ok(())
314}
315
316fn unexpected(element: &BytesStart<'_>) -> TextError {
317 TextError::UnexpectedElement(String::from_utf8_lossy(element.name().as_ref()).into_owned())
318}