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,
124 pub parser_diagnostic: ParserDiagnostic,
126 pub entity_compact_storage: EntityCompactStorage,
128 pub validation_exit: ValidationExit,
130 pub xpath_attr_empty_exit: XpathAttrEmptyExit,
132 pub html_serializer: HtmlSerializer,
134 pub validation_no_dtd_exit: ValidationNoDtdExit,
136 pub global_state_init: GlobalStateInit,
139 pub xsl_transform: XslTransform,
141}
142
143fn parse_version(v: &str) -> (u32, u32, u32) {
145 let mut it = v.trim_start_matches('v').split('.');
146 let major = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
147 let minor = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
148 let patch = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
149 (major, minor, patch)
150}
151
152fn at_least(version: &str, major: u32, minor: u32) -> bool {
153 let (maj, min, _) = parse_version(version);
154 (maj, min) >= (major, minor)
155}
156
157pub fn capabilities_for_libxml2(version: &str) -> Capabilities {
162 Capabilities {
163 xpath_node_set_serialization: {
164 let (maj, min, pat) = parse_version(version);
165 if (maj, min) > (2, 9) || (maj == 2 && min == 9 && pat >= 10) {
166 XPathNodeSetSerialization::NewlineSeparated
167 } else {
168 XPathNodeSetSerialization::Concatenated
169 }
170 },
171 parser_diagnostic: {
172 let (maj, min, pat) = parse_version(version);
173 if (maj, min, pat) >= (2, 9, 10) && (maj, min, pat) < (2, 9, 11) {
174 ParserDiagnostic::Regression
175 } else if (maj, min) >= (2, 12) {
176 ParserDiagnostic::Single
177 } else {
178 ParserDiagnostic::Dual
179 }
180 },
181 entity_compact_storage: if at_least(version, 2, 13) {
182 EntityCompactStorage::Compact
183 } else {
184 EntityCompactStorage::Plain
185 },
186 validation_exit: if at_least(version, 2, 13) {
187 ValidationExit::Reworked
188 } else {
189 ValidationExit::Legacy
190 },
191 xpath_attr_empty_exit: {
192 let (maj, min) = (parse_version(version).0, parse_version(version).1);
193 if (maj, min) < (2, 11) {
194 XpathAttrEmptyExit::Legacy
195 } else if (maj, min) < (2, 12)
196 || (maj == 2 && min == 12 && parse_version(version).2 < 6)
197 {
198 XpathAttrEmptyExit::NoError
199 } else {
200 XpathAttrEmptyExit::Error11
201 }
202 },
203 html_serializer: if at_least(version, 2, 15) {
204 HtmlSerializer::SingleLine
205 } else {
206 HtmlSerializer::Formatted
207 },
208 validation_no_dtd_exit: if at_least(version, 2, 15) {
209 ValidationNoDtdExit::Ok0
210 } else {
211 ValidationNoDtdExit::Error3
212 },
213 global_state_init: if at_least(version, 2, 12) {
214 GlobalStateInit::Lazy
215 } else {
216 GlobalStateInit::Eager
217 },
218 xsl_transform: XslTransform::Stable,
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct CompatibilityProfile {
229 pub libxml2_version: &'static str,
231 pub libxslt_version: &'static str,
233 pub capabilities: Capabilities,
235}
236
237impl CompatibilityProfile {
238 pub fn current() -> CompatibilityProfile {
240 CompatibilityProfile {
241 libxml2_version: "2.15.3",
242 libxslt_version: "1.1.45",
243 capabilities: capabilities_for_libxml2("2.15.3"),
244 }
245 }
246
247 pub fn for_libxml2(version: &str) -> CompatibilityProfile {
251 assert!(
252 parse_version(version) <= parse_version("2.15.3"),
253 "no evidence-backed epoch for libxml2 {version}"
254 );
255 CompatibilityProfile {
256 libxml2_version: "2.15.3",
257 libxslt_version: "1.1.45",
258 capabilities: capabilities_for_libxml2(version),
259 }
260 }
261}
262
263impl fmt::Display for CompatibilityProfile {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(
266 f,
267 "profile(libxml2 {}, libxslt {}, caps={:?})",
268 self.libxml2_version, self.libxslt_version, self.capabilities
269 )
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
280 fn e001_xpath_node_set_boundary() {
281 assert_eq!(
282 capabilities_for_libxml2("2.9.4").xpath_node_set_serialization,
283 XPathNodeSetSerialization::Concatenated
284 );
285 assert_eq!(
286 capabilities_for_libxml2("2.9.10").xpath_node_set_serialization,
287 XPathNodeSetSerialization::NewlineSeparated
288 );
289 assert_eq!(
290 CompatibilityProfile::current()
291 .capabilities
292 .xpath_node_set_serialization,
293 XPathNodeSetSerialization::NewlineSeparated
294 );
295 }
296
297 #[test]
298 fn e002_parser_diagnostic_window() {
299 assert_eq!(
300 capabilities_for_libxml2("2.9.4").parser_diagnostic,
301 ParserDiagnostic::Dual
302 );
303 assert_eq!(
304 capabilities_for_libxml2("2.9.10").parser_diagnostic,
305 ParserDiagnostic::Regression
306 );
307 assert_eq!(
308 capabilities_for_libxml2("2.12.6").parser_diagnostic,
309 ParserDiagnostic::Single
310 );
311 }
312
313 #[test]
314 fn e004_entity_compact_boundary() {
315 assert_eq!(
316 capabilities_for_libxml2("2.12.6").entity_compact_storage,
317 EntityCompactStorage::Plain
318 );
319 assert_eq!(
320 capabilities_for_libxml2("2.13.0").entity_compact_storage,
321 EntityCompactStorage::Compact
322 );
323 }
324
325 #[test]
326 fn e005_validation_exit_boundary() {
327 assert_eq!(
328 capabilities_for_libxml2("2.12.6").validation_exit,
329 ValidationExit::Legacy
330 );
331 assert_eq!(
332 capabilities_for_libxml2("2.13.0").validation_exit,
333 ValidationExit::Reworked
334 );
335 }
336
337 #[test]
338 fn e003_xpath_attr_empty_exit_chain() {
339 assert_eq!(
340 capabilities_for_libxml2("2.9.14").xpath_attr_empty_exit,
341 XpathAttrEmptyExit::Legacy
342 );
343 assert_eq!(
344 capabilities_for_libxml2("2.11.5").xpath_attr_empty_exit,
345 XpathAttrEmptyExit::NoError
346 );
347 assert_eq!(
348 capabilities_for_libxml2("2.12.6").xpath_attr_empty_exit,
349 XpathAttrEmptyExit::Error11
350 );
351 }
352
353 #[test]
354 fn e006_e007_boundaries() {
355 assert_eq!(
356 capabilities_for_libxml2("2.14.1").validation_no_dtd_exit,
357 ValidationNoDtdExit::Error3
358 );
359 assert_eq!(
360 capabilities_for_libxml2("2.15.0").validation_no_dtd_exit,
361 ValidationNoDtdExit::Ok0
362 );
363 assert_eq!(
364 capabilities_for_libxml2("2.14.1").html_serializer,
365 HtmlSerializer::Formatted
366 );
367 assert_eq!(
368 capabilities_for_libxml2("2.15.0").html_serializer,
369 HtmlSerializer::SingleLine
370 );
371 }
372
373 #[test]
374 fn global_state_init_boundary() {
375 assert_eq!(
376 capabilities_for_libxml2("2.11.5").global_state_init,
377 GlobalStateInit::Eager
378 );
379 assert_eq!(
380 capabilities_for_libxml2("2.12.0").global_state_init,
381 GlobalStateInit::Lazy
382 );
383 }
384
385 #[test]
386 fn xslt_transform_stable_epoch() {
387 assert_eq!(
389 capabilities_for_libxml2("2.15.3").xsl_transform,
390 XslTransform::Stable
391 );
392 }
393
394 #[test]
395 fn current_profile_resolves_current_epochs() {
396 let p = CompatibilityProfile::current();
397 assert_eq!(p.libxml2_version, "2.15.3");
398 assert_eq!(p.capabilities.parser_diagnostic, ParserDiagnostic::Single);
399 assert_eq!(p.capabilities.html_serializer, HtmlSerializer::SingleLine);
400 assert_eq!(
401 p.capabilities.validation_no_dtd_exit,
402 ValidationNoDtdExit::Ok0
403 );
404 }
405}