aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-09-23 07:18:52 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-09-23 08:19:53 -0400
commit26aba525c0c55cb0ec77cf3a788cb59de2683cee (patch)
treec49c119f157c67b39746157c1851f595749911ac /src
parenthelp: what spine takes, what an artefact carries (diff)
uid and paths separator "~" in place of ":"
need a character to split filenames on in certain circumstances. there problems with use of a colon in filenames, which is legal on posix but not on Windows."~" fits the bill better being legal on every filesystem of interest and is unreserved in rfc 3986, needing no escaping in a url. The reference abstraction is renamed, its content unchanged. Over the sample collection two filenames move and nothing else does: the abstraction and database digests are identical, the archive's members and their sizes are unchanged, and only the member order shifts, "~" collating after letters where ":" sorted before them. The document's epub dc:identifier is a v5 uuid derived from the uid, so it changes for those filenames. Every document's uid changes in the search database (spine.search.db). (assisted by Claude-Code)
Diffstat (limited to 'src')
-rw-r--r--src/sisudoc/ocda/io_in/carried_names.d11
-rw-r--r--src/sisudoc/ocda/io_in/read_zip_pod.d6
-rw-r--r--src/sisudoc/ocda/meta/defaults.d2
-rw-r--r--src/sisudoc/ocda/meta/rgx.d20
-rw-r--r--src/sisudoc/ocda/meta/rgx_files.d20
-rw-r--r--src/sisudoc/outputs/io_out/defaults.d2
-rw-r--r--src/sisudoc/outputs/io_out/rgx.d20
7 files changed, 44 insertions, 37 deletions
diff --git a/src/sisudoc/ocda/io_in/carried_names.d b/src/sisudoc/ocda/io_in/carried_names.d
index 84a4f3c..eaa233d 100644
--- a/src/sisudoc/ocda/io_in/carried_names.d
+++ b/src/sisudoc/ocda/io_in/carried_names.d
@@ -128,9 +128,14 @@ template spineCarriedNames() {
enum size_t MAX_CARRIED_NAME_LENGTH = 255; /+ one path component +/
enum size_t MAX_CARRIED_PATH_LENGTH = 1024; /+ the whole path +/
enum size_t MAX_CARRIED_PATH_DEPTH = 10; /+ as read_zip_pod.d +/
- /+ ↓ allowed characters: a filename has no separator, a path has "/" +/
- static auto rgx_safe_carried_name = ctRegex!(`^[a-zA-Z0-9._ -]+$`);
- static auto rgx_safe_carried_path = ctRegex!(`^[a-zA-Z0-9._/ -]+$`);
+ /+ ↓ allowed characters: a filename has no separator, a path has "/".
+ "~" is here because it is spine's own uid separator (InlineMarkup.uid_sep),
+ which joins a pod name to a document name where the two differ, as in
+ sisu-manual~sisu_markup.en.ssp. It cannot occur in either half, every
+ filesystem of interest takes it, and it is unreserved in rfc 3986.
+ +/
+ static auto rgx_safe_carried_name = ctRegex!(`^[a-zA-Z0-9._ ~-]+$`);
+ static auto rgx_safe_carried_path = ctRegex!(`^[a-zA-Z0-9._/ ~-]+$`);
/+ ↓ the checks both rules share, "" when the name is acceptable +/
private string _carriedNameCommon(string _name) {
if (_name.length == 0) {
diff --git a/src/sisudoc/ocda/io_in/read_zip_pod.d b/src/sisudoc/ocda/io_in/read_zip_pod.d
index 18a957c..c96429b 100644
--- a/src/sisudoc/ocda/io_in/read_zip_pod.d
+++ b/src/sisudoc/ocda/io_in/read_zip_pod.d
@@ -75,8 +75,10 @@ template spineExtractZipPod() {
alias MAX_ENTRY_COUNT = MAX_POD_ENTRY_COUNT;
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._/ -]+$`);
+ /+ allowed entry name pattern: alphanumeric, dots, dashes, underscores,
+ forward slashes, and "~", spine's own uid separator (as carried_names.d)
+ +/
+ static auto rgx_safe_entry_name = ctRegex!(`^[a-zA-Z0-9._/ ~-]+$`);
struct ZipPodResult {
string tmp_dir; /+ temp directory containing extracted pod +/
diff --git a/src/sisudoc/ocda/meta/defaults.d b/src/sisudoc/ocda/meta/defaults.d
index 91df996..59dcc87 100644
--- a/src/sisudoc/ocda/meta/defaults.d
+++ b/src/sisudoc/ocda/meta/defaults.d
@@ -196,7 +196,7 @@ template InternalMarkup() {
string tc_p = "┆";
string img = "☼";
string sep = "␣"; // "~";"␣"; // "~";
- string uid_sep = ":";
+ string uid_sep = "~";
string on_o = "「"; string on_c = "」";
string mk_bullet = "● ";
static string indent_by_spaces_provided(int indent, string _indent_spaces ="░░") {
diff --git a/src/sisudoc/ocda/meta/rgx.d b/src/sisudoc/ocda/meta/rgx.d
index d3c9aa1..125fb52 100644
--- a/src/sisudoc/ocda/meta/rgx.d
+++ b/src/sisudoc/ocda/meta/rgx.d
@@ -208,17 +208,17 @@ static template spineRgxIn() {
static nbsp_char = ctRegex!(`░`, "mg");
static nbsp_chars = ctRegex!(`[░]+`, "mg");
static middle_dot = ctRegex!(`·`, "mg");
- static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
- static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
- static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
- static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
- static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
- static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
- static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
- static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
+ static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
+ static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._:~-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
+ static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
+ static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
+ static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._:~-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
+ static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
+ static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
+ static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
static insert_src_fn_ssi_or_sst = ctRegex!(`^<<\s*(?P<path>[a-zA-Z0-9._-]+/)*(?P<filename>[a-zA-Z0-9._-]+[.]ss[ti])$`);
- static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
- static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._-]+?)(?P<dir>[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._:~-]+?)(?P<dir>[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
/+ line breaks +/
static br_empty_line = ctRegex!(`\n[ ]*\n`, "mg");
static br_linebreaks_newlines = ctRegex!(`[\n┘┙]`, "mg");
diff --git a/src/sisudoc/ocda/meta/rgx_files.d b/src/sisudoc/ocda/meta/rgx_files.d
index 13c7e46..a20eca1 100644
--- a/src/sisudoc/ocda/meta/rgx_files.d
+++ b/src/sisudoc/ocda/meta/rgx_files.d
@@ -54,17 +54,17 @@ module sisudoc.ocda.meta.rgx_files;
@safe:
static template spineRgxFiles() {
static struct RgxFiles {
- static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
- static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
- static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
- static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
- static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
- static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
- static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
- static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
+ static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
+ static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._:~-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
+ static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
+ static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
+ static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._:~-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
+ static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
+ static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
+ static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
static insert_src_fn_ssi_or_sst = ctRegex!(`^<<\s*(?P<path>[a-zA-Z0-9._-]+/)*(?P<filename>[a-zA-Z0-9._-]+[.]ss[ti])$`);
- static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
- static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._-]+?)(?P<dir>[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._:~-]+?)(?P<dir>[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
/+ language codes +/
auto language_code_and_filename =
ctRegex!("(?:^|[/])(am|bg|bn|br|ca|cs|cy|da|de|el|en|eo|es|et|eu|fi|fr|ga|gl|he|hi|hr|hy|ia|is|it|ja|ko|la|lo|lt|lv|ml|mr|nl|no|nn|oc|pl|pt|pt_BR|ro|ru|sa|se|sk|sl|sq|sr|sv|ta|te|th|tk|tr|uk|ur|vi|zh)/[A-Za-z0-9._-].+?[.](?:sst|ssm)$");
diff --git a/src/sisudoc/outputs/io_out/defaults.d b/src/sisudoc/outputs/io_out/defaults.d
index 73a9af1..0896be0 100644
--- a/src/sisudoc/outputs/io_out/defaults.d
+++ b/src/sisudoc/outputs/io_out/defaults.d
@@ -85,7 +85,7 @@ template InternalMarkup() {
string tc_p = "┆";
string img = "☼";
string sep = "␣"; // "~";"␣"; // "~";
- string uid_sep = ":";
+ string uid_sep = "~";
string on_o = "「"; string on_c = "」";
string mk_bullet = "● ";
static string indent_by_spaces_provided(int indent, string _indent_spaces ="░░") {
diff --git a/src/sisudoc/outputs/io_out/rgx.d b/src/sisudoc/outputs/io_out/rgx.d
index cd7a8da..145b19b 100644
--- a/src/sisudoc/outputs/io_out/rgx.d
+++ b/src/sisudoc/outputs/io_out/rgx.d
@@ -63,17 +63,17 @@ static template spineRgxOut() {
static nbsp_char = ctRegex!(`░`, "mg");
static nbsp_chars = ctRegex!(`[░]+`, "mg");
static middle_dot = ctRegex!(`·`, "mg");
- static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
- static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
- static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
- static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
- static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
- static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
- static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
- static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
+ static src_pth_sst_or_ssm = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.](?P<extension>ss[tm]))$`);
+ static src_pth_pod_sst_or_ssm = ctRegex!(`^(?P<podpath>[/]?(?:[a-zA-Z0-9._:~-]+/)*)media/text/[a-z]{2}/(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*?[.]ss[tm])$`);
+ static src_pth_contents = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9][a-zA-Z0-9._-]*)/pod[.]manifest$`);
+ static src_pth_zip = ctRegex!(`^(?P<path>[/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod))$`);
+ static src_pth_types = ctRegex!(`^(?P<path>[/]?[a-zA-Z0-9._:~-]+/)*(?P<gotfile>(?P<filename>[a-zA-Z0-9._-]+[.]ss[tm])|(?P<filelist>[a-zA-Z0-9._-]+/pod[.]manifest)|(?P<filezip>[a-zA-Z0-9._-]+(?:[.]zip|[.]sisupod)))$`);
+ static src_fn = ctRegex!(`^([/]?(?:[a-zA-Z0-9._:~-]+/)*)(?P<fn_src>(?P<fn_base>[a-zA-Z0-9._-]+)[.](?P<fn_src_suffix>ss[tm]))$`);
+ static src_fn_master = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ssm)$`);
+ static src_fn_find_inserts = ctRegex!(`^(?P<path>/?(?:[a-zA-Z0-9._:~-]+/)*)(?P<filename>[a-zA-Z0-9._-]+[.]ss[im])$`);
static insert_src_fn_ssi_or_sst = ctRegex!(`^<<\s*(?P<path>[a-zA-Z0-9._-]+/)*(?P<filename>[a-zA-Z0-9._-]+[.]ss[ti])$`);
- static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
- static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._-]+?)(?P<dir>[a-zA-Z0-9._-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_base_parent_dir_name = ctRegex!(`[/](?P<dir>(?:[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
+ static src_formalised_file_path_parts = ctRegex!(`(?P<pth>(?:[/a-zA-Z0-9._:~-]+?)(?P<dir>[a-zA-Z0-9._:~-]+))(?:/media/text/[a-z]{2})$`); // formalizes dir structure
/+ line breaks +/
static br_empty_line = ctRegex!(`\n[ ]*\n`, "mg");
static br_linebreaks_newlines = ctRegex!(`[\n┘┙]`, "mg");