// *******************************************************************************************************************
        //   The following functions are instrumental for reading MathML.
        // *******************************************************************************************************************
        /// <summary>
        /// Reads the formula currently rendered in the MathML renderer.  If that formula is invalid, the reading is aborted.
        /// </summary>
        private void ReadXmlFormula()
        {
            // If there is a problem with the formula, abort the reading
            if( LABEL_Warning.Visible == true )
                return;

            // This function sets up the reading of the entire MathML formula, and then reads it.
            PromptBuilder p = new PromptBuilder();

            // Append the Prompt Builders constructed from concatenating all nodes within the
            //   initial <math> element
            for( int i = 0; i < MATHML_Display.MathElement.Arguments.Count; i++ ) {
                p.AppendPromptBuilder( ReadNode( MATHML_Display.MathElement.Arguments.Item( i ) ) );
            }

            // ... and, finally, read the MathML contents.
            StopSpeech();
            Speaker.SpeakAsync( p );
        }
        /// <summary>
        /// This function reads an excerpt of MathML from "LISTBOX_ShowExcerpts".
        /// </summary>
        /// <param name="excerpt">The ID of the MathML excerpt to read</param>
        /// <param name="goDeeper">True to read ID tags recursively, false to read ID tags woodenly</param>
        /// <returns>A PromptBuilder representing a spoken rendering of the specified math excerpt</returns>
        private PromptBuilder ReadText( int excerpt, bool goDeeper )
        {
            PromptBuilder p = new PromptBuilder();

            string excerptAndPrompt = LISTBOX_ShowExcerpts.Items[ excerpt ].ToString();
            string excerptTag = GetXmlWrapper( excerptAndPrompt );
            //excerptTag = excerptTag.Substring( 6 );

            string tx = RetrieveExcerptFromListbox( excerptAndPrompt );

            Char[] c = tx.ToCharArray();

            bool speakMiChar = true;

            // These checks will determine if extra words are needed to delineate that we are in a
            //   special mathML tag
            bool spokeTag = false;
            string textExcerpt = "";

            for( int j = 1; j < ( MasterToken._elemCount ); j++ ) {
                Token t = MasterToken._mathMLelement[ j ];
                if( excerptTag == t._symbol ) {
                    p.AppendText( t._speech );
                    spokeTag = true;
                }
                if( t._symbol == "mtext" )
                    textExcerpt = MasterToken._elemEnglish[ j ][ 0 ];
            } // Check the mo group

            if( !spokeTag )
                p.AppendText( excerptTag );

            p.AppendBreak( new TimeSpan( 0, 0, 0, 0, 250 ) );

            // Check for Text tag; should be read literally
            if( ( excerptTag == "mtext" ) | ( excerptTag == textExcerpt ) ) {
                p.AppendText( tx );
                return p;
            }

            // This function will convert the text in the Textbox to speech
            for( int i = 0; i < ( tx.Length ); i++ ) {
                // Letters convert to an <m:mi> tag
                if( Char.IsLetter( c[ i ] ) & ( Convert.ToInt32( c[ i ] ) < 256 ) ) {			// Greek letters are considered letters!
                    // First, check if this letter begins a function name!
                    speakMiChar = true;

                    for( int j = 0; j < ( MasterToken._miCount ); j++ ) {
                        Token t = MasterToken._mi[ j ] as Token;
                        string sym = t._symbol;

                        if( sym.Length <= ( tx.Length - i ) ) {
                            if( tx.Substring( i, sym.Length ) == sym ) {
                                p.AppendText( t._speech + " " );
                                i = i + sym.Length - 1;
                                speakMiChar = false;
                                break;
                            }
                        }
                    } // Check the mi group

                    if( speakMiChar )
                        p.AppendTextWithHint( Convert.ToString( c[ i ] ) + " ", SayAs.SpellOut );
                } // Letter check

                if( Char.IsDigit( c[ i ] ) || c[ i ] == '.' ) {
                    int startNum = i;
                    int finNum = i;

                    for( finNum = i; finNum < tx.Length; finNum++ )
                        if( !Char.IsDigit( c[ finNum ] ) && c[ finNum ] != '.' && c[ finNum ] != ',' )
                            break;

                    string sayNum = "";

                    for( int k = i; k < finNum; k++ )
                        sayNum += c[ k ];

                    sayNum = Convert.ToDouble( sayNum ).ToString();
                    p.AppendTextWithHint( sayNum + " ", SayAs.Text );

                    i = finNum - 1;
                } // check for Numbers

                if( i < tx.Length ) {

                    // Otherwise, must find the speech text in the Master Token List XML
                    int j = 0;
                    for( j = 0; j < ( MasterToken._moCount ); j++ ) {
                        Token t = MasterToken._mo[ j ] as Token;
                        Char[] sym = t._symbol.ToCharArray();
                        if( c[ i ] == sym[ 0 ] )
                            p.AppendText( t._speech + " " );
                    } // Check the mo group

                    if( speakMiChar ) {
                        for( j = 0; j < ( MasterToken._miCount ); j++ ) {
                            Token t = MasterToken._mi[ j ] as Token;
                            string sym = t._symbol;
                            if( ( i + sym.Length ) < tx.Length )
                                if( tx.Substring( i, sym.Length ) == sym )
                                    p.AppendText( t._speech + " " );
                        } // Check the mi group
                    }

                    // ⊰- Finally, we need to test for Excerpt tags!
                    if( c[ i ] == '⊰' ) {
                        int thisID = Convert.ToInt32( tx.Substring( i + 1, 3 ) );

                        if( goDeeper == true ) {
                            PromptBuilder appendThis = new PromptBuilder();
                            appendThis = ReadText( thisID, true );
                            p.AppendPromptBuilder( appendThis );
                        }
                        else {
                            p.AppendText( "Line " );
                            p.AppendText( thisID.ToString() + " " );
                        }
                        i = i + 4;

                    } // Check for excerpt tags // -⊱

                    p.AppendBreak( new TimeSpan( 0, 0, 0, 0, 125 ) );
                }

            }

            return p;
        }
        /// <summary>
        /// This function reads all text from within an interior node of the MathML construct.
        /// </summary>
        /// <param name="node">The node to recursively process ("0" would read the entire formula)</param>
        /// <returns>A PromptBuilder representing a spoken rendering of the specified MathML node</returns>
        private PromptBuilder ReadNode( System.Xml.XmlNode node )
        {
            // This function reads all text from within an interior node of the MathML construct.  It
            //   is designed to recursively iterate on all non-trivial MathML elements.  In other words,
            //   it reads all <mo>, <mi>, <mn>, <mtext>, and raw text within the node, but recursively
            //   iterates on all children of this node that are not any of the named tags (like <mroot>,
            //   <mfrac>, <msubsup>, etc.)

            TimeSpan shortPause = new TimeSpan( 0, 0, 0, 0, 50 );
            TimeSpan longPause = new TimeSpan( 0, 0, 0, 0, 250 );

            PromptBuilder p = new PromptBuilder();
            p.AppendText( " " );

            // Return an empty PromptBuilder if the node is empty
            if( node == null )
                return p;

            // These elements need to be parsed
            //if (node.Name == "mrow") p.AppendPromptBuilder(ReadNode(node, mo, mi));
            if( node.Name == "mfrac" ) {
                // Fraction
                p.AppendBreak( shortPause );
                p.AppendText( " Start fraction " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " over " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End fraction " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "msqrt" ) {
                // Square Root
                p.AppendBreak( shortPause );
                p.AppendText( " The square root of " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End Square root " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "mroot" ) {
                // Generic Root
                PromptBuilder rootValue = new PromptBuilder();

                p.AppendBreak( shortPause );
                p.AppendText( " Thee " );

                // Read Radical may stylize the radical (square, cube, first, twelfth, etc.)
                rootValue = ReadRadical( node.ChildNodes.Item( 1 ) );

                p.AppendPromptBuilder( rootValue );
                p.AppendBreak( shortPause );

                p.AppendText( " Root of " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End root " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "msub" ) {
                // Subscript
                p.AppendBreak( shortPause );
                p.AppendText( " Subscripted text " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " Subscript " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End subscript " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "msup" ) {
                // Superscript
                if( MENU_Options_ReadAsPower.Checked ) {
                    // Read as a power
                    bool isBaseSimple = TestForSimpleExpression( node.ChildNodes.Item( 0 ) );

                    if( isBaseSimple ) {
                        // Read naturally if the base is simple
                        p.AppendBreak( shortPause );
                        p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );

                        p.AppendPromptBuilder( ReadExponent( node.ChildNodes.Item( 1 ) ) );

                    }
                    else {
                        // Otherwise, read more formally
                        p.AppendBreak( shortPause );
                        p.AppendText( " Power Base" );
                        p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                        p.AppendBreak( shortPause );
                        p.AppendPromptBuilder( ReadExponent( node.ChildNodes.Item( 1 ) ) );
                        p.AppendText( " End power " );
                        p.AppendBreak( longPause );
                    }
                }
                else {
                    // Read very formally as a superscript
                    p.AppendBreak( shortPause );
                    p.AppendText( " Superscripted text " );
                    p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                    p.AppendBreak( shortPause );

                    p.AppendText( " Superscript " );
                    p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                    p.AppendBreak( shortPause );

                    p.AppendText( " End superscript " );
                    p.AppendBreak( longPause );
                }
                return p;
            }
            if( node.Name == "msubsup" ) {
                // Subscript/superscript
                p.AppendBreak( shortPause );
                p.AppendText( " Sub and super scripted text " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " Subscript " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " Superscript " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 2 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End superscript " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "mover" ) {
                // Over
                p.AppendBreak( shortPause );
                p.AppendText( " Start over text " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " over " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End over text " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "munder" ) {
                // Under
                p.AppendBreak( shortPause );
                p.AppendText( " Start under text " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " under " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End under text " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "munderover" ) {
                // Under/Over
                p.AppendBreak( shortPause );
                p.AppendText( " Start over/under text " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 1 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " over " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 2 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " under " );
                p.AppendPromptBuilder( ReadNode( node.ChildNodes.Item( 0 ) ) );
                p.AppendBreak( shortPause );

                p.AppendText( " End over/under text " );
                p.AppendBreak( longPause );
                return p;
            }
            if( node.Name == "mfenced" ) {
                // Fenced (a set of numbers/variables)
                if( ( node.PreviousSibling != null ) && ( node.PreviousSibling.Name == "mi" ) ) {
                    // Read as a Function parameter set
                    p.AppendBreak( shortPause );
                    p.AppendText( " of " );

                    int counter = 1;
                    foreach( XmlNode n in node.ChildNodes ) {
                        p.AppendPromptBuilder( ReadNode( n ) );
                        if ( counter++ < node.ChildNodes.Count )
                            p.AppendText( " and " );
                        //counter++;
                        p.AppendBreak( shortPause );
                    }

                    if ( node.ChildNodes.Count > 1 )
                        p.AppendText( " end parameters " );

                    p.AppendBreak( longPause );
                    return p;
                }
                else {
                    // Read as an ordinary set
                    p.AppendBreak( shortPause );
                    p.AppendText( " Open set " );

                    foreach( XmlNode n in node.ChildNodes ) {
                        p.AppendPromptBuilder( ReadNode( n ) );
                        p.AppendBreak( longPause - shortPause );
                    }

                    p.AppendText( " Close set " );
                    p.AppendBreak( longPause );
                    return p;
                }
            }

            for( int i = 0; i < node.ChildNodes.Count; i++ ) {
                System.Xml.XmlNode readNode = node.ChildNodes.Item( i );

                if( readNode.Name == "#text" ) {
                    p.AppendText( " " + readNode.InnerText + " " );                              // This is straight text
                    p.AppendBreak( shortPause );
                }

                if( readNode.Name == "mn" )
                    p.AppendText( readNode.InnerText + " " );    // This is a number
                if( readNode.Name == "mo" )                                                     // This is an operator
                {
                    // Must search master list for its pronunciation
                    for( int j = 0; j < ( MasterToken._moCount ); j++ ) {
                        Token t = MasterToken._mo[ j ];
                        if( readNode.InnerText == t._symbol ) {
                            // Barf.  Need this hack because Invisible Plus does not render correctly... so it looks like Invisible Times.
                            if( t._symbol == "⁢" ) {			// If t.symbol is Invisible Times, check if it should be Invisible Plus
                                if( ( readNode.PreviousSibling.Name == "mn" ) & ( readNode.NextSibling.Name == "mfrac" ) ) {
                                    // Ah.  In this case, find how to say Invisible Plus
                                    for( int k = 0; k < MasterToken._moCount; k++ )
                                        if( MasterToken._mo[ k ]._symbol == "⁤" )
                                            p.AppendText( MasterToken._mo[ k ]._speech + " " );
                                }
                                else
                                    p.AppendText( t._speech + " " );
                            }
                            else
                                p.AppendText( t._speech + " " );
                        }
                    } // Check the mo group
                }
                if( readNode.Name == "mi" )                                                      // This is a letter or symbol
                {
                    bool speakMi = true;

                    // Check for symbols first... if not found in the master list, the token will be pronouced as a letter
                    for( int j = 0; j < ( MasterToken._miCount ); j++ ) {
                        Token t = MasterToken._mi[ j ];
                        if( readNode.InnerText == t._symbol ) {
                            p.AppendText( t._speech + " " );
                            speakMi = false;
                            break;
                        }
                    } // Check the mi group

                    if( speakMi )
                        p.AppendTextWithHint( Convert.ToString( readNode.InnerText ) + " ", SayAs.SpellOut );

                }

                // Basically, if this node is not named one of these things, then it should be
                //   read now.  This means <mrow> and <mtext> elements.
                if( ( readNode.Name != "mi" ) && ( readNode.Name != "mo" ) && ( readNode.Name != "mn" ) &&
                     ( readNode.Name != "#text" ) )
                    p.AppendPromptBuilder( ReadNode( readNode ) );

            }

            // Make sure that a trailing space is appended to the end of an <mrow> element
            if( node.Name == "mrow" )
                p.AppendText( " " );

            return p;
        }
        /// <summary>
        /// This function is used to read a radical.
        /// </summary>
        /// <param name="node">The node that represents the radical portion of a "mroot" MathML element</param>
        /// <returns>Could be "square", "cube", "n-th", or "x"</returns>
        private PromptBuilder ReadRadical( System.Xml.XmlNode node )
        {
            PromptBuilder p = new PromptBuilder();
            TimeSpan shortPause = new TimeSpan( 0, 0, 0, 0, 50 );

            if( ( node.ChildNodes.Count == 1 ) && ( node.ChildNodes[ 0 ].Name == "mn" ) ) {
                if( node.ChildNodes[ 0 ].InnerText == "2" ) {
                    // Squared
                    p.AppendText( " squared " );
                }
                else if( node.ChildNodes[ 0 ].InnerText == "3" ) {
                    // Cubed
                    p.AppendText( " cube " );
                }
                else if( node.ChildNodes[ 0 ].InnerText.Contains( '.' ) ) {
                    // Contains a decimal... just read it
                    p.AppendPromptBuilder( ReadNode( node ) );
                }
                else {
                    // to the n-th power

                    string wordToRead = AddNumericalSuffix( node.ChildNodes[ 0 ].InnerText );

                    p.AppendText( wordToRead );
                }
            }
            else {
                // If not a simple number, simply read the node
                p.AppendPromptBuilder( ReadNode( node ) );
            }

            return p;
        }
        /// <summary>
        /// If superscripts are read as powers, this function is used to "read" an exponent.
        /// </summary>
        /// <param name="node">The node that represents the exponent of a power (2nd "mrow" argument of "msup" MathML element)</param>
        /// <returns>Could be "squared", "cubed", "to the n power", or "exponent ... end exponent"</returns>
        private PromptBuilder ReadExponent( System.Xml.XmlNode node )
        {
            PromptBuilder p = new PromptBuilder();
            TimeSpan shortPause = new TimeSpan( 0, 0, 0, 0, 50 );

            if( ( node.ChildNodes.Count == 1 ) && ( node.ChildNodes[ 0 ].Name == "mn" ) ) {
                if( node.ChildNodes[ 0 ].InnerText == "2" ) {
                    // Squared
                    p.AppendText( " squared " );
                }
                else if( node.ChildNodes[ 0 ].InnerText == "3" ) {
                    // Cubed
                    p.AppendText( " cubed " );
                }
                else if( node.ChildNodes[ 0 ].InnerText.Contains( '.' ) ) {
                    // to the x power
                    p.AppendText( " to the " );
                    p.AppendPromptBuilder( ReadNode( node ) );
                    p.AppendText( " power " );
                }
                else {
                    // to the n-th power

                    string wordToRead = AddNumericalSuffix( node.ChildNodes[ 0 ].InnerText );

                    p.AppendText( " to the " );
                    p.AppendText( wordToRead );
                    p.AppendText( " power " );
                }
            }
            else if( TestForSimpleExpression( node ) ) {
                // Variable exponent... to the x power
                p.AppendText( " to the " );
                p.AppendPromptBuilder( ReadNode( node ) );
                p.AppendText( " power " );
            }
            else {
                // Complex exponent
                p.AppendText( " exponent " );
                p.AppendPromptBuilder( ReadNode( node ) );
                p.AppendText( " end exponent " );
            }
            p.AppendBreak( shortPause );

            return p;
        }