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
50 #[test]
58 fn no_shared_crate_depends_on_a_format_crate() {
59 let manifests: [(&str, &str); 9] = [
60 ("oxml-core", include_str!("../../oxml-core/Cargo.toml")),
61 ("oxml-opc", include_str!("../../oxml-opc/Cargo.toml")),
62 ("oxml-media", include_str!("../../oxml-media/Cargo.toml")),
63 ("oxml-layout", include_str!("../../oxml-layout/Cargo.toml")),
64 ("oxml-drawing", include_str!("../Cargo.toml")),
65 ("oxml-pdf", include_str!("../../oxml-pdf/Cargo.toml")),
66 ("oxml-sml", include_str!("../../oxml-sml/Cargo.toml")),
67 (
68 "oxml-cli-support",
69 include_str!("../../oxml-cli-support/Cargo.toml"),
70 ),
71 (
72 "oxml-py-support",
73 include_str!("../../oxml-py-support/Cargo.toml"),
74 ),
75 ];
76
77 for (crate_name, manifest) in manifests {
78 for line in manifest.lines() {
79 let line = line.trim();
80 if line.starts_with('#') || line.is_empty() {
81 continue;
82 }
83 let names_format_crate = line.starts_with("rdocx")
86 || line.starts_with("rpptx")
87 || line.starts_with("\"rdocx")
88 || line.starts_with("\"rpptx");
89 assert!(
90 !names_format_crate,
91 "{crate_name} depends on a format crate: {line}"
92 );
93 }
94 }
95 }
96 use std::collections::HashSet;
97 use std::path::PathBuf;
98 use std::process::Command;
99
100 use quick_xml::Reader;
101 use quick_xml::events::Event;
102
103 use super::geometry::CT_CustomGeometry2D;
104 use super::namespace::{A_NS, A_PREFIX, PIC_NS, PIC_PREFIX};
105 use super::preset_shape_data::preset_shape_definition;
106
107 fn workspace_file(path: &str) -> PathBuf {
108 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
109 .join("../..")
110 .join(path)
111 }
112
113 fn run_generator(arguments: &[&str]) -> std::process::Output {
114 Command::new("python3")
115 .arg(workspace_file("tools/gen-presets/generate.py"))
116 .args(arguments)
117 .current_dir(workspace_file("."))
118 .output()
119 .expect("run the preset shape generator")
120 }
121
122 #[test]
123 fn generator_reproduces_checked_in_table() {
124 let output = run_generator(&["--check"]);
125 assert!(
126 output.status.success(),
127 "generator check failed: {}",
128 String::from_utf8_lossy(&output.stderr)
129 );
130 }
131
132 #[test]
133 fn generated_table_covers_every_corpus_preset() {
134 let corpus = std::env::var_os("RDOCX_PPTX_CORPUS_DIR")
135 .map(PathBuf::from)
136 .unwrap_or_else(|| workspace_file("corpus/pptx"));
137 if !corpus.is_dir() {
138 assert_ne!(
139 std::env::var_os("RDOCX_PPTX_CORPUS_REQUIRED").as_deref(),
140 Some(std::ffi::OsStr::new("1")),
141 "the pinned corpus is required but {} does not exist",
142 corpus.display()
143 );
144 eprintln!("corpus gate skipped because {} is absent", corpus.display());
145 return;
146 }
147 let corpus = corpus.to_str().expect("corpus path is UTF-8");
148 let output = run_generator(&["--check", "--corpus", corpus]);
149 assert!(
150 output.status.success(),
151 "corpus preset check failed: {}",
152 String::from_utf8_lossy(&output.stderr)
153 );
154 }
155
156 #[test]
157 fn source_has_187_direct_definitions() {
158 let source = std::fs::read(workspace_file(
159 "tools/gen-presets/presetShapeDefinitions.xml",
160 ))
161 .expect("read the pinned official preset source");
162 let mut reader = Reader::from_reader(source.as_slice());
163 let mut buffer = Vec::new();
164 let mut depth = 0usize;
165 let mut direct_definitions = 0usize;
166 let mut names = HashSet::new();
167 loop {
168 match reader
169 .read_event_into(&mut buffer)
170 .expect("parse source XML")
171 {
172 Event::Start(element) => {
173 if depth == 1 {
174 direct_definitions += 1;
175 names.insert(element.name().as_ref().to_vec());
176 }
177 depth += 1;
178 }
179 Event::Empty(element) if depth == 1 => {
180 direct_definitions += 1;
181 names.insert(element.name().as_ref().to_vec());
182 }
183 Event::End(_) => depth -= 1,
184 Event::Eof => break,
185 _ => {}
186 }
187 buffer.clear();
188 }
189 assert_eq!(direct_definitions, 187);
190 assert_eq!(names.len(), 186);
191 let source = String::from_utf8(source).expect("source XML is UTF-8");
192 let duplicates: Vec<_> = source
193 .split("<upDownArrow>")
194 .skip(1)
195 .map(|suffix| {
196 suffix
197 .split("</upDownArrow>")
198 .next()
199 .expect("complete upDownArrow definition")
200 })
201 .collect();
202 assert_eq!(duplicates.len(), 2);
203 assert_eq!(duplicates[0].as_bytes(), duplicates[1].as_bytes());
204 }
205
206 #[test]
207 fn generated_lookup_has_known_and_unknown_cases() {
208 let rectangle = preset_shape_definition("rect").expect("known rectangle preset");
209 assert!(CT_CustomGeometry2D::from_xml(rectangle).is_ok());
210 assert!(preset_shape_definition("notAStandardPreset").is_none());
211 }
212
213 #[test]
214 fn drawingml_namespace_uris_match_the_specification() {
215 assert_eq!(
216 A_NS,
217 "http://schemas.openxmlformats.org/drawingml/2006/main"
218 );
219 assert_eq!(A_PREFIX, "a");
220 assert_eq!(
221 PIC_NS,
222 "http://schemas.openxmlformats.org/drawingml/2006/picture"
223 );
224 assert_eq!(PIC_PREFIX, "pic");
225 }
226
227 #[test]
228 fn oxml_drawing_is_an_explicit_publication_candidate() {
229 let manifest = include_str!("../Cargo.toml");
230 assert!(manifest.contains("version = \"0.3.0\""));
231 assert!(manifest.contains("publish = true"));
232 }
233}