@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 -24
View File
@@ -1,3 +1,4 @@
#include "section_link.h"
#include "util.h"
#include "kutil.h"
#include "log.h"
@@ -20,15 +21,54 @@ std::string Document_class::create_tex_output_directories()
return output_directory;
}
// @link :section (section_link.h). Section.tex() announces every heading
// in a comment line; the links are resolved against them, every link on a
// line. The resolver this replaces (2026-10-12) read ONE link per line --
// std::regex_match once per line -- so a second @link ... :section @ on a
// line reached the pdf as a dead \hyperlink{__seclink__...}; it ignored
// :basename, and it wrote \hyperlink{} for an unknown title.
std::string Document_class::resolve_tex_section_links(
const std::vector<std::pair<std::string, std::string>>& pieces)
{
static const std::regex announce_rgx(
R"(%__sectionlink__(.*?)__KTSL__(.*?)__KTSL__(.*)__\n?)");
static const std::regex link_rgx(R"(__seclink__(.*?)__KTSL__(.*?)__)");
std::vector<Section_target> table {};
strings_t files {};
for (const auto& [basename, text] : pieces) {
if (!basename.empty()) files.push_back(basename);
for (const std::string& line : find_all(text, announce_rgx, 0)) {
std::smatch m {};
std::regex_match(line, m, announce_rgx);
table.push_back({normalize_section_title(m[3]), m[2], m[1]});
}
}
std::string result {};
for (const auto& [basename, text] : pieces) {
std::string stripped = std::regex_replace(text, announce_rgx, "");
auto begin = stripped.cbegin();
for (std::sregex_iterator it(stripped.cbegin(), stripped.cend(), link_rgx), end; it != end; ++it) {
const std::smatch& m = *it;
result.append(begin, m[0].first);
result += resolve_section_target(table, m[1], m[2], basename, files).id;
begin = m[0].second;
}
result.append(begin, stripped.cend());
}
return result;
}
std::string Document_class::tex()
{
(void)K::log(3);
create_tex_output_directories();
// :text content is already processed (klammers applied by the outer Machine).
// Use it directly; don't re-process through a sub-Machine.
std::string latex_text {};
// The pieces stay apart until the :section links are resolved, so an
// error can name the chapter that holds the link; "" is the :text.
std::vector<std::pair<std::string, std::string>> pieces {};
if (!m_text.empty()) {
latex_text = add_latex_caption_numbers(m_text);
pieces.push_back({"", add_latex_caption_numbers(m_text)});
}
if (m_files.size() > 0) {
for (auto input_filename : m_files) {
@@ -39,38 +79,25 @@ std::string Document_class::tex()
if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) {
Machine M = m_machine;
M.m_state.set("document_landscape", landscape ? "true" : "false"); // , "Landscape mode");
// The chapter's basename: Section.tex() builds stable
// \hypertarget names from it (a counter alone collided
// when the cache served some chapters and not others), and
// phases.check_section_links reads its presence as "inside
// a @document". Same in the html path.
M.m_state.set("K_document_file", basename);
M.read(inpath);
std::string processed = trim(M.apply("tex", false));
processed = add_latex_caption_numbers(processed);
write_to_cache(m_cache_dir, basename, processed);
latex_text += processed;
pieces.push_back({basename, processed});
} else {
latex_text += read_from_cache(m_cache_dir, basename);
pieces.push_back({basename, read_from_cache(m_cache_dir, basename)});
}
}
}
std::string latex_text = resolve_tex_section_links(pieces);
auto split_lines = regex_split(latex_text, std::regex(R"(\n)"), false);
latex_text = resolve_caption_references("latex", split_lines, latex_line_states(split_lines));
std::map<std::string, std::string> section_to_id {};
std::regex section_id_rgx(R"(%__sectionlink__(.*?)__(.*?)__)");
for (std::string id_section : find_all(latex_text, section_id_rgx, 0)) {
std::smatch match {};
std::regex_match(id_section, match, section_id_rgx);
section_to_id[match[2]] = match[1];
}
latex_text = std::regex_replace(latex_text, section_id_rgx, "");
std::regex seclink_rgx("(.*?)__seclink__(.*?)__(.*)");
auto lines = regex_split(latex_text, std::regex(R"(\n)"), false);
for (auto i = 0ul; i < lines.size(); i++) {
if (lines[i].find("__seclink__") != std::string::npos) {
std::smatch match {};
std::regex_match(lines[i], match, seclink_rgx);
std::stringstream ss {};
ss << match[1] << section_to_id[match[2]] << match[3];
lines[i] = ss.str();
}
}
latex_text = join(lines, "\n");
latex_text = string_replace(latex_text, "___AT___", "^@");
// @typeface: collect what the document asked for, declare each as a
// \newfontfamily switch command in the preamble, and replace every