parent
b22e74379a
commit
077c1fc1a8
File diff suppressed because it is too large
Load Diff
@ -1,626 +0,0 @@ |
||||
use super::date_time::{DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, TimezoneOffset}; |
||||
use super::decimal::ParseDecimalError; |
||||
use super::duration::{DayTimeDuration, YearMonthDuration}; |
||||
use super::*; |
||||
use std::error::Error; |
||||
use std::fmt; |
||||
use std::num::ParseIntError; |
||||
use std::str::FromStr; |
||||
|
||||
/// A parsing error
|
||||
#[derive(Debug, Clone)] |
||||
pub struct XsdParseError { |
||||
kind: XsdParseErrorKind, |
||||
} |
||||
|
||||
#[derive(Debug, Clone)] |
||||
enum XsdParseErrorKind { |
||||
ParseInt(ParseIntError), |
||||
ParseDecimal(ParseDecimalError), |
||||
DateTime(DateTimeError), |
||||
Message(&'static str), |
||||
} |
||||
|
||||
const OVERFLOW_ERROR: XsdParseError = XsdParseError { |
||||
kind: XsdParseErrorKind::Message("Overflow error"), |
||||
}; |
||||
|
||||
impl fmt::Display for XsdParseError { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
match &self.kind { |
||||
XsdParseErrorKind::ParseInt(error) => { |
||||
write!(f, "Error while parsing integer: {error}") |
||||
} |
||||
XsdParseErrorKind::ParseDecimal(error) => { |
||||
write!(f, "Error while parsing decimal: {error}") |
||||
} |
||||
XsdParseErrorKind::DateTime(error) => error.fmt(f), |
||||
XsdParseErrorKind::Message(msg) => write!(f, "{msg}"), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl XsdParseError { |
||||
const fn msg(message: &'static str) -> Self { |
||||
Self { |
||||
kind: XsdParseErrorKind::Message(message), |
||||
} |
||||
} |
||||
} |
||||
|
||||
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), |
||||
XsdParseErrorKind::Message(_) => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ParseIntError> for XsdParseError { |
||||
fn from(error: ParseIntError) -> Self { |
||||
Self { |
||||
kind: XsdParseErrorKind::ParseInt(error), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<ParseDecimalError> for XsdParseError { |
||||
fn from(error: ParseDecimalError) -> Self { |
||||
Self { |
||||
kind: XsdParseErrorKind::ParseDecimal(error), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<DateTimeError> for XsdParseError { |
||||
fn from(error: DateTimeError) -> Self { |
||||
Self { |
||||
kind: XsdParseErrorKind::DateTime(error), |
||||
} |
||||
} |
||||
} |
||||
|
||||
// [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
|
||||
// [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
|
||||
// [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
|
||||
// [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
|
||||
// [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
|
||||
// [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
|
||||
// [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
|
||||
// [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
|
||||
// [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
|
||||
// [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
|
||||
struct DurationParts { |
||||
year_month: Option<i64>, |
||||
day_time: Option<Decimal>, |
||||
} |
||||
|
||||
fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { |
||||
// States
|
||||
const START: u32 = 0; |
||||
const AFTER_YEAR: u32 = 1; |
||||
const AFTER_MONTH: u32 = 2; |
||||
const AFTER_DAY: u32 = 3; |
||||
const AFTER_T: u32 = 4; |
||||
const AFTER_HOUR: u32 = 5; |
||||
const AFTER_MINUTE: u32 = 6; |
||||
const AFTER_SECOND: u32 = 7; |
||||
|
||||
let (is_negative, input) = if let Some(left) = input.strip_prefix('-') { |
||||
(true, left) |
||||
} else { |
||||
(false, input) |
||||
}; |
||||
let mut input = expect_char(input, 'P', "Durations must start with 'P'")?; |
||||
let mut state = START; |
||||
let mut year_month: Option<i64> = None; |
||||
let mut day_time: Option<Decimal> = None; |
||||
while !input.is_empty() { |
||||
if let Some(left) = input.strip_prefix('T') { |
||||
if state >= AFTER_T { |
||||
return Err(XsdParseError::msg("Duplicated time separator 'T'")); |
||||
} |
||||
state = AFTER_T; |
||||
input = left; |
||||
} else { |
||||
let (number_str, left) = decimal_prefix(input); |
||||
match left.chars().next() { |
||||
Some('Y') if state < AFTER_YEAR => { |
||||
year_month = Some( |
||||
year_month |
||||
.unwrap_or_default() |
||||
.checked_add( |
||||
apply_i64_neg(i64::from_str(number_str)?, is_negative)? |
||||
.checked_mul(12) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_YEAR; |
||||
} |
||||
Some('M') if state < AFTER_MONTH => { |
||||
year_month = Some( |
||||
year_month |
||||
.unwrap_or_default() |
||||
.checked_add(apply_i64_neg(i64::from_str(number_str)?, is_negative)?) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_MONTH; |
||||
} |
||||
Some('D') if state < AFTER_DAY => { |
||||
if number_str.contains('.') { |
||||
return Err(XsdParseError::msg( |
||||
"Decimal numbers are not allowed for days", |
||||
)); |
||||
} |
||||
day_time = Some( |
||||
day_time |
||||
.unwrap_or_default() |
||||
.checked_add( |
||||
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? |
||||
.checked_mul(86400) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_DAY; |
||||
} |
||||
Some('H') if state == AFTER_T => { |
||||
if number_str.contains('.') { |
||||
return Err(XsdParseError::msg( |
||||
"Decimal numbers are not allowed for hours", |
||||
)); |
||||
} |
||||
day_time = Some( |
||||
day_time |
||||
.unwrap_or_default() |
||||
.checked_add( |
||||
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? |
||||
.checked_mul(3600) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_HOUR; |
||||
} |
||||
Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => { |
||||
if number_str.contains('.') { |
||||
return Err(XsdParseError::msg( |
||||
"Decimal numbers are not allowed for minutes", |
||||
)); |
||||
} |
||||
day_time = Some( |
||||
day_time |
||||
.unwrap_or_default() |
||||
.checked_add( |
||||
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? |
||||
.checked_mul(60) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_MINUTE; |
||||
} |
||||
Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => { |
||||
day_time = Some( |
||||
day_time |
||||
.unwrap_or_default() |
||||
.checked_add(apply_decimal_neg( |
||||
Decimal::from_str(number_str)?, |
||||
is_negative, |
||||
)?) |
||||
.ok_or(OVERFLOW_ERROR)?, |
||||
); |
||||
state = AFTER_SECOND; |
||||
} |
||||
Some(_) => return Err(XsdParseError::msg("Unexpected type character")), |
||||
None => { |
||||
return Err(XsdParseError::msg( |
||||
"Numbers in durations must be followed by a type character", |
||||
)) |
||||
} |
||||
} |
||||
input = &left[1..]; |
||||
} |
||||
} |
||||
|
||||
Ok(( |
||||
DurationParts { |
||||
year_month, |
||||
day_time, |
||||
}, |
||||
input, |
||||
)) |
||||
} |
||||
|
||||
fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, XsdParseError> { |
||||
if is_negative { |
||||
value.checked_neg().ok_or(OVERFLOW_ERROR) |
||||
} else { |
||||
Ok(value) |
||||
} |
||||
} |
||||
|
||||
fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, XsdParseError> { |
||||
if is_negative { |
||||
value.checked_neg().ok_or(OVERFLOW_ERROR) |
||||
} else { |
||||
Ok(value) |
||||
} |
||||
} |
||||
|
||||
pub fn parse_duration(input: &str) -> Result<Duration, XsdParseError> { |
||||
let parts = ensure_complete(input, duration_parts)?; |
||||
if parts.year_month.is_none() && parts.day_time.is_none() { |
||||
return Err(XsdParseError::msg("Empty duration")); |
||||
} |
||||
Ok(Duration::new( |
||||
parts.year_month.unwrap_or(0), |
||||
parts.day_time.unwrap_or_default(), |
||||
)) |
||||
} |
||||
|
||||
pub fn parse_year_month_duration(input: &str) -> Result<YearMonthDuration, XsdParseError> { |
||||
let parts = ensure_complete(input, duration_parts)?; |
||||
if parts.day_time.is_some() { |
||||
return Err(XsdParseError::msg( |
||||
"There must not be any day or time component in a yearMonthDuration", |
||||
)); |
||||
} |
||||
Ok(YearMonthDuration::new(parts.year_month.ok_or( |
||||
XsdParseError::msg("No year and month values found"), |
||||
)?)) |
||||
} |
||||
|
||||
pub fn parse_day_time_duration(input: &str) -> Result<DayTimeDuration, XsdParseError> { |
||||
let parts = ensure_complete(input, duration_parts)?; |
||||
if parts.year_month.is_some() { |
||||
return Err(XsdParseError::msg( |
||||
"There must not be any year or month component in a dayTimeDuration", |
||||
)); |
||||
} |
||||
Ok(DayTimeDuration::new(parts.day_time.ok_or( |
||||
XsdParseError::msg("No day or time values found"), |
||||
)?)) |
||||
} |
||||
|
||||
// [16] dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
|
||||
fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), XsdParseError> { |
||||
let (year, input) = year_frag(input)?; |
||||
let input = expect_char(input, '-', "The year and month must be separated by '-'")?; |
||||
let (month, input) = month_frag(input)?; |
||||
let input = expect_char(input, '-', "The month and day must be separated by '-'")?; |
||||
let (day, input) = day_frag(input)?; |
||||
let input = expect_char(input, 'T', "The date and time must be separated by 'T'")?; |
||||
let (hour, input) = hour_frag(input)?; |
||||
let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?; |
||||
let (minute, input) = minute_frag(input)?; |
||||
let input = expect_char( |
||||
input, |
||||
':', |
||||
"The minutes and seconds must be separated by ':'", |
||||
)?; |
||||
let (second, input) = second_frag(input)?; |
||||
// We validate 24:00:00
|
||||
if hour == 24 && minute != 0 && second != Decimal::from(0) { |
||||
return Err(XsdParseError::msg( |
||||
"Times are not allowed to be after 24:00:00", |
||||
)); |
||||
} |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok(( |
||||
DateTime::new(year, month, day, hour, minute, second, timezone_offset)?, |
||||
input, |
||||
)) |
||||
} |
||||
|
||||
pub fn parse_date_time(input: &str) -> Result<DateTime, XsdParseError> { |
||||
ensure_complete(input, date_time_lexical_rep) |
||||
} |
||||
|
||||
// [17] timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
|
||||
fn time_lexical_rep(input: &str) -> Result<(Time, &str), XsdParseError> { |
||||
let (hour, input) = hour_frag(input)?; |
||||
let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?; |
||||
let (minute, input) = minute_frag(input)?; |
||||
let input = expect_char( |
||||
input, |
||||
':', |
||||
"The minutes and seconds must be separated by ':'", |
||||
)?; |
||||
let (second, input) = second_frag(input)?; |
||||
// We validate 24:00:00
|
||||
if hour == 24 && minute != 0 && second != Decimal::from(0) { |
||||
return Err(XsdParseError::msg( |
||||
"Times are not allowed to be after 24:00:00", |
||||
)); |
||||
} |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((Time::new(hour, minute, second, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_time(input: &str) -> Result<Time, XsdParseError> { |
||||
ensure_complete(input, time_lexical_rep) |
||||
} |
||||
|
||||
// [18] dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag? Constraint: Day-of-month Representations
|
||||
fn date_lexical_rep(input: &str) -> Result<(Date, &str), XsdParseError> { |
||||
let (year, input) = year_frag(input)?; |
||||
let input = expect_char(input, '-', "The year and month must be separated by '-'")?; |
||||
let (month, input) = month_frag(input)?; |
||||
let input = expect_char(input, '-', "The month and day must be separated by '-'")?; |
||||
let (day, input) = day_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((Date::new(year, month, day, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_date(input: &str) -> Result<Date, XsdParseError> { |
||||
ensure_complete(input, date_lexical_rep) |
||||
} |
||||
|
||||
// [19] gYearMonthLexicalRep ::= yearFrag '-' monthFrag timezoneFrag?
|
||||
fn g_year_month_lexical_rep(input: &str) -> Result<(GYearMonth, &str), XsdParseError> { |
||||
let (year, input) = year_frag(input)?; |
||||
let input = expect_char(input, '-', "The year and month must be separated by '-'")?; |
||||
let (month, input) = month_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((GYearMonth::new(year, month, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_g_year_month(input: &str) -> Result<GYearMonth, XsdParseError> { |
||||
ensure_complete(input, g_year_month_lexical_rep) |
||||
} |
||||
|
||||
// [20] gYearLexicalRep ::= yearFrag timezoneFrag?
|
||||
fn g_year_lexical_rep(input: &str) -> Result<(GYear, &str), XsdParseError> { |
||||
let (year, input) = year_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((GYear::new(year, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_g_year(input: &str) -> Result<GYear, XsdParseError> { |
||||
ensure_complete(input, g_year_lexical_rep) |
||||
} |
||||
|
||||
// [21] gMonthDayLexicalRep ::= '--' monthFrag '-' dayFrag timezoneFrag? Constraint: Day-of-month Representations
|
||||
fn g_month_day_lexical_rep(input: &str) -> Result<(GMonthDay, &str), XsdParseError> { |
||||
let input = expect_char(input, '-', "gMonthDay values must start with '--'")?; |
||||
let input = expect_char(input, '-', "gMonthDay values must start with '--'")?; |
||||
let (month, input) = month_frag(input)?; |
||||
let input = expect_char(input, '-', "The month and day must be separated by '-'")?; |
||||
let (day, input) = day_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((GMonthDay::new(month, day, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_g_month_day(input: &str) -> Result<GMonthDay, XsdParseError> { |
||||
ensure_complete(input, g_month_day_lexical_rep) |
||||
} |
||||
|
||||
// [22] gDayLexicalRep ::= '---' dayFrag timezoneFrag?
|
||||
fn g_day_lexical_rep(input: &str) -> Result<(GDay, &str), XsdParseError> { |
||||
let input = expect_char(input, '-', "gDay values must start with '---'")?; |
||||
let input = expect_char(input, '-', "gDay values must start with '---'")?; |
||||
let input = expect_char(input, '-', "gDay values must start with '---'")?; |
||||
let (day, input) = day_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((GDay::new(day, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_g_day(input: &str) -> Result<GDay, XsdParseError> { |
||||
ensure_complete(input, g_day_lexical_rep) |
||||
} |
||||
|
||||
// [23] gMonthLexicalRep ::= '--' monthFrag timezoneFrag?
|
||||
fn g_month_lexical_rep(input: &str) -> Result<(GMonth, &str), XsdParseError> { |
||||
let input = expect_char(input, '-', "gMonth values must start with '--'")?; |
||||
let input = expect_char(input, '-', "gMonth values must start with '--'")?; |
||||
let (month, input) = month_frag(input)?; |
||||
let (timezone_offset, input) = optional_end(input, timezone_frag)?; |
||||
Ok((GMonth::new(month, timezone_offset)?, input)) |
||||
} |
||||
|
||||
pub fn parse_g_month(input: &str) -> Result<GMonth, XsdParseError> { |
||||
ensure_complete(input, g_month_lexical_rep) |
||||
} |
||||
|
||||
// [56] yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
|
||||
fn year_frag(input: &str) -> Result<(i64, &str), XsdParseError> { |
||||
let (sign, input) = if let Some(left) = input.strip_prefix('-') { |
||||
(-1, left) |
||||
} else { |
||||
(1, input) |
||||
}; |
||||
let (number_str, input) = integer_prefix(input); |
||||
if number_str.len() < 4 { |
||||
return Err(XsdParseError::msg("The year should be encoded on 4 digits")); |
||||
} |
||||
if number_str.len() > 4 && number_str.starts_with('0') { |
||||
return Err(XsdParseError::msg( |
||||
"The years value must not start with 0 if it can be encoded in at least 4 digits", |
||||
)); |
||||
} |
||||
let number = i64::from_str(number_str)?; |
||||
Ok((sign * number, input)) |
||||
} |
||||
|
||||
// [57] monthFrag ::= ('0' [1-9]) | ('1' [0-2])
|
||||
fn month_frag(input: &str) -> Result<(u8, &str), XsdParseError> { |
||||
let (number_str, input) = integer_prefix(input); |
||||
if number_str.len() != 2 { |
||||
return Err(XsdParseError::msg("Month must be encoded with two digits")); |
||||
} |
||||
let number = u8::from_str(number_str)?; |
||||
if !(1..=12).contains(&number) { |
||||
return Err(XsdParseError::msg("Month must be between 01 and 12")); |
||||
} |
||||
Ok((number, input)) |
||||
} |
||||
|
||||
// [58] dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
|
||||
fn day_frag(input: &str) -> Result<(u8, &str), XsdParseError> { |
||||
let (number_str, input) = integer_prefix(input); |
||||
if number_str.len() != 2 { |
||||
return Err(XsdParseError::msg("Day must be encoded with two digits")); |
||||
} |
||||
let number = u8::from_str(number_str)?; |
||||
if !(1..=31).contains(&number) { |
||||
return Err(XsdParseError::msg("Day must be between 01 and 31")); |
||||
} |
||||
Ok((number, input)) |
||||
} |
||||
|
||||
// [59] hourFrag ::= ([01] digit) | ('2' [0-3])
|
||||
// We also allow 24 for ease of parsing
|
||||
fn hour_frag(input: &str) -> Result<(u8, &str), XsdParseError> { |
||||
let (number_str, input) = integer_prefix(input); |
||||
if number_str.len() != 2 { |
||||
return Err(XsdParseError::msg("Hours must be encoded with two digits")); |
||||
} |
||||
let number = u8::from_str(number_str)?; |
||||
if !(0..=24).contains(&number) { |
||||
return Err(XsdParseError::msg("Hours must be between 00 and 24")); |
||||
} |
||||
Ok((number, input)) |
||||
} |
||||
|
||||
// [60] minuteFrag ::= [0-5] digit
|
||||
fn minute_frag(input: &str) -> Result<(u8, &str), XsdParseError> { |
||||
let (number_str, input) = integer_prefix(input); |
||||
if number_str.len() != 2 { |
||||
return Err(XsdParseError::msg( |
||||
"Minutes must be encoded with two digits", |
||||
)); |
||||
} |
||||
let number = u8::from_str(number_str)?; |
||||
if !(0..=59).contains(&number) { |
||||
return Err(XsdParseError::msg("Minutes must be between 00 and 59")); |
||||
} |
||||
Ok((number, input)) |
||||
} |
||||
|
||||
// [61] secondFrag ::= ([0-5] digit) ('.' digit+)?
|
||||
fn second_frag(input: &str) -> Result<(Decimal, &str), XsdParseError> { |
||||
let (number_str, input) = decimal_prefix(input); |
||||
let (before_dot_str, _) = number_str.split_once('.').unwrap_or((number_str, "")); |
||||
if before_dot_str.len() != 2 { |
||||
return Err(XsdParseError::msg( |
||||
"Seconds must be encoded with two digits", |
||||
)); |
||||
} |
||||
let number = Decimal::from_str(number_str)?; |
||||
if number < Decimal::from(0) || number >= Decimal::from(60) { |
||||
return Err(XsdParseError::msg("Seconds must be between 00 and 60")); |
||||
} |
||||
if number_str.ends_with('.') { |
||||
return Err(XsdParseError::msg( |
||||
"Seconds are not allowed to end with a dot", |
||||
)); |
||||
} |
||||
Ok((number, input)) |
||||
} |
||||
|
||||
// [63] timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
|
||||
fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), XsdParseError> { |
||||
if let Some(left) = input.strip_prefix('Z') { |
||||
return Ok((TimezoneOffset::UTC, left)); |
||||
} |
||||
let (sign, input) = if let Some(left) = input.strip_prefix('-') { |
||||
(-1, left) |
||||
} else if let Some(left) = input.strip_prefix('+') { |
||||
(1, left) |
||||
} else { |
||||
(1, input) |
||||
}; |
||||
|
||||
let (hour_str, input) = integer_prefix(input); |
||||
if hour_str.len() != 2 { |
||||
return Err(XsdParseError::msg( |
||||
"The timezone hours must be encoded with two digits", |
||||
)); |
||||
} |
||||
let hours = i16::from_str(hour_str)?; |
||||
|
||||
let input = expect_char( |
||||
input, |
||||
':', |
||||
"The timezone hours and minutes must be separated by ':'", |
||||
)?; |
||||
let (minutes, input) = minute_frag(input)?; |
||||
|
||||
if hours > 13 && !(hours == 14 && minutes == 0) { |
||||
return Err(XsdParseError::msg( |
||||
"The timezone hours must be between 00 and 13", |
||||
)); |
||||
} |
||||
|
||||
Ok(( |
||||
TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes)))?, |
||||
input, |
||||
)) |
||||
} |
||||
|
||||
fn ensure_complete<T>( |
||||
input: &str, |
||||
parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>, |
||||
) -> Result<T, XsdParseError> { |
||||
let (result, left) = parse(input)?; |
||||
if !left.is_empty() { |
||||
return Err(XsdParseError::msg("Unrecognized value suffix")); |
||||
} |
||||
Ok(result) |
||||
} |
||||
|
||||
fn expect_char<'a>( |
||||
input: &'a str, |
||||
constant: char, |
||||
error_message: &'static str, |
||||
) -> Result<&'a str, XsdParseError> { |
||||
if let Some(left) = input.strip_prefix(constant) { |
||||
Ok(left) |
||||
} else { |
||||
Err(XsdParseError::msg(error_message)) |
||||
} |
||||
} |
||||
|
||||
fn integer_prefix(input: &str) -> (&str, &str) { |
||||
let mut end = input.len(); |
||||
for (i, c) in input.char_indices() { |
||||
if !c.is_ascii_digit() { |
||||
end = i; |
||||
break; |
||||
} |
||||
} |
||||
input.split_at(end) |
||||
} |
||||
|
||||
fn decimal_prefix(input: &str) -> (&str, &str) { |
||||
let mut end = input.len(); |
||||
let mut dot_seen = false; |
||||
for (i, c) in input.char_indices() { |
||||
if c.is_ascii_digit() { |
||||
// Ok
|
||||
} else if c == '.' && !dot_seen { |
||||
dot_seen = true; |
||||
} else { |
||||
end = i; |
||||
break; |
||||
} |
||||
} |
||||
input.split_at(end) |
||||
} |
||||
|
||||
fn optional_end<T>( |
||||
input: &str, |
||||
parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>, |
||||
) -> Result<(Option<T>, &str), XsdParseError> { |
||||
Ok(if input.is_empty() { |
||||
(None, input) |
||||
} else { |
||||
let (result, input) = parse(input)?; |
||||
(Some(result), input) |
||||
}) |
||||
} |
Loading…
Reference in new issue