Skip to content

Commit b54a703

Browse files
committed
wip: parse
1 parent cd7ea40 commit b54a703

File tree

5 files changed

+194
-89
lines changed

5 files changed

+194
-89
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"csv": "^5.1.1",
4343
"iconv-lite": "^0.5.0",
4444
"tough-cookie": "^4.0.0",
45-
"ts-node": "^9.1.1"
45+
"ts-node": "^9.1.1",
46+
"xlsx": "^0.16.9"
4647
}
4748
}

src/app.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@ import parse from './parser'
44
import * as colors from 'colors'
55
import * as commandLineArgs from 'command-line-args'
66

7-
console.log('twinte-parser v1.3.2'.green.bold)
7+
console.log('twinte-parser v1.3.2')
88

99
const ops = commandLineArgs([
10-
{ name: 'year', alias: 'y', defaultValue: undefined }
10+
{ name: 'year', alias: 'y', defaultValue: undefined },
1111
])
1212

1313
const main = async () => {
14-
let csv: string
14+
let xlsx: Buffer
1515

16-
if (fs.existsSync('./kdb.csv')) {
17-
console.log('i Cache file (kdb.csv) found.'.cyan)
18-
csv = fs.readFileSync('./kdb.csv', 'utf-8')
16+
if (fs.existsSync('./kdb.xlsx')) {
17+
console.log('i Cache file (kdb.xlsx) found.')
18+
xlsx = fs.readFileSync('./kdb.xlsx')
1919
} else {
20-
csv = await kdbGetter(ops.year)
21-
fs.writeFileSync('./kdb.csv', csv)
20+
xlsx = await kdbGetter(ops.year)
21+
fs.writeFileSync('./kdb.xlsx', xlsx)
2222
}
23-
const classes = parse(csv)
23+
const classes = parse(xlsx)
2424
fs.writeFileSync('data.json', JSON.stringify(classes))
2525
console.log(
26-
'Parsed data has been saved at ${working directory}/data.json'.green
26+
'Parsed data has been saved at ${working directory}/data.json'
2727
)
2828
}
2929

src/kdbDownloader.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ const searchAll = async (flowExecutionKey: string): Promise<string> => {
5454
return extractFlowExecutionKey(iconv.decode(res.data, 'utf8'))
5555
}
5656

57-
const downloadCSV = async (flowExecutionKey: string): Promise<string> => {
57+
const downloadExcel = async (flowExecutionKey: string): Promise<Buffer> => {
5858
const res = await axios.post<Buffer>(
5959
'https://kdb.tsukuba.ac.jp/campusweb/campussquare.do',
6060
postBody({
6161
_flowExecutionKey: flowExecutionKey,
62-
_eventId: 'output',
62+
_eventId: 'outputOpeningCourseExcel',
6363
index: '',
6464
locale: '',
6565
nendo: 2020,
@@ -76,20 +76,20 @@ const downloadCSV = async (flowExecutionKey: string): Promise<string> => {
7676
_gaiyoFlg: 1,
7777
_risyuFlg: 1,
7878
_excludeFukaikoFlg: 1,
79-
outputFormat: 0,
79+
outputFormat: 1,
8080
})
8181
)
82-
return iconv.decode(res.data, 'Shift_JIS')
82+
return res.data
8383
}
8484

8585
/**
8686
* KDBからCSVを取得
8787
*/
8888
export default async (
8989
year: number = new Date().getFullYear()
90-
): Promise<string> => {
90+
): Promise<Buffer> => {
9191
let flowExecutionKey = ''
9292
flowExecutionKey = await grantSession()
9393
flowExecutionKey = await searchAll(flowExecutionKey)
94-
return downloadCSV(flowExecutionKey)
94+
return downloadExcel(flowExecutionKey)
9595
}

src/parser.ts

+80-72
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Lecture, Day, Module } from './types'
22
import * as parseCsv from 'csv-parse/lib/sync'
33
import * as _cliProgress from 'cli-progress'
4+
import { read as readXLSX, utils } from 'xlsx'
45

56
/**
67
* '月1,2' 等の曜日と時限の文字列を解析し、分解する
@@ -10,7 +11,7 @@ import * as _cliProgress from 'cli-progress'
1011
const analyzeDayAndPeriod = (str: string): { day: Day; period: number }[] => {
1112
const result: { day: Day; period: number }[] = []
1213
//全ての曜日に対して
13-
Object.entries(Day).forEach(k => {
14+
Object.entries(Day).forEach((k) => {
1415
const day = k[1] as Day //月, 火 , .... , 日 のどれか
1516
// 1から6限について
1617
for (let i = 1; i <= 6; i++) {
@@ -23,18 +24,18 @@ const analyzeDayAndPeriod = (str: string): { day: Day; period: number }[] => {
2324
if (new RegExp(`${day}.*${i}`).test(str)) {
2425
result.push({
2526
day: day,
26-
period: i
27+
period: i,
2728
})
2829
}
2930
}
3031
// 月1-4 のようなハイフン表記のテスト
3132
const longTermTest = new RegExp(`([${day}]).*(\\d)-(\\d)`).exec(str)
3233
if (longTermTest) {
3334
for (let i = Number(longTermTest[2]); i <= Number(longTermTest[3]); i++) {
34-
if (!result.find(el => el.day === day && el.period === i))
35+
if (!result.find((el) => el.day === day && el.period === i))
3536
result.push({
3637
day: day,
37-
period: i
38+
period: i,
3839
})
3940
}
4041
}
@@ -43,7 +44,7 @@ const analyzeDayAndPeriod = (str: string): { day: Day; period: number }[] => {
4344
if (//gm.test(str))
4445
result.push({
4546
day: Day.Intensive,
46-
period: 0
47+
period: 0,
4748
})
4849

4950
//どのテストにも合格しなかったが空文字でなければ仮にunknownとする
@@ -61,7 +62,6 @@ const analyzeDayAndPeriod = (str: string): { day: Day; period: number }[] => {
6162
const analyzeModule = (str: string): Module[] => {
6263
const result: Module[] = []
6364

64-
6565
/*
6666
/春[ABC]*A/は春A 春AB 春ABCに
6767
/春[ABC]*B/は春B 春AB 春BCに
@@ -70,14 +70,14 @@ const analyzeModule = (str: string): Module[] => {
7070
if (/[ABC]*A/gm.test(str)) result.push(Module.SpringA)
7171
if (/[ABC]*B/gm.test(str)) result.push(Module.SpringB)
7272
if (/[ABC]*C/gm.test(str)) result.push(Module.SpringC)
73-
73+
7474
// 夏季休業中、通年のマッチング
7575
if (str.includes(Module.SummerVacation)) result.push(Module.SummerVacation)
7676

7777
if (/[ABC]*A/gm.test(str)) result.push(Module.FallA)
7878
if (/[ABC]*B/gm.test(str)) result.push(Module.FallB)
7979
if (/[ABC]*C/gm.test(str)) result.push(Module.FallC)
80-
80+
8181
// 春季休業中のマッチング
8282
if (str.includes(Module.SpringVacation)) result.push(Module.SpringVacation)
8383

@@ -104,78 +104,86 @@ const analyzeYear = (str: string): number[] => {
104104
res.push(i)
105105
}
106106
} else if (//.test(str)) {
107-
res.push(...str.split('・').map(e => Number(e)))
107+
res.push(...str.split('・').map((e) => Number(e)))
108108
}
109109
return res
110110
}
111111

112-
/**
113-
* CSVをパースする
114-
* @param csv KDBからダウンロードしたcsv文字列
115-
*/
116-
export default (csv: string): Lecture[] => {
117-
//コンソールの進捗バー
118-
const bar = new _cliProgress.Bar({}, _cliProgress.Presets.shades_classic)
119-
120-
const rows = parseCsv(csv)
121-
122-
////// コンソール表示
123-
console.log('● Parsing'.green.bold)
124-
bar.start(rows.length, 0)
125-
let done = 0
126-
//////
127-
128-
const classes = rows.map(columns => {
129-
const classData: Lecture = {
130-
lectureCode: columns[0],
131-
name: columns[1],
132-
credits: Number(columns[3]),
133-
type: Number(columns[2]),
134-
overview: columns[9],
135-
remarks: columns[10],
136-
year: analyzeYear(columns[4]),
137-
details: [],
138-
instructor: columns[8]
139-
}
140-
141-
const moduleString = columns[5]
142-
const periodString = columns[6]
143-
const roomString = columns[7]
144-
145-
// 空文字は省く
146-
const moduleArray = moduleString.split('\n')
147-
const periodArray = periodString.split('\n')
148-
const roomArray = roomString.split('\n')
112+
const analyzeRow = (columns: string[]) => {
113+
const classData: Lecture = {
114+
lectureCode: columns[0],
115+
name: columns[1],
116+
credits: Number(columns[3]),
117+
type: Number(columns[2]),
118+
overview: columns[9],
119+
remarks: columns[10],
120+
year: analyzeYear(columns[4]),
121+
details: [],
122+
instructor: columns[8],
123+
}
149124

150-
const count = Math.max(
151-
moduleArray.length,
152-
Math.max(periodArray.length, roomArray.length)
125+
const moduleString = columns[5]
126+
const periodString = columns[6]
127+
const roomString = columns[7]
128+
129+
// 空文字は省く
130+
const moduleArray = moduleString.split('\r\n')
131+
const periodArray = periodString.split('\r\n')
132+
const roomArray = roomString.split('\r\n')
133+
134+
const count = Math.max(
135+
moduleArray.length,
136+
Math.max(periodArray.length, roomArray.length)
137+
)
138+
139+
if (
140+
!(
141+
(moduleArray.length === count || moduleArray.length === 1) &&
142+
(periodArray.length === count || periodArray.length === 1) &&
143+
(roomArray.length === count || roomArray.length === 1)
153144
)
145+
)
146+
console.log('Warning!')
154147

155-
for (let i = 0; i < count; i++) {
156-
const modules = analyzeModule(
157-
moduleArray.length === 1 ? moduleArray[0] : moduleArray[i]
158-
)
159-
const when = analyzeDayAndPeriod(
160-
periodArray.length === 1 ? periodArray[0] : periodArray[i]
161-
)
162-
modules.forEach(mod =>
163-
when.forEach(w =>
164-
classData.details.push({
165-
module: mod,
166-
period: w.period,
167-
day: w.day,
168-
room: roomArray.length === 1 ? roomArray[0] : roomArray[i]
169-
})
170-
)
148+
for (let i = 0; i < count; i++) {
149+
const modules = analyzeModule(
150+
moduleArray.length === 1 ? moduleArray[0] : moduleArray[i] || ''
151+
)
152+
const when = analyzeDayAndPeriod(
153+
periodArray.length === 1 ? periodArray[0] : periodArray[i] || ''
154+
)
155+
modules.forEach((mod) =>
156+
when.forEach((w) =>
157+
classData.details.push({
158+
module: mod,
159+
period: w.period,
160+
day: w.day,
161+
room: roomArray.length === 1 ? roomArray[0] : roomArray[i] || '',
162+
})
171163
)
172-
}
173-
done++
174-
bar.update(done)
175-
return classData
176-
})
164+
)
165+
}
166+
return classData
167+
}
177168

178-
bar.stop()
179-
console.log('✔ Done'.green.bold)
169+
/**
170+
* CSVをパースする
171+
* @param csv KDBからダウンロードしたcsv文字列
172+
*/
173+
export default (data: Buffer): Lecture[] => {
174+
const sheet = readXLSX(data).Sheets['開設科目一覧']
175+
176+
const classes: Lecture[] = []
177+
178+
console.log('● Parsing')
179+
for (let r = 5; ; r++) {
180+
const columns: string[] = []
181+
for (let c = 0; c < 16; c++)
182+
columns.push(sheet[utils.encode_cell({ r, c })].v)
183+
if (columns[0] === '') break
184+
// console.log(r)
185+
classes.push(analyzeRow(columns))
186+
}
187+
console.log('✔ Done')
180188
return classes
181189
}

0 commit comments

Comments
 (0)