1use crate::parser::{Parser, ParserError};
2use crate::reader::XmlReader;
3use crate::shared::Image;
4use crate::util::{find_attr, maybe_text};
5use log::debug;
6use quick_xml::events::Event;
7use std::fmt;
8use std::mem::take;
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct Label {
13 pub id: u32,
14 pub name: String,
15 pub contactinfo: Option<String>,
16 pub profile: Option<String>,
17 pub parent_label: Option<LabelInfo>,
18 pub sublabels: Vec<LabelInfo>,
19 pub urls: Vec<String>,
20 pub data_quality: String,
21 pub images: Vec<Image>,
22}
23
24impl Label {
25 pub fn builder(id: u32, name: &str) -> LabelBuilder {
26 LabelBuilder {
27 inner: Label {
28 id,
29 name: name.to_string(),
30 ..Default::default()
31 },
32 }
33 }
34}
35
36#[derive(Clone, Debug, Default, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct LabelInfo {
39 pub id: u32,
40 pub name: String,
41}
42
43impl fmt::Display for Label {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 write!(f, "{}", self.name)
46 }
47}
48
49pub struct LabelsReader {
50 buf: Vec<u8>,
51 reader: XmlReader,
52 parser: LabelParser,
53}
54
55impl LabelsReader {
56 pub fn new(reader: XmlReader, buf: Vec<u8>) -> Self {
57 Self {
58 buf,
59 reader,
60 parser: LabelParser::new(),
61 }
62 }
63}
64
65impl Iterator for LabelsReader {
66 type Item = Label;
67 fn next(&mut self) -> Option<Self::Item> {
68 loop {
69 match self.reader.read_event_into(&mut self.buf).unwrap() {
70 Event::Eof => {
71 return None;
72 }
73 ev => self.parser.process(&ev).unwrap(),
74 };
75 if self.parser.item_ready {
76 return Some(self.parser.take());
77 }
78 self.buf.clear();
79 }
80 }
81}
82
83#[derive(Debug, Default)]
84enum ParserState {
85 #[default]
86 Label,
87 Name,
88 Id,
89 Images,
90 Contactinfo,
91 Profile,
92 ParentLabel,
93 Sublabels,
94 Sublabel,
95 Urls,
96 DataQuality,
97}
98
99#[derive(Debug, Default)]
100pub struct LabelParser {
101 state: ParserState,
102 current_item: Label,
103 current_sublabel_id: Option<u32>,
104 current_parent_id: Option<u32>,
105 item_ready: bool,
106}
107
108impl Parser for LabelParser {
109 type Item = Label;
110 fn new() -> Self {
111 Self::default()
112 }
113 fn take(&mut self) -> Self::Item {
114 self.item_ready = false;
115 take(&mut self.current_item)
116 }
117
118 fn process(&mut self, ev: &Event) -> Result<(), ParserError> {
119 self.state = match self.state {
120 ParserState::Label => match ev {
121 Event::Start(e) if e.local_name().as_ref() == b"label" => ParserState::Label,
122
123 Event::Start(e) => match e.local_name().as_ref() {
124 b"name" => ParserState::Name,
125 b"id" => ParserState::Id,
126 b"contactinfo" => ParserState::Contactinfo,
127 b"profile" => ParserState::Profile,
128 b"parentLabel" => {
129 self.current_parent_id = Some(find_attr(e, b"id")?.parse()?);
130 ParserState::ParentLabel
131 }
132 b"sublabels" => ParserState::Sublabels,
133 b"urls" => ParserState::Urls,
134 b"images" => ParserState::Images,
135 b"data_quality" => ParserState::DataQuality,
136 _ => ParserState::Label,
137 },
138 Event::End(e) if e.local_name().as_ref() == b"label" => {
139 self.item_ready = true;
140 ParserState::Label
141 }
142
143 _ => ParserState::Label,
144 },
145
146 ParserState::Id => match ev {
147 Event::Text(e) => {
148 self.current_item.id = e.unescape()?.parse()?;
149 debug!("Began parsing Label {}", self.current_item.id);
150 ParserState::Id
151 }
152 _ => ParserState::Label,
153 },
154
155 ParserState::Name => match ev {
156 Event::Text(e) => {
157 self.current_item.name = e.unescape()?.to_string();
158 ParserState::Name
159 }
160 _ => ParserState::Label,
161 },
162
163 ParserState::Images => match ev {
164 Event::Empty(e) if e.local_name().as_ref() == b"image" => {
165 let image = Image::from_event(e)?;
166 self.current_item.images.push(image);
167 ParserState::Images
168 }
169 Event::End(e) if e.local_name().as_ref() == b"images" => ParserState::Label,
170
171 _ => ParserState::Images,
172 },
173
174 ParserState::Contactinfo => match ev {
175 Event::Text(e) => {
176 self.current_item.contactinfo = maybe_text(e)?;
177 ParserState::Contactinfo
178 }
179 _ => ParserState::Label,
180 },
181
182 ParserState::Profile => match ev {
183 Event::Text(e) => {
184 self.current_item.profile = maybe_text(e)?;
185 ParserState::Profile
186 }
187 _ => ParserState::Label,
188 },
189
190 ParserState::ParentLabel => match ev {
191 Event::Text(e) => {
192 let Some(id) = self.current_parent_id else {
193 return Err(ParserError::MissingData("Label parent ID"));
194 };
195 let parent_label = LabelInfo {
196 id,
197 name: e.unescape()?.to_string(),
198 };
199 self.current_item.parent_label = Some(parent_label);
200 self.current_parent_id = None;
201 ParserState::ParentLabel
202 }
203 _ => ParserState::Label,
204 },
205
206 ParserState::Sublabels => match ev {
207 Event::Start(e) if e.local_name().as_ref() == b"label" => {
208 self.current_sublabel_id = Some(find_attr(e, b"id")?.parse()?);
209 ParserState::Sublabel
210 }
211 Event::End(e) if e.local_name().as_ref() == b"sublabels" => ParserState::Label,
212
213 _ => ParserState::Sublabels,
214 },
215
216 ParserState::Sublabel => match ev {
217 Event::Text(e) => {
218 let Some(id) = self.current_sublabel_id else {
219 return Err(ParserError::MissingData("Label sublabel ID"));
220 };
221 let sublabel = LabelInfo {
222 id,
223 name: e.unescape()?.to_string(),
224 };
225 self.current_item.sublabels.push(sublabel);
226 self.current_sublabel_id = None;
227 ParserState::Sublabels
228 }
229 _ => ParserState::Sublabels,
230 },
231
232 ParserState::Urls => match ev {
233 Event::Text(e) => {
234 self.current_item.urls.push(e.unescape()?.to_string());
235 ParserState::Urls
236 }
237 Event::End(e) if e.local_name().as_ref() == b"urls" => ParserState::Label,
238
239 _ => ParserState::Urls,
240 },
241
242 ParserState::DataQuality => match ev {
243 Event::Text(e) => {
244 self.current_item.data_quality = e.unescape()?.to_string();
245 ParserState::DataQuality
246 }
247 _ => ParserState::Label,
248 },
249 };
250
251 Ok(())
252 }
253}
254
255pub struct LabelBuilder {
256 inner: Label,
257}
258
259impl LabelBuilder {
260 pub fn id(mut self, id: u32) -> Self {
261 self.inner.id = id;
262 self
263 }
264
265 pub fn name(mut self, name: &str) -> Self {
266 self.inner.name = name.to_string();
267 self
268 }
269
270 pub fn contactinfo(mut self, contactinfo: &str) -> Self {
271 self.inner.contactinfo = Some(contactinfo.to_string());
272 self
273 }
274
275 pub fn profile(mut self, profile: &str) -> Self {
276 self.inner.profile = Some(profile.to_string());
277 self
278 }
279
280 pub fn parent_label(mut self, id: u32, name: &str) -> Self {
281 self.inner.parent_label = Some(LabelInfo {
282 id,
283 name: name.to_string(),
284 });
285 self
286 }
287
288 pub fn sublabel(mut self, id: u32, name: &str) -> Self {
289 self.inner.sublabels.push(LabelInfo {
290 id,
291 name: name.to_string(),
292 });
293 self
294 }
295
296 pub fn url(mut self, url: &str) -> Self {
297 self.inner.urls.push(url.to_string());
298 self
299 }
300
301 pub fn data_quality(mut self, data_quality: &str) -> Self {
302 self.inner.data_quality = data_quality.to_string();
303 self
304 }
305
306 pub fn image(mut self, ty: &str, width: i16, height: i16) -> Self {
307 self.inner.images.push(Image {
308 r#type: ty.to_string(),
309 uri: None,
310 uri150: None,
311 width,
312 height,
313 });
314 self
315 }
316
317 pub fn build(self) -> Label {
318 self.inner
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use pretty_assertions::assert_eq;
325 use std::io::{BufRead, BufReader, Cursor};
326
327 use super::{Label, LabelsReader};
328
329 fn parse(xml: &'static str) -> Label {
330 let reader: Box<dyn BufRead> = Box::new(BufReader::new(Cursor::new(xml)));
331 let mut reader = quick_xml::Reader::from_reader(reader);
332 reader.config_mut().trim_text(true);
333 LabelsReader::new(reader, Vec::new()).next().unwrap()
334 }
335
336 #[test]
337 fn test_label_1000_20231001() {
338 let expected = Label::builder(1000, "Warner Bros. Records")
339 .contactinfo("3300 Warner Boulevard\r\nBurbank, CA 91505-4964\r\nUSA")
340 .profile("Label Code: LC 0392 / LC 00392\r\n\r\nFounded in 1958 by Jack Warner as a soundtrack factory for Warner Bros. movie studios, Warner Bros. Records and its family of subsidiary labels, which includes Reprise Records, Sire Records, Maverick Records, Warner Nashville, Warner Jazz, Warner Western, and Word Label Group encompassed a full spectrum of musical genres.\r\nAfter more than 60 years using the Warner Bros. name and logo (and following the end of a 15-year licensing agreement with AT&T/WarnerMedia, until 2018 Time Warner), the label was rebranded in May 2019 to simply [l=Warner Records].")
341 .data_quality("Needs Vote")
342 .parent_label(90718, "Warner Bros. Records Inc.")
343 .sublabel(29742, "Warner Resound")
344 .sublabel(41256, "Warner Special Products")
345 .url("http://www.warnerrecords.com")
346 .url("http://myspace.com/warnerbrosrecords")
347 .image("primary", 600, 818)
348 .image("secondary", 600, 600)
349 .build();
350 let parsed = parse(
351 r#"
352<label>
353 <images>
354 <image type="primary" uri="" uri150="" width="600" height="818"/>
355 <image type="secondary" uri="" uri150="" width="600" height="600"/>
356 </images>
357 <id>1000</id>
358 <name>Warner Bros. Records</name>
359 <contactinfo>3300 Warner Boulevard
360Burbank, CA 91505-4964
361USA</contactinfo>
362 <profile>Label Code: LC 0392 / LC 00392
363
364Founded in 1958 by Jack Warner as a soundtrack factory for Warner Bros. movie studios, Warner Bros. Records and its family of subsidiary labels, which includes Reprise Records, Sire Records, Maverick Records, Warner Nashville, Warner Jazz, Warner Western, and Word Label Group encompassed a full spectrum of musical genres.
365After more than 60 years using the Warner Bros. name and logo (and following the end of a 15-year licensing agreement with AT&T/WarnerMedia, until 2018 Time Warner), the label was rebranded in May 2019 to simply [l=Warner Records].</profile>
366 <data_quality>Needs Vote</data_quality>
367 <parentLabel id="90718">Warner Bros. Records Inc.</parentLabel>
368 <urls>
369 <url>http://www.warnerrecords.com</url>
370 <url>http://myspace.com/warnerbrosrecords</url>
371 </urls>
372 <sublabels>
373 <label id="29742">Warner Resound</label>
374 <label id="41256">Warner Special Products</label>
375 </sublabels>
376</label> "#,
377 );
378 assert_eq!(expected, parsed);
379 }
380}