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