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 crate::String;
8 use std::marker::{Send, Sync};
9 
10 use crate::IngestOptions;
11 use csl::{Atom, Locale, QuoteTerm, SimpleTermSelector};
12 
13 #[cfg(feature = "markup")]
14 pub mod markup;
15 pub mod micro_html;
16 #[cfg(feature = "pandoc")]
17 pub mod pandoc;
18 mod superscript;
19 
20 // pub use self::pandoc::Pandoc;
21 // pub use self::plain::PlainText;
22 // pub use self::markup::Markup;
23 
24 use csl::{Affixes, DisplayMode, Formatting};
25 use serde::{de::DeserializeOwned, Serialize};
26 
27 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28 pub struct LocalizedQuotes {
29     pub outer: (Atom, Atom),
30     pub inner: (Atom, Atom),
31     /// Default false, pulled from LocaleOptions
32     pub punctuation_in_quote: bool,
33 }
34 
35 impl LocalizedQuotes {
closing(&self, is_inner: bool) -> &str36     pub fn closing(&self, is_inner: bool) -> &str {
37         if is_inner {
38             self.outer.1.as_ref()
39         } else {
40             self.inner.1.as_ref()
41         }
42     }
opening(&self, is_inner: bool) -> &str43     pub fn opening(&self, is_inner: bool) -> &str {
44         if is_inner {
45             self.outer.0.as_ref()
46         } else {
47             self.inner.0.as_ref()
48         }
49     }
50 
simple() -> Self51     pub fn simple() -> Self {
52         LocalizedQuotes {
53             outer: (Atom::from("\u{201C}"), Atom::from("\u{201D}")),
54             inner: (Atom::from("\u{2018}"), Atom::from("\u{2019}")),
55             punctuation_in_quote: false,
56         }
57     }
58 
from_locale(locale: &Locale) -> Self59     pub fn from_locale(locale: &Locale) -> Self {
60         let getter = |qt: QuoteTerm| {
61             locale
62                 .simple_terms
63                 .get(&SimpleTermSelector::Quote(qt))
64                 .unwrap()
65                 .singular()
66         };
67         let open_outer = getter(QuoteTerm::OpenQuote);
68         let close_outer = getter(QuoteTerm::CloseQuote);
69         let open_inner = getter(QuoteTerm::OpenInnerQuote);
70         let close_inner = getter(QuoteTerm::CloseInnerQuote);
71         LocalizedQuotes {
72             outer: (Atom::from(open_outer), Atom::from(close_outer)),
73             inner: (Atom::from(open_inner), Atom::from(close_inner)),
74             punctuation_in_quote: locale.options_node.punctuation_in_quote.unwrap_or(false),
75         }
76     }
77 }
78 
79 impl Default for LocalizedQuotes {
default() -> Self80     fn default() -> Self {
81         LocalizedQuotes::simple()
82     }
83 }
84 
85 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
86 pub enum FormatCmd {
87     FontStyleItalic,
88     FontStyleOblique,
89     FontStyleNormal,
90     FontWeightBold,
91     FontWeightNormal,
92     FontWeightLight,
93     FontVariantSmallCaps,
94     FontVariantNormal,
95     TextDecorationUnderline,
96     TextDecorationNone,
97     VerticalAlignmentSuperscript,
98     VerticalAlignmentSubscript,
99     VerticalAlignmentBaseline,
100     DisplayBlock,
101     DisplayIndent,
102     DisplayLeftMargin,
103     DisplayRightInline,
104 }
105 
106 use std::hash::Hash;
107 
108 pub trait OutputFormat: Send + Sync + Clone + Default + PartialEq + std::fmt::Debug {
109     type Input: std::fmt::Debug + DeserializeOwned + Default + Clone + Send + Sync + Eq + Hash;
110     type Build: std::fmt::Debug + Default + Clone + Send + Sync + Eq;
111     type Output: Default + Clone + Send + Sync + Eq + Serialize;
112     type BibMeta: Serialize;
113 
meta(&self) -> Self::BibMeta114     fn meta(&self) -> Self::BibMeta;
115 
ingest(&self, input: &str, options: &IngestOptions) -> Self::Build116     fn ingest(&self, input: &str, options: &IngestOptions) -> Self::Build;
117 
118     /// Affixes are not included in the formatting on a text node. They are converted into text
119     /// nodes themselves, with no formatting except whatever is applied by a parent group.
120     ///
121     /// [Spec](https://docs.citationstyles.org/en/stable/specification.html#affixes)
122 
123     // TODO: make formatting an Option<Formatting>
text_node(&self, s: String, formatting: Option<Formatting>) -> Self::Build124     fn text_node(&self, s: String, formatting: Option<Formatting>) -> Self::Build;
125 
126     /// Group some text nodes. You might want to optimise for the case where delimiter is empty.
group( &self, nodes: Vec<Self::Build>, delimiter: &str, formatting: Option<Formatting>, ) -> Self::Build127     fn group(
128         &self,
129         nodes: Vec<Self::Build>,
130         delimiter: &str,
131         formatting: Option<Formatting>,
132     ) -> Self::Build;
133 
seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build134     fn seq(&self, nodes: impl Iterator<Item = Self::Build>) -> Self::Build;
135 
join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build136     fn join_delim(&self, a: Self::Build, delim: &str, b: Self::Build) -> Self::Build;
137 
is_empty(&self, a: &Self::Build) -> bool138     fn is_empty(&self, a: &Self::Build) -> bool;
output(&self, intermediate: Self::Build, punctuation_in_quote: bool) -> Self::Output139     fn output(&self, intermediate: Self::Build, punctuation_in_quote: bool) -> Self::Output {
140         self.output_in_context(
141             intermediate,
142             Formatting::default(),
143             Some(punctuation_in_quote),
144         )
145     }
146 
output_in_context( &self, intermediate: Self::Build, _format_stacked: Formatting, punctuation_in_quote: Option<bool>, ) -> Self::Output147     fn output_in_context(
148         &self,
149         intermediate: Self::Build,
150         _format_stacked: Formatting,
151         punctuation_in_quote: Option<bool>,
152     ) -> Self::Output;
153 
plain(&self, s: &str) -> Self::Build154     fn plain(&self, s: &str) -> Self::Build;
155 
affixed_text_quoted( &self, s: String, format_inner: Option<Formatting>, affixes: Option<&Affixes>, quotes: Option<LocalizedQuotes>, ) -> Self::Build156     fn affixed_text_quoted(
157         &self,
158         s: String,
159         format_inner: Option<Formatting>,
160         affixes: Option<&Affixes>,
161         quotes: Option<LocalizedQuotes>,
162     ) -> Self::Build {
163         self.affixed_quoted(self.text_node(s, format_inner), affixes, quotes)
164     }
165 
affixed_text( &self, s: String, format_inner: Option<Formatting>, affixes: Option<&Affixes>, ) -> Self::Build166     fn affixed_text(
167         &self,
168         s: String,
169         format_inner: Option<Formatting>,
170         affixes: Option<&Affixes>,
171     ) -> Self::Build {
172         self.affixed(self.text_node(s, format_inner), affixes)
173     }
174 
quoted(&self, b: Self::Build, quotes: LocalizedQuotes) -> Self::Build175     fn quoted(&self, b: Self::Build, quotes: LocalizedQuotes) -> Self::Build;
176 
177     #[inline]
affixed(&self, b: Self::Build, affixes: Option<&Affixes>) -> Self::Build178     fn affixed(&self, b: Self::Build, affixes: Option<&Affixes>) -> Self::Build {
179         self.affixed_quoted(b, affixes, None)
180     }
181 
affixed_quoted( &self, b: Self::Build, affixes: Option<&Affixes>, quotes: Option<LocalizedQuotes>, ) -> Self::Build182     fn affixed_quoted(
183         &self,
184         b: Self::Build,
185         affixes: Option<&Affixes>,
186         quotes: Option<LocalizedQuotes>,
187     ) -> Self::Build {
188         use std::iter::once;
189         let b = if let Some(lq) = quotes {
190             self.quoted(b, lq)
191         } else {
192             b
193         };
194         let mut pre_and_content = if let Some(prefix) = affixes.as_ref().map(|a| &a.prefix) {
195             if !prefix.is_empty() {
196                 // TODO: use the localized quotes.
197                 self.seq(once(self.ingest(prefix, &IngestOptions::for_affixes())).chain(once(b)))
198             } else {
199                 b
200             }
201         } else {
202             b
203         };
204         if let Some(suffix) = affixes.as_ref().map(|a| &a.suffix) {
205             if !suffix.is_empty() {
206                 self.append_suffix(&mut pre_and_content, suffix);
207             }
208         }
209         pre_and_content
210     }
211 
append_suffix(&self, pre_and_content: &mut Self::Build, suffix: &str)212     fn append_suffix(&self, pre_and_content: &mut Self::Build, suffix: &str);
ends_with_full_stop(&self, build: &Self::Build) -> bool213     fn ends_with_full_stop(&self, build: &Self::Build) -> bool;
214 
apply_text_case(&self, mutable: &mut Self::Build, options: &IngestOptions)215     fn apply_text_case(&self, mutable: &mut Self::Build, options: &IngestOptions);
216 
with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build217     fn with_format(&self, a: Self::Build, f: Option<Formatting>) -> Self::Build;
with_display( &self, a: Self::Build, display: Option<DisplayMode>, in_bibliography: bool, ) -> Self::Build218     fn with_display(
219         &self,
220         a: Self::Build,
221         display: Option<DisplayMode>,
222         in_bibliography: bool,
223     ) -> Self::Build;
224 
hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build225     fn hyperlinked(&self, a: Self::Build, target: Option<&str>) -> Self::Build;
226 
stack_preorder(&self, s: &mut String, stack: &[FormatCmd])227     fn stack_preorder(&self, s: &mut String, stack: &[FormatCmd]);
stack_postorder(&self, s: &mut String, stack: &[FormatCmd])228     fn stack_postorder(&self, s: &mut String, stack: &[FormatCmd]);
tag_stack(&self, formatting: Formatting, display: Option<DisplayMode>) -> Vec<FormatCmd>229     fn tag_stack(&self, formatting: Formatting, display: Option<DisplayMode>) -> Vec<FormatCmd>;
230 }
231