diff options
Diffstat (limited to 'src')
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 - ~ "\">" - ~ "<< prev" - ~ "</a> || </font>"; - } - arrow_next = - "<font size=\"2\" color=\"#666666\">" - ~ "<a href=\"" - ~ _url_next - ~ "\">" - ~ "next >>" - ~ "</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: " - ~ "<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: " - ~ "<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: <<text:a xl:type="simple" xl:href="https://www.sisudoc.org">www.sisudoc.org</text:a>> and <<text:a xl:type="simple" xl:href="https://www.sisudoc.org">www.sisudoc.org</text:a>></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 & 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); + } } |
