Browse Source

Merge pull request #513 from a01200356/wolframalpha

WolframAlpha infobox
Adam Tauber 9 years ago
parent
commit
308613e586

+ 72 - 27
searx/engines/wolframalpha_api.py

@@ -1,43 +1,60 @@
-# Wolfram Alpha (Maths)
+# Wolfram Alpha (Science)
 #
 #
-# @website     http://www.wolframalpha.com
-# @provide-api yes (http://api.wolframalpha.com/v2/)
+# @website     https://www.wolframalpha.com
+# @provide-api yes (https://api.wolframalpha.com/v2/)
 #
 #
 # @using-api   yes
 # @using-api   yes
 # @results     XML
 # @results     XML
 # @stable      yes
 # @stable      yes
-# @parse       result
+# @parse       url, infobox
 
 
 from urllib import urlencode
 from urllib import urlencode
 from lxml import etree
 from lxml import etree
-from re import search
 
 
 # search-url
 # search-url
-base_url = 'http://api.wolframalpha.com/v2/query'
-search_url = base_url + '?appid={api_key}&{query}&format=plaintext'
-site_url = 'http://www.wolframalpha.com/input/?{query}'
+search_url = 'https://api.wolframalpha.com/v2/query?appid={api_key}&{query}'
+site_url = 'https://www.wolframalpha.com/input/?{query}'
 api_key = ''  # defined in settings.yml
 api_key = ''  # defined in settings.yml
 
 
 # xpath variables
 # xpath variables
 failure_xpath = '/queryresult[attribute::success="false"]'
 failure_xpath = '/queryresult[attribute::success="false"]'
 answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext'
 answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext'
-input_xpath = '//pod[starts-with(attribute::title, "Input")]/subpod/plaintext'
+input_xpath = '//pod[starts-with(attribute::id, "Input")]/subpod/plaintext'
+pods_xpath = '//pod'
+subpods_xpath = './subpod'
+pod_id_xpath = './@id'
+pod_title_xpath = './@title'
+plaintext_xpath = './plaintext'
+image_xpath = './img'
+img_src_xpath = './@src'
+img_alt_xpath = './@alt'
+
+# pods to display as image in infobox
+# this pods do return a plaintext, but they look better and are more useful as images
+image_pods = {'VisualRepresentation',
+              'Illustration'}
 
 
 
 
 # do search-request
 # do search-request
 def request(query, params):
 def request(query, params):
     params['url'] = search_url.format(query=urlencode({'input': query}),
     params['url'] = search_url.format(query=urlencode({'input': query}),
                                       api_key=api_key)
                                       api_key=api_key)
+    params['headers']['Referer'] = site_url.format(query=urlencode({'i': query}))
 
 
     return params
     return params
 
 
 
 
 # replace private user area characters to make text legible
 # replace private user area characters to make text legible
 def replace_pua_chars(text):
 def replace_pua_chars(text):
-    pua_chars = {u'\uf74c': 'd',
-                 u'\uf74d': u'\u212f',
-                 u'\uf74e': 'i',
-                 u'\uf7d9': '='}
+    pua_chars = {u'\uf522': u'\u2192',  # rigth arrow
+                 u'\uf7b1': u'\u2115',  # set of natural numbers
+                 u'\uf7b4': u'\u211a',  # set of rational numbers
+                 u'\uf7b5': u'\u211d',  # set of real numbers
+                 u'\uf7bd': u'\u2124',  # set of integer numbers
+                 u'\uf74c': 'd',        # differential
+                 u'\uf74d': u'\u212f',  # euler's number
+                 u'\uf74e': 'i',        # imaginary number
+                 u'\uf7d9': '='}        # equals sign
 
 
     for k, v in pua_chars.iteritems():
     for k, v in pua_chars.iteritems():
         text = text.replace(k, v)
         text = text.replace(k, v)
@@ -55,23 +72,51 @@ def response(resp):
     if search_results.xpath(failure_xpath):
     if search_results.xpath(failure_xpath):
         return []
         return []
 
 
-    # parse answers
-    answers = search_results.xpath(answer_xpath)
-    if answers:
-        for answer in answers:
-            answer = replace_pua_chars(answer.text)
+    try:
+        infobox_title = search_results.xpath(input_xpath)[0].text
+    except:
+        infobox_title = None
 
 
-            results.append({'answer': answer})
+    pods = search_results.xpath(pods_xpath)
+    result_chunks = []
+    for pod in pods:
+        pod_id = pod.xpath(pod_id_xpath)[0]
+        pod_title = pod.xpath(pod_title_xpath)[0]
 
 
-    # if there's no input section in search_results, check if answer has the input embedded (before their "=" sign)
-    try:
-        query_input = search_results.xpath(input_xpath)[0].text
-    except IndexError:
-        query_input = search(u'([^\uf7d9]+)', answers[0].text).group(1)
+        subpods = pod.xpath(subpods_xpath)
+        if not subpods:
+            continue
+
+        # Appends either a text or an image, depending on which one is more suitable
+        for subpod in subpods:
+            content = subpod.xpath(plaintext_xpath)[0].text
+            image = subpod.xpath(image_xpath)
+
+            if content and pod_id not in image_pods:
+
+                # if no input pod was found, title is first plaintext pod
+                if not infobox_title:
+                    infobox_title = content
+
+                content = replace_pua_chars(content)
+                result_chunks.append({'label': pod_title, 'value': content})
+
+            elif image:
+                result_chunks.append({'label': pod_title,
+                                      'image': {'src': image[0].xpath(img_src_xpath)[0],
+                                                'alt': image[0].xpath(img_alt_xpath)[0]}})
+
+    if not result_chunks:
+        return []
+
+    # append infobox
+    results.append({'infobox': infobox_title,
+                    'attributes': result_chunks,
+                    'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
 
 
     # append link to site
     # append link to site
-    result_url = site_url.format(query=urlencode({'i': query_input.encode('utf-8')}))
-    results.append({'url': result_url,
-                    'title': query_input + " - Wolfram|Alpha"})
+    results.append({'url': resp.request.headers['Referer'].decode('utf8'),
+                    'title': 'Wolfram|Alpha',
+                    'content': infobox_title})
 
 
     return results
     return results

+ 41 - 20
searx/engines/wolframalpha_noapi.py

@@ -1,26 +1,26 @@
-# WolframAlpha (Maths)
+# Wolfram|Alpha (Science)
 #
 #
-# @website     http://www.wolframalpha.com/
-# @provide-api yes (http://api.wolframalpha.com/v2/)
+# @website     https://www.wolframalpha.com/
+# @provide-api yes (https://api.wolframalpha.com/v2/)
 #
 #
 # @using-api   no
 # @using-api   no
-# @results     HTML
+# @results     JSON
 # @stable      no
 # @stable      no
-# @parse       answer
+# @parse       url, infobox
 
 
 from cgi import escape
 from cgi import escape
 from json import loads
 from json import loads
 from time import time
 from time import time
 from urllib import urlencode
 from urllib import urlencode
+from lxml.etree import XML
 
 
 from searx.poolrequests import get as http_get
 from searx.poolrequests import get as http_get
 
 
 # search-url
 # search-url
 url = 'https://www.wolframalpha.com/'
 url = 'https://www.wolframalpha.com/'
-search_url = url + 'input/?{query}'
 
 
 search_url = url + 'input/json.jsp'\
 search_url = url + 'input/json.jsp'\
-    '?async=true'\
+    '?async=false'\
     '&banners=raw'\
     '&banners=raw'\
     '&debuggingdata=false'\
     '&debuggingdata=false'\
     '&format=image,plaintext,imagemap,minput,moutput'\
     '&format=image,plaintext,imagemap,minput,moutput'\
@@ -33,13 +33,17 @@ search_url = url + 'input/json.jsp'\
     '&sponsorcategories=true'\
     '&sponsorcategories=true'\
     '&statemethod=deploybutton'
     '&statemethod=deploybutton'
 
 
-# xpath variables
-scripts_xpath = '//script'
-title_xpath = '//title'
-failure_xpath = '//p[attribute::class="pfail"]'
+referer_url = url + 'input/?{query}'
+
 token = {'value': '',
 token = {'value': '',
          'last_updated': None}
          'last_updated': None}
 
 
+# pods to display as image in infobox
+# this pods do return a plaintext, but they look better and are more useful as images
+image_pods = {'VisualRepresentation',
+              'Illustration',
+              'Symbol'}
+
 
 
 # seems, wolframalpha resets its token in every hour
 # seems, wolframalpha resets its token in every hour
 def obtain_token():
 def obtain_token():
@@ -62,13 +66,15 @@ def request(query, params):
     if time() - token['last_updated'] > 3600:
     if time() - token['last_updated'] > 3600:
         obtain_token()
         obtain_token()
     params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
     params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
-    params['headers']['Referer'] = 'https://www.wolframalpha.com/input/?i=' + query
+    params['headers']['Referer'] = referer_url.format(query=urlencode({'i': query}))
 
 
     return params
     return params
 
 
 
 
 # get response from search-request
 # get response from search-request
 def response(resp):
 def response(resp):
+    results = []
+
     resp_json = loads(resp.text)
     resp_json = loads(resp.text)
 
 
     if not resp_json['queryresult']['success']:
     if not resp_json['queryresult']['success']:
@@ -76,20 +82,35 @@ def response(resp):
 
 
     # TODO handle resp_json['queryresult']['assumptions']
     # TODO handle resp_json['queryresult']['assumptions']
     result_chunks = []
     result_chunks = []
+    infobox_title = None
     for pod in resp_json['queryresult']['pods']:
     for pod in resp_json['queryresult']['pods']:
+        pod_id = pod.get('id', '')
         pod_title = pod.get('title', '')
         pod_title = pod.get('title', '')
+
         if 'subpods' not in pod:
         if 'subpods' not in pod:
             continue
             continue
+
+        if pod_id == 'Input' or not infobox_title:
+            infobox_title = pod['subpods'][0]['plaintext']
+
         for subpod in pod['subpods']:
         for subpod in pod['subpods']:
-            if 'img' in subpod:
-                result_chunks.append(u'<p>{0}<br /><img src="{1}" alt="{2}" /></p>'
-                                     .format(escape(pod_title or subpod['img']['alt']),
-                                             escape(subpod['img']['src']),
-                                             escape(subpod['img']['alt'])))
+            if subpod['plaintext'] != '' and pod_id not in image_pods:
+                # append unless it's not an actual answer
+                if subpod['plaintext'] != '(requires interactivity)':
+                    result_chunks.append({'label': pod_title, 'value': subpod['plaintext']})
+
+            elif 'img' in subpod:
+                result_chunks.append({'label': pod_title, 'image': subpod['img']})
 
 
     if not result_chunks:
     if not result_chunks:
         return []
         return []
 
 
-    return [{'url': resp.request.headers['Referer'].decode('utf-8'),
-             'title': 'Wolframalpha',
-             'content': ''.join(result_chunks)}]
+    results.append({'infobox': infobox_title,
+                    'attributes': result_chunks,
+                    'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
+
+    results.append({'url': resp.request.headers['Referer'].decode('utf8'),
+                    'title': 'Wolfram|Alpha',
+                    'content': infobox_title})
+
+    return results

+ 2 - 2
searx/settings.yml

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

File diff suppressed because it is too large
+ 0 - 0
searx/static/themes/default/css/style.css


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

@@ -476,6 +476,7 @@ color: @color-font-light;
 	   margin: 0px 2px 5px 5px;
 	   margin: 0px 2px 5px 5px;
 	   padding: 0px 2px 2px;
 	   padding: 0px 2px 2px;
 	   max-width: 21em;
 	   max-width: 21em;
+       word-wrap: break-word;
 
 
 	   .infobox {
 	   .infobox {
 	   	    margin: 10px 0 10px;
 	   	    margin: 10px 0 10px;
@@ -485,7 +486,7 @@ color: @color-font-light;
                     /* box-shadow: 0px 0px 5px #CCC; */
                     /* box-shadow: 0px 0px 5px #CCC; */
 
 
 	   	    img {
 	   	    img {
-		    	max-width: 20em;
+		    	max-width: 90%;
 			max-heigt: 12em;
 			max-heigt: 12em;
 			display: block;
 			display: block;
 			margin: 5px;
 			margin: 5px;
@@ -497,7 +498,7 @@ color: @color-font-light;
 		    }
 		    }
 
 
 		    table {
 		    table {
-		    	  width: auto;
+                  table-layout: fixed;
 
 
 			  td {
 			  td {
 		       	     vertical-align: top;
 		       	     vertical-align: top;

+ 1 - 1
searx/static/themes/oscar/css/oscar.min.css

@@ -17,7 +17,7 @@ input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbo
 .result_download{margin-right:5px}
 .result_download{margin-right:5px}
 #pagination{margin-top:30px;padding-bottom:50px}
 #pagination{margin-top:30px;padding-bottom:50px}
 .label-default{color:#aaa;background:#fff}
 .label-default{color:#aaa;background:#fff}
-.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word}
+.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}
 .infobox .infobox_part:last-child{margin-bottom:0}
 .infobox .infobox_part:last-child{margin-bottom:0}
 .search_categories{margin:10px 0;text-transform:capitalize}
 .search_categories{margin:10px 0;text-transform:capitalize}
 .cursor-text{cursor:text !important}
 .cursor-text{cursor:text !important}

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

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

+ 8 - 1
searx/templates/default/infobox.html

@@ -7,7 +7,14 @@
     <div class="attributes">
     <div class="attributes">
         <table>
         <table>
             {% for attribute in infobox.attributes %}
             {% for attribute in infobox.attributes %}
-            <tr><td>{{ attribute.label }}</td><td>{{ attribute.value }}</td></tr>
+            <tr>
+                <td>{{ attribute.label }}</td>
+                {% if attribute.image %}
+                <td><img src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
+                {% else %}
+                <td>{{ attribute.value }}</td>
+                {% endif %}
+            </tr>
             {% endfor %}
             {% endfor %}
         </table>
         </table>
     </div>
     </div>

+ 5 - 1
searx/templates/oscar/infobox.html

@@ -1,6 +1,6 @@
 <div class="panel panel-default infobox">
 <div class="panel panel-default infobox">
     <div class="panel-heading">
     <div class="panel-heading">
-        <h4 class="panel-title">{{ infobox.infobox }}</h4>
+        <h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4>
     </div>
     </div>
     <div class="panel-body">
     <div class="panel-body">
         {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
         {% 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 @@
             {% for attribute in infobox.attributes %}
             {% for attribute in infobox.attributes %}
             <tr>
             <tr>
                 <td>{{ attribute.label }}</td>
                 <td>{{ attribute.label }}</td>
+                {% if attribute.image %}
+                <td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
+                {% else %}
                 <td>{{ attribute.value }}</td>
                 <td>{{ attribute.value }}</td>
+                {% endif %}
             </tr>
             </tr>
             {% endfor %}
             {% endfor %}
         </table>
         </table>

+ 112 - 255
tests/unit/engines/test_wolframalpha_api.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from collections import defaultdict
 from collections import defaultdict
 import mock
 import mock
+from requests import Request
 from searx.engines import wolframalpha_api
 from searx.engines import wolframalpha_api
 from searx.testing import SearxTestCase
 from searx.testing import SearxTestCase
 
 
@@ -9,17 +10,17 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
 
 
     def test_request(self):
     def test_request(self):
         query = 'test_query'
         query = 'test_query'
-        api_key = 'XXXXXX-XXXXXXXXXX'
         dicto = defaultdict(dict)
         dicto = defaultdict(dict)
-        dicto['api_key'] = api_key
         params = wolframalpha_api.request(query, dicto)
         params = wolframalpha_api.request(query, dicto)
 
 
+        # TODO: test api_key
         self.assertIn('url', params)
         self.assertIn('url', params)
+        self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
         self.assertIn(query, params['url'])
         self.assertIn(query, params['url'])
-        self.assertIn('wolframalpha.com', params['url'])
+        self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
 
 
-        self.assertIn('api_key', params)
-        self.assertIn(api_key, params['api_key'])
+    def test_replace_pua_chars(self):
+        self.assertEqual('i', wolframalpha_api.replace_pua_chars(u'\uf74e'))
 
 
     def test_response(self):
     def test_response(self):
         self.assertRaises(AttributeError, wolframalpha_api.response, None)
         self.assertRaises(AttributeError, wolframalpha_api.response, None)
@@ -27,281 +28,137 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
         self.assertRaises(AttributeError, wolframalpha_api.response, '')
         self.assertRaises(AttributeError, wolframalpha_api.response, '')
         self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
         self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
 
 
+        referer_url = 'referer_url'
+        request = Request(headers={'Referer': referer_url})
+
+        # test failure
         xml = '''<?xml version='1.0' encoding='UTF-8'?>
         xml = '''<?xml version='1.0' encoding='UTF-8'?>
         <queryresult success='false' error='false' />
         <queryresult success='false' error='false' />
         '''
         '''
-        # test failure
         response = mock.Mock(content=xml)
         response = mock.Mock(content=xml)
         self.assertEqual(wolframalpha_api.response(response), [])
         self.assertEqual(wolframalpha_api.response(response), [])
 
 
+        # test basic case
         xml = """<?xml version='1.0' encoding='UTF-8'?>
         xml = """<?xml version='1.0' encoding='UTF-8'?>
         <queryresult success='true'
         <queryresult success='true'
             error='false'
             error='false'
-            numpods='6'
-            datatypes=''
-            timedout=''
-            timedoutpods=''
-            timing='0.684'
-            parsetiming='0.138'
-            parsetimedout='false'
-            recalculate=''
-            id='MSPa416020a7966dachc463600000f9c66cc21444cfg'
-            host='http://www3.wolframalpha.com'
-            server='6'
-            related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?...'
+            numpods='3'
+            datatypes='Math'
+            id='queryresult_id'
+            host='http://www4c.wolframalpha.com'
+            related='related_url'
             version='2.6'>
             version='2.6'>
-         <pod title='Input'
-             scanner='Identity'
-             id='Input'
-             position='100'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext>sqrt(-1)</plaintext>
-          </subpod>
-         </pod>
-         <pod title='Result'
-             scanner='Simplification'
-             id='Result'
-             position='200'
-             error='false'
-             numsubpods='1'
-             primary='true'>
-          <subpod title=''>
-           <plaintext></plaintext>
-          </subpod>
-          <states count='1'>
-           <state name='Step-by-step solution'
-               input='Result__Step-by-step solution' />
-          </states>
-         </pod>
-         <pod title='Polar coordinates'
-             scanner='Numeric'
-             id='PolarCoordinates'
-             position='300'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext>r1 (radius), θ90° (angle)</plaintext>
-          </subpod>
-         </pod>
-         <pod title='Position in the complex plane'
-             scanner='Numeric'
-             id='PositionInTheComplexPlane'
-             position='400'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext></plaintext>
-          </subpod>
-         </pod>
-         <pod title='All 2nd roots of -1'
-             scanner='RootsOfUnity'
-             id=''
-             position='500'
-             error='false'
-             numsubpods='2'>
-          <subpod title=''>
-           <plaintext>  (principal root)</plaintext>
-          </subpod>
-          <subpod title=''>
-           <plaintext>-</plaintext>
-          </subpod>
-         </pod>
-         <pod title='Plot of all roots in the complex plane'
-             scanner='RootsOfUnity'
-             id='PlotOfAllRootsInTheComplexPlane'
-             position='600'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext></plaintext>
-          </subpod>
-         </pod>
-        </queryresult>
-        """
-        # test private user area char in response
-        response = mock.Mock(content=xml)
-        results = wolframalpha_api.response(response)
-        self.assertEqual(type(results), list)
-        self.assertEqual(len(results), 2)
-        self.assertIn('i', results[0]['answer'])
-        self.assertIn('sqrt(-1) - Wolfram|Alpha', results[1]['title'])
-        self.assertEquals('http://www.wolframalpha.com/input/?i=sqrt%28-1%29', results[1]['url'])
-
-        xml = """<?xml version='1.0' encoding='UTF-8'?>
-            <queryresult success='true'
-                error='false'
-                numpods='2'
-                datatypes=''
-                timedout=''
-                timedoutpods=''
-                timing='1.286'
-                parsetiming='0.255'
-                parsetimedout='false'
-                recalculate=''
-                id='MSPa195222ad740ede5214h30000480ca61h003d3gd6'
-                host='http://www3.wolframalpha.com'
-                server='20'
-                related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?id=...'
-                version='2.6'>
-             <pod title='Indefinite integral'
-                 scanner='Integral'
-                 id='IndefiniteIntegral'
-                 position='100'
-                 error='false'
+            <pod title='Input'
+                 scanner='Identity'
+                 id='Input'
+                 numsubpods='1'>
+                  <subpod title=''>
+                       <img src='input_img_src.gif'
+                           alt='input_img_alt'
+                           title='input_img_title' />
+                       <plaintext>input_plaintext</plaintext>
+                  </subpod>
+             </pod>
+             <pod title='Result'
+                 scanner='Simplification'
+                 id='Result'
                  numsubpods='1'
                  numsubpods='1'
                  primary='true'>
                  primary='true'>
-              <subpod title=''>
-               <plaintext>∫1/xxlog(x)+constant</plaintext>
-              </subpod>
-              <states count='1'>
-               <state name='Step-by-step solution'
-                   input='IndefiniteIntegral__Step-by-step solution' />
-              </states>
-              <infos count='1'>
-               <info text='log(x) is the natural logarithm'>
-                <link url='http://reference.wolfram.com/mathematica/ref/Log.html'
-                    text='Documentation'
-                    title='Mathematica' />
-                <link url='http://functions.wolfram.com/ElementaryFunctions/Log'
-                    text='Properties'
-                    title='Wolfram Functions Site' />
-                <link url='http://mathworld.wolfram.com/NaturalLogarithm.html'
-                    text='Definition'
-                    title='MathWorld' />
-               </info>
-              </infos>
+                  <subpod title=''>
+                       <img src='result_img_src.gif'
+                           alt='result_img_alt'
+                           title='result_img_title' />
+                       <plaintext>result_plaintext</plaintext>
+                  </subpod>
              </pod>
              </pod>
-             <pod title='Plots of the integral'
-                 scanner='Integral'
-                 id='Plot'
-                 position='200'
-                 error='false'
-                 numsubpods='2'>
-              <subpod title=''>
-               <plaintext></plaintext>
-               <states count='1'>
-                <statelist count='2'
-                    value='Complex-valued plot'
-                    delimiters=''>
-                 <state name='Complex-valued plot'
-                     input='Plot__1_Complex-valued plot' />
-                 <state name='Real-valued plot'
-                     input='Plot__1_Real-valued plot' />
-                </statelist>
-               </states>
-              </subpod>
-              <subpod title=''>
-               <plaintext></plaintext>
-               <states count='1'>
-                <statelist count='2'
-                    value='Complex-valued plot'
-                    delimiters=''>
-                 <state name='Complex-valued plot'
-                     input='Plot__2_Complex-valued plot' />
-                 <state name='Real-valued plot'
-                     input='Plot__2_Real-valued plot' />
-                </statelist>
-               </states>
-              </subpod>
+             <pod title='Manipulatives illustration'
+                 scanner='Arithmetic'
+                 id='Illustration'
+                 numsubpods='1'>
+                  <subpod title=''>
+                       <img src='illustration_img_src.gif'
+                           alt='illustration_img_alt' />
+                       <plaintext>illustration_plaintext</plaintext>
+                  </subpod>
              </pod>
              </pod>
-             <assumptions count='1'>
-              <assumption type='Clash'
-                  word='integral'
-                  template='Assuming &quot;${word}&quot; is ${desc1}. Use as ${desc2} instead'
-                  count='2'>
-               <value name='IntegralsWord'
-                   desc='an integral'
-                   input='*C.integral-_*IntegralsWord-' />
-               <value name='MathematicalFunctionIdentityPropertyClass'
-                   desc='a function property'
-                   input='*C.integral-_*MathematicalFunctionIdentityPropertyClass-' />
-              </assumption>
-             </assumptions>
-            </queryresult>
+        </queryresult>
         """
         """
-        # test integral
-        response = mock.Mock(content=xml)
+        response = mock.Mock(content=xml, request=request)
         results = wolframalpha_api.response(response)
         results = wolframalpha_api.response(response)
         self.assertEqual(type(results), list)
         self.assertEqual(type(results), list)
         self.assertEqual(len(results), 2)
         self.assertEqual(len(results), 2)
-        self.assertIn('log(x)+c', results[0]['answer'])
-        self.assertIn('∫1/xx - Wolfram|Alpha'.decode('utf-8'), results[1]['title'])
-        self.assertEquals('http://www.wolframalpha.com/input/?i=%E2%88%AB1%2Fx%EF%9D%8Cx', results[1]['url'])
+        self.assertEqual('input_plaintext', results[0]['infobox'])
+
+        self.assertEqual(len(results[0]['attributes']), 3)
+        self.assertEqual('Input', results[0]['attributes'][0]['label'])
+        self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
+        self.assertEqual('Result', results[0]['attributes'][1]['label'])
+        self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
+        self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
+        self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
+        self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
+
+        self.assertEqual(len(results[0]['urls']), 1)
 
 
+        self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+        self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+        self.assertEqual(referer_url, results[1]['url'])
+        self.assertEqual('Wolfram|Alpha', results[1]['title'])
+
+        # test calc
         xml = """<?xml version='1.0' encoding='UTF-8'?>
         xml = """<?xml version='1.0' encoding='UTF-8'?>
         <queryresult success='true'
         <queryresult success='true'
             error='false'
             error='false'
-            numpods='4'
-            datatypes='Solve'
-            timedout=''
-            timedoutpods=''
-            timing='0.79'
-            parsetiming='0.338'
+            numpods='2'
+            datatypes=''
             parsetimedout='false'
             parsetimedout='false'
-            recalculate=''
-            id='MSPa7481f7i06d25h3deh2900004810i3a78d9b4fdc'
+            id='queryresult_id'
             host='http://www5b.wolframalpha.com'
             host='http://www5b.wolframalpha.com'
-            server='23'
-            related='http://www5b.wolframalpha.com/api/v2/relatedQueries.jsp?id=...'
-            version='2.6'>
-         <pod title='Input interpretation'
-             scanner='Identity'
-             id='Input'
-             position='100'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext>solve x^2+x0</plaintext>
-          </subpod>
-         </pod>
-         <pod title='Results'
-             scanner='Solve'
-             id='Result'
-             position='200'
-             error='false'
-             numsubpods='2'
-             primary='true'>
-          <subpod title=''>
-           <plaintext>x-1</plaintext>
-          </subpod>
-          <subpod title=''>
-           <plaintext>x0</plaintext>
-          </subpod>
-          <states count='1'>
-           <state name='Step-by-step solution'
-               input='Result__Step-by-step solution' />
-          </states>
-         </pod>
-         <pod title='Root plot'
-             scanner='Solve'
-             id='RootPlot'
-             position='300'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext></plaintext>
-          </subpod>
-         </pod>
-         <pod title='Number line'
-             scanner='Solve'
-             id='NumberLine'
-             position='400'
-             error='false'
-             numsubpods='1'>
-          <subpod title=''>
-           <plaintext></plaintext>
-          </subpod>
-         </pod>
+            related='related_url'
+            version='2.6' >
+            <pod title='Indefinite integral'
+                scanner='Integral'
+                id='IndefiniteIntegral'
+                error='false'
+                numsubpods='1'
+                primary='true'>
+                <subpod title=''>
+                    <img src='integral_image.gif'
+                        alt='integral_img_alt'
+                        title='integral_img_title' />
+                    <plaintext>integral_plaintext</plaintext>
+                </subpod>
+            </pod>
+            <pod title='Plot of the integral'
+                scanner='Integral'
+                id='Plot'
+                error='false'
+                numsubpods='1'>
+                <subpod title=''>
+                    <img src='plot.gif'
+                        alt='plot_alt'
+                        title='' />
+                    <plaintext></plaintext>
+                </subpod>
+            </pod>
         </queryresult>
         </queryresult>
         """
         """
-        # test ecuation with multiple answers
-        response = mock.Mock(content=xml)
+        response = mock.Mock(content=xml, request=request)
         results = wolframalpha_api.response(response)
         results = wolframalpha_api.response(response)
         self.assertEqual(type(results), list)
         self.assertEqual(type(results), list)
-        self.assertEqual(len(results), 3)
-        self.assertIn('x=-1', results[0]['answer'])
-        self.assertIn('x=0', results[1]['answer'])
-        self.assertIn('solve x^2+x0 - Wolfram|Alpha'.decode('utf-8'), results[2]['title'])
-        self.assertEquals('http://www.wolframalpha.com/input/?i=solve+x%5E2%2Bx%EF%9F%990', results[2]['url'])
+        self.assertEqual(len(results), 2)
+        self.assertEqual('integral_plaintext', results[0]['infobox'])
+
+        self.assertEqual(len(results[0]['attributes']), 2)
+        self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
+        self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
+        self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
+        self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
+        self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
+
+        self.assertEqual(len(results[0]['urls']), 1)
+
+        self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+        self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+        self.assertEqual(referer_url, results[1]['url'])
+        self.assertEqual('Wolfram|Alpha', results[1]['title'])

+ 202 - 3
tests/unit/engines/test_wolframalpha_noapi.py

@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from collections import defaultdict
 from collections import defaultdict
+import mock
+from requests import Request
 from searx.engines import wolframalpha_noapi
 from searx.engines import wolframalpha_noapi
 from searx.testing import SearxTestCase
 from searx.testing import SearxTestCase
 
 
@@ -9,15 +11,212 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase):
     def test_request(self):
     def test_request(self):
         query = 'test_query'
         query = 'test_query'
         dicto = defaultdict(dict)
         dicto = defaultdict(dict)
-        dicto['pageno'] = 1
         params = wolframalpha_noapi.request(query, dicto)
         params = wolframalpha_noapi.request(query, dicto)
+
         self.assertIn('url', params)
         self.assertIn('url', params)
+        self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
         self.assertIn(query, params['url'])
         self.assertIn(query, params['url'])
-        self.assertIn('wolframalpha.com', params['url'])
+        self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
 
 
     def test_response(self):
     def test_response(self):
         self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
         self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
         self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
         self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
         self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
-        # TODO
+
+        referer_url = 'referer_url'
+        request = Request(headers={'Referer': referer_url})
+
+        # test failure
+        json = '''
+        {"queryresult" : {
+            "success" : false,
+            "error" : false,
+            "numpods" : 0,
+            "id" : "",
+            "host" : "https:\/\/www5a.wolframalpha.com",
+            "didyoumeans" : {}
+        }}
+        '''
+        response = mock.Mock(text=json, request=request)
+        self.assertEqual(wolframalpha_noapi.response(response), [])
+
+        # test basic case
+        json = '''
+        {"queryresult" : {
+            "success" : true,
+            "error" : false,
+            "numpods" : 6,
+            "datatypes" : "Math",
+            "id" : "queryresult_id",
+            "host" : "https:\/\/www5b.wolframalpha.com",
+            "related" : "related_url",
+            "version" : "2.6",
+            "pods" : [
+                {
+                    "title" : "Input",
+                    "scanners" : [
+                        "Identity"
+                    ],
+                    "id" : "Input",
+                    "error" : false,
+                    "numsubpods" : 1,
+                    "subpods" : [
+                        {
+                            "title" : "",
+                            "img" : {
+                                "src" : "input_img_src.gif",
+                                "alt" : "input_img_alt",
+                                "title" : "input_img_title"
+                            },
+                            "plaintext" : "input_plaintext",
+                            "minput" : "input_minput"
+                        }
+                    ]
+                },
+                {
+                    "title" : "Result",
+                    "scanners" : [
+                        "Simplification"
+                    ],
+                    "id" : "Result",
+                    "error" : false,
+                    "numsubpods" : 1,
+                    "primary" : true,
+                    "subpods" : [
+                        {
+                            "title" : "",
+                            "img" : {
+                                "src" : "result_img_src.gif",
+                                "alt" : "result_img_alt",
+                                "title" : "result_img_title"
+                            },
+                            "plaintext" : "result_plaintext",
+                            "moutput" : "result_moutput"
+                        }
+                    ]
+                },
+                {
+                    "title" : "Manipulatives illustration",
+                    "scanners" : [
+                        "Arithmetic"
+                    ],
+                    "id" : "Illustration",
+                    "error" : false,
+                    "numsubpods" : 1,
+                    "subpods" : [
+                        {
+                            "title" : "",
+                            "CDFcontent" : "Resizeable",
+                            "img" : {
+                                "src" : "illustration_img_src.gif",
+                                "alt" : "illustration_img_alt",
+                                "title" : "illustration_img_title"
+                            },
+                            "plaintext" : "illustration_img_plaintext"
+                        }
+                    ]
+                }
+            ]
+        }}
+        '''
+        response = mock.Mock(text=json, request=request)
+        results = wolframalpha_noapi.response(response)
+        self.assertEqual(type(results), list)
+        self.assertEqual(len(results), 2)
+        self.assertEqual('input_plaintext', results[0]['infobox'])
+
+        self.assertEqual(len(results[0]['attributes']), 3)
+        self.assertEqual('Input', results[0]['attributes'][0]['label'])
+        self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
+        self.assertEqual('Result', results[0]['attributes'][1]['label'])
+        self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
+        self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
+        self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
+        self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
+
+        self.assertEqual(len(results[0]['urls']), 1)
+
+        self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+        self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+        self.assertEqual(referer_url, results[1]['url'])
+        self.assertEqual('Wolfram|Alpha', results[1]['title'])
+
+        # test calc
+        json = """
+        {"queryresult" : {
+            "success" : true,
+            "error" : false,
+            "numpods" : 2,
+            "datatypes" : "",
+            "id" : "queryresult_id",
+            "host" : "https:\/\/www4b.wolframalpha.com",
+            "related" : "related_url",
+            "version" : "2.6",
+            "pods" : [
+                {
+                    "title" : "Indefinite integral",
+                    "scanners" : [
+                        "Integral"
+                    ],
+                    "id" : "IndefiniteIntegral",
+                    "error" : false,
+                    "numsubpods" : 1,
+                    "primary" : true,
+                    "subpods" : [
+                        {
+                            "title" : "",
+                            "img" : {
+                                "src" : "integral_img_src.gif",
+                                "alt" : "integral_img_alt",
+                                "title" : "integral_img_title"
+                            },
+                            "plaintext" : "integral_plaintext",
+                            "minput" : "integral_minput",
+                            "moutput" : "integral_moutput"
+                        }
+                    ]
+                },
+                {
+                    "title" : "Plot of the integral",
+                    "scanners" : [
+                        "Integral"
+                    ],
+                    "id" : "Plot",
+                    "error" : false,
+                    "numsubpods" : 1,
+                    "subpods" : [
+                        {
+                            "title" : "",
+                            "img" : {
+                                "src" : "plot.gif",
+                                "alt" : "plot_alt",
+                                "title" : "plot_title"
+                            },
+                            "plaintext" : "",
+                            "minput" : "plot_minput"
+                        }
+                    ]
+                }
+            ]
+        }}
+        """
+        response = mock.Mock(text=json, request=request)
+        results = wolframalpha_noapi.response(response)
+        self.assertEqual(type(results), list)
+        self.assertEqual(len(results), 2)
+        self.assertEqual('integral_plaintext', results[0]['infobox'])
+
+        self.assertEqual(len(results[0]['attributes']), 2)
+        self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
+        self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
+        self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
+        self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
+        self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
+
+        self.assertEqual(len(results[0]['urls']), 1)
+
+        self.assertEqual(referer_url, results[0]['urls'][0]['url'])
+        self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
+        self.assertEqual(referer_url, results[1]['url'])
+        self.assertEqual('Wolfram|Alpha', results[1]['title'])

Some files were not shown because too many files changed in this diff