99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
1
2
|
#!/usr/bin/env python3
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
3
4
|
import sys
import argparse
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
5
|
import datetime
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
6
|
import urllib.request
|
86487554
Geoffrey PREUD'HOMME
Support de la dat...
|
7
|
import dateutil.parser
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
8
|
from icalendar import Calendar
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
9
|
from html.parser import HTMLParser
|
f6d0d112
Geoffrey PREUD'HOMME
Meilleur respect ...
|
10
|
from icalendar import vDatetime, Calendar, Event as CalEvent
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
11
|
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
12
|
# Parse command line arguments
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
13
14
|
parser = argparse.ArgumentParser(description='Convertit l\'emploi du temps IMA en ICS')
parser.add_argument('annee', metavar='ANNEE', type=int, help='année (3 ou 4)')
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
15
16
17
18
|
parser.add_argument('edt', metavar='EDT', type=str, help='la page pointant vers l\'emploi du temps concerné')
parser.add_argument('-o', '--output', dest='file', type=str, default='-', help='fichier de sortie, - pour stdout')
args = parser.parse_args()
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
if args.annee == 3:
url = 'http://dptima3.polytech-lille.net/' + args.edt + '.html'
SLOTS = [(( 8, 0), (10, 0)),
((10, 20), (12, 20)),
((13, 50), (15, 50)),
((16, 10), (18, 10))]
DATE_FORMAT = '%d/%m/%Y'
elif args.annee == 4:
url = 'http://www.lifl.fr/~forget/EDT/' + args.edt + '.html'
SLOTS = [(( 8, 0), ( 9, 0)),
(( 9, 10), (10, 10)),
((10, 20), (11, 20)),
((11, 30), (12, 30)),
((13, 50), (14, 50)),
((15, 00), (16, 00)),
((16, 10), (17, 10)),
((17, 20), (18, 20))]
DATE_FORMAT = '%d/%m/%y'
else:
raise ValueError('Année inconnue : ' + annee)
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
40
|
DAYS_PER_WEEK = 5.5
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
TABLE_1_DATE_X = 1
TABLE_1_FIRST_SLOT_X = 2
TABLE_2_DATE_X = 0
TABLE_2_FIRST_SLOT_X = 1
class TableHTMLParser(HTMLParser):
tables = [] # Tables
table = False # Current table content
line = False # Current line content
cell = False # Current cell content
cellx = 1
celly = 1
# Logic
def iscell(self):
"""
Return if we are currently in a cell
"""
return isinstance(self.cell, str)
def isline(self):
"""
Return if we are currently in a line
"""
return isinstance(self.line, list)
def istable(self):
"""
Return if we are currently in a table
"""
return isinstance(self.table, list)
# Actions
def endcell(self):
if self.iscell():
self.line.append((self.cell.strip(), self.cellx, self.celly))
self.cell = False
def endline(self):
self.endcell()
if self.isline():
self.table.append(self.line.copy())
self.line = False
def endtable(self):
self.endline()
if self.istable():
self.tables.append(self.table.copy())
self.table = False
# Inheritance
def handle_starttag(self, tag, attrs):
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
if tag == 'table':
self.table = []
elif tag == 'tr':
self.endline()
self.line = []
elif tag == 'td':
self.endcell()
self.cell = ''
self.cellx = 1
self.celly = 1
for attr in attrs:
if attr[0] == 'colspan':
self.cellx = int(attr[1])
elif attr[0] == 'rowspan':
self.celly = int(attr[1])
def handle_endtag(self, tag):
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
112
113
114
115
116
117
118
119
|
if tag == 'table':
self.endtable()
elif tag == 'tr':
self.endline()
elif tag == 'td':
self.endcell()
def handle_data(self, data):
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
120
121
122
|
if self.iscell():
self.cell += data
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
123
124
125
126
127
128
129
|
# TODO Do something that really is OOP or do not...
class Event:
# Mined data
shortText = ''
longText = ''
date = False
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
130
131
|
begSlot = 0
endSlot = 0
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
# Generated data
shortName = ''
longName = ''
location = ''
startTime = False
endTime = False
active = False
def feedShortText(self, shortText):
self.shortText = shortText
def feedLongText(self, longText):
self.longText = longText
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
147
148
149
150
151
|
def feedBegSlot(self, slot):
self.begSlot = slot
def feedEndSlot(self, slot):
self.endSlot = slot
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
152
153
154
155
156
157
158
159
|
def feedDate(self, date):
self.date = date
def endFeed(self):
self.shortName = self.shortText
self.longName = self.longText
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
160
|
if self.shortName:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
161
162
|
self.active = True
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
163
164
|
if self.date and isinstance(self.begSlot, int) and isinstance(self.endSlot, int):
h, m = SLOTS[self.begSlot][0]
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
165
|
self.startTime = self.date + datetime.timedelta(hours=h, minutes=m)
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
166
|
h, m = SLOTS[self.endSlot][1]
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
167
168
169
170
171
172
173
174
175
176
177
178
|
self.endTime = self.date + datetime.timedelta(hours=h, minutes=m)
if self.longName:
e = self.longName.split('(')
if len(e) >= 2:
f = e[1].split(')')
self.longName = e[0].strip()
self.location = f[0].strip()
def __str__(self):
if self.active:
|
f6d0d112
Geoffrey PREUD'HOMME
Meilleur respect ...
|
179
|
return self.shortName + ' [' + self.longName + '] ' + (str(self.startTime) + ' - ' + (str(self.endTime) + ' ') if self.startTime else '') + (('@ ' + self.location) if self.location else '')
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
180
181
182
183
184
|
else:
return 'Inactive event'
def getEvent(self):
e = CalEvent()
|
f6d0d112
Geoffrey PREUD'HOMME
Meilleur respect ...
|
185
186
187
188
189
190
191
192
|
e.add('uid', '-'.join([
'polytech',
'ima' + str(args.annee),
args.edt,
vDatetime(self.startTime).to_ical().decode(),
vDatetime(self.endTime).to_ical().decode(),
self.shortName
]))
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
193
194
195
196
197
|
e.add('summary', self.shortName)
e.add('description', self.longName)
e.add('dtstart', self.startTime)
e.add('dtend', self.endTime)
e.add('location', self.location)
|
86487554
Geoffrey PREUD'HOMME
Support de la dat...
|
198
199
|
e.add('last-modified', updateTime)
e.add('dtstamp', updateTime)
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
200
201
202
|
return e
with urllib.request.urlopen(url) as handle:
|
86487554
Geoffrey PREUD'HOMME
Support de la dat...
|
203
|
updateTime = dateutil.parser.parse(handle.headers['Last-Modified'])
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
204
|
htmlStr = handle.read().decode('iso-8859-15')
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
# Read HTML tables
parser = TableHTMLParser()
parser.feed(htmlStr)
# Dupplicates cells with colspan & rowspan
tables = []
for parserTable in parser.tables:
# Figuring out dimensions
X, Y = 0, 0
for cell in parserTable[0]:
X += cell[1]
for line in parserTable:
Y += line[0][2]
# Constructing table with reals dimensions
table = []
for y in range(Y):
line = []
for x in range(X):
line.append(False)
table.append(line)
# Filling table with parsed table
x, y = 0, 0
for line in parserTable:
for cell in line:
# Offsetting to the right if cell is not empty
while isinstance(table[y][x], str):
x += 1
# Copying values
for y2 in range(y, y + cell[2]):
for x2 in range(x, x + cell[1]):
table[y2][x2] = cell[0]
x = 0
y += 1
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
244
245
246
247
248
249
250
251
|
tables.append(table)
# Creating events
days = dict()
# Parsing table 1
for line in tables[0]:
try:
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
252
|
date = datetime.datetime.strptime(line[TABLE_1_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
253
254
255
256
|
except (ValueError, TypeError):
# This is not a date, no data to grab here
continue
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
257
258
259
260
261
262
263
|
for weekSlot in range(int(DAYS_PER_WEEK * len(SLOTS))):
daySlot = weekSlot % len(SLOTS)
# New day
if daySlot == 0:
if weekSlot != 0:
date = date + datetime.timedelta(days=1)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
264
|
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
265
266
|
if date not in days:
days[date] = [Event() for s in range(len(SLOTS))]
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
267
|
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
268
269
|
cell = line[TABLE_1_FIRST_SLOT_X + weekSlot]
days[date][daySlot].feedShortText(cell)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
270
271
272
273
|
# Parsing table 2
for line in tables[1]:
try:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
274
|
date = datetime.datetime.strptime(line[TABLE_2_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
275
276
277
278
279
280
281
282
283
284
285
286
287
|
except ValueError:
# This is not a date, no data to grab here
continue
if date not in days:
days[date] = [Event() for s in range(len(SLOTS))]
for slot in range(len(SLOTS)):
days[date][slot].feedLongText(line[slot + TABLE_2_FIRST_SLOT_X])
# Feeding back time and slot to events
events = []
for day in days:
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
288
|
prevEvent = False
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
289
290
|
for slot in range(len(SLOTS)):
event = days[day][slot]
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
291
|
sameAsPrevious = False
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
292
|
if prevEvent:
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
293
294
|
sameAsPrevious = event.longText == prevEvent.longText if prevEvent.longText else event.shortText == prevEvent.shortText
if sameAsPrevious:
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
295
296
297
298
|
prevEvent.feedEndSlot(slot)
else:
prevEvent.endFeed()
events.append(prevEvent)
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
299
|
if not prevEvent or (prevEvent and not sameAsPrevious):
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
300
301
302
303
304
305
|
event.feedDate(day)
event.feedBegSlot(slot)
event.feedEndSlot(slot)
prevEvent = event
prevEvent.endFeed()
events.append(prevEvent)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
306
307
308
|
# Creating calendar
cal = Calendar()
|
f6d0d112
Geoffrey PREUD'HOMME
Meilleur respect ...
|
309
|
cal.add('proid', '-//geoffrey.frogeye.fr//NONSGML Icalendar Calendar//EN')
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
310
|
cal.add('version', '2.0')
|
f6d0d112
Geoffrey PREUD'HOMME
Meilleur respect ...
|
311
312
|
cal.add('calscale', 'GREGORIAN')
cal.add('x-wr-calname', 'Polytech IMA ' + str(args.annee) + ' ' + args.edt)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
313
314
315
|
for event in events:
if event.active:
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
316
317
318
|
cal.add_component(event.getEvent())
# Writing calendar to file
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
319
320
321
322
323
324
|
data = cal.to_ical()
if args.file == '-':
sys.stdout.write(data.decode('utf-8'))
else:
with open(args.file, 'wb') as f:
f.write(data)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
|
|