~ubuntu-branches/debian/wheezy/agtl/wheezy

« back to all changes in this revision

Viewing changes to files/advancedcaching/coordfinder.py

  • Committer: Bazaar Package Importer
  • Author(s): Heiko Stuebner
  • Date: 2011-01-22 13:55:12 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20110122135512-1mik0vssgpnx2fgu
Tags: 0.8.0.3-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: utf-8 -*-
 
3
 
 
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.
 
9
#
 
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.
 
14
#
 
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/>.
 
17
#
 
18
#   Author: Daniel Fett agtl@danielfett.de
 
19
#   Jabber: fett.daniel@jaber.ccc.de
 
20
#   Bugtracker and GIT Repository: http://github.com/webhamster/advancedcaching
 
21
#
 
22
 
 
23
from __future__ import division
 
24
TEST = ('''
 
25
 
 
26
 
 
27
 
 
28
''', {'A': 2, 'D': 4, 'G': 3,'T': 1, 'R': 2, 'S': 1, 'H': 4, 'B': 2, 'C': 9, 'E': 0, 'F': 1})
 
29
HTML = '''
 
30
 
 
31
<br /></p> 
 
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>
 
46
N 49°
 
47
(B-C+A+0,5*D).(F+D)(F-G)(D-2*B)
 
48
<p><br /> 
 
49
<br /></p></span> 
 
50
                
 
51
            </div> 
 
52
            <p> 
 
53
</p> 
 
54
'''
 
55
import geo
 
56
import re
 
57
import logging
 
58
logger = logging.getLogger('coordfinder')
 
59
 
 
60
class CalcCoordinateManager(object):
 
61
    def __init__(self, vars):
 
62
        self.__vars = vars
 
63
        self.__known_signatures = []
 
64
        self.__filtered_signatures = []
 
65
        self.requires = set()
 
66
        self.coords = []
 
67
        logger.debug("New coordfinder started")
 
68
 
 
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))
 
72
 
 
73
    def __add_coords(self, coords, apply_filter = True):
 
74
        for x in coords:
 
75
            logger.debug("Adding: %s, apply_filter = %s" % (x, apply_filter))
 
76
            if x.signature in self.__known_signatures:
 
77
                continue
 
78
            if apply_filter and x.signature in self.__filtered_signatures:
 
79
                continue
 
80
            self.__known_signatures.append(x.signature)
 
81
            self.requires |= x.requires
 
82
            self.coords.append(x)
 
83
        logger.debug("Now having %d coords, %d requires" % (len(self.coords), len(self.requires)))
 
84
            
 
85
    def __remove_coord(self, signature):
 
86
        self.__filtered_signatures.append(signature)
 
87
        self.__known_signatures = []
 
88
        self.requires = set()
 
89
        logger.debug("Removing: %s" % signature)
 
90
        new_coords = []
 
91
        for x in self.coords:
 
92
            if x.signature != signature:
 
93
                self.requires |= x.requires
 
94
                new_coords.append(x)
 
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)))
 
98
 
 
99
    def add_replacement(self, signature, replacement_text, source):
 
100
        self.__remove_coord(signature)
 
101
        self.__add_coords(CalcCoordinate.find(replacement_text, source), False)
 
102
 
 
103
        
 
104
    def set_var(self, char, value):
 
105
        if value != '':
 
106
            self.__vars[char] = value
 
107
        else:
 
108
            del self.__vars[char]
 
109
        self.update()
 
110
 
 
111
    def update(self):
 
112
        logger.debug("updating...")
 
113
        for c in self.coords:
 
114
            c.set_vars(self.__vars)
 
115
            if c.has_requires():
 
116
                c.try_get_solution()
 
117
 
 
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]
 
120
 
 
121
    def get_plain_coordinates(self):
 
122
        return [(c.result, c.source) for c in self.coords if len(c.requires) == 0]
 
123
 
 
124
    def get_vars(self):
 
125
        return self.__vars
 
126
 
 
127
 
 
128
class CalcCoordinate():
 
129
 
 
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."
 
136
 
 
137
    HIGH_RESULT_THRESHOLD = 1000
 
138
 
 
139
    EXPECTED_LENGTHS = [(1,2), (1,2), (3,), (1,2,3), (1,2), (3,)]
 
140
 
 
141
    def __init__(self, ns, lat_deg, lat_min, lat_min_2, ew, lon_deg, lon_min, lon_min_2, source):
 
142
        self.ns = ns
 
143
        self.ew = ew
 
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))
 
152
        self.warnings = []
 
153
        self.vars = {}
 
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])
 
155
        self.source = source
 
156
 
 
157
    def __prepare(self, text):
 
158
        return (re.sub('[^A-Za-z()+*/0-9-.,]', '', text)).replace(',', '.')
 
159
 
 
160
    def set_vars(self, var):
 
161
        self.warnings = []
 
162
        self.vars = var
 
163
 
 
164
    def has_requires(self):
 
165
        for i in self.requires:
 
166
            if not i in self.vars:
 
167
                return False
 
168
        return True
 
169
 
 
170
    def try_get_solution(self):
 
171
 
 
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]
 
175
        
 
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]))
 
179
        
 
180
        result = ("%%s%s %s.%s %%s%s %s.%s" % tuple(results)) % (self.ns, self.ew)
 
181
        #print self.replaced_result
 
182
        try:
 
183
            self.result = geo.try_parse_coordinate(result)
 
184
            self.result.name = self.orig                
 
185
        except (Exception):
 
186
            self.warnings.append(self.WARNING_CANNOT_PARSE % result)
 
187
            self.result = False
 
188
        
 
189
 
 
190
 
 
191
    def __replace(self, text):
 
192
        for char, value in self.vars.items():
 
193
            text = text.replace(char, str(value))
 
194
        return text
 
195
 
 
196
    def resolve(self, text):
 
197
        c = 1
 
198
        while c > 0:
 
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)
 
204
            try:
 
205
                text = "%03d" % int(text)
 
206
            except Exception:
 
207
                text = '?'
 
208
        return text
 
209
 
 
210
    def __safe_eval(self, text):
 
211
        try:
 
212
            tmp = eval(text,{"__builtins__":None},{})
 
213
        except (SyntaxError, Exception):
 
214
            self.warnings.append(self.WARNING_SYNTAX)
 
215
            return '?'
 
216
        if round(tmp) != round(tmp, 1):
 
217
            self.warnings.append(self.WARNING_FLOAT % text)
 
218
        tmp = int(tmp)
 
219
        if tmp < 0:
 
220
            self.warnings.append(self.WARNING_NEGATIVE % tmp)
 
221
        if tmp > self.HIGH_RESULT_THRESHOLD:
 
222
            self.warnings.append(self.WARNING_VERY_HIGH % tmp)
 
223
        return str(tmp)
 
224
 
 
225
    def __str__(self):
 
226
        return "<%s> from %s" % (self.orig, self.source)
 
227
 
 
228
    SINGLE_CALC_PART = ur'''((?:\([A-Za-z +*/0-9-.,]+\)|[A-Za-z ()+*/0-9-])+)'''
 
229
 
 
230
    @staticmethod
 
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]
 
240
 
 
241
    @staticmethod
 
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)
 
245
 
 
246
if __name__ == "__main__":
 
247
    from simplegui import SimpleGui
 
248
    print '\n\n========================================================='
 
249
    h = SimpleGui._strip_html(HTML) 
 
250
    print h
 
251
    #for x in h:
 
252
    #    print "%d -> %s" % (ord(x), x)
 
253
    print '---------------------------------------------------------'
 
254
    for instance in CalcCoordinate.find(h)[0]:
 
255
        print "Found: %s" % (instance.orig)
 
256