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 citeproc_io::output::{
8     micro_html::micro_html_to_string, FormatCmd, LocalizedQuotes, OutputFormat,
9 };
10 use citeproc_io::{lazy, IngestOptions, SmartString};
11 
12 use csl::{DisplayMode, Formatting};
13 
14 #[derive(Debug, Clone, PartialEq)]
15 pub struct SortStringFormat;
16 
17 impl Default for SortStringFormat {
default() -> Self18     fn default() -> Self {
19         SortStringFormat
20     }
21 }
22 
23 // We don't want these characters in a sort string
remove_quotes(s: SmartString) -> SmartString24 fn remove_quotes(s: SmartString) -> SmartString {
25     lazy::lazy_char_transform_owned(s, |c: char| {
26         if c == '\'' || c == '"' || c == ',' {
27             None
28         } else {
29             Some(c)
30         }
31         .into_iter()
32     })
33 }
34 
35 impl OutputFormat for SortStringFormat {
36     type Input = SmartString;
37     type Build = SmartString;
38     type Output = SmartString;
39     type BibMeta = ();
40 
meta(&self) -> Self::BibMeta41     fn meta(&self) -> Self::BibMeta {}
42 
43     #[inline]
ingest(&self, input: &str, options: &IngestOptions) -> Self::Build44     fn ingest(&self, input: &str, options: &IngestOptions) -> Self::Build {
45         remove_quotes(micro_html_to_string(input, options))
46     }
47 
48     #[inline]
plain(&self, s: &str) -> Self::Build49     fn plain(&self, s: &str) -> Self::Build {
50         s.into()
51     }
52 
53     #[inline]
text_node(&self, s: SmartString, _: Option<Formatting>) -> Self::Build54     fn text_node(&self, s: SmartString, _: Option<Formatting>) -> Self::Build {
55         s
56     }
57 
join_delim(&self, mut a: Self::Build, delim: &str, b: Self::Build) -> Self::Build58     fn join_delim(&self, mut a: Self::Build, delim: &str, b: Self::Build) -> Self::Build {
59         a.push_str(&delim);
60         a.push_str(&b);
61         a
62     }
63 
seq(&self, mut nodes: impl Iterator<Item = Self::Build>) -> Self::Build64     fn seq(&self, mut nodes: impl Iterator<Item = Self::Build>) -> Self::Build {
65         if let Some(first) = nodes.next() {
66             nodes.fold(first, |mut a, b| {
67                 a.push_str(&b);
68                 a
69             })
70         } else {
71             SmartString::new()
72         }
73     }
74 
group( &self, nodes: Vec<Self::Build>, delimiter: &str, _f: Option<Formatting>, ) -> Self::Build75     fn group(
76         &self,
77         nodes: Vec<Self::Build>,
78         delimiter: &str,
79         _f: Option<Formatting>,
80     ) -> Self::Build {
81         let std_string = nodes.join(delimiter);
82         SmartString::from(std_string)
83     }
84 
quoted(&self, b: Self::Build, _quotes: LocalizedQuotes) -> Self::Build85     fn quoted(&self, b: Self::Build, _quotes: LocalizedQuotes) -> Self::Build {
86         // We don't want quotes because sorting macros should ignore them
87         // quotes.opening(false).to_owned() + &b + quotes.closing(false)
88         b
89     }
90 
91     #[inline]
with_format(&self, a: Self::Build, _f: Option<Formatting>) -> Self::Build92     fn with_format(&self, a: Self::Build, _f: Option<Formatting>) -> Self::Build {
93         a
94     }
95 
96     #[inline]
with_display(&self, a: Self::Build, _d: Option<DisplayMode>, _in_bib: bool) -> Self::Build97     fn with_display(&self, a: Self::Build, _d: Option<DisplayMode>, _in_bib: bool) -> Self::Build {
98         a
99     }
100 
101     #[inline]
hyperlinked(&self, a: Self::Build, _target: Option<&str>) -> Self::Build102     fn hyperlinked(&self, a: Self::Build, _target: Option<&str>) -> Self::Build {
103         a
104     }
105 
106     #[inline]
is_empty(&self, a: &Self::Build) -> bool107     fn is_empty(&self, a: &Self::Build) -> bool {
108         a.is_empty()
109     }
110 
111     #[inline]
output_in_context( &self, intermediate: Self::Build, _formatting: Formatting, _punctuation_in_quote: Option<bool>, ) -> Self::Output112     fn output_in_context(
113         &self,
114         intermediate: Self::Build,
115         _formatting: Formatting,
116         _punctuation_in_quote: Option<bool>,
117     ) -> Self::Output {
118         intermediate
119     }
120 
121     #[inline]
stack_preorder(&self, _s: &mut SmartString, _stack: &[FormatCmd])122     fn stack_preorder(&self, _s: &mut SmartString, _stack: &[FormatCmd]) {}
123     #[inline]
stack_postorder(&self, _s: &mut SmartString, _stack: &[FormatCmd])124     fn stack_postorder(&self, _s: &mut SmartString, _stack: &[FormatCmd]) {}
125     #[inline]
tag_stack(&self, _formatting: Formatting, _: Option<DisplayMode>) -> Vec<FormatCmd>126     fn tag_stack(&self, _formatting: Formatting, _: Option<DisplayMode>) -> Vec<FormatCmd> {
127         Vec::new()
128     }
129 
130     #[inline]
append_suffix(&self, pre_and_content: &mut Self::Build, suffix: &str)131     fn append_suffix(&self, pre_and_content: &mut Self::Build, suffix: &str) {
132         // TODO: do moving punctuation here as well
133         pre_and_content.push_str(suffix)
134     }
135 
ends_with_full_stop(&self, _build: &Self::Build) -> bool136     fn ends_with_full_stop(&self, _build: &Self::Build) -> bool {
137         // not needed
138         false
139     }
140 
141     #[inline]
apply_text_case(&self, build: &mut Self::Build, options: &IngestOptions)142     fn apply_text_case(&self, build: &mut Self::Build, options: &IngestOptions) {
143         let is_uppercase = !build.chars().any(|c| c.is_lowercase());
144         let string = std::mem::replace(build, SmartString::new());
145         *build = options.transform_case(string, false, true, is_uppercase);
146     }
147 }
148