Home | History | Annotate | Download | only in searchvox
      1 // Copyright 2012 Google Inc. All Rights Reserved.
      2 
      3 /**
      4  * @fileoverview Provides different rules for each type of result.
      5  * @author peterxiao (a] google.com (Peter Xiao)
      6  */
      7 
      8 goog.provide('cvox.SearchResults');
      9 goog.provide('cvox.UnknownResult');
     10 
     11 goog.require('cvox.AbstractResult');
     12 goog.require('cvox.ChromeVox');
     13 goog.require('cvox.SearchUtil');
     14 
     15 /**
     16  * @constructor
     17  */
     18 cvox.SearchResults = function() {
     19 };
     20 
     21 /**
     22  * Speaks a result based on given selectors.
     23  * @param {Element} result Search result to be spoken.
     24  * @param {Array} selectTexts Array of selectors or text to speak.
     25  */
     26 cvox.SearchResults.speakResultBySelectTexts = function(result, selectTexts) {
     27   for (var j = 0; j < selectTexts.length; j++) {
     28     var selectText = selectTexts[j];
     29     if (selectText.select) {
     30       var elems = result.querySelectorAll(selectText.select);
     31       for (var i = 0; i < elems.length; i++) {
     32         cvox.ChromeVox.speakNode(elems.item(i), 1);
     33       }
     34     }
     35     if (selectText.text) {
     36       cvox.ChromeVox.tts.speak(selectText.text, 1);
     37     }
     38   }
     39 };
     40 
     41 /**
     42  * Unknown Result Type. This is used if we don't know what to do.
     43  * @constructor
     44  * @extends {cvox.AbstractResult}
     45  */
     46 cvox.UnknownResult = function() {
     47 };
     48 goog.inherits(cvox.UnknownResult, cvox.AbstractResult);
     49 
     50 /* Normal Result Type. */
     51 /**
     52  * @constructor
     53  * @extends {cvox.AbstractResult}
     54  */
     55 cvox.NormalResult = function() {
     56 };
     57 goog.inherits(cvox.NormalResult, cvox.AbstractResult);
     58 
     59 /**
     60  * Checks the result if it is a normal result.
     61  * @param {Element} result Result to be checked.
     62  * @return {boolean} Whether or not the element is a normal result.
     63  * @override
     64  */
     65 cvox.NormalResult.prototype.isType = function(result) {
     66   var NORMAL_SELECT = '.rc';
     67   return result.querySelector(NORMAL_SELECT) !== null;
     68 };
     69 
     70 /**
     71  * Speak a normal search result.
     72  * @param {Element} result Normal result to be spoken.
     73  * @return {boolean} Whether or not the result was spoken.
     74  * @override
     75  */
     76 cvox.NormalResult.prototype.speak = function(result) {
     77   if (!result) {
     78     return false;
     79   }
     80   var NORMAL_TITLE_SELECT = '.rc .r';
     81   var NORMAL_URL_SELECT = '.kv';
     82   var NORMAL_DESC_SELECT = '.rc .st';
     83   var SITE_LINK_SELECT = '.osl';
     84   var MORE_RESULTS_SELECT = '.sld';
     85   var MORE_RESULTS_LINK_SELECT = '.mrf';
     86 
     87   var NORMAL_SELECTORS = [
     88     { select: NORMAL_TITLE_SELECT },
     89     { select: NORMAL_DESC_SELECT },
     90     { select: NORMAL_URL_SELECT },
     91     { select: SITE_LINK_SELECT },
     92     { select: MORE_RESULTS_SELECT },
     93     { select: MORE_RESULTS_LINK_SELECT }];
     94   cvox.SearchResults.speakResultBySelectTexts(result, NORMAL_SELECTORS);
     95 
     96   var DISCUSS_TITLE_SELECT = '.mas-1st-col div';
     97   var DISCUSS_DATE_SELECT = '.mas-col div';
     98   var discussTitles = result.querySelectorAll(DISCUSS_TITLE_SELECT);
     99   var discussDates = result.querySelectorAll(DISCUSS_DATE_SELECT);
    100   for (var i = 0; i < discussTitles.length; i++) {
    101     cvox.ChromeVox.speakNode(discussTitles.item(i), 1);
    102     cvox.ChromeVox.speakNode(discussDates.item(i), 1);
    103   }
    104   return true;
    105 };
    106 
    107 /* Weather Result */
    108 /**
    109  * @constructor
    110  * @extends {cvox.AbstractResult}
    111  */
    112 cvox.WeatherResult = function() {
    113 };
    114 goog.inherits(cvox.WeatherResult, cvox.AbstractResult);
    115 
    116 /**
    117  * Checks the result if it is a weather result.
    118  * @param {Element} result Result to be checked.
    119  * @return {boolean} Whether or not the element is a weather result.
    120  * @override
    121  */
    122 cvox.WeatherResult.prototype.isType = function(result) {
    123   var WEATHER_SELECT = '#wob_wc';
    124   return result.querySelector(WEATHER_SELECT) !== null;
    125 };
    126 
    127 /**
    128  * Speak a weather forecast.
    129  * @param {Element} forecast Weather forecast to be spoken.
    130  */
    131 cvox.WeatherResult.speakForecast = function(forecast) {
    132   if (!forecast) {
    133     return;
    134   }
    135   var FORE_DAY_SELECT = '.vk_lgy';
    136   var FORE_COND_SELECT = 'img';
    137   var FORE_HIGH_SELECT = '.vk_gy';
    138   var FORE_LOW_SELECT = '.vk_lgy';
    139 
    140   var FORE_SELECTORS = [
    141     { select: FORE_DAY_SELECT },
    142     { select: FORE_COND_SELECT },
    143     { select: FORE_HIGH_SELECT },
    144     { select: FORE_LOW_SELECT }
    145   ];
    146   cvox.SearchResults.speakResultBySelectTexts(forecast, FORE_SELECTORS);
    147 };
    148 
    149 /**
    150  * Speak a weather search result.
    151  * @param {Element} result Weather result to be spoken.
    152  * @return {boolean} Whether or not the result was spoken.
    153  * @override
    154  */
    155 cvox.WeatherResult.prototype.speak = function(result) {
    156   if (!result) {
    157     return false;
    158   }
    159   /* TODO(peterxiao): Internationalization? */
    160   var WEATHER_INTRO = 'The weather forcast for';
    161   var WEATHER_TEMP_UNITS = 'degrees fahrenheit';
    162   var WEATHER_PREC_INTRO = 'precipitation is';
    163   var WEATHER_HUMID_INTRO = 'humidity is';
    164   var WEATHER_WIND_INTRO = 'wind is';
    165   var FORE_INTRO = 'Forecasts for this week';
    166   var WEATHER_LOC_SELECT = '.vk_h';
    167   var WEATHER_WHEN_SELECT = '#wob_dts';
    168   var WEATHER_COND_SELECT = '#wob_dc';
    169   var WEATHER_TEMP_SELECT = '#wob_tm';
    170   var WEATHER_PREC_SELECT = '#wob_pp';
    171   var WEATHER_HUMID_SELECT = '#wob_hm';
    172   var WEATHER_WIND_SELECT = '#wob_ws';
    173 
    174   var WEATHER_SELECT_TEXTS = [
    175     { text: WEATHER_INTRO },
    176     { select: WEATHER_LOC_SELECT },
    177     { select: WEATHER_WHEN_SELECT },
    178     { select: WEATHER_COND_SELECT },
    179     { select: WEATHER_TEMP_SELECT },
    180     { text: WEATHER_TEMP_UNITS },
    181     { text: WEATHER_PREC_INTRO },
    182     { select: WEATHER_PREC_SELECT },
    183     { text: WEATHER_HUMID_INTRO },
    184     { select: WEATHER_HUMID_SELECT },
    185     { text: WEATHER_WIND_INTRO },
    186     { select: WEATHER_WIND_SELECT }
    187   ];
    188   cvox.SearchResults.speakResultBySelectTexts(result, WEATHER_SELECT_TEXTS);
    189 
    190   var WEATHER_FORCAST_CLASS = 'wob_df';
    191   var forecasts = result.getElementsByClassName(WEATHER_FORCAST_CLASS);
    192   cvox.ChromeVox.tts.speak(FORE_INTRO, 1);
    193   for (var i = 0; i < forecasts.length; i++) {
    194     var forecast = forecasts.item(i);
    195     cvox.WeatherResult.speakForecast(forecast);
    196   }
    197   return true;
    198 };
    199 
    200 /* Knowledge Panel Result */
    201 /**
    202  * @constructor
    203  * @extends {cvox.AbstractResult}
    204  */
    205 cvox.KnowResult = function() {
    206 };
    207 goog.inherits(cvox.KnowResult, cvox.AbstractResult);
    208 
    209 /**
    210  * Checks the result if it is a know result.
    211  * @param {Element} result Result to be checked.
    212  * @return {boolean} Whether or not the element is a know result.
    213  * @override
    214  */
    215 cvox.KnowResult.prototype.isType = function(result) {
    216   var KNOP_SELECT = '.kno-ec';
    217   return result.querySelector(KNOP_SELECT) !== null;
    218 };
    219 
    220 /**
    221  * Speak a knowledge panel search result.
    222  * @param {Element} result Knowledge panel result to be spoken.
    223  * @return {boolean} Whether or not the result was spoken.
    224  * @override
    225  */
    226 cvox.KnowResult.prototype.speak = function(result) {
    227   cvox.ChromeVox.speakNode(result, 1);
    228   return true;
    229 };
    230 
    231 /**
    232  * Extracts the wikipedia URL from knowledge panel.
    233  * @param {Element} result Result to extract from.
    234  * @return {?string} URL.
    235  * @override
    236  */
    237 cvox.KnowResult.prototype.getURL = function(result) {
    238   var LINK_SELECTOR = '.q';
    239   return cvox.SearchUtil.extractURL(result.querySelector(LINK_SELECTOR));
    240 };
    241 
    242 /**
    243  * Extracts the node to sync to in the knowledge panel.
    244  * @param {Element} result Result.
    245  * @return {?Node} Node to sync to.
    246  * @override
    247  */
    248 cvox.KnowResult.prototype.getSyncNode = function(result) {
    249   var HEADER_SELECTOR = '.kno-ecr-pt';
    250   return result.querySelector(HEADER_SELECTOR);
    251 };
    252 
    253 /* Calculator Type */
    254 /**
    255  * @constructor
    256  * @extends {cvox.AbstractResult}
    257  */
    258 cvox.CalcResult = function() {
    259 };
    260 goog.inherits(cvox.CalcResult, cvox.AbstractResult);
    261 
    262 /**
    263  * Checks the result if it is a calculator result.
    264  * @param {Element} result Result to be checked.
    265  * @return {boolean} Whether or not the element is a calculator result.
    266  * @override
    267  */
    268 cvox.CalcResult.prototype.isType = function(result) {
    269   var CALC_SELECT = '#cwmcwd';
    270   return result.querySelector(CALC_SELECT) !== null;
    271 };
    272 
    273 /**
    274  * Speak a calculator search result.
    275  * @param {Element} result Calculator result to be spoken.
    276  * @return {boolean} Whether or not the result was spoken.
    277  * @override
    278  */
    279 cvox.CalcResult.prototype.speak = function(result) {
    280   if (!result) {
    281     return false;
    282   }
    283   var CALC_QUERY_SELECT = '#cwles';
    284   var CALC_RESULT_SELECT = '#cwos';
    285   var CALC_SELECTORS = [
    286     { select: CALC_QUERY_SELECT },
    287     { select: CALC_RESULT_SELECT }
    288   ];
    289   cvox.SearchResults.speakResultBySelectTexts(result, CALC_SELECTORS);
    290   return true;
    291 };
    292 
    293 /* Game Type */
    294 /**
    295  * @constructor
    296  * @extends {cvox.AbstractResult}
    297  */
    298 cvox.GameResult = function() {
    299 };
    300 goog.inherits(cvox.GameResult, cvox.AbstractResult);
    301 
    302 /**
    303  * Checks the result if it is a game result.
    304  * @param {Element} result Result to be checked.
    305  * @return {boolean} Whether or not the element is a game result.
    306  * @override
    307  */
    308 cvox.GameResult.prototype.isType = function(result) {
    309   var GAME_SELECT = '.xpdbox';
    310   return result.querySelector(GAME_SELECT) !== null;
    311 };
    312 
    313 /* Image Type */
    314 /**
    315  * @constructor
    316  * @extends {cvox.AbstractResult}
    317  */
    318 cvox.ImageResult = function() {
    319 };
    320 goog.inherits(cvox.ImageResult, cvox.AbstractResult);
    321 
    322 /**
    323  * Checks the result if it is a image result.
    324  * @param {Element} result Result to be checked.
    325  * @return {boolean} Whether or not the element is a image result.
    326  * @override
    327  */
    328 cvox.ImageResult.prototype.isType = function(result) {
    329   var IMAGE_CLASSES = 'rg_di';
    330   return result.className === IMAGE_CLASSES;
    331 };
    332 
    333 /**
    334  * Speak an image result.
    335  * @param {Element} result Image result to be spoken.
    336  * @return {boolean} Whether or not the result was spoken.
    337  * @override
    338  */
    339 cvox.ImageResult.prototype.speak = function(result) {
    340   if (!result) {
    341     return false;
    342   }
    343   /* Grab image result metadata. */
    344   var META_CLASS = 'rg_meta';
    345   var metaDiv = result.querySelector('.' + META_CLASS);
    346   var metaJSON = metaDiv.innerHTML;
    347   var metaData = JSON.parse(metaJSON);
    348 
    349   var imageSelectTexts = [];
    350 
    351   var filename = metaData['fn'];
    352   if (filename) {
    353     imageSelectTexts.push({ text: filename });
    354   }
    355 
    356   var rawDimensions = metaData['is'];
    357   if (rawDimensions) {
    358     /* Dimensions contain HTML codes, so we convert them. */
    359     var tmpDiv = document.createElement('div');
    360     tmpDiv.innerHTML = rawDimensions;
    361     var dimensions = tmpDiv.textContent || tmpDiv.innerText;
    362     imageSelectTexts.push({ text: dimensions });
    363   }
    364 
    365   var url = metaData['isu'];
    366   if (url) {
    367     imageSelectTexts.push({ text: url});
    368   }
    369   cvox.SearchResults.speakResultBySelectTexts(result, imageSelectTexts);
    370   return true;
    371 };
    372 
    373 /* Category Result */
    374 /**
    375  * @constructor
    376  * @extends {cvox.AbstractResult}
    377  */
    378 cvox.CategoryResult = function() {
    379 };
    380 goog.inherits(cvox.CategoryResult, cvox.AbstractResult);
    381 
    382 /**
    383  * Checks the result if it is a category result.
    384  * @param {Element} result Result to be checked.
    385  * @return {boolean} Whether or not the element is a category result.
    386  * @override
    387  */
    388 cvox.CategoryResult.prototype.isType = function(result) {
    389   var CATEGORY_CLASSES = 'rg_fbl nj';
    390   return result.className === CATEGORY_CLASSES;
    391 };
    392 
    393 /**
    394  * Speak a category result.
    395  * @param {Element} result Category result to be spoken.
    396  * @return {boolean} Whether or not the result was spoken.
    397  * @override
    398  */
    399 cvox.CategoryResult.prototype.speak = function(result) {
    400   if (!result) {
    401     return false;
    402   }
    403   var LABEL_SELECT = '.rg_bb_label';
    404   var label = result.querySelector(LABEL_SELECT);
    405   cvox.ChromeVox.speakNode(label, 1);
    406   return true;
    407 };
    408 
    409 /* Ad Result */
    410 /**
    411  * @constructor
    412  * @extends {cvox.AbstractResult}
    413  */
    414 cvox.AdResult = function() {
    415 };
    416 goog.inherits(cvox.AdResult, cvox.AbstractResult);
    417 
    418 /**
    419  * Checks the result if it is an ad result.
    420  * @param {Element} result Result to be checked.
    421  * @return {boolean} Whether or not the element is an ad result.
    422  * @override
    423  */
    424 cvox.AdResult.prototype.isType = function(result) {
    425   var ADS_CLASS = 'ads-ad';
    426   return result.className === ADS_CLASS;
    427 };
    428 
    429 /**
    430  * Speak an ad result.
    431  * @param {Element} result Ad result to be spoken.
    432  * @return {boolean} Whether or not the result was spoken.
    433  * @override
    434  */
    435 cvox.AdResult.prototype.speak = function(result) {
    436   if (!result) {
    437     return false;
    438   }
    439   var HEADER_SELECT = 'h3';
    440   var DESC_SELECT = '.ads-creative';
    441   var URL_SELECT = '.ads-visurl';
    442   var AD_SELECTS = [
    443     { select: HEADER_SELECT },
    444     { select: DESC_SELECT },
    445     { select: URL_SELECT }];
    446   cvox.SearchResults.speakResultBySelectTexts(result, AD_SELECTS);
    447   return true;
    448 };
    449 
    450 /**
    451  * To add new result types, create a new object with the following properties:
    452  * isType: Function to indicate if an element is the object's type.
    453  * speak: Function that takes in a result and speaks the type to the user.
    454  * getURL: Function that takes in a result and extracts the URL to follow.
    455  */
    456 cvox.SearchResults.RESULT_TYPES = [
    457   cvox.UnknownResult,
    458   cvox.NormalResult,
    459   cvox.KnowResult,
    460   cvox.WeatherResult,
    461   cvox.AdResult,
    462   cvox.CalcResult,
    463   cvox.GameResult,
    464   cvox.ImageResult,
    465   cvox.CategoryResult
    466 ];
    467