Skip to main content

sheetkit_xml/
drawing.rs

1//! DrawingML Spreadsheet Drawing XML schema structures.
2//!
3//! Represents `xl/drawings/drawing{N}.xml` in the OOXML package.
4
5use serde::{Deserialize, Serialize};
6
7use crate::namespaces;
8
9/// Root element for a spreadsheet drawing part.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename = "wsDr")]
12pub struct WsDr {
13    #[serde(rename = "@xmlns:xdr")]
14    pub xmlns_xdr: String,
15
16    #[serde(rename = "@xmlns:a")]
17    pub xmlns_a: String,
18
19    #[serde(rename = "@xmlns:r")]
20    pub xmlns_r: String,
21
22    #[serde(rename = "xdr:twoCellAnchor", default)]
23    pub two_cell_anchors: Vec<TwoCellAnchor>,
24
25    #[serde(rename = "xdr:oneCellAnchor", default)]
26    pub one_cell_anchors: Vec<OneCellAnchor>,
27}
28
29impl Default for WsDr {
30    fn default() -> Self {
31        Self {
32            xmlns_xdr: namespaces::DRAWING_ML_SPREADSHEET.to_string(),
33            xmlns_a: namespaces::DRAWING_ML.to_string(),
34            xmlns_r: namespaces::RELATIONSHIPS.to_string(),
35            two_cell_anchors: vec![],
36            one_cell_anchors: vec![],
37        }
38    }
39}
40
41/// An anchor defined by two cell markers (from/to).
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct TwoCellAnchor {
44    #[serde(rename = "xdr:from")]
45    pub from: MarkerType,
46
47    #[serde(rename = "xdr:to")]
48    pub to: MarkerType,
49
50    #[serde(rename = "xdr:graphicFrame", skip_serializing_if = "Option::is_none")]
51    pub graphic_frame: Option<GraphicFrame>,
52
53    #[serde(rename = "xdr:pic", skip_serializing_if = "Option::is_none")]
54    pub pic: Option<Picture>,
55
56    #[serde(rename = "xdr:sp", skip_serializing_if = "Option::is_none")]
57    pub shape: Option<Shape>,
58
59    #[serde(rename = "xdr:clientData")]
60    pub client_data: ClientData,
61}
62
63/// An anchor defined by one cell marker and an extent.
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct OneCellAnchor {
66    #[serde(rename = "xdr:from")]
67    pub from: MarkerType,
68
69    #[serde(rename = "xdr:ext")]
70    pub ext: Extent,
71
72    #[serde(rename = "xdr:pic", skip_serializing_if = "Option::is_none")]
73    pub pic: Option<Picture>,
74
75    #[serde(rename = "xdr:clientData")]
76    pub client_data: ClientData,
77}
78
79/// A cell marker indicating column, column offset, row, and row offset.
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct MarkerType {
82    #[serde(rename = "xdr:col")]
83    pub col: u32,
84
85    #[serde(rename = "xdr:colOff")]
86    pub col_off: u64,
87
88    #[serde(rename = "xdr:row")]
89    pub row: u32,
90
91    #[serde(rename = "xdr:rowOff")]
92    pub row_off: u64,
93}
94
95/// Extent (size) in EMU (English Metric Units).
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct Extent {
98    #[serde(rename = "@cx")]
99    pub cx: u64,
100
101    #[serde(rename = "@cy")]
102    pub cy: u64,
103}
104
105/// Graphic frame containing a chart reference.
106#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107pub struct GraphicFrame {
108    #[serde(rename = "xdr:nvGraphicFramePr")]
109    pub nv_graphic_frame_pr: NvGraphicFramePr,
110
111    #[serde(rename = "xdr:xfrm")]
112    pub xfrm: Xfrm,
113
114    #[serde(rename = "a:graphic")]
115    pub graphic: Graphic,
116}
117
118/// Non-visual graphic frame properties.
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120pub struct NvGraphicFramePr {
121    #[serde(rename = "xdr:cNvPr")]
122    pub c_nv_pr: CNvPr,
123
124    #[serde(rename = "xdr:cNvGraphicFramePr")]
125    pub c_nv_graphic_frame_pr: CNvGraphicFramePr,
126}
127
128/// Common non-visual properties (id and name).
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130pub struct CNvPr {
131    #[serde(rename = "@id")]
132    pub id: u32,
133
134    #[serde(rename = "@name")]
135    pub name: String,
136}
137
138/// Non-visual graphic frame properties (empty marker).
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub struct CNvGraphicFramePr {}
141
142/// Transform (position and size) for a graphic frame.
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct Xfrm {
145    #[serde(rename = "a:off")]
146    pub off: Offset,
147
148    #[serde(rename = "a:ext")]
149    pub ext: AExt,
150}
151
152/// Offset position.
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct Offset {
155    #[serde(rename = "@x")]
156    pub x: i64,
157
158    #[serde(rename = "@y")]
159    pub y: i64,
160}
161
162/// DrawingML extent (width/height).
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub struct AExt {
165    #[serde(rename = "@cx")]
166    pub cx: u64,
167
168    #[serde(rename = "@cy")]
169    pub cy: u64,
170}
171
172/// Graphic element containing chart data.
173#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct Graphic {
175    #[serde(rename = "a:graphicData")]
176    pub graphic_data: GraphicData,
177}
178
179/// Graphic data referencing a chart.
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct GraphicData {
182    #[serde(rename = "@uri")]
183    pub uri: String,
184
185    #[serde(rename = "c:chart")]
186    pub chart: ChartRef,
187}
188
189/// Reference to a chart part via relationship ID.
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
191pub struct ChartRef {
192    #[serde(rename = "@xmlns:c")]
193    pub xmlns_c: String,
194
195    #[serde(rename = "@r:id")]
196    pub r_id: String,
197}
198
199/// Picture element for images.
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub struct Picture {
202    #[serde(rename = "xdr:nvPicPr")]
203    pub nv_pic_pr: NvPicPr,
204
205    #[serde(rename = "xdr:blipFill")]
206    pub blip_fill: BlipFill,
207
208    #[serde(rename = "xdr:spPr")]
209    pub sp_pr: SpPr,
210}
211
212/// Non-visual picture properties.
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
214pub struct NvPicPr {
215    #[serde(rename = "xdr:cNvPr")]
216    pub c_nv_pr: CNvPr,
217
218    #[serde(rename = "xdr:cNvPicPr")]
219    pub c_nv_pic_pr: CNvPicPr,
220}
221
222/// Non-visual picture-specific properties (empty marker).
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct CNvPicPr {}
225
226/// Blip fill referencing an embedded image.
227#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
228pub struct BlipFill {
229    #[serde(rename = "a:blip")]
230    pub blip: Blip,
231
232    #[serde(rename = "a:stretch")]
233    pub stretch: Stretch,
234}
235
236/// Blip (Binary Large Image or Picture) reference.
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub struct Blip {
239    #[serde(rename = "@r:embed")]
240    pub r_embed: String,
241}
242
243/// Stretch fill mode.
244#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct Stretch {
246    #[serde(rename = "a:fillRect")]
247    pub fill_rect: FillRect,
248}
249
250/// Fill rectangle (empty element indicating full fill).
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct FillRect {}
253
254/// Shape properties for a picture.
255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
256pub struct SpPr {
257    #[serde(rename = "a:xfrm")]
258    pub xfrm: Xfrm,
259
260    #[serde(rename = "a:prstGeom")]
261    pub prst_geom: PrstGeom,
262}
263
264/// Preset geometry (e.g., rectangle).
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub struct PrstGeom {
267    #[serde(rename = "@prst")]
268    pub prst: String,
269}
270
271/// Shape element (`<xdr:sp>`).
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
273pub struct Shape {
274    #[serde(rename = "xdr:nvSpPr")]
275    pub nv_sp_pr: NvSpPr,
276
277    #[serde(rename = "xdr:spPr")]
278    pub sp_pr: ShapeSpPr,
279
280    #[serde(rename = "xdr:txBody", skip_serializing_if = "Option::is_none")]
281    pub tx_body: Option<TxBody>,
282}
283
284/// Non-visual shape properties.
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct NvSpPr {
287    #[serde(rename = "xdr:cNvPr")]
288    pub c_nv_pr: CNvPr,
289
290    #[serde(rename = "xdr:cNvSpPr")]
291    pub c_nv_sp_pr: CNvSpPr,
292}
293
294/// Non-visual shape-specific properties (empty marker).
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
296pub struct CNvSpPr {}
297
298/// Shape properties with optional fill and line.
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct ShapeSpPr {
301    #[serde(rename = "a:xfrm")]
302    pub xfrm: Xfrm,
303
304    #[serde(rename = "a:prstGeom")]
305    pub prst_geom: PrstGeom,
306
307    #[serde(rename = "a:solidFill", skip_serializing_if = "Option::is_none")]
308    pub solid_fill: Option<SolidFill>,
309
310    #[serde(rename = "a:ln", skip_serializing_if = "Option::is_none")]
311    pub ln: Option<Ln>,
312}
313
314/// Solid fill with an sRGB color.
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct SolidFill {
317    #[serde(rename = "a:srgbClr")]
318    pub srgb_clr: SrgbClr,
319}
320
321/// sRGB color value.
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct SrgbClr {
324    #[serde(rename = "@val")]
325    pub val: String,
326}
327
328/// Line properties.
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct Ln {
331    #[serde(rename = "@w", skip_serializing_if = "Option::is_none")]
332    pub w: Option<u64>,
333
334    #[serde(rename = "a:solidFill", skip_serializing_if = "Option::is_none")]
335    pub solid_fill: Option<SolidFill>,
336}
337
338/// Text body element.
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub struct TxBody {
341    #[serde(rename = "a:bodyPr")]
342    pub body_pr: BodyPr,
343
344    #[serde(rename = "a:lstStyle")]
345    pub lst_style: LstStyle,
346
347    #[serde(rename = "a:p")]
348    pub paragraphs: Vec<Paragraph>,
349}
350
351/// Body properties for text (empty marker with optional attributes).
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353pub struct BodyPr {}
354
355/// List style for text (empty marker).
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357pub struct LstStyle {}
358
359/// A text paragraph.
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
361pub struct Paragraph {
362    #[serde(rename = "a:r", default, skip_serializing_if = "Vec::is_empty")]
363    pub runs: Vec<TextRun>,
364}
365
366/// A text run within a paragraph.
367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
368pub struct TextRun {
369    #[serde(rename = "a:rPr", skip_serializing_if = "Option::is_none")]
370    pub r_pr: Option<RunProperties>,
371
372    #[serde(rename = "a:t")]
373    pub t: String,
374}
375
376/// Run-level text properties.
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct RunProperties {
379    #[serde(rename = "@lang", skip_serializing_if = "Option::is_none")]
380    pub lang: Option<String>,
381
382    #[serde(rename = "@sz", skip_serializing_if = "Option::is_none")]
383    pub sz: Option<u32>,
384}
385
386/// Client data (empty element required by spec).
387#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
388pub struct ClientData {}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    #[test]
395    fn test_ws_dr_default() {
396        let dr = WsDr::default();
397        assert_eq!(dr.xmlns_xdr, namespaces::DRAWING_ML_SPREADSHEET);
398        assert_eq!(dr.xmlns_a, namespaces::DRAWING_ML);
399        assert_eq!(dr.xmlns_r, namespaces::RELATIONSHIPS);
400        assert!(dr.two_cell_anchors.is_empty());
401        assert!(dr.one_cell_anchors.is_empty());
402    }
403
404    #[test]
405    fn test_marker_type_serialize() {
406        let marker = MarkerType {
407            col: 1,
408            col_off: 0,
409            row: 2,
410            row_off: 0,
411        };
412        let xml = quick_xml::se::to_string(&marker).unwrap();
413        assert!(xml.contains("<xdr:col>1</xdr:col>"));
414        assert!(xml.contains("<xdr:row>2</xdr:row>"));
415    }
416
417    #[test]
418    fn test_extent_serialize() {
419        let ext = Extent {
420            cx: 9525000,
421            cy: 4762500,
422        };
423        let xml = quick_xml::se::to_string(&ext).unwrap();
424        assert!(xml.contains("cx=\"9525000\""));
425        assert!(xml.contains("cy=\"4762500\""));
426    }
427
428    #[test]
429    fn test_chart_ref_serialize() {
430        let chart_ref = ChartRef {
431            xmlns_c: namespaces::DRAWING_ML_CHART.to_string(),
432            r_id: "rId1".to_string(),
433        };
434        let xml = quick_xml::se::to_string(&chart_ref).unwrap();
435        assert!(xml.contains("r:id=\"rId1\""));
436    }
437
438    #[test]
439    fn test_blip_serialize() {
440        let blip = Blip {
441            r_embed: "rId2".to_string(),
442        };
443        let xml = quick_xml::se::to_string(&blip).unwrap();
444        assert!(xml.contains("r:embed=\"rId2\""));
445    }
446
447    #[test]
448    fn test_prst_geom_serialize() {
449        let geom = PrstGeom {
450            prst: "rect".to_string(),
451        };
452        let xml = quick_xml::se::to_string(&geom).unwrap();
453        assert!(xml.contains("prst=\"rect\""));
454    }
455}