xsd_schema/xpath/
atomize.rs1use super::error::XPathError;
16use super::functions::XPathValue;
17use super::iterator::XmlItem;
18use super::{DomNavigator, DomNodeType};
19use crate::navigator::TypedValue;
20use crate::types::value::{XmlAtomicValue, XmlValue, XmlValueKind};
21use crate::types::XmlTypeCode;
22
23pub fn atomize_node<N: DomNavigator>(nav: &N) -> Result<Option<XmlValue>, XPathError> {
31 match nav.typed_value() {
32 TypedValue::Value(v) => Ok(Some(v)),
33 TypedValue::Untyped => {
34 let v = match nav.node_type() {
35 DomNodeType::Comment | DomNodeType::ProcessingInstruction => {
36 XmlValue::string(nav.value())
37 }
38 _ => XmlValue::untyped(nav.value()),
39 };
40 Ok(Some(v))
41 }
42 TypedValue::Nilled => Ok(None),
43 TypedValue::Absent => Err(XPathError::no_typed_value()),
44 }
45}
46
47pub fn atomize(value: &XmlValue) -> Result<XmlValue, XPathError> {
62 match &value.value {
63 XmlValueKind::Atomic(_) | XmlValueKind::UntypedAtomic(_) => Ok(value.clone()),
65
66 XmlValueKind::Union(inner) => atomize(inner),
68
69 XmlValueKind::List { items, .. } if items.len() > 1 => {
71 Err(XPathError::more_than_one_item())
72 }
73
74 XmlValueKind::List { items, item_type } if items.len() == 1 => Ok(XmlValue::new(
76 *item_type,
77 XmlValueKind::Atomic(items[0].clone()),
78 )),
79
80 XmlValueKind::List { .. } => Err(XPathError::type_mismatch("item()", "empty-sequence()")),
82 }
83}
84
85pub fn atomize_opt(value: Option<&XmlValue>) -> Result<Option<XmlValue>, XPathError> {
99 match value {
100 None => Ok(None),
101 Some(v) => atomize(v).map(Some),
102 }
103}
104
105pub fn atomize_required(value: Option<&XmlValue>) -> Result<XmlValue, XPathError> {
118 match value {
119 None => Err(XPathError::type_mismatch("item()", "empty-sequence()")),
120 Some(v) => atomize(v),
121 }
122}
123
124pub fn string_value(value: &XmlValue) -> String {
138 value.to_string_value()
139}
140
141pub fn string_value_opt(value: Option<&XmlValue>) -> String {
153 match value {
154 None => String::new(),
155 Some(v) => string_value(v),
156 }
157}
158
159pub fn to_number(value: &XmlValue) -> f64 {
174 match &value.value {
175 XmlValueKind::Atomic(atom) => atomic_to_number(atom),
176 XmlValueKind::UntypedAtomic(s) => s.trim().parse().unwrap_or(f64::NAN),
177 XmlValueKind::Union(inner) => to_number(inner),
178 XmlValueKind::List { .. } => f64::NAN,
179 }
180}
181
182fn atomic_to_number(atom: &XmlAtomicValue) -> f64 {
184 match atom {
185 XmlAtomicValue::Double(d) => *d,
186 XmlAtomicValue::Float(f) => *f as f64,
187 XmlAtomicValue::Decimal(d) => d.to_string().parse().unwrap_or(f64::NAN),
188 XmlAtomicValue::Integer(i) => i.to_string().parse().unwrap_or(f64::NAN),
189 XmlAtomicValue::Boolean(b) => {
190 if *b {
191 1.0
192 } else {
193 0.0
194 }
195 }
196 XmlAtomicValue::String(s) => s.trim().parse().unwrap_or(f64::NAN),
197 _ => f64::NAN,
198 }
199}
200
201pub fn to_number_opt(value: Option<&XmlValue>) -> f64 {
205 match value {
206 None => f64::NAN,
207 Some(v) => to_number(v),
208 }
209}
210
211pub fn is_empty_list(value: &XmlValue) -> bool {
216 matches!(&value.value, XmlValueKind::List { items, .. } if items.is_empty())
217}
218
219pub fn effective_type_code(value: &XmlValue) -> XmlTypeCode {
223 match &value.value {
224 XmlValueKind::Union(inner) => effective_type_code(inner),
225 _ => value.type_code,
226 }
227}
228
229pub fn is_node_type(type_code: XmlTypeCode) -> bool {
233 matches!(
234 type_code,
235 XmlTypeCode::Node
236 | XmlTypeCode::Document
237 | XmlTypeCode::Element
238 | XmlTypeCode::Attribute
239 | XmlTypeCode::Namespace
240 | XmlTypeCode::ProcessingInstruction
241 | XmlTypeCode::Comment
242 | XmlTypeCode::Text
243 )
244}
245
246pub fn is_node(value: &XmlValue) -> bool {
248 is_node_type(effective_type_code(value))
249}
250
251pub fn unwrap_union(value: &XmlValue) -> &XmlValue {
255 match &value.value {
256 XmlValueKind::Union(inner) => unwrap_union(inner),
257 _ => value,
258 }
259}
260
261pub(crate) fn first_node_string_value<N: DomNavigator>(value: &XPathValue<N>) -> String {
267 match value {
268 XPathValue::Empty => String::new(),
269 XPathValue::Item(XmlItem::Node(n)) => n.value(),
270 XPathValue::Item(XmlItem::Atomic(v)) => v.to_string_value(),
271 XPathValue::Sequence(items) => {
272 let mut first_node: Option<&N> = None;
274 for item in items {
275 if let XmlItem::Node(n) = item {
276 if let Some(current) = first_node {
277 if crate::xpath::node_ops::compare_document_order(n, current)
278 == std::cmp::Ordering::Less
279 {
280 first_node = Some(n);
281 }
282 } else {
283 first_node = Some(n);
284 }
285 }
286 }
287 if let Some(n) = first_node {
288 return n.value();
289 }
290 if let Some(XmlItem::Atomic(v)) = items.first() {
292 v.to_string_value()
293 } else {
294 String::new()
295 }
296 }
297 }
298}
299
300pub(crate) fn to_string_10<N: DomNavigator>(value: &XPathValue<N>) -> String {
305 first_node_string_value(value)
306}
307
308pub(crate) fn to_number_10<N: DomNavigator>(value: &XPathValue<N>) -> f64 {
312 match value {
313 XPathValue::Empty => f64::NAN,
314 XPathValue::Item(XmlItem::Atomic(v)) => to_number(v),
315 _ => to_string_10(value).trim().parse().unwrap_or(f64::NAN),
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use num_bigint::BigInt;
323 use rust_decimal::Decimal;
324
325 #[test]
326 fn test_atomize_atomic() {
327 let value = XmlValue::string("hello");
328 let result = atomize(&value).unwrap();
329 assert_eq!(result.to_string_value(), "hello");
330 }
331
332 #[test]
333 fn test_atomize_untyped() {
334 let value = XmlValue::untyped("test");
335 let result = atomize(&value).unwrap();
336 assert_eq!(result.to_string_value(), "test");
337 }
338
339 #[test]
340 fn test_atomize_opt_none() {
341 let result = atomize_opt(None).unwrap();
342 assert!(result.is_none());
343 }
344
345 #[test]
346 fn test_atomize_opt_some() {
347 let value = XmlValue::integer(BigInt::from(42));
348 let result = atomize_opt(Some(&value)).unwrap();
349 assert!(result.is_some());
350 }
351
352 #[test]
353 fn test_atomize_required_none() {
354 let result = atomize_required(None);
355 assert!(result.is_err());
356 if let Err(XPathError::XPTY0004 { .. }) = result {
357 } else {
359 panic!("Expected XPTY0004 error");
360 }
361 }
362
363 #[test]
364 fn test_string_value() {
365 assert_eq!(string_value(&XmlValue::string("hello")), "hello");
366 assert_eq!(string_value(&XmlValue::boolean(true)), "true");
367 assert_eq!(string_value(&XmlValue::integer(BigInt::from(123))), "123");
368 }
369
370 #[test]
371 fn test_string_value_opt_none() {
372 assert_eq!(string_value_opt(None), "");
373 }
374
375 #[test]
376 fn test_to_number() {
377 assert_eq!(to_number(&XmlValue::double(2.5)), 2.5);
378 assert_eq!(to_number(&XmlValue::float(2.5)), 2.5);
379 assert_eq!(to_number(&XmlValue::integer(BigInt::from(42))), 42.0);
380 assert_eq!(to_number(&XmlValue::decimal(Decimal::new(125, 2))), 1.25);
381 assert_eq!(to_number(&XmlValue::string("2.5")), 2.5);
382 assert!(to_number(&XmlValue::string("not a number")).is_nan());
383 }
384
385 #[test]
386 fn test_to_number_opt_none() {
387 assert!(to_number_opt(None).is_nan());
388 }
389
390 #[test]
391 fn test_to_number_untyped() {
392 assert_eq!(to_number(&XmlValue::untyped("42.5")), 42.5);
393 assert_eq!(to_number(&XmlValue::untyped(" 2.5 ")), 2.5); }
395
396 #[test]
397 fn test_effective_type_code() {
398 let value = XmlValue::string("test");
399 assert_eq!(effective_type_code(&value), XmlTypeCode::String);
400
401 let value = XmlValue::integer(BigInt::from(1));
402 assert_eq!(effective_type_code(&value), XmlTypeCode::Integer);
403 }
404
405 #[test]
406 fn test_is_node_type() {
407 assert!(is_node_type(XmlTypeCode::Element));
408 assert!(is_node_type(XmlTypeCode::Attribute));
409 assert!(is_node_type(XmlTypeCode::Document));
410 assert!(!is_node_type(XmlTypeCode::String));
411 assert!(!is_node_type(XmlTypeCode::Integer));
412 }
413
414 #[test]
415 fn test_is_node() {
416 assert!(!is_node(&XmlValue::string("test")));
418 assert!(!is_node(&XmlValue::integer(BigInt::from(1))));
419
420 let node_value = XmlValue::new(
423 XmlTypeCode::Element,
424 XmlValueKind::UntypedAtomic("element content".to_string()),
425 );
426 assert!(is_node(&node_value));
427 }
428
429 use crate::xpath::RoXmlNavigator;
432
433 #[test]
434 fn test_first_node_string_value_empty() {
435 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
436 assert_eq!(first_node_string_value(&value), "");
437 }
438
439 #[test]
440 fn test_first_node_string_value_single_atomic() {
441 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("hello");
442 assert_eq!(first_node_string_value(&value), "hello");
443 }
444
445 #[test]
446 fn test_first_node_string_value_single_node() {
447 let doc = roxmltree::Document::parse("<root>text content</root>").unwrap();
448 let mut nav = RoXmlNavigator::new(&doc);
449 nav.move_to_first_child(); let value = XPathValue::from_node(nav);
451 assert_eq!(first_node_string_value(&value), "text content");
452 }
453
454 #[test]
455 fn test_first_node_string_value_multi_node_sequence() {
456 let doc = roxmltree::Document::parse("<r><a>first</a><b>second</b></r>").unwrap();
457 let mut nav_a = RoXmlNavigator::new(&doc);
458 nav_a.move_to_first_child(); nav_a.move_to_first_child(); let mut nav_b = nav_a.clone();
461 nav_b.move_to_next_sibling(); let value = XPathValue::from_sequence(vec![XmlItem::Node(nav_a), XmlItem::Node(nav_b)]);
463 assert_eq!(first_node_string_value(&value), "first");
465 }
466
467 #[test]
468 fn test_to_string_10_delegates() {
469 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::string("abc");
470 assert_eq!(to_string_10(&value), "abc");
471 }
472
473 #[test]
474 fn test_to_number_10_empty() {
475 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::empty();
476 assert!(to_number_10(&value).is_nan());
477 }
478
479 #[test]
480 fn test_to_number_10_atomic() {
481 let value: XPathValue<RoXmlNavigator<'static>> = XPathValue::double(2.75);
482 assert_eq!(to_number_10(&value), 2.75);
483 }
484
485 #[test]
486 fn test_to_number_10_node_numeric() {
487 let doc = roxmltree::Document::parse("<n>42.5</n>").unwrap();
488 let mut nav = RoXmlNavigator::new(&doc);
489 nav.move_to_first_child(); let value = XPathValue::from_node(nav);
491 assert_eq!(to_number_10(&value), 42.5);
492 }
493
494 #[test]
495 fn test_to_number_10_node_non_numeric() {
496 let doc = roxmltree::Document::parse("<n>not a number</n>").unwrap();
497 let mut nav = RoXmlNavigator::new(&doc);
498 nav.move_to_first_child(); let value = XPathValue::from_node(nav);
500 assert!(to_number_10(&value).is_nan());
501 }
502}