From e0818aa61c59fc44035c171d21733b2059b382ff Mon Sep 17 00:00:00 2001
From: exyi <exyi@outlook.cz>
Date: Sun, 27 Sep 2020 10:18:37 +0000
Subject: [PATCH] Add KSP task grabber [untested]

---
 frontend/src/ksp-task-grabber.ts | 82 ++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 frontend/src/ksp-task-grabber.ts

diff --git a/frontend/src/ksp-task-grabber.ts b/frontend/src/ksp-task-grabber.ts
new file mode 100644
index 0000000..2a63cce
--- /dev/null
+++ b/frontend/src/ksp-task-grabber.ts
@@ -0,0 +1,82 @@
+export type TaskAssignmentData = {
+    id: string,
+    name: string,
+    points: number,
+    description: HTMLElement
+}
+
+type TaskLocation = {
+    /** Relative location of HTML file containing this task */
+    url: string
+    /** id of the element where the specific task begins */
+    startElement: string
+}
+
+function getLocation(id: string, solution: boolean): TaskLocation {
+    const m = /^(\d+)-(Z?)(\d)-(\d)$/.exec(id)
+    if (!m) throw new Error(`Invalid task id: ${m}`)
+    const [_, rocnik, z, serie, uloha] = m[1]
+    if (z == 'Z') {
+        const urlX = solution ? "reseni" : "zadani"
+        return {
+            url: `z/ulohy/${rocnik}/${urlX}${serie}.html`,
+            startElement: `task${uloha}`
+        }
+    } else {
+        const urlX = solution ? "solution" : "tasks"
+        return {
+            url: `tasks/${rocnik}/${urlX}${serie}.html`,
+            startElement: `task${uloha}`
+        }
+    }
+}
+
+function parseTask(startElementId: string, html: string, contentType: DOMParserSupportedType): string {
+    const parser = new DOMParser()
+    const doc = parser.parseFromString(html, contentType)
+
+    const titleElement = doc.getElementById(startElementId)
+    if (!titleElement)
+        throw new Error(`Document does not contain ${startElementId}`)
+    const elements = []
+
+    let e = titleElement
+
+    while (e.nextElementSibling &&
+           e.nextElementSibling?.tagName.toLowerCase() == "hr")
+        e = e.nextElementSibling as HTMLElement
+
+    while (!e.classList.contains("story") &&
+        //    !e.classList.contains("clearfloat") &&
+           e.tagName.toLowerCase() != "h3" &&
+           e.innerText.trim() != "Řešení"
+        )
+    {
+        elements.push(e)
+        if (!e.nextElementSibling) break;
+        e = e.nextElementSibling as HTMLElement
+    }
+
+    let r = ""
+    for (const e of elements)
+        r += e.outerHTML + "\n"
+    return r
+}
+
+async function loadTask({ url, startElement }: TaskLocation) {
+    const r = await fetch(url, { headers: { "Accept": "text/html,application/xhtml+xml" } })
+    if (r.status >= 400) {
+        throw Error("Bad request")
+    }
+    const rText = await r.text()
+    const contentType = r.headers.get("Content-Type") as DOMParserSupportedType || "text/html"
+    return parseTask(startElement, rText, contentType)
+}
+
+export function loadAssignment(id: string) {
+    return loadTask(getLocation(id, false))
+}
+
+export function loadSolution(id: string) {
+    return loadTask(getLocation(id, true))
+}