libxml_rs/compatibility/profiles/
mod.rs1use core::fmt;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum XPathNodeSetSerialization {
36 Concatenated,
38 NewlineSeparated,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ParserDiagnostic {
46 Dual,
49 Regression,
51 Single,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum EntityCompactStorage {
59 Plain,
61 Compact,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum ValidationExit {
68 Legacy,
70 Reworked,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum XpathAttrEmptyExit {
77 Legacy,
79 NoError,
81 Error11,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum HtmlSerializer {
88 Formatted,
90 SingleLine,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum ValidationNoDtdExit {
97 Error3,
99 Ok0,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum GlobalStateInit {
106 Eager,
108 Lazy,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum XslTransform {
115 Stable,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct Capabilities {
122 pub xpath_node_set_serialization: XPathNodeSetSerialization,
123 pub parser_diagnostic: ParserDiagnostic,
124 pub entity_compact_storage: EntityCompactStorage,
125 pub validation_exit: ValidationExit,
126 pub xpath_attr_empty_exit: XpathAttrEmptyExit,
127 pub html_serializer: HtmlSerializer,
128 pub validation_no_dtd_exit: ValidationNoDtdExit,
129 pub global_state_init: GlobalStateInit,
130 pub xsl_transform: XslTransform,
131}
132
133fn parse_version(v: &str) -> (u32, u32, u32) {
135 let mut it = v.trim_start_matches('v').split('.');
136 let major = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
137 let minor = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
138 let patch = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
139 (major, minor, patch)
140}
141
142fn at_least(version: &str, major: u32, minor: u32) -> bool {
143 let (maj, min, _) = parse_version(version);
144 (maj, min) >= (major, minor)
145}
146
147pub fn capabilities_for_libxml2(version: &str) -> Capabilities {
152 Capabilities {
153 xpath_node_set_serialization: {
154 let (maj, min, pat) = parse_version(version);
155 if (maj, min) > (2, 9) || (maj == 2 && min == 9 && pat >= 10) {
156 XPathNodeSetSerialization::NewlineSeparated
157 } else {
158 XPathNodeSetSerialization::Concatenated
159 }
160 },
161 parser_diagnostic: {
162 let (maj, min, pat) = parse_version(version);
163 if (maj, min, pat) >= (2, 9, 10) && (maj, min, pat) < (2, 9, 11) {
164 ParserDiagnostic::Regression
165 } else if (maj, min) >= (2, 12) {
166 ParserDiagnostic::Single
167 } else {
168 ParserDiagnostic::Dual
169 }
170 },
171 entity_compact_storage: if at_least(version, 2, 13) {
172 EntityCompactStorage::Compact
173 } else {
174 EntityCompactStorage::Plain
175 },
176 validation_exit: if at_least(version, 2, 13) {
177 ValidationExit::Reworked
178 } else {
179 ValidationExit::Legacy
180 },
181 xpath_attr_empty_exit: {
182 let (maj, min) = (parse_version(version).0, parse_version(version).1);
183 if (maj, min) < (2, 11) {
184 XpathAttrEmptyExit::Legacy
185 } else if (maj, min) < (2, 12)
186 || (maj == 2 && min == 12 && parse_version(version).2 < 6)
187 {
188 XpathAttrEmptyExit::NoError
189 } else {
190 XpathAttrEmptyExit::Error11
191 }
192 },
193 html_serializer: if at_least(version, 2, 15) {
194 HtmlSerializer::SingleLine
195 } else {
196 HtmlSerializer::Formatted
197 },
198 validation_no_dtd_exit: if at_least(version, 2, 15) {
199 ValidationNoDtdExit::Ok0
200 } else {
201 ValidationNoDtdExit::Error3
202 },
203 global_state_init: if at_least(version, 2, 12) {
204 GlobalStateInit::Lazy
205 } else {
206 GlobalStateInit::Eager
207 },
208 xsl_transform: XslTransform::Stable,
209 }
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub struct CompatibilityProfile {
219 pub libxml2_version: &'static str,
221 pub libxslt_version: &'static str,
223 pub capabilities: Capabilities,
225}
226
227impl CompatibilityProfile {
228 pub fn current() -> CompatibilityProfile {
230 CompatibilityProfile {
231 libxml2_version: "2.15.3",
232 libxslt_version: "1.1.45",
233 capabilities: capabilities_for_libxml2("2.15.3"),
234 }
235 }
236
237 pub fn for_libxml2(version: &str) -> CompatibilityProfile {
241 assert!(
242 parse_version(version) <= parse_version("2.15.3"),
243 "no evidence-backed epoch for libxml2 {version}"
244 );
245 CompatibilityProfile {
246 libxml2_version: "2.15.3",
247 libxslt_version: "1.1.45",
248 capabilities: capabilities_for_libxml2(version),
249 }
250 }
251}
252
253impl fmt::Display for CompatibilityProfile {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 write!(
256 f,
257 "profile(libxml2 {}, libxslt {}, caps={:?})",
258 self.libxml2_version, self.libxslt_version, self.capabilities
259 )
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
270 fn e001_xpath_node_set_boundary() {
271 assert_eq!(
272 capabilities_for_libxml2("2.9.4").xpath_node_set_serialization,
273 XPathNodeSetSerialization::Concatenated
274 );
275 assert_eq!(
276 capabilities_for_libxml2("2.9.10").xpath_node_set_serialization,
277 XPathNodeSetSerialization::NewlineSeparated
278 );
279 assert_eq!(
280 CompatibilityProfile::current()
281 .capabilities
282 .xpath_node_set_serialization,
283 XPathNodeSetSerialization::NewlineSeparated
284 );
285 }
286
287 #[test]
288 fn e002_parser_diagnostic_window() {
289 assert_eq!(
290 capabilities_for_libxml2("2.9.4").parser_diagnostic,
291 ParserDiagnostic::Dual
292 );
293 assert_eq!(
294 capabilities_for_libxml2("2.9.10").parser_diagnostic,
295 ParserDiagnostic::Regression
296 );
297 assert_eq!(
298 capabilities_for_libxml2("2.12.6").parser_diagnostic,
299 ParserDiagnostic::Single
300 );
301 }
302
303 #[test]
304 fn e004_entity_compact_boundary() {
305 assert_eq!(
306 capabilities_for_libxml2("2.12.6").entity_compact_storage,
307 EntityCompactStorage::Plain
308 );
309 assert_eq!(
310 capabilities_for_libxml2("2.13.0").entity_compact_storage,
311 EntityCompactStorage::Compact
312 );
313 }
314
315 #[test]
316 fn e005_validation_exit_boundary() {
317 assert_eq!(
318 capabilities_for_libxml2("2.12.6").validation_exit,
319 ValidationExit::Legacy
320 );
321 assert_eq!(
322 capabilities_for_libxml2("2.13.0").validation_exit,
323 ValidationExit::Reworked
324 );
325 }
326
327 #[test]
328 fn e003_xpath_attr_empty_exit_chain() {
329 assert_eq!(
330 capabilities_for_libxml2("2.9.14").xpath_attr_empty_exit,
331 XpathAttrEmptyExit::Legacy
332 );
333 assert_eq!(
334 capabilities_for_libxml2("2.11.5").xpath_attr_empty_exit,
335 XpathAttrEmptyExit::NoError
336 );
337 assert_eq!(
338 capabilities_for_libxml2("2.12.6").xpath_attr_empty_exit,
339 XpathAttrEmptyExit::Error11
340 );
341 }
342
343 #[test]
344 fn e006_e007_boundaries() {
345 assert_eq!(
346 capabilities_for_libxml2("2.14.1").validation_no_dtd_exit,
347 ValidationNoDtdExit::Error3
348 );
349 assert_eq!(
350 capabilities_for_libxml2("2.15.0").validation_no_dtd_exit,
351 ValidationNoDtdExit::Ok0
352 );
353 assert_eq!(
354 capabilities_for_libxml2("2.14.1").html_serializer,
355 HtmlSerializer::Formatted
356 );
357 assert_eq!(
358 capabilities_for_libxml2("2.15.0").html_serializer,
359 HtmlSerializer::SingleLine
360 );
361 }
362
363 #[test]
364 fn global_state_init_boundary() {
365 assert_eq!(
366 capabilities_for_libxml2("2.11.5").global_state_init,
367 GlobalStateInit::Eager
368 );
369 assert_eq!(
370 capabilities_for_libxml2("2.12.0").global_state_init,
371 GlobalStateInit::Lazy
372 );
373 }
374
375 #[test]
376 fn xslt_transform_stable_epoch() {
377 assert_eq!(
379 capabilities_for_libxml2("2.15.3").xsl_transform,
380 XslTransform::Stable
381 );
382 }
383
384 #[test]
385 fn current_profile_resolves_current_epochs() {
386 let p = CompatibilityProfile::current();
387 assert_eq!(p.libxml2_version, "2.15.3");
388 assert_eq!(p.capabilities.parser_diagnostic, ParserDiagnostic::Single);
389 assert_eq!(p.capabilities.html_serializer, HtmlSerializer::SingleLine);
390 assert_eq!(
391 p.capabilities.validation_no_dtd_exit,
392 ValidationNoDtdExit::Ok0
393 );
394 }
395}