nom_xml/prolog/
external_id.rs

1use std::{cell::RefCell, collections::HashMap, fs::File, rc::Rc};
2
3use crate::{
4    error::Error, io::parse_external_entity_file, parse::Parse, prolog::subset::Subset, Config,
5    ExternalEntityParseConfig, IResult, Name,
6};
7use nom::{
8    branch::alt,
9    bytes::complete::{is_not, tag},
10    combinator::map,
11    sequence::{delimited, tuple},
12};
13
14use super::{
15    id::ID,
16    subset::entity::{entity_value::EntityValue, EntitySource},
17};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum ExternalID {
21    System(String),
22    Public {
23        pubid: String,
24        system_identifier: Box<ExternalID>, // Box<ExternalID::System>
25    },
26}
27
28impl<'a> Parse<'a> for ExternalID {
29    type Args = ();
30    type Output = IResult<&'a str, Self>;
31    //[75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral
32    fn parse(input: &'a str, _args: Self::Args) -> Self::Output {
33        alt((Self::parse_system, Self::parse_public))(input)
34    }
35}
36
37impl ExternalID {
38    fn parse_system(input: &str) -> IResult<&str, ExternalID> {
39        map(
40            tuple((
41                tag("SYSTEM"),
42                Self::parse_multispace1,
43                Self::parse_system_literal,
44            )),
45            |(_system, _whitespace, system_literal)| ExternalID::System(system_literal),
46        )(input)
47    }
48
49    fn parse_public(input: &str) -> IResult<&str, ExternalID> {
50        map(
51            tuple((
52                tag("PUBLIC"),
53                Self::parse_multispace1,
54                ID::parse_public_id_literal,
55                Self::parse_multispace1,
56                Self::parse_system_literal,
57            )),
58            |(_public, _whitespace1, pubid_literal, _whitespace2, system_literal)| {
59                ExternalID::Public {
60                    pubid: pubid_literal,
61                    system_identifier: Box::new(ExternalID::System(system_literal)),
62                }
63            },
64        )(input)
65    }
66
67    // [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")
68    fn parse_system_literal(input: &str) -> IResult<&str, String> {
69        map(
70            alt((
71                delimited(tag("\""), is_not("\""), tag("\"")),
72                delimited(tag("'"), is_not("'"), tag("'")),
73            )),
74            |s: &str| s.to_string(),
75        )(input)
76    }
77
78    pub fn get_external_entity_from_id(
79        &self,
80        input: &str,
81        entity_references: Rc<RefCell<HashMap<(Name, EntitySource), EntityValue>>>,
82        config: &Config,
83    ) -> Result<Option<Vec<Subset>>, Box<dyn std::error::Error>> {
84        if let Config {
85            external_parse_config:
86                ExternalEntityParseConfig {
87                    allow_ext_parse: true,
88                    base_directory,
89                    ..
90                },
91        } = &config
92        {
93            if let ExternalID::System(system_identifier) = self {
94                let file_path = base_directory.as_ref().map_or_else(
95                    || system_identifier.clone(),
96                    |base| format!("{}/{}", base, system_identifier),
97                );
98
99                match File::open(file_path) {
100                    Ok(mut file) => {
101                        match parse_external_entity_file(
102                            &mut file,
103                            config,
104                            entity_references.clone(),
105                        ) {
106                            Ok((_entities, subsets)) => {
107                                let (_input, (_whitespace1, _close_tag, _whitespace2)) =
108                                    tuple((
109                                        Self::parse_multispace0,
110                                        tag(">"),
111                                        Self::parse_multispace0,
112                                    ))(input)?;
113                                Ok(subsets)
114                            }
115                            _ => Err(nom::Err::Error(Error::NomError(nom::error::Error::new(
116                                "Failed to match [entity] from `parse_external_entity_file`"
117                                    .to_string(),
118                                nom::error::ErrorKind::Fail,
119                            )))
120                            .into()),
121                        }
122                    }
123                    Err(e) => Err(Error::from(e).into()),
124                }
125            } else {
126                Err(nom::Err::Error(nom::error::Error::new(
127                    "Only ExternalID::System is supported for direct parsing",
128                    nom::error::ErrorKind::Fail,
129                ))
130                .into())
131            }
132        } else {
133            Err(nom::Err::Error(nom::error::Error::new(
134                "External parsing is disabled in the configuration",
135                nom::error::ErrorKind::Fail,
136            ))
137            .into())
138        }
139    }
140}