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