1pub mod color;
2pub mod effect;
3pub mod fill;
4pub mod geometry;
5pub mod line;
6pub mod namespace;
7pub mod order;
8mod preset_shape_data;
9pub mod shape_props;
10pub mod style_ref;
11pub mod table;
12pub mod text;
13pub mod theme;
14pub mod xfrm;
15
16#[cfg(test)]
17mod table_gate_tests {
18 use crate::table::CT_Table;
19
20 #[test]
21 fn merged_table_round_trips_with_merge_origins_intact() {
22 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblGrid><a:gridCol w="1000"/><a:gridCol w="2000"/><a:gridCol w="3000"/></a:tblGrid><a:tr h="4000"><a:tc rowSpan="2" gridSpan="2"><a:txBody><a:bodyPr/><a:lstStyle/><a:p/></a:txBody><a:tcPr/></a:tc><a:tc hMerge="1"><a:tcPr/></a:tc><a:tc><a:tcPr/></a:tc></a:tr><a:tr h="5000"><a:tc vMerge="1"><a:tcPr/></a:tc><a:tc hMerge="1" vMerge="1"><a:tcPr/></a:tc><a:tc><a:tcPr/></a:tc></a:tr></a:tbl>"#;
23 let table = CT_Table::from_xml(xml).unwrap();
24 assert_eq!(
25 table
26 .grid
27 .columns
28 .iter()
29 .map(|width| width.0)
30 .collect::<Vec<_>>(),
31 vec![1000, 2000, 3000]
32 );
33 assert_eq!(table.rows[0].height.0, 4000);
34 assert_eq!(table.rows[1].height.0, 5000);
35 let origin = &table.rows[0].cells[0];
36 assert_eq!((origin.row_span, origin.grid_span), (2, 2));
37 assert!(!origin.horizontal_merge && !origin.vertical_merge);
38 assert!(table.rows[0].cells[1].horizontal_merge);
39 assert!(table.rows[1].cells[0].vertical_merge);
40 assert!(table.rows[1].cells[1].horizontal_merge);
41 assert!(table.rows[1].cells[1].vertical_merge);
42 let written = table.to_xml().unwrap();
43 assert_eq!(table, CT_Table::from_xml(&written).unwrap());
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use std::collections::HashSet;
50 use std::path::PathBuf;
51 use std::process::Command;
52
53 use quick_xml::Reader;
54 use quick_xml::events::Event;
55
56 use super::geometry::CT_CustomGeometry2D;
57 use super::namespace::{A_NS, A_PREFIX, PIC_NS, PIC_PREFIX};
58 use super::preset_shape_data::preset_shape_definition;
59
60 fn workspace_file(path: &str) -> PathBuf {
61 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
62 .join("../..")
63 .join(path)
64 }
65
66 fn run_generator(arguments: &[&str]) -> std::process::Output {
67 Command::new("python3")
68 .arg(workspace_file("tools/gen-presets/generate.py"))
69 .args(arguments)
70 .current_dir(workspace_file("."))
71 .output()
72 .expect("run the preset shape generator")
73 }
74
75 #[test]
76 fn generator_reproduces_checked_in_table() {
77 let output = run_generator(&["--check"]);
78 assert!(
79 output.status.success(),
80 "generator check failed: {}",
81 String::from_utf8_lossy(&output.stderr)
82 );
83 }
84
85 #[test]
86 fn generated_table_covers_every_corpus_preset() {
87 let corpus = std::env::var_os("RDOCX_PPTX_CORPUS_DIR")
88 .map(PathBuf::from)
89 .unwrap_or_else(|| workspace_file("corpus/pptx"));
90 if !corpus.is_dir() {
91 assert_ne!(
92 std::env::var_os("RDOCX_PPTX_CORPUS_REQUIRED").as_deref(),
93 Some(std::ffi::OsStr::new("1")),
94 "the pinned corpus is required but {} does not exist",
95 corpus.display()
96 );
97 eprintln!("corpus gate skipped because {} is absent", corpus.display());
98 return;
99 }
100 let corpus = corpus.to_str().expect("corpus path is UTF-8");
101 let output = run_generator(&["--check", "--corpus", corpus]);
102 assert!(
103 output.status.success(),
104 "corpus preset check failed: {}",
105 String::from_utf8_lossy(&output.stderr)
106 );
107 }
108
109 #[test]
110 fn source_has_187_direct_definitions() {
111 let source = std::fs::read(workspace_file(
112 "tools/gen-presets/presetShapeDefinitions.xml",
113 ))
114 .expect("read the pinned official preset source");
115 let mut reader = Reader::from_reader(source.as_slice());
116 let mut buffer = Vec::new();
117 let mut depth = 0usize;
118 let mut direct_definitions = 0usize;
119 let mut names = HashSet::new();
120 loop {
121 match reader
122 .read_event_into(&mut buffer)
123 .expect("parse source XML")
124 {
125 Event::Start(element) => {
126 if depth == 1 {
127 direct_definitions += 1;
128 names.insert(element.name().as_ref().to_vec());
129 }
130 depth += 1;
131 }
132 Event::Empty(element) if depth == 1 => {
133 direct_definitions += 1;
134 names.insert(element.name().as_ref().to_vec());
135 }
136 Event::End(_) => depth -= 1,
137 Event::Eof => break,
138 _ => {}
139 }
140 buffer.clear();
141 }
142 assert_eq!(direct_definitions, 187);
143 assert_eq!(names.len(), 186);
144 let source = String::from_utf8(source).expect("source XML is UTF-8");
145 let duplicates: Vec<_> = source
146 .split("<upDownArrow>")
147 .skip(1)
148 .map(|suffix| {
149 suffix
150 .split("</upDownArrow>")
151 .next()
152 .expect("complete upDownArrow definition")
153 })
154 .collect();
155 assert_eq!(duplicates.len(), 2);
156 assert_eq!(duplicates[0].as_bytes(), duplicates[1].as_bytes());
157 }
158
159 #[test]
160 fn generated_lookup_has_known_and_unknown_cases() {
161 let rectangle = preset_shape_definition("rect").expect("known rectangle preset");
162 assert!(CT_CustomGeometry2D::from_xml(rectangle).is_ok());
163 assert!(preset_shape_definition("notAStandardPreset").is_none());
164 }
165
166 #[test]
167 fn drawingml_namespace_uris_match_the_specification() {
168 assert_eq!(
169 A_NS,
170 "http://schemas.openxmlformats.org/drawingml/2006/main"
171 );
172 assert_eq!(A_PREFIX, "a");
173 assert_eq!(
174 PIC_NS,
175 "http://schemas.openxmlformats.org/drawingml/2006/picture"
176 );
177 assert_eq!(PIC_PREFIX, "pic");
178 }
179
180 #[test]
181 fn oxml_drawing_is_an_explicit_publication_candidate() {
182 let manifest = include_str!("../Cargo.toml");
183 assert!(manifest.contains("version = \"0.1.3\""));
184 assert!(manifest.contains("publish = true"));
185 }
186}