@chukyo-umebo/web_parser 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/cjs/albo/index.js +5 -0
- package/dist/cjs/albo/parser/albo.js +52 -0
- package/dist/cjs/albo/types/albo.js +19 -0
- package/dist/cjs/common/dom.js +116 -0
- package/dist/cjs/common/utils.js +13 -0
- package/dist/cjs/cubics/index.js +5 -0
- package/dist/cjs/cubics/parser/cubics.js +94 -0
- package/dist/cjs/cubics/types/cubics.js +38 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/manabo/index.js +23 -0
- package/dist/cjs/manabo/parser/manaboClass.js +428 -0
- package/dist/cjs/manabo/parser/manaboEntry.js +35 -0
- package/dist/cjs/manabo/parser/manaboMail.js +169 -0
- package/dist/cjs/manabo/parser/manaboNews.js +29 -0
- package/dist/cjs/manabo/parser/manaboTimetable.js +75 -0
- package/dist/cjs/manabo/types/manaboClass.js +126 -0
- package/dist/cjs/manabo/types/manaboEntry.js +21 -0
- package/dist/cjs/manabo/types/manaboMail.js +66 -0
- package/dist/cjs/manabo/types/manaboNews.js +11 -0
- package/dist/cjs/manabo/types/manaboTimetable.js +32 -0
- package/dist/esm/albo/index.d.ts +3 -0
- package/dist/esm/albo/index.d.ts.map +1 -0
- package/dist/esm/albo/index.js +1 -0
- package/dist/esm/albo/parser/albo.d.ts +4 -0
- package/dist/esm/albo/parser/albo.d.ts.map +1 -0
- package/dist/esm/albo/parser/albo.js +48 -0
- package/dist/esm/albo/types/albo.d.ts +34 -0
- package/dist/esm/albo/types/albo.d.ts.map +1 -0
- package/dist/esm/albo/types/albo.js +16 -0
- package/dist/esm/common/dom.d.ts +18 -0
- package/dist/esm/common/dom.d.ts.map +1 -0
- package/dist/esm/common/dom.js +99 -0
- package/dist/esm/common/utils.d.ts +3 -0
- package/dist/esm/common/utils.d.ts.map +1 -0
- package/dist/esm/common/utils.js +8 -0
- package/dist/esm/cubics/index.d.ts +3 -0
- package/dist/esm/cubics/index.d.ts.map +1 -0
- package/dist/esm/cubics/index.js +1 -0
- package/dist/esm/cubics/parser/cubics.d.ts +4 -0
- package/dist/esm/cubics/parser/cubics.d.ts.map +1 -0
- package/dist/esm/cubics/parser/cubics.js +90 -0
- package/dist/esm/cubics/types/cubics.d.ts +60 -0
- package/dist/esm/cubics/types/cubics.d.ts.map +1 -0
- package/dist/esm/cubics/types/cubics.js +35 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/manabo/index.d.ts +11 -0
- package/dist/esm/manabo/index.d.ts.map +1 -0
- package/dist/esm/manabo/index.js +5 -0
- package/dist/esm/manabo/parser/manaboClass.d.ts +9 -0
- package/dist/esm/manabo/parser/manaboClass.d.ts.map +1 -0
- package/dist/esm/manabo/parser/manaboClass.js +419 -0
- package/dist/esm/manabo/parser/manaboEntry.d.ts +5 -0
- package/dist/esm/manabo/parser/manaboEntry.d.ts.map +1 -0
- package/dist/esm/manabo/parser/manaboEntry.js +30 -0
- package/dist/esm/manabo/parser/manaboMail.d.ts +8 -0
- package/dist/esm/manabo/parser/manaboMail.d.ts.map +1 -0
- package/dist/esm/manabo/parser/manaboMail.js +161 -0
- package/dist/esm/manabo/parser/manaboNews.d.ts +4 -0
- package/dist/esm/manabo/parser/manaboNews.d.ts.map +1 -0
- package/dist/esm/manabo/parser/manaboNews.js +25 -0
- package/dist/esm/manabo/parser/manaboTimetable.d.ts +4 -0
- package/dist/esm/manabo/parser/manaboTimetable.d.ts.map +1 -0
- package/dist/esm/manabo/parser/manaboTimetable.js +71 -0
- package/dist/esm/manabo/types/manaboClass.d.ts +268 -0
- package/dist/esm/manabo/types/manaboClass.d.ts.map +1 -0
- package/dist/esm/manabo/types/manaboClass.js +123 -0
- package/dist/esm/manabo/types/manaboEntry.d.ts +21 -0
- package/dist/esm/manabo/types/manaboEntry.d.ts.map +1 -0
- package/dist/esm/manabo/types/manaboEntry.js +18 -0
- package/dist/esm/manabo/types/manaboMail.d.ts +95 -0
- package/dist/esm/manabo/types/manaboMail.d.ts.map +1 -0
- package/dist/esm/manabo/types/manaboMail.js +63 -0
- package/dist/esm/manabo/types/manaboNews.d.ts +13 -0
- package/dist/esm/manabo/types/manaboNews.d.ts.map +1 -0
- package/dist/esm/manabo/types/manaboNews.js +8 -0
- package/dist/esm/manabo/types/manaboTimetable.d.ts +57 -0
- package/dist/esm/manabo/types/manaboTimetable.d.ts.map +1 -0
- package/dist/esm/manabo/types/manaboTimetable.js +29 -0
- package/package.json +37 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { loadDocument, queryAll, queryOne, getAttribute, getTextContent } from "../../common/dom";
|
|
2
|
+
import { CubicsAsTimetableSchema } from "../types/cubics";
|
|
3
|
+
import { normalizeWhitespace, extractFirstQuotedValue } from "../../common/utils";
|
|
4
|
+
export const parseCubicsAsTimetable = (html) => {
|
|
5
|
+
const document = loadDocument(html);
|
|
6
|
+
const studentTable = queryOne("table.output", document);
|
|
7
|
+
const studentRows = studentTable ? queryAll("tr", studentTable) : [];
|
|
8
|
+
const row1Cells = studentRows[0] ? queryAll("td", studentRows[0]) : [];
|
|
9
|
+
const row2Cells = studentRows[1] ? queryAll("td", studentRows[1]) : [];
|
|
10
|
+
const row3Cells = studentRows[2] ? queryAll("td", studentRows[2]) : [];
|
|
11
|
+
const row4Cells = studentRows[3] ? queryAll("td", studentRows[3]) : [];
|
|
12
|
+
const getCellText = (cells, index) => normalizeWhitespace(getTextContent(cells[index] ?? null));
|
|
13
|
+
const student = {
|
|
14
|
+
id: getCellText(row1Cells, 0),
|
|
15
|
+
name: getCellText(row1Cells, 1),
|
|
16
|
+
division: getCellText(row2Cells, 0),
|
|
17
|
+
affiliation: getCellText(row2Cells, 1),
|
|
18
|
+
status: getCellText(row2Cells, 2),
|
|
19
|
+
className: getCellText(row2Cells, 3),
|
|
20
|
+
faculty: getCellText(row3Cells, 0),
|
|
21
|
+
department: getCellText(row3Cells, 1),
|
|
22
|
+
course: getCellText(row3Cells, 2),
|
|
23
|
+
address: getCellText(row4Cells, 0),
|
|
24
|
+
};
|
|
25
|
+
const periodInput = queryOne("input[name='lblSpcfProd']", document);
|
|
26
|
+
const fallbackPeriod = queryOne("div.searcharea ul li", document);
|
|
27
|
+
const periodRangeRaw = getAttribute(periodInput, "value") ?? normalizeWhitespace(getTextContent(fallbackPeriod));
|
|
28
|
+
const periodRange = normalizeWhitespace(periodRangeRaw);
|
|
29
|
+
const curriculumTable = queryOne("table.output_curriculum", document);
|
|
30
|
+
const headerRow = curriculumTable ? queryOne("tr", curriculumTable) : null;
|
|
31
|
+
const headerCells = headerRow ? queryAll("th", headerRow).slice(1) : [];
|
|
32
|
+
const days = headerCells.map((element) => {
|
|
33
|
+
const text = normalizeWhitespace(getTextContent(element));
|
|
34
|
+
const segments = text.split(" ").filter((segment) => segment.length > 0);
|
|
35
|
+
return {
|
|
36
|
+
label: segments[0] ?? "",
|
|
37
|
+
date: segments[1] ?? "",
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const periodRows = curriculumTable ? queryAll("tr", curriculumTable).slice(1) : [];
|
|
41
|
+
const periods = periodRows.map((row) => {
|
|
42
|
+
const periodLabel = normalizeWhitespace(getTextContent(queryOne("th", row)));
|
|
43
|
+
const cells = queryAll("td", row);
|
|
44
|
+
const slots = cells.map((cell) => {
|
|
45
|
+
const lessonCode = getAttribute(queryOne('input[name$=".hdnLsnCd1"]', cell), "value");
|
|
46
|
+
const lessonYear = getAttribute(queryOne('input[name$=".hdnLsnOpcFcy1"]', cell), "value");
|
|
47
|
+
const termCode = getAttribute(queryOne('input[name$=".hdnTacTrmCd1"]', cell), "value");
|
|
48
|
+
const timetableCode = getAttribute(queryOne('input[name$=".hdnTmtxCd"]', cell), "value");
|
|
49
|
+
const optionDate = getAttribute(queryOne('input[name$=".hdnOpcDt"]', cell), "value");
|
|
50
|
+
const detailAnchor = queryAll("a", cell).find((anchor) => (getAttribute(anchor, "onclick") ?? "").includes("execPopupWindowOpen"));
|
|
51
|
+
const detailOnclick = detailAnchor ? getAttribute(detailAnchor, "onclick") ?? "" : "";
|
|
52
|
+
const infoAnchors = queryAll("a.chiphelp", cell);
|
|
53
|
+
const classroom = normalizeWhitespace(getTextContent(infoAnchors[0] ?? null));
|
|
54
|
+
const subject = normalizeWhitespace(getTextContent(infoAnchors[1] ?? null));
|
|
55
|
+
const slot = {
|
|
56
|
+
classroom: classroom.length ? classroom : null,
|
|
57
|
+
subject: subject.length ? subject : null,
|
|
58
|
+
detailUrl: extractFirstQuotedValue(detailOnclick),
|
|
59
|
+
lessonCode,
|
|
60
|
+
lessonYear,
|
|
61
|
+
termCode,
|
|
62
|
+
timetableCode,
|
|
63
|
+
optionDate,
|
|
64
|
+
};
|
|
65
|
+
if (!slot.classroom && !slot.subject && !slot.detailUrl && !slot.lessonCode) {
|
|
66
|
+
return {
|
|
67
|
+
classroom: null,
|
|
68
|
+
subject: null,
|
|
69
|
+
detailUrl: null,
|
|
70
|
+
lessonCode: null,
|
|
71
|
+
lessonYear: null,
|
|
72
|
+
termCode: null,
|
|
73
|
+
timetableCode: null,
|
|
74
|
+
optionDate: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return slot;
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
periodLabel,
|
|
81
|
+
slots,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
return CubicsAsTimetableSchema.safeParse({
|
|
85
|
+
student,
|
|
86
|
+
periodRange,
|
|
87
|
+
days,
|
|
88
|
+
periods,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const CubicsAsTimetableSlotSchema: z.ZodObject<{
|
|
3
|
+
classroom: z.ZodNullable<z.ZodString>;
|
|
4
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
5
|
+
detailUrl: z.ZodNullable<z.ZodString>;
|
|
6
|
+
lessonCode: z.ZodNullable<z.ZodString>;
|
|
7
|
+
lessonYear: z.ZodNullable<z.ZodString>;
|
|
8
|
+
termCode: z.ZodNullable<z.ZodString>;
|
|
9
|
+
timetableCode: z.ZodNullable<z.ZodString>;
|
|
10
|
+
optionDate: z.ZodNullable<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type CubicsAsTimetableSlotDTO = z.infer<typeof CubicsAsTimetableSlotSchema>;
|
|
13
|
+
export declare const CubicsAsTimetablePeriodSchema: z.ZodObject<{
|
|
14
|
+
periodLabel: z.ZodString;
|
|
15
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
16
|
+
classroom: z.ZodNullable<z.ZodString>;
|
|
17
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
18
|
+
detailUrl: z.ZodNullable<z.ZodString>;
|
|
19
|
+
lessonCode: z.ZodNullable<z.ZodString>;
|
|
20
|
+
lessonYear: z.ZodNullable<z.ZodString>;
|
|
21
|
+
termCode: z.ZodNullable<z.ZodString>;
|
|
22
|
+
timetableCode: z.ZodNullable<z.ZodString>;
|
|
23
|
+
optionDate: z.ZodNullable<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type CubicsAsTimetablePeriodDTO = z.infer<typeof CubicsAsTimetablePeriodSchema>;
|
|
27
|
+
export declare const CubicsAsTimetableSchema: z.ZodObject<{
|
|
28
|
+
student: z.ZodObject<{
|
|
29
|
+
id: z.ZodString;
|
|
30
|
+
name: z.ZodString;
|
|
31
|
+
division: z.ZodString;
|
|
32
|
+
affiliation: z.ZodString;
|
|
33
|
+
status: z.ZodString;
|
|
34
|
+
className: z.ZodString;
|
|
35
|
+
faculty: z.ZodString;
|
|
36
|
+
department: z.ZodString;
|
|
37
|
+
course: z.ZodString;
|
|
38
|
+
address: z.ZodString;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
periodRange: z.ZodString;
|
|
41
|
+
days: z.ZodArray<z.ZodObject<{
|
|
42
|
+
label: z.ZodString;
|
|
43
|
+
date: z.ZodString;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
periods: z.ZodArray<z.ZodObject<{
|
|
46
|
+
periodLabel: z.ZodString;
|
|
47
|
+
slots: z.ZodArray<z.ZodObject<{
|
|
48
|
+
classroom: z.ZodNullable<z.ZodString>;
|
|
49
|
+
subject: z.ZodNullable<z.ZodString>;
|
|
50
|
+
detailUrl: z.ZodNullable<z.ZodString>;
|
|
51
|
+
lessonCode: z.ZodNullable<z.ZodString>;
|
|
52
|
+
lessonYear: z.ZodNullable<z.ZodString>;
|
|
53
|
+
termCode: z.ZodNullable<z.ZodString>;
|
|
54
|
+
timetableCode: z.ZodNullable<z.ZodString>;
|
|
55
|
+
optionDate: z.ZodNullable<z.ZodString>;
|
|
56
|
+
}, z.core.$strip>>;
|
|
57
|
+
}, z.core.$strip>>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export type CubicsAsTimetableDTO = z.infer<typeof CubicsAsTimetableSchema>;
|
|
60
|
+
//# sourceMappingURL=cubics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cubics.d.ts","sourceRoot":"","sources":["../../../../src/cubics/types/cubics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,2BAA2B;;;;;;;;;iBAStC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEnF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;iBAGxC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEvF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqBlC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const CubicsAsTimetableSlotSchema = z.object({
|
|
3
|
+
classroom: z.string().nullable(),
|
|
4
|
+
subject: z.string().nullable(),
|
|
5
|
+
detailUrl: z.string().nullable(),
|
|
6
|
+
lessonCode: z.string().nullable(),
|
|
7
|
+
lessonYear: z.string().nullable(),
|
|
8
|
+
termCode: z.string().nullable(),
|
|
9
|
+
timetableCode: z.string().nullable(),
|
|
10
|
+
optionDate: z.string().nullable(),
|
|
11
|
+
});
|
|
12
|
+
export const CubicsAsTimetablePeriodSchema = z.object({
|
|
13
|
+
periodLabel: z.string(),
|
|
14
|
+
slots: z.array(CubicsAsTimetableSlotSchema),
|
|
15
|
+
});
|
|
16
|
+
export const CubicsAsTimetableSchema = z.object({
|
|
17
|
+
student: z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
name: z.string(),
|
|
20
|
+
division: z.string(),
|
|
21
|
+
affiliation: z.string(),
|
|
22
|
+
status: z.string(),
|
|
23
|
+
className: z.string(),
|
|
24
|
+
faculty: z.string(),
|
|
25
|
+
department: z.string(),
|
|
26
|
+
course: z.string(),
|
|
27
|
+
address: z.string(),
|
|
28
|
+
}),
|
|
29
|
+
periodRange: z.string(),
|
|
30
|
+
days: z.array(z.object({
|
|
31
|
+
label: z.string(),
|
|
32
|
+
date: z.string(),
|
|
33
|
+
})),
|
|
34
|
+
periods: z.array(CubicsAsTimetablePeriodSchema),
|
|
35
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { parseManaboClassDirectory, parseManaboClassContent, parseManaboClassEntry, parseManaboClassNews, parseManaboClassSyllabus, parseManaboClassQuizResult, } from "./parser/manaboClass";
|
|
2
|
+
export { parseManaboEntryForm, parseManaboEntryResponse } from "./parser/manaboEntry";
|
|
3
|
+
export { parseManaboReceivedMail, parseManaboSentMail, parseManaboMailView, parseManaboMailSend, parseManaboMailMember } from "./parser/manaboMail";
|
|
4
|
+
export { parseManaboNews } from "./parser/manaboNews";
|
|
5
|
+
export { parseManaboTimetable } from "./parser/manaboTimetable";
|
|
6
|
+
export { type ManaboClassDirectoryItemDTO, type ManaboClassDirectoryDTO, type ManaboClassContentIconDTO, type ManaboClassContentDurationDTO, type ManaboClassContentFileDTO, type ManaboClassContentActionDTO, type ManaboClassContentAttachedFileDTO, type ManaboClassContentContentDTO, type ManaboClassFileContentDTO, type ManaboClassReportContentDTO, type ManaboClassContentDTO, type ManaboClassEntryRowDTO, type ManaboClassEntryDTO, type ManaboClassNewsItemDTO, type ManaboClassNewsDTO, type ManaboClassSyllabusEvaluationDTO, type ManaboClassSyllabusTextbookDTO, type ManaboClassSyllabusReferenceDTO, type ManaboClassSyllabusPlanItemDTO, type ManaboClassSyllabusDTO, type ManaboClassQuizResultQuestionDTO, type ManaboClassQuizResultDTO, } from "./types/manaboClass";
|
|
7
|
+
export { type ManaboEntryFormDTO, type ManaboEntryResponseDTO } from "./types/manaboEntry";
|
|
8
|
+
export { type ManaboMailListPageDTO, type ManaboReceivedMailItemDTO, type ManaboReceivedMailDTO, type ManaboSentMailItemDTO, type ManaboSentMailDTO, type ManaboMailViewDTO, type ManaboMailSendDTO, type ManaboMailMemberDTO, } from "./types/manaboMail";
|
|
9
|
+
export { type ManaboNewsItemDTO, type ManaboNewsDTO } from "./types/manaboNews";
|
|
10
|
+
export { type ManaboTimetableSlotDTO, type ManaboTimetablePeriodDTO, type ManaboTimetableTermDTO, type ManaboTimetableViewModeDTO, type ManaboTimetableDTO, } from "./types/manaboTimetable";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/manabo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,EACxB,0BAA0B,GAC7B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEpJ,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EACH,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,iCAAiC,EACtC,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,gCAAgC,EACrC,KAAK,8BAA8B,EACnC,KAAK,+BAA+B,EACpC,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,KAAK,gCAAgC,EACrC,KAAK,wBAAwB,GAChC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE3F,OAAO,EACH,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EACH,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GAC1B,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { parseManaboClassDirectory, parseManaboClassContent, parseManaboClassEntry, parseManaboClassNews, parseManaboClassSyllabus, parseManaboClassQuizResult, } from "./parser/manaboClass";
|
|
2
|
+
export { parseManaboEntryForm, parseManaboEntryResponse } from "./parser/manaboEntry";
|
|
3
|
+
export { parseManaboReceivedMail, parseManaboSentMail, parseManaboMailView, parseManaboMailSend, parseManaboMailMember } from "./parser/manaboMail";
|
|
4
|
+
export { parseManaboNews } from "./parser/manaboNews";
|
|
5
|
+
export { parseManaboTimetable } from "./parser/manaboTimetable";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ManaboClassContentDTO, type ManaboClassDirectoryDTO, type ManaboClassEntryDTO, type ManaboClassNewsDTO, type ManaboClassQuizResultDTO, type ManaboClassSyllabusDTO } from "../types/manaboClass";
|
|
2
|
+
import type { ZodSafeParseResult } from "zod";
|
|
3
|
+
export declare const parseManaboClassDirectory: (html: string) => ZodSafeParseResult<ManaboClassDirectoryDTO>;
|
|
4
|
+
export declare const parseManaboClassContent: (html: string) => ZodSafeParseResult<ManaboClassContentDTO>;
|
|
5
|
+
export declare const parseManaboClassEntry: (html: string) => ZodSafeParseResult<ManaboClassEntryDTO>;
|
|
6
|
+
export declare const parseManaboClassNews: (html: string) => ZodSafeParseResult<ManaboClassNewsDTO>;
|
|
7
|
+
export declare const parseManaboClassSyllabus: (html: string) => ZodSafeParseResult<ManaboClassSyllabusDTO>;
|
|
8
|
+
export declare const parseManaboClassQuizResult: (html: string) => ZodSafeParseResult<ManaboClassQuizResultDTO>;
|
|
9
|
+
//# sourceMappingURL=manaboClass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manaboClass.d.ts","sourceRoot":"","sources":["../../../../src/manabo/parser/manaboClass.ts"],"names":[],"mappings":"AAcA,OAAO,EAOH,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC9B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,KAAK,CAAC;AAc9C,eAAO,MAAM,yBAAyB,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,uBAAuB,CAsBlG,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,qBAAqB,CAgJ9F,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,mBAAmB,CAwB1F,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,kBAAkB,CAexF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,sBAAsB,CAsKhG,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,MAAM,MAAM,KAAG,kBAAkB,CAAC,wBAAwB,CAmEpG,CAAC"}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { loadDocument, queryAll, queryOne, getAttribute, getTextContent, getInnerHtml, nextSiblingElement, matches, isElement, closest, } from "../../common/dom";
|
|
2
|
+
import { ManaboClassContentSchema, ManaboClassDirectorySchema, ManaboClassEntrySchema, ManaboClassNewsSchema, ManaboClassQuizResultSchema, ManaboClassSyllabusSchema, } from "../types/manaboClass";
|
|
3
|
+
const normalizeWhitespace = (value) => value.replace(/\s+/g, " ").trim();
|
|
4
|
+
const findNextMatchingSibling = (element, selector) => {
|
|
5
|
+
let next = nextSiblingElement(element);
|
|
6
|
+
while (next) {
|
|
7
|
+
if (matches(next, selector)) {
|
|
8
|
+
return next;
|
|
9
|
+
}
|
|
10
|
+
next = nextSiblingElement(next);
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
export const parseManaboClassDirectory = (html) => {
|
|
15
|
+
const document = loadDocument(html);
|
|
16
|
+
const classNode = queryOne(".class-top-directory .x-content-drop", document);
|
|
17
|
+
const classId = getAttribute(classNode, "class_id") ?? "";
|
|
18
|
+
const className = normalizeWhitespace(getTextContent(queryOne(".class-top-directory .span-class-name", document)));
|
|
19
|
+
const directories = queryAll(".div-panel-directory li[directory_id]", document)
|
|
20
|
+
.map((node) => {
|
|
21
|
+
const directoryId = getAttribute(node, "directory_id") ?? "";
|
|
22
|
+
const title = normalizeWhitespace(getTextContent(queryOne(".x-directory-name", node)));
|
|
23
|
+
return {
|
|
24
|
+
directoryId,
|
|
25
|
+
title,
|
|
26
|
+
};
|
|
27
|
+
})
|
|
28
|
+
.filter((directory) => directory.directoryId.length > 0 && directory.title.length > 0);
|
|
29
|
+
return ManaboClassDirectorySchema.safeParse({
|
|
30
|
+
classId,
|
|
31
|
+
className,
|
|
32
|
+
directories,
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
export const parseManaboClassContent = (html) => {
|
|
36
|
+
const document = loadDocument(html);
|
|
37
|
+
const extractDurations = (context) => {
|
|
38
|
+
const containers = queryAll(".div-small", context);
|
|
39
|
+
const durations = [];
|
|
40
|
+
containers.forEach((container) => {
|
|
41
|
+
const boldElements = queryAll("b", container);
|
|
42
|
+
boldElements.forEach((bold) => {
|
|
43
|
+
const label = normalizeWhitespace(getTextContent(bold));
|
|
44
|
+
const valueFragments = [];
|
|
45
|
+
let current = bold.next;
|
|
46
|
+
while (current) {
|
|
47
|
+
if (isElement(current) && current.name.toLowerCase() === "b") {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (isElement(current)) {
|
|
51
|
+
const tagName = current.name.toLowerCase();
|
|
52
|
+
if (tagName === "br") {
|
|
53
|
+
valueFragments.push(" ");
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
valueFragments.push(getTextContent(current));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
valueFragments.push(getTextContent(current));
|
|
61
|
+
}
|
|
62
|
+
current = current.next;
|
|
63
|
+
}
|
|
64
|
+
const value = normalizeWhitespace(valueFragments.join(" "));
|
|
65
|
+
if (label.length > 0 || value.length > 0) {
|
|
66
|
+
durations.push({
|
|
67
|
+
label,
|
|
68
|
+
value,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return durations;
|
|
74
|
+
};
|
|
75
|
+
const contents = [];
|
|
76
|
+
const rows = queryAll(".table-class-content tbody tr", document);
|
|
77
|
+
rows.forEach((row) => {
|
|
78
|
+
if (matches(row, ".toggle-area")) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const cells = queryAll("td", row);
|
|
82
|
+
if (!cells.length) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const detailCell = cells[1];
|
|
86
|
+
const pluginIconWrapper = queryOne(".plugin-icon-wrapper", row);
|
|
87
|
+
const pluginIconSrc = getAttribute(queryOne(".plugin-icon", row), "src") ?? "";
|
|
88
|
+
const isIconChecked = (pluginIconWrapper?.attribs?.class ?? "")
|
|
89
|
+
.split(/\s+/)
|
|
90
|
+
.filter((value) => value.length > 0)
|
|
91
|
+
.includes("img-checked");
|
|
92
|
+
const icon = {
|
|
93
|
+
pluginIconSrc,
|
|
94
|
+
isIconChecked,
|
|
95
|
+
};
|
|
96
|
+
const isFileRow = !!detailCell?.attribs?.colspan && detailCell.attribs.colspan === "2";
|
|
97
|
+
if (isFileRow) {
|
|
98
|
+
const commentNode = queryOne(".x-drag-title .cf", row);
|
|
99
|
+
const comment = commentNode ? getInnerHtml(commentNode).trim() : "";
|
|
100
|
+
const files = queryAll(".margin-top-md .display-block", row)
|
|
101
|
+
.map((block) => {
|
|
102
|
+
const link = queryOne("a", block);
|
|
103
|
+
const fileName = normalizeWhitespace(getTextContent(link));
|
|
104
|
+
const href = getAttribute(link, "href") ?? "";
|
|
105
|
+
const fileIcon = getAttribute(queryOne("img.icon", block), "src") ?? "";
|
|
106
|
+
return {
|
|
107
|
+
fileName,
|
|
108
|
+
href,
|
|
109
|
+
icon: fileIcon,
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.filter((file) => file.fileName.length > 0 || file.href.length > 0 || file.icon.length > 0);
|
|
113
|
+
contents.push({
|
|
114
|
+
type: "file",
|
|
115
|
+
icon,
|
|
116
|
+
attachedFile: {
|
|
117
|
+
comment,
|
|
118
|
+
files,
|
|
119
|
+
duration: extractDurations(row),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const titleAnchor = queryOne("a.a-content-open", row);
|
|
125
|
+
const titleElement = titleAnchor ?? queryOne("b", row);
|
|
126
|
+
const title = titleElement ? normalizeWhitespace(getTextContent(titleElement)) : "";
|
|
127
|
+
const contentId = titleAnchor ? getAttribute(titleAnchor, "content_id") ?? "" : "";
|
|
128
|
+
const pluginKey = titleAnchor ? getAttribute(titleAnchor, "plugin_key") ?? "" : "";
|
|
129
|
+
const duration = extractDurations(row);
|
|
130
|
+
const toggleRow = findNextMatchingSibling(row, ".toggle-area");
|
|
131
|
+
const toggleContainer = toggleRow ? queryOne(".x-content-hide", toggleRow) ?? toggleRow : null;
|
|
132
|
+
const descriptionNode = toggleContainer ? queryOne(".description", toggleContainer) : null;
|
|
133
|
+
const description = descriptionNode ? getInnerHtml(descriptionNode).trim() : "";
|
|
134
|
+
const errorNodes = toggleContainer ? queryAll(".confirm .error", toggleContainer) : [];
|
|
135
|
+
const errorMessages = errorNodes.map((errorNode) => normalizeWhitespace(getTextContent(errorNode))).filter((message) => message.length > 0);
|
|
136
|
+
const hasNotAvailableYetError = errorMessages.some((message) => message.includes("まだ受講できません"));
|
|
137
|
+
const isNotAvailableYet = hasNotAvailableYetError;
|
|
138
|
+
// Treat "まだ受講できません" as a future availability notice, not an expiration.
|
|
139
|
+
const isExpired = errorNodes.length > 0 ? !hasNotAvailableYetError : false;
|
|
140
|
+
const actions = toggleContainer
|
|
141
|
+
? queryAll(".confirm a.btn", toggleContainer).map((actionNode) => ({
|
|
142
|
+
title: normalizeWhitespace(getTextContent(actionNode)),
|
|
143
|
+
href: getAttribute(actionNode, "href") ?? "",
|
|
144
|
+
}))
|
|
145
|
+
: [];
|
|
146
|
+
contents.push({
|
|
147
|
+
type: "report",
|
|
148
|
+
icon,
|
|
149
|
+
content: {
|
|
150
|
+
title,
|
|
151
|
+
contentId,
|
|
152
|
+
pluginKey,
|
|
153
|
+
duration,
|
|
154
|
+
},
|
|
155
|
+
toggleArea: {
|
|
156
|
+
description,
|
|
157
|
+
isExpired,
|
|
158
|
+
isNotAvailableYet,
|
|
159
|
+
actions,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return ManaboClassContentSchema.safeParse({
|
|
164
|
+
contents,
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
export const parseManaboClassEntry = (html) => {
|
|
168
|
+
const document = loadDocument(html);
|
|
169
|
+
const rows = queryAll("table.table-default tbody tr", document)
|
|
170
|
+
.filter((row) => {
|
|
171
|
+
const cells = queryAll("td", row);
|
|
172
|
+
// colspan属性を持つセルがある場合(空の状態を示す行)をフィルタリング
|
|
173
|
+
const hasColspan = cells.some((cell) => getAttribute(cell, "colspan") !== null);
|
|
174
|
+
return !hasColspan;
|
|
175
|
+
})
|
|
176
|
+
.map((row) => {
|
|
177
|
+
const cells = queryAll("td", row);
|
|
178
|
+
const directory = normalizeWhitespace(getTextContent(cells[0] ?? null));
|
|
179
|
+
const lectureDateRaw = normalizeWhitespace(getTextContent(cells[1] ?? null));
|
|
180
|
+
const status = normalizeWhitespace(getTextContent(cells[2] ?? null));
|
|
181
|
+
return {
|
|
182
|
+
directory,
|
|
183
|
+
lectureDate: lectureDateRaw.length ? lectureDateRaw : null,
|
|
184
|
+
status,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
return ManaboClassEntrySchema.safeParse({
|
|
188
|
+
rows,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
export const parseManaboClassNews = (html) => {
|
|
192
|
+
const document = loadDocument(html);
|
|
193
|
+
const items = queryAll("dl.x-openclose", document).map((dl) => {
|
|
194
|
+
const title = normalizeWhitespace(getTextContent(queryOne("dt b", dl)));
|
|
195
|
+
const bodyHtml = getInnerHtml(queryOne("dd", dl)).trim() || "";
|
|
196
|
+
return {
|
|
197
|
+
id: getAttribute(dl, "id"),
|
|
198
|
+
title,
|
|
199
|
+
bodyHtml,
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
return ManaboClassNewsSchema.safeParse({
|
|
203
|
+
items,
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
export const parseManaboClassSyllabus = (html) => {
|
|
207
|
+
const document = loadDocument(html);
|
|
208
|
+
const mainTable = queryOne(".panel-body .table.table-default", document);
|
|
209
|
+
const rows = mainTable ? queryAll("tr", mainTable).filter((row) => closest(row, "table") === mainTable) : [];
|
|
210
|
+
let object = "";
|
|
211
|
+
let goal = [];
|
|
212
|
+
let method = "";
|
|
213
|
+
let usedMethods = [];
|
|
214
|
+
let evaluation = [];
|
|
215
|
+
const textbooks = [];
|
|
216
|
+
let references = [];
|
|
217
|
+
let officeHour = "";
|
|
218
|
+
let plan = [];
|
|
219
|
+
let comment = "";
|
|
220
|
+
let prePostStudy = "";
|
|
221
|
+
const isDataNode = (node) => !!node && typeof node.data === "string";
|
|
222
|
+
const collectSiblingText = (element) => {
|
|
223
|
+
if (!element) {
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
226
|
+
const fragments = [];
|
|
227
|
+
let current = element.next;
|
|
228
|
+
while (current) {
|
|
229
|
+
if (isElement(current)) {
|
|
230
|
+
if (current.name.toLowerCase() === "br") {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
fragments.push(getTextContent(current));
|
|
234
|
+
}
|
|
235
|
+
else if (isDataNode(current)) {
|
|
236
|
+
fragments.push(current.data);
|
|
237
|
+
}
|
|
238
|
+
current = current.next;
|
|
239
|
+
}
|
|
240
|
+
return normalizeWhitespace(fragments.join(" "));
|
|
241
|
+
};
|
|
242
|
+
rows.forEach((row) => {
|
|
243
|
+
const heading = normalizeWhitespace(getTextContent(queryOne("th", row)));
|
|
244
|
+
const cell = queryOne("td", row);
|
|
245
|
+
switch (heading) {
|
|
246
|
+
case "授業概要・目的": {
|
|
247
|
+
object = normalizeWhitespace(getTextContent(cell));
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "学修到達目標": {
|
|
251
|
+
goal = queryAll("li", cell)
|
|
252
|
+
.map((item) => normalizeWhitespace(getTextContent(item)))
|
|
253
|
+
.filter((item) => item.length > 0);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case "授業方法": {
|
|
257
|
+
method = normalizeWhitespace(getTextContent(cell));
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "活用される授業方法": {
|
|
261
|
+
usedMethods = queryAll("input.usemethod[checked]", cell)
|
|
262
|
+
.map((input) => collectSiblingText(input))
|
|
263
|
+
.filter((value) => value.length > 0);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "成績評価方法・基準": {
|
|
267
|
+
evaluation = queryAll("table tr", cell)
|
|
268
|
+
.map((evalRow) => {
|
|
269
|
+
const columns = queryAll("td", evalRow);
|
|
270
|
+
const type = normalizeWhitespace(getTextContent(columns[0] ?? null));
|
|
271
|
+
const weight = normalizeWhitespace(getTextContent(columns[1] ?? null));
|
|
272
|
+
if (!type || !weight) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return { type, weight };
|
|
276
|
+
})
|
|
277
|
+
.filter((item) => item !== null);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "教科書・教材・参考文献": {
|
|
281
|
+
queryAll("table tr", cell).forEach((bookRow) => {
|
|
282
|
+
const cells = queryAll("td", bookRow);
|
|
283
|
+
const label = normalizeWhitespace(getTextContent(cells[0] ?? null));
|
|
284
|
+
const valueCell = cells[1] ?? null;
|
|
285
|
+
if (!valueCell) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (label.includes("教科書・教材")) {
|
|
289
|
+
const title = normalizeWhitespace(getTextContent(valueCell));
|
|
290
|
+
if (title) {
|
|
291
|
+
textbooks.push({
|
|
292
|
+
type: label,
|
|
293
|
+
title,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (label.includes("参考文献")) {
|
|
298
|
+
references = queryAll("li", valueCell)
|
|
299
|
+
.map((item) => {
|
|
300
|
+
const title = normalizeWhitespace(getTextContent(item));
|
|
301
|
+
const code = getAttribute(item, "data-code") ?? "";
|
|
302
|
+
if (!title || !code) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
title,
|
|
307
|
+
code,
|
|
308
|
+
};
|
|
309
|
+
})
|
|
310
|
+
.filter((item) => item !== null);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "質問への対応(オフィスアワー等)": {
|
|
316
|
+
officeHour = normalizeWhitespace(getTextContent(cell));
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case "授業計画": {
|
|
320
|
+
const planTable = queryOne("table", cell);
|
|
321
|
+
plan = queryAll("tbody tr", planTable)
|
|
322
|
+
.map((planRow) => {
|
|
323
|
+
const cols = queryAll("td", planRow);
|
|
324
|
+
const noText = normalizeWhitespace(getTextContent(cols[0] ?? null));
|
|
325
|
+
const item = normalizeWhitespace(getTextContent(cols[1] ?? null));
|
|
326
|
+
const content = normalizeWhitespace(getTextContent(cols[2] ?? null));
|
|
327
|
+
const no = Number.parseInt(noText, 10);
|
|
328
|
+
if (!Number.isFinite(no) || !item || !content) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return { no, item, content };
|
|
332
|
+
})
|
|
333
|
+
.filter((entry) => entry !== null);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case "履修者へのコメント": {
|
|
337
|
+
comment = normalizeWhitespace(getTextContent(cell));
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case "事前事後学習": {
|
|
341
|
+
prePostStudy = normalizeWhitespace(getTextContent(cell)).replace(/\s+\(/g, "(");
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
default:
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
return ManaboClassSyllabusSchema.safeParse({
|
|
349
|
+
object,
|
|
350
|
+
goal,
|
|
351
|
+
method,
|
|
352
|
+
usedMethods,
|
|
353
|
+
evaluation,
|
|
354
|
+
textbooks,
|
|
355
|
+
references,
|
|
356
|
+
officeHour,
|
|
357
|
+
plan,
|
|
358
|
+
comment,
|
|
359
|
+
prePostStudy,
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
export const parseManaboClassQuizResult = (html) => {
|
|
363
|
+
const document = loadDocument(html);
|
|
364
|
+
const scoreText = normalizeWhitespace(getTextContent(queryOne(".text-center .Red b", document)));
|
|
365
|
+
const totalTextRaw = normalizeWhitespace(getTextContent(queryOne(".text-center", document)));
|
|
366
|
+
const totalText = totalTextRaw.split("/")[1] ?? "0";
|
|
367
|
+
const obtained = Number(scoreText.replace(/[^\d]/g, "")) || 0;
|
|
368
|
+
const total = Number(totalText.replace(/[^\d]/g, "")) || 0;
|
|
369
|
+
const totalItemsText = normalizeWhitespace(getTextContent(queryOne(".row.margin-top-md .col-sm-2", document)));
|
|
370
|
+
let currentPage = "";
|
|
371
|
+
const questions = queryAll("table.table-default-grade tbody tr", document).map((row) => {
|
|
372
|
+
const cells = queryAll("td", row);
|
|
373
|
+
let cellIndex = 0;
|
|
374
|
+
const firstCell = cells[cellIndex];
|
|
375
|
+
if (getAttribute(firstCell, "rowspan")) {
|
|
376
|
+
currentPage = normalizeWhitespace(getTextContent(firstCell));
|
|
377
|
+
cellIndex += 1;
|
|
378
|
+
}
|
|
379
|
+
else if (cells.length === 7) {
|
|
380
|
+
currentPage = normalizeWhitespace(getTextContent(firstCell));
|
|
381
|
+
cellIndex += 1;
|
|
382
|
+
}
|
|
383
|
+
const questionNumber = normalizeWhitespace(getTextContent(cells[cellIndex] ?? null));
|
|
384
|
+
cellIndex += 1;
|
|
385
|
+
const questionCell = cells[cellIndex] ?? null;
|
|
386
|
+
const instruction = queryOne(".div-instructions", questionCell ?? null);
|
|
387
|
+
const questionText = normalizeWhitespace(instruction ? getTextContent(instruction) : getTextContent(questionCell));
|
|
388
|
+
cellIndex += 1;
|
|
389
|
+
const correctAnswer = normalizeWhitespace(getTextContent(cells[cellIndex] ?? null));
|
|
390
|
+
cellIndex += 1;
|
|
391
|
+
const studentAnswer = normalizeWhitespace(getTextContent(cells[cellIndex] ?? null));
|
|
392
|
+
cellIndex += 1;
|
|
393
|
+
const resultCell = cells[cellIndex] ?? null;
|
|
394
|
+
const resultIcon = queryOne("img", resultCell ?? null);
|
|
395
|
+
const resultText = normalizeWhitespace(getTextContent(resultCell));
|
|
396
|
+
cellIndex += 1;
|
|
397
|
+
const teacherComment = normalizeWhitespace(getTextContent(cells[cellIndex] ?? null));
|
|
398
|
+
const resultAltRaw = normalizeWhitespace(getAttribute(resultIcon, "alt") ?? "");
|
|
399
|
+
return {
|
|
400
|
+
page: currentPage,
|
|
401
|
+
questionNumber,
|
|
402
|
+
questionText,
|
|
403
|
+
correctAnswer,
|
|
404
|
+
studentAnswer,
|
|
405
|
+
resultIconAlt: resultAltRaw.length ? resultAltRaw : null,
|
|
406
|
+
resultIconSrc: getAttribute(resultIcon, "src"),
|
|
407
|
+
resultText: resultText.length ? resultText : null,
|
|
408
|
+
teacherComment: teacherComment.length ? teacherComment : null,
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
return ManaboClassQuizResultSchema.safeParse({
|
|
412
|
+
score: {
|
|
413
|
+
obtained,
|
|
414
|
+
total,
|
|
415
|
+
},
|
|
416
|
+
totalItemsText: totalItemsText.length ? totalItemsText : null,
|
|
417
|
+
questions,
|
|
418
|
+
});
|
|
419
|
+
};
|