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::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