@link :section links to a section by its heading, with an alone value (from dev 831455f36fcd)

A section link names the TEXT of a heading: :section alone means the current
file, a value names the file of the same @document that has it. Resolved by
@document at assembly in html and tex; errors for a missing file, a missing
title, a doubled title and a link outside a @document. The Markdown
converter reads a Markdown anchor as a section link to the heading its slug
names. Also from dev: the converter's four silent defects fixed and --chapter
(9ae2c4cd), and the engine's alone-value error text.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Fable 5.1 committed 2026-10-12 20:55:39 +02:00
1 parent 32aa47d41c
commit f4dcdf3ffd
15 files changed
+594 -114

No files matched your search

+51
View File
@@ -1,3 +1,4 @@
#include "section_link.h"
#include <iomanip>
#include "util.h"
#include "error.h"
@@ -315,6 +316,7 @@ void Document_class::process_input_files()
//if (true || !in_modification_order(inpath, cached_filename, m_no_cache)) {
if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) {
Machine M = m_machine;
M.m_state.set("K_document_file", basename); // see document_latex.cpp
M.read(inpath);
html_text = trim(M.apply("html"));
html_text = trim(html::make_paragraphs(html_text)) + "\n";
@@ -686,6 +688,54 @@ elements_t Document_class::page(const std::string& body_text, const std::string&
return page;
}
// @link :section (section_link.h). Every heading element that carries an
// id -- numbered or not, so @h1..@h7 and @part are linkable too, which is
// why this reads the elements rather than the headings table that
// insert_section_numbers() builds from the numbered ones -- is a target;
// the Link klammer's href marker is resolved against them. A single page
// gets href="#id". A book's pages are embedded in index.html and shown by
// go_to_section(), so a link there is the "pagelink" anchor that
// sks/link/js/link.js binds: data-page and data-id, with the app's own
// "#page#id" hash as the href so the location survives without the script.
// Before 2026-10-12 html had no resolver and the marker was the href.
void Document_class::resolve_section_links()
{
static const std::regex heading_rgx(R"(<(h[1-7]|kt-chapter|kt-part)\b([^>]*)>(.*?)</\1>)");
static const std::regex link_rgx(R"(href="__seclink__(.*?)__KTSL__(.*?)__")");
std::vector<Section_target> table {};
strings_t files {};
for (const auto& [basename, text] : m_file_components) {
files.push_back(basename);
for (std::sregex_iterator it(text.cbegin(), text.cend(), heading_rgx), end; it != end; ++it) {
const std::smatch& m = *it;
std::string attrs = m[2];
std::smatch id_match {};
if (std::regex_search(attrs, id_match, id_attr_rgx)) {
table.push_back({normalize_section_title(m[3]), basename, id_match[1]});
}
}
}
const bool book = m_structure == docstruct_t::book;
for (auto& [basename, text] : m_file_components) {
std::string result {};
auto begin = text.cbegin();
for (std::sregex_iterator it(text.cbegin(), text.cend(), link_rgx), end; it != end; ++it) {
const std::smatch& m = *it;
result.append(begin, m[0].first);
const Section_target t = resolve_section_target(table, m[1], m[2], basename, files);
if (book) {
result += "href=\"#" + t.basename + "#" + t.id + "\" class=\"pagelink\" data-page=\""
+ t.basename + "\" data-id=\"" + t.id + "\"";
} else {
result += "href=\"#" + t.id + "\"";
}
begin = m[0].second;
}
result.append(begin, text.cend());
text = result;
}
}
std::string Document_class::make_single_html_page(const std::string& output_directory)
{
(void)K::log(3);
@@ -787,6 +837,7 @@ std::string Document_class::html()
}
auto headings = insert_section_numbers();
insert_section_numbers("___NUM2___", false);
resolve_section_links();
insert_html_caption_numbers();
resolve_html_references();
if (m_structure != docstruct_t::book) {