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
186 let mut buf = Vec::new();
187 let mut in_message_header = false;
188 let mut in_message_sender = false;
189 let mut in_message_recipient = false;
190 let mut in_sender_party_name = false;
191 let mut in_recipient_party_name = false;
192 let mut current_text = String::new();
193
194 loop {
196 match reader.read_event_into(&mut buf) {
197 Ok(Event::Start(ref e)) => {
198 match e.name().as_ref() {
199 b"MessageHeader" => in_message_header = true,
200 b"MessageId" if in_message_header => current_text.clear(),
201 b"MessageThreadId" if in_message_header => current_text.clear(),
202 b"MessageCreatedDateTime" if in_message_header => current_text.clear(),
203 b"MessageSender" if in_message_header => in_message_sender = true,
204 b"MessageRecipient" if in_message_header => in_message_recipient = true,
205 b"PartyName" if in_message_sender => {
206 in_sender_party_name = true;
207 current_text.clear();
208 },
209 b"PartyName" if in_message_recipient => {
210 in_recipient_party_name = true;
211 current_text.clear();
212 },
213 b"FullName" if in_sender_party_name || in_recipient_party_name => {
214 current_text.clear();
215 },
216 _ => {}
217 }
218 },
219 Ok(Event::Text(ref e)) => {
220 current_text.push_str(&e.unescape().unwrap_or_default());
221 },
222 Ok(Event::End(ref e)) => {
223 match e.name().as_ref() {
224 b"MessageHeader" => {
225 in_message_header = false;
226 break; },
228 b"MessageId" if in_message_header => {
229 if !current_text.trim().is_empty() {
230 message_id = current_text.trim().to_string();
231 }
232 current_text.clear();
233 },
234 b"MessageThreadId" if in_message_header => {
235 if !current_text.trim().is_empty() {
236 message_thread_id = Some(current_text.trim().to_string());
237 }
238 current_text.clear();
239 },
240 b"MessageCreatedDateTime" if in_message_header => {
241 if let Ok(parsed_time) = chrono::DateTime::parse_from_rfc3339(current_text.trim()) {
243 message_created_date_time = parsed_time.with_timezone(&Utc);
244 }
245 current_text.clear();
246 },
247 b"MessageSender" => in_message_sender = false,
248 b"MessageRecipient" => in_message_recipient = false,
249 b"PartyName" if in_message_sender => {
250 in_sender_party_name = false;
251 },
252 b"PartyName" if in_message_recipient => {
253 in_recipient_party_name = false;
254 },
255 b"FullName" if in_sender_party_name => {
256 if !current_text.trim().is_empty() {
257 sender_party_names.push(LocalizedString::new(current_text.trim().to_string()));
258 }
259 current_text.clear();
260 },
261 b"FullName" if in_recipient_party_name => {
262 if !current_text.trim().is_empty() {
263 recipient_party_names.push(LocalizedString::new(current_text.trim().to_string()));
264 }
265 current_text.clear();
266 },
267 _ => {}
268 }
269 },
270 Ok(Event::Eof) => break,
271 Err(e) => {
272 return Err(ParseError::XmlError(format!("XML parsing error in header: {}", e)));
273 }
274 _ => {}
275 }
276 buf.clear();
277 }
278
279 Ok(MessageHeader {
280 message_id,
281 message_type: MessageType::NewReleaseMessage,
282 message_created_date_time,
283 message_sender: MessageSender {
284 party_id: Vec::new(),
285 party_name: sender_party_names,
286 trading_name: None,
287 extensions: None,
288 attributes: None,
289 comments: None,
290 },
291 message_recipient: MessageRecipient {
292 party_id: Vec::new(),
293 party_name: recipient_party_names,
294 trading_name: None,
295 extensions: None,
296 attributes: None,
297 comments: None,
298 },
299 message_control_type: None,
300 message_thread_id,
301 extensions: None,
302 attributes: None,
303 comments: None,
304 })
305 }
306
307 fn parse_minimal_release<R: BufRead>(
308 &self,
309 reader: &mut Reader<R>,
310 validator: &mut crate::parser::xml_validator::XmlValidator,
311 ) -> Result<Release, ParseError> {
312 use ddex_core::models::common::{LocalizedString, Identifier, IdentifierType};
313 use ddex_core::models::graph::{Artist, ReleaseResourceReference, ReleaseType};
314
315 let mut release_reference = format!("R_{:?}", self.version); let mut release_ids = Vec::new();
318 let mut release_titles = Vec::new();
319 let mut release_type: Option<ReleaseType> = None;
320 let mut display_artists = Vec::new();
321 let mut resource_references = Vec::new();
322 let mut current_text = String::new();
323
324 let mut in_release_title = false;
326 let mut in_title_text = false;
327 let mut in_release_type = false;
328 let mut in_release_reference = false;
329 let mut in_release_id = false;
330 let mut in_icpn = false;
331 let mut in_grin = false;
332 let mut in_grid = false;
333 let mut in_display_artist = false;
334 let mut in_artist_party_name = false;
335 let mut in_artist_full_name = false;
336 let mut in_resource_reference_list = false;
337 let mut in_resource_reference = false;
338
339 let mut buf = Vec::new();
341 let mut depth = 1;
342 while depth > 0 {
343 match reader.read_event_into(&mut buf) {
344 Ok(ref event) => {
345 validator.validate_event(event, reader)?;
347
348 match event {
349 Event::Start(ref e) => {
350 depth += 1;
351 match e.name().as_ref() {
352 b"ReleaseReference" => {
353 in_release_reference = true;
354 current_text.clear();
355 },
356 b"ReleaseId" => in_release_id = true,
357 b"ICPN" if in_release_id => {
358 in_icpn = true;
359 current_text.clear();
360 },
361 b"GRIN" if in_release_id => {
362 in_grin = true;
363 current_text.clear();
364 },
365 b"GRid" if in_release_id => {
366 in_grid = true;
367 current_text.clear();
368 },
369 b"ReleaseTitle" => in_release_title = true,
370 b"TitleText" if in_release_title => {
371 in_title_text = true;
372 current_text.clear();
373 },
374 b"ReleaseType" => {
375 in_release_type = true;
376 current_text.clear();
377 },
378 b"DisplayArtist" => in_display_artist = true,
379 b"PartyName" if in_display_artist => {
380 in_artist_party_name = true;
381 },
382 b"FullName" if in_artist_party_name => {
383 in_artist_full_name = true;
384 current_text.clear();
385 },
386 b"ReleaseResourceReferenceList" => in_resource_reference_list = true,
387 b"ReleaseResourceReference" if in_resource_reference_list => {
388 in_resource_reference = true;
389 current_text.clear();
390 },
391 _ => {}
392 }
393 },
394 Event::Text(ref e) => {
395 if in_title_text || in_release_type || in_release_reference ||
396 in_icpn || in_grin || in_grid || in_artist_full_name || in_resource_reference {
397 current_text.push_str(&e.unescape().unwrap_or_default());
398 }
399 },
400 Event::End(ref e) => {
401 depth -= 1;
402 match e.name().as_ref() {
403 b"ReleaseReference" => {
404 if !current_text.trim().is_empty() {
405 release_reference = current_text.trim().to_string();
406 }
407 in_release_reference = false;
408 current_text.clear();
409 },
410 b"ReleaseId" => in_release_id = false,
411 b"ICPN" if in_icpn => {
412 if !current_text.trim().is_empty() {
413 release_ids.push(Identifier {
414 id_type: IdentifierType::UPC,
415 namespace: None,
416 value: current_text.trim().to_string(),
417 });
418 }
419 in_icpn = false;
420 current_text.clear();
421 },
422 b"GRIN" if in_grin => {
423 if !current_text.trim().is_empty() {
424 release_ids.push(Identifier {
425 id_type: IdentifierType::GRid,
426 namespace: None,
427 value: current_text.trim().to_string(),
428 });
429 }
430 in_grin = false;
431 current_text.clear();
432 },
433 b"GRid" if in_grid => {
434 if !current_text.trim().is_empty() {
435 release_ids.push(Identifier {
436 id_type: IdentifierType::GRID,
437 namespace: None,
438 value: current_text.trim().to_string(),
439 });
440 }
441 in_grid = false;
442 current_text.clear();
443 },
444 b"ReleaseTitle" => in_release_title = false,
445 b"TitleText" if in_title_text => {
446 if !current_text.trim().is_empty() {
447 release_titles.push(LocalizedString::new(current_text.trim().to_string()));
448 }
449 in_title_text = false;
450 current_text.clear();
451 },
452 b"ReleaseType" => {
453 if !current_text.trim().is_empty() {
454 release_type = match current_text.trim() {
455 "Album" => Some(ReleaseType::Album),
456 "Single" => Some(ReleaseType::Single),
457 "EP" => Some(ReleaseType::EP),
458 "Compilation" => Some(ReleaseType::Compilation),
459 other => Some(ReleaseType::Other(other.to_string())),
460 };
461 }
462 in_release_type = false;
463 current_text.clear();
464 },
465 b"DisplayArtist" => in_display_artist = false,
466 b"PartyName" if in_artist_party_name => {
467 in_artist_party_name = false;
468 },
469 b"FullName" if in_artist_full_name => {
470 if !current_text.trim().is_empty() {
471 display_artists.push(Artist {
472 party_reference: None,
473 artist_role: vec!["MainArtist".to_string()],
474 display_artist_name: vec![LocalizedString::new(current_text.trim().to_string())],
475 sequence_number: None,
476 });
477 }
478 in_artist_full_name = false;
479 current_text.clear();
480 },
481 b"ReleaseResourceReferenceList" => in_resource_reference_list = false,
482 b"ReleaseResourceReference" if in_resource_reference => {
483 if !current_text.trim().is_empty() {
484 resource_references.push(ReleaseResourceReference {
485 resource_reference: current_text.trim().to_string(),
486 sequence_number: None,
487 disc_number: None,
488 track_number: None,
489 side: None,
490 is_hidden: false,
491 is_bonus: false,
492 extensions: None,
493 comments: None,
494 });
495 }
496 in_resource_reference = false;
497 current_text.clear();
498 },
499 _ => {}
500 }
501 },
502 Event::Eof => break,
503 _ => {}
504 }
505 }
506 Err(e) => {
507 return Err(ParseError::XmlError(format!("XML parsing error in release: {}", e)));
508 }
509 }
510 buf.clear();
511 }
512
513 if release_titles.is_empty() {
515 release_titles.push(LocalizedString::new(format!("Release {:?}", self.version)));
516 }
517
518 let release = Release {
519 release_reference,
520 release_id: release_ids,
521 release_title: release_titles,
522 release_subtitle: None,
523 release_type,
524 genre: Vec::new(),
525 release_resource_reference_list: resource_references,
526 display_artist: display_artists,
527 party_list: Vec::new(),
528 release_date: Vec::new(),
529 territory_code: Vec::new(),
530 excluded_territory_code: Vec::new(),
531 extensions: None,
532 attributes: None,
533 comments: None,
534 };
535
536 Ok(release)
537 }
538
539 fn parse_sound_recording<R: BufRead>(
540 &self,
541 reader: &mut Reader<R>,
542 validator: &mut crate::parser::xml_validator::XmlValidator,
543 ) -> Result<ddex_core::models::graph::Resource, ParseError> {
544 use ddex_core::models::common::{LocalizedString, Identifier, IdentifierType};
545 use ddex_core::models::graph::{Resource, ResourceType};
546 use std::time::Duration;
547
548 let mut resource_reference = format!("RES_{:?}", self.version); let mut resource_ids = Vec::new();
551 let mut reference_titles = Vec::new();
552 let mut duration: Option<Duration> = None;
553 let mut current_text = String::new();
554
555 let mut in_resource_reference = false;
557 let mut in_sound_recording_id = false;
558 let mut in_isrc = false;
559 let mut in_title = false;
560 let mut in_title_text = false;
561 let mut in_duration = false;
562 let mut in_display_artist = false;
563 let mut in_artist_party_name = false;
564 let mut in_artist_full_name = false;
565
566 let mut buf = Vec::new();
568 let mut depth = 1;
569 while depth > 0 {
570 match reader.read_event_into(&mut buf) {
571 Ok(ref event) => {
572 validator.validate_event(event, reader)?;
574
575 match event {
576 Event::Start(ref e) => {
577 depth += 1;
578 match e.name().as_ref() {
579 b"ResourceReference" => {
580 in_resource_reference = true;
581 current_text.clear();
582 },
583 b"SoundRecordingId" => in_sound_recording_id = true,
584 b"ISRC" if in_sound_recording_id => {
585 in_isrc = true;
586 current_text.clear();
587 },
588 b"Title" => in_title = true,
589 b"TitleText" if in_title => {
590 in_title_text = true;
591 current_text.clear();
592 },
593 b"Duration" => {
594 in_duration = true;
595 current_text.clear();
596 },
597 b"DisplayArtist" => in_display_artist = true,
598 b"PartyName" if in_display_artist => {
599 in_artist_party_name = true;
600 },
601 b"FullName" if in_artist_party_name => {
602 in_artist_full_name = true;
603 current_text.clear();
604 },
605 _ => {}
606 }
607 },
608 Event::Text(ref e) => {
609 if in_resource_reference || in_isrc || in_title_text ||
610 in_duration || in_artist_full_name {
611 current_text.push_str(&e.unescape().unwrap_or_default());
612 }
613 },
614 Event::End(ref e) => {
615 depth -= 1;
616 match e.name().as_ref() {
617 b"ResourceReference" => {
618 if !current_text.trim().is_empty() {
619 resource_reference = current_text.trim().to_string();
620 }
621 in_resource_reference = false;
622 current_text.clear();
623 },
624 b"SoundRecordingId" => in_sound_recording_id = false,
625 b"ISRC" if in_isrc => {
626 if !current_text.trim().is_empty() {
627 resource_ids.push(Identifier {
628 id_type: IdentifierType::ISRC,
629 namespace: None,
630 value: current_text.trim().to_string(),
631 });
632 }
633 in_isrc = false;
634 current_text.clear();
635 },
636 b"Title" => in_title = false,
637 b"TitleText" if in_title_text => {
638 if !current_text.trim().is_empty() {
639 reference_titles.push(LocalizedString::new(current_text.trim().to_string()));
640 }
641 in_title_text = false;
642 current_text.clear();
643 },
644 b"Duration" => {
645 if !current_text.trim().is_empty() {
646 if let Ok(parsed_duration) = parse_duration(¤t_text.trim()) {
648 duration = Some(parsed_duration);
649 }
650 }
651 in_duration = false;
652 current_text.clear();
653 },
654 b"DisplayArtist" => in_display_artist = false,
655 b"PartyName" if in_artist_party_name => {
656 in_artist_party_name = false;
657 },
658 b"FullName" if in_artist_full_name => {
659 in_artist_full_name = false;
662 current_text.clear();
663 },
664 _ => {}
665 }
666 },
667 Event::Eof => break,
668 _ => {}
669 }
670 }
671 Err(e) => {
672 return Err(ParseError::XmlError(format!("XML parsing error in sound recording: {}", e)));
673 }
674 }
675 buf.clear();
676 }
677
678 if reference_titles.is_empty() {
680 reference_titles.push(LocalizedString::new(format!("Sound Recording {:?}", self.version)));
681 }
682
683 let resource = Resource {
684 resource_reference,
685 resource_type: ResourceType::SoundRecording,
686 resource_id: resource_ids,
687 reference_title: reference_titles,
688 duration,
689 technical_details: Vec::new(),
690 rights_controller: Vec::new(),
691 p_line: Vec::new(),
692 c_line: Vec::new(),
693 extensions: None,
694 };
695
696 Ok(resource)
697 }
698
699 fn parse_release_deal<R: BufRead>(
700 &self,
701 reader: &mut Reader<R>,
702 validator: &mut crate::parser::xml_validator::XmlValidator,
703 ) -> Result<ddex_core::models::graph::Deal, ParseError> {
704 use ddex_core::models::common::ValidityPeriod;
705 use ddex_core::models::graph::{Deal, DealTerms, CommercialModelType, UseType};
706 use chrono::{DateTime, Utc};
707
708 let mut deal_reference: Option<String> = None;
710 let mut territory_codes = Vec::new();
711 let mut use_types = Vec::new();
712 let mut commercial_model_types = Vec::new();
713 let mut validity_period: Option<ValidityPeriod> = None;
714 let mut start_date: Option<DateTime<Utc>> = None;
715 let mut current_text = String::new();
716
717 let mut in_deal_reference = false;
719 let mut in_deal_terms = false;
720 let mut in_territory_code = false;
721 let mut in_use_type = false;
722 let mut in_commercial_model_type = false;
723 let mut in_validity_period = false;
724 let mut in_start_date = false;
725
726 let mut buf = Vec::new();
728 let mut depth = 1;
729 while depth > 0 {
730 match reader.read_event_into(&mut buf) {
731 Ok(ref event) => {
732 validator.validate_event(event, reader)?;
734
735 match event {
736 Event::Start(ref e) => {
737 depth += 1;
738 match e.name().as_ref() {
739 b"DealReference" => {
740 in_deal_reference = true;
741 current_text.clear();
742 },
743 b"DealTerms" => in_deal_terms = true,
744 b"TerritoryCode" if in_deal_terms => {
745 in_territory_code = true;
746 current_text.clear();
747 },
748 b"UseType" if in_deal_terms => {
749 in_use_type = true;
750 current_text.clear();
751 },
752 b"CommercialModelType" if in_deal_terms => {
753 in_commercial_model_type = true;
754 current_text.clear();
755 },
756 b"ValidityPeriod" if in_deal_terms => {
757 in_validity_period = true;
758 },
759 b"StartDate" if in_validity_period => {
760 in_start_date = true;
761 current_text.clear();
762 },
763 _ => {}
764 }
765 },
766 Event::Text(ref e) => {
767 if in_deal_reference || in_territory_code || in_use_type ||
768 in_commercial_model_type || in_start_date {
769 current_text.push_str(&e.unescape().unwrap_or_default());
770 }
771 },
772 Event::End(ref e) => {
773 depth -= 1;
774 match e.name().as_ref() {
775 b"DealReference" => {
776 if !current_text.trim().is_empty() {
777 deal_reference = Some(current_text.trim().to_string());
778 }
779 in_deal_reference = false;
780 current_text.clear();
781 },
782 b"DealTerms" => in_deal_terms = false,
783 b"TerritoryCode" if in_territory_code => {
784 if !current_text.trim().is_empty() {
785 territory_codes.push(current_text.trim().to_string());
786 }
787 in_territory_code = false;
788 current_text.clear();
789 },
790 b"UseType" if in_use_type => {
791 if !current_text.trim().is_empty() {
792 let use_type = match current_text.trim() {
793 "Stream" => UseType::Stream,
794 "Download" => UseType::Download,
795 "OnDemandStream" => UseType::OnDemandStream,
796 "NonInteractiveStream" => UseType::NonInteractiveStream,
797 other => UseType::Other(other.to_string()),
798 };
799 use_types.push(use_type);
800 }
801 in_use_type = false;
802 current_text.clear();
803 },
804 b"CommercialModelType" if in_commercial_model_type => {
805 if !current_text.trim().is_empty() {
806 let commercial_model = match current_text.trim() {
807 "PayAsYouGoModel" => CommercialModelType::PayAsYouGoModel,
808 "SubscriptionModel" => CommercialModelType::SubscriptionModel,
809 "AdSupportedModel" => CommercialModelType::AdSupportedModel,
810 other => CommercialModelType::Other(other.to_string()),
811 };
812 commercial_model_types.push(commercial_model);
813 }
814 in_commercial_model_type = false;
815 current_text.clear();
816 },
817 b"ValidityPeriod" => {
818 validity_period = Some(ValidityPeriod {
820 start_date: start_date.clone(),
821 end_date: None, });
823 in_validity_period = false;
824 },
825 b"StartDate" if in_start_date => {
826 if !current_text.trim().is_empty() {
827 if let Ok(parsed_date) = DateTime::parse_from_rfc3339(current_text.trim()) {
829 start_date = Some(parsed_date.with_timezone(&Utc));
830 }
831 }
832 in_start_date = false;
833 current_text.clear();
834 },
835 _ => {}
836 }
837 },
838 Event::Eof => break,
839 _ => {}
840 }
841 }
842 Err(e) => {
843 return Err(ParseError::XmlError(format!("XML parsing error in release deal: {}", e)));
844 }
845 }
846 buf.clear();
847 }
848
849 let deal_terms = DealTerms {
850 validity_period,
851 start_date,
852 end_date: None,
853 territory_code: territory_codes,
854 excluded_territory_code: Vec::new(),
855 distribution_channel: Vec::new(),
856 excluded_distribution_channel: Vec::new(),
857 commercial_model_type: commercial_model_types,
858 use_type: use_types,
859 price_information: Vec::new(),
860 wholesale_price: Vec::new(),
861 suggested_retail_price: Vec::new(),
862 pre_order_date: None,
863 pre_order_preview_date: None,
864 instant_gratification_date: None,
865 takedown_date: None,
866 };
867
868 let deal = Deal {
869 deal_reference,
870 deal_release_reference: Vec::new(), deal_terms,
872 };
873
874 Ok(deal)
875 }
876}
877
878fn parse_duration(duration_str: &str) -> Result<std::time::Duration, std::time::Duration> {
880 use std::time::Duration;
881 if duration_str.starts_with("PT") {
883 let duration_part = &duration_str[2..]; let mut total_seconds = 0u64;
885 let mut current_number = String::new();
886
887 for ch in duration_part.chars() {
888 match ch {
889 '0'..='9' | '.' => current_number.push(ch),
890 'H' => {
891 if let Ok(hours) = current_number.parse::<f64>() {
892 total_seconds += (hours * 3600.0) as u64;
893 }
894 current_number.clear();
895 },
896 'M' => {
897 if let Ok(minutes) = current_number.parse::<f64>() {
898 total_seconds += (minutes * 60.0) as u64;
899 }
900 current_number.clear();
901 },
902 'S' => {
903 if let Ok(seconds) = current_number.parse::<f64>() {
904 total_seconds += seconds as u64;
905 }
906 current_number.clear();
907 },
908 _ => {}
909 }
910 }
911
912 Ok(Duration::from_secs(total_seconds))
913 } else {
914 if let Ok(seconds) = duration_str.parse::<f64>() {
916 Ok(Duration::from_secs_f64(seconds))
917 } else {
918 Err(Duration::from_secs(0)) }
920 }
921}