dtd_parser/
element.rs

1use std::{fmt::Display, ops::Deref};
2
3use nom::{
4    branch::alt,
5    bytes::complete::tag,
6    character::complete::{multispace0, multispace1, space0},
7    combinator::{map, opt, value},
8    multi::{many0, many1},
9    sequence::{preceded, terminated, tuple},
10};
11use nom_tracable::tracable_parser;
12
13use super::{name, MixedPCDATA, Name, Repeatable, Result, Span};
14
15/// 元素是 XML 以及 HTML 文档的主要构建模块。
16///
17/// HTML 元素的例子是 "body" 和 "table"。
18/// XML 元素的例子是 "note" 和 "message" 。
19/// 元素可包含文本、其他元素或者是空的。空的 HTML 元素的例子是 "hr"、"br" 以及 "img"。
20#[derive(Debug, Display)]
21#[display(fmt = "<!ELEMENT {} {}>", name, category)]
22pub struct ElementDecl {
23    name: Name,
24    category: ElementCategory,
25}
26
27impl ElementDecl {
28    pub fn name(&self) -> &str {
29        self.name.deref()
30    }
31
32    pub fn category(&self) -> &ElementCategory {
33        &self.category
34    }
35}
36
37#[derive(Debug, Display)]
38pub enum ElementCategory {
39    #[display(fmt = "EMPTY")]
40    Empty,
41    #[display(fmt = "#PCDATA")]
42    PCDATA,
43    #[display(fmt = "#CDATA")]
44    CDATA,
45    #[display(fmt = "ANY")]
46    Any,
47    #[display(fmt = "{}", "_0")]
48    Mixed(Repeatable<MixedPCDATA>),
49    #[display(fmt = "{}", "_0")]
50    Children(Repeatable<Child>),
51}
52
53#[tracable_parser]
54fn parenthesis_open(i: Span) -> Result<Span> {
55    tag("(")(i)
56}
57
58#[tracable_parser]
59fn parenthesis_close(i: Span) -> Result<Span> {
60    tag(")")(i)
61}
62
63/// Mixed	   ::=   	'(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
64/// 			| '(' S? '#PCDATA' S? ')'
65#[tracable_parser]
66fn mixed(i: Span) -> Result<ElementCategory> {
67    alt((
68        map(mixed_many_names, |x| {
69            ElementCategory::Mixed(Repeatable::ZeroOrManyTimes(MixedPCDATA(x)))
70        }),
71        map(mixed_no_names, |_| {
72            ElementCategory::Mixed(Repeatable::Once(MixedPCDATA(Vec::new())))
73        }),
74    ))(i)
75}
76
77#[tracable_parser]
78fn mixed_no_names(i: Span) -> Result<()> {
79    value(
80        (),
81        tuple((
82            tag("("),
83            multispace0,
84            tag("#PCDATA"),
85            multispace0,
86            tag(")"),
87            space0,
88        )),
89    )(i)
90}
91
92#[tracable_parser]
93fn mixed_many_names(i: Span) -> Result<Vec<Name>> {
94    map(
95        tuple((
96            tuple((tag("("), multispace0, tag("#PCDATA"), multispace0)),
97            many0(preceded(tuple((multispace0, tag("|"), multispace0)), name)),
98            tuple((multispace0, tag(")*"))),
99        )),
100        |(_, x, _)| x,
101    )(i)
102}
103
104#[derive(AsRef, AsMut, Debug, Deref, DerefMut, Display, IntoIterator)]
105#[display(
106    fmt = "({})",
107    "_0.iter().map(|v|v.to_string()).collect::<Vec<_>>().join(\", \")"
108)]
109pub struct Seq<T>(Vec<T>)
110where
111    T: Display;
112
113#[derive(AsRef, AsMut, Debug, Deref, DerefMut, Display, IntoIterator)]
114#[display(
115    fmt = "({})",
116    "_0.iter().map(|v|v.to_string()).collect::<Vec<_>>().join(\" | \")"
117)]
118pub struct Choices<T>(Vec<T>)
119where
120    T: Display;
121
122#[derive(Debug, Display)]
123pub enum Child {
124    #[display(fmt = "{}", "_0")]
125    Name(Name),
126    #[display(fmt = "{}", "_0")]
127    Seq(Seq<Repeatable<Child>>),
128    #[display(fmt = "{}", "_0")]
129    Choices(Choices<Repeatable<Child>>),
130}
131
132///   	children	   ::=   	(choice | seq) ('?' | '*' | '+')?
133#[tracable_parser]
134fn children(i: Span) -> Result<ElementCategory> {
135    map(
136        tuple((
137            alt((map(choices, Child::Choices), map(sequence, Child::Seq))),
138            opt(alt((tag("?"), tag("*"), tag("+")))),
139        )),
140        |(child, repeatable)| {
141            let repeatable = if let Some(repeatable) = repeatable {
142                match *repeatable {
143                    "?" => |child| Repeatable::AtMostOnce(child),
144                    "*" => |child| Repeatable::ZeroOrManyTimes(child),
145                    "+" => |child| Repeatable::AtLeastOnce(child),
146                    _ => unreachable!(),
147                }
148            } else {
149                |child| Repeatable::Once(child)
150            };
151            ElementCategory::Children(repeatable(child))
152        },
153    )(i)
154}
155
156///   	seq	   ::=   	'(' S? cp ( S? ',' S? cp )* S? ')'	[VC: Proper Group/PE Nesting]
157#[tracable_parser]
158fn sequence(i: Span) -> Result<Seq<Repeatable<Child>>> {
159    map(
160        tuple((
161            preceded(tuple((parenthesis_open, multispace0)), cuple),
162            terminated(
163                many0(preceded(tuple((multispace0, tag(","), multispace0)), cuple)),
164                tuple((multispace0, parenthesis_close)),
165            ),
166        )),
167        |(cp, many)| Seq(std::iter::once(cp).chain(many.into_iter()).collect()),
168    )(i)
169}
170
171///   	choice	   ::=   	'(' S? cp ( S? '|' S? cp )+ S? ')'	[VC: Proper Group/PE Nesting]
172#[tracable_parser]
173fn choices(i: Span) -> Result<Choices<Repeatable<Child>>> {
174    map(
175        tuple((
176            parenthesis_open,
177            multispace0,
178            cuple,
179            many1(tuple((multispace0, tag("|"), multispace0, cuple))),
180            multispace0,
181            parenthesis_close,
182        )),
183        |(_, _, cp, many, _, _)| {
184            Choices(
185                std::iter::once(cp)
186                    .chain(many.into_iter().map(|(_, _, _, x)| x))
187                    .collect(),
188            )
189        },
190    )(i)
191}
192
193///   	cp	   ::=   	(Name | choice | seq) ('?' | '*' | '+')?
194#[tracable_parser]
195fn cuple(i: Span) -> Result<Repeatable<Child>> {
196    map(
197        tuple((
198            alt((
199                map(name, Child::Name),
200                map(choices, Child::Choices),
201                map(sequence, Child::Seq),
202            )),
203            opt(alt((tag("?"), tag("*"), tag("+")))),
204        )),
205        |(child, repeatable)| {
206            let repeatable = if let Some(repeatable) = repeatable {
207                match *repeatable {
208                    "?" => |child| Repeatable::AtMostOnce(child),
209                    "*" => |child| Repeatable::ZeroOrManyTimes(child),
210                    "+" => |child| Repeatable::AtLeastOnce(child),
211                    _ => unreachable!(),
212                }
213            } else {
214                |child| Repeatable::Once(child)
215            };
216            repeatable(child)
217        },
218    )(i)
219}
220
221#[tracable_parser]
222fn empty(i: Span) -> Result<ElementCategory> {
223    map(tag("EMPTY"), |_| ElementCategory::Empty)(i)
224}
225
226#[tracable_parser]
227fn any(i: Span) -> Result<ElementCategory> {
228    map(tag("ANY"), |_| ElementCategory::Any)(i)
229}
230
231/// <!ELEMENT 元素名称 类别>
232#[tracable_parser]
233pub(super) fn element_decl(i: Span) -> Result<ElementDecl> {
234    map(
235        tuple((
236            tag("<!ELEMENT"),
237            multispace1,
238            name,
239            multispace1,
240            alt((empty, any, mixed, children)),
241            multispace0,
242            tag(">"),
243        )),
244        |(_, _, n, _, c, _, _)| ElementDecl {
245            name: n,
246            category: c,
247        },
248    )(i)
249}
250
251// <!ELEMENT 元素名称 (元素内容)>
252// 空元素通过类别关键词EMPTY进行声明:
253// <!ELEMENT 元素名称 EMPTY>
254// 只有 PCDATA 的元素通过圆括号中的 #PCDATA 进行声明:
255// <!ELEMENT 元素名称 (#PCDATA)>
256// 通过类别关键词 ANY 声明的元素,可包含任何可解析数据的组合:
257// <!ELEMENT 元素名称 ANY>
258// 带有一个或多个子元素的元素通过圆括号中的子元素名进行声明:
259// <!ELEMENT 元素名称 (子元素名称 1)>
260// 或者
261// <!ELEMENT 元素名称 (子元素名称 1,子元素名称 2,.....)>
262// 声明只出现一次的元素
263// <!ELEMENT 元素名称 (子元素名称)>
264// 例子:
265// <!ELEMENT note (message)>
266// 上面的例子声明了:message 子元素必须出现一次,并且必须只在 "note" 元素中出现一次。
267//
268// 声明最少出现一次的元素
269// <!ELEMENT 元素名称 (子元素名称+)>
270// 例子:
271// <!ELEMENT note (message+)>
272// 上面的例子中的加号声明了:message 子元素必须在 "note" 元素内出现至少一次。
273//
274// 声明出现零次或多次的元素
275// <!ELEMENT 元素名称 (子元素名称*)>
276// 例子:
277// <!ELEMENT note (message*)>
278// 上面的例子中的星号声明了:子元素 message 可在 "note" 元素内出现零次或多次。
279//
280// 声明出现零次或一次的元素
281// <!ELEMENT 元素名称 (子元素名称?)>
282// 例子:
283// <!ELEMENT note (message?)>
284// 上面的例子中的问号声明了:子元素 message 可在 "note" 元素内出现零次或一次。
285//
286// 声明“非.../既...”类型的内容
287// 例子:
288// <!ELEMENT note (to,from,header,(message|body))>
289// 上面的例子声明了:"note" 元素必须包含 "to" 元素、"from" 元素、"header" 元素,以及非 "message" 元素既 "body" 元素。
290//
291// 声明混合型的内容
292// 例子:
293// <!ELEMENT note (#PCDATA|to|from|header|message)*>
294// 上面的例子声明了:"note" 元素可包含出现零次或多次的 PCDATA、"to"、"from"、"header" 或者 "message"。
295
296#[cfg(test)]
297mod tests {
298
299    use super::{children, element_decl, mixed};
300    use crate::assert_ok;
301    use crate::span;
302
303    use nom::Finish;
304
305    #[test]
306    fn test_element_decl() {
307        let input = span("<!ELEMENT b (#PCDATA)>");
308        let el = element_decl(input).finish();
309        assert_ok!(input, el);
310        let input = span("<!ELEMENT p (#PCDATA|a|ul|b|i|em)*>");
311        let el = element_decl(input).finish();
312        assert_ok!(input, el);
313        let el = element_decl(span(
314            "<!ELEMENT p (#PCDATA | %font; | %phrase; | %special; | %form;)* >",
315        ))
316        .finish();
317        assert_ok!(input, el);
318    }
319
320    #[test]
321    fn test_element_decl_multiline() {
322        let input = span(
323            r#"<!ELEMENT generated  (#PCDATA |   emphasis | strong | literal | math
324    | reference | footnote_reference | citation_reference
325    | substitution_reference | title_reference
326    | abbreviation | acronym | subscript | superscript
327    | inline | problematic | generated
328    | target | image | raw
329        )* >"#,
330        );
331        let el = element_decl(input).finish();
332        assert_ok!(input, el);
333    }
334
335    #[test]
336    fn test_element_decl_multiline2() {
337        let el = element_decl(span(
338            r#"<!ELEMENT document
339    ( (title, subtitle?)?,
340      meta?,
341      decoration?,
342      (docinfo, transition?)?,
343       ( ( (  paragraph | compound | container | literal_block | doctest_block
344    | math_block | line_block | block_quote
345    | table | figure | image | footnote | citation | rubric
346    | bullet_list | enumerated_list | definition_list | field_list
347    | option_list
348    | attention | caution | danger | error | hint | important | note
349    | tip | warning | admonition
350    | reference | target | substitution_definition | comment | pending
351    | system_message | raw
352         | topic | sidebar)+, transition? )*,
353       ( (  section
354        ), (transition?, (  section
355        ) )* )? ) )>"#,
356        ));
357        assert!(el.is_ok(), "{:?}", el.as_ref().unwrap_err());
358    }
359
360    #[test]
361    fn test_child() {
362        let el = children(span("(title, subtitle?)?"));
363        assert!(el.is_ok(), "{:?}", el.as_ref().unwrap_err());
364    }
365
366    #[test]
367    fn test_mixed_simple() {
368        let input = span("<!ELEMENT option_string (#PCDATA)>");
369        let el = element_decl(input).finish();
370        assert_ok!(input, el);
371    }
372}