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
|
86487554
Geoffrey PREUD'HOMME
Support de la dat...
|
8
|
import dateutil.parser
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
9
|
from icalendar import Calendar
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
10
|
from html.parser import HTMLParser
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
11
|
from icalendar import vDatetime, Calendar, Event as CalEvent
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
12
|
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
13
|
# Parse command line arguments
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
14
15
|
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...
|
16
17
18
19
|
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
|
20
21
22
23
24
25
26
27
|
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...
|
28
|
url = 'http://ima3.polytech-lille.net/IMA4/' + args.edt + '.html'
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
29
|
SLOTS = [(( 8, 0), ( 9, 0)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
30
|
(( 9, 0), (10, 00)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
31
|
((10, 20), (11, 20)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
32
|
((11, 20), (12, 20)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
33
|
((13, 50), (14, 50)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
34
|
((14, 50), (15, 50)),
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
35
|
((16, 10), (17, 10)),
|
a3046d70
Geoffrey PREUD'HOMME
Support des règle...
|
36
|
((17, 10), (18, 10))]
|
721af5a1
Geoffrey PREUD'HOMME
Nouveau lien pour...
|
37
|
DATE_FORMAT = '%d/%m/%Y'
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
38
39
40
|
else:
raise ValueError('Année inconnue : ' + annee)
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
41
|
DAYS_PER_WEEK = 5.5
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
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
95
|
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
|
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
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
|
113
114
115
116
117
118
119
120
|
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
|
121
122
123
|
if self.iscell():
self.cell += data
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
124
125
126
127
128
129
130
|
# 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...
|
131
132
|
begSlot = 0
endSlot = 0
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# 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...
|
148
149
150
151
152
|
def feedBegSlot(self, slot):
self.begSlot = slot
def feedEndSlot(self, slot):
self.endSlot = slot
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
153
154
155
156
157
158
159
160
|
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...
|
161
|
if self.shortName:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
162
163
|
self.active = True
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
164
165
|
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
|
166
|
self.startTime = self.date + datetime.timedelta(hours=h, minutes=m)
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
167
|
h, m = SLOTS[self.endSlot][1]
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
168
169
170
171
172
173
174
175
176
177
178
179
|
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 ...
|
180
|
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
|
181
182
183
184
185
|
else:
return 'Inactive event'
def getEvent(self):
e = CalEvent()
|
964ae38e
Geoffrey PREUD'HOMME
Meilleur respect ...
|
186
187
188
189
190
191
192
193
|
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
|
194
195
196
197
198
|
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...
|
199
200
|
e.add('last-modified', updateTime)
e.add('dtstamp', updateTime)
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
201
202
203
|
return e
with urllib.request.urlopen(url) as handle:
|
86487554
Geoffrey PREUD'HOMME
Support de la dat...
|
204
|
updateTime = dateutil.parser.parse(handle.headers['Last-Modified'])
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
205
|
htmlStr = handle.read().decode('iso-8859-15')
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
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
244
|
# 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
|
245
246
247
248
249
250
251
252
|
tables.append(table)
# Creating events
days = dict()
# Parsing table 1
for line in tables[0]:
try:
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
253
|
date = datetime.datetime.strptime(line[TABLE_1_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
254
255
256
257
|
except (ValueError, TypeError):
# This is not a date, no data to grab here
continue
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
258
259
260
261
262
263
264
|
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
|
265
|
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
266
267
|
if date not in days:
days[date] = [Event() for s in range(len(SLOTS))]
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
268
|
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
269
270
|
cell = line[TABLE_1_FIRST_SLOT_X + weekSlot]
days[date][daySlot].feedShortText(cell)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
271
272
273
274
|
# Parsing table 2
for line in tables[1]:
try:
|
3e3f7292
Geoffrey PREUD'HOMME
IMA 4 support
|
275
|
date = datetime.datetime.strptime(line[TABLE_2_DATE_X], DATE_FORMAT)
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
276
277
278
279
280
281
282
283
284
285
286
287
288
|
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...
|
289
|
prevEvent = False
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
290
291
|
for slot in range(len(SLOTS)):
event = days[day][slot]
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
292
|
sameAsPrevious = False
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
293
|
if prevEvent:
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
294
295
|
sameAsPrevious = event.longText == prevEvent.longText if prevEvent.longText else event.shortText == prevEvent.shortText
if sameAsPrevious:
|
ab9e873d
Geoffrey PREUD'HOMME
No longer cut les...
|
296
297
298
299
|
prevEvent.feedEndSlot(slot)
else:
prevEvent.endFeed()
events.append(prevEvent)
|
2ae9506d
Geoffrey PREUD'HOMME
Affiche les évène...
|
300
|
if not prevEvent or (prevEvent and not sameAsPrevious):
|
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:
|
99b1d135
Geoffrey PREUD'HOMME
Initial commit
|
323
324
325
|
cal.add_component(event.getEvent())
# Writing calendar to file
|
82e73c44
Geoffrey PREUD'HOMME
Interface en lign...
|
326
327
328
329
330
331
|
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
|
|
|