diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-30 16:18:59 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-30 23:14:23 -0400 |
| commit | 993a3f655359f98dc26d3d3222e07792e2ccddf7 (patch) | |
| tree | 1633a8183b2affec4a2951c8357ab89e9073bd25 /src | |
| parent | search database: show all included notes (diff) | |
abstraction 2.1: a poem states its object range
- A poem is a container of verse, and stores its range of verse ocn,
- its verse are the citable units with ocn.
- A note in the last verse of a poem previously was not gathered into
the endnotes section, this now is fixed
Format 2.0 -> 2.1: the property is an addition, the poem is not a
citable object (but contans a range of objects), its verse are
(individual citable objects), and every reader checks the major version.
A 2.0 database reads as having no ranges.
(assisted by Claude-Code)
Diffstat (limited to 'src')
| -rw-r--r-- | src/sisudoc/ocda/abstraction/db_in.d | 5 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/doc_has.d | 12 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp.d | 17 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp_in.d | 10 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/metadoc_from_src.d | 50 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/metadoc_from_src_functions.d | 12 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/metadoc_object_setter.d | 6 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/ocn_align.d | 9 | ||||
| -rw-r--r-- | src/sisudoc/outputs/io_out/sqlite_ocda_db.d | 8 |
9 files changed, 106 insertions, 23 deletions
diff --git a/src/sisudoc/ocda/abstraction/db_in.d b/src/sisudoc/ocda/abstraction/db_in.d index 986a09d..28d6423 100644 --- a/src/sisudoc/ocda/abstraction/db_in.d +++ b/src/sisudoc/ocda/abstraction/db_in.d @@ -347,6 +347,11 @@ template spineAbstractionDbRead() { obj.metainfo.parent_ocn = row[_col["parent_ocn"]].as!int; obj.metainfo.parent_lev_markup = row[_col["parent_lev"]].as!int; obj.metainfo.last_descendant_ocn = row[_col["last_descendant_ocn"]].as!int; + /+ ↓ 2.1: a 2.0 database has no such columns, and reads as no range +/ + if ("ocn_range_first" in _col) { + obj.metainfo.ocn_range_first = row[_col["ocn_range_first"]].as!int; + obj.metainfo.ocn_range_last = row[_col["ocn_range_last"]].as!int; + } obj.metainfo.children_headings = _jsonInts(row[_col["children"]].as!string); obj.metainfo.markedup_ancestors = _jsonEight(row[_col["ancestors"]].as!string); obj.metainfo.collapsed_ancestors = _jsonEight(row[_col["ancestors_collapsed"]].as!string); diff --git a/src/sisudoc/ocda/abstraction/doc_has.d b/src/sisudoc/ocda/abstraction/doc_has.d index 4ecf018..f01af78 100644 --- a/src/sisudoc/ocda/abstraction/doc_has.d +++ b/src/sisudoc/ocda/abstraction/doc_has.d @@ -264,13 +264,11 @@ template spineDocHasFromAbstraction() { _tag_assoc[obj.metainfo.identifier]["seg_lv4"] = obj.tags.html_segment_anchor_tag_is; } - /+ ↓ An object that is not written into any segment still carries an - identifier, and can share it: a poem block and its verses have one - ocn between them, the verses are written and the block itself is not. - The block comes second, so an unguarded assignment replaced a good - segment name with an empty one, and the link came out as - ".xhtml#297". seg_lv4 above is guarded already, which is why this - showed in the epub and not in the html. + /+ ↓ An object that is not written into a segment still carries an + identifier, and could share it: a poem block carried the ocn of + its last verse until spec:2.1, with an issue affecting epub output. + Since spec:2.1 the poem has no identifier; the guard stays, for any + object that is not in a segment. +/ if (obj.tags.epub_segment_anchor_tag_is.length > 0) { _tag_assoc[obj.metainfo.identifier]["seg_lv1to4"] diff --git a/src/sisudoc/ocda/abstraction/ssp.d b/src/sisudoc/ocda/abstraction/ssp.d index c22741b..dfb8c10 100644 --- a/src/sisudoc/ocda/abstraction/ssp.d +++ b/src/sisudoc/ocda/abstraction/ssp.d @@ -59,6 +59,16 @@ module sisudoc.ocda.abstraction.ssp; part when a property is added (a reader of the newer format still reads the older one), the major part when one changes meaning or goes away. + 2.1 2026-09-29 .ocn_range, the first and last ocn of what a + container holds. A poem is a container of verses and + is cited as their range: it now opens its run of + verses rather than closing it, carries ocn 0 and no + identifier, and states the range. It used to carry + the last verse's ocn and identifier, so one number + named two objects. A 2.0 reader reads a 2.1 file: + the poem was never rendered and has no text, the + verses are unchanged, and the property is an + addition. 2.0 2026-09-18 source.digest changes meaning: it now covers every markup file the document is made of, where it was the sha256 of the master file alone. For a .sst that was @@ -74,7 +84,7 @@ module sisudoc.ocda.abstraction.ssp; reference to a heading above level 4 lands on; was 0.1 +/ -enum ssp_format_version = "2.0"; +enum ssp_format_version = "2.1"; enum ssp_format_version_major = "2"; /+ ↓ escaping for values that are folded onto one line @@ -181,6 +191,11 @@ template sspObjectRecord() { if (obj.metainfo.last_descendant_ocn != 0) { output ~= ".last_descendant: " ~ obj.metainfo.last_descendant_ocn.to!string; } + /+ ↓ a container: the first and last ocn of what it holds (2.1) +/ + if (obj.metainfo.ocn_range_first != 0) { + output ~= ".ocn_range: " ~ obj.metainfo.ocn_range_first.to!string + ~ " " ~ obj.metainfo.ocn_range_last.to!string; + } /+ ↓ child headings (from pre-computed map) +/ if (obj.metainfo.children_headings.length > 0) { string[] ch; diff --git a/src/sisudoc/ocda/abstraction/ssp_in.d b/src/sisudoc/ocda/abstraction/ssp_in.d index 0e461ad..0c67ab2 100644 --- a/src/sisudoc/ocda/abstraction/ssp_in.d +++ b/src/sisudoc/ocda/abstraction/ssp_in.d @@ -76,7 +76,7 @@ template spineAbstractionRead() { object sections in the order the file gives them +/ struct SSPdocument { - string format; // "% SiSU Document Abstraction v2.0" + string format; // "% SiSU Document Abstraction v2.1" string source; // "% Source: ..." string[string] source_info; // the @source block string[] source_info_order; @@ -230,6 +230,14 @@ template spineAbstractionRead() { case "section": obj.metainfo.is_of_section = _val; break; case "parent": obj.metainfo.parent_ocn = _val.to!int; break; case "last_descendant": obj.metainfo.last_descendant_ocn = _val.to!int; break; + case "ocn_range": { + auto _r = _ints(_val); + if (_r.length == 2) { + obj.metainfo.ocn_range_first = _r[0]; + obj.metainfo.ocn_range_last = _r[1]; + } + break; + } case "children": obj.metainfo.children_headings = _ints(_val); break; case "ancestors": obj.metainfo.markedup_ancestors = _eight(_val); diff --git a/src/sisudoc/ocda/meta/metadoc_from_src.d b/src/sisudoc/ocda/meta/metadoc_from_src.d index 3670a39..cbd25e2 100644 --- a/src/sisudoc/ocda/meta/metadoc_from_src.d +++ b/src/sisudoc/ocda/meta/metadoc_from_src.d @@ -148,6 +148,8 @@ template docAbstraction() { "start" : "", "end" : "" ]; + /+ ↓ where the open poem's first verse will go, -1 when no poem is open +/ + long _poem_open_idx = -1; string[] lv_ancestors_txt = [ "", "", "", "", "", "", "", "", ]; /+ ↓ dom structure state, per document (not shared, parallel safe) +/ int[] dom_structure_markedup_tags_status = [ 0, 0, 0, 0, 0, 0, 0, 0,]; @@ -426,6 +428,7 @@ template docAbstraction() { object_reset(an_object); processing.remove("verse"); object_number_poem["start"] = obj_cite_digits.object_number.to!string; + _poem_open_idx = the_document_body_section.length.to!long; } { ST_txt_by_line_block_start _get = line.txt_by_line_block_start(pith, dochas, object_number_poem); @@ -602,6 +605,46 @@ template docAbstraction() { pith = _get.pith; } } + /+ ↓ poem closed: poem object has been appended after its last verse. + It is a container, not a citable unit - its verses are - so it + carries no ocn of its own and states the range of its verses + instead, and it goes to the head of them, so that a writer + meeting it knows what follows and where it ends, and can wrap the + verses in one pass. (an issue with a note in the last verse of a + poem has been addressed) + +/ + if (_poem_open_idx >= 0 + && the_document_body_section.length > _poem_open_idx + 1 + && the_document_body_section[$-1].metainfo.is_a == "poem" + ) { + size_t _s = _poem_open_idx.to!size_t; + size_t _p = the_document_body_section.length - 1; + size_t _first = _s; + while (_first < _p && the_document_body_section[_first].metainfo.is_a != "verse") { ++_first; } + size_t _last = _p; + while (_last > _s && the_document_body_section[_last - 1].metainfo.is_a != "verse") { --_last; } + if (_first < _p && _last > _s) { + --_last; + if ((the_document_body_section[_last].text).match(rgx.inline_notes_al_all_note)) { + object_notes = note_section.gather_notes_for_endnote_section( + the_document_body_section, + tag_in_seg, + (_last).to!int, + ); + } + auto _poem = the_document_body_section[_p]; + _poem.metainfo.ocn_range_first = the_document_body_section[_first].metainfo.ocn; + _poem.metainfo.ocn_range_last = the_document_body_section[_last].metainfo.ocn; + _poem.tags.html_segment_anchor_tag_is = the_document_body_section[_first].tags.html_segment_anchor_tag_is; + _poem.tags.epub_segment_anchor_tag_is = the_document_body_section[_first].tags.epub_segment_anchor_tag_is; + the_document_body_section + = the_document_body_section[0 .. _s] + ~ _poem + ~ the_document_body_section[_s .. _p]; + previous_length = the_document_body_section.length.to!int; + } + _poem_open_idx = -1; + } } else { // line.empty, post contents, empty variables: assert( @@ -1031,12 +1074,13 @@ template docAbstraction() { = obj.tags.html_segment_anchor_tag_is; } /+ ↓ An object that is not written into any segment still carries an - identifier, and can share it: a poem block and its verses have one - ocn between them, the verses are written and the block itself is not. + identifier, and could share it: a poem block carried the ocn of its last + verse until 2.1, and the verses are written where the block is not. The block comes second, so an unguarded assignment replaced a good segment name with an empty one, and the link came out as ".xhtml#297". seg_lv4 above is guarded already, which is why this - showed in the epub and not in the html. + showed in the epub and not in the html. Since 2.1 the poem has no + identifier; the guard stays, for any object that is not in a segment. +/ if (!(obj.tags.epub_segment_anchor_tag_is.empty)) { tag_assoc[(obj.metainfo.identifier)]["seg_lv1to4"] diff --git a/src/sisudoc/ocda/meta/metadoc_from_src_functions.d b/src/sisudoc/ocda/meta/metadoc_from_src_functions.d index 922f389..fc12087 100644 --- a/src/sisudoc/ocda/meta/metadoc_from_src_functions.d +++ b/src/sisudoc/ocda/meta/metadoc_from_src_functions.d @@ -2163,11 +2163,13 @@ template docAbstractionFunctions() { heading_ptr-1, an_object["is"] ); - comp_obj_poem_ocn = set_object_generic("body", "body", "block", "poem", "", obj_cite_digits.object_number); - comp_obj_poem_ocn.metainfo.identifier = obj_cite_digits.identifier; - comp_obj_poem_ocn.metainfo.object_number_off = obj_cite_digits.off; - comp_obj_poem_ocn.metainfo.o_n_book_index = obj_cite_digits.bkidx; - comp_obj_poem_ocn.metainfo.object_number_type = obj_cite_digits.type; + /+ ↓ the poem: a container of the verses just written, not a citable unit + on its own, it now states the range of its verses + (ocn_range_first/_last), (it previously carried the last verse's ocn + and identifier, one number occurring on two objects) + +/ + comp_obj_poem_ocn = set_object_generic("body", "body", "block", "poem", "", 0); + comp_obj_poem_ocn.metainfo.identifier = ""; the_document_body_section ~= comp_obj_poem_ocn; pith["block_is"] = eN.blk_is.poem; pith["block_state"] = eN.blk_state.off; diff --git a/src/sisudoc/ocda/meta/metadoc_object_setter.d b/src/sisudoc/ocda/meta/metadoc_object_setter.d index 6c090db..eff01a2 100644 --- a/src/sisudoc/ocda/meta/metadoc_object_setter.d +++ b/src/sisudoc/ocda/meta/metadoc_object_setter.d @@ -230,6 +230,12 @@ template ObjectSetter() { int parent_lev_markup = 0; int parent_ocn = 0; int last_descendant_ocn = 0; + /+ ↓ the objects a container holds, as the first and last of their ocns: a + poem is not itself a citable object, its verses are, and it is cited as + the range of citable objects, ocn ("173-180"). 0 where not a container. + +/ + int ocn_range_first = 0; + int ocn_range_last = 0; int[] children_headings; SHA_256_Hashes_ sha256; } diff --git a/src/sisudoc/ocda/meta/ocn_align.d b/src/sisudoc/ocda/meta/ocn_align.d index 9f9fdaf..e93f12a 100644 --- a/src/sisudoc/ocda/meta/ocn_align.d +++ b/src/sisudoc/ocda/meta/ocn_align.d @@ -163,10 +163,11 @@ template spineOcnAlign() { foreach (obj; doc.abstraction[section]) { int _ocn = obj.metainfo.ocn.to!int; if (_ocn <= 0) { continue; } - /+ ↓ first in document order wins. An ocn is not unique: a poem and - its first verse share one, by design, so the same number names - two objects and the first of them is the one both languages - will have reached first. + /+ ↓ first in document order wins. Until format spec:2.1 a (verse) ocn + was not unique: a poem carried the ocn of its last verse, so the + same number named two objects. A poem now carries 0 and is passed + over above; the rule stays, and costs nothing, for an artefact + written before spec:2.1. +/ if (_ocn !in _p.kinds) { _p.kinds[_ocn] = _kindOf(obj); } _p.ocn_count += 1; diff --git a/src/sisudoc/outputs/io_out/sqlite_ocda_db.d b/src/sisudoc/outputs/io_out/sqlite_ocda_db.d index f312d91..561d89c 100644 --- a/src/sisudoc/outputs/io_out/sqlite_ocda_db.d +++ b/src/sisudoc/outputs/io_out/sqlite_ocda_db.d @@ -182,6 +182,8 @@ template spineAbstractionDb() { parent_ocn INTEGER DEFAULT 0, parent_lev INTEGER DEFAULT 0, last_descendant_ocn INTEGER DEFAULT 0, + ocn_range_first INTEGER DEFAULT 0, + ocn_range_last INTEGER DEFAULT 0, children TEXT CHECK(children IS NULL OR json_valid(children)), ancestors TEXT @@ -436,7 +438,7 @@ template spineAbstractionDb() { "INSERT INTO objects (" ~ "doc_id, section, seq, ocn, is_a, is_of_part, is_of_section, is_of_type," ~ "heading_level, heading_lev_collapsed, identifier," - ~ "parent_ocn, parent_lev, last_descendant_ocn," + ~ "parent_ocn, parent_lev, last_descendant_ocn, ocn_range_first, ocn_range_last," ~ "children, ancestors, ancestors_collapsed," ~ "dom_status, dom_status_collapsed," ~ "dummy_heading, object_number_off," @@ -451,7 +453,7 @@ template spineAbstractionDb() { ~ ") VALUES (" ~ ":doc_id, :section, :seq, :ocn, :is_a, :is_of_part, :is_of_section, :is_of_type," ~ ":heading_level, :heading_lev_collapsed, :identifier," - ~ ":parent_ocn, :parent_lev, :last_descendant_ocn," + ~ ":parent_ocn, :parent_lev, :last_descendant_ocn, :ocn_range_first, :ocn_range_last," ~ ":children, :ancestors, :ancestors_collapsed," ~ ":dom_status, :dom_status_collapsed," ~ ":dummy_heading, :object_number_off," @@ -560,6 +562,8 @@ template spineAbstractionDb() { obj_stmt.bind(":parent_ocn", obj.metainfo.parent_ocn); obj_stmt.bind(":parent_lev", obj.metainfo.parent_lev_markup); obj_stmt.bind(":last_descendant_ocn", obj.metainfo.last_descendant_ocn); + obj_stmt.bind(":ocn_range_first", obj.metainfo.ocn_range_first); + obj_stmt.bind(":ocn_range_last", obj.metainfo.ocn_range_last); bindJsonInts(":children", obj.metainfo.children_headings); bindJsonInts(":ancestors", obj.metainfo.markedup_ancestors); |
