nom_xml/prolog/
xmldecl.rs

1use crate::{parse::Parse, IResult};
2use nom::{
3    branch::alt,
4    bytes::complete::tag,
5    character::complete::{alpha1, alphanumeric1, digit1},
6    combinator::{map, opt},
7    error::ErrorKind,
8    multi::many0,
9    sequence::{delimited, pair, preceded, tuple},
10};
11use std::str::FromStr;
12
13#[derive(Clone, PartialEq, Eq)]
14pub enum Standalone {
15    Yes,
16    No,
17}
18
19impl FromStr for Standalone {
20    type Err = nom::Err<nom::error::Error<&'static str>>;
21
22    fn from_str(s: &str) -> Result<Self, Self::Err> {
23        match s {
24            "yes" => Ok(Standalone::Yes),
25            "no" => Ok(Standalone::No),
26            _ => Err(nom::Err::Error(nom::error::Error::new("", ErrorKind::Alt))),
27        }
28    }
29}
30
31#[derive(Clone, PartialEq, Eq)]
32pub struct XmlDecl {
33    pub version: String,
34    pub encoding: Option<String>,
35    pub standalone: Option<Standalone>,
36}
37impl<'a> Parse<'a> for XmlDecl {
38    type Args = ();
39    type Output = IResult<&'a str, Self>;
40    // [23] XMLDecl	::=  '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
41    fn parse(input: &'a str, _args: Self::Args) -> Self::Output {
42        map(
43            tuple((
44                tag("<?xml"),
45                Self::parse_version_info,
46                opt(Self::parse_encoding_decl),
47                opt(Self::parse_sd_decl),
48                Self::parse_multispace0,
49                tag("?>"),
50            )),
51            |(_start, version, encoding, standalone, _whitespace, _end)| Self {
52                version,
53                encoding,
54                standalone,
55            },
56        )(input)
57    }
58}
59
60impl XmlDecl {
61    // [24] VersionInfo	::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')
62    fn parse_version_info(input: &str) -> IResult<&str, String> {
63        map(
64            tuple((
65                Self::parse_multispace1,
66                tag("version"),
67                Self::parse_eq,
68                alt((
69                    delimited(tag("'"), Self::parse_version_num, tag("'")),
70                    delimited(tag("\""), Self::parse_version_num, tag("\"")),
71                )),
72            )),
73            |(_whitespace, _version_literal, _eq, version)| version,
74        )(input)
75    }
76
77    // [26] VersionNum	::= '1.' [0-9]+
78    fn parse_version_num(input: &str) -> IResult<&str, String> {
79        map(preceded(tag("1."), digit1), |version| {
80            format!("1.{}", version)
81        })(input)
82    }
83    // [80] EncodingDecl	::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )
84    fn parse_encoding_decl(input: &str) -> IResult<&str, String> {
85        map(
86            tuple((
87                Self::parse_multispace1,
88                tag("encoding"),
89                Self::parse_eq,
90                alt((
91                    delimited(tag("'"), Self::parse_enc_name, tag("'")),
92                    delimited(tag("\""), Self::parse_enc_name, tag("\"")),
93                )),
94            )),
95            |(_whitespace, _encoding_literal, _eq, encoding)| encoding,
96        )(input)
97    }
98
99    // [81] EncName	::= [A-Za-z] ([A-Za-z0-9._] | '-')*
100    fn parse_enc_name(input: &str) -> IResult<&str, String> {
101        map(
102            pair(
103                alt((alpha1, tag("-"))),
104                many0(alt((alphanumeric1, tag("."), tag("_"), tag("-")))),
105            ),
106            |(first, rest)| format!("{}{}", first, rest.join("")),
107        )(input)
108    }
109
110    // [32] SDDecl	::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))
111    fn parse_sd_decl(input: &str) -> IResult<&str, Standalone> {
112        map(
113            tuple((
114                Self::parse_multispace1,
115                tag("standalone"),
116                Self::parse_eq,
117                alt((
118                    delimited(tag("'"), alt((tag("yes"), tag("no"))), tag("'")),
119                    delimited(tag("\""), alt((tag("yes"), tag("no"))), tag("\"")),
120                )),
121            )),
122            |(_whtiespace, _standalone_literal, _eq, standalone)| {
123                Standalone::from_str(standalone).unwrap()
124            },
125        )(input)
126    }
127}