#+TITLE: sdp document abstraction
#+AUTHOR: Ralph Amissah
#+EMAIL: ralph.amissah@gmail.com
#+STARTUP: indent
#+LANGUAGE: en
#+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:ao:
#+TAGS: assert(a) class(c) debug(d) mixin(m) sdp(s) tangle(T) template(t) WEB(W) noexport(n)

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

* Document Abstraction                                     :abstract:process:
Process markup document, create document abstraction.

** _1. pre loop processing_                                               :pre:
*** imports                                                         :imports:

[[./ao_defaults.org][ao_defaults]]

#+name: abs_imports
#+BEGIN_SRC d
import
  ao_defaults,                  // sdp/ao_defaults.d
  ao_object_setter,             // sdp/ao_object_setter.d
  ao_rgx;                       // sdp/ao_rgx.d
#+END_SRC

*** mixins                                                           :mixins:

#+name: abs_mixins
#+BEGIN_SRC d
mixin ObjectSetter;
mixin InternalMarkup;
#+END_SRC

*** initialize                                                   :initialize:

#+name: abs_init_struct
#+BEGIN_SRC d
/+ initialize +/
auto rgx = Rgx();
ObjComposite[] contents_the_objects;
string[string] an_object, processing;
string[] anchor_tags;
string segment_object_belongs_to;
auto set_abstract_object = ObjectAbstractSet();
auto note_section = NotesSection();
/+ enum +/
enum State { off, on }
enum TriState { off, on, closing } // make aware, possibility of third state
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 DocStructCollapsedHeading { lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7 } // not yet used
/+ 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 +/
long counter, previous_count;
int[string] line_occur;
int verse_line, heading_pointer;
/+ paragraph attributes +/
string[string] indent;
bool bullet = true;
string content_non_header = "8";
auto obj_im = ObjInlineMarkup();
auto obj_att = ObjAttributes();
/+ ocn +/
int obj_cite_number, obj_cite_number_;
auto object_citation_number = OCNemitter();
int obj_cite_number_emit(int obj_cite_number_status_flag) {
  return object_citation_number.obj_cite_number_emitter(obj_cite_number_status_flag);
}
/+ book index variables +/
string book_idx_tmp;
string[][string][string] bookindex_unordered_hashes;
auto bookindex_extract_hash = BookIndexNuggetHash();
string[][string][string] bkidx_hash(string bookindex_section, int obj_cite_number) {
  return bookindex_extract_hash.bookindex_nugget_hash(bookindex_section, obj_cite_number);
}
/+ node +/
string _node;
auto node_construct = NodeStructureMetadata();
#+END_SRC

*** scope

#+name: abs_init_rest
#+BEGIN_SRC d
scope(success) {
}
scope(failure) {
}
scope(exit) {
  destroy(contents_the_objects);
  destroy(an_object);
  destroy(processing);
  destroy(biblio_arr_json);
}
#+END_SRC

*** init rest

#+name: abs_init_rest
#+BEGIN_SRC d
line_occur = [
  "heading" : 0,
  "para"    : 0,
];
auto type = flags_type_init;
void tell_lo(int obj_cite_number, in char[] line) {
  writefln(
    "* %s %s",
    to!string(obj_cite_number),
    to!string(line)
  );
}
string[string] obj_cite_number_poem = [
  "start" : "",
  "end"   : ""
];
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_collapsed_number" : 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)")
];
#+END_SRC

** _2. 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 = replaceAll(line, 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 obj_cite_number is on or turned off                   :ocn:

#+name: abs_in_loop_body_00
#+BEGIN_SRC d
if (!line.empty) {
  _check_obj_cite_number_status_(line, type);
}
#+END_SRC

*** [#A] separate regular markup text from code blocks [+5]
**** code blocks                                                 :block:code:

#+name: abs_in_loop_body_00_code_block
#+BEGIN_SRC d
if (type["code"] == TriState.on) {
  /+ block object: code +/
  _code_block_(line, an_object, type);
  continue;
#+END_SRC

**** non code objects (other blocks or regular text) [+4]          :non_code:

#+name: abs_in_loop_body_00_non_code_block
#+BEGIN_SRC d
} else if (!matchFirst(line, rgx.skip_from_regular_parse)) {
  /+ object other than "code block" object
     (includes regular text paragraph, headings & blocks other than code) +/
#+END_SRC

***** within block group [+1]                                  :block:active:
****** within block group: biblio                                    :biblio:

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

****** within block group: poem                                        :poem:

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

****** within block group: group                                      :group:

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

****** within block group: block                                      :block:

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

****** within block group: quote                                      :quote:

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

****** within block group: table                                      :table:

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

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

#+name: abs_in_loop_body_non_code_obj
#+BEGIN_SRC d
} else {
  /+ not within a block group +/
#+END_SRC

****** assert

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

****** block open

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

****** line not empty [+2]

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
} else if (!line.empty) {
  /+ line not empty +/
  /+ non blocks (headings, paragraphs) & closed blocks +/
#+END_SRC

******* asserts                                                      :assert:

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

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

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
  if ((matchFirst(line, rgx.book_index))
  || (matchFirst(line, rgx.book_index_open))
  || (type["book_index"] == State.on ))  {
    /+ book_index +/
    _book_index_(line, book_idx_tmp, an_object, type);
#+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
    if (auto m = matchFirst(line, rgx.comment)) {
      /+ matched comment +/
      debug(comment) {
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";
      contents_the_objects ~=
        set_abstract_object.contents_comment(strip(an_object["obj"]));
      _header_set_common_(line_occur, an_object, type);
      processing.remove("verse");
      ++counter;
#+END_SRC

******** flag not set & line not 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))
    && ((type["para"] == State.off)
    && (type["heading"] == State.off))) {
      /+ heading or para but neither flag nor line exists +/
      if ((dochead_make_aa["make"]["headings"].length > 2)
      && (type["make_headings"] == State.off)) {
        /+ heading found +/
        _heading_found_(line, dochead_make_aa["make"]["headings"], heading_match_str, heading_match_rgx, type);
      }
      if ((type["make_headings"] == State.on)
      && ((line_occur["para"] == State.off)
      && (line_occur["heading"] == State.off))
      && ((type["para"] == State.off)
      && (type["heading"] == State.off))) {
        /+ heading make set +/
        _heading_make_set_(line, line_occur, heading_match_rgx, type);
      }
      if (matchFirst(line, rgx.heading)) {
        /+ heading match +/
        _heading_matched_(line, line_occur, an_object, lv, collapsed_lev, type, dochead_meta_aa);
      } else if (line_occur["para"] == State.off) {
        /+ para match +/
        _para_match_(line, an_object, indent, bullet, type, 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) {                         // heading
        writeln(line);
      }
      an_object["obj"] ~= 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(line);
      }
      an_object["obj"] ~= line;
      ++line_occur["para"];
    }
  }
#+END_SRC

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

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
} else if (type["blocks"] == TriState.closing) {
  /+ line empty, with blocks flag +/
  _block_flag_line_empty_(line, an_object, contents_the_objects, bookindex_unordered_hashes, obj_cite_number, _node, counter, type, obj_cite_number_poem, dochead_make_aa); // watch
#+END_SRC

****** line empty [+1]

#+name: abs_in_loop_body_not_block_obj
#+BEGIN_SRC d
} else {
  /+ line empty +/
#+END_SRC

******* assert line empty                                            :assert:

#+name: abs_in_loop_body_not_block_obj_line_empty
#+BEGIN_SRC d
/+ line.empty, post contents, empty variables: +/
assert(
  line.empty,
  "line should be empty"
);
assert(
  (type["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 ((type["heading"] == State.on)
&& (line_occur["heading"] > State.off)) {
  /+ heading object (current line empty) +/
  obj_cite_number = obj_cite_number_emit(type["obj_cite_number_status"]);
  an_object["bookindex"] =
    ("bookindex" in an_object) ? an_object["bookindex"] : "";
  bookindex_unordered_hashes =
    bkidx_hash(an_object["bookindex"], obj_cite_number);
  an_object["is"] = "heading";
  auto substantive_object_and_anchor_tags_tuple =
    obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa); // tuple this with anchor tags?
  an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
  anchor_tags = substantive_object_and_anchor_tags_tuple[1];
  if (to!int(an_object["lev_markup_number"]) == 4) {
    segment_object_belongs_to = anchor_tags[0];
  } else if (to!int(an_object["lev_markup_number"]) < 4) {
    segment_object_belongs_to = "";
  }
  _node =
    node_construct.node_emitter_heading(
      an_object["lev_markup_number"],
      an_object["lev_collapsed_number"],
      segment_object_belongs_to,
      obj_cite_number,
      counter,
      heading_pointer,
      an_object["is"]
    ); // heading
  an_object["attrib"] =
    obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
  ++heading_pointer;
  debug(segments) {
    writeln(an_object["lev_markup_number"]);
    writeln(segment_object_belongs_to);
  }
  contents_the_objects ~=
    set_abstract_object.contents_heading(
      an_object["substantive"],
      an_object["attrib"],
      obj_cite_number,
      anchor_tags,
      to!string(an_object["lev"]),
      to!int(an_object["lev_markup_number"]),
      to!int(an_object["lev_collapsed_number"]),
    );
  // track previous heading and make assertions
  debug(objectrelated1) { // check
    writeln(line);
    // writeln(an_object["obj"]);
    // writeln(contents_am[counter]["obj_cite_number"], " ", contents_am[counter]["obj"]);
    // writeln(m.hit, "\n");
  }
  _header_set_common_(line_occur, an_object, type);
  an_object.remove("lev");
  an_object.remove("lev_markup_number");
  // an_object["lev_markup_number"]="9";
  processing.remove("verse");
  ++counter;
#+END_SRC

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

#+name: abs_in_loop_body_not_block_obj_line_empty
#+BEGIN_SRC d
} else if ((type["para"] == State.on) && (line_occur["para"] > State.off)) {
  /+ paragraph object (current line empty) +/
  obj_cite_number = obj_cite_number_emit(type["obj_cite_number_status"]);
  an_object["bookindex"] =
    ("bookindex" in an_object) ? an_object["bookindex"] : "";
  bookindex_unordered_hashes =
    bkidx_hash(an_object["bookindex"], obj_cite_number);
  an_object["is"] = "para";
  _node =
    node_construct.node_emitter(
      content_non_header,
      segment_object_belongs_to,
      obj_cite_number,
      counter,
      heading_pointer-1,
      an_object["is"]
    );
  auto substantive_object_and_anchor_tags_tuple =
    obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
  an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
  anchor_tags = substantive_object_and_anchor_tags_tuple[1];
  an_object["attrib"] =
    obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
  contents_the_objects ~=
    set_abstract_object.contents_para(
      an_object["is"],
      an_object["substantive"],
      an_object["attrib"],
      obj_cite_number,
      indent,
      bullet
    );
  // contents_the_objects ~=
  //   set_abstract_object.contents_para(
  //     an_object,
  //     obj_cite_number,
  //     indent,
  //     bullet
  //   );
  _header_set_common_(line_occur, an_object, type);
  indent["hang_position"] = "0";
  indent["base_position"] = "0";
  bullet = false;
  processing.remove("verse");
  ++counter;
} else {
  assert(
    line == null,
    "line variable should be empty, should not occur"
  );
  // check what happens when paragraph separated by 2 newlines
}
#+END_SRC

*** close non code objects (regular text)

#+name: abs_in_loop_body_00_closed
#+BEGIN_SRC d
    } // close else for line empty
  } // close else for not the above
} // close after non code, other blocks or regular text
#+END_SRC

*** regular text objects                                     :text:paragraph:

#+name: abs_in_loop_body_01
#+BEGIN_SRC d
/+ unless (contents_the_objects.length == 0) ? +/
if (contents_the_objects.length > 0) {
  if (((contents_the_objects[$-1].is_a == "para")
  || (contents_the_objects[$-1].is_a == "heading"))
  && (counter-1 > previous_count)) {
    if (match(contents_the_objects[$-1].object,
    rgx.inline_notes_delimiter_al_regular_number_note)) {
      previous_count=contents_the_objects.length -1;
      note_section.gather_notes_for_endnote_section(
        contents_the_objects,
        segment_object_belongs_to,
        contents_the_objects.length -1
      );
    }
  }
}
#+END_SRC

** _3. post loop processing_                                             :post:
*** misc

#+name: abs_post
#+BEGIN_SRC d
debug(objectrelated2) { // check
    writeln(line);
}
/+
  Backmatter:
  * endnotes
  * glossary
  * references / bibliography
  * book index
+/
// TODO FIGURE OUT, you need this possibility
// obj_im.obj_inline_markup_and_anchor_tags("doc_end_reset", "", dochead_make_aa);
#+END_SRC

*** [#B] endnotes                                                  :endnotes:

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

*** [#B] bibliography                                          :bibliography:

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

*** [#B] bookindex                                               :book:index:

#+name: abs_post
#+BEGIN_SRC d
auto bi = BookIndexReportSection();
auto bi_tuple =
  bi.bookindex_build_section(bookindex_unordered_hashes, obj_cite_number, segment_object_belongs_to);
static assert(!isTypeTuple!(bi_tuple));
auto bookindex_section = bi_tuple[0];
obj_cite_number = bi_tuple[1];
debug(bookindex) {                         // bookindex
  foreach (bi_entry; bookindex_section) {
    writeln(bi_entry);
  }
}
#+END_SRC

*** [#B] document                                                  :document:

#+name: abs_post
#+BEGIN_SRC d
auto document_the =
  contents_the_objects ~ endnotes_section ~ bookindex_section;
#+END_SRC

*** misc heading

#+name: abs_post
destroy(contents_the_objects);
destroy(endnotes_section);
destroy(bookindex_section);
// struct Document {
//   char content;
//   char head_make;
//   char head_meta;
//   char bookindex_section;
//   char biblio;
// }
// struct Document {
//   char content;
//   char head_make;
//   char head_meta;
//   char bookindex_section;
//   char biblio;
// }
#+END_SRC

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

#+name: abs_post
#+BEGIN_SRC d
auto t = tuple(
  document_the,
  bookindex_unordered_hashes,
  biblio_ordered
);
return t;
#+END_SRC

* 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
auto object_reset(ref string[string] an_object) {
  an_object.remove("obj");
  an_object.remove("substantive");
  an_object.remove("is");
  an_object.remove("attrib");
  an_object.remove("bookindex");
}
#+END_SRC

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

#+name: abs_functions_header_set_common
#+BEGIN_SRC d
auto _header_set_common_(
  ref int[string] line_occur,
  ref string[string] an_object,
  ref int[string] type
) {
  // line_occur["header"] = State.off;
  line_occur["heading"] = State.off;
  line_occur["para"]= State.off;
  type["heading"] = State.off;
  type["para"] = State.off;
  object_reset(an_object);
}
#+END_SRC

** check obj_cite_number status in document                             :ocn:

#+name: abs_functions_ocn_status
#+BEGIN_SRC d
void _check_obj_cite_number_status_(
  char[] line,
  ref int[string] type
) {
  if ((!line.empty) && (type["obj_cite_number_status_multi_obj"] == TriState.off)) {
    /+ not multi-line object, check whether obj_cite_number is on or turned off +/
    if (matchFirst(line, rgx.obj_cite_number_block_marks)) {
      /+ switch off obj_cite_number +/
      if (matchFirst(line, rgx.obj_cite_number_off_block)) {
        type["obj_cite_number_status_multi_obj"] = TriState.on;
        debug(ocnoff) {
          writeln(line);
        }
      }
      if (matchFirst(line, rgx.obj_cite_number_off_block_dh)) {
        type["obj_cite_number_status_multi_obj"] = TriState.closing;
        debug(ocnoff) {
          writeln(line);
        }
      }
    } else {
      if (type["obj_cite_number_status_multi_obj"] == TriState.off) {
        if (matchFirst(line, rgx.obj_cite_number_off)) {
          type["obj_cite_number_status"] = TriState.on;
        } else if (matchFirst(line, rgx.obj_cite_number_off_dh)) {
          type["obj_cite_number_status"] = TriState.closing;
        } else {
          // type["obj_cite_number_status"] = TriState.closing;
          type["obj_cite_number_status"] = TriState.off;
        }
      } else {
        type["obj_cite_number_status"] =
          type["obj_cite_number_status_multi_obj"];
      }
    }
  } else if ((!line.empty) && (type["obj_cite_number_status_multi_obj"] > TriState.off)) {
    if (matchFirst(line, rgx.obj_cite_number_off_block_close)) {
      type["obj_cite_number_status_multi_obj"] = TriState.off;
      type["obj_cite_number_status"] = TriState.off;
      debug(ocnoff) {
        writeln(line);
      }
    }
  }
}
#+END_SRC

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

#+name: abs_functions_block
#+BEGIN_SRC d
void _start_block_(
  char[] line,
  ref int[string] type,
  string[string] obj_cite_number_poem
) {
#+END_SRC

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

#+name: abs_functions_block
#+BEGIN_SRC d
  if (matchFirst(line, rgx.block_curly_code_open)) {
    /+ curly code open +/
    debug(code) {                              // code (curly) open
      writefln(
        "* [code curly] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["code"] = TriState.on;
    type["curly_code"] = TriState.on;
  } else if (matchFirst(line, rgx.block_curly_poem_open)) {
    /+ curly poem open +/
    debug(poem) {                              // poem (curly) open
      writefln(
        "* [poem curly] %s",
        line
      );
    }
    obj_cite_number_poem["start"] =
      to!string(obj_cite_number);
    type["blocks"] = TriState.on;
    type["verse_new"] = State.on;
    type["poem"] = TriState.on;
    type["curly_poem"] = TriState.on;
  } else if (matchFirst(line, rgx.block_curly_group_open)) {
    /+ curly group open +/
    debug(group) {                             // group (curly) open
      writefln(
        "* [group curly] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["group"] = TriState.on;
    type["curly_group"] = TriState.on;
  } else if (matchFirst(line, rgx.block_curly_block_open)) {
    /+ curly block open +/
    debug(block) {                             // block (curly) open
      writefln(
        "* [block curly] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["block"] = TriState.on;
    type["curly_block"] = TriState.on;
  } else if (matchFirst(line, rgx.block_curly_quote_open)) {
    /+ curly quote open +/
    debug(quote) {                             // quote (curly) open
      writefln(
        "* [quote curly] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["quote"] = TriState.on;
    type["curly_quote"] = TriState.on;
  } else if (matchFirst(line, rgx.block_curly_table_open)) {
    /+ curly table open +/
    debug(table) {                             // table (curly) open
      writefln(
        "* [table curly] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["table"] = TriState.on;
    type["curly_table"] = TriState.on;
#+END_SRC

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

#+name: abs_functions_block
#+BEGIN_SRC d
  } else if (matchFirst(line, rgx.block_tic_code_open)) {
    /+ tic code open +/
    debug(code) {                              // code (tic) open
      writefln(
        "* [code tic] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["code"] = TriState.on;
    type["tic_code"] = TriState.on;
  } else if (matchFirst(line, rgx.block_tic_poem_open)) {
    /+ tic poem open +/
    debug(poem) {                              // poem (tic) open
      writefln(
        "* [poem tic] %s",
        line
      );
    }
    obj_cite_number_poem["start"] = to!string(obj_cite_number);
    type["blocks"] = TriState.on;
    type["verse_new"] = State.on;
    type["poem"] = TriState.on;
    type["tic_poem"] = TriState.on;
  } else if (matchFirst(line, rgx.block_tic_group_open)) {
    /+ tic group open +/
    debug(group) {                             // group (tic) open
      writefln(
        "* [group tic] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["group"] = TriState.on;
    type["tic_group"] = TriState.on;
  } else if (matchFirst(line, rgx.block_tic_block_open)) {
    /+ tic block open +/
    debug(block) {                             // block (tic) open
      writefln(
        "* [block tic] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["block"] = TriState.on;
    type["tic_block"] = TriState.on;
  } else if (matchFirst(line, rgx.block_tic_quote_open)) {
    /+ tic quote open +/
    debug(quote) {                             // quote (tic) open
      writefln(
        "* [quote tic] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["quote"] = TriState.on;
    type["tic_quote"] = TriState.on;
  } else if (matchFirst(line, rgx.block_tic_table_open)) {
    /+ tic table open +/
    debug(table) {                             // table (tic) open
      writefln(
        "* [table tic] %s",
        line
      );
    }
    type["blocks"] = TriState.on;
    type["table"] = TriState.on;
    type["tic_table"] = TriState.on;
  }
#+END_SRC

**** function close for block starts

#+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_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type
) {
  if (type["curly_code"] == TriState.on) {
    if (matchFirst(line, rgx.block_curly_code_close)) {
      debug(code) {                              // code (curly) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["code"] = TriState.closing;
      type["curly_code"] = TriState.off;
    } else {
      debug(code) {                              // code (curly) line
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";          // code (curly) line
    }
  } else if (type["tic_code"] == TriState.on) {
    if (matchFirst(line, rgx.block_tic_close)) {
      debug(code) {                              // code (tic) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["code"] = TriState.closing;
      type["tic_code"] = TriState.off;
    } else {
      debug(code) {                              // code (tic) line
        writeln(line);
      }
      an_object["obj"] ~= 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(string abr) {
  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_(string abr) {
  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,
  ref int[string] type,
  ref int bib_entry,
  ref string biblio_entry_str_json,
  ref string[] biblio_arr_json
) {
  if (matchFirst(line, rgx.heading_biblio)) {
    type["heading_biblio"] = TriState.on;
  }
  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 = biblio_entry_tags_jsonstr;
    } else if (!(biblio_entry_str_json.empty)) {
      bib_entry = State.off;
      if (!(biblio_entry_str_json == 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 if (biblio_entry["sortby_deemed_author_year_title"].str.empty) {
        //   writeln("check problem entry (Sort Field missing): ", biblio_entry_str_json);
        } else {
          biblio_arr_json ~= biblio_entry_str_json;
        }
        biblio_entry_str_json = biblio_entry_tags_jsonstr;
      }
    } else { // CHECK ERROR
      writeln("?? 2. ERROR ", biblio_entry_str_json, "??");
      biblio_entry_str_json = "";
    }
  } else if (matchFirst(line, rgx.biblio_tags)) {
    debug(biblioblock) {
      writeln(line);
    }
    auto bt = match(line, rgx.biblio_tags);
    bib_entry = State.off;
    st=to!string(bt.captures[1]);
    auto header_tag_value=to!string(bt.captures[2]);
    JSONValue j = parseJSON(biblio_entry_str_json);
    biblio_tag_name = (match(st, 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"] =
        split(header_tag_value, rgx.arr_delimiter);
      string tmp;
      biblioAuthorLoop:
      foreach (au; j["author_arr"].array) {
        if (auto x = match(au.str, rgx.name_delimiter)) {
          tmp ~= x.captures[2] ~ " " ~ x.captures[1] ~ ", ";
        } else {
          tmp ~= au.str;
        }
      }
      tmp = replace(tmp, rgx.trailing_comma, "");
      // tmp = replace(tmp, regex(r"(,[ ]*)$","g"), "");
      j["author"].str = tmp;
      goto default;
    case "editor_raw": // editor_arr editor (fn sn)
      j["editor_arr"] =
        split(header_tag_value, rgx.arr_delimiter);
      string tmp;
      biblioEditorLoop:
      foreach (ed; j["editor_arr"].array) {
        if (auto x = match(ed.str, rgx.name_delimiter)) {
          tmp ~= x.captures[2] ~ " " ~ x.captures[1] ~ ", ";
        } else {
          tmp ~= ed.str;
        }
      }
      tmp = replace(tmp, rgx.trailing_comma, "");
      // tmp = replace(tmp, regex(r"(,[ ]*)$","g"), "");
      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 ((match(line, 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

**** TODO 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_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type,
  ref long counter,
  string[string] obj_cite_number_poem,
  string[string][string] dochead_make_aa,
) {
  if (type["curly_poem"] == TriState.on) {
    if (matchFirst(line, rgx.block_curly_poem_close)) {
      an_object["obj"]="verse"; // check that this is as you please
      debug(poem) {                            // poem (curly) close
        writefln(
          "* [poem curly] %s",
          line
        );
      }
      if (processing.length > 0) {
        an_object["obj"] = processing["verse"];
      }
      debug(poem) {                            // poem (curly) close
        writeln(__LINE__);
        writefln(
          "* %s %s",
          obj_cite_number,
          line
        );
        // writeln(an_object.keys);
        // writeln(an_object.length);
      }
      if (an_object.length > 0) {
        debug(poem) {                            // poem (curly) close
          writeln(
            obj_cite_number,
            an_object["obj"]
          );
        }
        an_object["is"] = "verse";
        auto substantive_object_and_anchor_tags_tuple =
          obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
        an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
        anchor_tags = substantive_object_and_anchor_tags_tuple[1];
        an_object["attrib"] =
          obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
        contents_the_objects ~=
          set_abstract_object.contents_block(
            an_object["is"],
            an_object["substantive"],
            an_object["attrib"],
            obj_cite_number
          );
        object_reset(an_object);
        processing.remove("verse");
        ++counter;
      }
      obj_cite_number_poem["end"] =
        to!string(obj_cite_number);
      type["blocks"] = TriState.closing;
      type["poem"] = TriState.closing;
      type["curly_poem"] = TriState.off;
    } else {
      processing["verse"] ~= line ~= "\n";
      if (type["verse_new"] == State.on) {
        obj_cite_number =
          obj_cite_number_emit(type["obj_cite_number_status"]);
        type["verse_new"] = State.off;
      } else if (matchFirst(line, rgx.line_delimiter_only)) {
        verse_line = TriState.off;
        type["verse_new"] = State.on;
      }
      if (type["verse_new"] == State.on) {
        verse_line=1;
        an_object["obj"] = processing["verse"];
        debug(poem) {                          // poem verse
          writefln(
            "* %s curly\n%s",
            obj_cite_number,
            an_object["obj"]
          );
        }
        processing.remove("verse");
        an_object["is"] = "verse";
        _node = node_construct.node_emitter(
          content_non_header,
          segment_object_belongs_to,
          obj_cite_number,
          counter,
          heading_pointer-1,
          an_object["is"]
        );
        auto substantive_object_and_anchor_tags_tuple =
          obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
        an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
        anchor_tags = substantive_object_and_anchor_tags_tuple[1];
        an_object["attrib"] =
          obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
        contents_the_objects ~=
          set_abstract_object.contents_block(
            an_object["is"],
            an_object["substantive"],
            an_object["attrib"],
            obj_cite_number
          );
        object_reset(an_object);
        processing.remove("verse");
        ++counter;
      }
    }
  } else if (type["tic_poem"] == TriState.on) {
    if (auto m = matchFirst(line, rgx.block_tic_close)) { // tic_poem_close
      an_object["obj"]="verse"; // check that this is as you please
      debug(poem) {                            // poem (curly) close
        writefln(
          "* [poem tic] %s",
          line
        );
      }
      if (processing.length > 0) {       // needs looking at
        an_object["obj"] = processing["verse"];
      }
      if (an_object.length > 0) {
        debug(poem) {                            // poem (tic) close
          writeln(__LINE__);
          writeln(obj_cite_number, line);
        }
        processing.remove("verse");
        an_object["is"] = "verse";
        auto substantive_object_and_anchor_tags_tuple =
          obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
        an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
        anchor_tags = substantive_object_and_anchor_tags_tuple[1];
        an_object["attrib"] =
          obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
        contents_the_objects ~=
          set_abstract_object.contents_block(
            an_object["is"],
            an_object["substantive"],
            an_object["attrib"],
            obj_cite_number
          );
        obj_cite_number_poem["end"] = to!string(obj_cite_number);
        object_reset(an_object);
        processing.remove("verse");
        ++counter;
      }
      type["blocks"] = TriState.closing;
      type["poem"] = TriState.closing;
      type["tic_poem"] = TriState.off;
    } else {
      processing["verse"] ~= line ~= "\n";
      if (type["verse_new"] == State.on) {
        obj_cite_number =
          obj_cite_number_emit(type["obj_cite_number_status"]);
        type["verse_new"] = State.off;
      } else if (matchFirst(line, rgx.line_delimiter_only)) {
        type["verse_new"] = State.on;
        verse_line = TriState.off;
      }
      if (type["verse_new"] == State.on) {
        verse_line=1;
        an_object["obj"] = processing["verse"];
        debug(poem) {                            // poem (tic) close
          writefln(
            "* %s tic\n%s",
            obj_cite_number,
            an_object["obj"]
          );
        }
        processing.remove("verse");
        an_object["is"] = "verse";
        _node =
          node_construct.node_emitter(
            content_non_header,
            segment_object_belongs_to,
            obj_cite_number,
            counter,
            heading_pointer-1,
            an_object["is"]
          );
        auto substantive_object_and_anchor_tags_tuple =
          obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
        an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
        anchor_tags = substantive_object_and_anchor_tags_tuple[1];
        an_object["attrib"] =
          obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
        contents_the_objects ~=
          set_abstract_object.contents_block(
            an_object["is"],
            an_object["substantive"],
            an_object["attrib"],
            obj_cite_number
          );
        object_reset(an_object);
        processing.remove("verse");
        ++counter;
      }
    }
  }
}
#+END_SRC

**** group block                                                      :group:

#+name: abs_functions_block_group
#+BEGIN_SRC d
void _group_block_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type
) {
  if (type["curly_group"] == State.on) {
    if (matchFirst(line, rgx.block_curly_group_close)) {
      debug(group) {                           // group (curly) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["group"] = TriState.closing;
      type["curly_group"] = TriState.off;
    } else {
      debug(group) {                           // group
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build group array (or string)
    }
  } else if (type["tic_group"] == TriState.on) {
    if (matchFirst(line, rgx.block_tic_close)) {
      debug(group) {                           // group (tic) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["group"] = TriState.closing;
      type["tic_group"] = TriState.off;
    } else {
      debug(group) {                           // group
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build group array (or string)
    }
  }
}
#+END_SRC

**** block block                                                      :block:

#+name: abs_functions_block_block
#+BEGIN_SRC d
void _block_block_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type
) {
  if (type["curly_block"] == TriState.on) {
    if (matchFirst(line, rgx.block_curly_block_close)) {
      debug(block) {                           // block (curly) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["block"] = TriState.closing;
      type["curly_block"] = TriState.off;
    } else {
      debug(block) {                           // block
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build block array (or string)
    }
  } else if (type["tic_block"] == TriState.on) {
    if (matchFirst(line, rgx.block_tic_close)) {
      debug(block) {                           // block (tic) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["block"] = TriState.closing;
      type["tic_block"] = TriState.off;
    } else {
      debug(block) {                           // block
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build block array (or string)
    }
  }
}
#+END_SRC

**** quote block                                                      :quote:

#+name: abs_functions_block_quote
#+BEGIN_SRC d
void _quote_block_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type
) {
  if (type["curly_quote"] == TriState.on) {
    if (matchFirst(line, rgx.block_curly_quote_close)) {
      debug(quote) {                           // quote (curly) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["quote"] = TriState.closing;
      type["curly_quote"] = TriState.off;
    } else {
      debug(quote) {                           // quote
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build quote array (or string)
    }
  } else if (type["tic_quote"] == TriState.on) {
    if (matchFirst(line, rgx.block_tic_close)) {
      debug(quote) {                           // quote (tic) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["quote"] = TriState.closing;
      type["tic_quote"] = TriState.off;
    } else {
      debug(quote) {                           // quote
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build quote array (or string)
    }
  }
}
#+END_SRC

**** table block                                                      :table:

#+name: abs_functions_block_table
#+BEGIN_SRC d
void _table_block_(
  char[] line,
  ref string[string] an_object,
  ref int[string] type
) {
  if (type["curly_table"] == TriState.on) {
    if (matchFirst(line, rgx.block_curly_table_close)) {
      debug(table) {                           // table (curly) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["table"] = TriState.closing;
      type["curly_table"] = TriState.off;
    } else {
      debug(table) {                           // table
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build table array (or string)
    }
  } else if (type["tic_table"] == TriState.on) {
    if (matchFirst(line, rgx.block_tic_close)) {
      debug(table) {                           // table (tic) close
        writeln(line);
      }
      type["blocks"] = TriState.closing;
      type["table"] = TriState.closing;
      type["tic_table"] = TriState.off;
    } else {
      debug(table) {                           // table
        writeln(line);
      }
      an_object["obj"] ~= line ~= "\n";           // build table array (or string)
    }
  }
}
#+END_SRC

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

#+name: abs_functions_block_line_status_empty
#+BEGIN_SRC d
void _block_flag_line_empty_(
  char[] line,
  ref string[string] an_object,
  ref ObjComposite[] contents_the_objects,
  ref string[][string][string] bookindex_unordered_hashes,
  ref int obj_cite_number,
  ref string _node,
  ref long counter,
  ref int[string] type,
  string[string] obj_cite_number_poem,
  string[string][string] dochead_make_aa,
) {
  // line.empty, post contents, empty variables ---------------
  assert(
    line.empty,
    "line should be empty"
  );
  assert(
    (type["blocks"] == TriState.closing),
    "code block status: closed"
  );
  assertions_flag_types_block_status_none_or_closed(type);
  if (type["code"] == TriState.closing) {
    obj_cite_number =
      obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    an_object["is"] = "code";
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
      );
    auto substantive_object_and_anchor_tags_tuple =
      obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
    an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
    anchor_tags = substantive_object_and_anchor_tags_tuple[1];
    an_object["attrib"] =
      obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
    contents_the_objects ~=
      set_abstract_object.contents_block_code(
        an_object["is"],
        an_object["substantive"],
        an_object["attrib"],
        obj_cite_number
      );
    object_reset(an_object);
    processing.remove("verse");
    ++counter;
    type["blocks"] = TriState.off;
    type["code"] = TriState.off;
  } else if (type["poem"] == TriState.closing) {
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    // obj_cite_number = obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["is"] = "verse"; // check also
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
        // "verse"
      );
    contents_the_objects ~=
      set_abstract_object.contents_block_obj_cite_number_string(
        "poem",
        "",
        (obj_cite_number_poem["start"], obj_cite_number_poem["end"]),
        _node
      ); // bookindex
    object_reset(an_object);
    processing.remove("verse");
    // ++obj_cite_number;
    type["blocks"] = TriState.off;
    type["poem"] = TriState.off;
  } else if (type["table"] == TriState.closing) {
    obj_cite_number =
      obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    an_object["is"] = "table";
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
      );
    auto substantive_object_and_anchor_tags_tuple =
      obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
    an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
    anchor_tags = substantive_object_and_anchor_tags_tuple[1];
    an_object["attrib"] =
      obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
    contents_the_objects ~=
      set_abstract_object.contents_block(
        an_object["is"],
        an_object["substantive"],
        an_object["attrib"],
        obj_cite_number
      );
    object_reset(an_object);
    processing.remove("verse");
    ++counter;
    type["blocks"] = TriState.off;
    type["table"] = TriState.off;
  } else if (type["group"] == TriState.closing) {
    obj_cite_number =
      obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    an_object["is"] = "group";
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
      );
    auto substantive_object_and_anchor_tags_tuple =
      obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
    an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
    anchor_tags = substantive_object_and_anchor_tags_tuple[1];
    an_object["attrib"] =
      obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
    contents_the_objects ~=
      set_abstract_object.contents_block(
        an_object["is"],
        an_object["substantive"],
        an_object["attrib"],
        obj_cite_number
      );
    object_reset(an_object);
    processing.remove("verse");
    ++counter;
    type["blocks"] = TriState.off;
    type["group"] = TriState.off;
  } else if (type["block"] == TriState.closing) {
    obj_cite_number = obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    an_object["is"] = "block";
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
       );
    auto substantive_object_and_anchor_tags_tuple =
      obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
    an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
    anchor_tags = substantive_object_and_anchor_tags_tuple[1];
    an_object["attrib"] =
      obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
    contents_the_objects ~=
      set_abstract_object.contents_block(
        an_object["is"],
        an_object["substantive"],
        an_object["attrib"],
        obj_cite_number
      );
    object_reset(an_object);
    processing.remove("verse");
    ++counter;
    type["blocks"] = TriState.off;
    type["block"] = TriState.off;
  } else if (type["quote"] == TriState.closing) {
    obj_cite_number =
      obj_cite_number_emit(type["obj_cite_number_status"]);
    an_object["bookindex"] =
      ("bookindex" in an_object) ? an_object["bookindex"] : "";
    bookindex_unordered_hashes =
      bkidx_hash(an_object["bookindex"], obj_cite_number);
    an_object["is"] = "quote";
    _node =
      node_construct.node_emitter(
        content_non_header,
        segment_object_belongs_to,
        obj_cite_number,
        counter,
        heading_pointer-1,
        an_object["is"]
      );
    auto substantive_object_and_anchor_tags_tuple =
      obj_im.obj_inline_markup_and_anchor_tags(an_object, dochead_make_aa);
    an_object["substantive"] = substantive_object_and_anchor_tags_tuple[0];
    anchor_tags = substantive_object_and_anchor_tags_tuple[1];
    an_object["attrib"] =
      obj_att.obj_attributes(an_object["is"], an_object["obj"], _node);
    contents_the_objects ~=
      set_abstract_object.contents_block(
        an_object["is"],
        an_object["substantive"],
        an_object["attrib"],
        obj_cite_number
      );
    object_reset(an_object);
    processing.remove("verse");
    ++counter;
    type["blocks"] = TriState.off;
    type["quote"] = TriState.off;
  }
}
#+END_SRC

** book index                                                     :bookindex:

#+name: abs_functions_book_index
#+BEGIN_SRC d
auto _book_index_(
  char[] line,
  ref string book_idx_tmp,
  ref string[string] an_object,
  ref int[string] type
) {
  if (auto m = match(line, rgx.book_index)) {
    /+ match book_index +/
    debug(bookindexmatch) {                       // book index
      writefln(
        "* [bookindex] %s\n",
        to!string(m.captures[1]),
      );
      // writeln(scr_txt_marker["blue"], to!string(m.captures[1]), "\n");
    }
    an_object["bookindex"] = to!string(m.captures[1]);
  } else if (auto m = match(line, rgx.book_index_open))  {
    /+ match open book_index +/
    type["book_index"] = State.on;
    book_idx_tmp = to!string(m.captures[1]);
    debug(bookindexmatch) {                       // book index
      writefln(
        "* [bookindex] %s\n",
        book_idx_tmp,
      );
      // writeln(scr_txt_marker["blue"], book_idx_tmp, "\n");
    }
  } else if (type["book_index"] == State.on )  {
    /+ book_index flag set +/
    if (auto m = match(line, rgx.book_index_close))  {
      type["book_index"] = State.off;
      an_object["bookindex"] = book_idx_tmp ~ to!string(m.captures[1]);
      debug(bookindexmatch) {                     // book index
        writefln(
          "* [bookindex] %s\n",
          book_idx_tmp,
        );
        // writeln(scr_txt_marker["blue"], book_idx_tmp, "\n");
      }
      book_idx_tmp = "";
    } else {
      book_idx_tmp ~= line;
    }
  }
}
#+END_SRC

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

#+name: abs_functions_heading
#+BEGIN_SRC d
auto _heading_found_(
  char[] line,
  string dochead_make_identify_unmarked_headings,
  ref string[string] heading_match_str,
  ref Regex!(char)[string] heading_match_rgx,
  ref int[string] type
) {
  if ((dochead_make_identify_unmarked_headings.length > 2)
  && (type["make_headings"] == State.off)) {
    /+ headings found +/
    debug(headingsfound) {
      writeln(dochead_make_identify_unmarked_headings);
    }
    char[][] make_headings_spl =
      split(
        cast(char[]) dochead_make_identify_unmarked_headings,
        rgx.make_heading_delimiter);
    debug(headingsfound) {
      writeln(make_headings_spl.length);
      writeln(make_headings_spl);
    }
    switch (make_headings_spl.length) {
    case 7 :
      if (!empty(make_headings_spl[6])) {
        heading_match_str["h_4"] =
          "^(" ~ to!string(make_headings_spl[6]) ~ ")";
        heading_match_rgx["h_4"] =
          regex(heading_match_str["h_4"]);
      }
      goto case;
    case 6 :
      if (!empty(make_headings_spl[5])) {
        heading_match_str["h_3"] =
          "^(" ~ to!string(make_headings_spl[5]) ~ ")";
        heading_match_rgx["h_3"] =
          regex(heading_match_str["h_3"]);
      }
      goto case;
    case 5 :
      if (!empty(make_headings_spl[4])) {
        heading_match_str["h_2"] =
          "^(" ~ to!string(make_headings_spl[4]) ~ ")";
        heading_match_rgx["h_2"] =
          regex(heading_match_str["h_2"]);
      }
      goto case;
    case 4 :
      if (!empty(make_headings_spl[3])) {
        heading_match_str["h_1"] =
          "^(" ~ to!string(make_headings_spl[3]) ~ ")";
        heading_match_rgx["h_1"] =
          regex(heading_match_str["h_1"]);
      }
      goto case;
    case 3 :
      if (!empty(make_headings_spl[2])) {
        heading_match_str["h_D"] =
          "^(" ~ to!string(make_headings_spl[2]) ~ ")";
        heading_match_rgx["h_D"] =
          regex(heading_match_str["h_D"]);
      }
      goto case;
    case 2 :
      if (!empty(make_headings_spl[1])) {
        heading_match_str["h_C"] =
          "^(" ~ to!string(make_headings_spl[1]) ~ ")";
        heading_match_rgx["h_C"] =
          regex(heading_match_str["h_C"]);
      }
      goto case;
    case 1 :
      if (!empty(make_headings_spl[0])) {
        heading_match_str["h_B"] =
          "^(" ~ to!string(make_headings_spl[0]) ~ ")";
        heading_match_rgx["h_B"] =
          regex(heading_match_str["h_B"]);
      }
      break;
    default:
      break;
    }
    type["make_headings"] = State.on;
  }
}
#+END_SRC

*** TODO heading make set                                           :heading:

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

*** heading match                                                   :heading:

#+name: abs_functions_heading
#+BEGIN_SRC d
auto _heading_matched_(
  char[] line,
  ref int[string] line_occur,
  ref string[string] an_object,
  ref int[string] lv,
  ref int[string] collapsed_lev,
  ref int[string] type,
  ref string[string][string] dochead_meta_aa
) {
  if (auto m = match(line, rgx.heading)) {
    /+ heading match +/
    type["heading"] = State.on;
    type["heading_biblio"] = State.off;
    type["para"] = State.off;
    ++line_occur["heading"];
    an_object["obj"] ~= line ~= "\n";
    an_object["lev"] ~= m.captures[1];
    // writeln("an object level: ", an_object);
    assertions_doc_structure(an_object, lv); // includes most of the logic for collapsed levels
    switch (an_object["lev"]) {
    case "A":
      an_object["obj"]=replaceFirst(an_object["obj"], rgx.variable_doc_title, dochead_meta_aa["title"]["main"]);
      an_object["obj"]=replaceFirst(an_object["obj"], rgx.variable_doc_author, dochead_meta_aa["creator"]["author"]);
      collapsed_lev["h0"] = 1;
      an_object["lev_collapsed_number"] =
        to!string(collapsed_lev["h0"]);
      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"] =
        to!string(collapsed_lev["h1"]);
      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"] =
        to!string(collapsed_lev["h2"]);
      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"] =
        to!string(collapsed_lev["h3"]);
      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"] =
        to!string(collapsed_lev["h4"]);
      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"] =
          to!string(collapsed_lev["h5"]);
      } else if (lv["h4"] > State.off) {
        collapsed_lev["h5"] = collapsed_lev["h4"] + 1;
        an_object["lev_collapsed_number"] =
          to!string(collapsed_lev["h5"]);
      }
      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"] =
          to!string(collapsed_lev["h6"]);
      } else if (lv["h5"] > State.off) {
        collapsed_lev["h6"] = collapsed_lev["h5"] + 1;
        an_object["lev_collapsed_number"] =
          to!string(collapsed_lev["h6"]);
      }
      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"] =
          to!string(collapsed_lev["h7"]);
      } else if (lv["h6"] > State.off) {
        collapsed_lev["h7"] = collapsed_lev["h6"] + 1;
        an_object["lev_collapsed_number"] =
          to!string(collapsed_lev["h7"]);
      }
      lv["lv"] = DocStructMarkupHeading.h_text_4;
      ++lv["h7"];
      goto default;
    default:
      an_object["lev_markup_number"] = to!string(lv["lv"]);
    }
    debug(heading) {                         // heading
      // writeln(m.captures[1], " ", m.captures[2], "\n");      // figure inclusion of post capture text
      // writeln(m.hit, "\n");
      writeln(strip(line));
    }
  }
}
#+END_SRC

*** para match                                                         :para:

#+name: abs_functions_para
#+BEGIN_SRC d
auto _para_match_(
  char[] line,
  ref string[string] an_object,
  ref string[string] indent,
  ref bool bullet,
  ref int[string] type,
  ref int[string] line_occur
) {
  if (line_occur["para"] == State.off) {
    /+ para matches +/
      // paragraphs
      // (fl  ag_type["heading"] = true) &&
    if (auto m = matchFirst(line, rgx.para_indent)) {
      debug(paraindent) {                    // para indent
        writeln(line);
      }
      type["para"] = State.on;
      an_object["obj"] ~= line ~= "\n";
      indent["hang_position"] = to!string(m.captures[1]);
      indent["base_position"] = "0";
      bullet = false;
    } else if (matchFirst(line, rgx.para_bullet)) {
      debug(parabullet) {                    // para bullet
        writeln(line);
      }
      type["para"] = State.on;
      an_object["obj"] ~= line;
      indent["hang_position"] = "0";
      indent["base_position"] = "0";
      bullet = true;
    } else if (auto m = matchFirst(line, rgx.para_indent_hang)) {
      debug(paraindenthang) {                // para indent hang
        writeln(line);
      }
      type["para"] = State.on;
      an_object["obj"] ~= line;
      indent["hang_position"] = to!string(m.captures[1]);
      indent["base_position"] = to!string(m.captures[2]);
      bullet = false;
    } else if (auto m = matchFirst(line, rgx.para_bullet_indent)) {
      debug(parabulletindent) {              // para bullet indent
        writeln(line);
      }
      type["para"] = State.on;
      an_object["obj"] ~= line;
      indent["hang_position"] = to!string(m.captures[1]);
      indent["base_position"] = "0";
      bullet = true;
    } else {
      // !line.empty
      type["para"] = State.on;
      an_object["obj"] ~= line;
      indent["hang_position"] = "0";
      indent["base_position"] = "0";
      bullet = false;
    }
    ++line_occur["para"];
  }
}
#+END_SRC

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

#+name: ao_emitters_ocn
#+BEGIN_SRC d
struct OCNemitter {
  int obj_cite_number, obj_cite_number_;
  int obj_cite_number_emitter(int obj_cite_number_status_flag)
  in { assert(obj_cite_number_status_flag <= 2); }
  body {
    obj_cite_number=(obj_cite_number_status_flag == 0)
    ? ++obj_cite_number_
    : 0;
    assert(obj_cite_number >= 0);
    return obj_cite_number;
  }
  invariant() {
  }
}
#+END_SRC

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

#+name: ao_emitters_obj_inline_markup_munge
#+BEGIN_SRC d
struct ObjInlineMarkupMunge {
// struct ObjInlineMarkupMunge : AssertObjInlineMarkup {
  string[string] obj_txt;
  int n_foot, n_foot_reg, n_foot_sp_asterisk, n_foot_sp_plus;
  string obj_txt_out, tail, note;
  private auto initialize_note_numbers() {
    n_foot = 0;
    n_foot_reg = 0;
    n_foot_sp_asterisk = 0;
    n_foot_sp_plus = 0;
  }
  private auto object_notes_(string obj_txt_in)
  in { }
  body {
    auto rgx = Rgx();
    auto mkup = InlineMarkup();
    obj_txt_out = "";
    tail = "";
    obj_txt_in = replaceAll(
      obj_txt_in,
      rgx.inline_notes_curly_sp_asterisk,
      (mkup.en_a_o ~ "*" ~ " $1" ~ mkup.en_a_c)
    );
    obj_txt_in =
      replaceAll(
        obj_txt_in,
        rgx.inline_notes_curly_sp_plus,
        (mkup.en_a_o ~ "+" ~ " $1" ~ mkup.en_a_c)
      );
    obj_txt_in =
      replaceAll(
        obj_txt_in,
        rgx.inline_notes_curly,
        (mkup.en_a_o ~ " $1" ~ mkup.en_a_c)
      );
    if (match(obj_txt_in, rgx.inline_notes_al_gen)) {
      foreach(m; matchAll(obj_txt_in, rgx.inline_text_and_note_al)) {
        if (match(obj_txt_in, rgx.inline_al_delimiter_open_asterisk)) {
          ++n_foot_sp_asterisk;
          n_foot=n_foot_sp_asterisk;
        } else if (match(obj_txt_in, rgx.inline_al_delimiter_open_plus)) {
          ++n_foot_sp_plus;
          n_foot=n_foot_sp_plus;
        } else {
          ++n_foot_reg;
          n_foot=n_foot_reg;
        }
        obj_txt_out ~= replaceFirst(
          m.hit,
          rgx.inline_al_delimiter_open_regular,
          (mkup.en_a_o ~ to!string(n_foot))
        );
        tail = m.post;
        // if (!empty(m.post)) {
        //   tail = m.post;
        // } else {
        //   tail = "";
        // }
      }
    } else {
      obj_txt_out = obj_txt_in;
    }
    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);
      }
    }
    return obj_txt_out;
  }
  string para(string obj_txt_in)
  in { }
  body {
    auto rgx = Rgx();
    obj_txt["munge"]=obj_txt_in;
    obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.para_attribs, "");
    obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.obj_cite_number_off_all, "");
    obj_txt["munge"]=object_notes_(obj_txt["munge"]);
    debug(munge) {
      writeln(__LINE__);
      writeln(obj_txt_in);
      writeln(__LINE__);
      writeln(to!string(obj_txt["munge"]));
    }
    return obj_txt["munge"];
  }
  string heading(string obj_txt_in)
  in { }
  body {
    auto rgx = Rgx();
    obj_txt["munge"]=obj_txt_in;
    obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.heading, "");
    obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.obj_cite_number_off_all, "");
    obj_txt["munge"]=object_notes_(obj_txt["munge"]);
    debug(munge) {
      writeln(__LINE__);
      writeln(obj_txt_in);
      writeln(__LINE__);
      writeln(to!string(obj_txt["munge"]));
    }
    return obj_txt["munge"];
  }
  invariant() {
  }
  /+ revisit +/
  // string header_make(string obj_txt_in)
  // in { }
  // body {
  //   obj_txt["munge"]=obj_txt_in;
  //   return obj_txt["munge"];
  // }
  // invariant() {
  // }
  // string header_meta(string obj_txt_in)
  // in { }
  // body {
  //   obj_txt["munge"]=obj_txt_in;
  //   return obj_txt["munge"];
  // }
  // invariant() {
  // }
  string code(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
  string group(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    obj_txt["munge"]=object_notes_(obj_txt["munge"]);
    return obj_txt["munge"];
  }
  invariant() {
  }
  string block(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    obj_txt["munge"]=object_notes_(obj_txt["munge"]);
    return obj_txt["munge"];
  }
  invariant() {
  }
  string verse(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    obj_txt["munge"]=object_notes_(obj_txt["munge"]);
    return obj_txt["munge"];
  }
  invariant() {
  }
  string quote(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
  string table(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
  string comment(string obj_txt_in)
  in { }
  body {
    obj_txt["munge"]=obj_txt_in;
    return obj_txt["munge"];
  }
  invariant() {
  }
}
#+END_SRC

**** object inline markup                                     :markup:inline:
***** open

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

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

#+name: ao_emitters_obj_inline_markup_and_anchor_tags
#+BEGIN_SRC d
  auto obj_inline_markup_and_anchor_tags(string[string] obj_, string[string][string] dochead_make_aa)
  in { }
  body {
    obj_txt["munge"]=obj_["obj"].dup;
    obj_txt["munge"]=(match(obj_["is"], ctRegex!(`verse|code`)))
      ? obj_txt["munge"]
      : strip(obj_txt["munge"]);
    static __gshared string[] anchor_tags_ = [];
    switch (obj_["is"]) {
    case "heading":
      static __gshared string anchor_tag = "";
      // TODO WORK ON, you still need to ensure that level 1 anchor_tags are unique
      obj_txt["munge"]=_configured_auto_heading_numbering_and_segment_anchor_tags(obj_txt["munge"], obj_, dochead_make_aa);
      obj_txt["munge"]=_make_segment_anchor_tags_if_none_provided(obj_txt["munge"], obj_["lev"]);
      if (auto m = match(obj_txt["munge"], 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"]);
      }
      obj_txt["munge"]=munge.heading(obj_txt["munge"]);
      break;
    case "para":
      obj_txt["munge"]=munge.para(obj_txt["munge"]);
      break;
    case "code":
      obj_txt["munge"]=munge.code(obj_txt["munge"]);
      break;
    case "group":
      obj_txt["munge"]=munge.group(obj_txt["munge"]);
      break;
    case "block":
      obj_txt["munge"]=munge.block(obj_txt["munge"]);
      break;
    case "verse":
      obj_txt["munge"]=munge.verse(obj_txt["munge"]);
      break;
    case "quote":
      obj_txt["munge"]=munge.quote(obj_txt["munge"]);
      break;
    case "table":
      obj_txt["munge"]=munge.table(obj_txt["munge"]);
      break;
    case "comment":
      obj_txt["munge"]=munge.comment(obj_txt["munge"]);
      break;
    case "doc_end_reset":
      munge.initialize_note_numbers();
      break;
    default:
      break;
    }
    auto t = tuple(
     obj_txt["munge"],
     anchor_tags_,
    );
    anchor_tags_=[];
    return t;
  }
  invariant() {
  }
#+END_SRC

***** private:

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

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

#+name: ao_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d
  static string _configured_auto_heading_numbering_and_segment_anchor_tags(string munge_, string[string] obj_, string[string][string] dochead_make_aa) {
    if (dochead_make_aa["make"]["num_top"].length > 0) {
      if (!(match(munge_, rgx.heading_anchor_tag))) {
        static __gshared uint heading_num_top_level=9;
        static __gshared uint heading_num_depth=2;
        static __gshared uint heading_num_0 = 0;
        static __gshared uint heading_num_1 = 0;
        static __gshared uint heading_num_2 = 0;
        static __gshared uint heading_num_3 = 0;
        static __gshared string heading_number_auto_composite = "";
        if (heading_num_top_level==9) {
          if (dochead_make_aa["make"]["num_depth"].length > 0) {
            heading_num_depth = to!uint(dochead_make_aa["make"]["num_depth"]);
          }
          switch (dochead_make_aa["make"]["num_top"]) {
          case "A":
            break;
          case "B":
            heading_num_top_level=1;
            break;
          case "C":
            heading_num_top_level=2;
            break;
          case "D":
            heading_num_top_level=3;
            break;
          case "1":
            heading_num_top_level=4;
            break;
          case "2":
            heading_num_top_level=5;
            break;
          case "3":
            heading_num_top_level=6;
            break;
          case "4":
            heading_num_top_level=7;
            break;
          default:
            break;
          }
        }
        /+ num_depth minimum 0 (1.) default 2 (1.1.1) max 3 (1.1.1.1) implement +/
        if (heading_num_top_level > to!uint(obj_["lev_markup_number"])) {
          heading_num_0 = 0;
          heading_num_1 = 0;
          heading_num_2 = 0;
          heading_num_3 = 0;
        } else if (heading_num_top_level == to!uint(obj_["lev_markup_number"])) {
          heading_num_0 ++;
          heading_num_1 = 0;
          heading_num_2 = 0;
          heading_num_3 = 0;
        } else if (heading_num_top_level == (to!uint(obj_["lev_markup_number"]) - 1)) {
          heading_num_1 ++;
          heading_num_2 = 0;
          heading_num_3 = 0;
        } else if (heading_num_top_level == (to!uint(obj_["lev_markup_number"]) - 2)) {
          heading_num_2 ++;
          heading_num_3 = 0;
        } else if (heading_num_top_level == (to!uint(obj_["lev_markup_number"]) - 3)) {
          heading_num_3 ++;
        } else {
          //
        }
        if (heading_num_3 > 0) {
          heading_number_auto_composite =
            (heading_num_depth == 3)
            ? ( to!string(heading_num_0) ~ "." ~
                  to!string(heading_num_1) ~ "." ~
                  to!string(heading_num_2) ~ "." ~
                  to!string(heading_num_3)
                )
            : "";
        } else if (heading_num_2 > 0) {
          heading_number_auto_composite =
            ((heading_num_depth >= 2)
            && (heading_num_depth <= 3))
            ?  ( to!string(heading_num_0) ~ "." ~
                  to!string(heading_num_1) ~ "." ~
                  to!string(heading_num_2)
                )
            : "";
        } else if (heading_num_1 > 0) {
          heading_number_auto_composite =
            ((heading_num_depth >= 1)
            && (heading_num_depth <= 3))
            ? ( to!string(heading_num_0) ~ "." ~
                  to!string(heading_num_1)
                )
            : "";
        } else if (heading_num_0 > 0) {
          heading_number_auto_composite =
            ((heading_num_depth >= 0)
            && (heading_num_depth <= 3))
            ?  (to!string(heading_num_0))
            : "";
        } else {
          heading_number_auto_composite = "";
        }
        debug(heading_number_auto) {
          writeln(heading_number_auto_composite);
        }
        if (!empty(heading_number_auto_composite)) {
          munge_=replaceFirst(munge_, rgx.heading,
            "$1~$2 " ~ heading_number_auto_composite ~ ". ");
          munge_=replaceFirst(munge_, rgx.heading_marker_missing_tag,
            "$1~" ~ heading_number_auto_composite ~ " ");
        }
      }
    }
    return munge_;
  }
#+END_SRC

******* unittests

#+name: ao_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d
#+END_SRC

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

#+name: ao_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags
#+BEGIN_SRC d

  static string _make_segment_anchor_tags_if_none_provided(string munge_, string lev_) {
    if (!(match(munge_, rgx.heading_anchor_tag))) { // if (anchor_tags_.length == 0) {
      if (match(munge_, rgx.heading_identify_anchor_tag)) {
        if (auto m = match(munge_, rgx.heading_extract_named_anchor_tag)) {
          munge_=replaceFirst(munge_, rgx.heading_marker_missing_tag,
            "$1~" ~ toLower(m.captures[1]) ~ "_"  ~ m.captures[2] ~ " ");
        } else if (auto m = match(munge_, rgx.heading_extract_unnamed_anchor_tag)) {
          munge_=replaceFirst(munge_, 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 uint heading_num_lev1 = 0;
        heading_num_lev1 ++;
        munge_=replaceFirst(munge_, rgx.heading_marker_missing_tag,
          "$1~" ~ "x" ~ to!string(heading_num_lev1) ~ " ");
      }
    }
    return munge_;
  }
#+END_SRC

******* unittests

#+name: ao_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);
    // assert(ObjInlineMarkup._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

***** close

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

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

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

***** attributes structure open, public

#+name: ao_emitters_obj_attributes_public
#+BEGIN_SRC d
  string obj_attributes(string obj_is_, string obj_raw, string _node)
  in { }
  body {
    scope(exit) {
      destroy(obj_is_);
      destroy(obj_raw);
      destroy(_node);
    }
    _obj_attrib.remove("json");
    _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_, _node);
    debug(structattrib) {
      if (oa_j["is"].str() == "heading") {
        // writeln(__LINE__);
        writeln(_obj_attrib["json"]);
        // writeln(_node);
        writeln(
          "is: ", oa_j["is"].str(),
          "; obj_cite_number: ", oa_j["obj_cite_number"].integer()
        );
      }
    }
    return _obj_attrib["json"];
  }
  invariant() {
  }
#+END_SRC

***** private

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

****** attrubutes
******* para and block

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _para_and_blocks(string obj_txt_in)
  in { }
  body {
    auto rgx = Rgx();
    if (matchFirst(obj_txt_in, rgx.para_bullet)) {
      _obj_attributes =" \"bullet\": \"true\","
      ~ " \"indent_start\": 0,"
      ~ " \"indent_rest\": 0,";
    } else if (auto m = matchFirst(obj_txt_in, rgx.para_bullet_indent)) {
      _obj_attributes =" \"bullet\": \"true\","
      ~ " \"indent_start\": " ~ to!string(m.captures[1]) ~ ","
      ~ " \"indent_rest\": " ~ to!string(m.captures[1]) ~ ",";
    } else if (auto m = matchFirst(obj_txt_in, rgx.para_indent_hang)) {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_start\": " ~ to!string(m.captures[1]) ~ ","
      ~ " \"indent_rest\": " ~  to!string(m.captures[2]) ~ ",";
    } else if (auto m = matchFirst(obj_txt_in, rgx.para_indent)) {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_start\": " ~ to!string(m.captures[1]) ~ ","
      ~ " \"indent_rest\": " ~ to!string(m.captures[1]) ~ ",";
    } else {
      _obj_attributes =" \"bullet\": \"false\","
      ~ " \"indent_start\": 0,"
      ~ " \"indent_rest\": 0,";
    }
    return _obj_attributes;
  }
#+END_SRC

******* para

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _para(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"para\","
    ~ " \"is\": \"para\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* heading

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _heading(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"para\","
    ~ " \"is\": \"heading\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* code

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _code(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"code\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* group

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _group(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"group\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* block

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _block(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"block\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* verse

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _verse(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"verse\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* quote

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _quote(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"quote\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* table

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _table(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"content\","
    ~ " \"of\": \"block\","
    ~ " \"is\": \"table\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

******* comment

#+name: ao_emitters_obj_attributes_private_an_attribute
#+BEGIN_SRC d
  string _comment(string obj_txt_in)
  in { }
  body {
    _obj_attributes = " \"use\": \"comment\","
    ~ " \"of\": \"comment\","
    ~ " \"is\": \"comment\"";
    return _obj_attributes;
  }
  invariant() {
  }
#+END_SRC

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

#+name: ao_emitters_obj_attributes_private_json
#+BEGIN_SRC d
  string _set_additional_values_parse_as_json(string _obj_attrib, string obj_is_, string _node) {
    JSONValue oa_j = parseJSON(_obj_attrib);
    JSONValue node_j = parseJSON(_node);
    assert(
      (oa_j.type == JSON_TYPE.OBJECT) &&
      (node_j.type == JSON_TYPE.OBJECT)
    );
    if (obj_is_ == "heading") {
      oa_j.object["obj_cite_number"] = node_j["obj_cite_number"];
      oa_j.object["lev_markup_number"] = node_j["lev_markup_number"];
      oa_j.object["lev_collapsed_number"] = node_j["lev_collapsed_number"];
      oa_j.object["heading_pointer"] =
        node_j["heading_pointer"]; // check
      oa_j.object["doc_object_pointer"] =
        node_j["doc_object_pointer"]; // check
    }
    oa_j.object["parent_obj_cite_number"] = node_j["parent_obj_cite_number"];
    oa_j.object["parent_lev_markup_number"] = node_j["parent_lev_markup_number"];
    _obj_attrib = oa_j.toString();
    return _obj_attrib;
  }
#+END_SRC

***** close

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

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

#+name: ao_emitters_book_index_nugget
#+BEGIN_SRC d
struct BookIndexNuggetHash {
  string main_term, sub_term, sub_term_bits;
  int obj_cite_number_offset, obj_cite_number_endpoint;
  string[] obj_cite_numbers;
  string[][string][string] bi;
  string[][string][string] hash_nugget;
  string[] bi_main_terms_split_arr;
  string[][string][string] bookindex_nugget_hash(string bookindex_section, int obj_cite_number)
  in {
    debug(bookindexraw) {
      if (!bookindex_section.empty) {
        writeln(
          "* [bookindex] ",
          "[", to!string(obj_cite_number), "] ", bookindex_section
        );
      }
    }
  }
  body {
    auto rgx = Rgx();
    if (!bookindex_section.empty) {
      auto bi_main_terms_split_arr =
        split(bookindex_section, rgx.bi_main_terms_split);
      foreach (bi_main_terms_content; bi_main_terms_split_arr) {
        auto bi_main_term_and_rest =
          split(bi_main_terms_content, rgx.bi_main_term_plus_rest_split);
        if (auto m = match(
          bi_main_term_and_rest[0],
          rgx.bi_term_and_obj_cite_numbers_match)
        ) {
          main_term = strip(m.captures[1]);
          obj_cite_number_offset = to!int(m.captures[2]);
          obj_cite_number_endpoint=(obj_cite_number + obj_cite_number_offset);
          obj_cite_numbers ~= (to!string(obj_cite_number) ~ "-" ~ to!string(obj_cite_number_endpoint));
        } else {
          main_term = strip(bi_main_term_and_rest[0]);
          obj_cite_numbers ~= to!string(obj_cite_number);
        }
        bi[main_term]["_a"] ~= obj_cite_numbers;
        obj_cite_numbers=null;
        if (bi_main_term_and_rest.length > 1) {
          auto bi_sub_terms_split_arr =
            split(
              bi_main_term_and_rest[1],
              rgx.bi_sub_terms_plus_obj_cite_number_offset_split
            );
          foreach (sub_terms_bits; bi_sub_terms_split_arr) {
            if (auto m = match(sub_terms_bits, rgx.bi_term_and_obj_cite_numbers_match)) {
              sub_term = strip(m.captures[1]);
              obj_cite_number_offset = to!int(m.captures[2]);
              obj_cite_number_endpoint=(obj_cite_number + obj_cite_number_offset);
              obj_cite_numbers ~= (to!string(obj_cite_number) ~ " - " ~ to!string(obj_cite_number_endpoint));
            } else {
              sub_term = strip(sub_terms_bits);
              obj_cite_numbers ~= to!string(obj_cite_number);
            }
            if (!empty(sub_term)) {
              bi[main_term][sub_term] ~= obj_cite_numbers;
            }
            obj_cite_numbers=null;
          }
        }
      }
    }
    hash_nugget = bi;
    return hash_nugget;
  }
  invariant() {
  }
}
#+END_SRC

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

#+name: ao_emitters_book_index_report_indented
#+BEGIN_SRC d
struct BookIndexReportIndent {
  int mkn, skn;
  auto bookindex_report_indented(
    string[][string][string] bookindex_unordered_hashes
  ) {
    auto mainkeys=
      bookindex_unordered_hashes.byKey.array.sort().release;
    foreach (mainkey; mainkeys) {
      debug(bookindex) {
        writeln(mainkey);
      }
      auto subkeys=
        bookindex_unordered_hashes[mainkey].byKey.array.sort().release;
      foreach (subkey; subkeys) {
        debug(bookindex) {
          writeln("  ", subkey);
          writeln("    ", to!string(
            bookindex_unordered_hashes[mainkey][subkey]
          ));
        }
        // bookindex_the[mkn][mainkey][skn][subkey] ~= (bookindex_unordered_hashes[mainkey][subkey]);
        ++skn;
      }
      ++mkn;
    }
  }
}
#+END_SRC

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

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

***** bookindex write section

#+name: ao_emitters_book_index_report_section
#+BEGIN_SRC d
  auto bookindex_write_section(
    string[][string][string] bookindex_unordered_hashes
  ) {
    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 = replaceAll(ref_, 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 = replaceAll(ref_, rgx.book_index_go, "$1");
          write(" {", ref_, "}#", go, ", ");
        }
        writeln(" \\\\");
        ++skn;
      }
      ++mkn;
    }
  }
#+END_SRC

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

#+name: ao_emitters_book_index_report_section
#+BEGIN_SRC d
  auto bookindex_build_section(
    string[][string][string] bookindex_unordered_hashes,
    int obj_cite_number,
    string segment_object_belongs_to,
  ) {
    string type;
    string lev, lev_markup_number, lev_collapsed_number;
    string attrib;
    string[string] indent;
    auto set_abstract_object = ObjectAbstractSet();
    auto mainkeys =
      bookindex_unordered_hashes.byKey.array.sort().release;
    string bi_tmp;
    ObjComposite[] bookindex_section;
    // writeln(mainkeys.length);
    // B~ Book Index
    attrib="";
    lev="B";
    lev_markup_number="1";
    lev_collapsed_number="1";
    bookindex_section ~=
      set_abstract_object.contents_heading(
        "Book Index",
        attrib,
        obj_cite_number,
        [],
        to!string(lev),
        to!int(lev_markup_number),
        to!int(lev_collapsed_number)
      );
    ++obj_cite_number;
    ++mkn;
    // 1~ Index
    attrib="";
    lev="1";
    lev_markup_number="4";
    lev_collapsed_number="2";
    bookindex_section ~=
      set_abstract_object.contents_heading(
        "Index",
        attrib,
        obj_cite_number,
        ["book_index"],
        to!string(lev),
        to!int(lev_markup_number),
        to!int(lev_collapsed_number)
      );
    ++obj_cite_number;
    ++mkn;
    foreach (mainkey; mainkeys) {
      bi_tmp = "!{" ~ mainkey ~ "}! ";
      // bi_tmp = "_0_1 !{" ~ mainkey ~ "}! ";
      foreach (ref_; bookindex_unordered_hashes[mainkey]["_a"]) {
        auto go = replaceAll(ref_, rgx.book_index_go, "$1");
        bi_tmp ~= (segment_object_belongs_to.empty)
        ? (" {" ~ ref_ ~ "}#" ~ go ~ ", ")
        : (" {" ~ ref_ ~ "}[../" ~ segment_object_belongs_to ~ ".fn_suffix]#" ~ go ~ ", ");
      }
      bi_tmp ~= " \\\\\n    ";
      bookindex_unordered_hashes[mainkey].remove("_a");
      auto subkeys =
        bookindex_unordered_hashes[mainkey].byKey.array.sort().release;
      foreach (subkey; subkeys) {
        bi_tmp ~= subkey ~ ", ";
        foreach (ref_; bookindex_unordered_hashes[mainkey][subkey]) {
          auto go = replaceAll(ref_, rgx.book_index_go, "$1");
          bi_tmp ~= (segment_object_belongs_to.empty)
          ? (" {" ~ ref_ ~ "}#" ~ go ~ ", ")
          : (" {" ~ ref_ ~ "}[../" ~ segment_object_belongs_to ~ ".fn_suffix]#" ~ go ~ ", ");
        }
        bi_tmp ~= " \\\\\n    ";
        ++skn;
      }
      bi_tmp = replaceFirst(bi_tmp, rgx.trailing_linebreak, "");
      type="para";
      attrib="";
      indent["hang_position"] = "0";
      indent["base_position"] = "1";
      attrib="";
      // bookindex_section ~=
      //   set_abstract_object.contents_para(
      //     obj,
      //     obj_cite_number,
      //     indent,
      //     false
      //   );
      bookindex_section ~=
        set_abstract_object.contents_para(
          type,
          bi_tmp,
          attrib,
          obj_cite_number,
          indent,
          false
        );
      ++obj_cite_number;
      ++mkn;
    }
    auto t = tuple(bookindex_section, obj_cite_number);
    return t;
  }
#+END_SRC

***** book index struct close

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

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

#+name: ao_emitters_endnotes
#+BEGIN_SRC d
struct NotesSection {
  string object_notes;
  long previous_count;
  int mkn;
  auto rgx = Rgx();
#+END_SRC

**** gather notes for endnote section struct open

#+name: ao_emitters_endnotes
#+BEGIN_SRC d
  private auto gather_notes_for_endnote_section(
    ObjComposite[] contents_am,
    string segment_object_belongs_to,
    ulong counter
  )
  in {
    // endnotes/ footnotes for
    // doc objects other than paragraphs & headings
    // various forms of grouped text
    assert((contents_am[counter].is_a == "para")
    || (contents_am[counter].is_a == "heading"));
    assert(counter > previous_count);
    previous_count=counter;
    assert(
      match(contents_am[counter].object,
      rgx.inline_notes_delimiter_al_regular_number_note)
    );
  }
  body {
    foreach(m;
    matchAll(contents_am[counter].object,
    rgx.inline_notes_delimiter_al_regular_number_note)) {
      debug(endnotes_build) {
        writeln(
          "{^{", m.captures[1], ".}^}[../", segment_object_belongs_to, ".fn_suffix]#noteref_\n  ", m.captures[1], " ",
          m.captures[2]); // sometimes need segment name (segmented html & epub)
        // writeln("{^{", m.captures[1], ".}^}#", contents_am[counter]["obj_cite_number"], " ", m.captures[2]);
      }
      object_notes ~= (segment_object_belongs_to.empty)
      ? ("{^{" ~ m.captures[1] ~ ".}^}#noteref_" ~
        m.captures[1] ~ " " ~ m.captures[2] ~ "』")
      : ("{^{" ~ m.captures[1] ~ ".}^}[../" ~ segment_object_belongs_to ~ ".fn_suffix]#noteref_" ~
        m.captures[1] ~ " " ~ m.captures[2] ~ "』");
    }
    return object_notes;
  }
#+END_SRC

**** gathered notes

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

**** endnote objects

#+name: ao_emitters_endnotes
#+BEGIN_SRC d
  private auto endnote_objects(int obj_cite_number)
  in {
  }
  body {
    auto set_abstract_object = ObjectAbstractSet();
    ObjComposite[] endnotes_section;
    auto endnotes_ = gathered_notes();
    // auto endnotes_ = (split(object_notes, rgx.break_string))[0..$-1];
    string type;
    string lev, lev_markup_number, lev_collapsed_number;
    string attrib;
    string[string] indent;
    // B~ Endnotes
    attrib="";
    lev="B";
    lev_markup_number="1";
    lev_collapsed_number="1";
    endnotes_section ~=
      set_abstract_object.contents_heading(
        "Endnotes",
        attrib,
        obj_cite_number,
        [],
        to!string(lev),
        to!int(lev_markup_number),
        to!int(lev_collapsed_number)
      );
    ++obj_cite_number;
    ++mkn;
    // 1~ Endnotes
    attrib="";
    lev="1";
    lev_markup_number="4";
    lev_collapsed_number="2";
    endnotes_section ~=
      set_abstract_object.contents_heading(
        "Endnotes",
        attrib,
        obj_cite_number,
        ["endnotes"],
        to!string(lev),
        to!int(lev_markup_number),
        to!int(lev_collapsed_number)
      );
    ++obj_cite_number;
    ++mkn;
    foreach (endnote; endnotes_) {
      attrib="";
      // endnotes ~=
      //   set_abstract_object.contents_para(
      //     obj,
      //     obj_cite_number,
      //     indent,
      //     false
      //   );
      endnotes_section ~=
        set_abstract_object.contents_endnote(
          endnote,
        );
      ++mkn;
    }
    auto t = tuple(endnotes_section, obj_cite_number);
    return t;
  }
#+END_SRC

**** gather notes for endnote section struct close

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

*** bibliography                                               :bibliography:
**** biblio struct open

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

**** biblio

#+name: ao_emitters_bibliography
#+BEGIN_SRC d
  public JSONValue[] _bibliography_(
    ref string[] biblio_unsorted_incomplete,
    ref JSONValue[] bib_arr_json
  )
  in { }
  body {
    JSONValue[] biblio_unsorted =
      _biblio_unsorted_complete_(biblio_unsorted_incomplete, bib_arr_json);
    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);
      //  writeln("0: ", biblio_sorted__[0]);
      int counter;
      int[7] x;
      while (counter < x.length) {
        writeln(counter, ": ", biblio_sorted__[counter]["fulltitle"]);
        counter++;
      }
    }
    return biblio_sorted__;
  }
#+END_SRC

**** biblio unsorted complete

#+name: ao_emitters_bibliography
#+BEGIN_SRC d
  final private JSONValue[] _biblio_unsorted_complete_(
    string[] biblio_unordered,
    ref JSONValue[] bib_arr_json
  ) {
    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;
    return biblio_unsorted_array_of_json_objects;
  }
#+END_SRC

**** biblio sort

#+name: ao_emitters_bibliography
#+BEGIN_SRC d
  final private JSONValue[] biblio_sort(JSONValue[] biblio_unordered) {
    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"]);
          // writeln(j["deemed_author"], " (", j["author"], ") ",  j["fulltitle"]);
        }
      }
    }
    return biblio_sorted_;
  }
#+END_SRC

**** biblio debug

#+name: ao_emitters_bibliography
#+BEGIN_SRC d
  void biblio_debug(JSONValue[] biblio_sorted) {
    debug(biblio0) {
      foreach (j; biblio_sorted) {
        if (!empty(j["fulltitle"].str)) {
          writeln(j["sortby_deemed_author_year_title"]);
        }
      }
    }
  }
#+END_SRC

**** biblio struct close

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

*** node structure metadata                         :structure:metadata:node:
**** metadata node struct open

#+name: ao_emitters_metadata
#+BEGIN_SRC d
struct NodeStructureMetadata {
  int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7;
  int obj_cite_number;
  int[string] p_; // p_ parent_
  string _node;
#+END_SRC

**** TODO metadata node emitter

#+name: ao_emitters_metadata
#+BEGIN_SRC d
  string node_emitter(
    string lev_markup_number,
    string segment_anchor_tag,
    int obj_cite_number_,
    long counter_,
    int pointer_,
    string is_
  )
  in {
    auto rgx = Rgx();
    assert(is_ != "heading");
    assert(to!int(obj_cite_number_) >= 0);
  }
  body {
    // scope(failure) {
    //   writeln(__FILE__, ":", __LINE__, " failed here:");
    //   writeln("  is  : ", is_);
    //   writeln("  node: ", _node);
    // }
    assert(is_ != "heading"); // should not be necessary
    assert(to!int(obj_cite_number_) >= 0); // should not be necessary
    int obj_cite_number=to!int(obj_cite_number_);
    if (lv7 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_4;
      p_["obj_cite_number"] = lv7;
    } else if (lv6 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_3;
      p_["obj_cite_number"] = lv6;
    } else if (lv5 > State.off) {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_2;
      p_["obj_cite_number"] = lv5;
    } else {
      p_["lev_markup_number"] = DocStructMarkupHeading.h_text_1;
      p_["obj_cite_number"] = lv4;
    }
    _node=("{ " ~
      "\"is\": \"" ~ is_ ~ "\"" ~
      ", \"heading_pointer\": " ~ to!string(pointer_) ~
      ", \"doc_object_pointer\": " ~ to!string(counter_) ~
      ", \"obj_cite_number\": " ~ to!string(obj_cite_number_) ~
      ", \"segment_anchor_tag\": \"" ~ segment_anchor_tag ~ "\"" ~
      ", \"parent_obj_cite_number\": " ~ to!string(p_["obj_cite_number"]) ~
      ", \"parent_lev_markup_number\": " ~ to!string(p_["lev_markup_number"]) ~
      " }"
    );
    debug(node) {
      if (match(lev_markup_number, rgx.levels_numbered_headings)) {
        writeln("* ", to!string(_node));
      } else {
        writeln("* ", to!string(_node));
      }
    }
    JSONValue j = parseJSON(_node);
    assert(j["parent_lev_markup_number"].integer >= 4);
    assert(j["parent_lev_markup_number"].integer <= 7);
    assert(j["parent_obj_cite_number"].integer >= 0);
    return _node;
  }
  invariant() {
  }
#+END_SRC

**** TODO metadata emitter heading

#+name: ao_emitters_metadata
#+BEGIN_SRC d
  string node_emitter_heading(
    string lev_markup_number,
    string lev_collapsed_number,
    string segment_anchor_tag,
    int obj_cite_number_,
    long counter_,
    int pointer_,
    string is_
  )
  in {
    auto rgx = Rgx();
    assert(is_ == "heading");
    assert(to!int(obj_cite_number_) >= 0);
    assert(
      match(lev_markup_number, rgx.levels_numbered),
      ("not a valid heading level: " ~ lev_markup_number ~ " at " ~ to!string(obj_cite_number_))
    );
    // assert(to!int(obj_cite_number_) >= 0);
    if (match(lev_markup_number, rgx.levels_numbered)) {
      if (to!int(lev_markup_number) == 0) {
        assert(to!int(obj_cite_number_) == 1);
        // writeln(lev_markup_number);
      }
    }
  }
  body {
    // scope(failure) {
    //   writeln(__FILE__, ":", __LINE__, " failed here:");
    //   writeln("  is  : ", is_);
    //   writeln("  node: ", _node);
    // }
    auto rgx = Rgx();
    int obj_cite_number = to!int(obj_cite_number_);
    switch (lev_markup_number) { // switch (to!string(lv)) {
    case "0":
      lv = DocStructMarkupHeading.h_sect_A;
      lv0 = obj_cite_number;
      lv1=0; lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"] = 0;
      p_["obj_cite_number"] = 0;
      break;
    case "1":
      lv = DocStructMarkupHeading.h_sect_B;
      lv1 = obj_cite_number;
      lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_sect_A;
      p_["obj_cite_number"] = lv0;
      break;
    case "2":
      lv = DocStructMarkupHeading.h_sect_C;
      lv2 = obj_cite_number;
      lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_sect_B;
      p_["obj_cite_number"] = lv1;
      break;
    case "3":
      lv = DocStructMarkupHeading.h_sect_D;
      lv3=obj_cite_number;
      lv4=0; lv5=0; lv6=0; lv7=0;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_sect_C;
      p_["obj_cite_number"] = lv2;
      break;
    case "4":
      lv = DocStructMarkupHeading.h_text_1;
      lv4 = obj_cite_number;
      lv5=0; lv6=0; lv7=0;
      if (lv3 > State.off) {
        p_["lev_markup_number"] =
          DocStructMarkupHeading.h_sect_D;
        p_["obj_cite_number"] = lv3;
      } else if (lv2 > State.off) {
        p_["lev_markup_number"] =
          DocStructMarkupHeading.h_sect_C;
        p_["obj_cite_number"] = lv2;
      } else if (lv1 > State.off) {
        p_["lev_markup_number"] =
          DocStructMarkupHeading.h_sect_B;
        p_["obj_cite_number"] = lv1;
      } else {
        p_["lev_markup_number"] =
          DocStructMarkupHeading.h_sect_A;
        p_["obj_cite_number"] = lv0;
      }
      break;
    case "5":
      lv = DocStructMarkupHeading.h_text_2;
      lv5 = obj_cite_number;
      lv6=0; lv7=0;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_text_1;
      p_["obj_cite_number"] = lv4;
      break;
    case "6":
      lv = DocStructMarkupHeading.h_text_3;
      lv6 = obj_cite_number;
      lv7=0;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_text_2;
      p_["obj_cite_number"] = lv5;
      break;
    case "7":
      lv = DocStructMarkupHeading.h_text_4;
      lv7 = obj_cite_number;
      p_["lev_markup_number"] =
        DocStructMarkupHeading.h_text_3;
      p_["obj_cite_number"] = lv6;
      break;
    default:
      // if (lv7 > State.off) {
      //   p_["lev_markup_number"] = 7; p_["obj_cite_number"] = lv7;
      // } else if (lv6 > State.off) {
      //   p_["lev_markup_number"] = 6; p_["obj_cite_number"] = lv6;
      // } else if (lv5 > State.off) {
      //   p_["lev_markup_number"] = 5; p_["obj_cite_number"] = lv5;
      // } else {
      //   p_["lev_markup_number"] = 4; p_["obj_cite_number"] = lv4;
      // }
      break;
    }
    _node=("{ " ~
      "\"is\": \"" ~ is_ ~ "\"" ~
      ", \"heading_pointer\": " ~ to!string(pointer_) ~
      ", \"doc_object_pointer\": " ~ to!string(counter_) ~
      ", \"obj_cite_number\": " ~ to!string(obj_cite_number_) ~
      ",  \"lev_markup_number\": " ~ to!string(lev_markup_number) ~
      ",  \"lev_collapsed_number\": " ~ to!string(lev_collapsed_number) ~
      ", \"segment_anchor_tag\": \"" ~ segment_anchor_tag ~ "\"" ~
      ", \"parent_obj_cite_number\": " ~ to!string(p_["obj_cite_number"]) ~
      ", \"parent_lev_markup_number\": " ~ to!string(p_["lev_markup_number"]) ~
      " }"
    );
    debug(heading) {
      if (match(lev_markup_number, rgx.levels_numbered_headings)) {
        writeln("* ", to!string(_node));
      }
    }
    debug(node) {
      if (match(lev_markup_number, rgx.levels_numbered_headings)) {
        writeln("* ", to!string(_node));
      } else {
        writeln("* ", to!string(_node));
      }
    }
    JSONValue j = parseJSON(_node);
    assert(j["parent_lev_markup_number"].integer <= 7);
    assert(j["parent_obj_cite_number"].integer >= 0);
    if (match(lev_markup_number, rgx.levels_numbered_headings)) {
      assert(j["lev_markup_number"].integer <= 7);
      assert(j["obj_cite_number"].integer >= 0);
      if (j["parent_lev_markup_number"].integer > 0) {
        assert(j["parent_lev_markup_number"].integer < j["lev_markup_number"].integer);
        if (j["obj_cite_number"].integer != 0) {
          assert(j["parent_obj_cite_number"].integer < j["obj_cite_number"].integer);
        }
      }
      if (j["lev_markup_number"].integer == 0) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_sect_A);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_sect_B) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_sect_A);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_sect_C) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_sect_B);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_sect_D) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_sect_C);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_text_1) {
        assert(j["parent_lev_markup_number"].integer <= DocStructMarkupHeading.h_sect_D);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_text_2) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_text_1);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_text_3) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_text_2);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_text_4) {
        assert(j["parent_lev_markup_number"].integer == DocStructMarkupHeading.h_text_3);
      } else if  (j["lev_markup_number"].integer == DocStructMarkupHeading.h_text_5) {
        // writeln(j["parent_lev_markup_number"].integer);
        // assert(j["parent_lev_markup_number"].integer >= 4);
        // assert(j["parent_lev_markup_number"].integer <= 7);
      }
    }
    return _node;
  }
  invariant() {
  }
#+END_SRC

**** metadata node struct close

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

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

#+name: abs_functions_assertions
#+BEGIN_SRC d
auto assertions_doc_structure(string[string] an_object, int[string] lv) {
  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 (to!string(an_object["lev"])) {
  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

*** mixin template: assertions on blocks                             :blocks:

#+name: abs_functions_assertions
#+BEGIN_SRC d
auto assertions_flag_types_block_status_none_or_closed(int[string] type) {
  assert(
    (type["code"] == TriState.off)
    || (type["code"] == TriState.closing),
    "code block status: off or closing");
  assert(
    (type["poem"] == TriState.off)
    || (type["poem"] == TriState.closing),
    "poem status: off or closing");
  assert(
    (type["table"] == TriState.off)
    || (type["table"] == TriState.closing),
    "table status: off or closing");
  assert(
    (type["group"] == TriState.off)
    || (type["group"] == TriState.closing),
    "group block status: off or closing");
  assert(
    (type["block"] == TriState.off)
    || (type["block"] == TriState.closing),
    "block status: off or closing");
}
#+END_SRC

* Object Setter                                             :abstract:object:

set abstracted objects for downstream processing

** initialize structs                                                :struct:
*** heading attribute

#+name: ao_structs_init
#+BEGIN_SRC d
struct HeadingAttrib {
  string lev                  = "9";
  int lev_markup_number       = 9;
  int lev_collapsed_number    = 9;
}
#+END_SRC

*** paragraph attribute

#+name: ao_structs_init
#+BEGIN_SRC d
struct ParaAttrib {
  int indent_start            = 0;
  int indent_rest             = 0;
  bool bullet                 = false;
}
#+END_SRC

*** block attribute

#+name: ao_structs_init
#+BEGIN_SRC d
struct BlockAttrib {
  string syntax               = "";
}
#+END_SRC

*** comment attribute

#+name: ao_structs_init
#+BEGIN_SRC d
struct Comment {
  // no .attrib and no .obj_cite_number
}
#+END_SRC

*** TODO node

#+name: ao_structs_init
#+BEGIN_SRC d
struct Node {
  int ocn                     = 0;
  int parent_lev              = 0;
  int parent_ocn              = 0;
  string node                 = "";
}
#+END_SRC

*** TODO composite object

#+name: ao_structs_init
#+BEGIN_SRC d
struct ObjComposite {
  // size_t id;
  string use                  = "";
  string of                   = "";
  string is_a                 = "";
  string object               = "";
  string obj_cite_number      = "";  // not used for calculations? output only? else int
  string[] anchor_tags        = [];
  HeadingAttrib heading_attrib;
  ParaAttrib para_attrib;
  BlockAttrib block_attrib;
  Node node_structure;
}
#+END_SRC

*** object composite array

#+name: ao_structs_init
#+BEGIN_SRC d
struct ObjCompositeArr {
  ObjComposite[] oca;
}
#+END_SRC

** object setter                                                        :set:
*** comment                                                         :comment:

#+name: ao_object_setter_comment
#+BEGIN_SRC d
auto contents_comment(in string object) {
  ObjComposite object_set;
  object_set.use                  = "comment";
  object_set.of                   = "comment";
  object_set.is_a                 = "comment";
  object_set.object               = object;
  return object_set;
}
#+END_SRC

*** heading                                                         :heading:

#+name: ao_object_setter_heading
#+BEGIN_SRC d
auto contents_heading(
  in string object,
  in string attrib,
  in int obj_cite_number,
  in string[] tags,
  in string lev,
  in int lev_markup_number,
  in int lev_collapsed_number,
) {
  ObjComposite object_set;
  object_set.use                                 = "content";
  object_set.of                                  = "para";
  object_set.is_a                                = "heading";
  object_set.object                              = object;
  object_set.obj_cite_number                     = (obj_cite_number==0) ? "" : to!string(obj_cite_number);
  object_set.anchor_tags                         ~= tags;
  object_set.heading_attrib.lev                  = lev;
  object_set.heading_attrib.lev_markup_number    = lev_markup_number;
  object_set.heading_attrib.lev_collapsed_number = lev_collapsed_number;
  // object_set.node_structure.node               = _node;
  return object_set;
}
#+END_SRC

*** para                                                               :para:

#+name: ao_object_setter_para
#+BEGIN_SRC d
auto contents_para(
  in string is_a,
  in string object,
  in string attrib,
  in int obj_cite_number,
  in string[string] indent,
  in bool bullet
) {
  ObjComposite object_set;
  object_set.use                      = "content";
  object_set.of                       = "para";
  object_set.is_a                     = "para";
  object_set.object                   = object.strip;
  object_set.obj_cite_number          = (obj_cite_number==0) ? "" : to!string(obj_cite_number);
  object_set.para_attrib.indent_start = 0; // indent["hang_position"];
  object_set.para_attrib.indent_rest  = 0; // indent["base_position"];
  object_set.para_attrib.bullet       = false;
  // object_set.node_structure.node               = _node;
  return object_set;
}
#+END_SRC

*** para_endnote                                                       :para:

#+name: ao_object_setter_endnote
#+BEGIN_SRC d
auto contents_endnote(
  in string object,
) {
  auto m = (matchFirst(object, rgx.note_ref));
  string notenumber = to!string(m.captures[1]);
  string anchor_tag = "note_" ~ notenumber;
  ObjComposite object_set;
  object_set.use                      = "content";
  object_set.of                       = "para";
  object_set.is_a                     = "endnote";
  object_set.object                   = object.strip;
  object_set.obj_cite_number          = "";
  object_set.para_attrib.indent_start = 0;
  object_set.para_attrib.indent_rest  = 0;
  object_set.para_attrib.bullet       = false;
  return object_set;
}
#+END_SRC

*** block                                                             :block:

#+name: ao_object_setter_block
#+BEGIN_SRC d
auto contents_block(
  in string type,
  in string object,
  in string attrib,
  in int obj_cite_number
) {
  ObjComposite object_set;
  object_set.use                 = "content";
  object_set.of                  = "block";
  object_set.is_a                = type;
  object_set.object              = object;
  object_set.obj_cite_number     = (obj_cite_number==0) ? "" : to!string(obj_cite_number);
  // object_set.node_structure.node               = _node;
  return object_set;
}
#+END_SRC

*** block code                                                   :block:code:

#+name: ao_object_setter_block_code
#+BEGIN_SRC d
auto contents_block_code(
  in string type,
  in string object,
  in string attrib_language_syntax,
  in int obj_cite_number
) {
  ObjComposite object_set;
  object_set.use                 = "content";
  object_set.of                  = "block";
  object_set.is_a                = type;
  object_set.block_attrib.syntax = attrib_language_syntax;
  object_set.object              = object;
  object_set.obj_cite_number     = (obj_cite_number==0) ? "" : to!string(obj_cite_number);
  // object_set.node_structure.node               = _node;
  return object_set;
}
#+END_SRC

*** block obj_cite_number string                                      :block:

#+name: ao_object_setter_block_obj_cite_number_string
#+BEGIN_SRC d
auto contents_block_obj_cite_number_string(
  in string type,
  in string object,
  in string obj_cite_number,
  in string _node
) {
  ObjComposite object_set;
  object_set.use                               = "content";
  object_set.of                                = "block";
  object_set.is_a                              = type;
  object_set.object                            = object;
  object_set.obj_cite_number                   = obj_cite_number;
  object_set.node_structure.node               = _node;
  return object_set;
}
#+END_SRC

* Tangles (code structure)                                   :tangle:io:file:
** ao abstract doc source:                            :ao_abstract_doc_source.d:

#+BEGIN_SRC d :tangle ../src/sdp/ao_abstract_doc_source.d
/++
  document abstraction:
  abstraction of sisu markup for downstream processing
  ao_abstract_doc_source.d
+/
template SiSUdocAbstraction() {
  private:
  struct Abstraction {
    /+ ↓ abstraction imports +/
    <<abs_imports>>
    /+ ↓ abstraction mixins +/
    <<abs_mixins>>
    /+ ↓ abstraction struct init +/
    <<abs_init_struct>>
    /+ ↓ abstract marked up document +/
    auto abstract_doc_source(
      char[][] markup_sourcefile_content,
      string[string][string] dochead_make_aa,
      string[string][string] dochead_meta_aa
    ) {
      /+ ↓ abstraction init +/
      <<abs_init_rest>>
      /+ abstraction init ↑ +/
      /+ ↓ loop markup document/text line by line +/
      srcDocLoop:
      foreach (line; markup_sourcefile_content) {
        /+ ↓ markup document/text line by line +/
        <<abs_in_loop_body_00>>
        <<abs_in_loop_body_00_code_block>>
        <<abs_in_loop_body_00_non_code_block>>
          <<abs_in_loop_body_non_code_obj>>
            <<abs_in_loop_body_open_block_obj>>
            <<abs_in_loop_body_not_block_obj>>
              <<abs_in_loop_body_not_block_obj_line_empty>>
        <<abs_in_loop_body_00_closed>>
        <<abs_in_loop_body_01>>
      } /+ ← closed: loop markup document/text line by line +/
      /+ ↓ post loop markup document/text +/
      <<abs_post>>
      /+ 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_block>>
    <<abs_functions_block_code>>
    <<abs_functions_block_biblio>>
    <<abs_functions_block_poem>>
    <<abs_functions_block_group>>
    <<abs_functions_block_block>>
    <<abs_functions_block_quote>>
    <<abs_functions_block_table>>
    <<abs_functions_block_line_status_empty>>
    <<abs_functions_book_index>>
    <<abs_functions_heading>>
    <<abs_functions_para>>
    /+ abstraction functions ↑ +/
    /+ ↓ abstraction function emitters +/
    <<ao_emitters_ocn>>
    <<ao_emitters_obj_inline_markup_munge>>
    <<ao_emitters_obj_inline_markup>>
    <<ao_emitters_obj_inline_markup_and_anchor_tags>>
    <<ao_emitters_obj_inline_markup_private>>
    <<ao_emitters_obj_inline_markup_heading_numbering_segment_anchor_tags>>
    <<ao_emitters_obj_inline_markup_close>>
    <<ao_emitters_obj_attributes>>
    <<ao_emitters_obj_attributes_public>>
    <<ao_emitters_obj_attributes_private>>
    <<ao_emitters_obj_attributes_private_an_attribute>>
    <<ao_emitters_obj_attributes_private_json>>
    <<ao_emitters_obj_attributes_private_close>>
    <<ao_emitters_book_index_nugget>>
    <<ao_emitters_book_index_report_indented>>
    <<ao_emitters_book_index_report_section>>
    <<ao_emitters_endnotes>>
    <<ao_emitters_bibliography>>
    <<ao_emitters_metadata>>
    /+ abstraction functions emitters ↑ +/
    /+ ↓ abstraction functions assertions +/
    <<abs_functions_assertions>>
    /+ abstraction functions assertions ↑ +/
  } /+ ← closed: struct Abstraction +/
} /+ ← closed: template SiSUdocAbstraction +/
#+END_SRC

** ao object setter:                                     :ao_object_setter.d:

#+BEGIN_SRC d :tangle ../src/sdp/ao_object_setter.d
/++
  object setter:
  setting of sisu objects for downstream processing
  ao_object_setter.d
+/
template ObjectSetter() {
  /+ structs +/
  <<ao_structs_init>>
  /+ structs setter +/
  struct ObjectAbstractSet {
    import std.conv : to;
    <<ao_object_setter_comment>>
    <<ao_object_setter_heading>>
    <<ao_object_setter_para>>
    <<ao_object_setter_endnote>>
    <<ao_object_setter_block>>
    <<ao_object_setter_block_code>>
    <<ao_object_setter_block_obj_cite_number_string>>
  }
}
#+END_SRC

* TODO work on

- bespoke struct for sisu objects to replace JSON strings
- book index *sort* fix (Aa-Zz instead of A-Za-z)
- determine what goes in node info
  - understand collapsed level
- convert file utf8 for processing from to utf32

- general concepts
  - ranges
  - templates
  - unitest?

|---------------------+------------------------------------------+------------------------+--------|
| header              | sisu /header markup/                       | markup                 |        |
| - metadata          |                                          |                        |        |
| - make instructions |                                          |                        |        |
|---------------------+------------------------------------------+------------------------+--------|
| table of contents   | markup of headings                       | (regular content)      | output |
|---------------------+------------------------------------------+------------------------+--------|
| substantive content | sisu /content markup/                      | markup                 | output |
|                     | headings (providing document structure), | (regular content)      |        |
|                     | paragraphs, blocks                       |                        |        |
|                     | blocks (code, poem, group, table)        |                        |        |
|---------------------+------------------------------------------+------------------------+--------|
| endnotes            | markup within substantive content        | markup                 | output |
|                     | (extracted from sisu /content markup/)     | (from regular content) |        |
|---------------------+------------------------------------------+------------------------+--------|
| glossary            | identify special section                 | markup                 | output |
|                     | regular /content markup/                   |                        |        |
|---------------------+------------------------------------------+------------------------+--------|
| bibliography        | identify section,                        | markup (special)       | output |
|                     | special /bibliography markup/              |                        |        |
|---------------------+------------------------------------------+------------------------+--------|
| book index          | extracted from markup attached to        | markup                 | output |
|                     | related substantive content objects      |                        |        |
|                     | (special tags in sisu /content markup/)    | (from regular content) |        |
|---------------------+------------------------------------------+------------------------+--------|
| metadata            |                                          | (from regular header)  | output |
|---------------------+------------------------------------------+------------------------+--------|