Skip to main content

xsd_schema/xpath/
boolean.rs

1//! Boolean operations for XPath evaluation.
2//!
3//! This module implements the XPath 2.0 effective boolean value (EBV) rules
4//! as defined in the XPath 2.0 specification section 2.4.3.
5//!
6//! ## Effective Boolean Value Rules
7//!
8//! The effective boolean value of a value is determined as follows:
9//!
10//! - If the value is an empty sequence, EBV is `false`
11//! - If the value is a single node, EBV is `true`
12//! - If the value is a singleton xs:boolean, EBV is the value
13//! - If the value is a singleton xs:string/xs:untypedAtomic/xs:anyURI, EBV is `!value.is_empty()`
14//! - If the value is a singleton numeric type, EBV is `value != 0 && !value.is_nan()`
15//! - For other atomic types or sequences of length > 1, a type error is raised
16
17use num_bigint::BigInt;
18
19use super::error::XPathError;
20use crate::types::{
21    value::{XmlAtomicValue, XmlValue, XmlValueKind},
22    XmlTypeCode,
23};
24
25/// Compute the effective boolean value of an atomic XmlValue.
26///
27/// This implements the core EBV logic for atomic values. For sequences,
28/// use `effective_boolean_value_sequence` or the iterator-based version.
29///
30/// # Arguments
31///
32/// * `value` - The atomic value to evaluate
33///
34/// # Returns
35///
36/// * `Ok(bool)` - The effective boolean value
37/// * `Err(XPathError)` - FORG0006 if the type doesn't support EBV
38///
39/// # Examples
40///
41/// ```
42/// use xsd_schema::xpath::boolean::effective_boolean_value;
43/// use xsd_schema::types::XmlValue;
44///
45/// assert_eq!(effective_boolean_value(&XmlValue::boolean(true)).unwrap(), true);
46/// assert_eq!(effective_boolean_value(&XmlValue::string("")).unwrap(), false);
47/// assert_eq!(effective_boolean_value(&XmlValue::string("hello")).unwrap(), true);
48/// ```
49pub fn effective_boolean_value(value: &XmlValue) -> Result<bool, XPathError> {
50    match &value.value {
51        // Boolean: use the value directly
52        XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => Ok(*b),
53
54        // String types: non-empty is true
55        XmlValueKind::Atomic(XmlAtomicValue::String(s)) => Ok(!s.is_empty()),
56        XmlValueKind::UntypedAtomic(s) => Ok(!s.is_empty()),
57        XmlValueKind::Atomic(XmlAtomicValue::AnyUri(s)) => Ok(!s.is_empty()),
58
59        // Float: non-zero and non-NaN is true
60        XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Ok(!f.is_nan() && *f != 0.0),
61
62        // Double: non-zero and non-NaN is true
63        XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => Ok(!d.is_nan() && *d != 0.0),
64
65        // Decimal: non-zero is true
66        XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => Ok(!d.is_zero()),
67
68        // Integer: non-zero is true
69        XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => Ok(*i != BigInt::from(0)),
70
71        // Union: unwrap and evaluate
72        XmlValueKind::Union(inner) => effective_boolean_value(inner),
73
74        // List values: error (multiple items)
75        XmlValueKind::List { .. } => Err(XPathError::invalid_argument_type(
76            "fn:boolean",
77            format_type_for_error(value.type_code),
78        )),
79
80        // Other atomic types: error
81        _ => Err(XPathError::invalid_argument_type(
82            "fn:boolean",
83            format_type_for_error(value.type_code),
84        )),
85    }
86}
87
88/// Compute the effective boolean value with support for optional values.
89///
90/// This handles the case where a value may be absent (empty sequence).
91///
92/// # Arguments
93///
94/// * `value` - Optional value to evaluate
95///
96/// # Returns
97///
98/// * `Ok(false)` if value is None (empty sequence)
99/// * `Ok(bool)` the EBV of the value
100/// * `Err(XPathError)` if the type doesn't support EBV
101pub fn effective_boolean_value_opt(value: Option<&XmlValue>) -> Result<bool, XPathError> {
102    match value {
103        None => Ok(false), // Empty sequence is false
104        Some(v) => effective_boolean_value(v),
105    }
106}
107
108/// Logical NOT operation on an XPath value.
109///
110/// Returns the negation of the effective boolean value.
111///
112/// # Arguments
113///
114/// * `value` - The value to negate
115///
116/// # Returns
117///
118/// * `Ok(bool)` - The negated boolean value
119/// * `Err(XPathError)` - If EBV cannot be computed
120pub fn not(value: &XmlValue) -> Result<bool, XPathError> {
121    effective_boolean_value(value).map(|b| !b)
122}
123
124/// Logical NOT with optional value support.
125pub fn not_opt(value: Option<&XmlValue>) -> Result<bool, XPathError> {
126    effective_boolean_value_opt(value).map(|b| !b)
127}
128
129/// Check if a type code represents a numeric type.
130///
131/// Numeric types support EBV via the "non-zero and non-NaN" rule.
132pub fn is_numeric_type(type_code: XmlTypeCode) -> bool {
133    matches!(
134        type_code,
135        XmlTypeCode::Decimal
136            | XmlTypeCode::Float
137            | XmlTypeCode::Double
138            | XmlTypeCode::Integer
139            | XmlTypeCode::NonPositiveInteger
140            | XmlTypeCode::NegativeInteger
141            | XmlTypeCode::Long
142            | XmlTypeCode::Int
143            | XmlTypeCode::Short
144            | XmlTypeCode::Byte
145            | XmlTypeCode::NonNegativeInteger
146            | XmlTypeCode::UnsignedLong
147            | XmlTypeCode::UnsignedInt
148            | XmlTypeCode::UnsignedShort
149            | XmlTypeCode::UnsignedByte
150            | XmlTypeCode::PositiveInteger
151    )
152}
153
154/// Check if a type code represents a string-like type.
155///
156/// String-like types support EBV via the "non-empty" rule.
157pub fn is_string_like_type(type_code: XmlTypeCode) -> bool {
158    matches!(
159        type_code,
160        XmlTypeCode::String
161            | XmlTypeCode::NormalizedString
162            | XmlTypeCode::Token
163            | XmlTypeCode::Language
164            | XmlTypeCode::NmToken
165            | XmlTypeCode::Name
166            | XmlTypeCode::NCName
167            | XmlTypeCode::Id
168            | XmlTypeCode::IdRef
169            | XmlTypeCode::Entity
170            | XmlTypeCode::UntypedAtomic
171            | XmlTypeCode::AnyUri
172    )
173}
174
175/// Check if a type code supports effective boolean value.
176pub fn supports_ebv(type_code: XmlTypeCode) -> bool {
177    type_code == XmlTypeCode::Boolean
178        || is_numeric_type(type_code)
179        || is_string_like_type(type_code)
180}
181
182/// Format a type code for error messages.
183fn format_type_for_error(type_code: XmlTypeCode) -> String {
184    match type_code {
185        XmlTypeCode::None => "none".to_string(),
186        XmlTypeCode::Item => "item()".to_string(),
187        XmlTypeCode::Node => "node()".to_string(),
188        XmlTypeCode::Document => "document-node()".to_string(),
189        XmlTypeCode::Element => "element()".to_string(),
190        XmlTypeCode::Attribute => "attribute()".to_string(),
191        XmlTypeCode::Namespace => "namespace-node()".to_string(),
192        XmlTypeCode::ProcessingInstruction => "processing-instruction()".to_string(),
193        XmlTypeCode::Comment => "comment()".to_string(),
194        XmlTypeCode::Text => "text()".to_string(),
195        XmlTypeCode::AnyType => "xs:anyType".to_string(),
196        XmlTypeCode::AnySimpleType => "xs:anySimpleType".to_string(),
197        XmlTypeCode::AnyAtomicType => "xs:anyAtomicType".to_string(),
198        XmlTypeCode::UntypedAtomic => "xs:untypedAtomic".to_string(),
199        XmlTypeCode::String => "xs:string".to_string(),
200        XmlTypeCode::Boolean => "xs:boolean".to_string(),
201        XmlTypeCode::Decimal => "xs:decimal".to_string(),
202        XmlTypeCode::Float => "xs:float".to_string(),
203        XmlTypeCode::Double => "xs:double".to_string(),
204        XmlTypeCode::Integer => "xs:integer".to_string(),
205        XmlTypeCode::Duration => "xs:duration".to_string(),
206        XmlTypeCode::DateTime => "xs:dateTime".to_string(),
207        XmlTypeCode::Time => "xs:time".to_string(),
208        XmlTypeCode::Date => "xs:date".to_string(),
209        XmlTypeCode::GYearMonth => "xs:gYearMonth".to_string(),
210        XmlTypeCode::GYear => "xs:gYear".to_string(),
211        XmlTypeCode::GMonthDay => "xs:gMonthDay".to_string(),
212        XmlTypeCode::GDay => "xs:gDay".to_string(),
213        XmlTypeCode::GMonth => "xs:gMonth".to_string(),
214        XmlTypeCode::HexBinary => "xs:hexBinary".to_string(),
215        XmlTypeCode::Base64Binary => "xs:base64Binary".to_string(),
216        XmlTypeCode::QName => "xs:QName".to_string(),
217        XmlTypeCode::Notation => "xs:NOTATION".to_string(),
218        XmlTypeCode::YearMonthDuration => "xs:yearMonthDuration".to_string(),
219        XmlTypeCode::DayTimeDuration => "xs:dayTimeDuration".to_string(),
220        _ => format!("type({})", type_code as u8),
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use crate::types::XmlTypeCode;
228    use num_bigint::BigInt;
229    use rust_decimal::Decimal;
230
231    #[test]
232    fn test_boolean_ebv() {
233        assert!(effective_boolean_value(&XmlValue::boolean(true)).unwrap());
234        assert!(!effective_boolean_value(&XmlValue::boolean(false)).unwrap());
235    }
236
237    #[test]
238    fn test_string_ebv() {
239        assert!(!effective_boolean_value(&XmlValue::string("")).unwrap());
240        assert!(effective_boolean_value(&XmlValue::string("hello")).unwrap());
241        assert!(effective_boolean_value(&XmlValue::string(" ")).unwrap()); // Whitespace is non-empty
242    }
243
244    #[test]
245    fn test_untyped_atomic_ebv() {
246        assert!(!effective_boolean_value(&XmlValue::untyped("")).unwrap());
247        assert!(effective_boolean_value(&XmlValue::untyped("value")).unwrap());
248    }
249
250    #[test]
251    fn test_numeric_ebv() {
252        // Integer
253        assert!(!effective_boolean_value(&XmlValue::integer(BigInt::from(0))).unwrap());
254        assert!(effective_boolean_value(&XmlValue::integer(BigInt::from(42))).unwrap());
255        assert!(effective_boolean_value(&XmlValue::integer(BigInt::from(-1))).unwrap());
256
257        // Decimal
258        assert!(!effective_boolean_value(&XmlValue::decimal(Decimal::ZERO)).unwrap());
259        assert!(effective_boolean_value(&XmlValue::decimal(Decimal::new(123, 2))).unwrap());
260
261        // Double
262        assert!(!effective_boolean_value(&XmlValue::double(0.0)).unwrap());
263        assert!(effective_boolean_value(&XmlValue::double(1.5)).unwrap());
264        assert!(!effective_boolean_value(&XmlValue::double(f64::NAN)).unwrap());
265        assert!(effective_boolean_value(&XmlValue::double(f64::INFINITY)).unwrap());
266
267        // Float
268        assert!(!effective_boolean_value(&XmlValue::float(0.0)).unwrap());
269        assert!(effective_boolean_value(&XmlValue::float(1.5)).unwrap());
270        assert!(!effective_boolean_value(&XmlValue::float(f32::NAN)).unwrap());
271    }
272
273    #[test]
274    fn test_empty_sequence_ebv() {
275        assert!(!effective_boolean_value_opt(None).unwrap());
276    }
277
278    #[test]
279    fn test_not() {
280        assert!(!not(&XmlValue::boolean(true)).unwrap());
281        assert!(not(&XmlValue::boolean(false)).unwrap());
282        assert!(not(&XmlValue::string("")).unwrap());
283        assert!(!not(&XmlValue::string("x")).unwrap());
284    }
285
286    #[test]
287    fn test_unsupported_type_error() {
288        // DateTime doesn't support EBV
289        let dt = XmlValue::new(
290            XmlTypeCode::DateTime,
291            XmlValueKind::Atomic(XmlAtomicValue::DateTime(
292                crate::types::value::DateTimeValue {
293                    year: 2024,
294                    month: 1,
295                    day: 15,
296                    hour: 12,
297                    minute: 30,
298                    second: Decimal::ZERO,
299                    timezone: None,
300                },
301            )),
302        );
303        let result = effective_boolean_value(&dt);
304        assert!(result.is_err());
305        if let Err(XPathError::FORG0006Named { function, .. }) = result {
306            assert_eq!(function, "fn:boolean");
307        } else {
308            panic!("Expected FORG0006Named error");
309        }
310    }
311
312    #[test]
313    fn test_is_numeric_type() {
314        assert!(is_numeric_type(XmlTypeCode::Integer));
315        assert!(is_numeric_type(XmlTypeCode::Decimal));
316        assert!(is_numeric_type(XmlTypeCode::Float));
317        assert!(is_numeric_type(XmlTypeCode::Double));
318        assert!(is_numeric_type(XmlTypeCode::Long));
319        assert!(!is_numeric_type(XmlTypeCode::String));
320        assert!(!is_numeric_type(XmlTypeCode::Boolean));
321    }
322
323    #[test]
324    fn test_is_string_like_type() {
325        assert!(is_string_like_type(XmlTypeCode::String));
326        assert!(is_string_like_type(XmlTypeCode::UntypedAtomic));
327        assert!(is_string_like_type(XmlTypeCode::AnyUri));
328        assert!(!is_string_like_type(XmlTypeCode::Integer));
329        assert!(!is_string_like_type(XmlTypeCode::Boolean));
330    }
331}