The @@@klammerset system command formally declares a klammerset — a named, logically related group of klammer definitions — with an operative, idempotent declaration (:requires and :files load in order at the declaration point, relative to the declaring file). A bare symbol given to ktext -k, kdesc --input, or :requires resolves to x/x.k on the search path: the document's directory, then KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset lists the available sets. sks/sks.k is the first declared klammerset, so `-k sks` loads the SKS by name. The engine's lookup classes were renamed *_set → *_registry to keep the two concepts apart, and the whole C++ tree now follows standard const-correctness conventions. tst/ gains klammerset_test.sh (18 cases). (from dev 64b1abf23e56)
321 lines
12 KiB
C++
321 lines
12 KiB
C++
#include <regex>
|
|
|
|
#include "util.h"
|
|
#include "file.h"
|
|
#include "html_util.h"
|
|
#include "heading.h"
|
|
#include "log.h"
|
|
#include "show.h"
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Heading h)
|
|
{
|
|
os << h.m_level << " "
|
|
<< h.m_filename << " "
|
|
<< h.m_section << " "
|
|
<< h.m_id << " "
|
|
<< h.m_number << " "
|
|
<< h.m_title;
|
|
return os;
|
|
}
|
|
|
|
std::string levels_to_section(std::vector<unsigned int> levels)
|
|
{
|
|
(void)K::log(3);
|
|
std::string result {};
|
|
auto count = levels.size();
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
if (levels[i] == 0)
|
|
break;
|
|
result += std::to_string(levels[i]);
|
|
if (i < count - 1)
|
|
result += ".";
|
|
}
|
|
if (count > 1)
|
|
result = result.substr(0, result.size()-1);
|
|
return result;
|
|
}
|
|
|
|
int part_number = 1;
|
|
int unnumbered_id = 0;
|
|
|
|
/*
|
|
std::tuple<std::string, std::vector<Heading>, std::vector<unsigned int>, unsigned int>
|
|
add_section_numbers(const std::string& s, const std::string& basename, std::vector<unsigned int> levels, unsigned int initial_id,
|
|
std::map<std::string, std::string>& section_id_map)
|
|
{
|
|
(void)K::log(3);
|
|
auto depth { levels.size() };
|
|
std::vector<Heading> headings {};
|
|
std::string text { s };
|
|
std::regex section_rgx (R"((.*?)<h(\d)(.*?)>(.*?)</h\2>)");
|
|
std::regex part_rgx (R"((.*?)<kt-part(.*?)>Part (\d+)\s*<br>\s*(.*?)\s*</kt-part>)");
|
|
std::regex id_rgx(R"((.*?)id=\"([-\w]+)\"(.*))");
|
|
|
|
unsigned int id_number { initial_id };
|
|
//std::string result {};
|
|
std::stringstream result {};
|
|
for (std::string line : regex_split(s, std::regex(R"(\n)"), false)) {
|
|
std::smatch match {};
|
|
if (std::regex_match(line, match, part_rgx)) {
|
|
std::string pre { match[1] };
|
|
std::string attr { match[2] };
|
|
std::string level { match[3] };
|
|
std::string title { match[4] };
|
|
|
|
std::string id_prefix { "_part_"};
|
|
std::string id {};
|
|
std::smatch id_match {};
|
|
if (std::regex_match(attr, id_match, id_rgx)) {
|
|
id = id_match[2];
|
|
} else {
|
|
id = id_prefix + std::to_string(part_number);
|
|
}
|
|
//title = "Part " + std::to_string(part_number) + " - " + title;
|
|
//std::string section = "Part " + level;
|
|
std::string section = "Part " + std::to_string(part_number);
|
|
elements_t part_title
|
|
{ html::elt("kt-part",
|
|
{ html::elt("span", section).attr("class", "sectionnumber"),
|
|
html::elt("span", trim(title)).attr("class", "sectiontitle") })
|
|
.attr("id", id)
|
|
.attr("data-level", level) };
|
|
result << pre << part_title << "\n";
|
|
headings.push_back(Heading(0, basename, section, id, "0", title));
|
|
part_number += 1;
|
|
} else if (std::regex_match(line, match, section_rgx)) {
|
|
std::string pre { match[1] };
|
|
int level { std::stoi(match[2]) };
|
|
std::string attr { match[3] };
|
|
std::string title { match[4] };
|
|
bool numbered = attr.find("numbered") != std::string::npos;
|
|
std::string section {};
|
|
if (numbered) {
|
|
levels[level-1] = levels[level-1] + 1;
|
|
for (unsigned int li = level; li < depth; li++)
|
|
levels[li] = 0;
|
|
section = levels_to_section(levels);
|
|
}
|
|
std::string id {};
|
|
std::smatch id_match {};
|
|
if (std::regex_match(attr, id_match, id_rgx)) {
|
|
id = id_match[2];
|
|
} else {
|
|
if (section.size() == 0) {
|
|
id = "_su_" + std::to_string(unnumbered_id++);
|
|
} else {
|
|
id = "_s_" + string_replace(section, ".", "_");
|
|
}
|
|
}
|
|
std::string link_target = file_basename(basename) + link_delimiter + id;
|
|
section_id_map[title] = link_target;
|
|
|
|
//<h1 id="image-tests" style="clear:both;" class="headerlink">
|
|
// <span class="sectionnumber">1</span>Image tests</h1>
|
|
|
|
elements_t title_parts {};
|
|
if (numbered) {
|
|
title_parts.push_back(html::elt("span", trim(section)).attr("class", "sectionnumber"));
|
|
}
|
|
title_parts.push_back(html::elt("span", trim(title)).attr("class", "sectiontitle"));
|
|
|
|
// msg() << "title_parts: " << title_parts << "\n";
|
|
elements_t section_title
|
|
{ html::elt("h"+std::to_string(level), title_parts)
|
|
.attr("id", id)
|
|
.attr("data-level", std::to_string(level))};
|
|
// msg() << pre << section_title << "\n";
|
|
result << pre << section_title << "\n";
|
|
headings.push_back(Heading(level, basename, section, id, "0", title));
|
|
} else {
|
|
result << line << "\n";
|
|
}
|
|
}
|
|
//std::cout << "add_section_numbers: " << result.str() << " "
|
|
//<< headings.size() << " " << levels.size() << " " << id_number << "\n";
|
|
return std::tuple(result.str(), headings, levels, id_number);
|
|
}
|
|
*/
|
|
|
|
std::string make_html_table_of_contents(std::vector<Heading> headings)
|
|
{
|
|
elements_t toc { html::elt("h1", "Contents") };
|
|
for (auto [level, filename, section, id, number, title] : headings) {
|
|
toc.push_back(
|
|
html::elt("div",
|
|
html::elt("a", {
|
|
html::elt("span", section).attr("class", "level_number"),
|
|
title })
|
|
.attr("href", "^#" + id)
|
|
.attr("class", "level"))
|
|
.attr("class", "level" + std::to_string(level)));
|
|
}
|
|
//auto toc_elt = html::elt("div", toc).attr("id", "toc");
|
|
std::string result { "<div id=\"pagetoc\">\n" + to_string(toc) + "\n</div>\n" };
|
|
return result;
|
|
}
|
|
|
|
std::string toc_link_attrs(
|
|
const std::string& section, const std::string& title, const std::string& filename, const std::string& target, int level)
|
|
{
|
|
std::string basename = file_basename(filename);
|
|
std::stringstream ss {};
|
|
ss << "class=\"navlink\" data-page=\"" << basename
|
|
<< "\" data-target=\"" << target << "\">\n<span class=\"section-title\" level=\"" << level << "\">\n";
|
|
if (section.size() > 0) {
|
|
ss << "<span class=\"section-number\">" << section << "</span>\n";
|
|
}
|
|
ss << "<span class=\"section-title-text\">" << title << "</span>\n"
|
|
<< "</span>\n";
|
|
return ss.str();
|
|
}
|
|
|
|
|
|
std::pair<int, std::string>
|
|
make_navigation_table_of_contents(std::vector<Heading> headings)
|
|
{
|
|
std::stringstream toc {};
|
|
long unsigned int i = 1;
|
|
//int max_level = 0;
|
|
//int last_level = 0;
|
|
int max_level = 1;
|
|
int last_level = 1;
|
|
|
|
toc << "<ul id=\"toc_items\">\n";
|
|
int min_level = 7;
|
|
for (auto h : headings) {
|
|
min_level = std::min(h.m_level, min_level);
|
|
}
|
|
|
|
bool unnumbered_start = headings.size() > 0 ? headings[0].m_section.empty() : false;
|
|
if (unnumbered_start) { // Unnumbered first section (preface, for example)
|
|
toc << "<ul>\n";
|
|
}
|
|
for (auto [item_level, filename, section, id, number, title] : headings) {
|
|
// msg() << " level: " << item_level << "; filename: " << filename << "; section: " << section
|
|
// << "; id: " << id << "; number: " << number << "; title: " << title << "\n";
|
|
|
|
bool promote_unnumbered = unnumbered_start && min_level == 0 and section.empty();
|
|
if (promote_unnumbered) {
|
|
item_level--;
|
|
}
|
|
std::string basename = file_basename(filename);
|
|
max_level = std::max(max_level, item_level);
|
|
//int next_level = i < headings.size() ? headings[i].m_level : 0;
|
|
|
|
int next_level = i < headings.size() ? headings[i].m_level : 1;
|
|
|
|
if (promote_unnumbered) {
|
|
next_level = std::max(0, next_level - 1);
|
|
}
|
|
|
|
// std::cout << "|" << item_level << "|" << section << "|" << id << "|"
|
|
// << title << "| " << "next: " << next_level << "\n";
|
|
|
|
std::string link = toc_link_attrs(section, title, basename, id, item_level);
|
|
|
|
if (next_level == item_level) {
|
|
toc << "<li " << link << "</li>\n";
|
|
/*
|
|
} else if (next_level > item_level) {
|
|
toc << "<li><span class=\"caret\" data-level=\"" << item_level << "\">"
|
|
<< "<span " << link<< "</span></span>\n"
|
|
<< "<ul class=\"nested\">\n";
|
|
*/
|
|
} else if (next_level > item_level) {
|
|
toc << "<li><span class=\"caret\" data-level=\"" << item_level << "\"></span>\n"
|
|
<< "<span " << link<< "</span>\n"
|
|
<< "<ul class=\"nested";
|
|
/*
|
|
if (section.size() == 0) {
|
|
toc << " unnumbered";
|
|
}
|
|
*/
|
|
toc << "\">\n";
|
|
} else {
|
|
//toc << "<li " << link << "</li>\n</ul>\n</li>\n";
|
|
toc << "<li " << link << "</li>\n";
|
|
/*
|
|
if (unnumbered_start) {
|
|
next_level += 1;
|
|
}
|
|
*/
|
|
int offset = unnumbered_start ? 1 : 0;
|
|
//int offset = unnumbered_start ? 3 : 2;
|
|
offset = 0;
|
|
|
|
for (int lev = item_level; lev > next_level + offset; lev--) {
|
|
toc << "</ul>\n</li>\n";
|
|
}
|
|
unnumbered_start = false;
|
|
}
|
|
i++;
|
|
last_level = item_level;
|
|
}
|
|
toc << "</ul>\n";
|
|
for (int j = 0; j < last_level - 2; j++) {
|
|
toc << "</li>\n</ul>\n";
|
|
}
|
|
return {max_level, toc.str()};
|
|
}
|
|
|
|
void show_table_of_contents(
|
|
const std::string& kt_root_filename, const std::string& title, std::vector<Heading> headings)
|
|
{
|
|
long unsigned int width = kt_root_filename.size();
|
|
for (Heading h : headings) {
|
|
width = std::max(width, h.m_filename.size());
|
|
}
|
|
width += 2;
|
|
int tab = 2;
|
|
std::string current_filename = "";
|
|
std::string bar = " | ";
|
|
std::cout << "\n" << std::string(width - kt_root_filename.size(), ' ')
|
|
<< kt_root_filename << bar << title << "\n";
|
|
for (auto [level, filename, section, id, number, htitle] : headings) {
|
|
std::string f = "";
|
|
if (filename != current_filename) {
|
|
f = std::string(width - filename.size(), ' ') + filename + bar;
|
|
current_filename = filename;
|
|
} else {
|
|
f = std::string(width, ' ') + bar;
|
|
}
|
|
std::cout << f << std::string((level-1) * tab, ' ') << section << " " << htitle << "\n";
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
|
|
std::string cache_table_of_contents(
|
|
const std::string& kt_root_filename, const std::string& title, std::vector<Heading> headings)
|
|
{
|
|
long unsigned int width = kt_root_filename.size();
|
|
for (Heading h : headings) {
|
|
width = std::max(width, h.m_filename.size());
|
|
}
|
|
width += 2;
|
|
int tab = 2;
|
|
std::string current_filename = "";
|
|
std::string bar = " | ";
|
|
std::stringstream ss {};
|
|
ss << "\n" << std::string(width - kt_root_filename.size(), ' ')
|
|
<< kt_root_filename << bar << title << "\n";
|
|
for (auto [level, filename, section, id, number, htitle] : headings) {
|
|
std::string f = "";
|
|
if (filename != current_filename) {
|
|
f = std::string(width - filename.size(), ' ') + filename + bar;
|
|
current_filename = filename;
|
|
} else {
|
|
f = std::string(width, ' ') + bar;
|
|
}
|
|
ss << f << std::string((level) * tab, ' ');
|
|
if (section.size() > 0) {
|
|
ss << section << " ";
|
|
}
|
|
ss << htitle << "\n";
|
|
}
|
|
ss << "\n";
|
|
std::string cache_filename =
|
|
cache_directory(kt_root_filename, "_html_pages") + "/toc.txt";
|
|
string_to_file(cache_filename, ss.str());
|
|
return cache_filename;
|
|
}
|