99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
1
2
|
#!/usr/bin/env python3
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
3
|
import os.path
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
4
5
|
import sys
import argparse
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
6
|
import datetime
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
7
|
import urllib.request
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
8
|
from icalendar import Calendar
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
9
|
from html.parser import HTMLParser
|
964ae38e
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
|
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:
|
721af5a1
Geoffrey PREUD'HOMME
Nouveau lien pour...
|
27
|
url = 'http://ima3.polytech-lille.net/IMA4/' + args.edt + '.html'
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
28
|
SLOTS = [(( 8, 0), ( 9, 0)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
29
|
(( 9, 0), (10, 00)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
30
|
((10, 20), (11, 20)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
31
|
((11, 20), (12, 20)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
32
|
((13, 50), (14, 50)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
33
|
((14, 50), (15, 50)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
34
|
((16, 10), (17, 10)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
35
|
((17, 10), (18, 10))]
|
721af5a1
Geoffrey PREUD'HOMME
Nouveau lien pour...
|
36
|
DATE_FORMAT = '%d/%m/%Y'
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
37
38
39
40
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
|
else:
raise ValueError('Année inconnue : ' + annee)
DAYS_PER_WEEK = 6
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
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
123
124
125
|
# TODO Use HTTP header date
UPDATE_TIME = datetime.datetime.now()
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
126
127
128
129
130
131
132
|
# 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...
|
133
134
|
begSlot = 0
endSlot = 0
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# 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...
|
150
151
152
153
154
|
def feedBegSlot(self, slot):
self.begSlot = slot
def feedEndSlot(self, slot):
self.endSlot = slot
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
155
156
157
158
159
160
161
162
163
164
165
|
def feedDate(self, date):
self.date = date
def endFeed(self):
self.shortName = self.shortText
self.longName = self.longText
if self.longName:
self.active = True
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
166
167
|
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
|
168
|
self.startTime = self.date + datetime.timedelta(hours=h, minutes=m)
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
169
|
h, m = SLOTS[self.endSlot][1]
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
170
171
172
173
174
175
176
177
178
179
180
181
|
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:
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
182
|
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
|
183
184
185
186
187
|
else:
return 'Inactive event'
def getEvent(self):
e = CalEvent()
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
188
189
190
191
192
193
194
195
|
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
|
196
197
198
199
200
|
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)
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
201
202
|
e.add('last-modified', UPDATE_TIME)
e.add('dtstamp', UPDATE_TIME)
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
203
204
205
|
return e
with urllib.request.urlopen(url) as handle:
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
206
|
htmlStr = handle.read().decode('iso-8859-15')
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
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
244
245
|
# 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
|
246
247
248
249
250
251
252
253
|
tables.append(table)
# Creating events
days = dict()
# Parsing table 1
for line in tables[0]:
try:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
254
|
day1date = datetime.datetime.strptime(line[TABLE_1_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
255
256
257
258
|
except (ValueError, TypeError):
# This is not a date, no data to grab here
continue
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
259
260
261
262
263
264
265
|
for day in range(DAYS_PER_WEEK):
date = day1date + datetime.timedelta(days=day)
if date not in days:
days[date] = [Event() for s in range(len(SLOTS))]
for slot in range(len(SLOTS)):
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
266
267
268
269
270
271
|
try:
cell = line[day * len(SLOTS) + slot + TABLE_1_FIRST_SLOT_X]
except IndexError:
# Out of the table: saturday afternoon
break
days[date][slot].feedShortText(cell)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
272
273
274
275
276
|
continue
# Parsing table 2
for line in tables[1]:
try:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
277
|
date = datetime.datetime.strptime(line[TABLE_2_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
278
279
280
281
282
283
284
285
286
287
288
289
290
|
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...
|
291
|
prevEvent = False
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
292
293
|
for slot in range(len(SLOTS)):
event = days[day][slot]
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
294
|
if prevEvent:
|
7a72c101
Geoffrey PREUD'HOMME
Stricter verifica...
|
295
|
if prevEvent.longText == event.longText:
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
296
297
298
299
|
prevEvent.feedEndSlot(slot)
else:
prevEvent.endFeed()
events.append(prevEvent)
|
7a72c101
Geoffrey PREUD'HOMME
Stricter verifica...
|
300
|
if not prevEvent or (prevEvent and prevEvent.longText != event.longText):
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
301
302
303
304
305
306
|
event.feedDate(day)
event.feedBegSlot(slot)
event.feedEndSlot(slot)
prevEvent = event
prevEvent.endFeed()
events.append(prevEvent)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
307
308
309
|
# Creating calendar
cal = Calendar()
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
310
|
cal.add('proid', '-//geoffrey.frogeye.fr//NONSGML Icalendar Calendar//EN')
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
311
|
cal.add('version', '2.0')
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
312
313
|
cal.add('calscale', 'GREGORIAN')
cal.add('x-wr-calname', 'Polytech IMA ' + str(args.annee) + ' ' + args.edt)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
314
|
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
315
316
317
318
319
320
|
if os.path.isfile('custom.py'):
import custom
for event in events:
if event.active:
custom.filterEvent(event)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
321
322
|
for event in events:
if event.active:
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
323
|
print(event, file=sys.stderr)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
324
325
326
|
cal.add_component(event.getEvent())
# Writing calendar to file
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
327
328
329
330
331
332
|
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
|
|
|