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: 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!(numbering.abstract_numberings[0].nsid.value, "0000A990");
311 assert_eq!(
312 numbering.abstract_numberings[0].levels[0]
313 .number_format
314 .as_ref()
315 .unwrap()
316 .value,
317 "bullet"
318 );
319 assert_eq!(
320 numbering.numberings[0]
321 .abstract_num_id
322 .as_ref()
323 .unwrap()
324 .value,
325 Some(990_isize)
326 );
327}
328
329#[test]
330fn find_numbering_details() {
331 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
332 if let Some(num) = numbering.numbering_details(
333 numbering.numberings[0]
334 .abstract_num_id
335 .as_ref()
336 .unwrap()
337 .value
338 .unwrap(),
339 ) {
340 assert_eq!(
341 num.levels[0].number_format,
342 Some(NumFmt {
343 value: Cow::Borrowed("bullet")
344 })
345 );
346 }
347 if let Some(num) = numbering.numbering_details(1001) {
348 assert_eq!(
349 num.levels[0].number_format,
350 Some(NumFmt {
351 value: Cow::Borrowed("decimal")
352 })
353 );
354 assert_eq!(
355 num.levels[1].level_text,
356 Some(LevelText {
357 value: Cow::Borrowed("%2.")
358 })
359 );
360 }
361}
362
363#[test]
364fn xml_writing() {
365 fn replace_whitespace(input: &str, replacement: &str) -> String {
366 let mut result = String::new();
367 let mut last_was_whitespace_or_bracket = false;
368
369 for c in input.chars() {
370 if c.is_whitespace() {
371 if !last_was_whitespace_or_bracket {
372 result.push_str(replacement);
373 last_was_whitespace_or_bracket = true;
374 }
375 } else {
376 result.push(c);
377 last_was_whitespace_or_bracket = if c == '>' || c == '"' { true } else { false };
378 }
379 }
380
381 result
382 }
383
384 let numbering = Numbering::from_str(NUMBERING_XML).unwrap();
385 let result = numbering.to_string().unwrap();
386 assert_eq!(
387 replace_whitespace(NUMBERING_XML, " "),
388 replace_whitespace(
389 &result.replace(&format!(" xmlns:w14=\"{SCHEMA_WORDML_14}\""), ""),
390 " "
391 )
392 );
393}