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