disco_quick/
label.rs

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&#13;
360Burbank, CA 91505-4964&#13;
361USA</contactinfo>
362  <profile>Label Code: LC 0392 / LC 00392&#13;
363&#13;
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.&#13;
365After more than 60 years using the Warner Bros. name and logo (and following the end of a 15-year licensing agreement with AT&amp;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}