serde_ssml 0.4.0

A robust Rust library for parsing, manipulating, and generating Speech Synthesis Markup Language (SSML) documents.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
//! # SSML Parser Library
#![doc = include_str!("../README.md")]
#![deny(
    warnings,
    bad_style,
    dead_code,
    improper_ctypes,
    non_shorthand_field_patterns,
    no_mangle_generic_items,
    overflowing_literals,
    path_statements,
    patterns_in_fns_without_body,
    unconditional_recursion,
    unused,
    unused_allocation,
    unused_comparisons,
    unused_parens,
    while_true,
    missing_debug_implementations,
    missing_docs,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
    unused_results,
    unreachable_pub,
    deprecated,
    unknown_lints,
    unreachable_code,
    unused_mut
)]

use chumsky::prelude::*;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, time::Duration};

mod break_strength;
mod ser;
pub use break_strength::BreakStrength;
mod functions;
pub use functions::*;

/// Represents the entire SSML document structure.
///
/// # Fields
///
/// * `elements`: A vector of top-level SSML elements
///
/// # Example
///
/// ```rust
/// use serde_ssml::{SSML, SsmlElement};
///
/// // Manually constructing an SSML structure
/// let ssml = SSML {
///     elements: vec![
///         SsmlElement::Speak {
///             version: Some("1.1".to_string()),
///             xmlns: Some("http://www.w3.org/2001/10/synthesis".to_string()),
///             lang: Some("en-US".to_string()),
///             children: vec![
///                 SsmlElement::Text("Hello, world!".to_string())
///             ]
///         }
///     ]
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SSML {
    /// Top-level SSML elements
    pub elements: Vec<SsmlElement>,
}

/// Represents the various elements that can appear in an SSML (Speech Synthesis Markup Language) document.
///
/// # Variants
///
/// Each variant corresponds to a specific SSML element type, capturing its semantic meaning, attributes, and potential child elements.
///
/// ## Core Structural Elements
/// - `Speak`: The root element defining the entire speech synthesis document
///   - `version`: SSML specification version (e.g., "1.1")
///   - `xmlns`: XML namespace URI defining the SSML standard
///   - `lang`: Language of the spoken content (e.g., "en-US")
///   - `children`: Nested elements within the speak block
///
/// - `Voice`: Specifies voice characteristics for a section of text
///   - `name`: Identifier or name of the voice (e.g., "en-US-Standard-A")
///   - `children`: Text and elements to be spoken in the specified voice
///
/// ## Text Formatting
/// - `Paragraph`: Logical grouping of sentences, typically used for semantic structure
///   - `children`: Sentences or other elements within the paragraph
///
/// - `Sentence`: Represents a complete grammatical sentence
///   - `children`: Words, phrases, and other inline elements
///
/// ## Pronunciation and Interpretation
/// - `Phoneme`: Provides precise phonetic pronunciation
///   - `alphabet`: Phonetic alphabet used (e.g., "ipa")
///   - `ph`: Phonetic representation of the text
///   - `children`: Text or elements to be pronounced phonetically
///
/// - `SayAs`: Instructs how to interpret and pronounce specific content types
///   - `interpret_as`: Content type (e.g., "date", "cardinal", "telephone")
///   - `format`: Optional format specification
///   - `detail`: Additional interpretation details
///   - `children`: Content to be interpreted
///
/// - `Sub`: Provides an alternative pronunciation or text
///   - `alias`: Replacement text or pronunciation
///   - `children`: Original text to be substituted
///
/// ## Prosody and Emphasis
/// - `Prosody`: Controls speech characteristics like rate, pitch, and volume
///   - `rate`: Speech rate (e.g., "slow", "fast", "150%")
///   - `pitch`: Pitch modification (e.g., "high", "+10%", "x-low")
///   - `contour`: Advanced pitch contour specification
///   - `range`: Pitch variation range
///   - `volume`: Volume level (e.g., "loud", "soft", "+6dB")
///   - `children`: Elements affected by prosody settings
///
/// - `Emphasis`: Highlights the importance of text
///   - `level`: Emphasis intensity (e.g., "strong", "moderate", "reduced")
///   - `children`: Text to be emphasized
///
/// ## Timing and Structural Controls
/// - `Break`: Introduces a pause or break in speech
///   - `time`: Duration of the break (e.g., "500ms", "1s")
///   - `strength`: Relative strength of the break (e.g., "weak", "strong")
///
/// - `Mark`: Provides a synchronization point for external systems
///   - `name`: Unique identifier for the mark
///
/// ## Multimedia and Metadata
/// - `Audio`: Embeds audio content within speech
///   - `src`: Source URI of the audio file
///   - `children`: Fallback text or description
///
/// - `Desc`: Provides a textual description (often for accessibility)
///   - `children`: Descriptive text or elements
///
/// - `LexiconUri`: References an external pronunciation dictionary
///   - `uri`: Location of the lexicon resource
///
/// ## Language and Localization
/// - `Lang`: Changes the language for a section of text
///   - `xml_lang`: Language code (e.g., "fr-FR", "es-ES")
///   - `children`: Text in the specified language
///
/// ## Raw Content
/// - `Text`: Represents plain text content
///
/// # Example
///
/// ```rust
/// use serde_ssml::SsmlElement;
/// use serde_ssml::BreakStrength;
/// use std::time::Duration;
///
/// // Creating a complex SSML structure demonstrating various elements
/// let speak_element = SsmlElement::Speak {
///     version: Some("1.1".to_string()),
///     xmlns: Some("http://www.w3.org/2001/10/synthesis".to_string()),
///     lang: Some("en-US".to_string()),
///     children: vec![
///         SsmlElement::Paragraph {
///             children: vec![
///                 SsmlElement::Sentence {
///                     children: vec![
///                         SsmlElement::Text("Welcome to ".to_string()),
///                         SsmlElement::Prosody {
///                             rate: "slow".to_string(),
///                             pitch: "low".to_string(),
///                             contour: "".to_string(),
///                             range: "".to_string(),
///                             volume: "soft".to_string(),
///                             children: vec![
///                                 SsmlElement::Emphasis {
///                                     level: "strong".to_string(),
///                                     children: vec![
///                                         SsmlElement::Text("speech synthesis".to_string())
///                                     ]
///                                 }
///                             ]
///                         },
///                         SsmlElement::Text("!".to_string())
///                     ]
///                 }
///             ]
///         },
///         SsmlElement::Break {
///             time: Some(Duration::from_millis(500)),
///             strength: Some(BreakStrength::Medium)
///         }
///     ]
/// };
/// ```
///
/// # Notes
/// - This enum captures the structural and semantic richness of SSML
/// - Not all possible SSML variations may be represented
/// - Parsing and rendering may depend on specific text-to-speech implementations
///
/// Represents the various elements that can appear in a Speech Synthesis Markup Language (SSML) document.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum SsmlElement {
    // Core structural elements
    /// Specifies a specific voice characteristics for a section of text.
    Voice {
        /// The name or identifier of the voice to be used.
        ///
        /// # Examples
        /// - "en-US-Standard-A"
        /// - "en-GB-Wavenet-B"
        /// - "female-adult"
        name: String,

        /// The child elements to be spoken using the specified voice.
        ///
        /// Allows nesting of text, emphasis, and other SSML elements
        /// within the voice context.
        children: Vec<SsmlElement>,
    },

    /// Represents the root element of an SSML document.
    Speak {
        /// The version of the SSML specification being used.
        ///
        /// # Examples
        /// - "1.0"
        /// - "1.1"
        version: Option<String>,

        /// The XML namespace URI for the SSML standard.
        ///
        /// # Example
        /// "http://www.w3.org/2001/10/synthesis"
        xmlns: Option<String>,

        /// The language of the spoken content.
        ///
        /// # Examples
        /// - "en-US"
        /// - "fr-FR"
        /// - "es-ES"
        lang: Option<String>,

        /// The child elements contained within the speak block.
        ///
        /// Can include paragraphs, voices, breaks, and other SSML elements.
        children: Vec<SsmlElement>,
    },

    // Text formatting elements
    /// Represents a paragraph, which groups one or more sentences.
    Paragraph {
        /// The child elements within the paragraph.
        ///
        /// Typically contains sentences, text, or other inline elements.
        children: Vec<SsmlElement>,
    },

    /// Represents a single grammatical sentence.
    Sentence {
        /// The child elements within the sentence.
        ///
        /// Can include text, emphasis, breaks, and other inline elements.
        children: Vec<SsmlElement>,
    },

    // Pronunciation control
    /// Provides precise phonetic pronunciation for specific text.
    Phoneme {
        /// The phonetic alphabet used for pronunciation.
        ///
        /// # Examples
        /// - "ipa" (International Phonetic Alphabet)
        /// - "x-sampa"
        alphabet: String,

        /// The phonetic representation of the text.
        ///
        /// # Examples
        /// - "təˈmeɪtoʊ" (IPA for "tomato")
        /// - "h@ˈloʊ" (IPA for "hello")
        ph: String,

        /// The text or elements to be pronounced phonetically.
        children: Vec<SsmlElement>,
    },

    /// Specifies how to interpret and pronounce specific content types.
    SayAs {
        /// The type of content to be interpreted.
        ///
        /// # Examples
        /// - "date"
        /// - "cardinal"
        /// - "ordinal"
        /// - "telephone"
        interpret_as: String,

        /// Optional format specification for the content.
        ///
        /// # Examples
        /// - "mdy" (month-day-year)
        /// - "hms12" (12-hour time format)
        format: String,

        /// Additional interpretation details.
        ///
        /// Provides extra context for content interpretation.
        detail: String,

        /// The content to be interpreted.
        children: Vec<SsmlElement>,
    },

    /// Provides an alternative pronunciation or text substitution.
    Sub {
        /// The replacement text or pronunciation.
        ///
        /// # Examples
        /// - "World Wide Web Consortium" (for "W3C")
        /// - "Hypertext Markup Language" (for "HTML")
        alias: String,

        /// The original text to be substituted.
        children: Vec<SsmlElement>,
    },

    // Prosody and emphasis
    /// Controls speech characteristics like rate, pitch, and volume.
    Prosody {
        /// Speech rate modification.
        ///
        /// # Examples
        /// - "slow"
        /// - "fast"
        /// - "150%" (50% faster)
        rate: String,

        /// Pitch modification.
        ///
        /// # Examples
        /// - "high"
        /// - "low"
        /// - "+10%" (slightly higher pitch)
        pitch: String,

        /// Advanced pitch contour specification.
        ///
        /// # Example
        /// "(0%,+0%) (100%,-10%)" for custom pitch variations
        contour: String,

        /// Pitch variation range.
        ///
        /// # Examples
        /// - "x-low"
        /// - "x-high"
        range: String,

        /// Volume level modification.
        ///
        /// # Examples
        /// - "loud"
        /// - "soft"
        /// - "+6dB"
        volume: String,

        /// The elements affected by prosody settings.
        children: Vec<SsmlElement>,
    },

    /// Highlights the importance of text.
    Emphasis {
        /// Emphasis intensity level.
        ///
        /// # Examples
        /// - "strong"
        /// - "moderate"
        /// - "reduced"
        level: String,

        /// The text or elements to be emphasized.
        children: Vec<SsmlElement>,
    },

    // Timing controls
    /// Introduces a pause or break in speech.
    Break {
        /// Duration of the break.
        ///
        /// # Examples
        /// - "500ms"
        /// - "1s"
        time: Option<Duration>,

        /// Relative strength of the break.
        ///
        /// # Examples
        /// - "none"
        /// - "x-weak"
        /// - "weak"
        /// - "medium"
        /// - "strong"
        /// - "x-strong"
        strength: Option<BreakStrength>,
    },

    /// Provides a synchronization point for external systems.
    Mark {
        /// Unique identifier for the mark.
        ///
        /// # Examples
        /// - "start_section"
        /// - "pause_point"
        name: String,
    },

    // Audio and metadata
    /// Embeds audio content within speech synthesis.
    Audio {
        /// Source URI of the audio file.
        ///
        /// # Examples
        /// - "https://example.com/sound.mp3"
        /// - "file:///path/to/local/audio.wav"
        src: String,

        /// Fallback text or description.
        ///
        /// Displayed or spoken if audio cannot be played.
        children: Vec<SsmlElement>,
    },

    /// Provides a textual description (often for accessibility).
    Desc {
        /// Descriptive text or elements.
        children: Vec<SsmlElement>,
    },

    /// References an external pronunciation dictionary.
    LexiconUri {
        /// Location of the lexicon resource.
        ///
        /// # Examples
        /// - "https://example.com/lexicon.pls"
        /// - "file:///path/to/pronunciation/dictionary.xml"
        uri: String,
    },

    // Misc
    /// Changes the language for a section of text.
    Lang {
        /// Language code for the enclosed content.
        ///
        /// # Examples
        /// - "fr-FR"
        /// - "es-ES"
        /// - "de-DE"
        xml_lang: String,

        /// Text or elements in the specified language.
        children: Vec<SsmlElement>,
    },

    /// Represents raw text content.
    Text(String),
}

// Parse an attribute name (letters, digits, underscore, hyphen, colon)
fn attr_ident() -> impl Parser<char, String, Error = Simple<char>> {
    filter(|c: &char| c.is_ascii_alphabetic() || *c == '_' || *c == '-' || *c == ':')
        .chain::<char, _, _>(
            filter(|c: &char| c.is_ascii_alphanumeric() || *c == '_' || *c == '-' || *c == ':')
                .repeated(),
        )
        .collect()
}

// Parse an attribute (e.g., name="value")
fn attribute() -> impl Parser<char, (String, String), Error = Simple<char>> {
    attr_ident().padded().then_ignore(just('=').padded()).then(
        just('"')
            .ignore_then(none_of("\"").repeated().collect::<String>())
            .then_ignore(just('"')),
    )
}

// Build an SSML parser
fn ssml_parser() -> impl Parser<char, SSML, Error = Simple<char>> {
    // Parser for opening tags with attributes
    let open_tag = |name: &'static str| {
        just('<')
            .padded()
            .ignore_then(just(name).padded())
            .ignore_then(attribute().padded().repeated().collect::<Vec<_>>())
            .map(move |attrs| {
                let mut attrs_map = HashMap::new();
                for (key, value) in attrs {
                    let _ = attrs_map.insert(key, value);
                }
                attrs_map
            })
            .then_ignore(just('>'))
            .padded()
    };

    // Parser for closing tags
    let close_tag = |name: &'static str| {
        just("</")
            .padded()
            .ignore_then(just(name).padded())
            .then_ignore(just('>'))
            .to(())
            .padded()
    };

    // Parser for self-closing tags
    let self_close_tag = |name: &'static str| {
        just('<')
            .ignore_then(just(name).padded())
            .ignore_then(attribute().padded().repeated().collect::<Vec<_>>())
            .map(move |attrs| {
                let mut attrs_map = HashMap::new();
                for (key, value) in attrs {
                    let _ = attrs_map.insert(key, value);
                }
                attrs_map
            })
            .then_ignore(just("/>"))
            .padded()
    };

    // Text content parser
    let text = none_of("<")
        .repeated()
        .at_least(1)
        .collect::<String>()
        .map(|txt| txt.trim().to_string())
        .map(SsmlElement::Text);

    // Parser for XML declaration
    let xml_decl = just("<?xml")
        .padded()
        .ignore_then(
            // Parse attributes like version="1.0"
            attribute().padded().repeated(),
        )
        .then_ignore(just("?>").padded())
        .ignored()
        .padded();

    // Recursive parser for nested elements
    let ssml_parser = recursive(|element| {
        let speak_element = open_tag("speak")
            .then(element.clone().repeated())
            .then_ignore(close_tag("speak"))
            .map(|(attrs, children)| SsmlElement::Speak {
                version: attrs.get("version").cloned(),
                xmlns: attrs.get("xmlns").cloned(),
                lang: attrs.get("xml:lang").cloned(),
                children,
            });

        let voice_element = open_tag("voice")
            .then(element.clone().repeated())
            .then_ignore(close_tag("voice"))
            .map(|(attrs, children)| SsmlElement::Voice {
                name: attrs.get("name").cloned().unwrap_or_default(),
                children,
            });

        let paragraph_element = open_tag("p")
            .then(element.clone().repeated())
            .then_ignore(close_tag("p"))
            .map(|(_, children)| SsmlElement::Paragraph { children });

        let sentence_element = open_tag("s")
            .then(element.clone().repeated())
            .then_ignore(close_tag("s"))
            .map(|(_, children)| SsmlElement::Sentence { children });

        let phoneme_element = open_tag("phoneme")
            .then(element.clone().repeated())
            .then_ignore(close_tag("phoneme"))
            .map(|(attrs, children)| SsmlElement::Phoneme {
                alphabet: attrs.get("alphabet").cloned().unwrap_or_default(),
                ph: attrs.get("ph").cloned().unwrap_or_default(),
                children,
            });

        let say_as_element = open_tag("say-as")
            .then(element.clone().repeated())
            .then_ignore(close_tag("say-as"))
            .map(|(attrs, children)| SsmlElement::SayAs {
                interpret_as: attrs.get("interpret-as").cloned().unwrap_or_default(),
                format: attrs.get("format").cloned().unwrap_or_default(),
                detail: attrs.get("detail").cloned().unwrap_or_default(),
                children,
            });

        let sub_element = open_tag("sub")
            .then(element.clone().repeated())
            .then_ignore(close_tag("sub"))
            .map(|(attrs, children)| SsmlElement::Sub {
                alias: attrs.get("alias").cloned().unwrap_or_default(),
                children,
            });

        let prosody_element = open_tag("prosody")
            .then(element.clone().repeated())
            .then_ignore(close_tag("prosody"))
            .map(|(attrs, children)| SsmlElement::Prosody {
                rate: attrs.get("rate").cloned().unwrap_or_default(),
                pitch: attrs.get("pitch").cloned().unwrap_or_default(),
                contour: attrs.get("contour").cloned().unwrap_or_default(),
                range: attrs.get("range").cloned().unwrap_or_default(),
                volume: attrs.get("volume").cloned().unwrap_or_default(),
                children,
            });

        let emphasis_element = open_tag("emphasis")
            .then(element.clone().repeated())
            .then_ignore(close_tag("emphasis"))
            .map(|(attrs, children)| SsmlElement::Emphasis {
                level: attrs.get("level").cloned().unwrap_or_default(),
                children,
            });

        let audio_element = open_tag("audio")
            .then(element.clone().repeated())
            .then_ignore(close_tag("audio"))
            .map(|(attrs, children)| SsmlElement::Audio {
                src: attrs.get("src").cloned().unwrap_or_default(),
                children,
            });

        let desc_element = open_tag("desc")
            .then(element.clone().repeated())
            .then_ignore(close_tag("desc"))
            .map(|(_, children)| SsmlElement::Desc { children });

        let lang_element = open_tag("lang")
            .then(element.clone().repeated())
            .then_ignore(close_tag("lang"))
            .map(|(attrs, children)| SsmlElement::Lang {
                xml_lang: attrs.get("xml:lang").cloned().unwrap_or_default(),
                children,
            });

        let break_element = self_close_tag("break")
            .map(|attrs| SsmlElement::Break {
                time: attrs.get("time").and_then(|t| duration_str::parse(t).ok()),
                strength: attrs.get("strength").and_then(|s| s.parse().ok()),
            })
            .or(open_tag("break")
                .then_ignore(close_tag("break"))
                .map(|attrs| SsmlElement::Break {
                    time: attrs.get("time").and_then(|t| duration_str::parse(t).ok()),
                    strength: attrs.get("strength").and_then(|s| s.parse().ok()),
                }));

        let mark_element = self_close_tag("mark")
            .map(|attrs| SsmlElement::Mark {
                name: attrs.get("name").cloned().unwrap_or_default(),
            })
            .or(open_tag("mark")
                .then_ignore(close_tag("mark"))
                .map(|attrs| SsmlElement::Mark {
                    name: attrs.get("name").cloned().unwrap_or_default(),
                }));

        let lexicon_element = self_close_tag("lexicon")
            .map(|attrs| SsmlElement::LexiconUri {
                uri: attrs.get("uri").cloned().unwrap_or_default(),
            })
            .or(open_tag("lexicon")
                .then_ignore(close_tag("lexicon"))
                .map(|attrs| SsmlElement::LexiconUri {
                    uri: attrs.get("uri").cloned().unwrap_or_default(),
                }));

        choice((
            speak_element,
            voice_element,
            paragraph_element,
            sentence_element,
            phoneme_element,
            say_as_element,
            sub_element,
            prosody_element,
            emphasis_element,
            audio_element,
            desc_element,
            lang_element,
            break_element,
            mark_element,
            lexicon_element,
            text,
        ))
    })
    .repeated()
    .collect::<Vec<_>>()
    .map(|elements| SSML { elements });

    xml_decl
        .or_not()
        .ignore_then(ssml_parser)
        .then_ignore(end())
}

/// Represents a parse error, containing a list of simple error tokens.
pub type ParseError = Vec<Simple<char>>;

/// Parses a SSML (Speech Synthesis Markup Language) string into a structured representation.
///
/// # Arguments
///
/// * `input` - A string slice containing the SSML content to parse
///
/// # Returns
///
/// * `Result<SSML, Vec<Simple<char>>>` -
///     - `Ok(SSML)` if parsing is successful, containing the parsed SSML structure
///     - `Err(Vec<Simple<char>>)` if parsing fails, containing parse errors
///
/// # Examples
///
/// Basic usage:
/// ```rust
/// use serde_ssml::{from_str, SsmlElement};
///
/// // Parse a simple SSML string
/// let input = r#"<speak>Hello, world!</speak>"#;
/// let result = from_str(input);
///
/// match result {
///     Ok(ssml) => {
///         assert_eq!(ssml.elements.len(), 1);
///
///         // Check the content of the speak element
///         if let SsmlElement::Speak { children, .. } = &ssml.elements[0] {
///             assert_eq!(children.len(), 1);
///
///             // Verify the text content
///             if let SsmlElement::Text(text) = &children[0] {
///                 assert_eq!(text, "Hello, world!");
///             }
///         }
///     }
///     Err(errors) => {
///         panic!("Parsing failed: {:?}", errors);
///     }
/// }
/// ```
///
/// Parsing with nested elements:
/// ```rust
/// let complex_input = r#"
/// <speak version="1.1" xml:lang="en-US">
///     <p>
///         <s>This is a <emphasis level="strong">test</emphasis> sentence.</s>
///     </p>
///     <voice name="en-GB">
///         <p>This text will be spoken with a British accent.</p>
///     </voice>
/// </speak>
/// "#;
///
/// let result = serde_ssml::from_str(complex_input);
/// assert!(result.is_ok());
/// ```
///
/// Handling parsing errors:
/// ```rust
/// let invalid_input = r#"<speak>Unclosed tag"#;
/// let result = serde_ssml::from_str(invalid_input);
///
/// assert!(result.is_err());
/// ```
///
/// # Supported SSML Elements
///
/// The parser supports a wide range of SSML elements, including:
/// - `<speak>`: Root element with optional attributes
/// - `<voice>`: Voice selection and customization
/// - `<p>`: Paragraph
/// - `<s>`: Sentence
/// - `<break>`: Pause or break in speech
/// - `<emphasis>`: Text emphasis
/// - `<phoneme>`: Pronunciation control
/// - `<say-as>`: Interpretation of content
/// - `<prosody>`: Speech rate, pitch, and volume control
/// - And many more...
///
/// # Notes
///
/// - The parser is lenient with whitespace and nested structures
/// - Attributes are parsed and stored for various elements
/// - Text content is preserved as `SsmlElement::Text`
///
/// # Limitations
///
/// - Does not validate against official SSML schemas
/// - Parsing is based on structural recognition, not semantic validation
pub fn from_str(input: impl AsRef<str>) -> Result<SSML, ParseError> {
    ssml_parser().parse(input.as_ref())
}

/// Converts a structured SSML representation into a serialized SSML string.
pub fn to_string(ssml: &SSML) -> String {
    ser::to_ssml(ssml)
}

// Example usage and demonstration module
#[cfg(test)]
mod documentation_examples {
    use super::*;

    #[test]
    fn example_parsing_and_traversing() {
        let input = r#"
        <speak version="1.1" xml:lang="en-US">
            <p>
                <s>This is a <emphasis level="strong">important</emphasis> message.</s>
            </p>
        </speak>
        "#;

        // Parse the SSML
        let ssml = from_str(input).expect("Failed to parse SSML");

        // Traverse and extract information
        if let SsmlElement::Speak {
            version,
            lang,
            children,
            ..
        } = &ssml.elements[0]
        {
            assert_eq!(version.as_deref(), Some("1.1"));
            assert_eq!(lang.as_deref(), Some("en-US"));

            // Recursive function to find emphasized text
            fn find_emphasized_text(element: &SsmlElement) -> Option<String> {
                match element {
                    SsmlElement::Emphasis { children, .. } => children.iter().find_map(|child| {
                        if let SsmlElement::Text(text) = child {
                            Some(text.clone())
                        } else {
                            None
                        }
                    }),
                    SsmlElement::Paragraph { children }
                    | SsmlElement::Sentence { children }
                    | SsmlElement::Voice { children, .. }
                    | SsmlElement::Prosody { children, .. }
                    | SsmlElement::Audio { children, .. }
                    | SsmlElement::Lang { children, .. } => {
                        children.iter().find_map(find_emphasized_text)
                    }
                    _ => None,
                }
            }

            // Find the emphasized text
            let emphasized_text = children
                .iter()
                .find_map(find_emphasized_text)
                .expect("No emphasized text found");

            assert_eq!(emphasized_text, "important");
        }
    }

    #[test]
    fn example_constructing_ssml() {
        // Programmatically construct an SSML document
        let ssml = SSML {
            elements: vec![SsmlElement::Speak {
                version: Some("1.1".to_string()),
                xmlns: Some("http://www.w3.org/2001/10/synthesis".to_string()),
                lang: Some("en-US".to_string()),
                children: vec![
                    SsmlElement::Paragraph {
                        children: vec![SsmlElement::Sentence {
                            children: vec![
                                SsmlElement::Text("Welcome to ".to_string()),
                                SsmlElement::Emphasis {
                                    level: "strong".to_string(),
                                    children: vec![SsmlElement::Text("SSML".to_string())],
                                },
                                SsmlElement::Text(" parsing!".to_string()),
                            ],
                        }],
                    },
                    SsmlElement::Break {
                        time: Some(Duration::from_millis(500)),
                        strength: Some(BreakStrength::Medium),
                    },
                ],
            }],
        };

        // Convert back to string (hypothetical - actual serialization would require additional implementation)
        // This demonstrates the structure of the parsed/constructed SSML
        assert_eq!(ssml.elements.len(), 1);

        if let SsmlElement::Speak { children, .. } = &ssml.elements[0] {
            assert_eq!(children.len(), 2);
        }
    }
}