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