1use nom::{
2 branch::alt,
3 bytes::complete::{is_a, tag},
4 character::complete::alphanumeric1,
5 combinator::map,
6 multi::many0,
7 sequence::{delimited, pair, preceded},
8};
9
10use crate::{parse::Parse, IResult};
11
12use super::external_id::ExternalID;
13
14#[derive(Clone, PartialEq, Eq)]
15pub enum ID {
16 ExternalID(ExternalID),
17 PublicID(String),
18}
19
20impl<'a> Parse<'a> for ID {
21 type Args = ();
22 type Output = IResult<&'a str, Self>;
23 fn parse(input: &'a str, _args: Self::Args) -> Self::Output {
25 alt((
26 map(
27 preceded(
28 pair(tag("PUBLIC"), Self::parse_multispace1),
29 Self::parse_public_id_literal,
30 ),
31 ID::PublicID,
32 ),
33 map(|i| ExternalID::parse(i, ()), ID::ExternalID),
34 ))(input)
35 }
36}
37
38impl ID {
39 pub fn parse_public_id_literal(input: &str) -> IResult<&str, String> {
41 map(
42 alt((
43 delimited(
44 tag("\""),
45 many0(|i| Self::parse_pubid_char(i, false)),
46 tag("\""),
47 ),
48 delimited(
49 tag("'"),
50 many0(|i| Self::parse_pubid_char(i, true)),
51 tag("'"),
52 ),
53 )),
54 |pubid_literal: Vec<&str>| pubid_literal.concat(),
55 )(input)
56 }
57
58 pub fn parse_pubid_char(input: &str, exclude_single_quote: bool) -> IResult<&str, &str> {
60 let chars = if exclude_single_quote {
61 " \r\n-()+,./:=?;!*#@$_%"
62 } else {
63 " \r\n-'()+,./:=?;!*#@$_%"
64 };
65
66 alt((tag(" "), tag("\r"), tag("\n"), alphanumeric1, is_a(chars)))(input)
67 }
68}