diff options
| author | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-11 12:32:13 -0400 |
|---|---|---|
| committer | Ralph Amissah <ralph.amissah@gmail.com> | 2026-09-12 12:14:39 -0400 |
| commit | 4e6774904ce811b074d8b3694b04dc5c1e8219e4 (patch) | |
| tree | 236137b4f7f36e683791ea4fa8d4f0f43e30945a /src/sisudoc | |
| parent | ocda: ST_DocHas from a loaded abstraction (diff) | |
ocda: heading cross reference linking
The segment a cross reference to a heading lands on, gixed upstream in
ocda, where the field is created, rather than inferred by the loader.
A heading above level 4 opens no html segment of its own, so a link to
it has to land on the level 4 heading that follows. The build loop
cannot know that when it reads the heading, and says so in a comment of
its own: "for html segname need following lv4 not yet known". It
back-fills tag_assoc when the level 4 heading arrives (lv0to3_tags), and
the answer then lives only in that map.
tags.segment_lv4_is now holds it, resolved in a pass over the finished
head and body sections: walking backwards, the last level 4 heading seen
is the next one for everything above it, which is the same answer the
back-fill gives. Emitted in the .ssp only where it differs from
.segment_html_is, which is every heading at level 4 or below, so it is
sparse: 317 lines over the 35 document reference set, none removed.
Carried in the database as a column of its own, and read back by both
readers.
docHasFromAbstraction reads it instead of working it out. With that, and
on top of the two defect fixes, a document loaded from an artefact and
one parsed from markup agree:
key sets identical, all 35 documents
values 3 entries differ of some 30,000, all of them
_the_title, where the abstraction supplies an epub
segment the parser leaves unset
link targets all 1,651 agree, against 89 differing before any
of this work and 1 after the defect fixes alone
Format stays v1.1. That version is new in this same run of work and
nothing outside spine has read it, so this belongs in it rather than in
a bump of its own.
(assisted by Claude-Code)
Diffstat (limited to 'src/sisudoc')
| -rw-r--r-- | src/sisudoc/ocda/abstraction/db_in.d | 1 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/doc_has.d | 40 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp.d | 12 | ||||
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp_in.d | 1 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/metadoc_from_src.d | 31 | ||||
| -rw-r--r-- | src/sisudoc/ocda/meta/metadoc_object_setter.d | 10 | ||||
| -rw-r--r-- | src/sisudoc/outputs/io_out/create_ocda_db.d | 6 |
7 files changed, 76 insertions, 25 deletions
diff --git a/src/sisudoc/ocda/abstraction/db_in.d b/src/sisudoc/ocda/abstraction/db_in.d index 0d78f12..6e9b0e2 100644 --- a/src/sisudoc/ocda/abstraction/db_in.d +++ b/src/sisudoc/ocda/abstraction/db_in.d @@ -277,6 +277,7 @@ template spineAbstractionDbRead() { obj.tags.segment_anchor_tag_epub = row[_col["segment_epub"]].as!string; obj.tags.html_segment_anchor_tag_is = row[_col["segment_html_is"]].as!string; obj.tags.epub_segment_anchor_tag_is = row[_col["segment_epub_is"]].as!string; + obj.tags.segment_lv4_is = row[_col["segment_lv4_is"]].as!string; obj.tags.anchor_tag_html = row[_col["anchor"]].as!string; obj.tags.heading_lev_anchor_tag = row[_col["heading_lev_anchor"]].as!string; { diff --git a/src/sisudoc/ocda/abstraction/doc_has.d b/src/sisudoc/ocda/abstraction/doc_has.d index 75a4640..051da33 100644 --- a/src/sisudoc/ocda/abstraction/doc_has.d +++ b/src/sisudoc/ocda/abstraction/doc_has.d @@ -163,20 +163,6 @@ template spineDocHasFromAbstraction() { _out.quotes = _count(_doc_has_block, "quotes"); string[] _images; string[string][string] _tag_assoc; - /+ ↓ keys waiting for the level 4 segment that will hold them. - - a heading above level 4 opens no html segment, so a cross reference - to it has to land on the next level 4 heading, which has not been - seen yet when the heading is read. The parser does this the same - way, back-filling a pending tag when the level 4 heading arrives - (metadoc_from_src.d, "tag_assoc[lv0_to_lv3_html_tag]"), and the - order of the objects is what both are reading it from. - +/ - string[] _awaiting_lv4; - void _backfill(string _seg) { - foreach (_k; _awaiting_lv4) { _tag_assoc[_k]["seg_lv4"] = _seg; } - _awaiting_lv4 = []; - } /+ ↓ segnames["html"] is seeded with the toc, which is a segment of its own before any level 4 heading opens one +/ _out.segnames_lv4 ~= "toc"; @@ -226,6 +212,7 @@ template spineDocHasFromAbstraction() { +/ if (obj.tags.heading_lev_anchor_tag.length > 0 && obj.tags.in_segment_html.length > 0 + && obj.metainfo.heading_lev_markup >= 4 ) { _tag_assoc[obj.tags.heading_lev_anchor_tag]["seg_lv4"] = obj.tags.in_segment_html; @@ -245,14 +232,23 @@ template spineDocHasFromAbstraction() { if (obj.metainfo.heading_lev_markup <= 4) { _out.segnames_lv_0_to_4 ~= obj.tags.segment_anchor_tag_epub; } - if (obj.metainfo.heading_lev_markup == 4) { - if (_section == "body") { _out.segnames_lv4 ~= obj.tags.anchor_tag_html; } - /+ ↓ this is the segment the headings above it were waiting for +/ - _backfill(obj.tags.in_segment_html); - } else if (obj.metainfo.heading_lev_markup < 4) { - foreach (_k; [obj.metainfo.identifier, obj.tags.heading_lev_anchor_tag, - obj.tags.anchor_tag_html]) { - if (_k.length > 0) { _awaiting_lv4 ~= _k; } + if (_section == "body" && obj.metainfo.heading_lev_markup == 4) { + _out.segnames_lv4 ~= obj.tags.anchor_tag_html; + } + /+ ↓ a heading above level 4 opens no html segment of its own, so + a cross reference to it lands on the level 4 segment that + follows. That is .segment_lv4_is, resolved in ocda where the + finished sections are to hand and carried by the artefact, so + there is nothing to work out here + +/ + if (obj.tags.segment_lv4_is.length > 0) { + /+ ↓ seg_lv4 only, and not through heading_lev_anchor_tag: that + names the level 4 heading itself, whose own entry says it + opens that segment rather than merely sits in it. epub + segments go down to level 1, so seg_lv1to4 is untouched + +/ + foreach (_k; [obj.metainfo.identifier, obj.tags.anchor_tag_html]) { + if (_k.length > 0) { _tag_assoc[_k]["seg_lv4"] = obj.tags.segment_lv4_is; } } } } diff --git a/src/sisudoc/ocda/abstraction/ssp.d b/src/sisudoc/ocda/abstraction/ssp.d index 6b487d7..487a33b 100644 --- a/src/sisudoc/ocda/abstraction/ssp.d +++ b/src/sisudoc/ocda/abstraction/ssp.d @@ -61,7 +61,9 @@ module sisudoc.ocda.abstraction.ssp; 1.1 2026-09-10 @source block; the document metadata the output writers read carried in full (14 meta and 3 make properties - added); was 0.1 + added); .segment_lv4_is, the segment a cross + reference to a heading above level 4 lands on; + was 0.1 +/ enum ssp_format_version = "1.1"; enum ssp_format_version_major = "1"; @@ -380,6 +382,14 @@ template sspObjectRecord() { if (obj.tags.epub_segment_anchor_tag_is.length > 0) { output ~= ".segment_epub_is: " ~ obj.tags.epub_segment_anchor_tag_is; } + /+ ↓ the level 4 segment a cross reference to this heading lands on, + emitted only where it is not the segment the heading sits in, which + is every heading at level 4 or below +/ + if (obj.tags.segment_lv4_is.length > 0 + && obj.tags.segment_lv4_is != obj.tags.html_segment_anchor_tag_is + ) { + output ~= ".segment_lv4_is: " ~ obj.tags.segment_lv4_is; + } /+ ↓ heading ancestors text +/ { bool has_hat = false; diff --git a/src/sisudoc/ocda/abstraction/ssp_in.d b/src/sisudoc/ocda/abstraction/ssp_in.d index 873cb7e..59a5b80 100644 --- a/src/sisudoc/ocda/abstraction/ssp_in.d +++ b/src/sisudoc/ocda/abstraction/ssp_in.d @@ -311,6 +311,7 @@ template spineAbstractionRead() { case "segment_epub": obj.tags.segment_anchor_tag_epub = _val; break; case "segment_html_is": obj.tags.html_segment_anchor_tag_is = _val; break; case "segment_epub_is": obj.tags.epub_segment_anchor_tag_is = _val; break; + case "segment_lv4_is": obj.tags.segment_lv4_is = _val; break; case "heading_ancestors_text": { string[8] _h; diff --git a/src/sisudoc/ocda/meta/metadoc_from_src.d b/src/sisudoc/ocda/meta/metadoc_from_src.d index c5e2ce8..fb30a94 100644 --- a/src/sisudoc/ocda/meta/metadoc_from_src.d +++ b/src/sisudoc/ocda/meta/metadoc_from_src.d @@ -1246,6 +1246,37 @@ template docAbstraction() { the_document_bookindex_section = get_ancestors.the_document_bookindex_section; the_document_blurb_section = get_ancestors.the_document_blurb_section; } + /+ ↓ the html segment a cross reference to a heading lands on. + a heading above level 4 opens no html segment, so a link to it goes + to the level 4 heading that follows it. The build loop cannot know + that yet and back-fills tag_assoc when the level 4 heading arrives + (see lv0to3_tags); this is the same answer, kept on the object, so + that it travels with the abstraction instead of living only in a map + the parser holds. Walking backwards, the last level 4 seen is the + next one for everything above it. + +/ + { + string _next_lv4; + foreach_reverse (ref obj; the_document_body_section) { + if (obj.metainfo.is_a != "heading") { continue; } + if (obj.metainfo.heading_lev_markup == 4) { + _next_lv4 = obj.tags.anchor_tag_html; + } else if (obj.metainfo.heading_lev_markup < 4) { + obj.tags.segment_lv4_is = _next_lv4; + } + } + /+ ↓ the head section's title sits above the body and resolves to the + body's first level 4 heading, which is what _next_lv4 now holds + +/ + foreach_reverse (ref obj; the_document_head_section) { + if (obj.metainfo.is_a != "heading") { continue; } + if (obj.metainfo.heading_lev_markup == 4) { + _next_lv4 = obj.tags.anchor_tag_html; + } else if (obj.metainfo.heading_lev_markup < 4) { + obj.tags.segment_lv4_is = _next_lv4; + } + } + } // get descendants if (the_document_body_section.length > 1) { auto pairs = after_doc_get_descendants( diff --git a/src/sisudoc/ocda/meta/metadoc_object_setter.d b/src/sisudoc/ocda/meta/metadoc_object_setter.d index 2eab062..6c090db 100644 --- a/src/sisudoc/ocda/meta/metadoc_object_setter.d +++ b/src/sisudoc/ocda/meta/metadoc_object_setter.d @@ -130,6 +130,16 @@ template ObjectSetter() { string segment_anchor_tag_epub = ""; string html_segment_anchor_tag_is = ""; string epub_segment_anchor_tag_is = ""; + /+ the html segment a cross reference to this heading lands on. + a heading above level 4 opens no html segment of its own, so a link to + it has to go to the level 4 heading that follows, which is not known + when the heading is read ("for html segname need following lv4 not yet + known", metadoc_from_src.d). It is resolved in a pass over the + finished sections and kept here, so that it is a property of the + object like any other and travels with the abstraction rather than + living only in the tag_associations map the parser builds. + +/ + string segment_lv4_is = ""; string heading_lev_anchor_tag = ""; string segname_prev = ""; string segname_next = ""; diff --git a/src/sisudoc/outputs/io_out/create_ocda_db.d b/src/sisudoc/outputs/io_out/create_ocda_db.d index f184854..b2cfd62 100644 --- a/src/sisudoc/outputs/io_out/create_ocda_db.d +++ b/src/sisudoc/outputs/io_out/create_ocda_db.d @@ -172,6 +172,7 @@ template spineAbstractionDb() { segment_epub TEXT, segment_html_is TEXT, segment_epub_is TEXT, + segment_lv4_is TEXT, anchor TEXT, heading_lev_anchor TEXT, heading_ancestors_text TEXT @@ -324,7 +325,7 @@ template spineAbstractionDb() { ~ "indent_base, indent_hang, bullet, lang," ~ "has_links, has_notes_reg, has_notes_star, has_images, has_images_no_dim," ~ "segment, segment_prev, segment_next, segment_epub," - ~ "segment_html_is, segment_epub_is," + ~ "segment_html_is, segment_epub_is, segment_lv4_is," ~ "anchor, heading_lev_anchor, heading_ancestors_text," ~ "table_cols, table_widths, table_aligns, table_header," ~ "code_linenumbers, text" @@ -339,7 +340,7 @@ template spineAbstractionDb() { ~ ":indent_base, :indent_hang, :bullet, :lang," ~ ":has_links, :has_notes_reg, :has_notes_star, :has_images, :has_images_no_dim," ~ ":segment, :segment_prev, :segment_next, :segment_epub," - ~ ":segment_html_is, :segment_epub_is," + ~ ":segment_html_is, :segment_epub_is, :segment_lv4_is," ~ ":anchor, :heading_lev_anchor, :heading_ancestors_text," ~ ":table_cols, :table_widths, :table_aligns, :table_header," ~ ":code_linenumbers, :text" @@ -483,6 +484,7 @@ template spineAbstractionDb() { bindStr(":segment_epub", obj.tags.segment_anchor_tag_epub); bindStr(":segment_html_is", obj.tags.html_segment_anchor_tag_is); bindStr(":segment_epub_is", obj.tags.epub_segment_anchor_tag_is); + bindStr(":segment_lv4_is", obj.tags.segment_lv4_is); bindStr(":anchor", obj.tags.anchor_tag_html); bindStr(":heading_lev_anchor", obj.tags.heading_lev_anchor_tag); bindJsonStrs(":heading_ancestors_text", obj.tags.heading_ancestors_text); |
