1use std::fmt;
14
15use num_bigint::BigInt;
16use rust_decimal::prelude::ToPrimitive;
17use rust_decimal::Decimal;
18
19use super::{PrimitiveTypeCode, XmlTypeCode};
20use crate::ids::SimpleTypeKey;
21use crate::namespace::qname::QualifiedName;
22
23#[derive(Debug, Clone, PartialEq)]
28pub struct XmlValue {
29 pub type_code: XmlTypeCode,
31 pub schema_type: Option<SimpleTypeKey>,
33 pub value: XmlValueKind,
35}
36
37impl XmlValue {
38 pub fn new(type_code: XmlTypeCode, value: XmlValueKind) -> Self {
40 Self {
41 type_code,
42 schema_type: None,
43 value,
44 }
45 }
46
47 pub fn with_schema_type(
49 type_code: XmlTypeCode,
50 schema_type: SimpleTypeKey,
51 value: XmlValueKind,
52 ) -> Self {
53 Self {
54 type_code,
55 schema_type: Some(schema_type),
56 value,
57 }
58 }
59
60 pub fn untyped(s: impl Into<String>) -> Self {
62 Self {
63 type_code: XmlTypeCode::UntypedAtomic,
64 schema_type: None,
65 value: XmlValueKind::UntypedAtomic(s.into()),
66 }
67 }
68
69 pub fn string(s: impl Into<String>) -> Self {
71 Self {
72 type_code: XmlTypeCode::String,
73 schema_type: None,
74 value: XmlValueKind::Atomic(XmlAtomicValue::String(s.into())),
75 }
76 }
77
78 pub fn boolean(b: bool) -> Self {
80 Self {
81 type_code: XmlTypeCode::Boolean,
82 schema_type: None,
83 value: XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)),
84 }
85 }
86
87 pub fn decimal(d: Decimal) -> Self {
89 Self {
90 type_code: XmlTypeCode::Decimal,
91 schema_type: None,
92 value: XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)),
93 }
94 }
95
96 pub fn integer(i: BigInt) -> Self {
98 Self {
99 type_code: XmlTypeCode::Integer,
100 schema_type: None,
101 value: XmlValueKind::Atomic(XmlAtomicValue::Integer(i)),
102 }
103 }
104
105 pub fn float(f: f32) -> Self {
107 Self {
108 type_code: XmlTypeCode::Float,
109 schema_type: None,
110 value: XmlValueKind::Atomic(XmlAtomicValue::Float(f)),
111 }
112 }
113
114 pub fn double(d: f64) -> Self {
116 Self {
117 type_code: XmlTypeCode::Double,
118 schema_type: None,
119 value: XmlValueKind::Atomic(XmlAtomicValue::Double(d)),
120 }
121 }
122
123 pub fn is_atomic(&self) -> bool {
125 matches!(
126 self.value,
127 XmlValueKind::Atomic(_) | XmlValueKind::UntypedAtomic(_)
128 )
129 }
130
131 pub fn is_list(&self) -> bool {
133 matches!(self.value, XmlValueKind::List { .. })
134 }
135
136 pub fn is_union(&self) -> bool {
138 matches!(self.value, XmlValueKind::Union(_))
139 }
140
141 pub fn is_untyped(&self) -> bool {
143 matches!(self.value, XmlValueKind::UntypedAtomic(_))
144 }
145
146 pub fn primitive_type(&self) -> Option<PrimitiveTypeCode> {
148 PrimitiveTypeCode::from_type_code(self.type_code)
149 }
150
151 pub fn to_string_value(&self) -> String {
153 match &self.value {
154 XmlValueKind::Atomic(atom) => atom.to_string(),
155 XmlValueKind::List { items, .. } => items
156 .iter()
157 .map(|v| v.to_string())
158 .collect::<Vec<_>>()
159 .join(" "),
160 XmlValueKind::Union(inner) => inner.to_string_value(),
161 XmlValueKind::UntypedAtomic(s) => s.clone(),
162 }
163 }
164
165 pub fn as_boolean(&self) -> Option<bool> {
167 match &self.value {
168 XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) => Some(*b),
169 _ => None,
170 }
171 }
172
173 pub fn as_string(&self) -> Option<&str> {
175 match &self.value {
176 XmlValueKind::Atomic(XmlAtomicValue::String(s)) => Some(s),
177 XmlValueKind::UntypedAtomic(s) => Some(s),
178 _ => None,
179 }
180 }
181
182 pub fn as_decimal(&self) -> Option<Decimal> {
184 match &self.value {
185 XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => Some(*d),
186 XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => {
187 i.to_string().parse().ok()
189 }
190 _ => None,
191 }
192 }
193
194 pub fn as_integer(&self) -> Option<&BigInt> {
196 match &self.value {
197 XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => Some(i),
198 _ => None,
199 }
200 }
201
202 pub fn as_double(&self) -> Option<f64> {
204 match &self.value {
205 XmlValueKind::Atomic(XmlAtomicValue::Double(d)) => Some(*d),
206 XmlValueKind::Atomic(XmlAtomicValue::Float(f)) => Some(*f as f64),
207 XmlValueKind::Atomic(XmlAtomicValue::Decimal(d)) => d.to_string().parse().ok(),
208 XmlValueKind::Atomic(XmlAtomicValue::Integer(i)) => i.to_string().parse().ok(),
209 _ => None,
210 }
211 }
212
213 pub fn as_qname(&self) -> Option<&QualifiedName> {
215 match &self.value {
216 XmlValueKind::Atomic(XmlAtomicValue::QName(qn)) => Some(qn),
217 _ => None,
218 }
219 }
220
221 #[cfg(feature = "xsd11")]
231 pub fn to_xpath_value<N: crate::xpath::DomNavigator>(
232 &self,
233 item_schema_type: Option<SimpleTypeKey>,
234 ) -> crate::xpath::XPathValue<N> {
235 use crate::xpath::iterator::XmlItem;
236 use crate::xpath::XPathValue;
237
238 match &self.value {
239 XmlValueKind::Atomic(_) | XmlValueKind::UntypedAtomic(_) => {
240 XPathValue::from_atomic(self.clone())
241 }
242 XmlValueKind::List { item_type, items } => {
243 let xml_items: Vec<XmlItem<N>> = items
244 .iter()
245 .map(|atom| {
246 let val = XmlValue {
247 type_code: atom.type_code(),
248 schema_type: item_schema_type,
249 value: XmlValueKind::Atomic(atom.clone()),
250 };
251 XmlItem::Atomic(val)
252 })
253 .collect();
254 let _ = item_type; XPathValue::from_sequence(xml_items)
256 }
257 XmlValueKind::Union(inner) => inner.to_xpath_value(item_schema_type),
258 }
259 }
260}
261
262#[derive(Debug, Clone, PartialEq)]
264pub enum XmlValueKind {
265 Atomic(XmlAtomicValue),
267 List {
269 item_type: XmlTypeCode,
271 items: Vec<XmlAtomicValue>,
273 },
274 Union(Box<XmlValue>),
276 UntypedAtomic(String),
278}
279
280#[derive(Debug, Clone, PartialEq)]
285pub enum XmlAtomicValue {
286 String(String),
289
290 Boolean(bool),
293
294 Decimal(Decimal),
297 Integer(BigInt),
299 Float(f32),
301 Double(f64),
303
304 DateTime(DateTimeValue),
307 Date(DateValue),
309 Time(TimeValue),
311 Duration(DurationValue),
313 GYearMonth(GYearMonthValue),
315 GYear(GYearValue),
317 GMonthDay(GMonthDayValue),
319 GDay(GDayValue),
321 GMonth(GMonthValue),
323 YearMonthDuration(YearMonthDurationValue),
325 DayTimeDuration(DayTimeDurationValue),
327
328 HexBinary(Vec<u8>),
331 Base64Binary(Vec<u8>),
333
334 AnyUri(String),
337
338 QName(QualifiedName),
341 Notation(QualifiedName),
343}
344
345impl XmlAtomicValue {
346 pub fn type_code(&self) -> XmlTypeCode {
348 match self {
349 Self::String(_) => XmlTypeCode::String,
350 Self::Boolean(_) => XmlTypeCode::Boolean,
351 Self::Decimal(_) => XmlTypeCode::Decimal,
352 Self::Integer(_) => XmlTypeCode::Integer,
353 Self::Float(_) => XmlTypeCode::Float,
354 Self::Double(_) => XmlTypeCode::Double,
355 Self::DateTime(_) => XmlTypeCode::DateTime,
356 Self::Date(_) => XmlTypeCode::Date,
357 Self::Time(_) => XmlTypeCode::Time,
358 Self::Duration(_) => XmlTypeCode::Duration,
359 Self::GYearMonth(_) => XmlTypeCode::GYearMonth,
360 Self::GYear(_) => XmlTypeCode::GYear,
361 Self::GMonthDay(_) => XmlTypeCode::GMonthDay,
362 Self::GDay(_) => XmlTypeCode::GDay,
363 Self::GMonth(_) => XmlTypeCode::GMonth,
364 Self::YearMonthDuration(_) => XmlTypeCode::YearMonthDuration,
365 Self::DayTimeDuration(_) => XmlTypeCode::DayTimeDuration,
366 Self::HexBinary(_) => XmlTypeCode::HexBinary,
367 Self::Base64Binary(_) => XmlTypeCode::Base64Binary,
368 Self::AnyUri(_) => XmlTypeCode::AnyUri,
369 Self::QName(_) => XmlTypeCode::QName,
370 Self::Notation(_) => XmlTypeCode::Notation,
371 }
372 }
373
374 pub fn primitive_type(&self) -> PrimitiveTypeCode {
376 match self {
377 Self::String(_) => PrimitiveTypeCode::String,
378 Self::Boolean(_) => PrimitiveTypeCode::Boolean,
379 Self::Decimal(_) | Self::Integer(_) => PrimitiveTypeCode::Decimal,
380 Self::Float(_) => PrimitiveTypeCode::Float,
381 Self::Double(_) => PrimitiveTypeCode::Double,
382 Self::DateTime(_) => PrimitiveTypeCode::DateTime,
383 Self::Date(_) => PrimitiveTypeCode::Date,
384 Self::Time(_) => PrimitiveTypeCode::Time,
385 Self::Duration(_) | Self::YearMonthDuration(_) | Self::DayTimeDuration(_) => {
386 PrimitiveTypeCode::Duration
387 }
388 Self::GYearMonth(_) => PrimitiveTypeCode::GYearMonth,
389 Self::GYear(_) => PrimitiveTypeCode::GYear,
390 Self::GMonthDay(_) => PrimitiveTypeCode::GMonthDay,
391 Self::GDay(_) => PrimitiveTypeCode::GDay,
392 Self::GMonth(_) => PrimitiveTypeCode::GMonth,
393 Self::HexBinary(_) => PrimitiveTypeCode::HexBinary,
394 Self::Base64Binary(_) => PrimitiveTypeCode::Base64Binary,
395 Self::AnyUri(_) => PrimitiveTypeCode::AnyUri,
396 Self::QName(_) => PrimitiveTypeCode::QName,
397 Self::Notation(_) => PrimitiveTypeCode::Notation,
398 }
399 }
400}
401
402impl fmt::Display for XmlAtomicValue {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 match self {
405 Self::String(s) => write!(f, "{}", s),
406 Self::Boolean(b) => write!(f, "{}", if *b { "true" } else { "false" }),
407 Self::Decimal(d) => {
408 if d.fract().is_zero() {
410 write!(f, "{}", d.trunc())
411 } else {
412 write!(f, "{}", d.normalize())
413 }
414 }
415 Self::Integer(i) => write!(f, "{}", i),
416 Self::Float(v) => format_float(*v, f),
417 Self::Double(v) => format_double(*v, f),
418 Self::DateTime(dt) => write!(f, "{}", dt),
419 Self::Date(d) => write!(f, "{}", d),
420 Self::Time(t) => write!(f, "{}", t),
421 Self::Duration(d) => write!(f, "{}", d),
422 Self::GYearMonth(v) => write!(f, "{}", v),
423 Self::GYear(v) => write!(f, "{}", v),
424 Self::GMonthDay(v) => write!(f, "{}", v),
425 Self::GDay(v) => write!(f, "{}", v),
426 Self::GMonth(v) => write!(f, "{}", v),
427 Self::YearMonthDuration(v) => write!(f, "{}", v),
428 Self::DayTimeDuration(v) => write!(f, "{}", v),
429 Self::HexBinary(bytes) => {
430 write!(f, "{}", hex::encode_upper(bytes))
431 }
432 Self::Base64Binary(bytes) => {
433 use base64::Engine;
434 write!(
435 f,
436 "{}",
437 base64::engine::general_purpose::STANDARD.encode(bytes)
438 )
439 }
440 Self::AnyUri(uri) => write!(f, "{}", uri),
441 Self::QName(qn) => {
442 write!(f, "QName({:?}:{})", qn.namespace_uri, qn.local_name.0)
444 }
445 Self::Notation(n) => {
446 write!(f, "NOTATION({:?}:{})", n.namespace_uri, n.local_name.0)
447 }
448 }
449 }
450}
451
452fn fix_scientific_notation(s: &str) -> String {
456 if let Some(e_pos) = s.find('E') {
458 let mantissa = &s[..e_pos];
459 let exponent = &s[e_pos..]; if !mantissa.contains('.') {
461 format!("{}.0{}", mantissa, exponent)
462 } else {
463 s.to_string()
464 }
465 } else {
466 s.to_string()
467 }
468}
469
470fn format_float(v: f32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 if v.is_nan() {
473 write!(f, "NaN")
474 } else if v.is_infinite() {
475 if v.is_sign_positive() {
476 write!(f, "INF")
477 } else {
478 write!(f, "-INF")
479 }
480 } else if v == 0.0 {
481 if v.is_sign_negative() {
483 write!(f, "-0")
484 } else {
485 write!(f, "0")
486 }
487 } else if v.abs() >= 1e-6 && v.abs() < 1e6 {
488 write!(f, "{}", v)
490 } else {
491 let s = format!("{:E}", v);
493 write!(f, "{}", fix_scientific_notation(&s))
494 }
495}
496
497fn format_double(v: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499 if v.is_nan() {
500 write!(f, "NaN")
501 } else if v.is_infinite() {
502 if v.is_sign_positive() {
503 write!(f, "INF")
504 } else {
505 write!(f, "-INF")
506 }
507 } else if v == 0.0 {
508 if v.is_sign_negative() {
510 write!(f, "-0")
511 } else {
512 write!(f, "0")
513 }
514 } else if v.abs() >= 1e-6 && v.abs() < 1e6 {
515 write!(f, "{}", v)
517 } else {
518 let s = format!("{:E}", v);
520 write!(f, "{}", fix_scientific_notation(&s))
521 }
522}
523
524fn format_year(year: i32, f: &mut fmt::Formatter<'_>) -> fmt::Result {
533 if year < 0 {
534 write!(f, "-{:04}", -year)
535 } else {
536 write!(f, "{:04}", year)
537 }
538}
539
540#[derive(Debug, Clone, PartialEq)]
542pub struct DateTimeValue {
543 pub year: i32,
544 pub month: u8,
545 pub day: u8,
546 pub hour: u8,
547 pub minute: u8,
548 pub second: Decimal,
549 pub timezone: Option<TimezoneOffset>,
550}
551
552impl DateTimeValue {
553 fn to_comparable_instant(&self) -> Decimal {
556 let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
557 let days = date_to_days(self.year, self.month as i32, self.day as i32);
558 Decimal::from(days) * Decimal::from(86400)
559 + Decimal::from(self.hour as i64) * Decimal::from(3600)
560 + Decimal::from(self.minute as i64) * Decimal::from(60)
561 + self.second
562 - Decimal::from(tz_minutes) * Decimal::from(60)
563 }
564}
565
566impl PartialOrd for DateTimeValue {
567 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
568 self.to_comparable_instant()
569 .partial_cmp(&other.to_comparable_instant())
570 }
571}
572
573impl fmt::Display for DateTimeValue {
574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575 format_year(self.year, f)?;
576 write!(
577 f,
578 "-{:02}-{:02}T{:02}:{:02}:",
579 self.month, self.day, self.hour, self.minute
580 )?;
581 format_seconds(self.second, f)?;
582 if let Some(tz) = &self.timezone {
583 write!(f, "{}", tz)?;
584 }
585 Ok(())
586 }
587}
588
589#[derive(Debug, Clone, PartialEq)]
591pub struct DateValue {
592 pub year: i32,
593 pub month: u8,
594 pub day: u8,
595 pub timezone: Option<TimezoneOffset>,
596}
597
598impl DateValue {
599 fn to_comparable_instant(&self) -> Decimal {
600 let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
601 let days = date_to_days(self.year, self.month as i32, self.day as i32);
602 Decimal::from(days) * Decimal::from(86400) - Decimal::from(tz_minutes) * Decimal::from(60)
603 }
604}
605
606impl PartialOrd for DateValue {
607 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
608 self.to_comparable_instant()
609 .partial_cmp(&other.to_comparable_instant())
610 }
611}
612
613impl fmt::Display for DateValue {
614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615 format_year(self.year, f)?;
616 write!(f, "-{:02}-{:02}", self.month, self.day)?;
617 if let Some(tz) = &self.timezone {
618 write!(f, "{}", tz)?;
619 }
620 Ok(())
621 }
622}
623
624#[derive(Debug, Clone, PartialEq)]
626pub struct TimeValue {
627 pub hour: u8,
628 pub minute: u8,
629 pub second: Decimal,
630 pub timezone: Option<TimezoneOffset>,
631}
632
633impl TimeValue {
634 fn to_comparable_seconds(&self) -> Decimal {
635 let tz_minutes = self.timezone.map_or(0i64, |tz| tz.0 as i64);
636 Decimal::from(self.hour as i64) * Decimal::from(3600)
637 + Decimal::from(self.minute as i64) * Decimal::from(60)
638 + self.second
639 - Decimal::from(tz_minutes) * Decimal::from(60)
640 }
641}
642
643impl PartialOrd for TimeValue {
644 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
645 self.to_comparable_seconds()
646 .partial_cmp(&other.to_comparable_seconds())
647 }
648}
649
650impl fmt::Display for TimeValue {
651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652 write!(f, "{:02}:{:02}:", self.hour, self.minute)?;
653 format_seconds(self.second, f)?;
654 if let Some(tz) = &self.timezone {
655 write!(f, "{}", tz)?;
656 }
657 Ok(())
658 }
659}
660
661#[derive(Debug, Clone, PartialEq)]
663pub struct DurationValue {
664 pub negative: bool,
665 pub years: u32,
666 pub months: u32,
667 pub days: u32,
668 pub hours: u32,
669 pub minutes: u32,
670 pub seconds: Decimal,
671}
672
673impl DurationValue {
674 fn to_approx_total_seconds(&self) -> Decimal {
680 let month_secs = Decimal::from(2629746i64);
682 let total_months = Decimal::from(self.years as i64) * Decimal::from(12)
683 + Decimal::from(self.months as i64);
684 let day_time_secs = Decimal::from(self.days as i64) * Decimal::from(86400)
685 + Decimal::from(self.hours as i64) * Decimal::from(3600)
686 + Decimal::from(self.minutes as i64) * Decimal::from(60)
687 + self.seconds;
688 let total = total_months * month_secs + day_time_secs;
689 if self.negative {
690 -total
691 } else {
692 total
693 }
694 }
695}
696
697impl PartialOrd for DurationValue {
698 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
699 self.to_approx_total_seconds()
700 .partial_cmp(&other.to_approx_total_seconds())
701 }
702}
703
704impl fmt::Display for DurationValue {
705 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
706 let total_months = self.years * 12 + self.months;
708 let years = total_months / 12;
709 let months = total_months % 12;
710
711 let (days, hours, minutes, seconds) =
713 normalize_day_time(self.days, self.hours, self.minutes, self.seconds);
714
715 if self.negative {
716 write!(f, "-")?;
717 }
718 write!(f, "P")?;
719 if years > 0 {
720 write!(f, "{}Y", years)?;
721 }
722 if months > 0 {
723 write!(f, "{}M", months)?;
724 }
725 if days > 0 {
726 write!(f, "{}D", days)?;
727 }
728 if hours > 0 || minutes > 0 || !seconds.is_zero() {
729 write!(f, "T")?;
730 if hours > 0 {
731 write!(f, "{}H", hours)?;
732 }
733 if minutes > 0 {
734 write!(f, "{}M", minutes)?;
735 }
736 if !seconds.is_zero() {
737 format_duration_seconds(seconds, f)?;
738 write!(f, "S")?;
739 }
740 }
741 if years == 0 && months == 0 && days == 0 && hours == 0 && minutes == 0 && seconds.is_zero()
743 {
744 write!(f, "T0S")?;
745 }
746 Ok(())
747 }
748}
749
750#[derive(Debug, Clone, PartialEq)]
752pub struct YearMonthDurationValue {
753 pub negative: bool,
754 pub years: u32,
755 pub months: u32,
756}
757
758impl fmt::Display for YearMonthDurationValue {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 let total_months = self.years * 12 + self.months;
762 let years = total_months / 12;
763 let months = total_months % 12;
764
765 if self.negative && (years > 0 || months > 0) {
767 write!(f, "-")?;
768 }
769 write!(f, "P")?;
770 if years > 0 {
771 write!(f, "{}Y", years)?;
772 }
773 if months > 0 {
774 write!(f, "{}M", months)?;
775 }
776 if years == 0 && months == 0 {
777 write!(f, "0M")?;
778 }
779 Ok(())
780 }
781}
782
783impl PartialOrd for YearMonthDurationValue {
784 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
785 let self_months = if self.negative {
786 -(self.years as i64 * 12 + self.months as i64)
787 } else {
788 self.years as i64 * 12 + self.months as i64
789 };
790 let other_months = if other.negative {
791 -(other.years as i64 * 12 + other.months as i64)
792 } else {
793 other.years as i64 * 12 + other.months as i64
794 };
795 self_months.partial_cmp(&other_months)
796 }
797}
798
799#[derive(Debug, Clone, PartialEq)]
801pub struct DayTimeDurationValue {
802 pub negative: bool,
803 pub days: u32,
804 pub hours: u32,
805 pub minutes: u32,
806 pub seconds: Decimal,
807}
808
809impl fmt::Display for DayTimeDurationValue {
810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811 let (days, hours, minutes, seconds) =
813 normalize_day_time(self.days, self.hours, self.minutes, self.seconds);
814
815 if self.negative && (days > 0 || hours > 0 || minutes > 0 || !seconds.is_zero()) {
817 write!(f, "-")?;
818 }
819 write!(f, "P")?;
820 if days > 0 {
821 write!(f, "{}D", days)?;
822 }
823 if hours > 0 || minutes > 0 || !seconds.is_zero() {
824 write!(f, "T")?;
825 if hours > 0 {
826 write!(f, "{}H", hours)?;
827 }
828 if minutes > 0 {
829 write!(f, "{}M", minutes)?;
830 }
831 if !seconds.is_zero() {
832 format_duration_seconds(seconds, f)?;
833 write!(f, "S")?;
834 }
835 }
836 if days == 0 && hours == 0 && minutes == 0 && seconds.is_zero() {
837 write!(f, "T0S")?;
838 }
839 Ok(())
840 }
841}
842
843impl PartialOrd for DayTimeDurationValue {
844 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
845 let self_secs = {
846 let s = Decimal::from(self.days as i64) * Decimal::from(86400i64)
847 + Decimal::from(self.hours as i64) * Decimal::from(3600i64)
848 + Decimal::from(self.minutes as i64) * Decimal::from(60i64)
849 + self.seconds;
850 if self.negative {
851 -s
852 } else {
853 s
854 }
855 };
856 let other_secs = {
857 let s = Decimal::from(other.days as i64) * Decimal::from(86400i64)
858 + Decimal::from(other.hours as i64) * Decimal::from(3600i64)
859 + Decimal::from(other.minutes as i64) * Decimal::from(60i64)
860 + other.seconds;
861 if other.negative {
862 -s
863 } else {
864 s
865 }
866 };
867 self_secs.partial_cmp(&other_secs)
868 }
869}
870
871#[derive(Debug, Clone, PartialEq)]
873pub struct GYearMonthValue {
874 pub year: i32,
875 pub month: u8,
876 pub timezone: Option<TimezoneOffset>,
877}
878
879impl PartialOrd for GYearMonthValue {
880 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
881 let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
882 let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
883 let d1 = date_to_days(self.year, self.month as i32, 1) * 1440 - tz1;
884 let d2 = date_to_days(other.year, other.month as i32, 1) * 1440 - tz2;
885 d1.partial_cmp(&d2)
886 }
887}
888
889impl fmt::Display for GYearMonthValue {
890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891 format_year(self.year, f)?;
892 write!(f, "-{:02}", self.month)?;
893 if let Some(tz) = &self.timezone {
894 write!(f, "{}", tz)?;
895 }
896 Ok(())
897 }
898}
899
900#[derive(Debug, Clone, PartialEq)]
902pub struct GYearValue {
903 pub year: i32,
904 pub timezone: Option<TimezoneOffset>,
905}
906
907impl PartialOrd for GYearValue {
908 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
909 let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
910 let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
911 let d1 = date_to_days(self.year, 1, 1) * 1440 - tz1;
912 let d2 = date_to_days(other.year, 1, 1) * 1440 - tz2;
913 d1.partial_cmp(&d2)
914 }
915}
916
917impl fmt::Display for GYearValue {
918 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919 format_year(self.year, f)?;
920 if let Some(tz) = &self.timezone {
921 write!(f, "{}", tz)?;
922 }
923 Ok(())
924 }
925}
926
927#[derive(Debug, Clone, PartialEq)]
929pub struct GMonthDayValue {
930 pub month: u8,
931 pub day: u8,
932 pub timezone: Option<TimezoneOffset>,
933}
934
935impl PartialOrd for GMonthDayValue {
936 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
937 let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
938 let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
939 let d1 = date_to_days(2000, self.month as i32, self.day as i32) * 1440 - tz1;
941 let d2 = date_to_days(2000, other.month as i32, other.day as i32) * 1440 - tz2;
942 d1.partial_cmp(&d2)
943 }
944}
945
946impl fmt::Display for GMonthDayValue {
947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948 write!(f, "--{:02}-{:02}", self.month, self.day)?;
949 if let Some(tz) = &self.timezone {
950 write!(f, "{}", tz)?;
951 }
952 Ok(())
953 }
954}
955
956#[derive(Debug, Clone, PartialEq)]
958pub struct GDayValue {
959 pub day: u8,
960 pub timezone: Option<TimezoneOffset>,
961}
962
963impl PartialOrd for GDayValue {
964 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
965 let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
966 let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
967 let d1 = (self.day as i64) * 1440 - tz1;
968 let d2 = (other.day as i64) * 1440 - tz2;
969 d1.partial_cmp(&d2)
970 }
971}
972
973impl fmt::Display for GDayValue {
974 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
975 write!(f, "---{:02}", self.day)?;
976 if let Some(tz) = &self.timezone {
977 write!(f, "{}", tz)?;
978 }
979 Ok(())
980 }
981}
982
983#[derive(Debug, Clone, PartialEq)]
985pub struct GMonthValue {
986 pub month: u8,
987 pub timezone: Option<TimezoneOffset>,
988}
989
990impl PartialOrd for GMonthValue {
991 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
992 let tz1 = self.timezone.map_or(0i64, |tz| tz.0 as i64);
993 let tz2 = other.timezone.map_or(0i64, |tz| tz.0 as i64);
994 let d1 = (self.month as i64) * 1440 - tz1;
995 let d2 = (other.month as i64) * 1440 - tz2;
996 d1.partial_cmp(&d2)
997 }
998}
999
1000impl fmt::Display for GMonthValue {
1001 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1002 write!(f, "--{:02}", self.month)?;
1003 if let Some(tz) = &self.timezone {
1004 write!(f, "{}", tz)?;
1005 }
1006 Ok(())
1007 }
1008}
1009
1010#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1012pub struct TimezoneOffset(pub i16);
1013
1014impl TimezoneOffset {
1015 pub const UTC: Self = Self(0);
1017
1018 pub fn from_hm(hours: i8, minutes: i8) -> Self {
1020 Self(hours as i16 * 60 + minutes as i16)
1021 }
1022
1023 pub fn hours(&self) -> i8 {
1025 (self.0 / 60) as i8
1026 }
1027
1028 pub fn minutes(&self) -> i8 {
1030 (self.0 % 60).abs() as i8
1031 }
1032}
1033
1034impl fmt::Display for TimezoneOffset {
1035 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1036 if self.0 == 0 {
1037 write!(f, "Z")
1038 } else {
1039 let sign = if self.0 > 0 { '+' } else { '-' };
1040 let hours = (self.0.abs() / 60) as u8;
1041 let minutes = (self.0.abs() % 60) as u8;
1042 write!(f, "{}{:02}:{:02}", sign, hours, minutes)
1043 }
1044 }
1045}
1046
1047fn date_to_days(year: i32, month: i32, day: i32) -> i64 {
1051 let (y, m) = if month <= 2 {
1053 (year as i64 - 1, month as i64 + 12)
1054 } else {
1055 (year as i64, month as i64)
1056 };
1057 365 * y + y / 4 - y / 100 + y / 400 + (153 * (m - 3) + 2) / 5 + day as i64 - 307
1059}
1060
1061fn normalize_day_time(
1065 days: u32,
1066 hours: u32,
1067 minutes: u32,
1068 seconds: Decimal,
1069) -> (u32, u32, u32, Decimal) {
1070 let whole_secs = seconds.trunc();
1071 let frac_secs = seconds - whole_secs;
1072
1073 let total_secs: u64 = whole_secs.to_u64().unwrap_or(0);
1074 let mut mins = minutes as u64 + total_secs / 60;
1075 let rem_secs = (total_secs % 60) as u32;
1076 let mut hrs = hours as u64 + mins / 60;
1077 mins %= 60;
1078 let d = days as u64 + hrs / 24;
1079 hrs %= 24;
1080
1081 let out_seconds = Decimal::from(rem_secs) + frac_secs;
1082 (d as u32, hrs as u32, mins as u32, out_seconds)
1083}
1084
1085fn format_seconds(s: Decimal, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1087 if s.fract().is_zero() {
1088 write!(f, "{:02}", s.trunc())
1089 } else {
1090 let formatted = format!("{}", s);
1092 if let Some(dot_pos) = formatted.find('.') {
1093 let int_part = &formatted[..dot_pos];
1094 let frac_part = formatted[dot_pos + 1..].trim_end_matches('0');
1095 if int_part.len() == 1 {
1096 write!(f, "0{}.{}", int_part, frac_part)
1097 } else {
1098 write!(f, "{}.{}", int_part, frac_part)
1099 }
1100 } else {
1101 write!(f, "{:02}", s)
1102 }
1103 }
1104}
1105
1106fn format_duration_seconds(s: Decimal, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1108 if s.fract().is_zero() {
1109 write!(f, "{}", s.trunc())
1110 } else {
1111 let formatted = format!("{}", s);
1112 if let Some(dot_pos) = formatted.find('.') {
1113 let int_part = &formatted[..dot_pos];
1114 let frac_part = formatted[dot_pos + 1..].trim_end_matches('0');
1115 write!(f, "{}.{}", int_part, frac_part)
1116 } else {
1117 write!(f, "{}", s)
1118 }
1119 }
1120}
1121
1122#[cfg(test)]
1127mod tests {
1128 use super::*;
1129
1130 #[test]
1131 fn test_xml_value_string() {
1132 let v = XmlValue::string("hello");
1133 assert_eq!(v.type_code, XmlTypeCode::String);
1134 assert!(v.is_atomic());
1135 assert_eq!(v.to_string_value(), "hello");
1136 assert_eq!(v.as_string(), Some("hello"));
1137 }
1138
1139 #[test]
1140 fn test_xml_value_boolean() {
1141 let v = XmlValue::boolean(true);
1142 assert_eq!(v.type_code, XmlTypeCode::Boolean);
1143 assert_eq!(v.as_boolean(), Some(true));
1144 assert_eq!(v.to_string_value(), "true");
1145
1146 let v = XmlValue::boolean(false);
1147 assert_eq!(v.to_string_value(), "false");
1148 }
1149
1150 #[test]
1151 fn test_xml_value_decimal() {
1152 let v = XmlValue::decimal(Decimal::new(12345, 2));
1153 assert_eq!(v.type_code, XmlTypeCode::Decimal);
1154 assert_eq!(v.as_decimal(), Some(Decimal::new(12345, 2)));
1155 }
1156
1157 #[test]
1158 fn test_xml_value_integer() {
1159 let v = XmlValue::integer(BigInt::from(42));
1160 assert_eq!(v.type_code, XmlTypeCode::Integer);
1161 assert_eq!(v.as_integer(), Some(&BigInt::from(42)));
1162 assert_eq!(v.to_string_value(), "42");
1163 }
1164
1165 #[test]
1166 fn test_xml_value_double() {
1167 let v = XmlValue::double(2.5);
1168 assert_eq!(v.type_code, XmlTypeCode::Double);
1169 assert_eq!(v.as_double(), Some(2.5));
1170 }
1171
1172 #[test]
1173 fn test_xml_value_untyped() {
1174 let v = XmlValue::untyped("raw text");
1175 assert_eq!(v.type_code, XmlTypeCode::UntypedAtomic);
1176 assert!(v.is_untyped());
1177 assert_eq!(v.as_string(), Some("raw text"));
1178 }
1179
1180 #[test]
1181 fn test_xml_atomic_value_type_code() {
1182 assert_eq!(
1183 XmlAtomicValue::String("test".into()).type_code(),
1184 XmlTypeCode::String
1185 );
1186 assert_eq!(
1187 XmlAtomicValue::Boolean(true).type_code(),
1188 XmlTypeCode::Boolean
1189 );
1190 assert_eq!(
1191 XmlAtomicValue::Integer(BigInt::from(1)).type_code(),
1192 XmlTypeCode::Integer
1193 );
1194 }
1195
1196 #[test]
1197 fn test_timezone_display() {
1198 assert_eq!(TimezoneOffset::UTC.to_string(), "Z");
1199 assert_eq!(TimezoneOffset::from_hm(5, 30).to_string(), "+05:30");
1200 assert_eq!(TimezoneOffset::from_hm(-8, 0).to_string(), "-08:00");
1201 }
1202
1203 #[test]
1204 fn test_date_display() {
1205 let d = DateValue {
1206 year: 2024,
1207 month: 3,
1208 day: 15,
1209 timezone: Some(TimezoneOffset::UTC),
1210 };
1211 assert_eq!(d.to_string(), "2024-03-15Z");
1212 }
1213
1214 #[test]
1215 fn test_duration_display() {
1216 let d = DurationValue {
1217 negative: false,
1218 years: 1,
1219 months: 2,
1220 days: 3,
1221 hours: 4,
1222 minutes: 5,
1223 seconds: Decimal::new(65, 1), };
1225 assert!(d.to_string().starts_with("P1Y2M3DT4H5M"));
1227 assert!(d.to_string().contains("6.5S"));
1228 }
1229
1230 #[test]
1231 fn test_float_special_values() {
1232 assert_eq!(format!("{}", XmlAtomicValue::Float(f32::INFINITY)), "INF");
1233 assert_eq!(
1234 format!("{}", XmlAtomicValue::Float(f32::NEG_INFINITY)),
1235 "-INF"
1236 );
1237 assert_eq!(format!("{}", XmlAtomicValue::Float(f32::NAN)), "NaN");
1238 }
1239
1240 #[test]
1241 fn test_hex_binary_display() {
1242 let v = XmlAtomicValue::HexBinary(vec![0xDE, 0xAD, 0xBE, 0xEF]);
1243 assert_eq!(format!("{}", v), "DEADBEEF");
1244 }
1245
1246 #[test]
1247 fn test_base64_binary_display() {
1248 let v = XmlAtomicValue::Base64Binary(b"Hello".to_vec());
1249 assert_eq!(format!("{}", v), "SGVsbG8=");
1250 }
1251}