Skip to content

Commit 39b4a75

Browse files
committed
Plugins: Replace wolfram regex with proper XML parsing
1 parent ab4053f commit 39b4a75

File tree

7 files changed

+121
-18
lines changed

7 files changed

+121
-18
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?xml version="1.0" encoding="UTF-8"?><queryresult success="true" error="false" numpods="1" datatypes="" parsetiming="0.352" parsetimedout="false" id="IjJNnkM68HI=6:eJxTTMoPSmNkYGAoZgESHvk5KWlMIJ4ekHBOzEkuzUksSQ1ILCpOTXDOzytJzStJgAsnOOYUZCQGlibmlWSWVIIN8MvPS4UYp0qMAaF5mSXFRsgqwSJgcef83Nz8vODK3KT8HLBIWSrQEMeS/Fzn/NK8EgBmrT2l" kernelId="204" processId="367370" version="2.6" inputstring="Number of atoms in universe" sbsallowed="false" parentId="dd4041aa-1211-46f1-9a63-f7433144e99a" requestId="dd4041aa-1211-46f1-9a63-f7433144e99a" timing="0.747" timedout="" timedoutpods=""><pod title="Value" scanner="Unit" id="Value" position="2200" error="false" numsubpods="1"><subpod title=""><plaintext>≈ 6×10^79 atoms</plaintext></subpod><expressiontypes count="1"><expressiontype name="Default"></expressiontype></expressiontypes></pod></queryresult>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?xml version="1.0" encoding="UTF-8"?><queryresult success="true" error="false" numpods="1" datatypes="Country" parsetiming="0.352" parsetimedout="false" id="Cggp3ukUnpg=6:eJy1T0kOwjAMDDviwhv4AJ8oIA4cKsIDGqgjRUrtKnGF+nqIqdpKnMGHkT3yzNi7O13tRCkV5wnO5Es77afcN7Gb9gky4x+NNwy5CRGKjJABuRjosTsYNnYmumWCI7LjVq/Eghrk0Gqh9RNKQKv6sIuL3InWEh2ohpBk21E2cBtZoFqyHH1bfO5dJLi5CqJ7pfrLD9KfHHqD5c+feAOHzG/l" kernelId="168" processId="297111" version="2.6" inputstring="Sweden population minus Finland poulation" sbsallowed="false" parentId="73e41e9c-6627-40e9-bd24-8f5b63a8855a" requestId="73e41e9c-6627-40e9-bd24-8f5b63a8855a" timing="0.708" timedout="" timedoutpods=""><warnings count="1"><spellcheck word="poulation" suggestion="population" text="Interpreting &quot;poulation&quot; as &quot;population&quot;"></spellcheck></warnings><sources count="1"><source url="https://www6b3.wolframalpha.com/sources/CountryDataSourceInformationNotes.html" text="Country data"></source></sources><pod title="Result" scanner="Data" id="Result" position="10" error="false" numsubpods="1" primary="true"><subpod title=""><microsources><microsource>CountryData</microsource></microsources><plaintext>5 million people (2023 estimates)</plaintext></subpod><expressiontypes count="1"><expressiontype name="Default"></expressiontype></expressiontypes><states count="1"><state name="Show details" input="Result__Show details"></state></states></pod></queryresult>

plugins/wolfram/test/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__author__ = "reggna"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="UTF-8"?><queryresult success="true" error="false" numpods="1" datatypes="Chemical" parsetiming="0.47300000000000003" parsetimedout="false" id="dWsJnaovFhw=6:eJxTTMoPSmNkYGAoZgESHvk5KWlMMJ5PZnEJhKcHJJwTc5JLcxJLUgMSi4pTE5zz80pS80oS4MIIlktiSWIaM0gfG5BwzSvJLKkM5gAZkZGam5mcmBPMCuSEA1UWpTGgWAbWBFIZUJRfkFoE1CaApA0uyAkU9M3PSSzyTSwuRjOCJu4FcxLT0lIz81Kp7mQAEO5n2g==" kernelId="471" processId="832549" version="2.6" inputstring="molar mass of water vs coffeine" sbsallowed="false" parentId="669aba16-5e9f-4ff1-b913-8f68cca8c826" requestId="669aba16-5e9f-4ff1-b913-8f68cca8c826" timing="2.928" timedout="" timedoutpods=""><sources count="1"><source url="https://www6b3.wolframalpha.com/sources/ChemicalDataSourceInformationNotes.html" text="Chemical data"></source></sources><pod title="Result" scanner="Data" id="Result" position="100" error="false" numsubpods="1" primary="true"><subpod title=""><microsources><microsource>ChemicalData</microsource></microsources><datasources><datasource>PubChem</datasource></datasources><plaintext>water | 18.015 g/mol (grams per mole)
2+
caffeine | 194.19 g/mol (grams per mole)</plaintext></subpod><expressiontypes count="1"><expressiontype name="Grid"></expressiontype></expressiontypes></pod></queryresult>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?xml version="1.0" encoding="UTF-8"?><queryresult success="true" error="false" numpods="1" datatypes="Country" parsetiming="0.152" parsetimedout="false" id="MumO3hkqvPc=6:eJxTTMoPSmNkYGAoZgESHvk5KWlMIJ4ekHBOzEkuzUksSQ1ILCpOTXDOzytJzStJgAsjWC6JJYlpzCB9bEDCNa8ks6QymB1kRH5pXklRZTBI2K0oMS85NY0BZplPZnEJRBMHkAgoyi9ILQJq40dog4txgRTkF4DsyszPQzUCABtlNKA=" kernelId="200" processId="352269" version="2.6" inputstring="population france" sbsallowed="false" parentId="116f09e7-c810-4aca-8975-cc6750ce5a84" requestId="116f09e7-c810-4aca-8975-cc6750ce5a84" timing="0.436" timedout="" timedoutpods=""><sources count="1"><source url="https://www6b3.wolframalpha.com/sources/CountryDataSourceInformationNotes.html" text="Country data"></source></sources><pod title="Result" scanner="Data" id="Result" position="10" error="false" numsubpods="1" primary="true"><subpod title=""><microsources><microsource>CountryData</microsource></microsources><plaintext>66.4 million people (world rank: 23rd) (2023 estimate)</plaintext></subpod><expressiontypes count="1"><expressiontype name="Default"></expressiontype></expressiontypes></pod></queryresult>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os
2+
import unittest
3+
import unittest.mock
4+
import requests
5+
from plugins.wolfram import wolfram
6+
7+
class WolframTest(unittest.TestCase):
8+
def setUp(self):
9+
# Path to test directory relative to project root
10+
self.test_dir = os.path.join("plugins", "wolfram", "test")
11+
12+
def _get_test_content(self, filename):
13+
with open(os.path.join(self.test_dir, filename), "r") as f:
14+
return f.read()
15+
16+
@unittest.mock.patch("requests.get")
17+
def test_population_france(self, mock_get):
18+
mock_response = unittest.mock.Mock()
19+
mock_response.ok = True
20+
mock_response.content = self._get_test_content("population_france.xml").encode("utf-8")
21+
mock_get.return_value = mock_response
22+
23+
answer = wolfram.get_answer("population france", "dummy_key")
24+
self.assertEqual(answer, "66.4 million people (world rank: 23rd) (2023 estimate)")
25+
26+
@unittest.mock.patch("requests.get")
27+
def test_sweden_population_minus_finland(self, mock_get):
28+
mock_response = unittest.mock.Mock()
29+
mock_response.ok = True
30+
mock_response.content = self._get_test_content("Sweden_population_minus_Finland_population.xml").encode("utf-8")
31+
mock_get.return_value = mock_response
32+
33+
answer = wolfram.get_answer("Sweden population minus Finland population", "dummy_key")
34+
self.assertEqual(answer, "5 million people (2023 estimates)")
35+
36+
@unittest.mock.patch("requests.get")
37+
def test_number_of_atoms_in_universe(self, mock_get):
38+
mock_response = unittest.mock.Mock()
39+
mock_response.ok = True
40+
mock_response.content = self._get_test_content("Number_of_atoms_in_universe.xml").encode("utf-8")
41+
mock_get.return_value = mock_response
42+
43+
answer = wolfram.get_answer("Number of atoms in universe", "dummy_key")
44+
self.assertEqual(answer, "≈ 6×10^79 atoms")
45+
46+
@unittest.mock.patch("requests.get")
47+
def test_molar_mass_water_vs_coffeine(self, mock_get):
48+
mock_response = unittest.mock.Mock()
49+
mock_response.ok = True
50+
mock_response.content = self._get_test_content("molar_mass_of_water_vs_coffeine.xml").encode("utf-8")
51+
mock_get.return_value = mock_response
52+
53+
answer = wolfram.get_answer("molar mass of water vs coffeine", "dummy_key")
54+
# Note: sanitize_string replaces newlines with spaces
55+
expected = "water | 18.015 g/mol (grams per mole) caffeine | 194.19 g/mol (grams per mole)"
56+
self.assertEqual(answer, expected)
57+
58+
@unittest.mock.patch("requests.get")
59+
def test_failed_response(self, mock_get):
60+
mock_response = unittest.mock.Mock()
61+
mock_response.ok = False
62+
mock_get.return_value = mock_response
63+
64+
answer = wolfram.get_answer("any query", "dummy_key")
65+
self.assertIsNone(answer)
66+
67+
@unittest.mock.patch("requests.get")
68+
def test_malformed_xml(self, mock_get):
69+
mock_response = unittest.mock.Mock()
70+
mock_response.ok = True
71+
mock_response.content = b"<invalid>xml"
72+
mock_get.return_value = mock_response
73+
74+
answer = wolfram.get_answer("any query", "dummy_key")
75+
self.assertIsNone(answer)
76+
77+
if __name__ == "__main__":
78+
unittest.main()

plugins/wolfram/wolfram.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,46 @@
44
import json
55
import requests
66
import urllib.parse
7+
import xml.etree.ElementTree as ET
78

89
import plugin
910
from utils import str_utils
1011

11-
RESULT_POD_START = "<pod title='Result"
12-
DECIMAL_APPROXIMATION_POD_START = "<pod title='Decimal approximation'"
13-
RESULT_SUB_POD = "<plaintext>"
1412
API_URL = "http://api.wolframalpha.com/v2/query?appid={key}&input={query}&format=plaintext"
1513

1614

1715
def get_answer(query, key):
1816
query = urllib.parse.quote(query)
19-
result = requests.get(API_URL.format(key=key, query=query)).text
20-
if RESULT_POD_START not in result:
21-
if DECIMAL_APPROXIMATION_POD_START not in result:
22-
return
23-
else:
24-
result = result[result.index(DECIMAL_APPROXIMATION_POD_START) :]
25-
else:
26-
result = result[result.index(RESULT_POD_START) :]
27-
if not RESULT_SUB_POD in result:
28-
return
29-
result = result[result.index(RESULT_SUB_POD) + len(RESULT_SUB_POD) :]
30-
result = result[: result.index("<")]
31-
return str_utils.sanitize_string(result)
17+
url = API_URL.format(key=key, query=query)
18+
try:
19+
response = requests.get(url)
20+
if not response.ok:
21+
return None
22+
root = ET.fromstring(response.content)
23+
except Exception:
24+
logging.exception("Unable to query or parse Wolfram response")
25+
return None
26+
27+
if root.attrib.get("success") != "true":
28+
return None
29+
30+
# Priority 1: primary pods
31+
for pod in root.findall("pod"):
32+
if pod.attrib.get("primary") == "true":
33+
plaintext = pod.find(".//plaintext")
34+
if plaintext is not None and plaintext.text:
35+
return str_utils.sanitize_string(plaintext.text)
36+
37+
# Priority 2: pods with specific titles
38+
target_titles = ["Result", "Value", "Decimal approximation"]
39+
for title in target_titles:
40+
for pod in root.findall("pod"):
41+
if pod.attrib.get("title") == title:
42+
plaintext = pod.find(".//plaintext")
43+
if plaintext is not None and plaintext.text:
44+
return str_utils.sanitize_string(plaintext.text)
45+
46+
return None
3247

3348

3449
class Wolfram(plugin.Plugin):
@@ -46,8 +61,12 @@ def on_pubmsg(self, server, user, channel, message):
4661
if message.startswith(self.trigger):
4762
try:
4863
query = message[len(self.trigger) + 1 :]
49-
result = html.unescape(get_answer(query, self.key))
50-
self.privmsg(server, channel, result)
64+
if not query:
65+
return
66+
answer = get_answer(query, self.key)
67+
if answer:
68+
result = html.unescape(answer)
69+
self.privmsg(server, channel, result)
5170
except:
5271
logging.exception("Unable to query")
5372

0 commit comments

Comments
 (0)