use crate::parser::helper::*;
use fnv::FnvHashMap;
use nom::{
bytes::complete::*,
character::complete::*,
combinator::*,
error::{make_error, ErrorKind},
multi::*,
AsChar, IResult,
};
pub fn phone_number(i: &str) -> IResult<&str, Number<'_>> {
if i.contains(' ') {
return Err(nom::Err::Error(make_error(i, ErrorKind::Tag)));
}
parse! { i =>
opt(tag_no_case("Tel:"));
let prefix = opt(prefix);
let national = take_while1(number);
check;
let params = opt(parameters);
};
Ok((
i,
Number {
national: (*national).into(),
prefix: prefix
.or_else(|| {
params
.as_ref()
.and_then(|m| m.get("phone-context"))
.map(|&s| s.strip_prefix('+').unwrap_or(s))
})
.map(|cs| cs.into()),
extension: params
.as_ref()
.and_then(|m| m.get("ext"))
.map(|&cs| cs.into()),
..Default::default()
},
))
}
fn prefix(i: &str) -> IResult<&str, &str> {
parse! { i =>
char('+');
take_till1(separator)
}
}
fn parameters(i: &str) -> IResult<&str, FnvHashMap<&str, &str>> {
parse! { i =>
let params = many1(parameter);
};
Ok((i, params.into_iter().collect()))
}
fn parameter(i: &str) -> IResult<&str, (&str, &str)> {
parse! { i =>
char(';');
let key = take_while(pname);
char('=');
let value = take_while(pchar);
};
Ok((i, (key, value)))
}
fn check(i: &str) -> IResult<&str, ()> {
if i.is_empty() || i.as_bytes()[0] == b';' {
Ok((i, ()))
} else {
Err(nom::Err::Error(make_error(i, ErrorKind::Tag)))
}
}
fn pname(c: char) -> bool {
c.is_alphanum() || c == '-'
}
fn pchar(c: char) -> bool {
parameter_unreserved(c) || unreserved(c)
}
fn number(c: char) -> bool {
digit(c) || separator(c)
}
fn digit(c: char) -> bool {
c.is_wide_digit() || c.is_hex_digit()
}
fn separator(c: char) -> bool {
c == '-' || c == '.' || c == '(' || c == ')'
}
fn unreserved(c: char) -> bool {
c.is_alphanum() || mark(c)
}
fn parameter_unreserved(c: char) -> bool {
c == '[' || c == ']' || c == '/' || c == ':' || c == '&' || c == '+' || c == '$'
}
fn mark(c: char) -> bool {
c == '-'
|| c == '_'
|| c == '.'
|| c == '!'
|| c == '~'
|| c == '*'
|| c == '\''
|| c == '('
|| c == ')'
}
#[cfg(test)]
mod test {
use crate::parser::helper::*;
use crate::parser::rfc3966;
#[test]
fn phone_number() {
assert_eq!(
rfc3966::phone_number("tel:2034567890;ext=456;phone-context=+44")
.unwrap()
.1,
Number {
national: "2034567890".into(),
prefix: Some("44".into()),
extension: Some("456".into()),
..Default::default()
}
);
assert_eq!(
rfc3966::phone_number("tel:+64-3-331-6005;ext=1235")
.unwrap()
.1,
Number {
national: "-3-331-6005".into(),
prefix: Some("64".into()),
extension: Some("1235".into()),
..Default::default()
}
);
}
#[test]
fn advisory_1() {
drop(rfc3966::phone_number(".;phone-context="));
}
}