ddex_parser/transform/
graph.rs

1// core/src/transform/graph.rs
2// Remove unused imports and variables
3use 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        // Enable strict XML validation
41        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        // Parse the actual header from XML
46        let message_header = self.parse_header_from_xml(&mut xml_reader)?;
47
48        // Reset reader to start for main parsing loop
49        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(); // Made mutable to collect parsed resources
58        let parties = Vec::new(); // Remove mut
59        let mut deals = Vec::new(); // Made mutable to collect parsed deals
60
61        // Parse with XML validation and depth tracking
62        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                    // Validate XML structure
71                    validator.validate_event(event, &xml_reader)?;
72
73                    // Check depth limit
74                    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                                    // Create a minimal release and manually validate the end event
89                                    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                                    // Parse the SoundRecording and add it to resources
98                                    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                                    // Parse the ReleaseDeal and add it to deals
107                                    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    /// Build graph model from XML with namespace context
161    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        // For now, delegate to the security-aware method
180        // In the future, this would use the namespace context for proper element resolution
181        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); // fallback
189        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        // Parse until we exit MessageHeader or reach EOF
203        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; // We're done parsing the header
235                        },
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                            // Try to parse the datetime, fall back to current time if invalid
250                            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        // Initialize all the fields we'll extract
332        let mut release_reference = format!("R_{:?}", self.version); // fallback
333        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        // State tracking for nested elements
341        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        // Parse the Release element and extract all real data
356        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                    // Validate each event so the validator stack stays consistent
362                    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 no title was found, provide a fallback
538        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        // Initialize all the fields we'll extract
573        let mut resource_reference = format!("RES_{:?}", self.version); // fallback
574        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        // State tracking for nested elements
580        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        // Parse the SoundRecording element and extract real data
591        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                    // Validate each event so the validator stack stays consistent
597                    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                                        // Parse duration in ISO 8601 format (PT3M30S) or as seconds
671                                        if let Ok(parsed_duration) = parse_duration(&current_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                                    // For now, we'll store artist names in the reference_title
684                                    // In a full implementation, we might want to track artists separately
685                                    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 no title was found, provide a fallback
711        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        // Initialize all the fields we'll extract
741        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        // State tracking for nested elements
750        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        // Parse the ReleaseDeal element and extract real data
759        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                    // Validate each event so the validator stack stays consistent
765                    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                                    // Create ValidityPeriod from collected start_date
851                                    validity_period = Some(ValidityPeriod {
852                                        start_date: start_date.clone(),
853                                        end_date: None, // Could be extended to parse EndDate if needed
854                                    });
855                                    in_validity_period = false;
856                                },
857                                b"StartDate" if in_start_date => {
858                                    if !current_text.trim().is_empty() {
859                                        // Try to parse the date/time
860                                        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(), // Could be extracted from parent context if needed
911            deal_terms,
912        };
913
914        Ok(deal)
915    }
916}
917
918// Helper function to parse duration strings
919fn parse_duration(duration_str: &str) -> Result<std::time::Duration, std::time::Duration> {
920    use std::time::Duration;
921    // Handle ISO 8601 duration format (PT3M30S)
922    if duration_str.starts_with("PT") {
923        let duration_part = &duration_str[2..]; // Remove "PT"
924        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        // Try to parse as plain seconds
955        if let Ok(seconds) = duration_str.parse::<f64>() {
956            Ok(Duration::from_secs_f64(seconds))
957        } else {
958            Err(Duration::from_secs(0)) // Return error as Duration (will be ignored)
959        }
960    }
961}