瀏覽代碼

Merge pull request #513 from a01200356/wolframalpha

WolframAlpha infobox
Adam Tauber 9 年之前
父節點
當前提交
308613e586

+ 72
- 27
searx/engines/wolframalpha_api.py 查看文件

1
-# Wolfram Alpha (Maths)
1
+# Wolfram Alpha (Science)
2
 #
2
 #
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/)
5
 #
5
 #
6
 # @using-api   yes
6
 # @using-api   yes
7
 # @results     XML
7
 # @results     XML
8
 # @stable      yes
8
 # @stable      yes
9
-# @parse       result
9
+# @parse       url, infobox
10
 
10
 
11
 from urllib import urlencode
11
 from urllib import urlencode
12
 from lxml import etree
12
 from lxml import etree
13
-from re import search
14
 
13
 
15
 # search-url
14
 # 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}'
19
 api_key = ''  # defined in settings.yml
17
 api_key = ''  # defined in settings.yml
20
 
18
 
21
 # xpath variables
19
 # xpath variables
22
 failure_xpath = '/queryresult[attribute::success="false"]'
20
 failure_xpath = '/queryresult[attribute::success="false"]'
23
 answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext'
21
 answer_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'}
25
 
36
 
26
 
37
 
27
 # do search-request
38
 # do search-request
28
 def request(query, params):
39
 def request(query, params):
29
     params['url'] = search_url.format(query=urlencode({'input': query}),
40
     params['url'] = search_url.format(query=urlencode({'input': query}),
30
                                       api_key=api_key)
41
                                       api_key=api_key)
42
+    params['headers']['Referer'] = site_url.format(query=urlencode({'i': query}))
31
 
43
 
32
     return params
44
     return params
33
 
45
 
34
 
46
 
35
 # replace private user area characters to make text legible
47
 # replace private user area characters to make text legible
36
 def replace_pua_chars(text):
48
 def 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
41
 
58
 
42
     for k, v in pua_chars.iteritems():
59
     for k, v in pua_chars.iteritems():
43
         text = text.replace(k, v)
60
         text = text.replace(k, v)
55
     if search_results.xpath(failure_xpath):
72
     if search_results.xpath(failure_xpath):
56
         return []
73
         return []
57
 
74
 
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
63
 
79
 
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]
65
 
85
 
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')}]})
71
 
116
 
72
     # append link to site
117
     # 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})
76
 
121
 
77
     return results
122
     return results

+ 41
- 20
searx/engines/wolframalpha_noapi.py 查看文件

1
-# WolframAlpha (Maths)
1
+# Wolfram|Alpha (Science)
2
 #
2
 #
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/)
5
 #
5
 #
6
 # @using-api   no
6
 # @using-api   no
7
-# @results     HTML
7
+# @results     JSON
8
 # @stable      no
8
 # @stable      no
9
-# @parse       answer
9
+# @parse       url, infobox
10
 
10
 
11
 from cgi import escape
11
 from cgi import escape
12
 from json import loads
12
 from json import loads
13
 from time import time
13
 from time import time
14
 from urllib import urlencode
14
 from urllib import urlencode
15
+from lxml.etree import XML
15
 
16
 
16
 from searx.poolrequests import get as http_get
17
 from searx.poolrequests import get as http_get
17
 
18
 
18
 # search-url
19
 # search-url
19
 url = 'https://www.wolframalpha.com/'
20
 url = 'https://www.wolframalpha.com/'
20
-search_url = url + 'input/?{query}'
21
 
21
 
22
 search_url = url + 'input/json.jsp'\
22
 search_url = url + 'input/json.jsp'\
23
-    '?async=true'\
23
+    '?async=false'\
24
     '&banners=raw'\
24
     '&banners=raw'\
25
     '&debuggingdata=false'\
25
     '&debuggingdata=false'\
26
     '&format=image,plaintext,imagemap,minput,moutput'\
26
     '&format=image,plaintext,imagemap,minput,moutput'\
33
     '&sponsorcategories=true'\
33
     '&sponsorcategories=true'\
34
     '&statemethod=deploybutton'
34
     '&statemethod=deploybutton'
35
 
35
 
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
+
40
 token = {'value': '',
38
 token = {'value': '',
41
          'last_updated': None}
39
          'last_updated': None}
42
 
40
 
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
+
43
 
47
 
44
 # seems, wolframalpha resets its token in every hour
48
 # seems, wolframalpha resets its token in every hour
45
 def obtain_token():
49
 def obtain_token():
62
     if time() - token['last_updated'] > 3600:
66
     if time() - token['last_updated'] > 3600:
63
         obtain_token()
67
         obtain_token()
64
     params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
68
     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}))
66
 
70
 
67
     return params
71
     return params
68
 
72
 
69
 
73
 
70
 # get response from search-request
74
 # get response from search-request
71
 def response(resp):
75
 def response(resp):
76
+    results = []
77
+
72
     resp_json = loads(resp.text)
78
     resp_json = loads(resp.text)
73
 
79
 
74
     if not resp_json['queryresult']['success']:
80
     if not resp_json['queryresult']['success']:
76
 
82
 
77
     # TODO handle resp_json['queryresult']['assumptions']
83
     # TODO handle resp_json['queryresult']['assumptions']
78
     result_chunks = []
84
     result_chunks = []
85
+    infobox_title = None
79
     for pod in resp_json['queryresult']['pods']:
86
     for pod in resp_json['queryresult']['pods']:
87
+        pod_id = pod.get('id', '')
80
         pod_title = pod.get('title', '')
88
         pod_title = pod.get('title', '')
89
+
81
         if 'subpods' not in pod:
90
         if 'subpods' not in pod:
82
             continue
91
             continue
92
+
93
+        if pod_id == 'Input' or not infobox_title:
94
+            infobox_title = pod['subpods'][0]['plaintext']
95
+
83
         for subpod in pod['subpods']:
96
         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']})
89
 
104
 
90
     if not result_chunks:
105
     if not result_chunks:
91
         return []
106
         return []
92
 
107
 
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

+ 2
- 2
searx/settings.yml 查看文件

310
     shortcut : wa
310
     shortcut : wa
311
     # You can use the engine using the official stable API, but you need an API key
311
     # You can use the engine using the official stable API, but you need an API key
312
     # See : http://products.wolframalpha.com/api/
312
     # See : http://products.wolframalpha.com/api/
313
-    #    engine : wolframalpha_api
314
-    #    api_key: 'apikey' # required!
313
+    # engine : wolframalpha_api
314
+    # api_key: '' # required!
315
     engine : wolframalpha_noapi
315
     engine : wolframalpha_noapi
316
     timeout: 6.0
316
     timeout: 6.0
317
     categories : science
317
     categories : science

+ 1
- 1
searx/static/themes/default/css/style.css
文件差異過大導致無法顯示
查看文件


+ 3
- 2
searx/static/themes/default/less/style.less 查看文件

476
 	   margin: 0px 2px 5px 5px;
476
 	   margin: 0px 2px 5px 5px;
477
 	   padding: 0px 2px 2px;
477
 	   padding: 0px 2px 2px;
478
 	   max-width: 21em;
478
 	   max-width: 21em;
479
+       word-wrap: break-word;
479
 
480
 
480
 	   .infobox {
481
 	   .infobox {
481
 	   	    margin: 10px 0 10px;
482
 	   	    margin: 10px 0 10px;
485
                     /* box-shadow: 0px 0px 5px #CCC; */
486
                     /* box-shadow: 0px 0px 5px #CCC; */
486
 
487
 
487
 	   	    img {
488
 	   	    img {
488
-		    	max-width: 20em;
489
+		    	max-width: 90%;
489
 			max-heigt: 12em;
490
 			max-heigt: 12em;
490
 			display: block;
491
 			display: block;
491
 			margin: 5px;
492
 			margin: 5px;
497
 		    }
498
 		    }
498
 
499
 
499
 		    table {
500
 		    table {
500
-		    	  width: auto;
501
+                  table-layout: fixed;
501
 
502
 
502
 			  td {
503
 			  td {
503
 		       	     vertical-align: top;
504
 		       	     vertical-align: top;

+ 1
- 1
searx/static/themes/oscar/css/oscar.min.css 查看文件

17
 .result_download{margin-right:5px}
17
 .result_download{margin-right:5px}
18
 #pagination{margin-top:30px;padding-bottom:50px}
18
 #pagination{margin-top:30px;padding-bottom:50px}
19
 .label-default{color:#aaa;background:#fff}
19
 .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}
21
 .infobox .infobox_part:last-child{margin-bottom:0}
21
 .infobox .infobox_part:last-child{margin-bottom:0}
22
 .search_categories{margin:10px 0;text-transform:capitalize}
22
 .search_categories{margin:10px 0;text-transform:capitalize}
23
 .cursor-text{cursor:text !important}
23
 .cursor-text{cursor:text !important}

+ 2
- 1
searx/static/themes/oscar/less/oscar/infobox.less 查看文件

1
 .infobox {
1
 .infobox {
2
     .infobox_part {
2
     .infobox_part {
3
         margin-bottom: 20px;
3
         margin-bottom: 20px;
4
-         word-wrap: break-word;
4
+        word-wrap: break-word;
5
+        table-layout: fixed;
5
     }
6
     }
6
     
7
     
7
     .infobox_part:last-child {
8
     .infobox_part:last-child {

+ 8
- 1
searx/templates/default/infobox.html 查看文件

7
     <div class="attributes">
7
     <div class="attributes">
8
         <table>
8
         <table>
9
             {% for attribute in infobox.attributes %}
9
             {% 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>
11
             {% endfor %}
18
             {% endfor %}
12
         </table>
19
         </table>
13
     </div>
20
     </div>

+ 5
- 1
searx/templates/oscar/infobox.html 查看文件

1
 <div class="panel panel-default infobox">
1
 <div class="panel panel-default infobox">
2
     <div class="panel-heading">
2
     <div class="panel-heading">
3
-        <h4 class="panel-title">{{ infobox.infobox }}</h4>
3
+        <h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4>
4
     </div>
4
     </div>
5
     <div class="panel-body">
5
     <div class="panel-body">
6
         {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
6
         {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
11
             {% for attribute in infobox.attributes %}
11
             {% for attribute in infobox.attributes %}
12
             <tr>
12
             <tr>
13
                 <td>{{ attribute.label }}</td>
13
                 <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 %}
14
                 <td>{{ attribute.value }}</td>
17
                 <td>{{ attribute.value }}</td>
18
+                {% endif %}
15
             </tr>
19
             </tr>
16
             {% endfor %}
20
             {% endfor %}
17
         </table>
21
         </table>

+ 112
- 255
tests/unit/engines/test_wolframalpha_api.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from collections import defaultdict
2
 from collections import defaultdict
3
 import mock
3
 import mock
4
+from requests import Request
4
 from searx.engines import wolframalpha_api
5
 from searx.engines import wolframalpha_api
5
 from searx.testing import SearxTestCase
6
 from searx.testing import SearxTestCase
6
 
7
 
9
 
10
 
10
     def test_request(self):
11
     def test_request(self):
11
         query = 'test_query'
12
         query = 'test_query'
12
-        api_key = 'XXXXXX-XXXXXXXXXX'
13
         dicto = defaultdict(dict)
13
         dicto = defaultdict(dict)
14
-        dicto['api_key'] = api_key
15
         params = wolframalpha_api.request(query, dicto)
14
         params = wolframalpha_api.request(query, dicto)
16
 
15
 
16
+        # TODO: test api_key
17
         self.assertIn('url', params)
17
         self.assertIn('url', params)
18
+        self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
18
         self.assertIn(query, params['url'])
19
         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'])
20
 
21
 
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'))
23
 
24
 
24
     def test_response(self):
25
     def test_response(self):
25
         self.assertRaises(AttributeError, wolframalpha_api.response, None)
26
         self.assertRaises(AttributeError, wolframalpha_api.response, None)
27
         self.assertRaises(AttributeError, wolframalpha_api.response, '')
28
         self.assertRaises(AttributeError, wolframalpha_api.response, '')
28
         self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
29
         self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
29
 
30
 
31
+        referer_url = 'referer_url'
32
+        request = Request(headers={'Referer': referer_url})
33
+
34
+        # test failure
30
         xml = '''<?xml version='1.0' encoding='UTF-8'?>
35
         xml = '''<?xml version='1.0' encoding='UTF-8'?>
31
         <queryresult success='false' error='false' />
36
         <queryresult success='false' error='false' />
32
         '''
37
         '''
33
-        # test failure
34
         response = mock.Mock(content=xml)
38
         response = mock.Mock(content=xml)
35
         self.assertEqual(wolframalpha_api.response(response), [])
39
         self.assertEqual(wolframalpha_api.response(response), [])
36
 
40
 
41
+        # test basic case
37
         xml = """<?xml version='1.0' encoding='UTF-8'?>
42
         xml = """<?xml version='1.0' encoding='UTF-8'?>
38
         <queryresult success='true'
43
         <queryresult success='true'
39
             error='false'
44
             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'
52
             version='2.6'>
50
             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'
153
                  numsubpods='1'
65
                  numsubpods='1'
154
                  primary='true'>
66
                  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>
175
              </pod>
73
              </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>
208
              </pod>
83
              </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>
223
         """
85
         """
224
-        # test integral
225
-        response = mock.Mock(content=xml)
86
+        response = mock.Mock(content=xml, request=request)
226
         results = wolframalpha_api.response(response)
87
         results = wolframalpha_api.response(response)
227
         self.assertEqual(type(results), list)
88
         self.assertEqual(type(results), list)
228
         self.assertEqual(len(results), 2)
89
         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)
232
 
102
 
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
233
         xml = """<?xml version='1.0' encoding='UTF-8'?>
109
         xml = """<?xml version='1.0' encoding='UTF-8'?>
234
         <queryresult success='true'
110
         <queryresult success='true'
235
             error='false'
111
             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=''
242
             parsetimedout='false'
114
             parsetimedout='false'
243
-            recalculate=''
244
-            id='MSPa7481f7i06d25h3deh2900004810i3a78d9b4fdc'
115
+            id='queryresult_id'
245
             host='http://www5b.wolframalpha.com'
116
             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>
297
         </queryresult>
144
         </queryresult>
298
         """
145
         """
299
-        # test ecuation with multiple answers
300
-        response = mock.Mock(content=xml)
146
+        response = mock.Mock(content=xml, request=request)
301
         results = wolframalpha_api.response(response)
147
         results = wolframalpha_api.response(response)
302
         self.assertEqual(type(results), list)
148
         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'])

+ 202
- 3
tests/unit/engines/test_wolframalpha_noapi.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from collections import defaultdict
2
 from collections import defaultdict
3
+import mock
4
+from requests import Request
3
 from searx.engines import wolframalpha_noapi
5
 from searx.engines import wolframalpha_noapi
4
 from searx.testing import SearxTestCase
6
 from searx.testing import SearxTestCase
5
 
7
 
9
     def test_request(self):
11
     def test_request(self):
10
         query = 'test_query'
12
         query = 'test_query'
11
         dicto = defaultdict(dict)
13
         dicto = defaultdict(dict)
12
-        dicto['pageno'] = 1
13
         params = wolframalpha_noapi.request(query, dicto)
14
         params = wolframalpha_noapi.request(query, dicto)
15
+
14
         self.assertIn('url', params)
16
         self.assertIn('url', params)
17
+        self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
15
         self.assertIn(query, params['url'])
18
         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'])
17
 
20
 
18
     def test_response(self):
21
     def test_response(self):
19
         self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
22
         self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
20
         self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
23
         self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
21
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
24
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
22
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
25
         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'])