Files
klammertext/sks/document/document_class.cpp
Andy Kopra 37b6ba1c4f Verbatim-safe typography, ^-punctuation quoting, full-range ^UUUU^, polyglot html
Typographic transforms (---, quote pairs, ~) no longer touch verbatim
text: @c/@code/@source_listing content and ^'...'^ spans show exactly the
characters written. "^" before any punctuation character quotes it in
every target (the apostrophe excepted: ^' opens a literal span), with the
new :resolve option on @@@target declaring per-target renderings. The
^UUUU^ code-point form accepts 4-6 hex digits, the full Unicode range.
The html output and transform spellings are polyglot (XML-valid), in
preparation for an EPUB target. New suites: transform_test, character_test
(engine), typography_test (SKS).

(from dev 07ce5ea86a0a)
2026-08-23 20:48:26 +02:00

228 lines
7.8 KiB
C++

#include "util.h"
#include "machine.h"
#include "error.h"
#include "kutil.h"
#include "html_util.h"
#include "latex_util.h"
#include "locator.h"
// #include "state.h"
#include "document_class.h"
#include "show.h"
#include "log.h"
#include "file.h"
bool strbool(const std::string& s, const Locator& loc)
{
std::vector<std::string> values = {"false", "False", "0", "true", "True", "1"};
if (is_not_in(s, values)) {
std::stringstream ss {};
ss << "The value \"" << s << "\" is not a Boolean value. Possible values are:\n "
<< join(values, ", ");
throw Argument_error(ss.str(), loc);
}
bool result = (find(values.begin(), values.end(), s) - values.begin()) > 2;
return result;
}
Document_class::Document_class(Machine& machine) : Klammer_base(machine)
{
(void)K::log(3);
// show("document in main SKS");
m_machine = machine;
sloc = get("K_loc");
m_title = get("title");
m_subtitle = get("subtitle");
m_page_title = get("page_title");
m_author = get("author");
m_date = get("date");
// m_nav = strbool(get("nav"), loc);
// m_toc = strbool(get("toc"), loc);
m_structure = docstruct(get("structure"));
m_date = get("date");
m_version = get("version");
m_logo = get("logo");
m_text = get("text");
// Filename lists: standalone-"/" separation, existence rescue for
// spaces, ~ expansion (resolve_filename_list in mac/file.cpp); the
// existence checks resolve relative names against the input directory,
// as parse_input_filename() will.
// Filename-bearing values arrive ESCAPED for the target (the state
// stores escaped values so they flow correctly into output); a filename
// is programmatic use, so decode the markers first -- the C++ mirror of
// the Python-side unescape_ktesc() rule. Found 2026-08-22: under tex,
// ":files my_chapter" searched for "myKTESC005fKTESCchapter".
m_files = resolve_filename_list(ktesc_resolve(get("files")), get("K_input_dir"));
m_css_text = get("css_text");
m_css_filenames = resolve_filename_list(ktesc_resolve(get("css_files")), get("K_input_dir"));
m_include_sks_css = strbool(get("include_sks_css"), loc);
frame_background_color = get("frame_background_color");
frame_text_color = get("frame_text_color");
nav_background_color = get("nav_background_color");
nav_text_color = get("nav_text_color");
js_text = get("js_text");
m_js_filenames = resolve_filename_list(ktesc_resolve(get("js_files")), get("K_input_dir"));
m_include_sks_js = strbool(get("include_sks_js"), loc);
// font_dirs = word_split(get("font_dirs"));
auto strip_quotes = [](std::string s) {
if (s.size() >= 2 && s.front() == '"' && s.back() == '"')
s = s.substr(1, s.size() - 2);
return s;
};
m_serif_font = strip_quotes(get("serif_font"));
m_sans_font = strip_quotes(get("sans_font"));
m_mono_font = strip_quotes(get("mono_font"));
m_font_scale = stof(get("font_scale"));
if (!m_serif_font.empty())
m_resolved_serif = resolve_font(m_serif_font);
if (!m_sans_font.empty())
m_resolved_sans = resolve_font(m_sans_font);
if (!m_mono_font.empty())
m_resolved_mono = resolve_font(m_mono_font);
paper_size = get("paper_size");
landscape = strbool(get("landscape"), loc);
leading = stof(get("leading"));
point_size = stoi(get("point_size"));
ragged_right = strbool(get("ragged_right"), loc);
cover = get("cover");
prolog = get("prolog");
m_two_column = strbool(get("two_column"), loc);
m_copyright = get("copyright");
m_bottom = get("bottom");
create_output_directory = strbool(get("create_output_directory"), loc);
output_directory_name = get("output_directory_name");
resources_in_file = strbool(get("resources_in_file"), loc);
//use_pages_dir = strbool(get("use_pages_dir"), loc);
// m_toc_only = strbool(get("K_toc_only"), loc);
m_kt_root_filename = get("K_input_filename");
m_no_cache = !strbool(get("cache"), loc);
write_files = !strbool(get("K_stdout_only"), loc);
m_input_dir = get("K_input_dir");
if (m_text.size() == 0 and m_files.size() == 0) {
throw Argument_error(
"Neither the :text or :files options have values"); //,
//get("K_loc"));
}
/*
for (std::string file : m_files) {
sources.push_back(string_from_file(find_kt_file(file)));
}
*/
// std::cout << "IN DOCUMENT CLASS:\n" << m_state << "\n";
}
void Document_class::save_string_input_as_file()
{
if (!m_text.empty()) {
std::string input_text_filename = m_cache_dir + "/_text.kt";
// msg() << "Write string input to cache file: "
// << input_text_filename << "\n";
string_to_file(input_text_filename, m_text);
m_files.insert(m_files.begin(), input_text_filename);
}
}
fs::path parse_input_filename(const std::string& s, const std::string& input_dir)
{
// The two-class rule (2026-08-22), replacing a three-stage search whose
// second stage could quietly shadow a file beside the document:
//
// * a BARE WORD -- no directory separator, no extension -- is the kt/
// SHORTCUT: ":files X" MEANS kt/X.kt in the root file's directory,
// and nothing else. Missing is an immediate error whose message
// teaches the convention, not a fallback. The kt/ directory is the
// conventional home for a document's input files, and putting the
// meaning entirely in the name lets several root documents share it.
//
// * anything else is a real PATHNAME, absolute or resolved against the
// input file's directory (K_input_dir), so a document renders
// identically wherever ktext is run from.
//
// Which file a name landed on is a DERIVED value, so "-v 1" reports it.
fs::path p(s);
bool bare = s.find('/') == std::string::npos && p.extension().empty();
if (bare) {
fs::path in_kt = fs::path(input_dir) / "kt" / (s + ".kt");
if (!file_exists(in_kt.string())) {
throw Argument_error(
"The \":files\" name \"" + s + "\" is a bare word, which by "
"convention means the file kt/" + s + ".kt in the root "
"document's directory:\n"
" " + in_kt.string() + "\n"
"That file does not exist. Create it there, or write a real "
"pathname (a name with a directory or the \".kt\" extension) "
"to use a file elsewhere.",
Locator::none());
}
// The resolved path itself shows kt/ -- no label needed.
(void)K::log(1, "Input file \"" + s + "\": " + in_kt.string());
return in_kt;
}
if (p.extension() != ".kt") {
p += ".kt";
}
if (p.is_absolute()) {
return p;
}
fs::path in_input_dir = fs::path(input_dir) / p;
(void)K::log(1, "Input file \"" + s + "\": " + in_input_dir.string());
return in_input_dir;
}
void Document_class::write(const std::string& filename, const std::string& contents)
{
write_file(filename, contents, write_files);
}
void write_file(const std::string& filename, const std::string& contents, bool write_p)
{
if (write_p) {
string_to_file(filename, contents);
} else {
// Not writing: name the file that would have been written. A
// derived value, so "-v 1" -- and never on stdout, which carries
// the document itself.
(void)K::log(1, "Output filename:", filename);
}
}
/*
void output_msg(const std::string& msg)
{
std::cout << red;
(void)K::log(1, msg);
std::cout << black;
}
void Document_class::create_directory(const std::string& directory)
{
if (write_files) {
fs::create_directories(directory);
} else {
output_msg("Output directory: " + directory);
}
}
*/