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