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