diff options
Diffstat (limited to 'src/sisudoc/ocda/abstraction/ssp.d')
| -rw-r--r-- | src/sisudoc/ocda/abstraction/ssp.d | 611 |
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 ~= ""; |
