text_to_polly_ssml/
xml_writer.rs

1//! Controls writing of the XML part of SSML. This contains all low level bindings in a sense
2//! to the tags. You should probably never use this directly.
3
4use color_eyre::{eyre::eyre, Result};
5use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
6use quick_xml::Writer;
7
8use std::io::Cursor;
9
10use crate::ssml_constants::*;
11
12/// An XML Writer. Used for manual manipulation of the SSML Output (which uses XML).
13///
14/// You should probably never use this directly, instead interacting with the parser,
15/// however if you'd like to build your own parser, and just reuse the XML Rendering
16/// then you'd want to use this.
17pub struct XmlWriter {
18    /// The XML Writer instance. The thing that actually writes the XML.
19    pub writer: Writer<Cursor<Vec<u8>>>,
20}
21
22impl XmlWriter {
23    /// Creates a new XML Writer. This writerr writes into a std::vec::Vec, and at any
24    /// point can be turned into a string. It is your job to close all tags before rendering
25    /// this. We don't close everything when you render it. You render what you put in.
26    ///
27    /// It should also note we automatically write the header:
28    ///
29    /// ```text
30    /// <?xml version="1.0"?>
31    /// ```
32    ///
33    /// Upon creation of an XML Writer. This is to try, and keep as close to the W3C docs
34    /// for SSML v1.1. Which you can read about
35    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/).
36    ///
37    /// # Examples
38    ///
39    /// ```rust
40    /// use text_to_polly_ssml::xml_writer::XmlWriter;
41    /// let result = XmlWriter::new();
42    /// assert!(result.is_ok());
43    /// ```
44    pub fn new() -> Result<XmlWriter> {
45        let mut writer = Writer::new(Cursor::new(Vec::new()));
46        writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?;
47        Ok(XmlWriter { writer: writer })
48    }
49
50    /// Starts an SSML <speak> tag. For AWS Polly this is the root tag, and should only have one
51    /// decleration as mentioned in their docs (As of April 20th, 2017):
52    ///
53    /// ```text
54    /// The <speak> tag is the root element of all Amazon Polly SSML text.
55    /// All SSML-enhanced text to be spoken must be included within this tag.
56    /// ```
57    ///
58    /// It should be noted although AWS Docs do not mention any attributes you can pass in
59    /// to the <speak> tag, we still have an optional `lang`, and `onlangfailure` attributes
60    /// to closely mirror the W3C Standard, as seen:
61    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/).
62    ///
63    ///
64    /// # Examples
65    ///
66    /// Rust Code:
67    ///
68    /// ```rust
69    /// use text_to_polly_ssml::xml_writer::XmlWriter;
70    /// let mut new_xml_writer = XmlWriter::new();
71    /// assert!(new_xml_writer.is_ok());
72    /// let start_speak_result = new_xml_writer.unwrap().start_ssml_speak(None, None);
73    /// assert!(start_speak_result.is_ok());
74    /// ```
75    ///
76    /// Generated SSML:
77    ///
78    /// ```text
79    /// <?xml version="1.0"?>
80    /// <speak xml:lang="en-US" onlangfailure="processorchoice"
81    ///    xmlns="http://www.w3.org/2001/10/synthesis"
82    ///    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
83    /// ```
84    pub fn start_ssml_speak(
85        &mut self,
86        lang: Option<String>,
87        onlangfailure: Option<String>,
88    ) -> Result<()> {
89        let mut elem = BytesStart::owned(b"speak".to_vec(), "speak".len());
90        elem.push_attribute(("xml:lang", &*lang.unwrap_or("en-US".to_owned())));
91        elem.push_attribute((
92            "onlangfailure",
93            &*onlangfailure.unwrap_or("processorchoice".to_owned()),
94        ));
95        elem.push_attribute(("xmlns", "http://www.w3.org/2001/10/synthesis"));
96        elem.push_attribute(("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"));
97        Ok(self.writer.write_event(Event::Start(elem))?)
98    }
99
100    /// Ends an SSML <speak> tag. For AWS Polly this should be the root tag, and you
101    /// should only close it when you are done.
102    ///
103    /// # Examples
104    ///
105    /// Rust Code:
106    ///
107    /// ```rust
108    /// use text_to_polly_ssml::xml_writer::XmlWriter;
109    /// let mut new_xml_writer = XmlWriter::new();
110    /// assert!(new_xml_writer.is_ok());
111    /// let end_speak_result = new_xml_writer.unwrap().end_ssml_speak();
112    /// assert!(end_speak_result.is_ok());
113    /// ```
114    ///
115    /// Generated SSML:
116    ///
117    /// ```text
118    /// <?xml version="1.0"?>
119    /// </speak>
120    /// ```
121    pub fn end_ssml_speak(&mut self) -> Result<()> {
122        Ok(self
123            .writer
124            .write_event(Event::End(BytesEnd::borrowed(b"speak")))?)
125    }
126
127    /// Creates an SSML <break> tag. AWS Polly follows the W3C SSMLv1.1 standard for
128    /// this tag.
129    ///
130    /// You can find the SSML <break> tag documented in the W3C's guide:
131    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/).
132    /// Although  you can find the specific implementation details on AWS's site:
133    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#break-tag).
134    ///
135    /// According to the W3C 1.1 Standard both the strength, and time are optional.
136    /// Though both can be used in combination.
137    ///
138    /// # Examples
139    ///
140    /// Rust Code:
141    ///
142    /// ```rust
143    /// use text_to_polly_ssml::xml_writer::XmlWriter;
144    /// let mut new_xml_writer = XmlWriter::new();
145    /// assert!(new_xml_writer.is_ok());
146    /// let mut xml_writer = new_xml_writer.unwrap();
147    /// let result = xml_writer.ssml_break(None, None);
148    /// assert!(result.is_ok());
149    /// ```
150    ///
151    /// Generated SSML:
152    ///
153    /// ```text
154    /// <?xml version="1.0"?>
155    /// <break />
156    /// ```
157    ///
158    /// ---
159    ///
160    /// Rust Code:
161    ///
162    /// ```rust
163    /// use text_to_polly_ssml::xml_writer::XmlWriter;
164    /// use text_to_polly_ssml::ssml_constants::{BreakStrength, BreakTime};
165    /// let mut new_xml_writer = XmlWriter::new();
166    /// assert!(new_xml_writer.is_ok());
167    /// let mut xml_writer = new_xml_writer.unwrap();
168    /// let result = xml_writer.ssml_break(Some(BreakStrength::XStrong), Some(BreakTime::new(10, true)));
169    /// assert!(result.is_ok());
170    /// ```
171    ///
172    /// Generated SSML:
173    ///
174    /// ```text
175    /// <?xml version="1.0"?>
176    /// <break strength="x-strong" time="10s" />
177    /// ```
178    pub fn ssml_break(
179        &mut self,
180        strength: Option<BreakStrength>,
181        time: Option<BreakTime>,
182    ) -> Result<()> {
183        let mut elem = BytesStart::owned(b"break".to_vec(), "break".len());
184
185        if strength.is_some() {
186            elem.push_attribute(("strength", &*format!("{}", strength.unwrap())));
187        }
188        if time.is_some() {
189            elem.push_attribute(("time", &*format!("{}", time.unwrap())));
190        }
191
192        Ok(self.writer.write_event(Event::Empty(elem))?)
193    }
194
195    /// Starts an SSML Lang tag. The Lang tag is useful for telling say
196    /// someone speaking in english that they're about to speak a french word. You can keep
197    /// the overall text english, but have a mix of french words in there. Although AWS polly
198    /// only documents support the `xml:lang` attribute, we also pass in `onlangfailure`
199    /// which is documented inside the W3C SSML 1.1 standard which can be found:
200    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_lang).
201    ///
202    /// You can find the AWS Documentation that mentions the lang tag:
203    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#lang-tag).
204    ///
205    /// # Examples
206    ///
207    /// Rust Code:
208    ///
209    /// ```rust
210    /// use text_to_polly_ssml::xml_writer::XmlWriter;
211    /// let mut new_xml_writer = XmlWriter::new();
212    /// assert!(new_xml_writer.is_ok());
213    /// let start_lang_result = new_xml_writer.unwrap().start_ssml_lang("fr-FR".to_owned(), None);
214    /// assert!(start_lang_result.is_ok());
215    /// ```
216    ///
217    /// Generated SSML:
218    ///
219    /// ```text
220    /// <?xml version="1.0"?>
221    /// <lang xml:lang="fr-FR" onlangfailure="processorchoice">
222    /// ```
223    ///
224    /// ---
225    ///
226    /// Rust Code:
227    /// ```rust
228    /// use text_to_polly_ssml::xml_writer::XmlWriter;
229    /// let mut new_xml_writer = XmlWriter::new();
230    /// assert!(new_xml_writer.is_ok());
231    /// let start_lang_result = new_xml_writer.unwrap().start_ssml_lang("fr-FR".to_owned(),
232    ///   Some("changevoice".to_owned()));
233    /// assert!(start_lang_result.is_ok());
234    /// ```
235    ///
236    /// Generated SSML:
237    ///
238    /// ```text
239    /// <?xml version="1.0"?>
240    /// <lang xml:lang="fr-FR" onlangfailure="changevoice">
241    /// ```
242    pub fn start_ssml_lang(&mut self, lang: String, onlangfailure: Option<String>) -> Result<()> {
243        let mut elem = BytesStart::owned(b"lang".to_vec(), "lang".len());
244        elem.push_attribute(("xml:lang", &*lang));
245        elem.push_attribute((
246            "onlangfailure",
247            &*onlangfailure.unwrap_or("processorchoice".to_owned()),
248        ));
249        Ok(self.writer.write_event(Event::Start(elem))?)
250    }
251
252    /// Ends an SSML <lang> tag.
253    ///
254    /// # Examples
255    ///
256    /// Rust Code:
257    ///
258    /// ```rust
259    /// use text_to_polly_ssml::xml_writer::XmlWriter;
260    /// let mut new_xml_writer = XmlWriter::new();
261    /// assert!(new_xml_writer.is_ok());
262    /// let end_lang_result = new_xml_writer.unwrap().end_ssml_lang();
263    /// assert!(end_lang_result.is_ok());
264    /// ```
265    ///
266    /// Generated SSML:
267    ///
268    /// ```text
269    /// <?xml version="1.0"?>
270    /// </lang>
271    /// ```
272    pub fn end_ssml_lang(&mut self) -> Result<()> {
273        Ok(self
274            .writer
275            .write_event(Event::End(BytesEnd::borrowed(b"lang")))?)
276    }
277
278    /// Starts an SSML Mark tag. Although this will make no difference in the voice
279    /// of the text, this will place a marker inside the SSML Metadata returned from Polly.
280    /// This can be useful if you want to perform some sort of actions on certain words.
281    /// AWS Polly follows the W3C SSML v1.1 Spec here, and documentation can be found:
282    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_mark).
283    ///
284    /// You can find the AWS Documentation that mentions the mark tag:
285    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#custom-tag).
286    ///
287    /// # Examples
288    ///
289    /// Rust Code:
290    ///
291    /// ```rust
292    /// use text_to_polly_ssml::xml_writer::XmlWriter;
293    /// let mut new_xml_writer = XmlWriter::new();
294    /// assert!(new_xml_writer.is_ok());
295    /// let start_mark_result = new_xml_writer.unwrap().start_ssml_mark("animal".to_owned());
296    /// assert!(start_mark_result.is_ok());
297    /// ```
298    ///
299    /// Generated SSML:
300    ///
301    /// ```text
302    /// <?xml version="1.0"?>
303    /// Mmark name="animal">
304    /// ```
305    pub fn start_ssml_mark(&mut self, name: String) -> Result<()> {
306        let mut elem = BytesStart::owned(b"mark".to_vec(), "mark".len());
307        elem.push_attribute(("name", &*name));
308        Ok(self.writer.write_event(Event::Start(elem))?)
309    }
310
311    /// Ends an SSML <mark> tag.
312    ///
313    /// # Examples
314    ///
315    /// Rust Code:
316    ///
317    /// ```rust
318    /// use text_to_polly_ssml::xml_writer::XmlWriter;
319    /// let mut new_xml_writer = XmlWriter::new();
320    /// assert!(new_xml_writer.is_ok());
321    /// let end_mark_result = new_xml_writer.unwrap().end_ssml_mark();
322    /// assert!(end_mark_result.is_ok());
323    /// ```
324    ///
325    /// Generated SSML:
326    ///
327    /// ```text
328    /// <?xml version="1.0"?>
329    /// </mark>
330    /// ```
331    pub fn end_ssml_mark(&mut self) -> Result<()> {
332        Ok(self
333            .writer
334            .write_event(Event::End(BytesEnd::borrowed(b"mark")))?)
335    }
336
337    /// Starts an SSML Paragraph Tag. The Paragraph Tag is useful for breaking
338    /// up multiple paragraphs of text. AWS Polly follows the W3C SSML v1.1 Standard Here.
339    /// As such the documentation for the paragraph tag can be found:
340    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_paragraph).
341    ///
342    /// You can find the AWS Documentation that mentions the paragraph tag:
343    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#p-tag).
344    ///
345    /// # Examples
346    ///
347    /// Rust Code:
348    ///
349    /// ```rust
350    /// use text_to_polly_ssml::xml_writer::XmlWriter;
351    /// let mut new_xml_writer = XmlWriter::new();
352    /// assert!(new_xml_writer.is_ok());
353    /// let start_p_result = new_xml_writer.unwrap().start_ssml_paragraph();
354    /// assert!(start_p_result.is_ok());
355    /// ```
356    ///
357    /// Generated SSML:
358    ///
359    /// ```text
360    /// <?xml version="1.0"?>
361    /// <p>
362    /// ```
363    pub fn start_ssml_paragraph(&mut self) -> Result<()> {
364        Ok(self
365            .writer
366            .write_event(Event::Start(BytesStart::owned(b"p".to_vec(), "p".len())))?)
367    }
368
369    /// Ends an SSML <p> tag.
370    ///
371    /// # Examples
372    ///
373    /// Rust Code:
374    ///
375    /// ```rust
376    /// use text_to_polly_ssml::xml_writer::XmlWriter;
377    /// let mut new_xml_writer = XmlWriter::new();
378    /// assert!(new_xml_writer.is_ok());
379    /// let end_p_result = new_xml_writer.unwrap().end_ssml_paragraph();
380    /// assert!(end_p_result.is_ok());
381    /// ```
382    ///
383    /// Generated SSML:
384    ///
385    /// ```text
386    /// <?xml version="1.0"?>
387    /// </p>
388    /// ```
389    pub fn end_ssml_paragraph(&mut self) -> Result<()> {
390        Ok(self
391            .writer
392            .write_event(Event::End(BytesEnd::borrowed(b"p")))?)
393    }
394
395    /// Starts an SSML Phoneme Tag. The Phoneme Tag is useful for custom pronunciation for words.
396    /// The Phoneme Tag should really only be used on a per word/short phrase basis. You don't
397    /// want to use a phoneme tag for an entire paragraph of text. The Phoneme Tag in polly
398    /// has two required attributes both "ph", and "alphabet". Which deviates from the W3C Standard,
399    /// which says only "ph" is required. However since Polly implements close to perfect the W3C
400    /// SSML v1.1 Standard here you should still probably read their documentation on the tag:
401    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_phoneme).
402    ///
403    /// You can find the AWS Documentation that mentions the phoneme tag:
404    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#phoneme-tag).
405    ///
406    /// # Examples
407    ///
408    /// Rust Code:
409    ///
410    /// ```rust
411    /// use text_to_polly_ssml::xml_writer::XmlWriter;
412    /// use text_to_polly_ssml::ssml_constants::PhonemeAlphabet;
413    /// let mut new_xml_writer = XmlWriter::new();
414    /// assert!(new_xml_writer.is_ok());
415    /// let start_phoneme_result = new_xml_writer.unwrap().start_ssml_phoneme(PhonemeAlphabet::Ipa,
416    ///  "d͡ʒt͡ʃΘɚoʊɛ".to_owned());
417    /// assert!(start_phoneme_result.is_ok());
418    /// ```
419    ///
420    /// Generated SSML:
421    ///
422    /// ```text
423    /// <?xml version="1.0"?>
424    /// <phoneme alphabet="ipa" ph="d͡ʒt͡ʃΘɚoʊɛ">
425    /// ```
426    pub fn start_ssml_phoneme(&mut self, alphabet: PhonemeAlphabet, ph: String) -> Result<()> {
427        let mut elem = BytesStart::owned(b"phoneme".to_vec(), "phoneme".len());
428        elem.push_attribute(("alphabet", &*format!("{}", alphabet)));
429        elem.push_attribute(("ph", &*ph));
430        Ok(self.writer.write_event(Event::Start(elem))?)
431    }
432
433    /// Ends an SSML <phoneme> tag.
434    ///
435    /// # Examples
436    ///
437    /// Rust Code:
438    ///
439    /// ```rust
440    /// use text_to_polly_ssml::xml_writer::XmlWriter;
441    /// let mut new_xml_writer = XmlWriter::new();
442    /// assert!(new_xml_writer.is_ok());
443    /// let end_phoneme_result = new_xml_writer.unwrap().end_ssml_phoneme();
444    /// assert!(end_phoneme_result.is_ok());
445    /// ```
446    ///
447    /// Generated SSML:
448    ///
449    /// ```text
450    /// <?xml version="1.0"?>
451    /// </phoneme>
452    /// ```
453    pub fn end_ssml_phoneme(&mut self) -> Result<()> {
454        Ok(self
455            .writer
456            .write_event(Event::End(BytesEnd::borrowed(b"phoneme")))?)
457    }
458
459    /// Starts an SSML Prosody Tag. The prosody tag seems to be the one that derives the most
460    /// from the SSML Specification. Which in some instances is fine because it makes for easier
461    /// reading (e.g. +20% pitch), but in other places is kind of sad we can't do that. (e.g.
462    /// things like duration). As such I'll only link to the AWS documentation.
463    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#prosody-tag).
464    ///
465    /// # Examples
466    ///
467    /// Rust Code:
468    ///
469    /// ```rust
470    /// use text_to_polly_ssml::xml_writer::XmlWriter;
471    /// let mut new_xml_writer = XmlWriter::new();
472    /// assert!(new_xml_writer.is_ok());
473    /// let start_prosody_result = new_xml_writer.unwrap().start_ssml_prosody(Some("+5db".to_owned()), None, None);
474    /// assert!(start_prosody_result.is_ok());
475    /// ```
476    ///
477    /// Generated SSML:
478    ///
479    /// ```text
480    /// <?xml version="1.0"?>
481    /// <prosody volume="+6db">
482    /// ```
483    ///
484    /// ---
485    ///
486    /// Rust Code:
487    ///
488    /// ```rust
489    /// use text_to_polly_ssml::xml_writer::XmlWriter;
490    /// use text_to_polly_ssml::ssml_constants::ProsodyRate;
491    /// let mut new_xml_writer = XmlWriter::new();
492    /// assert!(new_xml_writer.is_ok());
493    /// let start_prosody_result = new_xml_writer.unwrap()
494    ///   .start_ssml_prosody(Some("+6dB".to_owned()), Some(ProsodyRate::XFast),
495    ///    Some("+100%".to_owned()));
496    /// assert!(start_prosody_result.is_ok());
497    /// ```
498    ///
499    /// Generated SSML:
500    ///
501    /// ```text
502    /// <?xml version="1.0"?>
503    /// <prosody volume="+6db" rate="x-fast" pitch="+100%">
504    /// ```
505    pub fn start_ssml_prosody(
506        &mut self,
507        volume: Option<String>,
508        rate: Option<ProsodyRate>,
509        pitch: Option<String>,
510    ) -> Result<()> {
511        let mut elem = BytesStart::owned(b"prosody".to_vec(), "prosody".len());
512        if volume.is_none() && rate.is_none() && pitch.is_none() {
513            return Err(eyre!("Prosody Tag was supplied no values."));
514        }
515        if volume.is_some() {
516            elem.push_attribute(("volume", &*volume.unwrap()));
517        }
518        if rate.is_some() {
519            elem.push_attribute(("rate", &*format!("{}", rate.unwrap())));
520        }
521        if pitch.is_some() {
522            elem.push_attribute(("pitch", &*pitch.unwrap()));
523        }
524        Ok(self.writer.write_event(Event::Start(elem))?)
525    }
526
527    /// Ends an SSML <prosody> tag.
528    ///
529    /// # Examples
530    ///
531    /// Rust Code:
532    ///
533    /// ```rust
534    /// use text_to_polly_ssml::xml_writer::XmlWriter;
535    /// let mut new_xml_writer = XmlWriter::new();
536    /// assert!(new_xml_writer.is_ok());
537    /// let end_prosody_result = new_xml_writer.unwrap().end_ssml_prosody();
538    /// assert!(end_prosody_result.is_ok());
539    /// ```
540    ///
541    /// Generated SSML:
542    ///
543    /// ```text
544    /// <?xml version="1.0"?>
545    /// </prosody>
546    /// ```
547    pub fn end_ssml_prosody(&mut self) -> Result<()> {
548        Ok(self
549            .writer
550            .write_event(Event::End(BytesEnd::borrowed(b"prosody")))?)
551    }
552
553    /// Starts an SSML Sentence Tag. The Sentence Tag is useful for breaking
554    /// up multiple sentences of text. AWS Polly follows the W3C SSML v1.1 Standard Here.
555    /// As such the documentation for the sentence tag can be found:
556    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_sentence).
557    ///
558    /// You can find the AWS Documentation that mentions the sentence tag:
559    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#s-tag).
560    ///
561    /// # Examples
562    ///
563    /// Rust Code:
564    ///
565    /// ```rust
566    /// use text_to_polly_ssml::xml_writer::XmlWriter;
567    /// let mut new_xml_writer = XmlWriter::new();
568    /// assert!(new_xml_writer.is_ok());
569    /// let start_s_result = new_xml_writer.unwrap().start_ssml_sentence();
570    /// assert!(start_s_result.is_ok());
571    /// ```
572    ///
573    /// Generated SSML:
574    ///
575    /// ```text
576    /// <?xml version="1.0"?>
577    /// <s>
578    /// ```
579    pub fn start_ssml_sentence(&mut self) -> Result<()> {
580        Ok(self
581            .writer
582            .write_event(Event::Start(BytesStart::owned(b"s".to_vec(), "s".len())))?)
583    }
584
585    /// Ends an SSML <s> tag.
586    ///
587    /// # Examples
588    ///
589    /// Rust Code:
590    ///
591    /// ```rust
592    /// use text_to_polly_ssml::xml_writer::XmlWriter;
593    /// let mut new_xml_writer = XmlWriter::new();
594    /// assert!(new_xml_writer.is_ok());
595    /// let end_s_result = new_xml_writer.unwrap().end_ssml_sentence();
596    /// assert!(end_s_result.is_ok());
597    /// ```
598    ///
599    /// Generated SSML:
600    ///
601    /// ```text
602    /// <?xml version="1.0"?>
603    /// </s>
604    /// ```
605    pub fn end_ssml_sentence(&mut self) -> Result<()> {
606        Ok(self
607            .writer
608            .write_event(Event::End(BytesEnd::borrowed(b"s")))?)
609    }
610
611    /// Starts an SSML say-as Tag. The say-as tag is used for determing how a body of text
612    /// should be interpreted, for example a phone number, or if you want something spelled
613    /// out letter by letter. However AWS polly only supports the `interpret-as` attribute
614    /// which is required, and does not support the `format`, and `detail` attributes.
615    /// However for posterity you can read the W3C SSML v1.1 Spec:
616    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_say-as).
617    /// It should be noted the parameter for interpret-as is kept dynamic, since in the
618    /// spec it says this list ***should*** change rapidly.
619    ///
620    /// You can find the AWS Documentation that mentions the say-as tag:
621    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#say-as-tag).
622    ///
623    /// # Examples
624    ///
625    /// Rust Code:
626    ///
627    /// ```rust
628    /// use text_to_polly_ssml::xml_writer::XmlWriter;
629    /// let mut new_xml_writer = XmlWriter::new();
630    /// assert!(new_xml_writer.is_ok());
631    /// let start_say_as_result = new_xml_writer.unwrap().start_ssml_say_as("character".to_owned());
632    /// assert!(start_say_as_result.is_ok());
633    /// ```
634    ///
635    /// Generated SSML:
636    ///
637    /// ```text
638    /// <?xml version="1.0"?>
639    /// <say-as interpret-as="character">
640    /// ```
641    pub fn start_ssml_say_as(&mut self, interpret_as: String) -> Result<()> {
642        let mut elem = BytesStart::owned(b"say-as".to_vec(), "say-as".len());
643        elem.push_attribute(("interpret-as", &*interpret_as));
644        Ok(self.writer.write_event(Event::Start(elem))?)
645    }
646
647    /// Ends an SSML <say-as> tag.
648    ///
649    /// # Examples
650    ///
651    /// Rust Code:
652    ///
653    /// ```rust
654    /// use text_to_polly_ssml::xml_writer::XmlWriter;
655    /// let mut new_xml_writer = XmlWriter::new();
656    /// assert!(new_xml_writer.is_ok());
657    /// let end_say_as_result = new_xml_writer.unwrap().end_ssml_say_as();
658    /// assert!(end_say_as_result.is_ok());
659    /// ```
660    ///
661    /// Generated SSML:
662    ///
663    /// ```text
664    /// <?xml version="1.0"?>
665    /// </say-as>
666    /// ```
667    pub fn end_ssml_say_as(&mut self) -> Result<()> {
668        Ok(self
669            .writer
670            .write_event(Event::End(BytesEnd::borrowed(b"say-as")))?)
671    }
672
673    /// Starts an SSML sub Tag. The sub tag is used for a substitution of a word.
674    /// For example in elememtal symbols you may want to show the elememtal symbol, but have
675    /// the engine say the actual element name. AWS polly follows the W3C SSML v1.1 Spec:
676    /// [HERE](https://www.w3.org/TR/2010/REC-speech-synthesis11-20100907/#edef_sub).
677    ///
678    /// You can find the AWS Documentation that mentions the sub tag:
679    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#sub-tag).
680    ///
681    /// # Examples
682    ///
683    /// Rust Code:
684    ///
685    /// ```rust
686    /// use text_to_polly_ssml::xml_writer::XmlWriter;
687    /// let mut new_xml_writer = XmlWriter::new();
688    /// assert!(new_xml_writer.is_ok());
689    /// let start_sub_result = new_xml_writer.unwrap().start_ssml_sub("mercury".to_owned());
690    /// assert!(start_sub_result.is_ok());
691    /// ```
692    ///
693    /// Generated SSML:
694    ///
695    /// ```text
696    /// <?xml version="1.0"?>
697    /// <sub alias="mercury">
698    /// ```
699    pub fn start_ssml_sub(&mut self, alias: String) -> Result<()> {
700        let mut elem = BytesStart::owned(b"sub".to_vec(), "sub".len());
701        elem.push_attribute(("alias", &*alias));
702        Ok(self.writer.write_event(Event::Start(elem))?)
703    }
704
705    /// Ends an SSML <sub> tag.
706    ///
707    /// # Examples
708    ///
709    /// Rust Code:
710    ///
711    /// ```rust
712    /// use text_to_polly_ssml::xml_writer::XmlWriter;
713    /// let mut new_xml_writer = XmlWriter::new();
714    /// assert!(new_xml_writer.is_ok());
715    /// let end_sub_result = new_xml_writer.unwrap().end_ssml_sub();
716    /// assert!(end_sub_result.is_ok());
717    /// ```
718    ///
719    /// Generated SSML:
720    ///
721    /// ```text
722    /// <?xml version="1.0"?>
723    /// </sub>
724    /// ```
725    pub fn end_ssml_sub(&mut self) -> Result<()> {
726        Ok(self
727            .writer
728            .write_event(Event::End(BytesEnd::borrowed(b"sub")))?)
729    }
730
731    /// Starts an SSML Word/Token tag. The Word/Token tag for AWS Polly also deviates pretty
732    /// far from the W3C Spec. So here like a few tags who shall not be named I will also
733    /// only ilnk to the AWS Documentation for this tag. Which can be found:
734    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html#w-tag).
735    ///
736    /// # Examples
737    ///
738    /// Rust Code:
739    ///
740    /// ```rust
741    /// use text_to_polly_ssml::xml_writer::XmlWriter;
742    /// use text_to_polly_ssml::ssml_constants::WordRole;
743    /// let mut new_xml_writer = XmlWriter::new();
744    /// assert!(new_xml_writer.is_ok());
745    /// let start_w_result = new_xml_writer.unwrap().start_ssml_w(WordRole::Verb);
746    /// assert!(start_w_result.is_ok());
747    /// ```
748    ///
749    /// Generated SSML:
750    ///
751    /// ```text
752    /// <?xml version="1.0"?>
753    /// <w role="amazon:VB">
754    /// ```
755    pub fn start_ssml_w(&mut self, role: WordRole) -> Result<()> {
756        let mut elem = BytesStart::owned(b"w".to_vec(), "w".len());
757        elem.push_attribute(("role", &*format!("{}", role)));
758        Ok(self.writer.write_event(Event::Start(elem))?)
759    }
760
761    /// Ends an SSML <w> tag.
762    ///
763    /// # Examples
764    ///
765    /// Rust Code:
766    ///
767    /// ```rust
768    /// use text_to_polly_ssml::xml_writer::XmlWriter;
769    /// let mut new_xml_writer = XmlWriter::new();
770    /// assert!(new_xml_writer.is_ok());
771    /// let end_w_result = new_xml_writer.unwrap().end_ssml_w();
772    /// assert!(end_w_result.is_ok());
773    /// ```
774    ///
775    /// Generated SSML:
776    ///
777    /// ```text
778    /// <?xml version="1.0"?>
779    /// </w>
780    /// ```
781    pub fn end_ssml_w(&mut self) -> Result<()> {
782        Ok(self
783            .writer
784            .write_event(Event::End(BytesEnd::borrowed(b"w")))?)
785    }
786
787    /// Starts an SSML amazon domain tag. These tags are unique to AWS Polly. As such
788    /// the only place they are documented is inside the AWS Docs themsleves which are:
789    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html).
790    ///
791    /// # Examples
792    ///
793    /// Rust Code:
794    ///
795    /// ```rust
796    /// use text_to_polly_ssml::xml_writer::XmlWriter;
797    /// use text_to_polly_ssml::ssml_constants::AmazonDomainNames;
798    /// let mut new_xml_writer = XmlWriter::new();
799    /// assert!(new_xml_writer.is_ok());
800    /// let start_amazon_effect_result = new_xml_writer.unwrap()
801    ///   .start_ssml_amazon_domain(AmazonDomainNames::News);
802    /// assert!(start_amazon_effect_result.is_ok());
803    /// ```
804    ///
805    /// Generated SSML:
806    ///
807    /// ```text
808    /// <?xml version="1.0"?>
809    /// <amazon:domain name="news">
810    /// ```
811    pub fn start_ssml_amazon_domain(&mut self, name: AmazonDomainNames) -> Result<()> {
812        let mut elem = BytesStart::owned(b"amazon:domain".to_vec(), "amazon:domain".len());
813        elem.push_attribute(("name", &*format!("{}", name)));
814        Ok(self.writer.write_event(Event::Start(elem))?)
815    }
816
817    /// Ends an SSML <amazon:domain> tag.
818    ///
819    /// # Examples
820    ///
821    /// Rust Code:
822    ///
823    /// ```rust
824    /// use text_to_polly_ssml::xml_writer::XmlWriter;
825    /// let mut new_xml_writer = XmlWriter::new();
826    /// assert!(new_xml_writer.is_ok());
827    /// let end_amazon_effect_result = new_xml_writer.unwrap().end_ssml_amazon_domain();
828    /// assert!(end_amazon_effect_result.is_ok());
829    /// ```
830    ///
831    /// Generated SSML:
832    ///
833    /// ```text
834    /// <?xml version="1.0"?>
835    /// </amazon:domain>
836    /// ```
837    pub fn end_ssml_amazon_domain(&mut self) -> Result<()> {
838        Ok(self
839            .writer
840            .write_event(Event::End(BytesEnd::borrowed(b"amazon:domain")))?)
841    }
842
843    /// Starts an SSML amazon effect tag. These tags are unique to AWS Polly. As such
844    /// the only place they are documented is inside the AWS Docs themsleves which are:
845    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html).
846    ///
847    /// # Examples
848    ///
849    /// Rust Code:
850    ///
851    /// ```rust
852    /// use text_to_polly_ssml::xml_writer::XmlWriter;
853    /// use text_to_polly_ssml::ssml_constants::AmazonEffect;
854    /// let mut new_xml_writer = XmlWriter::new();
855    /// assert!(new_xml_writer.is_ok());
856    /// let start_amazon_effect_result = new_xml_writer.unwrap()
857    ///   .start_ssml_amazon_effect(AmazonEffect::Whispered);
858    /// assert!(start_amazon_effect_result.is_ok());
859    /// ```
860    ///
861    /// Generated SSML:
862    ///
863    /// ```text
864    /// <?xml version="1.0"?>
865    /// <amazon:effect name="whispered">
866    /// ```
867    pub fn start_ssml_amazon_effect(&mut self, name: AmazonEffect) -> Result<()> {
868        let mut elem = BytesStart::owned(b"amazon:effect".to_vec(), "amazon:effect".len());
869        elem.push_attribute(("name", &*format!("{}", name)));
870        Ok(self.writer.write_event(Event::Start(elem))?)
871    }
872
873    /// Ends an SSML <amazon:effect> tag.
874    ///
875    /// # Examples
876    ///
877    /// Rust Code:
878    ///
879    /// ```rust
880    /// use text_to_polly_ssml::xml_writer::XmlWriter;
881    /// let mut new_xml_writer = XmlWriter::new();
882    /// assert!(new_xml_writer.is_ok());
883    /// let end_amazon_effect_result = new_xml_writer.unwrap().end_ssml_amazon_effect();
884    /// assert!(end_amazon_effect_result.is_ok());
885    /// ```
886    ///
887    /// Generated SSML:
888    ///
889    /// ```text
890    /// <?xml version="1.0"?>
891    /// </amazon:effect>
892    /// ```
893    pub fn end_ssml_amazon_effect(&mut self) -> Result<()> {
894        Ok(self
895            .writer
896            .write_event(Event::End(BytesEnd::borrowed(b"amazon:effect")))?)
897    }
898
899    /// Starts an SSML vocal tract tag. These tags are unique to AWS Polly. As such
900    /// the only place they are documented is inside the AWS Docs themsleves which are:
901    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html).
902    ///
903    /// # Examples
904    ///
905    /// Rust Code:
906    ///
907    /// ```rust
908    /// use text_to_polly_ssml::xml_writer::XmlWriter;
909    /// let mut new_xml_writer = XmlWriter::new();
910    /// assert!(new_xml_writer.is_ok());
911    /// let start_amazon_effect_result = new_xml_writer.unwrap()
912    ///   .start_ssml_vocal_tract_length("+10%".to_owned());
913    /// assert!(start_amazon_effect_result.is_ok());
914    /// ```
915    ///
916    /// Generated SSML:
917    ///
918    /// ```text
919    /// <?xml version="1.0"?>
920    /// <amazon:effect vocal-tract-length="+10%">
921    /// ```
922    pub fn start_ssml_vocal_tract_length(&mut self, factor: String) -> Result<()> {
923        let mut elem = BytesStart::owned(b"amazon:effect".to_vec(), "amazon:effect".len());
924        elem.push_attribute(("vocal-tract-length", &*format!("{}", factor)));
925        Ok(self.writer.write_event(Event::Start(elem))?)
926    }
927
928    /// Starts an SSML phonation tag. These tags are unique to AWS Polly. As such
929    /// the only place they are documented is inside the AWS Docs themsleves which are:
930    /// [HERE](http://docs.aws.amazon.com/polly/latest/dg/supported-ssml.html).
931    ///
932    /// # Examples
933    ///
934    /// Rust Code:
935    ///
936    /// ```rust
937    /// use text_to_polly_ssml::xml_writer::XmlWriter;
938    /// use text_to_polly_ssml::ssml_constants::PhonationVolume;
939    /// let mut new_xml_writer = XmlWriter::new();
940    /// assert!(new_xml_writer.is_ok());
941    /// let start_amazon_phonation_result = new_xml_writer.unwrap()
942    ///   .start_ssml_phonation(PhonationVolume::Soft);
943    /// assert!(start_amazon_phonation_result.is_ok());
944    /// ```
945    ///
946    /// Generated SSML:
947    ///
948    /// ```text
949    /// <?xml version="1.0"?>
950    /// <amazon:effect phonation="soft">
951    /// ```
952    pub fn start_ssml_phonation(&mut self, volume: PhonationVolume) -> Result<()> {
953        let mut elem = BytesStart::owned(b"amazon:effect".to_vec(), "amazon:effect".len());
954        elem.push_attribute(("phonation", &*format!("{}", volume)));
955        Ok(self.writer.write_event(Event::Start(elem))?)
956    }
957
958    /// Starts an SSML <amazon:auto-breaths> tag.
959    ///
960    /// # Examples
961    ///
962    /// Rust Code:
963    ///
964    /// ```rust
965    /// use text_to_polly_ssml::xml_writer::XmlWriter;
966    /// use text_to_polly_ssml::ssml_constants::*;
967    /// let mut new_xml_writer = XmlWriter::new();
968    /// assert!(new_xml_writer.is_ok());
969    /// let start_amazon_auto_breaths_result = new_xml_writer.unwrap().start_ssml_auto_breaths(
970    ///   BreathVolumes::Def,
971    ///   AutoBreathFrequency::Def,
972    ///   BreathDuration::Def,
973    /// );
974    /// assert!(start_amazon_auto_breaths_result.is_ok());
975    /// ```
976    ///
977    /// Generated SSML:
978    ///
979    /// ```text
980    /// <?xml version="1.0"?>
981    /// <amazon:auto-breaths volume="default" frequency="default" duration="default">
982    /// ```
983    pub fn start_ssml_auto_breaths(
984        &mut self,
985        volume: BreathVolumes,
986        frequency: AutoBreathFrequency,
987        duration: BreathDuration,
988    ) -> Result<()> {
989        let mut elem =
990            BytesStart::owned(b"amazon:auto-breaths".to_vec(), "amazon:auto-breaths".len());
991        elem.push_attribute(("volume", &*format!("{}", volume)));
992        elem.push_attribute(("frequency", &*format!("{}", frequency)));
993        elem.push_attribute(("duration", &*format!("{}", duration)));
994        Ok(self.writer.write_event(Event::Start(elem))?)
995    }
996
997    /// Ends an SSML <amazon:auto-breaths> tag.
998    ///
999    /// # Examples
1000    ///
1001    /// Rust Code:
1002    ///
1003    /// ```rust
1004    /// use text_to_polly_ssml::xml_writer::XmlWriter;
1005    /// let mut new_xml_writer = XmlWriter::new();
1006    /// assert!(new_xml_writer.is_ok());
1007    /// let end_amazon_auto_breaths_result = new_xml_writer.unwrap().end_ssml_amazon_auto_breaths();
1008    /// assert!(end_amazon_auto_breaths_result.is_ok());
1009    /// ```
1010    ///
1011    /// Generated SSML:
1012    ///
1013    /// ```text
1014    /// <?xml version="1.0"?>
1015    /// </amazon:auto-breaths>
1016    /// ```
1017    pub fn end_ssml_amazon_auto_breaths(&mut self) -> Result<()> {
1018        Ok(self
1019            .writer
1020            .write_event(Event::End(BytesEnd::borrowed(b"amazon:auto-breaths")))?)
1021    }
1022
1023    /// Starts an SSML <amazon:breath> tag.
1024    ///
1025    /// # Examples
1026    ///
1027    /// Rust Code:
1028    ///
1029    /// ```rust
1030    /// use text_to_polly_ssml::xml_writer::XmlWriter;
1031    /// use text_to_polly_ssml::ssml_constants::*;
1032    /// let mut new_xml_writer = XmlWriter::new();
1033    /// assert!(new_xml_writer.is_ok());
1034    /// let start_amazon_breath_result = new_xml_writer.unwrap().write_amazon_breath(
1035    ///   BreathVolumes::Def,
1036    ///   BreathDuration::Def,
1037    /// );
1038    /// assert!(start_amazon_breath_result.is_ok());
1039    /// ```
1040    ///
1041    /// Generated SSML:
1042    ///
1043    /// ```text
1044    /// <?xml version="1.0"?>
1045    /// <amazon:breath volume="default" duration="default" />
1046    /// ```
1047    pub fn write_amazon_breath(
1048        &mut self,
1049        volume: BreathVolumes,
1050        duration: BreathDuration,
1051    ) -> Result<()> {
1052        let mut elem = BytesStart::owned(b"amazon:breath".to_vec(), "amazon:breath".len());
1053        elem.push_attribute(("volume", &*format!("{}", volume)));
1054        elem.push_attribute(("duration", &*format!("{}", duration)));
1055
1056        Ok(self.writer.write_event(Event::Empty(elem))?)
1057    }
1058
1059    /// Writes some raw text to the XML Document. Should only be used inbetween <p> tags.
1060    pub fn write_text(&mut self, text: &str) -> Result<()> {
1061        Ok(self
1062            .writer
1063            .write_event(Event::Text(BytesText::from_plain_str(text)))?)
1064    }
1065
1066    /// Renders the XML document in it's current state. This expects the document
1067    /// to be completely valid UTF-8, and will do no closing of tags for you.
1068    pub fn render(&mut self) -> String {
1069        String::from_utf8(self.writer.clone().into_inner().into_inner())
1070            .expect("SSML is not valid UTF-8!")
1071    }
1072}