asciidoc_parser/content/
substitution_group.rs

1use crate::{
2    Parser,
3    attributes::Attrlist,
4    content::{Content, Passthroughs, SubstitutionStep},
5};
6
7/// Each block and inline element has a default substitution group that is
8/// applied unless you customize the substitutions for a particular element.
9///
10/// `SubstitutionGroup` specifies the default or overridden substitution group
11/// to be applied.
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum SubstitutionGroup {
14    /// The normal substitution group is applied to the majority of the AsciiDoc
15    /// block and inline elements except for specific elements described in the
16    /// next sections.
17    Normal,
18
19    /// The header substitution group is applied to metadata lines (author and
20    /// revision information) in the document header. It’s also applied to the
21    /// values of attribute entries, regardless of whether those entries are
22    /// defined in the document header or body. Only special characters,
23    /// attribute references, and the inline pass macro are replaced in elements
24    /// that fall under the header group.
25    ///
26    /// You can use the inline pass macro in attribute entries to customize the
27    /// substitution types applied to the attribute’s value.
28    Header,
29
30    /// Literal, listing, and source blocks are processed using the verbatim
31    /// substitution group. Only special characters are replaced in these
32    /// blocks.
33    Verbatim,
34
35    /// No substitutions are applied to three of the elements in the pass
36    /// substitution group. These elements include the passthrough block, inline
37    /// pass macro, and triple plus macro.
38    ///
39    /// The inline single plus and double plus macros also belong to the pass
40    /// group. Only the special characters substitution is applied to these
41    /// elements.
42    Pass,
43
44    /// The none substitution group is applied to comment blocks. No
45    /// substitutions are applied to comments.
46    None,
47
48    /// The attribute entry value substitution group is applied to attribute
49    /// values. Only special characters and attribute references are applied to
50    /// these values.
51    AttributeEntryValue,
52
53    /// You can customize the substitutions applied to the content of an inline
54    /// pass macro by specifying one or more substitution values. Multiple
55    /// values must be separated by commas and may not contain any spaces. The
56    /// substitution value is either the formal name of a substitution type or
57    /// group, or its shorthand.
58    ///
59    /// See [Custom substitutions].
60    ///
61    /// [Custom substitutions]: https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/#custom-substitutions
62    Custom(Vec<SubstitutionStep>),
63}
64
65impl SubstitutionGroup {
66    /// Parse the custom substitution group syntax defined in [Custom
67    /// substitutions].
68    ///
69    /// [Custom substitutions]: https://docs.asciidoctor.org/asciidoc/latest/pass/pass-macro/#custom-substitutions
70    pub(crate) fn from_custom_string(start_from: Option<&Self>, mut custom: &str) -> Option<Self> {
71        custom = custom.trim();
72
73        if custom == "none" {
74            return Some(Self::None);
75        }
76
77        if custom == "n" || custom == "normal" {
78            return Some(Self::Normal);
79        }
80
81        if custom == "v" || custom == "verbatim" {
82            return Some(Self::Verbatim);
83        }
84
85        let mut steps: Vec<SubstitutionStep> = vec![];
86
87        for (count, mut step) in custom.split(",").enumerate() {
88            step = step.trim();
89
90            if step == "n" || step == "normal" {
91                steps = vec![
92                    SubstitutionStep::SpecialCharacters,
93                    SubstitutionStep::Quotes,
94                    SubstitutionStep::AttributeReferences,
95                    SubstitutionStep::CharacterReplacements,
96                    SubstitutionStep::Macros,
97                    SubstitutionStep::PostReplacement,
98                ];
99                continue;
100            }
101
102            if step == "v" || step == "verbatim" {
103                steps = vec![SubstitutionStep::SpecialCharacters];
104                continue;
105            }
106
107            let append = if step.starts_with('+') {
108                step = &step[1..];
109                true
110            } else {
111                false
112            };
113
114            let prepend = if !append && step.ends_with('+') {
115                step = &step[0..step.len() - 1];
116                true
117            } else {
118                false
119            };
120
121            let subtract = if !append && !prepend && step.starts_with('-') {
122                step = &step[1..];
123                true
124            } else {
125                false
126            };
127
128            if count == 0
129                && let Some(start_from) = start_from
130                && (append || prepend || subtract)
131            {
132                steps = start_from.steps().to_owned();
133            }
134
135            let step = match step {
136                "c" | "specialcharacters" | "specialchars" => SubstitutionStep::SpecialCharacters,
137                "q" | "quotes" => SubstitutionStep::Quotes,
138                "a" | "attributes" => SubstitutionStep::AttributeReferences,
139                "r" | "replacements" => SubstitutionStep::CharacterReplacements,
140                "m" | "macros" => SubstitutionStep::Macros,
141                "p" | "post_replacements" => SubstitutionStep::PostReplacement,
142                _ => {
143                    return None;
144                }
145            };
146
147            if prepend {
148                steps.insert(0, step);
149            } else if append {
150                steps.push(step);
151            } else if subtract {
152                steps.retain(|s| s != &step);
153            } else {
154                steps.push(step);
155            }
156        }
157
158        Some(Self::Custom(steps))
159    }
160
161    pub(crate) fn apply(
162        &self,
163        content: &mut Content<'_>,
164        parser: &Parser,
165        attrlist: Option<&Attrlist>,
166    ) {
167        let steps = self.steps();
168
169        let passthroughs: Option<Passthroughs> =
170            if steps.contains(&SubstitutionStep::Macros) || self == &Self::Header {
171                Some(Passthroughs::extract_from(content))
172            } else {
173                None
174            };
175
176        for step in steps {
177            step.apply(content, parser, attrlist);
178        }
179
180        if let Some(passthroughs) = passthroughs {
181            passthroughs.restore_to(content, parser);
182        }
183    }
184
185    pub(crate) fn override_via_attrlist(&self, attrlist: Option<&Attrlist>) -> Self {
186        let mut result = self.clone();
187
188        if let Some(attrlist) = attrlist {
189            if let Some(block_style) = attrlist.nth_attribute(1).and_then(|a| a.block_style()) {
190                result = match block_style {
191                    // TO DO: Many other style-specific substitution groups.
192                    "pass" => SubstitutionGroup::None,
193                    _ => result,
194                };
195            }
196
197            if let Some(sub_group) = attrlist
198                .named_attribute("subs")
199                .map(|attr| attr.value())
200                .and_then(|s| Self::from_custom_string(Some(self), s))
201            {
202                result = sub_group;
203            }
204        }
205
206        result
207    }
208
209    fn steps(&self) -> &[SubstitutionStep] {
210        match self {
211            Self::Normal => &[
212                SubstitutionStep::SpecialCharacters,
213                SubstitutionStep::Quotes,
214                SubstitutionStep::AttributeReferences,
215                SubstitutionStep::CharacterReplacements,
216                SubstitutionStep::Macros,
217                SubstitutionStep::PostReplacement,
218            ],
219
220            Self::Header | Self::AttributeEntryValue => &[
221                SubstitutionStep::SpecialCharacters,
222                SubstitutionStep::AttributeReferences,
223            ],
224
225            Self::Verbatim => &[SubstitutionStep::SpecialCharacters],
226
227            Self::Pass | Self::None => &[],
228
229            Self::Custom(steps) => steps,
230        }
231    }
232}