A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
773 lines
28 KiB
C++
773 lines
28 KiB
C++
#include "util.h"
|
|
#include "error.h"
|
|
#include "log.h"
|
|
#include "kutil.h"
|
|
#include "html_util.h"
|
|
#include "heading.h"
|
|
#include "document_class.h"
|
|
#include "character.h"
|
|
#include "reference.h"
|
|
#include "show.h"
|
|
#include "file.h"
|
|
|
|
std::string unescape_newlines(const std::string& s)
|
|
{
|
|
return string_replace(s, " ___NL___ ", "\n");
|
|
|
|
}
|
|
|
|
int ID = 0;
|
|
std::string BASE {};
|
|
|
|
static const std::regex newline_rgx(R"(\n)");
|
|
static const std::regex element_open_rgx(R"(<([\w-]+)(\s+.*?)?>)");
|
|
static const std::regex id_attr_rgx("id=\"(.*?)\"");
|
|
|
|
|
|
std::string insert_missing_ids(std::smatch match)
|
|
{
|
|
(void)K::log(3);
|
|
std::string result = match[0];
|
|
std::string tag = match[1];
|
|
if (html::tag_is_block_element(tag)) {
|
|
std::string attrs = match[2];
|
|
std::stringstream ss {};
|
|
if (attrs.find(" id=") == std::string::npos) {
|
|
ss << "id=\"_e" << ID++<< "\"";
|
|
}
|
|
ss << attrs;
|
|
if (!BASE.empty()) {
|
|
ss << " data-basename=\"" << BASE << "\"";
|
|
}
|
|
result = "<" + tag + " " + trim(ss.str()) + ">";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string insert_ids(const std::string& html_text)
|
|
{
|
|
(void)K::log(3);
|
|
std::string result = html_text;
|
|
result = freplace(result, element_open_rgx, insert_missing_ids);
|
|
return result;
|
|
}
|
|
|
|
std::string Document_class::make_help_page()
|
|
{
|
|
(void)K::log(3);
|
|
std::string kt_filename = klammertext_filename("doc/html_help.kt");
|
|
std::string basename = "help";
|
|
std::string help_page;
|
|
//if (!in_modification_order(kt_filename, output_filename, no_cache)) {
|
|
if (cache_requires_update(m_cache_dir, kt_filename, basename)) {
|
|
Machine Mh = m_machine;
|
|
Mh.read(fs::path(klammertext_filename("doc/html_help.kt")));
|
|
help_page = Mh.apply("html");
|
|
help_page += "\n<div class=\"endspace\"></div>\n";
|
|
help_page = html::make_paragraphs(help_page);
|
|
write_to_cache(m_cache_dir, basename, help_page);
|
|
} else {
|
|
help_page = read_from_cache(m_cache_dir, basename);
|
|
}
|
|
return help_page;
|
|
}
|
|
|
|
std::string Document_class::color_definitions()
|
|
{
|
|
std::stringstream ss {};
|
|
ss << ":root {\n"
|
|
<< " --frame_background_color: " << frame_background_color << ";\n"
|
|
<< " --frame_text_color: " << frame_text_color << ";\n"
|
|
<< " --nav_background_color: " << nav_background_color << ";\n"
|
|
<< " --nav_text_color: " << nav_text_color << ";\n}\n";
|
|
return ss.str();
|
|
}
|
|
|
|
strings_t Document_class::resolved_font_names()
|
|
{
|
|
strings_t result {};
|
|
auto add = [&](const Resolved_font& rf) {
|
|
if (!rf.family_name.empty())
|
|
result.push_back(rf.dir_name);
|
|
};
|
|
add(m_resolved_serif);
|
|
add(m_resolved_sans);
|
|
add(m_resolved_mono);
|
|
return result;
|
|
}
|
|
|
|
std::string Document_class::font_definitions()
|
|
{
|
|
std::stringstream ss {};
|
|
if (!m_serif_font.empty() || !m_sans_font.empty() || !m_mono_font.empty()) {
|
|
// Family names are quoted: an unquoted name with a digit-initial
|
|
// word ("Source Sans 3") is invalid CSS, and a font-family using
|
|
// var() with such a value computes to inherit, silently losing
|
|
// the font.
|
|
ss << ":root {\n";
|
|
if (!m_serif_font.empty())
|
|
ss << " --serif: \"" << m_serif_font << "\", serif;\n";
|
|
if (!m_sans_font.empty())
|
|
ss << " --sans-serif: \"" << m_sans_font << "\", sans-serif;\n";
|
|
if (!m_mono_font.empty())
|
|
ss << " --monospace: \"" << m_mono_font << "\", monospace;\n";
|
|
ss << "}\n";
|
|
}
|
|
// Emit scale factors so sans and mono fonts match the serif font.
|
|
// Three scaling methods (uncomment the desired one):
|
|
// x-height: serif_xh / other_xh (matches lowercase, like fontspec MatchLowercase)
|
|
// cap-height: serif_ch / other_ch (matches capitals)
|
|
// average: mean(serif_xh,serif_ch) / mean(other_xh,other_ch) (compromise)
|
|
float serif_xh = m_resolved_serif.xheight_ratio;
|
|
float serif_ch = m_resolved_serif.capheight_ratio;
|
|
float serif_avg = (serif_xh + serif_ch) / 2.0f;
|
|
if (serif_avg > 0.0f) {
|
|
auto scale = [&](const Resolved_font& other) -> std::string {
|
|
float other_avg = (other.xheight_ratio + other.capheight_ratio) / 2.0f;
|
|
if (other_avg > 0.0f && other_avg != serif_avg) {
|
|
char buf[16];
|
|
// float ratio = serif_xh / other.xheight_ratio; // x-height
|
|
// float ratio = serif_ch / other.capheight_ratio; // cap-height
|
|
float ratio = serif_avg / other_avg; // average
|
|
std::snprintf(buf, sizeof(buf), "%.4f", ratio);
|
|
return buf;
|
|
}
|
|
return "";
|
|
};
|
|
std::string sans_scale = scale(m_resolved_sans);
|
|
std::string mono_scale = scale(m_resolved_mono);
|
|
if (!sans_scale.empty() || !mono_scale.empty()) {
|
|
ss << ":root {\n";
|
|
if (!sans_scale.empty())
|
|
ss << " --sans-serif-scale: " << sans_scale << ";\n";
|
|
if (!mono_scale.empty())
|
|
ss << " --monospace-scale: " << mono_scale << ";\n";
|
|
ss << "}\n";
|
|
}
|
|
}
|
|
// Global font scale: applied to body font-size
|
|
if (m_font_scale != 1.0f) {
|
|
char buf[16];
|
|
std::snprintf(buf, sizeof(buf), "%.4f", m_font_scale);
|
|
ss << "body { font-size: " << buf << "rem; }\n";
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
void Document_class::write_basenames_js_file(const std::string& filename, const std::vector<std::string>& basenames)
|
|
{
|
|
(void)K::log(3);
|
|
std::string tab(" ");
|
|
std::stringstream ss {};
|
|
ss << "function pages_basenames()\n{\n return [\n";
|
|
int i = 0;
|
|
int last = basenames.size() - 1;
|
|
for (std::string base : basenames) {
|
|
ss << tab << "\"" << base << "\"";
|
|
if (i < last) {
|
|
ss << ",";
|
|
}
|
|
ss << "\n";
|
|
i++;
|
|
}
|
|
ss << " ];\n}\n";
|
|
write(filename, ss.str());
|
|
}
|
|
|
|
std::string Document_class::create_html_output_directories()
|
|
{
|
|
(void)K::log(3);
|
|
std::string output_directory =
|
|
absolute_pathname(get("K_output_dir")) + "/" + get("K_output_basename");
|
|
if (!m_toc_only) {
|
|
if (!file_exists(output_directory)) {
|
|
fs::create_directory(output_directory);
|
|
}
|
|
install_resolved_font(m_resolved_serif, output_directory);
|
|
install_resolved_font(m_resolved_sans, output_directory);
|
|
install_resolved_font(m_resolved_mono, output_directory);
|
|
}
|
|
m_cache_dir = cache_directory("_html_pages");
|
|
if (m_no_cache && fs::exists(m_cache_dir)) {
|
|
for (auto& entry : std::filesystem::directory_iterator(m_cache_dir)) {
|
|
// What ":cache false" actually removed: a derived consequence of
|
|
// an option, so "-v 1".
|
|
(void)K::log(1, "Removing cache directory:", entry.path().string());
|
|
std::filesystem::remove_all(entry);
|
|
}
|
|
}
|
|
if (!m_toc_only) {
|
|
if (!file_exists(output_directory, false, true)) {
|
|
fs::create_directory(output_directory);
|
|
}
|
|
}
|
|
return output_directory;
|
|
}
|
|
|
|
strings_t get_css_files(const strings_t& custom_css_files)
|
|
{
|
|
(void)K::log(3);
|
|
strings_t result = sks_files_of_type("css");
|
|
result.insert(result.end(), custom_css_files.begin(), custom_css_files.end());
|
|
return result;
|
|
}
|
|
|
|
|
|
std::vector<std::string> js_basenames(bool nav)
|
|
{
|
|
if (nav) {
|
|
return { "kutil", "document", "image", "resize_window", "resize_text", "resize_toc",
|
|
"toc", "level", "next_last", "help", "show_links", "search", "state", "code", "link" };
|
|
} else {
|
|
return {"kutil", "document", "image"};
|
|
}
|
|
}
|
|
|
|
strings_t Document_class::get_js_files(
|
|
bool nav, bool include_sks_js,
|
|
const std::string& pages_basenames_filename)
|
|
{
|
|
(void)K::log(3);
|
|
strings_t result {};
|
|
if (include_sks_js) {
|
|
for (auto b : js_basenames(nav)) {
|
|
std::vector<fs::path> js_files =
|
|
find_file_recursive(klammertext_dir() + "/sks", b + ".js");
|
|
if (js_files.size() == 0) {
|
|
throw File_error("File " + q_(b) + " not found.", Locator());
|
|
}
|
|
result.push_back(js_files[0].string());
|
|
}
|
|
if (!pages_basenames_filename.empty()) {
|
|
result.push_back(pages_basenames_filename);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
strings_t link_filenames(strings_t& full_filenames, const std::string& page_dir)
|
|
{
|
|
strings_t result {};
|
|
fs::path page_dir_path(page_dir);
|
|
// int i = 0;
|
|
std::transform(full_filenames.begin(), full_filenames.end(), std::back_inserter(result),
|
|
[&](const std::string& f) {
|
|
fs::path full(f);
|
|
//msg() << i << full << " " << full.filename() << "\n";
|
|
fs::path path(page_dir_path / full.filename());
|
|
//std::cout << i++ << path << "\n";
|
|
return path.string(); });
|
|
return result;
|
|
}
|
|
|
|
std::pair<std::vector<std::string>, std::vector<std::string>>
|
|
Document_class::html_auxiliary_files(const std::string& output_directory, const std::string& pages_basenames_filename,
|
|
const std::vector<std::string>& custom_css_files)
|
|
{
|
|
(void)K::log(3);
|
|
strings_t all_css_files = custom_css_files;
|
|
strings_t css_link_filenames {};
|
|
if (write_files && m_include_sks_css) {
|
|
all_css_files = get_css_files(m_css_filenames);
|
|
//output_dir, color_definitions(), css_files, write_files);
|
|
}
|
|
if (write_files) {
|
|
copy_preserving_basename(
|
|
all_css_files, output_directory, "css");
|
|
css_link_filenames = link_filenames(all_css_files, "css");
|
|
}
|
|
|
|
strings_t all_js_files {};
|
|
strings_t js_link_filenames {};
|
|
if (write_files) {
|
|
bool nav = m_structure == docstruct_t::book;
|
|
all_js_files = get_js_files(
|
|
nav, m_include_sks_js,
|
|
pages_basenames_filename); // , output_dir, write_files);
|
|
copy_preserving_basename(
|
|
all_js_files, output_directory, "js");
|
|
js_link_filenames = link_filenames(all_js_files, "js");
|
|
}
|
|
|
|
// msg() << "all_css_files:\n " << join(all_css_files, "\n ") << "\n\n";
|
|
// msg() << "all_js_files:\n " << join(all_js_files, "\n ") << "\n\n";
|
|
|
|
return {css_link_filenames, js_link_filenames};
|
|
}
|
|
|
|
|
|
void Document_class::process_input_files()
|
|
{
|
|
(void)K::log(3);
|
|
m_file_components.clear();
|
|
size_t basename_width = 0;
|
|
for (auto input_filename : m_files) {
|
|
fs::path inpath = input_filename;
|
|
if (!file_exists(inpath)) {
|
|
inpath = parse_input_filename(input_filename, m_input_dir);
|
|
}
|
|
std::string basename = file_basename(input_filename);
|
|
basename_width = std::max(basename_width, basename.size());
|
|
std::string html_text;
|
|
//if (true || !in_modification_order(inpath, cached_filename, m_no_cache)) {
|
|
if (m_no_cache || cache_requires_update(m_cache_dir, inpath, basename)) {
|
|
Machine M = m_machine;
|
|
M.read(inpath);
|
|
html_text = trim(M.apply("html"));
|
|
html_text = trim(html::make_paragraphs(html_text)) + "\n";
|
|
html_text = unescape_newlines(html_text);
|
|
BASE = basename;
|
|
html_text = insert_ids(html_text);
|
|
m_file_components.push_back({basename, html_text});
|
|
write_to_cache(m_cache_dir, basename, html_text);
|
|
} else {
|
|
html_text = read_from_cache(m_cache_dir, basename);
|
|
m_file_components.push_back({basename, html_text});
|
|
}
|
|
}
|
|
// for (auto [src, result] : m_file_components) {
|
|
// msg() << std::left << std::setw(basename_width) << src << " " << abbrev(result, 96) << "\n";
|
|
// }
|
|
}
|
|
|
|
std::string increment_level(int level, std::vector<int> &levels)
|
|
{
|
|
levels[level]++;
|
|
int zero_offset = level == 0 ? 2 : 1;
|
|
for (unsigned int i = level + zero_offset; i < levels.size(); i++) {
|
|
levels[i] = 0;
|
|
}
|
|
std::stringstream result {};
|
|
if (level == 0) {
|
|
result << levels[0];
|
|
} else { // Parts not included in numbering
|
|
for (auto i = 1; i <= level; i++) {
|
|
result << levels[i];
|
|
if (i < level) {
|
|
result << ".";
|
|
}
|
|
}
|
|
}
|
|
// msg() << level << sp_arrow << result.str() << sp_arrow << "\n";
|
|
return result.str();
|
|
}
|
|
|
|
|
|
std::string extract_id(const std::string& attr)
|
|
{
|
|
std::smatch match {};
|
|
if (!std::regex_match(attr, match, id_attr_rgx)) {
|
|
throw Internal_error("Pattern for id attribute incorrect: " + q_(attr));
|
|
}
|
|
return match[1];
|
|
}
|
|
|
|
std::vector<Heading>
|
|
Document_class::insert_section_numbers(const std::string& marker, bool add_to_toc)
|
|
{
|
|
std::vector<int> levels(9, 0);
|
|
std::smatch match {};
|
|
std::string pattern = R"(<([\w-]+)\s*(.*?)>(.*?)MARKER\s*</span>\s*(.*?)<.*)";
|
|
pattern = string_replace(pattern, "MARKER", marker);
|
|
std::regex heading_rgx(pattern);
|
|
std::vector<std::pair<std::string, std::string>> modified_components {};
|
|
std::vector<Heading> headings;
|
|
bool include_toc = m_structure != docstruct_t::plain;
|
|
for (auto [src, result] : m_file_components) {
|
|
std::stringstream modified {};
|
|
for (std::string line : regex_split(result, newline_rgx, false)) {
|
|
if (std::regex_match(line, match, heading_rgx)) {
|
|
// xheading
|
|
std::string tag = match[1];
|
|
std::string id = extract_id(match[2]);
|
|
std::string title = match[3];
|
|
std::string heading = match[4];
|
|
const int level = html_tags[match[1]];
|
|
// msg() << line << sp_arrow << broken_bar << tag << broken_bar
|
|
// << level << broken_bar << "ATTR" << broken_bar << title << broken_bar
|
|
// << heading << broken_bar << "\n";
|
|
std::string number = increment_level(level, levels);
|
|
line = string_replace(line, marker, number);
|
|
if (include_toc) {
|
|
line = "<a href=\"#_c" + id + "\" draggable=\"false\">" + line + "</a>";
|
|
}
|
|
//msg() << attr << " " << number << " " << heading << "\n";
|
|
if (add_to_toc) {
|
|
m_toc_components.push_back({tag, id, number, heading});
|
|
}
|
|
Heading hd(level, src, number, id, number, heading);
|
|
headings.push_back(hd);
|
|
// msg() << line << "\n\n";
|
|
|
|
}
|
|
modified << line << "\n";
|
|
}
|
|
modified_components.push_back({src, modified.str()});
|
|
}
|
|
m_file_components = modified_components;
|
|
return headings;
|
|
}
|
|
|
|
int get_caption_number(std::map<std::string, int>& numbers, const std::string& label)
|
|
{
|
|
int result;
|
|
if (numbers.count(label) == 0) {
|
|
result = 1;
|
|
numbers[label] = 2;
|
|
} else {
|
|
result = numbers[label];
|
|
numbers[label]++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Document_class::insert_html_caption_numbers()
|
|
{
|
|
// msg() << "insert_html_caption_numbers\n";
|
|
std::regex chapter_rgx(R"(.*sectionnumber">(\d+)</span>.*)");
|
|
std::regex caption_rgx(R"(__CAPTION__(.*?)__CAPTION__(.*?)__CAPTION__)");
|
|
std::string current_chapter {};
|
|
std::map<std::string, int> current_caption_number {};
|
|
std::vector<std::pair<std::string, std::string>> modified_components {};
|
|
|
|
for (auto [src, result] : m_file_components) {
|
|
std::stringstream modified {};
|
|
//std::vector<std::string> references {};
|
|
for (std::string line : regex_split(result, newline_rgx, false)) {
|
|
std::smatch chapter_match {};
|
|
if (std::regex_match(line, chapter_match, chapter_rgx)) {
|
|
current_chapter = chapter_match[1];
|
|
// msg() << "Chapter " << current_chapter << " " << line << "\n";
|
|
current_caption_number.clear();
|
|
}
|
|
// msg() << "Line: " << line << "\n";
|
|
std::smatch caption_match {};
|
|
if (std::regex_search(line, caption_match, caption_rgx)) {
|
|
std::string label = caption_match[1];
|
|
std::string ctext = caption_match[2];
|
|
int caption_number = get_caption_number(current_caption_number, label);
|
|
// msg() << " caption: " << current_chapter << "." << caption_number << "\n";
|
|
// msg() << "Label: " << label << " Text: " << ctext << "\n";
|
|
std::stringstream ss {};
|
|
ss << label << " ";
|
|
if (!current_chapter.empty()) {
|
|
ss << current_chapter << ".";
|
|
}
|
|
ss << caption_number << ctext;
|
|
line = std::regex_replace(line, caption_rgx, trim(ss.str()));
|
|
// msg() << "line: " << line << "\n";
|
|
// references.push_back(line);
|
|
}
|
|
// if (std::regex_search(line, reference_rgx)) {
|
|
// references.push_back(line);
|
|
// }
|
|
modified << line << "\n";
|
|
}
|
|
modified_components.push_back({src, modified.str()});
|
|
//m_reference_sequences.push_back(references);
|
|
}
|
|
m_file_components = modified_components;
|
|
}
|
|
|
|
|
|
std::vector<std::tuple<std::string, std::string, std::string>>
|
|
Document_class::reference_list(const std::string& target_marker)
|
|
{
|
|
std::regex reference_rgx("__REF__");
|
|
std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
|
|
std::vector<std::tuple<std::string, std::string, std::string>> references {};
|
|
for (auto [src, result] : m_file_components) {
|
|
for (std::string line : regex_split(result, newline_rgx, false)) {
|
|
if (std::regex_search(line, reference_rgx)) {
|
|
references.push_back({target_marker, src, line});
|
|
} else {
|
|
std::smatch match {};
|
|
if (std::regex_search(line, match, target_rgx)) {
|
|
references.push_back({"", match[2], match[1]});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return references;
|
|
}
|
|
|
|
std::map<std::string, std::string> Document_class::id_to_caption_number()
|
|
{
|
|
std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
|
|
std::regex caption_rgx(R"(<div id=.*?>(\w+\s+[\w.]+))");
|
|
std::map<std::string, std::string> result {};
|
|
for (auto [src, ftext] : m_file_components) {
|
|
auto lines = regex_split(ftext, newline_rgx, false);
|
|
for (unsigned int i = 0; i < lines.size(); i++) {
|
|
std::smatch match {};
|
|
if (std::regex_search(lines[i], match, target_rgx)) {
|
|
std::string id = match[1];
|
|
unsigned int j = i + 1;
|
|
std::smatch caption_match {};
|
|
while (j < lines.size()) {
|
|
if (std::regex_search(lines[j], caption_match, caption_rgx)) {
|
|
std::string caption = caption_match[1];
|
|
// msg() << id << sp_arrow << caption << "\n";
|
|
result[id] = caption;
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
std::pair<int, int> offset_spec_to_count(const std::string& spec)
|
|
{
|
|
std::regex rgx(R"((\w+)\s*(\d*))");
|
|
std::smatch match {};
|
|
int offset = 1;
|
|
int dir = 1;
|
|
if (std::regex_match(spec, match, rgx)) {
|
|
std::string desc = match[1];
|
|
std::string value = match[2];
|
|
if (!value.empty()) {
|
|
offset = std::stoi(value);
|
|
}
|
|
if (desc == "before") {
|
|
dir = -1;
|
|
}
|
|
} else {
|
|
throw Internal_error("Reference offset spec incorrect: " + spec);
|
|
}
|
|
return {offset, dir};
|
|
}
|
|
|
|
std::map<std::string, std::vector<std::pair<std::string, std::string>>>
|
|
Document_class::html_references()
|
|
{
|
|
std::string target_marker = "@";
|
|
auto references = reference_list(target_marker);
|
|
// msg() << "\n";
|
|
for (unsigned int i = 0; i < references.size(); i++) {
|
|
auto [mark, name, ref] = references[i];
|
|
// msg() << " " << i << ": " << mark << " " << name << " " << ref << "\n";
|
|
}
|
|
// msg() << "\n";
|
|
auto id_captions = id_to_caption_number();
|
|
|
|
std::regex ref_rgx(R"(__REF__(.*?)__(.*?)__)");
|
|
std::string offset_spec;
|
|
std::string type;
|
|
|
|
std::map<std::string, std::vector<std::pair<std::string, std::string>>> result {};
|
|
|
|
for (unsigned int i = 0; i < references.size(); i++) {
|
|
auto [mark, name, ref] = references[i];
|
|
// msg() << " " << i << ": " << mark << " " << name << " " << ref << "\n";
|
|
if (mark == target_marker) {
|
|
std::smatch match {};
|
|
if (std::regex_search(ref, match, ref_rgx)) {
|
|
offset_spec = match[1];
|
|
type = match[2];
|
|
} else {
|
|
throw Internal_error("Reference form is incorrect: " + ref);
|
|
}
|
|
auto [offset, dir] = offset_spec_to_count(offset_spec);
|
|
int j = i + dir;
|
|
while (offset > 0 && j >= 0 && j < (int)references.size()) {
|
|
// msg() << " check: " << std::get<2>(references[j]) << "\n";
|
|
if (std::get<1>(references[j]) == type) {
|
|
offset--;
|
|
}
|
|
if (offset == 0) {
|
|
break;
|
|
}
|
|
j += dir;
|
|
}
|
|
if (j < 0 || j >= (int)references.size()) {
|
|
// msg() << "Not found: " << std::get<2>(references[i]) << "\n";
|
|
} else {
|
|
std::string id = std::get<2>(references[j]);
|
|
std::string line = std::get<2>(references[i]);
|
|
// msg() << "Ref: " << line << boldblack << " found: " << black << id << "\n";
|
|
std::string link = "<a href=\"#" + id + "\">" + id_captions[id] + "</a>";
|
|
// msg() << link << "\n";
|
|
std::string modified_line = std::regex_replace(line, ref_rgx, link);
|
|
// msg() << "modified_line: " << name << sp_arrow << modified_line << "\n";
|
|
result[name].push_back({line, modified_line});
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Document_class::resolve_html_references()
|
|
{
|
|
auto references = html_references();
|
|
std::vector<std::pair<std::string, std::string>> modified_components {};
|
|
for (auto [src, ftext] : m_file_components) {
|
|
// msg() << "resolve " << src << ":\n";
|
|
std::string modified_text = ftext;
|
|
for (auto [old_line, new_line] : references[src]) {
|
|
// msg() << old_line << sp_arrow << new_line << "\n";
|
|
modified_text = string_replace(modified_text, old_line, new_line);
|
|
}
|
|
modified_components.push_back({src, modified_text});
|
|
}
|
|
m_file_components = modified_components;
|
|
}
|
|
|
|
std::string Document_class::single_page_toc()
|
|
{
|
|
// <h1 id="_e9"><span class="sectionnumber">2.1</span> Section two.one</h1>
|
|
std::regex id_rgx("id=\"(.*?)\"");
|
|
std::stringstream ss {};
|
|
ss << "<div class=\"toctitle\">Contents</div>\n";
|
|
int minimum_depth = 7;
|
|
// Adjust left minimum margin based on whether parts or chapters are the top level:
|
|
for (auto [tag, id, number, heading] : m_toc_components) {
|
|
minimum_depth = std::min(minimum_depth, html_tags[tag]);
|
|
}
|
|
for (auto [tag, id, number, heading] : m_toc_components) {
|
|
int depth = html_tags[tag];
|
|
// msg() << "TOC: " << depth << " " << id << " " << number << " " << heading << "\n";
|
|
ss << "<div id=\"_c" << id << "\" class=\"tab" << depth - minimum_depth << " toc\"><a href=\"#" << id << "\">"
|
|
<< "<span class=\"tocnumber\">" << number << "</span>" << heading << "</a></div>\n";
|
|
}
|
|
// msg() << ss.str();
|
|
return ss.str();
|
|
}
|
|
|
|
|
|
elements_t Document_class::page(const std::string& body_text, const std::string& output_dir, int max_level)
|
|
{
|
|
(void)K::log(3);
|
|
std::string toc {}; // Calculate
|
|
bool text_only = m_structure == docstruct_t::plain;
|
|
|
|
// msg() << "output_dir: " << output_dir << "\n";
|
|
|
|
auto [css_filenames, js_filenames] =
|
|
html_auxiliary_files(output_dir, "", m_css_filenames);
|
|
|
|
std::string toc_text {};
|
|
|
|
elements_t body {};
|
|
html::add_title(body, m_title, m_logo);
|
|
if (m_structure == docstruct_t::book) {
|
|
html::add_nav(body, max_level);
|
|
}
|
|
html::add_text(body, body_text, toc_text, text_only);
|
|
// html::add_bottom_spacer(body);
|
|
html::add_status_bar(body, m_date, m_version, m_copyright);
|
|
if (!text_only) {
|
|
html::add_page_cache(body);
|
|
}
|
|
html::add_js_links(body, js_filenames, output_dir) ;
|
|
|
|
std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;
|
|
|
|
std::string page_title = m_page_title.empty() ? m_title : m_page_title;
|
|
|
|
elements_t page = html::make_page(
|
|
body, page_title,
|
|
css_text, css_filenames, resolved_font_names());
|
|
|
|
return page;
|
|
}
|
|
|
|
std::string Document_class::make_single_html_page(const std::string& output_directory)
|
|
{
|
|
(void)K::log(3);
|
|
std::stringstream ss {};;
|
|
|
|
if (m_structure != docstruct_t::plain) {
|
|
ss << single_page_toc();
|
|
}
|
|
for (auto [basename, page_text] : m_file_components) {
|
|
ss << "<!-- " << basename << ".kt -->\n\n" << page_text << "\n";
|
|
}
|
|
std::string single_page = to_string(page(ss.str(), output_directory, 10));
|
|
std::string output_file = output_directory + "/index.html";
|
|
// msg() << "single_page to " << output_file << "\n";
|
|
string_to_file(output_file, single_page);
|
|
return "";
|
|
|
|
}
|
|
|
|
std::string Document_class::make_html_navigation_structure(
|
|
const std::string& output_directory, std::vector<Heading> headings)
|
|
{
|
|
// Which structure path was taken is a DERIVED fact -- ":structure book"
|
|
// chose it -- so it belongs at "-v 1". It was a msg(), which printed it
|
|
// to stdout on every book render, ahead of the document.
|
|
(void)K::log(1, "Navigation format");
|
|
std::vector<std::string> pages_basenames {};
|
|
|
|
for (auto [basename, page_text] : m_file_components) {
|
|
pages_basenames.push_back(basename);
|
|
// A trace of the work, not a decision: verbosity 3.
|
|
(void)K::log(3, basename + ": " + abbrev(page_text, 96));
|
|
}
|
|
std::string pages_basenames_filename = m_cache_dir + "/_pages_basenames.js";
|
|
write_basenames_js_file(pages_basenames_filename, pages_basenames);
|
|
std::string help_content = make_help_page();
|
|
|
|
// Build embedded pagecache to avoid Same Origin Policy errors with file:// protocol
|
|
std::stringstream pagecache;
|
|
pagecache << "<div id=\"pagecache\" style=\"display:none\">\n";
|
|
for (auto [basename, page_text] : m_file_components) {
|
|
pagecache << "<div id=\"_page_" << basename
|
|
<< "\" class=\"content_page\">"
|
|
<< page_text << "</div>\n";
|
|
}
|
|
pagecache << "<div id=\"help_page\">" << help_content << "</div>\n";
|
|
pagecache << "<div id=\"search_page\"></div>\n";
|
|
pagecache << "</div>\n";
|
|
|
|
std::pair<int, std::string> toc_spec = make_navigation_table_of_contents(headings);
|
|
int max_level = std::get<0>(toc_spec);
|
|
std::string toc_body = std::get<1>(toc_spec);
|
|
std::string nav_body = "Navigation";
|
|
auto [all_css_files, all_js_files] =
|
|
html_auxiliary_files(output_directory, pages_basenames_filename, m_css_filenames);
|
|
std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;
|
|
std::stringstream t {};
|
|
t << html::page(
|
|
m_title, m_page_title, output_directory, nav_body, max_level, toc_body,
|
|
m_file_components[0].second,
|
|
m_date, m_version, m_copyright,
|
|
css_text, all_css_files, all_js_files,
|
|
resolved_font_names(),
|
|
m_logo);
|
|
std::string result = t.str();
|
|
result = string_replace(result,
|
|
"<div id=\"pagecache\"></div>",
|
|
pagecache.str());
|
|
std::string output_basename = m_machine.m_state.value("K_output_basename");
|
|
std::string output_filename = output_directory + "/index.html";
|
|
write(output_filename, result);
|
|
return "";
|
|
}
|
|
|
|
|
|
std::string Document_class::html()
|
|
{
|
|
(void)K::log(3);
|
|
// msg() << "HTML document\n";
|
|
// m_cache_dir = cache_directory(kt_root_filename, "_html_pages");
|
|
std::string output_directory = create_html_output_directories();
|
|
save_string_input_as_file();
|
|
process_input_files();
|
|
auto headings = insert_section_numbers();
|
|
insert_section_numbers("___NUM2___", false);
|
|
insert_html_caption_numbers();
|
|
resolve_html_references();
|
|
if (m_structure != docstruct_t::book) {
|
|
make_single_html_page(output_directory);
|
|
} else {
|
|
make_html_navigation_structure(output_directory, headings);
|
|
}
|
|
return "";
|
|
}
|