diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-29 14:07:32 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-29 14:07:32 -0400 |
| commit | 8b8a8c1b61c740528e1409a2429f7ec4403f9576 (patch) | |
| tree | 9bd2fa19837c187232cfc93960c9291a6fd4b4b9 /dlang/src | |
| parent | format 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')
| -rw-r--r-- | dlang/src/ocda/dump.d | 2 | ||||
| -rw-r--r-- | dlang/src/ocda/load_db.d | 14 | ||||
| -rw-r--r-- | dlang/src/ocda/load_ssp.d | 5 | ||||
| -rw-r--r-- | dlang/src/ocda/model.d | 2 |
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; |
