Skip to main content

xsd_schema/xpath/
type_info.rs

1//! Type information utilities for XPath evaluation.
2//!
3//! This module provides utilities for working with XPath/XSD type information.
4
5use crate::types::value::XmlValue;
6use crate::types::XmlTypeCode;
7
8/// XPath 2.0 result type classification.
9///
10/// Used to categorize the result of XPath expressions.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum XPath2ResultType {
13    /// String result
14    String,
15    /// Boolean result
16    Boolean,
17    /// Numeric result (integer, decimal, float, double)
18    Number,
19    /// Node set result
20    NodeSet,
21    /// Single navigator/node result
22    Navigator,
23    /// DateTime result
24    DateTime,
25    /// Duration result
26    Duration,
27    /// QName result
28    QName,
29    /// AnyUri result
30    AnyUri,
31    /// Other atomic type
32    Other,
33    /// Any/unknown type
34    Any,
35}
36
37/// Get the XPath2 result type for an XmlValue.
38pub fn get_value_result_type(value: &XmlValue) -> XPath2ResultType {
39    get_result_type(value.type_code)
40}
41
42/// Get the XPath2 result type for a type code.
43pub fn get_result_type(type_code: XmlTypeCode) -> XPath2ResultType {
44    match type_code {
45        // String types
46        XmlTypeCode::String
47        | XmlTypeCode::NormalizedString
48        | XmlTypeCode::Token
49        | XmlTypeCode::Language
50        | XmlTypeCode::NmToken
51        | XmlTypeCode::Name
52        | XmlTypeCode::NCName
53        | XmlTypeCode::Id
54        | XmlTypeCode::IdRef
55        | XmlTypeCode::Entity
56        | XmlTypeCode::UntypedAtomic => XPath2ResultType::String,
57
58        // Boolean
59        XmlTypeCode::Boolean => XPath2ResultType::Boolean,
60
61        // Numeric types
62        XmlTypeCode::Decimal
63        | XmlTypeCode::Float
64        | XmlTypeCode::Double
65        | XmlTypeCode::Integer
66        | XmlTypeCode::NonPositiveInteger
67        | XmlTypeCode::NegativeInteger
68        | XmlTypeCode::Long
69        | XmlTypeCode::Int
70        | XmlTypeCode::Short
71        | XmlTypeCode::Byte
72        | XmlTypeCode::NonNegativeInteger
73        | XmlTypeCode::UnsignedLong
74        | XmlTypeCode::UnsignedInt
75        | XmlTypeCode::UnsignedShort
76        | XmlTypeCode::UnsignedByte
77        | XmlTypeCode::PositiveInteger => XPath2ResultType::Number,
78
79        // DateTime types
80        XmlTypeCode::DateTime
81        | XmlTypeCode::Date
82        | XmlTypeCode::Time
83        | XmlTypeCode::GYear
84        | XmlTypeCode::GYearMonth
85        | XmlTypeCode::GMonth
86        | XmlTypeCode::GMonthDay
87        | XmlTypeCode::GDay => XPath2ResultType::DateTime,
88
89        // Duration types
90        XmlTypeCode::Duration | XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration => {
91            XPath2ResultType::Duration
92        }
93
94        // QName
95        XmlTypeCode::QName | XmlTypeCode::Notation => XPath2ResultType::QName,
96
97        // AnyUri
98        XmlTypeCode::AnyUri => XPath2ResultType::AnyUri,
99
100        // Node types
101        XmlTypeCode::Node
102        | XmlTypeCode::Document
103        | XmlTypeCode::Element
104        | XmlTypeCode::Attribute
105        | XmlTypeCode::Namespace
106        | XmlTypeCode::ProcessingInstruction
107        | XmlTypeCode::Comment
108        | XmlTypeCode::Text => XPath2ResultType::Navigator,
109
110        // Abstract/special types
111        XmlTypeCode::Item | XmlTypeCode::AnyType | XmlTypeCode::AnySimpleType => {
112            XPath2ResultType::Any
113        }
114
115        // Catch-all
116        _ => XPath2ResultType::Other,
117    }
118}
119
120/// Check if a type code represents a numeric type.
121pub fn is_numeric_type(type_code: XmlTypeCode) -> bool {
122    type_code.is_numeric()
123}
124
125/// Check if a type code represents a string type.
126pub fn is_string_type(type_code: XmlTypeCode) -> bool {
127    type_code.is_string_derived() || type_code == XmlTypeCode::UntypedAtomic
128}
129
130/// Check if a type code represents a date/time type.
131pub fn is_datetime_type(type_code: XmlTypeCode) -> bool {
132    matches!(
133        type_code,
134        XmlTypeCode::DateTime
135            | XmlTypeCode::Date
136            | XmlTypeCode::Time
137            | XmlTypeCode::GYear
138            | XmlTypeCode::GYearMonth
139            | XmlTypeCode::GMonth
140            | XmlTypeCode::GMonthDay
141            | XmlTypeCode::GDay
142    )
143}
144
145/// Check if a type code represents a duration type.
146pub fn is_duration_type(type_code: XmlTypeCode) -> bool {
147    matches!(
148        type_code,
149        XmlTypeCode::Duration | XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration
150    )
151}
152
153/// Check if a type code represents a node type.
154pub fn is_node_type(type_code: XmlTypeCode) -> bool {
155    type_code.is_node()
156}
157
158/// Get the base primitive type for a derived type.
159pub fn get_base_primitive(type_code: XmlTypeCode) -> XmlTypeCode {
160    match type_code {
161        // String derivatives
162        XmlTypeCode::NormalizedString
163        | XmlTypeCode::Token
164        | XmlTypeCode::Language
165        | XmlTypeCode::NmToken
166        | XmlTypeCode::Name
167        | XmlTypeCode::NCName
168        | XmlTypeCode::Id
169        | XmlTypeCode::IdRef
170        | XmlTypeCode::Entity => XmlTypeCode::String,
171
172        // Integer derivatives
173        XmlTypeCode::NonPositiveInteger
174        | XmlTypeCode::NegativeInteger
175        | XmlTypeCode::Long
176        | XmlTypeCode::Int
177        | XmlTypeCode::Short
178        | XmlTypeCode::Byte
179        | XmlTypeCode::NonNegativeInteger
180        | XmlTypeCode::UnsignedLong
181        | XmlTypeCode::UnsignedInt
182        | XmlTypeCode::UnsignedShort
183        | XmlTypeCode::UnsignedByte
184        | XmlTypeCode::PositiveInteger => XmlTypeCode::Integer,
185
186        // Duration derivatives
187        XmlTypeCode::YearMonthDuration | XmlTypeCode::DayTimeDuration => XmlTypeCode::Duration,
188
189        // Already primitive or not applicable
190        _ => type_code,
191    }
192}
193
194/// Get the XSD type name for a type code.
195pub fn type_code_to_name(type_code: XmlTypeCode) -> &'static str {
196    match type_code {
197        XmlTypeCode::None => "none",
198        XmlTypeCode::Item => "item()",
199        XmlTypeCode::Node => "node()",
200        XmlTypeCode::Document => "document-node()",
201        XmlTypeCode::Element => "element()",
202        XmlTypeCode::Attribute => "attribute()",
203        XmlTypeCode::Namespace => "namespace-node()",
204        XmlTypeCode::ProcessingInstruction => "processing-instruction()",
205        XmlTypeCode::Comment => "comment()",
206        XmlTypeCode::Text => "text()",
207        XmlTypeCode::AnyType => "xs:anyType",
208        XmlTypeCode::AnySimpleType => "xs:anySimpleType",
209        XmlTypeCode::AnyAtomicType => "xs:anyAtomicType",
210        XmlTypeCode::UntypedAtomic => "xs:untypedAtomic",
211        XmlTypeCode::String => "xs:string",
212        XmlTypeCode::NormalizedString => "xs:normalizedString",
213        XmlTypeCode::Token => "xs:token",
214        XmlTypeCode::Language => "xs:language",
215        XmlTypeCode::NmToken => "xs:NMTOKEN",
216        XmlTypeCode::Name => "xs:Name",
217        XmlTypeCode::NCName => "xs:NCName",
218        XmlTypeCode::Id => "xs:ID",
219        XmlTypeCode::IdRef => "xs:IDREF",
220        XmlTypeCode::Entity => "xs:ENTITY",
221        XmlTypeCode::Boolean => "xs:boolean",
222        XmlTypeCode::Decimal => "xs:decimal",
223        XmlTypeCode::Float => "xs:float",
224        XmlTypeCode::Double => "xs:double",
225        XmlTypeCode::Integer => "xs:integer",
226        XmlTypeCode::NonPositiveInteger => "xs:nonPositiveInteger",
227        XmlTypeCode::NegativeInteger => "xs:negativeInteger",
228        XmlTypeCode::Long => "xs:long",
229        XmlTypeCode::Int => "xs:int",
230        XmlTypeCode::Short => "xs:short",
231        XmlTypeCode::Byte => "xs:byte",
232        XmlTypeCode::NonNegativeInteger => "xs:nonNegativeInteger",
233        XmlTypeCode::UnsignedLong => "xs:unsignedLong",
234        XmlTypeCode::UnsignedInt => "xs:unsignedInt",
235        XmlTypeCode::UnsignedShort => "xs:unsignedShort",
236        XmlTypeCode::UnsignedByte => "xs:unsignedByte",
237        XmlTypeCode::PositiveInteger => "xs:positiveInteger",
238        XmlTypeCode::Duration => "xs:duration",
239        XmlTypeCode::DateTime => "xs:dateTime",
240        XmlTypeCode::Time => "xs:time",
241        XmlTypeCode::Date => "xs:date",
242        XmlTypeCode::GYearMonth => "xs:gYearMonth",
243        XmlTypeCode::GYear => "xs:gYear",
244        XmlTypeCode::GMonthDay => "xs:gMonthDay",
245        XmlTypeCode::GDay => "xs:gDay",
246        XmlTypeCode::GMonth => "xs:gMonth",
247        XmlTypeCode::HexBinary => "xs:hexBinary",
248        XmlTypeCode::Base64Binary => "xs:base64Binary",
249        XmlTypeCode::AnyUri => "xs:anyURI",
250        XmlTypeCode::QName => "xs:QName",
251        XmlTypeCode::Notation => "xs:NOTATION",
252        XmlTypeCode::YearMonthDuration => "xs:yearMonthDuration",
253        XmlTypeCode::DayTimeDuration => "xs:dayTimeDuration",
254        _ => "unknown",
255    }
256}
257
258/// Parse a type name to a type code.
259pub fn name_to_type_code(name: &str) -> Option<XmlTypeCode> {
260    // Strip xs: prefix if present
261    let name = name.strip_prefix("xs:").unwrap_or(name);
262
263    match name {
264        "string" => Some(XmlTypeCode::String),
265        "boolean" => Some(XmlTypeCode::Boolean),
266        "decimal" => Some(XmlTypeCode::Decimal),
267        "float" => Some(XmlTypeCode::Float),
268        "double" => Some(XmlTypeCode::Double),
269        "integer" => Some(XmlTypeCode::Integer),
270        "long" => Some(XmlTypeCode::Long),
271        "int" => Some(XmlTypeCode::Int),
272        "short" => Some(XmlTypeCode::Short),
273        "byte" => Some(XmlTypeCode::Byte),
274        "unsignedLong" => Some(XmlTypeCode::UnsignedLong),
275        "unsignedInt" => Some(XmlTypeCode::UnsignedInt),
276        "unsignedShort" => Some(XmlTypeCode::UnsignedShort),
277        "unsignedByte" => Some(XmlTypeCode::UnsignedByte),
278        "positiveInteger" => Some(XmlTypeCode::PositiveInteger),
279        "nonNegativeInteger" => Some(XmlTypeCode::NonNegativeInteger),
280        "negativeInteger" => Some(XmlTypeCode::NegativeInteger),
281        "nonPositiveInteger" => Some(XmlTypeCode::NonPositiveInteger),
282        "duration" => Some(XmlTypeCode::Duration),
283        "dateTime" => Some(XmlTypeCode::DateTime),
284        "time" => Some(XmlTypeCode::Time),
285        "date" => Some(XmlTypeCode::Date),
286        "gYearMonth" => Some(XmlTypeCode::GYearMonth),
287        "gYear" => Some(XmlTypeCode::GYear),
288        "gMonthDay" => Some(XmlTypeCode::GMonthDay),
289        "gDay" => Some(XmlTypeCode::GDay),
290        "gMonth" => Some(XmlTypeCode::GMonth),
291        "hexBinary" => Some(XmlTypeCode::HexBinary),
292        "base64Binary" => Some(XmlTypeCode::Base64Binary),
293        "anyURI" => Some(XmlTypeCode::AnyUri),
294        "QName" => Some(XmlTypeCode::QName),
295        "NOTATION" => Some(XmlTypeCode::Notation),
296        "normalizedString" => Some(XmlTypeCode::NormalizedString),
297        "token" => Some(XmlTypeCode::Token),
298        "language" => Some(XmlTypeCode::Language),
299        "NMTOKEN" => Some(XmlTypeCode::NmToken),
300        "Name" => Some(XmlTypeCode::Name),
301        "NCName" => Some(XmlTypeCode::NCName),
302        "ID" => Some(XmlTypeCode::Id),
303        "IDREF" => Some(XmlTypeCode::IdRef),
304        "ENTITY" => Some(XmlTypeCode::Entity),
305        "untypedAtomic" => Some(XmlTypeCode::UntypedAtomic),
306        "anyAtomicType" => Some(XmlTypeCode::AnyAtomicType),
307        "yearMonthDuration" => Some(XmlTypeCode::YearMonthDuration),
308        "dayTimeDuration" => Some(XmlTypeCode::DayTimeDuration),
309        _ => None,
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use num_bigint::BigInt;
317
318    #[test]
319    fn test_get_result_type() {
320        assert_eq!(
321            get_result_type(XmlTypeCode::String),
322            XPath2ResultType::String
323        );
324        assert_eq!(
325            get_result_type(XmlTypeCode::Boolean),
326            XPath2ResultType::Boolean
327        );
328        assert_eq!(
329            get_result_type(XmlTypeCode::Integer),
330            XPath2ResultType::Number
331        );
332        assert_eq!(
333            get_result_type(XmlTypeCode::DateTime),
334            XPath2ResultType::DateTime
335        );
336        assert_eq!(
337            get_result_type(XmlTypeCode::Duration),
338            XPath2ResultType::Duration
339        );
340        assert_eq!(
341            get_result_type(XmlTypeCode::Element),
342            XPath2ResultType::Navigator
343        );
344    }
345
346    #[test]
347    fn test_get_value_result_type() {
348        let value = XmlValue::string("test");
349        assert_eq!(get_value_result_type(&value), XPath2ResultType::String);
350
351        let value = XmlValue::integer(BigInt::from(42));
352        assert_eq!(get_value_result_type(&value), XPath2ResultType::Number);
353    }
354
355    #[test]
356    fn test_is_numeric_type() {
357        assert!(is_numeric_type(XmlTypeCode::Integer));
358        assert!(is_numeric_type(XmlTypeCode::Decimal));
359        assert!(is_numeric_type(XmlTypeCode::Double));
360        assert!(!is_numeric_type(XmlTypeCode::String));
361    }
362
363    #[test]
364    fn test_type_code_to_name() {
365        assert_eq!(type_code_to_name(XmlTypeCode::String), "xs:string");
366        assert_eq!(type_code_to_name(XmlTypeCode::Integer), "xs:integer");
367        assert_eq!(type_code_to_name(XmlTypeCode::Element), "element()");
368    }
369
370    #[test]
371    fn test_name_to_type_code() {
372        assert_eq!(name_to_type_code("string"), Some(XmlTypeCode::String));
373        assert_eq!(name_to_type_code("xs:integer"), Some(XmlTypeCode::Integer));
374        assert_eq!(name_to_type_code("unknown"), None);
375    }
376
377    #[test]
378    fn test_get_base_primitive() {
379        assert_eq!(get_base_primitive(XmlTypeCode::Long), XmlTypeCode::Integer);
380        assert_eq!(get_base_primitive(XmlTypeCode::Token), XmlTypeCode::String);
381        assert_eq!(get_base_primitive(XmlTypeCode::String), XmlTypeCode::String);
382    }
383}