1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 //
5 // Copyright © 2018 Corporation for Digital Scholarship
6
7 use super::{LocalizedQuotes, OutputFormat};
8 use crate::utils::{Intercalate, JoinMany};
9 use csl::{FontStyle, FontVariant, FontWeight, Formatting, TextDecoration, VerticalAlignment};
10
11 use pandoc_types::definition::Inline::*;
12 use pandoc_types::definition::{Attr, Inline, QuoteType, Target};
13
14 #[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
15 pub struct Pandoc {}
16
17 impl Default for Pandoc {
default() -> Self18 fn default() -> Self {
19 Pandoc {}
20 }
21 }
22
23 impl Pandoc {
24 /// Wrap some nodes with formatting
25 ///
26 /// In pandoc, Emph, Strong and SmallCaps, Superscript and Subscript are all single-use styling
27 /// elements. So formatting with two of those styles at once requires wrapping twice, in any
28 /// order.
29
fmt_vec(&self, inlines: Vec<Inline>, formatting: Option<Formatting>) -> Vec<Inline>30 fn fmt_vec(&self, inlines: Vec<Inline>, formatting: Option<Formatting>) -> Vec<Inline> {
31 if let Some(f) = formatting {
32 let mut current = inlines;
33
34 current = match f.font_style {
35 FontStyle::Italic | FontStyle::Oblique => vec![Emph(current)],
36 _ => current,
37 };
38 current = match f.font_weight {
39 FontWeight::Bold => vec![Strong(current)],
40 // Light => unimplemented!(),
41 _ => current,
42 };
43 current = match f.font_variant {
44 FontVariant::SmallCaps => vec![SmallCaps(current)],
45 _ => current,
46 };
47 current = match f.text_decoration {
48 Some(TextDecoration::Underline) => vec![Span(attr_class("underline"), current)],
49 _ => current,
50 };
51 current = match f.vertical_alignment {
52 Some(VerticalAlignment::Superscript) => vec![Superscript(current)],
53 Some(VerticalAlignment::Subscript) => vec![Subscript(current)],
54 _ => current,
55 };
56
57 current
58 } else {
59 inlines
60 }
61 }
62 }
63
64 impl OutputFormat for Pandoc {
65 type Input = Vec<Inline>;
66 type Build = Vec<Inline>;
67 type Output = Vec<Inline>;
68
69 #[inline]
ingest(&self, input: Self::Input) -> Self::Build70 fn ingest(&self, input: Self::Input) -> Self::Build {
71 input
72 }
73
74 #[inline]
plain(&self, s: &str) -> Self::Build75 fn plain(&self, s: &str) -> Self::Build {
76 self.text_node(s.to_owned(), None)
77 }
78
text_node(&self, text: String, f: Option<Formatting>) -> Vec<Inline>79 fn text_node(&self, text: String, f: Option<Formatting>) -> Vec<Inline> {
80 let v: Vec<Inline> = text
81 // TODO: write a nom tokenizer, don't use split/intercalate
82 .split(' ')
83 .map(|s| Str(s.to_owned()))
84 .intercalate(&Space)
85 .into_iter()
86 .filter(|t| match t {
87 Str(ref s) if s == "" => false,
88 _ => true,
89 })
90 .collect();
91
92 self.fmt_vec(v, f)
93 }
94
95 #[inline]
seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build96 fn seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build {
97 itertools::concat(nodes)
98 }
99
join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build100 fn join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build {
101 [a, b].join_many(&self.plain(delim))
102 }
103
group( &self, nodes: Vec<Self::Build>, delimiter: &str, formatting: Option<Formatting>, ) -> Self::Build104 fn group(
105 &self,
106 nodes: Vec<Self::Build>,
107 delimiter: &str,
108 formatting: Option<Formatting>,
109 ) -> Self::Build {
110 if nodes.len() == 1 {
111 self.fmt_vec(nodes.into_iter().nth(0).unwrap(), formatting)
112 } else {
113 let delim = self.plain(delimiter);
114 self.fmt_vec(nodes.join_many(&delim), formatting)
115 }
116 }
117
118 #[inline]
with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build119 fn with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build {
120 self.fmt_vec(a, f)
121 }
122
quoted(&self, b: Self::Build, quotes: &LocalizedQuotes) -> Self::Build123 fn quoted(&self, b: Self::Build, quotes: &LocalizedQuotes) -> Self::Build {
124 let qt = match quotes {
125 LocalizedQuotes::Single(..) => QuoteType::SingleQuote,
126 LocalizedQuotes::Double(..) => QuoteType::DoubleQuote,
127 };
128 vec![Inline::Quoted(qt, b)]
129 }
130
hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build131 fn hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build {
132 // TODO: allow internal linking using the Attr parameter (e.g.
133 // first-reference-note-number)
134 if let Some(target) = target {
135 vec![Inline::Link(
136 Default::default(),
137 a,
138 Target(target.to_string(), "".to_string()),
139 )]
140 } else {
141 a
142 }
143 }
144
output(&self, inter: Vec<Inline>) -> Vec<Inline>145 fn output(&self, inter: Vec<Inline>) -> Vec<Inline> {
146 let null = FlipFlopState::default();
147 flip_flop_inlines(&inter, &null)
148 // TODO: convert quotes to inner and outer quote terms
149 }
150 }
151
152 #[derive(Default, Debug, Clone)]
153 struct FlipFlopState {
154 in_emph: bool,
155 in_strong: bool,
156 in_small_caps: bool,
157 in_outer_quotes: bool,
158 }
159
attr_class(class: &str) -> Attr160 fn attr_class(class: &str) -> Attr {
161 Attr("".to_owned(), vec![class.to_owned()], vec![])
162 }
163
flip_flop_inlines(inlines: &[Inline], state: &FlipFlopState) -> Vec<Inline>164 fn flip_flop_inlines(inlines: &[Inline], state: &FlipFlopState) -> Vec<Inline> {
165 inlines
166 .iter()
167 .map(|inl| flip_flop(inl, state).unwrap_or_else(|| inl.clone()))
168 .collect()
169 }
170
flip_flop(inline: &Inline, state: &FlipFlopState) -> Option<Inline>171 fn flip_flop(inline: &Inline, state: &FlipFlopState) -> Option<Inline> {
172 use pandoc_types::definition::*;
173 let fl = |ils: &[Inline], st| flip_flop_inlines(ils, st);
174 match inline {
175 // Note(ref blocks) => {
176 // if let Some(Block::Para(ref ils)) = blocks.into_iter().nth(0) {
177 // Some(Note(vec![Block::Para(fl(ils, state))]))
178 // } else {
179 // None
180 // }
181 // }
182 Emph(ref ils) => {
183 let mut flop = state.clone();
184 flop.in_emph = !flop.in_emph;
185 if state.in_emph {
186 Some(Span(attr_class("csl-no-emph"), fl(ils, &flop)))
187 } else {
188 Some(Emph(fl(ils, &flop)))
189 }
190 }
191
192 Strong(ref ils) => {
193 let mut flop = state.clone();
194 flop.in_strong = !flop.in_strong;
195 let subs = fl(ils, &flop);
196 if state.in_strong {
197 Some(Span(attr_class("csl-no-strong"), subs))
198 } else {
199 Some(Strong(subs))
200 }
201 }
202
203 SmallCaps(ref ils) => {
204 let mut flop = state.clone();
205 flop.in_small_caps = !flop.in_small_caps;
206 let subs = fl(ils, &flop);
207 if state.in_small_caps {
208 Some(Span(attr_class("csl-no-smallcaps"), subs))
209 } else {
210 Some(SmallCaps(subs))
211 }
212 }
213
214 Quoted(ref _q, ref ils) => {
215 let mut flop = state.clone();
216 flop.in_outer_quotes = !flop.in_outer_quotes;
217 let subs = fl(ils, &flop);
218 if !state.in_outer_quotes {
219 Some(Quoted(QuoteType::SingleQuote, subs))
220 } else {
221 Some(Quoted(QuoteType::DoubleQuote, subs))
222 }
223 }
224
225 Strikeout(ref ils) => {
226 let subs = fl(ils, state);
227 Some(Strikeout(subs))
228 }
229
230 Superscript(ref ils) => {
231 let subs = fl(ils, state);
232 Some(Superscript(subs))
233 }
234
235 Subscript(ref ils) => {
236 let subs = fl(ils, state);
237 Some(Subscript(subs))
238 }
239
240 Link(attr, ref ils, t) => {
241 let subs = fl(ils, state);
242 Some(Link(attr.clone(), subs, t.clone()))
243 }
244
245 _ => None,
246 }
247
248 // a => a
249 }
250
251 #[cfg(test)]
252 mod test {
253 use super::*;
254
255 #[test]
test_space()256 fn test_space() {
257 let f = Pandoc::default();
258 assert_eq!(f.plain(" ")[0], Space);
259 assert_eq!(f.plain(" "), &[Space, Space]);
260 assert_eq!(f.plain(" h "), &[Space, Str("h".into()), Space]);
261 assert_eq!(
262 f.plain(" hello "),
263 &[Space, Space, Str("hello".into()), Space]
264 );
265 }
266
267 #[test]
test_flip_emph()268 fn test_flip_emph() {
269 let f = Pandoc::default();
270 let a = f.plain("normal");
271 let b = f.text_node("emph".into(), Some(Formatting::italic()));
272 let c = f.plain("normal");
273 let group = f.group(vec![a, b, c], " ", Some(Formatting::italic()));
274 let out = f.output(group.clone());
275 assert_ne!(group, out);
276 }
277 }
278