Skip to main content

ooxml_omml/
math.rs

1//! Office Math Markup Language (OMML) types.
2//!
3//! OMML is used for mathematical formulas in Word, Excel, and PowerPoint.
4//! Defined in ECMA-376 Part 4, Section 22.
5//!
6//! # Structure
7//!
8//! Math content is organized as:
9//! - `MathZone` (`m:oMath`) - top-level container for inline math
10//! - `MathParagraph` (`m:oMathPara`) - display math with alignment
11//! - Various math constructs: fractions, radicals, scripts, etc.
12
13use crate::error::{Error, Result};
14use quick_xml::Reader;
15use quick_xml::events::Event;
16use std::io::{BufRead, Cursor};
17
18/// A math zone (`<m:oMath>`).
19///
20/// Contains a sequence of math elements that form a mathematical expression.
21#[derive(Debug, Clone, Default)]
22pub struct MathZone {
23    /// Math elements in this zone.
24    pub elements: Vec<MathElement>,
25}
26
27impl MathZone {
28    /// Create a new empty math zone.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Extract plain text representation of the math content.
34    pub fn text(&self) -> String {
35        self.elements.iter().map(|e| e.text()).collect()
36    }
37}
38
39impl ooxml_xml::FromXml for MathZone {
40    fn from_xml<R: BufRead>(
41        reader: &mut Reader<R>,
42        _start_tag: &quick_xml::events::BytesStart,
43        is_empty: bool,
44    ) -> std::result::Result<Self, ooxml_xml::ParseError> {
45        if is_empty {
46            return Ok(Self::default());
47        }
48        parse_math_zone_from_reader(reader).map_err(|e| match e {
49            Error::Xml(xml_err) => ooxml_xml::ParseError::Xml(xml_err),
50            Error::Invalid(msg) => ooxml_xml::ParseError::InvalidValue(msg),
51        })
52    }
53}
54
55/// A math element - one of the possible OMML constructs.
56#[derive(Debug, Clone)]
57pub enum MathElement {
58    /// Text run (`m:r`).
59    Run(MathRun),
60    /// Fraction (`m:f`).
61    Fraction(Fraction),
62    /// Radical/root (`m:rad`).
63    Radical(Radical),
64    /// N-ary operator like sum or integral (`m:nary`).
65    Nary(Nary),
66    /// Subscript (`m:sSub`).
67    Subscript(Script),
68    /// Superscript (`m:sSup`).
69    Superscript(Script),
70    /// Subscript and superscript (`m:sSubSup`).
71    SubSuperscript(SubSuperscript),
72    /// Pre-subscript/superscript (`m:sPre`).
73    PreScript(PreScript),
74    /// Delimiter/parentheses (`m:d`).
75    Delimiter(Delimiter),
76    /// Matrix (`m:m`).
77    Matrix(Matrix),
78    /// Function like sin, cos (`m:func`).
79    Function(Function),
80    /// Accent like hat, tilde (`m:acc`).
81    Accent(Accent),
82    /// Bar over/under (`m:bar`).
83    Bar(Bar),
84    /// Box (`m:box`).
85    Box(MathBox),
86    /// Border box (`m:borderBox`).
87    BorderBox(BorderBox),
88    /// Equation array (`m:eqArr`).
89    EquationArray(EquationArray),
90    /// Lower limit (`m:limLow`).
91    LowerLimit(Limit),
92    /// Upper limit (`m:limUpp`).
93    UpperLimit(Limit),
94    /// Group character/brace (`m:groupChr`).
95    GroupChar(GroupChar),
96    /// Phantom (`m:phant`).
97    Phantom(Phantom),
98}
99
100impl MathElement {
101    /// Get text representation.
102    pub fn text(&self) -> String {
103        match self {
104            MathElement::Run(r) => r.text.clone(),
105            MathElement::Fraction(f) => {
106                format!("({})/({})", f.numerator.text(), f.denominator.text())
107            }
108            MathElement::Radical(r) => {
109                if r.degree.elements.is_empty() {
110                    format!("sqrt({})", r.base.text())
111                } else {
112                    format!("root[{}]({})", r.degree.text(), r.base.text())
113                }
114            }
115            MathElement::Nary(n) => {
116                let op = n.operator.as_deref().unwrap_or("∑");
117                format!(
118                    "{}[{},{}]({})",
119                    op,
120                    n.subscript.text(),
121                    n.superscript.text(),
122                    n.base.text()
123                )
124            }
125            MathElement::Subscript(s) => format!("{}_{}", s.base.text(), s.script.text()),
126            MathElement::Superscript(s) => format!("{}^{}", s.base.text(), s.script.text()),
127            MathElement::SubSuperscript(s) => format!(
128                "{}_{}^{}",
129                s.base.text(),
130                s.subscript.text(),
131                s.superscript.text()
132            ),
133            MathElement::PreScript(p) => format!(
134                "_{}^{}{}",
135                p.subscript.text(),
136                p.superscript.text(),
137                p.base.text()
138            ),
139            MathElement::Delimiter(d) => {
140                let begin = d.begin_char.as_deref().unwrap_or("(");
141                let end = d.end_char.as_deref().unwrap_or(")");
142                let inner: Vec<_> = d.elements.iter().map(|e| e.text()).collect();
143                format!(
144                    "{}{}{}",
145                    begin,
146                    inner.join(d.separator_char.as_deref().unwrap_or(",")),
147                    end
148                )
149            }
150            MathElement::Matrix(m) => {
151                let rows: Vec<_> = m
152                    .rows
153                    .iter()
154                    .map(|row| {
155                        let cells: Vec<_> = row.iter().map(|c| c.text()).collect();
156                        cells.join(", ")
157                    })
158                    .collect();
159                format!("[{}]", rows.join("; "))
160            }
161            MathElement::Function(f) => format!("{}({})", f.name.text(), f.argument.text()),
162            MathElement::Accent(a) => format!(
163                "{}({})",
164                a.character.as_deref().unwrap_or("^"),
165                a.base.text()
166            ),
167            MathElement::Bar(b) => format!("bar({})", b.base.text()),
168            MathElement::Box(b) => b.content.text(),
169            MathElement::BorderBox(b) => format!("[{}]", b.content.text()),
170            MathElement::EquationArray(e) => {
171                let eqs: Vec<_> = e.equations.iter().map(|eq| eq.text()).collect();
172                eqs.join("\n")
173            }
174            MathElement::LowerLimit(l) => format!("lim_{}({})", l.limit.text(), l.base.text()),
175            MathElement::UpperLimit(l) => format!("lim^{}({})", l.limit.text(), l.base.text()),
176            MathElement::GroupChar(g) => format!(
177                "group[{}]({})",
178                g.character.as_deref().unwrap_or("⏟"),
179                g.base.text()
180            ),
181            MathElement::Phantom(p) => p.content.text(),
182        }
183    }
184}
185
186/// A math text run (`<m:r>`).
187#[derive(Debug, Clone, Default)]
188pub struct MathRun {
189    /// Text content.
190    pub text: String,
191    /// Run properties.
192    pub properties: Option<MathRunProperties>,
193}
194
195/// Math run properties (`<m:rPr>`).
196#[derive(Debug, Clone, Default)]
197pub struct MathRunProperties {
198    /// Script type (roman, script, fraktur, etc.).
199    pub script: Option<MathScript>,
200    /// Style (plain, bold, italic, bold-italic).
201    pub style: Option<MathStyle>,
202    /// Literal text (no special formatting).
203    pub literal: bool,
204    /// Normal text (use document formatting).
205    pub normal: bool,
206}
207
208/// Math script types (`m:scr`).
209///
210/// Controls the mathematical alphabet used for a run's characters.
211/// Defined in ECMA-376 §22.1.2.87 (ST_Script).
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum MathScript {
214    /// Serif (roman) alphabet — the default mathematical alphabet.
215    Roman,
216    /// Script (calligraphic) alphabet, e.g. ℒ, ℱ.
217    Script,
218    /// Fraktur (Gothic) alphabet, e.g. ℌ, ℨ.
219    Fraktur,
220    /// Double-struck (blackboard bold) alphabet, e.g. ℝ, ℤ.
221    DoubleStruck,
222    /// Sans-serif alphabet.
223    SansSerif,
224    /// Monospace (typewriter) alphabet.
225    Monospace,
226}
227
228/// Math text styles (`m:sty`).
229///
230/// Controls whether characters in a run are rendered plain, bold, italic, or bold-italic.
231/// Defined in ECMA-376 §22.1.2.90 (ST_Style).
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum MathStyle {
234    /// Plain (upright, not bold) — value `"p"`.
235    Plain,
236    /// Bold — value `"b"`.
237    Bold,
238    /// Italic — value `"i"`.
239    Italic,
240    /// Bold italic — value `"bi"`.
241    BoldItalic,
242}
243
244/// A fraction (`<m:f>`).
245#[derive(Debug, Clone, Default)]
246pub struct Fraction {
247    /// Numerator.
248    pub numerator: MathZone,
249    /// Denominator.
250    pub denominator: MathZone,
251    /// Fraction type.
252    pub fraction_type: Option<FractionType>,
253}
254
255/// Fraction display types.
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum FractionType {
258    /// Normal fraction bar.
259    Bar,
260    /// Skewed (diagonal).
261    Skewed,
262    /// Linear (inline).
263    Linear,
264    /// No bar.
265    NoBar,
266}
267
268/// A radical/root (`<m:rad>`).
269#[derive(Debug, Clone, Default)]
270pub struct Radical {
271    /// Base expression (under the radical).
272    pub base: MathZone,
273    /// Degree (for nth roots).
274    pub degree: MathZone,
275    /// Hide the degree.
276    pub hide_degree: bool,
277}
278
279/// An n-ary operator (`<m:nary>`).
280#[derive(Debug, Clone, Default)]
281pub struct Nary {
282    /// Operator character (∑, ∫, ∏, etc.).
283    pub operator: Option<String>,
284    /// Subscript (lower bound).
285    pub subscript: MathZone,
286    /// Superscript (upper bound).
287    pub superscript: MathZone,
288    /// Base expression.
289    pub base: MathZone,
290    /// Limit location (under/over or subscript/superscript).
291    pub limit_location: Option<LimitLocation>,
292    /// Whether operator grows with content.
293    pub grow: bool,
294}
295
296/// Limit location for n-ary operators.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub enum LimitLocation {
299    /// Under and over the operator.
300    UnderOver,
301    /// Subscript and superscript.
302    SubSup,
303}
304
305/// A script (subscript or superscript).
306#[derive(Debug, Clone, Default)]
307pub struct Script {
308    /// Base expression.
309    pub base: MathZone,
310    /// Script expression.
311    pub script: MathZone,
312}
313
314/// Combined subscript and superscript (`<m:sSubSup>`).
315#[derive(Debug, Clone, Default)]
316pub struct SubSuperscript {
317    /// Base expression.
318    pub base: MathZone,
319    /// Subscript.
320    pub subscript: MathZone,
321    /// Superscript.
322    pub superscript: MathZone,
323}
324
325/// Pre-script (subscript/superscript before base) (`<m:sPre>`).
326#[derive(Debug, Clone, Default)]
327pub struct PreScript {
328    /// Subscript.
329    pub subscript: MathZone,
330    /// Superscript.
331    pub superscript: MathZone,
332    /// Base expression.
333    pub base: MathZone,
334}
335
336/// A delimiter/parentheses (`<m:d>`).
337#[derive(Debug, Clone, Default)]
338pub struct Delimiter {
339    /// Beginning character (default: '(').
340    pub begin_char: Option<String>,
341    /// Separator character (default: ',').
342    pub separator_char: Option<String>,
343    /// Ending character (default: ')').
344    pub end_char: Option<String>,
345    /// Elements inside the delimiter.
346    pub elements: Vec<MathZone>,
347    /// Whether delimiters grow with content.
348    pub grow: bool,
349}
350
351/// A matrix (`<m:m>`).
352#[derive(Debug, Clone, Default)]
353pub struct Matrix {
354    /// Rows of the matrix.
355    pub rows: Vec<Vec<MathZone>>,
356}
357
358/// A function application (`<m:func>`).
359#[derive(Debug, Clone, Default)]
360pub struct Function {
361    /// Function name.
362    pub name: MathZone,
363    /// Function argument.
364    pub argument: MathZone,
365}
366
367/// An accent (`<m:acc>`).
368#[derive(Debug, Clone, Default)]
369pub struct Accent {
370    /// Accent character (default: combining circumflex).
371    pub character: Option<String>,
372    /// Base expression.
373    pub base: MathZone,
374}
375
376/// A bar over or under expression (`<m:bar>`).
377#[derive(Debug, Clone, Default)]
378pub struct Bar {
379    /// Base expression.
380    pub base: MathZone,
381    /// Position (top or bottom).
382    pub position: Option<VerticalPosition>,
383}
384
385/// Vertical position for bars and group characters (`m:pos`).
386///
387/// Determines whether a bar or grouping character appears above or below the base expression.
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub enum VerticalPosition {
390    /// Position above the base expression — value `"top"`.
391    Top,
392    /// Position below the base expression — value `"bot"`.
393    Bottom,
394}
395
396/// A box around content (`<m:box>`).
397#[derive(Debug, Clone, Default)]
398pub struct MathBox {
399    /// Box content.
400    pub content: MathZone,
401}
402
403/// A bordered box (`<m:borderBox>`).
404#[derive(Debug, Clone, Default)]
405pub struct BorderBox {
406    /// Box content.
407    pub content: MathZone,
408    /// Hide borders.
409    pub hide_top: bool,
410    pub hide_bottom: bool,
411    pub hide_left: bool,
412    pub hide_right: bool,
413}
414
415/// An equation array (`<m:eqArr>`).
416#[derive(Debug, Clone, Default)]
417pub struct EquationArray {
418    /// Equations in the array.
419    pub equations: Vec<MathZone>,
420}
421
422/// A limit expression (`<m:limLow>` or `<m:limUpp>`).
423#[derive(Debug, Clone, Default)]
424pub struct Limit {
425    /// Base expression.
426    pub base: MathZone,
427    /// Limit expression.
428    pub limit: MathZone,
429}
430
431/// A group character/brace (`<m:groupChr>`).
432#[derive(Debug, Clone, Default)]
433pub struct GroupChar {
434    /// Grouping character (default: underbrace).
435    pub character: Option<String>,
436    /// Position of the character.
437    pub position: Option<VerticalPosition>,
438    /// Base expression.
439    pub base: MathZone,
440}
441
442/// A phantom (invisible content for spacing) (`<m:phant>`).
443#[derive(Debug, Clone, Default)]
444pub struct Phantom {
445    /// Content.
446    pub content: MathZone,
447    /// Show the content.
448    pub show: bool,
449}
450
451// ============================================================================
452// Parsing
453// ============================================================================
454
455/// Parse an OMML math zone from XML.
456pub fn parse_math_zone(xml: &[u8]) -> Result<MathZone> {
457    let mut reader = Reader::from_reader(Cursor::new(xml));
458    parse_math_zone_from_reader(&mut reader)
459}
460
461/// Parse an OMML math zone from a reader.
462pub fn parse_math_zone_from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<MathZone> {
463    let mut buf = Vec::new();
464    let mut zone = MathZone::new();
465
466    loop {
467        match reader.read_event_into(&mut buf) {
468            Ok(Event::Start(e)) => {
469                let name = e.name();
470                let name = name.as_ref();
471                if let Some(element) = parse_math_element_start(name, reader)? {
472                    zone.elements.push(element);
473                }
474            }
475            Ok(Event::Empty(e)) => {
476                // Handle self-closing elements if needed
477                let _ = e;
478            }
479            Ok(Event::End(e)) => {
480                let name = e.name();
481                if name.as_ref() == b"m:oMath" || name.as_ref() == b"oMath" {
482                    break;
483                }
484            }
485            Ok(Event::Eof) => break,
486            Err(e) => return Err(Error::Xml(e)),
487            _ => {}
488        }
489        buf.clear();
490    }
491
492    Ok(zone)
493}
494
495/// Parse a math element from its start tag.
496fn parse_math_element_start<R: BufRead>(
497    name: &[u8],
498    reader: &mut Reader<R>,
499) -> Result<Option<MathElement>> {
500    match name {
501        b"m:r" | b"r" => Ok(Some(MathElement::Run(parse_math_run(reader)?))),
502        b"m:f" | b"f" => Ok(Some(MathElement::Fraction(parse_fraction(reader)?))),
503        b"m:rad" | b"rad" => Ok(Some(MathElement::Radical(parse_radical(reader)?))),
504        b"m:nary" | b"nary" => Ok(Some(MathElement::Nary(parse_nary(reader)?))),
505        b"m:sSub" | b"sSub" => Ok(Some(MathElement::Subscript(parse_script(
506            reader, b"m:sSub",
507        )?))),
508        b"m:sSup" | b"sSup" => Ok(Some(MathElement::Superscript(parse_script(
509            reader, b"m:sSup",
510        )?))),
511        b"m:sSubSup" | b"sSubSup" => Ok(Some(MathElement::SubSuperscript(parse_sub_superscript(
512            reader,
513        )?))),
514        b"m:sPre" | b"sPre" => Ok(Some(MathElement::PreScript(parse_pre_script(reader)?))),
515        b"m:d" | b"d" => Ok(Some(MathElement::Delimiter(parse_delimiter(reader)?))),
516        b"m:m" | b"m" => Ok(Some(MathElement::Matrix(parse_matrix(reader)?))),
517        b"m:func" | b"func" => Ok(Some(MathElement::Function(parse_function(reader)?))),
518        b"m:acc" | b"acc" => Ok(Some(MathElement::Accent(parse_accent(reader)?))),
519        b"m:bar" | b"bar" => Ok(Some(MathElement::Bar(parse_bar(reader)?))),
520        b"m:box" | b"box" => Ok(Some(MathElement::Box(parse_math_box(reader)?))),
521        b"m:borderBox" | b"borderBox" => {
522            Ok(Some(MathElement::BorderBox(parse_border_box(reader)?)))
523        }
524        b"m:eqArr" | b"eqArr" => Ok(Some(MathElement::EquationArray(parse_equation_array(
525            reader,
526        )?))),
527        b"m:limLow" | b"limLow" => Ok(Some(MathElement::LowerLimit(parse_limit(
528            reader,
529            b"m:limLow",
530        )?))),
531        b"m:limUpp" | b"limUpp" => Ok(Some(MathElement::UpperLimit(parse_limit(
532            reader,
533            b"m:limUpp",
534        )?))),
535        b"m:groupChr" | b"groupChr" => Ok(Some(MathElement::GroupChar(parse_group_char(reader)?))),
536        b"m:phant" | b"phant" => Ok(Some(MathElement::Phantom(parse_phantom(reader)?))),
537        _ => Ok(None),
538    }
539}
540
541/// Parse a math run.
542fn parse_math_run<R: BufRead>(reader: &mut Reader<R>) -> Result<MathRun> {
543    let mut buf = Vec::new();
544    let mut run = MathRun::default();
545    let mut in_text = false;
546
547    loop {
548        match reader.read_event_into(&mut buf) {
549            Ok(Event::Start(e)) => {
550                let name = e.name();
551                if name.as_ref() == b"m:t" || name.as_ref() == b"t" {
552                    in_text = true;
553                }
554            }
555            Ok(Event::Text(e)) => {
556                if in_text {
557                    run.text.push_str(&e.decode().unwrap_or_default());
558                }
559            }
560            Ok(Event::End(e)) => {
561                let name = e.name();
562                let name = name.as_ref();
563                if name == b"m:t" || name == b"t" {
564                    in_text = false;
565                } else if name == b"m:r" || name == b"r" {
566                    break;
567                }
568            }
569            Ok(Event::Eof) => break,
570            Err(e) => return Err(Error::Xml(e)),
571            _ => {}
572        }
573        buf.clear();
574    }
575
576    Ok(run)
577}
578
579/// Parse math argument (content inside m:e, m:num, m:den, etc.).
580fn parse_math_arg<R: BufRead>(reader: &mut Reader<R>, end_tag: &[u8]) -> Result<MathZone> {
581    let mut buf = Vec::new();
582    let mut zone = MathZone::new();
583
584    loop {
585        match reader.read_event_into(&mut buf) {
586            Ok(Event::Start(e)) => {
587                let name = e.name();
588                let name = name.as_ref();
589                if let Some(element) = parse_math_element_start(name, reader)? {
590                    zone.elements.push(element);
591                }
592            }
593            Ok(Event::End(e)) => {
594                let name = e.name();
595                if name.as_ref() == end_tag {
596                    break;
597                }
598            }
599            Ok(Event::Eof) => break,
600            Err(e) => return Err(Error::Xml(e)),
601            _ => {}
602        }
603        buf.clear();
604    }
605
606    Ok(zone)
607}
608
609/// Parse a fraction.
610fn parse_fraction<R: BufRead>(reader: &mut Reader<R>) -> Result<Fraction> {
611    let mut buf = Vec::new();
612    let mut fraction = Fraction::default();
613
614    loop {
615        match reader.read_event_into(&mut buf) {
616            Ok(Event::Start(e)) => {
617                let name = e.name();
618                let name = name.as_ref();
619                match name {
620                    b"m:num" | b"num" => fraction.numerator = parse_math_arg(reader, name)?,
621                    b"m:den" | b"den" => fraction.denominator = parse_math_arg(reader, name)?,
622                    _ => {}
623                }
624            }
625            Ok(Event::End(e)) => {
626                let name = e.name();
627                if name.as_ref() == b"m:f" || name.as_ref() == b"f" {
628                    break;
629                }
630            }
631            Ok(Event::Eof) => break,
632            Err(e) => return Err(Error::Xml(e)),
633            _ => {}
634        }
635        buf.clear();
636    }
637
638    Ok(fraction)
639}
640
641/// Parse a radical.
642fn parse_radical<R: BufRead>(reader: &mut Reader<R>) -> Result<Radical> {
643    let mut buf = Vec::new();
644    let mut radical = Radical::default();
645
646    loop {
647        match reader.read_event_into(&mut buf) {
648            Ok(Event::Start(e)) => {
649                let name = e.name();
650                let name = name.as_ref();
651                match name {
652                    b"m:e" | b"e" => radical.base = parse_math_arg(reader, name)?,
653                    b"m:deg" | b"deg" => radical.degree = parse_math_arg(reader, name)?,
654                    _ => {}
655                }
656            }
657            Ok(Event::End(e)) => {
658                let name = e.name();
659                if name.as_ref() == b"m:rad" || name.as_ref() == b"rad" {
660                    break;
661                }
662            }
663            Ok(Event::Eof) => break,
664            Err(e) => return Err(Error::Xml(e)),
665            _ => {}
666        }
667        buf.clear();
668    }
669
670    Ok(radical)
671}
672
673/// Parse an n-ary operator.
674fn parse_nary<R: BufRead>(reader: &mut Reader<R>) -> Result<Nary> {
675    let mut buf = Vec::new();
676    let mut nary = Nary::default();
677
678    loop {
679        match reader.read_event_into(&mut buf) {
680            Ok(Event::Start(e)) => {
681                let name = e.name();
682                let name = name.as_ref();
683                match name {
684                    b"m:e" | b"e" => nary.base = parse_math_arg(reader, name)?,
685                    b"m:sub" | b"sub" => nary.subscript = parse_math_arg(reader, name)?,
686                    b"m:sup" | b"sup" => nary.superscript = parse_math_arg(reader, name)?,
687                    _ => {}
688                }
689            }
690            Ok(Event::Empty(e)) => {
691                let name = e.name();
692                if name.as_ref() == b"m:chr" || name.as_ref() == b"chr" {
693                    for attr in e.attributes().filter_map(|a| a.ok()) {
694                        if attr.key.as_ref() == b"m:val" || attr.key.as_ref() == b"val" {
695                            nary.operator = Some(String::from_utf8_lossy(&attr.value).into_owned());
696                        }
697                    }
698                }
699            }
700            Ok(Event::End(e)) => {
701                let name = e.name();
702                if name.as_ref() == b"m:nary" || name.as_ref() == b"nary" {
703                    break;
704                }
705            }
706            Ok(Event::Eof) => break,
707            Err(e) => return Err(Error::Xml(e)),
708            _ => {}
709        }
710        buf.clear();
711    }
712
713    Ok(nary)
714}
715
716/// Parse a script (subscript or superscript).
717fn parse_script<R: BufRead>(reader: &mut Reader<R>, end_tag: &[u8]) -> Result<Script> {
718    let mut buf = Vec::new();
719    let mut script = Script::default();
720    let end_local = if end_tag.starts_with(b"m:") {
721        &end_tag[2..]
722    } else {
723        end_tag
724    };
725
726    loop {
727        match reader.read_event_into(&mut buf) {
728            Ok(Event::Start(e)) => {
729                let name = e.name();
730                let name = name.as_ref();
731                match name {
732                    b"m:e" | b"e" => script.base = parse_math_arg(reader, name)?,
733                    b"m:sub" | b"sub" | b"m:sup" | b"sup" => {
734                        script.script = parse_math_arg(reader, name)?
735                    }
736                    _ => {}
737                }
738            }
739            Ok(Event::End(e)) => {
740                let name = e.name();
741                let name = name.as_ref();
742                if name == end_tag || name == end_local {
743                    break;
744                }
745            }
746            Ok(Event::Eof) => break,
747            Err(e) => return Err(Error::Xml(e)),
748            _ => {}
749        }
750        buf.clear();
751    }
752
753    Ok(script)
754}
755
756/// Parse combined subscript and superscript.
757fn parse_sub_superscript<R: BufRead>(reader: &mut Reader<R>) -> Result<SubSuperscript> {
758    let mut buf = Vec::new();
759    let mut result = SubSuperscript::default();
760
761    loop {
762        match reader.read_event_into(&mut buf) {
763            Ok(Event::Start(e)) => {
764                let name = e.name();
765                let name = name.as_ref();
766                match name {
767                    b"m:e" | b"e" => result.base = parse_math_arg(reader, name)?,
768                    b"m:sub" | b"sub" => result.subscript = parse_math_arg(reader, name)?,
769                    b"m:sup" | b"sup" => result.superscript = parse_math_arg(reader, name)?,
770                    _ => {}
771                }
772            }
773            Ok(Event::End(e)) => {
774                let name = e.name();
775                if name.as_ref() == b"m:sSubSup" || name.as_ref() == b"sSubSup" {
776                    break;
777                }
778            }
779            Ok(Event::Eof) => break,
780            Err(e) => return Err(Error::Xml(e)),
781            _ => {}
782        }
783        buf.clear();
784    }
785
786    Ok(result)
787}
788
789/// Parse pre-script.
790fn parse_pre_script<R: BufRead>(reader: &mut Reader<R>) -> Result<PreScript> {
791    let mut buf = Vec::new();
792    let mut result = PreScript::default();
793
794    loop {
795        match reader.read_event_into(&mut buf) {
796            Ok(Event::Start(e)) => {
797                let name = e.name();
798                let name = name.as_ref();
799                match name {
800                    b"m:e" | b"e" => result.base = parse_math_arg(reader, name)?,
801                    b"m:sub" | b"sub" => result.subscript = parse_math_arg(reader, name)?,
802                    b"m:sup" | b"sup" => result.superscript = parse_math_arg(reader, name)?,
803                    _ => {}
804                }
805            }
806            Ok(Event::End(e)) => {
807                let name = e.name();
808                if name.as_ref() == b"m:sPre" || name.as_ref() == b"sPre" {
809                    break;
810                }
811            }
812            Ok(Event::Eof) => break,
813            Err(e) => return Err(Error::Xml(e)),
814            _ => {}
815        }
816        buf.clear();
817    }
818
819    Ok(result)
820}
821
822/// Parse a delimiter.
823fn parse_delimiter<R: BufRead>(reader: &mut Reader<R>) -> Result<Delimiter> {
824    let mut buf = Vec::new();
825    let mut delimiter = Delimiter::default();
826
827    loop {
828        match reader.read_event_into(&mut buf) {
829            Ok(Event::Start(e)) => {
830                let name = e.name();
831                let name = name.as_ref();
832                if name == b"m:e" || name == b"e" {
833                    delimiter.elements.push(parse_math_arg(reader, name)?);
834                }
835            }
836            Ok(Event::Empty(e)) => {
837                let name = e.name();
838                let name = name.as_ref();
839                for attr in e.attributes().filter_map(|a| a.ok()) {
840                    let val = String::from_utf8_lossy(&attr.value).into_owned();
841                    if attr.key.as_ref() == b"m:val" || attr.key.as_ref() == b"val" {
842                        match name {
843                            b"m:begChr" | b"begChr" => delimiter.begin_char = Some(val),
844                            b"m:sepChr" | b"sepChr" => delimiter.separator_char = Some(val),
845                            b"m:endChr" | b"endChr" => delimiter.end_char = Some(val),
846                            _ => {}
847                        }
848                    }
849                }
850            }
851            Ok(Event::End(e)) => {
852                let name = e.name();
853                if name.as_ref() == b"m:d" || name.as_ref() == b"d" {
854                    break;
855                }
856            }
857            Ok(Event::Eof) => break,
858            Err(e) => return Err(Error::Xml(e)),
859            _ => {}
860        }
861        buf.clear();
862    }
863
864    Ok(delimiter)
865}
866
867/// Parse a matrix.
868fn parse_matrix<R: BufRead>(reader: &mut Reader<R>) -> Result<Matrix> {
869    let mut buf = Vec::new();
870    let mut matrix = Matrix::default();
871    let mut current_row: Vec<MathZone> = Vec::new();
872    let mut in_row = false;
873
874    loop {
875        match reader.read_event_into(&mut buf) {
876            Ok(Event::Start(e)) => {
877                let name = e.name();
878                let name = name.as_ref();
879                match name {
880                    b"m:mr" | b"mr" => {
881                        in_row = true;
882                        current_row = Vec::new();
883                    }
884                    b"m:e" | b"e" if in_row => {
885                        current_row.push(parse_math_arg(reader, name)?);
886                    }
887                    _ => {}
888                }
889            }
890            Ok(Event::End(e)) => {
891                let name = e.name();
892                let name = name.as_ref();
893                match name {
894                    b"m:mr" | b"mr" => {
895                        matrix.rows.push(std::mem::take(&mut current_row));
896                        in_row = false;
897                    }
898                    b"m:m" => break,
899                    _ => {}
900                }
901            }
902            Ok(Event::Eof) => break,
903            Err(e) => return Err(Error::Xml(e)),
904            _ => {}
905        }
906        buf.clear();
907    }
908
909    Ok(matrix)
910}
911
912/// Parse a function.
913fn parse_function<R: BufRead>(reader: &mut Reader<R>) -> Result<Function> {
914    let mut buf = Vec::new();
915    let mut function = Function::default();
916
917    loop {
918        match reader.read_event_into(&mut buf) {
919            Ok(Event::Start(e)) => {
920                let name = e.name();
921                let name = name.as_ref();
922                match name {
923                    b"m:fName" | b"fName" => function.name = parse_math_arg(reader, name)?,
924                    b"m:e" | b"e" => function.argument = parse_math_arg(reader, name)?,
925                    _ => {}
926                }
927            }
928            Ok(Event::End(e)) => {
929                let name = e.name();
930                if name.as_ref() == b"m:func" || name.as_ref() == b"func" {
931                    break;
932                }
933            }
934            Ok(Event::Eof) => break,
935            Err(e) => return Err(Error::Xml(e)),
936            _ => {}
937        }
938        buf.clear();
939    }
940
941    Ok(function)
942}
943
944/// Parse an accent.
945fn parse_accent<R: BufRead>(reader: &mut Reader<R>) -> Result<Accent> {
946    let mut buf = Vec::new();
947    let mut accent = Accent::default();
948
949    loop {
950        match reader.read_event_into(&mut buf) {
951            Ok(Event::Start(e)) => {
952                let name = e.name();
953                let name = name.as_ref();
954                if name == b"m:e" || name == b"e" {
955                    accent.base = parse_math_arg(reader, name)?;
956                }
957            }
958            Ok(Event::Empty(e)) => {
959                let name = e.name();
960                if name.as_ref() == b"m:chr" || name.as_ref() == b"chr" {
961                    for attr in e.attributes().filter_map(|a| a.ok()) {
962                        if attr.key.as_ref() == b"m:val" || attr.key.as_ref() == b"val" {
963                            accent.character =
964                                Some(String::from_utf8_lossy(&attr.value).into_owned());
965                        }
966                    }
967                }
968            }
969            Ok(Event::End(e)) => {
970                let name = e.name();
971                if name.as_ref() == b"m:acc" || name.as_ref() == b"acc" {
972                    break;
973                }
974            }
975            Ok(Event::Eof) => break,
976            Err(e) => return Err(Error::Xml(e)),
977            _ => {}
978        }
979        buf.clear();
980    }
981
982    Ok(accent)
983}
984
985/// Parse a bar.
986fn parse_bar<R: BufRead>(reader: &mut Reader<R>) -> Result<Bar> {
987    let mut buf = Vec::new();
988    let mut bar = Bar::default();
989
990    loop {
991        match reader.read_event_into(&mut buf) {
992            Ok(Event::Start(e)) => {
993                let name = e.name();
994                let name = name.as_ref();
995                if name == b"m:e" || name == b"e" {
996                    bar.base = parse_math_arg(reader, name)?;
997                }
998            }
999            Ok(Event::End(e)) => {
1000                let name = e.name();
1001                if name.as_ref() == b"m:bar" || name.as_ref() == b"bar" {
1002                    break;
1003                }
1004            }
1005            Ok(Event::Eof) => break,
1006            Err(e) => return Err(Error::Xml(e)),
1007            _ => {}
1008        }
1009        buf.clear();
1010    }
1011
1012    Ok(bar)
1013}
1014
1015/// Parse a math box.
1016fn parse_math_box<R: BufRead>(reader: &mut Reader<R>) -> Result<MathBox> {
1017    let mut buf = Vec::new();
1018    let mut result = MathBox::default();
1019
1020    loop {
1021        match reader.read_event_into(&mut buf) {
1022            Ok(Event::Start(e)) => {
1023                let name = e.name();
1024                let name = name.as_ref();
1025                if name == b"m:e" || name == b"e" {
1026                    result.content = parse_math_arg(reader, name)?;
1027                }
1028            }
1029            Ok(Event::End(e)) => {
1030                let name = e.name();
1031                if name.as_ref() == b"m:box" || name.as_ref() == b"box" {
1032                    break;
1033                }
1034            }
1035            Ok(Event::Eof) => break,
1036            Err(e) => return Err(Error::Xml(e)),
1037            _ => {}
1038        }
1039        buf.clear();
1040    }
1041
1042    Ok(result)
1043}
1044
1045/// Parse a border box.
1046fn parse_border_box<R: BufRead>(reader: &mut Reader<R>) -> Result<BorderBox> {
1047    let mut buf = Vec::new();
1048    let mut result = BorderBox::default();
1049
1050    loop {
1051        match reader.read_event_into(&mut buf) {
1052            Ok(Event::Start(e)) => {
1053                let name = e.name();
1054                let name = name.as_ref();
1055                if name == b"m:e" || name == b"e" {
1056                    result.content = parse_math_arg(reader, name)?;
1057                }
1058            }
1059            Ok(Event::End(e)) => {
1060                let name = e.name();
1061                if name.as_ref() == b"m:borderBox" || name.as_ref() == b"borderBox" {
1062                    break;
1063                }
1064            }
1065            Ok(Event::Eof) => break,
1066            Err(e) => return Err(Error::Xml(e)),
1067            _ => {}
1068        }
1069        buf.clear();
1070    }
1071
1072    Ok(result)
1073}
1074
1075/// Parse an equation array.
1076fn parse_equation_array<R: BufRead>(reader: &mut Reader<R>) -> Result<EquationArray> {
1077    let mut buf = Vec::new();
1078    let mut result = EquationArray::default();
1079
1080    loop {
1081        match reader.read_event_into(&mut buf) {
1082            Ok(Event::Start(e)) => {
1083                let name = e.name();
1084                let name = name.as_ref();
1085                if name == b"m:e" || name == b"e" {
1086                    result.equations.push(parse_math_arg(reader, name)?);
1087                }
1088            }
1089            Ok(Event::End(e)) => {
1090                let name = e.name();
1091                if name.as_ref() == b"m:eqArr" || name.as_ref() == b"eqArr" {
1092                    break;
1093                }
1094            }
1095            Ok(Event::Eof) => break,
1096            Err(e) => return Err(Error::Xml(e)),
1097            _ => {}
1098        }
1099        buf.clear();
1100    }
1101
1102    Ok(result)
1103}
1104
1105/// Parse a limit.
1106fn parse_limit<R: BufRead>(reader: &mut Reader<R>, end_tag: &[u8]) -> Result<Limit> {
1107    let mut buf = Vec::new();
1108    let mut result = Limit::default();
1109    let end_local = if end_tag.starts_with(b"m:") {
1110        &end_tag[2..]
1111    } else {
1112        end_tag
1113    };
1114
1115    loop {
1116        match reader.read_event_into(&mut buf) {
1117            Ok(Event::Start(e)) => {
1118                let name = e.name();
1119                let name = name.as_ref();
1120                match name {
1121                    b"m:e" | b"e" => result.base = parse_math_arg(reader, name)?,
1122                    b"m:lim" | b"lim" => result.limit = parse_math_arg(reader, name)?,
1123                    _ => {}
1124                }
1125            }
1126            Ok(Event::End(e)) => {
1127                let name = e.name();
1128                let name = name.as_ref();
1129                if name == end_tag || name == end_local {
1130                    break;
1131                }
1132            }
1133            Ok(Event::Eof) => break,
1134            Err(e) => return Err(Error::Xml(e)),
1135            _ => {}
1136        }
1137        buf.clear();
1138    }
1139
1140    Ok(result)
1141}
1142
1143/// Parse a group character.
1144fn parse_group_char<R: BufRead>(reader: &mut Reader<R>) -> Result<GroupChar> {
1145    let mut buf = Vec::new();
1146    let mut result = GroupChar::default();
1147
1148    loop {
1149        match reader.read_event_into(&mut buf) {
1150            Ok(Event::Start(e)) => {
1151                let name = e.name();
1152                let name = name.as_ref();
1153                if name == b"m:e" || name == b"e" {
1154                    result.base = parse_math_arg(reader, name)?;
1155                }
1156            }
1157            Ok(Event::Empty(e)) => {
1158                let name = e.name();
1159                if name.as_ref() == b"m:chr" || name.as_ref() == b"chr" {
1160                    for attr in e.attributes().filter_map(|a| a.ok()) {
1161                        if attr.key.as_ref() == b"m:val" || attr.key.as_ref() == b"val" {
1162                            result.character =
1163                                Some(String::from_utf8_lossy(&attr.value).into_owned());
1164                        }
1165                    }
1166                }
1167            }
1168            Ok(Event::End(e)) => {
1169                let name = e.name();
1170                if name.as_ref() == b"m:groupChr" || name.as_ref() == b"groupChr" {
1171                    break;
1172                }
1173            }
1174            Ok(Event::Eof) => break,
1175            Err(e) => return Err(Error::Xml(e)),
1176            _ => {}
1177        }
1178        buf.clear();
1179    }
1180
1181    Ok(result)
1182}
1183
1184/// Parse a phantom.
1185fn parse_phantom<R: BufRead>(reader: &mut Reader<R>) -> Result<Phantom> {
1186    let mut buf = Vec::new();
1187    let mut result = Phantom::default();
1188
1189    loop {
1190        match reader.read_event_into(&mut buf) {
1191            Ok(Event::Start(e)) => {
1192                let name = e.name();
1193                let name = name.as_ref();
1194                if name == b"m:e" || name == b"e" {
1195                    result.content = parse_math_arg(reader, name)?;
1196                }
1197            }
1198            Ok(Event::End(e)) => {
1199                let name = e.name();
1200                if name.as_ref() == b"m:phant" || name.as_ref() == b"phant" {
1201                    break;
1202                }
1203            }
1204            Ok(Event::Eof) => break,
1205            Err(e) => return Err(Error::Xml(e)),
1206            _ => {}
1207        }
1208        buf.clear();
1209    }
1210
1211    Ok(result)
1212}
1213
1214// ============================================================================
1215// Serialization
1216// ============================================================================
1217
1218/// Serialize a math zone to XML.
1219pub fn serialize_math_zone(zone: &MathZone) -> String {
1220    let mut xml = String::new();
1221    xml.push_str("<m:oMath>");
1222    for element in &zone.elements {
1223        serialize_math_element(element, &mut xml);
1224    }
1225    xml.push_str("</m:oMath>");
1226    xml
1227}
1228
1229/// Serialize a math element to XML.
1230fn serialize_math_element(element: &MathElement, xml: &mut String) {
1231    match element {
1232        MathElement::Run(run) => serialize_math_run(run, xml),
1233        MathElement::Fraction(f) => {
1234            xml.push_str("<m:f>");
1235            xml.push_str("<m:num>");
1236            serialize_math_zone_content(&f.numerator, xml);
1237            xml.push_str("</m:num>");
1238            xml.push_str("<m:den>");
1239            serialize_math_zone_content(&f.denominator, xml);
1240            xml.push_str("</m:den>");
1241            xml.push_str("</m:f>");
1242        }
1243        MathElement::Radical(r) => {
1244            xml.push_str("<m:rad>");
1245            xml.push_str("<m:deg>");
1246            serialize_math_zone_content(&r.degree, xml);
1247            xml.push_str("</m:deg>");
1248            xml.push_str("<m:e>");
1249            serialize_math_zone_content(&r.base, xml);
1250            xml.push_str("</m:e>");
1251            xml.push_str("</m:rad>");
1252        }
1253        MathElement::Nary(n) => {
1254            xml.push_str("<m:nary>");
1255            if let Some(ref op) = n.operator {
1256                xml.push_str("<m:naryPr><m:chr m:val=\"");
1257                xml.push_str(&escape_xml(op));
1258                xml.push_str("\"/></m:naryPr>");
1259            }
1260            xml.push_str("<m:sub>");
1261            serialize_math_zone_content(&n.subscript, xml);
1262            xml.push_str("</m:sub>");
1263            xml.push_str("<m:sup>");
1264            serialize_math_zone_content(&n.superscript, xml);
1265            xml.push_str("</m:sup>");
1266            xml.push_str("<m:e>");
1267            serialize_math_zone_content(&n.base, xml);
1268            xml.push_str("</m:e>");
1269            xml.push_str("</m:nary>");
1270        }
1271        MathElement::Subscript(s) => {
1272            xml.push_str("<m:sSub>");
1273            xml.push_str("<m:e>");
1274            serialize_math_zone_content(&s.base, xml);
1275            xml.push_str("</m:e>");
1276            xml.push_str("<m:sub>");
1277            serialize_math_zone_content(&s.script, xml);
1278            xml.push_str("</m:sub>");
1279            xml.push_str("</m:sSub>");
1280        }
1281        MathElement::Superscript(s) => {
1282            xml.push_str("<m:sSup>");
1283            xml.push_str("<m:e>");
1284            serialize_math_zone_content(&s.base, xml);
1285            xml.push_str("</m:e>");
1286            xml.push_str("<m:sup>");
1287            serialize_math_zone_content(&s.script, xml);
1288            xml.push_str("</m:sup>");
1289            xml.push_str("</m:sSup>");
1290        }
1291        MathElement::SubSuperscript(s) => {
1292            xml.push_str("<m:sSubSup>");
1293            xml.push_str("<m:e>");
1294            serialize_math_zone_content(&s.base, xml);
1295            xml.push_str("</m:e>");
1296            xml.push_str("<m:sub>");
1297            serialize_math_zone_content(&s.subscript, xml);
1298            xml.push_str("</m:sub>");
1299            xml.push_str("<m:sup>");
1300            serialize_math_zone_content(&s.superscript, xml);
1301            xml.push_str("</m:sup>");
1302            xml.push_str("</m:sSubSup>");
1303        }
1304        MathElement::PreScript(p) => {
1305            xml.push_str("<m:sPre>");
1306            xml.push_str("<m:sub>");
1307            serialize_math_zone_content(&p.subscript, xml);
1308            xml.push_str("</m:sub>");
1309            xml.push_str("<m:sup>");
1310            serialize_math_zone_content(&p.superscript, xml);
1311            xml.push_str("</m:sup>");
1312            xml.push_str("<m:e>");
1313            serialize_math_zone_content(&p.base, xml);
1314            xml.push_str("</m:e>");
1315            xml.push_str("</m:sPre>");
1316        }
1317        MathElement::Delimiter(d) => {
1318            xml.push_str("<m:d>");
1319            if d.begin_char.as_deref() != Some("(") || d.end_char.as_deref() != Some(")") {
1320                xml.push_str("<m:dPr>");
1321                if let Some(ref beg) = d.begin_char {
1322                    xml.push_str("<m:begChr m:val=\"");
1323                    xml.push_str(&escape_xml(beg));
1324                    xml.push_str("\"/>");
1325                }
1326                if let Some(ref end) = d.end_char {
1327                    xml.push_str("<m:endChr m:val=\"");
1328                    xml.push_str(&escape_xml(end));
1329                    xml.push_str("\"/>");
1330                }
1331                xml.push_str("</m:dPr>");
1332            }
1333            for e in &d.elements {
1334                xml.push_str("<m:e>");
1335                serialize_math_zone_content(e, xml);
1336                xml.push_str("</m:e>");
1337            }
1338            xml.push_str("</m:d>");
1339        }
1340        MathElement::Matrix(m) => {
1341            xml.push_str("<m:m>");
1342            for row in &m.rows {
1343                xml.push_str("<m:mr>");
1344                for cell in row {
1345                    xml.push_str("<m:e>");
1346                    serialize_math_zone_content(cell, xml);
1347                    xml.push_str("</m:e>");
1348                }
1349                xml.push_str("</m:mr>");
1350            }
1351            xml.push_str("</m:m>");
1352        }
1353        MathElement::Function(f) => {
1354            xml.push_str("<m:func>");
1355            xml.push_str("<m:fName>");
1356            serialize_math_zone_content(&f.name, xml);
1357            xml.push_str("</m:fName>");
1358            xml.push_str("<m:e>");
1359            serialize_math_zone_content(&f.argument, xml);
1360            xml.push_str("</m:e>");
1361            xml.push_str("</m:func>");
1362        }
1363        MathElement::Accent(a) => {
1364            xml.push_str("<m:acc>");
1365            if let Some(ref chr) = a.character {
1366                xml.push_str("<m:accPr><m:chr m:val=\"");
1367                xml.push_str(&escape_xml(chr));
1368                xml.push_str("\"/></m:accPr>");
1369            }
1370            xml.push_str("<m:e>");
1371            serialize_math_zone_content(&a.base, xml);
1372            xml.push_str("</m:e>");
1373            xml.push_str("</m:acc>");
1374        }
1375        MathElement::Bar(b) => {
1376            xml.push_str("<m:bar>");
1377            if b.position != Some(VerticalPosition::Top) {
1378                xml.push_str("<m:barPr><m:pos m:val=\"bot\"/></m:barPr>");
1379            }
1380            xml.push_str("<m:e>");
1381            serialize_math_zone_content(&b.base, xml);
1382            xml.push_str("</m:e>");
1383            xml.push_str("</m:bar>");
1384        }
1385        MathElement::Box(b) => {
1386            xml.push_str("<m:box>");
1387            xml.push_str("<m:e>");
1388            serialize_math_zone_content(&b.content, xml);
1389            xml.push_str("</m:e>");
1390            xml.push_str("</m:box>");
1391        }
1392        MathElement::BorderBox(b) => {
1393            xml.push_str("<m:borderBox>");
1394            xml.push_str("<m:e>");
1395            serialize_math_zone_content(&b.content, xml);
1396            xml.push_str("</m:e>");
1397            xml.push_str("</m:borderBox>");
1398        }
1399        MathElement::EquationArray(ea) => {
1400            xml.push_str("<m:eqArr>");
1401            for eq in &ea.equations {
1402                xml.push_str("<m:e>");
1403                serialize_math_zone_content(eq, xml);
1404                xml.push_str("</m:e>");
1405            }
1406            xml.push_str("</m:eqArr>");
1407        }
1408        MathElement::LowerLimit(l) => {
1409            xml.push_str("<m:limLow>");
1410            xml.push_str("<m:e>");
1411            serialize_math_zone_content(&l.base, xml);
1412            xml.push_str("</m:e>");
1413            xml.push_str("<m:lim>");
1414            serialize_math_zone_content(&l.limit, xml);
1415            xml.push_str("</m:lim>");
1416            xml.push_str("</m:limLow>");
1417        }
1418        MathElement::UpperLimit(l) => {
1419            xml.push_str("<m:limUpp>");
1420            xml.push_str("<m:e>");
1421            serialize_math_zone_content(&l.base, xml);
1422            xml.push_str("</m:e>");
1423            xml.push_str("<m:lim>");
1424            serialize_math_zone_content(&l.limit, xml);
1425            xml.push_str("</m:lim>");
1426            xml.push_str("</m:limUpp>");
1427        }
1428        MathElement::GroupChar(g) => {
1429            xml.push_str("<m:groupChr>");
1430            if let Some(ref chr) = g.character {
1431                xml.push_str("<m:groupChrPr><m:chr m:val=\"");
1432                xml.push_str(&escape_xml(chr));
1433                xml.push_str("\"/></m:groupChrPr>");
1434            }
1435            xml.push_str("<m:e>");
1436            serialize_math_zone_content(&g.base, xml);
1437            xml.push_str("</m:e>");
1438            xml.push_str("</m:groupChr>");
1439        }
1440        MathElement::Phantom(p) => {
1441            xml.push_str("<m:phant>");
1442            xml.push_str("<m:e>");
1443            serialize_math_zone_content(&p.content, xml);
1444            xml.push_str("</m:e>");
1445            xml.push_str("</m:phant>");
1446        }
1447    }
1448}
1449
1450/// Serialize math zone content (elements within a zone).
1451fn serialize_math_zone_content(zone: &MathZone, xml: &mut String) {
1452    for element in &zone.elements {
1453        serialize_math_element(element, xml);
1454    }
1455}
1456
1457/// Serialize a math run.
1458fn serialize_math_run(run: &MathRun, xml: &mut String) {
1459    xml.push_str("<m:r>");
1460    if let Some(ref props) = run.properties
1461        && let Some(style) = props.style
1462    {
1463        xml.push_str("<m:rPr>");
1464        xml.push_str("<m:sty m:val=\"");
1465        xml.push_str(match style {
1466            MathStyle::Plain => "p",
1467            MathStyle::Bold => "b",
1468            MathStyle::Italic => "i",
1469            MathStyle::BoldItalic => "bi",
1470        });
1471        xml.push_str("\"/>");
1472        xml.push_str("</m:rPr>");
1473    }
1474    xml.push_str("<m:t>");
1475    xml.push_str(&escape_xml(&run.text));
1476    xml.push_str("</m:t>");
1477    xml.push_str("</m:r>");
1478}
1479
1480/// Escape special XML characters.
1481fn escape_xml(s: &str) -> String {
1482    s.replace('&', "&amp;")
1483        .replace('<', "&lt;")
1484        .replace('>', "&gt;")
1485        .replace('"', "&quot;")
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490    use super::*;
1491
1492    #[test]
1493    fn test_parse_simple_math() {
1494        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1495            <m:r><m:t>x+y</m:t></m:r>
1496        </m:oMath>"#;
1497
1498        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1499        assert_eq!(zone.text(), "x+y");
1500    }
1501
1502    #[test]
1503    fn test_parse_fraction() {
1504        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1505            <m:f>
1506                <m:num><m:r><m:t>1</m:t></m:r></m:num>
1507                <m:den><m:r><m:t>2</m:t></m:r></m:den>
1508            </m:f>
1509        </m:oMath>"#;
1510
1511        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1512        assert_eq!(zone.text(), "(1)/(2)");
1513    }
1514
1515    #[test]
1516    fn test_parse_nested_fraction() {
1517        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1518            <m:f>
1519                <m:num>
1520                    <m:r><m:t>a+</m:t></m:r>
1521                    <m:f>
1522                        <m:num><m:r><m:t>b</m:t></m:r></m:num>
1523                        <m:den><m:r><m:t>c</m:t></m:r></m:den>
1524                    </m:f>
1525                </m:num>
1526                <m:den><m:r><m:t>d</m:t></m:r></m:den>
1527            </m:f>
1528        </m:oMath>"#;
1529
1530        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1531        assert_eq!(zone.text(), "(a+(b)/(c))/(d)");
1532    }
1533
1534    #[test]
1535    fn test_parse_radical() {
1536        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1537            <m:rad>
1538                <m:deg></m:deg>
1539                <m:e><m:r><m:t>x</m:t></m:r></m:e>
1540            </m:rad>
1541        </m:oMath>"#;
1542
1543        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1544        assert_eq!(zone.text(), "sqrt(x)");
1545    }
1546
1547    #[test]
1548    fn test_parse_subscript() {
1549        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1550            <m:sSub>
1551                <m:e><m:r><m:t>x</m:t></m:r></m:e>
1552                <m:sub><m:r><m:t>i</m:t></m:r></m:sub>
1553            </m:sSub>
1554        </m:oMath>"#;
1555
1556        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1557        assert_eq!(zone.text(), "x_i");
1558    }
1559
1560    #[test]
1561    fn test_parse_delimiter() {
1562        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1563            <m:d>
1564                <m:e><m:r><m:t>a+b</m:t></m:r></m:e>
1565            </m:d>
1566        </m:oMath>"#;
1567
1568        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1569        assert_eq!(zone.text(), "(a+b)");
1570    }
1571
1572    #[test]
1573    fn test_parse_real_world_formula() {
1574        // Real formula from NapierOne corpus: ((30 + (90/2)) / 900) * 100 = 8%
1575        // Simplified version without WML formatting elements
1576        let xml = r#"<m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math">
1577            <m:r><m:t>       </m:t></m:r>
1578            <m:d>
1579                <m:e>
1580                    <m:f>
1581                        <m:num>
1582                            <m:r><m:t>30+</m:t></m:r>
1583                            <m:d>
1584                                <m:e>
1585                                    <m:f>
1586                                        <m:num><m:r><m:t>90</m:t></m:r></m:num>
1587                                        <m:den><m:r><m:t>2</m:t></m:r></m:den>
1588                                    </m:f>
1589                                </m:e>
1590                            </m:d>
1591                        </m:num>
1592                        <m:den><m:r><m:t>900</m:t></m:r></m:den>
1593                    </m:f>
1594                </m:e>
1595            </m:d>
1596            <m:r><m:t>*100=8%</m:t></m:r>
1597        </m:oMath>"#;
1598
1599        let zone = parse_math_zone(xml.as_bytes()).unwrap();
1600        let text = zone.text();
1601        // Verify key parts are present
1602        assert!(text.contains("30+"));
1603        assert!(text.contains("90"));
1604        assert!(text.contains("900"));
1605        assert!(text.contains("*100=8%"));
1606    }
1607}