1#![allow(unused_must_use)]
5
6use hard_xml::{XmlRead, XmlResult, XmlWrite, XmlWriter};
7use std::{borrow::Cow, io::Write};
8
9use crate::{
10 formatting::{CharacterProperty, Indent, JustificationVal},
11 schema::{SCHEMA_MAIN, SCHEMA_WORDML_14},
12};
13
14#[derive(Debug, Default, XmlRead, Clone)]
15#[cfg_attr(test, derive(PartialEq))]
16#[xml(tag = "w:numbering")]
17pub struct Numbering<'a> {
19 #[xml(child = "w:abstractNum")]
20 pub abstract_numberings: Vec<AbstractNum<'a>>,
23 #[xml(child = "w:num")]
24 pub numberings: Vec<Num>,
26}
27
28#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
29#[cfg_attr(test, derive(PartialEq))]
30#[xml(tag = "w:abstractNum")]
31pub struct AbstractNum<'a> {
32 #[xml(attr = "w:abstractNumId")]
33 pub abstract_num_id: Option<isize>,
34 #[xml(child = "w:nsid")]
35 pub nsid: Option<Nsid<'a>>,
36 #[xml(child = "w:multiLevelType")]
37 pub multi_level_type: MultiLevelType<'a>,
38 #[xml(child = "w:lvl")]
39 pub levels: Vec<Level<'a>>,
40}
41
42#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
43#[cfg_attr(test, derive(PartialEq))]
44#[xml(tag = "w:nsid")]
45pub struct Nsid<'a> {
46 #[xml(attr = "w:val")]
47 pub value: Cow<'a, str>,
48}
49
50#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
51#[cfg_attr(test, derive(PartialEq))]
52#[xml(tag = "w:multiLevelType")]
53pub struct MultiLevelType<'a> {
54 #[xml(attr = "w:val")]
55 pub value: Cow<'a, str>,
56}
57
58#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
59#[cfg_attr(test, derive(PartialEq))]
60#[xml(tag = "w:lvl")]
61pub struct Level<'a> {
62 #[xml(attr = "w:ilvl")]
63 pub i_level: Option<isize>,
64 #[xml(child = "w:start")]
65 pub start: Option<LevelStart>,
66 #[xml(child = "w:numFmt")]
67 pub number_format: Option<NumFmt<'a>>,
68 #[xml(child = "w:lvlText")]
69 pub level_text: Option<LevelText<'a>>,
70 #[xml(child = "w:lvlJc")]
71 pub justification: Option<LevelJustification>,
72 #[xml(child = "w:pPr")]
73 pub p_pr: Option<PPr>,
74 #[xml(child = "w:rPr")]
75 pub r_pr: Vec<CharacterProperty<'a>>,
76}
77
78#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
79#[cfg_attr(test, derive(PartialEq))]
80#[xml(tag = "w:pPr")]
81pub struct PPr {
82 #[xml(child = "w:ind")]
83 pub indent: Option<Indent>,
84}
85
86#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
87#[cfg_attr(test, derive(PartialEq))]
88#[xml(tag = "w:numFmt")]
89pub struct NumFmt<'a> {
91 #[xml(attr = "w:val")]
92 pub value: Cow<'a, str>,
93}
94
95#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
96#[cfg_attr(test, derive(PartialEq))]
97#[xml(tag = "w:start")]
98pub struct LevelStart {
100 #[xml(attr = "w:val")]
101 pub value: Option<isize>,
102}
103
104#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
105#[cfg_attr(test, derive(PartialEq))]
106#[xml(tag = "w:lvlText")]
107pub struct LevelText<'a> {
108 #[xml(attr = "w:val")]
109 pub value: Cow<'a, str>,
110}
111
112#[derive(Debug, XmlRead, XmlWrite, Clone)]
113#[cfg_attr(test, derive(PartialEq))]
114#[xml(tag = "w:lvlJc")]
115pub struct LevelJustification {
116 #[xml(attr = "w:val")]
117 pub value: JustificationVal,
118}
119
120#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
121#[cfg_attr(test, derive(PartialEq))]
122#[xml(tag = "w:num")]
123pub struct Num {
124 #[xml(attr = "w:numId")]
125 pub num_id: Option<isize>,
126 #[xml(child = "w:abstractNumId")]
127 pub abstract_num_id: Option<AbstractNumId>,
128 #[xml(child = "w:lvlOverride")]
129 pub level_overrides: Vec<LevelOverride>,
130}
131
132#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
133#[cfg_attr(test, derive(PartialEq))]
134#[xml(tag = "w:lvlOverride")]
135pub struct LevelOverride {
136 #[xml(attr = "w:ilvl")]
137 pub i_level: Option<isize>,
138 #[xml(child = "w:startOverride")]
139 pub start_override: Option<StartOverride>,
140}
141
142#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
143#[cfg_attr(test, derive(PartialEq))]
144#[xml(tag = "w:startOverride")]
145pub struct StartOverride {
146 #[xml(attr = "w:val")]
147 pub value: Option<isize>,
148}
149
150#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
151#[cfg_attr(test, derive(PartialEq))]
152#[xml(tag = "w:abstractNumId")]
153pub struct AbstractNumId {
154 #[xml(attr = "w:val")]
155 pub value: Option<isize>,
156}
157
158impl<'a> Numbering<'a> {
159 pub fn numbering_details(&self, id: isize) -> Option<AbstractNum> {
164 self.numberings.iter().find_map(|n| {
165 if n.num_id != Some(id) || n.abstract_num_id.is_none() {
166 None
167 } else {
168 if let Some(abstract_num_id) = &n.abstract_num_id {
169 if let Some(abstract_numbering) = self
170 .abstract_numberings
171 .iter()
172 .find(|an| an.abstract_num_id == abstract_num_id.value)
173 {
174 let mut an = abstract_numbering.clone();
175 n.level_overrides.iter().for_each(|o| {
176 let LevelOverride {
177 i_level,
178 start_override,
179 } = o;
180 if i_level.is_some() && start_override.is_some() {
181 if let Some(level) =
182 an.levels.iter_mut().find(|level| level.i_level == *i_level)
183 {
184 level.start = Some(LevelStart {
185 value: start_override.as_ref().unwrap().value.clone(),
186 });
187 }
188 }
189 });
190 return Some(an);
191 }
192 }
193
194 None
195 }
196 })
197 }
198}
199
200impl<'a> XmlWrite for Numbering<'a> {
201 fn to_writer<W: Write>(&self, writer: &mut XmlWriter<W>) -> XmlResult<()> {
202 let Numbering {
203 abstract_numberings: abstract_nums,
204 numberings: nums,
205 } = self;
206
207 log::debug!("[Numbering] Started writing.");
208
209 let _ = write!(writer.inner, "{}", crate::schema::SCHEMA_XML);
210
211 writer.write_element_start("w:numbering")?;
212
213 writer.write_attribute("xmlns:w", SCHEMA_MAIN)?;
214
215 writer.write_attribute("xmlns:w14", SCHEMA_WORDML_14)?;
216
217 writer.write_element_end_open()?;
218
219 for an in abstract_nums {
220 an.to_writer(writer)?;
221 }
222
223 for num in nums {
224 num.to_writer(writer)?;
225 }
226
227 writer.write_element_end_close("w:numbering")?;
228
229 log::debug!("[Numbering] Finished writing.");
230
231 Ok(())
232 }
233}
234
235#[cfg(test)]
236
237const NUMBERING_XML: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
238 <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
239 <w:abstractNum w:abstractNumId="990">
240 <w:nsid w:val="0000A990" />
241 <w:multiLevelType w:val="multilevel" />
242 <w:lvl w:ilvl="0">
243 <w:numFmt w:val="bullet" />
244 <w:lvlText w:val=" " />
245 <w:lvlJc w:val="left" />
246 <w:pPr>
247 <w:ind w:left="720" w:hanging="360" />
248 </w:pPr>
249 </w:lvl>
250 <w:lvl w:ilvl="1">
251 <w:numFmt w:val="bullet" />
252 <w:lvlText w:val=" " />
253 <w:lvlJc w:val="left" />
254 <w:pPr>
255 <w:ind w:left="1440" w:hanging="360" />
256 </w:pPr>
257 </w:lvl>
258 </w:abstractNum>
259 <w:abstractNum w:abstractNumId="99411">
260 <w:nsid w:val="00A99411" />
261 <w:multiLevelType w:val="multilevel" />
262 <w:lvl w:ilvl="0">
263 <w:start w:val="1" />
264 <w:numFmt w:val="decimal" />
265 <w:lvlText w:val="%1." />
266 <w:lvlJc w:val="left" />
267 <w:pPr>
268 <w:ind w:left="720" w:hanging="360" />
269 </w:pPr>
270 </w:lvl>
271 <w:lvl w:ilvl="1">
272 <w:start w:val="1" />
273 <w:numFmt w:val="decimal" />
274 <w:lvlText w:val="%2." />
275 <w:lvlJc w:val="left" />
276 <w:pPr>
277 <w:ind w:left="1440" w:hanging="360" />
278 </w:pPr>
279 </w:lvl>
280 <w:lvl w:ilvl="2">
281 <w:start w:val="1" />
282 <w:numFmt w:val="decimal" />
283 <w:lvlText w:val="%3." />
284 <w:lvlJc w:val="left" />
285 <w:pPr>
286 <w:ind w:left="2160" w:hanging="360" />
287 </w:pPr>
288 </w:lvl>
289 </w:abstractNum>
290 <w:num w:numId="1000">
291 <w:abstractNumId w:val="990" />
292 </w:num>
293 <w:num w:numId="1001">
294 <w:abstractNumId w:val="99411" />
295 <w:lvlOverride w:ilvl="0">
296 <w:startOverride w:val="1" />
297 </w:lvlOverride>
298 <w:lvlOverride w:ilvl="1">
299 <w:startOverride w:val="1" />
300 </w:lvlOverride>
301 </w:num>
302 </w:numbering>
303"#;
304
305#[test]
306fn xml_parsing() {
307 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
308 assert_eq!(numbering.abstract_numberings.len(), 2);
309 assert_eq!(numbering.numberings.len(), 2);
310 assert_eq!(
311 numbering.abstract_numberings[0]
312 .nsid
313 .as_ref()
314 .unwrap()
315 .value,
316 "0000A990"
317 );
318 assert_eq!(
319 numbering.abstract_numberings[0].levels[0]
320 .number_format
321 .as_ref()
322 .unwrap()
323 .value,
324 "bullet"
325 );
326 assert_eq!(
327 numbering.numberings[0]
328 .abstract_num_id
329 .as_ref()
330 .unwrap()
331 .value,
332 Some(990_isize)
333 );
334}
335
336#[test]
337fn find_numbering_details() {
338 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
339 if let Some(num) = numbering.numbering_details(
340 numbering.numberings[0]
341 .abstract_num_id
342 .as_ref()
343 .unwrap()
344 .value
345 .unwrap(),
346 ) {
347 assert_eq!(
348 num.levels[0].number_format,
349 Some(NumFmt {
350 value: Cow::Borrowed("bullet")
351 })
352 );
353 }
354 if let Some(num) = numbering.numbering_details(1001) {
355 assert_eq!(
356 num.levels[0].number_format,
357 Some(NumFmt {
358 value: Cow::Borrowed("decimal")
359 })
360 );
361 assert_eq!(
362 num.levels[1].level_text,
363 Some(LevelText {
364 value: Cow::Borrowed("%2.")
365 })
366 );
367 }
368}
369
370#[test]
371fn xml_writing() {
372 fn replace_whitespace(input: &str, replacement: &str) -> String {
373 let mut result = String::new();
374 let mut last_was_whitespace_or_bracket = false;
375
376 for c in input.chars() {
377 if c.is_whitespace() {
378 if !last_was_whitespace_or_bracket {
379 result.push_str(replacement);
380 last_was_whitespace_or_bracket = true;
381 }
382 } else {
383 result.push(c);
384 last_was_whitespace_or_bracket = if c == '>' || c == '"' { true } else { false };
385 }
386 }
387
388 result
389 }
390
391 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
392 let result = numbering.to_string().unwrap();
393 assert_eq!(
394 replace_whitespace(NUMBERING_XML, " "),
395 replace_whitespace(
396 &result.replace(&format!(" xmlns:w14=\"{SCHEMA_WORDML_14}\""), ""),
397 " "
398 )
399 );
400}