1use crate::{Field, Subfield};
2use std::ops::RangeInclusive;
3
4pub struct FieldQuery {
5 pub field_filter: Box<dyn Fn(&&Field) -> bool>,
6}
7
8impl From<RangeInclusive<i64>> for FieldQuery {
9 fn from(range: RangeInclusive<i64>) -> Self {
10 FieldQuery {
11 field_filter: Box::new(move |f: &&Field| match f.tag().parse::<i64>() {
12 Ok(tag_number) => range.contains(&tag_number),
13 Err(_) => false,
14 }),
15 }
16 }
17}
18
19impl From<&str> for FieldQuery {
20 fn from(spec_input: &str) -> Self {
21 let specs: Vec<String> = spec_input.split(':').map(str::to_owned).collect();
22 FieldQuery {
23 field_filter: Box::new(move |f: &&Field| specs.iter().any(|spec| f.matches_spec(spec))),
24 }
25 }
26}
27
28pub struct FieldQueryMut {
30 pub field_filter: Box<dyn FnMut(&&mut Field) -> bool>,
31}
32
33impl From<RangeInclusive<i64>> for FieldQueryMut {
34 fn from(range: RangeInclusive<i64>) -> Self {
35 FieldQueryMut {
36 field_filter: Box::new(move |f: &&mut Field| match f.tag().parse::<i64>() {
37 Ok(tag_number) => range.contains(&tag_number),
38 Err(_) => false,
39 }),
40 }
41 }
42}
43
44impl From<&str> for FieldQueryMut {
45 fn from(spec_input: &str) -> Self {
46 let specs: Vec<String> = spec_input.split(':').map(str::to_owned).collect();
47 FieldQueryMut {
48 field_filter: Box::new(move |f: &&mut Field| {
49 specs.iter().any(|spec| f.matches_spec(spec))
50 }),
51 }
52 }
53}
54
55#[derive(Clone, Debug)]
56pub struct ComplexSpecification {
57 tag: String,
58 ind1: Option<char>,
59 ind2: Option<char>,
60 subfields: Vec<char>,
61}
62
63impl ComplexSpecification {
64 pub fn subfield_filter(&self) -> impl Fn(&Subfield, &Field) -> bool + use<'_> {
65 |subfield: &Subfield, field: &Field| match subfield.code().chars().next() {
66 Some(first_char) => self.subfields.contains(&first_char) && self.matches_field(field),
67 None => false,
68 }
69 }
70
71 pub fn matches_field(&self, field: &Field) -> bool {
72 field.matches_spec(&self.tag) && self.ind1_matches(field) && self.ind2_matches(field)
73 }
74
75 fn ind1_matches(&self, field: &Field) -> bool {
76 match self.ind1 {
77 Some(i) => {
78 i == '*' || field.ind1() == i.to_string()
80 || field.ind1().trim().is_empty() && i == '_' }
82 None => true,
83 }
84 }
85
86 fn ind2_matches(&self, field: &Field) -> bool {
87 match self.ind2 {
88 Some(i) => {
89 i == '*'
90 || field.ind2() == i.to_string()
91 || field.ind1().trim().is_empty() && i == '_'
92 }
93 None => true,
94 }
95 }
96
97 fn parse_indicators_and_subfields(
98 remainder: &mut impl Iterator<Item = char>,
99 ) -> (Option<char>, Option<char>, Vec<char>) {
100 let first = remainder.next();
104 let ind1 = match first {
105 Some('(') => remainder.next(),
106 _ => None,
107 };
108 let ind2 = match first {
109 Some('(') => remainder.next(),
110 _ => None,
111 };
112 if first == Some('(') {
113 remainder.next(); }
115 let subfields: Vec<char> = match first {
116 Some('(') => remainder.collect(),
117 _ => first.into_iter().chain(remainder).collect(),
118 };
119
120 (ind1, ind2, subfields)
121 }
122}
123
124impl From<&str> for ComplexSpecification {
125 fn from(raw_spec: &str) -> ComplexSpecification {
126 let mut rest = raw_spec.chars();
127 let tag = rest.by_ref().take(3).collect::<String>();
128 let (ind1, ind2, subfields) = Self::parse_indicators_and_subfields(&mut rest);
129 ComplexSpecification {
130 tag,
131 ind1,
132 ind2,
133 subfields,
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::Record;
142
143 #[test]
144 fn test_can_filter_by_inclusive_range() {
145 let query = FieldQuery::from(600..=699);
146
147 let record = Record::from_breaker(
148 r#"=600 10$aZhang, Heng, $d 78-139 $v Juvenile literature.
149=650 \0$aEarthquakes $v Juvenile literature.
150=955 \0$a1234"#,
151 )
152 .unwrap();
153 let filter = query.field_filter;
154 let mut filtered = record.fields().iter().filter(filter);
155 assert_eq!(filtered.next().unwrap().tag(), "600");
156 assert_eq!(filtered.next().unwrap().tag(), "650");
157 assert!(filtered.next().is_none());
158 }
159
160 #[test]
161 fn test_can_filter_by_string_slice() {
162 let query = FieldQuery::from("x50");
163
164 let record = Record::from_breaker(
165 r#"=150 \\$aLion
166=450 \\$aPanthera leo
167=550 \\$wg$aPanthera
168=953 \\$axx00$bec11"#,
169 )
170 .unwrap();
171 let filter = query.field_filter;
172 let mut filtered = record.fields().iter().filter(filter);
173 assert_eq!(filtered.next().unwrap().tag(), "150");
174 assert_eq!(filtered.next().unwrap().tag(), "450");
175 assert_eq!(filtered.next().unwrap().tag(), "550");
176 assert!(filtered.next().is_none());
177 }
178
179 #[test]
180 fn test_can_filter_by_string_slice_with_multiple_specs() {
181 let query = FieldQuery::from("600:9XX");
182
183 let record = Record::from_breaker(
184 r#"=600 10$aZhang, Heng, $d 78-139 $v Juvenile literature.
185=650 \0$aEarthquakes $v Juvenile literature.
186=955 \0$a1234"#,
187 )
188 .unwrap();
189 let filter = query.field_filter;
190 let mut filtered = record.fields().iter().filter(filter);
191 assert_eq!(filtered.next().unwrap().tag(), "600");
192 assert_eq!(filtered.next().unwrap().tag(), "955");
193 assert!(filtered.next().is_none());
194 }
195
196 #[test]
197 fn test_if_filter_ignores_junk_and_non_numeric_tags() {
198 let query = FieldQuery::from("6XX:ABC$DEF");
199
200 let record =
202 Record::from_breaker(r#"=ABC \0$aEarthquakes $v Juvenile literature."#).unwrap();
203 let filter = query.field_filter;
204 let mut filtered = record.fields().iter().filter(filter);
205 assert!(filtered.next().is_none());
206 }
207
208 #[test]
209 fn test_can_create_complex_spec() {
210 let spec = ComplexSpecification::from("245a");
211 assert_eq!(spec.tag, "245");
212 assert_eq!(spec.ind1, None);
213 assert_eq!(spec.ind2, None);
214 assert_eq!(spec.subfields, vec!['a']);
215
216 let indicator_spec = ComplexSpecification::from("6xx(01)av");
217 assert_eq!(indicator_spec.tag, "6xx");
218 assert_eq!(indicator_spec.ind1, Some('0'));
219 assert_eq!(indicator_spec.ind2, Some('1'));
220 assert_eq!(indicator_spec.subfields, vec!['a', 'v']);
221 }
222
223 #[test]
224 fn test_can_create_subfield_filter() {
225 let mut field = Field::new("245").unwrap();
226 let _ = field.add_subfield("a", "My title");
227 let _ = field.add_subfield("b", "My subtitle");
228
229 let spec = ComplexSpecification::from("245a");
230 let mut filtered = field
231 .subfields()
232 .iter()
233 .filter(|sf| spec.subfield_filter()(&sf, &field));
234 assert_eq!(filtered.next().unwrap().code(), "a");
235 assert_eq!(filtered.next(), None);
236 }
237
238 #[test]
239 fn test_can_match_fields_to_complex_specifications() {
240 let mut field = Field::new("245").unwrap();
241 let _ = field.set_ind1("1");
242 let _ = field.set_ind2("4");
243
244 assert!(ComplexSpecification::from("245a").matches_field(&field));
245 assert!(ComplexSpecification::from("2xxa").matches_field(&field));
246 assert!(ComplexSpecification::from("245(14)a").matches_field(&field));
247 assert!(ComplexSpecification::from("245(1*)a").matches_field(&field));
248 assert!(!ComplexSpecification::from("245(00)a").matches_field(&field));
249 assert!(!ComplexSpecification::from("650(14)").matches_field(&field));
250 }
251}