Skip to main content

excelize_rs/xml/
vml.rs

1//! VML drawing part (`xl/drawings/vmlDrawingN.vml`).
2//!
3//! Ported from Go `vmlDrawing.go`. The VML format mixes namespaced elements
4//! and arbitrary inner XML, so this module uses a small manual parser/serializer
5//! built on `quick_xml` rather than full serde mapping.
6
7use quick_xml::events::{BytesStart, BytesText, Event};
8use quick_xml::name::QName;
9use quick_xml::{Reader, Writer};
10
11// ------------------------------------------------------------------
12// Public data model
13// ------------------------------------------------------------------
14
15/// Root element of a VML drawing part.
16#[derive(Debug, Default, Clone, PartialEq)]
17pub struct VmlDrawing {
18    pub xmlns_v: String,
19    pub xmlns_o: String,
20    pub xmlns_x: String,
21    pub xmlns_mv: Option<String>,
22    pub shape_layout: Option<VmlShapeLayout>,
23    pub shape_type: Option<VmlShapeType>,
24    pub shape: Vec<VmlShape>,
25}
26
27/// `<o:shapelayout>` element.
28#[derive(Debug, Default, Clone, PartialEq)]
29pub struct VmlShapeLayout {
30    pub ext: String,
31    pub idmap: Option<VmlIdmap>,
32}
33
34/// `<o:idmap>` element.
35#[derive(Debug, Default, Clone, PartialEq)]
36pub struct VmlIdmap {
37    pub ext: String,
38    pub data: i32,
39}
40
41/// `<v:shapetype>` element.
42#[derive(Debug, Default, Clone, PartialEq)]
43pub struct VmlShapeType {
44    pub id: String,
45    pub coord_size: String,
46    pub spt: i32,
47    pub prefer_relative: Option<String>,
48    pub path: String,
49    pub filled: Option<String>,
50    pub stroked: Option<String>,
51    pub stroke: Option<VmlStroke>,
52    pub formulas: Option<VmlFormulas>,
53    pub v_path: Option<VmlPath>,
54    pub lock: Option<VmlLock>,
55}
56
57/// `<v:stroke>` element.
58#[derive(Debug, Default, Clone, PartialEq)]
59pub struct VmlStroke {
60    pub join_style: String,
61}
62
63/// `<v:path>` element.
64#[derive(Debug, Default, Clone, PartialEq)]
65pub struct VmlPath {
66    pub extrusion_ok: Option<String>,
67    pub gradient_shape_ok: Option<String>,
68    pub connect_type: String,
69}
70
71/// `<v:formulas>` / `<v:f>` elements.
72#[derive(Debug, Default, Clone, PartialEq)]
73pub struct VmlFormulas {
74    pub formula: Vec<VmlFormula>,
75}
76
77#[derive(Debug, Default, Clone, PartialEq)]
78pub struct VmlFormula {
79    pub equation: String,
80}
81
82/// `<o:lock>` element.
83#[derive(Debug, Default, Clone, PartialEq)]
84pub struct VmlLock {
85    pub ext: String,
86    pub rotation: Option<String>,
87    pub aspect_ratio: Option<String>,
88}
89
90/// `<v:imagedata>` element.
91#[derive(Debug, Default, Clone, PartialEq)]
92pub struct VmlImageData {
93    pub id: Option<String>,
94    pub src: Option<String>,
95    pub rel_id: Option<String>,
96    pub title: Option<String>,
97    pub crop_top: Option<String>,
98    pub crop_left: Option<String>,
99    pub crop_bottom: Option<String>,
100    pub crop_right: Option<String>,
101    pub gain: Option<String>,
102    pub black_level: Option<String>,
103    pub gamma: Option<String>,
104    pub grayscale: Option<String>,
105    pub bilevel: Option<String>,
106}
107
108/// `<v:shape>` element. The inner XML is stored as an opaque string so that
109/// namespaced child elements (`v:fill`, `x:ClientData`, etc.) round-trip
110/// without loss.
111#[derive(Debug, Default, Clone, PartialEq)]
112pub struct VmlShape {
113    pub id: String,
114    pub spid: Option<String>,
115    pub shape_type: String,
116    pub style: String,
117    pub button: Option<String>,
118    pub filled: Option<String>,
119    pub fill_color: Option<String>,
120    pub inset_mode: Option<String>,
121    pub stroked: Option<String>,
122    pub stroke_color: Option<String>,
123    pub inner_xml: String,
124}
125
126// ------------------------------------------------------------------
127// Serialization
128// ------------------------------------------------------------------
129
130impl VmlLock {
131    pub(crate) fn write_xml<W: std::io::Write>(&self, w: &mut Writer<W>) {
132        let mut e = BytesStart::new("o:lock");
133        if !self.ext.is_empty() {
134            e.push_attribute(("v:ext", self.ext.as_str()));
135        }
136        if let Some(v) = &self.rotation {
137            e.push_attribute(("rotation", v.as_str()));
138        }
139        if let Some(v) = &self.aspect_ratio {
140            e.push_attribute(("aspectratio", v.as_str()));
141        }
142        w.write_event(Event::Empty(e)).ok();
143    }
144
145    pub(crate) fn to_xml_string(&self) -> String {
146        let mut buf = Vec::new();
147        self.write_xml(&mut Writer::new(&mut buf));
148        String::from_utf8_lossy(&buf).to_string()
149    }
150}
151
152impl VmlImageData {
153    pub(crate) fn write_xml<W: std::io::Write>(&self, w: &mut Writer<W>) {
154        let mut e = BytesStart::new("v:imagedata");
155        if let Some(v) = &self.id {
156            e.push_attribute(("id", v.as_str()));
157        }
158        if let Some(v) = &self.src {
159            e.push_attribute(("src", v.as_str()));
160        }
161        if let Some(v) = &self.rel_id {
162            e.push_attribute(("o:relid", v.as_str()));
163        }
164        if let Some(v) = &self.title {
165            e.push_attribute(("o:title", v.as_str()));
166        }
167        if let Some(v) = &self.crop_top {
168            e.push_attribute(("croptop", v.as_str()));
169        }
170        if let Some(v) = &self.crop_left {
171            e.push_attribute(("cropleft", v.as_str()));
172        }
173        if let Some(v) = &self.crop_bottom {
174            e.push_attribute(("cropbottom", v.as_str()));
175        }
176        if let Some(v) = &self.crop_right {
177            e.push_attribute(("cropright", v.as_str()));
178        }
179        if let Some(v) = &self.gain {
180            e.push_attribute(("gain", v.as_str()));
181        }
182        if let Some(v) = &self.black_level {
183            e.push_attribute(("blacklevel", v.as_str()));
184        }
185        if let Some(v) = &self.gamma {
186            e.push_attribute(("gamma", v.as_str()));
187        }
188        if let Some(v) = &self.grayscale {
189            e.push_attribute(("grayscale", v.as_str()));
190        }
191        if let Some(v) = &self.bilevel {
192            e.push_attribute(("bilevel", v.as_str()));
193        }
194        w.write_event(Event::Empty(e)).ok();
195    }
196
197    pub(crate) fn to_xml_string(&self) -> String {
198        let mut buf = Vec::new();
199        self.write_xml(&mut Writer::new(&mut buf));
200        String::from_utf8_lossy(&buf).to_string()
201    }
202}
203
204impl VmlDrawing {
205    /// Serialize the VML drawing to bytes including the XML header.
206    pub fn to_xml(&self) -> Vec<u8> {
207        let mut out = Vec::new();
208        out.extend_from_slice(crate::constants::XML_HEADER.as_bytes());
209        out.push(b'\n');
210        let mut w = Writer::new_with_indent(&mut out, b' ', 2);
211
212        let mut root = BytesStart::new("xml");
213        root.push_attribute(("xmlns:v", self.xmlns_v.as_str()));
214        root.push_attribute(("xmlns:o", self.xmlns_o.as_str()));
215        root.push_attribute(("xmlns:x", self.xmlns_x.as_str()));
216        if let Some(ns) = &self.xmlns_mv {
217            root.push_attribute(("xmlns:mv", ns.as_str()));
218        }
219        w.write_event(Event::Start(root)).ok();
220
221        if let Some(layout) = &self.shape_layout {
222            layout.write_xml(&mut w);
223        }
224        if let Some(st) = &self.shape_type {
225            st.write_xml(&mut w);
226        }
227        for shape in &self.shape {
228            shape.write_xml(&mut w);
229        }
230
231        w.write_event(Event::End(QName(b"xml").into())).ok();
232        out
233    }
234
235    /// Parse a VML drawing from raw bytes.
236    pub fn from_xml(data: &[u8]) -> Result<Self, quick_xml::Error> {
237        let mut reader = Reader::from_reader(data);
238        reader.config_mut().trim_text(true);
239        let mut buf = Vec::new();
240        let mut drawing = VmlDrawing::default();
241        let mut depth = 0usize;
242
243        loop {
244            let event = reader.read_event_into(&mut buf)?;
245            match event {
246                Event::Start(e) => {
247                    let name = e.name();
248                    let local = local_name(name.as_ref());
249                    if local == b"xml" && depth == 0 {
250                        parse_root_attrs(&e, &mut drawing);
251                        depth += 1;
252                    } else if local == b"shapelayout" {
253                        drawing.shape_layout = Some(parse_shape_layout(&e, &mut reader)?);
254                    } else if local == b"shapetype" {
255                        drawing.shape_type = Some(parse_shape_type(&e, &mut reader)?);
256                    } else if local == b"shape" {
257                        drawing.shape.push(parse_shape(&e, &mut reader)?);
258                    } else {
259                        depth += 1;
260                    }
261                }
262                Event::Empty(e) => {
263                    let name = e.name();
264                    let local = local_name(name.as_ref());
265                    if local == b"xml" && depth == 0 {
266                        parse_root_attrs(&e, &mut drawing);
267                    } else if local == b"shapelayout" {
268                        drawing.shape_layout = Some(parse_shape_layout(&e, &mut reader)?);
269                    } else if local == b"shapetype" {
270                        drawing.shape_type = Some(parse_shape_type(&e, &mut reader)?);
271                    } else if local == b"shape" {
272                        let mut shape = parse_shape_attrs(&e);
273                        shape.inner_xml = String::new();
274                        drawing.shape.push(shape);
275                    }
276                }
277                Event::End(e) => {
278                    let name = e.name();
279                    if local_name(name.as_ref()) == b"xml" {
280                        depth = depth.saturating_sub(1);
281                    }
282                }
283                Event::Eof => break,
284                _ => {}
285            }
286            buf.clear();
287        }
288        Ok(drawing)
289    }
290}
291
292// ------------------------------------------------------------------
293// Helper implementations
294// ------------------------------------------------------------------
295
296impl VmlShapeLayout {
297    fn write_xml<W: std::io::Write>(&self, w: &mut Writer<W>) {
298        let mut start = BytesStart::new("o:shapelayout");
299        if !self.ext.is_empty() {
300            start.push_attribute(("v:ext", self.ext.as_str()));
301        }
302        w.write_event(Event::Start(start)).ok();
303        if let Some(idmap) = &self.idmap {
304            let mut e = BytesStart::new("o:idmap");
305            if !idmap.ext.is_empty() {
306                e.push_attribute(("v:ext", idmap.ext.as_str()));
307            }
308            e.push_attribute(("data", idmap.data.to_string().as_str()));
309            w.write_event(Event::Empty(e)).ok();
310        }
311        w.write_event(Event::End(QName(b"o:shapelayout").into()))
312            .ok();
313    }
314}
315
316impl VmlShapeType {
317    fn write_xml<W: std::io::Write>(&self, w: &mut Writer<W>) {
318        let mut start = BytesStart::new("v:shapetype");
319        if !self.id.is_empty() {
320            start.push_attribute(("id", self.id.as_str()));
321        }
322        if !self.coord_size.is_empty() {
323            start.push_attribute(("coordsize", self.coord_size.as_str()));
324        }
325        if self.spt != 0 {
326            start.push_attribute(("o:spt", self.spt.to_string().as_str()));
327        }
328        if let Some(v) = &self.prefer_relative {
329            start.push_attribute(("o:preferrelative", v.as_str()));
330        }
331        if !self.path.is_empty() {
332            start.push_attribute(("path", self.path.as_str()));
333        }
334        if let Some(v) = &self.filled {
335            start.push_attribute(("filled", v.as_str()));
336        }
337        if let Some(v) = &self.stroked {
338            start.push_attribute(("stroked", v.as_str()));
339        }
340        w.write_event(Event::Start(start)).ok();
341        if let Some(stroke) = &self.stroke {
342            let mut e = BytesStart::new("v:stroke");
343            e.push_attribute(("joinstyle", stroke.join_style.as_str()));
344            w.write_event(Event::Empty(e)).ok();
345        }
346        if let Some(formulas) = &self.formulas {
347            w.write_event(Event::Start(BytesStart::new("v:formulas")))
348                .ok();
349            for f in &formulas.formula {
350                let mut e = BytesStart::new("v:f");
351                e.push_attribute(("eqn", f.equation.as_str()));
352                w.write_event(Event::Empty(e)).ok();
353            }
354            w.write_event(Event::End(QName(b"v:formulas").into())).ok();
355        }
356        if let Some(path) = &self.v_path {
357            let mut e = BytesStart::new("v:path");
358            if let Some(v) = &path.extrusion_ok {
359                e.push_attribute(("o:extrusionok", v.as_str()));
360            }
361            if let Some(v) = &path.gradient_shape_ok {
362                e.push_attribute(("gradientshapeok", v.as_str()));
363            }
364            e.push_attribute(("o:connecttype", path.connect_type.as_str()));
365            w.write_event(Event::Empty(e)).ok();
366        }
367        if let Some(lock) = &self.lock {
368            lock.write_xml(w);
369        }
370        w.write_event(Event::End(QName(b"v:shapetype").into())).ok();
371    }
372}
373
374impl VmlShape {
375    fn write_xml<W: std::io::Write>(&self, w: &mut Writer<W>) {
376        let mut start = BytesStart::new("v:shape");
377        if !self.id.is_empty() {
378            start.push_attribute(("id", self.id.as_str()));
379        }
380        if !self.shape_type.is_empty() {
381            start.push_attribute(("type", self.shape_type.as_str()));
382        }
383        if !self.style.is_empty() {
384            start.push_attribute(("style", self.style.as_str()));
385        }
386        if let Some(v) = &self.spid {
387            start.push_attribute(("o:spid", v.as_str()));
388        }
389        if let Some(v) = &self.button {
390            start.push_attribute(("o:button", v.as_str()));
391        }
392        if let Some(v) = &self.filled {
393            start.push_attribute(("filled", v.as_str()));
394        }
395        if let Some(v) = &self.fill_color {
396            start.push_attribute(("fillcolor", v.as_str()));
397        }
398        if let Some(v) = &self.inset_mode {
399            start.push_attribute(("o:insetmode", v.as_str()));
400        }
401        if let Some(v) = &self.stroked {
402            start.push_attribute(("stroked", v.as_str()));
403        }
404        if let Some(v) = &self.stroke_color {
405            start.push_attribute(("strokecolor", v.as_str()));
406        }
407        w.write_event(Event::Start(start)).ok();
408        if !self.inner_xml.is_empty() {
409            w.write_event(Event::Text(BytesText::from_escaped(
410                self.inner_xml.as_str(),
411            )))
412            .ok();
413        }
414        w.write_event(Event::End(QName(b"v:shape").into())).ok();
415    }
416}
417
418// ------------------------------------------------------------------
419// Parsing helpers
420// ------------------------------------------------------------------
421
422fn local_name(name: &[u8]) -> &[u8] {
423    if let Some(pos) = name.iter().rposition(|&b| b == b':') {
424        &name[pos + 1..]
425    } else {
426        name
427    }
428}
429
430fn parse_root_attrs(e: &BytesStart<'_>, drawing: &mut VmlDrawing) {
431    for attr in e.attributes().flatten() {
432        let key = String::from_utf8_lossy(attr.key.as_ref());
433        let value = String::from_utf8_lossy(&attr.value).to_string();
434        if key == "xmlns:v" {
435            drawing.xmlns_v = value;
436        } else if key == "xmlns:o" {
437            drawing.xmlns_o = value;
438        } else if key == "xmlns:x" {
439            drawing.xmlns_x = value;
440        } else if key == "xmlns:mv" {
441            drawing.xmlns_mv = Some(value);
442        }
443    }
444}
445
446fn parse_shape_layout<R: std::io::BufRead>(
447    e: &BytesStart<'_>,
448    reader: &mut Reader<R>,
449) -> Result<VmlShapeLayout, quick_xml::Error> {
450    let mut layout = VmlShapeLayout::default();
451    for attr in e.attributes().flatten() {
452        let key = String::from_utf8_lossy(attr.key.as_ref());
453        if key.ends_with(":ext") || key == "ext" {
454            layout.ext = String::from_utf8_lossy(&attr.value).to_string();
455        }
456    }
457    let mut buf = Vec::new();
458    loop {
459        match reader.read_event_into(&mut buf)? {
460            Event::Start(e) if local_name(e.name().as_ref()) == b"idmap" => {
461                let mut idmap = VmlIdmap::default();
462                for attr in e.attributes().flatten() {
463                    let key = String::from_utf8_lossy(attr.key.as_ref());
464                    if key.ends_with(":ext") || key == "ext" {
465                        idmap.ext = String::from_utf8_lossy(&attr.value).to_string();
466                    } else if key == "data" {
467                        idmap.data = String::from_utf8_lossy(&attr.value).parse().unwrap_or(0);
468                    }
469                }
470                layout.idmap = Some(idmap);
471            }
472            Event::End(e) if local_name(e.name().as_ref()) == b"shapelayout" => break,
473            Event::Eof => break,
474            _ => {}
475        }
476        buf.clear();
477    }
478    Ok(layout)
479}
480
481fn parse_shape_type<R: std::io::BufRead>(
482    e: &BytesStart<'_>,
483    reader: &mut Reader<R>,
484) -> Result<VmlShapeType, quick_xml::Error> {
485    let mut st = VmlShapeType::default();
486    for attr in e.attributes().flatten() {
487        let key = String::from_utf8_lossy(attr.key.as_ref());
488        let value = String::from_utf8_lossy(&attr.value).to_string();
489        if key == "id" {
490            st.id = value;
491        } else if key == "coordsize" {
492            st.coord_size = value;
493        } else if key.ends_with(":spt") || key == "spt" {
494            st.spt = value.parse().unwrap_or(0);
495        } else if key.ends_with(":preferrelative") || key == "preferrelative" {
496            st.prefer_relative = Some(value);
497        } else if key == "path" {
498            st.path = value;
499        } else if key == "filled" {
500            st.filled = Some(value);
501        } else if key == "stroked" {
502            st.stroked = Some(value);
503        }
504    }
505
506    let mut buf = Vec::new();
507    loop {
508        match reader.read_event_into(&mut buf)? {
509            Event::Empty(e) => {
510                let name = e.name();
511                let local = local_name(name.as_ref());
512                parse_shape_type_child(local, &e, &mut st);
513            }
514            Event::Start(e) => {
515                let name = e.name();
516                let local = local_name(name.as_ref());
517                if local == b"formulas" {
518                    st.formulas = Some(parse_formulas(reader)?);
519                } else {
520                    parse_shape_type_child(local, &e, &mut st);
521                }
522            }
523            Event::End(e) => {
524                let name = e.name();
525                if local_name(name.as_ref()) == b"shapetype" {
526                    break;
527                }
528            }
529            Event::Eof => break,
530            _ => {}
531        }
532        buf.clear();
533    }
534    Ok(st)
535}
536
537fn parse_shape_type_child(local: &[u8], e: &BytesStart<'_>, st: &mut VmlShapeType) {
538    if local == b"stroke" {
539        let mut stroke = VmlStroke::default();
540        for attr in e.attributes().flatten() {
541            let key = String::from_utf8_lossy(attr.key.as_ref());
542            if key == "joinstyle" {
543                stroke.join_style = String::from_utf8_lossy(&attr.value).to_string();
544            }
545        }
546        st.stroke = Some(stroke);
547    } else if local == b"path" {
548        let mut path = VmlPath {
549            connect_type: String::new(),
550            ..Default::default()
551        };
552        for attr in e.attributes().flatten() {
553            let key = String::from_utf8_lossy(attr.key.as_ref());
554            if key.ends_with(":extrusionok") || key == "extrusionok" {
555                path.extrusion_ok = Some(String::from_utf8_lossy(&attr.value).to_string());
556            } else if key == "gradientshapeok" {
557                path.gradient_shape_ok = Some(String::from_utf8_lossy(&attr.value).to_string());
558            } else if key.ends_with(":connecttype") || key == "connecttype" {
559                path.connect_type = String::from_utf8_lossy(&attr.value).to_string();
560            }
561        }
562        st.v_path = Some(path);
563    } else if local == b"lock" {
564        let mut lock = VmlLock::default();
565        for attr in e.attributes().flatten() {
566            let key = String::from_utf8_lossy(attr.key.as_ref());
567            let value = Some(String::from_utf8_lossy(&attr.value).to_string());
568            if key.ends_with(":ext") || key == "ext" {
569                lock.ext = value.unwrap_or_default();
570            } else if key == "rotation" {
571                lock.rotation = value;
572            } else if key == "aspectratio" {
573                lock.aspect_ratio = value;
574            }
575        }
576        st.lock = Some(lock);
577    }
578}
579
580fn parse_formulas<R: std::io::BufRead>(
581    reader: &mut Reader<R>,
582) -> Result<VmlFormulas, quick_xml::Error> {
583    let mut formulas = VmlFormulas::default();
584    let mut buf = Vec::new();
585    loop {
586        match reader.read_event_into(&mut buf)? {
587            Event::Empty(e) | Event::Start(e) if local_name(e.name().as_ref()) == b"f" => {
588                for attr in e.attributes().flatten() {
589                    let key = String::from_utf8_lossy(attr.key.as_ref());
590                    if key == "eqn" {
591                        formulas.formula.push(VmlFormula {
592                            equation: String::from_utf8_lossy(&attr.value).to_string(),
593                        });
594                    }
595                }
596            }
597            Event::End(e) if local_name(e.name().as_ref()) == b"formulas" => break,
598            Event::Eof => break,
599            _ => {}
600        }
601        buf.clear();
602    }
603    Ok(formulas)
604}
605
606fn parse_shape_attrs(e: &BytesStart<'_>) -> VmlShape {
607    let mut shape = VmlShape::default();
608    for attr in e.attributes().flatten() {
609        let key = String::from_utf8_lossy(attr.key.as_ref());
610        let value = String::from_utf8_lossy(&attr.value).to_string();
611        if key == "id" {
612            shape.id = value;
613        } else if key == "type" {
614            shape.shape_type = value;
615        } else if key == "style" {
616            shape.style = value;
617        } else if key.ends_with(":spid") || key == "spid" {
618            shape.spid = Some(value);
619        } else if key.ends_with(":button") || key == "button" {
620            shape.button = Some(value);
621        } else if key == "filled" {
622            shape.filled = Some(value);
623        } else if key == "fillcolor" {
624            shape.fill_color = Some(value);
625        } else if key.ends_with(":insetmode") || key == "insetmode" {
626            shape.inset_mode = Some(value);
627        } else if key == "stroked" {
628            shape.stroked = Some(value);
629        } else if key == "strokecolor" {
630            shape.stroke_color = Some(value);
631        }
632    }
633    shape
634}
635
636fn parse_shape<R: std::io::BufRead>(
637    e: &BytesStart<'_>,
638    reader: &mut Reader<R>,
639) -> Result<VmlShape, quick_xml::Error> {
640    let mut shape = parse_shape_attrs(e);
641    let mut inner = Vec::new();
642    let mut inner_writer = Writer::new(&mut inner);
643    let mut depth = 1usize;
644    let mut buf = Vec::new();
645
646    loop {
647        match reader.read_event_into(&mut buf)? {
648            Event::Start(ref se) => {
649                inner_writer.write_event(Event::Start(se.clone())).ok();
650                if local_name(se.name().as_ref()) == b"shape" {
651                    depth += 1;
652                }
653            }
654            Event::Empty(ref see) => {
655                inner_writer.write_event(Event::Empty(see.clone())).ok();
656            }
657            Event::Text(ref te) => {
658                inner_writer.write_event(Event::Text(te.clone())).ok();
659            }
660            Event::End(ref ee) => {
661                if local_name(ee.name().as_ref()) == b"shape" {
662                    depth -= 1;
663                    if depth == 0 {
664                        break;
665                    }
666                }
667                inner_writer.write_event(Event::End(ee.clone())).ok();
668            }
669            Event::Eof => break,
670            _ => {}
671        }
672        buf.clear();
673    }
674    shape.inner_xml = String::from_utf8_lossy(&inner).trim().to_string();
675    Ok(shape)
676}