1use crate::error::ParseError;
4use crate::parser::namespace_detector::NamespaceContext;
5use crate::parser::xml_validator::XmlValidator;
6use ddex_core::models::graph::{
7 ERNMessage, MessageHeader, MessageRecipient, MessageSender, MessageType, Release,
8};
9use ddex_core::models::versions::ERNVersion;
10use quick_xml::events::Event;
11use quick_xml::Reader;
12use std::io::BufRead;
13
14pub struct GraphBuilder {
15 version: ERNVersion,
16}
17
18impl GraphBuilder {
19 pub fn new(version: ERNVersion) -> Self {
20 Self { version }
21 }
22
23 pub fn build_from_xml<R: BufRead + std::io::Seek>(
24 &self,
25 reader: R,
26 ) -> Result<ERNMessage, ParseError> {
27 self.build_from_xml_with_security_config(
28 reader,
29 &crate::parser::security::SecurityConfig::default(),
30 )
31 }
32
33 pub fn build_from_xml_with_security_config<R: BufRead + std::io::Seek>(
34 &self,
35 mut reader: R,
36 _security_config: &crate::parser::security::SecurityConfig,
37 ) -> Result<ERNMessage, ParseError> {
38 let mut xml_reader = Reader::from_reader(&mut reader);
39
40 xml_reader.config_mut().trim_text(true);
42 xml_reader.config_mut().check_end_names = true;
43 xml_reader.config_mut().expand_empty_elements = false;
44
45 let message_header = self.parse_header_from_xml(&mut xml_reader)?;
47
48 reader.seek(std::io::SeekFrom::Start(0))?;
50 xml_reader = Reader::from_reader(&mut reader);
51 xml_reader.config_mut().trim_text(true);
52 xml_reader.config_mut().check_end_names = true;
53 xml_reader.config_mut().expand_empty_elements = false;
54
55 let mut validator = XmlValidator::strict();
56 let mut releases = Vec::new();
57 let mut resources = Vec::new(); let parties = Vec::new(); let mut deals = Vec::new(); let mut buf = Vec::new();
63 let mut in_release_list = false;
64 let mut in_resource_list = false;
65 let mut in_deal_list = false;
66
67 loop {
68 match xml_reader.read_event_into(&mut buf) {
69 Ok(ref event) => {
70 validator.validate_event(event, &xml_reader)?;
72
73 if validator.get_depth() > 100 {
75 return Err(ParseError::DepthLimitExceeded {
76 depth: validator.get_depth(),
77 limit: 100,
78 });
79 }
80
81 match event {
82 Event::Start(ref e) => {
83 match e.name().as_ref() {
84 b"ReleaseList" => in_release_list = true,
85 b"ResourceList" => in_resource_list = true,
86 b"DealList" => in_deal_list = true,
87 b"Release" if in_release_list => {
88 releases.push(
90 self.parse_minimal_release(
91 &mut xml_reader,
92 &mut validator,
93 )?,
94 );
95 }
96 b"SoundRecording" if in_resource_list => {
97 resources.push(
99 self.parse_sound_recording(
100 &mut xml_reader,
101 &mut validator,
102 )?,
103 );
104 }
105 b"ReleaseDeal" if in_deal_list => {
106 deals.push(
108 self.parse_release_deal(
109 &mut xml_reader,
110 &mut validator,
111 )?,
112 );
113 }
114 _ => {}
115 }
116 }
117 Event::End(ref e) => {
118 match e.name().as_ref() {
119 b"ReleaseList" => in_release_list = false,
120 b"ResourceList" => in_resource_list = false,
121 b"DealList" => in_deal_list = false,
122 _ => {}
123 }
124 }
125 Event::Eof => break,
126 _ => {}
127 }
128 }
129 Err(e) => {
130 return Err(ParseError::XmlError(format!("XML parsing error: {}", e)));
131 }
132 }
133 buf.clear();
134 }
135
136 Ok(ERNMessage {
137 message_header,
138 parties,
139 resources,
140 releases,
141 deals,
142 version: self.version,
143 profile: None,
144 message_audit_trail: None,
145 extensions: None,
146 legacy_extensions: None,
147 comments: None,
148 attributes: None,
149 })
150 }
151
152 pub fn build_from_xml_with_context<R: BufRead + std::io::Seek>(
154 &self,
155 reader: R,
156 _context: NamespaceContext,
157 ) -> Result<ERNMessage, ParseError> {
158 self.build_from_xml_with_context_and_security(
159 reader,
160 _context,
161 &crate::parser::security::SecurityConfig::default(),
162 )
163 }
164
165 pub fn build_from_xml_with_context_and_security<R: BufRead + std::io::Seek>(
166 &self,
167 reader: R,
168 _context: NamespaceContext,
169 security_config: &crate::parser::security::SecurityConfig,
170 ) -> Result<ERNMessage, ParseError> {
171 self.build_from_xml_with_security_config(reader, security_config)
174 }
175
176 fn parse_header_from_xml<R: BufRead>(&self, reader: &mut Reader<R>) -> Result<MessageHeader, ParseError> {
177 use chrono::Utc;
178 use ddex_core::models::common::LocalizedString;
179
180 let mut message_id = format!("MSG_{:?}", self.version); let mut message_thread_id: Option<String> = None;
182 let mut message_created_date_time = Utc::now();
183 let mut sender_party_names = Vec::new();
184 let mut recipient_party_names = Vec::new();
185 let mut sender_party_ids = Vec::new();
186 let mut recipient_party_ids = Vec::new();
187
188 let mut buf = Vec::new();
189 let mut in_message_header = false;
190 let mut in_message_sender = false;
191 let mut in_message_recipient = false;
192 let mut in_sender_party_name = false;
193 let mut in_recipient_party_name = false;
194 let mut current_text = String::new();
195
196 loop {
198 match reader.read_event_into(&mut buf) {
199 Ok(Event::Start(ref e)) => {
200 match e.name().as_ref() {
201 b"MessageHeader" => in_message_header = true,
202 b"MessageId" if in_message_header => current_text.clear(),
203 b"MessageThreadId" if in_message_header => current_text.clear(),
204 b"MessageCreatedDateTime" if in_message_header => current_text.clear(),
205 b"MessageSender" if in_message_header => in_message_sender = true,
206 b"MessageRecipient" if in_message_header => in_message_recipient = true,
207 b"PartyId" if in_message_sender => {
208 current_text.clear();
209 },
210 b"PartyId" if in_message_recipient => {
211 current_text.clear();
212 },
213 b"PartyName" if in_message_sender => {
214 in_sender_party_name = true;
215 current_text.clear();
216 },
217 b"PartyName" if in_message_recipient => {
218 in_recipient_party_name = true;
219 current_text.clear();
220 },
221 b"FullName" if in_sender_party_name || in_recipient_party_name => {
222 current_text.clear();
223 },
224 _ => {}
225 }
226 },
227 Ok(Event::Text(ref e)) => {
228 current_text.push_str(&e.unescape().unwrap_or_default());
229 },
230 Ok(Event::End(ref e)) => {
231 match e.name().as_ref() {
232 b"MessageHeader" => {
233 in_message_header = false;
234 break; },
236 b"MessageId" if in_message_header => {
237 if !current_text.trim().is_empty() {
238 message_id = current_text.trim().to_string();
239 }
240 current_text.clear();
241 },
242 b"MessageThreadId" if in_message_header => {
243 if !current_text.trim().is_empty() {
244 message_thread_id = Some(current_text.trim().to_string());
245 }
246 current_text.clear();
247 },
248 b"MessageCreatedDateTime" if in_message_header => {
249 if let Ok(parsed_time) = chrono::DateTime::parse_from_rfc3339(current_text.trim()) {
251 message_created_date_time = parsed_time.with_timezone(&Utc);
252 }
253 current_text.clear();
254 },
255 b"MessageSender" => in_message_sender = false,
256 b"MessageRecipient" => in_message_recipient = false,
257 b"PartyId" if in_message_sender => {
258 if !current_text.trim().is_empty() {
260 use ddex_core::models::common::{Identifier, IdentifierType};
261 sender_party_ids.push(Identifier {
262 id_type: IdentifierType::Proprietary,
263 namespace: None,
264 value: current_text.trim().to_string(),
265 });
266 }
267 current_text.clear();
268 },
269 b"PartyId" if in_message_recipient => {
270 if !current_text.trim().is_empty() {
272 use ddex_core::models::common::{Identifier, IdentifierType};
273 recipient_party_ids.push(Identifier {
274 id_type: IdentifierType::Proprietary,
275 namespace: None,
276 value: current_text.trim().to_string(),
277 });
278 }
279 current_text.clear();
280 },
281 b"PartyName" if in_message_sender => {
282 if !current_text.trim().is_empty() {
284 sender_party_names.push(LocalizedString::new(current_text.trim().to_string()));
285 }
286 current_text.clear();
287 in_sender_party_name = false;
288 },
289 b"PartyName" if in_message_recipient => {
290 if !current_text.trim().is_empty() {
292 recipient_party_names.push(LocalizedString::new(current_text.trim().to_string()));
293 }
294 current_text.clear();
295 in_recipient_party_name = false;
296 },
297 b"FullName" if in_sender_party_name => {
298 if !current_text.trim().is_empty() {
299 sender_party_names.push(LocalizedString::new(current_text.trim().to_string()));
300 }
301 current_text.clear();
302 },
303 b"FullName" if in_recipient_party_name => {
304 if !current_text.trim().is_empty() {
305 recipient_party_names.push(LocalizedString::new(current_text.trim().to_string()));
306 }
307 current_text.clear();
308 },
309 _ => {}
310 }
311 },
312 Ok(Event::Eof) => break,
313 Err(e) => {
314 return Err(ParseError::XmlError(format!("XML parsing error in header: {}", e)));
315 }
316 _ => {}
317 }
318 buf.clear();
319 }
320
321 Ok(MessageHeader {
322 message_id,
323 message_type: MessageType::NewReleaseMessage,
324 message_created_date_time,
325 message_sender: MessageSender {
326 party_id: sender_party_ids,
327 party_name: sender_party_names,
328 trading_name: None,
329 extensions: None,
330 attributes: None,
331 comments: None,
332 },
333 message_recipient: MessageRecipient {
334 party_id: recipient_party_ids,
335 party_name: recipient_party_names,
336 trading_name: None,
337 extensions: None,
338 attributes: None,
339 comments: None,
340 },
341 message_control_type: None,
342 message_thread_id,
343 extensions: None,
344 attributes: None,
345 comments: None,
346 })
347 }
348
349 fn parse_minimal_release<R: BufRead>(
350 &self,
351 reader: &mut Reader<R>,
352 validator: &mut crate::parser::xml_validator::XmlValidator,
353 ) -> Result<Release, ParseError> {
354 use ddex_core::models::common::{LocalizedString, Identifier, IdentifierType};
355 use ddex_core::models::graph::{Artist, ReleaseResourceReference, ReleaseType};
356
357 let mut release_reference = format!("R_{:?}", self.version); let mut release_ids = Vec::new();
360 let mut release_titles = Vec::new();
361 let mut release_type: Option<ReleaseType> = None;
362 let mut display_artists = Vec::new();
363 let mut resource_references = Vec::new();
364 let mut current_text = String::new();
365
366 let mut in_release_title = false;
368 let mut in_title_text = false;
369 let mut in_release_type = false;
370 let mut in_release_reference = false;
371 let mut in_release_id = false;
372 let mut in_icpn = false;
373 let mut in_grin = false;
374 let mut in_grid = false;
375 let mut in_display_artist = false;
376 let mut in_artist_party_name = false;
377 let mut in_artist_full_name = false;
378 let mut in_resource_reference_list = false;
379 let mut in_resource_reference = false;
380
381 let mut buf = Vec::new();
383 let mut depth = 1;
384 while depth > 0 {
385 match reader.read_event_into(&mut buf) {
386 Ok(ref event) => {
387 validator.validate_event(event, reader)?;
389
390 match event {
391 Event::Start(ref e) => {
392 depth += 1;
393 match e.name().as_ref() {
394 b"ReleaseReference" => {
395 in_release_reference = true;
396 current_text.clear();
397 },
398 b"ReleaseId" => in_release_id = true,
399 b"ICPN" if in_release_id => {
400 in_icpn = true;
401 current_text.clear();
402 },
403 b"GRIN" if in_release_id => {
404 in_grin = true;
405 current_text.clear();
406 },
407 b"GRid" if in_release_id => {
408 in_grid = true;
409 current_text.clear();
410 },
411 b"ReleaseTitle" => in_release_title = true,
412 b"TitleText" if in_release_title => {
413 in_title_text = true;
414 current_text.clear();
415 },
416 b"ReleaseType" => {
417 in_release_type = true;
418 current_text.clear();
419 },
420 b"DisplayArtist" => in_display_artist = true,
421 b"PartyName" if in_display_artist => {
422 in_artist_party_name = true;
423 },
424 b"FullName" if in_artist_party_name => {
425 in_artist_full_name = true;
426 current_text.clear();
427 },
428 b"ReleaseResourceReferenceList" => in_resource_reference_list = true,
429 b"ReleaseResourceReference" if in_resource_reference_list => {
430 in_resource_reference = true;
431 current_text.clear();
432 },
433 _ => {}
434 }
435 },
436 Event::Text(ref e) => {
437 if in_title_text || in_release_type || in_release_reference ||
438 in_icpn || in_grin || in_grid || in_artist_full_name || in_resource_reference {
439 current_text.push_str(&e.unescape().unwrap_or_default());
440 }
441 },
442 Event::End(ref e) => {
443 depth -= 1;
444 match e.name().as_ref() {
445 b"ReleaseReference" => {
446 if !current_text.trim().is_empty() {
447 release_reference = current_text.trim().to_string();
448 }
449 in_release_reference = false;
450 current_text.clear();
451 },
452 b"ReleaseId" => in_release_id = false,
453 b"ICPN" if in_icpn => {
454 if !current_text.trim().is_empty() {
455 release_ids.push(Identifier {
456 id_type: IdentifierType::UPC,
457 namespace: None,
458 value: current_text.trim().to_string(),
459 });
460 }
461 in_icpn = false;
462 current_text.clear();
463 },
464 b"GRIN" if in_grin => {
465 if !current_text.trim().is_empty() {
466 release_ids.push(Identifier {
467 id_type: IdentifierType::GRid,
468 namespace: None,
469 value: current_text.trim().to_string(),
470 });
471 }
472 in_grin = false;
473 current_text.clear();
474 },
475 b"GRid" if in_grid => {
476 if !current_text.trim().is_empty() {
477 release_ids.push(Identifier {
478 id_type: IdentifierType::GRID,
479 namespace: None,
480 value: current_text.trim().to_string(),
481 });
482 }
483 in_grid = false;
484 current_text.clear();
485 },
486 b"ReleaseTitle" => in_release_title = false,
487 b"TitleText" if in_title_text => {
488 if !current_text.trim().is_empty() {
489 release_titles.push(LocalizedString::new(current_text.trim().to_string()));
490 }
491 in_title_text = false;
492 current_text.clear();
493 },
494 b"ReleaseType" => {
495 if !current_text.trim().is_empty() {
496 release_type = match current_text.trim() {
497 "Album" => Some(ReleaseType::Album),
498 "Single" => Some(ReleaseType::Single),
499 "EP" => Some(ReleaseType::EP),
500 "Compilation" => Some(ReleaseType::Compilation),
501 other => Some(ReleaseType::Other(other.to_string())),
502 };
503 }
504 in_release_type = false;
505 current_text.clear();
506 },
507 b"DisplayArtist" => in_display_artist = false,
508 b"PartyName" if in_artist_party_name => {
509 in_artist_party_name = false;
510 },
511 b"FullName" if in_artist_full_name => {
512 if !current_text.trim().is_empty() {
513 display_artists.push(Artist {
514 party_reference: None,
515 artist_role: vec!["MainArtist".to_string()],
516 display_artist_name: vec![LocalizedString::new(current_text.trim().to_string())],
517 sequence_number: None,
518 });
519 }
520 in_artist_full_name = false;
521 current_text.clear();
522 },
523 b"ReleaseResourceReferenceList" => in_resource_reference_list = false,
524 b"ReleaseResourceReference" if in_resource_reference => {
525 if !current_text.trim().is_empty() {
526 resource_references.push(ReleaseResourceReference {
527 resource_reference: current_text.trim().to_string(),
528 sequence_number: None,
529 disc_number: None,
530 track_number: None,
531 side: None,
532 is_hidden: false,
533 is_bonus: false,
534 extensions: None,
535 comments: None,
536 });
537 }
538 in_resource_reference = false;
539 current_text.clear();
540 },
541 _ => {}
542 }
543 },
544 Event::Eof => break,
545 _ => {}
546 }
547 }
548 Err(e) => {
549 return Err(ParseError::XmlError(format!("XML parsing error in release: {}", e)));
550 }
551 }
552 buf.clear();
553 }
554
555 if release_titles.is_empty() {
557 release_titles.push(LocalizedString::new(format!("Release {:?}", self.version)));
558 }
559
560 let release = Release {
561 release_reference,
562 release_id: release_ids,
563 release_title: release_titles,
564 release_subtitle: None,
565 release_type,
566 genre: Vec::new(),
567 release_resource_reference_list: resource_references,
568 display_artist: display_artists,
569 party_list: Vec::new(),
570 release_date: Vec::new(),
571 territory_code: Vec::new(),
572 excluded_territory_code: Vec::new(),
573 extensions: None,
574 attributes: None,
575 comments: None,
576 };
577
578 Ok(release)
579 }
580
581 fn parse_sound_recording<R: BufRead>(
582 &self,
583 reader: &mut Reader<R>,
584 validator: &mut crate::parser::xml_validator::XmlValidator,
585 ) -> Result<ddex_core::models::graph::Resource, ParseError> {
586 use ddex_core::models::common::{LocalizedString, Identifier, IdentifierType};
587 use ddex_core::models::graph::{Resource, ResourceType};
588 use std::time::Duration;
589
590 let mut resource_reference = format!("RES_{:?}", self.version); let mut resource_ids = Vec::new();
593 let mut reference_titles = Vec::new();
594 let mut duration: Option<Duration> = None;
595 let mut current_text = String::new();
596
597 let mut in_resource_reference = false;
599 let mut in_sound_recording_id = false;
600 let mut in_isrc = false;
601 let mut in_title = false;
602 let mut in_title_text = false;
603 let mut in_duration = false;
604 let mut in_display_artist = false;
605 let mut in_artist_party_name = false;
606 let mut in_artist_full_name = false;
607
608 let mut buf = Vec::new();
610 let mut depth = 1;
611 while depth > 0 {
612 match reader.read_event_into(&mut buf) {
613 Ok(ref event) => {
614 validator.validate_event(event, reader)?;
616
617 match event {
618 Event::Start(ref e) => {
619 depth += 1;
620 match e.name().as_ref() {
621 b"ResourceReference" => {
622 in_resource_reference = true;
623 current_text.clear();
624 },
625 b"SoundRecordingId" => in_sound_recording_id = true,
626 b"ISRC" if in_sound_recording_id => {
627 in_isrc = true;
628 current_text.clear();
629 },
630 b"Title" => in_title = true,
631 b"TitleText" if in_title => {
632 in_title_text = true;
633 current_text.clear();
634 },
635 b"Duration" => {
636 in_duration = true;
637 current_text.clear();
638 },
639 b"DisplayArtist" => in_display_artist = true,
640 b"PartyName" if in_display_artist => {
641 in_artist_party_name = true;
642 },
643 b"FullName" if in_artist_party_name => {
644 in_artist_full_name = true;
645 current_text.clear();
646 },
647 _ => {}
648 }
649 },
650 Event::Text(ref e) => {
651 if in_resource_reference || in_isrc || in_title_text ||
652 in_duration || in_artist_full_name {
653 current_text.push_str(&e.unescape().unwrap_or_default());
654 }
655 },
656 Event::End(ref e) => {
657 depth -= 1;
658 match e.name().as_ref() {
659 b"ResourceReference" => {
660 if !current_text.trim().is_empty() {
661 resource_reference = current_text.trim().to_string();
662 }
663 in_resource_reference = false;
664 current_text.clear();
665 },
666 b"SoundRecordingId" => in_sound_recording_id = false,
667 b"ISRC" if in_isrc => {
668 if !current_text.trim().is_empty() {
669 resource_ids.push(Identifier {
670 id_type: IdentifierType::ISRC,
671 namespace: None,
672 value: current_text.trim().to_string(),
673 });
674 }
675 in_isrc = false;
676 current_text.clear();
677 },
678 b"Title" => in_title = false,
679 b"TitleText" if in_title_text => {
680 if !current_text.trim().is_empty() {
681 reference_titles.push(LocalizedString::new(current_text.trim().to_string()));
682 }
683 in_title_text = false;
684 current_text.clear();
685 },
686 b"Duration" => {
687 if !current_text.trim().is_empty() {
688 if let Ok(parsed_duration) = parse_duration(¤t_text.trim()) {
690 duration = Some(parsed_duration);
691 }
692 }
693 in_duration = false;
694 current_text.clear();
695 },
696 b"DisplayArtist" => in_display_artist = false,
697 b"PartyName" if in_artist_party_name => {
698 in_artist_party_name = false;
699 },
700 b"FullName" if in_artist_full_name => {
701 in_artist_full_name = false;
704 current_text.clear();
705 },
706 _ => {}
707 }
708 },
709 Event::Eof => break,
710 _ => {}
711 }
712 }
713 Err(e) => {
714 return Err(ParseError::XmlError(format!("XML parsing error in sound recording: {}", e)));
715 }
716 }
717 buf.clear();
718 }
719
720 if reference_titles.is_empty() {
722 reference_titles.push(LocalizedString::new(format!("Sound Recording {:?}", self.version)));
723 }
724
725 let resource = Resource {
726 resource_reference,
727 resource_type: ResourceType::SoundRecording,
728 resource_id: resource_ids,
729 reference_title: reference_titles,
730 duration,
731 technical_details: Vec::new(),
732 rights_controller: Vec::new(),
733 p_line: Vec::new(),
734 c_line: Vec::new(),
735 extensions: None,
736 };
737
738 Ok(resource)
739 }
740
741 fn parse_release_deal<R: BufRead>(
742 &self,
743 reader: &mut Reader<R>,
744 validator: &mut crate::parser::xml_validator::XmlValidator,
745 ) -> Result<ddex_core::models::graph::Deal, ParseError> {
746 use ddex_core::models::common::ValidityPeriod;
747 use ddex_core::models::graph::{Deal, DealTerms, CommercialModelType, UseType};
748 use chrono::{DateTime, Utc};
749
750 let mut deal_reference: Option<String> = None;
752 let mut territory_codes = Vec::new();
753 let mut use_types = Vec::new();
754 let mut commercial_model_types = Vec::new();
755 let mut validity_period: Option<ValidityPeriod> = None;
756 let mut start_date: Option<DateTime<Utc>> = None;
757 let mut current_text = String::new();
758
759 let mut in_deal_reference = false;
761 let mut in_deal_terms = false;
762 let mut in_territory_code = false;
763 let mut in_use_type = false;
764 let mut in_commercial_model_type = false;
765 let mut in_validity_period = false;
766 let mut in_start_date = false;
767
768 let mut buf = Vec::new();
770 let mut depth = 1;
771 while depth > 0 {
772 match reader.read_event_into(&mut buf) {
773 Ok(ref event) => {
774 validator.validate_event(event, reader)?;
776
777 match event {
778 Event::Start(ref e) => {
779 depth += 1;
780 match e.name().as_ref() {
781 b"DealReference" => {
782 in_deal_reference = true;
783 current_text.clear();
784 },
785 b"DealTerms" => in_deal_terms = true,
786 b"TerritoryCode" if in_deal_terms => {
787 in_territory_code = true;
788 current_text.clear();
789 },
790 b"UseType" if in_deal_terms => {
791 in_use_type = true;
792 current_text.clear();
793 },
794 b"CommercialModelType" if in_deal_terms => {
795 in_commercial_model_type = true;
796 current_text.clear();
797 },
798 b"ValidityPeriod" if in_deal_terms => {
799 in_validity_period = true;
800 },
801 b"StartDate" if in_validity_period => {
802 in_start_date = true;
803 current_text.clear();
804 },
805 _ => {}
806 }
807 },
808 Event::Text(ref e) => {
809 if in_deal_reference || in_territory_code || in_use_type ||
810 in_commercial_model_type || in_start_date {
811 current_text.push_str(&e.unescape().unwrap_or_default());
812 }
813 },
814 Event::End(ref e) => {
815 depth -= 1;
816 match e.name().as_ref() {
817 b"DealReference" => {
818 if !current_text.trim().is_empty() {
819 deal_reference = Some(current_text.trim().to_string());
820 }
821 in_deal_reference = false;
822 current_text.clear();
823 },
824 b"DealTerms" => in_deal_terms = false,
825 b"TerritoryCode" if in_territory_code => {
826 if !current_text.trim().is_empty() {
827 territory_codes.push(current_text.trim().to_string());
828 }
829 in_territory_code = false;
830 current_text.clear();
831 },
832 b"UseType" if in_use_type => {
833 if !current_text.trim().is_empty() {
834 let use_type = match current_text.trim() {
835 "Stream" => UseType::Stream,
836 "Download" => UseType::Download,
837 "OnDemandStream" => UseType::OnDemandStream,
838 "NonInteractiveStream" => UseType::NonInteractiveStream,
839 other => UseType::Other(other.to_string()),
840 };
841 use_types.push(use_type);
842 }
843 in_use_type = false;
844 current_text.clear();
845 },
846 b"CommercialModelType" if in_commercial_model_type => {
847 if !current_text.trim().is_empty() {
848 let commercial_model = match current_text.trim() {
849 "PayAsYouGoModel" => CommercialModelType::PayAsYouGoModel,
850 "SubscriptionModel" => CommercialModelType::SubscriptionModel,
851 "AdSupportedModel" => CommercialModelType::AdSupportedModel,
852 other => CommercialModelType::Other(other.to_string()),
853 };
854 commercial_model_types.push(commercial_model);
855 }
856 in_commercial_model_type = false;
857 current_text.clear();
858 },
859 b"ValidityPeriod" => {
860 validity_period = Some(ValidityPeriod {
862 start_date: start_date.clone(),
863 end_date: None, });
865 in_validity_period = false;
866 },
867 b"StartDate" if in_start_date => {
868 if !current_text.trim().is_empty() {
869 if let Ok(parsed_date) = DateTime::parse_from_rfc3339(current_text.trim()) {
871 start_date = Some(parsed_date.with_timezone(&Utc));
872 }
873 }
874 in_start_date = false;
875 current_text.clear();
876 },
877 _ => {}
878 }
879 },
880 Event::Eof => break,
881 _ => {}
882 }
883 }
884 Err(e) => {
885 return Err(ParseError::XmlError(format!("XML parsing error in release deal: {}", e)));
886 }
887 }
888 buf.clear();
889 }
890
891 let deal_terms = DealTerms {
892 validity_period,
893 start_date,
894 end_date: None,
895 territory_code: territory_codes,
896 excluded_territory_code: Vec::new(),
897 distribution_channel: Vec::new(),
898 excluded_distribution_channel: Vec::new(),
899 commercial_model_type: commercial_model_types,
900 use_type: use_types,
901 price_information: Vec::new(),
902 wholesale_price: Vec::new(),
903 suggested_retail_price: Vec::new(),
904 pre_order_date: None,
905 pre_order_preview_date: None,
906 instant_gratification_date: None,
907 takedown_date: None,
908 };
909
910 let deal = Deal {
911 deal_reference,
912 deal_release_reference: Vec::new(), deal_terms,
914 };
915
916 Ok(deal)
917 }
918}
919
920fn parse_duration(duration_str: &str) -> Result<std::time::Duration, std::time::Duration> {
922 use std::time::Duration;
923 if duration_str.starts_with("PT") {
925 let duration_part = &duration_str[2..]; let mut total_seconds = 0u64;
927 let mut current_number = String::new();
928
929 for ch in duration_part.chars() {
930 match ch {
931 '0'..='9' | '.' => current_number.push(ch),
932 'H' => {
933 if let Ok(hours) = current_number.parse::<f64>() {
934 total_seconds += (hours * 3600.0) as u64;
935 }
936 current_number.clear();
937 },
938 'M' => {
939 if let Ok(minutes) = current_number.parse::<f64>() {
940 total_seconds += (minutes * 60.0) as u64;
941 }
942 current_number.clear();
943 },
944 'S' => {
945 if let Ok(seconds) = current_number.parse::<f64>() {
946 total_seconds += seconds as u64;
947 }
948 current_number.clear();
949 },
950 _ => {}
951 }
952 }
953
954 Ok(Duration::from_secs(total_seconds))
955 } else {
956 if let Ok(seconds) = duration_str.parse::<f64>() {
958 Ok(Duration::from_secs_f64(seconds))
959 } else {
960 Err(Duration::from_secs(0)) }
962 }
963}