diff options
Diffstat (limited to 'src/SplusParser.ts')
| -rw-r--r-- | src/SplusParser.ts | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/SplusParser.ts b/src/SplusParser.ts new file mode 100644 index 0000000..be693fd --- /dev/null +++ b/src/SplusParser.ts @@ -0,0 +1,160 @@ +import {load} from 'cheerio'; + +interface IBlock { + title: string; + durationHours: number; + durationSlots: number; + info: string; + room: string; + lecturer: string +} + +export interface ILecture { + title: string; + day: number; + begin: number; + end: number; + info: string; + room: string, + lecturer: string; +} + +export class SplusParser { + private readonly startHour = 7; + + private readonly $: CheerioStatic; + private readonly $table: Cheerio; + private readonly $rows: Cheerio; + + private colWidths: number[] = []; + private colBlocked: number[] = []; + + private blocks: IBlock[][][] = []; + + constructor(data: string) { + this.$ = load(data); + + this.$table = this.$(this.$('table.grid-border-args')[0]); + this.$rows = this.$table.find('> tbody > tr'); + + this.parseColWidths(); + this.parseTable(); + } + + getLectures(filter?: (lecture: ILecture) => boolean): ILecture[] { + let lectures: ILecture[] = []; + + for (let i = 0; i < this.blocks.length; i++) { + const begin = this.startHour + i * 0.25; + + const slot = this.blocks[i]; + for (let j = 0; j < slot.length; j++) { + const day = slot[j]; + for (let k = 0; k < day.length; k++) { + const block = day[k]; + + const lecture = { + title: block.title, + day: j, + begin: begin, + end: begin + block.durationHours, + info: block.info, + room: block.room, + lecturer: block.lecturer + }; + + if(!filter || filter(lecture)) { + lectures.push(lecture); + } + } + } + } + + return lectures; + } + + private getDay(col: number) { + return this.colWidths.findIndex(width => (col -= width) < 0); + } + + private parseColWidths() { + const $cols = this.$(this.$rows[0]).find('td:not(:first-child)'); + + for (let i = 0; i < $cols.length; i++) { + const $col = this.$($cols[i]); + this.colWidths[i] = parseInt($col.attr('colspan')); + } + + let index = 0; + for (let i = 0; i < this.colWidths.length; i++) { + for (let j = 0; j < this.colWidths[i]; i++) { + this.colBlocked[index++] = 0; + } + } + } + + private parseTable() { + for (let i = 0; i < this.$rows.length - 1; i++) { + // i + 1 to skip the columns row + const $row = this.$(this.$rows[i + 1]); + + this.colBlocked = this.colBlocked.map(v => v > 0 ? v - 1 : 0); + + this.blocks.push(this.parseRow($row)); + } + } + + private parseRow($row: Cheerio) { + const $cols = $row.find('> td:not(:first-child)'); + const blocks: IBlock[][] = []; + + let offset = 0; + for (let i = 0; i < $cols.length; i++) { + while (this.colBlocked[i + offset] > 0) { + const day = this.getDay(i + offset); + if (blocks[day] === undefined) { + blocks[day] = []; + } + + offset++; + } + + const day = this.getDay(i + offset); + if (blocks[day] === undefined) { + blocks[day] = []; + } + + const $c = this.$($cols[i]); + if ($c.hasClass('object-cell-border')) { + const block = this.parseBlock($c); + blocks[day].push(block); + + this.colBlocked[i + offset] = block.durationSlots; + } + } + + return blocks; + } + + private parseBlock($block: Cheerio): IBlock { + const $tblTitle = $block.find('table:nth-child(1)'); + const $tblInfo = $block.find('table:nth-child(2)'); + const $tblLocation = $block.find('table:nth-child(3)'); + + const $tdRoom = $tblLocation.find('td:nth-child(1)'); + const $tdLecturer = $tblLocation.find('td:nth-child(2)'); + + // Remove the Modulhandbuch "mhb" link + $tblTitle.find('br + strong').remove(); + + const slots = parseInt($block.attr('rowspan')); + return { + title: $tblTitle.text().trim(), + durationHours: slots / 4, + durationSlots: slots, + info: $tblInfo.text().trim(), + room: $tdRoom.text().trim(), + lecturer: $tdLecturer.text().trim() + }; + } +} |
