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>, },
26}
27
28impl<'a> Parse<'a> for ExternalID {
29 type Args = ();
30 type Output = IResult<&'a str, Self>;
31 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 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}