#include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; struct quatalog_data_t { Json::Value terms_offered; Json::Value prerequisites; Json::Value list_of_terms; Json::Value catalog; }; enum struct TAG { BEGIN, END, INLINE, COMPLEX_BEGIN }; enum struct TERM { SPRING, SUMMER, SUMMER2, SUMMER3, FALL, WINTER }; enum struct OFFERED { YES, NO, DIFF_CODE, UNSCHEDULED }; const std::unordered_map offered_to_string { { OFFERED::YES,"offered" }, { OFFERED::NO,"not-offered" }, { OFFERED::DIFF_CODE,"offered-diff-code" }, { OFFERED::UNSCHEDULED,"unscheduled" } }; const std::unordered_map 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 term_to_number { { TERM::SPRING,"01" }, { TERM::SUMMER,"05" }, { TERM::SUMMER2,"0502" }, { TERM::SUMMER3,"0503" }, { TERM::FALL,"09" }, { TERM::WINTER,"12" } }; const std::unordered_map term_to_number_no_half { { TERM::SPRING,"01" }, { TERM::SUMMER,"05" }, { TERM::SUMMER2,"05" }, { TERM::SUMMER3,"05" }, { TERM::FALL,"09" }, { TERM::WINTER,"12" } }; const std::unordered_map attr_to_short_attr { { "Communication Intensive", "CI" }, { "Writing Intensive", "WI" }, { "HASS Inquiry", "HInq" }, { "Culminating Exp/Capstone", "CulmExp" }, { "PDII Option for Engr Majors", "PDII" } }; std::set get_all_courses(const quatalog_data_t&); std::string fix_course_ids(std::string); bool create_dir_if_not_exist(const fs::path&); Json::Value get_data(const Json::Value&,std::string); void generate_course_page(const std::string&,const quatalog_data_t&,Json::Value&,std::ostream&); void get_prerequisites(const quatalog_data_t&,std::string); void generate_opt_container(std::ostream&); std::string generate_credit_string(const Json::Value& credits); std::pair get_course_title_and_description(const std::string&,const quatalog_data_t&); void generate_years_table(const Json::Value&,const Json::Value&,const quatalog_data_t&,const std::string&,std::ostream&); void generate_year_row(const int,const Json::Value&,const Json::Value&,const quatalog_data_t&,const std::string&,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&); void generate_table_cell(const int,const enum TERM,const Json::Value&,const enum OFFERED,const std::string&,std::ostream&); void generate_attributes(const Json::Value&,std::ostream&,Json::Value&); 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&); void generate_course_pill(std::string,const quatalog_data_t&,std::ostream&); 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& = ""); int main(const int argc, const char** argv) { if(argc != 8) { std::cerr << "Bad number of arguments (" << argc << ")" << std::endl; std::cerr << "Usage: " << argv[0] << " " << " " << " " << " " << " " << " " << " " << 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]); const auto& searchable_catalog_filename = fs::path(argv[6]); const auto& courses_list_filename = fs::path(argv[7]); if(!create_dir_if_not_exist(out_dir_path)) { std::cerr << "What" << std::endl; return EXIT_FAILURE; } quatalog_data_t quatalog_data; 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; terms_offered_file.close(); prerequisites_file.close(); list_of_terms_file.close(); catalog_file.close(); auto courses = get_all_courses(quatalog_data); Json::Value searchable_catalog; Json::Value courses_list; for(const auto& course : courses) { courses_list.append(course); const auto& html_path = out_dir_path / (course + ".html"); auto file = std::ofstream(html_path); generate_course_page(course,quatalog_data,searchable_catalog,file); file.close(); } Json::StreamWriterBuilder swb; swb["indentation"] = " "; std::unique_ptr outWriter(swb.newStreamWriter()); std::fstream searchable_catalog_file{searchable_catalog_filename,std::ios::out}; std::fstream courses_list_file{courses_list_filename,std::ios::out}; outWriter->write(searchable_catalog,&searchable_catalog_file); outWriter->write(courses_list,&courses_list_file); searchable_catalog_file.close(); courses_list_file.close(); } std::set get_all_courses(const quatalog_data_t& qlog) { std::set 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'; } else if(course.substr(0,4) == "ITEC") { course.replace(0,4,"ITWS"); } else if(course.substr(0,6) == "IHSS-1") { course.replace(0,4,"INQR"); } return course; } 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); } Json::Value get_data(const Json::Value& data, std::string course_id) { Json::Value out; course_id[4] = '-'; if(course_id.substr(0,4) == "STSO") { const auto& stso = data[course_id]; course_id[3] = 'S'; const auto& stss = data[course_id]; course_id[3] = 'H'; const auto& stsh = data[course_id]; for(const auto& key : stsh.getMemberNames()) { out[key] = stsh[key]; if(out[key].isObject()) out[key]["prefix"] = "STSH"; } for(const auto& key : stss.getMemberNames()) { out[key] = stss[key]; if(out[key].isObject()) out[key]["prefix"] = "STSS"; } for(const auto& key : stso.getMemberNames()) { out[key] = stso[key]; if(out[key].isObject()) out[key]["prefix"] = "STSO"; } } else if(course_id.substr(0,4) == "ITWS") { const auto& itws = data[course_id]; course_id.replace(0,4,"ITEC"); const auto& itec = data[course_id]; for(const auto& key : itec.getMemberNames()) { out[key] = itec[key]; if(out[key].isObject()) out[key]["prefix"] = "ITEC"; } for(const auto& key : itws.getMemberNames()) { out[key] = itws[key]; if(out[key].isObject()) out[key]["prefix"] = "ITWS"; } } else if(course_id.substr(0,4) == "INQR") { const auto& inqr = data[course_id]; course_id.replace(0,4,"IHSS"); const auto& ihss = data[course_id]; for(const auto& key : ihss.getMemberNames()) { out[key] = ihss[key]; if(out[key].isObject()) out[key]["prefix"] = "IHSS"; } for(const auto& key : inqr.getMemberNames()) { out[key] = inqr[key]; if(out[key].isObject()) out[key]["prefix"] = "INQR"; } } else { out = data[course_id]; for(const auto& key : out.getMemberNames()) { if(out[key].isObject()) out[key]["prefix"] = course_id.substr(0,4); } } return out; } void generate_course_page(const std::string& course_id, const quatalog_data_t& quatalog_data, Json::Value& searchable_catalog, std::ostream& os) { std::cerr << "Generating course page for " << course_id << "..." << std::endl; const auto& title_and_description = get_course_title_and_description(course_id,quatalog_data); const auto& course_name = title_and_description.first; const auto& description = title_and_description.second; const auto& prereqs_entry = get_data(quatalog_data.prerequisites,course_id); const auto& terms_offered = get_data(quatalog_data.terms_offered,course_id); const auto& latest_term = terms_offered["latest_term"].asString(); const auto& credits = terms_offered[latest_term]["credits"]; const auto& credit_string = generate_credit_string(credits); const auto& credit_string_long = credit_string + " " + (credits["max"].asInt() == 1 ? "credit" : "credits"); Json::Value searchable_catalog_entry; searchable_catalog_entry["code"] = course_id; searchable_catalog_entry["name"] = course_name; searchable_catalog_entry["description"] = description; searchable_catalog_entry["credits"] = credit_string_long; const std::regex escape_string(R"(")"); const std::string& description_meta = std::regex_replace(description,escape_string,"""); tag(os,TAG::INLINE) << "" << '\n'; tag(os,TAG::BEGIN,"html"); tag(os,TAG::BEGIN,"head"); tag(os,TAG::BEGIN,"title"); tag(os,TAG::INLINE) << course_id << ": " << course_name << '\n'; tag(os,TAG::END,"title"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::END,"head"); tag(os,TAG::BEGIN,R"(body class="search_plugin_added")"); tag(os,TAG::BEGIN,R"(div id="qlog-header")"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::BEGIN,R"R(form onsubmit="search_helper(event)")R"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::END,"form"); tag(os,TAG::END,"div"); 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"); tag(os,TAG::BEGIN,R"(h2 id="code")"); tag(os,TAG::INLINE) << course_id << '\n'; tag(os,TAG::END,"h2"); 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")"); tag(os,TAG::INLINE) << credit_string_long << '\n'; tag(os,TAG::END,"span"); generate_attributes(prereqs_entry["attributes"],os,searchable_catalog_entry["attributes"]); 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"); tag(os,TAG::BEGIN,R"(div id="past-container")"); tag(os,TAG::BEGIN,R"(h1 id="past-title")"); tag(os,TAG::INLINE) << "Past Term Data" << '\n'; tag(os,TAG::END,"h2"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; generate_opt_container(os); generate_years_table(terms_offered,prereqs_entry["cross_listings"],quatalog_data,course_id,os); tag(os,TAG::END,"div"); tag(os,TAG::END,"div"); tag(os,TAG::END,"body"); tag(os,TAG::END,"html"); searchable_catalog.append(searchable_catalog_entry); } std::string generate_credit_string(const Json::Value& credits) { if(credits == Json::Value::null) return "?"; 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)); } void generate_years_table(const Json::Value& terms_offered, const Json::Value& cross_listings, const quatalog_data_t& qlog, const std::string& course_id, std::ostream& os) { tag(os,TAG::BEGIN,R"(table id="years-table")"); tag(os,TAG::BEGIN,"thead"); tag(os,TAG::BEGIN,"tr"); tag(os,TAG::INLINE) << "" << '\n'; tag(os,TAG::INLINE) << R"(Spring)" << '\n'; tag(os,TAG::INLINE) << R"(Summer)" << '\n'; tag(os,TAG::INLINE) << R"(Fall)" << '\n'; //tag(os,TAG::INLINE) << R"(Enrichment)" << '\n'; tag(os,TAG::END,"tr"); tag(os,TAG::BEGIN,"tr"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"((Session 1))" << '\n'; tag(os,TAG::INLINE) << R"((Session 2))" << '\n'; //tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\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,course_id,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, const std::string& course_id, std::ostream& os) { tag(os,TAG::BEGIN,"tr"); tag(os,TAG::INLINE) << R"()" << year << "" << '\n'; generate_table_cell(year,TERM::SPRING,terms_offered,is_course_offered(year,TERM::SPRING,terms_offered,cross_listings,qlog),course_id,os); const enum OFFERED summer1 = is_course_offered(year,TERM::SUMMER,terms_offered,cross_listings,qlog); if(summer1 != OFFERED::NO) { generate_table_cell(year,TERM::SUMMER,terms_offered,summer1,course_id,os); } 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)) { generate_table_cell(year,TERM::SUMMER,terms_offered,summer1,course_id,os); } else { generate_table_cell(year,TERM::SUMMER2,terms_offered,summer2,course_id,os); generate_table_cell(year,TERM::SUMMER3,terms_offered,summer3,course_id,os); } } generate_table_cell(year,TERM::FALL,terms_offered,is_course_offered(year,TERM::FALL,terms_offered,cross_listings,qlog),course_id,os); //generate_table_cell(year,TERM::WINTER,terms_offered,is_course_offered(year,TERM::WINTER,terms_offered,cross_listings,qlog),os); tag(os,TAG::END,"tr"); } void generate_table_cell(const int year, const enum TERM term, const Json::Value& terms_offered, const enum OFFERED is_offered, const std::string& course_id, std::ostream& os) { std::string year_term = std::to_string(year) + term_to_number.at(term); const auto& term_offered = terms_offered[year_term]; const auto& course_titles = term_offered["title"]; const auto& credit_string = generate_credit_string(term_offered["credits"]); tag(os,TAG::COMPLEX_BEGIN) << R"()" << '\n'; 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) << R"()" << course_titles[0].asString() << " (" << credit_string << "c)"; for(const auto& attr : term_offered["attributes"]) { os << ' ' << attr.asString(); } for(int i = 1; i < course_titles.size(); i++) { os << "
" << course_titles[i].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) << "
  • " << instructor.asString() << "
  • " << '\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"); } bool is_term_scheduled(const std::string& term_str, const quatalog_data_t& qlog) { static std::unordered_set 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) { const std::string& term_str = std::to_string(year) + term_to_number.at(term); 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) { const auto& data = get_data(qlog.terms_offered,cl.asString())[term_str]; if(data) { 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; 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")"); tag(os,TAG::INLINE) << R"()" << '\n'; tag(os,TAG::INLINE) << R"()" << '\n'; 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"); } std::pair get_course_title_and_description(const std::string& course_id, const quatalog_data_t& quatalog_data) { const auto& catalog_entry = get_data(quatalog_data.catalog,course_id); const auto& terms_offered = get_data(quatalog_data.terms_offered,course_id); const auto& latest_term = terms_offered["latest_term"].asString(); std::string title, description; if(catalog_entry) { title = catalog_entry["name"].asString(); } else { title = terms_offered[latest_term]["title"][0].asString(); } if(catalog_entry) { description = catalog_entry["description"].asString(); } else { 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."; } const auto& mid_digits = course_id.substr(6,2); if(mid_digits == "96" || mid_digits == "97") { title = "Topics in " + course_id.substr(0,4); description = "Course codes between X960 and X979 are for topics " "courses. They are often recycled and used for new " "or experimental courses."; } return std::make_pair(title,description); } void generate_course_pill(std::string course_id, const quatalog_data_t& qlog, std::ostream& os) { course_id = fix_course_ids(course_id); const auto& title = get_course_title_and_description(course_id,qlog).first; tag(os,TAG::INLINE) << R"()" << course_id; if(!title.empty()) { os << " " << title; } os << ""; } void generate_attributes(const Json::Value& attributes, std::ostream& os, Json::Value& attributes_catalog) { for(const auto& attribute : attributes) { const auto& attr_str = attribute.asString(); const auto& attr_short_itr = attr_to_short_attr.find(attr_str); if(attr_short_itr != attr_to_short_attr.end()) { attributes_catalog.append(attr_short_itr->second); } tag(os,TAG::BEGIN,R"(span class="attr-pill")"); tag(os,TAG::INLINE) << attr_str << '\n'; tag(os,TAG::END,"span"); } } 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) { 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") { 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++) { tag(os,TAG::INLINE) << R"(
    and
    )" << '\n'; 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) { case TAG::COMPLEX_BEGIN: return indent(os,indent_w++); 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) << "' << '\n'; } } std::ostream& indent(std::ostream& os, const int indent_w) { const std::string& INDENT = " "; for(int i = 0;i < indent_w;i++) { os << INDENT; } return os; }