Skip to main content

rs_docx/document/
numbering.rs

1//! Comments part
2//!
3//! The corresponding ZIP item is `/word/numbering.xml`.
4#![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")]
17/// Numbering defines ordered and unordered lists.
18pub struct Numbering<'a> {
19    #[xml(child = "w:abstractNum")]
20    /// Abstract numberings are not used directly when laying out your document.
21    /// Instead, they are referred to by the numberings.
22    pub abstract_numberings: Vec<AbstractNum<'a>>,
23    #[xml(child = "w:num")]
24    /// Numberings are used by your document and refer to abstract numberings for layout.
25    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")]
89/// TODO Replace by enum NumberFormat
90pub 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")]
98/// TODO Replace by enum NumberFormat
99pub 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    /// Actual numberings refer to abstract numberings, and may overrule some settings.
162    /// This helper function takes an numbering id that is provided in a paragraph, looks up
163    /// the details in the numbering section and merges it with the abstract numbering to get
164    /// a complete AbstractNum object.
165    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}