detailview fixed, debug data
[pywienerlinien] / parseHtml.py
1 from BeautifulSoup import BeautifulSoup, NavigableString
2 import urllib2
3 from datetime import time, datetime
4 from textwrap import wrap
5 import settings
6 import wlSearch
7
8 class ParserError(Exception):
9      def __init__(self, value='', code=0):
10          self.value = value
11          self.code = code
12
13      def __str__(self):
14          return repr(self.value)
15
16 class Parser:
17     STATE_ERROR = -1
18     STATE_START, STATE_SEARCH, STATE_RESULT = range(3)
19
20     def __init__(self, html):
21         self.soup = BeautifulSoup(html)
22         self._overview = None
23         self._details = None
24         self._current_state = 0
25
26     @classmethod
27     def get_tdtext(cls, x, cl):
28             return x.find('td', {'class': cl}).text
29     
30     @classmethod
31     def get_change(cls, x):
32         y = Parser.get_tdtext(x, 'col_change')
33         if y:
34             return int(y)
35         else:
36             return 0
37
38     @classmethod
39     def get_price(cls, x):
40         y = Parser.get_tdtext(x, 'col_price')
41         if y.find(','):
42             return float(y.replace(',', '.'))
43         else:
44             return 0.0
45
46     @classmethod
47     def get_date(cls, x):
48         y = Parser.get_tdtext(x, 'col_date')
49         if y:
50             return datetime.strptime(y, '%d.%m.%Y').date()
51         else:
52             return None
53         
54     @classmethod
55     def get_time(cls, x):
56         y = Parser.get_tdtext(x, 'col_time')
57         if y:
58             if (y.find("-") > 0):
59                 return map(lambda z: time(*map(int, z.split(':'))), y.split('-'))
60             else:
61                 return map(lambda z: time(*map(int, z.split(':'))), wrap(y, 5))
62         else:
63             return []
64         
65     @classmethod
66     def get_duration(cls, x):
67         y = Parser.get_tdtext(x, 'col_duration')
68         if y:
69             return time(*map(int, y.split(":")))
70         else:
71             return None
72
73     def __iter__(self):
74         for detail in self.details():
75             yield detail
76
77     def _parse_details(self):
78         if self._current_state < 0:
79             raise ParserError('Unable to parse details while in error state')
80
81         tours = self.soup.findAll('div', {'class': 'data_table tourdetail'})
82
83         trips = map(lambda x: map(lambda y: {
84                         'time': Parser.get_time(y),
85                         'station': map(lambda z: z[2:].strip(),
86                                        filter(lambda x: type(x) == NavigableString, y.find('td', {'class': 'col_station'}).contents)), # filter non NaviStrings
87                         'info': map(lambda x: x.strip(),
88                                     filter(lambda z: type(z) == NavigableString, y.find('td', {'class': 'col_info'}).contents)),
89                     }, x.find('tbody').findAll('tr')),
90                     tours) # all routes
91         return trips
92
93     @property
94     def details(self):
95         """returns list of trip details
96         [ [ { 'time': [datetime.time, datetime.time] if time else [],
97               'station': [u'start', u'end'] if station else [],
98               'info': [u'start station' if station else u'details for walking', u'end station' if station else u'walking duration']
99             }, ... # next trip step 
100           ], ... # next trip possibility
101         ]
102         """
103         if not self._details:
104             self._details = self._parse_details()
105
106         return self._details
107
108     def _parse_overview(self):
109
110         # get overview table
111         table = self.soup.find('table', {'id': 'tbl_fahrten'})
112
113         # check if there is an overview table
114         if table and table.findAll('tr'):
115             # get rows
116             rows = table.findAll('tr')[1:] # cut off headline
117             
118             overview = map(lambda x: {
119                                'date': Parser.get_date(x),
120                                'time': Parser.get_time(x),
121                                'duration': Parser.get_duration(x), # grab duration
122                                'change': Parser.get_change(x), 
123                                'price': Parser.get_price(x),
124                            },
125                            rows)
126         else:
127             #self._current_state = self.STATE_ERROR
128             raise ParserError('Unable to parse details')
129
130         return overview
131
132     @property
133     def overview(self):
134         """dict containing
135         date: datetime
136         time: [time, time]
137         duration: time
138         change: int
139         price: float
140         """
141         if not self._overview:
142             try:
143                 self._overview = self._parse_overview()
144             except AttributeError:
145                 f = open('DEBUG', 'w')
146                 f.write(str(self.soup))
147                 f.close()
148
149         return self._overview
150
151     def _check_request_state(self):
152         raise NotImplementedError()
153
154     @property
155     def request_state(self):
156         return self._current_state
157
158
159 class iParser:
160
161     def __init__(self):
162         self._stations = {}
163         self._lines = []
164
165     def get_stations(self, letter):
166         if not self._stations.has_key(letter):
167             bs = BeautifulSoup(urllib2.urlopen(settings.stations % letter))
168             self._stations[letter] = map(lambda x: x['value'], bs.find('select', {'id': 'letter'}).findAll('option'))
169
170         return self._stations[letter]
171
172     def get_lines(self):
173         if not self._lines:
174             bs = BeautifulSoup(urllib2.urlopen(settings.line_overview))
175             # get tables
176             lines = bs.findAll('table', {'class': 'linie'})
177             # cut line parameter out of href
178             self._lines = map(lambda x: map(lambda x: x['href'][x['href'].find('=') + 1:], x.findAll('a')), lines)
179
180         return self._lines