• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8goog.provide('cvox.SearchResults');
9goog.provide('cvox.UnknownResult');
10
11goog.require('cvox.AbstractResult');
12goog.require('cvox.ChromeVox');
13goog.require('cvox.SearchUtil');
14
15/**
16 * @constructor
17 */
18cvox.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 */
26cvox.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 */
46cvox.UnknownResult = function() {
47};
48goog.inherits(cvox.UnknownResult, cvox.AbstractResult);
49
50/* Normal Result Type. */
51/**
52 * @constructor
53 * @extends {cvox.AbstractResult}
54 */
55cvox.NormalResult = function() {
56};
57goog.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 */
65cvox.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 */
76cvox.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 */
112cvox.WeatherResult = function() {
113};
114goog.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 */
122cvox.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 */
131cvox.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 */
155cvox.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 */
205cvox.KnowResult = function() {
206};
207goog.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 */
215cvox.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 */
226cvox.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 */
237cvox.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 */
248cvox.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 */
258cvox.CalcResult = function() {
259};
260goog.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 */
268cvox.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 */
279cvox.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 */
298cvox.GameResult = function() {
299};
300goog.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 */
308cvox.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 */
318cvox.ImageResult = function() {
319};
320goog.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 */
328cvox.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 */
339cvox.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 */
378cvox.CategoryResult = function() {
379};
380goog.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 */
388cvox.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 */
399cvox.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 */
414cvox.AdResult = function() {
415};
416goog.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 */
424cvox.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 */
435cvox.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 */
456cvox.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