1use super::Controlfield;
3use super::Field;
4use super::Record;
5use super::Subfield;
6
7const MARC_BREAKER_SF_DELIMITER: &str = "$";
8const MARC_BREAKER_SF_DELIMITER_ESCAPE: &str = "{dollar}";
9
10pub fn escape_to_breaker(value: &str) -> String {
12 value.replace(MARC_BREAKER_SF_DELIMITER, MARC_BREAKER_SF_DELIMITER_ESCAPE)
13}
14
15pub fn unescape_from_breaker(value: &str) -> String {
17 value.replace(MARC_BREAKER_SF_DELIMITER_ESCAPE, MARC_BREAKER_SF_DELIMITER)
18}
19
20impl Controlfield {
21 pub fn to_breaker(&self) -> String {
23 if !self.content().is_empty() {
24 format!("={} {}", self.tag(), escape_to_breaker(self.content()))
25 } else {
26 format!("={}", self.tag())
27 }
28 }
29}
30
31impl Subfield {
32 pub fn to_breaker(&self) -> String {
41 format!(
42 "${}{}",
43 escape_to_breaker(self.code()),
44 escape_to_breaker(self.content()),
45 )
46 }
47}
48
49impl Field {
50 pub fn to_breaker(&self) -> String {
61 let mut s = format!(
62 "={} {}{}",
63 self.tag(),
64 if self.ind1() == " " {
65 "\\"
66 } else {
67 self.ind1()
68 },
69 if self.ind2() == " " {
70 "\\"
71 } else {
72 self.ind2()
73 },
74 );
75
76 for sf in self.subfields() {
77 s += sf.to_breaker().as_str();
78 }
79
80 s
81 }
82}
83
84impl Record {
85 pub fn to_breaker(&self) -> String {
91 let mut s = format!("=LDR {}", &escape_to_breaker(self.leader()));
92
93 for cfield in self.control_fields() {
94 s += format!("\n{}", cfield.to_breaker()).as_str();
95 }
96
97 for field in self.fields() {
98 s += format!("\n{}", field.to_breaker()).as_str();
99 }
100
101 s
102 }
103
104 pub fn from_breaker(breaker: &str) -> Result<Self, String> {
133 if !breaker.starts_with('=') {
134 return Err("Breaker fields must begin with =".to_owned());
135 };
136 let mut record = Record::new();
137
138 for line in breaker
139 .split("\n=")
141 .map(|line| format!("={}", line.trim_start_matches('=')))
143 {
144 record.add_breaker_line(&line)?;
145 }
146
147 Ok(record)
148 }
149
150 pub fn from_breaker_file(filename: &str) -> Result<Self, String> {
154 let breaker = std::fs::read_to_string(filename)
155 .map_err(|e| format!("Error reading breaker file: {e}"))?;
156 Record::from_breaker(&breaker)
157 }
158
159 fn add_breaker_line(&mut self, line: &str) -> Result<(), String> {
161 let mut len = line.len();
162 if len < 4 {
163 return Ok(());
165 }
166
167 let line = &line[1..];
169 len -= 1;
170
171 let tag = &line[..3];
172
173 if tag.eq("LDR") {
174 if len > 4 {
175 self.set_leader(&line[4..])?;
176 }
177 return Ok(());
178 }
179
180 if tag < "010" {
181 let content = if len > 4 {
182 unescape_from_breaker(&line[4..])
183 } else {
184 "".to_string()
185 };
186 let cf = Controlfield::new(tag, content)?;
187 self.control_fields_mut().push(cf);
188 return Ok(());
189 }
190
191 let mut field = Field::new(tag)?;
192
193 if len > 4 {
196 field.set_ind1(line[4..5].replace('\\', " "))?;
197 }
198
199 if len > 5 {
200 field.set_ind2(line[5..6].replace('\\', " "))?;
201 }
202
203 if len > 6 {
204 for sf in line[6..].split(MARC_BREAKER_SF_DELIMITER) {
205 if sf.is_empty() {
206 continue;
207 }
208 let code = &sf[..1];
209 let content = &sf[1..]; field.subfields_mut().push(Subfield::new(code, content)?);
211 }
212 }
213
214 self.fields_mut().push(field);
215
216 Ok(())
217 }
218}
219
220#[cfg(test)]
221mod breaker_tests {
222 #[test]
223 fn test_add_breaker_line() {
224 let mut record = crate::Record::default();
225
226 assert!(record.add_breaker_line("=LDR too short").is_err());
227
228 record
229 .add_breaker_line("=100 11$aSunshine$b$csquashes")
230 .unwrap();
231 assert_eq!(record.get_field_values("100", "a")[0], "Sunshine");
232 assert_eq!(record.get_field_values("100", "b")[0], "");
233 }
234}