1use 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#[derive(Clone, Debug, Default)]
14pub struct Numbering {
15 pub abstract_nums: HashMap<u32, AbstractNum>,
17 pub nums: HashMap<u32, Num>,
19 pub unknown_children: Vec<RawXmlNode>,
21}
22
23#[derive(Clone, Debug, Default)]
25pub struct AbstractNum {
26 pub abstract_num_id: u32,
28 pub multi_level_type: Option<String>,
30 pub levels: HashMap<u8, Level>,
32 pub unknown_children: Vec<RawXmlNode>,
34}
35
36#[derive(Clone, Debug)]
38pub struct Num {
39 pub num_id: u32,
41 pub abstract_num_id: u32,
43 pub level_overrides: Vec<LevelOverride>,
45}
46
47#[derive(Clone, Debug, Default)]
49pub struct Level {
50 pub ilvl: u8,
52 pub start: Option<u32>,
54 pub num_fmt: Option<NumberFormat>,
56 pub level_text: Option<String>,
58 pub lvl_jc: Option<String>,
60 pub p_pr: Option<LevelParagraphProperties>,
62 pub r_pr: Option<LevelRunProperties>,
64 pub unknown_children: Vec<RawXmlNode>,
66}
67
68#[derive(Clone, Debug)]
70pub struct LevelOverride {
71 pub ilvl: u8,
73 pub start_override: Option<u32>,
75 pub lvl: Option<Level>,
77}
78
79#[derive(Clone, Debug, Default)]
81pub struct LevelParagraphProperties {
82 pub ind_left: Option<i32>,
84 pub ind_hanging: Option<i32>,
86 pub unknown_children: Vec<RawXmlNode>,
88}
89
90#[derive(Clone, Debug, Default)]
92pub struct LevelRunProperties {
93 pub unknown_children: Vec<RawXmlNode>,
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
99pub enum NumberFormat {
100 Decimal,
102 UpperRoman,
104 LowerRoman,
106 UpperLetter,
108 LowerLetter,
110 Bullet,
112 ChineseCountingThousand,
114 None,
116 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 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 pub fn is_bullet(&self) -> bool {
156 matches!(self, NumberFormat::Bullet)
157 }
158}
159
160impl Numbering {
161 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 }
187 _ => {
188 let raw = RawXmlElement::from_reader(&mut reader, &e)?;
190 numbering.unknown_children.push(RawXmlNode::Element(raw));
191 }
192 }
193 }
194 Event::Empty(e) => {
195 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 pub fn to_xml(&self) -> Result<String> {
224 let mut buffer = Vec::new();
225 let mut writer = Writer::new(&mut buffer);
226
227 writer.write_event(Event::Decl(quick_xml::events::BytesDecl::new(
229 "1.0",
230 Some("UTF-8"),
231 Some("yes"),
232 )))?;
233
234 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 if let Some(p_pr) = &self.p_pr {
580 p_pr.write_to(writer)?;
581 }
582
583 if let Some(r_pr) = &self.r_pr {
585 r_pr.write_to(writer)?;
586 }
587
588 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 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 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 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 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 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 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
816fn 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 assert_eq!(numbering.abstract_nums.len(), 2);
884
885 assert_eq!(numbering.nums.len(), 2);
887
888 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 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 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 assert!(!numbering.is_bullet_list(1));
910
911 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 let numbering2 = Numbering::from_xml(&xml).unwrap();
922
923 assert_eq!(
925 numbering.abstract_nums.len(),
926 numbering2.abstract_nums.len()
927 );
928 assert_eq!(numbering.nums.len(), numbering2.nums.len());
929
930 assert_eq!(numbering.get_format(1, 0), numbering2.get_format(1, 0));
932 }
933}