on master help sign in
Merge pull request #513 from a01200356/wolframalpha

WolframAlpha infobox

commit308613e586108fd7b713fc2a14f8200a0a781105 authorAdam Tauber <asciimoo@gmail.com> dateTue 1 Mar 2016, 10:41 (11 years ago) parent45b50738f3b33d
11 files changed+449−314
searx/engines/wolframalpha_api.py+72−27 searx/engines/wolframalpha_noapi.py+41−20 searx/settings.yml+2−2 searx/static/themes/default/css/style.css+1−1 searx/static/themes/default/less/style.less+3−2 searx/static/themes/oscar/css/oscar.min.css+1−1 searx/static/themes/oscar/less/oscar/infobox.less+2−1 searx/templates/default/infobox.html+8−1 searx/templates/oscar/infobox.html+5−1 tests/unit/engines/test_wolframalpha_api.py+112−255 tests/unit/engines/test_wolframalpha_noapi.py+202−3
searx/engines/wolframalpha_api.py+72−27
@@ -1,43 +1,60 @@
1−# Wolfram Alpha (Maths)
1+# Wolfram Alpha (Science)
22#
3−# @website http://www.wolframalpha.com
4−# @provide-api yes (http://api.wolframalpha.com/v2/)
3+# @website https://www.wolframalpha.com
4+# @provide-api yes (https://api.wolframalpha.com/v2/)
55#
66# @using-api yes
77# @results XML
88# @stable yes
9−# @parse result
9+# @parse url, infobox
1010
1111from urllib import urlencode
1212from lxml import etree
13−from re import search
1413
1514# search-url
16−base_url = 'http://api.wolframalpha.com/v2/query'
17−search_url = base_url + '?appid={api_key}&{query}&format=plaintext'
18−site_url = 'http://www.wolframalpha.com/input/?{query}'
15+search_url = 'https://api.wolframalpha.com/v2/query?appid={api_key}&{query}'
16+site_url = 'https://www.wolframalpha.com/input/?{query}'
1917api_key = '' # defined in settings.yml
2018
2119# xpath variables
2220failure_xpath = '/queryresult[attribute::success="false"]'
2321answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext'
24−input_xpath = '//pod[starts-with(attribute::title, "Input")]/subpod/plaintext'
22+input_xpath = '//pod[starts-with(attribute::id, "Input")]/subpod/plaintext'
23+pods_xpath = '//pod'
24+subpods_xpath = './subpod'
25+pod_id_xpath = './@id'
26+pod_title_xpath = './@title'
27+plaintext_xpath = './plaintext'
28+image_xpath = './img'
29+img_src_xpath = './@src'
30+img_alt_xpath = './@alt'
31+
32+# pods to display as image in infobox
33+# this pods do return a plaintext, but they look better and are more useful as images
34+image_pods = {'VisualRepresentation',
35+ 'Illustration'}
2536
2637
2738# do search-request
2839def request(query, params):
2940 params['url'] = search_url.format(query=urlencode({'input': query}),
3041 api_key=api_key)
42+ params['headers']['Referer'] = site_url.format(query=urlencode({'i': query}))
3143
3244 return params
3345
3446
3547# replace private user area characters to make text legible
3648def replace_pua_chars(text):
37− pua_chars = {u'\uf74c': 'd',
38− u'\uf74d': u'\u212f',
39− u'\uf74e': 'i',
40− u'\uf7d9': '='}
49+ pua_chars = {u'\uf522': u'\u2192', # rigth arrow
50+ u'\uf7b1': u'\u2115', # set of natural numbers
51+ u'\uf7b4': u'\u211a', # set of rational numbers
52+ u'\uf7b5': u'\u211d', # set of real numbers
53+ u'\uf7bd': u'\u2124', # set of integer numbers
54+ u'\uf74c': 'd', # differential
55+ u'\uf74d': u'\u212f', # euler's number
56+ u'\uf74e': 'i', # imaginary number
57+ u'\uf7d9': '='} # equals sign
4158
4259 for k, v in pua_chars.iteritems():
4360 text = text.replace(k, v)
@@ -55,23 +72,51 @@ def response(resp):
5572 if search_results.xpath(failure_xpath):
5673 return []
5774
58− # parse answers
59− answers = search_results.xpath(answer_xpath)
60− if answers:
61− for answer in answers:
62− answer = replace_pua_chars(answer.text)
75+ try:
76+ infobox_title = search_results.xpath(input_xpath)[0].text
77+ except:
78+ infobox_title = None
6379
64− results.append({'answer': answer})
80+ pods = search_results.xpath(pods_xpath)
81+ result_chunks = []
82+ for pod in pods:
83+ pod_id = pod.xpath(pod_id_xpath)[0]
84+ pod_title = pod.xpath(pod_title_xpath)[0]
6585
66− # if there's no input section in search_results, check if answer has the input embedded (before their "=" sign)
67− try:
68− query_input = search_results.xpath(input_xpath)[0].text
69− except IndexError:
70− query_input = search(u'([^\uf7d9]+)', answers[0].text).group(1)
86+ subpods = pod.xpath(subpods_xpath)
87+ if not subpods:
88+ continue
89+
90+ # Appends either a text or an image, depending on which one is more suitable
91+ for subpod in subpods:
92+ content = subpod.xpath(plaintext_xpath)[0].text
93+ image = subpod.xpath(image_xpath)
94+
95+ if content and pod_id not in image_pods:
96+
97+ # if no input pod was found, title is first plaintext pod
98+ if not infobox_title:
99+ infobox_title = content
100+
101+ content = replace_pua_chars(content)
102+ result_chunks.append({'label': pod_title, 'value': content})
103+
104+ elif image:
105+ result_chunks.append({'label': pod_title,
106+ 'image': {'src': image[0].xpath(img_src_xpath)[0],
107+ 'alt': image[0].xpath(img_alt_xpath)[0]}})
108+
109+ if not result_chunks:
110+ return []
111+
112+ # append infobox
113+ results.append({'infobox': infobox_title,
114+ 'attributes': result_chunks,
115+ 'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
71116
72117 # append link to site
73− result_url = site_url.format(query=urlencode({'i': query_input.encode('utf-8')}))
74− results.append({'url': result_url,
75− 'title': query_input + " - Wolfram|Alpha"})
118+ results.append({'url': resp.request.headers['Referer'].decode('utf8'),
119+ 'title': 'Wolfram|Alpha',
120+ 'content': infobox_title})
76121
77122 return results
searx/engines/wolframalpha_noapi.py+41−20
@@ -1,26 +1,26 @@
1−# WolframAlpha (Maths)
1+# Wolfram|Alpha (Science)
22#
3−# @website http://www.wolframalpha.com/
4−# @provide-api yes (http://api.wolframalpha.com/v2/)
3+# @website https://www.wolframalpha.com/
4+# @provide-api yes (https://api.wolframalpha.com/v2/)
55#
66# @using-api no
7−# @results HTML
7+# @results JSON
88# @stable no
9−# @parse answer
9+# @parse url, infobox
1010
1111from cgi import escape
1212from json import loads
1313from time import time
1414from urllib import urlencode
15+from lxml.etree import XML
1516
1617from searx.poolrequests import get as http_get
1718
1819# search-url
1920url = 'https://www.wolframalpha.com/'
20−search_url = url + 'input/?{query}'
2121
2222search_url = url + 'input/json.jsp'\
23− '?async=true'\
23+ '?async=false'\
2424 '&banners=raw'\
2525 '&debuggingdata=false'\
2626 '&format=image,plaintext,imagemap,minput,moutput'\
@@ -33,13 +33,17 @@ search_url = url + 'input/json.jsp'\
3333 '&sponsorcategories=true'\
3434 '&statemethod=deploybutton'
3535
36−# xpath variables
37−scripts_xpath = '//script'
38−title_xpath = '//title'
39−failure_xpath = '//p[attribute::class="pfail"]'
36+referer_url = url + 'input/?{query}'
37+
4038token = {'value': '',
4139 'last_updated': None}
4240
41+# pods to display as image in infobox
42+# this pods do return a plaintext, but they look better and are more useful as images
43+image_pods = {'VisualRepresentation',
44+ 'Illustration',
45+ 'Symbol'}
46+
4347
4448# seems, wolframalpha resets its token in every hour
4549def obtain_token():
@@ -62,13 +66,15 @@ def request(query, params):
6266 if time() - token['last_updated'] > 3600:
6367 obtain_token()
6468 params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
65− params['headers']['Referer'] = 'https://www.wolframalpha.com/input/?i=' + query
69+ params['headers']['Referer'] = referer_url.format(query=urlencode({'i': query}))
6670
6771 return params
6872
6973
7074# get response from search-request
7175def response(resp):
76+ results = []
77+
7278 resp_json = loads(resp.text)
7379
7480 if not resp_json['queryresult']['success']:
@@ -76,20 +82,35 @@ def response(resp):
7682
7783 # TODO handle resp_json['queryresult']['assumptions']
7884 result_chunks = []
85+ infobox_title = None
7986 for pod in resp_json['queryresult']['pods']:
87+ pod_id = pod.get('id', '')
8088 pod_title = pod.get('title', '')
89+
8190 if 'subpods' not in pod:
8291 continue
92+
93+ if pod_id == 'Input' or not infobox_title:
94+ infobox_title = pod['subpods'][0]['plaintext']
95+
8396 for subpod in pod['subpods']:
84− if 'img' in subpod:
85− result_chunks.append(u'<p>{0}<br /><img src="{1}" alt="{2}" /></p>'
86− .format(escape(pod_title or subpod['img']['alt']),
87− escape(subpod['img']['src']),
88− escape(subpod['img']['alt'])))
97+ if subpod['plaintext'] != '' and pod_id not in image_pods:
98+ # append unless it's not an actual answer
99+ if subpod['plaintext'] != '(requires interactivity)':
100+ result_chunks.append({'label': pod_title, 'value': subpod['plaintext']})
101+
102+ elif 'img' in subpod:
103+ result_chunks.append({'label': pod_title, 'image': subpod['img']})
89104
90105 if not result_chunks:
91106 return []
92107
93− return [{'url': resp.request.headers['Referer'].decode('utf-8'),
94− 'title': 'Wolframalpha',
95− 'content': ''.join(result_chunks)}]
108+ results.append({'infobox': infobox_title,
109+ 'attributes': result_chunks,
110+ 'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
111+
112+ results.append({'url': resp.request.headers['Referer'].decode('utf8'),
113+ 'title': 'Wolfram|Alpha',
114+ 'content': infobox_title})
115+
116+ return results
searx/settings.yml+2−2
@@ -310,8 +310,8 @@ engines:
310310 shortcut : wa
311311 # You can use the engine using the official stable API, but you need an API key
312312 # See : http://products.wolframalpha.com/api/
313− # engine : wolframalpha_api
314− # api_key: 'apikey' # required!
313+ # engine : wolframalpha_api
314+ # api_key: '' # required!
315315 engine : wolframalpha_noapi
316316 timeout: 6.0
317317 categories : science
searx/static/themes/default/css/style.css+1−1
@@ -1 +1 @@
1−.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid #f00}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.highlight .gr{color:#f00}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00a000}.highlight .go{color:#888}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#04d}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:#008000}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#b00040}.highlight .m{color:#666}.highlight .s{color:#ba2121}.highlight .na{color:#7d9029}.highlight .nb{color:#008000}.highlight .nc{color:#00f;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999;font-weight:bold}.highlight .ne{color:#d2413a;font-weight:bold}.highlight .nf{color:#00f}.highlight .nl{color:#a0a000}.highlight .nn{color:#00f;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177c}.highlight .ow{color:#a2f;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#ba2121}.highlight .sc{color:#ba2121}.highlight .sd{color:#ba2121;font-style:italic}.highlight .s2{color:#ba2121}.highlight .se{color:#b62;font-weight:bold}.highlight .sh{color:#ba2121}.highlight .si{color:#b68;font-weight:bold}.highlight .sx{color:#008000}.highlight .sr{color:#b68}.highlight .s1{color:#ba2121}.highlight .ss{color:#19177c}.highlight .bp{color:#008000}.highlight .vc{color:#19177c}.highlight .vg{color:#19177c}.highlight .vi{color:#19177c}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:transparent}.highlight .lineno::-moz-selection{background:transparent}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444;padding:0;margin:0}body,#container{padding:0;margin:0}#container{width:100%;position:absolute;top:0}.search{padding:0;margin:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #e8e7e6}.search .checkbox_container label:hover{border-bottom:2px solid #3498db}.search .checkbox_container input[type="checkbox"]:checked+label{border-bottom:2px solid #2980b9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q{background:none repeat scroll 0 0 #fff;border:1px solid #3498db;color:#222;font-size:16px;height:28px;margin:0;outline:medium none;padding:2px;padding-left:8px;padding-right:0 !important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url('../img/search-icon.png') no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;margin:0;padding:0;list-style:none;border:1px solid #3498db;border-left-color:#3498db;border-right-color:#3498db;border-bottom-color:#3498db;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#fff;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none !important;cursor:pointer;font-weight:normal;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#fff}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9fcfff}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:bold;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498db;padding:4px 10px}a:link.hmarg{color:#3498db}a:visited.hmarg{color:#3498db}a:active.hmarg{color:#3498db}a:hover.hmarg{color:#3498db}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url('../img/searx.png') no-repeat;width:100%;min-height:80px;background-position:center}div.title h1{visibility:hidden}input[type="submit"]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498db;color:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}input[type="checkbox"]{visibility:hidden}fieldset{margin:8px;border:1px solid #3498db}#categories{margin:0 10px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type="checkbox"]:checked+label{background:#3498db;color:#fff}.engine_checkbox{padding:4px}label.allow{background:#e74c3c;padding:4px 8px;color:#fff;display:none}label.deny{background:#2ecc71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type="checkbox"]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type="checkbox"]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8e44ad}.result{margin:19px 0 18px 0;padding:0;clear:both}.result_title{margin-bottom:0}.result_title a{color:#2980b9;font-weight:normal;font-size:1.1em}.result_title a:hover{text-decoration:underline}.result_title a:visited{color:#8e44ad}.cache_link{font-size:10px !important}.result h3{font-size:1em;word-wrap:break-word;margin:5px 0 1px 0;padding:0}.result .content{font-size:.8em;margin:0;padding:0;max-width:54em;word-wrap:break-word;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .content br.last{clear:both}.result .url{font-size:.8em;margin:0 0 3px 0;padding:0;max-width:54em;word-wrap:break-word;color:#c0392b}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small_font{font-size:.8em}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px 10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#fff}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,0.6);font-size:.7em}.torrent_result{border-left:10px solid lightgray;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980b9}.torrent_result a:hover{text-decoration:underline}.torrent_result a:visited{color:#8e44ad}.definition_result{border-left:10px solid gray;padding-left:3px}.percentage{position:relative;width:300px}.percentage div{background:#444}table{width:100%}td{padding:0 4px}tr:hover{background:#ddd}#results{margin:auto;padding:0;width:50em;margin-bottom:20px}#sidebar{position:fixed;bottom:10px;left:10px;margin:0 2px 5px 5px;padding:0 2px 2px 2px;width:14em}#sidebar input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}#sidebar input[type="submit"]{text-decoration:underline}#suggestions form{display:inline}#suggestions,#answers{margin-top:20px;max-width:45em}#suggestions input,#answers input,#infoboxes input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}#suggestions input[type="submit"],#answers input[type="submit"],#infoboxes input[type="submit"]{text-decoration:underline}#suggestions-title{color:#888}#answers{border:2px solid #2980b9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;margin:0 2px 5px 5px;padding:0 2px 2px;max-width:21em}#infoboxes .infobox{margin:10px 0 10px;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:20em;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#infoboxes .infobox table{width:auto}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#infoboxes .infobox br{clear:both}#search_url{margin-top:8px}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url('../img/preference-icon.png') no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}#pagination{clear:both}#pagination br{clear:both}#apis{margin-top:8px;clear:both}#categories_container{position:relative}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed !important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#suggestions,#answers{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px 0;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis{display:none}#search_url{display:none}.result{border-top:1px solid #e8e7e6;margin:8px 0 8px 0}.result .thumbnail{max-width:98%}.image_result{max-width:98%}.image_result img{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:none repeat scroll 0 0 #3498db;border:0 none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#fff}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:white;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8}
1+.highlight .hll{background-color:#ffc}.highlight{background:#f8f8f8}.highlight .c{color:#408080;font-style:italic}.highlight .err{border:1px solid #f00}.highlight .k{color:#008000;font-weight:bold}.highlight .o{color:#666}.highlight .cm{color:#408080;font-style:italic}.highlight .cp{color:#bc7a00}.highlight .c1{color:#408080;font-style:italic}.highlight .cs{color:#408080;font-style:italic}.highlight .gd{color:#a00000}.highlight .ge{font-style:italic}.highlight .gr{color:#f00}.highlight .gh{color:#000080;font-weight:bold}.highlight .gi{color:#00a000}.highlight .go{color:#888}.highlight .gp{color:#000080;font-weight:bold}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#04d}.highlight .kc{color:#008000;font-weight:bold}.highlight .kd{color:#008000;font-weight:bold}.highlight .kn{color:#008000;font-weight:bold}.highlight .kp{color:#008000}.highlight .kr{color:#008000;font-weight:bold}.highlight .kt{color:#b00040}.highlight .m{color:#666}.highlight .s{color:#ba2121}.highlight .na{color:#7d9029}.highlight .nb{color:#008000}.highlight .nc{color:#00f;font-weight:bold}.highlight .no{color:#800}.highlight .nd{color:#a2f}.highlight .ni{color:#999;font-weight:bold}.highlight .ne{color:#d2413a;font-weight:bold}.highlight .nf{color:#00f}.highlight .nl{color:#a0a000}.highlight .nn{color:#00f;font-weight:bold}.highlight .nt{color:#008000;font-weight:bold}.highlight .nv{color:#19177c}.highlight .ow{color:#a2f;font-weight:bold}.highlight .w{color:#bbb}.highlight .mf{color:#666}.highlight .mh{color:#666}.highlight .mi{color:#666}.highlight .mo{color:#666}.highlight .sb{color:#ba2121}.highlight .sc{color:#ba2121}.highlight .sd{color:#ba2121;font-style:italic}.highlight .s2{color:#ba2121}.highlight .se{color:#b62;font-weight:bold}.highlight .sh{color:#ba2121}.highlight .si{color:#b68;font-weight:bold}.highlight .sx{color:#008000}.highlight .sr{color:#b68}.highlight .s1{color:#ba2121}.highlight .ss{color:#19177c}.highlight .bp{color:#008000}.highlight .vc{color:#19177c}.highlight .vg{color:#19177c}.highlight .vi{color:#19177c}.highlight .il{color:#666}.highlight pre{overflow:auto}.highlight .lineno{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}.highlight .lineno::selection{background:transparent}.highlight .lineno::-moz-selection{background:transparent}html{font-family:sans-serif;font-size:.9em;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;color:#444;padding:0;margin:0}body,#container{padding:0;margin:0}#container{width:100%;position:absolute;top:0}.search{padding:0;margin:0}.search .checkbox_container label{font-size:.9em;border-bottom:2px solid #e8e7e6}.search .checkbox_container label:hover{border-bottom:2px solid #3498db}.search .checkbox_container input[type="checkbox"]:checked+label{border-bottom:2px solid #2980b9}#search_wrapper{position:relative;width:50em;padding:10px}.center #search_wrapper{margin-left:auto;margin-right:auto}.q{background:none repeat scroll 0 0 #fff;border:1px solid #3498db;color:#222;font-size:16px;height:28px;margin:0;outline:medium none;padding:2px;padding-left:8px;padding-right:0 !important;width:100%;z-index:2}#search_submit{position:absolute;top:13px;right:1px;padding:0;border:0;background:url('../img/search-icon.png') no-repeat;background-size:24px 24px;opacity:.8;width:24px;height:30px;font-size:0}@media screen and (max-width:50em){#search_wrapper{width:90%;clear:both;overflow:hidden}}ul.autocompleter-choices{position:absolute;margin:0;padding:0;list-style:none;border:1px solid #3498db;border-left-color:#3498db;border-right-color:#3498db;border-bottom-color:#3498db;text-align:left;font-family:Verdana,Geneva,Arial,Helvetica,sans-serif;z-index:50;background-color:#fff;color:#444}ul.autocompleter-choices li{position:relative;margin:-2px 0 0 0;padding:.2em 1.5em .2em 1em;display:block;float:none !important;cursor:pointer;font-weight:normal;white-space:nowrap;font-size:1em;line-height:1.5em}ul.autocompleter-choices li.autocompleter-selected{background-color:#444;color:#fff}ul.autocompleter-choices li.autocompleter-selected span.autocompleter-queried{color:#9fcfff}ul.autocompleter-choices span.autocompleter-queried{display:inline;float:none;font-weight:bold;margin:0;padding:0}.row{max-width:800px;margin:20px auto;text-align:justify}.row h1{font-size:3em;margin-top:50px}.row p{padding:0 10px;max-width:700px}.row h3,.row ul{margin:4px 8px}.hmarg{margin:0 20px;border:1px solid #3498db;padding:4px 10px}a:link.hmarg{color:#3498db}a:visited.hmarg{color:#3498db}a:active.hmarg{color:#3498db}a:hover.hmarg{color:#3498db}.top_margin{margin-top:60px}.center{text-align:center}h1{font-size:5em}div.title{background:url('../img/searx.png') no-repeat;width:100%;min-height:80px;background-position:center}div.title h1{visibility:hidden}input[type="submit"]{padding:2px 6px;margin:2px 4px;display:inline-block;background:#3498db;color:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:0;cursor:pointer}input[type="checkbox"]{visibility:hidden}fieldset{margin:8px;border:1px solid #3498db}#categories{margin:0 10px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container{display:inline-block;position:relative;margin:0 3px;padding:0}.checkbox_container input{display:none}.checkbox_container label,.engine_checkbox label{cursor:pointer;padding:4px 10px;margin:0;display:block;text-transform:capitalize;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.checkbox_container input[type="checkbox"]:checked+label{background:#3498db;color:#fff}.engine_checkbox{padding:4px}label.allow{background:#e74c3c;padding:4px 8px;color:#fff;display:none}label.deny{background:#2ecc71;padding:4px 8px;color:#444;display:inline}.engine_checkbox input[type="checkbox"]:checked+label:nth-child(2)+label{display:none}.engine_checkbox input[type="checkbox"]:checked+label.allow{display:inline}a{text-decoration:none;color:#1a11be}a:visited{color:#8e44ad}.result{margin:19px 0 18px 0;padding:0;clear:both}.result_title{margin-bottom:0}.result_title a{color:#2980b9;font-weight:normal;font-size:1.1em}.result_title a:hover{text-decoration:underline}.result_title a:visited{color:#8e44ad}.cache_link{font-size:10px !important}.result h3{font-size:1em;word-wrap:break-word;margin:5px 0 1px 0;padding:0}.result .content{font-size:.8em;margin:0;padding:0;max-width:54em;word-wrap:break-word;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}.result .content br.last{clear:both}.result .url{font-size:.8em;margin:0 0 3px 0;padding:0;max-width:54em;word-wrap:break-word;color:#c0392b}.result .published_date{font-size:.8em;color:#888;Margin:5px 20px}.result .thumbnail{width:400px}.engines{color:#888}.small_font{font-size:.8em}.small p{margin:2px 0}.right{float:right}.invisible{display:none}.left{float:left}.highlight{color:#094089}.content .highlight{color:#000}.image_result{display:inline-block;margin:10px 10px;position:relative;max-height:160px}.image_result img{border:0;max-height:160px}.image_result p{margin:0;padding:0}.image_result p span a{display:none;color:#fff}.image_result p:hover span a{display:block;position:absolute;bottom:0;right:0;padding:4px;background-color:rgba(0,0,0,0.6);font-size:.7em}.torrent_result{border-left:10px solid lightgray;padding-left:3px}.torrent_result p{margin:3px;font-size:.8em}.torrent_result a{color:#2980b9}.torrent_result a:hover{text-decoration:underline}.torrent_result a:visited{color:#8e44ad}.definition_result{border-left:10px solid gray;padding-left:3px}.percentage{position:relative;width:300px}.percentage div{background:#444}table{width:100%}td{padding:0 4px}tr:hover{background:#ddd}#results{margin:auto;padding:0;width:50em;margin-bottom:20px}#sidebar{position:fixed;bottom:10px;left:10px;margin:0 2px 5px 5px;padding:0 2px 2px 2px;width:14em}#sidebar input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}#sidebar input[type="submit"]{text-decoration:underline}#suggestions form{display:inline}#suggestions,#answers{margin-top:20px;max-width:45em}#suggestions input,#answers input,#infoboxes input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}#suggestions input[type="submit"],#answers input[type="submit"],#infoboxes input[type="submit"]{text-decoration:underline}#suggestions-title{color:#888}#answers{border:2px solid #2980b9;padding:20px}#answers form,#infoboxes form{min-width:210px}#infoboxes{position:absolute;top:100px;right:20px;margin:0 2px 5px 5px;padding:0 2px 2px;max-width:21em;word-wrap:break-word;}#infoboxes .infobox{margin:10px 0 10px;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:90%;max-heigt:12em;display:block;margin:5px;padding:5px}#infoboxes .infobox h2{margin:0}#infoboxes .infobox table{table-layout:fixed;}#infoboxes .infobox table td{vertical-align:top}#infoboxes .infobox input{font-size:1em}#infoboxes .infobox br{clear:both}#search_url{margin-top:8px}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}#preferences{top:10px;padding:0;border:0;background:url('../img/preference-icon.png') no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}#pagination{clear:both}#pagination br{clear:both}#apis{margin-top:8px;clear:both}#categories_container{position:relative}@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%}.github{display:none}.checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}.preferences_container{display:none;postion:fixed !important;top:100px;right:0}}@media screen and (max-width:75em){div.title h1{font-size:1em}html.touch #categories{width:95%;height:30px;text-align:left;overflow-x:scroll;overflow-y:hidden;-webkit-overflow-scrolling:touch}html.touch #categories #categories_container{width:1000px;width:-moz-max-content;width:-webkit-max-content;width:max-content}html.touch #categories #categories_container .checkbox_container{display:inline-block;width:auto}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#suggestions,#answers{margin-top:5px}#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em}#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto}#sidebar{position:static;max-width:50em;margin:0 0 2px 0;padding:0;float:none;border:none;width:auto}#sidebar input{border:0}#apis{display:none}#search_url{display:none}.result{border-top:1px solid #e8e7e6;margin:8px 0 8px 0}.result .thumbnail{max-width:98%}.image_result{max-width:98%}.image_result img{max-width:98%}}.favicon{float:left;margin-right:4px;margin-top:2px}.preferences_back{background:none repeat scroll 0 0 #3498db;border:0 none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#fff}.hidden{opacity:0;overflow:hidden;font-size:.8em;position:absolute;bottom:-20px;width:100%;text-position:center;background:white;transition:opacity 1s ease}#categories_container:hover .hidden{transition:opacity 1s ease;opacity:.8}
searx/static/themes/default/less/style.less+3−2
@@ -476,6 +476,7 @@ color: @color-font-light;
476476 margin: 0px 2px 5px 5px;
477477 padding: 0px 2px 2px;
478478 max-width: 21em;
479+ word-wrap: break-word;
479480
480481 .infobox {
481482 margin: 10px 0 10px;
@@ -485,7 +486,7 @@ color: @color-font-light;
485486 /* box-shadow: 0px 0px 5px #CCC; */
486487
487488 img {
488− max-width: 20em;
489+ max-width: 90%;
489490 max-heigt: 12em;
490491 display: block;
491492 margin: 5px;
@@ -497,7 +498,7 @@ color: @color-font-light;
497498 }
498499
499500 table {
500− width: auto;
501+ table-layout: fixed;
501502
502503 td {
503504 vertical-align: top;
searx/static/themes/oscar/css/oscar.min.css+1−1
@@ -17,7 +17,7 @@ input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbo
1717.result_download{margin-right:5px}
1818#pagination{margin-top:30px;padding-bottom:50px}
1919.label-default{color:#aaa;background:#fff}
20−.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word}
20+.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}
2121.infobox .infobox_part:last-child{margin-bottom:0}
2222.search_categories{margin:10px 0;text-transform:capitalize}
2323.cursor-text{cursor:text !important}
searx/static/themes/oscar/less/oscar/infobox.less+2−1
@@ -1,7 +1,8 @@
11.infobox {
22 .infobox_part {
33 margin-bottom: 20px;
4− word-wrap: break-word;
4+ word-wrap: break-word;
5+ table-layout: fixed;
56 }
67
78 .infobox_part:last-child {
searx/templates/default/infobox.html+8−1
@@ -7,7 +7,14 @@
77 <div class="attributes">
88 <table>
99 {% for attribute in infobox.attributes %}
10− <tr><td>{{ attribute.label }}</td><td>{{ attribute.value }}</td></tr>
10+ <tr>
11+ <td>{{ attribute.label }}</td>
12+ {% if attribute.image %}
13+ <td><img src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
14+ {% else %}
15+ <td>{{ attribute.value }}</td>
16+ {% endif %}
17+ </tr>
1118 {% endfor %}
1219 </table>
1320 </div>
searx/templates/oscar/infobox.html+5−1
@@ -1,6 +1,6 @@
11<div class="panel panel-default infobox">
22 <div class="panel-heading">
3− <h4 class="panel-title">{{ infobox.infobox }}</h4>
3+ <h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4>
44 </div>
55 <div class="panel-body">
66 {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
@@ -11,7 +11,11 @@
1111 {% for attribute in infobox.attributes %}
1212 <tr>
1313 <td>{{ attribute.label }}</td>
14+ {% if attribute.image %}
15+ <td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
16+ {% else %}
1417 <td>{{ attribute.value }}</td>
18+ {% endif %}
1519 </tr>
1620 {% endfor %}
1721 </table>
tests/unit/engines/test_wolframalpha_api.py+112−255
@@ -1,6 +1,7 @@
11# -*- coding: utf-8 -*-
22from collections import defaultdict
33import mock
4+from requests import Request
45from searx.engines import wolframalpha_api
56from searx.testing import SearxTestCase
67
@@ -9,17 +10,17 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
910
1011 def test_request(self):
1112 query = 'test_query'
12− api_key = 'XXXXXX-XXXXXXXXXX'
1313 dicto = defaultdict(dict)
14− dicto['api_key'] = api_key
1514 params = wolframalpha_api.request(query, dicto)
1615
16+ # TODO: test api_key
1717 self.assertIn('url', params)
18+ self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
1819 self.assertIn(query, params['url'])
19− self.assertIn('wolframalpha.com', params['url'])
20+ self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
2021
21− self.assertIn('api_key', params)
22− self.assertIn(api_key, params['api_key'])
22+ def test_replace_pua_chars(self):
23+ self.assertEqual('i', wolframalpha_api.replace_pua_chars(u'\uf74e'))
2324
2425 def test_response(self):
2526 self.assertRaises(AttributeError, wolframalpha_api.response, None)
@@ -27,281 +28,137 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
2728 self.assertRaises(AttributeError, wolframalpha_api.response, '')
2829 self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
2930
31+ referer_url = 'referer_url'
32+ request = Request(headers={'Referer': referer_url})
33+
34+ # test failure
3035 xml = '''<?xml version='1.0' encoding='UTF-8'?>
3136 <queryresult success='false' error='false' />
3237 '''
33− # test failure
3438 response = mock.Mock(content=xml)
3539 self.assertEqual(wolframalpha_api.response(response), [])
3640
41+ # test basic case
3742 xml = """<?xml version='1.0' encoding='UTF-8'?>
3843 <queryresult success='true'
3944 error='false'
40− numpods='6'
41− datatypes=''
42− timedout=''
43− timedoutpods=''
44− timing='0.684'
45− parsetiming='0.138'
46− parsetimedout='false'
47− recalculate=''
48− id='MSPa416020a7966dachc463600000f9c66cc21444cfg'
49− host='http://www3.wolframalpha.com'
50− server='6'
51− related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?...'
45+ numpods='3'
46+ datatypes='Math'
47+ id='queryresult_id'
48+ host='http://www4c.wolframalpha.com'
49+ related='related_url'
5250 version='2.6'>
53− <pod title='Input'
54− scanner='Identity'
55− id='Input'
56− position='100'
57− error='false'
58− numsubpods='1'>
59− <subpod title=''>
60− <plaintext>sqrt(-1)</plaintext>
61− </subpod>
62− </pod>
63− <pod title='Result'
64− scanner='Simplification'
65− id='Result'
66− position='200'
67− error='false'
68− numsubpods='1'
69− primary='true'>
70− <subpod title=''>
71− <plaintext></plaintext>
72− </subpod>
73− <states count='1'>
74− <state name='Step-by-step solution'
75− input='Result__Step-by-step solution' />
76− </states>
77− </pod>
78− <pod title='Polar coordinates'
79− scanner='Numeric'
80− id='PolarCoordinates'
81− position='300'
82− error='false'
83− numsubpods='1'>
84− <subpod title=''>
85− <plaintext>r1 (radius), θ90° (angle)</plaintext>
86− </subpod>
87− </pod>
88− <pod title='Position in the complex plane'
89− scanner='Numeric'
90− id='PositionInTheComplexPlane'
91− position='400'
92− error='false'
93− numsubpods='1'>
94− <subpod title=''>
95− <plaintext></plaintext>
96− </subpod>
97− </pod>
98− <pod title='All 2nd roots of -1'
99− scanner='RootsOfUnity'
100− id=''
101− position='500'
102− error='false'
103− numsubpods='2'>
104− <subpod title=''>
105− <plaintext> (principal root)</plaintext>
106− </subpod>
107− <subpod title=''>
108− <plaintext>-</plaintext>
109− </subpod>
110− </pod>
111− <pod title='Plot of all roots in the complex plane'
112− scanner='RootsOfUnity'
113− id='PlotOfAllRootsInTheComplexPlane'
114− position='600'
115− error='false'
116− numsubpods='1'>
117− <subpod title=''>
118− <plaintext></plaintext>
119− </subpod>
120− </pod>
121− </queryresult>
122− """
123− # test private user area char in response
124− response = mock.Mock(content=xml)
125− results = wolframalpha_api.response(response)
126− self.assertEqual(type(results), list)
127− self.assertEqual(len(results), 2)
128− self.assertIn('i', results[0]['answer'])
129− self.assertIn('sqrt(-1) - Wolfram|Alpha', results[1]['title'])
130− self.assertEquals('http://www.wolframalpha.com/input/?i=sqrt%28-1%29', results[1]['url'])
131−
132− xml = """<?xml version='1.0' encoding='UTF-8'?>
133− <queryresult success='true'
134− error='false'
135− numpods='2'
136− datatypes=''
137− timedout=''
138− timedoutpods=''
139− timing='1.286'
140− parsetiming='0.255'
141− parsetimedout='false'
142− recalculate=''
143− id='MSPa195222ad740ede5214h30000480ca61h003d3gd6'
144− host='http://www3.wolframalpha.com'
145− server='20'
146− related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?id=...'
147− version='2.6'>
148− <pod title='Indefinite integral'
149− scanner='Integral'
150− id='IndefiniteIntegral'
151− position='100'
152− error='false'
51+ <pod title='Input'
52+ scanner='Identity'
53+ id='Input'
54+ numsubpods='1'>
55+ <subpod title=''>
56+ <img src='input_img_src.gif'
57+ alt='input_img_alt'
58+ title='input_img_title' />
59+ <plaintext>input_plaintext</plaintext>
60+ </subpod>
61+ </pod>
62+ <pod title='Result'
63+ scanner='Simplification'
64+ id='Result'
15365 numsubpods='1'
15466 primary='true'>
155− <subpod title=''>
156− <plaintext>∫1/xxlog(x)+constant</plaintext>
157− </subpod>
158− <states count='1'>
159− <state name='Step-by-step solution'
160− input='IndefiniteIntegral__Step-by-step solution' />
161− </states>
162− <infos count='1'>
163− <info text='log(x) is the natural logarithm'>
164− <link url='http://reference.wolfram.com/mathematica/ref/Log.html'
165− text='Documentation'
166− title='Mathematica' />
167− <link url='http://functions.wolfram.com/ElementaryFunctions/Log'
168− text='Properties'
169− title='Wolfram Functions Site' />
170− <link url='http://mathworld.wolfram.com/NaturalLogarithm.html'
171− text='Definition'
172− title='MathWorld' />
173− </info>
174− </infos>
67+ <subpod title=''>
68+ <img src='result_img_src.gif'
69+ alt='result_img_alt'
70+ title='result_img_title' />
71+ <plaintext>result_plaintext</plaintext>
72+ </subpod>
17573 </pod>
176− <pod title='Plots of the integral'
177− scanner='Integral'
178− id='Plot'
179− position='200'
180− error='false'
181− numsubpods='2'>
182− <subpod title=''>
183− <plaintext></plaintext>
184− <states count='1'>
185− <statelist count='2'
186− value='Complex-valued plot'
187− delimiters=''>
188− <state name='Complex-valued plot'
189− input='Plot__1_Complex-valued plot' />
190− <state name='Real-valued plot'
191− input='Plot__1_Real-valued plot' />
192− </statelist>
193− </states>
194− </subpod>
195− <subpod title=''>
196− <plaintext></plaintext>
197− <states count='1'>
198− <statelist count='2'
199− value='Complex-valued plot'
200− delimiters=''>
201− <state name='Complex-valued plot'
202− input='Plot__2_Complex-valued plot' />
203− <state name='Real-valued plot'
204− input='Plot__2_Real-valued plot' />
205− </statelist>
206− </states>
207− </subpod>
74+ <pod title='Manipulatives illustration'
75+ scanner='Arithmetic'
76+ id='Illustration'
77+ numsubpods='1'>
78+ <subpod title=''>
79+ <img src='illustration_img_src.gif'
80+ alt='illustration_img_alt' />
81+ <plaintext>illustration_plaintext</plaintext>
82+ </subpod>
20883 </pod>
209− <assumptions count='1'>
210− <assumption type='Clash'
211− word='integral'
212− template='Assuming &quot;${word}&quot; is ${desc1}. Use as ${desc2} instead'
213− count='2'>
214− <value name='IntegralsWord'
215− desc='an integral'
216− input='*C.integral-_*IntegralsWord-' />
217− <value name='MathematicalFunctionIdentityPropertyClass'
218− desc='a function property'
219− input='*C.integral-_*MathematicalFunctionIdentityPropertyClass-' />
220− </assumption>
221− </assumptions>
222− </queryresult>
84+ </queryresult>
22385 """
224− # test integral
225− response = mock.Mock(content=xml)
86+ response = mock.Mock(content=xml, request=request)
22687 results = wolframalpha_api.response(response)
22788 self.assertEqual(type(results), list)
22889 self.assertEqual(len(results), 2)
229− self.assertIn('log(x)+c', results[0]['answer'])
230− self.assertIn('∫1/xx - Wolfram|Alpha'.decode('utf-8'), results[1]['title'])
231− self.assertEquals('http://www.wolframalpha.com/input/?i=%E2%88%AB1%2Fx%EF%9D%8Cx', results[1]['url'])
90+ self.assertEqual('input_plaintext', results[0]['infobox'])
91+
92+ self.assertEqual(len(results[0]['attributes']), 3)
93+ self.assertEqual('Input', results[0]['attributes'][0]['label'])
94+ self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
95+ self.assertEqual('Result', results[0]['attributes'][1]['label'])
96+ self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
97+ self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
98+ self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
99+ self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
100+
101+ self.assertEqual(len(results[0]['urls']), 1)
232102
103+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
104+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
105+ self.assertEqual(referer_url, results[1]['url'])
106+ self.assertEqual('Wolfram|Alpha', results[1]['title'])
107+
108+ # test calc
233109 xml = """<?xml version='1.0' encoding='UTF-8'?>
234110 <queryresult success='true'
235111 error='false'
236− numpods='4'
237− datatypes='Solve'
238− timedout=''
239− timedoutpods=''
240− timing='0.79'
241− parsetiming='0.338'
112+ numpods='2'
113+ datatypes=''
242114 parsetimedout='false'
243− recalculate=''
244− id='MSPa7481f7i06d25h3deh2900004810i3a78d9b4fdc'
115+ id='queryresult_id'
245116 host='http://www5b.wolframalpha.com'
246− server='23'
247− related='http://www5b.wolframalpha.com/api/v2/relatedQueries.jsp?id=...'
248− version='2.6'>
249− <pod title='Input interpretation'
250− scanner='Identity'
251− id='Input'
252− position='100'
253− error='false'
254− numsubpods='1'>
255− <subpod title=''>
256− <plaintext>solve x^2+x0</plaintext>
257− </subpod>
258− </pod>
259− <pod title='Results'
260− scanner='Solve'
261− id='Result'
262− position='200'
263− error='false'
264− numsubpods='2'
265− primary='true'>
266− <subpod title=''>
267− <plaintext>x-1</plaintext>
268− </subpod>
269− <subpod title=''>
270− <plaintext>x0</plaintext>
271− </subpod>
272− <states count='1'>
273− <state name='Step-by-step solution'
274− input='Result__Step-by-step solution' />
275− </states>
276− </pod>
277− <pod title='Root plot'
278− scanner='Solve'
279− id='RootPlot'
280− position='300'
281− error='false'
282− numsubpods='1'>
283− <subpod title=''>
284− <plaintext></plaintext>
285− </subpod>
286− </pod>
287− <pod title='Number line'
288− scanner='Solve'
289− id='NumberLine'
290− position='400'
291− error='false'
292− numsubpods='1'>
293− <subpod title=''>
294− <plaintext></plaintext>
295− </subpod>
296− </pod>
117+ related='related_url'
118+ version='2.6' >
119+ <pod title='Indefinite integral'
120+ scanner='Integral'
121+ id='IndefiniteIntegral'
122+ error='false'
123+ numsubpods='1'
124+ primary='true'>
125+ <subpod title=''>
126+ <img src='integral_image.gif'
127+ alt='integral_img_alt'
128+ title='integral_img_title' />
129+ <plaintext>integral_plaintext</plaintext>
130+ </subpod>
131+ </pod>
132+ <pod title='Plot of the integral'
133+ scanner='Integral'
134+ id='Plot'
135+ error='false'
136+ numsubpods='1'>
137+ <subpod title=''>
138+ <img src='plot.gif'
139+ alt='plot_alt'
140+ title='' />
141+ <plaintext></plaintext>
142+ </subpod>
143+ </pod>
297144 </queryresult>
298145 """
299− # test ecuation with multiple answers
300− response = mock.Mock(content=xml)
146+ response = mock.Mock(content=xml, request=request)
301147 results = wolframalpha_api.response(response)
302148 self.assertEqual(type(results), list)
303− self.assertEqual(len(results), 3)
304− self.assertIn('x=-1', results[0]['answer'])
305− self.assertIn('x=0', results[1]['answer'])
306− self.assertIn('solve x^2+x0 - Wolfram|Alpha'.decode('utf-8'), results[2]['title'])
307− self.assertEquals('http://www.wolframalpha.com/input/?i=solve+x%5E2%2Bx%EF%9F%990', results[2]['url'])
149+ self.assertEqual(len(results), 2)
150+ self.assertEqual('integral_plaintext', results[0]['infobox'])
151+
152+ self.assertEqual(len(results[0]['attributes']), 2)
153+ self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
154+ self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
155+ self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
156+ self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
157+ self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
158+
159+ self.assertEqual(len(results[0]['urls']), 1)
160+
161+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
162+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
163+ self.assertEqual(referer_url, results[1]['url'])
164+ self.assertEqual('Wolfram|Alpha', results[1]['title'])
tests/unit/engines/test_wolframalpha_noapi.py+202−3
@@ -1,5 +1,7 @@
11# -*- coding: utf-8 -*-
22from collections import defaultdict
3+import mock
4+from requests import Request
35from searx.engines import wolframalpha_noapi
46from searx.testing import SearxTestCase
57
@@ -9,15 +11,212 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase):
911 def test_request(self):
1012 query = 'test_query'
1113 dicto = defaultdict(dict)
12− dicto['pageno'] = 1
1314 params = wolframalpha_noapi.request(query, dicto)
15+
1416 self.assertIn('url', params)
17+ self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
1518 self.assertIn(query, params['url'])
16− self.assertIn('wolframalpha.com', params['url'])
19+ self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
1720
1821 def test_response(self):
1922 self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
2023 self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
2124 self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
2225 self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
23− # TODO
26+
27+ referer_url = 'referer_url'
28+ request = Request(headers={'Referer': referer_url})
29+
30+ # test failure
31+ json = '''
32+ {"queryresult" : {
33+ "success" : false,
34+ "error" : false,
35+ "numpods" : 0,
36+ "id" : "",
37+ "host" : "https:\/\/www5a.wolframalpha.com",
38+ "didyoumeans" : {}
39+ }}
40+ '''
41+ response = mock.Mock(text=json, request=request)
42+ self.assertEqual(wolframalpha_noapi.response(response), [])
43+
44+ # test basic case
45+ json = '''
46+ {"queryresult" : {
47+ "success" : true,
48+ "error" : false,
49+ "numpods" : 6,
50+ "datatypes" : "Math",
51+ "id" : "queryresult_id",
52+ "host" : "https:\/\/www5b.wolframalpha.com",
53+ "related" : "related_url",
54+ "version" : "2.6",
55+ "pods" : [
56+ {
57+ "title" : "Input",
58+ "scanners" : [
59+ "Identity"
60+ ],
61+ "id" : "Input",
62+ "error" : false,
63+ "numsubpods" : 1,
64+ "subpods" : [
65+ {
66+ "title" : "",
67+ "img" : {
68+ "src" : "input_img_src.gif",
69+ "alt" : "input_img_alt",
70+ "title" : "input_img_title"
71+ },
72+ "plaintext" : "input_plaintext",
73+ "minput" : "input_minput"
74+ }
75+ ]
76+ },
77+ {
78+ "title" : "Result",
79+ "scanners" : [
80+ "Simplification"
81+ ],
82+ "id" : "Result",
83+ "error" : false,
84+ "numsubpods" : 1,
85+ "primary" : true,
86+ "subpods" : [
87+ {
88+ "title" : "",
89+ "img" : {
90+ "src" : "result_img_src.gif",
91+ "alt" : "result_img_alt",
92+ "title" : "result_img_title"
93+ },
94+ "plaintext" : "result_plaintext",
95+ "moutput" : "result_moutput"
96+ }
97+ ]
98+ },
99+ {
100+ "title" : "Manipulatives illustration",
101+ "scanners" : [
102+ "Arithmetic"
103+ ],
104+ "id" : "Illustration",
105+ "error" : false,
106+ "numsubpods" : 1,
107+ "subpods" : [
108+ {
109+ "title" : "",
110+ "CDFcontent" : "Resizeable",
111+ "img" : {
112+ "src" : "illustration_img_src.gif",
113+ "alt" : "illustration_img_alt",
114+ "title" : "illustration_img_title"
115+ },
116+ "plaintext" : "illustration_img_plaintext"
117+ }
118+ ]
119+ }
120+ ]
121+ }}
122+ '''
123+ response = mock.Mock(text=json, request=request)
124+ results = wolframalpha_noapi.response(response)
125+ self.assertEqual(type(results), list)
126+ self.assertEqual(len(results), 2)
127+ self.assertEqual('input_plaintext', results[0]['infobox'])
128+
129+ self.assertEqual(len(results[0]['attributes']), 3)
130+ self.assertEqual('Input', results[0]['attributes'][0]['label'])
131+ self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
132+ self.assertEqual('Result', results[0]['attributes'][1]['label'])
133+ self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
134+ self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
135+ self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
136+ self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
137+
138+ self.assertEqual(len(results[0]['urls']), 1)
139+
140+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
141+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
142+ self.assertEqual(referer_url, results[1]['url'])
143+ self.assertEqual('Wolfram|Alpha', results[1]['title'])
144+
145+ # test calc
146+ json = """
147+ {"queryresult" : {
148+ "success" : true,
149+ "error" : false,
150+ "numpods" : 2,
151+ "datatypes" : "",
152+ "id" : "queryresult_id",
153+ "host" : "https:\/\/www4b.wolframalpha.com",
154+ "related" : "related_url",
155+ "version" : "2.6",
156+ "pods" : [
157+ {
158+ "title" : "Indefinite integral",
159+ "scanners" : [
160+ "Integral"
161+ ],
162+ "id" : "IndefiniteIntegral",
163+ "error" : false,
164+ "numsubpods" : 1,
165+ "primary" : true,
166+ "subpods" : [
167+ {
168+ "title" : "",
169+ "img" : {
170+ "src" : "integral_img_src.gif",
171+ "alt" : "integral_img_alt",
172+ "title" : "integral_img_title"
173+ },
174+ "plaintext" : "integral_plaintext",
175+ "minput" : "integral_minput",
176+ "moutput" : "integral_moutput"
177+ }
178+ ]
179+ },
180+ {
181+ "title" : "Plot of the integral",
182+ "scanners" : [
183+ "Integral"
184+ ],
185+ "id" : "Plot",
186+ "error" : false,
187+ "numsubpods" : 1,
188+ "subpods" : [
189+ {
190+ "title" : "",
191+ "img" : {
192+ "src" : "plot.gif",
193+ "alt" : "plot_alt",
194+ "title" : "plot_title"
195+ },
196+ "plaintext" : "",
197+ "minput" : "plot_minput"
198+ }
199+ ]
200+ }
201+ ]
202+ }}
203+ """
204+ response = mock.Mock(text=json, request=request)
205+ results = wolframalpha_noapi.response(response)
206+ self.assertEqual(type(results), list)
207+ self.assertEqual(len(results), 2)
208+ self.assertEqual('integral_plaintext', results[0]['infobox'])
209+
210+ self.assertEqual(len(results[0]['attributes']), 2)
211+ self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
212+ self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
213+ self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
214+ self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
215+ self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
216+
217+ self.assertEqual(len(results[0]['urls']), 1)
218+
219+ self.assertEqual(referer_url, results[0]['urls'][0]['url'])
220+ self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
221+ self.assertEqual(referer_url, results[1]['url'])
222+ self.assertEqual('Wolfram|Alpha', results[1]['title'])