// ******************************************************************************************************************* // 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; }