aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/COPYRIGHT2
-rw-r--r--src/sisudoc/COPYRIGHT2
-rw-r--r--src/sisudoc/abstraction/package.d86
-rw-r--r--src/sisudoc/abstraction/ssp.d432
-rw-r--r--src/sisudoc/conf/compile_time_info.d2
-rw-r--r--src/sisudoc/io_in/paths_source.d16
-rw-r--r--src/sisudoc/io_in/read_config_files.d2
-rw-r--r--src/sisudoc/io_in/read_source_files.d2
-rw-r--r--src/sisudoc/io_in/read_zip_pod.d401
-rw-r--r--src/sisudoc/io_out/cgi_sqlite_search_form.d1959
-rw-r--r--src/sisudoc/io_out/create_abstraction_db.d355
-rw-r--r--src/sisudoc/io_out/create_zip_file.d2
-rw-r--r--src/sisudoc/io_out/curate/metadoc_curate.d (renamed from src/sisudoc/meta/metadoc_curate.d)4
-rw-r--r--src/sisudoc/io_out/curate/metadoc_curate_authors.d (renamed from src/sisudoc/meta/metadoc_curate_authors.d)4
-rw-r--r--src/sisudoc/io_out/curate/metadoc_curate_topics.d (renamed from src/sisudoc/meta/metadoc_curate_topics.d)4
-rw-r--r--src/sisudoc/io_out/defaults.d2
-rw-r--r--src/sisudoc/io_out/epub3.d2
-rw-r--r--src/sisudoc/io_out/html.d2
-rw-r--r--src/sisudoc/io_out/html_snippet.d2
-rw-r--r--src/sisudoc/io_out/hub.d2
-rw-r--r--src/sisudoc/io_out/latex.d27
-rw-r--r--src/sisudoc/io_out/metadata.d2
-rw-r--r--src/sisudoc/io_out/odt.d5
-rw-r--r--src/sisudoc/io_out/package.d2
-rw-r--r--src/sisudoc/io_out/paths_output.d2
-rw-r--r--src/sisudoc/io_out/rgx.d2
-rw-r--r--src/sisudoc/io_out/rgx_latex.d2
-rw-r--r--src/sisudoc/io_out/rgx_xhtml.d2
-rw-r--r--src/sisudoc/io_out/skel.d2
-rw-r--r--src/sisudoc/io_out/source_pod.d56
-rw-r--r--src/sisudoc/io_out/sqlite.d30
-rw-r--r--src/sisudoc/io_out/text.d2
-rw-r--r--src/sisudoc/io_out/xmls.d4
-rw-r--r--src/sisudoc/io_out/xmls_css.d202
-rw-r--r--src/sisudoc/meta/conf_make_meta_json.d2
-rw-r--r--src/sisudoc/meta/conf_make_meta_structs.d2
-rw-r--r--src/sisudoc/meta/conf_make_meta_yaml.d2
-rw-r--r--src/sisudoc/meta/defaults.d2
-rw-r--r--src/sisudoc/meta/doc_debugs.d2
-rw-r--r--src/sisudoc/meta/metadoc.d4
-rw-r--r--src/sisudoc/meta/metadoc_from_src.d21
-rw-r--r--src/sisudoc/meta/metadoc_from_src_functions.d3
-rw-r--r--src/sisudoc/meta/metadoc_object_setter.d3
-rw-r--r--src/sisudoc/meta/metadoc_show_config.d2
-rw-r--r--src/sisudoc/meta/metadoc_show_make.d2
-rw-r--r--src/sisudoc/meta/metadoc_show_metadata.d2
-rw-r--r--src/sisudoc/meta/metadoc_show_summary.d2
-rw-r--r--src/sisudoc/meta/package.d2
-rw-r--r--src/sisudoc/meta/rgx.d2
-rw-r--r--src/sisudoc/meta/rgx_files.d2
-rw-r--r--src/sisudoc/meta/rgx_yaml.d2
-rw-r--r--src/sisudoc/share/defaults.d2
-rw-r--r--[-rwxr-xr-x]src/sisudoc/spine.d280
53 files changed, 1912 insertions, 2050 deletions
diff --git a/src/COPYRIGHT b/src/COPYRIGHT
index 2a71121..79ae0e9 100644
--- a/src/COPYRIGHT
+++ b/src/COPYRIGHT
@@ -5,7 +5,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah
+ - Copyright: (C) 2015 - 2026 Ralph Amissah
- code under src/* src/sisudoc/*
- License: AGPL 3 or later:
diff --git a/src/sisudoc/COPYRIGHT b/src/sisudoc/COPYRIGHT
index 2a71121..79ae0e9 100644
--- a/src/sisudoc/COPYRIGHT
+++ b/src/sisudoc/COPYRIGHT
@@ -5,7 +5,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah
+ - Copyright: (C) 2015 - 2026 Ralph Amissah
- code under src/* src/sisudoc/*
- License: AGPL 3 or later:
diff --git a/src/sisudoc/abstraction/package.d b/src/sisudoc/abstraction/package.d
new file mode 100644
index 0000000..645a514
--- /dev/null
+++ b/src/sisudoc/abstraction/package.d
@@ -0,0 +1,86 @@
+/+
+- Name: SisuDoc Spine, Doc Reform [a part of]
+ - Description: documents, structuring, processing, publishing, search
+ - static content generator
+
+ - Author: Ralph Amissah
+ [ralph.amissah@gmail.com]
+
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
+
+ - License: AGPL 3 or later:
+
+ Spine (SiSU), a framework for document structuring, publishing and
+ search
+
+ Copyright (C) Ralph Amissah
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU AFERO General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see [https://www.gnu.org/licenses/].
+
+ If you have Internet connection, the latest version of the AGPL should be
+ available at these locations:
+ [https://www.fsf.org/licensing/licenses/agpl.html]
+ [https://www.gnu.org/licenses/agpl.html]
+
+ - Spine (by Doc Reform, related to SiSU) uses standard:
+ - docReform markup syntax
+ - standard SiSU markup syntax with modified headers and minor modifications
+ - docReform object numbering
+ - standard SiSU object citation numbering & system
+
+ - Homepages:
+ [https://www.sisudoc.org]
+ [https://www.doc-reform.org]
+
+ - Git
+ [https://git.sisudoc.org/]
+
++/
+/++
+ sisudoc.abstraction - public surface of the document-abstraction
+ library.
+
+ Pipeline position: markup -> abstraction -> output.
+ This package is the abstraction stage. The output stage lives in
+ sisudoc.io_out and consumes the values produced here.
+
+ Entry points:
+ - spineAbstraction!() (from sisudoc.meta.metadoc) - A-layer:
+ builds the in-memory document object model from a manifest
+ (pod path, .sst path). Reads the document body, parses YAML
+ headers, returns a struct with .abstraction (the object
+ model) and .matters (the conf/meta/src wrapper).
+ - docAbstraction!() (from sisudoc.meta.metadoc_from_src) -
+ B-layer: builds the abstraction from already-loaded body
+ text plus a pre-built ConfComposite. Pure, no file I/O.
+
+ The A-layer is a thin wrapper over the B-layer; consumers that
+ want a minimal-dependency entry should use docAbstraction!()
+ directly.
+
+ Serialisation:
+ - sisudoc.abstraction.ssp - PEG-parsable text serialisation of
+ the abstraction (the .ssp format). See specs/doc-abstraction-
+ format/ for the format reference.
+
+ This file is a re-export-only surface. No logic lives here; it
+ exists so external consumers can `import sisudoc.abstraction;` and
+ reach the entry points without depending on spine's directory
+ layout.
++/
+module sisudoc.abstraction;
+@safe:
+public import sisudoc.meta.metadoc; // spineAbstraction (A-layer)
+public import sisudoc.meta.metadoc_from_src; // docAbstraction (B-layer)
+public import sisudoc.abstraction.ssp; // spineAbstractionTxt (.ssp)
diff --git a/src/sisudoc/abstraction/ssp.d b/src/sisudoc/abstraction/ssp.d
new file mode 100644
index 0000000..6eecef0
--- /dev/null
+++ b/src/sisudoc/abstraction/ssp.d
@@ -0,0 +1,432 @@
+/+
+- Name: SisuDoc Spine, Doc Reform [a part of]
+ - Description: documents, structuring, processing, publishing, search
+ - static content generator
+
+ - Author: Ralph Amissah
+ [ralph.amissah@gmail.com]
+
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
+
+ - License: AGPL 3 or later:
+
+ Spine (SiSU), a framework for document structuring, publishing and
+ search
+
+ Copyright (C) Ralph Amissah
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU AFERO General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see [https://www.gnu.org/licenses/].
+
+ If you have Internet connection, the latest version of the AGPL should be
+ available at these locations:
+ [https://www.fsf.org/licensing/licenses/agpl.html]
+ [https://www.gnu.org/licenses/agpl.html]
+
+ - Spine (by Doc Reform, related to SiSU) uses standard:
+ - docReform markup syntax
+ - standard SiSU markup syntax with modified headers and minor modifications
+ - docReform object numbering
+ - standard SiSU object citation numbering & system
+
+ - Homepages:
+ [https://www.sisudoc.org]
+ [https://www.doc-reform.org]
+
+ - Git
+ [https://git.sisudoc.org/]
+
++/
+module sisudoc.abstraction.ssp;
+@safe:
+
+/+ ↓ write document abstraction as human-readable .ssp text file +/
+template spineAbstractionTxt() {
+ import std.conv : to;
+ import std.digest : toHexString;
+ import std.file;
+ import std.path;
+ import std.stdio;
+ import std.string;
+ import std.array;
+
+ void spineAbstractionTxt(D)(D doc) {
+ auto doc_abstraction = doc.abstraction;
+ auto doc_matters = doc.matters;
+ string[] output;
+
+ /+ ↓ header comment +/
+ output ~= "% SiSU Document Abstraction v0.1";
+ output ~= "% Source: " ~ doc_matters.src.filename;
+ output ~= "";
+
+ /+ ↓ @meta block +/
+ output ~= "@meta {";
+ auto meta = doc_matters.conf_make_meta.meta;
+ if (meta.title_main.length > 0)
+ output ~= " title.main: " ~ meta.title_main;
+ if (meta.title_subtitle.length > 0)
+ output ~= " title.subtitle: " ~ meta.title_subtitle;
+ if (meta.title_full.length > 0)
+ output ~= " title.full: " ~ meta.title_full;
+ if (meta.title_language.length > 0)
+ output ~= " title.language: " ~ meta.title_language;
+ if (meta.creator_author.length > 0)
+ output ~= " creator.author: " ~ meta.creator_author;
+ if (meta.creator_author_surname.length > 0)
+ output ~= " creator.author_surname: " ~ meta.creator_author_surname;
+ if (meta.creator_author_surname_fn.length > 0)
+ output ~= " creator.author_surname_fn: " ~ meta.creator_author_surname_fn;
+ if (meta.creator_author_email.length > 0)
+ output ~= " creator.author_email: " ~ meta.creator_author_email;
+ if (meta.creator_illustrator.length > 0)
+ output ~= " creator.illustrator: " ~ meta.creator_illustrator;
+ if (meta.creator_translator.length > 0)
+ output ~= " creator.translator: " ~ meta.creator_translator;
+ if (meta.date_published.length > 0)
+ output ~= " date.published: " ~ meta.date_published;
+ if (meta.date_created.length > 0)
+ output ~= " date.created: " ~ meta.date_created;
+ if (meta.date_issued.length > 0)
+ output ~= " date.issued: " ~ meta.date_issued;
+ if (meta.date_available.length > 0)
+ output ~= " date.available: " ~ meta.date_available;
+ if (meta.date_modified.length > 0)
+ output ~= " date.modified: " ~ meta.date_modified;
+ if (meta.date_valid.length > 0)
+ output ~= " date.valid: " ~ meta.date_valid;
+ if (meta.rights_copyright.length > 0)
+ output ~= " rights.copyright: " ~ meta.rights_copyright;
+ if (meta.rights_license.length > 0)
+ output ~= " rights.license: " ~ meta.rights_license;
+ if (meta.classify_topic_register.length > 0)
+ output ~= " classify.topic_register: " ~ meta.classify_topic_register;
+ if (meta.classify_subject.length > 0)
+ output ~= " classify.subject: " ~ meta.classify_subject;
+ if (meta.classify_keywords.length > 0)
+ output ~= " classify.keywords: " ~ meta.classify_keywords;
+ if (meta.classify_loc.length > 0)
+ output ~= " classify.loc: " ~ meta.classify_loc;
+ if (meta.classify_dewey.length > 0)
+ output ~= " classify.dewey: " ~ meta.classify_dewey;
+ if (meta.identifier_isbn.length > 0)
+ output ~= " identifier.isbn: " ~ meta.identifier_isbn;
+ if (meta.identifier_oclc.length > 0)
+ output ~= " identifier.oclc: " ~ meta.identifier_oclc;
+ if (meta.language_document.length > 0)
+ output ~= " language.document: " ~ meta.language_document;
+ if (meta.notes_abstract.length > 0)
+ output ~= " notes.abstract: " ~ meta.notes_abstract;
+ if (meta.notes_description.length > 0)
+ output ~= " notes.description: " ~ meta.notes_description;
+ if (meta.notes_summary.length > 0)
+ output ~= " notes.summary: " ~ meta.notes_summary;
+ output ~= "}";
+ output ~= "";
+
+ /+ ↓ @make block +/
+ output ~= "@make {";
+ auto make = doc_matters.conf_make_meta.make;
+ if (make.doc_type.length > 0)
+ output ~= " doc_type: " ~ make.doc_type;
+ if (make.auto_num_top_at_level.length > 0)
+ output ~= " auto_num_top_at_level: " ~ make.auto_num_top_at_level;
+ output ~= " auto_num_top_lv: " ~ make.auto_num_top_lv.to!string;
+ output ~= " auto_num_depth: " ~ make.auto_num_depth.to!string;
+ output ~= "}";
+ output ~= "";
+
+ /+ ↓ @doc_has block +/
+ output ~= "@doc_has {";
+ output ~= " inline_links: " ~ doc_matters.has.inline_links.to!string;
+ output ~= " inline_notes_reg: " ~ doc_matters.has.inline_notes_reg.to!string;
+ output ~= " inline_notes_star: " ~ doc_matters.has.inline_notes_star.to!string;
+ output ~= " tables: " ~ doc_matters.has.tables.to!string;
+ output ~= " codeblocks: " ~ doc_matters.has.codeblocks.to!string;
+ output ~= " images: " ~ doc_matters.has.images.to!string;
+ output ~= " poems: " ~ doc_matters.has.poems.to!string;
+ output ~= " groups: " ~ doc_matters.has.groups.to!string;
+ output ~= " blocks: " ~ doc_matters.has.blocks.to!string;
+ output ~= " quotes: " ~ doc_matters.has.quotes.to!string;
+ output ~= "}";
+ output ~= "";
+
+ /+ ↓ document sections +/
+ string[] section_order = ["head", "toc", "body", "endnotes",
+ "glossary", "bibliography", "bookindex", "blurb"];
+
+ foreach (section; section_order) {
+ if (section !in doc_abstraction) continue;
+ auto section_objs = doc_abstraction[section];
+ if (section_objs.length == 0) continue;
+
+ output ~= "@" ~ section ~ " {";
+ output ~= "";
+
+ foreach (obj; section_objs) {
+ /+ ↓ object declaration line +/
+ string obj_decl = "[" ~ obj.metainfo.ocn.to!string ~ "] ";
+
+ if (obj.metainfo.is_a == "heading") {
+ string lev = obj.metainfo.marked_up_level;
+ obj_decl ~= "heading :" ~ lev;
+ if (obj.metainfo.identifier.length > 0
+ && obj.metainfo.identifier != obj.metainfo.ocn.to!string) {
+ obj_decl ~= " " ~ obj.metainfo.identifier;
+ }
+ } else {
+ obj_decl ~= obj.metainfo.is_a;
+ }
+ output ~= obj_decl;
+
+ /+ ↓ properties (only non-default values) +/
+ if (obj.metainfo.is_of_part.length > 0)
+ output ~= ".part: " ~ obj.metainfo.is_of_part;
+ if (obj.metainfo.is_of_section.length > 0
+ && obj.metainfo.is_of_section != section)
+ output ~= ".section: " ~ obj.metainfo.is_of_section;
+ if (obj.metainfo.parent_ocn != 0)
+ output ~= ".parent: " ~ obj.metainfo.parent_ocn.to!string;
+ if (obj.metainfo.last_descendant_ocn != 0)
+ output ~= ".last_descendant: " ~ obj.metainfo.last_descendant_ocn.to!string;
+
+ /+ ↓ child headings (from pre-computed map) +/
+ if (obj.metainfo.children_headings.length > 0) {
+ string[] ch;
+ foreach (c; obj.metainfo.children_headings) {
+ ch ~= c.to!string;
+ }
+ output ~= ".children: " ~ ch.join(" ");
+ }
+
+ /+ ↓ ancestors (only if non-zero) +/
+ {
+ bool has_anc = false;
+ foreach (a; obj.metainfo.markedup_ancestors) {
+ if (a != 0) { has_anc = true; break; }
+ }
+ if (has_anc) {
+ string anc;
+ foreach (i, a; obj.metainfo.markedup_ancestors) {
+ if (i > 0) anc ~= " ";
+ anc ~= a.to!string;
+ }
+ output ~= ".ancestors: " ~ anc;
+ }
+ }
+ /+ ↓ collapsed ancestors (only if non-zero) +/
+ {
+ bool has_anc_c = false;
+ foreach (a; obj.metainfo.collapsed_ancestors) {
+ if (a != 0) { has_anc_c = true; break; }
+ }
+ if (has_anc_c) {
+ string anc;
+ foreach (i, a; obj.metainfo.collapsed_ancestors) {
+ if (i > 0) anc ~= " ";
+ anc ~= a.to!string;
+ }
+ output ~= ".ancestors_collapsed: " ~ anc;
+ }
+ }
+ /+ ↓ dom structure status (only if non-zero) +/
+ {
+ bool has_dom = false;
+ foreach (d; obj.metainfo.dom_structure_markedup_tags_status) {
+ if (d != 0) { has_dom = true; break; }
+ }
+ if (has_dom) {
+ string ds;
+ foreach (i, d; obj.metainfo.dom_structure_markedup_tags_status) {
+ if (i > 0) ds ~= " ";
+ ds ~= d.to!string;
+ }
+ output ~= ".dom_status: " ~ ds;
+ }
+ }
+ {
+ bool has_dom_c = false;
+ foreach (d; obj.metainfo.dom_structure_collapsed_tags_status) {
+ if (d != 0) { has_dom_c = true; break; }
+ }
+ if (has_dom_c) {
+ string ds;
+ foreach (i, d; obj.metainfo.dom_structure_collapsed_tags_status) {
+ if (i > 0) ds ~= " ";
+ ds ~= d.to!string;
+ }
+ output ~= ".dom_status_collapsed: " ~ ds;
+ }
+ }
+
+ if (obj.metainfo.heading_lev_collapsed < 9)
+ output ~= ".heading_lev_collapsed: " ~ obj.metainfo.heading_lev_collapsed.to!string;
+ if (obj.metainfo.parent_lev_markup != 0)
+ output ~= ".parent_lev: " ~ obj.metainfo.parent_lev_markup.to!string;
+ if (obj.metainfo.dummy_heading)
+ output ~= ".dummy: true";
+ if (obj.metainfo.object_number_off)
+ output ~= ".ocn_off: true";
+ if (obj.metainfo.o_n_type != 0)
+ output ~= ".o_n_type: " ~ obj.metainfo.o_n_type.to!string;
+ if (obj.metainfo.is_of_type.length > 0)
+ output ~= ".is_of_type: " ~ obj.metainfo.is_of_type;
+ if (obj.metainfo.attrib.length > 0)
+ output ~= ".attrib: " ~ obj.metainfo.attrib;
+ if (obj.metainfo.lang.length > 0)
+ output ~= ".meta_lang: " ~ obj.metainfo.lang;
+ if (obj.metainfo.syntax.length > 0)
+ output ~= ".meta_syntax: " ~ obj.metainfo.syntax;
+
+ /+ ↓ sha256 digest +/
+ {
+ bool has_sha = false;
+ foreach (b; obj.metainfo.sha256) {
+ if (b != 0) { has_sha = true; break; }
+ }
+ if (has_sha) {
+ output ~= ".sha256: " ~ obj.metainfo.sha256.toHexString.to!string;
+ }
+ }
+
+ /+ ↓ text attributes +/
+ if (obj.attrib.indent_base != 0 || obj.attrib.indent_hang != 0)
+ output ~= ".indent: " ~ obj.attrib.indent_base.to!string
+ ~ " " ~ obj.attrib.indent_hang.to!string;
+ if (obj.attrib.bullet)
+ output ~= ".bullet: true";
+ if (obj.attrib.language.length > 0)
+ output ~= ".lang: " ~ obj.attrib.language;
+
+ /+ ↓ has flags +/
+ {
+ string[] has_flags;
+ if (obj.has.inline_links) has_flags ~= "links";
+ if (obj.has.inline_notes_reg) has_flags ~= "notes_reg";
+ if (obj.has.inline_notes_star) has_flags ~= "notes_star";
+ if (obj.has.images) has_flags ~= "images";
+ if (obj.has.image_without_dimensions) has_flags ~= "images_no_dim";
+ if (has_flags.length > 0)
+ output ~= ".has: " ~ has_flags.join(" ");
+ }
+
+ /+ ↓ table properties +/
+ if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) {
+ output ~= ".table_cols: " ~ obj.table.number_of_columns.to!string;
+ if (obj.table.column_widths.length > 0) {
+ string[] ws;
+ foreach (w; obj.table.column_widths) ws ~= w.to!string;
+ output ~= ".table_widths: " ~ ws.join(" ");
+ }
+ if (obj.table.column_aligns.length > 0) {
+ output ~= ".table_aligns: " ~ obj.table.column_aligns.join(" ");
+ }
+ if (obj.table.heading)
+ output ~= ".table_header: true";
+ if (obj.table.walls)
+ output ~= ".table_walls: true";
+ }
+
+ /+ ↓ code block properties +/
+ if (obj.metainfo.is_a == "code") {
+ if (obj.code_block.syntax.length > 0)
+ output ~= ".code_syntax: " ~ obj.code_block.syntax;
+ if (obj.code_block.linenumbers)
+ output ~= ".code_linenumbers: true";
+ }
+
+ /+ ↓ stow (extracted links) +/
+ if (obj.stow.link.length > 0) {
+ foreach (lnk; obj.stow.link) {
+ if (lnk.length > 0)
+ output ~= ".stow_link: " ~ lnk;
+ }
+ }
+
+ /+ ↓ tag properties +/
+ if (obj.tags.in_segment_html.length > 0)
+ output ~= ".segment: " ~ obj.tags.in_segment_html;
+ if (obj.tags.anchor_tag_html.length > 0
+ && obj.tags.anchor_tag_html != obj.tags.in_segment_html)
+ output ~= ".anchor: " ~ obj.tags.anchor_tag_html;
+ if (obj.tags.segname_prev.length > 0)
+ output ~= ".segment_prev: " ~ obj.tags.segname_prev;
+ if (obj.tags.segname_next.length > 0)
+ output ~= ".segment_next: " ~ obj.tags.segname_next;
+ if (obj.tags.heading_lev_anchor_tag.length > 0)
+ output ~= ".heading_lev_anchor: " ~ obj.tags.heading_lev_anchor_tag;
+ if (obj.tags.segment_anchor_tag_epub.length > 0)
+ output ~= ".segment_epub: " ~ obj.tags.segment_anchor_tag_epub;
+ /+ ↓ heading ancestors text +/
+ {
+ bool has_hat = false;
+ foreach (h; obj.tags.heading_ancestors_text) {
+ if (h.length > 0) { has_hat = true; break; }
+ }
+ if (has_hat) {
+ output ~= ".heading_ancestors_text: " ~ obj.tags.heading_ancestors_text.join("|");
+ }
+ }
+ /+ ↓ lev4 subtoc +/
+ if (obj.tags.lev4_subtoc.length > 0) {
+ foreach (st; obj.tags.lev4_subtoc) {
+ if (st.length > 0)
+ output ~= ".lev4_subtoc: " ~ st;
+ }
+ }
+ /+ ↓ anchor tags +/
+ if (obj.tags.anchor_tags.length > 0) {
+ foreach (at; obj.tags.anchor_tags) {
+ if (at.length > 0)
+ output ~= ".anchor_tag: " ~ at;
+ }
+ }
+
+ /+ ↓ text content +/
+ if (obj.text.length > 0) {
+ foreach (line; obj.text.split("\n")) {
+ output ~= "| " ~ line;
+ }
+ }
+
+ output ~= "";
+ }
+
+ output ~= "}";
+ output ~= "";
+ }
+
+ /+ ↓ write to file +/
+ /+ path: <output_path>/<language>/abstraction/<doc_uid_out>.ssp +/
+ string out_root = (doc_matters.output_path.length > 0)
+ ? doc_matters.output_path : "";
+ string base_pth = (out_root
+ .chainPath(doc_matters.src.language, "abstraction")
+ .asNormalizedPath).array;
+ try {
+ if (!exists(base_pth)) {
+ base_pth.mkdirRecurse;
+ }
+ } catch (Exception ex) {
+ }
+ string out_file = ((base_pth.chainPath(
+ doc_matters.src.doc_uid_out ~ ".ssp")).asNormalizedPath).array;
+ if (doc_matters.opt.action.vox_gt_1) {
+ writeln(" ", out_file);
+ }
+ auto f = File(out_file, "w");
+ foreach (line; output) {
+ f.writeln(line);
+ }
+ }
+}
diff --git a/src/sisudoc/conf/compile_time_info.d b/src/sisudoc/conf/compile_time_info.d
index 3169237..e1ae3cf 100644
--- a/src/sisudoc/conf/compile_time_info.d
+++ b/src/sisudoc/conf/compile_time_info.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_in/paths_source.d b/src/sisudoc/io_in/paths_source.d
index 2c0d545..41353ed 100644
--- a/src/sisudoc/io_in/paths_source.d
+++ b/src/sisudoc/io_in/paths_source.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -821,6 +821,20 @@ template spinePathsPods() {
}
return _pods();
}
+ auto abstraction_root(string fn_src) {
+ auto pod_root_ = pod_root(fn_src);
+ auto pth_1_ = ((media_root(fn_src).zpod.chainPath("abstraction")).asNormalizedPath).array;
+ auto pth_2_ = ((media_root(fn_src).filesystem_open_zpod.chainPath("abstraction")).asNormalizedPath).array;
+ struct _pods {
+ auto zpod() {
+ return pth_1_;
+ }
+ auto filesystem_open_zpod() {
+ return pth_2_;
+ }
+ }
+ return _pods();
+ }
auto image_root(string fn_src) {
auto pod_root_ = pod_root(fn_src);
auto pth_1_ = ((media_root(fn_src).zpod.chainPath("image")).asNormalizedPath).array;
diff --git a/src/sisudoc/io_in/read_config_files.d b/src/sisudoc/io_in/read_config_files.d
index 531dc72..d3a3f45 100644
--- a/src/sisudoc/io_in/read_config_files.d
+++ b/src/sisudoc/io_in/read_config_files.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_in/read_source_files.d b/src/sisudoc/io_in/read_source_files.d
index 428c119..31cbd37 100644
--- a/src/sisudoc/io_in/read_source_files.d
+++ b/src/sisudoc/io_in/read_source_files.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_in/read_zip_pod.d b/src/sisudoc/io_in/read_zip_pod.d
new file mode 100644
index 0000000..d228f4e
--- /dev/null
+++ b/src/sisudoc/io_in/read_zip_pod.d
@@ -0,0 +1,401 @@
+/+
+- Name: SisuDoc Spine, Doc Reform [a part of]
+ - Description: documents, structuring, processing, publishing, search
+ - static content generator
+
+ - Author: Ralph Amissah
+ [ralph.amissah@gmail.com]
+
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
+
+ - License: AGPL 3 or later:
+
+ Spine (SiSU), a framework for document structuring, publishing and
+ search
+
+ Copyright (C) Ralph Amissah
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU AFERO General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see [https://www.gnu.org/licenses/].
+
+ If you have Internet connection, the latest version of the AGPL should be
+ available at these locations:
+ [https://www.fsf.org/licensing/licenses/agpl.html]
+ [https://www.gnu.org/licenses/agpl.html]
+
+ - Spine (by Doc Reform, related to SiSU) uses standard:
+ - docReform markup syntax
+ - standard SiSU markup syntax with modified headers and minor modifications
+ - docReform object numbering
+ - standard SiSU object citation numbering & system
+
+ - Homepages:
+ [https://www.sisudoc.org]
+ [https://www.doc-reform.org]
+
+ - Git
+ [https://git.sisudoc.org/]
+
++/
+/++
+ module read_zip_pod;<BR>
+ - extract pod zip archives to temp directory for processing<BR>
+ - validate zip entries for security (path traversal, size limits)
++/
+module sisudoc.io_in.read_zip_pod;
+@safe:
+template spineExtractZipPod() {
+ import std.algorithm : canFind;
+ import std.array : array;
+ import std.conv : to;
+ import std.file;
+ import std.path;
+ import std.regex;
+ import std.stdio;
+ import std.string : indexOf;
+
+ /+ security limits for zip extraction +/
+ enum size_t MAX_ENTRY_SIZE = 50 * 1024 * 1024; /+ 50 MB per entry +/
+ enum size_t MAX_TOTAL_SIZE = 500 * 1024 * 1024; /+ 500 MB total +/
+ enum size_t MAX_ENTRY_COUNT = 500; /+ max entries in archive +/
+ enum size_t MAX_PATH_DEPTH = 10; /+ max path components +/
+
+ /+ allowed entry name pattern: alphanumeric, dots, dashes, underscores, forward slashes +/
+ static auto rgx_safe_entry_name = ctRegex!(`^[a-zA-Z0-9._/ -]+$`);
+
+ struct ZipPodResult {
+ string tmp_dir; /+ temp directory containing extracted pod +/
+ string pod_dir; /+ path to pod directory within tmp_dir +/
+ bool ok; /+ extraction succeeded +/
+ string error_msg; /+ error description if !ok +/
+ }
+
+ /+ ↓ validate a single zip entry name for security +/
+ string validateEntryName(string name) {
+ /+ reject empty names +/
+ if (name.length == 0)
+ return "empty entry name";
+ /+ reject absolute paths +/
+ if (name[0] == '/')
+ return "absolute path in zip entry: " ~ name;
+ /+ reject path traversal +/
+ if (name.canFind(".."))
+ return "path traversal in zip entry: " ~ name;
+ /+ reject null bytes +/
+ if (name.indexOf('\0') >= 0)
+ return "null byte in zip entry name: " ~ name;
+ /+ reject backslashes (windows path separator tricks) +/
+ if (name.canFind("\\"))
+ return "backslash in zip entry: " ~ name;
+ /+ check path depth +/
+ size_t depth = 0;
+ foreach (c; name) {
+ if (c == '/') depth++;
+ }
+ if (depth > MAX_PATH_DEPTH)
+ return "path too deep in zip entry: " ~ name;
+ /+ check allowed characters +/
+ if (!(name.matchFirst(rgx_safe_entry_name)))
+ return "disallowed characters in zip entry: " ~ name;
+ return ""; /+ empty string means valid +/
+ }
+
+ /+ ↓ extract zip pod to temp directory, returns ZipPodResult +/
+ @trusted ZipPodResult extractZipPod(string zip_path) {
+ import std.zip;
+ ZipPodResult result;
+ result.ok = false;
+ /+ ↓ verify zip file exists +/
+ if (!exists(zip_path) || !zip_path.isFile) {
+ result.error_msg = "zip file not found: " ~ zip_path;
+ return result;
+ }
+ /+ ↓ derive pod name from zip filename +/
+ string zip_basename = zip_path.baseName.stripExtension;
+ /+ ↓ read and parse zip archive +/
+ ZipArchive zip;
+ try {
+ zip = new ZipArchive(read(zip_path));
+ } catch (ZipException ex) {
+ result.error_msg = "failed to read zip archive: " ~ zip_path ~ " - " ~ ex.msg;
+ return result;
+ } catch (Exception ex) {
+ result.error_msg = "error reading zip file: " ~ zip_path ~ " - " ~ ex.msg;
+ return result;
+ }
+ /+ ↓ validate entry count +/
+ if (zip.directory.length > MAX_ENTRY_COUNT) {
+ result.error_msg = "zip archive has too many entries ("
+ ~ zip.directory.length.to!string ~ " > " ~ MAX_ENTRY_COUNT.to!string ~ "): " ~ zip_path;
+ return result;
+ }
+ /+ ↓ validate all entries before extracting any +/
+ size_t total_size = 0;
+ foreach (name, member; zip.directory) {
+ /+ validate entry name +/
+ string name_err = validateEntryName(name);
+ if (name_err.length > 0) {
+ result.error_msg = name_err;
+ return result;
+ }
+ /+ check per-entry size +/
+ if (member.expandedSize > MAX_ENTRY_SIZE) {
+ result.error_msg = "zip entry too large ("
+ ~ member.expandedSize.to!string ~ " bytes): " ~ name;
+ return result;
+ }
+ /+ check total size +/
+ total_size += member.expandedSize;
+ if (total_size > MAX_TOTAL_SIZE) {
+ result.error_msg = "zip archive total size exceeds limit ("
+ ~ MAX_TOTAL_SIZE.to!string ~ " bytes): " ~ zip_path;
+ return result;
+ }
+ }
+ /+ ↓ create temp directory +/
+ string tmp_base = tempDir.buildPath("spine-zip-pod");
+ try {
+ if (!exists(tmp_base))
+ mkdirRecurse(tmp_base);
+ } catch (FileException ex) {
+ result.error_msg = "failed to create temp base directory: " ~ ex.msg;
+ return result;
+ }
+ /+ pod directory inside temp: tmp_base/pod_name/ +/
+ string pod_dir = tmp_base.buildPath(zip_basename);
+ try {
+ if (exists(pod_dir))
+ rmdirRecurse(pod_dir);
+ mkdirRecurse(pod_dir);
+ } catch (FileException ex) {
+ result.error_msg = "failed to create temp pod directory: " ~ ex.msg;
+ return result;
+ }
+ /+ ↓ extract entries +/
+ /+ zip internal structure uses paths like:
+ pod.manifest, conf/dr_document_make,
+ pod/media/text/en/filename.sst, image/filename.png
+ but the extracted pod directory needs to look like a normal pod:
+ pod.manifest, conf/dr_document_make,
+ media/text/en/filename.sst, image/filename.png
+ The "pod/" prefix in zip entries for text files maps to the pod root.
+ +/
+ /+ ↓ pre-compute canonical pod path for containment checks +/
+ auto canonical_pod = (pod_dir.asNormalizedPath).array.to!string ~ "/";
+ foreach (name, member; zip.directory) {
+ /+ skip directory entries +/
+ if (name.length > 0 && name[$-1] == '/')
+ continue;
+ /+ ↓ map zip internal path to filesystem path +/
+ /+ entries with "pod/" prefix: strip it so media/text/en/file.sst ends up at pod_dir/media/text/en/file.sst +/
+ string entry_path = name;
+ if (entry_path.length > 4 && entry_path[0..4] == "pod/") {
+ entry_path = entry_path[4..$];
+ }
+ string out_path = pod_dir.buildPath(entry_path);
+ /+ ↓ verify resolved path is within pod_dir (defense in depth) +/
+ auto canonical_out = (out_path.asNormalizedPath).array.to!string;
+ if (canonical_out.length < canonical_pod.length
+ || canonical_out[0..canonical_pod.length] != canonical_pod)
+ {
+ result.error_msg = "zip entry escapes extraction directory: " ~ name;
+ try { rmdirRecurse(pod_dir); } catch (FileException) {}
+ return result;
+ }
+ /+ ↓ create parent directories +/
+ string parent = out_path.dirName;
+ try {
+ if (!exists(parent))
+ mkdirRecurse(parent);
+ } catch (FileException ex) {
+ result.error_msg = "failed to create directory for: " ~ name ~ " - " ~ ex.msg;
+ try { rmdirRecurse(pod_dir); } catch (FileException) {}
+ return result;
+ }
+ /+ ↓ decompress and write file +/
+ try {
+ auto data = zip.expand(member);
+ std.file.write(out_path, data);
+ } catch (Exception ex) {
+ result.error_msg = "failed to extract: " ~ name ~ " - " ~ ex.msg;
+ try { rmdirRecurse(pod_dir); } catch (FileException) {}
+ return result;
+ }
+ }
+ /+ ↓ verify no symlinks were created (defense in depth) +/
+ string symlink_err = checkForSymlinks(pod_dir);
+ if (symlink_err.length > 0) {
+ result.error_msg = symlink_err;
+ try { rmdirRecurse(pod_dir); } catch (FileException) {}
+ return result;
+ }
+ /+ ↓ verify pod.manifest exists in extracted content +/
+ if (!exists(pod_dir.buildPath("pod.manifest"))) {
+ result.error_msg = "zip archive does not contain pod.manifest: " ~ zip_path;
+ try { rmdirRecurse(pod_dir); } catch (FileException) {}
+ return result;
+ }
+ result.tmp_dir = tmp_base;
+ result.pod_dir = pod_dir;
+ result.ok = true;
+ return result;
+ }
+
+ /+ ↓ recursively check for symlinks in extracted directory +/
+ @trusted string checkForSymlinks(string dir_path) {
+ try {
+ foreach (entry; dirEntries(dir_path, SpanMode.depth)) {
+ if (entry.isSymlink) {
+ return "symlink found in zip extraction: " ~ entry.name;
+ }
+ }
+ } catch (FileException ex) {
+ return "error checking for symlinks: " ~ ex.msg;
+ }
+ return "";
+ }
+
+ /+ ↓ download a zip pod from a URL to a temp file +/
+ enum size_t MAX_DOWNLOAD_SIZE = 200 * 1024 * 1024; /+ 200 MB download limit +/
+ enum int DOWNLOAD_TIMEOUT = 120; /+ seconds +/
+
+ static auto rgx_url_zip = ctRegex!(`^https?://[a-zA-Z0-9._:/-]+[.]zip$`);
+
+ struct DownloadResult {
+ string local_path; /+ path to downloaded temp file +/
+ bool ok;
+ string error_msg;
+ }
+
+ bool isUrl(string arg) {
+ return arg.length > 8
+ && (arg[0..8] == "https://" || arg[0..7] == "http://");
+ }
+
+ @trusted DownloadResult downloadZipUrl(string url) {
+ import std.process : execute, environment;
+ DownloadResult result;
+ result.ok = false;
+ /+ ↓ validate URL scheme +/
+ if (url.length < 8 || (url[0..8] != "https://" && url[0..7] != "http://")) {
+ result.error_msg = "only http/https URLs are supported: " ~ url;
+ return result;
+ }
+ if (url[0..7] == "http://" && url[0..8] != "https://") {
+ stderr.writeln("WARNING: downloading over insecure http: ", url);
+ }
+ /+ ↓ validate URL format +/
+ if (!(url.matchFirst(rgx_url_zip))) {
+ result.error_msg = "URL does not match expected zip URL pattern: " ~ url;
+ return result;
+ }
+ /+ ↓ reject URLs that could target internal services +/
+ {
+ import std.uni : toLower;
+ string url_lower = url.toLower;
+ /+ strip scheme to get host portion +/
+ string after_scheme = (url_lower[0..8] == "https://")
+ ? url_lower[8..$]
+ : url_lower[7..$];
+ /+ extract host (up to first / or :) +/
+ string host;
+ foreach (i, c; after_scheme) {
+ if (c == '/' || c == ':') {
+ host = after_scheme[0..i];
+ break;
+ }
+ }
+ if (host.length == 0) host = after_scheme;
+ if (host == "localhost"
+ || host == "127.0.0.1"
+ || host == "::1"
+ || host == "[::1]"
+ || host == "0.0.0.0"
+ || host.canFind("169.254.")
+ || host.canFind("10.")
+ || host.canFind("192.168.")
+ ) {
+ result.error_msg = "URL targets a local/private address: " ~ url;
+ return result;
+ }
+ }
+ /+ ↓ derive filename from URL +/
+ string url_basename = url.baseName;
+ if (url_basename.length == 0 || url_basename.indexOf('.') < 0) {
+ result.error_msg = "cannot determine filename from URL: " ~ url;
+ return result;
+ }
+ /+ ↓ create temp directory for download +/
+ string tmp_base = tempDir.buildPath("spine-zip-pod");
+ try {
+ if (!exists(tmp_base))
+ mkdirRecurse(tmp_base);
+ } catch (FileException ex) {
+ result.error_msg = "failed to create temp directory: " ~ ex.msg;
+ return result;
+ }
+ string tmp_file = tmp_base.buildPath(url_basename);
+ /+ ↓ download using curl +/
+ auto curl_result = execute([
+ "curl",
+ "--silent", "--show-error",
+ "--fail", /+ fail on HTTP errors +/
+ "--location", /+ follow redirects +/
+ "--max-redirs", "5", /+ limit redirects +/
+ "--max-time", DOWNLOAD_TIMEOUT.to!string,
+ "--max-filesize", MAX_DOWNLOAD_SIZE.to!string,
+ "--proto", "=https,http", /+ restrict protocols +/
+ "--output", tmp_file,
+ url
+ ]);
+ if (curl_result.status != 0) {
+ result.error_msg = "download failed: " ~ url;
+ if (curl_result.output.length > 0)
+ result.error_msg ~= " - " ~ curl_result.output;
+ /+ clean up partial download +/
+ try { if (exists(tmp_file)) remove(tmp_file); } catch (FileException) {}
+ return result;
+ }
+ if (!exists(tmp_file) || !tmp_file.isFile) {
+ result.error_msg = "download produced no file: " ~ url;
+ return result;
+ }
+ result.local_path = tmp_file;
+ result.ok = true;
+ return result;
+ }
+
+ /+ ↓ clean up a downloaded temp file +/
+ void cleanupDownload(ref DownloadResult dlr) {
+ if (dlr.local_path.length > 0 && exists(dlr.local_path)) {
+ try {
+ remove(dlr.local_path);
+ } catch (FileException ex) {
+ stderr.writeln("WARNING: failed to clean up downloaded file: ", dlr.local_path);
+ }
+ }
+ dlr.ok = false;
+ }
+
+ /+ ↓ clean up extracted temp directory +/
+ void cleanupZipPod(ref ZipPodResult zpr) {
+ if (zpr.pod_dir.length > 0 && exists(zpr.pod_dir)) {
+ try {
+ rmdirRecurse(zpr.pod_dir);
+ } catch (FileException ex) {
+ stderr.writeln("WARNING: failed to clean up temp zip extraction: ", zpr.pod_dir);
+ }
+ }
+ zpr.ok = false;
+ }
+}
diff --git a/src/sisudoc/io_out/cgi_sqlite_search_form.d b/src/sisudoc/io_out/cgi_sqlite_search_form.d
deleted file mode 100644
index e835b07..0000000
--- a/src/sisudoc/io_out/cgi_sqlite_search_form.d
+++ /dev/null
@@ -1,1959 +0,0 @@
-/+
-- Name: Spine, Doc Reform [a part of]
- - Description: documents, structuring, processing, publishing, search
- - static content generator
-
- - Author: Ralph Amissah
- [ralph.amissah@gmail.com]
-
- - Copyright: (C) 2015 - 2022 Ralph Amissah, All Rights
- Reserved.
-
- - License: AGPL 3 or later:
-
- Spine (SiSU), a framework for document structuring, publishing and
- search
-
- Copyright (C) Ralph Amissah
-
- This program is free software: you can redistribute it and/or modify it
- under the terms of the GNU AFERO General Public License as published by the
- Free Software Foundation, either version 3 of the License, or (at your
- option) any later version.
-
- This program is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
-
- You should have received a copy of the GNU General Public License along with
- this program. If not, see [https://www.gnu.org/licenses/].
-
- If you have Internet connection, the latest version of the AGPL should be
- available at these locations:
- [https://www.fsf.org/licensing/licenses/agpl.html]
- [https://www.gnu.org/licenses/agpl.html]
-
- - Spine (by Doc Reform, related to SiSU) uses standard:
- - docReform markup syntax
- - standard SiSU markup syntax with modified headers and minor modifications
- - docReform object numbering
- - standard SiSU object citation numbering & system
-
- - Homepages:
- [https://www.doc_reform.org]
- [https://www.sisudoc.org]
-
- - Git
- [https://git.sisudoc.org/projects/?p=software/spine.git;a=summary]
-
-+/
-module doc_reform.io_out.cgi_sqlite_search_form;
-template CGIsearchSQLite() {
- void CGIsearchSQLite(E,O,M)(E env, O opt_action, M make_and_meta_struct) {
- import
- std.file,
- std.format;
- import doc_reform.io_out;
- string _sqlite_db_fn = (opt_action.sqliteDB_filename.empty)
- ? make_and_meta_struct.conf.w_srv_db_sqlite_filename
- : opt_action.sqliteDB_filename;
- string _cgi_search_script = (opt_action.cgi_sqlite_search_filename.empty)
- ? make_and_meta_struct.conf.w_srv_cgi_search_script
- : opt_action.cgi_sqlite_search_filename;
- string _cgi_search_script_raw_fn_d = (opt_action.cgi_sqlite_search_filename_d.empty)
- ? make_and_meta_struct.conf.w_srv_cgi_search_script_raw_fn_d
- : opt_action.cgi_sqlite_search_filename_d;
- string get_doc_collection_subroot(string output_path) {
- string web_doc_root_path = environment.get("DOCUMENT_ROOT", "/var/www/html");
- auto m = output_path.matchFirst(regex("^(" ~ web_doc_root_path ~ ")"));
- return m.post;
- }
- string the_cgi_search_form = format(q"≓
-/+ dub.sdl
- name "spine search"
- description "spine cgi search"
-+/
-import std.format;
-import std.range;
-import std.regex;
-import arsd.cgi;
-import d2sqlite3;
-import std.process : environment;
-void cgi_function_intro(Cgi cgi) {
- string header;
- string table;
- string form;
- struct Config {
- string http_request_type;
- string http_host;
- // string server_name;
- string web_doc_root_path;
- string doc_collection_subroot;
- string cgi_root;
- string cgi_script;
- string data_path_html;
- string db_path;
- string query_string;
- string http_url;
- string request_method;
- }
- auto conf = Config();
- conf.http_request_type = environment.get("REQUEST_SCHEME", "http");
- conf.http_host = environment.get("HTTP_HOST", "localhost");
- // conf.server_name = environment.get("SERVER_NAME", "localhost");
- conf.web_doc_root_path = environment.get("DOCUMENT_ROOT", "/var/www/html");
- conf.doc_collection_subroot = "%s"; // (output_path - web_doc_root_path)
- conf.cgi_root = environment.get("CONTEXT_DOCUMENT_ROOT", "/usr/lib/cgi-bin/");
- // conf.cgi_script = environment.get("SCRIPT_NAME", "/cgi-bin/spine-search");
- conf.query_string = environment.get("QUERY_STRING", "");
- conf.http_url = environment.get("HTTP_REFERER", conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ conf.query_string);
- conf.db_path = "%s"; // (output_path + /sqlite)
- conf.request_method = environment.get("REQUEST_METHOD", "POST");
- struct CGI_val {
- string db_selected = "";
- string sql_match_limit = ""; // radio: ( 1000 | 2500 )
- string sql_match_offset = "";
- string search_text = "";
- string results_type = ""; // index
- bool checked_echo = false;
- bool checked_stats = false;
- bool checked_url = false;
- bool checked_searched = false;
- bool checked_tip = false;
- bool checked_sql = false;
- }
- auto cv = CGI_val();
- cv.db_selected = "%s";
- auto text_fields() {
- string canned_query_str = environment.get("QUERY_STRING", "");
- if ("query_string" in cgi.post) {
- canned_query_str = environment.get("QUERY_STRING", "");
- }
- string[string] canned_query;
- if (conf.request_method == "POST") {
- } else if (conf.request_method == "GET") {
- foreach (pair_str; canned_query_str.split("&")) {
- // cgi.write(pair_str ~ "<br>");
- string[] pair = pair_str.split("=");
- canned_query[pair[0]] = pair[1];
- }
- // foreach (field, content; canned_query) {
- // cgi.write(field ~ ": " ~ content ~ "<br>");
- // }
- }
- static struct Rgx {
- // static canned_query = ctRegex!(`\A(?P<matched>.+)\Z`, "m");
- static search_text_area = ctRegex!(`\A(?P<matched>.+)\Z`, "m");
- // static fulltext = ctRegex!(`\A(?P<matched>.+)\Z`, "m");
- static line = ctRegex!(`^(?P<matched>.+?)(?: ~|$)`, "m");
- static text = ctRegex!(`(?:^|\s~\s*)text:\s+(?P<matched>.+?)(?: ~|$)`, "m");
- static author = ctRegex!(`(?:^|\s~\s*)author:\s+(?P<matched>.+)$`, "m");
- static title = ctRegex!(`(?:^|\s~\s*)title:\s+(?P<matched>.+)$`, "m");
- static uid = ctRegex!(`(?:^|\s~\s*)uid:\s+(?P<matched>.+)$`, "m");
- static fn = ctRegex!(`(?:^|\s~\s*)fn:\s+(?P<matched>.+)$`, "m");
- static keywords = ctRegex!(`(?:^|\s~\s*)keywords:\s+(?P<matched>.+)$`, "m");
- static topic_register = ctRegex!(`(?:^|\s~\s*)topic_register:\s+(?P<matched>.+)$`, "m");
- static subject = ctRegex!(`(?:^|\s~\s*)subject:\s+(?P<matched>.+)$`, "m");
- static description = ctRegex!(`(?:^|\s~\s*)description:\s+(?P<matched>.+)$`, "m");
- static publisher = ctRegex!(`(?:^|\s~\s*)publisher:\s+(?P<matched>.+)$`, "m");
- static editor = ctRegex!(`(?:^|\s~\s*)editor:\s+(?P<matched>.+)$`, "m");
- static contributor = ctRegex!(`(?:^|\s~\s*)contributor:\s+(?P<matched>.+)$`, "m");
- static date = ctRegex!(`(?:^|\s~\s*)date:\s+(?P<matched>.+)$`, "m");
- static results_type = ctRegex!(`(?:^|\s~\s*)type:\s+(?P<matched>.+)$`, "m");
- static format = ctRegex!(`(?:^|\s~\s*)format:\s+(?P<matched>.+)$`, "m");
- static source = ctRegex!(`(?:^|\s~\s*)source:\s+(?P<matched>.+)$`, "m");
- static language = ctRegex!(`(?:^|\s~\s*)language:\s+(?P<matched>.+)$`, "m");
- static relation = ctRegex!(`(?:^|\s~\s*)relation:\s+(?P<matched>.+)$`, "m");
- static coverage = ctRegex!(`(?:^|\s~\s*)coverage:\s+(?P<matched>.+)$`, "m");
- static rights = ctRegex!(`(?:^|\s~\s*)rights:\s+(?P<matched>.+)$`, "m");
- static comment = ctRegex!(`(?:^|\s~\s*)comment:\s+(?P<matched>.+)$`, "m");
- // static abstract_ = ctRegex!(`(?:^|\s~\s*)abstract:\s+(?P<matched>.+)$`, "m");
- static src_filename_base = ctRegex!(`^src_filename_base:\s+(?P<matched>.+)$`, "m");
- }
- struct searchFields {
- string canned_query = ""; // GET canned_query == cq
- string search_text_area = ""; // POST search_text_area == tsa
- string text = ""; // text == txt
- string author = ""; // author == au
- string title = ""; // title == ti
- string uid = ""; // uid == uid
- string fn = ""; // fn == fn
- string keywords = ""; // keywords == kw
- string topic_register = ""; // topic_register == tr
- string subject = ""; // subject == su
- string description = ""; // description == de
- string publisher = ""; // publisher == pb
- string editor = ""; // editor == ed
- string contributor = ""; // contributor == ct
- string date = ""; // date == dt
- string format = ""; // format == fmt
- string source = ""; // source == src sfn
- string language = ""; // language == lng
- string relation = ""; // relation == rl
- string coverage = ""; // coverage == cv
- string rights = ""; // rights == rgt
- string comment = ""; // comment == cmt
- // string abstract = "";
- string src_filename_base = ""; // src_filename_base == bfn
- string results_type = ""; // results_type == rt radio
- string sql_match_limit = ""; // sql_match_limit == sml radio
- string sql_match_offset = ""; // sql_match_offset == smo
- string stats = ""; // stats == sts checked
- string echo = ""; // echo == ec checked
- string url = ""; // url == url checked
- string searched = ""; // searched == se checked
- string sql = ""; // sql == sql checked
- }
- auto rgx = Rgx();
- auto got = searchFields();
- if (environment.get("REQUEST_METHOD", "POST") == "POST") {
- if ("sf" in cgi.post) {
- got.search_text_area = cgi.post["sf"];
- if (auto m = got.search_text_area.matchFirst(rgx.text)) {
- got.text = m["matched"];
- got.canned_query ~= "sf=" ~ m["matched"];
- } else if (auto m = got.search_text_area.matchFirst(rgx.line)) {
- if (
- !(m["matched"].matchFirst(rgx.author))
- && !(m["matched"].matchFirst(rgx.title))
- ) {
- got.text = m["matched"];
- got.canned_query ~= "sf=" ~ m["matched"];
- }
- }
- if (auto m = got.search_text_area.matchFirst(rgx.author)) {
- got.author = m["matched"];
- got.canned_query ~= "&au=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.title)) {
- got.title = m["matched"];
- got.canned_query ~= "&ti=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.uid)) {
- got.uid = m["matched"];
- got.canned_query ~= "&uid=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.fn)) {
- got.fn = m["matched"];
- got.canned_query ~= "&fn=" ~ m["matched"];
- } else if ("fn" in cgi.post) {
- got.search_text_area ~= "\nfn: " ~ cgi.post["fn"] ~ "\n";
- }
- if (auto m = got.search_text_area.matchFirst(rgx.keywords)) {
- got.keywords = m["matched"];
- got.canned_query ~= "&kw=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.topic_register)) {
- got.topic_register = m["matched"];
- got.canned_query ~= "&tr=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.subject)) {
- got.subject = m["matched"];
- got.canned_query ~= "&su=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.description)) {
- got.description = m["matched"];
- got.canned_query ~= "&de=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.publisher)) {
- got.publisher = m["matched"];
- got.canned_query ~= "&pb=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.editor)) {
- got.editor = m["matched"];
- got.canned_query ~= "&ed=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.contributor)) {
- got.contributor = m["matched"];
- got.canned_query ~= "&ct=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.date)) {
- got.date = m["matched"];
- got.canned_query ~= "&dt=" ~ m["matched"];
- }
- // if (auto m = got.search_text_area.matchFirst(rgx.results_type)) {
- // got.results_type = m["matched"];
- // got.canned_query ~= "&rt=" ~ m["matched"];
- // }
- if (auto m = got.search_text_area.matchFirst(rgx.format)) {
- got.format = m["matched"];
- got.canned_query ~= "&fmt=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.source)) {
- got.source = m["matched"];
- got.canned_query ~= "&src=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.language)) {
- got.language = m["matched"];
- got.canned_query ~= "&lng=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.relation)) {
- got.relation = m["matched"];
- got.canned_query ~= "&rl=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.coverage)) {
- got.coverage = m["matched"];
- got.canned_query ~= "&cv=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.rights)) {
- got.rights = m["matched"];
- got.canned_query ~= "&rgt=" ~ m["matched"];
- }
- if (auto m = got.search_text_area.matchFirst(rgx.comment)) {
- got.comment = m["matched"];
- got.canned_query ~= "&cmt=" ~ m["matched"];
- }
- // if (auto m = search_text_area.matchFirst(rgx.abstract)) {
- // got.abstract = m["matched"];
- // }
- if (auto m = got.search_text_area.matchFirst(rgx.src_filename_base)) {
- got.src_filename_base = m["matched"];
- got.canned_query ~= "&bfn=" ~ m["matched"];
- }
- }
- if ("fn" in cgi.post) {
- got.fn = cgi.post["fn"];
- got.canned_query ~= "&fn=" ~ cgi.post["fn"];
- }
- if ("rt" in cgi.post) {
- got.results_type = cgi.post["rt"];
- got.canned_query ~= "&rt=" ~ cgi.post["rt"];
- }
- if ("sts" in cgi.post) {
- got.stats = cgi.post["sts"];
- got.canned_query ~= "&sts=" ~ cgi.post["sts"];
- }
- if ("ec" in cgi.post) {
- got.echo = cgi.post["ec"];
- got.canned_query ~= "&ec=" ~ cgi.post["ec"];
- }
- if ("url" in cgi.post) {
- got.url = cgi.post["url"];
- got.canned_query ~= "&url=" ~ cgi.post["url"];
- }
- if ("se" in cgi.post) {
- got.searched = cgi.post["se"];
- got.canned_query ~= "&se=" ~ cgi.post["se"];
- }
- if ("sql" in cgi.post) {
- got.sql = cgi.post["sql"];
- got.canned_query ~= "&sql=" ~ cgi.post["sql"];
- }
- if ("sml" in cgi.post) {
- got.sql_match_limit = cgi.post["sml"];
- got.canned_query ~= "&sml=" ~ cgi.post["sml"];
- }
- if ("smo" in cgi.post) {
- got.sql_match_offset = "0"; // cgi.post["smo"];
- got.canned_query ~= "&smo=0"; // ~ cgi.post["smo"];
- }
- got.canned_query = got.canned_query.strip.split(" ").join("%%20");
- conf.query_string = got.canned_query;
- // cgi.write("f.canned_query: " ~ got.canned_query ~ "<br>");
- } else if (environment.get("REQUEST_METHOD", "POST") == "GET") {
- got.canned_query = environment.get("QUERY_STRING", "");
- // cgi.write("f.canned_query: " ~ got.canned_query ~ "<br>");
- got.search_text_area = "";
- if ("sf" in canned_query && !(canned_query["sf"]).empty) {
- got.text = canned_query["sf"].split("%%20").join(" ");
- got.search_text_area ~= "text: " ~ got.text ~ "\n";
- }
- if ("au" in canned_query && !(canned_query["au"]).empty) {
- got.author = canned_query["au"].split("%%20").join(" ");
- got.search_text_area ~= "author: " ~ got.author ~ "\n";
- }
- if ("ti" in canned_query && !(canned_query["ti"]).empty) {
- got.title = canned_query["ti"].split("%%20").join(" ");
- got.search_text_area ~= "title: " ~ got.title ~ "\n";
- }
- if ("uid" in canned_query && !(canned_query["uid"]).empty) {
- got.uid = canned_query["uid"].split("%%20").join(" ");
- got.search_text_area ~= "uid: " ~ got.uid ~ "\n";
- }
- if ("fn" in canned_query && !(canned_query["fn"]).empty) {
- got.fn = canned_query["fn"].split("%%20").join(" ");
- got.search_text_area ~= "fn: " ~ got.fn ~ "\n";
- }
- if ("kw" in canned_query && !(canned_query["kw"]).empty) {
- got.keywords = canned_query["kw"].split("%%20").join(" ");
- got.search_text_area ~= "keywords: " ~ got.keywords ~ "\n";
- }
- if ("tr" in canned_query && !(canned_query["tr"]).empty) {
- got.topic_register = canned_query["tr"].split("%%20").join(" ");
- got.search_text_area ~= "topic_register: " ~ got.topic_register ~ "\n";
- }
- if ("su" in canned_query && !(canned_query["su"]).empty) {
- got.subject = canned_query["su"].split("%%20").join(" ");
- got.search_text_area ~= "subject: " ~ got.subject ~ "\n";
- }
- if ("de" in canned_query && !(canned_query["de"]).empty) {
- got.description = canned_query["de"].split("%%20").join(" ");
- got.search_text_area ~= "description: " ~ got.description ~ "\n";
- }
- if ("pb" in canned_query && !(canned_query["pb"]).empty) {
- got.publisher = canned_query["pb"].split("%%20").join(" ");
- got.search_text_area ~= "publisher: " ~ got.publisher ~ "\n";
- }
- if ("ed" in canned_query && !(canned_query["ed"]).empty) {
- got.editor = canned_query["ed"].split("%%20").join(" ");
- got.search_text_area ~= "editor: " ~ got.editor ~ "\n";
- }
- if ("ct" in canned_query && !(canned_query["ct"]).empty) {
- got.contributor = canned_query["ct"].split("%%20").join(" ");
- got.search_text_area ~= "contributor: " ~ got.contributor ~ "\n";
- }
- if ("dt" in canned_query && !(canned_query["dt"]).empty) {
- got.date = canned_query["dt"].split("%%20").join(" ");
- got.search_text_area ~= "date: " ~ got.date ~ "\n";
- }
- if ("rt" in canned_query && !(canned_query["rt"]).empty) {
- got.results_type = canned_query["rt"].split("%%20").join(" ");
- // got.search_text_area ~= "results_type: " ~ got.results_type ~ "\n";
- }
- if ("fmt" in canned_query && !(canned_query["fmt"]).empty) {
- got.format = canned_query["fmt"].split("%%20").join(" ");
- got.search_text_area ~= "format: " ~ got.format ~ "\n";
- }
- if ("src" in canned_query && !(canned_query["src"]).empty) {
- got.source = canned_query["src"].split("%%20").join(" ");
- got.search_text_area ~= "source: " ~ got.source ~ "\n";
- }
- if ("lng" in canned_query && !(canned_query["lng"]).empty) {
- got.language = canned_query["lng"].split("%%20").join(" ");
- got.search_text_area ~= "language: " ~ got.language ~ "\n";
- }
- if ("rl" in canned_query && !(canned_query["rl"]).empty) {
- got.relation = canned_query["rl"].split("%%20").join(" ");
- got.search_text_area ~= "relation: " ~ got.relation ~ "\n";
- }
- if ("cv" in canned_query && !(canned_query["cv"]).empty) {
- got.coverage = canned_query["cv"].split("%%20").join(" ");
- got.search_text_area ~= "coverage: " ~ got.coverage ~ "\n";
- }
- if ("rgt" in canned_query && !(canned_query["rgt"]).empty) {
- got.rights = canned_query["rgt"].split("%%20").join(" ");
- got.search_text_area ~= "rights: " ~ got.rights ~ "\n";
- }
- if ("cmt" in canned_query && !(canned_query["cmt"]).empty) {
- got.comment = canned_query["cmt"].split("%%20").join(" ");
- got.search_text_area ~= "comment: " ~ got.comment ~ "\n";
- }
- // if ("abstract" in canned_query && !(canned_query["abstract"]).empty) {
- // got.abstract = canned_query["abstract"];
- // }
- if ("bfn" in canned_query && !(canned_query["bfn"]).empty) { // search_field
- got.src_filename_base = canned_query["bfn"].split("%%20").join(" ");
- got.search_text_area ~= "src_filename_base: " ~ got.src_filename_base ~ "\n";
- }
- if ("sml" in canned_query && !(canned_query["sml"]).empty) {
- got.sql_match_limit = canned_query["sml"].split("%%20").join(" ");
- // got.search_text_area ~= "sql_match_limit: " ~ got.sql_match_limit ~ "\n";
- }
- // cgi.write("f.search_text_area: " ~ got.search_text_area ~ "<br>");
- }
- return got;
- }
- auto tf = text_fields; //
- struct SQL_select {
- string the_body = "";
- string the_range = "";
- }
- auto sql_select = SQL_select();
- string canned_url () {
- string _url = "";
- if (environment.get("REQUEST_METHOD", "POST") == "POST") {
- _url = conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ tf.canned_query;
- } else if (environment.get("REQUEST_METHOD", "POST") == "GET") {
- _url = conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ environment.get("QUERY_STRING", "");
- }
- return _url;
- }
- auto regex_canned_search () {
- static struct RgxCS {
- static track_offset = ctRegex!(`(?P<offset_key>[&]smo=)(?P<offset_val>[0-9]+)`);
- static results_type = ctRegex!(`[&]rt=(?P<results_type>idx|txt)`);
- static results_type_index = ctRegex!(`[&]rt=idx`);
- static results_type_text = ctRegex!(`[&]rt=txt`);
- static fn = ctRegex!(`[&]fn=(?P<fn>[^&]+)`);
- }
- return RgxCS();
- }
- string show_matched_objects (string fn) {
- auto rgx = regex_canned_search;
- string _matched_objects_text = "";
- string _url = canned_url;
- string _url_new = "";
- string _matches_show_text = "&rt=txt";
- string _matches_show_index = "&rt=idx";
- string _fn = "&fn=" ~ fn;
- _url_new = _url;
- if (_url_new.match(rgx.results_type_index)) {
- _url_new = _url_new.replace(rgx.results_type_index, _matches_show_text);
- } else if (_url.match(rgx.results_type_text)) {
- _url_new = _url_new.replace(rgx.results_type_text, _matches_show_index);
- } else {
- if (!(_url.match(rgx.results_type))) {
- _url_new = _url ~ _matches_show_text;
- }
- }
- if (!(_url_new.match(rgx.fn))) {
- _url_new = _url_new ~ _fn;
- }
- _matched_objects_text =
- "<font size=\"2\" color=\"#666666\">"
- ~ "<a href=\""
- ~ _url_new
- ~ "\">"
- ~ "※"
- ~ "</a></font>";
- return _matched_objects_text;
- }
- string base ; // = "";
- string tip ; // = "";
- string search_note ; // = "";
- uint sql_match_offset_count = 0;
- string previous_next () {
- auto rgx = regex_canned_search;
- string _previous_next = "";
- int _current_offset_value = 0;
- string _set_offset_next = "";
- string _set_offset_previous = "";
- string _url = canned_url;
- string _url_previous = "";
- string _url_next = "";
- string arrow_previous = "";
- string arrow_next = "";
- if (auto m = _url.matchFirst(rgx.track_offset)) {
- _current_offset_value = m.captures["offset_val"].to!int;
- _set_offset_next = m.captures["offset_key"] ~ ((m.captures["offset_val"]).to!int + cv.sql_match_limit.to!int).to!string;
- _url_next = _url.replace(rgx.track_offset, _set_offset_next);
- if (_current_offset_value < cv.sql_match_limit.to!int) {
- _url_previous = "";
- } else {
- _url_previous = "";
- _set_offset_previous = m.captures["offset_key"] ~ ((m.captures["offset_val"]).to!int - cv.sql_match_limit.to!int).to!string;
- _url_previous = _url.replace(rgx.track_offset, _set_offset_previous);
- }
- } else {// _current_offset_value = 0;
- _url_next = _url ~= "&smo=" ~ cv.sql_match_limit.to!string;
- }
- if (_url_previous.empty) {
- arrow_previous = "";
- } else {
- arrow_previous =
- "<font size=\"2\" color=\"#666666\">"
- ~ "<a href=\""
- ~ _url_previous
- ~ "\">"
- ~ "&lt;&lt prev"
- ~ "</a> || </font>";
- }
- arrow_next =
- "<font size=\"2\" color=\"#666666\">"
- ~ "<a href=\""
- ~ _url_next
- ~ "\">"
- ~ "next &gt;&gt"
- ~ "</a></font>";
- _previous_next = "<hr>" ~ arrow_previous ~ arrow_next;
- return _previous_next;
- }
- {
- header = format(q"┃
-<!DOCTYPE html>
-<html>
-<head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>
- %s
- </title>
- <meta name="sourcefile" content="SiSU.sst">
- <link rel="generator" href="sisudoc.org">
- <link rel="shortcut icon" href="https://%%s/image_sys/spine.ico">
- <style media = "all">
- *{
- padding : 0px;
- margin : 2px;
- }
- body {
- height : 100vh;
- background-color : #%s;
- }
- body {
- color : #%s;
- background : #%s;
- background-color : #%s;
- }
- a:link {
- color : #%s;
- text-decoration : none;
- }
- a:visited {
- color : #%s;
- text-decoration : none;
- }
- a:hover {
- color : #%s;
- background-color : #%s;
- }
- a.lnkocn:link {
- color : #%s;
- text-decoration : none;
- }
- a.lnkocn:visited {
- color : #%s;
- text-decoration : none;
- }
- a.lnkocn:hover {
- color : #%s;
- font-size : 15px;
- }
- a:hover img {
- background-color : #%s;
- }
- a:active {
- color : #%s;
- text-decoration : underline;
- }
- .flex-container {
- display : flex;
- }
- div.publication {
- margin-top : 2px;
- margin-bottom : 4px;
- margin-left : 0%%%%;
- margin-right : 0%%%%;
- }
- div.textview_ocn {
- margin-left : 0%%%%;
- margin-right : 1%%%%;
- }
- div.textview_found {
- margin-left : 1%%%%;
- margin-right : 1%%%%;
- }
- textarea {
- color : #%s;
- background-color : #%s;
- }
- span.match {
- color : #%s;
- background-color : #%s;
- }
- p.norm { }
- p.center { text-align : center; }
- p.i1 { padding-left : 1em; }
- p.i2 { padding-left : 2em; }
- p.i3 { padding-left : 3em; }
- p.i4 { padding-left : 4em; }
- p.i5 { padding-left : 5em; }
- p.i6 { padding-left : 6em; }
- p.i7 { padding-left : 7em; }
- p.i8 { padding-left : 8em; }
- p.i9 { padding-left : 9em; }
- /* hanging indent */
- p[indent="h0i0"] {
- padding-left : 0em;
- text-indent : 0em;
- }
- p.publication {
- font-size : 100%%%%;
- margin-left : 0em;
- margin-top : 0px;
- margin-bottom : 0px;
- margin-right : 6px;
- text-align : left;
- }
- p.ocn_is {
- font-size : 100%%%%;
- display : inline-block;
- }
- p.matched_ocn {
- font-size : 90%%%%;
- margin-left : 2em;
- margin-top : 0px;
- margin-bottom : 0px;
- margin-right : 6px;
- text-align : left;
- }
- p[indent="h0i1"] {
- padding-left : 1em;
- text-indent : -1em;
- }
- p[indent="h0i2"] {
- padding-left : 2em;
- text-indent : -2em;
- }
- p[indent="h0i3"] {
- padding-left : 3em;
- text-indent : -3em;
- }
- p[indent="h0i4"] {
- padding-left : 4em;
- text-indent : -4em;
- }
- p[indent="h0i5"] {
- padding-left : 5em;
- text-indent : -5em;
- }
- p[indent="h0i6"] {
- padding-left : 6em;
- text-indent : -6em;
- }
- p[indent="h0i7"] {
- padding-left : 7em;
- text-indent : -7em;
- }
- p[indent="h0i8"] {
- padding-left : 8em;
- text-indent : -8em;
- }
- p[indent="h0i9"] {
- padding-left : 9em;
- text-indent : -9em;
- }
- p[indent="h1i0"] {
- padding-left : 0em;
- text-indent : 1em;
- }
- p[indent="h1i1"] {
- padding-left : 1em;
- text-indent : 0em;
- }
- p[indent="h1i2"] {
- padding-left : 2em;
- text-indent : -1em;
- }
- p[indent="h1i3"] {
- padding-left : 3em;
- text-indent : -2em;
- }
- p[indent="h1i4"] {
- padding-left : 4em;
- text-indent : -3em;
- }
- p[indent="h1i5"] {
- padding-left : 5em;
- text-indent : -4em;
- }
- p[indent="h1i6"] {
- padding-left : 6em;
- text-indent : -5em;
- }
- p[indent="h1i7"] {
- padding-left : 7em;
- text-indent : -6em;
- }
- p[indent="h1i8"] {
- padding-left : 8em;
- text-indent : -7em;
- }
- p[indent="h1i9"] {
- padding-left : 9em;
- text-indent : -8em;
- }
- p[indent="h2i0"] {
- padding-left : 0em;
- text-indent : 2em;
- }
- p[indent="h2i1"] {
- padding-left : 1em;
- text-indent : 1em;
- }
- p[indent="h2i2"] {
- padding-left : 2em;
- text-indent : 0em;
- }
- p[indent="h2i3"] {
- padding-left : 3em;
- text-indent : -1em;
- }
- p[indent="h2i4"] {
- padding-left : 4em;
- text-indent : -2em;
- }
- p[indent="h2i5"] {
- padding-left : 5em;
- text-indent : -3em;
- }
- p[indent="h2i6"] {
- padding-left : 6em;
- text-indent : -4em;
- }
- p[indent="h2i7"] {
- padding-left : 7em;
- text-indent : -5em;
- }
- p[indent="h2i8"] {
- padding-left : 8em;
- text-indent : -6em;
- }
- p[indent="h2i9"] {
- padding-left : 9em;
- text-indent : -7em;
- }
- p[indent="h3i0"] {
- padding-left : 0em;
- text-indent : 3em;
- }
- p[indent="h3i1"] {
- padding-left : 1em;
- text-indent : 2em;
- }
- p[indent="h3i2"] {
- padding-left : 2em;
- text-indent : 1em;
- }
- p[indent="h3i3"] {
- padding-left : 3em;
- text-indent : 0em;
- }
- p[indent="h3i4"] {
- padding-left : 4em;
- text-indent : -1em;
- }
- p[indent="h3i5"] {
- padding-left : 5em;
- text-indent : -2em;
- }
- p[indent="h3i6"] {
- padding-left : 6em;
- text-indent : -3em;
- }
- p[indent="h3i7"] {
- padding-left : 7em;
- text-indent : -4em;
- }
- p[indent="h3i8"] {
- padding-left : 8em;
- text-indent : -5em;
- }
- p[indent="h3i9"] {
- padding-left : 9em;
- text-indent : -6em;
- }
- p[indent="h4i0"] {
- padding-left : 0em;
- text-indent : 4em;
- }
- p[indent="h4i1"] {
- padding-left : 1em;
- text-indent : 3em;
- }
- p[indent="h4i2"] {
- padding-left : 2em;
- text-indent : 2em;
- }
- p[indent="h4i3"] {
- padding-left : 3em;
- text-indent : 1em;
- }
- p[indent="h4i4"] {
- padding-left : 4em;
- text-indent : 0em;
- }
- p[indent="h4i5"] {
- padding-left : 5em;
- text-indent : -1em;
- }
- p[indent="h4i6"] {
- padding-left : 6em;
- text-indent : -2em;
- }
- p[indent="h4i7"] {
- padding-left : 7em;
- text-indent : -3em;
- }
- p[indent="h4i8"] {
- padding-left : 8em;
- text-indent : -4em;
- }
- p[indent="h4i9"] {
- padding-left : 9em;
- text-indent : -5em;
- }
- p[indent="h5i0"] {
- padding-left : 0em;
- text-indent : 5em;
- }
- p[indent="h5i1"] {
- padding-left : 1em;
- text-indent : 4em;
- }
- p[indent="h5i2"] {
- padding-left : 2em;
- text-indent : 3em;
- }
- p[indent="h5i3"] {
- padding-left : 3em;
- text-indent : 2em;
- }
- p[indent="h5i4"] {
- padding-left : 4em;
- text-indent : 1em;
- }
- p[indent="h5i5"] {
- padding-left : 5em;
- text-indent : 0em;
- }
- p[indent="h5i6"] {
- padding-left : 6em;
- text-indent : -1em;
- }
- p[indent="h5i7"] {
- padding-left : 7em;
- text-indent : -2em;
- }
- p[indent="h5i8"] {
- padding-left : 8em;
- text-indent : -3em;
- }
- p[indent="h5i9"] {
- padding-left : 9em;
- text-indent : -4em;
- }
- p[indent="h6i0"] {
- padding-left : 0em;
- text-indent : 6em;
- }
- p[indent="h6i1"] {
- padding-left : 1em;
- text-indent : 5em;
- }
- p[indent="h6i2"] {
- padding-left : 2em;
- text-indent : 4em;
- }
- p[indent="h6i3"] {
- padding-left : 3em;
- text-indent : 3em;
- }
- p[indent="h6i4"] {
- padding-left : 4em;
- text-indent : 2em;
- }
- p[indent="h6i5"] {
- padding-left : 5em;
- text-indent : 1em;
- }
- p[indent="h6i6"] {
- padding-left : 6em;
- text-indent : 0em;
- }
- p[indent="h6i7"] {
- padding-left : 7em;
- text-indent : -1em;
- }
- p[indent="h6i8"] {
- padding-left : 8em;
- text-indent : -2em;
- }
- p[indent="h6i9"] {
- padding-left : 9em;
- text-indent : -3em;
- }
- p[indent="h7i0"] {
- padding-left : 0em;
- text-indent : 7em;
- }
- p[indent="h7i1"] {
- padding-left : 1em;
- text-indent : 6em;
- }
- p[indent="h7i2"] {
- padding-left : 2em;
- text-indent : 5em;
- }
- p[indent="h7i3"] {
- padding-left : 3em;
- text-indent : 4em;
- }
- p[indent="h7i4"] {
- padding-left : 4em;
- text-indent : 3em;
- }
- p[indent="h7i5"] {
- padding-left : 5em;
- text-indent : 2em;
- }
- p[indent="h7i6"] {
- padding-left : 6em;
- text-indent : 1em;
- }
- p[indent="h7i7"] {
- padding-left : 7em;
- text-indent : 0em;
- }
- p[indent="h7i8"] {
- padding-left : 8em;
- text-indent : -1em;
- }
- p[indent="h7i9"] {
- padding-left : 9em;
- text-indent : -2em;
- }
- p[indent="h8i0"] {
- padding-left : 0em;
- text-indent : 8em;
- }
- p[indent="h8i1"] {
- padding-left : 1em;
- text-indent : 7em;
- }
- p[indent="h8i2"] {
- padding-left : 2em;
- text-indent : 6em;
- }
- p[indent="h8i3"] {
- padding-left : 3em;
- text-indent : 5em;
- }
- p[indent="h8i4"] {
- padding-left : 4em;
- text-indent : 4em;
- }
- p[indent="h8i5"] {
- padding-left : 5em;
- text-indent : 3em;
- }
- p[indent="h8i6"] {
- padding-left : 6em;
- text-indent : 2em;
- }
- p[indent="h8i7"] {
- padding-left : 7em;
- text-indent : 1em;
- }
- p[indent="h8i8"] {
- padding-left : 8em;
- text-indent : 0em;
- }
- p[indent="h8i9"] {
- padding-left : 9em;
- text-indent : -1em;
- }
- p[indent="h9i0"] {
- padding-left : 0em;
- text-indent : 9em;
- }
- p[indent="h9i1"] {
- padding-left : 1em;
- text-indent : 8em;
- }
- p[indent="h9i2"] {
- padding-left : 2em;
- text-indent : 7em;
- }
- p[indent="h9i3"] {
- padding-left : 3em;
- text-indent : 6em;
- }
- p[indent="h9i4"] {
- padding-left : 4em;
- text-indent : 5em;
- }
- p[indent="h9i5"] {
- padding-left : 5em;
- text-indent : 4em;
- }
- p[indent="h9i6"] {
- padding-left : 6em;
- text-indent : 3em;
- }
- p[indent="h9i7"] {
- padding-left : 7em;
- text-indent : 2em;
- }
- p[indent="h9i8"] {
- padding-left : 8em;
- text-indent : 1em;
- }
- p[indent="h9i9"] {
- padding-left : 9em;
- text-indent : 0em;
- }
- p.spaced { white-space : pre; }
- p.block {
- white-space : pre;
- }
- p.group { }
- p.alt { }
- p.verse {
- white-space : pre;
- margin-bottom : 6px;
- }
- p.caption {
- text-align : left;
- font-size : 80%%%%;
- display : inline;
- }
- p.endnote {
- font-size : 96%%%%;
- line-height : 120%%%%;
- text-align : left;
- margin-right : 15mm;
- padding-left : 1em;
- text-indent : -1em;
- }
- p.center {
- text-align : center;
- }
- p.bold {
- font-weight : bold;
- }
- p.bold_left {
- font-weight : bold;
- text-align : left;
- }
- p.centerbold {
- text-align : center;
- font-weight : bold;
- }
- p.em {
- font-weight : bold;
- font-style : normal;
- background : #FFF3B6;
- }
- p.small {
- font-size : 80%%%%;
- margin-top : 0px;
- margin-bottom : 0px;
- margin-right : 6px;
- text-align : left;
- }
- .tiny, .tiny_left, .tiny_right, .tiny_center {
- font-size : 10px;
- margin-top : 0px;
- margin-bottom : 0px;
- color : #EEEEEE;
- margin-right : 6px;
- text-align : left;
- }
- p.tiny { }
- p.tiny_left {
- margin-left : 0px;
- margin-right : 0px;
- text-align : left;
- }
- p.tiny_right {
- margin-right : 1em;
- text-align : right;
- }
- p.tiny_center {
- margin-left : 0px;
- margin-right : 0px;
- text-align : center;
- }
- p.book_index_lev1 {
- line-height : 100%%%%;
- margin-top : 4px;
- margin-bottom : 1px;
- }
- p.book_index_lev2 {
- line-height : 100%%%%;
- text-align : left;
- margin-left : 3em;
- margin-top : 1px;
- margin-bottom : 3px;
- }
- tt {
- font-family : inconsolata, "liberation mono", "bitstream vera mono", "dejavu mono", monaco, consolas, "andale mono", "courier new", "courier 10 pitch", courier, monospace;
- background-color : #555555;
- color : #DDDDDD;
- }
- pre {
- width : auto;
- display : block;
- clear : both;
- color : #555555;
- }
- pre.codeline {
- display : table;
- clear : both;
- table-layout : fixed;
- margin-left : 5%%%%;
- margin-right : 5%%%%;
- width : 90%%%%;
- white-space : pre-wrap;
- border-style : none;
- border-radius : 5px 5px 5px 5px;
- box-shadow : 0 2px 5px #AAAAAA inset;
- margin-bottom : 1em;
- padding : 0.5em 1em;
- page-break-inside : avoid;
- word-wrap : break-word;
- font-family : inconsolata, "liberation mono", "bitstream vera mono", "dejavu mono", monaco, consolas, "andale mono", "courier new", "courier 10 pitch", courier, monospace;
- white-space : pre;
- white-space : pre-wrap;
- white-space : -moz-pre-wrap;
- white-space : -o-pre-wrap;
- background-color : #555555;
- color : #DDDDDD;
- font-size : 95%%%%;
- line-height : 100%%%%;
- }
- pre.codeline::before {
- counter-reset : linenum;
- }
- pre.codeline span.tr {
- display : table-row;
- counter-increment : linenum;
- }
- pre.codeline span.th {
- display : table-cell;
- user-select : none;
- -moz-user-select : none;
- -webkit-user-select : none;
- padding : 0.5em 0.5em;
- }
- pre.codeline span.th::before {
- content : counter(linenum) ".";
- color : #999999;
- text-align : right;
- display : block;
- }
- pre.codeline span.th {
- width : 4em;
- }
- pre.codeline code {
- display : table-cell;
- }
- p.code {
- border-style : none;
- }
- note { white-space : pre; }
- em {
- font-weight : bold;
- font-style : italic;
- }
- p.left,th.left,td.left {
- text-align : left;
- }
- p.small_left,th.small_left,td.small_left {
- text-align : left;
- font-size : 80%%%%;
- }
- p.right,th.right,td.right {
- text-align : right;
- }
- ul, li {
- list-style-type : none;
- list-style : none;
- padding-left : 20px;
- font-weight : normal;
- line-height : 150%%%%;
- text-align : left;
- text-indent : 0mm;
- margin-left : 1em;
- margin-right : 2em;
- margin-top : 3px;
- margin-bottom : 3px;
- }
- li {
- background : (../image_sys/bullet_09.png) no-repeat 0px 6px;
- }
- ul { }
- h0, h1, h2, h3, h4, h5, h6, h7 {
- font-weight : bold;
- line-height : 120%%%%;
- text-align : left;
- margin-top : 20px;
- margin-bottom : 10px;
- }
- h4.norm, h5.norm, h6.norm, h7.norm {
- margin-top : 10px;
- margin-bottom : 0px;
- }
- h0 { font-size : 125%%%%; }
- h1 { font-size : 120%%%%; }
- h2 { font-size : 115%%%%; }
- h3 { font-size : 110%%%%; }
- h4 { font-size : 105%%%%; }
- h5 { font-size : 100%%%%; }
- h6 { font-size : 100%%%%; }
- h7 { font-size : 100%%%%; }
- h0, h1, h2, h3, h4, h5, h6, h7 {
- text-shadow : .2em .2em .3em #999999;
- }
- h1.i { margin-left : 2em; }
- h2.i { margin-left : 3em; }
- h3.i { margin-left : 4em; }
- h4.i { margin-left : 5em; }
- h5.i { margin-left : 6em; }
- h6.i { margin-left : 7em; }
- h7.i { margin-left : 8em; }
- h8.i { margin-left : 9em; }
- h9.i { margin-left : 10em; }
- .toc {
- font-weight : normal;
- margin-top : 6px;
- margin-bottom : 6px;
- }
- h0.toc {
- margin-left : 1em;
- font-size : 120%%%%;
- line-height : 150%%%%;
- }
- h1.toc {
- margin-left : 1em;
- font-size : 115%%%%;
- line-height : 150%%%%;
- }
- h2.toc {
- margin-left : 2em;
- font-size : 110%%%%;
- line-height : 140%%%%;
- }
- h3.toc {
- margin-left : 3em;
- font-size : 105%%%%;
- line-height : 120%%%%;
- }
- h4.toc {
- margin-left : 4em;
- font-size : 100%%%%;
- line-height : 120%%%%;
- }
- h5.toc {
- margin-left : 5em;
- font-size : 95%%%%;
- line-height : 110%%%%;
- }
- h6.toc {
- margin-left : 6em;
- font-size : 90%%%%;
- line-height : 110%%%%;
- }
- h7.toc {
- margin-left : 7em;
- font-size : 85%%%%;
- line-height : 100%%%%;
- }
- .subtoc {
- margin-right : 34%%%%;
- font-weight : normal;
- }
- h5.subtoc {
- margin-left : 2em;
- font-size : 80%%%%;
- margin-top : 2px;
- margin-bottom : 2px;
- }
- h6.subtoc {
- margin-left : 3em;
- font-size : 75%%%%;
- margin-top : 0px;
- margin-bottom : 0px;
- }
- h7.subtoc {
- margin-left : 4em;
- font-size : 70%%%%;
- margin-top : 0px;
- margin-bottom : 0px;
- }
- hr {
- display : block;
- height : 1px;
- width : 100%%%%;
- border : 0;
- border-top : 1px solid #AAAAAA;
- border-color : #AAAAAA
- background-color : #AAAAAA
- margin-left : 0%%%%;
- margin-right : 0em;
- margin-top : 0.5em;
- margin-bottom : 0.5em;
- padding : 0;
- }
-</style>
-</head>
-<body lang="en" xml:lang="en">
-┃",
- conf.http_host,
- );
- }
- {
- table = format(q"┃
-<table summary="band" border="0" cellpadding="2" cellspacing="0">
-<tr><td width="20%%%%">
- <table summary="home button / home information" border="0" cellpadding="2" cellspacing="0">
- <tr><td align="left">
- %s
- </td></tr>
- </table>
-</td>
-<td>
-</td></tr>
-</table>
-┃");
- }
- {
- string post_value(string field_name, string type="box", string set="on") {
- string val = "";
- switch (type) {
- case "field":
- val = ((field_name in cgi.post && !(cgi.post[field_name]).empty)
- ? cgi.post[field_name]
- : (field_name in cgi.get)
- ? cgi.get[field_name]
- : "");
- val = tf.search_text_area;
- break;
- case "box": // generic for checkbox or radio; checkbox set == "on" radio set == "name set"
- val = ((field_name in cgi.post && !(cgi.post[field_name]).empty)
- ? (cgi.post[field_name] == set ? "checked" : "off")
- : (field_name in cgi.get)
- ? (cgi.get[field_name] == set ? "checked" : "off")
- : "off");
- break;
- case "radio": // used generic bo
- val = ((field_name in cgi.post && !(cgi.post[field_name]).empty)
- ? (cgi.post[field_name] == set ? "checked" : "off")
- : (field_name in cgi.get)
- ? (cgi.get[field_name] == set ? "checked" : "off")
- : "checked");
- break;
- case "checkbox": // used generic bo
- val = ((field_name in cgi.post && !(cgi.post[field_name]).empty)
- ? (cgi.post[field_name] == set ? "checked" : "off")
- : (field_name in cgi.get)
- ? (cgi.get[field_name] == set ? "checked" : "off")
- : "checked");
- break;
- default:
- }
- return val;
- }
- string the_can(string fv) {
- string show_the_can = post_value("url");
- string _the_can = "";
- if (show_the_can == "checked") {
- tf = text_fields;
- string method_get_url = conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ environment.get("QUERY_STRING", "");
- string method_post_url_construct = conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ tf.canned_query;
- // assert(method_get_url == environment.get("HTTP_REFERER", conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ "?" ~ conf.query_string));
- if (conf.request_method == "POST") {
- _the_can =
- "<font size=\"2\" color=\"#666666\">"
- ~ "POST:&nbsp;"
- ~ "<a href=\""
- ~ method_post_url_construct
- ~ "\">"
- ~ method_post_url_construct
- ~ "</a></font>"
- ~ "<br>";
- } else if (conf.request_method == "GET") {
- _the_can =
- "<font size=\"2\" color=\"#666666\">"
- ~ "GET:&nbsp;&nbsp;"
- ~ "<a href=\""
- ~ method_get_url
- ~ "\">"
- ~ method_get_url
- ~ "</a></font>";
- }
- conf.http_url = conf.http_request_type ~ "://" ~ conf.http_host ~ conf.cgi_script ~ tf.canned_query;
- }
- return _the_can;
- }
- string provide_tip() {
- string searched_tip = post_value("se");
- string tip = "";
- if (searched_tip == "checked") {
- string search_field = post_value("sf", "field");
- tf = text_fields;
- tip = format(q"┃
-<font size="2" color="#666666">
-<b>database:</b> <font size="2" color="#004000">%%s</font>; <b>selected view:</b> <font size="2" color="#004000">index</font>
-<b>search string:</b> %%s %%s %%s %%s %%s %%s<br>
-%%s %%s %%s %%s %%s %%s
-</font>
-┃",
- cv.db_selected,
- (tf.text.empty ? "" : "\"text: <font size=\"2\" color=\"#004000\">" ~ tf.text ~ "</font>; "),
- (tf.title.empty ? "" : "\"title: <font size=\"2\" color=\"#004000\">" ~ tf.title ~ "</font>; "),
- (tf.author.empty ? "" : "\"author: <font size=\"2\" color=\"#004000\">" ~ tf.author ~ "</font>; "),
- (tf.date.empty ? "" : "\"date <font size=\"2\" color=\"#004000\">" ~ tf.date ~ "</font>; "),
- (tf.uid.empty ? "" : "\"uid: <font size=\"2\" color=\"#004000\">" ~ tf.uid ~ "</font>; "),
- (tf.fn.empty ? "" : "\"fn: <font size=\"2\" color=\"#004000\">" ~ tf.fn ~ "</font>; "),
- (tf.text.empty ? "" : "text: <font size=\"2\" color=\"#004000\">" ~ tf.text ~ "</font><br>"),
- (tf.title.empty ? "" : "title: <font size=\"2\" color=\"#004000\">" ~ tf.title ~ "</font><br>"),
- (tf.author.empty ? "" : "author: <font size=\"2\" color=\"#004000\">" ~ tf.author ~ "</font><br>"),
- (tf.date.empty ? "" : "date: <font size=\"2\" color=\"#004000\">" ~ tf.date ~ "</font><br>"),
- (tf.uid.empty ? "" : "\"uid: <font size=\"2\" color=\"#004000\">" ~ tf.uid ~ "</font>; "),
- (tf.fn.empty ? "" : "\"fn: <font size=\"2\" color=\"#004000\">" ~ tf.fn ~ "</font>; "),
- );
- }
- return tip;
- }
- form = format(q"┃
-<form action="%%s" id="SubmitForm" method="post" accept-charset="UTF-8">
- <table cellpadding="2">
- <tr><td valign=\"top\">
- <textarea id="find" name="sf" type="text" rows="6" cols="40" maxlength="256" wrap="virtual">%%s</textarea>
- </td>
- <td valign=\"top\">
- %%s
- %%s
- %%s
- </td></tr></table>
- <td valign=\"top\"><tr><td>
- <font size="2" color="#%s">
- <input type="hidden" name="db" value="%%s">
- <input type="submit" value="SiSU search">
- <input type="radio" name="rt" id="results_type_index" value="idx" %%s> index
- <input type="radio" name="rt" id="results_type_text" value="txt" %%s> text / grep;
- match limit:
- <input type="radio" name="sml" id="sql_match_limit_1000" value="1000" %%s> 1,000
- <input type="radio" name="sml" id="sql_match_limit_2500" value="2500" %%s> 2,500
- <br>
- <input type="checkbox" name="ec" %%s> echo query
- <input type="checkbox" name="url" %%s> search url
- <input type="checkbox" name="se" %%s> searched
- <input type="checkbox" name="sql" %%s> sql statement
- <input type="hidden" name="smo" value="0">
- <br>
- </font>
- </td></tr>
- </table>
-</form>
-┃",
- "%s",
- (post_value("ec") == "checked") ? post_value("sf", "field") : "",
- provide_tip,
- search_note,
- the_can(post_value("sf", "field")),
- cv.db_selected,
- post_value("rt", "box", "idx"),
- post_value("rt", "box", "txt"),
- post_value("sml", "box", "1000"),
- post_value("sml", "box", "2500"),
- post_value("ec"),
- post_value("url"),
- post_value("se"),
- post_value("sql"),
- );
- {
- string set_value(string field_name, string default_val) {
- string val;
- if (field_name in cgi.post) {
- val = cgi.post[field_name];
- } else if (field_name in cgi.get) {
- val = cgi.get[field_name];
- } else { val = default_val; }
- return val;
- }
- bool set_bool(string field_name) {
- bool val;
- if (field_name in cgi.post
- && cgi.post[field_name] == "on") {
- val = true;
- } else if (field_name in cgi.get
- && cgi.get[field_name] == "on") {
- val = true;
- } else { val = false; }
- return val;
- }
- cv.db_selected = set_value("selected_db", "%s"); // selected_db_name == db (spine.search.db or whatever)
- cv.sql_match_limit = set_value("sml", "1000");
- cv.sql_match_offset = set_value("smo", "0");
- cv.search_text = set_value("sf", "");
- cv.results_type = set_value("rt", "idx");
- cv.checked_echo = set_bool("ec");
- cv.checked_stats = set_bool("sts");
- cv.checked_url = set_bool("url");
- cv.checked_searched = set_bool("se");
- cv.checked_tip = set_bool("tip");
- cv.checked_sql = set_bool("sql");
- tf = text_fields;
- }
- }
- {
- cgi.write(header);
- cgi.write(table);
- cgi.write(form);
- // cgi.write(previous_next);
- { // debug environment
- // foreach (k, d; environment.toAA) {
- // cgi.write(k ~ ": " ~ d ~ "<br>");
- // }
- }
- { // debug cgi info
- // cgi.write("db_selected: " ~ cv.db_selected ~ "<br>\n");
- // cgi.write("search_text: " ~ cv.search_text ~ "<br>\n");
- // cgi.write("sql_match_limit: " ~ cv.sql_match_limit ~ ";\n");
- // cgi.write("sql_match_offset: " ~ cv.sql_match_offset ~ ";\n");
- // cgi.write("results_type: " ~ cv.results_type ~ "<br>\n");
- // cgi.write("cv.checked_echo: " ~ (cv.checked_echo ? "checked" : "off") ~ "; \n");
- // cgi.write("cv.checked_stats: " ~ (cv.checked_stats ? "checked" : "off") ~ "; \n");
- // cgi.write("cv.checked_url: " ~ (cv.checked_url ? "checked" : "off") ~ "; \n");
- // cgi.write("cv.checked_searched: " ~ (cv.checked_searched ? "checked" : "off") ~ ";<br>\n");
- // cgi.write("cv.checked_tip: " ~ (cv.checked_tip ? "checked" : "off") ~ "; \n");
- // cgi.write("cv.checked_sql: " ~ (cv.checked_sql ? "checked" : "off") ~ "<br>\n");
- }
- }
- auto db = Database(conf.db_path ~ cv.db_selected);
- {
- uint sql_match_offset_counter(T)(T cv) {
- sql_match_offset_count += cv.sql_match_limit.to!uint;
- return sql_match_offset_count;
- }
- void sql_search_query() {
- string highlight_text_matched(string _txt, string search_field) {
- string _mark_open = "┤";
- string _mark_close = "├";
- string _span_match = "<span class=\"match\">";
- string _span_close = "</span>";
- string _sf_str = search_field.strip.split("%%20").join(" ").strip;
- string[] _sf_arr = _sf_str.split(regex(r"\s+AND\s+|\s+OR\s+"));
- auto rgx_url = regex(r"<a href=[^>]+?>");
- foreach (_sf; _sf_arr) {
- auto rgx_matched_text = regex(_sf, "i");
- auto rgx_marked_pair = regex(r"┤(?P<keep>" ~ _sf ~ ")├", "i");
- if (auto m = _txt.matchFirst(rgx_url)) {
- _txt = replaceAll!(m =>
- _mark_open
- ~ m.captures[0]
- ~ _mark_close
- )(_txt, rgx_matched_text);
- _txt = replaceAll!(m =>
- replaceAll!(u =>
- u["keep"]
- )(m.hit, rgx_marked_pair)
- )(_txt, rgx_url);
- _txt = replaceAll!(m =>
- _span_match
- ~ m["keep"]
- ~ _span_close
- )(_txt, rgx_marked_pair);
- } else {
- _txt = replaceAll!(m =>
- _span_match
- ~ m.captures[0]
- ~ _span_close
- )(_txt, rgx_matched_text);
- }
- }
- return _txt;
- }
- string select_field_like(string db_field, string search_field) {
- string where_ = "";
- if (!(search_field.empty)) {
- string _sf = search_field.strip.split("%%20").join(" ");
- if (_sf.match(r" OR ")) {
- _sf = _sf.split(" OR ").join("%%' OR " ~ db_field ~ " LIKE '%%");
- }
- if (_sf.match(r" AND ")) {
- _sf = _sf.split(" AND ").join("%%' AND " ~ db_field ~ " LIKE '%%");
- }
- _sf = "( " ~ db_field ~ " LIKE\n '%%" ~ _sf ~ "%%' )";
- where_ ~= format(q"┃
- %%s
-┃",
- _sf
- );
- }
- return where_;
- }
- string[] _fields;
- _fields ~= select_field_like("doc_objects.clean", tf.text);
- _fields ~= select_field_like("metadata_and_text.title", tf.title);
- _fields ~= select_field_like("metadata_and_text.creator_author", tf.author);
- _fields ~= select_field_like("metadata_and_text.uid", tf.uid);
- _fields ~= select_field_like("metadata_and_text.src_filename_base", tf.fn);
- _fields ~= select_field_like("metadata_and_text.src_filename_base", tf.src_filename_base);
- _fields ~= select_field_like("metadata_and_text.language_document_char", tf.language);
- _fields ~= select_field_like("metadata_and_text.date_published", tf.date);
- _fields ~= select_field_like("metadata_and_text.classify_keywords", tf.keywords);
- _fields ~= select_field_like("metadata_and_text.classify_topic_register", tf.topic_register);
- string[] fields;
- foreach (f; _fields) {
- if (!(f.empty)) { fields ~= f; }
- }
- string fields_str = "";
- fields_str ~= fields.join(" AND ");
- sql_select.the_body ~= format(q"┃
-SELECT
- metadata_and_text.uid,
- metadata_and_text.title,
- metadata_and_text.creator_author_last_first,
- metadata_and_text.creator_author,
- metadata_and_text.src_filename_base,
- metadata_and_text.language_document_char,
- metadata_and_text.date_published,
- metadata_and_text.classify_keywords,
- metadata_and_text.classify_topic_register,
- doc_objects.body,
- doc_objects.seg_name,
- doc_objects.ocn,
- metadata_and_text.uid
-FROM
- doc_objects,
- metadata_and_text
-WHERE (
- %%s
- )
-AND
- doc_objects.uid_metadata_and_text = metadata_and_text.uid
-ORDER BY
- metadata_and_text.creator_author_last_first,
- metadata_and_text.date_published DESC,
- metadata_and_text.title,
- metadata_and_text.language_document_char,
- metadata_and_text.src_filename_base,
- doc_objects.ocn
-LIMIT %%s OFFSET %%s
-;┃",
- fields_str,
- cv.sql_match_limit,
- cv.sql_match_offset,
- );
- (cv.checked_sql)
- ? cgi.write(previous_next
- ~ "<hr><font size=\"2\" color=\"#666666\">"
- ~ sql_select.the_body.strip.split("\n ").join(" ").split("\n").join("<br>")
- ~ "</font>\n"
- )
- : "";
- cgi.write(previous_next);
- auto select_query_results = db.execute(sql_select.the_body).cached;
- string _old_uid = "";
- if (!select_query_results.empty) {
- string _date_published = "0000";
- string _close_para = "";
- string _matched_ocn_open = "";
- foreach (idx, row; select_query_results) {
- if (row["uid"].as!string != _old_uid) {
- _close_para = (idx == 1) ? "" : "</p>";
- _matched_ocn_open = (idx == 1) ? "" : "<p class=\"matched_ocn\">";
- _old_uid = row["uid"].as!string;
- _date_published = (row["date_published"].as!string.match(regex(r"^([0-9]{4})")))
- ? row["date_published"].as!string : "0000"; // used in regex that breaks if no match
- auto m = _date_published.match(regex(r"^([0-9]{4})"));
- string _date = (m.hit == "0000") ? "(year?) " : "(" ~ m.hit ~ ") ";
- cgi.write(
- _close_para
- ~ "<hr><div class=\"publication\">"
- ~ "<p class=\"publication\"><a href=\""
- ~ "https://" ~ conf.http_host ~ conf.doc_collection_subroot ~ "/"
- ~ row["language_document_char"].as!string ~ "/html/"
- ~ row["src_filename_base"].as!string ~ "/"
- ~ "toc.html"
- ~ "\">\""
- ~ row["title"].as!string ~ "\""
- ~ "</a> "
- ~ _date
- ~ "[" ~ row["language_document_char"].as!string ~ "] "
- ~ row["creator_author_last_first"].as!string
- ~ " "
- ~ show_matched_objects(row["src_filename_base"].as!string)
- ~ "</p>"
- ~ "</div>"
- );
- }
- if (cv.results_type == "txt") {
- if (row["ocn"].as!string != "0") {
- cgi.write(
- "<div class=\"flex-container\">"
- ~ "<div class=\"textview_ocn\" style=\"flex: 0 0 1.2em\">"
- ~ "<p class=\"ocn_is\"><a href=\""
- ~ "https://" ~ conf.http_host ~ conf.doc_collection_subroot ~ "/"
- ~ row["language_document_char"].as!string ~ "/html/"
- ~ row["src_filename_base"].as!string ~ "/"
- ~ row["seg_name"].as!string ~ ".html#" ~ row["ocn"].as!string
- ~ "\">"
- ~ row["ocn"].as!string
- ~ "</a>:</p>"
- ~ "</div>"
- ~ "<div class=\"textview_found\">"
- ~ highlight_text_matched(row["body"].as!string, tf.text)
- ~ "</div>"
- ~ "</div>"
- );
- } else {
- cgi.write(
- "<div class=\"flex-container\">"
- ~ "<div class=\"textview_ocn\" style=\"flex: 0 0 1.2em\">"
- ~ "<p class=\"ocn_is\"><a href=\""
- ~ "https://" ~ conf.http_host ~ conf.doc_collection_subroot ~ "/"
- ~ row["language_document_char"].as!string ~ "/html/"
- ~ row["src_filename_base"].as!string ~ "/toc.html"
- ~ "\">"
- ~ row["ocn"].as!string
- ~ "</a>:</p>"
- ~ "</div>"
- ~ "<div class=\"textview_found\">"
- ~ highlight_text_matched(row["body"].as!string, tf.text)
- ~ "</div>"
- ~ "</div>"
- );
- }
- } else {
- if (row["ocn"].as!string != "0") {
- cgi.write(
- _matched_ocn_open
- ~ "<a href=\""
- ~ "https://" ~ conf.http_host ~ conf.doc_collection_subroot ~ "/"
- ~ row["language_document_char"].as!string ~ "/html/"
- ~ row["src_filename_base"].as!string ~ "/"
- ~ row["seg_name"].as!string ~ ".html#" ~ row["ocn"].as!string
- ~ "\">"
- ~ row["ocn"].as!string
- ~ "</a>, "
- );
- } else {
- cgi.write(
- _matched_ocn_open
- ~ "<a href=\""
- ~ "https://" ~ conf.http_host ~ conf.doc_collection_subroot ~ "/"
- ~ row["language_document_char"].as!string ~ "/html/"
- ~ row["src_filename_base"].as!string ~ "/toc.html"
- ~ "\">"
- ~ row["ocn"].as!string
- ~ "</a>, "
- );
- }
- _matched_ocn_open = "";
- }
- }
- cgi.write( previous_next);
-
- } else { // offset_not_beyond_limit = false;
- cgi.write("select_query_results empty<p>\n");
- }
- cgi.write("<br><p class=\"center\"><a href=\"https://sisudoc.org/\" target=\"_top\">
-<label for=\"find\"><b>≅ SiSU spine</b></label>
-</a> <label for=\"find\">(generated) search form</label>
-<br><a href=\"https://git.sisudoc.org/\" target=\"_top\">
- git</a>
-</p>
-");
- }
- sql_search_query;
- }
- {
- db.close;
- }
- {
- string tail = format(q"┃
-</body>
-┃");
- cgi.write(tail);
- }
-}
-mixin GenericMain!cgi_function_intro;
-≓",
- get_doc_collection_subroot(make_and_meta_struct.conf.output_path),
- make_and_meta_struct.conf.output_path ~ "/sqlite/",
- _sqlite_db_fn,
- (opt_action.cgi_search_title.empty)
- ? make_and_meta_struct.conf.w_srv_cgi_search_form_title
- : opt_action.cgi_search_title,
- (opt_action.css_theme_default) ? "FFFFFF" : "000000",
- (opt_action.css_theme_default) ? "000000" : "CCCCCC",
- (opt_action.css_theme_default) ? "FFFFFF" : "000000",
- (opt_action.css_theme_default) ? "FFFFFF" : "000000",
- (opt_action.css_theme_default) ? "003399" : "FFFFFF",
- (opt_action.css_theme_default) ? "003399" : "999999",
- "000000",
- (opt_action.css_theme_default) ? "F9F9AA" : "555555",
- (opt_action.css_theme_default) ? "777777" : "BBBBBB",
- (opt_action.css_theme_default) ? "32CD32" : "9ACD32",
- (opt_action.css_theme_default) ? "777777" : "BBBBBB",
- (opt_action.css_theme_default) ? "FFFFFF" : "000000",
- (opt_action.css_theme_default) ? "003399" : "888888",
- (opt_action.css_theme_default) ? "000000" : "FFFFFF",
- (opt_action.css_theme_default) ? "FFFFFF" : "777777",
- (opt_action.css_theme_default) ? "000000" : "FFFF48",
- (opt_action.css_theme_default) ? "FFFF48" : "777748",
- (opt_action.cgi_search_title.empty)
- ? make_and_meta_struct.conf.w_srv_cgi_search_form_title
- : opt_action.cgi_search_title,
- (opt_action.css_theme_default) ? "222222" : "AAAAAA",
- _cgi_search_script,
- _sqlite_db_fn,
-).strip;
- string _cgi_path = (opt_action.output_dir_set.length > 0)
- ? opt_action.output_dir_set
- : (make_and_meta_struct.conf.w_srv_data_root_path.length > 0)
- ? make_and_meta_struct.conf.w_srv_data_root_path
- : "";
- auto pth_sqlite_cgi = spinePathsSQLiteCGI!()(_cgi_search_script_raw_fn_d, _cgi_search_script, _cgi_path);
- { // cgi-bin search form src d
- try {
- if (!exists(pth_sqlite_cgi.src)) {
- pth_sqlite_cgi.src.mkdirRecurse;
- }
- if (!exists(pth_sqlite_cgi.cgi_bin)) {
- pth_sqlite_cgi.cgi_bin.mkdirRecurse;
- }
- auto f = File(pth_sqlite_cgi.search_form_path_out, "w");
- f.write(the_cgi_search_form);
- // foreach (o; metadata_) {
- // f.writeln(o);
- // }
- } catch (ErrnoException ex) {
- // Handle error
- }
- // if (!(opt_action.quiet)) {
- // writeln(" ", pth_sqlite_cgi.search_form);
- // }
- }
- string the_dub_sdl = format(q"≓
-name "spine_cgi_sqlite_search"
-description "spine cgi sqlite search"
-authors "Ralph Amissah"
-copyright "Copyright © 2022, Ralph Amissah"
-license "GPL-3.0+"
-dependency "d2sqlite3" version="%s"
-dependency "arsd-official:cgi" version="%s"
- subConfiguration "arsd-official:cgi" "cgi"
-targetType "executable"
-targetPath "./cgi-bin"
-mainSourceFile "%s"
-configuration "default" {
- targetType "executable"
- targetName "%s"
- postGenerateCommands "notify-send -t 0 'D executable ready' 'spine cgi sqlite search d'"
-}
-≓",
- "~>0.18.3", // d2sqlite3 dependency version
- "~>7.2.0", // arsd-official:cgi dependency version
- "src/" ~ _cgi_search_script_raw_fn_d,
- _cgi_search_script
-).strip;
- { // dub.sdl
- try {
- auto f = File(pth_sqlite_cgi.dub_sdl_path_out, "w");
- f.write(the_dub_sdl);
- // foreach (o; metadata_) {
- // f.writeln(o);
- // }
- } catch (ErrnoException ex) {
- // Handle error
- }
- }
- // { // get cgi.d
- // // import std.net.curl, std.stdio;
- // // char[] cgi_d;
- // // if (opt_action.allow_downloads) {
- // // try {
- // // cgi_d = get!HTTP("https://raw.githubusercontent.com/adamdruppe/arsd/master/cgi.d");
- // // } catch (ErrnoException ex) {
- // // // Handle error
- // // // CurlCode perform(ThrowOnError throwOnError = Yes.throwOnError);
- // // CurlCode perform(ThrowOnError throwOnError = No.throwOnError);
- // // }
- // // if (cgi_d && cgi_d.length > 0) {
- // // try {
- // // auto f = File(pth_sqlite_cgi.cgi_d_path_out, "w");
- // // f.write(cgi_d);
- // // } catch (ErrnoException ex) {
- // // // Handle error
- // // }
- // // }
- // // }
- // }
- }
-}
diff --git a/src/sisudoc/io_out/create_abstraction_db.d b/src/sisudoc/io_out/create_abstraction_db.d
new file mode 100644
index 0000000..20ca074
--- /dev/null
+++ b/src/sisudoc/io_out/create_abstraction_db.d
@@ -0,0 +1,355 @@
+/+
+- Name: SisuDoc Spine, Doc Reform [a part of]
+ - Description: documents, structuring, processing, publishing, search
+ - static content generator
+
+ - Author: Ralph Amissah
+ [ralph.amissah@gmail.com]
+
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
+
+ - License: AGPL 3 or later:
+
+ Spine (SiSU), a framework for document structuring, publishing and
+ search
+
+ Copyright (C) Ralph Amissah
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU AFERO General Public License as published by the
+ Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see [https://www.gnu.org/licenses/].
+
+ If you have Internet connection, the latest version of the AGPL should be
+ available at these locations:
+ [https://www.fsf.org/licensing/licenses/agpl.html]
+ [https://www.gnu.org/licenses/agpl.html]
+
+ - Spine (by Doc Reform, related to SiSU) uses standard:
+ - docReform markup syntax
+ - standard SiSU markup syntax with modified headers and minor modifications
+ - docReform object numbering
+ - standard SiSU object citation numbering & system
+
+ - Homepages:
+ [https://www.sisudoc.org]
+ [https://www.doc-reform.org]
+
+ - Git
+ [https://git.sisudoc.org/]
+
++/
+module sisudoc.io_out.create_abstraction_db;
+
+/+ ↓ write document abstraction as per-document sqlite3 database +/
+template spineAbstractionDb() {
+ import std.conv : to;
+ import std.file;
+ import std.path;
+ import std.stdio;
+ import std.string;
+ import std.array;
+ import d2sqlite3;
+ import sisudoc.io_out.paths_output;
+
+ void spineAbstractionDb(D)(D doc) {
+ auto doc_abstraction = doc.abstraction;
+ auto doc_matters = doc.matters;
+
+ /+ ↓ determine output path +/
+ auto out_pth = spineOutPaths!()(doc_matters.output_path, doc_matters.src.language);
+ string base_dir = "abstraction";
+ string base_pth = ((out_pth.output_base.chainPath(base_dir)).asNormalizedPath).array;
+ try {
+ if (!exists(base_pth)) {
+ base_pth.mkdirRecurse;
+ }
+ } catch (Exception ex) {
+ }
+ string db_file = ((base_pth.chainPath(
+ doc_matters.src.doc_uid_out ~ ".abstraction.db")).asNormalizedPath).array;
+
+ /+ ↓ remove existing file to start fresh +/
+ try {
+ if (exists(db_file)) {
+ remove(db_file);
+ }
+ } catch (Exception ex) {
+ }
+
+ if (doc_matters.opt.action.vox_gt_1) {
+ writeln(" ", db_file);
+ }
+
+ /+ ↓ open database and create schema +/
+ auto db = Database(db_file);
+ db.run("PRAGMA journal_mode=WAL");
+ db.run("PRAGMA synchronous=NORMAL");
+
+ db.run("
+ CREATE TABLE metadata (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+ );
+
+ CREATE TABLE objects (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ section TEXT NOT NULL,
+ seq INTEGER NOT NULL,
+ ocn INTEGER DEFAULT 0,
+ is_a TEXT NOT NULL,
+ is_of_part TEXT,
+ is_of_type TEXT,
+ heading_level INTEGER,
+ identifier TEXT,
+ parent_ocn INTEGER DEFAULT 0,
+ last_descendant_ocn INTEGER DEFAULT 0,
+ ancestors TEXT,
+ dummy_heading INTEGER DEFAULT 0,
+ object_number_off INTEGER DEFAULT 0,
+ indent_base INTEGER DEFAULT 0,
+ indent_hang INTEGER DEFAULT 0,
+ bullet INTEGER DEFAULT 0,
+ lang TEXT,
+ has_links INTEGER DEFAULT 0,
+ has_notes_reg INTEGER DEFAULT 0,
+ has_notes_star INTEGER DEFAULT 0,
+ has_images INTEGER DEFAULT 0,
+ segment TEXT,
+ segment_prev TEXT,
+ segment_next TEXT,
+ anchor TEXT,
+ table_cols INTEGER,
+ table_widths TEXT,
+ table_header INTEGER,
+ code_syntax TEXT,
+ code_linenumbers INTEGER DEFAULT 0,
+ text TEXT
+ );
+
+ CREATE INDEX idx_objects_section ON objects(section);
+ CREATE INDEX idx_objects_ocn ON objects(ocn);
+ CREATE INDEX idx_objects_parent ON objects(parent_ocn);
+ CREATE INDEX idx_objects_is_a ON objects(is_a);
+ CREATE INDEX idx_objects_heading ON objects(heading_level)
+ WHERE heading_level IS NOT NULL;
+ ");
+
+ /+ ↓ populate metadata +/
+ db.run("BEGIN TRANSACTION");
+
+ auto meta_stmt = db.prepare(
+ "INSERT INTO metadata (key, value) VALUES (:key, :value)"
+ );
+ auto meta = doc_matters.conf_make_meta.meta;
+
+ void insertMeta(string key, string value) {
+ if (value.length > 0) {
+ meta_stmt.bind(":key", key);
+ meta_stmt.bind(":value", value);
+ meta_stmt.execute();
+ meta_stmt.reset();
+ }
+ }
+
+ insertMeta("title.main", meta.title_main);
+ insertMeta("title.subtitle", meta.title_subtitle);
+ insertMeta("title.full", meta.title_full);
+ insertMeta("title.language", meta.title_language);
+ insertMeta("creator.author", meta.creator_author);
+ insertMeta("creator.author_surname", meta.creator_author_surname);
+ insertMeta("creator.author_surname_fn", meta.creator_author_surname_fn);
+ insertMeta("creator.author_email", meta.creator_author_email);
+ insertMeta("creator.illustrator", meta.creator_illustrator);
+ insertMeta("creator.translator", meta.creator_translator);
+ insertMeta("date.published", meta.date_published);
+ insertMeta("date.created", meta.date_created);
+ insertMeta("date.issued", meta.date_issued);
+ insertMeta("date.available", meta.date_available);
+ insertMeta("date.modified", meta.date_modified);
+ insertMeta("date.valid", meta.date_valid);
+ insertMeta("rights.copyright", meta.rights_copyright);
+ insertMeta("rights.license", meta.rights_license);
+ insertMeta("classify.topic_register", meta.classify_topic_register);
+ insertMeta("classify.subject", meta.classify_subject);
+ insertMeta("classify.keywords", meta.classify_keywords);
+ insertMeta("classify.loc", meta.classify_loc);
+ insertMeta("classify.dewey", meta.classify_dewey);
+ insertMeta("identifier.isbn", meta.identifier_isbn);
+ insertMeta("identifier.oclc", meta.identifier_oclc);
+ insertMeta("language.document", meta.language_document);
+ insertMeta("notes.abstract", meta.notes_abstract);
+ insertMeta("notes.description", meta.notes_description);
+ insertMeta("notes.summary", meta.notes_summary);
+
+ /+ ↓ make settings +/
+ auto make = doc_matters.conf_make_meta.make;
+ insertMeta("make.doc_type", make.doc_type);
+ insertMeta("make.auto_num_top_at_level", make.auto_num_top_at_level);
+ insertMeta("make.auto_num_top_lv", make.auto_num_top_lv.to!string);
+ insertMeta("make.auto_num_depth", make.auto_num_depth.to!string);
+
+ /+ ↓ doc_has counts +/
+ insertMeta("doc_has.inline_links", doc_matters.has.inline_links.to!string);
+ insertMeta("doc_has.inline_notes_reg", doc_matters.has.inline_notes_reg.to!string);
+ insertMeta("doc_has.inline_notes_star", doc_matters.has.inline_notes_star.to!string);
+ insertMeta("doc_has.tables", doc_matters.has.tables.to!string);
+ insertMeta("doc_has.codeblocks", doc_matters.has.codeblocks.to!string);
+ insertMeta("doc_has.images", doc_matters.has.images.to!string);
+ insertMeta("doc_has.poems", doc_matters.has.poems.to!string);
+ insertMeta("doc_has.groups", doc_matters.has.groups.to!string);
+ insertMeta("doc_has.blocks", doc_matters.has.blocks.to!string);
+ insertMeta("doc_has.quotes", doc_matters.has.quotes.to!string);
+
+ meta_stmt.finalize();
+
+ /+ ↓ populate objects +/
+ auto obj_stmt = db.prepare(
+ "INSERT INTO objects ("
+ ~ "section, seq, ocn, is_a, is_of_part, is_of_type,"
+ ~ "heading_level, identifier, parent_ocn, last_descendant_ocn,"
+ ~ "ancestors, dummy_heading, object_number_off,"
+ ~ "indent_base, indent_hang, bullet, lang,"
+ ~ "has_links, has_notes_reg, has_notes_star, has_images,"
+ ~ "segment, segment_prev, segment_next, anchor,"
+ ~ "table_cols, table_widths, table_header,"
+ ~ "code_syntax, code_linenumbers, text"
+ ~ ") VALUES ("
+ ~ ":section, :seq, :ocn, :is_a, :is_of_part, :is_of_type,"
+ ~ ":heading_level, :identifier, :parent_ocn, :last_descendant_ocn,"
+ ~ ":ancestors, :dummy_heading, :object_number_off,"
+ ~ ":indent_base, :indent_hang, :bullet, :lang,"
+ ~ ":has_links, :has_notes_reg, :has_notes_star, :has_images,"
+ ~ ":segment, :segment_prev, :segment_next, :anchor,"
+ ~ ":table_cols, :table_widths, :table_header,"
+ ~ ":code_syntax, :code_linenumbers, :text"
+ ~ ")"
+ );
+
+ string[] section_order = ["head", "toc", "body", "endnotes",
+ "glossary", "bibliography", "bookindex", "blurb"];
+
+ foreach (section; section_order) {
+ if (section !in doc_abstraction) continue;
+ auto section_objs = doc_abstraction[section];
+ if (section_objs.length == 0) continue;
+
+ foreach (seq, obj; section_objs) {
+ obj_stmt.bind(":section", section);
+ obj_stmt.bind(":seq", cast(int) seq);
+ obj_stmt.bind(":ocn", obj.metainfo.ocn);
+ obj_stmt.bind(":is_a", obj.metainfo.is_a);
+
+ /+ ↓ nullable string fields +/
+ void bindStr(string param, string val) {
+ import std.typecons : Nullable;
+ if (val.length > 0) {
+ obj_stmt.bind(param, val);
+ } else {
+ obj_stmt.bind(param, Nullable!string());
+ }
+ }
+
+ bindStr(":is_of_part", obj.metainfo.is_of_part);
+ bindStr(":is_of_type", obj.metainfo.is_of_type);
+
+ /+ ↓ heading level +/
+ {
+ import std.typecons : Nullable;
+ if (obj.metainfo.is_a == "heading" && obj.metainfo.heading_lev_markup < 9) {
+ obj_stmt.bind(":heading_level", obj.metainfo.heading_lev_markup);
+ } else {
+ obj_stmt.bind(":heading_level", Nullable!int());
+ }
+ }
+
+ bindStr(":identifier", obj.metainfo.identifier);
+ obj_stmt.bind(":parent_ocn", obj.metainfo.parent_ocn);
+ obj_stmt.bind(":last_descendant_ocn", obj.metainfo.last_descendant_ocn);
+
+ /+ ↓ ancestors as space-separated integers +/
+ {
+ bool has_ancestors = false;
+ foreach (a; obj.metainfo.markedup_ancestors) {
+ if (a != 0) { has_ancestors = true; break; }
+ }
+ if (has_ancestors) {
+ string anc;
+ foreach (i, a; obj.metainfo.markedup_ancestors) {
+ if (i > 0) anc ~= " ";
+ anc ~= a.to!string;
+ }
+ obj_stmt.bind(":ancestors", anc);
+ } else {
+ import std.typecons : Nullable;
+ obj_stmt.bind(":ancestors", Nullable!string());
+ }
+ }
+
+ obj_stmt.bind(":dummy_heading", obj.metainfo.dummy_heading ? 1 : 0);
+ obj_stmt.bind(":object_number_off", obj.metainfo.object_number_off ? 1 : 0);
+ obj_stmt.bind(":indent_base", obj.attrib.indent_base);
+ obj_stmt.bind(":indent_hang", obj.attrib.indent_hang);
+ obj_stmt.bind(":bullet", obj.attrib.bullet ? 1 : 0);
+ bindStr(":lang", obj.attrib.language);
+ obj_stmt.bind(":has_links", obj.has.inline_links ? 1 : 0);
+ obj_stmt.bind(":has_notes_reg", obj.has.inline_notes_reg ? 1 : 0);
+ obj_stmt.bind(":has_notes_star", obj.has.inline_notes_star ? 1 : 0);
+ obj_stmt.bind(":has_images", obj.has.images ? 1 : 0);
+ bindStr(":segment", obj.tags.in_segment_html);
+ bindStr(":segment_prev", obj.tags.segname_prev);
+ bindStr(":segment_next", obj.tags.segname_next);
+ bindStr(":anchor", obj.tags.anchor_tag_html);
+
+ /+ ↓ table properties +/
+ {
+ import std.typecons : Nullable;
+ if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) {
+ obj_stmt.bind(":table_cols", obj.table.number_of_columns);
+ if (obj.table.column_widths.length > 0) {
+ string[] ws;
+ foreach (w; obj.table.column_widths) ws ~= w.to!string;
+ obj_stmt.bind(":table_widths", ws.join(" "));
+ } else {
+ obj_stmt.bind(":table_widths", Nullable!string());
+ }
+ obj_stmt.bind(":table_header", obj.table.heading ? 1 : 0);
+ } else {
+ obj_stmt.bind(":table_cols", Nullable!int());
+ obj_stmt.bind(":table_widths", Nullable!string());
+ obj_stmt.bind(":table_header", Nullable!int());
+ }
+ }
+
+ /+ ↓ code block properties +/
+ {
+ import std.typecons : Nullable;
+ if (obj.metainfo.is_a == "code") {
+ bindStr(":code_syntax", obj.code_block.syntax);
+ obj_stmt.bind(":code_linenumbers", obj.code_block.linenumbers ? 1 : 0);
+ } else {
+ obj_stmt.bind(":code_syntax", Nullable!string());
+ obj_stmt.bind(":code_linenumbers", 0);
+ }
+ }
+
+ /+ ↓ text content +/
+ bindStr(":text", obj.text);
+
+ obj_stmt.execute();
+ obj_stmt.reset();
+ }
+ }
+
+ obj_stmt.finalize();
+ db.run("COMMIT TRANSACTION");
+ }
+}
diff --git a/src/sisudoc/io_out/create_zip_file.d b/src/sisudoc/io_out/create_zip_file.d
index 7bd58bc..4063ab5 100644
--- a/src/sisudoc/io_out/create_zip_file.d
+++ b/src/sisudoc/io_out/create_zip_file.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/metadoc_curate.d b/src/sisudoc/io_out/curate/metadoc_curate.d
index da20b3e..8e87167 100644
--- a/src/sisudoc/meta/metadoc_curate.d
+++ b/src/sisudoc/io_out/curate/metadoc_curate.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -47,7 +47,7 @@
[https://git.sisudoc.org/]
+/
-module sisudoc.meta.metadoc_curate;
+module sisudoc.io_out.curate.metadoc_curate;
@safe:
template spineMetaDocCurate() {
auto spineMetaDocCurate(T,H)(
diff --git a/src/sisudoc/meta/metadoc_curate_authors.d b/src/sisudoc/io_out/curate/metadoc_curate_authors.d
index d8b5261..6a356e7 100644
--- a/src/sisudoc/meta/metadoc_curate_authors.d
+++ b/src/sisudoc/io_out/curate/metadoc_curate_authors.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -47,7 +47,7 @@
[https://git.sisudoc.org/]
+/
-module sisudoc.meta.metadoc_curate_authors;
+module sisudoc.io_out.curate.metadoc_curate_authors;
@safe:
import std.algorithm;
import std.array;
diff --git a/src/sisudoc/meta/metadoc_curate_topics.d b/src/sisudoc/io_out/curate/metadoc_curate_topics.d
index 3045dcb..05643b9 100644
--- a/src/sisudoc/meta/metadoc_curate_topics.d
+++ b/src/sisudoc/io_out/curate/metadoc_curate_topics.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -47,7 +47,7 @@
[https://git.sisudoc.org/]
+/
-module sisudoc.meta.metadoc_curate_topics;
+module sisudoc.io_out.curate.metadoc_curate_topics;
@safe:
import std.algorithm;
import std.array;
diff --git a/src/sisudoc/io_out/defaults.d b/src/sisudoc/io_out/defaults.d
index e373b76..290ca89 100644
--- a/src/sisudoc/io_out/defaults.d
+++ b/src/sisudoc/io_out/defaults.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/epub3.d b/src/sisudoc/io_out/epub3.d
index c8ca757..c715630 100644
--- a/src/sisudoc/io_out/epub3.d
+++ b/src/sisudoc/io_out/epub3.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/html.d b/src/sisudoc/io_out/html.d
index fc9ef54..a294f30 100644
--- a/src/sisudoc/io_out/html.d
+++ b/src/sisudoc/io_out/html.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/html_snippet.d b/src/sisudoc/io_out/html_snippet.d
index 7f1edea..d02cb28 100644
--- a/src/sisudoc/io_out/html_snippet.d
+++ b/src/sisudoc/io_out/html_snippet.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/hub.d b/src/sisudoc/io_out/hub.d
index f98be01..6ca047a 100644
--- a/src/sisudoc/io_out/hub.d
+++ b/src/sisudoc/io_out/hub.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/latex.d b/src/sisudoc/io_out/latex.d
index 96511c4..8a1ae3e 100644
--- a/src/sisudoc/io_out/latex.d
+++ b/src/sisudoc/io_out/latex.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -447,7 +447,7 @@ template outputLaTeX() {
}
string _latex_image_path(string _image_path) {
auto pth_latex = spinePathsLaTeX(doc_matters);
- _image_path = pth_latex.latex_path_stuff ~ "/" ~ _image_path;
+ _image_path = pth_latex.base_filename(doc_matters.src.filename) ~ "/" ~ _image_path;
return _image_path;
}
string _if_images(string _linked_content) {
@@ -679,7 +679,7 @@ template outputLaTeX() {
\pagenumbering{alph}
\setcounter{page}{1}
\markboth{%s}{%s}
-\br\linebreak Copyright {\begin{small}{\copyright\end{small}} %s \br\linebreak
+\br\linebreak Copyright \begin{small}\copyright\end{small} %s \br\linebreak
%s
\clearpage┃";
_txt = format(_tex_para,
@@ -1518,16 +1518,17 @@ template outputLaTeXstyStatic() {
\usepackage{multicol}
\setlength{\marginparsep}{4mm}
\setlength{\marginparwidth}{8mm}
-\usepackage[scaled]{dejavu}
+\usepackage{dejavu}
\renewcommand*\familydefault{\sfdefault}
\usepackage{inconsolata}
\usepackage[T1]{fontenc}
\usepackage{newunicodechar}
%% \usepackage[utf8]{inputenc}
\usepackage{alltt}
+\PassOptionsToPackage{hyphens}{url}
\usepackage[
unicode=true,
- pdfusetitle,
+ pdfusetitle,
pdfsubject={},
pdfkeywords={}, %% keywords list {} {} {},
pdftoolbar=true,
@@ -1542,14 +1543,15 @@ template outputLaTeXstyStatic() {
bookmarksopen=false,
bookmarksnumbered=false,
backref=false,
- breaklinks=false,
+ breaklinks=true,
colorlinks=true,
urlcolor=black,
filecolor=black,
linkcolor=black,
citecolor=black, %% links_mono_or_color_set
]{hyperref}
-\PassOptionsToPackage{hyphens}{url}\usepackage{hyperref}
+\usepackage{xurl}
+%% \PassOptionsToPackage{hyphens}{url}\usepackage{hyperref}
\usepackage[usenames]{color}
\definecolor{myblack}{rgb}{0,0,0}
\definecolor{myred}{rgb}{0.75,0,0}
@@ -1681,14 +1683,14 @@ template outputLaTeXstyStatic() {
\newcommand{\spaces}[1]{{\hspace*{#1ex}}}
\newcommand{\s}{\hspace*{1ex}}
\newcommand{\hardspace}{\hspace*{1ex}}
-\newcommand{\-}{\hspace*{1ex}}
+\renewcommand{\-}{\hspace*{1ex}}
\newcommand{\caret}{{\^{~}}}
\newcommand{\pipe}{{\textbar}}
\newcommand{\curlyOpen}{{}
\newcommand{\curlyClose}{}}
\newcommand{\lt}{{UseTextSymbol{OML}{<}}}
\newcommand{\gt}{{UseTextSymbol{OML}{>}}}
-\newcommand{\slash}{{/}}
+\renewcommand{\slash}{{/}}
\newcommand{\underscore}{\_}
\newcommand{\exclaim}{\Verbatim{!}}
\newcommand{\linktext}[2]{%%
@@ -1733,9 +1735,10 @@ template outputLaTeXstyStatic() {
\end{longtable}
\end{tiny}
}
-%% \tolerance=300
-%% \clubpenalty=300
-%% \widowpenalty=300
+\tolerance=200
+\clubpenalty=150
+\widowpenalty=150
+\setlength{\emergencystretch}{3em}
%% \usepackage{atbegshi} %% http://ctan.org/pkg/atbegshi %% (BUG tmp FIX deal with problem, remove first page which is blank)
%% \AtBeginDocument{\AtBeginShipoutNext{\AtBeginShipoutDiscard}} %% (BUG tmp FIX deal with problem, remove first page which is blank)
┃",
diff --git a/src/sisudoc/io_out/metadata.d b/src/sisudoc/io_out/metadata.d
index a89b31a..92b3bf9 100644
--- a/src/sisudoc/io_out/metadata.d
+++ b/src/sisudoc/io_out/metadata.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/odt.d b/src/sisudoc/io_out/odt.d
index c8f5fe9..7a85bfb 100644
--- a/src/sisudoc/io_out/odt.d
+++ b/src/sisudoc/io_out/odt.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -892,7 +892,7 @@ template outputODT() {
}
return doc_odt;
}
-
+ ;
string odt_tail() {
string _odt_tail = format(q"┃<text:p text:style-name="P_normal">spine: &lt;<text:a xl:type="simple" xl:href="https://www.sisudoc.org">www.sisudoc.org</text:a>&gt; and &lt;<text:a xl:type="simple" xl:href="https://www.sisudoc.org">www.sisudoc.org</text:a>&gt;</text:p>
</office:text></office:body></office:document-content>┃",);
@@ -2117,7 +2117,6 @@ template outputODT() {
pth_odt.manifest_rdf("fs"); /+ (manifest.rdf) +/
pth_odt.settings_xml("fs"); /+ (settings.xml) +/
pth_odt.styles_xml("fs"); /+ (styles_xml) +/
-
pth_odt.content_xml("fs");
pth_odt.manifest_xml("fs");
pth_odt.meta_xml("fs");
diff --git a/src/sisudoc/io_out/package.d b/src/sisudoc/io_out/package.d
index 7cc69ff..e0512dc 100644
--- a/src/sisudoc/io_out/package.d
+++ b/src/sisudoc/io_out/package.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/paths_output.d b/src/sisudoc/io_out/paths_output.d
index c3e677d..a9d0928 100644
--- a/src/sisudoc/io_out/paths_output.d
+++ b/src/sisudoc/io_out/paths_output.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/rgx.d b/src/sisudoc/io_out/rgx.d
index f54deda..384222c 100644
--- a/src/sisudoc/io_out/rgx.d
+++ b/src/sisudoc/io_out/rgx.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/rgx_latex.d b/src/sisudoc/io_out/rgx_latex.d
index 05c1adb..1ae6147 100644
--- a/src/sisudoc/io_out/rgx_latex.d
+++ b/src/sisudoc/io_out/rgx_latex.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/rgx_xhtml.d b/src/sisudoc/io_out/rgx_xhtml.d
index 58d6138..b1b1004 100644
--- a/src/sisudoc/io_out/rgx_xhtml.d
+++ b/src/sisudoc/io_out/rgx_xhtml.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/skel.d b/src/sisudoc/io_out/skel.d
index b616695..92e0d52 100644
--- a/src/sisudoc/io_out/skel.d
+++ b/src/sisudoc/io_out/skel.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/source_pod.d b/src/sisudoc/io_out/source_pod.d
index bfc2fac..b015a04 100644
--- a/src/sisudoc/io_out/source_pod.d
+++ b/src/sisudoc/io_out/source_pod.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -72,6 +72,15 @@ template spinePod() {
assert (doc_matters.src.filename.match(rgx_files.src_fn));
if (doc_matters.opt.action.source_or_pod) {
try {
+ /+ ↓ clean slate: remove per-document pod directory before regeneration,
+ but only on the first language of a multi-language document,
+ so that subsequent languages' files are not wiped +/
+ if (doc_matters.src.language == doc_matters.pod.manifest_list_of_languages[0]) {
+ string doc_pod_dir = pths_pod.base_filesystem_(doc_matters.src.filename);
+ if (exists(doc_pod_dir) && doc_pod_dir.isDir) {
+ doc_pod_dir.rmdirRecurse;
+ }
+ }
{
podArchive_directory_tree(doc_matters, pths_pod);
}
@@ -114,6 +123,11 @@ template spinePod() {
if (!exists(pths_pod.css(doc_matters.src.filename).filesystem_open_zpod)) {
pths_pod.css(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse;
}
+ if (doc_matters.opt.action.pod2) {
+ if (!exists(pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod)) {
+ pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse;
+ }
+ }
if (!exists(pths_pod.image_root(doc_matters.src.filename).filesystem_open_zpod)) {
pths_pod.image_root(doc_matters.src.filename).filesystem_open_zpod.mkdirRecurse;
}
@@ -179,6 +193,43 @@ template spinePod() {
}
}
}
+ } { // bundle abstraction .ssp file (only for --pod2)
+ if (doc_matters.opt.action.pod2) {
+ if (doc_matters.src.language == doc_matters.pod.manifest_list_of_languages[$-1]) { // wait until all language versions of .ssp generated
+ import sisudoc.io_out.paths_output;
+ /+ doc_uid_out for any language follows the same pattern, differing
+ only in the trailing ".{lng}". Strip the current language to
+ reuse the base across all languages. +/
+ string _doc_uid_base
+ = doc_matters.src.doc_uid_out[0 .. $ - doc_matters.src.lng.length];
+ foreach (_lang; doc_matters.pod.manifest_list_of_languages) { // do for all language versions
+ auto out_pth_lng = spineOutPaths!()(doc_matters.output_path, _lang);
+ string abstraction_dir = ((out_pth_lng.output_base.chainPath("abstraction")).asNormalizedPath).array;
+ string ssp_filename = _doc_uid_base ~ _lang ~ ".ssp";
+ string fn_src_in = ((abstraction_dir.chainPath(ssp_filename)).asNormalizedPath).array.to!string;
+ auto fn_src_out_pod_zip_base
+ = pths_pod.abstraction_root(doc_matters.src.filename).zpod.to!string
+ ~ "/" ~ ssp_filename;
+ auto fn_src_out_filesystem
+ = pths_pod.abstraction_root(doc_matters.src.filename).filesystem_open_zpod.to!string
+ ~ "/" ~ ssp_filename;
+ if (exists(fn_src_in)) {
+ debug(io) { writeln("(io debug) src out found: ", fn_src_in); }
+ { // take DIGEST write to pod file digests.txt
+ auto data = (cast(byte[]) (fn_src_in).read);
+ _digests[_lang]["ssp"] ~= data.sha256Of.toHexString
+ ~ "::" ~ data.length.to!string ~ " - " ~ ssp_filename ~ "\n";
+ }
+ fn_src_in.copy(fn_src_out_filesystem);
+ zip = podArchive("file_path_text", fn_src_in, fn_src_out_pod_zip_base, zip);
+ } else {
+ if (doc_matters.opt.action.debug_do_pod && doc_matters.opt.action.vox_gt_2) {
+ writeln("WARNING (io) src out NOT found (abstraction): ", fn_src_in);
+ }
+ }
+ }
+ }
+ }
} { // bundle dr_document_make
auto fn_src_in = ((doc_matters.src.is_pod)
? doc_matters.src.conf_dir_path
@@ -475,6 +526,9 @@ template spinePod() {
// if (doc_matters.opt.action.vox_gt_2) { writeln(_digests[_lang]["ssi"]); }
f.writeln(_digests[_lang]["ssi"]);
}
+ if (("ssp" in _digests[_lang]) && (_digests[_lang]["ssp"].length > 0)) {
+ f.writeln(_digests[_lang]["ssp"]);
+ }
}
}
if ("shared" in _digests) {
diff --git a/src/sisudoc/io_out/sqlite.d b/src/sisudoc/io_out/sqlite.d
index a62e658..4ce5e0d 100644
--- a/src/sisudoc/io_out/sqlite.d
+++ b/src/sisudoc/io_out/sqlite.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -71,7 +71,12 @@ template SQLiteHubBuildTablesAndPopulate() {
} else {
try {
pth_sqlite.base.mkdirRecurse;
- } catch (FileException ex) { }
+ } catch (FileException ex) {
+ stderr.writeln("FATAL: cannot create --sqlite-db-path directory: ", pth_sqlite.base);
+ stderr.writeln(" (", ex.msg, ")");
+ import core.runtime;
+ core.runtime.Runtime.terminate();
+ }
}
template SQLiteDbStatementComposite() {
void SQLiteDbStatementComposite(Db,D)(
@@ -85,7 +90,12 @@ template SQLiteHubBuildTablesAndPopulate() {
} else {
try {
pth_sqlite.base.mkdirRecurse;
- } catch (FileException ex) { }
+ } catch (FileException ex) {
+ stderr.writeln("FATAL: cannot create --sqlite-db-path directory: ", pth_sqlite.base);
+ stderr.writeln(" (", ex.msg, ")");
+ import core.runtime;
+ core.runtime.Runtime.terminate();
+ }
}
_db_statement ~= SQLiteTablesReCreate!()();
SQLiteDbRun!()(db, _db_statement, doc.matters.opt.action, "TABLE RE-CREATE");
@@ -154,7 +164,12 @@ template SQLiteHubDiscreteBuildTablesAndPopulate() {
} else {
try {
pth_sqlite.base.mkdirRecurse;
- } catch (FileException ex) { }
+ } catch (FileException ex) {
+ stderr.writeln("FATAL: cannot create --sqlite-db-path directory: ", pth_sqlite.base);
+ stderr.writeln(" (", ex.msg, ")");
+ import core.runtime;
+ core.runtime.Runtime.terminate();
+ }
}
auto db = Database(pth_sqlite.sqlite_file(doc.matters.src.filename));
template SQLiteDiscreteDbStatementComposite() {
@@ -1671,7 +1686,12 @@ template SQLiteTablesCreate() {
} else {
try {
pth_sqlite.base.mkdirRecurse;
- } catch (FileException ex) { }
+ } catch (FileException ex) {
+ stderr.writeln("FATAL: cannot create --sqlite-db-path directory: ", pth_sqlite.base);
+ stderr.writeln(" (", ex.msg, ")");
+ import core.runtime;
+ core.runtime.Runtime.terminate();
+ }
}
auto db = Database(pth_sqlite.sqlite_file);
{
diff --git a/src/sisudoc/io_out/text.d b/src/sisudoc/io_out/text.d
index 9401bae..7c4315a 100644
--- a/src/sisudoc/io_out/text.d
+++ b/src/sisudoc/io_out/text.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/io_out/xmls.d b/src/sisudoc/io_out/xmls.d
index bf52524..7b503dd 100644
--- a/src/sisudoc/io_out/xmls.d
+++ b/src/sisudoc/io_out/xmls.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -488,7 +488,7 @@ string tail(M)(M doc_matters) {
<a href="https://sisudoc.org" class="lnkicon">≅ SiSU Spine ፨</a> (object numbering &amp; object search)
</p>
<p class="small_center" id="sisu_spine"><a name="sisu_spine"></a>
- (web 1993, object numbering 1997, object search 2002 ...) 2025
+ (web 1993, object numbering 1997, object search 2002 ...) 2026
</p>
</div>
<a name="bottom" id="bottom"></a>
diff --git a/src/sisudoc/io_out/xmls_css.d b/src/sisudoc/io_out/xmls_css.d
index aef861a..cdada08 100644
--- a/src/sisudoc/io_out/xmls_css.d
+++ b/src/sisudoc/io_out/xmls_css.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -1217,6 +1217,56 @@ p.form {
footer {
background-color : #00704E;
}
+/* ------------------------------------------------------------------ */
+/* Homepage / body-flow HTML5 markup */
+/* <ul>/<li> and <details>/<summary> aligned with <p> and headings. */
+/* Scoped to direct body children to avoid affecting div.toc lists. */
+/* ------------------------------------------------------------------ */
+body > ul,
+body > ol {
+ margin-left : 5%%;
+ margin-right : 2em;
+ margin-top : 0.8em;
+ margin-bottom : 0.8em;
+ padding-left : 1.5em;
+ list-style-position : outside;
+}
+body > ul { list-style-type : disc; }
+body > ol { list-style-type : decimal; }
+body > ul li,
+body > ol li {
+ margin-left : 0;
+ margin-right : 0;
+ margin-top : 0.3em;
+ margin-bottom : 0.3em;
+ line-height : 133%%;
+ background : none;
+ text-align : left;
+ text-indent : 0;
+}
+details {
+ margin-top : 1em;
+ margin-bottom : 0.5em;
+}
+summary {
+ margin-left : 5%%;
+ margin-right : 2em;
+ padding-left : 0.2em;
+ padding-top : 0.4em;
+ padding-bottom : 0.4em;
+ font-size : 1.6rem;
+ line-height : 133%%;
+ cursor : pointer;
+}
+details > ul,
+details > ol {
+ margin-left : 5%%;
+ padding-left : 1.5em;
+}
+details > ul li,
+details > ol li {
+ margin-left : 0;
+}
┃",
_color_ocn_light,
_css_indent,
@@ -1957,6 +2007,56 @@ p.form {
footer {
background-color : #FF704E;
}
+/* ------------------------------------------------------------------ */
+/* Homepage / body-flow HTML5 markup */
+/* <ul>/<li> and <details>/<summary> aligned with <p> and headings. */
+/* Scoped to direct body children to avoid affecting div.toc lists. */
+/* ------------------------------------------------------------------ */
+body > ul,
+body > ol {
+ margin-left : 5%%;
+ margin-right : 2em;
+ margin-top : 0.8em;
+ margin-bottom : 0.8em;
+ padding-left : 1.5em;
+ list-style-position : outside;
+}
+body > ul { list-style-type : disc; }
+body > ol { list-style-type : decimal; }
+body > ul li,
+body > ol li {
+ margin-left : 0;
+ margin-right : 0;
+ margin-top : 0.3em;
+ margin-bottom : 0.3em;
+ line-height : 133%%;
+ background : none;
+ text-align : left;
+ text-indent : 0;
+}
+details {
+ margin-top : 1em;
+ margin-bottom : 0.5em;
+}
+summary {
+ margin-left : 5%%;
+ margin-right : 2em;
+ padding-left : 0.2em;
+ padding-top : 0.4em;
+ padding-bottom : 0.4em;
+ font-size : 1.6rem;
+ line-height : 133%%;
+ cursor : pointer;
+}
+details > ul,
+details > ol {
+ margin-left : 5%%;
+ padding-left : 1.5em;
+}
+details > ul li,
+details > ol li {
+ margin-left : 0;
+}
┃",
_color_ocn_dark,
_css_indent,
@@ -2634,6 +2734,56 @@ p.form {
footer {
background-color : #00704E;
}
+/* ------------------------------------------------------------------ */
+/* Homepage / body-flow HTML5 markup */
+/* <ul>/<li> and <details>/<summary> aligned with <p> and headings. */
+/* Scoped to direct body children to avoid affecting div.toc lists. */
+/* ------------------------------------------------------------------ */
+body > ul,
+body > ol {
+ margin-left : 5%%;
+ margin-right : 2em;
+ margin-top : 0.8em;
+ margin-bottom : 0.8em;
+ padding-left : 1.5em;
+ list-style-position : outside;
+}
+body > ul { list-style-type : disc; }
+body > ol { list-style-type : decimal; }
+body > ul li,
+body > ol li {
+ margin-left : 0;
+ margin-right : 0;
+ margin-top : 0.3em;
+ margin-bottom : 0.3em;
+ line-height : 133%%;
+ background : none;
+ text-align : left;
+ text-indent : 0;
+}
+details {
+ margin-top : 1em;
+ margin-bottom : 0.5em;
+}
+summary {
+ margin-left : 5%%;
+ margin-right : 2em;
+ padding-left : 0.2em;
+ padding-top : 0.4em;
+ padding-bottom : 0.4em;
+ font-size : 1.6rem;
+ line-height : 133%%;
+ cursor : pointer;
+}
+details > ul,
+details > ol {
+ margin-left : 5%%;
+ padding-left : 1.5em;
+}
+details > ul li,
+details > ol li {
+ margin-left : 0;
+}
┃",
_color_ocn_light,
_css_indent,
@@ -3300,6 +3450,56 @@ p.form {
footer {
background-color : #FF704E;
}
+/* ------------------------------------------------------------------ */
+/* Homepage / body-flow HTML5 markup */
+/* <ul>/<li> and <details>/<summary> aligned with <p> and headings. */
+/* Scoped to direct body children to avoid affecting div.toc lists. */
+/* ------------------------------------------------------------------ */
+body > ul,
+body > ol {
+ margin-left : 5%%;
+ margin-right : 2em;
+ margin-top : 0.8em;
+ margin-bottom : 0.8em;
+ padding-left : 1.5em;
+ list-style-position : outside;
+}
+body > ul { list-style-type : disc; }
+body > ol { list-style-type : decimal; }
+body > ul li,
+body > ol li {
+ margin-left : 0;
+ margin-right : 0;
+ margin-top : 0.3em;
+ margin-bottom : 0.3em;
+ line-height : 133%%;
+ background : none;
+ text-align : left;
+ text-indent : 0;
+}
+details {
+ margin-top : 1em;
+ margin-bottom : 0.5em;
+}
+summary {
+ margin-left : 5%%;
+ margin-right : 2em;
+ padding-left : 0.2em;
+ padding-top : 0.4em;
+ padding-bottom : 0.4em;
+ font-size : 1.6rem;
+ line-height : 133%%;
+ cursor : pointer;
+}
+details > ul,
+details > ol {
+ margin-left : 5%%;
+ padding-left : 1.5em;
+}
+details > ul li,
+details > ol li {
+ margin-left : 0;
+}
┃",
_color_ocn_dark,
_css_indent,
diff --git a/src/sisudoc/meta/conf_make_meta_json.d b/src/sisudoc/meta/conf_make_meta_json.d
index 4e9e5cd..c996b12 100644
--- a/src/sisudoc/meta/conf_make_meta_json.d
+++ b/src/sisudoc/meta/conf_make_meta_json.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/conf_make_meta_structs.d b/src/sisudoc/meta/conf_make_meta_structs.d
index 9503c83..6bfd8fb 100644
--- a/src/sisudoc/meta/conf_make_meta_structs.d
+++ b/src/sisudoc/meta/conf_make_meta_structs.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/conf_make_meta_yaml.d b/src/sisudoc/meta/conf_make_meta_yaml.d
index 4b56b51..f4ee7d9 100644
--- a/src/sisudoc/meta/conf_make_meta_yaml.d
+++ b/src/sisudoc/meta/conf_make_meta_yaml.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/defaults.d b/src/sisudoc/meta/defaults.d
index 97b1d5f..53a791e 100644
--- a/src/sisudoc/meta/defaults.d
+++ b/src/sisudoc/meta/defaults.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/doc_debugs.d b/src/sisudoc/meta/doc_debugs.d
index b28ba2e..40a0af5 100644
--- a/src/sisudoc/meta/doc_debugs.d
+++ b/src/sisudoc/meta/doc_debugs.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/metadoc.d b/src/sisudoc/meta/metadoc.d
index ffe297e..421ff78 100644
--- a/src/sisudoc/meta/metadoc.d
+++ b/src/sisudoc/meta/metadoc.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -61,9 +61,7 @@ template spineAbstraction() {
import sisudoc.io_in.paths_source;
import sisudoc.io_in.read_config_files;
import sisudoc.io_in.read_source_files;
- import sisudoc.io_out.hub;
mixin spineBiblio;
- mixin outputHub;
enum makeMeta { make, meta }
enum docAbst { doc_abstract_obj, doc_has }
@system auto spineAbstraction(E,P,O,Cfg,M)(
diff --git a/src/sisudoc/meta/metadoc_from_src.d b/src/sisudoc/meta/metadoc_from_src.d
index 4240a3f..4967c1f 100644
--- a/src/sisudoc/meta/metadoc_from_src.d
+++ b/src/sisudoc/meta/metadoc_from_src.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -1331,6 +1331,25 @@ template docAbstraction() {
}
}
}
+ /+ ↓ compute children_headings for heading objects +/
+ {
+ int[][int] heading_children;
+ foreach (obj; the_document_body_section) {
+ if (obj.metainfo.is_a == "heading" && obj.metainfo.parent_ocn != 0) {
+ heading_children[obj.metainfo.parent_ocn] ~= obj.metainfo.ocn;
+ }
+ }
+ foreach (ref obj; the_document_head_section) {
+ if (obj.metainfo.is_a == "heading" && obj.metainfo.ocn in heading_children) {
+ obj.metainfo.children_headings = heading_children[obj.metainfo.ocn];
+ }
+ }
+ foreach (ref obj; the_document_body_section) {
+ if (obj.metainfo.is_a == "heading" && obj.metainfo.ocn in heading_children) {
+ obj.metainfo.children_headings = heading_children[obj.metainfo.ocn];
+ }
+ }
+ }
// TODO
// - note create/insert heading object sole purpose eof close all open tags
// sort out:
diff --git a/src/sisudoc/meta/metadoc_from_src_functions.d b/src/sisudoc/meta/metadoc_from_src_functions.d
index 63143e9..bb3cd4e 100644
--- a/src/sisudoc/meta/metadoc_from_src_functions.d
+++ b/src/sisudoc/meta/metadoc_from_src_functions.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -1929,7 +1929,6 @@ template docAbstractionFunctions() {
return ret;
}
// ↑ - table
-
@system ST_flow_block_flag_line_empty flow_block_flag_line_empty_(B,CMM,Ts)(
char[] line,
string[string] an_object,
diff --git a/src/sisudoc/meta/metadoc_object_setter.d b/src/sisudoc/meta/metadoc_object_setter.d
index abcb799..018c51b 100644
--- a/src/sisudoc/meta/metadoc_object_setter.d
+++ b/src/sisudoc/meta/metadoc_object_setter.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -173,6 +173,7 @@ template ObjectSetter() {
int parent_lev_markup = 0;
int parent_ocn = 0;
int last_descendant_ocn = 0;
+ int[] children_headings;
ubyte[32] sha256;
}
struct ObjGenericComposite {
diff --git a/src/sisudoc/meta/metadoc_show_config.d b/src/sisudoc/meta/metadoc_show_config.d
index 5bbeea6..c4be08a 100644
--- a/src/sisudoc/meta/metadoc_show_config.d
+++ b/src/sisudoc/meta/metadoc_show_config.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/metadoc_show_make.d b/src/sisudoc/meta/metadoc_show_make.d
index 57721cf..4001e15 100644
--- a/src/sisudoc/meta/metadoc_show_make.d
+++ b/src/sisudoc/meta/metadoc_show_make.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/metadoc_show_metadata.d b/src/sisudoc/meta/metadoc_show_metadata.d
index 331ab7f..4159bc3 100644
--- a/src/sisudoc/meta/metadoc_show_metadata.d
+++ b/src/sisudoc/meta/metadoc_show_metadata.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/metadoc_show_summary.d b/src/sisudoc/meta/metadoc_show_summary.d
index ceb4fd5..037b34a 100644
--- a/src/sisudoc/meta/metadoc_show_summary.d
+++ b/src/sisudoc/meta/metadoc_show_summary.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/package.d b/src/sisudoc/meta/package.d
index 911952b..99de2f3 100644
--- a/src/sisudoc/meta/package.d
+++ b/src/sisudoc/meta/package.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/rgx.d b/src/sisudoc/meta/rgx.d
index 86ca40c..db485eb 100644
--- a/src/sisudoc/meta/rgx.d
+++ b/src/sisudoc/meta/rgx.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/rgx_files.d b/src/sisudoc/meta/rgx_files.d
index 299c2a4..abf6e46 100644
--- a/src/sisudoc/meta/rgx_files.d
+++ b/src/sisudoc/meta/rgx_files.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/meta/rgx_yaml.d b/src/sisudoc/meta/rgx_yaml.d
index ee57469..2d3c20b 100644
--- a/src/sisudoc/meta/rgx_yaml.d
+++ b/src/sisudoc/meta/rgx_yaml.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/share/defaults.d b/src/sisudoc/share/defaults.d
index c285260..4972992 100644
--- a/src/sisudoc/share/defaults.d
+++ b/src/sisudoc/share/defaults.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
diff --git a/src/sisudoc/spine.d b/src/sisudoc/spine.d
index f53fc09..a47c6db 100755..100644
--- a/src/sisudoc/spine.d
+++ b/src/sisudoc/spine.d
@@ -6,7 +6,7 @@
- Author: Ralph Amissah
[ralph.amissah@gmail.com]
- - Copyright: (C) 2015 - 2025 Ralph Amissah, All Rights Reserved.
+ - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved.
- License: AGPL 3 or later:
@@ -63,9 +63,9 @@ import std.process;
import sisudoc.conf.compile_time_info;
import sisudoc.meta;
import sisudoc.meta.metadoc;
-import sisudoc.meta.metadoc_curate;
-import sisudoc.meta.metadoc_curate_authors;
-import sisudoc.meta.metadoc_curate_topics;
+import sisudoc.io_out.curate.metadoc_curate;
+import sisudoc.io_out.curate.metadoc_curate_authors;
+import sisudoc.io_out.curate.metadoc_curate_topics;
import sisudoc.meta.metadoc_from_src;
import sisudoc.meta.conf_make_meta_structs;
import sisudoc.meta.conf_make_meta_json;
@@ -77,6 +77,7 @@ import sisudoc.meta.rgx_files;
import sisudoc.io_in.paths_source;
import sisudoc.io_in.read_config_files;
import sisudoc.io_in.read_source_files;
+import sisudoc.io_in.read_zip_pod;
import sisudoc.io_out.hub;
mixin(import("version.txt"));
mixin(import("configuration.txt"));
@@ -164,7 +165,10 @@ string program_name = "spine";
"pdf-color-links" : false,
"pdf-init" : false,
"pod" : false,
+ "pod2" : false,
"serial" : false,
+ "show-abstraction" : false,
+ "show-abstraction-db" : false,
"show-config" : false,
"show-curate" : false,
"show-curate-authors" : false,
@@ -275,6 +279,7 @@ string program_name = "spine";
"pdf-color-links", "mono or color links for pdfs", &opts["pdf-color-links"],
"pdf-init", "initialise latex shared files (see latex-header-sty)", &opts["pdf-init"],
"pod", "spine (doc reform) pod source content bundled", &opts["pod"],
+ "pod2", "pod with document abstraction (.ssp) bundled", &opts["pod2"],
"quiet|q", "output to terminal", &opts["vox_is1"],
"section-backmatter", "document backmatter (default)" , &opts["backmatter"],
"section-biblio", "document biblio (default)", &opts["section_biblio"],
@@ -286,6 +291,8 @@ string program_name = "spine";
"section-toc", "table of contents (default)", &opts["section_toc"],
"serial", "serial processing", &opts["serial"],
"skip-output", "skip output", &opts["skip-output"],
+ "show-abstraction", "show document abstraction (write .ssp file)", &opts["show-abstraction"],
+ "show-abstraction-db", "show document abstraction (write .db sqlite file)", &opts["show-abstraction-db"],
"show-config", "show config", &opts["show-config"],
"show-curate", "show curate", &opts["show-curate"],
"show-curate-authors", "show curate authors", &opts["show-curate-authors"],
@@ -492,11 +499,20 @@ string program_name = "spine";
return ((opts["ocn-off"]) || (opts["no-ocn"])) ? true : false;
}
@trusted bool pod() {
- return opts["pod"];
+ return (opts["pod"] || opts["pod2"]) ? true : false;
+ }
+ @trusted bool pod2() {
+ return opts["pod2"];
}
@trusted bool show_config() {
return opts["show-config"];
}
+ @trusted bool show_abstraction() {
+ return (opts["show-abstraction"] || pod2) ? true : false;
+ }
+ @trusted bool show_abstraction_db() {
+ return opts["show-abstraction-db"];
+ }
@trusted bool show_curate() {
return opts["show-curate"];
}
@@ -537,7 +553,7 @@ string program_name = "spine";
return opts["source"];
}
@trusted bool source_or_pod() {
- return (opts["pod"] || opts["source"]) ? true : false;
+ return (opts["pod"] || opts["pod2"] || opts["source"]) ? true : false;
}
@trusted bool sqlite_discrete() {
return opts["sqlite-discrete"];
@@ -730,21 +746,23 @@ string program_name = "spine";
}
auto output_task_scheduler() {
int[] schedule;
- if (source_or_pod) { schedule ~= outTask.source_or_pod; }
- if (sqlite_discrete) { schedule ~= outTask.sqlite; }
- if (epub) { schedule ~= outTask.epub; }
- if (html_scroll) { schedule ~= outTask.html_scroll; }
- if (html_seg) { schedule ~= outTask.html_seg; }
- if (html_stuff) { schedule ~= outTask.html_stuff; }
- if (odt) { schedule ~= outTask.odt; }
- if (latex) { schedule ~= outTask.latex; }
- if (text) { schedule ~= outTask.text; }
- if (skel) { schedule ~= outTask.skel; }
+ if (source_or_pod) schedule ~= outTask.source_or_pod;
+ if (sqlite_discrete) schedule ~= outTask.sqlite;
+ if (epub) schedule ~= outTask.epub;
+ if (html_scroll) schedule ~= outTask.html_scroll;
+ if (html_seg) schedule ~= outTask.html_seg;
+ if (html_stuff) schedule ~= outTask.html_stuff;
+ if (odt) schedule ~= outTask.odt;
+ if (latex) schedule ~= outTask.latex;
+ if (text) schedule ~= outTask.text;
+ if (skel) schedule ~= outTask.skel;
return schedule.sort().uniq;
}
@trusted bool abstraction() {
return (
opts["abstraction"]
+ || show_abstraction
+ || show_abstraction_db
|| concordance
|| source_or_pod
|| curate
@@ -771,6 +789,8 @@ string program_name = "spine";
|| latex
|| odt
|| manifest
+ || show_abstraction
+ || show_abstraction_db
|| show_make
|| show_metadata
|| show_summary
@@ -785,6 +805,8 @@ string program_name = "spine";
@trusted bool meta_processing_general() {
return (
opts["abstraction"]
+ || show_abstraction
+ || show_abstraction_db
|| curate
|| html
|| epub
@@ -856,14 +878,45 @@ string program_name = "spine";
auto _manifested = PathMatters!()(_opt_action, _env, "");
auto _manifests = [ _manifested ];
auto _conf_file_details = configFilePaths!()(_manifested, _env, _opt_action.config_path_set);
+ /+ ↓ track extracted zip pod temp directories for cleanup +/
+ mixin spineExtractZipPod;
+ ZipPodResult[] _zip_pod_extractions;
+ DownloadResult[] _url_downloads;
+ /+ ↓ pre-process args: resolve URL arguments to local temp files +/
+ string[] _resolved_args;
+ foreach (arg; args[1..$]) {
+ if (isUrl(arg)) {
+ auto _dlr = downloadZipUrl(arg);
+ if (_dlr.ok) {
+ _url_downloads ~= _dlr;
+ _resolved_args ~= _dlr.local_path;
+ if (_opt_action.vox_gt_1) {
+ writeln("downloaded: ", arg, " -> ", _dlr.local_path);
+ }
+ } else {
+ writeln("ERROR >> Download failed: ", arg, " - ", _dlr.error_msg);
+ }
+ } else {
+ _resolved_args ~= arg;
+ }
+ }
ConfComposite _siteConfig;
if (
_opt_action.require_processing_files
&& _opt_action.config_path_set.empty
) {
- foreach(arg; args[1..$]) {
+ foreach(arg; _resolved_args) {
if (!(arg.match(rgx.flag_action))) { /+ cli markup source path +/ // get first input markup source file names for processing
- _manifested = PathMatters!()(_opt_action, _env, arg);
+ string _config_arg = arg;
+ /+ ↓ if first non-flag arg is a zip, extract for config discovery +/
+ if (arg.match(rgx_files.src_pth_zip)) {
+ auto _zpr = extractZipPod(arg);
+ if (_zpr.ok) {
+ _zip_pod_extractions ~= _zpr;
+ _config_arg = _zpr.pod_dir;
+ }
+ }
+ _manifested = PathMatters!()(_opt_action, _env, _config_arg);
{ /+ local site config +/
_conf_file_details = configFilePaths!()(_manifested, _env, _opt_action.config_path_set);
auto _config_local_site_struct = readConfigSite!()(_conf_file_details, _opt_action, _cfg);
@@ -897,7 +950,7 @@ string program_name = "spine";
}
ConfComposite _make_and_meta_struct = _siteConfig;
destroy(_siteConfig);
- foreach(arg; args[1..$]) {
+ foreach(arg; _resolved_args) {
if (arg.match(rgx.flag_action)) { /+ cli instruction, flag do +/
flag_action ~= " " ~ arg; // flags not taken by getopt
} else if (_opt_action.require_processing_files) { /+ cli, assumed to be path to source files +/
@@ -1047,7 +1100,166 @@ string program_name = "spine";
_manifests ~= _manifested;
}
} else if (arg.match(rgx_files.src_pth_zip)) {
- // fns_src ~= arg; // gather input markup source file names for processing
+ /+ ↓ zip pod archive: extract to temp dir, process as pod +/
+ /+ check if this zip was already extracted during config discovery +/
+ string _zip_pod_dir;
+ foreach (ref _zpr; _zip_pod_extractions) {
+ if (_zpr.ok && _zpr.pod_dir.length > 0
+ && _zpr.pod_dir.baseName == arg.baseName.stripExtension)
+ {
+ _zip_pod_dir = _zpr.pod_dir;
+ break;
+ }
+ }
+ if (_zip_pod_dir.length == 0) {
+ auto _zpr = extractZipPod(arg);
+ if (!_zpr.ok) {
+ writeln("ERROR >> Processing Skipped! Zip extraction failed: ", arg, " - ", _zpr.error_msg);
+ } else {
+ _zip_pod_extractions ~= _zpr;
+ _zip_pod_dir = _zpr.pod_dir;
+ }
+ }
+ if (_zip_pod_dir.length > 0) {
+ /+ process extracted pod directory same as regular pod +/
+ auto _zip_manifest = PodManifest!()(_opt_action, _zip_pod_dir);
+ if (_zip_manifest.pod_manifest_file_with_path
+ && _opt_action.abstraction
+ ) {
+ string pod_manifest_root_content_paths_to_markup_location_raw_;
+ string markup_contents_location_;
+ string sisudoc_txt_ = _zip_manifest.pod_manifest_file_with_path;
+ enforce(
+ exists(sisudoc_txt_)!=0,
+ "file not found: <<" ~
+ sisudoc_txt_ ~ ">>"
+ );
+ if (exists(sisudoc_txt_)) {
+ try {
+ import dyaml;
+ Node pod_manifest_yaml;
+ try {
+ pod_manifest_yaml = Loader.fromFile(sisudoc_txt_).load();
+ } catch (ErrnoException ex) {
+ } catch (FileException ex) {
+ writeln("ERROR failed to read config file");
+ } catch (Throwable) {
+ writeln("ERROR failed to read config file content, not parsed as yaml");
+ }
+ if ("doc" in pod_manifest_yaml) {
+ if (pod_manifest_yaml["doc"].type.mapping
+ && pod_manifest_yaml["doc"].tag.match(rgx_y.yaml_tag_is_map)
+ ) {
+ if ("path" in pod_manifest_yaml["doc"]) {
+ if (pod_manifest_yaml["doc"]["path"].tag.match(rgx_y.yaml_tag_is_seq)) {
+ foreach (string _path; pod_manifest_yaml["doc"]["path"]) {
+ markup_contents_location_ ~= _path ~ "\n";
+ pod_manifest_root_content_paths_to_markup_location_raw_ ~=
+ _path ~ "\n";
+ }
+ } else if (
+ pod_manifest_yaml["doc"]["path"].type.string
+ && pod_manifest_yaml["doc"]["path"].tag.match(rgx_y.yaml_tag_is_str)
+ ) {
+ markup_contents_location_ = pod_manifest_yaml["doc"]["path"].get!string;
+ pod_manifest_root_content_paths_to_markup_location_raw_ =
+ pod_manifest_yaml["doc"]["path"].get!string;
+ }
+ }
+ if ("filename" in pod_manifest_yaml["doc"]) {
+ if (pod_manifest_yaml["doc"]["filename"].tag.match(rgx_y.yaml_tag_is_seq)) {
+ foreach (string _filename; pod_manifest_yaml["doc"]["filename"]) {
+ if ("language" in pod_manifest_yaml["doc"]) {
+ if (pod_manifest_yaml["doc"]["language"].tag.match(rgx_y.yaml_tag_is_seq)) {
+ foreach (string _lang; pod_manifest_yaml["doc"]["language"]) {
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang ~ "/"
+ ~ _filename ~ "\n";
+ }
+ } else if (pod_manifest_yaml["doc"]["language"].tag.match(rgx_y.yaml_tag_is_str)
+ ) {
+ markup_contents_location_ =
+ "media/text/"
+ ~ pod_manifest_yaml["doc"]["language"].get!string
+ ~ "/" ~ _filename ~ "\n";
+ } else {
+ string _lang_default = "en";
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang_default ~ "/"
+ ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ }
+ } else {
+ string _lang_default = "en";
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang_default ~ "/"
+ ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ }
+ }
+ } else if (
+ pod_manifest_yaml["doc"]["filename"].type.string
+ && pod_manifest_yaml["doc"]["filename"].tag.match(rgx_y.yaml_tag_is_str)
+ ) {
+ if ("language" in pod_manifest_yaml["doc"]) {
+ if (pod_manifest_yaml["doc"]["language"].tag.match(rgx_y.yaml_tag_is_seq)) {
+ foreach (string _lang; pod_manifest_yaml["doc"]["language"]) {
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang ~ "/"
+ ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ }
+ } else if (pod_manifest_yaml["doc"]["language"].tag.match(rgx_y.yaml_tag_is_str)) {
+ markup_contents_location_ =
+ "media/text/"
+ ~ pod_manifest_yaml["doc"]["language"].get!string
+ ~ "/" ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ } else {
+ string _lang_default = "en";
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang_default ~ "/"
+ ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ }
+ } else {
+ string _lang_default = "en";
+ markup_contents_location_ ~=
+ "media/text/"
+ ~ _lang_default ~ "/"
+ ~ pod_manifest_yaml["doc"]["filename"].get!string ~ "\n";
+ }
+ }
+ }
+ }
+ }
+ } catch (ErrnoException ex) {
+ } catch (FileException ex) {
+ // Handle errors
+ }
+ } else {
+ writeln("manifest not found: ", sisudoc_txt_);
+ }
+ auto markup_contents_locations_arr
+ = (cast(char[]) markup_contents_location_).split;
+ auto tmp_dir_ = (sisudoc_txt_).dirName.array;
+ foreach (markup_contents_location; markup_contents_locations_arr) {
+ assert(markup_contents_location.match(rgx_files.src_pth_sst_or_ssm),
+ "not a recognised file: <<" ~
+ markup_contents_location ~ ">>"
+ );
+ auto markup_contents_location_pth_ = (markup_contents_location).to!string;
+ Regex!(char) lang_rgx_ = regex(r"/(" ~ _opt_action.languages_set.join("|") ~ ")/");
+ if (_opt_action.languages_set[0] == "all"
+ || (markup_contents_location_pth_).match(lang_rgx_)
+ ) {
+ auto _fns = (((tmp_dir_).chainPath(markup_contents_location_pth_)).array).to!string;
+ _manifested = PathMatters!()(_opt_action, _env, _zip_pod_dir, _fns, markup_contents_locations_arr);
+ _manifests ~= _manifested;
+ }
+ }
+ }
+ }
} else { // anything remaining, unused
arg_unrecognized ~= " " ~ arg;
}
@@ -1112,6 +1324,16 @@ string program_name = "spine";
import sisudoc.meta.metadoc_show_config;
spineShowConfig!()(doc.matters);
}
+ /+ ↓ document abstraction text representation +/
+ if (doc.matters.opt.action.show_abstraction) {
+ import sisudoc.abstraction.ssp;
+ spineAbstractionTxt!()(doc);
+ }
+ /+ ↓ document abstraction sqlite database +/
+ if (doc.matters.opt.action.show_abstraction_db) {
+ import sisudoc.io_out.create_abstraction_db;
+ spineAbstractionDb!()(doc);
+ }
if (doc.matters.opt.action.curate) {
auto _hvst = spineMetaDocCurate!()(doc.matters, hvst);
if (
@@ -1211,6 +1433,16 @@ string program_name = "spine";
import sisudoc.meta.metadoc_show_config;
spineShowConfig!()(doc.matters);
}
+ /+ ↓ document abstraction text representation +/
+ if (doc.matters.opt.action.show_abstraction) {
+ import sisudoc.abstraction.ssp;
+ spineAbstractionTxt!()(doc);
+ }
+ /+ ↓ document abstraction sqlite database +/
+ if (doc.matters.opt.action.show_abstraction_db) {
+ import sisudoc.io_out.create_abstraction_db;
+ spineAbstractionDb!()(doc);
+ }
if (doc.matters.opt.action.curate) {
auto _hvst = spineMetaDocCurate!()(doc.matters, hvst);
if (
@@ -1277,4 +1509,12 @@ string program_name = "spine";
}
}
} // else { writeln("NO METADATA CURATED"); }
+ /+ ↓ clean up any extracted zip pod temp directories +/
+ foreach (ref _zpr; _zip_pod_extractions) {
+ cleanupZipPod(_zpr);
+ }
+ /+ ↓ clean up any downloaded temp files +/
+ foreach (ref _dlr; _url_downloads) {
+ cleanupDownload(_dlr);
+ }
}