| OLD | NEW |
| (Empty) |
| 1 // Copyright 2012 Google Inc. All Rights Reserved. | |
| 2 | |
| 3 /** | |
| 4 * @fileoverview Provides different rules for each type of result. | |
| 5 * @author peterxiao@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 ]; | |
| OLD | NEW |