| SAPI 5.1
comes with several samples for the .NET platform that use the SpechLib
COM wrapper type library. While playing around with one of these and
some information in Access databases, I realized it would be fairly trivial
to create a speech - enabled Stock quote app using some code I had written
for a previous article, "Screenscraping
RealTime Quotes".
All I needed was a database of all the stock
symbols and company names so that I could take the RecoContext_Recognition
result phrase and use it in a simple SQL Statement to lookup
a valid stock symbol from either a company name or spelled stock symbol
uttered by the user. Then I could get the quote from MSN or Yahoo very
easily, present it in a textbox, and have SAPI speak the information
to the user.
After searching the .NET for a while, I finally landed on a web page
that had a listing of all the US Stock symbols and their respective company
names. While not perfect, it was "good enough", so I prepared it for
import into an Access database by cleaning up all the rows, putting in
delimiters, and so on, in order to be able to bring it into MSAcess correctly.
With that completed, all I really needed to do was some preprocessing
cleanup such as removing spaces and punctuation so the database record
would be found, and I had an app that would let you speak a company name
or symbol. If the
match
is
found
in
the
database,
we use
the code
from
my
Screenscraping
article to go and retrieve the current realtime stock quote info from
Yahoo finance, and speak it back to the user. I present the key methods
below, and you can download the entire app and source code including
the Access database of all US stock symbols below.
Before I get started with this, there is one caveat: You must have
the SAPI 5.1 runtime installed for this to work. The only way to distribute
SAPI 5.1 with your app is to include the SAPI MSM merge modules, and
that's just a little too "large" for an article with a downloadable installer
file
here. So, no SAPI, no speakie! To
get SAPI 5.1 , go here.
The key method we use to capture the recognition event and kick off the
SQL query is as follows:
public void RecoContext_Recognition(int StreamNumber,
object StreamPosition,
SpeechRecognitionType RecognitionType,
ISpeechRecoResult Result)
{
string word=Result.PhraseInfo.GetText(0, -1, true);
if(word.ToUpper()=="CLEAR")
{
recognition_textbox.Clear();
return;
}
DisableSpeechRecognition();
SpeakText(word + " " + "One moment please.");
recognition_textbox.AppendText(GetStockQuote(word));
SpeakText(recognition_textbox.Text);
EnableSpeechRecognition();
} |
You can see that when we have a recognition event, we
use the Result.PhraseInfo.GetText method to isolate
the words the user has uttered as the SAPI engine understands it / them.
We fire the DisableSpeechRecognition method to "stop listening" so we don't
get any more garbage while we look up the stock symbol, and then we SpeakText the formatted stock quote results from the GetStockQuote method.
GetStockQuote, which also calls GetQuote, looks like
this:
private string GetStockQuote (string word)
{
string path=Environment.CurrentDirectory ;
string strConn="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +path+ "\\" +"stocks.mdb" ;
OleDbConnection myConn = new OleDbConnection(strConn) ;
//Make a Select Command
string result=String.Empty;
try
{
string strCom = "Select * from Companies where name like '" + word +"%' or symbol ='" + word + "'";
OleDbCommand myCommand =new OleDbCommand(strCom,myConn);
myConn.Open();
System.Data.OleDb.OleDbDataReader reader;
//Execute the command and get the Data into "reader"
reader=myCommand.ExecuteReader( ) ;
// for now, we'll just do the first record.
//while(reader.Read())
reader.Read();
{
result += reader.GetValue(1).ToString() + " " + GetQuote(reader.GetValue(0).ToString());
}
}
catch (Exception ex)
{
SpeakText("There was an error. Please try again.");
}
return result + "\n\r";
} |
Finally, "GetQuote" is my custom code to go to Yahoo and bring back
the information into an XmlDocument:
public string GetQuote(string sSymbol)
{
XmlDocument xmlDoc=GetXmlYahoo(sSymbol);
string strSymbol = xmlDoc.SelectSingleNode("//StockQuotes/Quote/Symbol").InnerText ;
string strPrice = xmlDoc.SelectSingleNode("//StockQuotes/Quote/Price").InnerText ;
string strChange = xmlDoc.SelectSingleNode("//StockQuotes/Quote/Change").InnerText ;
string strTime = xmlDoc.SelectSingleNode("//StockQuotes/Quote/Time").InnerText ;
if(Convert.ToDouble(strChange) >0)
{
strChange="up " + strChange;
}
else
{
strChange="down " + strChange;
}
string strResult= " symbol " + strSymbol + " Current Trade Price " + strPrice + " Change " + strChange + " at " + strTime;
return strResult;
}
public XmlDocument GetXmlYahoo(string symbolList )
{
string url="http://finance.yahoo.com/q?s=";
url+=symbolList;
url+="&d=e";
WebRequest webRequest = WebRequest.Create(url);
//this.url = url;
string beginStr = "";
try
{
WebResponse webResponse = webRequest.GetResponse();
beginStr = new StreamReader(webResponse.GetResponseStream(),
Encoding.Default).ReadToEnd();
webResponse.Close();
// clean up some YHOO finance "junk" first so Regex matches won't fail
beginStr = beginStr.Replace("\n", "");
beginStr=beginStr.Substring(beginStr.IndexOf("Order Books"));
beginStr=beginStr.Replace("<font color=ff0020>","");
beginStr=beginStr.Replace("</font></font>","</font>");
beginStr=beginStr.Replace("<b>","").Replace("</b>","");
beginStr=beginStr.Replace("<i>","").Replace("</i>","");
}
catch (Exception)
{
beginStr = "";
}
XmlDocument xmlDocument = new XmlDocument();
XmlElement elemQuotes = xmlDocument.CreateElement("StockQuotes");
xmlDocument.AppendChild(elemQuotes);
// match string for our Regex Matches collection
string mainStr = "<td nowrap align=left><font face=arial size=-1><a href=\"(?<href>[^\"]+)\"> (?<symbol>[^<]+)</a></font></td><td nowrap align=center><font face=arial size=-1> (?<time>[^<]+)</font></td><td nowrap><font face=arial size=-1>(?<price>[^>]+) </font></td><td nowrap><font face=arial size=-1>(?<change>[^<]+)</font></td>";
new Regex(mainStr, RegexOptions.Compiled);
IEnumerator iEnumerator = Regex.Matches(beginStr, mainStr).GetEnumerator();
try
{
iEnumerator.MoveNext();
{
Match match = (Match)iEnumerator.Current;
XmlElement elemQuote = xmlDocument.CreateElement("Quote");
XmlElement elemSymbol = xmlDocument.CreateElement("Symbol");
XmlElement elemTime = xmlDocument.CreateElement("Time");
XmlElement elemPrice = xmlDocument.CreateElement("Price");
XmlElement elemChange = xmlDocument.CreateElement("Change");
elemSymbol.InnerText = match.Groups["symbol"].Value;
elemPrice.InnerText = match.Groups["price"].Value.Replace(",", ".");
elemTime.InnerText=match.Groups["time"].Value.Replace(",", ".");
elemChange.InnerText = match.Groups["change"].Value.Replace(",", ".");
elemQuote.AppendChild(elemSymbol);
elemQuote.AppendChild(elemPrice);
elemQuote.AppendChild(elemChange);
elemQuote.AppendChild(elemTime);
xmlDocument.DocumentElement.AppendChild(elemQuote);
}
}
catch(Exception ex)
{ SpeakText("There was an error. Try Again please.");}
return xmlDocument;
}
|
The rest of the methods, which you can find in the downloadable Visual
Studio.NET 2003 Solution, are utility methods and are more or less "self-documenting".
While this is not feature complete (a more complete app would probably
use a custom compiled grammar for recognition, rather than the Dictation
class) its really not too bad for a first stab. Hope you enjoy it.
Download the code that accompanies this article
| Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here. --><-- NOTE: Post QUESTIONS on FORUMS! |  |
Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.
|