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<'a>>,
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", with = "crate::rounded_float")]
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: Option<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: Option<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", with = "crate::rounded_float")]
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", with = "crate::rounded_float")]
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: Option<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<'a> {
124 #[xml(attr = "w:numId", with = "crate::rounded_float")]
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<'a>>,
130}
131
132#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
133#[cfg_attr(test, derive(PartialEq))]
134#[xml(tag = "w:lvlOverride")]
135pub struct LevelOverride<'a> {
136 #[xml(attr = "w:ilvl", with = "crate::rounded_float")]
137 pub i_level: Option<isize>,
138 #[xml(child = "w:startOverride")]
139 pub start_override: Option<StartOverride>,
140 #[xml(child = "w:lvl")]
141 pub level: Option<Level<'a>>,
142}
143
144#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
145#[cfg_attr(test, derive(PartialEq))]
146#[xml(tag = "w:startOverride")]
147pub struct StartOverride {
148 #[xml(attr = "w:val", with = "crate::rounded_float")]
149 pub value: Option<isize>,
150}
151
152#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
153#[cfg_attr(test, derive(PartialEq))]
154#[xml(tag = "w:abstractNumId")]
155pub struct AbstractNumId {
156 #[xml(attr = "w:val", with = "crate::rounded_float")]
157 pub value: Option<isize>,
158}
159
160impl<'a> Numbering<'a> {
161 pub fn numbering_details(&self, id: isize) -> Option<AbstractNum<'_>> {
166 self.numberings.iter().find_map(|n| {
167 if n.num_id != Some(id) || n.abstract_num_id.is_none() {
168 None
169 } else {
170 if let Some(abstract_num_id) = &n.abstract_num_id {
171 if let Some(abstract_numbering) = self
172 .abstract_numberings
173 .iter()
174 .find(|an| an.abstract_num_id == abstract_num_id.value)
175 {
176 let mut an = abstract_numbering.clone();
177 n.level_overrides.iter().for_each(|o| {
178 let LevelOverride {
179 i_level,
180 start_override,
181 level: _,
182 } = o;
183 if i_level.is_some() && start_override.is_some() {
184 if let Some(level) =
185 an.levels.iter_mut().find(|level| level.i_level == *i_level)
186 {
187 level.start = Some(LevelStart {
188 value: start_override.as_ref().unwrap().value,
189 });
190 }
191 }
192 });
193 return Some(an);
194 }
195 }
196
197 None
198 }
199 })
200 }
201}
202
203impl<'a> XmlWrite for Numbering<'a> {
204 fn to_writer<W: Write>(&self, writer: &mut XmlWriter<W>) -> XmlResult<()> {
205 let Numbering {
206 abstract_numberings: abstract_nums,
207 numberings: nums,
208 } = self;
209
210 log::debug!("[Numbering] Started writing.");
211
212 let _ = write!(writer.inner, "{}", crate::schema::SCHEMA_XML);
213
214 writer.write_element_start("w:numbering")?;
215
216 writer.write_attribute("xmlns:w", SCHEMA_MAIN)?;
217
218 writer.write_attribute("xmlns:w14", SCHEMA_WORDML_14)?;
219
220 writer.write_element_end_open()?;
221
222 for an in abstract_nums {
223 an.to_writer(writer)?;
224 }
225
226 for num in nums {
227 num.to_writer(writer)?;
228 }
229
230 writer.write_element_end_close("w:numbering")?;
231
232 log::debug!("[Numbering] Finished writing.");
233
234 Ok(())
235 }
236}
237
238#[cfg(test)]
239
240const NUMBERING_XML: &str = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
241 <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
242 <w:abstractNum w:abstractNumId="990">
243 <w:nsid w:val="0000A990" />
244 <w:multiLevelType w:val="multilevel" />
245 <w:lvl w:ilvl="0">
246 <w:numFmt w:val="bullet" />
247 <w:lvlText w:val=" " />
248 <w:lvlJc w:val="left" />
249 <w:pPr>
250 <w:ind w:left="720" w:hanging="360" />
251 </w:pPr>
252 </w:lvl>
253 <w:lvl w:ilvl="1">
254 <w:numFmt w:val="bullet" />
255 <w:lvlText w:val=" " />
256 <w:lvlJc w:val="left" />
257 <w:pPr>
258 <w:ind w:left="1440" w:hanging="360" />
259 </w:pPr>
260 </w:lvl>
261 </w:abstractNum>
262 <w:abstractNum w:abstractNumId="99411">
263 <w:nsid w:val="00A99411" />
264 <w:multiLevelType w:val="multilevel" />
265 <w:lvl w:ilvl="0">
266 <w:start w:val="1" />
267 <w:numFmt w:val="decimal" />
268 <w:lvlText w:val="%1." />
269 <w:lvlJc w:val="left" />
270 <w:pPr>
271 <w:ind w:left="720" w:hanging="360" />
272 </w:pPr>
273 </w:lvl>
274 <w:lvl w:ilvl="1">
275 <w:start w:val="1" />
276 <w:numFmt w:val="decimal" />
277 <w:lvlText w:val="%2." />
278 <w:lvlJc w:val="left" />
279 <w:pPr>
280 <w:ind w:left="1440" w:hanging="360" />
281 </w:pPr>
282 </w:lvl>
283 <w:lvl w:ilvl="2">
284 <w:start w:val="1" />
285 <w:numFmt w:val="decimal" />
286 <w:lvlText w:val="%3." />
287 <w:lvlJc w:val="left" />
288 <w:pPr>
289 <w:ind w:left="2160" w:hanging="360" />
290 </w:pPr>
291 </w:lvl>
292 </w:abstractNum>
293 <w:num w:numId="1000">
294 <w:abstractNumId w:val="990" />
295 </w:num>
296 <w:num w:numId="1001">
297 <w:abstractNumId w:val="99411" />
298 <w:lvlOverride w:ilvl="0">
299 <w:startOverride w:val="1" />
300 </w:lvlOverride>
301 <w:lvlOverride w:ilvl="1">
302 <w:startOverride w:val="1" />
303 </w:lvlOverride>
304 </w:num>
305 </w:numbering>
306"#;
307
308#[test]
309fn xml_parsing() {
310 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
311 assert_eq!(numbering.abstract_numberings.len(), 2);
312 assert_eq!(numbering.numberings.len(), 2);
313 assert_eq!(
314 numbering.abstract_numberings[0]
315 .nsid
316 .as_ref()
317 .unwrap()
318 .value
319 .as_ref()
320 .unwrap()
321 .as_ref(),
322 "0000A990"
323 );
324 assert_eq!(
325 numbering.abstract_numberings[0].levels[0]
326 .number_format
327 .as_ref()
328 .unwrap()
329 .value,
330 "bullet"
331 );
332 assert_eq!(
333 numbering.numberings[0]
334 .abstract_num_id
335 .as_ref()
336 .unwrap()
337 .value,
338 Some(990_isize)
339 );
340}
341
342#[test]
343fn find_numbering_details() {
344 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
345 if let Some(num) = numbering.numbering_details(
346 numbering.numberings[0]
347 .abstract_num_id
348 .as_ref()
349 .unwrap()
350 .value
351 .unwrap(),
352 ) {
353 assert_eq!(
354 num.levels[0].number_format,
355 Some(NumFmt {
356 value: Cow::Borrowed("bullet")
357 })
358 );
359 }
360 if let Some(num) = numbering.numbering_details(1001) {
361 assert_eq!(
362 num.levels[0].number_format,
363 Some(NumFmt {
364 value: Cow::Borrowed("decimal")
365 })
366 );
367 assert_eq!(
368 num.levels[1].level_text,
369 Some(LevelText {
370 value: Some(Cow::Borrowed("%2."))
371 })
372 );
373 }
374}
375
376#[test]
377fn xml_writing() {
378 fn replace_whitespace(input: &str, replacement: &str) -> String {
379 let mut result = String::new();
380 let mut last_was_whitespace_or_bracket = false;
381
382 for c in input.chars() {
383 if c.is_whitespace() {
384 if !last_was_whitespace_or_bracket {
385 result.push_str(replacement);
386 last_was_whitespace_or_bracket = true;
387 }
388 } else {
389 result.push(c);
390 last_was_whitespace_or_bracket = if c == '>' || c == '"' { true } else { false };
391 }
392 }
393
394 result
395 }
396
397 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
398 let result = numbering.to_string().unwrap();
399 assert_eq!(
400 replace_whitespace(NUMBERING_XML, " "),
401 replace_whitespace(
402 &result.replace(&format!(" xmlns:w14=\"{SCHEMA_WORDML_14}\""), ""),
403 " "
404 )
405 );
406}
407#[test]
408fn lvl_override_parsing() {
409 let xml = r#"
410 <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
411 <w:num w:numId="2">
412 <w:abstractNumId w:val="1"/>
413 <w:lvlOverride w:ilvl="0">
414 <w:startOverride w:val="10"/>
415 <w:lvl w:ilvl="0">
416 <w:numFmt w:val="upperLetter"/>
417 <w:lvlText w:val="%1."/>
418 </w:lvl>
419 </w:lvlOverride>
420 </w:num>
421 </w:numbering>
422 "#;
423 let numbering = Numbering::from_str(xml).unwrap();
424 let num = &numbering.numberings[0];
425 let override_def = &num.level_overrides[0];
426
427 assert_eq!(override_def.i_level, Some(0));
428 assert_eq!(
429 override_def.start_override.as_ref().unwrap().value,
430 Some(10)
431 );
432
433 let level = override_def.level.as_ref().unwrap();
434 assert_eq!(level.i_level, Some(0));
435 assert_eq!(level.number_format.as_ref().unwrap().value, "upperLetter");
436}