/+ - 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.ocda.abstraction.load; @safe: /+ ↓ one way in, whatever the document is being read from spine's pipeline is markup -> abstraction -> output. once the abstraction is serialised, there is more than one thing an abstraction can be read from, and a consumer should not have to know which it was handed: .sst / .ssm + images the markup source pod (dir) + images the same, bundled pod .zip the same, zipped .ssp + images the abstraction, as text .ocda.db the abstraction, sqlite, images inside A source may also be remote. spine already takes a URL argument ending in .zip: it is downloaded to a temp file (downloadZipUrl in ocda/io_in/read_zip_pod.d, guarded by rgx_url_zip and by --allow- downloads) and the local path is processed in its place. The same should be offered for .ocda.db, which is the one other artefact that is self-sufficient enough to be fetched on its own: it carries its images. What that needs is small and is not done here: - rgx_url_zip is `^https?://...[.]zip$`; a sibling pattern for `[.]ocda[.]db$`, or one pattern covering both - the same download path, which is already generic apart from that regex, and the same temp-file cleanup - dispatch, which this module already does once the file is local A .ssp URL would want its images too, so it is a pod or a database that travels, not a .ssp on its own. This module names those sources, tells them apart, and loads the two that are self-describing artefacts, returning the value the parser produces. The three source forms are deliberately *not* loaded here. They are the parser's job (sisudoc.ocda.meta.metadoc, spineAbstraction), and it needs the environment, the options and the configuration that spine.d assembles, none of which belongs in a loader. What this module gives that case is the dispatch and a plain statement of where to go. +/ template spineAbstractionLoad() { import std.algorithm : endsWith; import std.conv : to; import std.file; import std.path; import std.stdio; import std.string; import sisudoc.ocda.abstraction.db_in; mixin spineAbstractionDbRead; // brings the .ssp reader with it enum AbstractionSource { unknown, markup, // .sst or .ssm, with its images beside it pod, // a directory holding pod.manifest pod_zip, // that directory, zipped ssp, // .ssp, the abstraction as text, images beside it ocda_db, // .ocda.db, the abstraction as sqlite, images inside it } string abstractionSourceName(AbstractionSource _s) { final switch (_s) { case AbstractionSource.unknown: return "unknown"; case AbstractionSource.markup: return "markup source (.sst/.ssm)"; case AbstractionSource.pod: return "pod directory"; case AbstractionSource.pod_zip: return "pod zip"; case AbstractionSource.ssp: return ".ssp (abstraction as text)"; case AbstractionSource.ocda_db: return ".ocda.db (abstraction as sqlite)"; } } /+ ↓ what is this? by name, and for a directory by what it holds +/ AbstractionSource abstractionSourceOf(string _path) { if (_path.length == 0) { return AbstractionSource.unknown; } if (_path.isValidPath && _path.exists && _path.isDir) { return (_path.chainPath("pod.manifest").array.exists) ? AbstractionSource.pod : AbstractionSource.unknown; } if (_path.endsWith(".ocda.db")) { return AbstractionSource.ocda_db; } if (_path.endsWith(".ssp")) { return AbstractionSource.ssp; } if (_path.endsWith(".sst") || _path.endsWith(".ssm")) { return AbstractionSource.markup; } if (_path.endsWith(".zip")) { return AbstractionSource.pod_zip; } if (_path.endsWith(".db")) { return AbstractionSource.ocda_db; } return AbstractionSource.unknown; } struct LoadedAbstraction { AbstractionSource source; string path; bool loaded; // is .doc filled string note; // why not, when it is not SSPdocument doc; } /+ ↓ load what can be loaded from the path alone. .ssp and .ocda.db come back filled. the three source forms come back with loaded = false and a note saying where they are handled, because reading them needs the manifest, environment and configuration that spine.d builds, not a file path. +/ LoadedAbstraction abstractionLoad(string _path) { LoadedAbstraction _out; _out.path = _path; _out.source = abstractionSourceOf(_path); if (!_path.exists) { _out.note = "no such file or directory"; return _out; } final switch (_out.source) { case AbstractionSource.ssp: _out.doc = sspReadFile(_path); _out.loaded = (_out.doc.section_order.length > 0); if (!_out.loaded) { _out.note = "no object sections found"; } break; case AbstractionSource.ocda_db: _out.doc = dbReadFile(_path); _out.loaded = (_out.doc.section_order.length > 0); if (!_out.loaded) { _out.note = "no object sections found"; } break; case AbstractionSource.markup: case AbstractionSource.pod: case AbstractionSource.pod_zip: _out.note = "a source form: read by the parser," ~ " sisudoc.ocda.meta.metadoc spineAbstraction, which needs the" ~ " manifest, environment and configuration spine assembles"; break; case AbstractionSource.unknown: _out.note = "not a document source spine knows"; break; } return _out; } /+ ↓ what was loaded, in a few lines: for the eye, and for a check that a given artefact really does hold what it should +/ string[] abstractionLoadSummary(LoadedAbstraction _l) { string[] _out; _out ~= "source: " ~ abstractionSourceName(_l.source); _out ~= "path: " ~ _l.path; if (!_l.loaded) { _out ~= "loaded: no"; if (_l.note.length > 0) { _out ~= "note: " ~ _l.note; } return _out; } _out ~= "loaded: yes"; if (_l.doc.source.length > 0) { _out ~= "document: " ~ _l.doc.source; } if ("title.main" in _l.doc.meta) { _out ~= "title: " ~ _l.doc.meta["title.main"]; } if ("creator.author" in _l.doc.meta) { _out ~= "author: " ~ _l.doc.meta["creator.author"]; } _out ~= "header: " ~ _l.doc.meta.length.to!string ~ " meta, " ~ _l.doc.make.length.to!string ~ " make, " ~ _l.doc.doc_has.length.to!string ~ " doc_has"; int _objs, _citable, _headings; string[] _sections; foreach (section; _l.doc.section_order) { int _n; foreach (obj; _l.doc.abstraction[section]) { ++_objs; ++_n; if (obj.metainfo.ocn > 0) { ++_citable; } if (obj.metainfo.is_a == "heading") { ++_headings; } } _sections ~= section ~ " " ~ _n.to!string; } _out ~= "objects: " ~ _objs.to!string ~ " (" ~ _citable.to!string ~ " citable, " ~ _headings.to!string ~ " headings)"; _out ~= "sections: " ~ _sections.join(", "); return _out; } }