SSmlParser.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Speech / Src / Internal / Synthesis / SSmlParser.cs / 1 / SSmlParser.cs

                            //  
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//
// Description: 
//      The SSml parser parse takes as input an xml string and produces
//      SSML text fragments. 
// 
//      The state information is stored in a structure that is passed by
//      value to the parsing methods for each element. Each of them can 
//      change the state information which will be passed again by value
//      to the children elements. On return to the parsing function,
//      the original values
// 
//      The original ssml string is temporarily stripped out of the CR/LF to
//      have the XML reader to return the position as an indice from the 
//      beginning of the string. This is allowing to have the position 
//      information in the stream that is used in the progress event.
// 
// History:
//		2/1/2005	jeanfp		Created
//----------------------------------------------------------------------------
 
using System;
using System.Collections.Generic; 
using System.Globalization; 
using System.IO;
using System.Net; 
using System.Speech.Synthesis;
using System.Speech.Synthesis.TtsEngine;
using System.Text;
using System.Xml; 

#pragma warning disable 1634, 1691 // Allows suppression of certain PreSharp messages. 
 
namespace System.Speech.Internal.Synthesis
{ 
    internal static class SsmlParser
    {
        //*******************************************************************
        // 
        // Internal Methods
        // 
        //******************************************************************* 

        #region Internal Methods 

        /// 
        /// Parse an SSML stream and build a set of SSML Text Fragments
        ///  
        /// 
        ///  
        ///  
        internal static void Parse (string ssml, ISsmlParser engine, object voice)
        { 
            // Remove the CR and LF
            string ssmlNoCrLf = ssml.Replace ('\n', ' ');
            ssmlNoCrLf = ssmlNoCrLf.Replace ('\r', ' ');
            XmlTextReader reader = new XmlTextReader (new StringReader (ssmlNoCrLf)); 

            // Parse the stream 
            Parse (reader, engine, voice); 
        }
 
        /// 
        /// Parse an SSML stream and build a set of SSML Text Fragments
        /// 
        ///  
        /// 
        ///  
        internal static void Parse (XmlReader reader, ISsmlParser engine, object voice) 
        {
            try 
            {
                bool isSpeakElementFound = false;

                while (reader.Read ()) 
                {
                    // Ignore XmlDeclaration, ProcessingInstruction, Comment, DocumentType, Entity, Notation. 
                    if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "speak")) 
                    {
                        // SSML documents must start with the "speak" element 
                        if (isSpeakElementFound)
                        {
                            ThrowFormatException (SRID.GrammarDefTwice);
                        } 
                        else
                        { 
                            // The XML header is read, real work starts here 
                            ProcessSpeakElement (reader, engine, voice);
                            isSpeakElementFound = true; 
                        }
                    }
                }
 
                if (!isSpeakElementFound)
                { 
                    ThrowFormatException (SRID.SynthesizerNoSpeak); 
                }
            } 
            catch (XmlException eXml)
            {
                throw new FormatException (SR.Get (SRID.InvalidXml), eXml);
            } 
        }
 
        #endregion 

        //******************************************************************* 
        //
        // Private Methods
        //
        //******************************************************************** 

        #region Private Methods 
 
        /// 
        /// Validate the Speak element 
        /// 
        private static void ProcessSpeakElement (XmlReader reader, ISsmlParser engine, object voice)
        {
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            ssmlAttributes._voice = voice;
            ssmlAttributes._age = VoiceAge.NotSet; 
            ssmlAttributes._gender = VoiceGender.NotSet; 
            ssmlAttributes._unknownNamespaces = new List ();
 
            string sVersion = null;
            string sCulture = null;
            string sBaseUri = null;
            CultureInfo culture = null; 
            List extraSpeakAttributes = new List ();
            Exception innerException = null; 
 
            // Process attributes.
            while (reader.MoveToNextAttribute ()) 
            {
                bool isInvalidAttribute = false;

                // emptyNamespace 
                if (reader.NamespaceURI.Length == 0)
                { 
                    switch (reader.LocalName) 
                    {
                        case "version": 
                            CheckForDuplicates (ref sVersion, reader);
                            if (sVersion != "1.0")
                            {
                                ThrowFormatException (SRID.InvalidVersion); 
                            }
                            break; 
 
                        default:
                            isInvalidAttribute = true; 
                            break;
                    }
                }
                else if (reader.NamespaceURI == xmlNamespace) 
                {
                    switch (reader.LocalName) 
                    { 
                        case "lang":
                            CheckForDuplicates (ref sCulture, reader); 
                            try
                            {
                                culture = new CultureInfo (sCulture);
                            } 
                            catch (ArgumentException e)
                            { 
                                // Special case for "es-us", create a new CultureInfo based on attributes of spanish 
                                // and english
                                if (string.Compare ("es-us", sCulture, StringComparison.OrdinalIgnoreCase) == 0) 
                                {
                                    Helpers.CombineCulture ("es-ES", "en-US", new CultureInfo ("es"), 0x540A);
                                    culture = new CultureInfo (sCulture);
                                } 
                                else
                                { 
                                    innerException = e; 
                                    // Unknown Culture info, fall back to the base culture.
                                    int pos = reader.Value.IndexOf ("-", StringComparison.Ordinal); 
                                    if (pos > 0)
                                    {
                                        try
                                        { 
                                            culture = new CultureInfo (reader.Value.Substring (0, pos));
                                        } 
                                        catch (ArgumentException) 
                                        {
                                            isInvalidAttribute = true; 
                                        }
                                    }
                                    else
                                    { 
                                        isInvalidAttribute = true;
                                    } 
                                } 
                            }
                            break; 

                        case "base":
                            CheckForDuplicates (ref sBaseUri, reader);
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                else if (reader.NamespaceURI == xmlNamespaceXmlns)
                {
                    if (reader.Value != xmlNamespaceSsml && reader.Value != xmlNamespacePrompt) 
                    {
                        ssmlAttributes._unknownNamespaces.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                    } 
                    else if (reader.Value == xmlNamespacePrompt)
                    { 
                        engine.ContainsPexml (reader.LocalName);
                    }
                }
                else 
                {
                    extraSpeakAttributes.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                } 

                if (isInvalidAttribute) 
                {
                    ThrowFormatException (innerException, SRID.InvalidElement, reader.Name);
                }
            } 

            if (string.IsNullOrEmpty (sVersion)) 
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "version", "speak");
            } 

            if (string.IsNullOrEmpty (sCulture))
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "lang", "speak"); 
            }
 
            // append the local attributes to list of unknow attributes 
            List extraAttributes = null;
            foreach (SsmlXmlAttribute attribute in extraSpeakAttributes) 
            {
                ssmlAttributes.AddUnknowAttribute (attribute, ref extraAttributes);
            }
 
            voice = engine.ProcessSpeak (sVersion, sBaseUri, culture, ssmlAttributes._unknownNamespaces);
 
            ssmlAttributes._fragmentState.LangId = culture.LCID; 
            ssmlAttributes._voice = voice;
            ssmlAttributes._baseUri = sBaseUri; 

            // Process child elements.
            SsmlElement possibleChild = SsmlElement.Lexicon | SsmlElement.Meta | SsmlElement.MetaData | SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, "speak", possibleChild, ssmlAttributes, false, extraAttributes); 

            // Notify the engine that the element is processed 
            engine.EndSpeakElement (); 
        }
 
        /// 
        /// Generic method to process an SSML element.
        /// The element name is fetch from the element name array and
        /// the delegate for that element will be called. 
        /// 
        private static void ProcessElement (XmlReader reader, ISsmlParser engine, string sElement, SsmlElement possibleElements, SsmlAttributes ssmAttributesParent, bool fIgnore, List extraAttributes) 
        { 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;
 
            // Flush any remaining attributes from the previous element list
            if (extraAttributes != null && extraAttributes.Count > 0) 
            { 
                engine.StartProcessUnknownAttributes (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sElement, extraAttributes);
            } 

            // Move to containing element of attributes
            reader.MoveToElement ();
            if (!reader.IsEmptyElement) 
            {
                // Process each child element while not at end element 
                reader.Read (); 
                do
                { 
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            int iElement = Array.BinarySearch (_elementsName, reader.LocalName); 
                            if (iElement >= 0)
                            { 
                                _parseElements [iElement] (reader, engine, possibleElements, ssmlAttributes, fIgnore); 
                            }
                            else 
                            {
                                // Could be an element from some undefined namespace
                                if (!ssmlAttributes.IsOtherNamespaceElement (reader))
                                { 
                                    ThrowFormatException (SRID.InvalidElement, reader.Name);
                                } 
                                else 
                                {
                                    engine.ProcessUnknownElement (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, reader); 
                                    continue;
                                }
                            }
                            reader.Read (); 
                            break;
 
                        case XmlNodeType.Text: 
                            if ((possibleElements & SsmlElement.Text) != 0)
                            { 
                                engine.ProcessText (reader.Value, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, GetColumnPosition (reader), fIgnore);
                            }
                            else
                            { 
                                ThrowFormatException (SRID.InvalidElement, reader.Name);
                            } 
                            reader.Read (); 
                            break;
 
                        case XmlNodeType.EndElement:
                            break;

                        default: 
                            reader.Read ();
                            break; 
                    } 
                }
                while (reader.NodeType != XmlNodeType.EndElement && reader.NodeType != XmlNodeType.None); 
            }

            // Flush any remaining attributes from the previous element list
            if (extraAttributes != null && extraAttributes.Count > 0) 
            {
                engine.EndProcessUnknownAttributes (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sElement, extraAttributes); 
            } 
        }
 
        private static void ParseAudio (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Audio, reader.Name); 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;

            string sUri = null; 
            bool fRenderDesc = false;
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "src": 
                            CheckForDuplicates (ref sUri, reader); 
                            // Audio element
                            try 
                            {
                                engine.ProcessAudio (ssmlAttributes._voice, sUri, ssmlAttributes._baseUri, fIgnore);
                            }
                            catch (IOException) 
                            {
                                fRenderDesc = true; 
                            } 
                            catch (WebException)
                            { 
                                fRenderDesc = true;
                            }
                            break;
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }
 
            ssmlAttributes._fRenderDesc = fRenderDesc; 

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.Desc | SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, !fRenderDesc, extraAttributes);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseBreak (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Break, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 
            ssmlAttributes._fragmentState.Action = TtsEngineAction.Silence;
            ssmlAttributes._fragmentState.Emphasis = (int) EmphasisBreak.Default;

            string sTime = null; 
            string sStrength = null;
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "time": 
                            { 
                                CheckForDuplicates (ref sTime, reader);
                                ssmlAttributes._fragmentState.Emphasis = (int) EmphasisBreak.None; 
                                ssmlAttributes._fragmentState.Duration = ParseCSS2Time (sTime);
                                isInvalidAttribute = ssmlAttributes._fragmentState.Duration < 0;
                            }
                            break; 

                        case "strength": 
                            CheckForDuplicates (ref sStrength, reader); 
                            if (sTime == null)
                            { 
                                ssmlAttributes._fragmentState.Duration = 0;
                                int pos = Array.BinarySearch (_breakStrength, sStrength);
                                if (pos < 0)
                                { 
                                    isInvalidAttribute = true;
                                } 
                                else 
                                {
                                    // SSML Spec if both strength and time are supplied, ignore strength 
                                    if (ssmlAttributes._fragmentState.Emphasis != (int) EmphasisBreak.None)
                                    {
                                        ssmlAttributes._fragmentState.Emphasis = (int) _breakEmphasis [pos];
                                    } 
                                }
                            } 
                            break; 

#if SPEECHSERVER 
                        case "size":
                            CheckForDuplicates (ref sStrength, reader);
                            if (sTime == null)
                            { 
                                ssmlAttributes._fragmentState.Duration = 0;
                                int posSize = Array.BinarySearch (_breakSize, sStrength); 
                                if (posSize < 0) 
                                {
                                    isInvalidAttribute = true; 
                                }
                                else
                                {
                                    // SSML Spec if both strength and time are supplied, ignore strength 
                                    if (ssmlAttributes._fragmentState.Emphasis != (int) EmphasisBreak.None)
                                    { 
                                        ssmlAttributes._fragmentState.Emphasis = (int) _breakSizeWord [posSize]; 
                                    }
                                } 
                            }
                            break;
#endif
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidSpeakAttribute, reader.Name, "break");
                } 
            }
 
            engine.ProcessBreak (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, (EmphasisBreak) ssmlAttributes._fragmentState.Emphasis, ssmlAttributes._fragmentState.Duration, fIgnore); 

            // No Children allowed . 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);

            // Notify the engine that the element is processed
            engine.EndElement (); 
        }
 
        private static void ParseDesc (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Desc, reader.Name);

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sCulture = null;
            CultureInfo culture = null;
            while (reader.MoveToNextAttribute ())
            { 
                bool isInvalidAttribute = reader.NamespaceURI != xmlNamespace;
 
                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName) 
                    {
                        // The W3C spec says ignore
                        case "lang":
                            CheckForDuplicates (ref sCulture, reader); 
                            try
                            { 
                                culture = new CultureInfo (sCulture); 
                            }
                            catch (ArgumentException) 
                            {
                                isInvalidAttribute = true;
                            }
                            isInvalidAttribute &= culture != null; 
                            break;
 
                        default: 
                            isInvalidAttribute = true;
                            break; 
                    }
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            } 

            engine.ProcessDesc (culture); 

            // Process child elements.
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        private static void ParseEmphasis (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Emphasis, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;

            // Set the default value
            ssmlAttributes._fragmentState.Emphasis = (int) EmphasisWord.Moderate; 

            string sLevel = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;

                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName)
                    { 
                        // The W3C spec says ignore 
                        case "level":
                            CheckForDuplicates (ref sLevel, reader); 
                            int pos = Array.BinarySearch (_emphasisNames, sLevel);
                            if (pos < 0)
                            {
                                isInvalidAttribute = true; 
                            }
                            else 
                            { 
                                ssmlAttributes._fragmentState.Emphasis = (int) _emphasisWord [pos];
                            } 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            engine.ProcessEmphasis (!string.IsNullOrEmpty (sLevel), (EmphasisWord) ssmlAttributes._fragmentState.Emphasis);
 
            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes); 

            // Notify the engine that the element is processed
            engine.EndElement ();
        } 

        private static void ParseMark (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Mark, reader.Name); 

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent; 

            string sName = null; 
            while (reader.MoveToNextAttribute ())
            {
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute) 
                { 
                    switch (reader.LocalName)
                    { 
                        // The W3C spec says ignore
                        case "name":
                            CheckForDuplicates (ref sName, reader);
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 
 
            if (string.IsNullOrEmpty (sName))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "name", "mark");
            }

            ssmlAttributes._fragmentState.Action = TtsEngineAction.Bookmark; 
            engine.ProcessMark (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sName, fIgnore);
 
            // No Children allowed. 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement ();
        }
 
        private static void ParseMetaData (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            ValidateElement (element, SsmlElement.MetaData, reader.Name);
 
            // No processing for this element, skip
            if (!reader.IsEmptyElement)
            {
                int cEndNode = 1; 
                do
                { 
                    // Loop until we reach the end of the metadata element 
                    reader.Read ();
 
                    // Count the number of elements processed
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        cEndNode++; 
                    }
                    if (reader.NodeType == XmlNodeType.EndElement || reader.NodeType == XmlNodeType.None) 
                    { 
                        cEndNode--;
                    } 
                }
                while (cEndNode > 0);

                // Consume the end element 
                System.Diagnostics.Debug.Assert (reader.NodeType == XmlNodeType.EndElement);
            } 
        } 

        private static void ParseParagraph (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Paragraph, reader.Name);
 
            ParseTextBlock (reader, engine, true, sElement, ssmAttributesParent, fIgnore);
        } 
 
        private static void ParseSentence (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Sentence, reader.Name);

            ParseTextBlock (reader, engine, false, sElement, ssmAttributesParent, fIgnore); 
        }
 
        private static void ParseTextBlock (XmlReader reader, ISsmlParser engine, bool isParagraph, string sElement, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sCulture = null; 
            CultureInfo culture = null;
            while (reader.MoveToNextAttribute ()) 
            {
                bool isInvalidAttribute = reader.NamespaceURI != xmlNamespace;

                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName) 
                    { 
                        // The W3C spec says ignore
                        case "lang": 
                            CheckForDuplicates (ref sCulture, reader);
                            try
                            {
                                culture = new CultureInfo (sCulture); 
                            }
                            catch (ArgumentException) 
                            { 
                                isInvalidAttribute = true;
                            } 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            // Try to change the voice
            bool fNewCulture = culture != null && culture.LCID != ssmlAttributes._fragmentState.LangId; 
            ssmlAttributes._voice = engine.ProcessTextBlock (isParagraph, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, culture, fNewCulture, ssmlAttributes._gender, ssmlAttributes._age); 
            if (fNewCulture)
            { 
                ssmlAttributes._fragmentState.LangId = culture.LCID;
            }

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            if (isParagraph) 
            { 
                possibleChild |= SsmlElement.Sentence;
            } 
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            engine.EndProcessTextBlock (isParagraph);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        private static void ParsePhoneme (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Phoneme, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;

            string sAlphabet = null;
            AlphabetType alphabet = AlphabetType.Ipa; 
            string sPh = null;
            char [] aPhoneIds = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;

                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName)
                    { 
                        case "alphabet": 
                            CheckForDuplicates (ref sAlphabet, reader);
                            switch (sAlphabet) 
                            {
                                case "ipa":
                                    alphabet = AlphabetType.Ipa;
                                    break; 

                                case "sapi": 
                                case "x-sapi": 
                                case "x-microsoft-sapi":
                                    alphabet = AlphabetType.Sapi; 
                                    break;

                                case "ups":
                                case "x-ups": 
                                case "x-microsoft-ups":
                                    alphabet = AlphabetType.Ups; 
                                    break; 

                                default: 
                                    throw new FormatException (SR.Get (SRID.UnsupportedAlphabet));
                            }
                            break;
 
                        case "ph":
                            CheckForDuplicates (ref sPh, reader); 
                            break; 

                        default: 
                            isInvalidAttribute = true;
                            break;
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 

            if (string.IsNullOrEmpty (sPh))
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "ph", "phoneme"); 
            }
 
            // Try to convert the phoneme set 
            try
            { 
                switch (alphabet)
                {
                    case AlphabetType.Sapi:
                        aPhoneIds = PhonemeConverter.ConvertPronToId (sPh, ssmlAttributes._fragmentState.LangId).ToCharArray (); 
                        break;
 
                    case AlphabetType.Ups: 
                        aPhoneIds = PhonemeConverter.UpsConverter.ConvertPronToId (sPh).ToCharArray ();
                        alphabet = AlphabetType.Ipa; 
                        break;

                    case AlphabetType.Ipa:
                    default: 
                        aPhoneIds = sPh.ToCharArray ();
                        try 
                        { 
                            PhonemeConverter.ValidateUpsIds (aPhoneIds);
                        } 
                        catch (FormatException)
                        {
                            if (sAlphabet != null)
                            { 
                                throw;
                            } 
                            else 
                            {
                                // try with sapi (backward compatibility) 
                                // if not a sapi phoneme either throw the IPA exception
                                aPhoneIds = PhonemeConverter.ConvertPronToId (sPh, ssmlAttributes._fragmentState.LangId).ToCharArray ();
                                alphabet = AlphabetType.Sapi;
                            } 
                        }
                        break; 
                } 
            }
            catch (FormatException) 
            {
                ThrowFormatException (SRID.InvalidItemAttribute, "phoneme");
            }
 
            engine.ProcessPhoneme (ref ssmlAttributes._fragmentState, alphabet, sPh, aPhoneIds);
 
            // Process child elements. 
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, fIgnore, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement ();
        }
 
        private static void ParseProsody (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Prosody, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 
 
            string sPitch = null;
            string sContour = null; 
            string sRange = null;
            string sRate = null;
            string sDuration = null;
            string sVolume = null; 
            Prosody prosody = ssmlAttributes._fragmentState.Prosody != null ? ssmlAttributes._fragmentState.Prosody.Clone () : new Prosody ();
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "pitch": 
                            isInvalidAttribute = ParseNumberHz (reader, ref sPitch, _pitchNames, _pitchWords, ref prosody._pitch); 
                            break;
 
                        case "range":
                            isInvalidAttribute = ParseNumberHz (reader, ref sRange, _rangeNames, _rangeWords, ref prosody._range);
                            break;
 
                        case "rate":
                            isInvalidAttribute = ParseNumberRelative (reader, ref sRate, _rateNames, _rateWords, ref prosody._rate); 
                            break; 

                        case "volume": 
                            isInvalidAttribute = ParseNumberRelative (reader, ref sVolume, _volumeNames, _volumeWords, ref prosody._volume);
                            break;

                        case "duration": 
                            CheckForDuplicates (ref sDuration, reader);
                            prosody.Duration = ParseCSS2Time (sDuration); 
                            break; 

                        case "contour": 
                            CheckForDuplicates (ref sContour, reader);
                            prosody.SetContourPoints (ParseContour (sContour));
                            if (prosody.GetContourPoints () == null) { isInvalidAttribute = true; }
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 
 
            if (string.IsNullOrEmpty (sPitch) && string.IsNullOrEmpty (sContour) && string.IsNullOrEmpty (sRange) && string.IsNullOrEmpty (sRate) && string.IsNullOrEmpty (sDuration) && string.IsNullOrEmpty (sVolume))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "pitch, contour, range, rate, duration, volume", "prosody");
            }

            ssmlAttributes._fragmentState.Prosody = prosody; 

            engine.ProcessProsody (sPitch, sRange, sRate, sVolume, sDuration, sContour); 
 
            // Process child elements.
            SsmlElement possibleChild = SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes); 
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            // Notify the engine that the element is processed
            engine.EndElement (); 
        }
 
        private static void ParseSayAs (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.SayAs, reader.Name);

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sInterpretAs = null;
            string sFormat = null;
            string sDetail = null;
            System.Speech.Synthesis.TtsEngine.SayAs sayAs = new System.Speech.Synthesis.TtsEngine.SayAs (); 
            while (reader.MoveToNextAttribute ())
            { 
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;
 
                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName)
                    { 
                        case "type":
                        case "interpret-as": 
                            CheckForDuplicates (ref sInterpretAs, reader); 
                            sayAs.InterpretAs = sInterpretAs;
                            break; 

                        case "format":
                            CheckForDuplicates (ref sFormat, reader);
                            sayAs.Format = sFormat; 
                            break;
 
                        case "detail": 
                            CheckForDuplicates (ref sDetail, reader);
                            sayAs.Detail = sDetail; 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            if (string.IsNullOrEmpty (sInterpretAs))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "interpret-as", "say-as"); 
            }
 
            // Create SayAs attribute
            ssmlAttributes._fragmentState.SayAs = sayAs;

            engine.ProcessSayAs (sInterpretAs, sFormat, sDetail); 

            // Process child elements. 
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, fIgnore, extraAttributes); 

            // Notify the engine that the element is processed 
            engine.EndElement ();
        }

        private static void ParseSub (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Sub, reader.Name); 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sAlias = null; 
            int textPosition = 0;
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;
 
                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName) 
                    {
                        // The W3C spec says ignore 
                        case "alias":
                            CheckForDuplicates (ref sAlias, reader);
                            XmlTextReader textReader = reader as XmlTextReader;
                            if (textReader != null && engine.Ssml != null) 
                            {
                                textPosition = engine.Ssml.IndexOf (reader.Value, textReader.LinePosition + reader.LocalName.Length, StringComparison.Ordinal); 
                            } 
                            break;
 
                        default:
                            isInvalidAttribute = true;
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes)) 
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }

            if (string.IsNullOrEmpty (sAlias))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "alias", "sub");
            } 
 
            engine.ProcessSub (sAlias, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, textPosition, fIgnore);
 
            // The only allowed children element is text. Ignore it
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, true, extraAttributes);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
        private static void ParseVoice (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Voice, reader.Name);

            // Cannot have a voice element in a Prompt bout
            if (ssmAttributesParent._cPromptOutput > 0) 
            {
                ThrowFormatException (SRID.InvalidVoiceElementInPromptOutput); 
            } 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 

            string sCulture = null; 
            string sGender = null; 
            string sVariant = null;
            string sName = null; 
            string sAge = null;
            string xmlns = null;
            CultureInfo culture = null;
            int variant = -1; 

            List extraAttributes = null; 
            List extraAttributesVoice = null; 
            List localUnknownNamespaces = null;
 
            while (reader.MoveToNextAttribute ())
            {
                bool isInvalidAttribute = false;
 
                // empty namespace
                if (reader.NamespaceURI.Length == 0) 
                { 
                    switch (reader.LocalName)
                    { 
                        case "gender":
                            CheckForDuplicates (ref sGender, reader);
                            VoiceGender gender;
                            if (!SsmlParserHelpers.TryConvertGender (sGender, out gender)) 
                            {
                                isInvalidAttribute = true; 
                            } 
                            else
                            { 
                                ssmlAttributes._gender = gender;
                            }
                            break;
 
                        case "age":
                            CheckForDuplicates (ref sAge, reader); 
                            VoiceAge age; 
                            if (!SsmlParserHelpers.TryConvertAge (sAge, out age))
                            { 
                                isInvalidAttribute = true;
                            }
                            else
                            { 
                                ssmlAttributes._age = age;
                            } 
                            break; 

                        case "variant": 
                            // Ignore this field. We have no way with the current tokens to
                            // use it
                            CheckForDuplicates (ref sVariant, reader);
                            if (!int.TryParse (sVariant, out variant) || variant <= 0) 
                            {
                                isInvalidAttribute = true; 
                            } 
                            break;
 
                        case "name":
                            CheckForDuplicates (ref sName, reader);
                            break;
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                else
                {
                    if (reader.Prefix == "xmlns" && reader.Value == xmlNamespacePrompt)
                    { 
                        CheckForDuplicates (ref xmlns, reader);
                    } 
                    else 
                    {
                        if (reader.NamespaceURI == xmlNamespace) 
                        {
                            switch (reader.LocalName)
                            {
                                // The W3C spec says ignore 
                                case "lang":
                                    CheckForDuplicates (ref sCulture, reader); 
                                    try 
                                    {
                                        culture = new CultureInfo (sCulture); 
                                    }
                                    catch (ArgumentException)
                                    {
                                        isInvalidAttribute = true; 
                                    }
                                    break; 
 
                                default:
                                    isInvalidAttribute = true; 
                                    break;
                            }
                        }
                        else if (reader.NamespaceURI == xmlNamespaceXmlns) 
                        {
                            if (reader.Value != xmlNamespaceSsml) 
                            { 
                                if (localUnknownNamespaces == null)
                                { 
                                    localUnknownNamespaces = new List ();
                                }

                                SsmlXmlAttribute ns = new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI); 
                                localUnknownNamespaces.Add (ns);
                                ssmlAttributes._unknownNamespaces.Add (ns); 
                            } 
                        }
                        else 
                        {
                            if (extraAttributesVoice == null)
                            {
                                extraAttributesVoice = new List (); 
                            }
                            extraAttributesVoice.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                        } 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }
 
            // append the local attributes to list of unknow attributes 
            if (extraAttributesVoice != null)
            { 
                foreach (SsmlXmlAttribute attribute in extraAttributesVoice)
                {
                    ssmlAttributes.AddUnknowAttribute (attribute, ref extraAttributes);
                } 
            }
 
            if (string.IsNullOrEmpty (sCulture) && string.IsNullOrEmpty (sGender) && string.IsNullOrEmpty (sAge) && string.IsNullOrEmpty (sVariant) && string.IsNullOrEmpty (sName) && string.IsNullOrEmpty (xmlns)) 
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "'xml:lang' or 'gender' or 'age' or 'variant' or 'name'", "voice"); 
            }

            // Try to change the voice
            culture = culture == null ? new CultureInfo (ssmlAttributes._fragmentState.LangId) : culture; 
            bool fNewCulture = culture.LCID != ssmlAttributes._fragmentState.LangId;
            ssmlAttributes._voice = engine.ProcessVoice (sName, culture, ssmlAttributes._gender, ssmlAttributes._age, variant, fNewCulture, localUnknownNamespaces); 
            ssmlAttributes._fragmentState.LangId = culture.LCID; 

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            // remove the local namespaces 
            if (localUnknownNamespaces != null)
            { 
                foreach (SsmlXmlAttribute ns in localUnknownNamespaces) 
                {
                    ssmlAttributes._unknownNamespaces.Remove (ns); 
                }
            }

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseLexicon (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Lexicon, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 

            string sUri = null;
            string sMediaType = null;
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName)
                    {
                        case "uri": 
                            CheckForDuplicates (ref sUri, reader);
                            break; 
 
                        case "type":
                            CheckForDuplicates (ref sMediaType, reader); 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            if (string.IsNullOrEmpty (sUri))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "uri", "lexicon"); 
            }
 
            // Add the base path if it exist
            Uri uri = new Uri (sUri, UriKind.RelativeOrAbsolute);
            if (!uri.IsAbsoluteUri && ssmlAttributes._baseUri != null)
            { 
                sUri = ssmlAttributes._baseUri + '/' + sUri;
                uri = new Uri (sUri, UriKind.RelativeOrAbsolute); 
            } 

            engine.ProcessLexicon (uri, sMediaType); 

            // No Children allowed.
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        #region Prompt Engine 

        private delegate bool ProcessPromptEngine0 (object voice);
        private delegate bool ProcessPromptEngine1 (object voice, string value);
 
        private static void ParsePromptEngine0 (XmlReader reader, ISsmlParser engine, SsmlElement elementAllowed, SsmlElement element, ProcessPromptEngine0 process, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            string sElement = ValidateElement (elementAllowed, element, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            // No attributes allowed 
            while (reader.MoveToNextAttribute ())
            { 
                if (reader.NamespaceURI == xmlNamespaceXmlns && reader.Value == xmlNamespacePrompt)
                {
                    engine.ContainsPexml (reader.LocalName);
                } 
                else
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 

            // Notify the engine that the element is processed
            if (!process (ssmlAttributes._voice))
            { 
                ThrowFormatException (SRID.InvalidElement, reader.Name);
            } 
 
            // Process Children
            ProcessElement (reader, engine, sElement, SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes), ssmlAttributes, fIgnore, null); 
        }

        private static string ParsePromptEngine1 (XmlReader reader, ISsmlParser engine, SsmlElement elementAllowed, SsmlElement element, string attribute, ProcessPromptEngine1 process, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (elementAllowed, element, reader.Name); 
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;
 
            // 1 attribute
            string value = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                if (reader.LocalName == attribute) 
                {
                    CheckForDuplicates (ref value, reader);
                }
                else 
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                } 
            }
 
            // Notify the engine that the element is processed
            if (!process (ssmlAttributes._voice, value))
            {
                ThrowFormatException (SRID.InvalidElement, reader.Name); 
            }
 
            // No Children allowed 
            ProcessElement (reader, engine, sElement, SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes), ssmlAttributes, fIgnore, null);
            return value; 
        }

        private static void ParsePromptOutput (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Increase the ref count for the Prompt output
            ssmAttributesParent._cPromptOutput++; 
 
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineOutput, new ProcessPromptEngine0 (engine.BeginPromptEngineOutput), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed
            engine.EndElement ();

            // Decrease the ref count for the Prompt output 
            ssmAttributesParent._cPromptOutput--;
            engine.EndPromptEngineOutput (ssmAttributesParent._voice); 
        } 

        private static void ParseDiv (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineDiv, new ProcessPromptEngine0 (engine.ProcessPromptEngineDiv), ssmAttributesParent, fIgnore);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseDatabase (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.PromptEngineDatabase, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            // No attributes allowed
            string fname = null;
            string delta = null;
            string idset = null; 
            while (reader.MoveToNextAttribute ())
            { 
                // Namespace must be empty 
                bool isInvalidAttribute = false;
 
                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName)
                    { 
                        case "fname":
                            CheckForDuplicates (ref fname, reader); 
                            break; 

                        case "idset": 
                            CheckForDuplicates (ref idset, reader);
                            break;

                        case "delta": 
                            CheckForDuplicates (ref delta, reader);
                            break; 
 
                        default:
                            isInvalidAttribute = true; 
                            break;
                    }
                }
                if (isInvalidAttribute) 
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                } 
            }
            // Notify the engine that the element is processed 
            if (!engine.ProcessPromptEngineDatabase (ssmlAttributes._voice, fname, delta, idset))
            {
                ThrowFormatException (SRID.InvalidElement, reader.Name);
            } 

            // No Children allowed 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, fIgnore, null); 

            // Notify the engine that the element is processed 
            engine.EndElement ();
        }

        private static void ParseId (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineId, "id", new ProcessPromptEngine1 (engine.ProcessPromptEngineId), ssmAttributesParent, fIgnore); 
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        }

        private static void ParseTts (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineTTS, new ProcessPromptEngine0 (engine.BeginPromptEngineTts), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineTts (ssmAttributesParent._voice); 
        }

        private static void ParseWithTag (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            string tag = ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineWithTag, "tag", new ProcessPromptEngine1 (engine.BeginPromptEngineWithTag), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineWithTag (ssmAttributesParent._voice, tag); 
        }

        private static void ParseRule (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            string name = ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineRule, "name", new ProcessPromptEngine1 (engine.BeginPromptEngineRule), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineRule (ssmAttributesParent._voice, name); 
        }

        #endregion
 
        private static void CheckForDuplicates (ref string dest, XmlReader reader)
        { 
            if (!string.IsNullOrEmpty (dest)) 
            {
                StringBuilder attribute = new StringBuilder (reader.LocalName); 
                if (reader.NamespaceURI.Length > 0)
                {
                    attribute.Append (reader.NamespaceURI);
                    attribute.Append (":"); 
                }
                ThrowFormatException (SRID.InvalidAttributeDefinedTwice, reader.Value, attribute); 
            } 
            dest = reader.Value;
        } 

        private static int ParseCSS2Time (string time)
        {
            time = time.Trim (Helpers._achTrimChars); 
            int pos = time.IndexOf ("ms", StringComparison.Ordinal);
            int duration = -1; 
            float fDuration; 
            if (pos > 0 && time.Length == pos + 2)
            { 
                if (!float.TryParse (time.Substring (0, pos), out fDuration))
                {
                    duration = -1;
                } 
                else
                { 
                    duration = (int) (fDuration + 0.5); 
                }
            } 
            else
                if ((pos = time.IndexOf ('s')) > 0 && time.Length == pos + 1)
                {
                    if (!float.TryParse (time.Substring (0, pos), out fDuration)) 
                    {
                        duration = -1; 
                    } 
                    else
                    { 
                        duration = (int) (fDuration * 1000);
                    }
                }
            return duration; 
        }
 
        private static ContourPoint [] ParseContour (string contour) 
        {
            char [] achContour = contour.ToCharArray (); 
            List points = new List ();
            int start = 0;

            try 
            {
                while (start < achContour.Length) 
                { 
                    bool percent, ignored, hz;
                    // Form is (0%, +20Hz) 
                    if ((start = NextChar (achContour, start, '(', false, out ignored)) < 0)
                    {
                        // End of the string found exit
                        break; 
                    }
 
                    int comma = NextChar (achContour, start, ',', true, out percent); 
                    int parenthesis = NextChar (achContour, comma, ')', true, out ignored);
 
                    ProsodyNumber timePosition = new ProsodyNumber ();
                    ProsodyNumber target = new ProsodyNumber ();

                    // Parse the 2 numbers 
                    if (!percent || !TryParseNumber (contour.Substring (start, comma - (start + 1)), ref timePosition) || timePosition.SsmlAttributeId == ProsodyNumber.AbsoluteNumber)
                    { 
                        return null; 
                    }
                    if (!TryParseHz (contour.Substring (comma, parenthesis - (comma + 1)), ref target, true, out hz)) 
                    {
                        return null;
                    }
 
                    // First point
                    if (points.Count == 0) 
                    { 
                        // fake a zero entry if none is provided by duplicating the first entry
                        if (timePosition.Number > 0 && timePosition.Number < 100) 
                        {
                            points.Add (new ContourPoint (0, target.Number, ContourPointChangeType.Hz));
                        }
                    } 
                    else
                    { 
                        // Accept only increasing start points 
                        // Add a 100% if necessary
                        if (points [points.Count - 1].Start > timePosition.Number) 
                        {
                            return null;
                        }
                    } 

                    if (timePosition.Number >= 0 && timePosition.Number <= 1) 
                    { 
                        points.Add (new ContourPoint (timePosition.Number, target.Number, (hz ? ContourPointChangeType.Hz : ContourPointChangeType.Percentage)));
                    } 
                    start = parenthesis;
                }
            }
            catch (InvalidOperationException) 
            {
                return null; 
            } 

            if (points.Count < 1) 
            {
                return null;
            }
 
            // Add a 100% if necessary
            if (!points [points.Count - 1].Start.Equals (1.0)) 
            { 
                points.Add (new ContourPoint (1, points [points.Count - 1].Change, points [points.Count - 1].ChangeType));
            } 
            return points.ToArray ();
        }

        private static int NextChar (char [] ach, int start, char expected, bool skipDigit, out bool percent) 
        {
            percent = false; 
 
            // skip the whitespace
            while (start < ach.Length && (ach [start] == ' ' || ach [start] == '\t' || ach [start] == '\n' || ach [start] == '\r')) 
            {
                start++;
            }
 
            // skip the digits
            if (skipDigit) 
            { 
                while (start < ach.Length && ach [start] != expected && ((percent = ach [start] == '%') || char.IsDigit (ach [start]) || ach [start] == 'H' || ach [start] == 'z' || ach [start] == '.' || ach [start] == '+' || ach [start] == '-'))
                { 
                    start++;
                }

                // skip the trailing white spaces 
                while (start < ach.Length && (ach [start] == ' ' || ach [start] == '\t' || ach [start] == '\n' || ach [start] == '\r'))
                { 
                    start++; 
                }
            } 

            // Check if we found the character we wanted
            if (!(start < ach.Length && ach [start] == expected))
            { 
                // Check for the end of the string
                if (!skipDigit && start == ach.Length) 
                { 
                    return -1;
                } 
                // bail out
                throw new InvalidOperationException ();
            }
            return start + 1; 
        }
 
        private static bool ParseNumberHz (XmlReader reader, ref string attribute, string [] attributeValues, int [] attributeConst, ref ProsodyNumber number) 
        {
            bool isInvalidAttribute = false; 
            bool isHz;

            CheckForDuplicates (ref attribute, reader);
            int pos = Array.BinarySearch (attributeValues, attribute); 
            if (pos < 0)
            { 
                if (!TryParseHz (attribute, ref number, false, out isHz)) 
                {
                    isInvalidAttribute = true; 
                }
            }
            else
            { 
                number = new ProsodyNumber (attributeConst [pos]);
            } 
            return isInvalidAttribute; 
        }
 
        private static bool ParseNumberRelative (XmlReader reader, ref string attribute, string [] attributeValues, int [] attributeConst, ref ProsodyNumber number)
        {
            bool isInvalidAttribute = false;
 
            CheckForDuplicates (ref attribute, reader);
            int pos = Array.BinarySearch (attributeValues, attribute); 
            if (pos < 0) 
            {
                if (!TryParseNumber (attribute, ref number)) 
                {
                    isInvalidAttribute = true;
                }
            } 
            else
            { 
                number = new ProsodyNumber (attributeConst [pos]); 
            }
            return isInvalidAttribute; 
        }

        private static bool TryParseNumber (string sNumber, ref ProsodyNumber number)
        { 
            bool fResult = false;
            decimal value = 0; 
 
            // always reset the unit to Default
            number.Unit = ProsodyUnit.Default; 
            sNumber = sNumber.Trim (Helpers._achTrimChars);
            if (!string.IsNullOrEmpty (sNumber))
            {
                if (!decimal.TryParse (sNumber, out value)) 
                {
                    if (sNumber [sNumber.Length - 1] == '%') 
                    { 
                        if (decimal.TryParse (sNumber.Substring (0, sNumber.Length - 1), out value))
                        { 
                            float percent = (float) value / 100f;
                            if (sNumber [0] != '+' && sNumber [0] != '-')
                            {
                                number.Number = number.Number * percent; 
                            }
                            else 
                            { 
                                number.Number += number.Number * (percent);
                            } 

                            fResult = true;
                        }
                    } 
                }
                else 
                { 
                    if (sNumber [0] != '+' && sNumber [0] != '-')
                    { 
                        number.Number = (float) value;
                        number.SsmlAttributeId = ProsodyNumber.AbsoluteNumber;
                    }
                    else 
                    {
                        if (number.IsNumberPercent) 
                        { 
                            number.Number *= (float) value;
                        } 
                        else
                        {
                            number.Number += (float) value;
                        } 
                    }
                    number.IsNumberPercent = false; 
                    fResult = true; 
                }
            } 
            return fResult;
        }

        private static bool TryParseHz (string sNumber, ref ProsodyNumber number, bool acceptHzRelative, out bool isHz) 
        {
            isHz = false; 
 
            // Find the Hz at the end of the number
            bool fResult = false; 
            number.SsmlAttributeId = ProsodyNumber.AbsoluteNumber;
            ProsodyUnit unit = ProsodyUnit.Default;

            sNumber = sNumber.Trim (Helpers._achTrimChars); 
            if (sNumber.IndexOf ("Hz", StringComparison.Ordinal) == sNumber.Length - 2)
            { 
                unit = ProsodyUnit.Hz; 
            }
            else if (sNumber.IndexOf ("st", StringComparison.Ordinal) == sNumber.Length - 2) 
            {
                unit = ProsodyUnit.Semitone;
            }
 
            if (unit != ProsodyUnit.Default)
            { 
                // Try as an Absolute Hz value 
                fResult = TryParseNumber (sNumber.Substring (0, sNumber.Length - 2), ref number) && (acceptHzRelative || number.SsmlAttributeId == ProsodyNumber.AbsoluteNumber);
                isHz = true; 
            }
            else
            {
                // Must be a relative number 
                fResult = TryParseNumber (sNumber, ref number) && number.SsmlAttributeId == ProsodyNumber.AbsoluteNumber;
            } 
 
            return fResult;
        } 

        /// 
        /// Ensure the this element is properly placed in the SSML markup
        ///  
        /// 
        ///  
        ///  
        private static string ValidateElement (SsmlElement possibleElements, SsmlElement currentElement, string sElement)
        { 
            if ((possibleElements & currentElement) == 0)
            {
                ThrowFormatException (SRID.InvalidElement, sElement);
            } 
            return sElement;
        } 
 
        /// 
        /// Throws an Exception with the error specified by the resource ID. 
        /// 
        /// 
        /// 
        private static void ThrowFormatException (SRID id, params object [] args) 
        {
            throw new FormatException (SR.Get (id, args)); 
        } 

        ///  
        /// Throws an Exception with the error specified by the resource ID.
        /// 
        /// 
        ///  
        /// 
        private static void ThrowFormatException (Exception innerException, SRID id, params object [] args) 
        { 
            throw new FormatException (SR.Get (id, args), innerException);
        } 

        /// 
        /// Non speakable element
        ///  
        private static void NoOp (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmlAttributes, bool fIgnore)
        { 
            // No Children allowed . 
            ProcessElement (reader, engine, null, 0, ssmlAttributes, true, null);
        } 

        private static SsmlElement ElementPromptEngine (SsmlAttributes ssmlAttributes)
        {
            return ssmlAttributes._cPromptOutput > 0 ? SsmlElement.PromptEngineChildren : 0; 
        }
 
        private static int GetColumnPosition (XmlReader reader) 
        {
            XmlTextReader textReader = reader as XmlTextReader; 
            return textReader != null ? textReader.LinePosition - 1 : 0;
        }

        #endregion 

        //******************************************************************* 
        // 
        // Private Types
        // 
        //********************************************************************

        #region Private Types
 
        private struct SsmlAttributes
        { 
            internal object _voice; 
            internal FragmentState _fragmentState;
            internal bool _fRenderDesc; 
            internal VoiceAge _age;
            internal VoiceGender _gender;
            internal string _baseUri;
            internal short _cPromptOutput; // (jeanfp) must be short, otherwise it crashes on the 32 bit version of XP! 
            internal List _unknownNamespaces;
 
            internal bool AddUnknowAttribute (SsmlXmlAttribute attribute, ref List extraAttributes) 
            {
                foreach (SsmlXmlAttribute ns in _unknownNamespaces) 
                {
                    if (ns._name == attribute._prefix)
                    {
                        if (extraAttributes == null) 
                        {
                            extraAttributes = new List (); 
                        } 
                        extraAttributes.Add (attribute);
                        return true; 
                    }
                }
                return false;
            } 

            internal bool AddUnknowAttribute (XmlReader reader, ref List extraAttributes) 
            { 
                foreach (SsmlXmlAttribute ns in _unknownNamespaces)
                { 
                    if (ns._name == reader.Prefix)
                    {
                        if (extraAttributes == null)
                        { 
                            extraAttributes = new List ();
                        } 
                        extraAttributes.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                        return true;
                    } 
                }
                return false;
            }
 
            internal bool IsOtherNamespaceElement (XmlReader reader)
            { 
                foreach (SsmlXmlAttribute ns in _unknownNamespaces) 
                {
                    if (ns._name == reader.Prefix) 
                    {
                        return true;
                    }
                } 
                return false;
            } 
 
        }
 
        private delegate void ParseElementDelegates (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmlAttributes, bool fIgnore);

        #endregion
 
        //********************************************************************
        // 
        // Private Fields 
        //
        //******************************************************************* 

        #region Private Fields

        private static readonly string [] _elementsName = new string [] 
        {
            "audio", 
            "break", 
            "database",
            "desc", 
            "div",
            "emphasis",
            "id",
            "lexicon", 
            "mark",
            "meta", 
            "metadata", 
            "p",
            "paragraph", 
            "phoneme",
            "prompt_output",
            "prosody",
            "rule", 
            "s",
            "say-as", 
            "sentence", 
            "speak",
            "sub", 
            "tts",
            "voice",
            "withtag",
        }; 

        private static readonly ParseElementDelegates [] _parseElements = new ParseElementDelegates [] 
            { 
                new ParseElementDelegates (ParseAudio),
                new ParseElementDelegates (ParseBreak), 
                new ParseElementDelegates (ParseDatabase),
                new ParseElementDelegates (ParseDesc),
                new ParseElementDelegates (ParseDiv),
                new ParseElementDelegates (ParseEmphasis), 
                new ParseElementDelegates (ParseId),
                new ParseElementDelegates (ParseLexicon), 
                new ParseElementDelegates (ParseMark), 
                new ParseElementDelegates (NoOp),
                new ParseElementDelegates (ParseMetaData), 
                new ParseElementDelegates (ParseParagraph),
                new ParseElementDelegates (ParseParagraph),
                new ParseElementDelegates (ParsePhoneme),
                new ParseElementDelegates (ParsePromptOutput), 
                new ParseElementDelegates (ParseProsody),
                new ParseElementDelegates (ParseRule), 
                new ParseElementDelegates (ParseSentence), 
                new ParseElementDelegates (ParseSayAs),
                new ParseElementDelegates (ParseSentence), 
                new ParseElementDelegates (NoOp),
                new ParseElementDelegates (ParseSub),
                new ParseElementDelegates (ParseTts),
                new ParseElementDelegates (ParseVoice), 
                new ParseElementDelegates (ParseWithTag)
            }; 
 
        private static readonly string [] _breakStrength = new string []
        { 
            "medium", "none", "strong", "weak", "x-strong", "x-weak"
        };

        ///  
        /// Must be in the same order as the _breakStrength enumeration
        ///  
        private static readonly EmphasisBreak [] _breakEmphasis = new EmphasisBreak [] 
        {
            EmphasisBreak.Medium, EmphasisBreak.None, EmphasisBreak.Strong, EmphasisBreak.Weak, EmphasisBreak.ExtraStrong, EmphasisBreak.ExtraWeak 
        };

#if SPEECHSERVER
 
        private static readonly string [] _breakSize = new string []
        { 
            "large", "medium", "none", "small" 
        };
 
        /// 
        /// Must be in the same order as the _breakSize enumeration
        /// 
        private static readonly EmphasisBreak [] _breakSizeWord = new EmphasisBreak [] 
        {
            EmphasisBreak.Strong, EmphasisBreak.Medium, EmphasisBreak.None, EmphasisBreak.Weak 
        }; 

#endif 

        private static readonly string [] _emphasisNames = new string []
        {
            "moderate", "none", "reduced", "strong" 
        };
 
        ///  
        /// Must be in the same order as the _emphasisNames enumeration
        ///  
        private static readonly EmphasisWord [] _emphasisWord = new EmphasisWord []
        {
            EmphasisWord.Moderate, EmphasisWord.None, EmphasisWord.Reduced, EmphasisWord.Strong
        }; 

        ///  
        /// Must be in the same order as the _emphasisNames enumeration 
        /// 
        private static readonly int [] _pitchWords = new int [] 
        {
            (int) ProsodyPitch.Default, (int) ProsodyPitch.High, (int) ProsodyPitch.Low, (int) ProsodyPitch.Medium, (int) ProsodyPitch.ExtraHigh, (int) ProsodyPitch.ExtraLow
        };
 
        private static readonly string [] _pitchNames = new string []
        { 
            "default", "high", "low", "medium", "x-high", "x-low", 
        };
 
        /// 
        /// Must be in the same order as the _emphasisNames enumeration
        /// 
        private static readonly int [] _rangeWords = new int [] 
        {
            (int) ProsodyRange.Default, (int) ProsodyRange.High, (int) ProsodyRange.Low, (int) ProsodyRange.Medium, (int) ProsodyRange.ExtraHigh, (int) ProsodyRange.ExtraLow 
        }; 

        private static readonly string [] _rangeNames = new string [] 
        {
            "default", "high", "low", "medium", "x-high", "x-low",
        };
 
        /// 
        /// Must be in the same order as the _emphasisNames enumeration 
        ///  
        private static readonly int [] _rateWords = new int []
        { 
            (int) ProsodyRate.Default, (int) ProsodyRate.Fast, (int) ProsodyRate.Medium, (int) ProsodyRate.Slow, (int) ProsodyRate.ExtraFast, (int) ProsodyRate.ExtraSlow
        };

        private static readonly string [] _rateNames = new string [] 
        {
            "default", "fast", "medium", "slow", "x-fast", "x-slow", 
        }; 

        ///  
        /// Must be in the same order as the _emphasisNames enumeration
        /// 
        private static readonly int [] _volumeWords = new int []
        { 
            (int) ProsodyVolume.Default, (int) ProsodyVolume.Loud, (int) ProsodyVolume.Medium, (int) ProsodyVolume.Silent, (int) ProsodyVolume.Soft, (int) ProsodyVolume.ExtraLoud, (int) ProsodyVolume.ExtraSoft
        }; 
 
        private static readonly string [] _volumeNames = new string []
        { 
            "default", "loud", "medium", "silent", "soft", "x-loud", "x-soft",
        };

        private const string xmlNamespace = "http://www.w3.org/XML/1998/namespace"; 
        private const string xmlNamespaceSsml = "http://www.w3.org/2001/10/synthesis";
        private const string xmlNamespaceXmlns = "http://www.w3.org/2000/xmlns/"; 
        private const string xmlNamespacePrompt = "http://schemas.microsoft.com/Speech/2003/03/PromptEngine"; 

        #endregion 
    }

    internal static class SsmlParserHelpers
    { 
        internal static bool TryConvertAge (string sAge, out VoiceAge age)
        { 
            bool fResult = false; 
            int iAge;
            age = VoiceAge.NotSet; 

            switch (sAge)
            {
                case "child": 
                    age = VoiceAge.Child;
                    break; 
 
                case "teenager":
                case "teen": 
                    age = VoiceAge.Teen;
                    break;

                case "adult": 
                    age = VoiceAge.Adult;
                    break; 
 
                case "elder":
                case "senior": 
                    age = VoiceAge.Senior;
                    break;

            } 
            if (age != VoiceAge.NotSet)
            { 
                fResult = true; 
            }
            else if (Int32.TryParse (sAge, out iAge)) 
            {
                if (iAge <= ((int) VoiceAge.Teen + (int) VoiceAge.Child) / 2)
                {
                    age = VoiceAge.Child; 
                }
                else if (iAge <= ((int) VoiceAge.Adult + (int) VoiceAge.Teen) / 2) 
                { 
                    age = VoiceAge.Teen;
                } 
                else if (iAge <= ((int) VoiceAge.Senior + (int) VoiceAge.Adult) / 2)
                {
                    age = VoiceAge.Adult;
                } 
                else
                { 
                    age = VoiceAge.Senior; 
                }
                fResult = true; 
            }
            return fResult;
        }
 
        internal static bool TryConvertGender (string sGender, out VoiceGender gender)
        { 
            bool fResult = false; 
            gender = VoiceGender.NotSet;
 
            int pos = Array.BinarySearch (_genderNames, sGender);
            if (pos >= 0)
            {
                gender = _genders [pos]; 
                fResult = true;
            } 
            return fResult; 
        }
 
        private static readonly string [] _genderNames = new string []
        {
            "female", "male", "neutral"
        }; 

        ///  
        /// Must be in the same order as the _genderNames enumeration 
        /// 
        private static readonly VoiceGender [] _genders = new VoiceGender [] 
        {
            VoiceGender.Female, VoiceGender.Male, VoiceGender.Neutral
        };
    } 

    //******************************************************************** 
    // 
    // Internal Types
    // 
    //*******************************************************************

    #region Internal Types
 
    [Flags]
    internal enum SsmlElement 
    { 
        Speak = 0x0001,
        Voice = 0x0002, 
        Audio = 0x0004,
        Lexicon = 0x0008,
        Meta = 0x0010,
        MetaData = 0x0020, 
        Sentence = 0x0040,
        Paragraph = 0x0080, 
        SayAs = 0x0100, 
        Phoneme = 0x0200,
        Sub = 0x0400, 
        Emphasis = 0x0800,
        Break = 0x1000,
        Prosody = 0x2000,
        Mark = 0x4000, 
        Desc = 0x8000,
        Text = 0x10000, 
        PromptEngineOutput = 0x20000, 
        PromptEngineDatabase = 0x40000,
        PromptEngineDiv = 0x80000, 
        PromptEngineId = 0x100000,
        PromptEngineTTS = 0x200000,
        PromptEngineWithTag = 0x400000,
        PromptEngineRule = 0x800000, 

        ParagraphOrSentence = Sentence | Paragraph, 
 
        AudioMarkTextWithStyle = Audio | Mark | Break | Emphasis | Phoneme | Prosody | SayAs | Sub | Voice | Text | PromptEngineOutput,
        PromptEngineChildren = PromptEngineDatabase | PromptEngineDiv | PromptEngineId | PromptEngineTTS | PromptEngineWithTag | PromptEngineRule 
    }

    #endregion
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//  
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//
// Description: 
//      The SSml parser parse takes as input an xml string and produces
//      SSML text fragments. 
// 
//      The state information is stored in a structure that is passed by
//      value to the parsing methods for each element. Each of them can 
//      change the state information which will be passed again by value
//      to the children elements. On return to the parsing function,
//      the original values
// 
//      The original ssml string is temporarily stripped out of the CR/LF to
//      have the XML reader to return the position as an indice from the 
//      beginning of the string. This is allowing to have the position 
//      information in the stream that is used in the progress event.
// 
// History:
//		2/1/2005	jeanfp		Created
//----------------------------------------------------------------------------
 
using System;
using System.Collections.Generic; 
using System.Globalization; 
using System.IO;
using System.Net; 
using System.Speech.Synthesis;
using System.Speech.Synthesis.TtsEngine;
using System.Text;
using System.Xml; 

#pragma warning disable 1634, 1691 // Allows suppression of certain PreSharp messages. 
 
namespace System.Speech.Internal.Synthesis
{ 
    internal static class SsmlParser
    {
        //*******************************************************************
        // 
        // Internal Methods
        // 
        //******************************************************************* 

        #region Internal Methods 

        /// 
        /// Parse an SSML stream and build a set of SSML Text Fragments
        ///  
        /// 
        ///  
        ///  
        internal static void Parse (string ssml, ISsmlParser engine, object voice)
        { 
            // Remove the CR and LF
            string ssmlNoCrLf = ssml.Replace ('\n', ' ');
            ssmlNoCrLf = ssmlNoCrLf.Replace ('\r', ' ');
            XmlTextReader reader = new XmlTextReader (new StringReader (ssmlNoCrLf)); 

            // Parse the stream 
            Parse (reader, engine, voice); 
        }
 
        /// 
        /// Parse an SSML stream and build a set of SSML Text Fragments
        /// 
        ///  
        /// 
        ///  
        internal static void Parse (XmlReader reader, ISsmlParser engine, object voice) 
        {
            try 
            {
                bool isSpeakElementFound = false;

                while (reader.Read ()) 
                {
                    // Ignore XmlDeclaration, ProcessingInstruction, Comment, DocumentType, Entity, Notation. 
                    if ((reader.NodeType == XmlNodeType.Element) && (reader.LocalName == "speak")) 
                    {
                        // SSML documents must start with the "speak" element 
                        if (isSpeakElementFound)
                        {
                            ThrowFormatException (SRID.GrammarDefTwice);
                        } 
                        else
                        { 
                            // The XML header is read, real work starts here 
                            ProcessSpeakElement (reader, engine, voice);
                            isSpeakElementFound = true; 
                        }
                    }
                }
 
                if (!isSpeakElementFound)
                { 
                    ThrowFormatException (SRID.SynthesizerNoSpeak); 
                }
            } 
            catch (XmlException eXml)
            {
                throw new FormatException (SR.Get (SRID.InvalidXml), eXml);
            } 
        }
 
        #endregion 

        //******************************************************************* 
        //
        // Private Methods
        //
        //******************************************************************** 

        #region Private Methods 
 
        /// 
        /// Validate the Speak element 
        /// 
        private static void ProcessSpeakElement (XmlReader reader, ISsmlParser engine, object voice)
        {
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            ssmlAttributes._voice = voice;
            ssmlAttributes._age = VoiceAge.NotSet; 
            ssmlAttributes._gender = VoiceGender.NotSet; 
            ssmlAttributes._unknownNamespaces = new List ();
 
            string sVersion = null;
            string sCulture = null;
            string sBaseUri = null;
            CultureInfo culture = null; 
            List extraSpeakAttributes = new List ();
            Exception innerException = null; 
 
            // Process attributes.
            while (reader.MoveToNextAttribute ()) 
            {
                bool isInvalidAttribute = false;

                // emptyNamespace 
                if (reader.NamespaceURI.Length == 0)
                { 
                    switch (reader.LocalName) 
                    {
                        case "version": 
                            CheckForDuplicates (ref sVersion, reader);
                            if (sVersion != "1.0")
                            {
                                ThrowFormatException (SRID.InvalidVersion); 
                            }
                            break; 
 
                        default:
                            isInvalidAttribute = true; 
                            break;
                    }
                }
                else if (reader.NamespaceURI == xmlNamespace) 
                {
                    switch (reader.LocalName) 
                    { 
                        case "lang":
                            CheckForDuplicates (ref sCulture, reader); 
                            try
                            {
                                culture = new CultureInfo (sCulture);
                            } 
                            catch (ArgumentException e)
                            { 
                                // Special case for "es-us", create a new CultureInfo based on attributes of spanish 
                                // and english
                                if (string.Compare ("es-us", sCulture, StringComparison.OrdinalIgnoreCase) == 0) 
                                {
                                    Helpers.CombineCulture ("es-ES", "en-US", new CultureInfo ("es"), 0x540A);
                                    culture = new CultureInfo (sCulture);
                                } 
                                else
                                { 
                                    innerException = e; 
                                    // Unknown Culture info, fall back to the base culture.
                                    int pos = reader.Value.IndexOf ("-", StringComparison.Ordinal); 
                                    if (pos > 0)
                                    {
                                        try
                                        { 
                                            culture = new CultureInfo (reader.Value.Substring (0, pos));
                                        } 
                                        catch (ArgumentException) 
                                        {
                                            isInvalidAttribute = true; 
                                        }
                                    }
                                    else
                                    { 
                                        isInvalidAttribute = true;
                                    } 
                                } 
                            }
                            break; 

                        case "base":
                            CheckForDuplicates (ref sBaseUri, reader);
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                else if (reader.NamespaceURI == xmlNamespaceXmlns)
                {
                    if (reader.Value != xmlNamespaceSsml && reader.Value != xmlNamespacePrompt) 
                    {
                        ssmlAttributes._unknownNamespaces.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                    } 
                    else if (reader.Value == xmlNamespacePrompt)
                    { 
                        engine.ContainsPexml (reader.LocalName);
                    }
                }
                else 
                {
                    extraSpeakAttributes.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                } 

                if (isInvalidAttribute) 
                {
                    ThrowFormatException (innerException, SRID.InvalidElement, reader.Name);
                }
            } 

            if (string.IsNullOrEmpty (sVersion)) 
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "version", "speak");
            } 

            if (string.IsNullOrEmpty (sCulture))
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "lang", "speak"); 
            }
 
            // append the local attributes to list of unknow attributes 
            List extraAttributes = null;
            foreach (SsmlXmlAttribute attribute in extraSpeakAttributes) 
            {
                ssmlAttributes.AddUnknowAttribute (attribute, ref extraAttributes);
            }
 
            voice = engine.ProcessSpeak (sVersion, sBaseUri, culture, ssmlAttributes._unknownNamespaces);
 
            ssmlAttributes._fragmentState.LangId = culture.LCID; 
            ssmlAttributes._voice = voice;
            ssmlAttributes._baseUri = sBaseUri; 

            // Process child elements.
            SsmlElement possibleChild = SsmlElement.Lexicon | SsmlElement.Meta | SsmlElement.MetaData | SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, "speak", possibleChild, ssmlAttributes, false, extraAttributes); 

            // Notify the engine that the element is processed 
            engine.EndSpeakElement (); 
        }
 
        /// 
        /// Generic method to process an SSML element.
        /// The element name is fetch from the element name array and
        /// the delegate for that element will be called. 
        /// 
        private static void ProcessElement (XmlReader reader, ISsmlParser engine, string sElement, SsmlElement possibleElements, SsmlAttributes ssmAttributesParent, bool fIgnore, List extraAttributes) 
        { 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;
 
            // Flush any remaining attributes from the previous element list
            if (extraAttributes != null && extraAttributes.Count > 0) 
            { 
                engine.StartProcessUnknownAttributes (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sElement, extraAttributes);
            } 

            // Move to containing element of attributes
            reader.MoveToElement ();
            if (!reader.IsEmptyElement) 
            {
                // Process each child element while not at end element 
                reader.Read (); 
                do
                { 
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            int iElement = Array.BinarySearch (_elementsName, reader.LocalName); 
                            if (iElement >= 0)
                            { 
                                _parseElements [iElement] (reader, engine, possibleElements, ssmlAttributes, fIgnore); 
                            }
                            else 
                            {
                                // Could be an element from some undefined namespace
                                if (!ssmlAttributes.IsOtherNamespaceElement (reader))
                                { 
                                    ThrowFormatException (SRID.InvalidElement, reader.Name);
                                } 
                                else 
                                {
                                    engine.ProcessUnknownElement (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, reader); 
                                    continue;
                                }
                            }
                            reader.Read (); 
                            break;
 
                        case XmlNodeType.Text: 
                            if ((possibleElements & SsmlElement.Text) != 0)
                            { 
                                engine.ProcessText (reader.Value, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, GetColumnPosition (reader), fIgnore);
                            }
                            else
                            { 
                                ThrowFormatException (SRID.InvalidElement, reader.Name);
                            } 
                            reader.Read (); 
                            break;
 
                        case XmlNodeType.EndElement:
                            break;

                        default: 
                            reader.Read ();
                            break; 
                    } 
                }
                while (reader.NodeType != XmlNodeType.EndElement && reader.NodeType != XmlNodeType.None); 
            }

            // Flush any remaining attributes from the previous element list
            if (extraAttributes != null && extraAttributes.Count > 0) 
            {
                engine.EndProcessUnknownAttributes (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sElement, extraAttributes); 
            } 
        }
 
        private static void ParseAudio (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Audio, reader.Name); 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;

            string sUri = null; 
            bool fRenderDesc = false;
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "src": 
                            CheckForDuplicates (ref sUri, reader); 
                            // Audio element
                            try 
                            {
                                engine.ProcessAudio (ssmlAttributes._voice, sUri, ssmlAttributes._baseUri, fIgnore);
                            }
                            catch (IOException) 
                            {
                                fRenderDesc = true; 
                            } 
                            catch (WebException)
                            { 
                                fRenderDesc = true;
                            }
                            break;
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }
 
            ssmlAttributes._fRenderDesc = fRenderDesc; 

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.Desc | SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, !fRenderDesc, extraAttributes);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseBreak (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Break, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 
            ssmlAttributes._fragmentState.Action = TtsEngineAction.Silence;
            ssmlAttributes._fragmentState.Emphasis = (int) EmphasisBreak.Default;

            string sTime = null; 
            string sStrength = null;
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "time": 
                            { 
                                CheckForDuplicates (ref sTime, reader);
                                ssmlAttributes._fragmentState.Emphasis = (int) EmphasisBreak.None; 
                                ssmlAttributes._fragmentState.Duration = ParseCSS2Time (sTime);
                                isInvalidAttribute = ssmlAttributes._fragmentState.Duration < 0;
                            }
                            break; 

                        case "strength": 
                            CheckForDuplicates (ref sStrength, reader); 
                            if (sTime == null)
                            { 
                                ssmlAttributes._fragmentState.Duration = 0;
                                int pos = Array.BinarySearch (_breakStrength, sStrength);
                                if (pos < 0)
                                { 
                                    isInvalidAttribute = true;
                                } 
                                else 
                                {
                                    // SSML Spec if both strength and time are supplied, ignore strength 
                                    if (ssmlAttributes._fragmentState.Emphasis != (int) EmphasisBreak.None)
                                    {
                                        ssmlAttributes._fragmentState.Emphasis = (int) _breakEmphasis [pos];
                                    } 
                                }
                            } 
                            break; 

#if SPEECHSERVER 
                        case "size":
                            CheckForDuplicates (ref sStrength, reader);
                            if (sTime == null)
                            { 
                                ssmlAttributes._fragmentState.Duration = 0;
                                int posSize = Array.BinarySearch (_breakSize, sStrength); 
                                if (posSize < 0) 
                                {
                                    isInvalidAttribute = true; 
                                }
                                else
                                {
                                    // SSML Spec if both strength and time are supplied, ignore strength 
                                    if (ssmlAttributes._fragmentState.Emphasis != (int) EmphasisBreak.None)
                                    { 
                                        ssmlAttributes._fragmentState.Emphasis = (int) _breakSizeWord [posSize]; 
                                    }
                                } 
                            }
                            break;
#endif
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidSpeakAttribute, reader.Name, "break");
                } 
            }
 
            engine.ProcessBreak (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, (EmphasisBreak) ssmlAttributes._fragmentState.Emphasis, ssmlAttributes._fragmentState.Duration, fIgnore); 

            // No Children allowed . 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);

            // Notify the engine that the element is processed
            engine.EndElement (); 
        }
 
        private static void ParseDesc (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Desc, reader.Name);

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sCulture = null;
            CultureInfo culture = null;
            while (reader.MoveToNextAttribute ())
            { 
                bool isInvalidAttribute = reader.NamespaceURI != xmlNamespace;
 
                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName) 
                    {
                        // The W3C spec says ignore
                        case "lang":
                            CheckForDuplicates (ref sCulture, reader); 
                            try
                            { 
                                culture = new CultureInfo (sCulture); 
                            }
                            catch (ArgumentException) 
                            {
                                isInvalidAttribute = true;
                            }
                            isInvalidAttribute &= culture != null; 
                            break;
 
                        default: 
                            isInvalidAttribute = true;
                            break; 
                    }
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            } 

            engine.ProcessDesc (culture); 

            // Process child elements.
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        private static void ParseEmphasis (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Emphasis, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;

            // Set the default value
            ssmlAttributes._fragmentState.Emphasis = (int) EmphasisWord.Moderate; 

            string sLevel = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;

                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName)
                    { 
                        // The W3C spec says ignore 
                        case "level":
                            CheckForDuplicates (ref sLevel, reader); 
                            int pos = Array.BinarySearch (_emphasisNames, sLevel);
                            if (pos < 0)
                            {
                                isInvalidAttribute = true; 
                            }
                            else 
                            { 
                                ssmlAttributes._fragmentState.Emphasis = (int) _emphasisWord [pos];
                            } 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            engine.ProcessEmphasis (!string.IsNullOrEmpty (sLevel), (EmphasisWord) ssmlAttributes._fragmentState.Emphasis);
 
            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes); 

            // Notify the engine that the element is processed
            engine.EndElement ();
        } 

        private static void ParseMark (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Mark, reader.Name); 

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent; 

            string sName = null; 
            while (reader.MoveToNextAttribute ())
            {
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute) 
                { 
                    switch (reader.LocalName)
                    { 
                        // The W3C spec says ignore
                        case "name":
                            CheckForDuplicates (ref sName, reader);
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 
 
            if (string.IsNullOrEmpty (sName))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "name", "mark");
            }

            ssmlAttributes._fragmentState.Action = TtsEngineAction.Bookmark; 
            engine.ProcessMark (ssmlAttributes._voice, ref ssmlAttributes._fragmentState, sName, fIgnore);
 
            // No Children allowed. 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement ();
        }
 
        private static void ParseMetaData (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            ValidateElement (element, SsmlElement.MetaData, reader.Name);
 
            // No processing for this element, skip
            if (!reader.IsEmptyElement)
            {
                int cEndNode = 1; 
                do
                { 
                    // Loop until we reach the end of the metadata element 
                    reader.Read ();
 
                    // Count the number of elements processed
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        cEndNode++; 
                    }
                    if (reader.NodeType == XmlNodeType.EndElement || reader.NodeType == XmlNodeType.None) 
                    { 
                        cEndNode--;
                    } 
                }
                while (cEndNode > 0);

                // Consume the end element 
                System.Diagnostics.Debug.Assert (reader.NodeType == XmlNodeType.EndElement);
            } 
        } 

        private static void ParseParagraph (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Paragraph, reader.Name);
 
            ParseTextBlock (reader, engine, true, sElement, ssmAttributesParent, fIgnore);
        } 
 
        private static void ParseSentence (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Sentence, reader.Name);

            ParseTextBlock (reader, engine, false, sElement, ssmAttributesParent, fIgnore); 
        }
 
        private static void ParseTextBlock (XmlReader reader, ISsmlParser engine, bool isParagraph, string sElement, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sCulture = null; 
            CultureInfo culture = null;
            while (reader.MoveToNextAttribute ()) 
            {
                bool isInvalidAttribute = reader.NamespaceURI != xmlNamespace;

                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName) 
                    { 
                        // The W3C spec says ignore
                        case "lang": 
                            CheckForDuplicates (ref sCulture, reader);
                            try
                            {
                                culture = new CultureInfo (sCulture); 
                            }
                            catch (ArgumentException) 
                            { 
                                isInvalidAttribute = true;
                            } 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            // Try to change the voice
            bool fNewCulture = culture != null && culture.LCID != ssmlAttributes._fragmentState.LangId; 
            ssmlAttributes._voice = engine.ProcessTextBlock (isParagraph, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, culture, fNewCulture, ssmlAttributes._gender, ssmlAttributes._age); 
            if (fNewCulture)
            { 
                ssmlAttributes._fragmentState.LangId = culture.LCID;
            }

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            if (isParagraph) 
            { 
                possibleChild |= SsmlElement.Sentence;
            } 
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            engine.EndProcessTextBlock (isParagraph);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        private static void ParsePhoneme (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Phoneme, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null; 

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;

            string sAlphabet = null;
            AlphabetType alphabet = AlphabetType.Ipa; 
            string sPh = null;
            char [] aPhoneIds = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;

                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName)
                    { 
                        case "alphabet": 
                            CheckForDuplicates (ref sAlphabet, reader);
                            switch (sAlphabet) 
                            {
                                case "ipa":
                                    alphabet = AlphabetType.Ipa;
                                    break; 

                                case "sapi": 
                                case "x-sapi": 
                                case "x-microsoft-sapi":
                                    alphabet = AlphabetType.Sapi; 
                                    break;

                                case "ups":
                                case "x-ups": 
                                case "x-microsoft-ups":
                                    alphabet = AlphabetType.Ups; 
                                    break; 

                                default: 
                                    throw new FormatException (SR.Get (SRID.UnsupportedAlphabet));
                            }
                            break;
 
                        case "ph":
                            CheckForDuplicates (ref sPh, reader); 
                            break; 

                        default: 
                            isInvalidAttribute = true;
                            break;
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 

            if (string.IsNullOrEmpty (sPh))
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "ph", "phoneme"); 
            }
 
            // Try to convert the phoneme set 
            try
            { 
                switch (alphabet)
                {
                    case AlphabetType.Sapi:
                        aPhoneIds = PhonemeConverter.ConvertPronToId (sPh, ssmlAttributes._fragmentState.LangId).ToCharArray (); 
                        break;
 
                    case AlphabetType.Ups: 
                        aPhoneIds = PhonemeConverter.UpsConverter.ConvertPronToId (sPh).ToCharArray ();
                        alphabet = AlphabetType.Ipa; 
                        break;

                    case AlphabetType.Ipa:
                    default: 
                        aPhoneIds = sPh.ToCharArray ();
                        try 
                        { 
                            PhonemeConverter.ValidateUpsIds (aPhoneIds);
                        } 
                        catch (FormatException)
                        {
                            if (sAlphabet != null)
                            { 
                                throw;
                            } 
                            else 
                            {
                                // try with sapi (backward compatibility) 
                                // if not a sapi phoneme either throw the IPA exception
                                aPhoneIds = PhonemeConverter.ConvertPronToId (sPh, ssmlAttributes._fragmentState.LangId).ToCharArray ();
                                alphabet = AlphabetType.Sapi;
                            } 
                        }
                        break; 
                } 
            }
            catch (FormatException) 
            {
                ThrowFormatException (SRID.InvalidItemAttribute, "phoneme");
            }
 
            engine.ProcessPhoneme (ref ssmlAttributes._fragmentState, alphabet, sPh, aPhoneIds);
 
            // Process child elements. 
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, fIgnore, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement ();
        }
 
        private static void ParseProsody (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Prosody, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 
 
            string sPitch = null;
            string sContour = null; 
            string sRange = null;
            string sRate = null;
            string sDuration = null;
            string sVolume = null; 
            Prosody prosody = ssmlAttributes._fragmentState.Prosody != null ? ssmlAttributes._fragmentState.Prosody.Clone () : new Prosody ();
            while (reader.MoveToNextAttribute ()) 
            { 
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName) 
                    {
                        case "pitch": 
                            isInvalidAttribute = ParseNumberHz (reader, ref sPitch, _pitchNames, _pitchWords, ref prosody._pitch); 
                            break;
 
                        case "range":
                            isInvalidAttribute = ParseNumberHz (reader, ref sRange, _rangeNames, _rangeWords, ref prosody._range);
                            break;
 
                        case "rate":
                            isInvalidAttribute = ParseNumberRelative (reader, ref sRate, _rateNames, _rateWords, ref prosody._rate); 
                            break; 

                        case "volume": 
                            isInvalidAttribute = ParseNumberRelative (reader, ref sVolume, _volumeNames, _volumeWords, ref prosody._volume);
                            break;

                        case "duration": 
                            CheckForDuplicates (ref sDuration, reader);
                            prosody.Duration = ParseCSS2Time (sDuration); 
                            break; 

                        case "contour": 
                            CheckForDuplicates (ref sContour, reader);
                            prosody.SetContourPoints (ParseContour (sContour));
                            if (prosody.GetContourPoints () == null) { isInvalidAttribute = true; }
                            break; 

                        default: 
                            isInvalidAttribute = true; 
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 
 
            if (string.IsNullOrEmpty (sPitch) && string.IsNullOrEmpty (sContour) && string.IsNullOrEmpty (sRange) && string.IsNullOrEmpty (sRate) && string.IsNullOrEmpty (sDuration) && string.IsNullOrEmpty (sVolume))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "pitch, contour, range, rate, duration, volume", "prosody");
            }

            ssmlAttributes._fragmentState.Prosody = prosody; 

            engine.ProcessProsody (sPitch, sRange, sRate, sVolume, sDuration, sContour); 
 
            // Process child elements.
            SsmlElement possibleChild = SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes); 
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            // Notify the engine that the element is processed
            engine.EndElement (); 
        }
 
        private static void ParseSayAs (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.SayAs, reader.Name);

            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 
            List extraAttributes = null;
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sInterpretAs = null;
            string sFormat = null;
            string sDetail = null;
            System.Speech.Synthesis.TtsEngine.SayAs sayAs = new System.Speech.Synthesis.TtsEngine.SayAs (); 
            while (reader.MoveToNextAttribute ())
            { 
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;
 
                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName)
                    { 
                        case "type":
                        case "interpret-as": 
                            CheckForDuplicates (ref sInterpretAs, reader); 
                            sayAs.InterpretAs = sInterpretAs;
                            break; 

                        case "format":
                            CheckForDuplicates (ref sFormat, reader);
                            sayAs.Format = sFormat; 
                            break;
 
                        case "detail": 
                            CheckForDuplicates (ref sDetail, reader);
                            sayAs.Detail = sDetail; 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            if (string.IsNullOrEmpty (sInterpretAs))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "interpret-as", "say-as"); 
            }
 
            // Create SayAs attribute
            ssmlAttributes._fragmentState.SayAs = sayAs;

            engine.ProcessSayAs (sInterpretAs, sFormat, sDetail); 

            // Process child elements. 
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, fIgnore, extraAttributes); 

            // Notify the engine that the element is processed 
            engine.EndElement ();
        }

        private static void ParseSub (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Sub, reader.Name); 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null;

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            string sAlias = null; 
            int textPosition = 0;
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0;
 
                if (!isInvalidAttribute)
                { 
                    switch (reader.LocalName) 
                    {
                        // The W3C spec says ignore 
                        case "alias":
                            CheckForDuplicates (ref sAlias, reader);
                            XmlTextReader textReader = reader as XmlTextReader;
                            if (textReader != null && engine.Ssml != null) 
                            {
                                textPosition = engine.Ssml.IndexOf (reader.Value, textReader.LinePosition + reader.LocalName.Length, StringComparison.Ordinal); 
                            } 
                            break;
 
                        default:
                            isInvalidAttribute = true;
                            break;
                    } 
                }
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes)) 
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }

            if (string.IsNullOrEmpty (sAlias))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "alias", "sub");
            } 
 
            engine.ProcessSub (sAlias, ssmlAttributes._voice, ref ssmlAttributes._fragmentState, textPosition, fIgnore);
 
            // The only allowed children element is text. Ignore it
            ProcessElement (reader, engine, sElement, SsmlElement.Text, ssmlAttributes, true, extraAttributes);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
        private static void ParseVoice (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            // Validate the SSML markup 
            string sElement = ValidateElement (element, SsmlElement.Voice, reader.Name);

            // Cannot have a voice element in a Prompt bout
            if (ssmAttributesParent._cPromptOutput > 0) 
            {
                ThrowFormatException (SRID.InvalidVoiceElementInPromptOutput); 
            } 

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 

            string sCulture = null; 
            string sGender = null; 
            string sVariant = null;
            string sName = null; 
            string sAge = null;
            string xmlns = null;
            CultureInfo culture = null;
            int variant = -1; 

            List extraAttributes = null; 
            List extraAttributesVoice = null; 
            List localUnknownNamespaces = null;
 
            while (reader.MoveToNextAttribute ())
            {
                bool isInvalidAttribute = false;
 
                // empty namespace
                if (reader.NamespaceURI.Length == 0) 
                { 
                    switch (reader.LocalName)
                    { 
                        case "gender":
                            CheckForDuplicates (ref sGender, reader);
                            VoiceGender gender;
                            if (!SsmlParserHelpers.TryConvertGender (sGender, out gender)) 
                            {
                                isInvalidAttribute = true; 
                            } 
                            else
                            { 
                                ssmlAttributes._gender = gender;
                            }
                            break;
 
                        case "age":
                            CheckForDuplicates (ref sAge, reader); 
                            VoiceAge age; 
                            if (!SsmlParserHelpers.TryConvertAge (sAge, out age))
                            { 
                                isInvalidAttribute = true;
                            }
                            else
                            { 
                                ssmlAttributes._age = age;
                            } 
                            break; 

                        case "variant": 
                            // Ignore this field. We have no way with the current tokens to
                            // use it
                            CheckForDuplicates (ref sVariant, reader);
                            if (!int.TryParse (sVariant, out variant) || variant <= 0) 
                            {
                                isInvalidAttribute = true; 
                            } 
                            break;
 
                        case "name":
                            CheckForDuplicates (ref sName, reader);
                            break;
 
                        default:
                            isInvalidAttribute = true; 
                            break; 
                    }
                } 
                else
                {
                    if (reader.Prefix == "xmlns" && reader.Value == xmlNamespacePrompt)
                    { 
                        CheckForDuplicates (ref xmlns, reader);
                    } 
                    else 
                    {
                        if (reader.NamespaceURI == xmlNamespace) 
                        {
                            switch (reader.LocalName)
                            {
                                // The W3C spec says ignore 
                                case "lang":
                                    CheckForDuplicates (ref sCulture, reader); 
                                    try 
                                    {
                                        culture = new CultureInfo (sCulture); 
                                    }
                                    catch (ArgumentException)
                                    {
                                        isInvalidAttribute = true; 
                                    }
                                    break; 
 
                                default:
                                    isInvalidAttribute = true; 
                                    break;
                            }
                        }
                        else if (reader.NamespaceURI == xmlNamespaceXmlns) 
                        {
                            if (reader.Value != xmlNamespaceSsml) 
                            { 
                                if (localUnknownNamespaces == null)
                                { 
                                    localUnknownNamespaces = new List ();
                                }

                                SsmlXmlAttribute ns = new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI); 
                                localUnknownNamespaces.Add (ns);
                                ssmlAttributes._unknownNamespaces.Add (ns); 
                            } 
                        }
                        else 
                        {
                            if (extraAttributesVoice == null)
                            {
                                extraAttributesVoice = new List (); 
                            }
                            extraAttributesVoice.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                        } 
                    }
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                } 
            }
 
            // append the local attributes to list of unknow attributes 
            if (extraAttributesVoice != null)
            { 
                foreach (SsmlXmlAttribute attribute in extraAttributesVoice)
                {
                    ssmlAttributes.AddUnknowAttribute (attribute, ref extraAttributes);
                } 
            }
 
            if (string.IsNullOrEmpty (sCulture) && string.IsNullOrEmpty (sGender) && string.IsNullOrEmpty (sAge) && string.IsNullOrEmpty (sVariant) && string.IsNullOrEmpty (sName) && string.IsNullOrEmpty (xmlns)) 
            {
                ThrowFormatException (SRID.MissingRequiredAttribute, "'xml:lang' or 'gender' or 'age' or 'variant' or 'name'", "voice"); 
            }

            // Try to change the voice
            culture = culture == null ? new CultureInfo (ssmlAttributes._fragmentState.LangId) : culture; 
            bool fNewCulture = culture.LCID != ssmlAttributes._fragmentState.LangId;
            ssmlAttributes._voice = engine.ProcessVoice (sName, culture, ssmlAttributes._gender, ssmlAttributes._age, variant, fNewCulture, localUnknownNamespaces); 
            ssmlAttributes._fragmentState.LangId = culture.LCID; 

            // Process child elements. 
            SsmlElement possibleChild = SsmlElement.ParagraphOrSentence | SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes);
            ProcessElement (reader, engine, sElement, possibleChild, ssmlAttributes, fIgnore, extraAttributes);

            // remove the local namespaces 
            if (localUnknownNamespaces != null)
            { 
                foreach (SsmlXmlAttribute ns in localUnknownNamespaces) 
                {
                    ssmlAttributes._unknownNamespaces.Remove (ns); 
                }
            }

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseLexicon (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.Lexicon, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
            List extraAttributes = null; 
 
            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent; 

            string sUri = null;
            string sMediaType = null;
            while (reader.MoveToNextAttribute ()) 
            {
                // Namespace must be empty 
                bool isInvalidAttribute = reader.NamespaceURI.Length != 0; 

                if (!isInvalidAttribute) 
                {
                    switch (reader.LocalName)
                    {
                        case "uri": 
                            CheckForDuplicates (ref sUri, reader);
                            break; 
 
                        case "type":
                            CheckForDuplicates (ref sMediaType, reader); 
                            break;

                        default:
                            isInvalidAttribute = true; 
                            break;
                    } 
                } 
                if (isInvalidAttribute && !ssmlAttributes.AddUnknowAttribute (reader, ref extraAttributes))
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name);
                }
            }
 
            if (string.IsNullOrEmpty (sUri))
            { 
                ThrowFormatException (SRID.MissingRequiredAttribute, "uri", "lexicon"); 
            }
 
            // Add the base path if it exist
            Uri uri = new Uri (sUri, UriKind.RelativeOrAbsolute);
            if (!uri.IsAbsoluteUri && ssmlAttributes._baseUri != null)
            { 
                sUri = ssmlAttributes._baseUri + '/' + sUri;
                uri = new Uri (sUri, UriKind.RelativeOrAbsolute); 
            } 

            engine.ProcessLexicon (uri, sMediaType); 

            // No Children allowed.
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, true, extraAttributes);
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        } 

        #region Prompt Engine 

        private delegate bool ProcessPromptEngine0 (object voice);
        private delegate bool ProcessPromptEngine1 (object voice, string value);
 
        private static void ParsePromptEngine0 (XmlReader reader, ISsmlParser engine, SsmlElement elementAllowed, SsmlElement element, ProcessPromptEngine0 process, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup 
            string sElement = ValidateElement (elementAllowed, element, reader.Name);
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();

            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            // No attributes allowed 
            while (reader.MoveToNextAttribute ())
            { 
                if (reader.NamespaceURI == xmlNamespaceXmlns && reader.Value == xmlNamespacePrompt)
                {
                    engine.ContainsPexml (reader.LocalName);
                } 
                else
                { 
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                }
            } 

            // Notify the engine that the element is processed
            if (!process (ssmlAttributes._voice))
            { 
                ThrowFormatException (SRID.InvalidElement, reader.Name);
            } 
 
            // Process Children
            ProcessElement (reader, engine, sElement, SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes), ssmlAttributes, fIgnore, null); 
        }

        private static string ParsePromptEngine1 (XmlReader reader, ISsmlParser engine, SsmlElement elementAllowed, SsmlElement element, string attribute, ProcessPromptEngine1 process, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (elementAllowed, element, reader.Name); 
 
            // Make a local copy of the ssmlAttribute
            SsmlAttributes ssmlAttributes = new SsmlAttributes (); 

            // This is equivalent to a memcpy
            ssmlAttributes = ssmAttributesParent;
 
            // 1 attribute
            string value = null; 
            while (reader.MoveToNextAttribute ()) 
            {
                if (reader.LocalName == attribute) 
                {
                    CheckForDuplicates (ref value, reader);
                }
                else 
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                } 
            }
 
            // Notify the engine that the element is processed
            if (!process (ssmlAttributes._voice, value))
            {
                ThrowFormatException (SRID.InvalidElement, reader.Name); 
            }
 
            // No Children allowed 
            ProcessElement (reader, engine, sElement, SsmlElement.AudioMarkTextWithStyle | ElementPromptEngine (ssmlAttributes), ssmlAttributes, fIgnore, null);
            return value; 
        }

        private static void ParsePromptOutput (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Increase the ref count for the Prompt output
            ssmAttributesParent._cPromptOutput++; 
 
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineOutput, new ProcessPromptEngine0 (engine.BeginPromptEngineOutput), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed
            engine.EndElement ();

            // Decrease the ref count for the Prompt output 
            ssmAttributesParent._cPromptOutput--;
            engine.EndPromptEngineOutput (ssmAttributesParent._voice); 
        } 

        private static void ParseDiv (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineDiv, new ProcessPromptEngine0 (engine.ProcessPromptEngineDiv), ssmAttributesParent, fIgnore);

            // Notify the engine that the element is processed 
            engine.EndElement ();
        } 
 
        private static void ParseDatabase (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            // Validate the SSML markup
            string sElement = ValidateElement (element, SsmlElement.PromptEngineDatabase, reader.Name);

            // Make a local copy of the ssmlAttribute 
            SsmlAttributes ssmlAttributes = new SsmlAttributes ();
 
            // This is equivalent to a memcpy 
            ssmlAttributes = ssmAttributesParent;
 
            // No attributes allowed
            string fname = null;
            string delta = null;
            string idset = null; 
            while (reader.MoveToNextAttribute ())
            { 
                // Namespace must be empty 
                bool isInvalidAttribute = false;
 
                if (!isInvalidAttribute)
                {
                    switch (reader.LocalName)
                    { 
                        case "fname":
                            CheckForDuplicates (ref fname, reader); 
                            break; 

                        case "idset": 
                            CheckForDuplicates (ref idset, reader);
                            break;

                        case "delta": 
                            CheckForDuplicates (ref delta, reader);
                            break; 
 
                        default:
                            isInvalidAttribute = true; 
                            break;
                    }
                }
                if (isInvalidAttribute) 
                {
                    ThrowFormatException (SRID.InvalidItemAttribute, reader.Name); 
                } 
            }
            // Notify the engine that the element is processed 
            if (!engine.ProcessPromptEngineDatabase (ssmlAttributes._voice, fname, delta, idset))
            {
                ThrowFormatException (SRID.InvalidElement, reader.Name);
            } 

            // No Children allowed 
            ProcessElement (reader, engine, sElement, 0, ssmlAttributes, fIgnore, null); 

            // Notify the engine that the element is processed 
            engine.EndElement ();
        }

        private static void ParseId (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore) 
        {
            ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineId, "id", new ProcessPromptEngine1 (engine.ProcessPromptEngineId), ssmAttributesParent, fIgnore); 
 
            // Notify the engine that the element is processed
            engine.EndElement (); 
        }

        private static void ParseTts (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            ParsePromptEngine0 (reader, engine, element, SsmlElement.PromptEngineTTS, new ProcessPromptEngine0 (engine.BeginPromptEngineTts), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineTts (ssmAttributesParent._voice); 
        }

        private static void ParseWithTag (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            string tag = ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineWithTag, "tag", new ProcessPromptEngine1 (engine.BeginPromptEngineWithTag), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineWithTag (ssmAttributesParent._voice, tag); 
        }

        private static void ParseRule (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmAttributesParent, bool fIgnore)
        { 
            string name = ParsePromptEngine1 (reader, engine, element, SsmlElement.PromptEngineRule, "name", new ProcessPromptEngine1 (engine.BeginPromptEngineRule), ssmAttributesParent, fIgnore);
 
            // Notify the engine that the element is processed 
            engine.EndElement ();
            engine.EndPromptEngineRule (ssmAttributesParent._voice, name); 
        }

        #endregion
 
        private static void CheckForDuplicates (ref string dest, XmlReader reader)
        { 
            if (!string.IsNullOrEmpty (dest)) 
            {
                StringBuilder attribute = new StringBuilder (reader.LocalName); 
                if (reader.NamespaceURI.Length > 0)
                {
                    attribute.Append (reader.NamespaceURI);
                    attribute.Append (":"); 
                }
                ThrowFormatException (SRID.InvalidAttributeDefinedTwice, reader.Value, attribute); 
            } 
            dest = reader.Value;
        } 

        private static int ParseCSS2Time (string time)
        {
            time = time.Trim (Helpers._achTrimChars); 
            int pos = time.IndexOf ("ms", StringComparison.Ordinal);
            int duration = -1; 
            float fDuration; 
            if (pos > 0 && time.Length == pos + 2)
            { 
                if (!float.TryParse (time.Substring (0, pos), out fDuration))
                {
                    duration = -1;
                } 
                else
                { 
                    duration = (int) (fDuration + 0.5); 
                }
            } 
            else
                if ((pos = time.IndexOf ('s')) > 0 && time.Length == pos + 1)
                {
                    if (!float.TryParse (time.Substring (0, pos), out fDuration)) 
                    {
                        duration = -1; 
                    } 
                    else
                    { 
                        duration = (int) (fDuration * 1000);
                    }
                }
            return duration; 
        }
 
        private static ContourPoint [] ParseContour (string contour) 
        {
            char [] achContour = contour.ToCharArray (); 
            List points = new List ();
            int start = 0;

            try 
            {
                while (start < achContour.Length) 
                { 
                    bool percent, ignored, hz;
                    // Form is (0%, +20Hz) 
                    if ((start = NextChar (achContour, start, '(', false, out ignored)) < 0)
                    {
                        // End of the string found exit
                        break; 
                    }
 
                    int comma = NextChar (achContour, start, ',', true, out percent); 
                    int parenthesis = NextChar (achContour, comma, ')', true, out ignored);
 
                    ProsodyNumber timePosition = new ProsodyNumber ();
                    ProsodyNumber target = new ProsodyNumber ();

                    // Parse the 2 numbers 
                    if (!percent || !TryParseNumber (contour.Substring (start, comma - (start + 1)), ref timePosition) || timePosition.SsmlAttributeId == ProsodyNumber.AbsoluteNumber)
                    { 
                        return null; 
                    }
                    if (!TryParseHz (contour.Substring (comma, parenthesis - (comma + 1)), ref target, true, out hz)) 
                    {
                        return null;
                    }
 
                    // First point
                    if (points.Count == 0) 
                    { 
                        // fake a zero entry if none is provided by duplicating the first entry
                        if (timePosition.Number > 0 && timePosition.Number < 100) 
                        {
                            points.Add (new ContourPoint (0, target.Number, ContourPointChangeType.Hz));
                        }
                    } 
                    else
                    { 
                        // Accept only increasing start points 
                        // Add a 100% if necessary
                        if (points [points.Count - 1].Start > timePosition.Number) 
                        {
                            return null;
                        }
                    } 

                    if (timePosition.Number >= 0 && timePosition.Number <= 1) 
                    { 
                        points.Add (new ContourPoint (timePosition.Number, target.Number, (hz ? ContourPointChangeType.Hz : ContourPointChangeType.Percentage)));
                    } 
                    start = parenthesis;
                }
            }
            catch (InvalidOperationException) 
            {
                return null; 
            } 

            if (points.Count < 1) 
            {
                return null;
            }
 
            // Add a 100% if necessary
            if (!points [points.Count - 1].Start.Equals (1.0)) 
            { 
                points.Add (new ContourPoint (1, points [points.Count - 1].Change, points [points.Count - 1].ChangeType));
            } 
            return points.ToArray ();
        }

        private static int NextChar (char [] ach, int start, char expected, bool skipDigit, out bool percent) 
        {
            percent = false; 
 
            // skip the whitespace
            while (start < ach.Length && (ach [start] == ' ' || ach [start] == '\t' || ach [start] == '\n' || ach [start] == '\r')) 
            {
                start++;
            }
 
            // skip the digits
            if (skipDigit) 
            { 
                while (start < ach.Length && ach [start] != expected && ((percent = ach [start] == '%') || char.IsDigit (ach [start]) || ach [start] == 'H' || ach [start] == 'z' || ach [start] == '.' || ach [start] == '+' || ach [start] == '-'))
                { 
                    start++;
                }

                // skip the trailing white spaces 
                while (start < ach.Length && (ach [start] == ' ' || ach [start] == '\t' || ach [start] == '\n' || ach [start] == '\r'))
                { 
                    start++; 
                }
            } 

            // Check if we found the character we wanted
            if (!(start < ach.Length && ach [start] == expected))
            { 
                // Check for the end of the string
                if (!skipDigit && start == ach.Length) 
                { 
                    return -1;
                } 
                // bail out
                throw new InvalidOperationException ();
            }
            return start + 1; 
        }
 
        private static bool ParseNumberHz (XmlReader reader, ref string attribute, string [] attributeValues, int [] attributeConst, ref ProsodyNumber number) 
        {
            bool isInvalidAttribute = false; 
            bool isHz;

            CheckForDuplicates (ref attribute, reader);
            int pos = Array.BinarySearch (attributeValues, attribute); 
            if (pos < 0)
            { 
                if (!TryParseHz (attribute, ref number, false, out isHz)) 
                {
                    isInvalidAttribute = true; 
                }
            }
            else
            { 
                number = new ProsodyNumber (attributeConst [pos]);
            } 
            return isInvalidAttribute; 
        }
 
        private static bool ParseNumberRelative (XmlReader reader, ref string attribute, string [] attributeValues, int [] attributeConst, ref ProsodyNumber number)
        {
            bool isInvalidAttribute = false;
 
            CheckForDuplicates (ref attribute, reader);
            int pos = Array.BinarySearch (attributeValues, attribute); 
            if (pos < 0) 
            {
                if (!TryParseNumber (attribute, ref number)) 
                {
                    isInvalidAttribute = true;
                }
            } 
            else
            { 
                number = new ProsodyNumber (attributeConst [pos]); 
            }
            return isInvalidAttribute; 
        }

        private static bool TryParseNumber (string sNumber, ref ProsodyNumber number)
        { 
            bool fResult = false;
            decimal value = 0; 
 
            // always reset the unit to Default
            number.Unit = ProsodyUnit.Default; 
            sNumber = sNumber.Trim (Helpers._achTrimChars);
            if (!string.IsNullOrEmpty (sNumber))
            {
                if (!decimal.TryParse (sNumber, out value)) 
                {
                    if (sNumber [sNumber.Length - 1] == '%') 
                    { 
                        if (decimal.TryParse (sNumber.Substring (0, sNumber.Length - 1), out value))
                        { 
                            float percent = (float) value / 100f;
                            if (sNumber [0] != '+' && sNumber [0] != '-')
                            {
                                number.Number = number.Number * percent; 
                            }
                            else 
                            { 
                                number.Number += number.Number * (percent);
                            } 

                            fResult = true;
                        }
                    } 
                }
                else 
                { 
                    if (sNumber [0] != '+' && sNumber [0] != '-')
                    { 
                        number.Number = (float) value;
                        number.SsmlAttributeId = ProsodyNumber.AbsoluteNumber;
                    }
                    else 
                    {
                        if (number.IsNumberPercent) 
                        { 
                            number.Number *= (float) value;
                        } 
                        else
                        {
                            number.Number += (float) value;
                        } 
                    }
                    number.IsNumberPercent = false; 
                    fResult = true; 
                }
            } 
            return fResult;
        }

        private static bool TryParseHz (string sNumber, ref ProsodyNumber number, bool acceptHzRelative, out bool isHz) 
        {
            isHz = false; 
 
            // Find the Hz at the end of the number
            bool fResult = false; 
            number.SsmlAttributeId = ProsodyNumber.AbsoluteNumber;
            ProsodyUnit unit = ProsodyUnit.Default;

            sNumber = sNumber.Trim (Helpers._achTrimChars); 
            if (sNumber.IndexOf ("Hz", StringComparison.Ordinal) == sNumber.Length - 2)
            { 
                unit = ProsodyUnit.Hz; 
            }
            else if (sNumber.IndexOf ("st", StringComparison.Ordinal) == sNumber.Length - 2) 
            {
                unit = ProsodyUnit.Semitone;
            }
 
            if (unit != ProsodyUnit.Default)
            { 
                // Try as an Absolute Hz value 
                fResult = TryParseNumber (sNumber.Substring (0, sNumber.Length - 2), ref number) && (acceptHzRelative || number.SsmlAttributeId == ProsodyNumber.AbsoluteNumber);
                isHz = true; 
            }
            else
            {
                // Must be a relative number 
                fResult = TryParseNumber (sNumber, ref number) && number.SsmlAttributeId == ProsodyNumber.AbsoluteNumber;
            } 
 
            return fResult;
        } 

        /// 
        /// Ensure the this element is properly placed in the SSML markup
        ///  
        /// 
        ///  
        ///  
        private static string ValidateElement (SsmlElement possibleElements, SsmlElement currentElement, string sElement)
        { 
            if ((possibleElements & currentElement) == 0)
            {
                ThrowFormatException (SRID.InvalidElement, sElement);
            } 
            return sElement;
        } 
 
        /// 
        /// Throws an Exception with the error specified by the resource ID. 
        /// 
        /// 
        /// 
        private static void ThrowFormatException (SRID id, params object [] args) 
        {
            throw new FormatException (SR.Get (id, args)); 
        } 

        ///  
        /// Throws an Exception with the error specified by the resource ID.
        /// 
        /// 
        ///  
        /// 
        private static void ThrowFormatException (Exception innerException, SRID id, params object [] args) 
        { 
            throw new FormatException (SR.Get (id, args), innerException);
        } 

        /// 
        /// Non speakable element
        ///  
        private static void NoOp (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmlAttributes, bool fIgnore)
        { 
            // No Children allowed . 
            ProcessElement (reader, engine, null, 0, ssmlAttributes, true, null);
        } 

        private static SsmlElement ElementPromptEngine (SsmlAttributes ssmlAttributes)
        {
            return ssmlAttributes._cPromptOutput > 0 ? SsmlElement.PromptEngineChildren : 0; 
        }
 
        private static int GetColumnPosition (XmlReader reader) 
        {
            XmlTextReader textReader = reader as XmlTextReader; 
            return textReader != null ? textReader.LinePosition - 1 : 0;
        }

        #endregion 

        //******************************************************************* 
        // 
        // Private Types
        // 
        //********************************************************************

        #region Private Types
 
        private struct SsmlAttributes
        { 
            internal object _voice; 
            internal FragmentState _fragmentState;
            internal bool _fRenderDesc; 
            internal VoiceAge _age;
            internal VoiceGender _gender;
            internal string _baseUri;
            internal short _cPromptOutput; // (jeanfp) must be short, otherwise it crashes on the 32 bit version of XP! 
            internal List _unknownNamespaces;
 
            internal bool AddUnknowAttribute (SsmlXmlAttribute attribute, ref List extraAttributes) 
            {
                foreach (SsmlXmlAttribute ns in _unknownNamespaces) 
                {
                    if (ns._name == attribute._prefix)
                    {
                        if (extraAttributes == null) 
                        {
                            extraAttributes = new List (); 
                        } 
                        extraAttributes.Add (attribute);
                        return true; 
                    }
                }
                return false;
            } 

            internal bool AddUnknowAttribute (XmlReader reader, ref List extraAttributes) 
            { 
                foreach (SsmlXmlAttribute ns in _unknownNamespaces)
                { 
                    if (ns._name == reader.Prefix)
                    {
                        if (extraAttributes == null)
                        { 
                            extraAttributes = new List ();
                        } 
                        extraAttributes.Add (new SsmlXmlAttribute (reader.Prefix, reader.LocalName, reader.Value, reader.NamespaceURI)); 
                        return true;
                    } 
                }
                return false;
            }
 
            internal bool IsOtherNamespaceElement (XmlReader reader)
            { 
                foreach (SsmlXmlAttribute ns in _unknownNamespaces) 
                {
                    if (ns._name == reader.Prefix) 
                    {
                        return true;
                    }
                } 
                return false;
            } 
 
        }
 
        private delegate void ParseElementDelegates (XmlReader reader, ISsmlParser engine, SsmlElement element, SsmlAttributes ssmlAttributes, bool fIgnore);

        #endregion
 
        //********************************************************************
        // 
        // Private Fields 
        //
        //******************************************************************* 

        #region Private Fields

        private static readonly string [] _elementsName = new string [] 
        {
            "audio", 
            "break", 
            "database",
            "desc", 
            "div",
            "emphasis",
            "id",
            "lexicon", 
            "mark",
            "meta", 
            "metadata", 
            "p",
            "paragraph", 
            "phoneme",
            "prompt_output",
            "prosody",
            "rule", 
            "s",
            "say-as", 
            "sentence", 
            "speak",
            "sub", 
            "tts",
            "voice",
            "withtag",
        }; 

        private static readonly ParseElementDelegates [] _parseElements = new ParseElementDelegates [] 
            { 
                new ParseElementDelegates (ParseAudio),
                new ParseElementDelegates (ParseBreak), 
                new ParseElementDelegates (ParseDatabase),
                new ParseElementDelegates (ParseDesc),
                new ParseElementDelegates (ParseDiv),
                new ParseElementDelegates (ParseEmphasis), 
                new ParseElementDelegates (ParseId),
                new ParseElementDelegates (ParseLexicon), 
                new ParseElementDelegates (ParseMark), 
                new ParseElementDelegates (NoOp),
                new ParseElementDelegates (ParseMetaData), 
                new ParseElementDelegates (ParseParagraph),
                new ParseElementDelegates (ParseParagraph),
                new ParseElementDelegates (ParsePhoneme),
                new ParseElementDelegates (ParsePromptOutput), 
                new ParseElementDelegates (ParseProsody),
                new ParseElementDelegates (ParseRule), 
                new ParseElementDelegates (ParseSentence), 
                new ParseElementDelegates (ParseSayAs),
                new ParseElementDelegates (ParseSentence), 
                new ParseElementDelegates (NoOp),
                new ParseElementDelegates (ParseSub),
                new ParseElementDelegates (ParseTts),
                new ParseElementDelegates (ParseVoice), 
                new ParseElementDelegates (ParseWithTag)
            }; 
 
        private static readonly string [] _breakStrength = new string []
        { 
            "medium", "none", "strong", "weak", "x-strong", "x-weak"
        };

        ///  
        /// Must be in the same order as the _breakStrength enumeration
        ///  
        private static readonly EmphasisBreak [] _breakEmphasis = new EmphasisBreak [] 
        {
            EmphasisBreak.Medium, EmphasisBreak.None, EmphasisBreak.Strong, EmphasisBreak.Weak, EmphasisBreak.ExtraStrong, EmphasisBreak.ExtraWeak 
        };

#if SPEECHSERVER
 
        private static readonly string [] _breakSize = new string []
        { 
            "large", "medium", "none", "small" 
        };
 
        /// 
        /// Must be in the same order as the _breakSize enumeration
        /// 
        private static readonly EmphasisBreak [] _breakSizeWord = new EmphasisBreak [] 
        {
            EmphasisBreak.Strong, EmphasisBreak.Medium, EmphasisBreak.None, EmphasisBreak.Weak 
        }; 

#endif 

        private static readonly string [] _emphasisNames = new string []
        {
            "moderate", "none", "reduced", "strong" 
        };
 
        ///  
        /// Must be in the same order as the _emphasisNames enumeration
        ///  
        private static readonly EmphasisWord [] _emphasisWord = new EmphasisWord []
        {
            EmphasisWord.Moderate, EmphasisWord.None, EmphasisWord.Reduced, EmphasisWord.Strong
        }; 

        ///  
        /// Must be in the same order as the _emphasisNames enumeration 
        /// 
        private static readonly int [] _pitchWords = new int [] 
        {
            (int) ProsodyPitch.Default, (int) ProsodyPitch.High, (int) ProsodyPitch.Low, (int) ProsodyPitch.Medium, (int) ProsodyPitch.ExtraHigh, (int) ProsodyPitch.ExtraLow
        };
 
        private static readonly string [] _pitchNames = new string []
        { 
            "default", "high", "low", "medium", "x-high", "x-low", 
        };
 
        /// 
        /// Must be in the same order as the _emphasisNames enumeration
        /// 
        private static readonly int [] _rangeWords = new int [] 
        {
            (int) ProsodyRange.Default, (int) ProsodyRange.High, (int) ProsodyRange.Low, (int) ProsodyRange.Medium, (int) ProsodyRange.ExtraHigh, (int) ProsodyRange.ExtraLow 
        }; 

        private static readonly string [] _rangeNames = new string [] 
        {
            "default", "high", "low", "medium", "x-high", "x-low",
        };
 
        /// 
        /// Must be in the same order as the _emphasisNames enumeration 
        ///  
        private static readonly int [] _rateWords = new int []
        { 
            (int) ProsodyRate.Default, (int) ProsodyRate.Fast, (int) ProsodyRate.Medium, (int) ProsodyRate.Slow, (int) ProsodyRate.ExtraFast, (int) ProsodyRate.ExtraSlow
        };

        private static readonly string [] _rateNames = new string [] 
        {
            "default", "fast", "medium", "slow", "x-fast", "x-slow", 
        }; 

        ///  
        /// Must be in the same order as the _emphasisNames enumeration
        /// 
        private static readonly int [] _volumeWords = new int []
        { 
            (int) ProsodyVolume.Default, (int) ProsodyVolume.Loud, (int) ProsodyVolume.Medium, (int) ProsodyVolume.Silent, (int) ProsodyVolume.Soft, (int) ProsodyVolume.ExtraLoud, (int) ProsodyVolume.ExtraSoft
        }; 
 
        private static readonly string [] _volumeNames = new string []
        { 
            "default", "loud", "medium", "silent", "soft", "x-loud", "x-soft",
        };

        private const string xmlNamespace = "http://www.w3.org/XML/1998/namespace"; 
        private const string xmlNamespaceSsml = "http://www.w3.org/2001/10/synthesis";
        private const string xmlNamespaceXmlns = "http://www.w3.org/2000/xmlns/"; 
        private const string xmlNamespacePrompt = "http://schemas.microsoft.com/Speech/2003/03/PromptEngine"; 

        #endregion 
    }

    internal static class SsmlParserHelpers
    { 
        internal static bool TryConvertAge (string sAge, out VoiceAge age)
        { 
            bool fResult = false; 
            int iAge;
            age = VoiceAge.NotSet; 

            switch (sAge)
            {
                case "child": 
                    age = VoiceAge.Child;
                    break; 
 
                case "teenager":
                case "teen": 
                    age = VoiceAge.Teen;
                    break;

                case "adult": 
                    age = VoiceAge.Adult;
                    break; 
 
                case "elder":
                case "senior": 
                    age = VoiceAge.Senior;
                    break;

            } 
            if (age != VoiceAge.NotSet)
            { 
                fResult = true; 
            }
            else if (Int32.TryParse (sAge, out iAge)) 
            {
                if (iAge <= ((int) VoiceAge.Teen + (int) VoiceAge.Child) / 2)
                {
                    age = VoiceAge.Child; 
                }
                else if (iAge <= ((int) VoiceAge.Adult + (int) VoiceAge.Teen) / 2) 
                { 
                    age = VoiceAge.Teen;
                } 
                else if (iAge <= ((int) VoiceAge.Senior + (int) VoiceAge.Adult) / 2)
                {
                    age = VoiceAge.Adult;
                } 
                else
                { 
                    age = VoiceAge.Senior; 
                }
                fResult = true; 
            }
            return fResult;
        }
 
        internal static bool TryConvertGender (string sGender, out VoiceGender gender)
        { 
            bool fResult = false; 
            gender = VoiceGender.NotSet;
 
            int pos = Array.BinarySearch (_genderNames, sGender);
            if (pos >= 0)
            {
                gender = _genders [pos]; 
                fResult = true;
            } 
            return fResult; 
        }
 
        private static readonly string [] _genderNames = new string []
        {
            "female", "male", "neutral"
        }; 

        ///  
        /// Must be in the same order as the _genderNames enumeration 
        /// 
        private static readonly VoiceGender [] _genders = new VoiceGender [] 
        {
            VoiceGender.Female, VoiceGender.Male, VoiceGender.Neutral
        };
    } 

    //******************************************************************** 
    // 
    // Internal Types
    // 
    //*******************************************************************

    #region Internal Types
 
    [Flags]
    internal enum SsmlElement 
    { 
        Speak = 0x0001,
        Voice = 0x0002, 
        Audio = 0x0004,
        Lexicon = 0x0008,
        Meta = 0x0010,
        MetaData = 0x0020, 
        Sentence = 0x0040,
        Paragraph = 0x0080, 
        SayAs = 0x0100, 
        Phoneme = 0x0200,
        Sub = 0x0400, 
        Emphasis = 0x0800,
        Break = 0x1000,
        Prosody = 0x2000,
        Mark = 0x4000, 
        Desc = 0x8000,
        Text = 0x10000, 
        PromptEngineOutput = 0x20000, 
        PromptEngineDatabase = 0x40000,
        PromptEngineDiv = 0x80000, 
        PromptEngineId = 0x100000,
        PromptEngineTTS = 0x200000,
        PromptEngineWithTag = 0x400000,
        PromptEngineRule = 0x800000, 

        ParagraphOrSentence = Sentence | Paragraph, 
 
        AudioMarkTextWithStyle = Audio | Mark | Break | Emphasis | Phoneme | Prosody | SayAs | Sub | Voice | Text | PromptEngineOutput,
        PromptEngineChildren = PromptEngineDatabase | PromptEngineDiv | PromptEngineId | PromptEngineTTS | PromptEngineWithTag | PromptEngineRule 
    }

    #endregion
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK