2023-02-09 22:00:15 +00:00
|
|
|
#include<regex>
|
2023-02-07 23:10:56 +00:00
|
|
|
#include<fstream>
|
|
|
|
#include<iostream>
|
|
|
|
#include<filesystem>
|
2023-02-09 22:00:15 +00:00
|
|
|
#include<unordered_set>
|
|
|
|
#include<unordered_map>
|
2023-02-07 23:10:56 +00:00
|
|
|
#include<json/json.h>
|
2023-02-10 01:56:18 +00:00
|
|
|
namespace fs = std::filesystem;
|
2023-02-07 23:10:56 +00:00
|
|
|
|
|
|
|
struct quatalog_data_t {
|
|
|
|
Json::Value terms_offered;
|
|
|
|
Json::Value prerequisites;
|
|
|
|
Json::Value list_of_terms;
|
|
|
|
Json::Value catalog;
|
|
|
|
};
|
2023-02-09 22:51:42 +00:00
|
|
|
enum struct TAG { BEGIN, END, INLINE, COMPLEX_BEGIN };
|
2023-02-09 22:00:15 +00:00
|
|
|
enum struct TERM { SPRING, SUMMER, SUMMER2, SUMMER3, FALL, WINTER };
|
|
|
|
enum struct OFFERED { YES, NO, DIFF_CODE, UNSCHEDULED };
|
|
|
|
const std::unordered_map<enum OFFERED,std::string> offered_to_string {
|
|
|
|
{ OFFERED::YES,"offered" },
|
|
|
|
{ OFFERED::NO,"not-offered" },
|
|
|
|
{ OFFERED::DIFF_CODE,"offered-diff-code" },
|
|
|
|
{ OFFERED::UNSCHEDULED,"unscheduled" }
|
|
|
|
};
|
|
|
|
const std::unordered_map<enum TERM,std::string> term_to_string {
|
|
|
|
{ TERM::SPRING,"spring" },
|
|
|
|
{ TERM::SUMMER,"summer" },
|
|
|
|
{ TERM::SUMMER2,"summer2" },
|
|
|
|
{ TERM::SUMMER3,"summer3" },
|
|
|
|
{ TERM::FALL,"fall" },
|
|
|
|
{ TERM::WINTER,"winter" }
|
|
|
|
};
|
|
|
|
const std::unordered_map<enum TERM,std::string> term_to_number {
|
|
|
|
{ TERM::SPRING,"01" },
|
|
|
|
{ TERM::SUMMER,"05" },
|
|
|
|
{ TERM::SUMMER2,"0502" },
|
|
|
|
{ TERM::SUMMER3,"0503" },
|
|
|
|
{ TERM::FALL,"09" },
|
|
|
|
{ TERM::WINTER,"12" }
|
|
|
|
};
|
2023-02-10 01:56:18 +00:00
|
|
|
std::unordered_set<std::string> get_all_courses(const quatalog_data_t&);
|
|
|
|
std::string fix_course_ids(std::string);
|
2023-02-07 23:10:56 +00:00
|
|
|
bool create_dir_if_not_exist(const fs::path&);
|
2023-02-10 22:25:07 +00:00
|
|
|
Json::Value get_data(const Json::Value&,std::string);
|
2023-02-10 22:36:19 +00:00
|
|
|
void generate_course_page(const std::string&,const quatalog_data_t&,Json::Value&,std::ostream&);
|
2023-02-09 05:19:35 +00:00
|
|
|
void get_prerequisites(const quatalog_data_t&,std::string);
|
2023-02-09 22:00:15 +00:00
|
|
|
void generate_opt_container(std::ostream&);
|
2023-02-09 22:51:42 +00:00
|
|
|
std::string generate_credit_string(const Json::Value& credits);
|
2023-02-07 23:10:56 +00:00
|
|
|
std::string get_course_title(const std::string&,const quatalog_data_t&);
|
2023-02-09 22:00:15 +00:00
|
|
|
void generate_years_table(const Json::Value&,const Json::Value&,const quatalog_data_t&,std::ostream&);
|
|
|
|
void generate_year_row(const int,const Json::Value&,const Json::Value&,const quatalog_data_t&,std::ostream&);
|
|
|
|
bool is_term_scheduled(const std::string&,const quatalog_data_t&);
|
|
|
|
enum OFFERED is_course_offered(const int,const enum TERM,const Json::Value&,const Json::Value&,const quatalog_data_t&);
|
2023-02-09 22:51:42 +00:00
|
|
|
void generate_table_cell(const int,const enum TERM,const Json::Value&,const enum OFFERED,std::ostream&);
|
2023-02-09 05:37:18 +00:00
|
|
|
void generate_attributes(const Json::Value&,std::ostream&);
|
2023-02-09 04:22:43 +00:00
|
|
|
void generate_list(const Json::Value&,const std::string&,const std::string&,const quatalog_data_t&,std::ostream&);
|
|
|
|
void generate_prereq_display(const Json::Value&,const quatalog_data_t&,std::ostream&);
|
2023-02-09 05:19:35 +00:00
|
|
|
void generate_course_pill(std::string,const quatalog_data_t&,std::ostream&);
|
2023-02-09 04:22:43 +00:00
|
|
|
void generate_prereq(const Json::Value&,const quatalog_data_t&,std::ostream&);
|
|
|
|
void generate_or_prereq(const Json::Value&,const quatalog_data_t&,std::ostream&);
|
|
|
|
void generate_and_prereq(const Json::Value&,const quatalog_data_t&,std::ostream&);
|
|
|
|
std::ostream& indent(std::ostream&,const int);
|
|
|
|
std::ostream& tag(std::ostream&,enum TAG,const std::string& = "");
|
2023-02-07 23:10:56 +00:00
|
|
|
|
|
|
|
int main(const int argc,
|
|
|
|
const char** argv) {
|
2023-02-11 01:42:57 +00:00
|
|
|
if(argc != 8) {
|
2023-02-07 23:10:56 +00:00
|
|
|
std::cerr << "Bad number of arguments (" << argc << ")" << std::endl;
|
|
|
|
std::cerr << "Usage: " << argv[0]
|
|
|
|
<< " <terms_offered_file>"
|
|
|
|
<< " <prerequisites_file>"
|
|
|
|
<< " <list_of_terms_file>"
|
|
|
|
<< " <catalog_file>"
|
|
|
|
<< " <out_directory>"
|
2023-02-11 01:42:57 +00:00
|
|
|
<< " <courses_list_file>"
|
2023-02-10 22:25:07 +00:00
|
|
|
<< " <searchable_catalog_file>"
|
2023-02-11 01:42:57 +00:00
|
|
|
<< " <courses_list_file>"
|
2023-02-07 23:10:56 +00:00
|
|
|
<< std::endl;
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
const auto& terms_offered_filename = std::string(argv[1]);
|
|
|
|
const auto& prerequisites_filename = std::string(argv[2]);
|
|
|
|
const auto& list_of_terms_filename = std::string(argv[3]);
|
|
|
|
const auto& catalog_filename = std::string(argv[4]);
|
|
|
|
const auto& out_dir_path = fs::path(argv[5]);
|
2023-02-10 22:25:07 +00:00
|
|
|
const auto& searchable_catalog_filename = fs::path(argv[6]);
|
2023-02-11 01:42:57 +00:00
|
|
|
const auto& courses_list_filename = fs::path(argv[7]);
|
2023-02-07 23:10:56 +00:00
|
|
|
|
|
|
|
if(!create_dir_if_not_exist(out_dir_path)) {
|
|
|
|
std::cerr << "What" << std::endl;
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
2023-02-11 01:42:57 +00:00
|
|
|
quatalog_data_t quatalog_data;
|
|
|
|
|
2023-02-07 23:10:56 +00:00
|
|
|
std::fstream terms_offered_file{terms_offered_filename,std::ios::in};
|
|
|
|
std::fstream prerequisites_file{prerequisites_filename,std::ios::in};
|
|
|
|
std::fstream list_of_terms_file{list_of_terms_filename,std::ios::in};
|
|
|
|
std::fstream catalog_file{catalog_filename,std::ios::in};
|
|
|
|
|
|
|
|
terms_offered_file >> quatalog_data.terms_offered;
|
|
|
|
prerequisites_file >> quatalog_data.prerequisites;
|
|
|
|
list_of_terms_file >> quatalog_data.list_of_terms;
|
|
|
|
catalog_file >> quatalog_data.catalog;
|
2023-02-10 01:56:18 +00:00
|
|
|
|
2023-02-11 01:42:57 +00:00
|
|
|
terms_offered_file.close();
|
|
|
|
prerequisites_file.close();
|
|
|
|
list_of_terms_file.close();
|
|
|
|
catalog_file.close();
|
|
|
|
|
2023-02-10 01:56:18 +00:00
|
|
|
auto courses = get_all_courses(quatalog_data);
|
2023-02-10 22:36:19 +00:00
|
|
|
Json::Value searchable_catalog;
|
2023-02-11 01:42:57 +00:00
|
|
|
Json::Value courses_list;
|
2023-02-10 01:56:18 +00:00
|
|
|
for(const auto& course : courses) {
|
2023-02-11 01:42:57 +00:00
|
|
|
courses_list.append(course);
|
2023-02-10 01:56:18 +00:00
|
|
|
const auto& html_path = out_dir_path / (course + ".html");
|
|
|
|
auto file = std::ofstream(html_path);
|
2023-02-10 22:36:19 +00:00
|
|
|
generate_course_page(course,quatalog_data,searchable_catalog,file);
|
2023-02-10 22:25:07 +00:00
|
|
|
file.close();
|
2023-02-10 01:56:18 +00:00
|
|
|
}
|
2023-02-10 22:25:07 +00:00
|
|
|
|
|
|
|
Json::StreamWriterBuilder swb;
|
|
|
|
swb["indentation"] = " ";
|
|
|
|
std::unique_ptr<Json::StreamWriter> outWriter(swb.newStreamWriter());
|
|
|
|
|
|
|
|
std::fstream searchable_catalog_file{searchable_catalog_filename,std::ios::out};
|
2023-02-11 01:42:57 +00:00
|
|
|
std::fstream courses_list_file{courses_list_filename,std::ios::out};
|
|
|
|
|
2023-02-10 22:36:19 +00:00
|
|
|
outWriter->write(searchable_catalog,&searchable_catalog_file);
|
2023-02-11 01:42:57 +00:00
|
|
|
outWriter->write(courses_list,&courses_list_file);
|
2023-02-10 22:25:07 +00:00
|
|
|
|
|
|
|
searchable_catalog_file.close();
|
2023-02-11 01:48:05 +00:00
|
|
|
courses_list_file.close();
|
2023-02-10 01:56:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::unordered_set<std::string> get_all_courses(const quatalog_data_t& qlog) {
|
|
|
|
std::unordered_set<std::string> output;
|
|
|
|
for(const std::string& course : qlog.catalog.getMemberNames()) {
|
|
|
|
if(course.length() != 9) continue;
|
|
|
|
output.insert(fix_course_ids(course));
|
|
|
|
}
|
|
|
|
for(const std::string& course : qlog.terms_offered.getMemberNames()) {
|
|
|
|
output.insert(fix_course_ids(course));
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string fix_course_ids(std::string course) {
|
|
|
|
course[4] = '-';
|
|
|
|
if(course.substr(0,3) == "STS") {
|
|
|
|
course[3] = 'O';
|
2023-02-09 04:22:43 +00:00
|
|
|
}
|
2023-02-10 01:56:18 +00:00
|
|
|
return course;
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool create_dir_if_not_exist(const fs::path& path) {
|
|
|
|
if(fs::exists(path)) {
|
|
|
|
if(!fs::is_directory(path)) {
|
|
|
|
std::cerr << "out_directory argument "
|
|
|
|
<< path
|
|
|
|
<< "is not a directory"
|
|
|
|
<< std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fs::create_directory(path);
|
|
|
|
}
|
|
|
|
|
2023-02-10 22:06:30 +00:00
|
|
|
Json::Value get_data(const Json::Value& data,
|
|
|
|
std::string course_id) {
|
2023-02-09 22:00:15 +00:00
|
|
|
course_id[4] = '-';
|
2023-02-09 05:19:35 +00:00
|
|
|
if(course_id.substr(0,3) != "STS") {
|
|
|
|
return data[course_id];
|
|
|
|
}
|
|
|
|
const auto& stso = data[course_id];
|
|
|
|
course_id[3] = 'S';
|
2023-02-09 22:51:42 +00:00
|
|
|
const auto& stss = data[course_id];
|
2023-02-09 05:19:35 +00:00
|
|
|
course_id[3] = 'H';
|
|
|
|
const auto& stsh = data[course_id];
|
2023-02-10 22:06:30 +00:00
|
|
|
|
|
|
|
Json::Value out;
|
|
|
|
|
|
|
|
for(const auto& key : stso.getMemberNames()) {
|
|
|
|
out[key] = stso[key];
|
|
|
|
}
|
|
|
|
for(const auto& key : stss.getMemberNames()) {
|
|
|
|
out[key] = stss[key];
|
|
|
|
}
|
|
|
|
for(const auto& key : stsh.getMemberNames()) {
|
|
|
|
out[key] = stsh[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
2023-02-09 05:19:35 +00:00
|
|
|
}
|
|
|
|
|
2023-02-07 23:10:56 +00:00
|
|
|
void generate_course_page(const std::string& course_id,
|
|
|
|
const quatalog_data_t& quatalog_data,
|
2023-02-10 22:36:19 +00:00
|
|
|
Json::Value& searchable_catalog,
|
2023-02-07 23:10:56 +00:00
|
|
|
std::ostream& os) {
|
2023-02-10 01:56:18 +00:00
|
|
|
std::cerr << "Generating course page for " << course_id << "..." << std::endl;
|
2023-02-10 05:21:48 +00:00
|
|
|
std::string course_name = get_course_title(course_id,quatalog_data);
|
|
|
|
std::string description;
|
2023-02-07 23:10:56 +00:00
|
|
|
const auto& catalog_entry = quatalog_data.catalog[course_id];
|
2023-02-09 05:19:35 +00:00
|
|
|
const auto& prereqs_entry = get_data(quatalog_data.prerequisites,course_id);
|
|
|
|
const auto& terms_offered = get_data(quatalog_data.terms_offered,course_id);
|
2023-02-07 23:10:56 +00:00
|
|
|
const auto& latest_term = terms_offered["latest_term"].asString();
|
|
|
|
const auto& credits = terms_offered[latest_term]["credits"];
|
2023-02-09 22:51:42 +00:00
|
|
|
const auto& credit_string = generate_credit_string(credits);
|
2023-02-07 23:10:56 +00:00
|
|
|
|
|
|
|
if(catalog_entry) {
|
|
|
|
description = catalog_entry["description"].asString();
|
|
|
|
} else {
|
2023-02-10 01:56:18 +00:00
|
|
|
description = "This course is not in the most recent catalog. "
|
|
|
|
"It may have been discontinued, had its course "
|
|
|
|
"code changed, or just not be in the catalog for "
|
|
|
|
"some other reason.";
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
|
|
|
const auto& mid_digits = course_id.substr(6,2);
|
|
|
|
if(mid_digits == "96" || mid_digits == "97") {
|
|
|
|
course_name = "Topics in " + course_id.substr(0,4);
|
2023-02-10 22:36:19 +00:00
|
|
|
description = "Course codes between X960 and X979 are for topics "
|
|
|
|
"courses. They are often recycled and used for new "
|
|
|
|
"or experimental courses.";
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
|
|
|
|
2023-02-10 22:25:07 +00:00
|
|
|
Json::Value searchable_catalog_entry;
|
|
|
|
searchable_catalog_entry["code"] = course_id;
|
|
|
|
searchable_catalog_entry["name"] = course_name;
|
|
|
|
searchable_catalog_entry["description"] = description;
|
2023-02-10 22:36:19 +00:00
|
|
|
searchable_catalog.append(searchable_catalog_entry);
|
2023-02-10 22:06:30 +00:00
|
|
|
|
2023-02-09 22:00:15 +00:00
|
|
|
const std::regex escape_string(R"(")");
|
|
|
|
const std::string& description_meta = std::regex_replace(description,escape_string,""");
|
|
|
|
|
2023-02-10 05:03:12 +00:00
|
|
|
tag(os,TAG::INLINE) << "<!DOCTYPE html>" << '\n';
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::BEGIN,"html");
|
|
|
|
tag(os,TAG::BEGIN,"head");
|
|
|
|
tag(os,TAG::BEGIN,"title");
|
2023-02-10 22:25:07 +00:00
|
|
|
tag(os,TAG::INLINE) << course_id << ": " << course_name << '\n';
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::END,"title");
|
2023-02-10 22:25:07 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<meta property="og:title" content=")" << course_id << ": " << course_name << R"(">)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<meta property="og:description" content=")" << description_meta << R"(">)" << '\n';
|
2023-02-10 04:29:40 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="stylesheet" href="../css/common.css">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="stylesheet" href="../css/coursedisplay.css">)" << '\n';
|
2023-02-11 03:04:08 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="shortcut icon" href="./favicon/quatalogIcon.png">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="icon" href="./favicon/favicon.ico">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="apple-touch-icon" sizes="180x180" href="./favicon/apple-touch-icon.png">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="icon" type="image/png" sizes="32x32" href="./favicon/favicon-32x32.png">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="icon" type="image/png" sizes="16x16" href="./favicon/favicon-16x16.png">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<link rel="manifest" href="./favicon/site.webmanifest">)" << '\n';
|
2023-02-10 23:42:45 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<script src="../js/fuse.js"></script>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<script src="../js/search_helper.js"></script>)" << '\n';
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::END,"head");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(body class="search_plugin_added")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="qlog-header")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<a id="qlog-wordmark" href="../"><svg><use href="../images/quatalogHWordmark.svg#QuatalogHWordmark"></use></svg></a>)" << '\n';
|
2023-02-11 01:11:03 +00:00
|
|
|
tag(os,TAG::BEGIN,R"R(form onsubmit="search_helper(event)")R");
|
2023-02-10 20:53:47 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<input type="text" id="search" class="header-search" placeholder="Search...">)" << '\n';
|
|
|
|
tag(os,TAG::END,"form");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"div");
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(div id="cd-flex")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="course-info-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(h1 id="name")");
|
|
|
|
tag(os,TAG::INLINE) << course_name << '\n';
|
|
|
|
tag(os,TAG::END,"h1");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(h2 id="code")");
|
|
|
|
tag(os,TAG::INLINE) << course_id << '\n';
|
|
|
|
tag(os,TAG::END,"h2");
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::BEGIN,"p");
|
|
|
|
tag(os,TAG::INLINE) << description << '\n';
|
|
|
|
tag(os,TAG::END,"p");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="cattrs-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span id="credits-pill" class="attr-pill")");
|
2023-02-09 22:51:42 +00:00
|
|
|
tag(os,TAG::INLINE) << credit_string << " " << (credits["credMax"].asInt() == 1 ? "credit" : "credits") << '\n';
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::END,"span");
|
2023-02-09 05:37:18 +00:00
|
|
|
generate_attributes(prereqs_entry["attributes"],os);
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
generate_list(prereqs_entry["cross_listings"],"Cross-listed with:","crosslist",quatalog_data,os);
|
|
|
|
generate_list(prereqs_entry["corequisites"],"Corequisites:","coreq",quatalog_data,os);
|
|
|
|
generate_prereq_display(prereqs_entry["prerequisites"],quatalog_data,os);
|
|
|
|
tag(os,TAG::END,"div");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(div id="past-container")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(h1 id="past-title")");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::INLINE) << "Past Term Data" << '\n';
|
|
|
|
tag(os,TAG::END,"h2");
|
2023-02-10 04:29:40 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<input type="radio" id="simple-view-input" name="view-select" value="simple" checked="checked">)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<input type="radio" id="detail-view-input" name="view-select" value="detailed">)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
generate_opt_container(os);
|
|
|
|
generate_years_table(terms_offered,prereqs_entry["cross_listings"],quatalog_data,os);
|
|
|
|
tag(os,TAG::END,"div");
|
2023-02-09 04:22:43 +00:00
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"body");
|
|
|
|
tag(os,TAG::END,"html");
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
|
|
|
|
2023-02-09 22:51:42 +00:00
|
|
|
std::string generate_credit_string(const Json::Value& credits) {
|
|
|
|
const int credMin = credits["min"].asInt();
|
|
|
|
const int credMax = credits["max"].asInt();
|
|
|
|
return (credMin == credMax) ? std::to_string(credMin) : (std::to_string(credMin) + "-" + std::to_string(credMax));
|
|
|
|
}
|
|
|
|
|
2023-02-09 22:00:15 +00:00
|
|
|
void generate_years_table(const Json::Value& terms_offered,
|
|
|
|
const Json::Value& cross_listings,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
2023-02-10 20:53:47 +00:00
|
|
|
tag(os,TAG::BEGIN,R"(table id="years-table")");
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::BEGIN,"thead");
|
2023-02-09 22:51:42 +00:00
|
|
|
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::BEGIN,"tr");
|
|
|
|
tag(os,TAG::INLINE) << "<th></th>" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="spring season-label">Spring</th>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="summer season-label" colspan="2">Summer</th>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="fall season-label">Fall</th>)" << '\n';
|
|
|
|
//tag(os,TAG::INLINE) << R"(<th class="winter season-label">Enrichment</th>)" << '\n';
|
|
|
|
tag(os,TAG::END,"tr");
|
|
|
|
tag(os,TAG::BEGIN,"tr");
|
|
|
|
tag(os,TAG::INLINE) << R"(<th colspan="2"></th>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="summer2 midsum-label">(Session 1)</th>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="summer3 midsum-label">(Session 2)</th>)" << '\n';
|
|
|
|
//tag(os,TAG::INLINE) << R"(<th colspan="2"></th>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<th></th>)" << '\n';
|
|
|
|
tag(os,TAG::END,"tr");
|
|
|
|
|
|
|
|
tag(os,TAG::END,"thead");
|
|
|
|
tag(os,TAG::BEGIN,"tbody");
|
|
|
|
|
|
|
|
const int current_year = std::stoi(qlog.list_of_terms["current_term"].asString().substr(0,4));
|
|
|
|
const int oldest_year = std::stoi(qlog.list_of_terms["oldest_term"].asString().substr(0,4));
|
|
|
|
for(int year = current_year;year >= oldest_year;year--) {
|
|
|
|
generate_year_row(year,terms_offered,cross_listings,qlog,os);
|
|
|
|
}
|
|
|
|
|
|
|
|
tag(os,TAG::END,"tbody");
|
|
|
|
tag(os,TAG::END,"table");
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_year_row(const int year,
|
|
|
|
const Json::Value& terms_offered,
|
|
|
|
const Json::Value& cross_listings,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
|
|
|
tag(os,TAG::BEGIN,"tr");
|
|
|
|
tag(os,TAG::INLINE) << R"(<th class="year">)" << year << "</th>" << '\n';
|
|
|
|
|
2023-02-09 22:51:42 +00:00
|
|
|
generate_table_cell(year,TERM::SPRING,terms_offered,is_course_offered(year,TERM::SPRING,terms_offered,cross_listings,qlog),os);
|
|
|
|
|
2023-02-09 22:00:15 +00:00
|
|
|
const enum OFFERED summer1 = is_course_offered(year,TERM::SUMMER,terms_offered,cross_listings,qlog);
|
|
|
|
if(summer1 != OFFERED::NO) {
|
2023-02-09 22:51:42 +00:00
|
|
|
generate_table_cell(year,TERM::SUMMER,terms_offered,summer1,os);
|
2023-02-09 22:00:15 +00:00
|
|
|
} else {
|
|
|
|
const enum OFFERED summer2 = is_course_offered(year,TERM::SUMMER2,terms_offered,cross_listings,qlog);
|
|
|
|
const enum OFFERED summer3 = is_course_offered(year,TERM::SUMMER3,terms_offered,cross_listings,qlog);
|
|
|
|
if((summer2 == OFFERED::NO || summer2 == OFFERED::UNSCHEDULED)
|
|
|
|
&& (summer3 == OFFERED::NO || summer3 == OFFERED::UNSCHEDULED)) {
|
2023-02-09 22:51:42 +00:00
|
|
|
generate_table_cell(year,TERM::SUMMER,terms_offered,summer1,os);
|
2023-02-09 22:00:15 +00:00
|
|
|
} else {
|
2023-02-09 22:51:42 +00:00
|
|
|
generate_table_cell(year,TERM::SUMMER2,terms_offered,summer2,os);
|
|
|
|
generate_table_cell(year,TERM::SUMMER3,terms_offered,summer3,os);
|
2023-02-09 22:00:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 22:51:42 +00:00
|
|
|
generate_table_cell(year,TERM::FALL,terms_offered,is_course_offered(year,TERM::FALL,terms_offered,cross_listings,qlog),os);
|
|
|
|
//generate_table_cell(year,TERM::WINTER,terms_offered,is_course_offered(year,TERM::WINTER,terms_offered,cross_listings,qlog),os);
|
2023-02-09 22:00:15 +00:00
|
|
|
|
|
|
|
tag(os,TAG::END,"tr");
|
|
|
|
}
|
|
|
|
|
2023-02-09 22:51:42 +00:00
|
|
|
void generate_table_cell(const int year,
|
|
|
|
const enum TERM term,
|
|
|
|
const Json::Value& terms_offered,
|
2023-02-09 22:00:15 +00:00
|
|
|
const enum OFFERED is_offered,
|
|
|
|
std::ostream& os) {
|
2023-02-09 22:51:42 +00:00
|
|
|
std::string year_term = std::to_string(year) + term_to_number.at(term);
|
|
|
|
const auto& term_offered = terms_offered[year_term];
|
|
|
|
const auto& course_title = term_offered["title"].asString();
|
|
|
|
const auto& credit_string = generate_credit_string(term_offered["credits"]);
|
|
|
|
|
|
|
|
tag(os,TAG::COMPLEX_BEGIN) << R"(<td )";
|
2023-02-09 22:00:15 +00:00
|
|
|
if(term == TERM::SUMMER) {
|
|
|
|
os << R"(colspan="2" )";
|
|
|
|
}
|
|
|
|
os << R"(class="term )"
|
|
|
|
<< term_to_string.at(term) << ' '
|
2023-02-09 22:51:42 +00:00
|
|
|
<< offered_to_string.at(is_offered)
|
2023-02-09 22:00:15 +00:00
|
|
|
<< R"(">)" << '\n';
|
2023-02-09 22:51:42 +00:00
|
|
|
if(is_offered == OFFERED::YES) {
|
|
|
|
tag(os,TAG::BEGIN,R"(div class="view-container detail-view-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="term-course-info")");
|
|
|
|
|
|
|
|
tag(os,TAG::INLINE) << course_title << " (" << credit_string << "c)";
|
|
|
|
for(const auto& attr : term_offered["attributes"]) {
|
|
|
|
os << ' ' << attr.asString();
|
|
|
|
}
|
|
|
|
os << '\n';
|
|
|
|
|
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::BEGIN,R"(ul class="prof-list")");
|
|
|
|
for(const auto& instructor : term_offered["instructors"]) {
|
|
|
|
tag(os,TAG::INLINE) << "<li>" << instructor.asString() << "</li>" << '\n';
|
|
|
|
}
|
|
|
|
tag(os,TAG::END,"ul");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="course-capacity")");
|
|
|
|
const auto& seats = term_offered["seats"];
|
|
|
|
tag(os,TAG::INLINE) << "Seats Taken: " << seats["taken"] << '/' << seats["capacity"] << '\n';
|
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
}
|
|
|
|
tag(os,TAG::END,"td");
|
2023-02-09 22:00:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool is_term_scheduled(const std::string& term_str,
|
|
|
|
const quatalog_data_t& qlog) {
|
|
|
|
static std::unordered_set<std::string> terms;
|
|
|
|
if(terms.empty()) {
|
|
|
|
for(const auto& term : qlog.list_of_terms["all_terms"]) {
|
|
|
|
terms.insert(term.asString());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return terms.count(term_str);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum OFFERED is_course_offered(const int year,
|
|
|
|
const enum TERM term,
|
|
|
|
const Json::Value& terms_offered,
|
|
|
|
const Json::Value& cross_listings,
|
|
|
|
const quatalog_data_t& qlog) {
|
2023-02-09 22:51:42 +00:00
|
|
|
const std::string& term_str = std::to_string(year) + term_to_number.at(term);
|
2023-02-09 22:00:15 +00:00
|
|
|
if(!is_term_scheduled(term_str,qlog)) {
|
|
|
|
return OFFERED::UNSCHEDULED;
|
|
|
|
} else if(terms_offered.isMember(term_str)) {
|
|
|
|
return OFFERED::YES;
|
|
|
|
} else {
|
|
|
|
for(const auto& cl : cross_listings) {
|
2023-02-11 23:54:49 +00:00
|
|
|
const auto& data = get_data(qlog.terms_offered,cl.asString())[term_str];
|
|
|
|
if(data) {
|
2023-02-09 22:00:15 +00:00
|
|
|
return OFFERED::DIFF_CODE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return OFFERED::NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_opt_container(std::ostream& os) {
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="opt-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="key-panel")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="yes-code" class="key-code")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="code-icon" id="yes-code-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<svg><use href="../icons.svg#circle-check"></use></svg>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "Offered" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="no-code" class="key-code")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="code-icon" id="no-code-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<svg><use href="../icons.svg#circle-no"></use></svg>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "Not Offered" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="diff-code" class="key-code")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="code-icon" id="diff-code-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<svg><use href="../icons.svg#circle-question"></use></svg>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "Offered as Cross-Listing Only" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="nil-code" class="key-code")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="code-icon" id="nil-code-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<svg><use href="../icons.svg#circle-empty"></use></svg>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "No Term Data" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="control-panel")");
|
|
|
|
tag(os,TAG::BEGIN,R"(label for="simple-view-input" id="simple-view-label" class="view-option-label")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="view-icon" id="simple-view-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<span class="view-icon-selected"><svg><use href="../icons.svg#circle-dot"></use></svg></span>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<span class="view-icon-unselected"><svg><use href="../icons.svg#circle-empty"></use></svg></span>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "Simple View" << '\n';
|
|
|
|
tag(os,TAG::END,"label");
|
|
|
|
tag(os,TAG::BEGIN,R"(label for="detail-view-input" id="detail-view-label" class="view-option-label")");
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="view-icon" id="detail-view-icon")");
|
2023-02-10 04:58:17 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<span class="view-icon-selected"><svg><use href="../icons.svg#circle-dot"></use></svg></span>)" << '\n';
|
|
|
|
tag(os,TAG::INLINE) << R"(<span class="view-icon-unselected"><svg><use href="../icons.svg#circle-empty"></use></svg></span>)" << '\n';
|
2023-02-09 22:00:15 +00:00
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
tag(os,TAG::INLINE) << "Detailed View" << '\n';
|
|
|
|
tag(os,TAG::END,"label");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
}
|
|
|
|
|
2023-02-09 04:22:43 +00:00
|
|
|
std::string get_course_title(const std::string& course_id,
|
|
|
|
const quatalog_data_t& quatalog_data) {
|
2023-02-09 22:51:42 +00:00
|
|
|
const auto& catalog_entry = get_data(quatalog_data.catalog,course_id);
|
|
|
|
const auto& terms_offered = get_data(quatalog_data.terms_offered,course_id);
|
2023-02-07 23:10:56 +00:00
|
|
|
const auto& latest_term = terms_offered["latest_term"].asString();
|
|
|
|
if(catalog_entry) {
|
|
|
|
return catalog_entry["name"].asString();
|
|
|
|
} else {
|
2023-02-09 04:22:43 +00:00
|
|
|
return terms_offered[latest_term]["title"].asString();
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 05:19:35 +00:00
|
|
|
void generate_course_pill(std::string course_id,
|
2023-02-09 04:22:43 +00:00
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
2023-02-09 05:19:35 +00:00
|
|
|
if(course_id.substr(0,3) == "STS") {
|
|
|
|
course_id[3] = 'O';
|
|
|
|
}
|
2023-02-09 22:51:42 +00:00
|
|
|
course_id[4] = '-';
|
2023-02-09 04:22:43 +00:00
|
|
|
const auto& title = get_course_title(course_id,qlog);
|
2023-02-09 05:37:18 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<a class="course-pill" href=")" << course_id
|
|
|
|
<< R"(.html">)"
|
2023-02-09 04:22:43 +00:00
|
|
|
<< course_id;
|
|
|
|
if(!title.empty()) {
|
|
|
|
os << " " << title;
|
|
|
|
}
|
|
|
|
os << "</a>";
|
|
|
|
}
|
|
|
|
|
2023-02-09 05:37:18 +00:00
|
|
|
void generate_attributes(const Json::Value& attributes,
|
|
|
|
std::ostream& os) {
|
|
|
|
for(const auto& attribute : attributes) {
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="attr-pill")");
|
|
|
|
tag(os,TAG::INLINE) << attribute.asString() << '\n';
|
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 04:22:43 +00:00
|
|
|
void generate_list(const Json::Value& list,
|
|
|
|
const std::string& list_name,
|
|
|
|
const std::string& css_prefix,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
|
|
|
if(list.empty()) return;
|
|
|
|
tag(os,TAG::BEGIN,R"(div id=")" + css_prefix + R"(-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id=")" + css_prefix + R"(-title" class="rel-info-title")");
|
|
|
|
tag(os,TAG::INLINE) << list_name << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,"div id=" + css_prefix + R"(-classes" class="rel-info-courses")");
|
|
|
|
for(const auto& cl : list) {
|
|
|
|
generate_course_pill(cl.asString(),qlog,os);
|
|
|
|
os << '\n';
|
|
|
|
}
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_prereq_display(const Json::Value& prereqs,const quatalog_data_t& qlog,std::ostream& os) {
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="prereq-container" class="rel-info-container")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="prereq-title" class="rel-info-title")");
|
|
|
|
tag(os,TAG::INLINE) << "Prereqs:" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div id="prereq-classes" class="rel-info-courses")");
|
|
|
|
generate_prereq(prereqs,qlog,os);
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_prereq(const Json::Value& prereq,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
2023-02-09 22:00:15 +00:00
|
|
|
if(prereq.empty()) {
|
|
|
|
tag(os,TAG::BEGIN,R"(span class="none-rect")");
|
|
|
|
tag(os,TAG::INLINE) << "none" << '\n';
|
|
|
|
tag(os,TAG::END,"span");
|
|
|
|
} else if(prereq["type"] == "course") {
|
2023-02-09 04:22:43 +00:00
|
|
|
std::string course = prereq["course"].asString();
|
|
|
|
generate_course_pill(course,qlog,os);
|
|
|
|
os << '\n';
|
|
|
|
} else if(prereq["type"] == "or") {
|
|
|
|
generate_or_prereq(prereq["nested"],qlog,os);
|
|
|
|
} else if(prereq["type"] == "and") {
|
|
|
|
generate_and_prereq(prereq["nested"],qlog,os);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_or_prereq(const Json::Value& prereqs,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
|
|
|
tag(os,TAG::BEGIN,R"(div class="pr-or-con")");
|
|
|
|
tag(os,TAG::BEGIN,R"(div class="pr-or-title")");
|
|
|
|
tag(os,TAG::INLINE) << "one of:" << '\n';
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::BEGIN,R"(div class="pr-or")");
|
|
|
|
for(const auto& pr : prereqs) {
|
|
|
|
generate_prereq(pr,qlog,os);
|
|
|
|
}
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
tag(os,TAG::END,"div");
|
|
|
|
}
|
|
|
|
|
|
|
|
void generate_and_prereq(const Json::Value& prereqs,
|
|
|
|
const quatalog_data_t& qlog,
|
|
|
|
std::ostream& os) {
|
|
|
|
generate_prereq(prereqs[0],qlog,os);
|
|
|
|
for(Json::Value::ArrayIndex i = 1; i != prereqs.size();i++) {
|
2023-02-09 05:37:18 +00:00
|
|
|
tag(os,TAG::INLINE) << R"(<div class="pr-and">and</div>)" << '\n';
|
2023-02-09 04:22:43 +00:00
|
|
|
generate_prereq(prereqs[i],qlog,os);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream& tag(std::ostream& os,enum TAG mode,const std::string& tagname /* = "" */) {
|
|
|
|
static int indent_w = 0;
|
|
|
|
switch(mode) {
|
2023-02-09 22:51:42 +00:00
|
|
|
case TAG::COMPLEX_BEGIN:
|
|
|
|
return indent(os,indent_w++);
|
2023-02-09 04:22:43 +00:00
|
|
|
case TAG::INLINE:
|
|
|
|
return indent(os,indent_w);
|
|
|
|
case TAG::BEGIN:
|
|
|
|
return indent(os,indent_w++) << '<' << tagname << '>' << '\n';
|
|
|
|
case TAG::END:
|
|
|
|
return indent(os,--indent_w) << "</" << tagname << '>' << '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream& indent(std::ostream& os,
|
|
|
|
const int indent_w) {
|
2023-02-09 22:51:42 +00:00
|
|
|
const std::string& INDENT = " ";
|
2023-02-09 04:22:43 +00:00
|
|
|
for(int i = 0;i < indent_w;i++) {
|
|
|
|
os << INDENT;
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|
2023-02-09 04:22:43 +00:00
|
|
|
return os;
|
2023-02-07 23:10:56 +00:00
|
|
|
}
|