2026-07-18 18:48:23 +02:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <iterator>
|
|
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
|
|
#include "util.h"
|
|
|
|
|
#include "kutil.h"
|
|
|
|
|
#include "error.h"
|
|
|
|
|
#include "file.h"
|
|
|
|
|
#include "html_util.h"
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "show.h"
|
|
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const attr_t& a)
|
|
|
|
|
{
|
|
|
|
|
if (std::holds_alternative<int>(a))
|
|
|
|
|
os << std::get<int>(a);
|
|
|
|
|
else if (std::holds_alternative<float>(a))
|
|
|
|
|
os << std::get<float>(a);
|
|
|
|
|
else if (std::holds_alternative<std::string>(a))
|
|
|
|
|
os << std::get<std::string>(a);
|
|
|
|
|
return os;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _indent = 0;
|
|
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const html_element_t& h)
|
|
|
|
|
{
|
|
|
|
|
if (std::holds_alternative<std::string>(h))
|
|
|
|
|
os << std::get<std::string>(h);
|
|
|
|
|
else if (std::holds_alternative<HTML>(h))
|
|
|
|
|
os << std::get<HTML>(h);
|
|
|
|
|
return os;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const elements_t& elements)
|
|
|
|
|
{
|
|
|
|
|
// for (const HTML& h : elements) {
|
|
|
|
|
for (const html_element_t& h : elements) {
|
|
|
|
|
/*n
|
|
|
|
|
if (std::holds_alternative<std::string>(h))
|
|
|
|
|
os << h;
|
|
|
|
|
else if (std::holds_alternative<HTML>(h))
|
|
|
|
|
os << h;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
os << h;
|
|
|
|
|
}
|
|
|
|
|
return os;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool attribute_with_value(attr_t attr)
|
|
|
|
|
{
|
|
|
|
|
return std::holds_alternative<std::string>(attr)
|
|
|
|
|
&& !(std::get<std::string>(attr)).empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const HTML& h)
|
|
|
|
|
{
|
|
|
|
|
bool void_element = h.void_element(h.m_tag);
|
|
|
|
|
//bool newline = !(h.void_element(h.m_tag) or h.empty_element(h.m_tag));
|
|
|
|
|
//bool empty_element = h.empty_element(h.m_tag) and trim(h.m_text).size() == 0;
|
|
|
|
|
|
|
|
|
|
os << "<" << h.m_tag;
|
|
|
|
|
for (auto [t,v] : h.m_attrs) {
|
|
|
|
|
os << " " << t;
|
|
|
|
|
if (attribute_with_value(v)) {
|
|
|
|
|
os << "=\"" << v << "\"";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
os << ">";
|
|
|
|
|
if (h.m_elements.size() > 0) {
|
|
|
|
|
os << "\n" << h.m_elements;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//if (!empty_element) {
|
|
|
|
|
if (trim(h.m_text).size() > 0) {
|
|
|
|
|
if (h.m_text.find(' ') != std::string::npos) {
|
|
|
|
|
os << "\n";
|
|
|
|
|
}
|
|
|
|
|
os << trim(h.m_text); // << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
//os << h.m_text;
|
|
|
|
|
std::string intext = "\n" + h.m_text;
|
|
|
|
|
std::string sp(_indent, ' ');
|
|
|
|
|
intext = std::regex_replace(intext, std::regex("\n"), "\n" + sp);
|
|
|
|
|
os << intext;
|
|
|
|
|
*/
|
|
|
|
|
//os << trim(h.m_text) << "\n";
|
|
|
|
|
// }
|
|
|
|
|
/*
|
|
|
|
|
if (true or newline) {
|
|
|
|
|
os << "\n";
|
|
|
|
|
_indent -= 2;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
//if (!void_element and !empty_element) {
|
|
|
|
|
if (!void_element) {
|
|
|
|
|
if (h.m_text.find(' ') != std::string::npos) {
|
|
|
|
|
os << "\n";
|
|
|
|
|
}
|
|
|
|
|
os << "</" << h.m_tag << ">";
|
|
|
|
|
}
|
|
|
|
|
os << "\n";
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
if (true or !void_element and !empty_element) {
|
|
|
|
|
//std::string sp(_indent, ' ');
|
|
|
|
|
//os << sp << "</" << h.m_tag << ">";
|
|
|
|
|
os << "\n</" << h.m_tag << ">";
|
|
|
|
|
}
|
|
|
|
|
os << "\n";
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
return os;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string to_string(elements_t e)
|
|
|
|
|
{
|
|
|
|
|
std::stringstream ss{};
|
|
|
|
|
ss << e;
|
|
|
|
|
return ss.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool HTML::void_element(std::string tag) const
|
|
|
|
|
{
|
|
|
|
|
if (tag == "!DOCTYPE")
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return std::find
|
|
|
|
|
(m_void_tags.begin(), m_void_tags.end(), tag) != m_void_tags.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HTML::empty_element(std::string tag) const
|
|
|
|
|
{
|
|
|
|
|
if (tag == preamble_tag)
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return std::find
|
|
|
|
|
(m_empty_tags.begin(), m_empty_tags.end(), tag) != m_empty_tags.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//HTML& HTML::attr(std::string name, std::variant<int, float, std::string> value)
|
|
|
|
|
HTML& HTML::attr(std::string name, attr_t value)
|
|
|
|
|
{
|
|
|
|
|
m_attrs.push_back({name, value});
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML& HTML::attrs(std::string s)
|
|
|
|
|
{
|
|
|
|
|
static const std::regex attr_rgx(R"((\w+)\s*=\s*\"(.*?)\")");
|
|
|
|
|
auto attrs_begin = std::sregex_iterator(s.begin(), s.end(), attr_rgx);
|
|
|
|
|
auto attrs_end = std::sregex_iterator();
|
|
|
|
|
//std::cout << "Found " << std::distance(attrs_begin, attrs_end) << " attrs\n";
|
|
|
|
|
for (std::sregex_iterator iter = attrs_begin; iter != attrs_end; ++iter) {
|
|
|
|
|
std::smatch match = *iter;
|
|
|
|
|
//std::cout << "attr: " << s << ": " << (*iter)[1] << " " << (*iter)[2] << "\n";
|
|
|
|
|
m_attrs.push_back({(*iter)[1], (*iter)[2]});
|
|
|
|
|
}
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Functions:
|
|
|
|
|
|
|
|
|
|
namespace html {
|
|
|
|
|
|
|
|
|
|
HTML elt(std::string tag)
|
|
|
|
|
{
|
|
|
|
|
HTML h(tag);
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML elt(std::string tag, std::string text)
|
|
|
|
|
{
|
|
|
|
|
HTML h(tag, trim(text));
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML elt(std::string tag, elements_t elements)
|
|
|
|
|
{
|
|
|
|
|
HTML h(tag, elements);
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML elt(std::string tag, HTML e, elements_t elts)
|
|
|
|
|
{
|
|
|
|
|
elements_t elements = {e};
|
|
|
|
|
elements.insert(elements.end(), elts.begin(), elts.end());
|
|
|
|
|
HTML h(tag, elements);
|
|
|
|
|
return h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML preamble()
|
|
|
|
|
{
|
|
|
|
|
return elt(preamble_tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
|
|
|
// <meta http-equiv="Pragma" content="no-cache" />
|
|
|
|
|
// <meta http-equiv="Expires" content="0" />
|
|
|
|
|
|
|
|
|
|
elements_t meta_elements()
|
|
|
|
|
{
|
|
|
|
|
elements_t result {
|
|
|
|
|
elt("meta")
|
|
|
|
|
.attr("name", "viewport")
|
|
|
|
|
.attr("content", "width=device-width, initial-scale=1"),
|
|
|
|
|
elt("meta")
|
|
|
|
|
.attr("charset", "UTF-8"),
|
|
|
|
|
// Does not conform to HTML 5:
|
|
|
|
|
// elt("meta")
|
|
|
|
|
// .attr("http-equiv", "Cache-Control")
|
|
|
|
|
// .attr("content", "no-cache, no-store, must-revalidate"),
|
|
|
|
|
// elt("meta")
|
|
|
|
|
// .attr("http-equiv", "Pragma")
|
|
|
|
|
// .attr("content", "no-cache"),
|
|
|
|
|
// elt("meta")
|
|
|
|
|
// .attr("http-equiv", "Expires")
|
|
|
|
|
// .attr("content", "0")
|
|
|
|
|
};
|
|
|
|
|
/*
|
|
|
|
|
result.push_back(
|
|
|
|
|
elt("link")
|
|
|
|
|
.attr("rel", "icon")
|
|
|
|
|
.attr("href", "/favicon.ico")
|
|
|
|
|
.attr("type", "image/x-icon"))
|
|
|
|
|
*/
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t local_font_elements(strings_t fontnames)
|
|
|
|
|
{
|
|
|
|
|
elements_t result {};
|
|
|
|
|
for (auto name : fontnames) {
|
|
|
|
|
// std::cout << "Font: " << name << "\n";
|
|
|
|
|
result.push_back(elt("link")
|
|
|
|
|
.attr("href", "fonts/" + name + ".css")
|
|
|
|
|
.attr("rel", "stylesheet"));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t javascript(std::string output_dir, strings_t js_filenames)
|
|
|
|
|
{
|
|
|
|
|
//elements_t result { jquery_elements() };
|
|
|
|
|
elements_t result {};
|
|
|
|
|
|
|
|
|
|
std::string js_dir = output_dir + "/js/";
|
|
|
|
|
fs::create_directory(js_dir);
|
|
|
|
|
for (auto fname : js_filenames) {
|
|
|
|
|
/*
|
|
|
|
|
std::string base = file_basename(fname) + ".js";
|
|
|
|
|
std::string js_page_filename = js_dir + base;
|
|
|
|
|
if (file_exists(js_page_filename)) {
|
|
|
|
|
fs::remove(js_page_filename);
|
|
|
|
|
}
|
|
|
|
|
fs::copy(fname, js_page_filename);
|
|
|
|
|
*/
|
|
|
|
|
result.push_back(elt("script")
|
|
|
|
|
.attr("src", fname)); //"js/" + base));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML head(std::string title,
|
|
|
|
|
std::string css,
|
|
|
|
|
strings_t css_filenames,
|
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/
Sync with klammertext-dev through b90b0e09:
- Argument types end to end: :python_cast values are applied (Python
@eval receives real bools/numbers/lists), argument values are
validated against their argtype patterns with the argtype's
description as the error message, argtypes can declare :default
(overridable per declaration), and parameterized type families are
supported: rest(N) casts a rest argument to an N-dimensional list
(bar-count = dimension).
- Unified indexed_range syntax (selector with parenthesized subsets,
composable mnemonic names) for table lines and spans.
- Table klammer: caption fonts fixed in both targets, :column_width /
:leading / :colsep wired, :colspan and :rowspan render (HTML
attributes; \multicolumn / \multirow), calculated cell values (:calc)
with prefix operators, display-precision semantics, :calc_format and
:decimal period|comma.
- Fonts: closed-world resolution on the Klammertext font store
(infrastructure in mac/font_store; no Google Fonts links or fetch).
Default fonts live in the top-level fnt/; additional fonts install
into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples,
preview, install — classification by font metadata). CSS font family
names are quoted (digit-initial families were silently lost).
- Environment files moved from mac/env/ to the top-level env/; shell
profiles source env/runtime.env. Dead per-host variants removed.
- Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
|
|
|
strings_t local_fonts)
|
2026-07-18 18:48:23 +02:00
|
|
|
{
|
|
|
|
|
elements_t elts = meta_elements();
|
|
|
|
|
// msg() << "ELTS sks: " << elts << "\n";
|
|
|
|
|
for (auto e : local_font_elements(local_fonts))
|
|
|
|
|
elts.push_back(e);
|
|
|
|
|
|
|
|
|
|
//std::string css_dir = output_dir + "/css/";
|
|
|
|
|
// No; a single file at the top level...?
|
|
|
|
|
|
|
|
|
|
// std::string css_dir = output_dir + "/";
|
|
|
|
|
// fs::create_directory(css_dir);
|
|
|
|
|
|
|
|
|
|
for (auto fname : css_filenames) {
|
|
|
|
|
// msg() << "CSS: " << fname << "\n";
|
|
|
|
|
/*
|
|
|
|
|
std::string base = file_basename(fname) + ".css";
|
|
|
|
|
std::string page_filename = css_dir + base;
|
|
|
|
|
if (!file_exists(page_filename)) {
|
|
|
|
|
fs::copy(fname, page_filename);
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
elts.push_back(elt("link")
|
|
|
|
|
.attr("rel", "stylesheet")
|
|
|
|
|
.attr("href", fname)); // base));
|
|
|
|
|
}
|
|
|
|
|
if (!css.empty()) {
|
|
|
|
|
elts.push_back(elt("style", css));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!title.empty()) {
|
|
|
|
|
elts.push_back(elt("title", trim(title)));
|
|
|
|
|
}
|
|
|
|
|
HTML result = elt("head", elts);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HTML button(std::string id, std::string name,
|
|
|
|
|
std::string attrib="", std::string value="")
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::string> hidden = {"Clear", "Back"};
|
|
|
|
|
HTML result = elt("span", string_replace(name, " ", " ")).id(id).cls("navb");
|
|
|
|
|
if (attrib != "") {
|
|
|
|
|
result.attr(attrib, value);
|
|
|
|
|
}
|
|
|
|
|
if (std::find(hidden.begin(), hidden.end(), name) != hidden.end()) {
|
|
|
|
|
result.attr("hidden", "");
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elements_t navtools(int maximum_depth=5)
|
|
|
|
|
{
|
|
|
|
|
elements_t result {};
|
|
|
|
|
for (auto [name, id] : std::vector<std::pair<std::string, std::string>>
|
|
|
|
|
{{ "Text only", "textshow" },
|
|
|
|
|
{ "Next", "next" },
|
|
|
|
|
{ "Previous", "previous" }}) {
|
|
|
|
|
//{ "Fit", "fit" }}) {
|
|
|
|
|
result.push_back(button(id, name));
|
|
|
|
|
}
|
|
|
|
|
for (int n = 1; n <= maximum_depth; n++) {
|
|
|
|
|
result.push_back(
|
|
|
|
|
elt("span", std::to_string(n)).cls("navb depthchoice").attr("data-depth", std::to_string(n)));
|
|
|
|
|
}
|
|
|
|
|
elements_t fit_elements = {
|
|
|
|
|
elt("input", "")
|
|
|
|
|
.id("fit_check")
|
|
|
|
|
.attr("name", "fit_check")
|
|
|
|
|
.attr("type", "checkbox")
|
|
|
|
|
.attr("active", "false")
|
|
|
|
|
.attr("autocomplete", "off"),
|
|
|
|
|
//.attr("checked", ""),
|
|
|
|
|
elt("label", "Fit")
|
|
|
|
|
.id("fit_check_label")
|
|
|
|
|
.attr("for", "fit_check")
|
|
|
|
|
};
|
|
|
|
|
result.push_back(elt("span", fit_elements)
|
|
|
|
|
.attr("id", "fit"));
|
|
|
|
|
|
|
|
|
|
elements_t linkshow_elements = {
|
|
|
|
|
elt("input", "")
|
|
|
|
|
.id("linkshow_check")
|
|
|
|
|
.attr("name", "linkshow_check")
|
|
|
|
|
.attr("type", "checkbox")
|
|
|
|
|
.attr("active", "false")
|
|
|
|
|
.attr("autocomplete", "off"),
|
|
|
|
|
//.attr("checked", ""),
|
|
|
|
|
elt("label", "Links")
|
|
|
|
|
.id("linkshow_check_label")
|
|
|
|
|
.attr("for", "linkshow_check")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result.push_back(elt("span", linkshow_elements)
|
|
|
|
|
.attr("id", "linkshow"));
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t search()
|
|
|
|
|
{
|
|
|
|
|
elements_t result {};
|
|
|
|
|
for (auto [name, id] : std::vector<std::pair<std::string, std::string>>
|
|
|
|
|
{{ "Clear", "search_clear" },
|
|
|
|
|
{ "Back", "search_back_label"},
|
|
|
|
|
{ "Search", "search_label" }
|
|
|
|
|
})
|
|
|
|
|
result.push_back(button(id, name));
|
|
|
|
|
elements_t search_input { elt("span", "Ⓧ").id("search_clear_input").attr("hidden", "") };
|
|
|
|
|
result.push_back(
|
|
|
|
|
elt("span", {
|
|
|
|
|
elt("input", "").id("search_input")
|
|
|
|
|
.attr("type", "text").attr("name", "search_input")
|
|
|
|
|
.attr("autocomplete", "off")
|
|
|
|
|
.attr("required", "required"),
|
|
|
|
|
elt("span", search_input).id("search_clear_input_box")
|
|
|
|
|
}).id("search_box"));
|
|
|
|
|
//result.push_back(button("help", "Help"));
|
|
|
|
|
/*
|
|
|
|
|
elements_t search_input {};
|
|
|
|
|
search_input.push_back(
|
|
|
|
|
elt("span", result).id("foldersearch"));
|
|
|
|
|
return search_input;
|
|
|
|
|
*/
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HTML navigation(int max_level) {
|
|
|
|
|
return elt("div", {
|
|
|
|
|
elt("span", navtools(max_level)).id("navtools"),
|
|
|
|
|
elt("span", { elt("span", search()).id("searchtools"),
|
|
|
|
|
elt("span", button("help", "Help"))})})
|
|
|
|
|
.id("nav");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t middle(std::string text,
|
|
|
|
|
//std::string nav,
|
|
|
|
|
std::string toc)
|
|
|
|
|
{
|
|
|
|
|
elements_t result {};
|
|
|
|
|
toc = trim(toc);
|
|
|
|
|
text = trim(text);
|
|
|
|
|
if (!toc.empty()) {
|
|
|
|
|
result.push_back(elt("div", toc)
|
|
|
|
|
.attr("id", "toc"));
|
|
|
|
|
|
|
|
|
|
result.push_back(elt("div")
|
|
|
|
|
.attr("id", "resizer"));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!text.empty()) {
|
|
|
|
|
text += "<div class=\"endspace\"></div>\n";
|
|
|
|
|
elements_t content {};
|
|
|
|
|
content.push_back(elt("div", text).attr("id", "text"));
|
|
|
|
|
//if (!nav.empty()) {
|
|
|
|
|
//content.push_back(elt("div", "<h1>SEARCH</h1>")
|
|
|
|
|
// .attr("id", "search_page"));
|
|
|
|
|
//content.push_back(elt("div", "<h1>HELP</h1>").attr("id", "help_page"));
|
|
|
|
|
//}
|
|
|
|
|
result.push_back(elt("div", content).attr("id", "content"));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
throw Argument_error("The text content of the HTML page is not defined");
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t status(std::string date, std::string version, std::string copyright)
|
|
|
|
|
{
|
|
|
|
|
elements_t result {};
|
|
|
|
|
if (!date.empty()) {
|
|
|
|
|
result.push_back(elt("span", date).cls("footeritem"));
|
|
|
|
|
}
|
|
|
|
|
if (!version.empty()) {
|
|
|
|
|
result.push_back(elt("span", version).cls("footeritem"));
|
|
|
|
|
}
|
|
|
|
|
if (!copyright.empty()) {
|
|
|
|
|
result.push_back(elt("span", copyright).cls("footeritem"));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t page(
|
|
|
|
|
std::string title,
|
|
|
|
|
std::string page_title,
|
|
|
|
|
std::string output_dir,
|
|
|
|
|
std::string nav,
|
|
|
|
|
int max_level,
|
|
|
|
|
std::string toc,
|
|
|
|
|
std::string text,
|
|
|
|
|
std::string date,
|
|
|
|
|
std::string version,
|
|
|
|
|
std::string copyright,
|
|
|
|
|
std::string css,
|
|
|
|
|
strings_t css_filenames,
|
|
|
|
|
strings_t js_filenames,
|
|
|
|
|
strings_t local_fonts,
|
|
|
|
|
std::string logo)
|
|
|
|
|
{
|
|
|
|
|
if (page_title.empty())
|
|
|
|
|
page_title = title;
|
|
|
|
|
elements_t body {};
|
|
|
|
|
if (!title.empty()) {
|
|
|
|
|
elements_t title_bar {};
|
|
|
|
|
title_bar.push_back(elt("span", trim(title)).attr("id", "title_text"));
|
|
|
|
|
if (!logo.empty()) {
|
|
|
|
|
title_bar.push_back(elt("span", trim(logo)).attr("id", "logo"));
|
|
|
|
|
}
|
|
|
|
|
body.push_back(elt("div", title_bar).attr("id", "title"));
|
|
|
|
|
}
|
|
|
|
|
if (!nav.empty()) {
|
|
|
|
|
body.push_back(navigation(max_level));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!nav.empty() and !toc.empty()) {
|
|
|
|
|
body.push_back(elt("div",
|
|
|
|
|
elt("div",
|
|
|
|
|
middle(text, toc))
|
|
|
|
|
.attr("id", "middle")));
|
|
|
|
|
} else {
|
|
|
|
|
//std::cout << "ONLY TEXT\n" << text << "\n\n";
|
|
|
|
|
body.push_back(elt("div",
|
|
|
|
|
elt("div",
|
|
|
|
|
text)
|
|
|
|
|
.attr("id", "text"))
|
|
|
|
|
.attr("id", "middle"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t status_elements = status(date, version, copyright);
|
|
|
|
|
if (!empty(status_elements)) {
|
|
|
|
|
body.push_back(elt("div", status_elements).attr("id", "status"));
|
|
|
|
|
}
|
|
|
|
|
//if (!status.empty())
|
|
|
|
|
// body.push_back(elt("div", trim(status)).attr("id", "status"));
|
|
|
|
|
|
|
|
|
|
if (!nav.empty() and !toc.empty())
|
|
|
|
|
body.push_back(elt("div", "").attr("id", "pagecache"));
|
|
|
|
|
|
|
|
|
|
if (!js_filenames.empty()) {
|
|
|
|
|
auto js_elements { javascript(output_dir, js_filenames) };
|
|
|
|
|
body.insert(std::end(body), std::begin(js_elements), std::end(js_elements));
|
|
|
|
|
}
|
|
|
|
|
elements_t result { preamble() };
|
|
|
|
|
if (page_title.empty())
|
|
|
|
|
page_title = title;
|
|
|
|
|
result.push_back(
|
|
|
|
|
elt("html",
|
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/
Sync with klammertext-dev through b90b0e09:
- Argument types end to end: :python_cast values are applied (Python
@eval receives real bools/numbers/lists), argument values are
validated against their argtype patterns with the argtype's
description as the error message, argtypes can declare :default
(overridable per declaration), and parameterized type families are
supported: rest(N) casts a rest argument to an N-dimensional list
(bar-count = dimension).
- Unified indexed_range syntax (selector with parenthesized subsets,
composable mnemonic names) for table lines and spans.
- Table klammer: caption fonts fixed in both targets, :column_width /
:leading / :colsep wired, :colspan and :rowspan render (HTML
attributes; \multicolumn / \multirow), calculated cell values (:calc)
with prefix operators, display-precision semantics, :calc_format and
:decimal period|comma.
- Fonts: closed-world resolution on the Klammertext font store
(infrastructure in mac/font_store; no Google Fonts links or fetch).
Default fonts live in the top-level fnt/; additional fonts install
into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples,
preview, install — classification by font metadata). CSS font family
names are quoted (digit-initial families were silently lost).
- Environment files moved from mac/env/ to the top-level env/; shell
profiles source env/runtime.env. Dead per-host variants removed.
- Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
|
|
|
{ head(page_title, css, css_filenames, local_fonts),
|
2026-07-18 18:48:23 +02:00
|
|
|
elt("body", body)
|
|
|
|
|
}).attr("lang", "en"));
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void add_title(elements_t& body, std::string title, std::string logo)
|
|
|
|
|
{
|
|
|
|
|
elements_t title_bar {};
|
|
|
|
|
title_bar.push_back(elt("span", trim(title)).attr("id", "title_text"));
|
|
|
|
|
if (!logo.empty()) {
|
|
|
|
|
title_bar.push_back(elt("span", trim(logo)).attr("id", "logo"));
|
|
|
|
|
}
|
|
|
|
|
body.push_back(elt("div", title_bar).attr("id", "title"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_nav(elements_t& body, int max_level)
|
|
|
|
|
{
|
|
|
|
|
body.push_back(navigation(max_level));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_text(elements_t& body, std::string text, std::string toc, bool text_only)
|
|
|
|
|
{
|
|
|
|
|
if (text_only) {
|
|
|
|
|
elements_t content {};
|
|
|
|
|
content.push_back(elt("div", text).attr("id", "text"));
|
|
|
|
|
body.push_back(
|
|
|
|
|
elt("div", elt("div", content).attr("id", "content")).attr("id", "middle"));
|
|
|
|
|
} else {
|
|
|
|
|
body.push_back(elt("div", middle(text, toc)).attr("id", "middle"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_bottom_spacer(elements_t& body)
|
|
|
|
|
{
|
|
|
|
|
body.push_back(elt("div").cls("endspace"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_status_bar(
|
|
|
|
|
elements_t& body, std::string date, std::string version, std::string copyright)
|
|
|
|
|
{
|
|
|
|
|
elements_t status_elements = status(date, version, copyright);
|
|
|
|
|
if (!empty(status_elements)) {
|
|
|
|
|
body.push_back(elt("div", status_elements).attr("id", "status"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_page_cache(elements_t& body)
|
|
|
|
|
{
|
|
|
|
|
body.push_back(elt("div", "").attr("id", "pagecache"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_js_links(elements_t& body, strings_t js_filenames, std::string output_dir)
|
|
|
|
|
{
|
|
|
|
|
if (!js_filenames.empty()) {
|
|
|
|
|
auto js_elements { javascript(output_dir, js_filenames) };
|
|
|
|
|
body.insert(std::end(body), std::begin(js_elements), std::end(js_elements));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements_t make_page(
|
|
|
|
|
elements_t body,
|
|
|
|
|
std::string page_title, std::string css,
|
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/
Sync with klammertext-dev through b90b0e09:
- Argument types end to end: :python_cast values are applied (Python
@eval receives real bools/numbers/lists), argument values are
validated against their argtype patterns with the argtype's
description as the error message, argtypes can declare :default
(overridable per declaration), and parameterized type families are
supported: rest(N) casts a rest argument to an N-dimensional list
(bar-count = dimension).
- Unified indexed_range syntax (selector with parenthesized subsets,
composable mnemonic names) for table lines and spans.
- Table klammer: caption fonts fixed in both targets, :column_width /
:leading / :colsep wired, :colspan and :rowspan render (HTML
attributes; \multicolumn / \multirow), calculated cell values (:calc)
with prefix operators, display-precision semantics, :calc_format and
:decimal period|comma.
- Fonts: closed-world resolution on the Klammertext font store
(infrastructure in mac/font_store; no Google Fonts links or fetch).
Default fonts live in the top-level fnt/; additional fonts install
into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples,
preview, install — classification by font metadata). CSS font family
names are quoted (digit-initial families were silently lost).
- Environment files moved from mac/env/ to the top-level env/; shell
profiles source env/runtime.env. Dead per-host variants removed.
- Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
|
|
|
strings_t css_filenames, strings_t local_fonts)
|
2026-07-18 18:48:23 +02:00
|
|
|
{
|
|
|
|
|
elements_t page { preamble() };
|
|
|
|
|
page.push_back(
|
|
|
|
|
elt("html",
|
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/
Sync with klammertext-dev through b90b0e09:
- Argument types end to end: :python_cast values are applied (Python
@eval receives real bools/numbers/lists), argument values are
validated against their argtype patterns with the argtype's
description as the error message, argtypes can declare :default
(overridable per declaration), and parameterized type families are
supported: rest(N) casts a rest argument to an N-dimensional list
(bar-count = dimension).
- Unified indexed_range syntax (selector with parenthesized subsets,
composable mnemonic names) for table lines and spans.
- Table klammer: caption fonts fixed in both targets, :column_width /
:leading / :colsep wired, :colspan and :rowspan render (HTML
attributes; \multicolumn / \multirow), calculated cell values (:calc)
with prefix operators, display-precision semantics, :calc_format and
:decimal period|comma.
- Fonts: closed-world resolution on the Klammertext font store
(infrastructure in mac/font_store; no Google Fonts links or fetch).
Default fonts live in the top-level fnt/; additional fonts install
into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples,
preview, install — classification by font metadata). CSS font family
names are quoted (digit-initial families were silently lost).
- Environment files moved from mac/env/ to the top-level env/; shell
profiles source env/runtime.env. Dead per-host variants removed.
- Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
|
|
|
{ head(page_title, css, css_filenames, local_fonts),
|
2026-07-18 18:48:23 +02:00
|
|
|
elt("body", body)
|
|
|
|
|
}).attr("lang", "en"));
|
|
|
|
|
return page;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool tag_is_block_element(std::string tag)
|
|
|
|
|
{
|
|
|
|
|
auto location = std::find(block_elements.begin(), block_elements.end(), tag);
|
|
|
|
|
return location != block_elements.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool block_element(std::string par)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3);
|
|
|
|
|
bool result = false;
|
|
|
|
|
par = trim(par);
|
|
|
|
|
if (par[0] == '<') {
|
|
|
|
|
static const std::regex element_rgx(R"(^\s*</?([^\s>]+).*?>)");
|
|
|
|
|
std::smatch match {};
|
|
|
|
|
std::regex_search(par, match, element_rgx);
|
|
|
|
|
result = tag_is_block_element(match[1]);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string make_paragraphs(std::string html_text)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3);
|
|
|
|
|
std::string result {};
|
|
|
|
|
//html_text = std::regex_replace(html_text, std::regex(R"((<pre .*?>)\s+)"), "$1\n");
|
|
|
|
|
//html_text = std::regex_replace(html_text, std::regex(R"(\s+</pre>)"), "\n</pre>");
|
|
|
|
|
static const std::regex paragraph_split_rgx(R"(\n *(\n *)+)");
|
|
|
|
|
for (auto par : regex_split(html_text, paragraph_split_rgx)) {
|
|
|
|
|
par = trim(par);
|
|
|
|
|
if (par.size() > 0) {
|
|
|
|
|
if (!block_element(par)) {
|
|
|
|
|
par = "<p>" + par + "</p>";
|
|
|
|
|
}
|
|
|
|
|
result += par + "\n\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string minimize_css(std::string src)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3);
|
|
|
|
|
std::string result = src;
|
|
|
|
|
//std::smatch match {};
|
|
|
|
|
std::regex line_end_space(R"(\\}[ ]+)", std::regex::awk);
|
|
|
|
|
result = regex_replace(result, line_end_space, "}");
|
|
|
|
|
std::regex whitespace(R"([\n ]+)", std::regex::awk);
|
|
|
|
|
result = regex_replace(result, whitespace, " ");
|
|
|
|
|
std::regex line_end(R"(}[ ]+)", std::regex::awk);
|
|
|
|
|
result = regex_replace(result, line_end, "}\n");
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string minimize_js(std::string src)
|
|
|
|
|
{
|
|
|
|
|
(void)K::log(3);
|
|
|
|
|
return src;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|