Parcourir la source

Merge pull request #513 from a01200356/wolframalpha

WolframAlpha infobox
Adam Tauber il y a 9 ans
Parent
révision
308613e586

+ 72
- 27
searx/engines/wolframalpha_api.py Voir le fichier

@@ -1,43 +1,60 @@
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 6
 # @using-api   yes
7 7
 # @results     XML
8 8
 # @stable      yes
9
-# @parse       result
9
+# @parse       url, infobox
10 10
 
11 11
 from urllib import urlencode
12 12
 from lxml import etree
13
-from re import search
14 13
 
15 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 17
 api_key = ''  # defined in settings.yml
20 18
 
21 19
 # xpath variables
22 20
 failure_xpath = '/queryresult[attribute::success="false"]'
23 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 38
 # do search-request
28 39
 def request(query, params):
29 40
     params['url'] = search_url.format(query=urlencode({'input': query}),
30 41
                                       api_key=api_key)
42
+    params['headers']['Referer'] = site_url.format(query=urlencode({'i': query}))
31 43
 
32 44
     return params
33 45
 
34 46
 
35 47
 # replace private user area characters to make text legible
36 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 59
     for k, v in pua_chars.iteritems():
43 60
         text = text.replace(k, v)
@@ -55,23 +72,51 @@ def response(resp):
55 72
     if search_results.xpath(failure_xpath):
56 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 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 122
     return results

+ 41
- 20
searx/engines/wolframalpha_noapi.py Voir le fichier

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

@@ -310,8 +310,8 @@ engines:
310 310
     shortcut : wa
311 311
     # You can use the engine using the official stable API, but you need an API key
312 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 315
     engine : wolframalpha_noapi
316 316
     timeout: 6.0
317 317
     categories : science

+ 1
- 1
searx/static/themes/default/css/style.css
Fichier diff supprimé car celui-ci est trop grand
Voir le fichier


+ 3
- 2
searx/static/themes/default/less/style.less Voir le fichier

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

+ 1
- 1
searx/static/themes/oscar/css/oscar.min.css Voir le fichier

@@ -17,7 +17,7 @@ input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbo
17 17
 .result_download{margin-right:5px}
18 18
 #pagination{margin-top:30px;padding-bottom:50px}
19 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 21
 .infobox .infobox_part:last-child{margin-bottom:0}
22 22
 .search_categories{margin:10px 0;text-transform:capitalize}
23 23
 .cursor-text{cursor:text !important}

+ 2
- 1
searx/static/themes/oscar/less/oscar/infobox.less Voir le fichier

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

+ 8
- 1
searx/templates/default/infobox.html Voir le fichier

@@ -7,7 +7,14 @@
7 7
     <div class="attributes">
8 8
         <table>
9 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 18
             {% endfor %}
12 19
         </table>
13 20
     </div>

+ 5
- 1
searx/templates/oscar/infobox.html Voir le fichier

@@ -1,6 +1,6 @@
1 1
 <div class="panel panel-default infobox">
2 2
     <div class="panel-heading">
3
-        <h4 class="panel-title">{{ infobox.infobox }}</h4>
3
+        <h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4>
4 4
     </div>
5 5
     <div class="panel-body">
6 6
         {% 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 @@
11 11
             {% for attribute in infobox.attributes %}
12 12
             <tr>
13 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 17
                 <td>{{ attribute.value }}</td>
18
+                {% endif %}
15 19
             </tr>
16 20
             {% endfor %}
17 21
         </table>

+ 112
- 255
tests/unit/engines/test_wolframalpha_api.py Voir le fichier

@@ -1,6 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 from collections import defaultdict
3 3
 import mock
4
+from requests import Request
4 5
 from searx.engines import wolframalpha_api
5 6
 from searx.testing import SearxTestCase
6 7
 
@@ -9,17 +10,17 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
9 10
 
10 11
     def test_request(self):
11 12
         query = 'test_query'
12
-        api_key = 'XXXXXX-XXXXXXXXXX'
13 13
         dicto = defaultdict(dict)
14
-        dicto['api_key'] = api_key
15 14
         params = wolframalpha_api.request(query, dicto)
16 15
 
16
+        # TODO: test api_key
17 17
         self.assertIn('url', params)
18
+        self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
18 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 25
     def test_response(self):
25 26
         self.assertRaises(AttributeError, wolframalpha_api.response, None)
@@ -27,281 +28,137 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
27 28
         self.assertRaises(AttributeError, wolframalpha_api.response, '')
28 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 35
         xml = '''<?xml version='1.0' encoding='UTF-8'?>
31 36
         <queryresult success='false' error='false' />
32 37
         '''
33
-        # test failure
34 38
         response = mock.Mock(content=xml)
35 39
         self.assertEqual(wolframalpha_api.response(response), [])
36 40
 
41
+        # test basic case
37 42
         xml = """<?xml version='1.0' encoding='UTF-8'?>
38 43
         <queryresult success='true'
39 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 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 65
                  numsubpods='1'
154 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 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 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 87
         results = wolframalpha_api.response(response)
227 88
         self.assertEqual(type(results), list)
228 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 109
         xml = """<?xml version='1.0' encoding='UTF-8'?>
234 110
         <queryresult success='true'
235 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 114
             parsetimedout='false'
243
-            recalculate=''
244
-            id='MSPa7481f7i06d25h3deh2900004810i3a78d9b4fdc'
115
+            id='queryresult_id'
245 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 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 147
         results = wolframalpha_api.response(response)
302 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 Voir le fichier

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