/*
#+OPTIONS: ^:nil _:nil#+OPTIONS: ^:nil _:nil
* sisu_emitter.d
*/
mixin template Emitters() {
  mixin InternalMarkup;
  class CLI {
    string[string] extract_actions(string cmdlnins, string[string] actions)
    in { }
    body {
      switch (cmdlnins) {
      case "--no-assert":
        actions["assert"] = "no";
        break;
      default:
        break;
      }
      return actions;
    }
  }
  class OCNemitter : AssertOCN {
    int ocn, ocn_;
    int ocn_emitter(int ocn_status_flag)
    in { assert(ocn_status_flag <= 2); }
    body {
      if (ocn_status_flag == 0) {
        ocn=++ocn_;
      } else {
        ocn=0;
      }
      return ocn;
    }
    invariant() {
    }
  }
  class ObjAttributes {
    string[string] obj_txt;
    string para_and_blocks(string obj_txt_in)
    in { }
    body {
      auto rgx = new Rgx();
      obj_txt["munge"]=obj_txt_in;
      if (match(obj_txt_in, rgx.para_bullet)) {
        obj_txt["attrib"] =" \"bullet\": \"true\","
        ~ " \"indent_first\": 0,"
        ~ " \"indent_rest\": 0,";
      } else if (auto m = match(obj_txt_in, rgx.para_bullet_indent)) {
        obj_txt["attrib"] =" \"bullet\": \"true\","
        ~ " \"indent_first\": " ~ to!string(m.captures[1]) ~ ","
        ~ " \"indent_rest\": " ~ to!string(m.captures[1]) ~ ",";
      } else if (auto m = match(obj_txt_in, rgx.para_indent_hang)) {
        obj_txt["attrib"] =" \"bullet\": \"false\","
        ~ " \"indent_first\": " ~ to!string(m.captures[1]) ~ ","
        ~ " \"indent_rest\": " ~  to!string(m.captures[2]) ~ ",";
      } else if (auto m = match(obj_txt_in, rgx.para_indent)) {
        obj_txt["attrib"] =" \"bullet\": \"false\","
        ~ " \"indent_first\": " ~ to!string(m.captures[1]) ~ ","
        ~ " \"indent_rest\": " ~ to!string(m.captures[1]) ~ ",";
      } else {
        obj_txt["attrib"] =" \"bullet\": \"false\","
        ~ " \"indent_first\": 0,"
        ~ " \"indent_rest\": 0,";
      }
      return obj_txt["attrib"];
    }
    string para(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"para\","
      ~ " \"is\": \"para\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string heading(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"para\","
      ~ " \"is\": \"heading\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string header_make(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"head\","
      ~ " \"of\": \"header\","
      ~ " \"is\": \"header_make\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string header_metadata(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"head\","
      ~ " \"of\": \"header\","
      ~ " \"is\": \"header_metadata\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string code(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"code\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string group(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"group\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string block(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"block\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string verse(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"verse\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string quote(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"quote\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string table(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"content\","
      ~ " \"of\": \"block\","
      ~ " \"is\": \"table\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
    string comment(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      obj_txt["attrib"] = " \"use\": \"comment\","
      ~ " \"of\": \"comment\","
      ~ " \"is\": \"comment\"";
      return obj_txt["attrib"];
    }
    invariant() {
    }
  }
  class ObjInlineMarkupMunge {
    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 = new Rgx();
      auto mkup = new InternalMarkup();
      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;
        }
      } 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 = new 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.ocn_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 = new Rgx();
      obj_txt["munge"]=obj_txt_in;
      obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.heading, "");
      obj_txt["munge"]=replaceFirst(obj_txt["munge"], rgx.ocn_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() {
    }
    string header_make(string obj_txt_in)
    in { }
    body {
      obj_txt["munge"]=obj_txt_in;
      return obj_txt["munge"];
    }
    invariant() {
    }
    string header_metadata(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() {
    }
  }
  class ObjInlineMarkup : AssertObjInlineMarkup {
    auto munge = new ObjInlineMarkupMunge();
    string[string] obj_txt;
    string obj_inline_markup(string obj_is_, string obj_raw)
    in { }
    body {
      obj_txt["munge"]=obj_raw.dup;
      obj_txt["munge"]=(match(obj_is_, ctRegex!(`verse|code`)))
        ? obj_txt["munge"]
        : strip(obj_txt["munge"]);
      switch (obj_is_) {
      case "header_make":
        obj_txt["munge"]=munge.header_make(obj_txt["munge"]);
        break;
      case "header_metadata":
        obj_txt["munge"]=munge.header_metadata(obj_txt["munge"]);
        break;
      case "heading":
        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;
      }
      return obj_txt["munge"];
    }
    invariant() {
    }
  }
  class ObjAttrib : AssertObjAttrib {
// auto sink = appender!(char[])();
    auto attrib = new ObjAttributes();
    string[string] obj_attrib;
    string obj_attributes(string obj_is_, string obj_raw, string node)
    in { }
    body {
      scope(exit) {
        destroy(obj_raw);
        destroy(node);
      }
      JSONValue node_j = parseJSON(node);
      obj_attrib.remove("json");
      obj_attrib["json"] ="{";
      switch (obj_is_) {
      case "header_make":
        obj_attrib["json"] ~= attrib.header_make(obj_raw);
        break;
      case "header_metadata":
        obj_attrib["json"] ~= attrib.header_metadata(obj_raw);
        break;
      case "heading":
        obj_attrib["json"] ~= attrib.heading(obj_raw); //
        break;
      case "para":
        obj_attrib["json"] ~= attrib.para_and_blocks(obj_raw)
        ~ attrib.para(obj_raw);
        break;
      case "code":
        obj_attrib["json"] ~= attrib.code(obj_raw);
        break;
      case "group":
        obj_attrib["json"] ~= attrib.para_and_blocks(obj_raw)
        ~ attrib.group(obj_raw);
        break;
      case "block":
        obj_attrib["json"] ~= attrib.para_and_blocks(obj_raw)
        ~ attrib.block(obj_raw);
        break;
      case "verse":
        obj_attrib["json"] ~= attrib.verse(obj_raw);
        break;
      case "quote":
        obj_attrib["json"] ~= attrib.quote(obj_raw);
        break;
      case "table":
        obj_attrib["json"] ~= attrib.table(obj_raw);
        break;
      case "comment":
        obj_attrib["json"] ~= attrib.comment(obj_raw);
        break;
      default:
        obj_attrib["json"] ~= attrib.para(obj_raw);
        break;
      }
      obj_attrib["json"] ~=" }";
      JSONValue oa_j = parseJSON(obj_attrib["json"]);
      assert(
        (oa_j.type == JSON_TYPE.OBJECT) &&
        (node_j.type == JSON_TYPE.OBJECT)
      );
      if (obj_is_ == "heading") {
        oa_j.object["ocn"] = node_j["ocn"];
        oa_j.object["lvn"] = node_j["lvn"];
        oa_j.object["lcn"] = node_j["lcn"];
        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_ocn"] = node_j["parent_ocn"];
      oa_j.object["parent_lvn"] = node_j["parent_lvn"];
      obj_attrib["json"] = oa_j.toString();
      debug(structattrib) {
        if (oa_j["is"].str() == "heading") {
          writeln(obj_attrib["json"]);
          writeln(
            "is: ", oa_j["is"].str(),
            "; ocn: ", oa_j["ocn"].integer()
          );
        }
      }
      return obj_attrib["json"];
    }
    invariant() {
    }
  }
  class HeaderDocMetadataMakeJson {
    auto rgx = new Rgx();
    string hm, hs;
    auto header_metadata_and_make_jsonstr(
      string header,
      JSONValue[string] dochead_metadata,
      JSONValue[string] dochead_make
    )
    in { }
    body {
      scope(exit) {
        destroy(header);
        destroy(dochead_metadata);
        destroy(dochead_make);
      }
      if (auto t = match(header, rgx.head_main)) {
        char[][] obj_spl = split(
          cast(char[]) header,
          rgx.line_delimiter_ws_strip
        );
        auto hm = to!string(t.captures[1]);
        if (match(hm, rgx.main_headers)) {
          foreach (line; obj_spl) {
            if (auto m = match(line, rgx.head_main)) {
              if (!empty(m.captures[2])) {
                if (hm == "creator") {
                  dochead_metadata[hm]["author"].str =
                    to!string(m.captures[2]);
                } else if (hm == "title") {
                  dochead_metadata[hm]["main"].str =
                    to!string(m.captures[2]);
                } else if (hm == "publisher") {
                  dochead_metadata[hm]["name"].str =
                    to!string(m.captures[2]);
                }
              }
            } else if (auto s = match(line, rgx.head_sub)) {
              if (!empty(s.captures[2])) {
                auto hs = to!string(s.captures[1]);
                if ((hm == "make" )
                && (dochead_make[hm].type() == JSON_TYPE.OBJECT)) {
                  switch (hm) {
                  case "make":
                    if (match(hs, rgx.subhead_make)) {
                      if (dochead_make[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_make[hm][hs].str = to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  default:
                    break;
                  }
                } else if (dochead_metadata[hm].type() == JSON_TYPE.OBJECT) {
                  switch (hm) {
                  case "creator":
                    if (match(hs, rgx.subhead_creator)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "title":
                    if (match(hs, rgx.subhead_title)) {
                      if ((hs == "subtitle")
                      && (dochead_metadata[hm]["sub"].type() == JSON_TYPE.STRING)) {
                        dochead_metadata[hm]["sub"].str =
                          to!string(s.captures[2]);
                      } else if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "rights":
                    if (match(hs, rgx.subhead_rights)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "date":
                    if (match(hs, rgx.subhead_date)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "original":
                    if (match(hs, rgx.subhead_original)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "classify":
                    if (match(hs, rgx.subhead_classify)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "identifier":
                    if (match(hs, rgx.subhead_identifier)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "notes":
                    if (match(hs, rgx.subhead_notes)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "publisher":
                    if (match(hs, rgx.subhead_publisher)) {
                      if (dochead_metadata[hm][hs].type() == JSON_TYPE.STRING) {
                        dochead_metadata[hm][hs].str =
                          to!string(s.captures[2]);
                      }
                    } else {
                      writeln("not a valid header type:", hm, ":", hs);
                      destroy(hm);
                      destroy(hs);
                    }
                    break;
                  case "links":
                    destroy(hm);
                    destroy(hs);
                    break;
                  default:
                    break;
                  }
                }
              }
            }
          }
        } else {
          writeln("not a valid header type:", hm);
        }
      }
      auto t = tuple(dochead_metadata, dochead_make);
      static assert(!isTypeTuple!(t));
      return t;
    }
  }
  class HeaderMetadataMakeHash {
    auto rgx = new Rgx();
    string header_main;
    string[string] head;
    string[string] header_topic_hash(string header)
    in { }
    body {
      if (auto t = match(header, rgx.head_main)) {
        char[][] obj_spl = split(
          cast(char[]) header,
          rgx.line_delimiter_ws_strip
        );
        auto header_main = to!string(t.captures[1]);
        head[header_main] = "{";
        foreach (line; obj_spl) {
          if (auto m = match(line, rgx.head_main)) {
            if (!empty(m.captures[2])) {
              head[header_main] ~=
                "\"" ~ header_main ~
                "\": \"" ~
                to!string(m.captures[2]) ~
                "\",";
            }
          } else if (auto s = match(line, rgx.head_sub)) {
            head[header_main] ~= "\"" ~ s.captures[1] ~ "\":";
            if (!empty(s.captures[2])) {
              head[header_main] ~= "\"" ~ s.captures[2] ~ "\",";
            }
          }
        }
        head[header_main] = replaceFirst(
          head[header_main],
          rgx.tailing_comma,
          ""
        );
        head[header_main] ~= "}";
        debug(headerjson) {
          JSONValue j = parseJSON(head[header_main]);
          assert(
            (j.type == JSON_TYPE.OBJECT)
          );
        }
      }
      return head;
    }
    invariant() {
    }
  }
  class BookIndexNuggetHash : AssertBookIndexNuggetHash {
    string main_term, sub_term, sub_term_bits;
    uint ocn_offset, ocn_endpoint;
    string[] ocns;
    string[][string][string] bi;
    string[][string][string] hash_nugget;
    string[] bi_main_terms_split_arr;
    string[][string][string] bookindex_nugget_hash(string bookindex, int ocn)
    in { }
    body {
      auto rgx = new Rgx();
      if (!bookindex.empty) {
        auto bi_main_terms_split_arr =
          split(bookindex, 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_ocns_match)
          ) {
            main_term = strip(m.captures[1]);
            ocn_offset = to!uint(m.captures[2]);
            ocn_endpoint=(ocn + ocn_offset);
            ocns ~= (to!string(ocn) ~ "-" ~ to!string(ocn_endpoint));
          } else {
            main_term = strip(bi_main_term_and_rest[0]);
            ocns ~= to!string(ocn);
          }
          bi[main_term]["_a"] ~= ocns;
          ocns=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_ocn_offset_split
              );
            foreach (sub_terms_bits; bi_sub_terms_split_arr) {
              if (auto m = match(sub_terms_bits, rgx.bi_term_and_ocns_match)) {
                sub_term = strip(m.captures[1]);
                ocn_offset = to!uint(m.captures[2]);
                ocn_endpoint=(ocn + ocn_offset);
                ocns ~= (to!string(ocn) ~ " - " ~ to!string(ocn_endpoint));
              } else {
                sub_term = strip(sub_terms_bits);
                ocns ~= to!string(ocn);
              }
              if (!empty(sub_term)) {
                bi[main_term][sub_term] ~= ocns;
              }
              ocns=null;
            }
          }
        }
      }
      hash_nugget = bi;
      return hash_nugget;
    }
    invariant() {
    }
  }
  class BookIndexReport {
    int mkn, skn;
    auto bookindex_report_sorted(
      string[][string][string] bookindex_unordered_hashes
    ) {
      auto mainkeys=bookindex_unordered_hashes.byKey.array.
        sort!("toLower(a) < toLower(b)", SwapStrategy.stable).release;
      foreach (mainkey; mainkeys) {
        auto subkeys=bookindex_unordered_hashes[mainkey].byKey.array.
          sort!("toLower(a) < toLower(b)", SwapStrategy.stable).release;
        foreach (subkey; subkeys) {
          debug(bookindex) {
            writeln(
              mainkey, ": ",
              subkey, ": ",
              to!string(bookindex_unordered_hashes[mainkey][subkey])
            );
          }
          skn++;
        }
        mkn++;
      }
    }
  }
  class 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]
            ));
          }
          skn++;
        }
        mkn++;
      }
    }
  }
  class BookIndexReportSection {
    mixin ObjectSetters;
    int mkn, skn;
    auto rgx = new Rgx();
    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++;
      }
    }
    auto bookindex_build_section(
      string[][string][string] bookindex_unordered_hashes,
      int ocn
    ) {
      string type;
      int type_heading;
      string lev, lvn, lcn;
      string attrib;
      string indent_first;
      string indent_second;
      auto set_oa = new ObjectAbstractSet();
      auto mainkeys =
        bookindex_unordered_hashes.byKey.array.sort().release;
      string bi_tmp;
      string[string][1024] bookindex_arbitrary_max_length_set;
      writeln(mainkeys.length);
      type_heading=1;
      bi_tmp = "Book Index";
      attrib="";
      lev="B";
      lvn="1";
      lcn="1";
      bookindex_arbitrary_max_length_set[mkn] =
        set_oa.contents_heading(
          type_heading,
          bi_tmp,
          attrib,
          ocn,
          lev,
          lvn,
          lcn
        );
      ocn++;
      mkn++;
      type_heading=1;
      bi_tmp = "Index";
      attrib="";
      lev="1";
      lvn="4";
      lcn="2";
      bookindex_arbitrary_max_length_set[mkn] =
        set_oa.contents_heading(
          type_heading,
          bi_tmp,
          attrib,
          ocn,
          lev,
          lvn,
          lcn
        );
      ocn++;
      mkn++;
      foreach (mainkey; mainkeys) {
        bi_tmp = "!{" ~ mainkey ~ "}! ";
        foreach (ref_; bookindex_unordered_hashes[mainkey]["_a"]) {
          auto go = replaceAll(ref_, rgx.book_index_go, "$1");
          bi_tmp ~= " {" ~ ref_ ~ "}#" ~ 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 ~= " {" ~ ref_ ~ "}#" ~ go ~ ", ";
          }
          bi_tmp ~= " \\\\\n    ";
          skn++;
        }
        bi_tmp = replaceFirst(bi_tmp, rgx.trailing_linebreak, "");
        type="para";
        attrib="";
        indent_first = "0";
        indent_second = "1";
        attrib="";
        bookindex_arbitrary_max_length_set[mkn] =
          set_oa.contents_para(
            type,
            bi_tmp,
            attrib,
            ocn,
            indent_first,
            indent_second,
            false
          );
        ocn++;
        mkn++;
      }
      auto bookindex =
        bookindex_arbitrary_max_length_set[0..mkn].dup;
      auto t = tuple(bookindex, ocn);
      return t;
    }
    auto bookindex_build_section_(
      string[][string][string] bookindex_unordered_hashes
    ) {
      auto mainkeys =
        bookindex_unordered_hashes.byKey.array.sort().release;
      string bi_tmp;
      string[1024] bookindex_arbitrary_max_length_set;
      writeln(mainkeys.length);
      foreach (mainkey; mainkeys) {
        bi_tmp = "_0_1 !{" ~ mainkey ~ "}! ";
        foreach (ref_; bookindex_unordered_hashes[mainkey]["_a"]) {
          auto go = replaceAll(ref_, rgx.book_index_go, "$1");
          bi_tmp ~= " {" ~ ref_ ~ "}#" ~ 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 ~= " {" ~ ref_ ~ "}#" ~ go ~ ", ";
          }
          bi_tmp ~= " \\\\\n    ";
          skn++;
        }
        bi_tmp = replaceFirst(bi_tmp, rgx.trailing_linebreak, "");
        bookindex_arbitrary_max_length_set[mkn] = bi_tmp;
        mkn++;
      }
      auto bookindex =
        bookindex_arbitrary_max_length_set[0..mkn].dup;
      return bookindex;
    }
  }
  class NotesSection {
    mixin ObjectSetters;
    string object_notes;
    int previous_count;
    int mkn;
    auto rgx = new Rgx();
    private auto gather_notes_for_endnote_section(
      string[string][131072] contents_arbitrary_max_length_set,
      int counter
    )
    in {
      assert((contents_arbitrary_max_length_set[counter]["is"] == "para")
      || (contents_arbitrary_max_length_set[counter]["is"] == "heading"));
      assert(counter > previous_count);
      previous_count=counter;
      assert(
        match(contents_arbitrary_max_length_set[counter]["obj"],
        rgx.inline_notes_delimiter_al_regular_number_note)
      );
    }
    body {
      foreach(m;
      matchAll(contents_arbitrary_max_length_set[counter]["obj"],
      rgx.inline_notes_delimiter_al_regular_number_note)) {
        debug(endnotes_build) {
          writeln(
            "{^{", m.captures[1], ".}^}#noteref_", m.captures[1], " ",
            m.captures[2]); // sometimes need segment name (segmented html & epub)
        }
        object_notes ~=
          "{^{" ~ m.captures[1] ~ ".}^}#noteref_" ~
          m.captures[1] ~ " " ~ m.captures[2] ~ "』";
      }
      return object_notes;
    }
    private auto gathered_notes()
    in {
    }
    body {
      string[] endnotes_;
      if (object_notes.length > 1) {
        endnotes_ = (split(object_notes, rgx.break_string))[0..$-1];
      }
      return endnotes_;
    }
    private auto endnote_objects(int ocn)
    in {
    }
    body {
      auto set_oa = new ObjectAbstractSet();
      string[string][1024] endnotes_arbitrary_max_length_set;
      auto endnotes_ = gathered_notes();
      string type;
      int type_heading;
      string lev, lvn, lcn;
      string attrib;
      string indent_first;
      string indent_second;
      type_heading=1;
      attrib="";
      lev="B";
      lvn="1";
      lcn="1";
      endnotes_arbitrary_max_length_set[mkn] =
        set_oa.contents_heading(
          type_heading,
          "Endnotes",
          attrib,
          ocn,
          lev,
          lvn,
          lcn
        );
      ocn++;
      mkn++;
      type_heading=1;
      attrib="";
      lev="1";
      lvn="4";
      lcn="2";
      endnotes_arbitrary_max_length_set[mkn] =
        set_oa.contents_heading(
          type_heading,
          "Endnotes",
          attrib,
          ocn,
          lev,
          lvn,
          lcn
        );
      ocn++;
      mkn++;
      foreach (endnote; endnotes_) {
        type="para";
        attrib="";
        indent_first = "0";
        indent_second = "0";
        attrib="";
        endnotes_arbitrary_max_length_set[mkn] =
          set_oa.contents_para(
            type,
            endnote,
            attrib,
            ocn,
            indent_first,
            indent_second,
            false
          );
        ocn++;
        mkn++;
      }
      auto endnotes =
        endnotes_arbitrary_max_length_set[0..mkn].dup;
      auto t = tuple(endnotes, ocn);
      return t;
    }
  }
  class Bibliography {
    public JSONValue[] bibliography(string[] biblio_unsorted_incomplete)
    in { }
    body {
      JSONValue[] biblio_unsorted =
        biblio_unsorted_complete(biblio_unsorted_incomplete);
      JSONValue[] biblio_sorted = biblio_sort(biblio_unsorted);
      biblio_debug(biblio_sorted);
      return biblio_sorted;
    }
    final private JSONValue[] biblio_unsorted_complete(
      string[] biblio_unordered
    ) {
      JSONValue[1024] bib_arr_json;
      int count_biblio_entry;
      count_biblio_entry=0;
      foreach (bibent; biblio_unordered) {
        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[count_biblio_entry] = j;
        count_biblio_entry++;
      }
      JSONValue[] biblio_unsorted_array_of_json_objects =
        bib_arr_json[0..(count_biblio_entry)].dup;
      return biblio_unsorted_array_of_json_objects;
    }
    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"]);
          }
        }
      }
      return biblio_sorted;
    }
    auto biblio_debug(JSONValue[] biblio_sorted) {
      debug(biblio) {
        foreach (j; biblio_sorted) {
          if (!empty(j["fulltitle"].str)) {
            writeln(j["sortby_deemed_author_year_title"]);
          }
        }
      }
    }
  }
  class NodeStructureMetadata : AssertNodeJSON {
    int lv, lv0, lv1, lv2, lv3, lv4, lv5, lv6, lv7;
    uint ocn;
    uint[string] p_; // p_ parent_
    string node;
    string node_emitter(
      string lvn,
      int ocn_,
      int counter_,
      int pointer_,
      string is_
    )
    in {
      auto rgx = new Rgx();
    }
    body {
      assert(is_ != "heading"); // should not be necessary
      assert(to!int(ocn_) >= 0); // should not be necessary
      uint ocn=to!uint(ocn_);
      if (lv7 > 0) {
        p_["lvn"] = 7; p_["ocn"] = lv7;
      } else if (lv6 > 0) {
        p_["lvn"] = 6; p_["ocn"] = lv6;
      } else if (lv5 > 0) {
        p_["lvn"] = 5; p_["ocn"] = lv5;
      } else {
        p_["lvn"] = 4; p_["ocn"] = lv4;
      }
      node=("{ " ~
        "\"is\": \"" ~ is_ ~ "\"" ~
        ", \"heading_pointer\": " ~ to!string(pointer_) ~
        ", \"doc_object_pointer\": " ~ to!string(counter_) ~
        ", \"ocn\": " ~ to!string(ocn_) ~
        ", \"parent_ocn\": " ~ to!string(p_["ocn"]) ~
        ", \"parent_lvn\": " ~ to!string(p_["lvn"]) ~
        " }"
      );
      return node;
    }
    invariant() {
    }
    string node_emitter_heading(
      string lvn,
      string lcn,
      int ocn_,
      int counter_,
      int pointer_,
      string is_
    )
    in {
      auto rgx = new Rgx();
    }
    body {
      uint ocn=to!uint(ocn_);
      switch (lvn) { // switch (to!string(lv)) {
      case "0":
        lv=0;
        lv0=ocn; lv1=0; lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
        p_["lvn"] = 0; p_["ocn"] = 0;
        break;
      case "1":
        lv=1;
        lv1=ocn; lv2=0; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
        p_["lvn"] = 0; p_["ocn"] = lv0;
        break;
      case "2":
        lv=2;
        lv2=ocn; lv3=0; lv4=0; lv5=0; lv6=0; lv7=0;
        p_["lvn"] = 1; p_["ocn"] = lv1;
        break;
      case "3":
        lv=3;
        lv3=ocn; lv4=0; lv5=0; lv6=0; lv7=0;
        p_["lvn"] = 2; p_["ocn"] = lv2;
        break;
      case "4":
        lv=4;
        lv4=ocn; lv5=0; lv6=0; lv7=0;
        if (lv3 > 0) {
          p_["lvn"] = 3; p_["ocn"] = lv3;
        } else if (lv2 > 0) {
          p_["lvn"] = 2; p_["ocn"] = lv2;
        } else if (lv1 > 0) {
          p_["lvn"] = 1; p_["ocn"] = lv1;
        } else {
          p_["lvn"] = 0; p_["ocn"] = lv0;
        }
        break;
      case "5":
        lv=5;
        lv5=ocn; lv6=0; lv7=0;
        p_["lvn"] = 4; p_["ocn"] = lv4;
        break;
      case "6":
        lv=6;
        lv6=ocn; lv7=0;
        p_["lvn"] = 5; p_["ocn"] = lv5;
        break;
      case "7":
        lv=7;
        lv7=ocn;
        p_["lvn"] = 6; p_["ocn"] = lv6;
        break;
      default:
        break;
      }
      node=("{ " ~
        "\"is\": \"" ~ is_ ~ "\"" ~
        ", \"heading_pointer\": " ~ to!string(pointer_) ~
        ", \"doc_object_pointer\": " ~ to!string(counter_) ~
        ", \"ocn\": " ~ to!string(ocn_) ~
        ",  \"lvn\": " ~ to!string(lvn) ~
        ",  \"lcn\": " ~ to!string(lcn) ~
        ", \"parent_ocn\": " ~ to!string(p_["ocn"]) ~
        ", \"parent_lvn\": " ~ to!string(p_["lvn"]) ~
        " }"
      );
      return node;
    }
    invariant() {
    }
  }
}