summaryrefslogtreecommitdiffhomepage
path: root/dlang/src/ocda
diff options
context:
space:
mode:
authorRalph Amissah <ralph.amissah@gmail.com>2026-09-29 14:07:32 -0400
committerRalph Amissah <ralph.amissah@gmail.com>2026-09-29 14:07:32 -0400
commit8b8a8c1b61c740528e1409a2429f7ec4403f9576 (patch)
tree9bd2fa19837c187232cfc93960c9291a6fd4b4b9 /dlang/src/ocda
parentformat 2.0: abstraction as library, in D and Gleam (diff)
model 2.1: ocn_range, the verses a poem holds
The format now states, for a container, the first and last ocn of what it holds: .ocn_range in the .ssp, ocn_range_first and ocn_range_last in the .ocda.db. A poem is the one container, and since 2.1 it carries ocn 0 and comes before its verses. Both fields in the model, read by both loaders, in both languages, and written by the dump. A 2.0 database has neither column, so each loader asks sqlite whether it is there and reads a 2.0 file as having no ranges, which is what 2.0 meant; checked on a published 2.0 database, D and Gleam giving the same dump. Over the markup document samples written by spine at 2.1, the four dumps (D and Gleam, .ssp and .ocda.db) are byte identical for every document-language. (assisted by Claude-Code)
Diffstat (limited to 'dlang/src/ocda')
-rw-r--r--dlang/src/ocda/dump.d2
-rw-r--r--dlang/src/ocda/load_db.d14
-rw-r--r--dlang/src/ocda/load_ssp.d5
-rw-r--r--dlang/src/ocda/model.d2
4 files changed, 22 insertions, 1 deletions
diff --git a/dlang/src/ocda/dump.d b/dlang/src/ocda/dump.d
index 7a4f11a..58d87ff 100644
--- a/dlang/src/ocda/dump.d
+++ b/dlang/src/ocda/dump.d
@@ -83,6 +83,8 @@ string dumpDocument(ref OcdaDocument doc) {
kv("segment_epub_is", obj.segment_epub_is);
kn("parent", obj.parent_ocn);
kn("last_descendant", obj.last_descendant_ocn);
+ kn("ocn_range_first", obj.ocn_range_first);
+ kn("ocn_range_last", obj.ocn_range_last);
kv("children", ints(obj.children));
kv("ancestors", ints(obj.ancestors));
kb("dummy", obj.dummy_heading);
diff --git a/dlang/src/ocda/load_db.d b/dlang/src/ocda/load_db.d
index dec0626..eb3d71b 100644
--- a/dlang/src/ocda/load_db.d
+++ b/dlang/src/ocda/load_db.d
@@ -132,6 +132,16 @@ OcdaDocument loadDbFile(string path, string lang = "", bool any_version = false)
}
}
+ /+ ↓ 2.1 added the container range as two columns. A 2.0 database has
+ neither, and reads as having no range, which is what 2.0 meant. +/
+ bool has_range = false;
+ foreach (c; db.rows("PRAGMA table_info(objects)")) {
+ if (c[1] == "ocn_range_first") { has_range = true; }
+ }
+ string range_cols = has_range
+ ? "coalesce(ocn_range_first,0), coalesce(ocn_range_last,0)"
+ : "0, 0";
+
/+ ↓ the objects, in document order within each section +/
foreach (r; db.rows(
"SELECT section, seq, ocn, is_a, coalesce(is_of_part,''),
@@ -152,7 +162,7 @@ OcdaDocument loadDbFile(string path, string lang = "", bool any_version = false)
coalesce(code_linenumbers,0),
coalesce(heading_ancestors_text,''), coalesce(sha256,''),
coalesce(text,''), id, coalesce(heading_lev_anchor,''),
- coalesce(segment_epub,''), coalesce(segment_epub_is,'')
+ coalesce(segment_epub,''), coalesce(segment_epub_is,''), " ~ range_cols ~ "
FROM objects WHERE doc_id = " ~ chosen.doc_id.to!string
~ " ORDER BY CASE section
WHEN 'head' THEN 1 WHEN 'toc' THEN 2 WHEN 'body' THEN 3
@@ -192,6 +202,8 @@ OcdaDocument loadDbFile(string path, string lang = "", bool any_version = false)
obj.heading_lev_anchor = r[37];
obj.segment_epub = r[38];
obj.segment_epub_is = r[39];
+ obj.ocn_range_first = r[40].to!int;
+ obj.ocn_range_last = r[41].to!int;
obj.table_cols = r[28].to!int;
obj.table_widths = jsonWords(r[29]);
obj.table_aligns = jsonWords(r[30]);
diff --git a/dlang/src/ocda/load_ssp.d b/dlang/src/ocda/load_ssp.d
index 34d156c..d9ec869 100644
--- a/dlang/src/ocda/load_ssp.d
+++ b/dlang/src/ocda/load_ssp.d
@@ -208,6 +208,11 @@ private void parseProperty(string line, ref OcdaObject obj, ref OcdaUnknown unkn
case "anchor_tag": obj.anchor_tags ~= value; break;
case "parent": obj.parent_ocn = toIntSafe(value); break;
case "last_descendant": obj.last_descendant_ocn = toIntSafe(value); break;
+ case "ocn_range": {
+ auto r = parseIntArray(value);
+ if (r.length == 2) { obj.ocn_range_first = r[0]; obj.ocn_range_last = r[1]; }
+ break;
+ }
case "children": obj.children = parseIntArray(value); break;
case "ancestors": obj.ancestors = parseIntArray(value); break;
case "dummy": obj.dummy_heading = (value == "true"); break;
diff --git a/dlang/src/ocda/model.d b/dlang/src/ocda/model.d
index 403502a..6c8fc37 100644
--- a/dlang/src/ocda/model.d
+++ b/dlang/src/ocda/model.d
@@ -27,6 +27,8 @@ struct OcdaObject {
string[] anchor_tags; // named targets the markup placed on this object
int parent_ocn;
int last_descendant_ocn;
+ int ocn_range_first;// a container (a poem): the first and last
+ int ocn_range_last; // ocn of what it holds; 0 otherwise (2.1)
int[] children; // direct child heading OCNs
int[] ancestors; // markedup_ancestors[8]
bool dummy_heading;