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                            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                                    // 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(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    /// Build graph model from XML with namespace context
153    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        // For now, delegate to the security-aware method
172        // In the future, this would use the namespace context for proper element resolution
173        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); // fallback
181        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        // Parse until we exit MessageHeader or reach EOF
195        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; // We're done parsing the header
227                        },
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                            // Try to parse the datetime, fall back to current time if invalid
242                            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        // Initialize all the fields we'll extract
316        let mut release_reference = format!("R_{:?}", self.version); // fallback
317        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        // State tracking for nested elements
325        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        // Parse the Release element and extract all real data
340        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                    // Validate each event so the validator stack stays consistent
346                    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 no title was found, provide a fallback
514        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        // Initialize all the fields we'll extract
549        let mut resource_reference = format!("RES_{:?}", self.version); // fallback
550        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        // State tracking for nested elements
556        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        // Parse the SoundRecording element and extract real data
567        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                    // Validate each event so the validator stack stays consistent
573                    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                                        // Parse duration in ISO 8601 format (PT3M30S) or as seconds
647                                        if let Ok(parsed_duration) = parse_duration(&current_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                                    // For now, we'll store artist names in the reference_title
660                                    // In a full implementation, we might want to track artists separately
661                                    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 no title was found, provide a fallback
679        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        // Initialize all the fields we'll extract
709        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        // State tracking for nested elements
718        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        // Parse the ReleaseDeal element and extract real data
727        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                    // Validate each event so the validator stack stays consistent
733                    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                                    // Create ValidityPeriod from collected start_date
819                                    validity_period = Some(ValidityPeriod {
820                                        start_date: start_date.clone(),
821                                        end_date: None, // Could be extended to parse EndDate if needed
822                                    });
823                                    in_validity_period = false;
824                                },
825                                b"StartDate" if in_start_date => {
826                                    if !current_text.trim().is_empty() {
827                                        // Try to parse the date/time
828                                        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(), // Could be extracted from parent context if needed
871            deal_terms,
872        };
873
874        Ok(deal)
875    }
876}
877
878// Helper function to parse duration strings
879fn parse_duration(duration_str: &str) -> Result<std::time::Duration, std::time::Duration> {
880    use std::time::Duration;
881    // Handle ISO 8601 duration format (PT3M30S)
882    if duration_str.starts_with("PT") {
883        let duration_part = &duration_str[2..]; // Remove "PT"
884        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        // Try to parse as plain seconds
915        if let Ok(seconds) = duration_str.parse::<f64>() {
916            Ok(Duration::from_secs_f64(seconds))
917        } else {
918            Err(Duration::from_secs(0)) // Return error as Duration (will be ignored)
919        }
920    }
921}