#+TITLE:       sdp document abstraction
#+AUTHOR:      Ralph Amissah
#+EMAIL:       [[mailto:ralph.amissah@gmail.com][ralph.amissah@gmail.com]]
#+DESCRIPTION: documents - structuring, publishing in multiple formats & search
#+KEYWORDS
#+LANGUAGE:    en
#+STARTUP:     indent content
#+OPTIONS:     H:3 num:nil toc:t \n:nil @:t ::t |:t ^:nil _:nil -:t f:t *:t <:t
#+OPTIONS:     TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc
#+OPTIONS:     author:nil email:nil creator:nil timestamp:nil
#+PROPERTY:    header-args :padline no :exports code :noweb yes
#+EXPORT_SELECT_TAGS:  export
#+EXPORT_EXCLUDE_TAGS: noexport
#+FILETAGS:            :sdp:rel:meta:
#+TAGS: assert(a) class(c) debug(d) mixin(m) sdp(s) tangle(T) template(t) WEB(W) noexport(n)

- [[./sdp.org][sdp]]  [[./][org/]]

* 1. Document Abstraction                :module:sdp:metadoc_from_src:
Process markup document, create document abstraction.

** 0. module template

#+BEGIN_SRC d :tangle ../src/sdp/meta/metadoc_from_src.d
/++
  document abstraction:
  abstraction of sisu markup for downstream processing
  metadoc_from_src.d
+/
module sdp.meta.metadoc_from_src;
template SiSUdocAbstraction() {
  /+ ↓ abstraction imports +/
  <<abs_top_imports>>
  /+ ↓ abstraction mixins +/
  <<abs_top_mixins>>
  /+ ↓ abstraction struct init +/
  <<abs_top_init_struct>>
  /+ ↓ abstract marked up document +/
  auto SiSUdocAbstraction(Src,CMM,Opt,Mfst)(
    Src                  markup_sourcefile_content,
    CMM                  conf_make_meta,
    Opt                  opt_action,
    Mfst                 manifest_matter,
  ) {
    static auto rgx = Rgx();
    debug(asserts) {
      static assert(is(typeof(markup_sourcefile_content) == char[][]));
    }
    /+ ↓ abstraction init +/
    <<abs_init_rest>>
    /+ abstraction init ↑ +/
    <<make_tests>>
    /+ ↓ ↻ loop markup document/text line by line +/
    srcDocLoop:
    foreach (line; markup_sourcefile_content) {
      // "line" variable can be empty but should never be null
      /+ ↓ markup document/text line by line +/
      <<abs_in_loop_body_00>>
      if (obj_type_status["code"] == TriState.on) {
        <<abs_in_loop_body_00_code_block>>
      } else if (!matchFirst(line, rgx.skip_from_regular_parse)) {
        /+ object other than "code block" object
           (includes regular text paragraph, headings & blocks other than code) +/
        /+ heading, glossary, blurb, poem, group, block, quote, table +/
        <<abs_in_loop_body_non_code_obj>>
        } else {
          /+ not within a block group +/
          <<abs_in_loop_body_open_block_obj_assert>>
          if (line.matchFirst(rgx.block_open)) {
            <<abs_in_loop_body_open_block_obj>>
          } else if (!line.empty) {
            /+ line not empty +/
            /+ non blocks (headings, paragraphs) & closed blocks +/
            <<abs_in_loop_body_not_block_obj>>
          } else if (obj_type_status["blocks"] == TriState.closing) {
            /+ line empty, with blocks flag +/
            <<abs_in_loop_body_not_block_obj_line_empty_blocks_flags>>
          } else {
            /+ line.empty, post contents, empty variables: +/
            <<abs_in_loop_body_not_block_obj_line_empty>>
          } // close else for line empty
        } // close else for not the above
      } // close after non code, other blocks or regular text
      <<abs_in_loop_body_01>>
    } /+ ← closed: loop markup document/text line by line +/
    /+ ↓ post loop markup document/text +/
    <<abs_post>>
    <<abs_return_tuple>>
    /+ post loop markup document/text ↑ +/
  } /+ ← closed: abstract doc source +/
  /+ ↓ abstraction functions +/
  <<abs_functions_object_reset>>
  <<abs_functions_header_set_common>>
  <<abs_functions_ocn_status>>
  <<abs_functions_substitutions>>
  <<abs_functions_block>>
  <<abs_functions_block_quote>>
  <<abs_functions_block_group>>
  <<abs_functions_block_block>>
  <<abs_functions_block_poem>>
  <<abs_functions_block_code>>
  <<abs_functions_block_table>>
  <<abs_functions_block_biblio>>
  <<abs_functions_block_line_status_empty>>
  <<abs_functions_book_index>>
  <<abs_functions_heading>>
  <<abs_functions_para>>
  <<abs_functions_table>>
  /+ abstraction functions ↑ +/
  /+ ↓ abstraction function emitters +/
  <<meta_emitters_ocn>>
  /+ +/
  <<meta_emitters_obj_inline_markup_munge>>
  <<meta_emitters_obj_inline_markup>>
  <<meta_emitters_obj_inline_markup_and_anchor_tags_and_misc>>
  <<meta_emitters_obj_inline_markup_table_of_contents>>
  <<meta_emitters_obj_inline_markup_private>>
  <<meta_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags>>
  <<meta_emitters_obj_inline_markup_close>>
  /+ +/
  <<meta_emitters_obj_attributes>>
  <<meta_emitters_obj_attributes_public>>
  <<meta_emitters_obj_attributes_private>>
  <<meta_emitters_obj_attributes_private_an_attribute>>
  <<meta_emitters_obj_attributes_private_json>>
  <<meta_emitters_obj_attributes_private_close>>
  /+ +/
  <<meta_emitters_book_index_nugget>>
  <<meta_emitters_book_index_report_indented>>
  <<meta_emitters_book_index_report_section>>
  /+ +/
  <<meta_emitters_endnotes>>
  /+ +/
  <<meta_emitters_bibliography>>
  /+ +/
  <<meta_emitters_metadata>>
  /+ abstraction functions emitters ↑ +/
  /+ ↓ abstraction functions assertions +/
  <<abs_functions_assertions>>
  /+ abstraction functions assertions ↑ +/
} /+ ← closed: template SiSUdocAbstraction +/
<<template_doc_sect_keys_seq>>
#+END_SRC

** 1. _pre loop processing_                                              :pre:
*** imports                                                       :imports:
[[./meta_defaults.org][meta_defaults]]

#+name: abs_top_imports
#+BEGIN_SRC d
import sdp.meta;
import
  std.algorithm,
  std.container,
  std.file,
  std.json,
  std.path;
import
  sdp.meta.defaults,
  sdp.meta.object_setter,
  sdp.meta.rgx;
#+END_SRC

*** mixins                                                         :mixins:

#+name: abs_top_mixins
#+BEGIN_SRC d
mixin ObjectSetter;
mixin InternalMarkup;
mixin SiSUrgxInit;
#+END_SRC

*** initialize                                                 :initialize:
**** initialize general

#+name: abs_top_init_struct
#+BEGIN_SRC d
/+ initialize +/
ObjGenericComposite[][string] the_table_of_contents_section;
ObjGenericComposite[] the_document_head_section, the_document_body_section, the_bibliography_section, the_glossary_section, the_blurb_section;
ObjGenericComposite[] the_dom_tail_section;
string[string] an_object, processing;
string an_object_key;
string[] anchor_tags;
string anchor_tag_;
string segment_anchor_tag_that_object_belongs_to;
string segment_anchor_tag_that_object_belongs_to_uri;
/+ enum +/
enum State { off, on }
enum TriState { off, on, closing }
enum DocStructMarkupHeading {
  h_sect_A,
  h_sect_B,
  h_sect_C,
  h_sect_D,
  h_text_1,
  h_text_2,
  h_text_3,
  h_text_4,
  h_text_5, // extra level, drop
  content_non_header
} // header section A-D; header text 1-4
enum OCNstatus { on, off, bkidx, closing, reset, }
enum OCNtype { ocn, non, bkidx, }
/+ biblio variables +/
string biblio_tag_name, biblio_tag_entry, st;
string[] biblio_arr_json;
string biblio_entry_str_json;
JSONValue[] bib_arr_json;
int bib_entry;
/+ counters +/
int cntr, previous_count, previous_length;
bool reset_note_numbers=true;
int[string] line_occur;
int html_segnames_ptr=0;
int html_segnames_ptr_cntr=0;
int verse_line, heading_ptr;
/+ paragraph attributes +/
int[string] indent;
bool bullet = true;
string content_non_header = "8";
static auto obj_im = ObjInlineMarkup();
static auto obj_att = ObjAttributes();
/+ ocn +/
struct OCNset {
  int digit;
  int on;
  int off;
  int bkidx;
  int type;
}
OCNset obj_cite_digits;
int obj_cite_digit_, obj_cite_digit_off, obj_cite_digit_bkidx, obj_cite_digit_type;
auto object_citation_number = OCNemitter();
int[] dom_markedup         = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
int[] dom_markedup_buffer  = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
int[] dom_collapsed        = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
int[] dom_collapsed_buffer = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
enum DomTags { none, open, close, close_and_open, open_still, }
#+END_SRC

**** initialize heading ancestors

#+name: abs_top_init_struct
#+BEGIN_SRC d
pure auto obj_heading_ancestors(O)(
  O          obj,
  string[]   lv_ancestors_txt,
) {
  switch (obj.node.heading_lev_markup) {
  case 0:
    lv_ancestors_txt[0] = obj.text.to!string;
    foreach(k; 1..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 1:
    lv_ancestors_txt[1] = obj.text.to!string;
    foreach(k; 2..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 2:
    lv_ancestors_txt[2] = obj.text.to!string;
    foreach(k; 3..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 3:
    lv_ancestors_txt[3] = obj.text.to!string;
    foreach(k; 4..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 4:
    lv_ancestors_txt[4] = obj.text.to!string;
    foreach(k; 5..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 5:
    lv_ancestors_txt[5] = obj.text.to!string;
    foreach(k; 6..8) {
      lv_ancestors_txt[k] = "";
    }
    goto default;
  case 6:
    lv_ancestors_txt[6] = obj.text.to!string;
    lv_ancestors_txt[7] = "";
    goto default;
  case 7:
    lv_ancestors_txt[7] = obj.text.to!string;
    goto default;
  default:
    obj.tags.heading_ancestors_text = lv_ancestors_txt.dup;
  }
  return obj;
}
#+END_SRC

**** initialize dom markup tags

#+name: abs_top_init_struct
#+BEGIN_SRC d
pure auto obj_dom_structure_set_markup_tags(O)(
  O           obj,
  int[]       dom,
  int         lev
) {
  foreach (i; 0 .. 8) {
    if (i < lev) {
      if (dom[i] == DomTags.open
         || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.open_still;
      } else if (dom[i] == DomTags.close) {
        dom[i] = DomTags.none;
      }
    } else if (i == lev) {
      if (lev  == 0
        && dom[i] == DomTags.open_still
      ) {
        dom[i] = DomTags.close;
      } else if (dom[i] == DomTags.open
        || dom[i] == DomTags.open_still
        || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.close_and_open;
      } else {
        dom[i] = DomTags.open;
      }
    } else if (i > lev) {
      if (dom[i] == DomTags.close) {
        dom[i] = DomTags.none;
      } else if (dom[i] == DomTags.open
        || dom[i] == DomTags.open_still
        || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.close;
      }
    }
  }
  debug(dom_magic_numbers) {
    writeln("marked up: ", lev, ": ", dom);
  }
  obj.node.dom_markedup = dom.dup;
  return obj;
}
#+END_SRC

**** initialize dom collapsed tags

#+name: abs_top_init_struct
#+BEGIN_SRC d
pure auto obj_dom_set_collapsed_tags(O)(
  O           obj,
  int[]       dom,
  int         lev
) {
  foreach (i; 0 .. 8) {
    if (i < lev) {
      if (dom[i] == DomTags.open
         || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.open_still;
      } else if (dom[i] == DomTags.close) {
        dom[i] = DomTags.none;
      }
    } else if (i == lev) {
      if (lev  == 0
        && dom[i] == DomTags.open_still
      ) {
        dom[i] = DomTags.close;
      } else if (dom[i] == DomTags.open
        || dom[i] == DomTags.open_still
        || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.close_and_open;
      } else {
        dom[i] = DomTags.open;
      }
    } else if (i > lev) {
      if (dom[i] == DomTags.close) {
        dom[i] = DomTags.none;
      } else if (dom[i] == DomTags.open
        || dom[i] == DomTags.open_still
        || dom[i] == DomTags.close_and_open
      ) {
        dom[i] = DomTags.close;
      }
    }
  }
  debug(dom_magic_numbers) {
    writeln("collapsed: ", lev, ": ", dom);
  }
  obj.node.dom_collapsed = dom.dup;
  return obj;
}
#+END_SRC

**** initialize ocn emit

#+name: abs_top_init_struct
#+BEGIN_SRC d
static auto ocn_emit(int ocn_status_flag) {
  return object_citation_number.ocn_emitter(ocn_status_flag);
}
/+ book index variables +/
string book_idx_tmp;
string[][string][string] bookindex_unordered_hashes;
/+ node +/
ObjGenericComposite comp_obj_heading, comp_obj_location, comp_obj_block, comp_obj_code, comp_obj_poem_ocn, comp_obj_comment;
auto node_construct = NodeStructureMetadata();
enum sObj { content, anchor_tags, notes_reg, notes_star, links, image_no_dimensions }
#+END_SRC

*** scope

#+name: abs_init_rest
#+BEGIN_SRC d
scope(success) {
}
scope(failure) {
}
scope(exit) {
  destroy(the_document_head_section);
  destroy(the_table_of_contents_section);
  destroy(the_document_body_section);
  destroy(the_bibliography_section);
  destroy(an_object);
  destroy(processing);
  destroy(biblio_arr_json);
  previous_length=0;
  reset_note_numbers=true;
}
#+END_SRC

*** init rest

#+name: abs_init_rest
#+BEGIN_SRC d
mixin SiSUrgxInitFlags;
mixin SiSUnode;
auto node_para_int_    = node_metadata_para_int;
auto node_para_str_    = node_metadata_para_str;
ObjGenericComposite comp_obj_heading_, comp_obj_para, comp_obj_toc;
line_occur = [
  "heading"  : 0,
  "para"     : 0,
  "glossary" : 0,
  "blurb"    : 0,
];
auto obj_type_status = flags_type_init;
string[string] object_number_poem = [
  "start" : "",
  "end"   : ""
];
string[] lv_ancestors_txt = [ "", "", "", "", "", "", "", "", ];
int[string] lv = [
  "lv" : State.off,
  "h0" : State.off,
  "h1" : State.off,
  "h2" : State.off,
  "h3" : State.off,
  "h4" : State.off,
  "h5" : State.off,
  "h6" : State.off,
  "h7" : State.off,
  "lev_int_collapsed" : 0,
];
int[string] collapsed_lev = [
  "h0" : State.off,
  "h1" : State.off,
  "h2" : State.off,
  "h3" : State.off,
  "h4" : State.off,
  "h5" : State.off,
  "h6" : State.off,
  "h7" : State.off
];
string[string] heading_match_str = [
  "h_A": "^(none)",
  "h_B": "^(none)",
  "h_C": "^(none)",
  "h_D": "^(none)",
  "h_1": "^(none)",
  "h_2": "^(none)",
  "h_3": "^(none)",
  "h_4": "^(none)"
];
auto heading_match_rgx = [
  "h_A": regex(r"^(none)"),
  "h_B": regex(r"^(none)"),
  "h_C": regex(r"^(none)"),
  "h_D": regex(r"^(none)"),
  "h_1": regex(r"^(none)"),
  "h_2": regex(r"^(none)"),
  "h_3": regex(r"^(none)"),
  "h_4": regex(r"^(none)")
];
string _anchor_tag;
string toc_txt_;
an_object["glossary_nugget"]                 = "";
an_object["blurb_nugget"]                    = "";
comp_obj_heading_                            = comp_obj_heading_.init;
comp_obj_heading_.typeinfo.is_of_part        = "frontmatter";
comp_obj_heading_.typeinfo.is_of_section     = "toc";
comp_obj_heading_.typeinfo.is_of_type        = "para";
comp_obj_heading_.typeinfo.is_a              = "heading";
comp_obj_heading_.text                       = "Table of Contents";
comp_obj_heading_.node.ocn                   = 0;
comp_obj_heading_.misc.object_number_off     = "";
comp_obj_heading_.misc.object_number_type    = 0;
comp_obj_heading_.tags.segment_anchor_tag    = "toc";
comp_obj_heading_.node.marked_up_level       = "1";
comp_obj_heading_.node.heading_lev_markup    = 4;
comp_obj_heading_.node.heading_lev_collapsed = 1;
comp_obj_heading_.node.parent_ocn            = 1;
comp_obj_heading_.node.parent_lev_markup     = 0;
comp_obj_heading_.ptr.html_segnames          = html_segnames_ptr;
comp_obj_heading_.tags.anchor_tags           = ["toc"];
comp_obj_heading_.node.dom_markedup          = [ 1, 1, 0, 0, 1, 0, 0, 0];
comp_obj_heading_.node.dom_collapsed         = [ 1, 1, 1, 0, 0, 0, 0, 0];
auto toc_head                                = comp_obj_heading_;
html_segnames_ptr_cntr++;
the_table_of_contents_section = [
  "seg": [toc_head],
  "scroll": [toc_head],
];
static auto mkup = InlineMarkup();
static auto munge = ObjInlineMarkupMunge();
auto note_section = NotesSection();
auto bookindex_extract_hash = BookIndexNuggetHash();
string[][string] lev4_subtoc;
string[] html_segnames=["toc"];
int cnt1 = 1; int cnt2 = 1; int cnt3 = 1;
#+END_SRC

*** make tests

#+name: make_tests
#+BEGIN_SRC d
enum Substitute { match, markup, }
debug (substitutions) {
  writeln(__LINE__, ":", __FILE__, ": DEBUG substitutions:");
  if (!(conf_make_meta.make.headings.empty)) {
    writeln(conf_make_meta.make.headings);
  }
  if (conf_make_meta.make.substitute) {
    foreach(substitution_pair; conf_make_meta.make.substitute) {
       writeln("regex to match:       ", substitution_pair[Substitute.match]);
       writeln("substitution to make: ", substitution_pair[Substitute.markup]);
    }
  }
  if (conf_make_meta.make.bold) {
    writeln("regex to match:       ", conf_make_meta.make.bold[Substitute.match]);
    writeln("substitution to make: ", conf_make_meta.make.bold[Substitute.markup]);
  }
  if (conf_make_meta.make.emphasis) {
    writeln("regex to match:       ", conf_make_meta.make.emphasis[Substitute.match]);
    writeln("substitution to make: ", conf_make_meta.make.emphasis[Substitute.markup]);
  }
  if (conf_make_meta.make.italics) {
    writeln("regex to match:       ", conf_make_meta.make.italics[Substitute.match]);
    writeln("substitution to make: ", conf_make_meta.make.italics[Substitute.markup]);
  }
}
#+END_SRC

** 2. ↻ *LOOP* _loop: process document body_ [+6]                         :loop:
*** Loop scope                                                      :scope:

#+name: abs_in_loop_body_00
#+BEGIN_SRC d
/+ scope +/
scope(exit) {
}
scope(failure) {
  stderr.writefln(
    "%s\n%s\n%s:%s failed here:\n  line: %s",
    __MODULE__, __FUNCTION__,
    __FILE__, __LINE__,
    line,
  );
}
line = (line).replaceAll(rgx.true_dollar, "$$$$");
  /+ dollar represented as $$ needed to stop submatching on $
     (substitutions using ${identifiers} must take into account (i.e. happen earlier))
   +/
debug(source) {                                  // source lines
  writeln(line);
}
debug(srclines) {
  if (!line.empty) {                             // source lines, not empty
    writefln(
      "* %s",
      line
    );
  }
}
#+END_SRC

*** check whether object_number is on or turned off                 :ocn:

#+name: abs_in_loop_body_00
#+BEGIN_SRC d
if (!line.empty) {
  obj_type_status = _check_ocn_status_(line, obj_type_status);
}
#+END_SRC

*** [#A] separate _code blocks_ from _other markup text_ [+5]
**** _code blocks_                                            :block:code:

#+name: abs_in_loop_body_00_code_block
#+BEGIN_SRC d
/+ block object: code +/
_code_block_(line, an_object, obj_type_status);
continue;
#+END_SRC

**** _non code objects_ (other blocks or regular text) [+4]     :non_code:
***** in section (biblio, glossary, blurb) +(block group)+ [+1] :block:active:
****** in section: biblio                                       :biblio:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
if (line.matchFirst(rgx.heading_biblio)
|| (obj_type_status["biblio_section"] == State.on
&& (!(line.matchFirst(rgx.heading_blurb_glossary)))
&& (!(line.matchFirst(rgx.heading)))
&& (!(line.matchFirst(rgx.comment))))) {
  /+ within section (block object): biblio +/
  obj_type_status["glossary_section"] = State.off;
  obj_type_status["biblio_section"]   = State.on;
  obj_type_status["blurb_section"]    = State.off;
  if (opt_action.backmatter && opt_action.section_biblio) {
    _biblio_block_(line, obj_type_status, bib_entry, biblio_entry_str_json, biblio_arr_json);
    debug(bibliobuild) {
      writeln("-  ", biblio_entry_str_json);
      writeln("-> ", biblio_arr_json.length);
    }
  }
  continue;
#+END_SRC

****** in section: glossary                                   :glossary:

if there is a glossary section you need to:
- extract it
- create standard headings
- markup contents in standard way like regular paragraphs
  - need indentation and regular paragraph inline markup
- reconstitute the document with the glossary section following the endnotes

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (line.matchFirst(rgx.heading_glossary)
|| (obj_type_status["glossary_section"] == State.on
&& (!(line.matchFirst(rgx.heading_biblio_blurb)))
&& (!(line.matchFirst(rgx.heading)))
&& (!(line.matchFirst(rgx.comment))))) {
  /+ within section (block object): glossary +/
  debug(glossary) {
    writeln(__LINE__);
    writeln(line);
  }
  obj_type_status["glossary_section"] = State.on;
  obj_type_status["biblio_section"]   = State.off;
  obj_type_status["blurb_section"]    = State.off;
  if (opt_action.backmatter && opt_action.section_glossary) {
    indent=[
      "hang_position" : 0,
      "base_position" : 0,
    ];
    bullet = false;
    obj_type_status["para"] = State.on;
    line_occur["para"] = State.off;
    an_object_key="glossary_nugget"; //
    if (line.matchFirst(rgx.heading_glossary)) {
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "glossary";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Glossary";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "_part_glossary";
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading_.node.dom_markedup          = [ 1, 1, 0, 0, 0, 0, 0, 0];
      comp_obj_heading_.node.dom_collapsed         = [ 1, 1, 0, 0, 0, 0, 0, 0];
      the_glossary_section                         ~= comp_obj_heading_;
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "glossary";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Glossary";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "glossary";
      comp_obj_heading_.node.marked_up_level       = "1";
      comp_obj_heading_.node.heading_lev_markup    = 4;
      comp_obj_heading_.node.heading_lev_collapsed = 2;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading_.node.dom_markedup          = [ 1, 1, 0, 0, 1, 0, 0, 0];
      comp_obj_heading_.node.dom_collapsed         = [ 1, 1, 1, 0, 0, 0, 0, 0];
      comp_obj_heading_.tags.anchor_tags           = ["glossary"];
      the_glossary_section                         ~= comp_obj_heading_;
    } else {
      _para_match_(line, an_object, an_object_key, indent, bullet, obj_type_status, line_occur);
      comp_obj_para                           = comp_obj_para.init;
      comp_obj_para.typeinfo.is_of_part       = "backmatter";
      comp_obj_para.typeinfo.is_of_section    = "glossary";
      comp_obj_para.typeinfo.is_of_type       = "para";
      comp_obj_para.typeinfo.is_a             = "glossary";
      comp_obj_para.text                      = line.to!string.strip;
      comp_obj_para.node.ocn                  = 0;
      comp_obj_para.misc.object_number_off    = "";
      comp_obj_para.misc.object_number_type   = 0;
      comp_obj_para.attrib.indent_hang        = indent["hang_position"];
      comp_obj_para.attrib.indent_base        = indent["base_position"];
      comp_obj_para.attrib.bullet             = bullet;
      the_glossary_section                    ~= comp_obj_para;
    }
    obj_type_status["ocn_status"] = OCNstatus.on;
  }
  continue;
#+END_SRC

****** in section: blurb                                         :blurb:

if there is a blurb section you need to:
- extract it
- create standard headings (or use line provided in 1~ heading)
- markup contents in standard way like regular paragraphs
  - need regular paragraph inline markup
- reconstitute the document with the blurb section at the very end of the doucment

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (line.matchFirst(rgx.heading_blurb)
|| (obj_type_status["blurb_section"] == State.on
&& (!(line.matchFirst(rgx.heading_biblio_glossary)))
&& (!(line.matchFirst(rgx.heading)))
&& (!(line.matchFirst(rgx.comment))))) {
  /+ within section (block object): blurb +/
  debug(blurb) {
    writeln(__LINE__);
    writeln(line);
  }
  obj_type_status["glossary_section"] = State.off;
  obj_type_status["biblio_section"]   = State.off;
  obj_type_status["blurb_section"]    = State.on;
  if (opt_action.backmatter && opt_action.section_blurb) {
    indent=[
      "hang_position" : 0,
      "base_position" : 0,
    ];
    bullet = false;
    if (auto m = line.matchFirst(rgx.para_indent)) {
      debug(paraindent) {                    // para indent
        writeln(line);
      }
      indent["hang_position"] = (m.captures[1]).to!int;
      indent["base_position"] = 0;
    } else if (line.matchFirst(rgx.para_bullet)) {
      debug(parabullet) {                    // para bullet
        writeln(line);
      }
      bullet = true;
    } else if (auto m = line.matchFirst(rgx.para_indent_hang)) {
      debug(paraindenthang) {                // para indent hang
        writeln(line);
      }
      indent=[
        "hang_position" : (m.captures[1]).to!int,
        "base_position" : (m.captures[2]).to!int,
      ];
    } else if (auto m = line.matchFirst(rgx.para_bullet_indent)) {
      debug(parabulletindent) {              // para bullet indent
        writeln(line);
      }
      indent=[
        "hang_position" : (m.captures[1]).to!int,
        "base_position" : 0,
      ];
      bullet = true;
    }
    obj_type_status["para"] = State.on;
    line_occur["para"] = State.off;
    an_object_key="blurb_nugget";
    if (line.matchFirst(rgx.heading_blurb)) {
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "blurb";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Blurb";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "_part_blurb";
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading_.node.dom_markedup          = [ 1, 1, 0, 0, 0, 0, 0, 0];
      comp_obj_heading_.node.dom_collapsed         = [ 1, 1, 0, 0, 0, 0, 0, 0];
      the_blurb_section                            ~= comp_obj_heading_;
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "blurb";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Blurb";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "blurb";
      comp_obj_heading_.node.marked_up_level       = "1";
      comp_obj_heading_.node.heading_lev_markup    = 4;
      comp_obj_heading_.node.heading_lev_collapsed = 2;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading_.tags.anchor_tags           = ["blurb"];
      comp_obj_heading_.node.dom_markedup          = [ 1, 1, 0, 0, 1, 0, 0, 0];
      comp_obj_heading_.node.dom_collapsed         = [ 1, 1, 1, 0, 0, 0, 0, 0];
      the_blurb_section                            ~= comp_obj_heading_;
    } else if (line.matchFirst(rgx.heading)
    && (opt_action.backmatter && opt_action.section_blurb)) {
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "blurb";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = line.to!string;
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "blurb";
      comp_obj_heading_.node.marked_up_level       = an_object["lev"].to!string;
      comp_obj_heading_.node.heading_lev_markup    = an_object["lev_markup_number"].to!int;    // make int, remove need to conv
      comp_obj_heading_.node.heading_lev_collapsed = an_object["lev_collapsed_number"].to!int; // make int, remove need to conv
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      the_blurb_section                            ~= comp_obj_heading_;
    } else {
      _para_match_(line, an_object, an_object_key, indent, bullet, obj_type_status, line_occur);
      comp_obj_para                           = comp_obj_para.init;
      comp_obj_para.typeinfo.is_of_part       = "backmatter";
      comp_obj_para.typeinfo.is_of_section    = "blurb";
      comp_obj_para.typeinfo.is_of_type       = "para";
      comp_obj_para.typeinfo.is_a             = "blurb";
      comp_obj_para.text                      = munge.url_links(line.to!string.strip).replaceFirst(rgx.para_attribs, "");
      comp_obj_para.node.ocn                  = 0;
      comp_obj_para.misc.object_number_off    = "";
      comp_obj_para.misc.object_number_type   = 0;
      comp_obj_para.attrib.indent_hang        = indent["hang_position"];
      comp_obj_para.attrib.indent_base        = indent["base_position"];
      comp_obj_para.has.inline_links          = true;
      comp_obj_para.attrib.bullet             = bullet;
      the_blurb_section                       ~= comp_obj_para;
    }
    obj_type_status["ocn_status"] = OCNstatus.on;
  }
  continue;
#+END_SRC

***** in blocks [+1]                                       :block:active:
****** in block: quote                                           :quote:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (obj_type_status["quote"] == TriState.on) {
  /+ within block object: quote +/
  line = _doc_header_and_make_substitutions_(line, conf_make_meta);
  line = _doc_header_and_make_substitutions_fontface_(line, conf_make_meta);
  _quote_block_(line, an_object, obj_type_status);
  continue;
#+END_SRC

****** in block: group                                           :group:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
/+ within block object: group +/
} else if (obj_type_status["group"] == TriState.on) {
  /+ within block object: group +/
  line = _doc_header_and_make_substitutions_(line, conf_make_meta);
  line = _doc_header_and_make_substitutions_fontface_(line, conf_make_meta);
  line = (line)
    .replaceAll(rgx.para_delimiter, mkup.br_paragraph ~ "$1");
  _group_block_(line, an_object, obj_type_status);
  continue;
#+END_SRC

****** in block: block                                           :block:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (obj_type_status["block"] == TriState.on) {
  /+ within block object: block +/
  line = _doc_header_and_make_substitutions_(line, conf_make_meta);
  line = _doc_header_and_make_substitutions_fontface_(line, conf_make_meta);
  if (auto m = line.match(rgx.spaces_line_start)) {
    line = (line)
      .replaceAll(rgx.spaces_line_start, (m.captures[1]).translate([ ' ' : mkup.nbsp ]));
  }
  if (auto m = line.match(rgx.spaces_multiple)) {
    line = (line)
      .replaceAll(rgx.spaces_multiple, (m.captures[1]).translate([ ' ' : mkup.nbsp ]));
  }
  _block_block_(line, an_object, obj_type_status);
  continue;
#+END_SRC

****** in block: poem                                             :poem:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (obj_type_status["poem"] == TriState.on) {
  /+ within block object: poem +/
  _poem_block_(line, an_object, obj_type_status, cntr, object_number_poem, conf_make_meta);
  continue;
#+END_SRC

****** in block: table                                           :table:

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else if (obj_type_status["table"] == TriState.on) {
  /+ within block object: table +/
  _table_block_(line, an_object, obj_type_status, conf_make_meta);
  continue;
#+END_SRC

***** not identified as being within block group (could still be, or not) [+3]
****** assert

#+name: abs_in_loop_body_open_block_obj_assert
#+BEGIN_SRC d
assert(
  (obj_type_status["blocks"] == TriState.off)
  || (obj_type_status["blocks"] == TriState.closing),
  "block status: none or closed"
);
assertions_flag_types_block_status_none_or_closed(obj_type_status);
#+END_SRC

****** catch misc +block open+

#+name: abs_in_loop_body_open_block_obj
#+BEGIN_SRC d
if (line.matchFirst(rgx.block_poem_open)) {
  /+ poem to verse exceptions! +/
  object_reset(an_object);
  processing.remove("verse");
  object_number_poem["start"] = obj_cite_digits.on.to!string;
}
_start_block_(line, obj_type_status, object_number_poem);
continue;
#+END_SRC

****** line not empty [+2]
******* asserts                                                :assert:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
assert(
  !line.empty,
  "line tested, line not empty surely:\n  \"" ~ line ~ "\""
);
assert(
  (obj_type_status["blocks"] == TriState.off)
  || (obj_type_status["blocks"] == TriState.closing),
  "code block status: none or closed"
);
if (obj_type_status["blocks"] == TriState.closing) {
  debug(check) {                           // block
    writeln(__LINE__);
    writeln(line);
  }
  assert(
    line.matchFirst(rgx.book_index)
    || line.matchFirst(rgx.book_index_open)
    || obj_type_status["book_index"] == State.on,
    "\nblocks closed, unless followed by book index, non-matching line:\n  \""
    ~ line ~ "\""
  );
}
#+END_SRC

******* book index                                          :bookindex:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
if (line.matchFirst(rgx.book_index)
|| line.matchFirst(rgx.book_index_open)
|| obj_type_status["book_index"] == State.on )  {
  /+ book_index +/
  _book_index_(line, book_idx_tmp, an_object, obj_type_status, opt_action);
#+END_SRC

******* not book index [+1]

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
} else {
  /+ not book_index +/
#+END_SRC

******** matched: comment                              :comment:match:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
  an_object_key="body_nugget";
  if (auto m = matchFirst(line, rgx.comment)) {
    /+ matched comment +/
    debug(comment) {
      writeln(line);
    }
    an_object[an_object_key]                ~= line ~= "\n";
    comp_obj_comment                        = comp_obj_comment.init;
    comp_obj_comment.typeinfo.is_of_part    = "comment"; // breaks flow
    comp_obj_comment.typeinfo.is_of_section = "comment"; // breaks flow
    comp_obj_comment.typeinfo.is_of_type    = "comment";
    comp_obj_comment.typeinfo.is_a          = "comment";
    comp_obj_comment.text                   = an_object[an_object_key].strip;
    the_document_body_section               ~= comp_obj_comment;
    _common_reset_(line_occur, an_object, obj_type_status);
    processing.remove("verse");
    ++cntr;
#+END_SRC

******** flag !set & line !exist: heading or para  :heading:paragraph:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
  } else if (((line_occur["para"] == State.off)
  && (line_occur["heading"] == State.off))
  && ((obj_type_status["para"] == State.off)
  && (obj_type_status["heading"] == State.off))) {
    /+ heading or para but neither flag nor line exists +/
    if ((conf_make_meta.make.headings.length > 2)
    && (obj_type_status["make_headings"] == State.off)) {
      /+ heading found +/
      _heading_found_(line, conf_make_meta.make.headings, heading_match_str, heading_match_rgx, obj_type_status);
    }
    if ((obj_type_status["make_headings"] == State.on)
    && ((line_occur["para"] == State.off)
    && (line_occur["heading"] == State.off))
    && ((obj_type_status["para"] == State.off)
    && (obj_type_status["heading"] == State.off))) {
      /+ heading make set +/
      line = _heading_make_set_(line, line_occur, heading_match_rgx, obj_type_status);
    }
    /+ TODO node info: all headings identified at this point,
       - extract node info here??
       - how long can it wait?
       - should be incorporated in composite objects
       - should happen before endnote links set (they need to be moved down?)
    +/
    if (line.matchFirst(rgx.heading)) {
      /+ heading match +/
      line = _doc_header_and_make_substitutions_(line, conf_make_meta);
      _heading_matched_(line, line_occur, an_object, an_object_key, lv, collapsed_lev, obj_type_status, conf_make_meta);
    } else if (line_occur["para"] == State.off) {
      /+ para match +/
      an_object_key="body_nugget";
      line = _doc_header_and_make_substitutions_(line, conf_make_meta);
      line = _doc_header_and_make_substitutions_fontface_(line, conf_make_meta);
      _para_match_(line, an_object, an_object_key, indent, bullet, obj_type_status, line_occur);
    }
#+END_SRC

******** line exist: heading                                 :heading:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
  } else if (line_occur["heading"] > State.off) {
    /+ heading +/
    debug(heading) {
      writeln(line);
    }
    an_object[an_object_key] ~= line ~= "\n";
    ++line_occur["heading"];
#+END_SRC

******** line exist: para                                       :para:

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
  } else if (line_occur["para"] > State.off) {
    /+ paragraph +/
    debug(para) {
      writeln(an_object_key, "-> ", line);
    }
    line = _doc_header_and_make_substitutions_(line, conf_make_meta);
    line = _doc_header_and_make_substitutions_fontface_(line, conf_make_meta);
    an_object[an_object_key] ~= " " ~ line;
    ++line_occur["para"];
  }
}
#+END_SRC

****** line empty, with block flag

#+name: abs_in_loop_body_not_block_obj_line_empty_blocks_flags
#+BEGIN_SRC d
_block_flag_line_empty_(
  bookindex_extract_hash,
  line,
  an_object,
  the_document_body_section,
  bookindex_unordered_hashes,
  obj_cite_digits,
  comp_obj_heading,
  cntr,
  obj_type_status,
  object_number_poem,
  conf_make_meta,
);
#+END_SRC

****** line empty [+1]
******* assert line empty                                      :assert:

#+name: abs_in_loop_body_not_block_obj_line_empty
#+BEGIN_SRC d
assert(
  line.empty,
  "\nline should be empty:\n  \""
  ~ line ~ "\""
);
assert(
  (obj_type_status["blocks"] == State.off),
  "code block status: none"
);
#+END_SRC

******* heading object                                 :heading:object:

#+name: abs_in_loop_body_not_block_obj_line_empty
#+BEGIN_SRC d
if ((obj_type_status["heading"] == State.on)
&& (line_occur["heading"] > State.off)) {
  /+ heading object (current line empty) +/
  obj_cite_digits = (an_object["lev_markup_number"].to!int == 0)
  ? ocn_emit(OCNstatus.reset)
  : ocn_emit(OCNstatus.on); // : ocn_emit(obj_type_status["ocn_status"]);
  an_object["is"] = "heading";
  an_object_key="body_nugget";
  auto substantive_object_and_anchor_tags_tuple
    = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
  an_object["substantive"] = substantive_object_and_anchor_tags_tuple[sObj.content];
  anchor_tags = substantive_object_and_anchor_tags_tuple[sObj.anchor_tags];
  if (an_object["lev_markup_number"].to!int == 4) {
    segment_anchor_tag_that_object_belongs_to = anchor_tags[0];
    segment_anchor_tag_that_object_belongs_to_uri = anchor_tags[0] ~ ".fnSuffix";
    anchor_tag_ = anchor_tags[0];
  } else if (an_object["lev_markup_number"].to!int > 4) {
    segment_anchor_tag_that_object_belongs_to = anchor_tag_;
    segment_anchor_tag_that_object_belongs_to_uri = anchor_tag_ ~ ".fnSuffix#" ~ obj_cite_digits.on.to!string;
  } else if (an_object["lev_markup_number"].to!int < 4) {
  string segn;
    switch (an_object["lev_markup_number"].to!int) {
    case 0:
      segn = "_the_title";
      goto default;
    case 1:
      segn = "_part_" ~ cnt1.to!string;
      ++cnt1;
      goto default;
    case 2:
      segn = "_part_" ~  cnt1.to!string ~ "_" ~ cnt2.to!string;
      ++cnt2;
      goto default;
    case 3:
      segn =  "_part_" ~  cnt1.to!string ~ "_" ~ cnt2.to!string ~ "_" ~ cnt3.to!string;
      ++cnt3;
      goto default;
    default:
      segment_anchor_tag_that_object_belongs_to = segn;
      segment_anchor_tag_that_object_belongs_to_uri = segn ~ ".fnSuffix";
      break;
    }
  }
  an_object["bookindex_nugget"]
    = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
  bookindex_unordered_hashes
    = bookindex_extract_hash.bookindex_nugget_hash(an_object["bookindex_nugget"], obj_cite_digits, segment_anchor_tag_that_object_belongs_to);
  /+ (incrementally build toc) table of contents here! +/
  _anchor_tag=to!string(obj_cite_digits.on);
  the_table_of_contents_section = obj_im.table_of_contents_gather_headings(
    an_object,
    conf_make_meta,
    segment_anchor_tag_that_object_belongs_to,
    _anchor_tag,
    lev4_subtoc,
    the_table_of_contents_section,
  );
  if (an_object["lev_markup_number"] == "4") {
    html_segnames ~= segment_anchor_tag_that_object_belongs_to;
    html_segnames_ptr = html_segnames_ptr_cntr;
    html_segnames_ptr_cntr++;
  }
  auto comp_obj_heading
    = node_construct.node_emitter_heading(
      an_object["substantive"],                     // string
      an_object["lev"],                             // string
      an_object["lev_markup_number"],               // string
      an_object["lev_collapsed_number"],            // string
      segment_anchor_tag_that_object_belongs_to,    // string
      obj_cite_digits,                              // OCNset
      cntr,                                         // int
      heading_ptr,                                  // int
      lv_ancestors_txt,                             // string[]
      an_object["is"],                              // string
      html_segnames_ptr,                            // int
      substantive_object_and_anchor_tags_tuple[sObj.notes_reg],
      substantive_object_and_anchor_tags_tuple[sObj.notes_star],
      substantive_object_and_anchor_tags_tuple[sObj.links],
    );
  ++heading_ptr;
  debug(segments) {
    writeln(an_object["lev_markup_number"]);
    writeln(segment_anchor_tag_that_object_belongs_to);
  }
  the_document_body_section ~= comp_obj_heading;
  debug(objectrelated1) { // check
    writeln(line);
  }
  _common_reset_(line_occur, an_object, obj_type_status);
  an_object.remove("lev");
  an_object.remove("lev_markup_number");
  processing.remove("verse");
  ++cntr;
#+END_SRC

******* paragraph object                             :paragraph:object:

#+name: abs_in_loop_body_not_block_obj_line_empty
#+BEGIN_SRC d
} else if ((obj_type_status["para"] == State.on)
&& (line_occur["para"] > State.off)) {
  /+ paragraph object (current line empty) +/
  obj_cite_digits = ocn_emit(obj_type_status["ocn_status"]);
  an_object["bookindex_nugget"]
    = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
  bookindex_unordered_hashes
    = bookindex_extract_hash.bookindex_nugget_hash(an_object["bookindex_nugget"], obj_cite_digits, segment_anchor_tag_that_object_belongs_to);
  an_object["is"] = "para";
  auto comp_obj_heading
    = node_construct.node_location_emitter(
      content_non_header,
      segment_anchor_tag_that_object_belongs_to,
      obj_cite_digits,
      cntr,
      heading_ptr-1,
      an_object["is"],
    );
  auto substantive_obj_misc_tuple
    = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
  an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
  anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
  comp_obj_para                                             = comp_obj_para.init;
  comp_obj_para.typeinfo.is_of_part                         = "body";
  comp_obj_para.typeinfo.is_of_section                      = "body";
  comp_obj_para.typeinfo.is_of_type                         = "para";
  comp_obj_para.typeinfo.is_a                               = "para";
  comp_obj_para.text                                        = an_object["substantive"].to!string.strip;
  comp_obj_para.node.ocn                                    = obj_cite_digits.digit;
  comp_obj_para.misc.object_number_off                      = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
  comp_obj_para.misc.o_n_book_index                         = obj_cite_digits.bkidx;
  comp_obj_para.misc.object_number_type                     = obj_cite_digits.type;
  comp_obj_para.attrib.indent_hang                          = indent["hang_position"];
  comp_obj_para.attrib.indent_base                          = indent["base_position"];
  comp_obj_para.attrib.bullet                               = bullet;
  comp_obj_para.tags.anchor_tags                            = anchor_tags;
  comp_obj_para.has.inline_notes_reg                        = substantive_obj_misc_tuple[sObj.notes_reg];
  comp_obj_para.has.inline_notes_star                       = substantive_obj_misc_tuple[sObj.notes_star];
  comp_obj_para.has.inline_links                            = substantive_obj_misc_tuple[sObj.links];
  comp_obj_para.has.contains_image_without_dimensions       = substantive_obj_misc_tuple[sObj.image_no_dimensions];
  the_document_body_section                                 ~= comp_obj_para;
  _common_reset_(line_occur, an_object, obj_type_status);
  indent=[
    "hang_position" : 0,
    "base_position" : 0,
  ];
  bullet = false;
  processing.remove("verse");
  ++cntr;
} else {
  // could be useful to test line variable should be empty and never null
}
#+END_SRC

*** regular text objects                                   :text:paragraph:

#+name: abs_in_loop_body_01
#+BEGIN_SRC d
/+ unless (the_document_body_section.length == 0) ? +/
if (the_document_body_section.length > 0) {
  if (((the_document_body_section[$-1].typeinfo.is_a == "para")
    || (the_document_body_section[$-1].typeinfo.is_a == "heading")
    || (the_document_body_section[$-1].typeinfo.is_a == "quote")
    || (the_document_body_section[$-1].typeinfo.is_a == "group")
    || (the_document_body_section[$-1].typeinfo.is_a == "block")
    || (the_document_body_section[$-1].typeinfo.is_a == "verse"))
  && (the_document_body_section.length > previous_length)) {
    if ((the_document_body_section[$-1].typeinfo.is_a == "heading")
    && (the_document_body_section[$-1].node.heading_lev_markup < 5)) {
      obj_type_status["biblio_section"]   = State.off;
      obj_type_status["glossary_section"] = State.off;
      obj_type_status["blurb_section"]    = State.off;
    }
    if (the_document_body_section[$-1].typeinfo.is_a == "verse") {
      /+ scan for endnotes for whole poem (each verse in poem) +/
      foreach (i; previous_length .. the_document_body_section.length) {
        if (the_document_body_section[i].typeinfo.is_a == "verse") {
          if ((the_document_body_section[i].text).match(
            rgx.inline_notes_delimiter_al_regular_number_note
          )) {
            note_section.gather_notes_for_endnote_section(
              the_document_body_section,
              segment_anchor_tag_that_object_belongs_to,
              (i).to!int,
            );
          }
        }
      }
    } else {
      /+ scan object for endnotes +/
      previous_length = the_document_body_section.length.to!int;
      if ((the_document_body_section[$-1].text).match(
        rgx.inline_notes_delimiter_al_regular_number_note
      )) {
        previous_count=(the_document_body_section.length -1).to!int;
        note_section.gather_notes_for_endnote_section(
          the_document_body_section,
          segment_anchor_tag_that_object_belongs_to,
          (the_document_body_section.length-1).to!int,
        );
      }
    }
    previous_length = the_document_body_section.length.to!int;
  }
}
#+END_SRC

** 3. _post main-loop processing_                                       :post:
*** misc

/+
  Backmatter:
  - endnotes
  - glossary
  - bibliography / references
  - book index
  - blurb
+/

*** tie up preparation of document sections
**** endnotes section (scroll & seg)                            :endnotes:

#+name: abs_post
#+BEGIN_SRC d
auto en_tuple
  = note_section.endnote_objects(obj_cite_digits, opt_action);
static assert(!isTypeTuple!(en_tuple));
auto the_endnotes_section = en_tuple[0];
obj_cite_digits = en_tuple[1];
debug(endnotes) {
  writefln(
    "%s %s",
    __LINE__,
    the_endnotes_section.length
  );
  foreach (o; the_endnotes_section) {
    writeln(o);
  }
}
#+END_SRC

**** no glossary section?                                       :glossary:

#+name: abs_post
#+BEGIN_SRC d
if (an_object["glossary_nugget"].length == 0) {
  comp_obj_heading_                            = comp_obj_heading_.init;
  comp_obj_heading_.typeinfo.is_of_part        = "empty";
  comp_obj_heading_.typeinfo.is_of_section     = "empty";
  comp_obj_heading_.typeinfo.is_of_type        = "para";
  comp_obj_heading_.typeinfo.is_a              = "heading";
  comp_obj_heading_.text                       = "(skip) there is no Glossary section";
  comp_obj_heading_.node.ocn                   = 0;
  comp_obj_heading_.misc.object_number_off     = "";
  comp_obj_heading_.misc.object_number_type    = 0;
  comp_obj_heading_.node.marked_up_level       = "B";
  comp_obj_heading_.node.heading_lev_markup    = 1;
  comp_obj_heading_.node.heading_lev_collapsed = 1;
  comp_obj_heading_.node.parent_ocn            = 1;
  comp_obj_heading_.node.parent_lev_markup     = 0;
  the_glossary_section                         ~= comp_obj_heading_;
} else {
  writeln("gloss");
}
debug(glossary) {
  foreach (gloss; the_glossary_section) {
    writeln(gloss.text);
  }
}
#+END_SRC

**** bibliography section (objects)                         :bibliography:

#+name: abs_post
#+BEGIN_SRC d
auto biblio_unsorted_incomplete = biblio_arr_json.dup;
auto biblio = Bibliography();
auto biblio_ordered
  = biblio._bibliography_(biblio_unsorted_incomplete, bib_arr_json);
#+END_SRC

#+name: abs_post
#+BEGIN_SRC d
if (biblio_ordered.length > 0) {
  comp_obj_heading_                            = comp_obj_heading_.init;
  comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
  comp_obj_heading_.typeinfo.is_of_section     = "bibliography";
  comp_obj_heading_.typeinfo.is_of_type        = "para";
  comp_obj_heading_.typeinfo.is_a              = "heading";
  comp_obj_heading_.text                       = "Bibliography";
  comp_obj_heading_.node.ocn                   = 0;
  comp_obj_heading_.misc.object_number_off     = "";
  comp_obj_heading_.misc.object_number_type    = 0;
  comp_obj_heading_.tags.segment_anchor_tag    = "_part_bibliography";
  comp_obj_heading_.node.marked_up_level       = "B";
  comp_obj_heading_.node.heading_lev_markup    = 1;
  comp_obj_heading_.node.heading_lev_collapsed = 1;
  comp_obj_heading_.node.parent_ocn            = 1;
  comp_obj_heading_.node.parent_lev_markup     = 0;
  the_bibliography_section                     ~= comp_obj_heading_;
  comp_obj_heading_                            = comp_obj_heading_.init;
  comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
  comp_obj_heading_.typeinfo.is_of_section     = "bibliography";
  comp_obj_heading_.typeinfo.is_of_type        = "para";
  comp_obj_heading_.typeinfo.is_a              = "heading";
  comp_obj_heading_.text                       = "Bibliography";
  comp_obj_heading_.node.ocn                   = 0;
  comp_obj_heading_.misc.object_number_off     = "";
  comp_obj_heading_.misc.object_number_type    = 0;
  comp_obj_heading_.tags.segment_anchor_tag    = "bibliography";
  comp_obj_heading_.node.marked_up_level       = "1";
  comp_obj_heading_.node.heading_lev_markup    = 4;
  comp_obj_heading_.node.heading_lev_collapsed = 2;
  comp_obj_heading_.node.parent_ocn            = 1;
  comp_obj_heading_.node.parent_lev_markup     = 0;
  comp_obj_heading_.tags.anchor_tags           = ["bibliography"];
  the_bibliography_section                     ~= comp_obj_heading_;
} else {
  comp_obj_heading_                            = comp_obj_heading_.init;
  comp_obj_heading_.typeinfo.is_of_part        = "empty";
  comp_obj_heading_.typeinfo.is_of_section     = "empty";
  comp_obj_heading_.typeinfo.is_of_type        = "para";
  comp_obj_heading_.typeinfo.is_a              = "heading";
  comp_obj_heading_.text                       = "(skip) there is no Bibliography";
  comp_obj_heading_.node.ocn                   = 0;
  comp_obj_heading_.misc.object_number_off     = "";
  comp_obj_heading_.misc.object_number_type    = 0;
  comp_obj_heading_.node.marked_up_level       = "B";
  comp_obj_heading_.node.heading_lev_markup    = 1;
  comp_obj_heading_.node.heading_lev_collapsed = 1;
  comp_obj_heading_.node.parent_ocn            = 1;
  comp_obj_heading_.node.parent_lev_markup     = 0;
  the_bibliography_section                     ~= comp_obj_heading_;
}
#+END_SRC

***** format biblio string

#+name: abs_post
#+BEGIN_SRC d
string out_;
foreach (entry; biblio_ordered) {
  out_ = format(
    "%s \"%s\"%s%s%s%s%s%s%s%s%s.",
    ((entry["author"].str.empty) ? entry["editor"].str : entry["author"].str),
    entry["fulltitle"].str,
    ((entry["journal"].str.empty) ? "" : ", /{" ~ entry["journal"].str ~ "}/"),
    ((entry["volume"].str.empty) ? "" : ", " ~ entry["volume"].str),
    ((entry["in"].str.empty) ? "" : ", " ~ entry["in"].str),
    ((!(entry["author"].str.empty) && (!(entry["editor"].str.empty))) ? entry["editor"].str : ""),
    ", " ~ entry["year"].str,
    ((entry["pages"].str.empty) ? "" : ", " ~ entry["pages"].str),
    ((entry["publisher"].str.empty) ? "" : ", " ~ entry["publisher"].str),
    ((entry["place"].str.empty) ? "" : ", " ~ entry["place"].str),
    ((entry["url"].str.empty) ? "" : ", [" ~ entry["url"].str ~ "]"),
  );
  comp_obj_para                           = comp_obj_para.init;
  comp_obj_para.typeinfo.is_of_part       = "backmatter";
  comp_obj_para.typeinfo.is_of_section    = "bibliography";
  comp_obj_para.typeinfo.is_of_type       = "para";
  comp_obj_para.typeinfo.is_a             = "bibliography";
  comp_obj_para.text                      = out_.to!string.strip;
  comp_obj_para.node.ocn                  = 0;
  comp_obj_para.misc.object_number_off    = "";
  comp_obj_para.misc.object_number_type   = 0;
  comp_obj_para.attrib.indent_hang        = 0;
  comp_obj_para.attrib.indent_base        = 1;
  comp_obj_para.attrib.bullet             = bullet;
  comp_obj_para.tags.anchor_tags          = anchor_tags;
  the_bibliography_section                ~= comp_obj_para;
}
#+END_SRC

#+name: abs_post
#+BEGIN_SRC d
debug(bibliosection) {
  foreach (o; the_bibliography_section) {
    writeln(o.text);
  }
}
#+END_SRC

***** bibliography components

auto biblio_entry_tags_jsonstr =  `{
  "is"                   : "",
  "sortby_deemed_author_year_title"  : "",
  "deemed_author"                    : "",
  "author_raw"                       : "",
  "author"                           : "",
  "author_arr"                       : [ "" ],
  "editor_raw"                       : "",
  "editor"                           : "",
  "editor_arr"                       : [ "" ],
  "title"                            : "",
  "subtitle"                         : "",
  "fulltitle"                        : "",
  "language"                         : "",
  "trans"                            : "",
  "src"                              : "",
  "journal"                          : "",
  "in"                               : "",
  "volume"                           : "",
  "edition"                          : "",
  "year"                             : "",
  "place"                            : "",
  "publisher"                        : "",
  "url"                              : "",
  "pages"                            : "",
  "note"                             : "",
  "short_name"                       : "",
  "id"                               : ""
}`; // is: book, article, magazine, newspaper, blog, other

**** bookindex section (scroll & seg)                         :book:index:

#+name: abs_post
#+BEGIN_SRC d
auto bi = BookIndexReportSection();
auto bi_tuple
  = bi.bookindex_build_abstraction_section(
    bookindex_unordered_hashes,
    obj_cite_digits,
    opt_action,
  );
destroy(bookindex_unordered_hashes);
static assert(!isTypeTuple!(bi_tuple));
auto the_bookindex_section = bi_tuple[0];
obj_cite_digits = bi_tuple[1];
debug(bookindex) {
  foreach (bi_entry; the_bookindex_section["seg"]) {
    writeln(bi_entry);
  }
}
#+END_SRC

**** no blurb section?                                             :blurb:

#+name: abs_post
#+BEGIN_SRC d
if (an_object["blurb_nugget"].length == 0) {
  comp_obj_heading_                            = comp_obj_heading_.init;
  comp_obj_heading_.typeinfo.is_of_part        = "empty";
  comp_obj_heading_.typeinfo.is_of_section     = "empty";
  comp_obj_heading_.typeinfo.is_of_type        = "para";
  comp_obj_heading_.typeinfo.is_a              = "heading";
  comp_obj_heading_.text                       = "(skip) there is no Blurb section";
  comp_obj_heading_.node.ocn                   = 0;
  comp_obj_para.misc.object_number_off         = "";
  comp_obj_para.misc.object_number_type        = 0;
  comp_obj_heading_.tags.segment_anchor_tag    = "";
  comp_obj_heading_.node.marked_up_level       = "B";
  comp_obj_heading_.node.heading_lev_markup    = 1;
  comp_obj_heading_.node.heading_lev_collapsed = 1;
  comp_obj_heading_.node.parent_ocn            = 1;
  comp_obj_heading_.node.parent_lev_markup     = 0;
  the_blurb_section                            ~= comp_obj_heading_;
}
debug(blurb) {
  foreach (blurb; the_blurb_section) {
    writeln(blurb.text);
  }
}
#+END_SRC

**** toc backmatter, table of contents backmatter (scroll & seg) :contents:

#+name: abs_post
#+BEGIN_SRC d
indent=[
  "hang_position" : 1,
  "base_position" : 1,
];
comp_obj_toc                           = comp_obj_toc.init;
comp_obj_toc.typeinfo.is_of_part       = "frontmatter";
comp_obj_toc.typeinfo.is_of_section    = "toc";
comp_obj_toc.typeinfo.is_of_type       = "para";
comp_obj_toc.typeinfo.is_a             = "toc";
comp_obj_toc.node.ocn                  = 0;
comp_obj_toc.misc.object_number_off    = "";
comp_obj_toc.misc.object_number_type   = 0;
comp_obj_toc.attrib.indent_hang        = indent["hang_position"];
comp_obj_toc.attrib.indent_base        = indent["base_position"];
comp_obj_toc.attrib.bullet             = false;
if (the_endnotes_section.length > 1) {
  toc_txt_ = format(
    "{ %s }%s%s%s",
    "Endnotes",
    mkup.mark_internal_site_lnk,
    "endnotes",               // segment_anchor_tag_that_object_belongs_to
    ".fnSuffix",
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["seg"]    ~= comp_obj_toc;
}
if (the_glossary_section.length > 1) {
  toc_txt_ = format(
    "{ %s }%s%s%s",
    "Glossary",
    mkup.mark_internal_site_lnk,
    "glossary",               // segment_anchor_tag_that_object_belongs_to
    ".fnSuffix",
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["seg"]    ~= comp_obj_toc;
  toc_txt_ = format(
    "{ %s }#%s",
    "Glossary",
    "glossary",               // _anchor_tag
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["scroll"] ~= comp_obj_toc;
}
if (the_bibliography_section.length > 1){
  toc_txt_ = format(
    "{ %s }%s%s%s",
    "Bibliography",
    mkup.mark_internal_site_lnk,
    "bibliography",           // segment_anchor_tag_that_object_belongs_to
    ".fnSuffix",
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["seg"]    ~= comp_obj_toc;

  toc_txt_ = format(
    "{ %s }#%s",
    "Bibliography",
    "bibliography",           // _anchor_tag
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["scroll"] ~= comp_obj_toc;
}
if (the_bookindex_section["seg"].length > 1) {
  toc_txt_ = format(
    "{ %s }%s%s%s",
    "Book Index",
    mkup.mark_internal_site_lnk,
    "bookindex",              // segment_anchor_tag_that_object_belongs_to
    ".fnSuffix",
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["seg"]    ~= comp_obj_toc;
}
if (the_bookindex_section["scroll"].length > 1) {
  toc_txt_ = format(
    "{ %s }#%s",
    "Book Index",
    "bookindex",              // _anchor_tag
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["scroll"] ~= comp_obj_toc;
}
if (the_blurb_section.length > 1) {
  toc_txt_ = format(
    "{ %s }%s%s%s",
    "Blurb",
    mkup.mark_internal_site_lnk,
    "blurb",                  // segment_anchor_tag_that_object_belongs_to
    ".fnSuffix",
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  comp_obj_toc.has.inline_links           = true;
  the_table_of_contents_section["seg"]    ~= comp_obj_toc;
  toc_txt_ = format(
    "{ %s }#%s",
    "Blurb",
    "blurb",                  // _anchor_tag
  );
  toc_txt_= munge.url_links(toc_txt_);
  comp_obj_toc.has.inline_links           = true;
  comp_obj_toc.text                       = toc_txt_.to!string.strip;
  the_table_of_contents_section["scroll"] ~= comp_obj_toc;
}
debug(toc) {
  writefln(
    "%s %s",
    __LINE__,
    the_table_of_contents_section["seg"].length
  );
  foreach (toc_linked_heading; the_table_of_contents_section["seg"]) {
    writeln(mkup.indent_by_spaces_provided(toc_linked_heading.attrib.indent_hang), toc_linked_heading.text);
  }
}
debug(tocscroll) {
  writefln(
    "%s %s",
    __LINE__,
    the_table_of_contents_section["seg"].length
  );
  foreach (toc_linked_heading; the_table_of_contents_section["scroll"]) {
    writeln(mkup.indent_by_spaces_provided(toc_linked_heading.attrib.indent_hang), toc_linked_heading.text);
  }
}
#+END_SRC

**** doc head (separate document head from body, make space for toc)

#+name: abs_post
#+BEGIN_SRC d
the_document_head_section ~= the_document_body_section[0];
the_document_body_section=the_document_body_section[1..$];
#+END_SRC

*** ↻ *LOOPs* _post main-loop loops_                                     :post:
**** 1. ↻ _Loop backmatter:_ loop up to lev4, extract html_segnames, set pointers

this extra loop is used/needed to determine pre and (in particular) next segment
for html, that is then used in a subsequent loop

NOTE there are issues attempting to do this on first pass as:
  - backmatter is created out of sequence and
  - it is not certain which are present

  - it is quite neat to have all in one place as we have here

  - could optimise a bit by
    - skipping this loop unless the html seg or epub output is selected

***** ↻ Loop section: endnotes

#+name: abs_post
#+BEGIN_SRC d
if (the_endnotes_section.length > 1) {
  html_segnames ~= "endnotes";
  html_segnames_ptr = html_segnames_ptr_cntr;
  foreach (ref section; the_endnotes_section) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  html_segnames_ptr_cntr++;
}
#+END_SRC

***** ↻ Loop section: glossary

#+name: abs_post
#+BEGIN_SRC d
if (the_glossary_section.length > 1) {
  html_segnames ~= "glossary";
  html_segnames_ptr = html_segnames_ptr_cntr;
  foreach (ref section; the_glossary_section) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  html_segnames_ptr_cntr++;
}
#+END_SRC

***** ↻ Loop section: bibliography

#+name: abs_post
#+BEGIN_SRC d
if (the_bibliography_section.length > 1) {
  html_segnames ~= "bibliography";
  html_segnames_ptr = html_segnames_ptr_cntr;
  foreach (ref section; the_bibliography_section) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  html_segnames_ptr_cntr++;
}
#+END_SRC

***** ↻ Loop section: book index

#+name: abs_post
#+BEGIN_SRC d
if (the_bookindex_section["scroll"].length > 1) {
  html_segnames ~= "bookindex";
  html_segnames_ptr = html_segnames_ptr_cntr;
  foreach (ref section; the_bookindex_section["scroll"]) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  foreach (ref section; the_bookindex_section["seg"]) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  html_segnames_ptr_cntr++;
}
#+END_SRC

***** ↻ Loop section: blurb

#+name: abs_post
#+BEGIN_SRC d
if (the_blurb_section.length > 1) {
  html_segnames ~= "blurb";
  html_segnames_ptr = html_segnames_ptr_cntr;
  foreach (ref section; the_blurb_section) {
    if (section.node.heading_lev_markup == 4) {
      section.ptr.html_segnames = html_segnames_ptr;
      break;
    }
  }
  html_segnames_ptr_cntr++;
}
#+END_SRC

**** 2. ↻ _Loop all objects:_ encode _structural relationships_ (sections, segments, objects)

needed for DOM structure, segnames & subtoc, backmatter pointers & unique image
list

if used minimally only for DOM structure, segnames, subtoc, could optimise by
- skipping second and third pass unless the output html seg or epub is being
  made!

- this loop could conveniently be used more extensively for ancestors as well
  (though this information can be extracted earlier)

Build here:
- DOM structure
  - ancestors & decendants
    - ancestors could be determined earlier, but convenient to have here
    - descendants could be in the form of: headings contained under current
      heading, and/or; the range of objects under the current heading
- numbering
  - already given
    - substantive object numbers
    - endnote
  - provide
    - glossary
    - bibliography
    - book index
    - blurb
    - other non-substantive objects (prefix & other stuff)
  - you could also decide on a sequential object list, containing all objects
    (both substantive and non-substantive objects), in addition to ocn, which
    are for substantive/ citable objects within the document

(as needed) up to document heading 1~, lev4 html:

- during this (the third) pass all previous and next segment names are known
- next are not yet known for backmatter during the second pas

***** Methods
****** images: extract

#+name: abs_post
#+BEGIN_SRC d
string[] _images;
auto extract_images(S)(S content_block) {
  string[] images_;
  if (auto m = content_block.matchAll(rgx.image)) {
    images_ ~= m.captures[1];
  }
  return images_;
}
string[] segnames_0_4;
#+END_SRC

****** images: dimensions

#+name: abs_post
#+BEGIN_SRC d
auto _image_dimensions(M,O)(M manifest_matter, O obj) {
  if (obj.has.contains_image_without_dimensions) {
    import std.math;
    import imageformats;
    int w, h, chans;
    real _w, _h;
    int max_width = 640;
    foreach (m; obj.text.matchAll(rgx.inline_image_without_dimensions)) {
      debug(images) {
        writeln(manifest_matter.src.image_dir_path ~ "/" ~ m.captures["img"]);
      }
      read_image_info(manifest_matter.src.image_dir_path ~ "/" ~ m.captures["img"], w, h, chans);
      // calculate, decide max width and proportionally reduce to keep w & h within it
      debug(images) {
        writeln("width: ", w, ", height: ", h);
      }
      if (w > max_width) {
        _w = max_width;
        _h = round((max_width / w.to!real) * h.to!real);
      } else {
        _w = w;
        _h = h;
      }
      obj.text = obj.text.replaceFirst(
        rgx.inline_image_without_dimensions,
        ("$1☼$3,w" ~ _w.to!string ~ "h" ~ _h.to!string ~ " $6")
      );
    }
    debug(images) {
      writeln("image without dimensions: ", obj.text);
    }
  }
  return obj;
}
#+END_SRC

***** ↻ Loop section: head

#+name: abs_post
#+BEGIN_SRC d
foreach (ref obj; the_document_head_section) {
  if (obj.typeinfo.is_a == "heading") {
    debug(dom) {
      writeln(obj.text);
    }
    if (obj.node.heading_lev_markup <= 4) {
      segnames_0_4 ~= obj.tags.segment_anchor_tag;
    }
    if (obj.node.heading_lev_markup == 0) {
      /+ TODO second hit (of two) with same assertion failure, check, fix and reinstate
      assert( obj.node.ocn == 1,
        "Title OCN should be 1 not: " ~ obj.node.ocn.to!string); // bug introduced 0.18.1
      +/
      obj.node.ocn = 1;
      obj.misc.object_number_type = OCNtype.ocn;
    }
    /+ dom structure (marked up & collapsed) +/
    if ((opt_action.html)
    || (opt_action.html_scroll)
    || (opt_action.html_seg)
    || (opt_action.epub)
    || (opt_action.sqlite_discrete)
    || (opt_action.sqlite_update)
    || (opt_action.postgresql)) {
      obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
      obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
    }
    obj = obj_heading_ancestors(obj, lv_ancestors_txt);
  }
}
#+END_SRC

***** ↻ Loop section: toc [to]

#+name: abs_post
#+BEGIN_SRC d
if (the_table_of_contents_section["scroll"].length > 1) {
  /+ scroll +/
  dom_markedup_buffer = dom_markedup.dup;
  dom_collapsed_buffer = dom_collapsed.dup;
  foreach (ref obj; the_table_of_contents_section["scroll"]) {
    if (obj.typeinfo.is_a == "heading") {
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    }
  }
  /+ seg +/
  dom_markedup = dom_markedup_buffer.dup;
  dom_collapsed = dom_collapsed_buffer.dup;
  foreach (ref obj; the_table_of_contents_section["seg"]) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    }
  }
}
#+END_SRC

***** ↻ Loop section: document body [bd]

#+name: abs_post
#+BEGIN_SRC d
/+ multiple 1~ levels, loop through document body +/
if (the_document_body_section.length > 1) {
  foreach (ref obj; the_document_body_section) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.lev4_subtoc = lev4_subtoc[obj.tags.segment_anchor_tag];
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "para") {
       _images ~= extract_images(obj.text);
       obj = _image_dimensions(manifest_matter, obj);
    }
  }
}
auto images=uniq(_images.sort());
#+END_SRC

***** ↻ Loop section: endnotes [en]

- endnotes have their own number, (also use in node) and they belong to calling object

#+name: abs_post
#+BEGIN_SRC d
/+ optional only one 1~ level +/
if (the_endnotes_section.length > 1) {
  dom_markedup_buffer = dom_markedup.dup;
  dom_collapsed_buffer = dom_collapsed.dup;
  dom_markedup = dom_markedup_buffer.dup;
  dom_collapsed = dom_collapsed_buffer.dup;
  foreach (ref obj; the_endnotes_section) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.digit;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    }
  }
}
#+END_SRC

***** ↻ Loop section: glossary [gl]

- add glossary numbering, (also use in node) no need to show in text

#+name: abs_post
#+BEGIN_SRC d
/+ optional only one 1~ level +/
if (the_glossary_section.length > 1) {
  foreach (ref obj; the_glossary_section) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.digit;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "glossary") {
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.digit;
    }
  }
}
#+END_SRC

***** ↻ Loop section: bibliography [bb]

- add bibliography numbering, (also use in node) no need to show in text

#+name: abs_post
#+BEGIN_SRC d
/+ optional only one 1~ level +/
if (the_bibliography_section.length > 1) {
  foreach (ref obj; the_bibliography_section) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.digit;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "bibliography") {
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.on;
    }
  }
}
#+END_SRC

***** ↻ Loop section: book index (scroll, seg) [bi]

- add book index numbering?, (also use in node) no need to show in text

#+name: abs_post
#+BEGIN_SRC d
/+ optional only one 1~ level +/
int ocn_ = obj_cite_digits.on;
int ocn_bkidx_ = 0;
int ocn_bidx_;
if (the_bookindex_section["scroll"].length > 1) {
  /+ scroll +/
  dom_markedup_buffer = dom_markedup.dup;
  dom_collapsed_buffer = dom_collapsed.dup;
  foreach (ref obj; the_bookindex_section["scroll"]) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
      }
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.on;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "bookindex") {
      obj_cite_digits                   = ocn_emit(OCNstatus.bkidx);
      obj.node.ocn                      = obj_cite_digits.digit;
      obj.misc.o_n_book_index           = obj_cite_digits.bkidx;
      obj.misc.object_number_type       = OCNtype.bkidx;
    }
  }
  /+ seg +/
  dom_markedup = dom_markedup_buffer.dup;
  dom_collapsed = dom_collapsed_buffer.dup;
  foreach (ref obj; the_bookindex_section["seg"]) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      obj.node.ocn = ++ocn_;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "bookindex") {
      ocn_bidx_ = ++ocn_bkidx_;
      obj.misc.o_n_book_index           = ocn_bidx_; // FIX need to distinguish from regular ocn
      obj.misc.object_number_type       = OCNtype.bkidx;
    }
  }
  /+ TODO assert failure, reinstate
  assert(obj_cite_digit_bkidx == ocn_bidx_
    obj_cite_digit_bkidx ~ " == ocn_" ~ ocn_ ~ "?");
  +/
}
#+END_SRC

***** ↻ Loop section: blurb [bl]

#+name: abs_post
#+BEGIN_SRC d
/+ optional only one 1~ level +/
if (the_blurb_section.length > 1) {
  foreach (ref obj; the_blurb_section) {
    if (obj.typeinfo.is_a == "heading") {
      debug(dom) {
        writeln(obj.text);
      }
      obj_cite_digits = ocn_emit(OCNstatus.on);
      obj.node.ocn = obj_cite_digits.on;
      if (obj.node.heading_lev_markup <= 4) {
        segnames_0_4 ~= obj.tags.segment_anchor_tag;
        if (obj.node.heading_lev_markup == 4) {
          obj.tags.segname_prev = html_segnames[obj.ptr.html_segnames - 1];
          if (html_segnames.length > obj.ptr.html_segnames + 1) {
            obj.tags.segname_next = html_segnames[obj.ptr.html_segnames + 1];
          }
          assert(obj.tags.segment_anchor_tag == html_segnames[obj.ptr.html_segnames],
          obj.tags.segment_anchor_tag ~ "!=" ~ html_segnames[obj.ptr.html_segnames]);
        }
      }
      /+ dom structure (marked up & collapsed) +/
      if ((opt_action.html)
      || (opt_action.html_scroll)
      || (opt_action.html_seg)
      || (opt_action.epub)
      || (opt_action.sqlite_discrete)
      || (opt_action.sqlite_update)
      || (opt_action.postgresql)) {
        obj = obj_dom_structure_set_markup_tags(obj, dom_markedup, obj.node.heading_lev_markup);
        obj = obj_dom_set_collapsed_tags(obj, dom_collapsed, obj.node.heading_lev_collapsed);
      }
      obj = obj_heading_ancestors(obj, lv_ancestors_txt);
    } else if (obj.typeinfo.is_a == "blurb") {
      obj_cite_digits = ocn_emit(OCNstatus.off);
      obj.misc.object_number_off  = obj_cite_digits.off.to!string;
      obj.misc.object_number_type = OCNtype.non;
    }
  }
}
#+END_SRC

**** TODO update BUG?

#+name: abs_post
#+BEGIN_SRC d
  /+ TODO
    - note create/insert heading object sole purpose eof close all open tags
      sort out:
      - obj.node.dom_markedup = dom_markedup;
      - obj.node.dom_collapsed = dom_collapsed;
  +/
comp_obj_heading_                            = comp_obj_heading_.init;
comp_obj_heading_.typeinfo.is_of_part        = "empty";
comp_obj_heading_.typeinfo.is_of_section     = "empty";
comp_obj_heading_.typeinfo.is_of_type        = "para";
comp_obj_heading_.typeinfo.is_a              = "heading";
comp_obj_heading_.node.ocn                   = 0;
comp_obj_para.misc.object_number_off         = "";
comp_obj_para.misc.object_number_type        = 0;
comp_obj_heading_.tags.segment_anchor_tag    = "";
comp_obj_heading_.node.marked_up_level       = "";
comp_obj_heading_.node.heading_lev_markup    = 9;
comp_obj_heading_.node.heading_lev_collapsed = 9;
comp_obj_heading_.node.parent_ocn            = 0;
comp_obj_heading_.node.parent_lev_markup     = 0;
comp_obj_heading_.node.dom_markedup          = dom_markedup.dup;
comp_obj_heading_.node.dom_collapsed         = dom_collapsed.dup;
comp_obj_heading_ = obj_dom_structure_set_markup_tags(comp_obj_heading_, dom_markedup, 0);
comp_obj_heading_ = obj_dom_set_collapsed_tags(comp_obj_heading_, dom_collapsed, 0);
comp_obj_heading_ = obj_heading_ancestors(comp_obj_heading_, lv_ancestors_txt);
// the_dom_tail_section                      ~= comp_obj_heading_; // remove tail for now, decide on later
#+END_SRC

** 4. _return document tuple_                                           :post:
*** _the document_                                                 :document:

#+name: abs_post
#+BEGIN_SRC d
auto document_the = [
  "head":             the_document_head_section,
  "toc_seg":          the_table_of_contents_section["seg"],
  "toc_scroll":       the_table_of_contents_section["scroll"],
  /+ substantive/body: +/
  "body":             the_document_body_section,
  /+ backmatter: +/
  "endnotes":         the_endnotes_section,
  "glossary":         the_glossary_section,
  "bibliography":     the_bibliography_section,
  "bookindex_scroll": the_bookindex_section["scroll"],
  "bookindex_seg":    the_bookindex_section["seg"],
  "blurb":            the_blurb_section,
  /+ dom tail only +/
  "tail":             the_dom_tail_section,
];
#+END_SRC

*** document _section keys_ sequence

#+name: abs_post
#+BEGIN_SRC d
string[][string] document_section_keys_sequenced = [
  "seg":    ["head", "toc_seg", "body",],
  "scroll": ["head", "toc_scroll", "body",],
  "sql":    ["head", "body",]
];
if (document_the["endnotes"].length > 1) {
  document_section_keys_sequenced["seg"]    ~= "endnotes";
  document_section_keys_sequenced["scroll"] ~= "endnotes";
}
if (document_the["glossary"].length > 1) {
  document_section_keys_sequenced["seg"]    ~= "glossary";
  document_section_keys_sequenced["scroll"] ~= "glossary";
  document_section_keys_sequenced["sql"]    ~= "glossary";
}
if (document_the["bibliography"].length > 1) {
  document_section_keys_sequenced["seg"]    ~= "bibliography";
  document_section_keys_sequenced["scroll"] ~= "bibliography";
  document_section_keys_sequenced["sql"]    ~= "bibliography";
}
if (document_the["bookindex_seg"].length > 1) {
  document_section_keys_sequenced["seg"]    ~= "bookindex_seg";
  document_section_keys_sequenced["sql"]    ~= "bookindex_seg";
}
if (document_the["bookindex_scroll"].length > 1) {
  document_section_keys_sequenced["scroll"] ~= "bookindex_scroll";
}
if (document_the["blurb"].length > 1) {
  document_section_keys_sequenced["seg"]    ~= "blurb";
  document_section_keys_sequenced["scroll"] ~= "blurb";
  document_section_keys_sequenced["sql"]    ~= "blurb";
}
if ((opt_action.html)
|| (opt_action.html_scroll)
|| (opt_action.html_seg)
|| (opt_action.epub)) {
  document_section_keys_sequenced["seg"]    ~= "tail";
  document_section_keys_sequenced["scroll"] ~= "tail";
}
auto sequenced_document_keys = docSectKeysSeq!()(document_section_keys_sequenced);
#+END_SRC

*** dup

#+name: abs_post
#+BEGIN_SRC d
auto segnames = html_segnames.dup;
#+END_SRC

*** clean out structure

#+name: abs_post
#+BEGIN_SRC d
destroy(the_document_head_section);
destroy(the_table_of_contents_section);
destroy(the_document_body_section);
destroy(the_endnotes_section);
destroy(the_glossary_section);
destroy(the_bibliography_section);
destroy(the_bookindex_section);
destroy(the_blurb_section);
destroy(html_segnames);
destroy(bookindex_unordered_hashes);
destroy(an_object);
obj_cite_digits        = ocn_emit(OCNstatus.reset);
biblio_arr_json        = [];
obj_cite_digit_        = 0;
html_segnames_ptr      = 0;
html_segnames_ptr_cntr = 0;
content_non_header     = "8";
dom_markedup           = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
dom_markedup_buffer    = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
dom_collapsed          = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
dom_collapsed_buffer   = [ 0, 0, 0, 0, 0, 0, 0, 0, 0,];
#+END_SRC

*** [#A] _return document tuple_                               :return:tuple:

#+name: abs_return_tuple
#+BEGIN_SRC d
auto t = tuple(
  document_the,
  sequenced_document_keys,
  segnames,
  segnames_0_4,
  images,
);
return t;
#+END_SRC

** 5. Functions                                          :abstract:function:

functions used in document abstraction

*** set & resets                                                    :reset:
**** object reset: remove (clean)                          :object:remove:

#+name: abs_functions_object_reset
#+BEGIN_SRC d
static auto object_reset(O)(O an_object) {
  debug(asserts) {
    static assert(is(typeof(an_object) == string[string]));
  }
  an_object.remove("body_nugget");
  an_object.remove("substantive");
  an_object.remove("is");
  an_object.remove("attrib");
  an_object.remove("bookindex_nugget");
  return an_object;
}
#+END_SRC

**** set, initialize or re-initialize                                :set:

#+name: abs_functions_header_set_common
#+BEGIN_SRC d
auto _common_reset_(L,O,T)(
  return ref L line_occur,
  return ref O an_object,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line_occur)      == int[string]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  line_occur["heading"]      = State.off;
  line_occur["para"]         = State.off;
  obj_type_status["heading"] = State.off;
  obj_type_status["para"]    = State.off;
  an_object = object_reset(an_object);
}
#+END_SRC

*** check object_number status in document                          :ocn:

#+name: abs_functions_ocn_status
#+BEGIN_SRC d
static auto _check_ocn_status_(L,T)(
  L            line,
  T            obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line) == char[]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if ((!line.empty) && (obj_type_status["ocn_status_off_for_multiple_objects"] == TriState.off)) {
    /+ not multi-line object, check whether object_number is on or turned off +/
    if (line.matchFirst(rgx.object_number_block_marks)) {
      /+ switch off object_number +/
      if (line.matchFirst(rgx.object_number_off_block)) {
        obj_type_status["ocn_status_off_for_multiple_objects"] = TriState.on;
        debug(ocnoff) {
          writeln(line);
        }
      }
      if (line.matchFirst(rgx.object_number_off_block_dh)) {
        obj_type_status["ocn_status_off_for_multiple_objects"] = TriState.closing;
        debug(ocnoff) {
          writeln(line);
        }
      }
    } else {
      if (obj_type_status["ocn_status_off_for_multiple_objects"] == TriState.off) {
        if (line.matchFirst(rgx.object_number_off)) {
          obj_type_status["ocn_status"] = OCNstatus.off;
        } else if (line.matchFirst(rgx.object_number_off_dh)) {
          obj_type_status["ocn_status"] = OCNstatus.closing;
        } else {
          obj_type_status["ocn_status"] = OCNstatus.on;
        }
      } else {
        obj_type_status["ocn_status"]
          = obj_type_status["ocn_status_off_for_multiple_objects"];
      }
    }
  } else if ((!line.empty) && (obj_type_status["ocn_status_off_for_multiple_objects"] > TriState.off)) {
    if (line.matchFirst(rgx.object_number_off_block_close)) {
      obj_type_status["ocn_status_off_for_multiple_objects"] = TriState.off;
      obj_type_status["ocn_status"]                          = OCNstatus.on;
      debug(ocnoff) {
        writeln(line);
      }
    }
  }
  return obj_type_status;
}
#+END_SRC

*** make substitutions
**** project

#+name: abs_functions_substitutions
#+BEGIN_SRC d
auto _doc_header_and_make_substitutions_(L,CMM)(
  L    line,
  CMM  conf_make_meta,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
  }
  enum Substitute { match, markup, }
  if (conf_make_meta.make.substitute) {
    foreach(substitution_pair; conf_make_meta.make.substitute) {
      line = line.replaceAll(
        regex("\b" ~ substitution_pair[Substitute.match]),
        substitution_pair[Substitute.markup]
      );
    }
  }
  return line;
}
#+END_SRC

**** fontface

#+name: abs_functions_substitutions
#+BEGIN_SRC d
auto _doc_header_and_make_substitutions_fontface_(L,CMM)(
  L    line,
  CMM  conf_make_meta,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
  }
  enum Substitute { match, markup, }
  if ( conf_make_meta.make.bold) {
    line = line.replaceAll(
      regex("\b" ~ conf_make_meta.make.bold[Substitute.match]),
      conf_make_meta.make.bold[Substitute.markup]
    );
  }
  if (conf_make_meta.make.emphasis) {
    line = line.replaceAll(
      regex("\b" ~ conf_make_meta.make.emphasis[Substitute.match]),
      conf_make_meta.make.emphasis[Substitute.markup]
    );
  }
  if (conf_make_meta.make.italics) {
    line = line.replaceAll(
      regex("\b" ~ conf_make_meta.make.italics[Substitute.match]),
      conf_make_meta.make.italics[Substitute.markup]
    );
  }
  return line;
}
#+END_SRC

*** block                                                           :block:
**** block start (open) block                                      :start:
***** { block starts function

#+name: abs_functions_block
#+BEGIN_SRC d
void _start_block_(L,T,N)(
             L line,
  return ref T obj_type_status,
  return ref N object_number_poem
) {
  debug(asserts) {
    static assert(is(typeof(line)                 == char[]));
    static assert(is(typeof(obj_type_status)      == int[string]));
    static assert(is(typeof(object_number_poem) == string[string]));
  }
#+END_SRC

****** block (various) curly open                                :curly:

#+name: abs_functions_block
#+BEGIN_SRC d
  static auto rgx = Rgx();
  string code_block_syntax = "";
  bool code_block_numbered = false;
  if (auto m = line.matchFirst(rgx.block_curly_code_open)) {
    /+ curly code open +/
    code_block_syntax = (m.captures[1]) ? m.captures[1].to!string : "";
    code_block_numbered = (m.captures[2] == "#") ? true : false;
    debug(code) {                              // code (curly) open
      writefln(
        "* [code curly] %s",
        line
      );
    }
    obj_type_status["blocks"]     = TriState.on;
    obj_type_status["code"]       = TriState.on;
    obj_type_status["curly_code"] = TriState.on;
  } else if (line.matchFirst(rgx.block_curly_poem_open)) {
    /+ curly poem open +/
    debug(poem) {                              // poem (curly) open
      writefln(
        "* [poem curly] %s",
        line
      );
    }
    object_number_poem["start"] = obj_cite_digits.on.to!string;
    obj_type_status["blocks"]     = TriState.on;
    obj_type_status["verse_new"]  = State.on;
    obj_type_status["poem"]       = TriState.on;
    obj_type_status["curly_poem"] = TriState.on;
  } else if (line.matchFirst(rgx.block_curly_group_open)) {
    /+ curly group open +/
    debug(group) {                             // group (curly) open
      writefln(
        "* [group curly] %s",
        line
      );
    }
    obj_type_status["blocks"]      = TriState.on;
    obj_type_status["group"]       = TriState.on;
    obj_type_status["curly_group"] = TriState.on;
  } else if (line.matchFirst(rgx.block_curly_block_open)) {
    /+ curly block open +/
    debug(block) {                             // block (curly) open
      writefln(
        "* [block curly] %s",
        line
      );
    }
    obj_type_status["blocks"]      = TriState.on;
    obj_type_status["block"]       = TriState.on;
    obj_type_status["curly_block"] = TriState.on;
  } else if (line.matchFirst(rgx.block_curly_quote_open)) {
    /+ curly quote open +/
    debug(quote) {                             // quote (curly) open
      writefln(
        "* [quote curly] %s",
        line
      );
    }
    obj_type_status["blocks"]      = TriState.on;
    obj_type_status["quote"]       = TriState.on;
    obj_type_status["curly_quote"] = TriState.on;
  } else if (auto m = line.matchFirst(rgx.block_curly_table_open)) {
    /+ curly table open +/
    debug(table) {                             // table (curly) open
      writefln(
        "* [table curly] %s",
        line
      );
    }
    an_object["table_head"]        = m.captures[1].to!string;
    an_object["block_type"]        = "curly";
    obj_type_status["blocks"]      = TriState.on;
    obj_type_status["table"]       = TriState.on;
    obj_type_status["curly_table"] = TriState.on;
  } else if (auto m = line.matchFirst(rgx.block_curly_table_special_markup)) {
    /+ table: special table block markup syntax! +/
    an_object["table_head"]                       = m.captures[1].to!string;
    an_object["block_type"]                       = "special";
    obj_type_status["blocks"]                     = TriState.on;
    obj_type_status["table"]                      = TriState.on;
    obj_type_status["curly_table_special_markup"] = TriState.on;
#+END_SRC

****** block (various) tic open                                    :tic:

#+name: abs_functions_block
#+BEGIN_SRC d
  } else if (auto m = line.matchFirst(rgx.block_tic_code_open)) {
    /+ tic code open +/
    code_block_syntax = (m.captures[1]) ? m.captures[1].to!string : "";
    code_block_numbered = (m.captures[2] == "#") ? true : false;
    debug(code) {                              // code (tic) open
      writefln(
        "* [code tic] %s",
        line
      );
    }
    obj_type_status["blocks"]   = TriState.on;
    obj_type_status["code"]     = TriState.on;
    obj_type_status["tic_code"] = TriState.on;
  } else if (line.matchFirst(rgx.block_tic_poem_open)) {
    /+ tic poem open +/
    debug(poem) {                              // poem (tic) open
      writefln(
        "* [poem tic] %s",
        line
      );
    }
    object_number_poem["start"] = obj_cite_digits.on.to!string;
    obj_type_status["blocks"]    = TriState.on;
    obj_type_status["verse_new"] = State.on;
    obj_type_status["poem"]      = TriState.on;
    obj_type_status["tic_poem"]  = TriState.on;
  } else if (line.matchFirst(rgx.block_tic_group_open)) {
    /+ tic group open +/
    debug(group) {                             // group (tic) open
      writefln(
        "* [group tic] %s",
        line
      );
    }
    obj_type_status["blocks"]    = TriState.on;
    obj_type_status["group"]     = TriState.on;
    obj_type_status["tic_group"] = TriState.on;
  } else if (line.matchFirst(rgx.block_tic_block_open)) {
    /+ tic block open +/
    debug(block) {                             // block (tic) open
      writefln(
        "* [block tic] %s",
        line
      );
    }
    obj_type_status["blocks"]    = TriState.on;
    obj_type_status["block"]     = TriState.on;
    obj_type_status["tic_block"] = TriState.on;
  } else if (line.matchFirst(rgx.block_tic_quote_open)) {
    /+ tic quote open +/
    debug(quote) {                             // quote (tic) open
      writefln(
        "* [quote tic] %s",
        line
      );
    }
    obj_type_status["blocks"]    = TriState.on;
    obj_type_status["quote"]     = TriState.on;
    obj_type_status["tic_quote"] = TriState.on;
  } else if (auto m = line.matchFirst(rgx.block_tic_table_open)) {
    /+ tic table open +/
    debug(table) {                             // table (tic) open
      writefln(
        "* [table tic] %s",
        line
      );
    }
    an_object["table_head"]      = m.captures[1].to!string;
    an_object["block_type"]      = "tic";
    obj_type_status["blocks"]    = TriState.on;
    obj_type_status["table"]     = TriState.on;
    obj_type_status["tic_table"] = TriState.on;
  }
#+END_SRC

***** }

#+name: abs_functions_block
#+BEGIN_SRC d
}
#+END_SRC

**** block continue (an open block)                             :continue:
***** _code block_ (special status, deal with first)                 :code:

#+name: abs_functions_block_code
#+BEGIN_SRC d
void _code_block_(L,O,T)(
             L line,
  return ref O an_object,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_code"] == TriState.on) {
    if (line.matchFirst(rgx.block_curly_code_close)) {
      debug(code) {                                    // code (curly) close
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key]
        .replaceFirst(rgx.newline_eol_delimiter_only, "")
        .stripRight;
      obj_type_status["blocks"]     = TriState.closing;
      obj_type_status["code"]       = TriState.closing;
      obj_type_status["curly_code"] = TriState.off;
    } else {
      debug(code) {                                    // code (curly) line
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";        // code (curly) line
    }
  } else if (obj_type_status["tic_code"] == TriState.on) {
    if (line.matchFirst(rgx.block_tic_close)) {
      debug(code) {                                    // code (tic) close
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key]
        .replaceFirst(rgx.newline_eol_delimiter_only, "")
        .stripRight;
      obj_type_status["blocks"]   = TriState.closing;
      obj_type_status["code"]     = TriState.closing;
      obj_type_status["tic_code"] = TriState.off;
    } else {
      debug(code) {                                    // code (tic) line
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";        // code (tic) line
    }
  }
}
#+END_SRC

***** biblio block                                               :biblio:
****** biblio tag map

#+name: abs_functions_block_biblio
#+BEGIN_SRC d
final string biblio_tag_map(A)(A abr) {
  debug(asserts) {
    static assert(is(typeof(abr) == string));
  }
  auto btm = [
    "au"                               : "author_raw",
    "ed"                               : "editor_raw",
    "ti"                               : "fulltitle",
    "lng"                              : "language",
    "jo"                               : "journal",
    "vol"                              : "volume",
    "edn"                              : "edition",
    "yr"                               : "year",
    "pl"                               : "place",
    "pb"                               : "publisher",
    "pub"                              : "publisher",
    "pg"                               : "pages",
    "pgs"                              : "pages",
    "sn"                               : "short_name"
  ];
  return btm[abr];
}
#+END_SRC

******* +consider+

#+name: none
#+BEGIN_SRC d
final string biblio_tag_map_(A)(A abr) {
  debug(asserts) {
    static assert(is(typeof(abr) == string));
  }
  string name;
  switch (abr) {
  case "au":
    name="author_raw";
    break;
  case "ed":
    name="editor_raw";
    break;
  case "ti":
    name="fulltitle";
    break;
  case "lng":
    name="language";
    break;
  case "jo":
    name="journal";
    break;
  case "vol":
    name="volume";
    break;
  case "edn":
    name="edition";
    break;
  case "yr":
    name="year";
    break;
  case "pl":
    name="place";
    break;
  case "pb":
    name="publisher";
    break;
  case "pub":
    name="publisher";
    break;
  case "pg":
    name="pages";
    break;
  case "pgs":
    name="pages";
    break;
  case "sn":
    name="short_name";
    break;
  default:
    name=abr;
    break;
  }
  return name;
}
#+END_SRC

****** biblio block

#+name: abs_functions_block_biblio
#+BEGIN_SRC d
void _biblio_block_(
  char[]                 line,
  return ref int[string] obj_type_status,
  return ref int         bib_entry,
  return ref string      biblio_entry_str_json,
  return ref string[]    biblio_arr_json
) {
  mixin SiSUbiblio;
  auto jsn = BibJsnStr();
  static auto rgx = Rgx();
  if (line.matchFirst(rgx.heading_biblio)) {
    obj_type_status["biblio_section"]   = TriState.on;
    obj_type_status["blurb_section"]    = State.off;
    obj_type_status["glossary_section"] = State.off;
  }
  if (line.empty) {
    debug {
      debug(biblioblock) {
        writeln("---");
      }
      debug(biblioblockinclude) {
        writeln(biblio_entry_str_json.length);
      }
    }
    if ((bib_entry == State.off)
    && (biblio_entry_str_json.empty)) {
      bib_entry = State.on;
      biblio_entry_str_json = jsn.biblio_entry_tags_jsonstr;
    } else if (!(biblio_entry_str_json.empty)) {
      bib_entry = State.off;
      if (!(biblio_entry_str_json == jsn.biblio_entry_tags_jsonstr)) {
        auto biblio_entry = parseJSON(biblio_entry_str_json);
        if (biblio_entry["fulltitle"].str.empty) {
          writeln("check problem entry (Title missing): ", biblio_entry_str_json);
        } else if ((biblio_entry["author_raw"].str.empty) && (biblio_entry["editor_raw"].str.empty)) {
          writeln("check problem entry (No author and no editor): ", biblio_entry_str_json);
        } else {
          biblio_arr_json ~= biblio_entry_str_json;
        }
        biblio_entry_str_json = jsn.biblio_entry_tags_jsonstr;
      }
    } else { // CHECK ERROR
      writeln("?? 2. ERROR ", biblio_entry_str_json, "??");
      biblio_entry_str_json = "";
    }
  } else if (line.matchFirst(rgx.biblio_tags)) {
    debug(biblioblock) {
      writeln(line);
    }
    auto bt = line.match(rgx.biblio_tags);
    bib_entry = State.off;
    st = bt.captures[1].to!string;
    auto header_tag_value=(bt.captures[2]).to!string;
    JSONValue j = parseJSON(biblio_entry_str_json);
    biblio_tag_name = (st.match(rgx.biblio_abbreviations))
      ? (biblio_tag_map(st))
      : st;
    j.object[biblio_tag_name] = header_tag_value;
    debug(bibliounsortedcheckduplicates) {
      writeln(biblio_tag_name, ": ", header_tag_value);
      writeln("--");
    }
    switch (biblio_tag_name) {
    case "author_raw": // author_arr author (fn sn)
      j["author_arr"]
       = header_tag_value.split(rgx.arr_delimiter);
      string tmp;
      biblioAuthorLoop:
      foreach (au; j["author_arr"].array) {
        if (auto x = au.str.match(rgx.name_delimiter)) {
          tmp ~= x.captures[2] ~ " " ~ x.captures[1] ~ ", ";
        } else {
          tmp ~= au.str;
        }
      }
      tmp = (tmp).replace(rgx.trailing_comma, "");
      j["author"].str = tmp;
      goto default;
    case "editor_raw": // editor_arr editor (fn sn)
      j["editor_arr"]
        = header_tag_value.split(rgx.arr_delimiter);
      string tmp;
      biblioEditorLoop:
      foreach (ed; j["editor_arr"].array) {
        if (auto x = ed.str.match(rgx.name_delimiter)) {
          tmp ~= x.captures[2] ~ " " ~ x.captures[1] ~ ", ";
        } else {
          tmp ~= ed.str;
        }
      }
      tmp = (tmp).replace(rgx.trailing_comma, "");
      j["editor"].str = tmp;
      goto default;
    case "fulltitle": // title & subtitle
      goto default;
    default:
      break;
    }
    auto s = j.toString();
    debug(biblio1) {
      writefln(
        "* %s: %s\n%s",
        biblio_tag_name,
        biblio_tag_entry,
        j[biblio_tag_name]
      );
    }
    if (line.match(rgx.comment)) {
      writeln("ERROR", line, "COMMENT");
      writeln("ERROR", s, "%%");
    }
    if (!(match(line, rgx.comment))) {
      debug(biblioblockinclude) {
        writeln(line);
      }
      biblio_entry_str_json = s;
    } else {
      biblio_entry_str_json = "";
    }
    header_tag_value="";
  }
}
#+END_SRC

***** quote block                                                 :quote:

#+name: abs_functions_block_quote
#+BEGIN_SRC d
void _quote_block_(L,O,T)(
             L line,
  return ref O an_object,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_quote"] == TriState.on) {
    if (line.matchFirst(rgx.block_curly_quote_close)) {
      debug(quote) {                              // quote (curly) close
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]      = TriState.closing;
      obj_type_status["quote"]       = TriState.closing;
      obj_type_status["curly_quote"] = TriState.off;
    } else {
      debug(quote) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build quote array (or string)
    }
  } else if (obj_type_status["tic_quote"] == TriState.on) {
    if (line.matchFirst(rgx.block_tic_close)) {
      debug(quote) {                              // quote (tic) close
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]    = TriState.closing;
      obj_type_status["quote"]     = TriState.closing;
      obj_type_status["tic_quote"] = TriState.off;
    } else {
      debug(quote) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build quote array (or string)
    }
  }
}
#+END_SRC

***** group block                                                 :group:

- apply inline markup
- discard leading and newline whitespace

#+name: abs_functions_block_group
#+BEGIN_SRC d
void _group_block_(L,O,T)(
             L line,
  return ref O an_object,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_group"] == State.on) {
    if (line.matchFirst(rgx.block_curly_group_close)) {
      debug(group) {
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]      = TriState.closing;
      obj_type_status["group"]       = TriState.closing;
      obj_type_status["curly_group"] = TriState.off;
    } else {
      debug(group) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build group array (or string)
    }
  } else if (obj_type_status["tic_group"] == TriState.on) {
    if (line.matchFirst(rgx.block_tic_close)) {
      debug(group) {
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]    = TriState.closing;
      obj_type_status["group"]     = TriState.closing;
      obj_type_status["tic_group"] = TriState.off;
    } else {
      debug(group) {                              // group
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build group array (or string)
    }
  }
}
#+END_SRC

***** block block                                                 :block:

- apply inline markup
- keep whitespace indentation
- keep newlines

#+name: abs_functions_block_block
#+BEGIN_SRC d
void _block_block_(L,O,T)(
             L line,
  return ref O an_object,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_block"] == TriState.on) {
    if (line.matchFirst(rgx.block_curly_block_close)) {
      debug(block) {                             // block (curly) close
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]      = TriState.closing;
      obj_type_status["block"]       = TriState.closing;
      obj_type_status["curly_block"] = TriState.off;
    } else {
      debug(block) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build block array (or string)
    }
  } else if (obj_type_status["tic_block"] == TriState.on) {
    if (line.matchFirst(rgx.block_tic_close)) {
      debug(block) {
        writeln(line);
      }
      an_object[an_object_key] = an_object[an_object_key].stripRight;
      obj_type_status["blocks"]    = TriState.closing;
      obj_type_status["block"]     = TriState.closing;
      obj_type_status["tic_block"] = TriState.off;
    } else {
      debug(block) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";   // build block array (or string)
    }
  }
}
#+END_SRC

***** poem block, verse objects                              :poem:verse:

why extra object stuff only in poem/verse?

#+name: abs_functions_block_poem
#+BEGIN_SRC d
void _poem_block_(L,O,T,C,N,CMM)(
             L   line,
  return ref O   an_object,
  return ref T   obj_type_status,
  return ref C   cntr,
             N   object_number_poem,
             CMM conf_make_meta,
) {
  debug(asserts) {
    static assert(is(typeof(line)                 == char[]));
    static assert(is(typeof(an_object)            == string[string]));
    static assert(is(typeof(obj_type_status)      == int[string]));
    static assert(is(typeof(cntr)                 == int));
    static assert(is(typeof(object_number_poem) == string[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_poem"] == TriState.on) {
    if (line.matchFirst(rgx.block_curly_poem_close)) {
      if (an_object_key in an_object
      || processing.length > 0) {
        an_object[an_object_key]                    = "";
        debug(poem) {                               // poem (curly) close
          writefln(
            "* [poem curly] %s",
            line
          );
        }
        if (processing.length > 0) {
          an_object[an_object_key] = processing["verse"];
        }
        debug(poem) {                               // poem (curly) close
          writeln(__LINE__);
          writefln(
            "* %s %s",
            obj_cite_digits.on,
            line
          );
        }
        if (an_object.length > 0) {
          debug(poem) {                             // poem (curly) close
            writeln(
              obj_cite_digits.on,
              an_object[an_object_key]
            );
          }
          an_object["is"]                           = "verse";
          auto substantive_obj_misc_tuple
            = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
          an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
          anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
          comp_obj_block                               = comp_obj_block.init;
          comp_obj_block.typeinfo.is_of_part           = "body";
          comp_obj_block.typeinfo.is_of_section        = "body";
          comp_obj_block.typeinfo.is_of_type           = "block";
          comp_obj_block.typeinfo.is_a                 = "verse";
          comp_obj_block.node.ocn                      = obj_cite_digits.on;
          comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
          comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
          comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
          comp_obj_block.text                          = an_object["substantive"];
          comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
          comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
          comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
          the_document_body_section                    ~= comp_obj_block;
        }
        object_reset(an_object);
        processing.remove("verse");
        ++cntr;
      }
      object_number_poem["end"]   = obj_cite_digits.on.to!string;
      obj_type_status["blocks"]     = TriState.closing;
      obj_type_status["poem"]       = TriState.closing;
      obj_type_status["curly_poem"] = TriState.off;
    } else {
      processing["verse"] ~= line ~= "\n";
      if (obj_type_status["verse_new"] == State.on) {
        obj_cite_digits = ocn_emit(OCNstatus.on);
        obj_type_status["verse_new"] = State.off;
      } else if (line.matchFirst(rgx.newline_eol_delimiter_only)) {
        processing["verse"] = processing["verse"].stripRight;
        verse_line = TriState.off;
        obj_type_status["verse_new"] = State.on;
      }
      if (obj_type_status["verse_new"] == State.on) {
        verse_line=1;
        an_object[an_object_key] = processing["verse"];
        debug(poem) {                          // poem verse
          writefln(
            "* %s curly\n%s",
            obj_cite_digits.on,
            an_object[an_object_key]
          );
        }
        processing.remove("verse");
        an_object["is"]                           = "verse";
        auto comp_obj_location = node_construct.node_location_emitter(
          content_non_header,
          segment_anchor_tag_that_object_belongs_to,
          obj_cite_digits,
          cntr,
          heading_ptr-1,
          an_object["is"]
        );
        auto substantive_obj_misc_tuple
          = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
        an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
        anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
        comp_obj_block                               = comp_obj_block.init;
        comp_obj_block.typeinfo.is_of_part           = "body";
        comp_obj_block.typeinfo.is_of_section        = "body";
        comp_obj_block.typeinfo.is_of_type           = "block";
        comp_obj_block.typeinfo.is_a                 = "verse";
        comp_obj_block.node.ocn                      = obj_cite_digits.on;
        comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
        comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
        comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
        comp_obj_block.text                          = an_object["substantive"];
        comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
        comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
        comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
        the_document_body_section                    ~= comp_obj_block;
        object_reset(an_object);
        processing.remove("verse");
        ++cntr;
      }
    }
  } else if (obj_type_status["tic_poem"] == TriState.on) {
    if (auto m = line.matchFirst(rgx.block_tic_close)) { // tic_poem_close
      an_object[an_object_key]="verse";
      debug(poem) {                                       // poem (curly) close
        writefln(
          "* [poem tic] %s",
          line
        );
      }
      if (processing.length > 0) {
        an_object[an_object_key] = processing["verse"];
      }
      if (an_object.length > 0) {
        debug(poem) {                                     // poem (tic) close
          writeln(__LINE__);
          writeln(obj_cite_digits.on, line);
        }
        processing.remove("verse");
        an_object["is"]                           = "verse";
        auto substantive_obj_misc_tuple
          = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
        an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
        anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
        comp_obj_block                               = comp_obj_block.init;
        comp_obj_block.typeinfo.is_of_part           = "body";
        comp_obj_block.typeinfo.is_of_section        = "body";
        comp_obj_block.typeinfo.is_of_type           = "block";
        comp_obj_block.typeinfo.is_a                 = "verse";
        comp_obj_block.node.ocn                      = obj_cite_digits.on;
        comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
        comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
        comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
        comp_obj_block.text                          = an_object["substantive"];
        comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
        comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
        comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
        the_document_body_section                    ~= comp_obj_block;
        object_number_poem["end"]                    = obj_cite_digits.on.to!string;
        object_reset(an_object);
        processing.remove("verse");
        ++cntr;
      }
      obj_type_status["blocks"]   = TriState.closing;
      obj_type_status["poem"]     = TriState.closing;
      obj_type_status["tic_poem"] = TriState.off;
    } else {
      processing["verse"] ~= line ~= "\n";
      if (obj_type_status["verse_new"] == State.on) {
        obj_cite_digits = ocn_emit(OCNstatus.on);
        obj_type_status["verse_new"] = State.off;
      } else if (line.matchFirst(rgx.newline_eol_delimiter_only)) {
        processing["verse"] = processing["verse"].stripRight;
        obj_type_status["verse_new"] = State.on;
        verse_line = TriState.off;
      }
      if (obj_type_status["verse_new"] == State.on) {
        verse_line=1;
        an_object[an_object_key] = processing["verse"];
        debug(poem) {                            // poem (tic) close
          writefln(
            "* %s tic\n%s",
            obj_cite_digits.on,
            an_object[an_object_key]
          );
        }
        processing.remove("verse");
        an_object["is"]                           = "verse";
        auto comp_obj_location
          = node_construct.node_location_emitter(
            content_non_header,
            segment_anchor_tag_that_object_belongs_to,
            obj_cite_digits,
            cntr,
            heading_ptr-1,
            an_object["is"]
          );
        auto substantive_obj_misc_tuple
          = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
        an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
        anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
        comp_obj_block                               = comp_obj_block.init;
        comp_obj_block.typeinfo.is_of_part           = "body";
        comp_obj_block.typeinfo.is_of_section        = "body";
        comp_obj_block.typeinfo.is_of_type           = "block";
        comp_obj_block.typeinfo.is_a                 = "verse";
        comp_obj_block.node.ocn                      = obj_cite_digits.on;
        comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
        comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
        comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
        comp_obj_block.text                          = an_object["substantive"];
        comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
        comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
        comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
        the_document_body_section                    ~= comp_obj_block;
        object_reset(an_object);
        processing.remove("verse");
        ++cntr;
      }
    }
  }
}
#+END_SRC

***** table block                                                 :table:

there are 3 types of table markup that need to be nomalized (given a single representation) here

- curly brace block
- tic block
- special notation block

you need:
- identify the type for the munging to create uniform presentation
  - curly, tick, special
  - table heading row, bool
- present table header info in uniform way
  - table_number_of_columns, int (count)
  - table_column_widths, int[] column widths (as given or calculate average)
  - show table walls, bool
- table content marked up in uniform way

#+name: abs_functions_block_table
#+BEGIN_SRC d
void _table_block_(L,O,T,CMM)(
             L line,
  return ref O an_object,
  return ref T   obj_type_status,
  return ref CMM conf_make_meta,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (obj_type_status["curly_table"] == TriState.on) {
    if (line.matchFirst(rgx.block_curly_table_close)) {
      debug(table) {                           // table (curly) close
        writeln(line);
      }
      obj_type_status["blocks"]      = TriState.closing;
      obj_type_status["table"]       = TriState.closing;
      obj_type_status["curly_table"] = TriState.off;
    } else {
      debug(table) {                           // table
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";           // build table array (or string)
    }
  } else if (obj_type_status["curly_table_special_markup"] == TriState.on) {
    if (line.empty) {
      obj_type_status["blocks"]                     = TriState.off;
      obj_type_status["table"]                      = TriState.off;
      obj_type_status["curly_table_special_markup"] = TriState.off;
      _table_closed_make_special_notation_table_(
        line,
        an_object,
        the_document_body_section,
        obj_cite_digits,
        comp_obj_heading,
        cntr,
        obj_type_status,
        conf_make_meta,
      );
    } else {
      debug(table) {
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";
    }
  } else if (obj_type_status["tic_table"] == TriState.on) {
    if (line.matchFirst(rgx.block_tic_close)) {
      debug(table) {                           // table (tic) close
        writeln(line);
      }
      obj_type_status["blocks"]    = TriState.closing;
      obj_type_status["table"]     = TriState.closing;
      obj_type_status["tic_table"] = TriState.off;
    } else {
      debug(table) {                           // table
        writeln(line);
      }
      an_object[an_object_key] ~= line ~= "\n";           // build table array (or string)
    }
  }
}
#+END_SRC

**** special table notation, make: table

process and use an_object["table_head"] (then empty it)
- present table header info in uniform way
  - table_number_of_columns, int (count)
  - table_column_widths, int[] column widths (as given or calculate average)
  - show table walls, bool

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
void _table_closed_make_special_notation_table_(N,CMM)(
  char[]                           line,
  return ref string[string]        an_object,
  return ref ObjGenericComposite[] the_document_body_section,
  return ref N                     obj_cite_digits,
  return ref ObjGenericComposite   _comp_obj_heading,
  return ref int                   cntr,
  return ref int[string]           obj_type_status,
  CMM                              conf_make_meta
) {
    comp_obj_block = comp_obj_block.init;
    obj_cite_digits = ocn_emit(OCNstatus.on);
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        "table"
      );
    an_object["is"] = "table";
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, "body_nugget", conf_make_meta);
    an_object["substantive"]                     = substantive_obj_misc_tuple[sObj.content];
    comp_obj_block.node.ocn                      = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
    comp_obj_block                               = table_instructions(comp_obj_block, an_object["table_head"]);
    comp_obj_block                               = table_substantive_munge_special(comp_obj_block, an_object["substantive"]);
    the_document_body_section                    ~= comp_obj_block;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
}
#+END_SRC

**** block end (close an open block): line empty, block flag _makes_ :close:

***** { line empty, _make block_

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
void _block_flag_line_empty_(B,N,CMM)(
  B                                   bookindex_extract_hash,
  char[]                              line,
  return ref string[string]           an_object,
  return ref ObjGenericComposite[]    the_document_body_section,
  return ref string[][string][string] bookindex_unordered_hashes,
  return ref N                        obj_cite_digits,
  return ref ObjGenericComposite      _comp_obj_heading,
  return ref int                      cntr,
  return ref int[string]              obj_type_status,
  string[string]                      object_number_poem,
  CMM                                 conf_make_meta,
) {
  assert(
    line.empty,
    "\nline should be empty:\n  \""
    ~ line ~ "\""
  );
  assert(
    (obj_type_status["blocks"] == TriState.closing),
    "code block status: closed"
  );
  assertions_flag_types_block_status_none_or_closed(obj_type_status);
#+END_SRC

****** make: quote block

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  if (obj_type_status["quote"] == TriState.closing) {
    obj_cite_digits = ocn_emit(OCNstatus.on);
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"] = "quote";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
    an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
    anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
    comp_obj_block                               = comp_obj_block.init;
    comp_obj_block.typeinfo.is_of_part           = "body";
    comp_obj_block.typeinfo.is_of_section        = "body";
    comp_obj_block.typeinfo.is_of_type           = "block";
    comp_obj_block.typeinfo.is_a                 = "quote";
    comp_obj_block.node.ocn                      = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digit_type;
    comp_obj_block.text                          = an_object["substantive"];
    comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
    comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
    comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
    the_document_body_section                    ~= comp_obj_block;
    obj_type_status["blocks"]                    = TriState.off;
    obj_type_status["quote"]                     = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
#+END_SRC

****** make: group block

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  } else if (obj_type_status["group"] == TriState.closing) {
    obj_cite_digits = ocn_emit(OCNstatus.on);
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"] = "group";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
    an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
    anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
    comp_obj_block                               = comp_obj_block.init;
    comp_obj_block.typeinfo.is_of_part           = "body";
    comp_obj_block.typeinfo.is_of_section        = "body";
    comp_obj_block.typeinfo.is_of_type           = "block";
    comp_obj_block.typeinfo.is_a                 = "group";
    comp_obj_block.node.ocn                      = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
    comp_obj_block.text                          = an_object["substantive"];
    comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
    comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
    comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
    the_document_body_section                    ~= comp_obj_block;
    obj_type_status["blocks"]                    = TriState.off;
    obj_type_status["group"]                     = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
#+END_SRC

****** make: block

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  } else if (obj_type_status["block"] == TriState.closing) {
    obj_cite_digits = ocn_emit(OCNstatus.on);
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"] = "block";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
    an_object["substantive"]                     = substantive_obj_misc_tuple[sObj.content];
    comp_obj_block                               = comp_obj_block.init;
    comp_obj_block.typeinfo.is_of_part           = "body";
    comp_obj_block.typeinfo.is_of_section        = "body";
    comp_obj_block.typeinfo.is_of_type           = "block";
    comp_obj_block.typeinfo.is_a                 = "block";
    comp_obj_block.node.ocn                      = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digit_type;
    comp_obj_block.text                          = an_object["substantive"];
    comp_obj_block.has.inline_notes_reg          = substantive_obj_misc_tuple[sObj.notes_reg];
    comp_obj_block.has.inline_notes_star         = substantive_obj_misc_tuple[sObj.notes_star];
    comp_obj_block.has.inline_links              = substantive_obj_misc_tuple[sObj.links];
    the_document_body_section                    ~= comp_obj_block;
    obj_type_status["blocks"]                    = TriState.off;
    obj_type_status["block"]                     = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
#+END_SRC

****** make: poem

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  } else if (obj_type_status["poem"] == TriState.closing) {
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"]                           = "verse";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    comp_obj_poem_ocn                               = comp_obj_poem_ocn.init;
    comp_obj_poem_ocn.typeinfo.is_of_part           = "body";
    comp_obj_poem_ocn.typeinfo.is_of_section        = "body";
    comp_obj_poem_ocn.typeinfo.is_of_type           = "block";
    comp_obj_poem_ocn.typeinfo.is_a                 = "poem";
    comp_obj_poem_ocn.node.ocn                      = obj_cite_digits.on;
    comp_obj_poem_ocn.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string; //
    comp_obj_poem_ocn.misc.o_n_book_index           = obj_cite_digits.bkidx; //
    comp_obj_poem_ocn.misc.object_number_type       = obj_cite_digits.type;
    comp_obj_poem_ocn.text                          = "";
    the_document_body_section                       ~= comp_obj_poem_ocn;
    obj_type_status["blocks"]                       = TriState.off;
    obj_type_status["poem"]                         = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
#+END_SRC

****** make: code block

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  } else if (obj_type_status["code"] == TriState.closing) {
    obj_cite_digits = ocn_emit(OCNstatus.on);
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"] = "code";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
    an_object["substantive"] = substantive_obj_misc_tuple[sObj.content];
    anchor_tags = substantive_obj_misc_tuple[sObj.anchor_tags];
    comp_obj_code                                = comp_obj_code.init;
    comp_obj_code.typeinfo.is_of_part            = "body";
    comp_obj_code.typeinfo.is_of_section         = "body";
    comp_obj_code.typeinfo.is_of_type            = "block";
    comp_obj_code.typeinfo.is_a                  = "code";
    comp_obj_code.node.ocn                       = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
    comp_obj_code.text                           = an_object["substantive"];
    comp_obj_code.has.inline_notes_reg           = substantive_obj_misc_tuple[sObj.notes_reg];
    comp_obj_code.has.inline_notes_star          = substantive_obj_misc_tuple[sObj.notes_star];
    comp_obj_code.has.inline_links               = substantive_obj_misc_tuple[sObj.links];
    the_document_body_section                    ~= comp_obj_code;
    obj_type_status["blocks"]                    = TriState.off;
    obj_type_status["code"]                      = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
#+END_SRC

****** make: table

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
  } else if (obj_type_status["table"] == TriState.closing) {
    comp_obj_block = comp_obj_block.init;
    obj_cite_digits = ocn_emit(OCNstatus.on);
    an_object["bookindex_nugget"]
      = ("bookindex_nugget" in an_object) ? an_object["bookindex_nugget"] : "";
    bookindex_unordered_hashes
      = bookindex_extract_hash.bookindex_nugget_hash(
        an_object["bookindex_nugget"],
        obj_cite_digits,
        segment_anchor_tag_that_object_belongs_to
      );
    an_object["is"] = "table";
    auto comp_obj_location
      = node_construct.node_location_emitter(
        content_non_header,
        segment_anchor_tag_that_object_belongs_to,
        obj_cite_digits,
        cntr,
        heading_ptr-1,
        an_object["is"]
      );
    auto substantive_obj_misc_tuple
      = obj_im.obj_inline_markup_and_anchor_tags_and_misc(an_object, an_object_key, conf_make_meta);
    an_object["substantive"]                     = substantive_obj_misc_tuple[sObj.content];
    comp_obj_block                               = comp_obj_block.init;
    comp_obj_block.node.ocn                      = obj_cite_digits.on;
    comp_obj_block.misc.object_number_off        = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    comp_obj_block.misc.o_n_book_index           = obj_cite_digits.bkidx;
    comp_obj_block.misc.object_number_type       = obj_cite_digits.type;
    comp_obj_block                               = table_instructions(comp_obj_block, an_object["table_head"]);
    comp_obj_block                               = table_substantive_munge(comp_obj_block, an_object["substantive"]);
    the_document_body_section                    ~= comp_obj_block;
    obj_type_status["blocks"]                    = TriState.off;
    obj_type_status["table"]                     = TriState.off;
    object_reset(an_object);
    processing.remove("verse");
    ++cntr;
  }
#+END_SRC

***** }

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
}
#+END_SRC

*** book index                                                  :bookindex:

#+name: abs_functions_book_index
#+BEGIN_SRC d
auto _book_index_(L,I,O,T,B)(
  L      line,
  return ref I  book_idx_tmp,
  return ref O  an_object,
  return ref T  obj_type_status,
  B             opt_action,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(book_idx_tmp)    == string));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (auto m = line.match(rgx.book_index)) {
    /+ match book_index +/
    debug(bookindexmatch) {                       // book index
      writefln(
        "* [bookindex] %s\n",
        m.captures[1].to!string,
      );
    }
    an_object["bookindex_nugget"] = m.captures[1].to!string;
  } else if (auto m = line.match(rgx.book_index_open))  {
    /+ match open book_index +/
    obj_type_status["book_index"] = State.on;
    if (opt_action.backmatter && opt_action.section_bookindex) {
      book_idx_tmp = m.captures[1].to!string;
      debug(bookindexmatch) {                       // book index
        writefln(
          "* [bookindex] %s\n",
          book_idx_tmp,
        );
      }
    }
  } else if (obj_type_status["book_index"] == State.on )  {
    /+ book_index flag set +/
    if (auto m = line.match(rgx.book_index_close))  {
      obj_type_status["book_index"] = State.off;
      if (opt_action.backmatter
      && opt_action.section_bookindex) {
        an_object["bookindex_nugget"] = book_idx_tmp ~ m.captures[1].to!string;
        debug(bookindexmatch) {                     // book index
          writefln(
            "* [bookindex] %s\n",
            book_idx_tmp,
          );
        }
      }
      book_idx_tmp = "";
    } else {
      if (opt_action.backmatter
      && opt_action.section_bookindex) {
        book_idx_tmp ~= line;
      }
    }
  }
}
#+END_SRC

*** heading or paragraph                                :heading:paragraph:
**** heading found                                               :heading:

#+name: abs_functions_heading
#+BEGIN_SRC d
auto _heading_found_(L,X,H,R,T)(
  L     line,
  X     _make_unmarked_headings,
  return ref H heading_match_str,
  return ref R heading_match_rgx,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)                                    == char[]));
    static assert(is(typeof(_make_unmarked_headings) == string[]));
    static assert(is(typeof(heading_match_str)                       == string[string]));
    static assert(is(typeof(heading_match_rgx)                       == Regex!(char)[string]));
    static assert(is(typeof(obj_type_status)                         == int[string]));
  }
  static auto rgx = Rgx();
  if ((_make_unmarked_headings.length > 2)
  && (obj_type_status["make_headings"] == State.off)) {
    /+ headings found +/
    debug(headingsfound) {
      writeln(_make_unmarked_headings);
    }
    debug(headingsfound) {
      writeln(_make_unmarked_headings.length);
      writeln(_make_unmarked_headings);
    }
    switch (_make_unmarked_headings.length) {
    case 7 :
      if (!empty(_make_unmarked_headings[6])) {
        heading_match_str["h_4"]
          = "^(" ~ _make_unmarked_headings[6].to!string ~ ")";
        heading_match_rgx["h_4"]
          = regex(heading_match_str["h_4"]);
      }
      goto case;
    case 6 :
      if (!empty(_make_unmarked_headings[5])) {
        heading_match_str["h_3"]
          = "^(" ~ _make_unmarked_headings[5].to!string ~ ")";
        heading_match_rgx["h_3"]
          = regex(heading_match_str["h_3"]);
      }
      goto case;
    case 5 :
      if (!empty(_make_unmarked_headings[4])) {
        heading_match_str["h_2"]
          = "^(" ~ _make_unmarked_headings[4].to!string ~ ")";
        heading_match_rgx["h_2"]
          = regex(heading_match_str["h_2"]);
      }
      goto case;
    case 4 :
      if (!empty(_make_unmarked_headings[3])) {
        heading_match_str["h_1"]
          = "^(" ~ _make_unmarked_headings[3].to!string ~ ")";
        heading_match_rgx["h_1"]
          = regex(heading_match_str["h_1"]);
      }
      goto case;
    case 3 :
      if (!empty(_make_unmarked_headings[2])) {
        heading_match_str["h_D"]
          = "^(" ~ _make_unmarked_headings[2].to!string ~ ")";
        heading_match_rgx["h_D"]
          = regex(heading_match_str["h_D"]);
      }
      goto case;
    case 2 :
      if (!empty(_make_unmarked_headings[1])) {
        heading_match_str["h_C"]
          = "^(" ~ _make_unmarked_headings[1].to!string ~ ")";
        heading_match_rgx["h_C"]
          = regex(heading_match_str["h_C"]);
      }
      goto case;
    case 1 :
      if (!empty(_make_unmarked_headings[0])) {
        heading_match_str["h_B"]
          = "^(" ~ _make_unmarked_headings[0].to!string ~ ")";
        heading_match_rgx["h_B"]
          = regex(heading_match_str["h_B"]);
      }
      break;
    default:
      break;
    }
    obj_type_status["make_headings"] = State.on;
  }
}
#+END_SRC

**** heading make set                                            :heading:

#+name: abs_functions_heading
#+BEGIN_SRC d
auto _heading_make_set_(L,C,R,T)(
             L line,
             C line_occur,
  return ref R heading_match_rgx,
  return ref T obj_type_status
) {
  debug(asserts) {
    static assert(is(typeof(line)              == char[]));
    static assert(is(typeof(line_occur)        == int[string]));
    static assert(is(typeof(heading_match_rgx) == Regex!(char)[string]));
    static assert(is(typeof(obj_type_status)   == int[string]));
  }
  if ((obj_type_status["make_headings"] == State.on)
  && ((line_occur["para"] == State.off)
  && (line_occur["heading"] == State.off))
  && ((obj_type_status["para"] == State.off)
  && (obj_type_status["heading"] == State.off))) {
    /+ heading make set +/
    if (line.matchFirst(heading_match_rgx["h_B"])) {
      line = "B~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_C"])) {
      line = "C~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_D"])) {
      line = "D~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_1"])) {
      line = "1~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_2"])) {
      line = "2~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_3"])) {
      line = "3~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
    if (line.matchFirst(heading_match_rgx["h_4"])) {
      line = "4~ " ~ line;
      debug(headingsfound) {
        writeln(line);
      }
    }
  }
  return line;
}
#+END_SRC

**** heading match                                               :heading:

#+name: abs_functions_heading
#+BEGIN_SRC d
auto _heading_matched_(L,C,O,K,Lv,Lc,T,CMM)(
             L   line,
  return ref C   line_occur,
  return ref O   an_object,
  return ref K   an_object_key,
  return ref Lv  lv,
  return ref Lc  collapsed_lev,
  return ref T   obj_type_status,
  return ref CMM conf_make_meta,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(line_occur)      == int[string]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(an_object_key)   == string));
    static assert(is(typeof(lv)              == int[string]));
    static assert(is(typeof(collapsed_lev)   == int[string]));
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  static auto rgx = Rgx();
  if (auto m = line.match(rgx.heading)) {
    /+ heading match +/
    ++line_occur["heading"];
    obj_type_status["heading"]            = State.on;
    obj_type_status["para"]               = State.off;
    if (line.match(rgx.heading_seg_and_above)) {
      obj_type_status["biblio_section"]   = State.off;
      obj_type_status["glossary_section"] = State.off;
      obj_type_status["blurb_section"]    = State.off;
    }
    an_object[an_object_key] ~= line ~= "\n";
    an_object["lev"] ~= m.captures[1];
    assertions_doc_structure(an_object, lv); // includes most of the logic for collapsed levels
    switch (an_object["lev"]) {
    case "A":                                // Title set
      an_object[an_object_key]=(an_object[an_object_key])
        .replaceFirst(rgx.variable_doc_title,
          (conf_make_meta.meta.title_full ~ ","))
        .replaceFirst(rgx.variable_doc_author,
          conf_make_meta.meta.creator_author);
      collapsed_lev["h0"] = 0;
      an_object["lev_collapsed_number"]
        = collapsed_lev["h0"].to!string;
      lv["lv"] = DocStructMarkupHeading.h_sect_A;
      ++lv["h0"];
      lv["h1"] = State.off;
      lv["h2"] = State.off;
      lv["h3"] = State.off;
      lv["h4"] = State.off;
      lv["h5"] = State.off;
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "B":
      collapsed_lev["h1"] = collapsed_lev["h0"] + 1;
      an_object["lev_collapsed_number"]
        = collapsed_lev["h1"].to!string;
      lv["lv"] = DocStructMarkupHeading.h_sect_B;
      ++lv["h1"];
      lv["h2"] = State.off;
      lv["h3"] = State.off;
      lv["h4"] = State.off;
      lv["h5"] = State.off;
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "C":
      collapsed_lev["h2"] = collapsed_lev["h1"] + 1;
      an_object["lev_collapsed_number"]
        = collapsed_lev["h2"].to!string;
      lv["lv"] = DocStructMarkupHeading.h_sect_C;
      ++lv["h2"];
      lv["h3"] = State.off;
      lv["h4"] = State.off;
      lv["h5"] = State.off;
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "D":
      collapsed_lev["h3"] = collapsed_lev["h2"] + 1;
      an_object["lev_collapsed_number"]
        = collapsed_lev["h3"].to!string;
      lv["lv"] = DocStructMarkupHeading.h_sect_D;
      ++lv["h3"];
      lv["h4"] = State.off;
      lv["h5"] = State.off;
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "1":
      if (lv["h3"] > State.off) {
        collapsed_lev["h4"] = collapsed_lev["h3"] + 1;
      } else if (lv["h2"] > State.off) {
        collapsed_lev["h4"] = collapsed_lev["h2"] + 1;
      } else if (lv["h1"] > State.off) {
        collapsed_lev["h4"] = collapsed_lev["h1"] + 1;
      } else if (lv["h0"] > State.off) {
        collapsed_lev["h4"] = collapsed_lev["h0"] + 1;
      }
      an_object["lev_collapsed_number"]
        = collapsed_lev["h4"].to!string;
      lv["lv"] = DocStructMarkupHeading.h_text_1;
      ++lv["h4"];
      lv["h5"] = State.off;
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "2":
      if (lv["h5"] > State.off) {
        an_object["lev_collapsed_number"]
          = collapsed_lev["h5"].to!string;
      } else if (lv["h4"] > State.off) {
        collapsed_lev["h5"] = collapsed_lev["h4"] + 1;
        an_object["lev_collapsed_number"]
          = collapsed_lev["h5"].to!string;
      }
      lv["lv"] = DocStructMarkupHeading.h_text_2;
      ++lv["h5"];
      lv["h6"] = State.off;
      lv["h7"] = State.off;
      goto default;
    case "3":
      if (lv["h6"] > State.off) {
        an_object["lev_collapsed_number"]
          = collapsed_lev["h6"].to!string;
      } else if (lv["h5"] > State.off) {
        collapsed_lev["h6"] = collapsed_lev["h5"] + 1;
        an_object["lev_collapsed_number"]
          = collapsed_lev["h6"].to!string;
      }
      lv["lv"] = DocStructMarkupHeading.h_text_3;
      ++lv["h6"];
      lv["h7"] = State.off;
      goto default;
    case "4":
      if (lv["h7"] > State.off) {
        an_object["lev_collapsed_number"]
          = collapsed_lev["h7"].to!string;
      } else if (lv["h6"] > State.off) {
        collapsed_lev["h7"] = collapsed_lev["h6"] + 1;
        an_object["lev_collapsed_number"]
          = collapsed_lev["h7"].to!string;
      }
      lv["lv"] = DocStructMarkupHeading.h_text_4;
      ++lv["h7"];
      goto default;
    default:
      an_object["lev_markup_number"] = lv["lv"].to!string;
    }
    debug(heading) {                         // heading
      writeln(line.strip);
    }
  }
}
#+END_SRC

**** para match                                                     :para:

#+name: abs_functions_para
#+BEGIN_SRC d
void _para_match_(L,O,K,I,B,T,C)(
             L  line,
  return ref O  an_object,
  return ref K  an_object_key,
  return ref I  indent,
  return ref B  bullet,
  return ref T  obj_type_status,
  return ref C  line_occur,
) {
  debug(asserts) {
    static assert(is(typeof(line)            == char[]));
    static assert(is(typeof(an_object)       == string[string]));
    static assert(is(typeof(an_object_key)   == string));
    static assert(is(typeof(indent)          == int[string]));
    static assert(is(typeof(bullet)          == bool));
    static assert(is(typeof(obj_type_status) == int[string]));
    static assert(is(typeof(line_occur)     == int[string]));
  }
  static auto rgx = Rgx();
  if (line_occur["para"] == State.off) {
    line = font_faces_line(line);
    /+ para matches +/
    obj_type_status["para"] = State.on;
    an_object[an_object_key] ~= line;        // body_nugget
    indent=[
      "hang_position" : 0,
      "base_position" : 0,
    ];
    bullet = false;
    if (auto m = line.matchFirst(rgx.para_indent)) {
      debug(paraindent) {                    // para indent
        writeln(line);
      }
      indent["hang_position"] = (m.captures[1]).to!int;
      indent["base_position"] = 0;
    } else if (line.matchFirst(rgx.para_bullet)) {
      debug(parabullet) {                    // para bullet
        writeln(line);
      }
      bullet = true;
    } else if (auto m = line.matchFirst(rgx.para_indent_hang)) {
      debug(paraindenthang) {                // para indent hang
        writeln(line);
      }
      indent=[
        "hang_position" : (m.captures[1]).to!int,
        "base_position" : (m.captures[2]).to!int,
      ];
    } else if (auto m = line.matchFirst(rgx.para_bullet_indent)) {
      debug(parabulletindent) {              // para bullet indent
        writeln(line);
      }
      indent=[
        "hang_position" : (m.captures[1]).to!int,
        "base_position" : 0,
      ];
      bullet = true;
    }
    ++line_occur["para"];
  }
}
#+END_SRC

**** text font face

#+name: abs_functions_para
#+BEGIN_SRC d
auto font_faces_line(T)(
  T  textline,
) {
  static auto rgx = Rgx();
  if (textline.match(rgx.inline_faces_line)) {
    textline = (textline)
      .replaceFirst(rgx.inline_emphasis_line,   ("*{$1}*$2"))
      .replaceFirst(rgx.inline_bold_line,       ("!{$1}!$2"))
      .replaceFirst(rgx.inline_underscore_line, ("_{$1}_$2"))
      .replaceFirst(rgx.inline_italics_line,    ("/{$1}/$2"));
  }
  return textline;
}
#+END_SRC

**** tables

- number of columns
- column widths (either as given or uniform, first often different from rest)
- column aligns (as given else default left for text, check whether can default right for digits)
- table heading (auto align left)
- table walls
- TODO need to be able to align columns left or right (digits)

***** table instructions

#+name: abs_functions_table
#+BEGIN_SRC d
auto table_instructions(O,H)(
  return ref O  table_object,
  return ref H  table_head,
) {
  static auto rgx = Rgx();
  table_object.typeinfo.is_of_part      = "body";
  table_object.typeinfo.is_of_section   = "body";
  table_object.typeinfo.is_of_type      = "block";
  table_object.typeinfo.is_a            = "table";
  table_object.has.inline_notes_reg     = false;
  table_object.has.inline_notes_star    = false;
  table_object.has.inline_links         = false;
  if (auto m = table_head.matchFirst(rgx.table_head_instructions)) {
    table_object.table.heading = ((m["c_heading"].length > 0) && (m["c_heading"] == "h")) ? true : false;
    table_object.table.number_of_columns = ((m["c_num"].length > 0) && (m["c_num"].to!int > 0)) ? m["c_num"].to!int : 0; // double check, may be obsolete
    foreach (cw; m["c_widths"].matchAll(rgx.table_col_widths)) {
      auto x = cw.hit.matchFirst(rgx.table_col_widths_and_alignment);
      table_object.table.column_widths ~= x["width"].to!int;
      table_object.table.column_aligns ~= (x["align"].empty) ? "" : x["align"];
    }
  }
  return table_object;
}
#+END_SRC

***** table array munge

#+name: abs_functions_table
#+BEGIN_SRC d
auto table_array_munge(O,T)(
  return ref O  table_object,
  return ref T  table_array,
) {
  static auto rgx = Rgx();
  static auto mng = InlineMarkup();
  string _table_substantive;
  ulong col_num;
  ulong col_num_;
  ulong col_num_chk = 0;
  foreach(idx_r, row; table_array) {
    debug(table_dev) {
      writeln("row ", idx_r);
    }
    col_num_ = 0;
    if (col_num == 0
    || col_num < row.length) {
      col_num = row.length;
    }
    if (col_num_chk == 0) {
      col_num_chk = col_num;
    } else if (col_num == 1) {
      debug(table_dev) {
        writeln("table note: ");
      }
    } else if (col_num_chk != col_num) {
      debug(table_dev) {
        writeln("warning irregular number of columns: ", col_num_chk, " != ", col_num);
      }
    } else {
    }
    foreach(idx_c, col; row) {
      debug(table_dev) {
        write(idx_c, ", ");
      }
      col_num_ = idx_c;
      _table_substantive ~= col ~ mng.tc_s;
      if (idx_r == 0 && comp_obj_block.table.heading) {
      } else if (col.match(rgx.numeric_col) && idx_r == 1) { // conditions reversed to avoid: gdc compiled program run segfault
        if ((comp_obj_block.table.column_aligns.length > idx_c)
        && (comp_obj_block.table.column_aligns[idx_c].matchFirst(rgx.table_col_align_match))) {
          comp_obj_block.table.column_aligns[idx_c]
            = comp_obj_block.table.column_aligns[idx_c];
        } else if (comp_obj_block.table.column_aligns.length > idx_c) {
          comp_obj_block.table.column_aligns[idx_c] = "r";
        } else {
          comp_obj_block.table.column_aligns ~= "r";
        }
      } else if (idx_r == 1) {
        if ((comp_obj_block.table.column_aligns.length > idx_c)
        && (comp_obj_block.table.column_aligns[idx_c].matchFirst(rgx.table_col_align_match))) {
          comp_obj_block.table.column_aligns[idx_c]
            = comp_obj_block.table.column_aligns[idx_c];
        } else if (comp_obj_block.table.column_aligns.length > idx_c) {
          comp_obj_block.table.column_aligns[idx_c] = "l";
        } else {
          comp_obj_block.table.column_aligns ~= "l";
        }
      }
    }
    debug(table_dev) {
      writeln("");
    }
    if (col_num_chk > 0 && (col_num != col_num_chk)) {
    } else if (col_num == col_num_chk){
    } else {
      col_num_chk = col_num;
    }
    _table_substantive = _table_substantive.replaceFirst(rgx.table_col_separator_nl, "\n");
  }
  if (comp_obj_block.table.number_of_columns != col_num) {
    if (comp_obj_block.table.number_of_columns == 0) {
      comp_obj_block.table.number_of_columns = (col_num).to!int;
    } else {
      debug(table_dev) {
        writeln(comp_obj_block.table.number_of_columns, " != ", col_num);
      }
    }
  }
  if (table_object.table.number_of_columns == 0
  && table_object.table.column_widths.length > 0) {
      writeln(__LINE__, " ERROR");
  }
  if (table_object.table.number_of_columns > 0
  && table_object.table.column_widths.length == 0) {
    double col_w = (100.00 / table_object.table.number_of_columns);
    foreach (i; 0..table_object.table.number_of_columns) {
      table_object.table.column_widths ~= col_w;
    }
  } else if (table_object.table.number_of_columns
  != table_object.table.column_widths.length) {
    debug(table_dev) {
      writeln(m.hit); // further logic required
    }
    if (table_object.table.number_of_columns > table_object.table.column_widths.length) {
      double col_w = (100.00 - (table_object.table.column_widths).sum)
        / (table_object.table.number_of_columns - table_object.table.column_widths.length);
      foreach (i; 0..table_object.table.column_widths.length) {
        table_object.table.column_widths ~= col_w;
      }
      foreach (i; 0..(table_object.table.number_of_columns - table_object.table.column_widths.length)) {
        table_object.table.column_widths ~= col_w;
      }
    } else if (table_object.table.number_of_columns < table_object.table.column_widths.length) {
      writeln(__LINE__, " warning, ERROR");
    }
  }
  if (table_object.table.column_widths.sum > 101
  || table_object.table.column_widths.sum < 95 ) {
    writeln("sum: ", table_object.table.column_widths.sum,
      ", array: ", table_object.table.column_widths,
      ", cols: ", table_object.table.number_of_columns);
    writeln(_table_substantive);
  }
  debug(table_res) {
    writeln("aligns: ", comp_obj_block.table.column_aligns, "\n",
      "no. of columns: ", comp_obj_block.table.number_of_columns, "\n",
      "col widths: ", comp_obj_block.table.column_widths,
        " sum: ", comp_obj_block.table.column_widths.sum, "\n",
      _table_substantive);
  }
  comp_obj_block.text = _table_substantive;
  return table_object;
}
#+END_SRC

****** table array munge simple open & close

#+name: abs_functions_table
#+BEGIN_SRC d
auto table_array_munge_open_close(O,T)(
  return ref O  table_object,
  return ref T  table_array,
) {
  static auto rgx = Rgx();
  static auto mng = InlineMarkup();
  string _table_substantive;
  foreach(row; table_array) {
    foreach(col; row) {
      _table_substantive ~= mng.tc_o ~ col ~ mng.tc_c;
    }
    _table_substantive ~= "\n";
  }
  debug(table_dev) {
    writeln(_table_substantive);
  }
  comp_obj_block.text = _table_substantive;
  return table_object;
}
#+END_SRC

***** table substantive munge

#+name: abs_functions_table
#+BEGIN_SRC d
auto table_substantive_munge(O,T)(
  return ref O  table_object,
  return ref T  table_substantive,
) {
  static auto rgx = Rgx();
  static auto munge = ObjInlineMarkupMunge();
  string[] _table_rows = (table_substantive).split(rgx.table_row_delimiter);
  string[] _table_cols;
  string[][] _table;
  foreach(col; _table_rows) {
    _table_cols = col.split(rgx.table_col_delimiter);
    _table ~= _table_cols;
  }
  table_object = table_array_munge(table_object, _table);
  return table_object;
}
#+END_SRC

***** table substantive munge special

#+name: abs_functions_table
#+BEGIN_SRC d
auto table_substantive_munge_special(O,T)(
  return ref O  table_object,
  return ref T  table_substantive,
) {
  static auto rgx = Rgx();
  static auto munge = ObjInlineMarkupMunge();
  string[] _table_rows = (table_substantive).split(rgx.table_row_delimiter_special);
  string[] _table_cols;
  string[][] _table;
  foreach(col; _table_rows) {
    _table_cols = col.split(rgx.table_col_delimiter_special);
    _table ~= _table_cols;
  }
  table_object = table_array_munge(table_object, _table);
  return table_object;
}
#+END_SRC

*** function emitters                                            :emitters:
**** object                                                       :object:
***** ocn                                                           :ocn:

#+name: meta_emitters_ocn
#+BEGIN_SRC d
pure struct OCNemitter {
  int ocn_, ocn_on, ocn_on_, ocn_off, ocn_off_, ocn_bkidx, ocn_bkidx_;
  auto ocn_emitter(int ocn_status_flag) {
    OCNset ocn;
    assert(ocn_status_flag <= OCNstatus.reset);
    ocn_on = ocn_off = ocn_bkidx = 0;
    switch(ocn_status_flag) {
    case OCNstatus.reset:
      ocn_ = ocn_on_ = ocn_off_ = 1;
      ocn_bkidx_ = 0;
      break;
    case OCNstatus.on:
      ocn_ = ocn_on = ++ocn_on_;
      break;
    case OCNstatus.off:
      ocn_ = ocn_off = ++ocn_off_;
      break;
    case OCNstatus.bkidx:
      ocn_ = ocn_bkidx = ++ocn_bkidx_;
      break;
    case OCNstatus.closing:
      break;
    default:
      ocn_ = 0;
    }
    assert(ocn_ >= 0);
    ocn.digit = ocn_;
    ocn.on    = ocn_on;
    ocn.off   = ocn_off;
    ocn.bkidx = ocn_bkidx;
    ocn.type  = ocn_status_flag;
    return ocn;
  }
  invariant() {
  }
}
#+END_SRC

***** object inline markup munge                          :markup:inline:

****** { struct, inline markup munge

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
static struct ObjInlineMarkupMunge {
  string[string] obj_txt;
  int n_foot, n_foot_reg, n_foot_sp_asterisk, n_foot_sp_plus;
  string asterisks_, plus_;
  string obj_txt_out, tail, note;
  static auto rgx = Rgx();
  static auto mkup = InlineMarkup();
  int stage_reset_note_numbers = true;
  private auto initialize_note_numbers() {
    n_foot = 0;
    n_foot_reg = 0;
    n_foot_sp_asterisk = 0;
    n_foot_sp_plus = 0;
  }
#+END_SRC

******* url links including images

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  static auto url_links(Ot)(Ot obj_txt_in) {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
    static auto mng = InlineMarkup();
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_mono, (mng.mono ~ "{$1}" ~ mng.mono));
    /+ url matched +/
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_notes_al_special, ""); // TODO reinstate when special footnotes are implemented
    if (obj_txt_in.match(rgx.smid_inline_url_generic)) {
      /+ link: naked url: http://url +/
      if (obj_txt_in.match(rgx.smid_inline_link_naked_url)) {
        obj_txt_in = (obj_txt_in).replaceAll(
            rgx.smid_inline_link_naked_url,
            ("$1"
              ~ mkup.lnk_o ~ "$2" ~ mkup.lnk_c
              ~  mkup.url_o ~ "$2" ~  mkup.url_c
              ~ "$3")            // ("$1{ $2 }$2$3")
          );
      }
      /+ link with helper for endnote including the url:
           {~^ link which includes url as footnote }http://url
         maps to:
           { link which includes url as footnote }http://url~{ { http://url }http://url }~
      +/
      if (obj_txt_in.match(rgx.smid_inline_link_endnote_url_helper)) {
        obj_txt_in = (obj_txt_in)
          .replaceAll(
            rgx.smid_inline_link_endnote_url_helper_punctuated,
            (mkup.lnk_o ~ "$1" ~ mkup.lnk_c
              ~ mkup.url_o ~ "$2" ~ mkup.url_c
              ~ "~{ " ~ mkup.lnk_o ~ " $2 " ~ mkup.lnk_c
              ~ mkup.url_o ~ "$2" ~ mkup.url_c
              ~  " }~$3") // ("{ $1 }$2~{ { $2 }$2 }~$3")
          )
          .replaceAll(
            rgx.smid_inline_link_endnote_url_helper,
            (mkup.lnk_o ~ "$1" ~ mkup.lnk_c
              ~ mkup.url_o ~ "$2" ~ mkup.url_c
              ~ "~{ " ~ mkup.lnk_o ~ " $2 " ~ mkup.lnk_c
              ~ mkup.url_o ~ "$2" ~ mkup.url_c
              ~  " }~") // ("{ $1 }$2~{ { $2 }$2 }~")
          );
      }
      /+ link with regular markup:
         { linked text or image }http://url
      +/
      if (obj_txt_in.match(rgx.smid_inline_link_markup_regular)) {
        obj_txt_in = (obj_txt_in).replaceAll(
          rgx.smid_inline_link_markup_regular,
          ("$1"
            ~ mkup.lnk_o ~ "$2" ~ mkup.lnk_c
            ~  mkup.url_o ~ "$3" ~  mkup.url_c
            ~ "$4")            // ("$1{ $2 }$3$4")
        );
      }
    }
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_mono_box, ("#{$1}#"));
    return obj_txt_in;
  }
#+END_SRC

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  static auto images(Ot)(Ot obj_txt_in) {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
    static auto mng = InlineMarkup();
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_mono, (mng.mono ~ "{$1}" ~ mng.mono)); // figure
    /+ url matched +/
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_notes_al_special, ""); // TODO reinstate when special footnotes are implemented
    if (obj_txt_in.match(rgx.smid_image_generic)) { /+ images with and without links +/
      debug(images) {
        writeln("Image: ", obj_txt_in);
      }
      if (obj_txt_in.match(rgx.smid_image_with_dimensions)) {
        obj_txt_in = (obj_txt_in).replaceAll(
            rgx.smid_image_with_dimensions,
            ("$1"
              ~ mkup.img ~ "$2,w$3h$4 "
              ~ "$5")            // ("$1{ $2 }$2$3")
          );
        debug(images) {
          writeln("IMAGE with size: ", obj_txt_in); // decide on representation
        }
      } else if (obj_txt_in.match(rgx.smid_image)) {
        obj_txt_in = (obj_txt_in).replaceAll(
            rgx.smid_image,
            ("$1"
              ~ mkup.img ~ "$2,w0h0 "
              ~ "$3")            // ("$1{ $2 }$2$3")
          );
        debug(images) {
          writeln("IMAGE: ", obj_txt_in); // decide on representation
        }
      }
    }
    obj_txt_in = obj_txt_in.replaceAll(rgx.inline_mono_box, ("#{$1}#")); // figure
    return obj_txt_in;
  }
#+END_SRC

******* footnotes endnotes markup

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto footnotes_endnotes_markup_and_number_or_stars(Ot)(Ot obj_txt_in, bool reset_note_numbers) {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
    /+ endnotes (regular) +/
    bool flg_notes_reg  = false;
    bool flg_notes_star = false;
    bool flg_notes_plus = false;
    obj_txt_in = (obj_txt_in).replaceAll(
      rgx.inline_notes_curly,
      (mkup.en_a_o ~ " $1" ~ mkup.en_a_c)
    );
    if (!(stage_reset_note_numbers) && reset_note_numbers) {
      stage_reset_note_numbers = true;
    }
    if (obj_txt_in.match(rgx.inline_notes_al_gen)) {
      if (auto m = obj_txt_in.matchAll(rgx.inline_text_and_note_al_)) {
        if (stage_reset_note_numbers) {
          n_foot = 0;
          n_foot_reg = 0;
          n_foot_sp_asterisk = 0;
          n_foot_sp_plus = 0;
        }
        stage_reset_note_numbers = false;
        foreach(n; m) {
          if (n.hit.to!string.match(rgx.inline_al_delimiter_open_symbol_star)) {
            flg_notes_star =  true;
            ++n_foot_sp_asterisk;
            asterisks_ = "*";
            n_foot=n_foot_sp_asterisk;
            obj_txt_out ~= n.hit.to!string.replaceFirst(
              rgx.inline_al_delimiter_open_symbol_star,
              (mkup.en_a_o ~ replicate(asterisks_, n_foot_sp_asterisk) ~ " ")
            ) ~ "\n";
          } else if (n.hit.to!string.match(rgx.inline_al_delimiter_open_symbol_plus)) {
            flg_notes_plus =  true;
            ++n_foot_sp_plus;
            plus_ = "*";
            n_foot=n_foot_sp_plus;
            obj_txt_out ~= n.hit.to!string.replaceFirst(
              rgx.inline_al_delimiter_open_symbol_plus,
              (mkup.en_a_o ~ replicate(plus_, n_foot_sp_plus) ~ " ")
            ) ~ "\n";
          } else if (n.hit.to!string.match(rgx.inline_al_delimiter_open_regular)) {
            flg_notes_reg =  true;
            ++n_foot_reg;
            n_foot=n_foot_reg;
            obj_txt_out ~= n.hit.to!string.replaceFirst(
              rgx.inline_al_delimiter_open_regular,
              (mkup.en_a_o ~ to!string(n_foot) ~ " ")
            ) ~ "\n";
          } else {
            obj_txt_out ~= n.hit.to!string ~ "\n";
          }
        }
      }
    } else {
      obj_txt_out = obj_txt_in;
    }
    auto t = tuple(
      obj_txt_out,
      flg_notes_reg,
      flg_notes_star,
      flg_notes_plus,
    );
    return t;
  }
#+END_SRC

******* object notes and links

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  private auto object_notes_and_links_(Ot)(Ot obj_txt_in, bool reset_note_numbers=false)
  in {
    debug(asserts) {
      assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt_out = "";
    bool urls = false;
    bool images_without_dimensions = false;
    tail = "";
    /+ special endnotes +/
    obj_txt_in = obj_txt_in.replaceAll(
      rgx.inline_notes_curly_sp_asterisk,
      (mkup.en_a_o ~ "*" ~ " $1" ~ mkup.en_a_c)
    );
    obj_txt_in
      = obj_txt_in.replaceAll(
        rgx.inline_notes_curly_sp_plus,
        (mkup.en_a_o ~ "+" ~ " $1" ~ mkup.en_a_c)
      );
    /+ image matched +/
    if (obj_txt_in.match(rgx.smid_image_generic)) {
      obj_txt_in = images(obj_txt_in);
      if (obj_txt_in.match(rgx.smid_mod_image_without_dimensions)) {
        images_without_dimensions = true;
      }
    }
    /+ url matched +/
    if (obj_txt_in.match(rgx.smid_inline_url)) {
      urls = true;
      obj_txt_in = url_links(obj_txt_in);
    }
    auto ftn = footnotes_endnotes_markup_and_number_or_stars(obj_txt_in, reset_note_numbers);
    obj_txt_out = ftn[0];
    debug(footnotes) {
      writeln(obj_txt_out, tail);
    }
    obj_txt_out = obj_txt_out ~ tail;
    debug(footnotesdone) {
      foreach(m; matchAll(obj_txt_out,
      (mkup.en_a_o ~ `\s*(.+?)` ~ mkup.en_a_c))) {
        writeln(m.captures[1]);
        writeln(m.hit);
      }
    }
    auto t = tuple(
      obj_txt_out,
      ftn[1],
      ftn[2],
      ftn[3],
      urls,
      images_without_dimensions,
    );
    return t;
  }
  auto init()
  in { }
  body {
    auto t = object_notes_and_links_("");
    return t;
  }
  invariant() {
  }
#+END_SRC

******* heading
- identified text by heading level marker followed by text until two new lines
- general markup

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto munge_heading(Ot)(Ot obj_txt_in, bool reset_note_numbers=false)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=(obj_txt_in)
     .replaceFirst(rgx.heading, "")
     .replaceFirst(rgx.object_number_off_all, "")
     .strip;
    auto t = object_notes_and_links_(obj_txt["munge"], reset_note_numbers);
    debug(munge) {
      writeln(__LINE__);
      writeln(obj_txt_in);
      writeln(__LINE__);
      writeln(obj_txt["munge"].to!string);
    }
    return t;
  }
  invariant() {
  }
#+END_SRC

******* para
- paragraph text without other identification until two new lines
- general markup
  - paragraph attributes
  - font faces (bold, italics, underscore etc.)
  - footnotes/endnotes
  - links

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto munge_para(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=(obj_txt_in)
      .replaceFirst(rgx.para_attribs, "")
      .replaceFirst(rgx.object_number_off_all, "");
    auto t = object_notes_and_links_(obj_txt["munge"]);
    debug(munge) {
      writeln(__LINE__);
      writeln(obj_txt_in);
      writeln(__LINE__);
      writeln(obj_txt["munge"].to!string);
    }
    return t;
  }
#+END_SRC

******* quote

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  string munge_quote(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
#+END_SRC

******* group
- group block identified by open an close tags
- general markup
  - paragraph attributes
  - font faces (bold, italics, underscore etc.)
  - footnotes/endnotes
  - links
- newlines detected and kept?

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto munge_group(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    auto t = object_notes_and_links_(obj_txt["munge"]);
    return t;
  }
  invariant() {
  }
#+END_SRC

******* block
- group block identified by open an close tags
- general markup
  - paragraph attributes
  - font faces (bold, italics, underscore etc.)
  - footnotes/endnotes
  - links
- newlines detected and kept?

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto munge_block(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=obj_txt_in;
    auto t = object_notes_and_links_(obj_txt["munge"]);
    return t;
  }
  invariant() {
  }
#+END_SRC

******* verse (poem)
- sub part of poem block which is identified by open an close tags, separated from other verse by double newline
- newlines
- indentation
- what part of general markup?
  - font faces (bold, italics, underscore etc.)
  - footnotes/endnotes
  - links?

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  auto munge_verse(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=obj_txt_in;
    auto t = object_notes_and_links_(obj_txt["munge"]);
    return t;
  }
  invariant() {
  }
#+END_SRC

******* code
- newlines
- indentation
- possibly identify syntax for coloring (obj attribute)
- numbered code blocks (markup/obj attribute?)
- no general markup
- one special character represented by mkup.nbsp ░

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  string munge_code(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt_in = (obj_txt_in).replaceAll(rgx.space, mkup.nbsp);
    obj_txt["munge"] = obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
#+END_SRC

******* table
- table block identified by open an close tags
- table markup

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  string munge_table(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
#+END_SRC

******* comment

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
  string munge_comment(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
#+END_SRC

****** }

#+name: meta_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
}
#+END_SRC

***** toc, tags, object inline markup                     :markup:inline:
****** {

#+name: meta_emitters_obj_inline_markup
#+BEGIN_SRC d
static struct ObjInlineMarkup {
  static auto rgx = Rgx();
  static auto munge = ObjInlineMarkupMunge();
  string[string] obj_txt;
#+END_SRC

******* object inline markup and anchor tags              :markup:inline:

#+name: meta_emitters_obj_inline_markup_and_anchor_tags_and_misc
#+BEGIN_SRC d
  auto obj_inline_markup_and_anchor_tags_and_misc(O,K,CMM)(
    O   obj_,
    K   obj_key_,
    CMM conf_make_meta,
  )
  in {
    debug(asserts) {
      static assert(is(typeof(obj_)            == string[string]));
      static assert(is(typeof(obj_key_)        == string));
    }
  }
  body {
    obj_txt["munge"] = obj_[obj_key_].dup;
    obj_txt["munge"] = (obj_["is"].match(ctRegex!(`verse|code`)))
    ? obj_txt["munge"]
    : obj_txt["munge"].strip;
    static __gshared string[] anchor_tags_ = [];
    auto x = munge.init;
    bool[string] obj_notes_and_links;
    obj_notes_and_links["notes_reg"]           = false;
    obj_notes_and_links["notes_star"]          = false;
    obj_notes_and_links["links"]               = false;
    obj_notes_and_links["image_no_dimensions"] = false;
    switch (obj_["is"]) {
    case "heading":
      static __gshared string anchor_tag = "";
      obj_txt["munge"]=_configured_auto_heading_numbering_and_segment_anchor_tags(obj_txt["munge"], obj_, conf_make_meta);
      obj_txt["munge"]=_make_segment_anchor_tags_if_none_provided(obj_txt["munge"], obj_["lev"]);
      if (auto m = obj_txt["munge"].match(rgx.heading_anchor_tag)) {
        anchor_tag = m.captures[1];
        anchor_tags_ ~= anchor_tag;
      } else if (obj_["lev"] == "1") {
        writeln("heading anchor tag missing: ", obj_txt["munge"]);
      }
      x = munge.munge_heading(obj_txt["munge"], reset_note_numbers);
      reset_note_numbers=false;
      goto default;
    case "para":
      x = munge.munge_para(obj_txt["munge"]);
      goto default;
    case "group":
      x = munge.munge_group(obj_txt["munge"]);
      goto default;
    case "block":
      x = munge.munge_block(obj_txt["munge"]);
      goto default;
    case "verse":
      x = munge.munge_verse(obj_txt["munge"]);
      goto default;
    case "code":
      obj_txt["munge"] = munge.munge_code(obj_txt["munge"]);
      break;
    case "table":
      obj_txt["munge"] = munge.munge_table(obj_txt["munge"]);
      break;
    case "quote":
      obj_txt["munge"] = munge.munge_quote(obj_txt["munge"]);
      break;
    case "comment":
      obj_txt["munge"] = munge.munge_comment(obj_txt["munge"]);
      break;
    case "doc_end_reset":
      munge.initialize_note_numbers();
      break;
    default:
      /+ para, heading, group, block, verse +/
      obj_txt["munge"]                  = x[0];
      obj_notes_and_links["notes_reg"]  = x[1];
      obj_notes_and_links["notes_star"] = x[2];
      obj_notes_and_links["notes_plus"] = x[3];
      obj_notes_and_links["links"]      = x[4];
      obj_notes_and_links["image_no_dimensions"] = x[5];
      break;
    }
    auto t = tuple(
      obj_txt["munge"],
      anchor_tags_,
      obj_notes_and_links["notes_reg"],
      obj_notes_and_links["notes_star"],
      obj_notes_and_links["links"],
      obj_notes_and_links["image_no_dimensions"],
    );
    anchor_tags_=[];
    return t;
  }
  invariant() {
  }
#+END_SRC

******* toc (table of contents), build, gather headings   :markup:inline:

#+name: meta_emitters_obj_inline_markup_table_of_contents
#+BEGIN_SRC d
  auto _clean_heading_toc_(Toc)(
    Toc heading_toc_,
  ) {
   debug(asserts) {
     static assert(is(typeof(heading_toc_) == char[]));
   }
   auto m = (cast(char[]) heading_toc_).matchFirst(rgx.heading);
   heading_toc_ = (m.post).replaceAll(
     rgx.inline_notes_curly_gen,
     "");
   return heading_toc_;
  };
  auto table_of_contents_gather_headings(O,CMM,Ts,Ta,X,Toc)(
    O            obj_,
    CMM          conf_make_meta,
    Ts           segment_anchor_tag_that_object_belongs_to,
    Ta           _anchor_tag,
    return ref X lev4_subtoc,
    Toc          the_table_of_contents_section,
  )
  in {
    debug(asserts) {
      static assert(is(typeof(obj_)                                      == string[string]));
      static assert(is(typeof(segment_anchor_tag_that_object_belongs_to) == string));
      static assert(is(typeof(_anchor_tag)                               == string));
      static assert(is(typeof(lev4_subtoc)                               == string[][string]));
      static assert(is(typeof(the_table_of_contents_section)             == ObjGenericComposite[][string]));
    }
  }
  body {
    ObjGenericComposite comp_obj_toc;
    mixin InternalMarkup;
    static auto mkup = InlineMarkup();
    char[] heading_toc_ = (obj_["substantive"].dup.strip.to!(char[])).replaceAll(rgx.inline_notes_al, "");
    heading_toc_ = _clean_heading_toc_(heading_toc_);
    auto attrib="";
    string toc_txt_, subtoc_txt_;
    int[string] indent;
    if (obj_["lev_markup_number"].to!int > 0) {
      indent=[
        "hang_position" : obj_["lev_markup_number"].to!int,
        "base_position" : obj_["lev_markup_number"].to!int,
      ];
      toc_txt_ = format(
        "{ %s }#%s",
        heading_toc_,
        _anchor_tag,
      );
      toc_txt_= munge.url_links(toc_txt_);
      comp_obj_toc                            = comp_obj_toc.init;
      comp_obj_toc.typeinfo.is_of_part        = "frontmatter";
      comp_obj_toc.typeinfo.is_of_section     = "toc";
      comp_obj_toc.typeinfo.is_of_type        = "para";
      comp_obj_toc.typeinfo.is_a              = "toc";
      comp_obj_toc.node.ocn                   = 0;
      comp_obj_toc.misc.object_number_off     = "";
      comp_obj_toc.misc.object_number_type    = 0;
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.attrib.bullet              = false;
      comp_obj_toc.text                       = toc_txt_.to!string.strip;
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["scroll"] ~= comp_obj_toc;
    } else {
      indent=[
        "hang_position" : 0,
        "base_position" : 0,
      ];
      comp_obj_toc                            = comp_obj_toc.init;
      comp_obj_toc.typeinfo.is_of_part        = "frontmatter";
      comp_obj_toc.typeinfo.is_of_section     = "toc";
      comp_obj_toc.typeinfo.is_of_type        = "para";
      comp_obj_toc.typeinfo.is_a              = "toc";
      comp_obj_toc.node.ocn                   = 0;
      comp_obj_toc.misc.object_number_off     = "";
      comp_obj_toc.misc.object_number_type    = 0;
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.attrib.bullet              = false;
      comp_obj_toc.text                       = "Table of Contents";
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["scroll"] ~= comp_obj_toc;
    }
    comp_obj_toc                              = comp_obj_toc.init;
    comp_obj_toc.typeinfo.is_of_part          = "frontmatter";
    comp_obj_toc.typeinfo.is_of_section       = "toc";
    comp_obj_toc.typeinfo.is_of_type          = "para";
    comp_obj_toc.typeinfo.is_a                = "toc";
    comp_obj_toc.node.ocn                     = 0;
    comp_obj_toc.misc.object_number_off       = "";
    comp_obj_toc.misc.object_number_type      = 0;
    comp_obj_toc.attrib.bullet                = false;
    comp_obj_toc.has.inline_links             = true;
    switch (obj_["lev_markup_number"].to!int) {
    case 0:
      indent=[
        "hang_position" : 0,
        "base_position" : 0,
      ];
      toc_txt_ = "{ Table of Contents }" ~ mkup.mark_internal_site_lnk ~ "toc.fnSuffix";
      toc_txt_= munge.url_links(toc_txt_);
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.text                       = toc_txt_.to!string.strip;
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["seg"]    ~= comp_obj_toc;
      break;
    case 1: .. case 3:
      indent=[
        "hang_position" : obj_["lev_markup_number"].to!int,
        "base_position" : obj_["lev_markup_number"].to!int,
      ];
      toc_txt_ = format(
        "%s",
        heading_toc_,
      );
      toc_txt_= munge.url_links(toc_txt_);
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.text                       = toc_txt_.to!string.strip;
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["seg"]    ~= comp_obj_toc;
      break;
    case 4:
      toc_txt_ = format(
        "{ %s }%s%s%s",
        heading_toc_,
        mkup.mark_internal_site_lnk,
        segment_anchor_tag_that_object_belongs_to,
        ".fnSuffix",
      );
      lev4_subtoc[segment_anchor_tag_that_object_belongs_to] = [];
      toc_txt_= munge.url_links(toc_txt_);
      indent=[
        "hang_position" : obj_["lev_markup_number"].to!int,
        "base_position" : obj_["lev_markup_number"].to!int,
      ];
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.text                       = toc_txt_.to!string.strip;
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["seg"]    ~= comp_obj_toc;
      break;
    case 5: .. case 7:
      toc_txt_ = format(
        "{ %s }%s%s%s#%s",
        heading_toc_,
        mkup.mark_internal_site_lnk,
        segment_anchor_tag_that_object_belongs_to,
        ".fnSuffix",
        _anchor_tag,
      );
      subtoc_txt_ = format(
        "{ %s }#%s",
        heading_toc_,
        _anchor_tag,
      );
      lev4_subtoc[segment_anchor_tag_that_object_belongs_to]
      ~= munge.url_links(obj_["lev_markup_number"]
           ~ "~ " ~ subtoc_txt_.to!string.strip
         );
      toc_txt_= munge.url_links(toc_txt_);
      indent=[
        "hang_position" : obj_["lev_markup_number"].to!int,
        "base_position" : obj_["lev_markup_number"].to!int,
      ];
      comp_obj_toc.attrib.indent_hang         = indent["hang_position"];
      comp_obj_toc.attrib.indent_base         = indent["base_position"];
      comp_obj_toc.text                       = toc_txt_.to!string.strip;
      comp_obj_toc.has.inline_links           = true;
      the_table_of_contents_section["seg"]    ~= comp_obj_toc;
      break;
    default:
      break;
    }
    return the_table_of_contents_section;
  }
  invariant() {
  }
#+END_SRC

******* private:

#+name: meta_emitters_obj_inline_markup_private
#+BEGIN_SRC d
private:
#+END_SRC

******** make heading number & segment anchor tags if instructed :markup:inline:segment:anchor:tags:

#+name: meta_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d
  static string _configured_auto_heading_numbering_and_segment_anchor_tags(M,O,CMM)(
    M   munge_,
    O   obj_,
    CMM conf_make_meta,
  ) {
    debug(asserts) {
      static assert(is(typeof(munge_)          == string));
      static assert(is(typeof(obj_)            == string[string]));
    }
    static __gshared int[] heading_num = [ 0, 0, 0, 0 ];
    static __gshared string heading_number_auto_composite = "";
    if (conf_make_meta.make.auto_num_top_lv) {
      if (obj_["lev_markup_number"].to!int == 0) {
        heading_num[0] = 0;
        heading_num[1] = 0;
        heading_num[2] = 0;
        heading_num[3] = 0;
        heading_number_auto_composite = "";
      }
      /+ auto_num_depth minimum 0
         (1.) default 2 (1.1.1) max 3 (1.1.1.1) implement +/
      if (
        conf_make_meta.make.auto_num_top_lv
        > obj_["lev_markup_number"].to!uint
      ) {
        heading_num[1] = 0;
        heading_num[2] = 0;
        heading_num[3] = 0;
      } else if (
        conf_make_meta.make.auto_num_top_lv
          == obj_["lev_markup_number"].to!uint
      ) {
        heading_num[0] ++;
        heading_num[1] = 0;
        heading_num[2] = 0;
        heading_num[3] = 0;
      } else if (
        conf_make_meta.make.auto_num_top_lv
          == (obj_["lev_markup_number"].to!uint - 1)
      ) {
        heading_num[1] ++;
        heading_num[2] = 0;
        heading_num[3] = 0;
      } else if (
        conf_make_meta.make.auto_num_top_lv
          == (obj_["lev_markup_number"].to!uint - 2)
      ) {
        heading_num[2] ++;
        heading_num[3] = 0;
      } else if (
        conf_make_meta.make.auto_num_top_lv
          == (obj_["lev_markup_number"].to!uint - 3)
      ) {
        heading_num[3] ++;
      }
      if (heading_num[3] > 0) {
        heading_number_auto_composite
          = (conf_make_meta.make.auto_num_depth.to!uint == 3)
          ? ( heading_num[0].to!string ~ "."
              ~ heading_num[1].to!string ~ "."
              ~ heading_num[2].to!string ~ "."
              ~ heading_num[3].to!string
            )
          : "";
      } else if (heading_num[2] > 0) {
        heading_number_auto_composite
          = ((conf_make_meta.make.auto_num_depth.to!uint >= 2)
          && (conf_make_meta.make.auto_num_depth.to!uint <= 3))
          ?  ( heading_num[0].to!string ~ "."
               ~ heading_num[1].to!string ~ "."
               ~ heading_num[2].to!string
             )
          : "";
      } else if (heading_num[1] > 0) {
        heading_number_auto_composite
          = ((conf_make_meta.make.auto_num_depth.to!uint >= 1)
          && (conf_make_meta.make.auto_num_depth.to!uint <= 3))
          ? ( heading_num[0].to!string ~ "."
               ~ heading_num[1].to!string
             )
          : "";
      } else if (heading_num[0] > 0) {
        heading_number_auto_composite
          = ((conf_make_meta.make.auto_num_depth.to!uint >= 0)
          && (conf_make_meta.make.auto_num_depth.to!uint <= 3))
          ?  (heading_num[0].to!string)
          : "";
      } else {
        heading_number_auto_composite = "";
      }
      debug(heading_number_auto) {
        writeln(heading_number_auto_composite);
      }
      if ((!empty(heading_number_auto_composite))
      && (obj_["lev_markup_number"].to!uint >= conf_make_meta.make.auto_num_top_lv)) {
        munge_=(munge_)
        .replaceFirst(rgx.heading,
          "$1~$2 " ~ heading_number_auto_composite ~ ". ")
        .replaceFirst(rgx.heading_marker_missing_tag,
          "$1~" ~ heading_number_auto_composite ~ " ");
      }
    }
    return munge_;
  }
#+END_SRC

******** make segment anchor tags if not provided :markup:inline:segment:anchor:tags:

#+name: meta_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d
  static string _make_segment_anchor_tags_if_none_provided(M,Lv)(M munge_, Lv lev_) {
    debug(asserts) {
      static assert(is(typeof(munge_) == string));
      static assert(is(typeof(lev_)   == string));
    }
    if (!(munge_.match(rgx.heading_anchor_tag))) {
      if (munge_.match(rgx.heading_identify_anchor_tag)) {
        if (auto m = munge_.match(rgx.heading_extract_named_anchor_tag)) {
          munge_=(munge_).replaceFirst(
            rgx.heading_marker_missing_tag,
            "$1~" ~ m.captures[1].toLower ~ "_"  ~ m.captures[2] ~ " ");
        } else if (auto m = munge_.match(rgx.heading_extract_unnamed_anchor_tag)) {
          munge_=(munge_).replaceFirst(
            rgx.heading_marker_missing_tag,
            "$1~" ~ "s" ~ m.captures[1] ~ " ");
        }
      } else if (lev_ == "1") { // (if not successful) manufacture a unique anchor tag for lev=="1"
        static __gshared int heading_num_lev1 = 0;
        heading_num_lev1 ++;
        munge_=(munge_).replaceFirst(
          rgx.heading_marker_missing_tag,
          "$1~" ~ "x" ~ heading_num_lev1.to!string ~ " ");
      }
    }
    return munge_;
  }
#+END_SRC

******** unittests

#+name: meta_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d
  unittest {
    string txt_lev="1";
    string txt_in, txt_out;

    txt_in = "1~copyright Copyright";
    txt_out ="1~copyright Copyright";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in = "1~ 6. Writing Copyright Licenses";
    txt_out ="1~s6 6. Writing Copyright Licenses";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ 1. Reinforcing trends";
    txt_out= "1~s1 1. Reinforcing trends";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ 11 SCIENCE AS A COMMONS";
    txt_out= "1~s11 11 SCIENCE AS A COMMONS";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ Chapter 1";
    txt_out="1~chapter_1 Chapter 1";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ Chapter 1.";
    txt_out="1~chapter_1 Chapter 1.";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ Chapter 1: Done";
    txt_out="1~chapter_1 Chapter 1: Done";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in=  "1~ Chapter 11 - The Battle Over the Institutional Ecology of the Digital Environment";
    txt_out= "1~chapter_11 Chapter 11 - The Battle Over the Institutional Ecology of the Digital Environment";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ CHAPTER I.";
    txt_out="1~x1 CHAPTER I.";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);

    txt_in= "1~ CHAPTER II.";
    txt_out="1~x2 CHAPTER II.";
    assert(_make_segment_anchor_tags_if_none_provided(txt_in, txt_lev) == txt_out);
  }
#+END_SRC

****** }

#+name: meta_emitters_obj_inline_markup_close
#+BEGIN_SRC d
}
#+END_SRC

***** object attrib                                          :attributes:
****** { attributes structure open, public

#+name: meta_emitters_obj_attributes
#+BEGIN_SRC d
struct ObjAttributes {
  string[string] _obj_attrib;
#+END_SRC

******* emitter obj attributes, public

#+name: meta_emitters_obj_attributes_public
#+BEGIN_SRC d
  string obj_attributes(Oi,OR,OH)(
    Oi obj_is_,
    OR obj_raw,
    OH _comp_obj_heading,
  )
  in {
    debug(asserts) {
      static assert(is(typeof(obj_is_)           == string));
      static assert(is(typeof(obj_raw)           == string));
      static assert(is(typeof(_comp_obj_heading) == ObjGenericComposite));
    }
  }
  body {
    scope(exit) {
      destroy(obj_is_);
      destroy(obj_raw);
      destroy(_comp_obj_heading);
    }
    _obj_attrib["json"] ="{";
    switch (obj_is_) {
    case "heading":
      _obj_attrib["json"] ~= _heading(obj_raw);
      break;
    case "para":
      _obj_attrib["json"] ~= _para_and_blocks(obj_raw)
      ~ _para(obj_raw);
      break;
    case "code":
      _obj_attrib["json"] ~= _code(obj_raw);
      break;
    case "group":
      _obj_attrib["json"] ~= _para_and_blocks(obj_raw)
      ~ _group(obj_raw);
      break;
    case "block":
      _obj_attrib["json"] ~= _para_and_blocks(obj_raw)
      ~ _block(obj_raw);
      break;
    case "verse":
      _obj_attrib["json"] ~= _verse(obj_raw);
      break;
    case "quote":
      _obj_attrib["json"] ~= _quote(obj_raw);
      break;
    case "table":
      _obj_attrib["json"] ~= _table(obj_raw);
      break;
    case "comment":
      _obj_attrib["json"] ~= _comment(obj_raw);
      break;
    default:
      _obj_attrib["json"] ~= _para(obj_raw);
      break;
    }
    _obj_attrib["json"] ~=" }";
    _obj_attrib["json"]=_set_additional_values_parse_as_json(_obj_attrib["json"], obj_is_, _comp_obj_heading);
    debug(structattrib) {
      if (oa_j["is"].str() == "heading") {
        writeln(_obj_attrib["json"]);
        writeln(
          "is: ", oa_j["is"].str(),
          "; object_number: ", oa_j["object_number"].integer()
        );
      }
    }
    return _obj_attrib["json"];
  }
  invariant() {
  }
#+END_SRC

******* private

#+name: meta_emitters_obj_attributes_private
#+BEGIN_SRC d
  private:
  string _obj_attributes;
#+END_SRC

******** para & blocks

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _para_and_blocks(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    if (obj_txt_in.matchFirst(rgx.para_bullet)) {
      _obj_attributes =" \"bullet\": \"true\","
      ~ " \"indent_hang\": 0,"
      ~ " \"indent_base\": 0,";
    } else if (auto m = obj_txt_in.matchFirst(rgx.para_bullet_indent)) {
      _obj_attributes =" \"bullet\": \"true\","
      ~ " \"indent_hang\": " ~ m.captures[1].to!string ~ ","
      ~ " \"indent_base\": " ~ m.captures[1].to!string ~ ",";
    } else if (auto m = obj_txt_in.matchFirst(rgx.para_indent_hang)) {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_hang\": " ~ m.captures[1].to!string ~ ","
      ~ " \"indent_base\": " ~ m.captures[2].to!string ~ ",";
    } else if (auto m = obj_txt_in.matchFirst(rgx.para_indent)) {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_hang\": " ~ m.captures[1].to!string ~ ","
      ~ " \"indent_base\": " ~ m.captures[1].to!string ~ ",";
    } else {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_hang\": 0,"
      ~ " \"indent_base\": 0,";
    }
    return _obj_attributes;
  }
#+END_SRC

******** heading

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _heading(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"para\","
    ~ " \"is\": \"heading\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** para

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _para(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"para\","
    ~ " \"is\": \"para\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** quote

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _quote(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"quote\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** group

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _group(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"group\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** block

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _block(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"block\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** verse

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _verse(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"verse\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** code

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _code(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"code\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** table

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _table(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"table\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** comment

#+name: meta_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _comment(Ot)(Ot obj_txt_in)
  in {
    debug(asserts) {
      static assert(is(typeof(obj_txt_in) == string));
    }
  }
  body {
    _obj_attributes = " \"use\": \"comment\","
    ~ " \"of\": \"comment\","
    ~ " \"is\": \"comment\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******** set additional attribute values, parse as json

#+name: meta_emitters_obj_attributes_private_json
#+BEGIN_SRC d
  string _set_additional_values_parse_as_json(OA,Oi,OH)(
    OA _obj_attrib,
    Oi obj_is_,
    OH _comp_obj_heading,
  ) {
    debug(asserts) {
      static assert(is(typeof(_obj_attrib)       == string));
      static assert(is(typeof(obj_is_)           == string));
      static assert(is(typeof(_comp_obj_heading) == ObjGenericComposite));
    }
    JSONValue oa_j = parseJSON(_obj_attrib);
    assert(
      (oa_j.type == JSON_TYPE.OBJECT)
    );
    if (obj_is_ == "heading") {
      oa_j.object["object_number"] = _comp_obj_heading.node.ocn;
      oa_j.object["lev_markup_number"] = _comp_obj_heading.node.heading_lev_markup;
      oa_j.object["lev_collapsed_number"] = _comp_obj_heading.node.heading_lev_collapsed;
      oa_j.object["heading_ptr"]
        = _comp_obj_heading.ptr.heading;
      oa_j.object["doc_object_ptr"]
        = _comp_obj_heading.ptr.doc_object;
    }
    oa_j.object["parent_object_number"] = _comp_obj_heading.node.parent_ocn;
    oa_j.object["parent_lev_markup_number"] = _comp_obj_heading.node.parent_lev_markup;
    _obj_attrib = oa_j.toString();
    return _obj_attrib;
  }
#+END_SRC

****** }

#+name: meta_emitters_obj_attributes_private_close
#+BEGIN_SRC d
}
#+END_SRC

**** book index                                               :book:index:
***** book index nugget hash                                :hash:nugget:

#+name: meta_emitters_book_index_nugget
#+BEGIN_SRC d
struct BookIndexNuggetHash {
  string main_term, sub_term, sub_term_bits;
  int object_number_offset, object_number_endpoint;
  string[] object_numbers;
  string[][string][string] bi;
  string[][string][string] hash_nugget;
  string[] bi_main_terms_split_arr;
  string[][string][string] bookindex_nugget_hash(BI,N,S)(
    BI bookindex_section,
    N  obj_cite_digits,
    S  segment_anchor_tag,
  )
  in {
    debug(asserts) {
      static assert(is(typeof(bookindex_section) == string));
      static assert(is(typeof(obj_cite_digits.on) == int));
    }
    debug(bookindexraw) {
      if (!bookindex_section.empty) {
        writeln(
          "* [bookindex] ",
          "[", obj_cite_digits.on.to!string, ": ", segment_anchor_tag, "] ", bookindex_section
        );
      }
    }
  }
  body {
    static auto rgx = Rgx();
    if (!bookindex_section.empty) {
      auto bi_main_terms_split_arr
        = bookindex_section.split(rgx.bi_main_terms_split);
      foreach (bi_main_terms_content; bi_main_terms_split_arr) {
        auto bi_main_term_and_rest
          = bi_main_terms_content.split(rgx.bi_main_term_plus_rest_split);
        if (auto m = bi_main_term_and_rest[0].match(
          rgx.bi_term_and_object_numbers_match)
        ) {
          main_term = m.captures[1].strip;
          object_number_offset = m.captures[2].to!int;
          object_number_endpoint=(obj_cite_digits.on + object_number_offset);
          object_numbers ~= (obj_cite_digits.on.to!string ~ "-" ~ to!string(object_number_endpoint)
          ~ ":" ~ segment_anchor_tag);
        } else {
          main_term = bi_main_term_and_rest[0].strip;
          object_numbers ~= obj_cite_digits.on.to!string
          ~ ":" ~ segment_anchor_tag;
        }
        bi[main_term]["_a"] ~= object_numbers;
        object_numbers=null;
        if (bi_main_term_and_rest.length > 1) {
          auto bi_sub_terms_split_arr
            = bi_main_term_and_rest[1].split(
              rgx.bi_sub_terms_plus_object_number_offset_split
            );
          foreach (sub_terms_bits; bi_sub_terms_split_arr) {
            if (auto m = sub_terms_bits.match(rgx.bi_term_and_object_numbers_match)) {
              sub_term = m.captures[1].strip;
              object_number_offset = m.captures[2].to!int;
              object_number_endpoint=(obj_cite_digits.on + object_number_offset);
              object_numbers ~= (obj_cite_digits.on.to!string ~ " - " ~ to!string(object_number_endpoint)
              ~ ":" ~ segment_anchor_tag);
            } else {
              sub_term = sub_terms_bits.strip;
              object_numbers ~= to!string(obj_cite_digits.on)
              ~ ":" ~ segment_anchor_tag;
            }
            if (!empty(sub_term)) {
              bi[main_term][sub_term] ~= object_numbers;
            }
            object_numbers=null;
          }
        }
      }
    }
    hash_nugget = bi;
    return hash_nugget;
  }
  invariant() {
  }
}
#+END_SRC

***** book index (sort &) report indented               :report:indented:

#+name: meta_emitters_book_index_report_indented
#+BEGIN_SRC d
struct BookIndexReportIndent {
  int mkn, skn;
  auto bookindex_report_indented(BI)(
    BI bookindex_unordered_hashes
  ) {
    debug(asserts) {
      static assert(is(typeof(bookindex_unordered_hashes) == string[][string][string]));
    }
    auto mainkeys
      = bookindex_unordered_hashes.byKey.array.sort().release;
    foreach (mainkey; mainkeys) {
      debug(bookindex1) {
        writeln(mainkey);
      }
      auto subkeys
        = bookindex_unordered_hashes[mainkey].byKey.array.sort().release;
      foreach (subkey; subkeys) {
        debug(bookindex1) {
          writeln("  ", subkey);
          writeln("    ", to!string(
            bookindex_unordered_hashes[mainkey][subkey]
          ));
        }
        ++skn;
      }
      ++mkn;
    }
  }
}
#+END_SRC

***** book index (sort &) report section                 :report:section:
****** { book index struct open

#+name: meta_emitters_book_index_report_section
#+BEGIN_SRC d
struct BookIndexReportSection {
  int  mkn, skn;
  static auto rgx = Rgx();
  static auto munge = ObjInlineMarkupMunge();
#+END_SRC

******* bookindex write section

#+name: meta_emitters_book_index_report_section
#+BEGIN_SRC d
  auto bookindex_write_section(BI)(
    BI bookindex_unordered_hashes
  ) {
    debug(asserts) {
      static assert(is(typeof(bookindex_unordered_hashes) == string[][string][string]));
    }
    auto mainkeys=bookindex_unordered_hashes.byKey.array.sort().release;
    foreach (mainkey; mainkeys) {
      write("_0_1 !{", mainkey, "}! ");
      foreach (ref_; bookindex_unordered_hashes[mainkey]["_a"]) {
        auto go = (ref_).replaceAll(rgx.book_index_go, "$1");
        write(" {", ref_, "}#", go, ", ");
      }
      writeln(" \\\\");
      bookindex_unordered_hashes[mainkey].remove("_a");
      auto subkeys
        = bookindex_unordered_hashes[mainkey].byKey.array.sort().release;
      foreach (subkey; subkeys) {
        write("  ", subkey, ", ");
        foreach (ref_; bookindex_unordered_hashes[mainkey][subkey]) {
          auto go = (ref_).replaceAll(rgx.book_index_go, "$1");
          write(" {", ref_, "}#", go, ", ");
        }
        writeln(" \\\\");
        ++skn;
      }
      ++mkn;
    }
  }
#+END_SRC

******* book index (sort &) build section                :report:section:

#+name: meta_emitters_book_index_report_section
#+BEGIN_SRC d
  auto bookindex_build_abstraction_section(BI,N,B)(
    BI bookindex_unordered_hashes,
    N  obj_cite_digits,
    B  opt_action,
  ) {
    debug(asserts) {
      static assert(is(typeof(bookindex_unordered_hashes)                == string[][string][string]));
      static assert(is(typeof(obj_cite_digits.on)                        == int));
    }
    mixin SiSUnode;
    mixin InternalMarkup;
    static auto mkup = InlineMarkup();
    string type_is;
    string lev;
    int heading_lev_markup, heading_lev_collapsed;
    string attrib;
    int[string] indent;
    auto mainkeys
      = bookindex_unordered_hashes.byKey.array.sort().release;
    ObjGenericComposite[][string] bookindex_section;
    ObjGenericComposite comp_obj_heading_, comp_obj_para;
    auto node_para_int_ = node_metadata_para_int;
    auto node_para_str_ = node_metadata_para_str;
    if ((mainkeys.length > 0)
    && (opt_action.backmatter
    && opt_action.section_bookindex)) {
      string bi_tmp_seg, bi_tmp_scroll;
      string[] bi_tmp_tags;
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "bookindex";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Book Index";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "_part_book_index";
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading.has.inline_links            = true;
      bookindex_section["scroll"]                  ~= comp_obj_heading_;
      bookindex_section["seg"]                     ~= comp_obj_heading_;
      ++mkn;
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "bookindex";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Index";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "bookindex";
      comp_obj_heading_.node.marked_up_level       = "1";
      comp_obj_heading_.node.heading_lev_markup    = 4;
      comp_obj_heading_.node.heading_lev_collapsed = 2;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading.has.inline_links            = false;
      comp_obj_heading_.tags.anchor_tags           = ["bookindex"];
      bookindex_section["scroll"]                  ~= comp_obj_heading_;
      bookindex_section["seg"]                     ~= comp_obj_heading_;
      ++mkn;
      import std.array : appender;
      auto buffer = appender!(char[])();
      string[dchar] transTable = [' ' : "_"];
      foreach (mainkey; mainkeys) {
        bi_tmp_tags = [""];
        bi_tmp_scroll = "!{" ~ mainkey ~ "}! ";
        buffer.clear();
        bi_tmp_tags ~= translate(mainkey, transTable);
        bi_tmp_seg = "!{" ~ mainkey ~ "}! ";
        auto bkidx_lnk_seg(string locs) {
          string markup = "";
          if (auto m = locs.matchFirst(rgx.book_index_go_seg)) {
            markup
              = munge.url_links("{ " ~ m["link"] ~ " }"
              ~ mkup.mark_internal_site_lnk ~ m["seg"] ~ ".fnSuffix"
              ~ "#" ~ m["ocn"] ~ ", ");
          } else {
            writeln(__LINE__, ": ", locs);
          }
          return markup;
        }
        auto bkidx_lnk_scroll(string locs) {
          string markup = "";
          if (auto m = locs.matchFirst(rgx.book_index_go)) {
            markup
              = munge.url_links("{ " ~ m["link"] ~ " }"
              ~ mkup.mark_internal_site_lnk
              ~ "#" ~ m["ocn"] ~ ", ");
          } else {
            writeln(__LINE__, ": ", locs);
          }
          return markup;
        }
        foreach (ref_; bookindex_unordered_hashes[mainkey]["_a"]) {
          bi_tmp_scroll ~= bkidx_lnk_scroll(ref_);
          bi_tmp_seg ~= bkidx_lnk_seg(ref_);
        }
        bi_tmp_scroll ~= " \\\\\n    ";
        bi_tmp_seg ~= " \\\\\n    ";
        bookindex_unordered_hashes[mainkey].remove("_a");
        auto subkeys
          = bookindex_unordered_hashes[mainkey].byKey.array.sort().release;
        foreach (subkey; subkeys) {
          bi_tmp_scroll ~= subkey ~ ", ";
          buffer.clear();
          bi_tmp_tags ~= translate(subkey, transTable);
          bi_tmp_seg ~= subkey ~ ", ";
          foreach (ref_; bookindex_unordered_hashes[mainkey][subkey]) {
            bi_tmp_scroll ~= bkidx_lnk_scroll(ref_);
            bi_tmp_seg ~= bkidx_lnk_seg(ref_);
          }
          bi_tmp_scroll ~= " \\\\\n    ";
          bi_tmp_seg ~= " \\\\\n    ";
          ++skn;
        }
        bi_tmp_scroll                           = (bi_tmp_scroll).replaceFirst(rgx.trailing_linebreak, "");
        bi_tmp_seg                              = (bi_tmp_seg).replaceFirst(rgx.trailing_linebreak, "");
        comp_obj_para                           = comp_obj_para.init;
        comp_obj_para.typeinfo.is_of_part       = "backmatter";
        comp_obj_para.typeinfo.is_of_section    = "bookindex";
        comp_obj_para.typeinfo.is_of_type       = "para";
        comp_obj_para.typeinfo.is_a             = "bookindex";
        comp_obj_para.text                      = bi_tmp_scroll.to!string.strip;
        comp_obj_para.node.ocn                  = 0;
        comp_obj_para.misc.object_number_off    = "";
        comp_obj_para.misc.object_number_type   = 0;
        comp_obj_para.tags.anchor_tags          = bi_tmp_tags;
        comp_obj_para.attrib.indent_hang        = 0;
        comp_obj_para.attrib.indent_base        = 1;
        comp_obj_para.attrib.bullet             = false;
        comp_obj_para.has.inline_links          = true;
        bookindex_section["scroll"]             ~= comp_obj_para;
        comp_obj_para.text                      = bi_tmp_seg.to!string.strip;
        bookindex_section["seg"]                ~= comp_obj_para;
        ++mkn;
      }
    } else {                              // no book index, (figure out what to do here)
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.text                       = "(skip) there is no Book Index";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      bookindex_section["scroll"]                  ~= comp_obj_heading_;
      bookindex_section["seg"]                     ~= comp_obj_heading_;
    }
    auto t = tuple(bookindex_section, obj_cite_digits);
    return t;
  }
#+END_SRC

****** }

#+name: meta_emitters_book_index_report_section
#+BEGIN_SRC d
}
#+END_SRC

**** (end)notes section                                 :endnotes:section:

#+name: meta_emitters_endnotes
#+BEGIN_SRC d
struct NotesSection {
  string[string] object_notes;
  int previous_count;
  int mkn;
  static auto rgx = Rgx();
#+END_SRC

***** { gather notes for endnote section struct open

#+name: meta_emitters_endnotes
#+BEGIN_SRC d
  private auto gather_notes_for_endnote_section(
    ObjGenericComposite[] contents_am,
    string                segment_anchor_tag_that_object_belongs_to,
    int                   cntr,
  )
  in {
    assert((contents_am[cntr].typeinfo.is_a == "para")
    || (contents_am[cntr].typeinfo.is_a == "heading")
    || (contents_am[cntr].typeinfo.is_a == "quote")
    || (contents_am[cntr].typeinfo.is_a == "group")
    || (contents_am[cntr].typeinfo.is_a == "block")
    || (contents_am[cntr].typeinfo.is_a == "verse"));
    assert(cntr >= previous_count);
    previous_count=cntr;
    assert(
      (contents_am[cntr].text).match(
      rgx.inline_notes_delimiter_al_regular_number_note)
    );
  }
  body {
    mixin InternalMarkup;
    static auto mkup = InlineMarkup();
    static auto munge = ObjInlineMarkupMunge();
    foreach(
      m;
      (contents_am[cntr].text).matchAll(
        rgx.inline_notes_delimiter_al_regular_number_note
      )
    ) {
      debug(endnotes_build) {
        writeln(
          "{^{", m.captures[1], ".}^}"
          ~ mkup.mark_internal_site_lnk,
          segment_anchor_tag_that_object_belongs_to,
            ".fnSuffix#noteref_\n  ", m.captures[1], " ",
          m.captures[2]); // sometimes need segment name (segmented html & epub)
      }
      // TODO NEXT you need anchor for segments at this point ->
      object_notes["anchor"] ~= "note_" ~ m.captures[1] ~ "』";
      object_notes["notes"] ~= (segment_anchor_tag_that_object_belongs_to.empty)
      ? (munge.url_links(
          "{^{" ~ m.captures[1] ~ ".}^}#noteref_"
          ~ m.captures[1]) ~ " "
          ~ m.captures[2] ~ "』"
        )
      : (munge.url_links(
          "{^{" ~ m.captures[1] ~ ".}^}"
           ~ mkup.mark_internal_site_lnk
           ~ segment_anchor_tag_that_object_belongs_to
           ~ ".fnSuffix#noteref_"
           ~ m.captures[1]) ~ " "
           ~ m.captures[2] ~ "』"
        );
    }
    return object_notes;
  }
#+END_SRC

****** gathered notes

#+name: meta_emitters_endnotes
#+BEGIN_SRC d
  private auto gathered_notes()
  in {
  }
  body {
    string[][string] endnotes_;
    if (object_notes.length > 1) {
      endnotes_["notes"] = (object_notes["notes"].split(rgx.break_string))[0..$-1];
      endnotes_["anchor"] = (object_notes["anchor"].split(rgx.break_string))[0..$-1];
    } else {
      endnotes_["notes"] = [];
      endnotes_["anchor"] = [];
    }
    return endnotes_;
  }
#+END_SRC

****** endnote objects

#+name: meta_emitters_endnotes
#+BEGIN_SRC d
  private auto endnote_objects(N,O)(
    N              obj_cite_digits,
    O              opt_action,
  )
  in {
  }
  body {
    mixin SiSUnode;
    ObjGenericComposite[] the_endnotes_section;
    auto endnotes_ = gathered_notes();
    string type_is;
    string lev, lev_markup_number, lev_collapsed_number;
    string attrib;
    int[string] indent;
    ObjGenericComposite comp_obj_heading_;
    if ((endnotes_["notes"].length > 0)
    && (opt_action.backmatter && opt_action.section_endnotes)) {
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "endnotes";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Endnotes";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "_part_endnotes";
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      the_endnotes_section                         ~= comp_obj_heading_;
      ++mkn;
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "backmatter";
      comp_obj_heading_.typeinfo.is_of_section     = "endnotes";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "Endnotes";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.tags.segment_anchor_tag    = "endnotes";
      comp_obj_heading_.node.marked_up_level       = "1";
      comp_obj_heading_.node.heading_lev_markup    = 4;
      comp_obj_heading_.node.heading_lev_collapsed = 2;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      comp_obj_heading_.tags.anchor_tags           = ["endnotes"];
      the_endnotes_section                         ~= comp_obj_heading_;
      ++mkn;
    } else {
      comp_obj_heading_                            = comp_obj_heading_.init;
      comp_obj_heading_.typeinfo.is_of_part        = "empty";
      comp_obj_heading_.typeinfo.is_of_section     = "empty";
      comp_obj_heading_.typeinfo.is_of_type        = "para";
      comp_obj_heading_.typeinfo.is_a              = "heading";
      comp_obj_heading_.text                       = "(skip) there are no Endnotes";
      comp_obj_heading_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_heading_.node.marked_up_level       = "B";
      comp_obj_heading_.node.heading_lev_markup    = 1;
      comp_obj_heading_.node.heading_lev_collapsed = 1;
      comp_obj_heading_.node.parent_ocn            = 1;
      comp_obj_heading_.node.parent_lev_markup     = 0;
      the_endnotes_section                         ~= comp_obj_heading_;
    }
    if (opt_action.backmatter && opt_action.section_endnotes) {
      ObjGenericComposite comp_obj_endnote_;
      comp_obj_endnote_                            = comp_obj_endnote_.init;
      comp_obj_endnote_.typeinfo.is_of_part        = "backmatter";
      comp_obj_endnote_.typeinfo.is_of_section     = "endnote";
      comp_obj_endnote_.typeinfo.is_of_type        = "para";
      comp_obj_endnote_.typeinfo.is_a              = "endnote";
      comp_obj_endnote_.node.ocn                   = 0;
      comp_obj_heading_.misc.object_number_off     = "";
      comp_obj_heading_.misc.object_number_type    = 0;
      comp_obj_endnote_.attrib.indent_hang         = 0;
      comp_obj_endnote_.attrib.indent_base         = 0;
      comp_obj_endnote_.attrib.bullet              = false;
      foreach (i, endnote; endnotes_["notes"]) {
        auto     m                            = endnote.matchFirst(rgx.note_ref);
        string   notenumber                   = m.captures[1].to!string;
        string   anchor_tag                   = "note_" ~ notenumber;
        comp_obj_endnote_.tags.anchor_tags    = [ endnotes_["anchor"][i] ];
        comp_obj_endnote_.has.inline_links    = true;
        comp_obj_endnote_.text                = endnote.strip;
        the_endnotes_section                  ~= comp_obj_endnote_;
      }
    }
    auto t = tuple(the_endnotes_section, obj_cite_digits);
    return t;
  }
#+END_SRC

***** }

#+name: meta_emitters_endnotes
#+BEGIN_SRC d
}
#+END_SRC

**** bibliography                                           :bibliography:
***** { biblio struct

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
struct Bibliography {
#+END_SRC

****** biblio

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
  public JSONValue[] _bibliography_(Bi,BJ)(
    return ref Bi biblio_unsorted_incomplete,
    return ref BJ bib_arr_json
  )
  in {
    debug(asserts) {
      static assert(is(typeof(biblio_unsorted_incomplete) == string[]));
      static assert(is(typeof(bib_arr_json)               == JSONValue[]));
    }
 }
  body {
    JSONValue[] biblio_unsorted
      = _biblio_unsorted_complete_(biblio_unsorted_incomplete, bib_arr_json);
    biblio_arr_json = [];
    biblio_unsorted_incomplete = [];
    JSONValue[] biblio_sorted__ = biblio_sort(biblio_unsorted);
    biblio_debug(biblio_sorted__);
    debug(biblio0) {
      writeln("---");
      writeln("unsorted incomplete: ", biblio_unsorted_incomplete.length);
      writeln("json:                ", bib_arr_json.length);
      writeln("unsorted:            ", biblio_unsorted.length);
      writeln("sorted:              ", biblio_sorted__.length);
      int cntr;
      int[7] x;
      while (cntr < x.length) {
        writeln(cntr, ": ", biblio_sorted__[cntr]["fulltitle"]);
        cntr++;
      }
    }
    return biblio_sorted__;
  }
#+END_SRC

****** biblio unsorted complete

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
  final private JSONValue[] _biblio_unsorted_complete_(Bi,BJ)(
    Bi            biblio_unordered,
    return ref BJ bib_arr_json
  ) {
    debug(asserts) {
      static assert(is(typeof(biblio_unordered) == string[]));
      static assert(is(typeof(bib_arr_json)     == JSONValue[]));
    }
    foreach (bibent; biblio_unordered) {
      /+ update bib to include deemed_author, needed for:
        sort_bibliography_array_by_deemed_author_year_title
        either: sort on multiple fields, or; create such sort field
      +/
      JSONValue j = parseJSON(bibent);
      if (!empty(j["fulltitle"].str)) {
        if (!empty(j["author_raw"].str)) {
          j["deemed_author"]=j["author_arr"][0];
        } else if (!empty(j["editor_raw"].str)) {
          j["deemed_author"]=j["editor_arr"][0];
        }
        j["sortby_deemed_author_year_title"] = (
          j["deemed_author"].str ~
           "; " ~
           j["year"].str ~
           "; "  ~
           j["fulltitle"].str
        );
      }
      bib_arr_json ~= j;
    }
    JSONValue[] biblio_unsorted_array_of_json_objects
      = bib_arr_json.dup;
    destroy(bib_arr_json);
    return biblio_unsorted_array_of_json_objects;
  }
#+END_SRC

****** biblio sort

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
  final private JSONValue[] biblio_sort(BJ)(BJ biblio_unordered) {
    debug(asserts) {
      static assert(is(typeof(biblio_unordered) == JSONValue[]));
    }
    JSONValue[] biblio_sorted_;
    biblio_sorted_
      = sort!((a, b){
        return ((a["sortby_deemed_author_year_title"].str) < (b["sortby_deemed_author_year_title"].str));
      })(biblio_unordered).array;
    debug(bibliosorted) {
      foreach (j; biblio_sorted_) {
        if (!empty(j["fulltitle"].str)) {
          writeln(j["sortby_deemed_author_year_title"]);
        }
      }
    }
    return biblio_sorted_;
  }
#+END_SRC

****** biblio debug

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
  void biblio_debug(BJ)(BJ biblio_sorted) {
    debug(asserts) {
      static assert(is(typeof(biblio_sorted) == JSONValue[]));
    }
    debug(biblio0) {
      foreach (j; biblio_sorted) {
        if (!empty(j["fulltitle"].str)) {
          writeln(j["sortby_deemed_author_year_title"]);
        }
      }
    }
  }
#+END_SRC

***** }

#+name: meta_emitters_bibliography
#+BEGIN_SRC d
}
#+END_SRC

**** node structure metadata                     :structure:metadata:node:
***** { metadata node struct

#+name: meta_emitters_metadata
#+BEGIN_SRC d
struct NodeStructureMetadata {
  int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7;
  int obj_cite_digit;
  int[string] p_; // p_ parent_
  static auto rgx = Rgx();
#+END_SRC

****** node metadata emitter

#+name: meta_emitters_metadata
#+BEGIN_SRC d
  ObjGenericComposite node_location_emitter(Lv,Ta,N,C,P,I)(
    Lv lev_markup_number,
    Ta segment_anchor_tag,
    N  obj_cite_digits,
    C  cntr_,
    P  ptr_,
    I  is_
  )
  in {
    debug(asserts) {
      static assert(is(typeof(lev_markup_number)  == string));
      static assert(is(typeof(segment_anchor_tag) == string));
      static assert(is(typeof(obj_cite_digits.on) == int));
      static assert(is(typeof(cntr_)              == int));
      static assert(is(typeof(ptr_)               == int));
      static assert(is(typeof(is_)                == string));
    }
    assert(is_ != "heading");
    assert(obj_cite_digits.on.to!int >= 0);
  }
  body {
    assert(is_ != "heading"); // should not be necessary
    assert(obj_cite_digits.on.to!int >= 0); // should not be necessary
    if (lv7 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_4;
      p_["object_number"] = lv7;
    } else if (lv6 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_3;
      p_["object_number"] = lv6;
    } else if (lv5 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_2;
      p_["object_number"] = lv5;
    } else {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_1;
      p_["object_number"] = lv4;
    }
    ObjGenericComposite comp_obj_location;
    comp_obj_location                         = comp_obj_location.init;
    comp_obj_location.typeinfo.is_a           = is_;
    comp_obj_location.node.ocn                = obj_cite_digits.on;
    comp_obj_location.tags.segment_anchor_tag = segment_anchor_tag.to!string;
    comp_obj_location.node.parent_ocn         = p_["object_number"];
    comp_obj_location.node.parent_lev_markup  = p_["lev_markup_number"];
    debug(node) {
      if (lev_markup_number.match(rgx.levels_numbered_headings)) {
        writeln("x ", _node.to!string);
      } else {
        writeln("- ", _node.to!string);
      }
    }
    assert(comp_obj_location.node.parent_lev_markup >= 4);
    assert(comp_obj_location.node.parent_lev_markup <= 7);
    assert(comp_obj_location.node.parent_ocn >= 0);
    return comp_obj_location;
  }
  invariant() {
  }
#+END_SRC

****** node metadata emitter heading, (including most segnames & their pointers)

#+name: meta_emitters_metadata
#+BEGIN_SRC d
  ObjGenericComposite node_emitter_heading(T,L,Lm,Lc,Ta,N,C,P,LA,I,PSn,fNr,fNs,fL)(
    T   _text,
    L   lev,
    Lm  lev_markup_number,
    Lc  lev_collapsed_number,
    Ta  segment_anchor_tag,
    N   obj_cite_digits,
    C   cntr_,
    P   ptr_,
    LA  lv_ancestors_txt,
    I   is_,
    PSn html_segnames_ptr,
    fNr flag_notes_reg,
    fNs flag_notes_star,
    fL  flag_links,
  )
  in {
    debug(asserts) {
      static assert(is(typeof(_text)                == string));
      static assert(is(typeof(lev)                  == string));
      static assert(is(typeof(lev_markup_number)    == string));
      static assert(is(typeof(lev_collapsed_number) == string));
      static assert(is(typeof(segment_anchor_tag)   == string));
      static assert(is(typeof(obj_cite_digits.on)   == int));
      static assert(is(typeof(cntr_)                == int));
      static assert(is(typeof(ptr_)                 == int));
      static assert(is(typeof(lv_ancestors_txt)     == string[]));
      static assert(is(typeof(is_)                  == string));
      static assert(is(typeof(html_segnames_ptr)    == int));
    }
    assert(is_ == "heading");
    assert((obj_cite_digits.on).to!int >= 0);
    assert(
      lev_markup_number.match(rgx.levels_numbered),
      ("not a valid heading level: " ~ lev_markup_number ~ " at " ~ obj_cite_digits.on.to!string)
    );
    if (lev_markup_number.match(rgx.levels_numbered)) {
      if (lev_markup_number.to!int == 0) {
        /+ TODO first hit (of two) with this assertion failure, check, fix & reinstate
        assert(obj_cite_digits.on.to!int == 1,
          "ERROR header lev markup number is: " ~
          lev_markup_number.to!string ~
          " obj_cite_digits.on.to!int should == 1 but is: " ~
           obj_cite_digits.on.to!string ~
          "\n" ~ _text);
        +/
      }
    }
  }
  body {
    switch (lev_markup_number.to!int) {
    case 0:
      lv = DocStructMarkupHeading.h_sect_A;
      lv0 = obj_cite_digit;
      lv1=0; lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"] = 0;
      p_["object_number"] = 0;
      break;
    case 1:
      lv = DocStructMarkupHeading.h_sect_B;
      lv1 = obj_cite_digit;
      lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_sect_A;
      p_["object_number"] = lv0;
      break;
    case 2:
      lv = DocStructMarkupHeading.h_sect_C;
      lv2 = obj_cite_digit;
      lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_sect_B;
      p_["object_number"] = lv1;
      break;
    case 3:
      lv = DocStructMarkupHeading.h_sect_D;
      lv3=obj_cite_digit;
      lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_sect_C;
      p_["object_number"] = lv2;
      break;
    case 4:
      lv = DocStructMarkupHeading.h_text_1;
      lv4 = obj_cite_digit;
      lv5=0; lv6=0; lv7=0;
      if (lv3 > State.off) {
        p_["lev_markup_number"]
          = DocStructMarkupHeading.h_sect_D;
        p_["object_number"] = lv3;
      } else if (lv2 > State.off) {
        p_["lev_markup_number"]
          = DocStructMarkupHeading.h_sect_C;
        p_["object_number"] = lv2;
      } else if (lv1 > State.off) {
        p_["lev_markup_number"]
          = DocStructMarkupHeading.h_sect_B;
        p_["object_number"] = lv1;
      } else {
        p_["lev_markup_number"]
          = DocStructMarkupHeading.h_sect_A;
        p_["object_number"] = lv0;
      }
      break;
    case 5:
      lv = DocStructMarkupHeading.h_text_2;
      lv5 = obj_cite_digit;
      lv6=0; lv7=0;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_text_1;
      p_["object_number"] = lv4;
      break;
    case 6:
      lv = DocStructMarkupHeading.h_text_3;
      lv6 = obj_cite_digit;
      lv7=0;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_text_2;
      p_["object_number"] = lv5;
      break;
    case 7:
      lv = DocStructMarkupHeading.h_text_4;
      lv7 = obj_cite_digit;
      p_["lev_markup_number"]
        = DocStructMarkupHeading.h_text_3;
      p_["object_number"] = lv6;
      break;
    default:
      break;
    }
    ObjGenericComposite _comp_obj_heading_;
    _comp_obj_heading_                                  = _comp_obj_heading_.init;
    _comp_obj_heading_.typeinfo.is_of_part              = "body";
    _comp_obj_heading_.typeinfo.is_of_section           = "body";
    _comp_obj_heading_.typeinfo.is_of_type              = "para";
    _comp_obj_heading_.typeinfo.is_a                    = "heading";
    _comp_obj_heading_.text                             = _text.to!string.strip;
    _comp_obj_heading_.node.ocn                         = obj_cite_digits.on;
    _comp_obj_heading_.misc.object_number_off           = (obj_cite_digits.off==0)   ? "" : obj_cite_digits.off.to!string;
    _comp_obj_heading_.misc.object_number_type          = obj_cite_digits.type;
    _comp_obj_heading_.tags.segment_anchor_tag          = segment_anchor_tag.to!string;
    _comp_obj_heading_.node.marked_up_level             = lev;
    _comp_obj_heading_.node.heading_lev_markup          = (!(lev_markup_number.empty) ? lev_markup_number.to!int : 0);
    _comp_obj_heading_.node.heading_lev_collapsed       = (!(lev_collapsed_number.empty) ? lev_collapsed_number.to!int : 0);
    _comp_obj_heading_.node.parent_ocn                  = p_["object_number"];
    _comp_obj_heading_.node.parent_lev_markup           = p_["lev_markup_number"];
    _comp_obj_heading_.tags.heading_ancestors_text      = lv_ancestors_txt;
    _comp_obj_heading_.ptr.doc_object                   = cntr_;
    _comp_obj_heading_.ptr.html_segnames                = ((lev_markup_number == "4") ? html_segnames_ptr : 0);
    _comp_obj_heading_.ptr.heading                      = ptr_;
    _comp_obj_heading_.has.inline_notes_reg             = flag_notes_reg;
    _comp_obj_heading_.has.inline_notes_star            = flag_notes_star;
    _comp_obj_heading_.has.inline_links                 = flag_links;
    debug(node) {
      if (lev_markup_number.match(rgx.levels_numbered_headings)) {
        writeln("* ", _node.to!string);
      }
    }
    debug(nodeheading) {
      if (lev_markup_number.match(rgx.levels_numbered_headings)) {
        writeln("* ", _node.to!string);
      }
    }
    assert(_comp_obj_heading_.node.parent_lev_markup <= 7);
    assert(_comp_obj_heading_.node.parent_ocn >= 0);
    if (lev_markup_number.match(rgx.levels_numbered_headings)) {
      assert(_comp_obj_heading_.node.heading_lev_markup <= 7);
      assert(_comp_obj_heading_.node.ocn >= 0);
      if (_comp_obj_heading_.node.parent_lev_markup > 0) {
        assert(_comp_obj_heading_.node.parent_lev_markup < _comp_obj_heading_.node.heading_lev_markup);
        if (_comp_obj_heading_.node.ocn != 0) {
          assert(_comp_obj_heading_.node.parent_ocn < _comp_obj_heading_.node.ocn);
        }
      }
      if (_comp_obj_heading_.node.heading_lev_markup == 0) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_sect_A);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_sect_B) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_sect_A);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_sect_C) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_sect_B);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_sect_D) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_sect_C);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_text_1) {
        assert(_comp_obj_heading_.node.parent_lev_markup <= DocStructMarkupHeading.h_sect_D);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_text_2) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_text_1);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_text_3) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_text_2);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_text_4) {
        assert(_comp_obj_heading_.node.parent_lev_markup == DocStructMarkupHeading.h_text_3);
      } else if  (_comp_obj_heading_.node.heading_lev_markup == DocStructMarkupHeading.h_text_5) {
      }
    }
    return _comp_obj_heading_;
  }
  invariant() {
  }
#+END_SRC

***** }

#+name: meta_emitters_metadata
#+BEGIN_SRC d
}
#+END_SRC

*** function assertions                                        :assertions:
**** assertions on markup document structure               :doc_structure:

#+name: abs_functions_assertions
#+BEGIN_SRC d
pure void assertions_doc_structure(O,Lv)(
  O  an_object,
  Lv lv
) {
  debug(asserts) {
    static assert(is(typeof(an_object) == string[string]));
    static assert(is(typeof(lv)        == int[string]));
  }
  if (lv["h3"] > State.off) {
    assert(lv["h0"] > State.off);
    assert(lv["h1"] > State.off);
    assert(lv["h2"] > State.off);
  } else if (lv["h2"] > State.off) {
    assert(lv["h0"] > State.off);
    assert(lv["h1"] > State.off);
    assert(lv["h3"] == State.off);
  } else if (lv["h1"] > State.off) {
    assert(lv["h0"] > State.off);
    assert(lv["h2"] == State.off);
    assert(lv["h3"] == State.off);
  } else if (lv["h0"] > State.off) {
    assert(lv["h1"] == State.off);
    assert(lv["h2"] == State.off);
    assert(lv["h3"] == State.off);
  } else {
    assert(lv["h0"] == State.off);
    assert(lv["h1"] == State.off);
    assert(lv["h2"] == State.off);
    assert(lv["h3"] == State.off);
  }
  if (lv["h7"] > State.off) {
    assert(lv["h4"] > State.off);
    assert(lv["h5"] > State.off);
    assert(lv["h6"] > State.off);
  } else if (lv["h6"] > State.off) {
    assert(lv["h4"] > State.off);
    assert(lv["h5"] > State.off);
    assert(lv["h7"] == State.off);
  } else if (lv["h5"] > State.off) {
    assert(lv["h4"] > State.off);
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  } else if (lv["h4"] > State.off) {
    assert(lv["h5"] == State.off);
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  } else {
    assert(lv["h4"] == State.off);
    assert(lv["h5"] == State.off);
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  }
  if (lv["h0"] == State.off) {
    assert(lv["h1"] == State.off);
    assert(lv["h2"] == State.off);
    assert(lv["h3"] == State.off);
    assert(lv["h4"] == State.off);
    assert(lv["h5"] == State.off);
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  }
  if (lv["h1"] == State.off) {
    assert(lv["h2"] == State.off);
    assert(lv["h3"] == State.off);
  }
  if (lv["h2"] == State.off) {
    assert(lv["h3"] == State.off);
  }
  if (lv["h3"] == State.off) {
  }
  if (lv["h4"] == State.off) {
    assert(lv["h5"] == State.off);
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  }
  if (lv["h5"] == State.off) {
    assert(lv["h6"] == State.off);
    assert(lv["h7"] == State.off);
  }
  if (lv["h6"] == State.off) {
    assert(lv["h7"] == State.off);
  }
  if (lv["h7"] == State.off) {
  }
  switch ((an_object["lev"]).to!string) {
  case "A":
    if (lv["h0"] == State.off) {
      assert(lv["h1"] == State.off);
      assert(lv["h2"] == State.off);
      assert(lv["h3"] == State.off);
      assert(lv["h4"] == State.off);
      assert(lv["h5"] == State.off);
      assert(lv["h6"] == State.off);
      assert(lv["h7"] == State.off);
    } else {                       // (lv["h0"] > State.off)
      assert(lv["h0"] == State.off,"error should not enter level A a second time");
    }
    break;
  case "B":
    if (lv["h1"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h2"] == State.off);
      assert(lv["h3"] == State.off);
    } else {                       // (lv["h1"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h1"] > State.off);
    }
    break;
  case "C":
    if (lv["h2"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h1"] > State.off);
      assert(lv["h3"] == State.off);
    } else {                       // (lv["h2"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h1"] > State.off);
      assert(lv["h2"] > State.off);
    }
    break;
  case "D":
    if (lv["h3"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h1"] > State.off);
      assert(lv["h2"] > State.off);
    } else {                      // (lv["h3"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h1"] > State.off);
      assert(lv["h2"] > State.off);
      assert(lv["h3"] > State.off);
    }
    break;
  case "1":
    if (lv["h4"] == State.off) {
      assert(lv["h0"] > State.off);
    } else {                      // (lv["h4"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
    }
    break;
  case "2":
    if (lv["h5"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
    } else {                      // (lv["h5"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
      assert(lv["h5"] > State.off);
    }
    break;
  case "3":
    if (lv["h6"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
      assert(lv["h5"] > State.off);
    } else {                      // (lv["h6"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
      assert(lv["h5"] > State.off);
      assert(lv["h6"] > State.off);
    }
    break;
  case "4":
    if (lv["h7"] == State.off) {
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
      assert(lv["h5"] > State.off);
      assert(lv["h6"] > State.off);
    } else {                      // (lv["h7"] > State.off)
      assert(lv["h0"] > State.off);
      assert(lv["h4"] > State.off);
      assert(lv["h5"] > State.off);
      assert(lv["h6"] > State.off);
      assert(lv["h7"] > State.off);
    }
    break;
  default:
    break;
  }
}
#+END_SRC

**** assertions on blocks                                         :blocks:

#+name: abs_functions_assertions
#+BEGIN_SRC d
pure void assertions_flag_types_block_status_none_or_closed(T)(T obj_type_status) {
  debug(asserts) {
    static assert(is(typeof(obj_type_status) == int[string]));
  }
  assert(
    (obj_type_status["code"] == TriState.off)
    || (obj_type_status["code"] == TriState.closing),
    "code block status: off or closing");
  assert(
    (obj_type_status["poem"] == TriState.off)
    || (obj_type_status["poem"] == TriState.closing),
    "poem status: off or closing");
  assert(
    (obj_type_status["table"] == TriState.off)
    || (obj_type_status["table"] == TriState.closing),
    "table status: off or closing");
  assert(
    (obj_type_status["group"] == TriState.off)
    || (obj_type_status["group"] == TriState.closing),
    "group block status: off or closing");
  assert(
    (obj_type_status["block"] == TriState.off)
    || (obj_type_status["block"] == TriState.closing),
    "block status: off or closing");
}
#+END_SRC

*** doc sect keys seq

#+name: template_doc_sect_keys_seq
#+BEGIN_SRC d
template docSectKeysSeq() {
  auto docSectKeysSeq(string[][string] document_section_keys_sequenced) {
    struct doc_sect_keys_seq {
      auto seg() {
        return document_section_keys_sequenced["seg"];
      }
      auto scroll() {
        return document_section_keys_sequenced["scroll"];
      }
      auto sql() {
        return document_section_keys_sequenced["sql"];
      }
    }
    return doc_sect_keys_seq();
  }
}
#+END_SRC

* 2. Object Setter (Set Abstract Object)        :module:sdp:meta_object_setter:

set abstracted objects for downstream processing

** 0. module template

#+BEGIN_SRC d :tangle ../src/sdp/meta/object_setter.d
/++
  object setter:
  setting of sisu objects for downstream processing
  meta_object_setter.d
+/
module sdp.meta.object_setter;
template ObjectSetter() {
  /+ structs +/
  <<meta_structs_init>>
}
#+END_SRC

** 1. initialize structs                                            :struct:
*** heading attribute

#+BEGIN_SRC d
struct HeadingAttrib {
  string lev                            = "9";
  int    heading_lev_markup             = 9;
  int    heading_lev_collapsed          = 9;
  int[]  closes_lev_collapsed           = [];
  int[]  closes_lev_markup              = [];
  int    array_ptr                      = 0;
  int    heading_array_ptr_segments     = 0;
}
#+END_SRC

*** [#A] _composite object_

#+name: meta_structs_init
#+BEGIN_SRC d
struct DocObj_TypeInfo_ {                                   // typeinfo
  string                 is_of_part                         = ""; // frontmatter, body, backmatter
  string                 is_of_section                      = ""; // toc, body, glossary, biography, book index, blurb
  string                 is_of_type                         = ""; // para, block ?
  string                 is_a                               = ""; // heading, para, table, code block, group, ...
  alias                  of_part                            = is_of_part;
  alias                  of_section                         = is_of_section;
  alias                  is_of                              = is_of_type;
}
struct DocObj_TxtAttrib_ {                                  // attrib
  int                    indent_base                        = 0;
  int                    indent_hang                        = 0;
  bool                   bullet                             = false;
  string                 language                           = ""; // not implemented, consider
}
struct DocObj_Has_ {                                        // has
  bool                   inline_links                       = false;
  bool                   inline_notes_reg                   = false;
  bool                   inline_notes_star                  = false;
  bool                   contains_image_without_dimensions  = false;
}
struct DocObj_Node_ {                                       // node
  enum ONtype { none, substantive, non_substantive, glossary, bibliography, book_index, blurb, comment }
  string[string][string] node;
  int                    ocn                                = 0;
  string object_number() const @property {
    return (ocn==0)
      ? ""
      : ocn.to!string;
  }
  int                    o_n_type                           = 0;
  string                 marked_up_level                    = "9";
  int                    heading_lev_markup                 = 9;
  int                    heading_lev_collapsed              = 9;
  int[]                  dom_markedup                       = [ 0, 0, 0, 0, 0, 0, 0, 0,];
  int[]                  dom_collapsed                      = [ 0, 0, 0, 0, 0, 0, 0, 0,];
  int[]                  heading_ancestors                  = [ 0, 0, 0, 0, 0, 0, 0, 0,];
  int                    parent_lev_markup                  = 0;
  int                    parent_ocn                         = 0;
  int[]                  ancestors                          = [];
}
struct DocObj_Table_ {                                      // table
  int                    number_of_columns                  = 0;
  double[]               column_widths                      = [];
  string[]               column_aligns                      = [];
  bool                   heading                            = false;
  bool                   walls                              = false; // not implemented
}
struct DocObj_CodeBlock_ {                                  // code_block
  string                 syntax                             = "";
}
struct DocObj_Pointer_ {                                    // ptr
  int                    doc_object                         = 0;
  int                    html_segnames                      = 0;
  int                    heading                            = 0;
}
struct DocObj_Tags_ {                                       // tags
  string[]               heading_ancestors_text             = [ "", "", "", "", "", "", "", "", ];
  string                 segment_anchor_tag                 = "";
  string                 segname_prev                       = "";
  string                 segname_next                       = "";
  string[]               lev4_subtoc                        = [];
  string[]               anchor_tags                        = [];
}
struct DocObj_Misc_ {                                       // misc
  int                    o_n_substantive                    = 0;
  int                    o_n_non_substantive                = 0;
  int                    o_n_glossary                       = 0;
  int                    o_n_bibliography                   = 0;
  int                    o_n_book_index                     = 0;
  int                    o_n_blurb                          = 0;
  string object_number_substantive() const @property {
    return (o_n_substantive==0)
      ? ""
      : o_n_substantive.to!string;
  }
  string object_number_non_substantive() const @property {
    return (o_n_non_substantive==0)
      ? ""
      : o_n_non_substantive.to!string;
  }
  string object_number_glossary() const @property {
    return (o_n_glossary==0)
      ? ""
      : o_n_glossary.to!string;
  }
  string object_number_bibliography() const @property {
    return (o_n_bibliography==0)
      ? ""
      : o_n_bibliography.to!string;
  }
  string object_number_book_index() const @property {
    return (o_n_book_index==0)
      ? ""
      : o_n_book_index.to!string;
  }
  string object_number_blurb() const @property {
    return (o_n_blurb==0)
      ? ""
      : o_n_blurb.to!string;
  }
  string                 object_number_off                  = "";
  bool                   visible_object_number              = false;
  int                    object_number_type                 = 0; // { ocn, non, bkidx }
}
struct ObjGenericComposite {
  string                 text                               = "";
  DocObj_TypeInfo_       typeinfo;
  DocObj_TxtAttrib_      attrib;
  DocObj_Tags_           tags;
  DocObj_Has_            has;
  DocObj_Table_          table;
  DocObj_CodeBlock_      code_block;
  DocObj_Misc_           misc;
  DocObj_Pointer_        ptr;
  DocObj_Node_           node;
}
#+END_SRC

*** The Objects: generic composite object array

#+name: meta_structs_init
#+BEGIN_SRC d
struct TheObjects {
  ObjGenericComposite[] oca;
}
#+END_SRC

* __END__