Last active
July 20, 2016 19:26
-
-
Save kotarou3/3bce33e8b9bcaaf97a39 to your computer and use it in GitHub Desktop.
Format UNSW timetable for conversion to PDF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let printoutHeight = 18; // In centimetres | |
function xpath(node, path) { | |
let elems = document.evaluate(path, node, null, XPathResult.ANY_TYPE, null); | |
let elem; | |
let result = []; | |
while (elem = elems.iterateNext()) | |
result.push(elem); | |
return result; | |
} | |
function removeElem(e) { | |
e.parentNode.removeChild(e); | |
} | |
let body = xpath(document, '//*[text() = "Instructor"]/ancestor::*[@class = "formBody"]')[0]; | |
// Remove all elements outside of the body (including the pesky "-?-" at the end) | |
xpath(body, "preceding::*[count(. | //body/descendant::*) = count(//body/descendant::*)]").forEach(removeElem); | |
xpath(body, "following::*[count(. | //body/descendant::*) = count(//body/descendant::*)]").forEach(removeElem); | |
xpath(body, "following::text()").forEach(removeElem); | |
// Extract the tables we want and remove the rest | |
let subjectsMatcher = Array.from(new Set(body.textContent.match(/[A-Z]{4}[0-9]{4}/g))).map((code) => `contains(text(), "${code}")`).join(' or '); | |
let tables = xpath(body, `*[.//*[${subjectsMatcher} or contains(text(), "Activity")]]`); | |
Array.from(body.children).forEach(removeElem); | |
tables.forEach((elem) => body.appendChild(elem)); | |
// Find and remove empty columns from main timetable | |
let timetable = xpath(body, './/td[text() = "Monday"]/parent::*/parent::*')[0]; | |
let headers = Array.from(timetable.children[0].children); | |
let columns = Array.from(timetable.children[1].children); | |
for (let c = 1; c < columns.length; ++c) { | |
if (!columns[c].textContent.match(/[A-Z]{4}[0-9]{4}/)) { | |
removeElem(headers[c]); | |
removeElem(columns[c]); | |
} | |
} | |
// Remove the unneeded "description" rows from the subject tables | |
xpath(body, `.//tr[td[${subjectsMatcher}]]/following-sibling::*`).forEach(removeElem); | |
// Remove class listing spacers | |
xpath(body, '//td[@class = "rowSpacer"]/parent::tr').forEach(removeElem); | |
// Merge identical instructor cells adjacent to each other | |
for (let list of xpath(body, '//td[text() = "Instructor"]/parent::*/parent::*')) { | |
let instructors = xpath(list, 'tr[position() > 1]/td[last()]'); | |
let prev = null; | |
let count = 0; | |
for (let instructor of instructors.concat(null)) { | |
if (instructor && prev && instructor.textContent && instructor.textContent === prev.textContent) { | |
++count; | |
removeElem(instructor); | |
} else { | |
if (count > 1 && prev.textContent.trim()) | |
prev.setAttribute("rowspan", count); | |
prev = instructor; | |
count = 1; | |
} | |
} | |
} | |
// Fix styles | |
let styles = new Map(); | |
for (let sheet of document.styleSheets) { | |
// Lovely same origin rule | |
try { | |
if (!sheet.cssRules) | |
continue; | |
} catch (e) { | |
continue; | |
} | |
for (let rule of sheet.cssRules) { | |
let style = styles.get(rule.selectorText); | |
if (!style) | |
styles.set(rule.selectorText, style = []); | |
style.push(rule.style); | |
} | |
} | |
// Remove excess padding | |
styles.get(".bsdsModule").forEach((style) => style.paddingBottom = ""); | |
styles.get(".container-fluid").forEach((style) => {style.paddingLeft = ""; style.paddingRight = "";}); | |
styles.get(".formBody").forEach((style) => style.paddingTop = ""); | |
styles.get("td, th").forEach((style) => style.lineHeight = ""); | |
styles.get(".rowHighlight td").forEach((style) => {style.paddingTop = ""; style.paddingBottom = "";}); | |
styles.get(".rowLowlight td").forEach((style) => {style.paddingTop = ""; style.paddingBottom = "";}); | |
styles.get(".tableHeading").forEach((style) => style.padding = ""); | |
// Fix table widths | |
xpath(document, "//table").forEach((table) => table.style.width = "100%"); | |
// Make timetable columns' width and height uniform | |
let rowCount = columns[0].getElementsByTagName("tr").length; | |
xpath(timetable, ".//table").forEach((elem) => elem.style.height = "100%"); | |
xpath(timetable, "tr//tr/td").forEach((elem) => elem.style.height = (parseInt(elem.getAttribute("height"), 10) / 72 / rowCount * printoutHeight) + "cm"); | |
headers[0].style.width = (100 / (2 * columns.length - 1)) + "%"; | |
headers.slice(1).forEach((elem) => elem.style.width = (100 / (columns.length - 0.5)) + "%"); | |
// Make class listings' columns' width uniform | |
xpath(body, '//td[text() = "Activity"]').forEach((elem) => elem.style.width = "20%"); | |
xpath(body, '//td[text() = "Section"]').forEach((elem) => elem.style.width = "10%"); | |
xpath(body, '//td[text() = "Weeks"]').forEach((elem) => elem.style.width = "10%"); | |
xpath(body, '//td[text() = "Location"]').forEach((elem) => elem.style.width = "25%"); | |
xpath(body, '//td[text() = "Instructor"]').forEach((elem) => elem.style.width = "15%"); | |
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=332740 | |
xpath(body, '//td[text() = "Activity"]/preceding::table[1]').forEach((elem) => elem.style.borderCollapse = "separate"); | |
xpath(body, '//td[text() = "Activity"]/ancestor::table[1]').forEach((elem) => elem.style.borderCollapse = "separate"); | |
styles.get(".rowHighlight").forEach((style) => style.borderTop = ""); | |
styles.get(".rowLowlight").forEach((style) => style.borderTop = ""); | |
xpath(body, '//td[text() = "Activity"]/parent::*/parent::*/child::*/td').forEach((elem) => elem.style.borderBottom = "1px solid #ddd"); | |
// Add a bit of spacing | |
xpath(timetable, "ancestor-or-self::table[1]")[0].style.pageBreakAfter = "always"; | |
tables.forEach((table) => table.style.pageBreakInside = "avoid"); | |
xpath(body, `.//tr[td[${subjectsMatcher}]]/ancestor::table[1]`).slice(1).forEach((elem) => elem.style.marginTop = "1em"); | |
// Remove width and height properties | |
xpath(document, "//*[@width]").forEach((elem) => elem.removeAttribute("width")); | |
xpath(document, "//*[@height]").forEach((elem) => elem.removeAttribute("height")); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment