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}