aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sisudoc/ocda
diff options
context:
space:
mode:
Diffstat (limited to 'src/sisudoc/ocda')
-rw-r--r--src/sisudoc/ocda/abstraction/ssp.d611
1 files changed, 310 insertions, 301 deletions
diff --git a/src/sisudoc/ocda/abstraction/ssp.d b/src/sisudoc/ocda/abstraction/ssp.d
index 1ec2d41..181e785 100644
--- a/src/sisudoc/ocda/abstraction/ssp.d
+++ b/src/sisudoc/ocda/abstraction/ssp.d
@@ -50,6 +50,314 @@
module sisudoc.ocda.abstraction.ssp;
@safe:
/+ ↓ write document abstraction as human-readable .ssp text file +/
+/+ ↓ one object record: the single definition of what an object looks
+ like in a .ssp file. the writer emits with it, and the reader is
+ checked against it by round trip, so the two cannot drift
++/
+template sspObjectRecord() {
+ import std.conv : to;
+ import std.digest : toHexString;
+ import std.stdio;
+ import std.string;
+ import std.array;
+ string[] sspObjectRecord(O)(O obj, string section, bool _vox = false) {
+ string[] output;
+ /+ ↓ object declaration line. a heading carries its identifier here,
+ on the declaration line, where it is not the ocn; every other
+ object carries it as .identifier below, on the same condition
+ +/
+ string obj_decl = "[" ~ obj.metainfo.ocn.to!string ~ "] ";
+ if (obj.metainfo.is_a == "heading") {
+ string lev = obj.metainfo.marked_up_level;
+ obj_decl ~= "heading :" ~ lev;
+ if (obj.metainfo.identifier.length > 0
+ && obj.metainfo.identifier != obj.metainfo.ocn.to!string) {
+ obj_decl ~= " " ~ obj.metainfo.identifier;
+ }
+ } else {
+ obj_decl ~= obj.metainfo.is_a;
+ }
+ output ~= obj_decl;
+ /+ ↓ properties (only non-default values) +/
+ if (obj.metainfo.is_a != "heading"
+ && obj.metainfo.identifier.length > 0
+ && obj.metainfo.identifier != obj.metainfo.ocn.to!string) {
+ output ~= ".identifier: " ~ obj.metainfo.identifier;
+ }
+ if (obj.metainfo.is_of_part.length > 0) {
+ output ~= ".part: " ~ obj.metainfo.is_of_part;
+ }
+ if (obj.metainfo.is_of_section.length > 0
+ && obj.metainfo.is_of_section != section) {
+ output ~= ".section: " ~ obj.metainfo.is_of_section;
+ }
+ /+ ↓ parent: emitted for every object except:
+ - the document root, the one object that has no parent
+ - toc entries, generated furniture rather than authored objects,
+ whose place is given by their indent level; the toc heading
+ itself does carry its parent (the document root)
+ A parent ocn of 0 is not "no parent recorded", it is a parent
+ heading the author has made non-substantive (ocn suppressed, or a
+ dummy heading introduced to segment an output); it is structurally
+ truthful and is stated explicitly rather than omitted so that the
+ two cases remain distinguishable in the file
+ +/
+ if (!(obj.metainfo.is_a == "heading"
+ && obj.metainfo.heading_lev_markup == 0)
+ && obj.metainfo.is_a != "toc"
+ ) {
+ output ~= ".parent: " ~ obj.metainfo.parent_ocn.to!string;
+ }
+ if (obj.metainfo.last_descendant_ocn != 0) {
+ output ~= ".last_descendant: " ~ obj.metainfo.last_descendant_ocn.to!string;
+ }
+ /+ ↓ child headings (from pre-computed map) +/
+ if (obj.metainfo.children_headings.length > 0) {
+ string[] ch;
+ foreach (c; obj.metainfo.children_headings) {
+ ch ~= c.to!string;
+ }
+ output ~= ".children: " ~ ch.join(" ");
+ }
+ /+ ↓ ancestors (only if non-zero) +/
+ {
+ bool has_anc = false;
+ foreach (a; obj.metainfo.markedup_ancestors) {
+ if (a != 0) { has_anc = true; break; }
+ }
+ if (has_anc) {
+ string anc;
+ foreach (i, a; obj.metainfo.markedup_ancestors) {
+ if (i > 0) { anc ~= " "; }
+ anc ~= a.to!string;
+ }
+ output ~= ".ancestors: " ~ anc;
+ }
+ }
+ /+ ↓ collapsed ancestors (only if non-zero) +/
+ {
+ bool has_anc_c = false;
+ foreach (a; obj.metainfo.collapsed_ancestors) {
+ if (a != 0) { has_anc_c = true; break; }
+ }
+ if (has_anc_c) {
+ string anc;
+ foreach (i, a; obj.metainfo.collapsed_ancestors) {
+ if (i > 0) anc ~= " ";
+ anc ~= a.to!string;
+ }
+ output ~= ".ancestors_collapsed: " ~ anc;
+ }
+ }
+ /+ ↓ dom structure status (only if non-zero) +/
+ {
+ bool has_dom = false;
+ foreach (d; obj.metainfo.dom_structure_markedup_tags_status) {
+ if (d != 0) { has_dom = true; break; }
+ }
+ if (has_dom) {
+ string ds;
+ foreach (i, d; obj.metainfo.dom_structure_markedup_tags_status) {
+ if (i > 0) { ds ~= " "; }
+ ds ~= d.to!string;
+ }
+ output ~= ".dom_status: " ~ ds;
+ }
+ }
+ {
+ bool has_dom_c = false;
+ foreach (d; obj.metainfo.dom_structure_collapsed_tags_status) {
+ if (d != 0) { has_dom_c = true; break; }
+ }
+ if (has_dom_c) {
+ string ds;
+ foreach (i, d; obj.metainfo.dom_structure_collapsed_tags_status) {
+ if (i > 0) { ds ~= " "; }
+ ds ~= d.to!string;
+ }
+ output ~= ".dom_status_collapsed: " ~ ds;
+ }
+ }
+ if (obj.metainfo.heading_lev_collapsed < 9) {
+ output ~= ".heading_lev_collapsed: " ~ obj.metainfo.heading_lev_collapsed.to!string;
+ }
+ if (obj.metainfo.parent_lev_markup != 0) {
+ output ~= ".parent_lev: " ~ obj.metainfo.parent_lev_markup.to!string;
+ }
+ if (obj.metainfo.dummy_heading) {
+ output ~= ".dummy: true";
+ }
+ if (obj.metainfo.object_number_off) {
+ output ~= ".ocn_off: true";
+ }
+ if (obj.metainfo.is_of_type.length > 0) {
+ output ~= ".is_of_type: " ~ obj.metainfo.is_of_type;
+ }
+ if (obj.metainfo.attrib.length > 0) {
+ output ~= ".attrib: " ~ obj.metainfo.attrib;
+ }
+ if (obj.metainfo.lang.length > 0) {
+ output ~= ".meta_lang: " ~ obj.metainfo.lang;
+ }
+ if (obj.metainfo.syntax.length > 0) {
+ output ~= ".meta_syntax: " ~ obj.metainfo.syntax;
+ }
+ /+ ↓ sha256 digest +/
+ {
+ bool has_sha = false;
+ foreach (b; obj.metainfo.sha256.text) {
+ if (b != 0) { has_sha = true; break; }
+ }
+ if (has_sha) {
+ if (_vox) {
+ writeln(obj.metainfo.sha256.summary);
+ }
+ output ~= ".sha256: " ~ obj.metainfo.sha256.text.toHexString.to!string;
+ }
+ }
+ /+ ↓ images, repeatable record, one per image in order of appearance:
+ .image: <filename> sha256:<HEX> bytes:<n> px:<width>x<height>
+ .image: <filename> missing:true
+ filename first (the object's handle on the image), remaining fields
+ self-describing key:value tokens so that further fields can be
+ added later without invalidating existing readers. px: is the
+ file's own pixel size, not the display dimensions (w##h##) which
+ the object text already carries
+ +/
+ if (obj.metainfo.sha256.images.length > 0) {
+ foreach (i; obj.metainfo.sha256.images) {
+ output ~= (i.fileMissing)
+ ? ".image: " ~ i.fileName
+ ~ " missing:true"
+ : ".image: " ~ i.fileName
+ ~ " sha256:" ~ i.fileHash_sha256.toHexString.to!string
+ ~ " bytes:" ~ i.fileSize.to!string
+ ~ ((i.imageWidth > 0 && i.imageHeight > 0)
+ ? " px:" ~ i.imageWidth.to!string
+ ~ "x" ~ i.imageHeight.to!string
+ : "");
+ }
+ }
+ /+ ↓ text attributes +/
+ if (obj.attrib.indent_base != 0 || obj.attrib.indent_hang != 0) {
+ output ~= ".indent: " ~ obj.attrib.indent_base.to!string
+ ~ " " ~ obj.attrib.indent_hang.to!string;
+ }
+ if (obj.attrib.bullet) {
+ output ~= ".bullet: true";
+ }
+ if (obj.attrib.language.length > 0) {
+ output ~= ".lang: " ~ obj.attrib.language;
+ }
+ /+ ↓ has flags +/
+ {
+ string[] has_flags;
+ if (obj.has.inline_links) { has_flags ~= "links"; }
+ if (obj.has.inline_notes_reg) { has_flags ~= "notes_reg"; }
+ if (obj.has.inline_notes_star) { has_flags ~= "notes_star"; }
+ if (obj.has.images) { has_flags ~= "images"; }
+ if (obj.has.image_without_dimensions) { has_flags ~= "images_no_dim"; }
+ if (has_flags.length > 0) {
+ output ~= ".has: " ~ has_flags.join(" ");
+ }
+ }
+ /+ ↓ table properties +/
+ if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) {
+ output ~= ".table_cols: " ~ obj.table.number_of_columns.to!string;
+ if (obj.table.column_widths.length > 0) {
+ string[] ws;
+ foreach (w; obj.table.column_widths) ws ~= w.to!string;
+ output ~= ".table_widths: " ~ ws.join(" ");
+ }
+ if (obj.table.column_aligns.length > 0) {
+ output ~= ".table_aligns: " ~ obj.table.column_aligns.join(" ");
+ }
+ if (obj.table.heading) {
+ output ~= ".table_header: true";
+ }
+ }
+ /+ ↓ code block properties +/
+ if (obj.metainfo.is_a == "code") {
+ if (obj.code_block.linenumbers) {
+ output ~= ".code_linenumbers: true";
+ }
+ }
+ /+ ↓ stow (extracted links) +/
+ if (obj.stow.link.length > 0) {
+ foreach (lnk; obj.stow.link) {
+ if (lnk.length > 0) {
+ output ~= ".stow_link: " ~ lnk;
+ }
+ }
+ }
+ /+ ↓ tag properties +/
+ if (obj.tags.in_segment_html.length > 0) {
+ output ~= ".segment: " ~ obj.tags.in_segment_html;
+ }
+ if (obj.tags.anchor_tag_html.length > 0
+ && obj.tags.anchor_tag_html != obj.tags.in_segment_html) {
+ output ~= ".anchor: " ~ obj.tags.anchor_tag_html;
+ }
+ if (obj.tags.segname_prev.length > 0) {
+ output ~= ".segment_prev: " ~ obj.tags.segname_prev;
+ }
+ if (obj.tags.segname_next.length > 0) {
+ output ~= ".segment_next: " ~ obj.tags.segname_next;
+ }
+ if (obj.tags.heading_lev_anchor_tag.length > 0) {
+ output ~= ".heading_lev_anchor: " ~ obj.tags.heading_lev_anchor_tag;
+ }
+ if (obj.tags.segment_anchor_tag_epub.length > 0) {
+ output ~= ".segment_epub: " ~ obj.tags.segment_anchor_tag_epub;
+ }
+ /+ ↓ the segment an object belongs to, as opposed to the segment a
+ heading opens. .segment (in_segment_html) is set on headings
+ only, so without these two a reader cannot tell which segment a
+ paragraph is in; the sqlite search writer reads the html one
+ +/
+ if (obj.tags.html_segment_anchor_tag_is.length > 0) {
+ output ~= ".segment_html_is: " ~ obj.tags.html_segment_anchor_tag_is;
+ }
+ if (obj.tags.epub_segment_anchor_tag_is.length > 0) {
+ output ~= ".segment_epub_is: " ~ obj.tags.epub_segment_anchor_tag_is;
+ }
+ /+ ↓ heading ancestors text +/
+ {
+ bool has_hat = false;
+ foreach (h; obj.tags.heading_ancestors_text) {
+ if (h.length > 0) { has_hat = true; break; }
+ }
+ if (has_hat) {
+ output ~= ".heading_ancestors_text: " ~ obj.tags.heading_ancestors_text.join("|");
+ }
+ }
+ /+ ↓ lev4 subtoc +/
+ if (obj.tags.lev4_subtoc.length > 0) {
+ foreach (st; obj.tags.lev4_subtoc) {
+ if (st.length > 0) {
+ output ~= ".lev4_subtoc: " ~ st;
+ }
+ }
+ }
+ /+ ↓ anchor tags +/
+ if (obj.tags.anchor_tags.length > 0) {
+ foreach (at; obj.tags.anchor_tags) {
+ if (at.length > 0) {
+ output ~= ".anchor_tag: " ~ at;
+ }
+ }
+ }
+ /+ ↓ text content +/
+ if (obj.text.length > 0) {
+ foreach (line; obj.text.split("\n")) {
+ output ~= "| " ~ line;
+ }
+ }
+ output ~= "";
+ return output;
+ }
+}
+
template spineAbstractionTxt() {
import std.conv : to;
import std.digest : toHexString;
@@ -58,6 +366,7 @@ template spineAbstractionTxt() {
import std.stdio;
import std.string;
import std.array;
+ mixin sspObjectRecord;
void spineAbstractionTxt(D)(D doc) {
auto doc_abstraction = doc.abstraction;
auto doc_matters = doc.matters;
@@ -195,307 +504,7 @@ template spineAbstractionTxt() {
output ~= "@" ~ section ~ " {";
output ~= "";
foreach (obj; section_objs) {
- /+ ↓ object declaration line. a heading carries its identifier here,
- on the declaration line, where it is not the ocn; every other
- object carries it as .identifier below, on the same condition
- +/
- string obj_decl = "[" ~ obj.metainfo.ocn.to!string ~ "] ";
- if (obj.metainfo.is_a == "heading") {
- string lev = obj.metainfo.marked_up_level;
- obj_decl ~= "heading :" ~ lev;
- if (obj.metainfo.identifier.length > 0
- && obj.metainfo.identifier != obj.metainfo.ocn.to!string) {
- obj_decl ~= " " ~ obj.metainfo.identifier;
- }
- } else {
- obj_decl ~= obj.metainfo.is_a;
- }
- output ~= obj_decl;
- /+ ↓ properties (only non-default values) +/
- if (obj.metainfo.is_a != "heading"
- && obj.metainfo.identifier.length > 0
- && obj.metainfo.identifier != obj.metainfo.ocn.to!string) {
- output ~= ".identifier: " ~ obj.metainfo.identifier;
- }
- if (obj.metainfo.is_of_part.length > 0) {
- output ~= ".part: " ~ obj.metainfo.is_of_part;
- }
- if (obj.metainfo.is_of_section.length > 0
- && obj.metainfo.is_of_section != section) {
- output ~= ".section: " ~ obj.metainfo.is_of_section;
- }
- /+ ↓ parent: emitted for every object except:
- - the document root, the one object that has no parent
- - toc entries, generated furniture rather than authored objects,
- whose place is given by their indent level; the toc heading
- itself does carry its parent (the document root)
- A parent ocn of 0 is not "no parent recorded", it is a parent
- heading the author has made non-substantive (ocn suppressed, or a
- dummy heading introduced to segment an output); it is structurally
- truthful and is stated explicitly rather than omitted so that the
- two cases remain distinguishable in the file
- +/
- if (!(obj.metainfo.is_a == "heading"
- && obj.metainfo.heading_lev_markup == 0)
- && obj.metainfo.is_a != "toc"
- ) {
- output ~= ".parent: " ~ obj.metainfo.parent_ocn.to!string;
- }
- if (obj.metainfo.last_descendant_ocn != 0) {
- output ~= ".last_descendant: " ~ obj.metainfo.last_descendant_ocn.to!string;
- }
- /+ ↓ child headings (from pre-computed map) +/
- if (obj.metainfo.children_headings.length > 0) {
- string[] ch;
- foreach (c; obj.metainfo.children_headings) {
- ch ~= c.to!string;
- }
- output ~= ".children: " ~ ch.join(" ");
- }
- /+ ↓ ancestors (only if non-zero) +/
- {
- bool has_anc = false;
- foreach (a; obj.metainfo.markedup_ancestors) {
- if (a != 0) { has_anc = true; break; }
- }
- if (has_anc) {
- string anc;
- foreach (i, a; obj.metainfo.markedup_ancestors) {
- if (i > 0) { anc ~= " "; }
- anc ~= a.to!string;
- }
- output ~= ".ancestors: " ~ anc;
- }
- }
- /+ ↓ collapsed ancestors (only if non-zero) +/
- {
- bool has_anc_c = false;
- foreach (a; obj.metainfo.collapsed_ancestors) {
- if (a != 0) { has_anc_c = true; break; }
- }
- if (has_anc_c) {
- string anc;
- foreach (i, a; obj.metainfo.collapsed_ancestors) {
- if (i > 0) anc ~= " ";
- anc ~= a.to!string;
- }
- output ~= ".ancestors_collapsed: " ~ anc;
- }
- }
- /+ ↓ dom structure status (only if non-zero) +/
- {
- bool has_dom = false;
- foreach (d; obj.metainfo.dom_structure_markedup_tags_status) {
- if (d != 0) { has_dom = true; break; }
- }
- if (has_dom) {
- string ds;
- foreach (i, d; obj.metainfo.dom_structure_markedup_tags_status) {
- if (i > 0) { ds ~= " "; }
- ds ~= d.to!string;
- }
- output ~= ".dom_status: " ~ ds;
- }
- }
- {
- bool has_dom_c = false;
- foreach (d; obj.metainfo.dom_structure_collapsed_tags_status) {
- if (d != 0) { has_dom_c = true; break; }
- }
- if (has_dom_c) {
- string ds;
- foreach (i, d; obj.metainfo.dom_structure_collapsed_tags_status) {
- if (i > 0) { ds ~= " "; }
- ds ~= d.to!string;
- }
- output ~= ".dom_status_collapsed: " ~ ds;
- }
- }
- if (obj.metainfo.heading_lev_collapsed < 9) {
- output ~= ".heading_lev_collapsed: " ~ obj.metainfo.heading_lev_collapsed.to!string;
- }
- if (obj.metainfo.parent_lev_markup != 0) {
- output ~= ".parent_lev: " ~ obj.metainfo.parent_lev_markup.to!string;
- }
- if (obj.metainfo.dummy_heading) {
- output ~= ".dummy: true";
- }
- if (obj.metainfo.object_number_off) {
- output ~= ".ocn_off: true";
- }
- // if (obj.metainfo.o_n_type != 0) {
- // output ~= ".o_n_type: " ~ obj.metainfo.o_n_type.to!string;
- // }
- if (obj.metainfo.is_of_type.length > 0) {
- output ~= ".is_of_type: " ~ obj.metainfo.is_of_type;
- }
- if (obj.metainfo.attrib.length > 0) {
- output ~= ".attrib: " ~ obj.metainfo.attrib;
- }
- if (obj.metainfo.lang.length > 0) {
- output ~= ".meta_lang: " ~ obj.metainfo.lang;
- }
- if (obj.metainfo.syntax.length > 0) {
- output ~= ".meta_syntax: " ~ obj.metainfo.syntax;
- }
- /+ ↓ sha256 digest +/
- {
- bool has_sha = false;
- foreach (b; obj.metainfo.sha256.text) {
- if (b != 0) { has_sha = true; break; }
- }
- if (has_sha) {
- if (doc_matters.opt.action.vox_gt_3) {
- writeln(obj.metainfo.sha256.summary);
- }
- output ~= ".sha256: " ~ obj.metainfo.sha256.text.toHexString.to!string;
- }
- }
- /+ ↓ images, repeatable record, one per image in order of appearance:
- .image: <filename> sha256:<HEX> bytes:<n> px:<width>x<height>
- .image: <filename> missing:true
- filename first (the object's handle on the image), remaining fields
- self-describing key:value tokens so that further fields can be
- added later without invalidating existing readers. px: is the
- file's own pixel size, not the display dimensions (w##h##) which
- the object text already carries
- +/
- if (obj.metainfo.sha256.images.length > 0) {
- foreach (i; obj.metainfo.sha256.images) {
- output ~= (i.fileMissing)
- ? ".image: " ~ i.fileName
- ~ " missing:true"
- : ".image: " ~ i.fileName
- ~ " sha256:" ~ i.fileHash_sha256.toHexString.to!string
- ~ " bytes:" ~ i.fileSize.to!string
- ~ ((i.imageWidth > 0 && i.imageHeight > 0)
- ? " px:" ~ i.imageWidth.to!string
- ~ "x" ~ i.imageHeight.to!string
- : "");
- }
- }
- /+ ↓ text attributes +/
- if (obj.attrib.indent_base != 0 || obj.attrib.indent_hang != 0) {
- output ~= ".indent: " ~ obj.attrib.indent_base.to!string
- ~ " " ~ obj.attrib.indent_hang.to!string;
- }
- if (obj.attrib.bullet) {
- output ~= ".bullet: true";
- }
- if (obj.attrib.language.length > 0) {
- output ~= ".lang: " ~ obj.attrib.language;
- }
- /+ ↓ has flags +/
- {
- string[] has_flags;
- if (obj.has.inline_links) { has_flags ~= "links"; }
- if (obj.has.inline_notes_reg) { has_flags ~= "notes_reg"; }
- if (obj.has.inline_notes_star) { has_flags ~= "notes_star"; }
- if (obj.has.images) { has_flags ~= "images"; }
- if (obj.has.image_without_dimensions) { has_flags ~= "images_no_dim"; }
- if (has_flags.length > 0) {
- output ~= ".has: " ~ has_flags.join(" ");
- }
- }
- /+ ↓ table properties +/
- if (obj.metainfo.is_a == "table" && obj.table.number_of_columns > 0) {
- output ~= ".table_cols: " ~ obj.table.number_of_columns.to!string;
- if (obj.table.column_widths.length > 0) {
- string[] ws;
- foreach (w; obj.table.column_widths) ws ~= w.to!string;
- output ~= ".table_widths: " ~ ws.join(" ");
- }
- if (obj.table.column_aligns.length > 0) {
- output ~= ".table_aligns: " ~ obj.table.column_aligns.join(" ");
- }
- if (obj.table.heading) {
- output ~= ".table_header: true";
- }
- // if (obj.table.walls) {
- // output ~= ".table_walls: true";
- // }
- }
- /+ ↓ code block properties +/
- if (obj.metainfo.is_a == "code") {
- // if (obj.code_block.syntax.length > 0) {
- // output ~= ".code_syntax: " ~ obj.code_block.syntax;
- // }
- if (obj.code_block.linenumbers) {
- output ~= ".code_linenumbers: true";
- }
- }
- /+ ↓ stow (extracted links) +/
- if (obj.stow.link.length > 0) {
- foreach (lnk; obj.stow.link) {
- if (lnk.length > 0) {
- output ~= ".stow_link: " ~ lnk;
- }
- }
- }
- /+ ↓ tag properties +/
- if (obj.tags.in_segment_html.length > 0) {
- output ~= ".segment: " ~ obj.tags.in_segment_html;
- }
- if (obj.tags.anchor_tag_html.length > 0
- && obj.tags.anchor_tag_html != obj.tags.in_segment_html) {
- output ~= ".anchor: " ~ obj.tags.anchor_tag_html;
- }
- if (obj.tags.segname_prev.length > 0) {
- output ~= ".segment_prev: " ~ obj.tags.segname_prev;
- }
- if (obj.tags.segname_next.length > 0) {
- output ~= ".segment_next: " ~ obj.tags.segname_next;
- }
- if (obj.tags.heading_lev_anchor_tag.length > 0) {
- output ~= ".heading_lev_anchor: " ~ obj.tags.heading_lev_anchor_tag;
- }
- if (obj.tags.segment_anchor_tag_epub.length > 0) {
- output ~= ".segment_epub: " ~ obj.tags.segment_anchor_tag_epub;
- }
- /+ ↓ the segment an object belongs to, as opposed to the segment a
- heading opens. .segment (in_segment_html) is set on headings
- only, so without these two a reader cannot tell which segment a
- paragraph is in; the sqlite search writer reads the html one
- +/
- if (obj.tags.html_segment_anchor_tag_is.length > 0) {
- output ~= ".segment_html_is: " ~ obj.tags.html_segment_anchor_tag_is;
- }
- if (obj.tags.epub_segment_anchor_tag_is.length > 0) {
- output ~= ".segment_epub_is: " ~ obj.tags.epub_segment_anchor_tag_is;
- }
- /+ ↓ heading ancestors text +/
- {
- bool has_hat = false;
- foreach (h; obj.tags.heading_ancestors_text) {
- if (h.length > 0) { has_hat = true; break; }
- }
- if (has_hat) {
- output ~= ".heading_ancestors_text: " ~ obj.tags.heading_ancestors_text.join("|");
- }
- }
- /+ ↓ lev4 subtoc +/
- if (obj.tags.lev4_subtoc.length > 0) {
- foreach (st; obj.tags.lev4_subtoc) {
- if (st.length > 0) {
- output ~= ".lev4_subtoc: " ~ st;
- }
- }
- }
- /+ ↓ anchor tags +/
- if (obj.tags.anchor_tags.length > 0) {
- foreach (at; obj.tags.anchor_tags) {
- if (at.length > 0) {
- output ~= ".anchor_tag: " ~ at;
- }
- }
- }
- /+ ↓ text content +/
- if (obj.text.length > 0) {
- foreach (line; obj.text.split("\n")) {
- output ~= "| " ~ line;
- }
- }
- output ~= "";
+ output ~= sspObjectRecord(obj, section, doc_matters.opt.action.vox_gt_3);
}
output ~= "}";
output ~= "";