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