linch_docx_rs/document/
numbering.rs

1//! Numbering definitions (numbering.xml)
2//!
3//! This module handles list numbering in DOCX documents.
4
5use crate::error::Result;
6use crate::xml::{get_w_val, RawXmlElement, RawXmlNode};
7use quick_xml::events::{BytesEnd, BytesStart, Event};
8use quick_xml::{Reader, Writer};
9use std::collections::HashMap;
10use std::io::BufRead;
11
12/// Numbering definitions from numbering.xml
13#[derive(Clone, Debug, Default)]
14pub struct Numbering {
15    /// Abstract numbering definitions
16    pub abstract_nums: HashMap<u32, AbstractNum>,
17    /// Numbering instances
18    pub nums: HashMap<u32, Num>,
19    /// Unknown children (preserved for round-trip)
20    pub unknown_children: Vec<RawXmlNode>,
21}
22
23/// Abstract numbering definition (w:abstractNum)
24#[derive(Clone, Debug, Default)]
25pub struct AbstractNum {
26    /// Abstract numbering ID
27    pub abstract_num_id: u32,
28    /// Multi-level type
29    pub multi_level_type: Option<String>,
30    /// Level definitions
31    pub levels: HashMap<u8, Level>,
32    /// Unknown children (preserved)
33    pub unknown_children: Vec<RawXmlNode>,
34}
35
36/// Numbering instance (w:num)
37#[derive(Clone, Debug)]
38pub struct Num {
39    /// Numbering ID (referenced by paragraphs)
40    pub num_id: u32,
41    /// Referenced abstract numbering ID
42    pub abstract_num_id: u32,
43    /// Level overrides
44    pub level_overrides: Vec<LevelOverride>,
45}
46
47/// Level definition (w:lvl)
48#[derive(Clone, Debug, Default)]
49pub struct Level {
50    /// Level index (0-8)
51    pub ilvl: u8,
52    /// Start value
53    pub start: Option<u32>,
54    /// Number format
55    pub num_fmt: Option<NumberFormat>,
56    /// Level text (e.g., "%1.", "%1.%2.")
57    pub level_text: Option<String>,
58    /// Level justification
59    pub lvl_jc: Option<String>,
60    /// Paragraph properties for this level
61    pub p_pr: Option<LevelParagraphProperties>,
62    /// Run properties for this level
63    pub r_pr: Option<LevelRunProperties>,
64    /// Unknown children (preserved)
65    pub unknown_children: Vec<RawXmlNode>,
66}
67
68/// Level override (w:lvlOverride)
69#[derive(Clone, Debug)]
70pub struct LevelOverride {
71    /// Level index
72    pub ilvl: u8,
73    /// Start override
74    pub start_override: Option<u32>,
75    /// Level definition override
76    pub lvl: Option<Level>,
77}
78
79/// Simplified paragraph properties for numbering levels
80#[derive(Clone, Debug, Default)]
81pub struct LevelParagraphProperties {
82    /// Left indentation (twips)
83    pub ind_left: Option<i32>,
84    /// Hanging indentation (twips)
85    pub ind_hanging: Option<i32>,
86    /// Unknown children (preserved)
87    pub unknown_children: Vec<RawXmlNode>,
88}
89
90/// Simplified run properties for numbering levels
91#[derive(Clone, Debug, Default)]
92pub struct LevelRunProperties {
93    /// Unknown children (preserved - we don't parse run props deeply here)
94    pub unknown_children: Vec<RawXmlNode>,
95}
96
97/// Number format
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub enum NumberFormat {
100    /// 1, 2, 3
101    Decimal,
102    /// I, II, III
103    UpperRoman,
104    /// i, ii, iii
105    LowerRoman,
106    /// A, B, C
107    UpperLetter,
108    /// a, b, c
109    LowerLetter,
110    /// •
111    Bullet,
112    /// 一, 二, 三
113    ChineseCountingThousand,
114    /// None (no number)
115    None,
116    /// Other format (preserved as string)
117    Other(String),
118}
119
120impl std::str::FromStr for NumberFormat {
121    type Err = std::convert::Infallible;
122
123    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
124        Ok(match s {
125            "decimal" => NumberFormat::Decimal,
126            "upperRoman" => NumberFormat::UpperRoman,
127            "lowerRoman" => NumberFormat::LowerRoman,
128            "upperLetter" => NumberFormat::UpperLetter,
129            "lowerLetter" => NumberFormat::LowerLetter,
130            "bullet" => NumberFormat::Bullet,
131            "chineseCountingThousand" => NumberFormat::ChineseCountingThousand,
132            "none" => NumberFormat::None,
133            other => NumberFormat::Other(other.to_string()),
134        })
135    }
136}
137
138impl NumberFormat {
139    /// Convert to string
140    pub fn as_str(&self) -> &str {
141        match self {
142            NumberFormat::Decimal => "decimal",
143            NumberFormat::UpperRoman => "upperRoman",
144            NumberFormat::LowerRoman => "lowerRoman",
145            NumberFormat::UpperLetter => "upperLetter",
146            NumberFormat::LowerLetter => "lowerLetter",
147            NumberFormat::Bullet => "bullet",
148            NumberFormat::ChineseCountingThousand => "chineseCountingThousand",
149            NumberFormat::None => "none",
150            NumberFormat::Other(s) => s,
151        }
152    }
153
154    /// Check if this is a bullet format
155    pub fn is_bullet(&self) -> bool {
156        matches!(self, NumberFormat::Bullet)
157    }
158}
159
160impl Numbering {
161    /// Parse numbering.xml content
162    pub fn from_xml(xml: &str) -> Result<Self> {
163        let mut reader = Reader::from_str(xml);
164        reader.config_mut().trim_text(true);
165
166        let mut numbering = Numbering::default();
167        let mut buf = Vec::new();
168
169        loop {
170            match reader.read_event_into(&mut buf)? {
171                Event::Start(e) => {
172                    let local = e.name().local_name();
173                    match local.as_ref() {
174                        b"abstractNum" => {
175                            let abs_num = AbstractNum::from_reader(&mut reader, &e)?;
176                            numbering
177                                .abstract_nums
178                                .insert(abs_num.abstract_num_id, abs_num);
179                        }
180                        b"num" => {
181                            let num = Num::from_reader(&mut reader, &e)?;
182                            numbering.nums.insert(num.num_id, num);
183                        }
184                        b"numbering" => {
185                            // Root element, continue
186                        }
187                        _ => {
188                            // Unknown - preserve
189                            let raw = RawXmlElement::from_reader(&mut reader, &e)?;
190                            numbering.unknown_children.push(RawXmlNode::Element(raw));
191                        }
192                    }
193                }
194                Event::Empty(e) => {
195                    // Empty elements at root level - preserve
196                    let raw = RawXmlElement {
197                        name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
198                        attributes: e
199                            .attributes()
200                            .filter_map(|a| a.ok())
201                            .map(|a| {
202                                (
203                                    String::from_utf8_lossy(a.key.as_ref()).to_string(),
204                                    String::from_utf8_lossy(&a.value).to_string(),
205                                )
206                            })
207                            .collect(),
208                        children: Vec::new(),
209                        self_closing: true,
210                    };
211                    numbering.unknown_children.push(RawXmlNode::Element(raw));
212                }
213                Event::Eof => break,
214                _ => {}
215            }
216            buf.clear();
217        }
218
219        Ok(numbering)
220    }
221
222    /// Serialize to XML
223    pub fn to_xml(&self) -> Result<String> {
224        let mut buffer = Vec::new();
225        let mut writer = Writer::new(&mut buffer);
226
227        // XML declaration
228        writer.write_event(Event::Decl(quick_xml::events::BytesDecl::new(
229            "1.0",
230            Some("UTF-8"),
231            Some("yes"),
232        )))?;
233
234        // Root element with namespaces
235        let mut start = BytesStart::new("w:numbering");
236        start.push_attribute((
237            "xmlns:w",
238            "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
239        ));
240        start.push_attribute((
241            "xmlns:r",
242            "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
243        ));
244        writer.write_event(Event::Start(start))?;
245
246        // Write abstract nums (sorted by ID for consistency)
247        let mut abs_ids: Vec<_> = self.abstract_nums.keys().collect();
248        abs_ids.sort();
249        for id in abs_ids {
250            self.abstract_nums[id].write_to(&mut writer)?;
251        }
252
253        // Write nums (sorted by ID)
254        let mut num_ids: Vec<_> = self.nums.keys().collect();
255        num_ids.sort();
256        for id in num_ids {
257            self.nums[id].write_to(&mut writer)?;
258        }
259
260        // Write unknown children
261        for child in &self.unknown_children {
262            child.write_to(&mut writer)?;
263        }
264
265        writer.write_event(Event::End(BytesEnd::new("w:numbering")))?;
266
267        String::from_utf8(buffer).map_err(|e| crate::error::Error::InvalidDocument(e.to_string()))
268    }
269
270    /// Get the format for a specific numId and level
271    pub fn get_format(&self, num_id: u32, level: u8) -> Option<&NumberFormat> {
272        let num = self.nums.get(&num_id)?;
273        let abs_num = self.abstract_nums.get(&num.abstract_num_id)?;
274        let lvl = abs_num.levels.get(&level)?;
275        lvl.num_fmt.as_ref()
276    }
277
278    /// Check if a numId represents a bullet list
279    pub fn is_bullet_list(&self, num_id: u32) -> bool {
280        if let Some(fmt) = self.get_format(num_id, 0) {
281            fmt.is_bullet()
282        } else {
283            false
284        }
285    }
286
287    /// Get level text for a specific numId and level
288    pub fn get_level_text(&self, num_id: u32, level: u8) -> Option<&str> {
289        let num = self.nums.get(&num_id)?;
290        let abs_num = self.abstract_nums.get(&num.abstract_num_id)?;
291        let lvl = abs_num.levels.get(&level)?;
292        lvl.level_text.as_deref()
293    }
294}
295
296impl AbstractNum {
297    fn from_reader<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<Self> {
298        let mut abs_num = AbstractNum::default();
299
300        // Get abstractNumId attribute
301        for attr in start.attributes().filter_map(|a| a.ok()) {
302            let key = attr.key.as_ref();
303            if key == b"w:abstractNumId" || key == b"abstractNumId" {
304                abs_num.abstract_num_id = String::from_utf8_lossy(&attr.value).parse().unwrap_or(0);
305            }
306        }
307
308        let mut buf = Vec::new();
309
310        loop {
311            match reader.read_event_into(&mut buf)? {
312                Event::Start(e) => {
313                    let local = e.name().local_name();
314                    match local.as_ref() {
315                        b"lvl" => {
316                            let lvl = Level::from_reader(reader, &e)?;
317                            abs_num.levels.insert(lvl.ilvl, lvl);
318                        }
319                        _ => {
320                            let raw = RawXmlElement::from_reader(reader, &e)?;
321                            abs_num.unknown_children.push(RawXmlNode::Element(raw));
322                        }
323                    }
324                }
325                Event::Empty(e) => {
326                    let local = e.name().local_name();
327                    match local.as_ref() {
328                        b"multiLevelType" => {
329                            abs_num.multi_level_type = get_w_val(&e);
330                        }
331                        _ => {
332                            let raw = RawXmlElement {
333                                name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
334                                attributes: e
335                                    .attributes()
336                                    .filter_map(|a| a.ok())
337                                    .map(|a| {
338                                        (
339                                            String::from_utf8_lossy(a.key.as_ref()).to_string(),
340                                            String::from_utf8_lossy(&a.value).to_string(),
341                                        )
342                                    })
343                                    .collect(),
344                                children: Vec::new(),
345                                self_closing: true,
346                            };
347                            abs_num.unknown_children.push(RawXmlNode::Element(raw));
348                        }
349                    }
350                }
351                Event::End(e) => {
352                    if e.name().local_name().as_ref() == b"abstractNum" {
353                        break;
354                    }
355                }
356                Event::Eof => break,
357                _ => {}
358            }
359            buf.clear();
360        }
361
362        Ok(abs_num)
363    }
364
365    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
366        let mut start = BytesStart::new("w:abstractNum");
367        start.push_attribute(("w:abstractNumId", self.abstract_num_id.to_string().as_str()));
368        writer.write_event(Event::Start(start))?;
369
370        // Multi-level type
371        if let Some(mlt) = &self.multi_level_type {
372            let mut elem = BytesStart::new("w:multiLevelType");
373            elem.push_attribute(("w:val", mlt.as_str()));
374            writer.write_event(Event::Empty(elem))?;
375        }
376
377        // Write levels (sorted)
378        let mut level_ids: Vec<_> = self.levels.keys().collect();
379        level_ids.sort();
380        for id in level_ids {
381            self.levels[id].write_to(writer)?;
382        }
383
384        // Unknown children
385        for child in &self.unknown_children {
386            child.write_to(writer)?;
387        }
388
389        writer.write_event(Event::End(BytesEnd::new("w:abstractNum")))?;
390        Ok(())
391    }
392}
393
394impl Num {
395    fn from_reader<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<Self> {
396        let mut num_id = 0u32;
397        let mut abstract_num_id = 0u32;
398        let mut level_overrides = Vec::new();
399
400        // Get numId attribute
401        for attr in start.attributes().filter_map(|a| a.ok()) {
402            let key = attr.key.as_ref();
403            if key == b"w:numId" || key == b"numId" {
404                num_id = String::from_utf8_lossy(&attr.value).parse().unwrap_or(0);
405            }
406        }
407
408        let mut buf = Vec::new();
409
410        loop {
411            match reader.read_event_into(&mut buf)? {
412                Event::Start(e) => {
413                    let local = e.name().local_name();
414                    if local.as_ref() == b"lvlOverride" {
415                        let lo = LevelOverride::from_reader(reader, &e)?;
416                        level_overrides.push(lo);
417                    } else {
418                        skip_element(reader, &e)?;
419                    }
420                }
421                Event::Empty(e) => {
422                    let local = e.name().local_name();
423                    if local.as_ref() == b"abstractNumId" {
424                        abstract_num_id = get_w_val(&e).and_then(|v| v.parse().ok()).unwrap_or(0);
425                    }
426                }
427                Event::End(e) => {
428                    if e.name().local_name().as_ref() == b"num" {
429                        break;
430                    }
431                }
432                Event::Eof => break,
433                _ => {}
434            }
435            buf.clear();
436        }
437
438        Ok(Num {
439            num_id,
440            abstract_num_id,
441            level_overrides,
442        })
443    }
444
445    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
446        let mut start = BytesStart::new("w:num");
447        start.push_attribute(("w:numId", self.num_id.to_string().as_str()));
448        writer.write_event(Event::Start(start))?;
449
450        // Abstract num ID
451        let mut elem = BytesStart::new("w:abstractNumId");
452        elem.push_attribute(("w:val", self.abstract_num_id.to_string().as_str()));
453        writer.write_event(Event::Empty(elem))?;
454
455        // Level overrides
456        for lo in &self.level_overrides {
457            lo.write_to(writer)?;
458        }
459
460        writer.write_event(Event::End(BytesEnd::new("w:num")))?;
461        Ok(())
462    }
463}
464
465impl Level {
466    fn from_reader<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<Self> {
467        let mut level = Level::default();
468
469        // Get ilvl attribute
470        for attr in start.attributes().filter_map(|a| a.ok()) {
471            let key = attr.key.as_ref();
472            if key == b"w:ilvl" || key == b"ilvl" {
473                level.ilvl = String::from_utf8_lossy(&attr.value).parse().unwrap_or(0);
474            }
475        }
476
477        let mut buf = Vec::new();
478
479        loop {
480            match reader.read_event_into(&mut buf)? {
481                Event::Start(e) => {
482                    let local = e.name().local_name();
483                    match local.as_ref() {
484                        b"pPr" => {
485                            level.p_pr = Some(LevelParagraphProperties::from_reader(reader)?);
486                        }
487                        b"rPr" => {
488                            level.r_pr = Some(LevelRunProperties::from_reader(reader)?);
489                        }
490                        _ => {
491                            let raw = RawXmlElement::from_reader(reader, &e)?;
492                            level.unknown_children.push(RawXmlNode::Element(raw));
493                        }
494                    }
495                }
496                Event::Empty(e) => {
497                    let local = e.name().local_name();
498                    match local.as_ref() {
499                        b"start" => {
500                            level.start = get_w_val(&e).and_then(|v| v.parse().ok());
501                        }
502                        b"numFmt" => {
503                            level.num_fmt = get_w_val(&e).map(|v| v.parse().unwrap());
504                        }
505                        b"lvlText" => {
506                            level.level_text = get_w_val(&e);
507                        }
508                        b"lvlJc" => {
509                            level.lvl_jc = get_w_val(&e);
510                        }
511                        _ => {
512                            let raw = RawXmlElement {
513                                name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
514                                attributes: e
515                                    .attributes()
516                                    .filter_map(|a| a.ok())
517                                    .map(|a| {
518                                        (
519                                            String::from_utf8_lossy(a.key.as_ref()).to_string(),
520                                            String::from_utf8_lossy(&a.value).to_string(),
521                                        )
522                                    })
523                                    .collect(),
524                                children: Vec::new(),
525                                self_closing: true,
526                            };
527                            level.unknown_children.push(RawXmlNode::Element(raw));
528                        }
529                    }
530                }
531                Event::End(e) => {
532                    if e.name().local_name().as_ref() == b"lvl" {
533                        break;
534                    }
535                }
536                Event::Eof => break,
537                _ => {}
538            }
539            buf.clear();
540        }
541
542        Ok(level)
543    }
544
545    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
546        let mut start = BytesStart::new("w:lvl");
547        start.push_attribute(("w:ilvl", self.ilvl.to_string().as_str()));
548        writer.write_event(Event::Start(start))?;
549
550        // Start value
551        if let Some(s) = self.start {
552            let mut elem = BytesStart::new("w:start");
553            elem.push_attribute(("w:val", s.to_string().as_str()));
554            writer.write_event(Event::Empty(elem))?;
555        }
556
557        // Number format
558        if let Some(fmt) = &self.num_fmt {
559            let mut elem = BytesStart::new("w:numFmt");
560            elem.push_attribute(("w:val", fmt.as_str()));
561            writer.write_event(Event::Empty(elem))?;
562        }
563
564        // Level text
565        if let Some(txt) = &self.level_text {
566            let mut elem = BytesStart::new("w:lvlText");
567            elem.push_attribute(("w:val", txt.as_str()));
568            writer.write_event(Event::Empty(elem))?;
569        }
570
571        // Level justification
572        if let Some(jc) = &self.lvl_jc {
573            let mut elem = BytesStart::new("w:lvlJc");
574            elem.push_attribute(("w:val", jc.as_str()));
575            writer.write_event(Event::Empty(elem))?;
576        }
577
578        // Paragraph properties
579        if let Some(p_pr) = &self.p_pr {
580            p_pr.write_to(writer)?;
581        }
582
583        // Run properties
584        if let Some(r_pr) = &self.r_pr {
585            r_pr.write_to(writer)?;
586        }
587
588        // Unknown children
589        for child in &self.unknown_children {
590            child.write_to(writer)?;
591        }
592
593        writer.write_event(Event::End(BytesEnd::new("w:lvl")))?;
594        Ok(())
595    }
596}
597
598impl LevelOverride {
599    fn from_reader<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<Self> {
600        let mut ilvl = 0u8;
601        let mut start_override = None;
602        let mut lvl = None;
603
604        // Get ilvl attribute
605        for attr in start.attributes().filter_map(|a| a.ok()) {
606            let key = attr.key.as_ref();
607            if key == b"w:ilvl" || key == b"ilvl" {
608                ilvl = String::from_utf8_lossy(&attr.value).parse().unwrap_or(0);
609            }
610        }
611
612        let mut buf = Vec::new();
613
614        loop {
615            match reader.read_event_into(&mut buf)? {
616                Event::Start(e) => {
617                    let local = e.name().local_name();
618                    if local.as_ref() == b"lvl" {
619                        lvl = Some(Level::from_reader(reader, &e)?);
620                    } else {
621                        skip_element(reader, &e)?;
622                    }
623                }
624                Event::Empty(e) => {
625                    let local = e.name().local_name();
626                    if local.as_ref() == b"startOverride" {
627                        start_override = get_w_val(&e).and_then(|v| v.parse().ok());
628                    }
629                }
630                Event::End(e) => {
631                    if e.name().local_name().as_ref() == b"lvlOverride" {
632                        break;
633                    }
634                }
635                Event::Eof => break,
636                _ => {}
637            }
638            buf.clear();
639        }
640
641        Ok(LevelOverride {
642            ilvl,
643            start_override,
644            lvl,
645        })
646    }
647
648    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
649        let mut start = BytesStart::new("w:lvlOverride");
650        start.push_attribute(("w:ilvl", self.ilvl.to_string().as_str()));
651        writer.write_event(Event::Start(start))?;
652
653        // Start override
654        if let Some(s) = self.start_override {
655            let mut elem = BytesStart::new("w:startOverride");
656            elem.push_attribute(("w:val", s.to_string().as_str()));
657            writer.write_event(Event::Empty(elem))?;
658        }
659
660        // Level override
661        if let Some(lvl) = &self.lvl {
662            lvl.write_to(writer)?;
663        }
664
665        writer.write_event(Event::End(BytesEnd::new("w:lvlOverride")))?;
666        Ok(())
667    }
668}
669
670impl LevelParagraphProperties {
671    fn from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<Self> {
672        let mut props = LevelParagraphProperties::default();
673        let mut buf = Vec::new();
674
675        loop {
676            match reader.read_event_into(&mut buf)? {
677                Event::Start(e) => {
678                    let raw = RawXmlElement::from_reader(reader, &e)?;
679                    props.unknown_children.push(RawXmlNode::Element(raw));
680                }
681                Event::Empty(e) => {
682                    let local = e.name().local_name();
683                    if local.as_ref() == b"ind" {
684                        // Parse indentation
685                        for attr in e.attributes().filter_map(|a| a.ok()) {
686                            let key = attr.key.as_ref();
687                            let val = String::from_utf8_lossy(&attr.value);
688                            match key {
689                                b"w:left" | b"left" => {
690                                    props.ind_left = val.parse().ok();
691                                }
692                                b"w:hanging" | b"hanging" => {
693                                    props.ind_hanging = val.parse().ok();
694                                }
695                                _ => {}
696                            }
697                        }
698                        // Also preserve as unknown for complete round-trip
699                        let raw = RawXmlElement {
700                            name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
701                            attributes: e
702                                .attributes()
703                                .filter_map(|a| a.ok())
704                                .map(|a| {
705                                    (
706                                        String::from_utf8_lossy(a.key.as_ref()).to_string(),
707                                        String::from_utf8_lossy(&a.value).to_string(),
708                                    )
709                                })
710                                .collect(),
711                            children: Vec::new(),
712                            self_closing: true,
713                        };
714                        props.unknown_children.push(RawXmlNode::Element(raw));
715                    } else {
716                        let raw = RawXmlElement {
717                            name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
718                            attributes: e
719                                .attributes()
720                                .filter_map(|a| a.ok())
721                                .map(|a| {
722                                    (
723                                        String::from_utf8_lossy(a.key.as_ref()).to_string(),
724                                        String::from_utf8_lossy(&a.value).to_string(),
725                                    )
726                                })
727                                .collect(),
728                            children: Vec::new(),
729                            self_closing: true,
730                        };
731                        props.unknown_children.push(RawXmlNode::Element(raw));
732                    }
733                }
734                Event::End(e) => {
735                    if e.name().local_name().as_ref() == b"pPr" {
736                        break;
737                    }
738                }
739                Event::Eof => break,
740                _ => {}
741            }
742            buf.clear();
743        }
744
745        Ok(props)
746    }
747
748    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
749        writer.write_event(Event::Start(BytesStart::new("w:pPr")))?;
750
751        // Write unknown children (which includes ind if it was preserved)
752        for child in &self.unknown_children {
753            child.write_to(writer)?;
754        }
755
756        writer.write_event(Event::End(BytesEnd::new("w:pPr")))?;
757        Ok(())
758    }
759}
760
761impl LevelRunProperties {
762    fn from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<Self> {
763        let mut props = LevelRunProperties::default();
764        let mut buf = Vec::new();
765
766        loop {
767            match reader.read_event_into(&mut buf)? {
768                Event::Start(e) => {
769                    let raw = RawXmlElement::from_reader(reader, &e)?;
770                    props.unknown_children.push(RawXmlNode::Element(raw));
771                }
772                Event::Empty(e) => {
773                    let raw = RawXmlElement {
774                        name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
775                        attributes: e
776                            .attributes()
777                            .filter_map(|a| a.ok())
778                            .map(|a| {
779                                (
780                                    String::from_utf8_lossy(a.key.as_ref()).to_string(),
781                                    String::from_utf8_lossy(&a.value).to_string(),
782                                )
783                            })
784                            .collect(),
785                        children: Vec::new(),
786                        self_closing: true,
787                    };
788                    props.unknown_children.push(RawXmlNode::Element(raw));
789                }
790                Event::End(e) => {
791                    if e.name().local_name().as_ref() == b"rPr" {
792                        break;
793                    }
794                }
795                Event::Eof => break,
796                _ => {}
797            }
798            buf.clear();
799        }
800
801        Ok(props)
802    }
803
804    fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
805        writer.write_event(Event::Start(BytesStart::new("w:rPr")))?;
806
807        for child in &self.unknown_children {
808            child.write_to(writer)?;
809        }
810
811        writer.write_event(Event::End(BytesEnd::new("w:rPr")))?;
812        Ok(())
813    }
814}
815
816/// Skip an element and all its children
817fn skip_element<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<()> {
818    let name = start.name();
819    let mut depth = 1;
820    let mut buf = Vec::new();
821
822    loop {
823        match reader.read_event_into(&mut buf)? {
824            Event::Start(e) if e.name() == name => depth += 1,
825            Event::End(e) if e.name() == name => {
826                depth -= 1;
827                if depth == 0 {
828                    break;
829                }
830            }
831            Event::Eof => break,
832            _ => {}
833        }
834        buf.clear();
835    }
836
837    Ok(())
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843
844    const SAMPLE_NUMBERING: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
845<w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
846  <w:abstractNum w:abstractNumId="0">
847    <w:multiLevelType w:val="hybridMultilevel"/>
848    <w:lvl w:ilvl="0">
849      <w:start w:val="1"/>
850      <w:numFmt w:val="decimal"/>
851      <w:lvlText w:val="%1."/>
852      <w:lvlJc w:val="left"/>
853    </w:lvl>
854    <w:lvl w:ilvl="1">
855      <w:start w:val="1"/>
856      <w:numFmt w:val="lowerLetter"/>
857      <w:lvlText w:val="%2)"/>
858      <w:lvlJc w:val="left"/>
859    </w:lvl>
860  </w:abstractNum>
861  <w:abstractNum w:abstractNumId="1">
862    <w:multiLevelType w:val="hybridMultilevel"/>
863    <w:lvl w:ilvl="0">
864      <w:start w:val="1"/>
865      <w:numFmt w:val="bullet"/>
866      <w:lvlText w:val="•"/>
867      <w:lvlJc w:val="left"/>
868    </w:lvl>
869  </w:abstractNum>
870  <w:num w:numId="1">
871    <w:abstractNumId w:val="0"/>
872  </w:num>
873  <w:num w:numId="2">
874    <w:abstractNumId w:val="1"/>
875  </w:num>
876</w:numbering>"#;
877
878    #[test]
879    fn test_parse_numbering() {
880        let numbering = Numbering::from_xml(SAMPLE_NUMBERING).unwrap();
881
882        // Should have 2 abstract nums
883        assert_eq!(numbering.abstract_nums.len(), 2);
884
885        // Should have 2 nums
886        assert_eq!(numbering.nums.len(), 2);
887
888        // Check abstract num 0
889        let abs0 = numbering.abstract_nums.get(&0).unwrap();
890        assert_eq!(abs0.multi_level_type, Some("hybridMultilevel".to_string()));
891        assert_eq!(abs0.levels.len(), 2);
892
893        // Check level 0
894        let lvl0 = abs0.levels.get(&0).unwrap();
895        assert_eq!(lvl0.start, Some(1));
896        assert_eq!(lvl0.num_fmt, Some(NumberFormat::Decimal));
897        assert_eq!(lvl0.level_text, Some("%1.".to_string()));
898
899        // Check num 1 references abstract num 0
900        let num1 = numbering.nums.get(&1).unwrap();
901        assert_eq!(num1.abstract_num_id, 0);
902    }
903
904    #[test]
905    fn test_is_bullet_list() {
906        let numbering = Numbering::from_xml(SAMPLE_NUMBERING).unwrap();
907
908        // numId 1 references abstractNum 0 which is decimal
909        assert!(!numbering.is_bullet_list(1));
910
911        // numId 2 references abstractNum 1 which is bullet
912        assert!(numbering.is_bullet_list(2));
913    }
914
915    #[test]
916    fn test_roundtrip() {
917        let numbering = Numbering::from_xml(SAMPLE_NUMBERING).unwrap();
918        let xml = numbering.to_xml().unwrap();
919
920        // Parse again
921        let numbering2 = Numbering::from_xml(&xml).unwrap();
922
923        // Should have same structure
924        assert_eq!(
925            numbering.abstract_nums.len(),
926            numbering2.abstract_nums.len()
927        );
928        assert_eq!(numbering.nums.len(), numbering2.nums.len());
929
930        // Check a specific value
931        assert_eq!(numbering.get_format(1, 0), numbering2.get_format(1, 0));
932    }
933}