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}