2
# -*- coding: utf-8 -*-
4
# Copyright (C) 2010 Daniel Fett
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
# Author: Daniel Fett agtl@danielfett.de
19
# Jabber: fett.daniel@jaber.ccc.de
20
# Bugtracker and GIT Repository: http://github.com/webhamster/advancedcaching
23
from __future__ import division
28
''', {'A': 2, 'D': 4, 'G': 3,'T': 1, 'R': 2, 'S': 1, 'H': 4, 'B': 2, 'C': 9, 'E': 0, 'F': 1})
32
<p><font face="Arial, sans-serif"><font size=
33
"4"><b>Final:</b></font></font></p>
34
<p style="font-style: normal"><font color="#000000"><font face=
35
"Arial, sans-serif"><font size="3"><b><span style=
36
"background: transparent">N 49°
37
(B-C+A+0,5*D).(F+D)(F-G)(C-2*A)</span></b></font></font></font></p>
38
<p style="font-style: normal"><font color="#000000"><font face=
39
"Arial, sans-serif"><font size="3"><b><span style=
40
"background: transparent">E 6°
41
(2*A+C).(G-E)(B-C+0,5*D)(F-D)</span></b></font></font></font></p>
42
<p style="font-style: normal; font-weight: normal"><font color=
43
"#000000"><font face="Arial, sans-serif"><font size=
44
"3"><span style="background: transparent">Es müssen keine Zäune
45
oder Mauern überwunden werden.</span></font></font></font></p>
47
(B-C+A+0,5*D).(F+D)(F-G)(D-2*B)
58
logger = logging.getLogger('coordfinder')
60
class CalcCoordinateManager(object):
61
def __init__(self, vars):
63
self.__known_signatures = []
64
self.__filtered_signatures = []
67
logger.debug("New coordfinder started")
69
def add_text(self, text, source):
70
logger.debug("Adding Text with length %d from source %s" % (len(text), source))
71
self.__add_coords(CalcCoordinate.find(text, source))
73
def __add_coords(self, coords, apply_filter = True):
75
logger.debug("Adding: %s, apply_filter = %s" % (x, apply_filter))
76
if x.signature in self.__known_signatures:
78
if apply_filter and x.signature in self.__filtered_signatures:
80
self.__known_signatures.append(x.signature)
81
self.requires |= x.requires
83
logger.debug("Now having %d coords, %d requires" % (len(self.coords), len(self.requires)))
85
def __remove_coord(self, signature):
86
self.__filtered_signatures.append(signature)
87
self.__known_signatures = []
89
logger.debug("Removing: %s" % signature)
92
if x.signature != signature:
93
self.requires |= x.requires
95
self.__known_signatures.append(x.signature)
96
self.coords = new_coords
97
logger.debug("Now having %d coords, %d requires" % (len(self.coords), len(self.requires)))
99
def add_replacement(self, signature, replacement_text, source):
100
self.__remove_coord(signature)
101
self.__add_coords(CalcCoordinate.find(replacement_text, source), False)
104
def set_var(self, char, value):
106
self.__vars[char] = value
108
del self.__vars[char]
112
logger.debug("updating...")
113
for c in self.coords:
114
c.set_vars(self.__vars)
118
def get_solutions(self):
119
return [(c.result, c.source) for c in self.coords if c.has_requires() and len(c.requires) > 0]
121
def get_plain_coordinates(self):
122
return [(c.result, c.source) for c in self.coords if len(c.requires) == 0]
128
class CalcCoordinate():
130
WARNING_NEGATIVE = "Negative intermediate result (%d)."
131
WARNING_VERY_HIGH = "Very high intermediate result (%d)."
132
WARNING_FLOAT = "Intermediate result with decimal point ('%s')."
133
WARNING_WRONG_LENGTH = "%d digits where %s digits were expected (%s)."
134
WARNING_CANNOT_PARSE = "Cannot parse result: %s."
135
WARNING_SYNTAX = "Could not parse formula."
137
HIGH_RESULT_THRESHOLD = 1000
139
EXPECTED_LENGTHS = [(1,2), (1,2), (3,), (1,2,3), (1,2), (3,)]
141
def __init__(self, ns, lat_deg, lat_min, lat_min_2, ew, lon_deg, lon_min, lon_min_2, source):
144
self.lat_deg = self.__prepare(lat_deg)
145
self.lat_min = self.__prepare(lat_min)
146
self.lat_min_2 = self.__prepare(lat_min_2)
147
self.lon_deg = self.__prepare(lon_deg)
148
self.lon_min = self.__prepare(lon_min)
149
self.lon_min_2 = self.__prepare(lon_min_2)
150
self.orig = "%s%s %s.%s %s%s %s.%s" % (self.ns, self.lat_deg, self.lat_min, self.lat_min_2, self.ew, self.lon_deg, self.lon_min, self.lon_min_2)
151
self.requires = set(x for i in [self.lat_deg, self.lat_min, self.lat_min_2, self.lon_deg, self.lon_min, self.lon_min_2] for x in re.sub('[^A-Za-z]', '', i))
154
self.signature = "|".join([ns, self.lat_deg, self.lat_min, self.lat_min_2, ew, self.lon_deg, self.lon_min, self.lon_min_2])
157
def __prepare(self, text):
158
return (re.sub('[^A-Za-z()+*/0-9-.,]', '', text)).replace(',', '.')
160
def set_vars(self, var):
164
def has_requires(self):
165
for i in self.requires:
166
if not i in self.vars:
170
def try_get_solution(self):
172
replaced = [self.__replace(x) for x in [self.lat_deg, self.lat_min, self.lat_min_2, self.lon_deg, self.lon_min, self.lon_min_2]]
173
self.replaced_result = ("%%s%s %s.%s %%s%s %s.%s" % tuple(replaced)) % (self.ns, self.ew)
174
results = [self.resolve(x) for x in replaced]
176
for i in range(len(results)):
177
if len(results[i]) not in self.EXPECTED_LENGTHS[i]:
178
self.warnings.append(self.WARNING_WRONG_LENGTH % (len(results[i]), " or ".join([str(x) for x in self.EXPECTED_LENGTHS[i]]), results[i]))
180
result = ("%%s%s %s.%s %%s%s %s.%s" % tuple(results)) % (self.ns, self.ew)
181
#print self.replaced_result
183
self.result = geo.try_parse_coordinate(result)
184
self.result.name = self.orig
186
self.warnings.append(self.WARNING_CANNOT_PARSE % result)
191
def __replace(self, text):
192
for char, value in self.vars.items():
193
text = text.replace(char, str(value))
196
def resolve(self, text):
199
text, c = re.subn('\([^()]+\)', lambda match: self.__safe_eval(match.group(0)), text)
200
if re.match('^[0-9]+$', text) == None:
201
# determine number of leading zeros
202
#lz = len(text) - len(str(int(text)))
203
text = self.__safe_eval(text)
205
text = "%03d" % int(text)
210
def __safe_eval(self, text):
212
tmp = eval(text,{"__builtins__":None},{})
213
except (SyntaxError, Exception):
214
self.warnings.append(self.WARNING_SYNTAX)
216
if round(tmp) != round(tmp, 1):
217
self.warnings.append(self.WARNING_FLOAT % text)
220
self.warnings.append(self.WARNING_NEGATIVE % tmp)
221
if tmp > self.HIGH_RESULT_THRESHOLD:
222
self.warnings.append(self.WARNING_VERY_HIGH % tmp)
226
return "<%s> from %s" % (self.orig, self.source)
228
SINGLE_CALC_PART = ur'''((?:\([A-Za-z +*/0-9-.,]+\)|[A-Za-z ()+*/0-9-])+)'''
231
def find(text, source):
232
text = re.sub(ur'''(?u)\s[^\W\d_]{2,}\s''', ' | ', text)
233
text = re.sub(ur'''(?u)\b[^\W\d_]{4,}\b''', ' | ', text)
234
text = text.replace('°', '|')
235
text = text.decode('utf-8', 'replace').encode('utf-8')
236
text = text.replace(unichr(160), ' ')
237
text = re.sub(ur''' +''', ' ', text)
238
matches = re.findall(ur'''(?<![a-zA-Z])([NSns])\s?([A-Z() -+*/0-9]+?)[\s|]{1,2}%(calc)s[.,\s]%(calc)s['`\s,/]+([EOWeow])\s?([A-Z() -+*/0-9]+?)[\s|]{1,2}%(calc)s[.,\s]%(calc)s[\s'`]*(?![a-zA-Z])''' % {'calc' : CalcCoordinate.SINGLE_CALC_PART}, text)
239
return [CalcCoordinate(*match, **{'source': source}) for match in matches]
242
def is_calc_string(text):
243
regex = ur'''^([NSns])\s?([A-Z() -+*/0-9]+?)[\s|]{1,2}%(calc)s[.,\s]%(calc)s['`\s,/]+([EOWeow])\s?([A-Z() -+*/0-9]+?)[\s|]{1,2}%(calc)s[.,\s]%(calc)s[\s'`]*$''' % {'calc' : CalcCoordinate.SINGLE_CALC_PART}
244
return (re.match(regex, text) != None)
246
if __name__ == "__main__":
247
from simplegui import SimpleGui
248
print '\n\n========================================================='
249
h = SimpleGui._strip_html(HTML)
252
# print "%d -> %s" % (ord(x), x)
253
print '---------------------------------------------------------'
254
for instance in CalcCoordinate.find(h)[0]:
255
print "Found: %s" % (instance.orig)