parent
05b8a5ac55
commit
10b1fa68f3
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,382 @@ |
|||||||
|
use super::parser::duration_lexical_rep; |
||||||
|
use super::parser::parse_value; |
||||||
|
use super::*; |
||||||
|
use crate::model::xsd::decimal::DecimalOverflowError; |
||||||
|
use std::cmp::Ordering; |
||||||
|
use std::convert::TryFrom; |
||||||
|
use std::fmt; |
||||||
|
use std::i64; |
||||||
|
use std::ops::Neg; |
||||||
|
use std::str::FromStr; |
||||||
|
use std::time::Duration as StdDuration; |
||||||
|
|
||||||
|
/// [XML Schema `duration` datatype](https://www.w3.org/TR/xmlschema11-2/#duration) implementation.
|
||||||
|
///
|
||||||
|
/// It stores the duration using the two components model suggested by the specification:
|
||||||
|
/// - a number of months encoded using a `i64`
|
||||||
|
/// - a number of seconds encoded using a `Decimal`
|
||||||
|
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash, Default)] |
||||||
|
pub struct Duration { |
||||||
|
months: i64, |
||||||
|
seconds: Decimal, |
||||||
|
} |
||||||
|
|
||||||
|
impl Duration { |
||||||
|
pub fn new(months: impl Into<i64>, seconds: impl Into<Decimal>) -> Self { |
||||||
|
Self { |
||||||
|
months: months.into(), |
||||||
|
seconds: seconds.into(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn from_le_bytes(bytes: [u8; 24]) -> Self { |
||||||
|
let mut months = [0; 8]; |
||||||
|
months.copy_from_slice(&bytes[0..8]); |
||||||
|
let mut seconds = [8; 16]; |
||||||
|
seconds.copy_from_slice(&bytes[8..24]); |
||||||
|
Self { |
||||||
|
months: i64::from_le_bytes(months), |
||||||
|
seconds: Decimal::from_le_bytes(seconds), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl TryFrom<StdDuration> for Duration { |
||||||
|
type Error = DecimalOverflowError; |
||||||
|
|
||||||
|
fn try_from(value: StdDuration) -> Result<Self, DecimalOverflowError> { |
||||||
|
Ok(Self { |
||||||
|
months: 0, |
||||||
|
seconds: Decimal::new( |
||||||
|
i128::try_from(value.as_nanos()).map_err(|_| DecimalOverflowError)?, |
||||||
|
9, |
||||||
|
)?, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FromStr for Duration { |
||||||
|
type Err = XsdParseError; |
||||||
|
|
||||||
|
fn from_str(input: &str) -> Result<Self, XsdParseError> { |
||||||
|
parse_value(duration_lexical_rep, input) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for Duration { |
||||||
|
#[allow(clippy::many_single_char_names)] |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
let mut ym = self.months; |
||||||
|
let mut ss = self.seconds; |
||||||
|
|
||||||
|
if ym < 0 || ss < 0.into() { |
||||||
|
write!(f, "-")?; |
||||||
|
ym = -ym; |
||||||
|
ss = -ss; |
||||||
|
} |
||||||
|
write!(f, "P")?; |
||||||
|
|
||||||
|
if ym == 0 && ss == 0.into() { |
||||||
|
return write!(f, "T0S"); |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
let y = ym / 12; |
||||||
|
let m = ym % 12; |
||||||
|
|
||||||
|
if y != 0 { |
||||||
|
if m != 0 { |
||||||
|
write!(f, "{}Y{}M", y, m)?; |
||||||
|
} else { |
||||||
|
write!(f, "{}Y", y)?; |
||||||
|
} |
||||||
|
} else if m != 0 || ss == 0.into() { |
||||||
|
write!(f, "{}M", m)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
let s_int = ss.as_i128(); |
||||||
|
let d = s_int / 86400; |
||||||
|
let h = (s_int % 86400) / 3600; |
||||||
|
let m = (s_int % 3600) / 60; |
||||||
|
let s = ss |
||||||
|
.checked_sub(Decimal::try_from(d * 86400 + h * 3600 + m * 60).unwrap()) |
||||||
|
.unwrap(); //could not fail
|
||||||
|
|
||||||
|
if d != 0 { |
||||||
|
write!(f, "{}D", d)?; |
||||||
|
} |
||||||
|
|
||||||
|
if h != 0 || m != 0 || s != 0.into() { |
||||||
|
write!(f, "T")?; |
||||||
|
if h != 0 { |
||||||
|
write!(f, "{}H", h)?; |
||||||
|
} |
||||||
|
if m != 0 { |
||||||
|
write!(f, "{}M", m)?; |
||||||
|
} |
||||||
|
if s != 0.into() { |
||||||
|
write!(f, "{}S", s)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialOrd for Duration { |
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
||||||
|
let first = DateTime::new(1969, 9, 1, 0, 0, 0.into(), None).ok()?; |
||||||
|
let first_result = first |
||||||
|
.checked_add_duration(*self)? |
||||||
|
.partial_cmp(&first.checked_add_duration(*other)?); |
||||||
|
let second = DateTime::new(1697, 2, 1, 0, 0, 0.into(), None).ok()?; |
||||||
|
let second_result = second |
||||||
|
.checked_add_duration(*self)? |
||||||
|
.partial_cmp(&second.checked_add_duration(*other)?); |
||||||
|
let third = DateTime::new(1903, 3, 1, 0, 0, 0.into(), None).ok()?; |
||||||
|
let third_result = third |
||||||
|
.checked_add_duration(*self)? |
||||||
|
.partial_cmp(&third.checked_add_duration(*other)?); |
||||||
|
let fourth = DateTime::new(1903, 7, 1, 0, 0, 0.into(), None).ok()?; |
||||||
|
let fourth_result = fourth |
||||||
|
.checked_add_duration(*self)? |
||||||
|
.partial_cmp(&fourth.checked_add_duration(*other)?); |
||||||
|
if first_result == second_result |
||||||
|
&& second_result == third_result |
||||||
|
&& third_result == fourth_result |
||||||
|
{ |
||||||
|
first_result |
||||||
|
} else { |
||||||
|
None |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Duration { |
||||||
|
/// [fn:years-from-duration](https://www.w3.org/TR/xpath-functions/#func-years-from-duration)
|
||||||
|
pub fn years(&self) -> i64 { |
||||||
|
self.months / 12 |
||||||
|
} |
||||||
|
|
||||||
|
/// [fn:months-from-duration](https://www.w3.org/TR/xpath-functions/#func-months-from-duration)
|
||||||
|
pub fn months(&self) -> i64 { |
||||||
|
self.months % 12 |
||||||
|
} |
||||||
|
|
||||||
|
/// [fn:days-from-duration](https://www.w3.org/TR/xpath-functions/#func-days-from-duration)
|
||||||
|
pub fn days(&self) -> i64 { |
||||||
|
(self.seconds.as_i128() / 86400) as i64 |
||||||
|
} |
||||||
|
|
||||||
|
/// [fn:hours-from-duration](https://www.w3.org/TR/xpath-functions/#func-hours-from-duration)
|
||||||
|
pub fn hours(&self) -> i64 { |
||||||
|
((self.seconds.as_i128() % 86400) / 3600) as i64 |
||||||
|
} |
||||||
|
|
||||||
|
/// [fn:minutes-from-duration](https://www.w3.org/TR/xpath-functions/#func-minutes-from-duration)
|
||||||
|
pub fn minutes(&self) -> i64 { |
||||||
|
((self.seconds.as_i128() % 3600) / 60) as i64 |
||||||
|
} |
||||||
|
|
||||||
|
/// [fn:seconds-from-duration](https://www.w3.org/TR/xpath-functions/#func-seconds-from-duration)
|
||||||
|
pub fn seconds(&self) -> Decimal { |
||||||
|
self.seconds.checked_rem(60).unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
pub(super) fn all_months(&self) -> i64 { |
||||||
|
self.months |
||||||
|
} |
||||||
|
|
||||||
|
pub(super) fn all_seconds(&self) -> Decimal { |
||||||
|
self.seconds |
||||||
|
} |
||||||
|
|
||||||
|
pub fn to_le_bytes(&self) -> [u8; 24] { |
||||||
|
let mut bytes = [0; 24]; |
||||||
|
bytes[0..8].copy_from_slice(&self.months.to_le_bytes()); |
||||||
|
bytes[8..24].copy_from_slice(&self.seconds.to_le_bytes()); |
||||||
|
bytes |
||||||
|
} |
||||||
|
|
||||||
|
/// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions/#func-add-yearMonthDurations) and [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions/#func-add-dayTimeDurations)
|
||||||
|
pub fn checked_add(&self, rhs: impl Into<Self>) -> Option<Self> { |
||||||
|
let rhs = rhs.into(); |
||||||
|
Some(Self { |
||||||
|
months: self.months.checked_add(rhs.months)?, |
||||||
|
seconds: self.seconds.checked_add(rhs.seconds)?, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions/#func-subtract-yearMonthDurations) and [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions/#func-subtract-dayTimeDurations)
|
||||||
|
pub fn checked_sub(&self, rhs: impl Into<Self>) -> Option<Self> { |
||||||
|
let rhs = rhs.into(); |
||||||
|
Some(Self { |
||||||
|
months: self.months.checked_sub(rhs.months)?, |
||||||
|
seconds: self.seconds.checked_sub(rhs.seconds)?, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Neg for Duration { |
||||||
|
type Output = Self; |
||||||
|
|
||||||
|
fn neg(self) -> Self { |
||||||
|
Self { |
||||||
|
months: self.months.neg(), |
||||||
|
seconds: self.seconds.neg(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn from_str() { |
||||||
|
let min = Duration::new( |
||||||
|
i64::MIN + 1, |
||||||
|
decimal::MIN.checked_add(decimal::STEP).unwrap(), |
||||||
|
); |
||||||
|
let max = Duration::new(i64::MAX, decimal::MAX); |
||||||
|
|
||||||
|
assert_eq!(Duration::from_str("P1Y").unwrap().to_string(), "P1Y"); |
||||||
|
assert_eq!(Duration::from_str("P1M").unwrap().to_string(), "P1M"); |
||||||
|
assert_eq!(Duration::from_str("P1D").unwrap().to_string(), "P1D"); |
||||||
|
assert_eq!(Duration::from_str("PT1H").unwrap().to_string(), "PT1H"); |
||||||
|
assert_eq!(Duration::from_str("PT1M").unwrap().to_string(), "PT1M"); |
||||||
|
assert_eq!(Duration::from_str("PT1.1S").unwrap().to_string(), "PT1.1S"); |
||||||
|
assert_eq!(Duration::from_str("-P1Y").unwrap().to_string(), "-P1Y"); |
||||||
|
assert_eq!(Duration::from_str("-P1M").unwrap().to_string(), "-P1M"); |
||||||
|
assert_eq!(Duration::from_str("-P1D").unwrap().to_string(), "-P1D"); |
||||||
|
assert_eq!(Duration::from_str("-PT1H").unwrap().to_string(), "-PT1H"); |
||||||
|
assert_eq!(Duration::from_str("-PT1M").unwrap().to_string(), "-PT1M"); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("-PT1.1S").unwrap().to_string(), |
||||||
|
"-PT1.1S" |
||||||
|
); |
||||||
|
assert_eq!(Duration::from_str(&max.to_string()).unwrap(), max); |
||||||
|
assert_eq!(Duration::from_str(&min.to_string()).unwrap(), min); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn equals() { |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P1Y").unwrap(), |
||||||
|
Duration::from_str("P12M").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("PT24H").unwrap(), |
||||||
|
Duration::from_str("P1D").unwrap() |
||||||
|
); |
||||||
|
assert_ne!( |
||||||
|
Duration::from_str("P1Y").unwrap(), |
||||||
|
Duration::from_str("P365D").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P0Y").unwrap(), |
||||||
|
Duration::from_str("P0D").unwrap() |
||||||
|
); |
||||||
|
assert_ne!( |
||||||
|
Duration::from_str("P1Y").unwrap(), |
||||||
|
Duration::from_str("P365D").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2Y").unwrap(), |
||||||
|
Duration::from_str("P24M").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P10D").unwrap(), |
||||||
|
Duration::from_str("PT240H").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2Y0M0DT0H0M0S").unwrap(), |
||||||
|
Duration::from_str("P24M").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P0Y0M10D").unwrap(), |
||||||
|
Duration::from_str("PT240H").unwrap() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn years() { |
||||||
|
assert_eq!(Duration::from_str("P20Y15M").unwrap().years(), 21); |
||||||
|
assert_eq!(Duration::from_str("-P15M").unwrap().years(), -1); |
||||||
|
assert_eq!(Duration::from_str("-P2DT15H").unwrap().years(), 0); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn months() { |
||||||
|
assert_eq!(Duration::from_str("P20Y15M").unwrap().months(), 3); |
||||||
|
assert_eq!(Duration::from_str("-P20Y18M").unwrap().months(), -6); |
||||||
|
assert_eq!(Duration::from_str("-P2DT15H0M0S").unwrap().months(), 0); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn days() { |
||||||
|
assert_eq!(Duration::from_str("P3DT10H").unwrap().days(), 3); |
||||||
|
assert_eq!(Duration::from_str("P3DT55H").unwrap().days(), 5); |
||||||
|
assert_eq!(Duration::from_str("P3Y5M").unwrap().days(), 0); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn hours() { |
||||||
|
assert_eq!(Duration::from_str("P3DT10H").unwrap().hours(), 10); |
||||||
|
assert_eq!(Duration::from_str("P3DT12H32M12S").unwrap().hours(), 12); |
||||||
|
assert_eq!(Duration::from_str("PT123H").unwrap().hours(), 3); |
||||||
|
assert_eq!(Duration::from_str("-P3DT10H").unwrap().hours(), -10); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn minutes() { |
||||||
|
assert_eq!(Duration::from_str("P3DT10H").unwrap().minutes(), 0); |
||||||
|
assert_eq!(Duration::from_str("-P5DT12H30M").unwrap().minutes(), -30); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn seconds() { |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P3DT10H12.5S").unwrap().seconds(), |
||||||
|
Decimal::from_str("12.5").unwrap() |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("-PT256S").unwrap().seconds(), |
||||||
|
Decimal::from_str("-16.0").unwrap() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn add() { |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2Y11M") |
||||||
|
.unwrap() |
||||||
|
.checked_add(Duration::from_str("P3Y3M").unwrap()), |
||||||
|
Some(Duration::from_str("P6Y2M").unwrap()) |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2DT12H5M") |
||||||
|
.unwrap() |
||||||
|
.checked_add(Duration::from_str("P5DT12H").unwrap()), |
||||||
|
Some(Duration::from_str("P8DT5M").unwrap()) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn sub() { |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2Y11M") |
||||||
|
.unwrap() |
||||||
|
.checked_sub(Duration::from_str("P3Y3M").unwrap()), |
||||||
|
Some(Duration::from_str("-P4M").unwrap()) |
||||||
|
); |
||||||
|
assert_eq!( |
||||||
|
Duration::from_str("P2DT12H") |
||||||
|
.unwrap() |
||||||
|
.checked_sub(Duration::from_str("P1DT10H30M").unwrap()), |
||||||
|
Some(Duration::from_str("P1DT1H30M").unwrap()) |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,3 +1,9 @@ |
|||||||
mod decimal; |
pub mod date_time; |
||||||
|
pub mod decimal; |
||||||
|
mod duration; |
||||||
|
mod parser; |
||||||
|
|
||||||
|
pub use self::date_time::{Date, DateTime, Time}; |
||||||
pub use self::decimal::Decimal; |
pub use self::decimal::Decimal; |
||||||
|
pub use self::duration::Duration; |
||||||
|
pub use self::parser::XsdParseError; |
||||||
|
@ -0,0 +1,462 @@ |
|||||||
|
use super::*; |
||||||
|
use nom::branch::alt; |
||||||
|
use nom::bytes::complete::{tag, take_while}; |
||||||
|
use nom::character::complete::{char, digit0, digit1}; |
||||||
|
use nom::combinator::{map, opt, recognize}; |
||||||
|
use nom::error::{ErrorKind, ParseError}; |
||||||
|
use nom::multi::many1; |
||||||
|
use nom::sequence::{preceded, terminated, tuple}; |
||||||
|
use nom::Err; |
||||||
|
use nom::{IResult, Needed}; |
||||||
|
use std::str::FromStr; |
||||||
|
|
||||||
|
use super::date_time::DateTimeError; |
||||||
|
use super::decimal::ParseDecimalError; |
||||||
|
use crate::model::xsd::date_time::TimezoneOffset; |
||||||
|
use nom::bytes::streaming::take_while_m_n; |
||||||
|
use rand::distributions::weighted::alias_method::Weight; |
||||||
|
use std::error::Error; |
||||||
|
use std::fmt; |
||||||
|
use std::num::ParseIntError; |
||||||
|
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub struct XsdParseError { |
||||||
|
kind: XsdParseErrorKind, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
enum XsdParseErrorKind { |
||||||
|
NomKind(ErrorKind), |
||||||
|
NomChar(char), |
||||||
|
MissingData(Needed), |
||||||
|
TooMuchData { count: usize }, |
||||||
|
Overflow, |
||||||
|
ParseInt(ParseIntError), |
||||||
|
ParseDecimal(ParseDecimalError), |
||||||
|
OutOfIntegerRange { value: u8, min: u8, max: u8 }, |
||||||
|
DateTime(DateTimeError), |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for XsdParseError { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
match &self.kind { |
||||||
|
XsdParseErrorKind::NomKind(kind) => { |
||||||
|
write!(f, "Invalid XML Schema value: {}", kind.description()) |
||||||
|
} |
||||||
|
XsdParseErrorKind::NomChar(c) => { |
||||||
|
write!(f, "Unexpected character in XML Schema value: '{}'", c) |
||||||
|
} |
||||||
|
XsdParseErrorKind::MissingData(Needed::Unknown) => { |
||||||
|
write!(f, "Too small XML Schema value") |
||||||
|
} |
||||||
|
XsdParseErrorKind::MissingData(Needed::Size(size)) => { |
||||||
|
write!(f, "Too small XML Schema value: missing {} chars", size) |
||||||
|
} |
||||||
|
XsdParseErrorKind::TooMuchData { count } => { |
||||||
|
write!(f, "Too long XML Schema value: {} extra chars", count) |
||||||
|
} |
||||||
|
XsdParseErrorKind::Overflow => write!(f, "Computation overflow or undeflow"), |
||||||
|
XsdParseErrorKind::ParseInt(error) => { |
||||||
|
write!(f, "Error while parsing integer: {}", error) |
||||||
|
} |
||||||
|
XsdParseErrorKind::ParseDecimal(error) => { |
||||||
|
write!(f, "Error while parsing decimal: {}", error) |
||||||
|
} |
||||||
|
XsdParseErrorKind::OutOfIntegerRange { value, min, max } => write!( |
||||||
|
f, |
||||||
|
"The integer {} is not between {} and {}", |
||||||
|
value, min, max |
||||||
|
), |
||||||
|
XsdParseErrorKind::DateTime(error) => error.fmt(f), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Error for XsdParseError { |
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> { |
||||||
|
match &self.kind { |
||||||
|
XsdParseErrorKind::ParseInt(error) => Some(error), |
||||||
|
XsdParseErrorKind::ParseDecimal(error) => Some(error), |
||||||
|
XsdParseErrorKind::DateTime(error) => Some(error), |
||||||
|
_ => None, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl ParseError<&str> for XsdParseError { |
||||||
|
fn from_error_kind(_input: &str, kind: ErrorKind) -> Self { |
||||||
|
Self { |
||||||
|
kind: XsdParseErrorKind::NomKind(kind), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn append(_input: &str, _kind: ErrorKind, other: Self) -> Self { |
||||||
|
other |
||||||
|
} |
||||||
|
|
||||||
|
fn from_char(_input: &str, c: char) -> Self { |
||||||
|
Self { |
||||||
|
kind: XsdParseErrorKind::NomChar(c), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn or(self, other: Self) -> Self { |
||||||
|
other |
||||||
|
} |
||||||
|
|
||||||
|
fn add_context(_input: &str, _ctx: &'static str, other: Self) -> Self { |
||||||
|
other |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<ParseIntError> for XsdParseError { |
||||||
|
fn from(error: ParseIntError) -> Self { |
||||||
|
XsdParseError { |
||||||
|
kind: XsdParseErrorKind::ParseInt(error), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<ParseDecimalError> for XsdParseError { |
||||||
|
fn from(error: ParseDecimalError) -> Self { |
||||||
|
XsdParseError { |
||||||
|
kind: XsdParseErrorKind::ParseDecimal(error), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<DateTimeError> for XsdParseError { |
||||||
|
fn from(error: DateTimeError) -> Self { |
||||||
|
XsdParseError { |
||||||
|
kind: XsdParseErrorKind::DateTime(error), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<Err<XsdParseError>> for XsdParseError { |
||||||
|
fn from(err: Err<XsdParseError>) -> Self { |
||||||
|
match err { |
||||||
|
Err::Incomplete(needed) => XsdParseError { |
||||||
|
kind: XsdParseErrorKind::MissingData(needed), |
||||||
|
}, |
||||||
|
Err::Error(e) | Err::Failure(e) => e, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type XsdResult<'a, T> = IResult<&'a str, T, XsdParseError>; |
||||||
|
|
||||||
|
const OVERFLOW_ERROR: XsdParseError = XsdParseError { |
||||||
|
kind: XsdParseErrorKind::Overflow, |
||||||
|
}; |
||||||
|
|
||||||
|
pub fn parse_value<'a, T>( |
||||||
|
f: impl Fn(&'a str) -> XsdResult<'a, T>, |
||||||
|
input: &'a str, |
||||||
|
) -> Result<T, XsdParseError> { |
||||||
|
let (left, result) = f(input)?; |
||||||
|
if left.is_empty() { |
||||||
|
Ok(result) |
||||||
|
} else { |
||||||
|
Err(XsdParseError { |
||||||
|
kind: XsdParseErrorKind::TooMuchData { count: left.len() }, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//TODO: check every computation
|
||||||
|
|
||||||
|
// [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
|
||||||
|
fn du_year_frag(input: &str) -> XsdResult<i64> { |
||||||
|
terminated(unsigned_no_decimal_pt_numeral, char('Y'))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
|
||||||
|
fn du_month_frag(input: &str) -> XsdResult<i64> { |
||||||
|
terminated(unsigned_no_decimal_pt_numeral, char('M'))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
|
||||||
|
fn du_day_frag(input: &str) -> XsdResult<i64> { |
||||||
|
terminated(unsigned_no_decimal_pt_numeral, char('D'))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
|
||||||
|
fn du_hour_frag(input: &str) -> XsdResult<i64> { |
||||||
|
terminated(unsigned_no_decimal_pt_numeral, char('H'))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
|
||||||
|
fn du_minute_frag(input: &str) -> XsdResult<i64> { |
||||||
|
terminated(unsigned_no_decimal_pt_numeral, char('M'))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
|
||||||
|
fn du_second_frag(input: &str) -> XsdResult<Decimal> { |
||||||
|
terminated( |
||||||
|
map_res( |
||||||
|
recognize(tuple((digit0, opt(preceded(char('.'), digit0))))), |
||||||
|
Decimal::from_str, |
||||||
|
), |
||||||
|
char('S'), |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
|
||||||
|
fn du_year_month_frag(input: &str) -> XsdResult<i64> { |
||||||
|
alt(( |
||||||
|
map(tuple((du_year_frag, opt(du_month_frag))), |(y, m)| { |
||||||
|
12 * y + m.unwrap_or(0) |
||||||
|
}), |
||||||
|
du_month_frag, |
||||||
|
))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
|
||||||
|
fn du_time_frag(input: &str) -> XsdResult<Decimal> { |
||||||
|
preceded( |
||||||
|
char('T'), |
||||||
|
alt(( |
||||||
|
map_res( |
||||||
|
tuple((du_hour_frag, opt(du_minute_frag), opt(du_second_frag))), |
||||||
|
|(h, m, s)| { |
||||||
|
Decimal::from(3600 * h + 60 * m.unwrap_or(0)) |
||||||
|
.checked_add(s.unwrap_or_else(Decimal::default)) |
||||||
|
.ok_or(OVERFLOW_ERROR) |
||||||
|
}, |
||||||
|
), |
||||||
|
map_res(tuple((du_minute_frag, opt(du_second_frag))), |(m, s)| { |
||||||
|
Decimal::from(m * 60) |
||||||
|
.checked_add(s.unwrap_or_else(Decimal::default)) |
||||||
|
.ok_or(OVERFLOW_ERROR) |
||||||
|
}), |
||||||
|
du_second_frag, |
||||||
|
)), |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
|
||||||
|
fn du_day_time_frag(input: &str) -> XsdResult<Decimal> { |
||||||
|
alt(( |
||||||
|
map_res(tuple((du_day_frag, opt(du_time_frag))), |(d, t)| { |
||||||
|
Decimal::from(d) |
||||||
|
.checked_mul(Decimal::from(86400)) |
||||||
|
.ok_or(OVERFLOW_ERROR)? |
||||||
|
.checked_add(t.unwrap_or_else(Decimal::default)) |
||||||
|
.ok_or(OVERFLOW_ERROR) |
||||||
|
}), |
||||||
|
du_time_frag, |
||||||
|
))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
|
||||||
|
pub fn duration_lexical_rep(input: &str) -> XsdResult<Duration> { |
||||||
|
map( |
||||||
|
tuple(( |
||||||
|
opt(char('-')), |
||||||
|
preceded( |
||||||
|
char('P'), |
||||||
|
alt(( |
||||||
|
map( |
||||||
|
tuple((du_year_month_frag, opt(du_day_time_frag))), |
||||||
|
|(y, d)| Duration::new(y, d.unwrap_or_else(Decimal::default)), |
||||||
|
), |
||||||
|
map(du_day_time_frag, |d| Duration::new(0, d)), |
||||||
|
)), |
||||||
|
), |
||||||
|
)), |
||||||
|
|(sign, duration)| { |
||||||
|
if sign == Some('-') { |
||||||
|
-duration |
||||||
|
} else { |
||||||
|
duration |
||||||
|
} |
||||||
|
}, |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [16] dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
|
||||||
|
pub fn date_time_lexical_rep(input: &str) -> XsdResult<DateTime> { |
||||||
|
map_res( |
||||||
|
tuple(( |
||||||
|
year_frag, |
||||||
|
char('-'), |
||||||
|
month_frag, |
||||||
|
char('-'), |
||||||
|
day_frag, |
||||||
|
char('T'), |
||||||
|
alt(( |
||||||
|
map( |
||||||
|
tuple((hour_frag, char(':'), minute_frag, char(':'), second_frag)), |
||||||
|
|(h, _, m, _, s)| (h, m, s), |
||||||
|
), |
||||||
|
end_of_day_frag, |
||||||
|
)), |
||||||
|
opt(timezone_frag), |
||||||
|
)), |
||||||
|
|(year, _, month, _, day, _, (hours, minutes, seconds), timezone)| { |
||||||
|
DateTime::new(year, month, day, hours, minutes, seconds, timezone) |
||||||
|
}, |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [17] timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
|
||||||
|
pub fn time_lexical_rep(input: &str) -> XsdResult<Time> { |
||||||
|
map_res( |
||||||
|
tuple(( |
||||||
|
alt(( |
||||||
|
map( |
||||||
|
tuple((hour_frag, char(':'), minute_frag, char(':'), second_frag)), |
||||||
|
|(h, _, m, _, s)| (h, m, s), |
||||||
|
), |
||||||
|
end_of_day_frag, |
||||||
|
)), |
||||||
|
opt(timezone_frag), |
||||||
|
)), |
||||||
|
|((hours, minutes, seconds), timezone)| Time::new(hours, minutes, seconds, timezone), |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [18] dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag? Constraint: Day-of-month Representations
|
||||||
|
pub fn date_lexical_rep(input: &str) -> XsdResult<Date> { |
||||||
|
map_res( |
||||||
|
tuple(( |
||||||
|
year_frag, |
||||||
|
char('-'), |
||||||
|
month_frag, |
||||||
|
char('-'), |
||||||
|
day_frag, |
||||||
|
opt(timezone_frag), |
||||||
|
)), |
||||||
|
|(year, _, month, _, day, timezone)| Date::new(year, month, day, timezone), |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [46] unsignedNoDecimalPtNumeral ::= digit+
|
||||||
|
fn unsigned_no_decimal_pt_numeral(input: &str) -> XsdResult<i64> { |
||||||
|
map_res(digit1, |i| i64::from_str(i))(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [56] yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
|
||||||
|
fn year_frag(input: &str) -> XsdResult<i64> { |
||||||
|
map_res( |
||||||
|
recognize(tuple(( |
||||||
|
opt(char('-')), |
||||||
|
take_while_m_n(4, usize::MAX, |c: char| c.is_ascii_digit()), |
||||||
|
))), |
||||||
|
i64::from_str, |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [57] monthFrag ::= ('0' [1-9]) | ('1' [0-2])
|
||||||
|
fn month_frag(input: &str) -> XsdResult<u8> { |
||||||
|
map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |v| { |
||||||
|
parsed_u8_range(v, 1, 12) |
||||||
|
})(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [58] dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
|
||||||
|
fn day_frag(input: &str) -> XsdResult<u8> { |
||||||
|
map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |v| { |
||||||
|
parsed_u8_range(v, 1, 31) |
||||||
|
})(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [59] hourFrag ::= ([01] digit) | ('2' [0-3])
|
||||||
|
fn hour_frag(input: &str) -> XsdResult<u8> { |
||||||
|
map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |v| { |
||||||
|
parsed_u8_range(v, 0, 23) |
||||||
|
})(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [60] minuteFrag ::= [0-5] digit
|
||||||
|
fn minute_frag(input: &str) -> XsdResult<u8> { |
||||||
|
map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |v| { |
||||||
|
parsed_u8_range(v, 0, 59) |
||||||
|
})(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [61] secondFrag ::= ([0-5] digit) ('.' digit+)?
|
||||||
|
fn second_frag(input: &str) -> XsdResult<Decimal> { |
||||||
|
map_res( |
||||||
|
recognize(tuple(( |
||||||
|
take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |
||||||
|
opt(preceded( |
||||||
|
char('.'), |
||||||
|
take_while(|c: char| c.is_ascii_digit()), |
||||||
|
)), |
||||||
|
))), |
||||||
|
|v| { |
||||||
|
let value = Decimal::from_str(v)?; |
||||||
|
if Decimal::from(0) <= value && value < Decimal::from(60) { |
||||||
|
Ok(value) |
||||||
|
} else { |
||||||
|
Err(XsdParseError { |
||||||
|
kind: XsdParseErrorKind::OutOfIntegerRange { |
||||||
|
value: value.as_i128() as u8, |
||||||
|
min: 0, |
||||||
|
max: 60, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
}, |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [62] endOfDayFrag ::= '24:00:00' ('.' '0'+)?
|
||||||
|
fn end_of_day_frag(input: &str) -> XsdResult<(u8, u8, Decimal)> { |
||||||
|
map( |
||||||
|
recognize(tuple(( |
||||||
|
tag("24:00:00"), |
||||||
|
opt(preceded(char('.'), many1(char('0')))), |
||||||
|
))), |
||||||
|
|_| (24, 0, 0.into()), |
||||||
|
)(input) |
||||||
|
} |
||||||
|
|
||||||
|
// [63] timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
|
||||||
|
fn timezone_frag(input: &str) -> XsdResult<TimezoneOffset> { |
||||||
|
alt(( |
||||||
|
map(char('Z'), |_| TimezoneOffset::utc()), |
||||||
|
map( |
||||||
|
tuple(( |
||||||
|
alt((map(char('+'), |_| 1), map(char('-'), |_| -1))), |
||||||
|
alt(( |
||||||
|
map( |
||||||
|
tuple(( |
||||||
|
map_res(take_while_m_n(2, 2, |c: char| c.is_ascii_digit()), |v| { |
||||||
|
parsed_u8_range(v, 0, 13) |
||||||
|
}), |
||||||
|
char(':'), |
||||||
|
minute_frag, |
||||||
|
)), |
||||||
|
|(hours, _, minutes)| i16::from(hours) * 60 + i16::from(minutes), |
||||||
|
), |
||||||
|
map(tag("14:00"), |_| 14 * 60), |
||||||
|
)), |
||||||
|
)), |
||||||
|
|(sign, value)| TimezoneOffset::new(sign * value), |
||||||
|
), |
||||||
|
))(input) |
||||||
|
} |
||||||
|
|
||||||
|
fn parsed_u8_range(input: &str, min: u8, max: u8) -> Result<u8, XsdParseError> { |
||||||
|
let value = u8::from_str(input)?; |
||||||
|
if min <= value && value <= max { |
||||||
|
Ok(value) |
||||||
|
} else { |
||||||
|
Err(XsdParseError { |
||||||
|
kind: XsdParseErrorKind::OutOfIntegerRange { value, min, max }, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn map_res<'a, O1, O2, E2: Into<XsdParseError>>( |
||||||
|
first: impl Fn(&'a str) -> XsdResult<'a, O1>, |
||||||
|
second: impl Fn(O1) -> Result<O2, E2>, |
||||||
|
) -> impl Fn(&'a str) -> XsdResult<'a, O2> { |
||||||
|
move |input| { |
||||||
|
let (input, o1) = first(input)?; |
||||||
|
Ok((input, second(o1).map_err(|e| Err::Error(e.into()))?)) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue