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 |