Download

/******************************************************************************
 This is the weather by ZIP code demo in which the user speaks the digits of
 the ZIP code whose weather forecast he wants and the HTTP GET transaction is
 performed asynchronously.
****************************************************************************/
#include <winsock2.h>
#include <shlwapi.h>
#include <wchar.h>
#include <string>
#include <iostream>

// Header for the XML parser that we use

#import <msxml4.dll> 
using namespace MSXML2;

// Header for the telephony, text-to-speech and speech recognition classes

#include "ClientCall.h"
using namespace IFB;

// Details for web service which furnishes the content that we speak - the weather forecast

static char szGet[]        = "GET /forecastrss?p=00000 HTTP/1.0\r\n"
                             "Host: weather.yahooapis.com\r\n"
                             "Accept: */*\r\n\r\n"; 

static char szServer[]     = "weather.yahooapis.com";

static WORD        wPort = 80;
static sockaddr_in server; 

// The namespace used in XML document returned  

static wchar_t wszNamespaces[] = L"xmlns:yweather='http://xml.weather.yahoo.com/ns/rss/1.0'";

// Header / payload separator

static char szSeparator[]  = "\r\n\r\n";

// Logical end of message 

static char szTerminator[] = "</rss>";

// The size of the query string

static int  lenGet    = sizeof(szGet) - 1;
static int  offsetGet = 0;

const  int  ZipGrammarId   = 22;   // We use off the wall numbers for the grammar IDs
const  int  QuitGrammarId  = 67;   // to make the point that they are completely arbitrary
const  int  ReqdConfidence = 6500; // Minimum confidence value to accept recognition

// Various prompts that we speak

static wchar_t wszPrompt[]     = L"Please tell me the ZIP code for the town whose weather "
                                 L"forecast you want to hear.";
static wchar_t wszSayWhat[]    = L"I'm sorry, but I didn't get that.";
static wchar_t wszGoodbye[]    = L"Good bye.";
static wchar_t wszWelcome[]    = L"Welcome to the weather demonstration application.";
static wchar_t wszPatience[]   = L"Please wait";
static wchar_t wszRegrets[]    = L"We are sorry but we had trouble determining the weather for that ZIP 

code. "
                                 L"If the ZIP code is valid you can try again later.";

// Function to play "music on hold"

void HoldForEvent(ClientCall &, HANDLE, LPCSTR);

// Function to apologize when we fail to recognize the caller's request

void NoRecognition(Recognizer *, Synthesizer *);

// This structure and the functions whose prototypes are just  
// below should be made into a class. We don't do that because we 
// want to salvage as much of this sample as we can for the C sample

struct HTTPCONTEXT
{
 SOCKET       sock;
 HANDLE       hEvent;
 DWORD        dwSent;
 DWORD        dwRcvd;
 DWORD        dwErrRcv;
 DWORD        dwErrSnd;
 OVERLAPPED   ovl;
 char         szResp[4096];
 char         nullbyte; 
 std::wstring weather;
 bool         iopending;
 char         szGet[1];
};

// Functions to handle the HTTP post to the web service

HTTPCONTEXT * AllocateServiceContext(int, LPCSTR);
void          SetServiceEvent(HTTPCONTEXT *, HANDLE);
void          ConnectToService(HTTPCONTEXT *);
void          MakeServiceRequest(HTTPCONTEXT *, LPCSTR);
void          ReceiveServiceResponse(HTTPCONTEXT *);
bool          FormatServiceResponse(HTTPCONTEXT *);
void          DisconnectFromService(HTTPCONTEXT *, bool);
void          DeallocateServiceContext(HTTPCONTEXT *);
void CALLBACK RecvDone(DWORD, DWORD, WSAOVERLAPPED *, DWORD);
void CALLBACK SendDone(DWORD, DWORD, WSAOVERLAPPED *, DWORD);

// Functions to parse the XML document returned by 
// the web service use MSXML 

std::wstring GetCity(IXMLDOMDocument2Ptr &);
std::wstring GetState(IXMLDOMDocument2Ptr &);
std::wstring GetWind(IXMLDOMDocument2Ptr &);
std::wstring GetOutlook(IXMLDOMDocument2Ptr &);
std::wstring GetTemperature(IXMLDOMDocument2Ptr &);
std::wstring Direction(int);

/******************************************************************************
 Unlike the .Net samples where we use a hash table, we maintain two parallel
 string arrays for translating wind directions to pronounceable phrases so
 that we can use the same implementation in the C sample
******************************************************************************/
LPCWSTR pszStates[52][2] = { L"AL", L"Alabama", 
                             L"AK", L"Alaska",
                             L"AZ", L"Arizona",
                             L"AR", L"Arkansas", 
                             L"CA", L"California",
                             L"CO", L"Colorado",
                             L"CT", L"Connecticut",
                             L"DE", L"Delaware",
                             L"DC", L"D C",
                             L"FL", L"Florida",
                             L"GA", L"Georgia",
                             L"HI", L"Hawaii",
                             L"ID", L"Idaho",
                             L"IL", L"Illinois",
                             L"IN", L"Indiana",
                             L"IA", L"Iowa",
                             L"KS", L"Kansas",
                             L"KY", L"Kentucky",
                             L"LA", L"Louisiana",
                             L"ME", L"Maine",
                             L"MD", L"Maryland",
                             L"MA", L"Massachusetts",
                             L"MI", L"Michigan",
                             L"MN", L"Minnesota",
                             L"MS", L"Mississippi",
                             L"MO", L"Missouri", 
                             L"MT", L"Montana",
                             L"NE", L"Nebraska",
                             L"NV", L"Nevada",
                             L"NH", L"New Hampshire",
                             L"NJ", L"New Jersey",
                             L"NM", L"New Mexico",
                             L"NY", L"New York",
                             L"NC", L"North Carolina",
                             L"ND", L"North Dakota",
                             L"OH", L"Ohio",
                             L"OK", L"Oklahoma",
                             L"OR", L"Oregon",
                             L"PA", L"Pennsylvania",
                             L"PR", L"Puerto Rico",
                             L"RI", L"Rhode Island",
                             L"SC", L"South Carolina",
                             L"SD", L"South Dakota",
                             L"TN", L"Tennessee",
                             L"TX", L"Texas",
                             L"UT", L"Utah",
                             L"VT", L"Vermont",
                             L"VA", L"Virginia",
                             L"WA", L"Washington",
                             L"WV", L"West Virginia",
                             L"WI", L"Wisconsin",
                             L"WY", L"Wyoming" };

/******************************************************************************
 Each digit in the "phrase" recognized is a property of the recognition. We 
 enumerate the propery names here.
******************************************************************************/
LPCSTR pszProperties[5] = { "Digit1",
                            "Digit2",
                            "Digit3",
                            "Digit4",
                            "Digit5" };

/******************************************************************************
 This exported function is called once when the application is first loaded
 by the server or the simulator
******************************************************************************/
BOOL CALLBACK Initialize(HANDLE hShutdown)
{
 char    *p;
 BOOL    bOK;
 hostent *phe;
 
 bOK = FALSE;

 // Get web service address

 phe = gethostbyname(szServer);

 if ( !phe )
  std::cout << "C++ Weather application: could not resolve the web service address." << std::endl;

 else 
 {
  // Save the address of the web service 

  memset(&server, 0, sizeof(server));
  memcpy(&server.sin_addr, phe->h_addr, sizeof(phe->h_addr));

  server.sin_port   = htons(wPort);
  server.sin_family = phe->h_addrtype;

  if ( p = strstr(szGet, "00000") )
  {
   offsetGet = p - szGet;
   bOK = TRUE;
  }
 }
 
 return bOK;
}

/******************************************************************************
 The exported Answer() method comprises the entire voice user interface and
 is called each time a new call arrives on the line bound to this application
******************************************************************************/
void CALLBACK Answer(ClientCall &call)
{
 int         i, conf;
 char        szZip[6] = { 0 };
 bool        done;
 HANDLE      hEvent;
 Recognizer  *prec;
 Synthesizer *psyn;
 HTTPCONTEXT *pctx;

 // We create an instance of the structure used to manage the communication 
 // with the web service. The event we create is signalled each time a network
 // operation to the web service completes

 pctx = AllocateServiceContext(lenGet, szGet);
 hEvent = CreateEvent(0, TRUE, FALSE, 0);
 SetServiceEvent(pctx, hEvent);
 
 try
 {
  // We want an exception if the user hangs up

  call.hangupThrows(true);

  // Get an instance of the recognizer
  // Load a grammar that handles the request for the ZIP code
  // Load another that handles the request to quit the application

  prec = call.getRecognizer();
  prec->loadGrammar("ZIP",  ZipGrammarId);
  prec->loadGrammar("Quit", QuitGrammarId);

  // Get an instance of the synthesizer to speak the greeting and wait for it

  psyn = call.getSynthesizer();
  psyn->speak(wszWelcome);
  psyn->wait();

  done = false;

  while ( !call.isDisconnected() && !done )
  {
   // Start listening first and then prompt the caller 

   prec->listen();
   psyn->speak(wszPrompt);

   // Wait for the recognizer to tell us how it did.
   // We will wait at most 10 seconds for the user to begin to speak.
   // In no case will we wait more than 30 seconds.

   conf = prec->wait(10000, 30000);

   // Stop listening

   prec->stopListening();

   // Dump the state of the recognition for debugging 

   prec->dump();

   // On an error or disconnect we are done 

   if ( conf < 0 )
    done = true;

   // We need at least minimum confidence to proceed. In this application we use 
   // 65% as a confidence threshold for recognition.  You can use whatever makes
   // sense in your application. A more robust application might have two thresholds. 
   // Confidence scores below the lower number might cause the application to 
   // prompt again, confidence scores above the higher number might cause the 
   // application to accept the recognition, and scores between the two numbers 
   // might require confirmation.

   if ( conf >= ReqdConfidence ) 
   {
    // If we recognized a phrase in the Quit grammar then we are done 

    if ( prec->getGrammarId() == QuitGrammarId )
     done = true;

    // Otherwise we need a five digit ZIP code

    else 
    {
     memset(szZip, 0, sizeof(szZip));

     for (i = 0; i < 5; i++ )
      prec->getPropertyText(pszProperties[i], &szZip[i], 2);

     if ( strlen(szZip) != 5 )
      conf = 0;
    }
   }

   // Quit if the user said good bye

   if ( done )
    break;

   // If we don't have the required confidence 
   // we apologize and then try again
   
   else if ( conf < ReqdConfidence )
   {
    NoRecognition(prec, psyn);
    continue;
   }

   // Tell the user to wait

   psyn->speak(wszPatience);

   // Initiate connection to web service
   // Play music on hold while connecting
   
   ConnectToService(pctx);
   HoldForEvent(call, hEvent, "strumming");

   // Initiate connection to web service
   // Play music on hold while posting

   MakeServiceRequest(pctx, szZip);
   HoldForEvent(call, hEvent, "strumming");

   // Initiate reception of web service response
   // Play music on hold while receiving data

   ReceiveServiceResponse(pctx);
   HoldForEvent(call, hEvent, "strumming");

   // Format the response from the web service

   if ( !FormatServiceResponse(pctx) )
    pctx->weather = wszRegrets;

   // Stop the music on hold, wait a bit and speak the forecast
 
   call.stopPlaying();
   call.wait(500);
   psyn->speak( pctx->weather.c_str() );

   // While speaking the weather, disconnect from the web service
   // and update the call detail record for this call

   DisconnectFromService(pctx, false);
   call.cdrStatusMessage(0, szZip);
   call.cdrStatusMessage(1, pctx->weather.c_str() );

   // Let the user hear his forecast before we
   // ask if he wants to go again

   psyn->wait();
  }

  // Be polite and say good bye before we hang up

  psyn->speak(wszGoodbye);
  psyn->wait();
 }

 // If the user hangs up we'll just cancel the web service request

 catch ( ClientCallTermination e )
 {
  DisconnectFromService(pctx, true);
  std::cout << "C++ weather demo exception: " << e.what() << std::endl;
 }

 // Deallocate the event and the context structures that we allocated

 CloseHandle(hEvent);
 DeallocateServiceContext(pctx);
}

/******************************************************************************
 This function plays "music on hold" while a network operation is in progress.

 Because we call this function multiple times we make sure that we don't 
 unnecessarily queue up more messages than we need. That guarantees that we'll 
 never wait for longer than the length of the music clip to be played. It's not 
 at all necessary to do that here. It would be useful if we wanted to impose a 
 timeout on our waits.
******************************************************************************/
void HoldForEvent(ClientCall &call, HANDLE hEvent, LPCSTR pszMusic)
{
 int millis;

 while ( true )
 {
  // No sense carrying on if caller is gone

  if ( call.isDisconnected() )
   break;

  // We end the wait on network events or on errors

  if ( WaitForSingleObject(hEvent, 0) != WAIT_TIMEOUT )
   break;

  // If there is a clip playing we determine how long it is

  millis = call.messageTimeRemaining();

  // Otherwise we queue up a new one and determine its length

  if ( millis == 0 )
   millis = call.playMessage(pszMusic);
   
  // Whether we started it or not, wait for clip to end, or 
  // network event to be signalled, or caller to hang up

  call.wait(hEvent, millis);
 }
}

/******************************************************************************
 We apologize if we don't understand the user 
******************************************************************************/
void NoRecognition(Recognizer *prec, Synthesizer *psyn)
{
 prec->reset();
 prec->listen();

 psyn->speak(wszSayWhat);
 psyn->wait();
}

/******************************************************************************
 This allocates the structure that maintains the state of the HTTP 
 POST to the web service 
******************************************************************************/
HTTPCONTEXT * AllocateServiceContext(int len, LPCSTR pszGet)
{
 int  offset;
 char *p;

 len = sizeof(HTTPCONTEXT) + len;
 p = new char[len];
 memset(p, 0, len);

 offset = offsetof(HTTPCONTEXT, szGet);
 strcpy(p + offset, szGet);

 return (HTTPCONTEXT *) p;
}

/******************************************************************************
 The completion of a network operation signals an event so that the caller 
 can track progress
******************************************************************************/
void SetServiceEvent(HTTPCONTEXT *pctx, HANDLE hEvent)
{
 pctx->hEvent = hEvent;
}

/******************************************************************************
 Attempt to connect to the web service asynchronously
******************************************************************************/
void ConnectToService(HTTPCONTEXT *pctx)
{
 char dont = true;

 pctx->dwRcvd   = 0;
 pctx->dwSent   = 0;
 pctx->dwErrRcv = 0;
 pctx->dwErrSnd = 0;

 memset(&pctx->szResp, 0, sizeof(pctx->szResp));

 pctx->sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 
                        0, 0, WSA_FLAG_OVERLAPPED);

 if ( pctx->sock != INVALID_SOCKET )
 {
  setsockopt(pctx->sock, SOL_SOCKET, SO_DONTLINGER, &dont, sizeof(dont));
  WSAEventSelect(pctx->sock, pctx->hEvent, FD_CONNECT);
  ResetEvent(pctx->hEvent);
  WSAConnect(pctx->sock, (sockaddr *) &server, sizeof(server), 0, 0, 0, 0);
 }
}

/******************************************************************************
 Make a request of the web service by appending the ZIP code to the POST, 
 then send it asynchronously
******************************************************************************/
void MakeServiceRequest(HTTPCONTEXT *pctx, LPCSTR pszZip)
{
 DWORD  dwErr;
 WSABUF buf;

 memcpy(&pctx->szGet[offsetGet], pszZip, strlen(pszZip));

 buf.buf = &pctx->szGet[0];
 buf.len = lenGet;

 memset(&pctx->ovl, 0, sizeof(pctx->ovl));
 pctx->ovl.hEvent = (HANDLE) pctx;

 pctx->iopending = true;
 ResetEvent(pctx->hEvent);

 if ( WSASend(pctx->sock, &buf, 1, &pctx->dwSent, 0, &pctx->ovl, &SendDone) != 0 )
  if ( (dwErr = WSAGetLastError()) != WSA_IO_PENDING )
  {
   pctx->iopending = false;
   pctx->dwErrSnd = dwErr;
   SetEvent(pctx->hEvent);
  }

 return;
}

/******************************************************************************
 Initiate the asynchronous reception of the response from the web service
******************************************************************************/
void ReceiveServiceResponse(HTTPCONTEXT *pctx)
{
 DWORD  dwErr, dwFlags, dwRcvd;
 WSABUF buf;

 if ( (pctx->dwSent == 0) || (pctx->dwErrSnd != 0) )
  SetEvent(pctx->hEvent);

 else
 {
  buf.buf = &pctx->szResp[0] + pctx->dwRcvd;
  buf.len = sizeof(pctx->szResp) - pctx->dwRcvd;

  memset(&pctx->ovl, 0, sizeof(pctx->ovl));
  pctx->ovl.hEvent = (HANDLE) pctx;

  pctx->iopending = true;
  ResetEvent(pctx->hEvent);

  dwFlags = 0;
  if ( WSARecv(pctx->sock, &buf, 1, &dwRcvd, &dwFlags, &pctx->ovl, &RecvDone) != 0 )
   if ( (dwErr = WSAGetLastError()) != WSA_IO_PENDING )
   {
    pctx->iopending = false;
    pctx->dwErrRcv = dwErr;
    SetEvent(pctx->hEvent);
   }
 }
}

/******************************************************************************
 Create a two sentence forecast from the web service response
******************************************************************************/
bool FormatServiceResponse(HTTPCONTEXT *pctx)
{
 bool                ok;
 LPCSTR              pszXML;
 HRESULT             hr;
 VARIANT             prop;
 std::wstring        city, state, wind, outlook, temp;
 IXMLDOMDocument2Ptr doc;

 // Assume the worst

 ok = false;

 // Find the beginning of the payload

 if ( pszXML = strstr(pctx->szResp, szSeparator) )
 {
  // so we can advance past the header to it

  pszXML += sizeof(szSeparator) - 1;

  // Get an XML parser and load the response into it, synchronously

  doc.CreateInstance(L"Msxml2.DomDocument.4.0");
  doc->async = false;
  doc->loadXML(_bstr_t(pszXML));

  // Oh, how we love X-Path

  prop.vt = VT_BSTR;
  prop.bstrVal = _bstr_t(L"XPath");
  hr = doc->setProperty(_bstr_t(L"SelectionLanguage"), prop);

  // and its namespaces

  prop.bstrVal = _bstr_t(wszNamespaces);
  hr = doc->setProperty(_bstr_t(L"SelectionNamespaces"), prop);

  // Pull the wind, location, temperature and outlook from the response

  wind    = GetWind(doc);
  city    = GetCity(doc);
  state   = GetState(doc);
  temp    = GetTemperature(doc);
  outlook = GetOutlook(doc);

  // The parsing functions return empty strings on failure

  if ( city.length() && outlook.length() && temp.length() && wind.length() )
  {
   // Build a two sentence response

   pctx->weather = L"In ";
   pctx->weather.append(city);
   pctx->weather.append(state);
   pctx->weather.append(L", the weather is ");
   pctx->weather.append(outlook);
   pctx->weather.append(L" with a temperature of "); 
   pctx->weather.append(temp);
   pctx->weather.append(L" degrees Fahrenheit. The winds are ");
   pctx->weather.append(wind);
   pctx->weather.append(L".");

   ok = true;
  }
 }
 
 return ok;
}

/******************************************************************************
 Break the connection to the web service
******************************************************************************/
void DisconnectFromService(HTTPCONTEXT *pctx, bool cancel)
{
 shutdown(pctx->sock, SD_BOTH);
 closesocket(pctx->sock);
 pctx->sock = INVALID_SOCKET;

 if ( cancel )
 {
  while ( pctx->iopending )
   SleepEx(100, TRUE);
 }
}

/******************************************************************************
 Deallocate the context structure
******************************************************************************/
void DeallocateServiceContext(HTTPCONTEXT *pctx)
{
 char *p;

 p = (char *) pctx;
 delete [] p;
}

/******************************************************************************
 Completion routine called after the request is delivered to the web service
******************************************************************************/
void CALLBACK SendDone(DWORD dwErr, DWORD cb, WSAOVERLAPPED *povl, DWORD dwFlags)
{
 HTTPCONTEXT *pctx = (HTTPCONTEXT *) (povl->hEvent);

 pctx->dwSent    = cb;
 pctx->dwErrSnd  = dwErr;
 pctx->iopending = false;

 SetEvent(pctx->hEvent);
}

/******************************************************************************
 When each read completes we check to see if we got all of the response
******************************************************************************/
bool IsRecvComplete(char *pszResp)
{
 char *p;
 bool complete;

 complete = false;

 if ( p = strstr(pszResp, szTerminator) )
 {
  p[ sizeof(szTerminator) - 1 ] = 0; 
  complete = true;
 }

 else
  complete = false;

 return complete;
}

/******************************************************************************
 Completion routine called everytime we receive data from the web service
******************************************************************************/
void CALLBACK RecvDone(DWORD dwErr, DWORD cb, WSAOVERLAPPED *povl, DWORD dwFlags)
{
 HTTPCONTEXT *pctx = (HTTPCONTEXT *) (povl->hEvent);

 pctx->dwRcvd  += cb;
 pctx->dwErrRcv = dwErr;

 if ( (cb == 0) || IsRecvComplete(pctx->szResp) )
 {
  pctx->iopending = false;
  SetEvent(pctx->hEvent);
 }

 else
  ReceiveServiceResponse(pctx);
}

/******************************************************************************
 Extracts and formats the city information
******************************************************************************/
std::wstring GetCity(IXMLDOMDocument2Ptr &doc)
{
 BSTR           temp;
 HRESULT        hr;
 std::wstring   city;
 IXMLDOMNodePtr node;

 city = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:location")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"city"))->get_text(&temp);
 
  if ( SUCCEEDED(hr) )
  {
   city = std::wstring(temp);
   SysFreeString(temp);
  }
 }

 return city;
}

/******************************************************************************
 Extracts and formats the city information
******************************************************************************/
std::wstring GetState(IXMLDOMDocument2Ptr &doc)
{
 int            i, count;
 BSTR           temp;
 HRESULT        hr;
 std::wstring   state;
 IXMLDOMNodePtr node;

 state = L" ";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:location")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"region"))->get_text(&temp);
 
  if ( SUCCEEDED(hr) )
  {
   count = sizeof(pszStates) / sizeof( pszStates[0] );

   for ( i = 0; i < count; i++ )
    if ( wcscmp(temp, pszStates[i][0] ) == 0 )
    {
     state.append(pszStates[i][1]);
     break;
    } 
   
   SysFreeString(temp);
  }
 }

 return state;
}

/******************************************************************************
  Pulls the wind speed and direction from the XML document
******************************************************************************/
std::wstring GetWind(IXMLDOMDocument2Ptr &doc)
{
 int            degrees;
 BSTR           temp1, temp2;
 HRESULT        hr1, hr2;
 std::wstring   wind;
 IXMLDOMNodePtr node;

 wind = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/yweather:wind")) )
 {
  hr1 = node->attributes->getNamedItem(_bstr_t(L"direction"))->get_text(&temp1);
  hr2 = node->attributes->getNamedItem(_bstr_t(L"speed"))->get_text(&temp2);

  if ( SUCCEEDED(hr1) && SUCCEEDED(hr2) )
  {
   degrees = _wtoi(temp1);

   wind = L"from the ";
   wind.append( Direction(degrees) );
   wind.append(L" at ");
   wind.append(temp2);
   wind.append(L" miles per hour");
  }

  if ( SUCCEEDED(hr1) )
   SysFreeString(temp1);

  if ( SUCCEEDED(hr2) ) 
   SysFreeString(temp2);
 }

 return wind;
}

/******************************************************************************
 Extracts and formats the current weather condition
******************************************************************************/
std::wstring GetOutlook(IXMLDOMDocument2Ptr &doc)
{
 BSTR           temp;
 HRESULT        hr;
 std::wstring   outlook;
 IXMLDOMNodePtr node;

 outlook = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/item/yweather:condition")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"text"))->get_text(&temp);

  if ( SUCCEEDED(hr) )
  {
   outlook = std::wstring(temp);
   SysFreeString(temp);
  }
 }

 return outlook;
}

/******************************************************************************
 Extracts and formats the current temperature
******************************************************************************/
std::wstring GetTemperature(IXMLDOMDocument2Ptr &doc)
{
 BSTR           bstr;
 HRESULT        hr;
 std::wstring   temp;
 IXMLDOMNodePtr node;

 temp = L"";

 if ( node = doc->selectSingleNode(_bstr_t(L"//channel/item/yweather:condition")) )
 {
  hr = node->attributes->getNamedItem(_bstr_t(L"temp"))->get_text(&bstr);

  if ( SUCCEEDED(hr) )
  {
   temp = std::wstring(bstr);
   SysFreeString(bstr);
  }
 }

 return temp;
}

/******************************************************************************
 Translates a compass reading to text
******************************************************************************/
std::wstring Direction(int degrees)
{
 std::wstring dir;

 if ( degrees <= 11 )
  dir = L"north";

 else if ( degrees <= 33 )
  dir = L"north northeast";

 else if ( degrees <= 56 )
  dir = L"northeast";

 else if ( degrees <= 78 )
  dir = L"east northeast";

 else if ( degrees <= 101 )
  dir = L"east";

 else if ( degrees <= 123 )
  dir = L"east southeast";

 else if ( degrees <= 146 )
  dir = L"southeast";

 else if ( degrees <= 168 )
  dir = L"south southeast";

 else if ( degrees <= 191 )
  dir = L"south";

 else if ( degrees <= 213 )
  dir = L"south southwest";

 else if ( degrees <= 236 )
  dir = L"southwest";

 else if ( degrees <= 258 )
  dir = L"west southwest";

 else if ( degrees <= 281 )
  dir = L"west";

 else if ( degrees <= 303 )
  dir = L"west northwest";

 else if ( degrees <= 326 )
  dir = L"northwest";

 else if ( degrees <= 348 )
  dir = L"north northwest";

 else 
  dir = L"north";

 return dir;
}