#+TITLE: sdp output html
#+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:output:
#+TAGS: assert(a) class(c) debug(d) mixin(m) sdp(s) tangle(T) template(t) WEB(W) noexport(n)

[[./sdp.org][sdp]]  [[./][org/]]
* 0. output hub [#A]

#+BEGIN_SRC d :tangle ../src/sdp/output_hub.d
/++
  output hub<br>
  check & generate output types requested
+/
template outputHub() {
  private import
    std.regex,
    std.algorithm,
    std.array,
    std.container,
    std.exception,
    std.getopt,
    std.process,
    std.stdio,
    std.file,
    std.path,
    std.range,
    std.regex,
    std.string,
    std.traits,
    std.typecons,
    std.uni,
    std.utf;
  import
    defaults,
    output_epub3,
    output_html,
    output_xhtmls,
    source_sisupod,
    create_zip_file,
    output_rgx,
    output_xhtmls;
  void outputHub(D,I)(D doc_abstraction, I doc_matters) {
    mixin SiSUoutputRgxInit;
    auto rgx = Rgx();
    if ((doc_matters.opt_action_bool["verbose"])) {
      writeln(doc_matters.keys_seq.seg);
    }
    if (doc_matters.opt_action_bool["source"]) {
      /+ mixin outputSource; +/
      writeln("source");
    }
    if (doc_matters.opt_action_bool["sisupod"]) {
      if ((doc_matters.opt_action_bool["verbose"])) {write("sisupod source processing... ");}
      SiSUpod!()(doc_matters);
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("sisupod done");}
    }
    if (doc_matters.opt_action_bool["text"]) {
      /+ mixin outputText; +/
      writeln("text processing");
    }
    if (doc_matters.opt_action_bool["html"]) {
      if ((doc_matters.opt_action_bool["verbose"])) {write("html scroll processing... ");}
      outputHTML!().scroll(doc_abstraction, doc_matters);
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("html scroll done");}
      if ((doc_matters.opt_action_bool["verbose"])) {write("html seg processing... ");}
      outputHTML!().seg(doc_abstraction, doc_matters);
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("html seg done");}
    } else if (doc_matters.opt_action_bool["html_seg"]) {
      if ((doc_matters.opt_action_bool["verbose"])) {write("html seg processing... ");}
      outputHTML!().seg(doc_abstraction, doc_matters);
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("html seg done");}
    } else if (doc_matters.opt_action_bool["html_scroll"]) {
      if ((doc_matters.opt_action_bool["verbose"])) {write("html scroll processing... ");}
      outputHTML!().scroll(doc_abstraction, doc_matters);
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("html scroll done");}
    }
    if (doc_matters.opt_action_bool["epub"]) {
      if ((doc_matters.opt_action_bool["verbose"])) {write("epub3 processing... ");}
      outputEPub3!()(doc_abstraction, doc_matters);
      // epub.css_write;
      if ((doc_matters.opt_action_bool["verbose"])) {writeln("epub3 done");}
    }
    if (doc_matters.opt_action_bool["pdf"]) {
      /+ mixin outputPDF; +/
      writeln("pdf processing");
    }
    if (doc_matters.opt_action_bool["odt"]) {
      /+ mixin outputODT; +/
      writeln("odt processing");
    }
    if (doc_matters.opt_action_bool["sqlite"]) {
      /+ mixin outputSQLite; +/
      writeln("sqlite processing");
    }
    if (doc_matters.opt_action_bool["postgresql"]) {
      /+ mixin outputPostgreSQL; +/
      writeln("pgsql processing");
    }
  }
}
#+END_SRC

* 1. output functions                                                :output:
** output imports

#+name: output_imports
#+BEGIN_SRC d
private import
  std.algorithm,
  std.array,
  std.container,
  std.digest.sha,
  std.exception,
  std.file,
  std.getopt,
  std.json,
  std.outbuffer,
  std.path,
  std.process,
  std.range,
  std.regex,
  std.stdio,
  std.string,
  std.traits,
  std.typecons,
  std.uni,
  std.utf,
  std.zip,
  std.conv : to;
import
  create_zip_file,
  defaults,
  output_rgx,
  output_xhtmls;
#+END_SRC

** _zip_
*** template                                                     :template:

#+BEGIN_SRC d :tangle ../src/sdp/create_zip_file.d
template createZipFile() {
  import std.file;
  import std.outbuffer;
  import std.string;
  import std.zip;
  void createZipFile(
    string zip_file_name,
    void[] compressed_zip_data,
  ) {
    try {
      write(zip_file_name, compressed_zip_data);
    } catch (ZipException ex) {
      // Handle Errors
    }
  }
}
#+END_SRC

** _sisupod_                                                         :sisupod:
*** template                                                     :template:

#+BEGIN_SRC d :tangle ../src/sdp/source_sisupod.d
template SiSUpod() {
  <<output_imports>>
  void SiSUpod(T)(T doc_matters) {
    <<source_sisupod_init>>
    try {
      <<source_sisupod_mkdirs>>
      <<source_sisupod_copy>>
      <<source_sisupod_zip>>
    }
    catch (ErrnoException ex) {
      // Handle error
    }
  }
}
#+END_SRC

*** mkdir

#+name: source_sisupod_init
#+BEGIN_SRC d
debug(asserts) {
  // static assert(is(typeof(doc_matters) == tuple));
}
mixin SiSUoutputRgxInit;
mixin SiSUpaths;
auto pth_sisupod = SiSUpodPathsZipped();
auto pth_sisupod_filesystem = SiSUpodPathsFilesystemArchive();
mixin SiSUlanguageCodes;
auto lang = Lang();
auto rgx = Rgx();
assert (doc_matters.source_filename.match(rgx.src_fn));
#+END_SRC

#+name: source_sisupod_mkdirs
#+BEGIN_SRC d
/+ create directory structure +/
if (!exists(pth_sisupod_filesystem.doc(doc_matters.source_filename))) {
  pth_sisupod_filesystem.doc(doc_matters.source_filename).mkdirRecurse;
}
if (!exists(pth_sisupod_filesystem.conf(doc_matters.source_filename))) {
  pth_sisupod_filesystem.conf(doc_matters.source_filename).mkdirRecurse;
}
if (!exists(pth_sisupod_filesystem.css(doc_matters.source_filename))) {
  pth_sisupod_filesystem.css(doc_matters.source_filename).mkdirRecurse;
}
if (!exists(pth_sisupod_filesystem.image(doc_matters.source_filename))) {
  pth_sisupod_filesystem.image(doc_matters.source_filename).mkdirRecurse;
}
if (!exists(pth_sisupod_filesystem.doc_lng(doc_matters.source_filename, doc_matters.language))) {
  pth_sisupod_filesystem.doc_lng(doc_matters.source_filename, doc_matters.language).mkdirRecurse;
}
#+END_SRC

*** copy

#+name: source_sisupod_copy
#+BEGIN_SRC d
debug(sisupod) {
  writeln(__LINE__, ": ",
    doc_matters.source_filename, " -> ",
    pth_sisupod_filesystem.fn_doc(
    doc_matters.source_filename,
    doc_matters.language
  ));
}
auto zip = new ZipArchive();
auto fn_sisupod = pth_sisupod.sisupod_filename(doc_matters.source_filename);
{ /+ bundle images +/
  foreach (image; doc_matters.image_list) {
    debug(sisupodimages) {
      writeln(
        "_sisu/image/", image, " -> ",
        pth_sisupod.image(doc_matters.source_filename), "/", image
      );
    }
    auto fn_src = "_sisu/image/"~ image;
    auto fn_out =  pth_sisupod.image(doc_matters.source_filename).to!string ~ "/" ~ image;
    auto fn_out_filesystem =  pth_sisupod_filesystem.image(doc_matters.source_filename).to!string ~ "/" ~ image;
    if (exists(fn_src)) {
      fn_src.copy(fn_out_filesystem);
      {
        auto zip_arc_member_file = new ArchiveMember();
        zip_arc_member_file.name = fn_out;
        auto zip_data = new OutBuffer();
        zip_data.write(cast(char[]) ((fn_src).read));
        zip_arc_member_file.expandedData = zip_data.toBytes();
        zip.addMember(zip_arc_member_file);
        createZipFile!()(pth_sisupod.sisupod_filename(fn_src), zip.build());
      }
    }
  }
}
{ /+ bundle sisu_document_make +/
  auto fn_src = "_sisu/sisu_document_make"; // check (_sisu/sisu_document_make)
  auto fn_out = pth_sisupod.conf(doc_matters.source_filename).to!string ~ "/" ~ "sisu_document_make";
  auto fn_out_filesystem = pth_sisupod_filesystem.conf(doc_matters.source_filename).to!string ~ "/" ~ "sisu_document_make";
  if (exists(fn_src)) {
    fn_src.copy(fn_out_filesystem);
    {
      auto zip_arc_member_file = new ArchiveMember();
      zip_arc_member_file.name = fn_out;
      auto zip_data = new OutBuffer();
      zip_data.write((fn_src).readText);
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      createZipFile!()(pth_sisupod.sisupod_filename(fn_src), zip.build());
    }
  }
}
{ /+ bundle primary file +/
  auto fn_src = doc_matters.source_filename;
  auto fn_out = pth_sisupod.fn_doc(doc_matters.source_filename, doc_matters.language).to!string;
  auto fn_out_filesystem = pth_sisupod_filesystem.fn_doc(doc_matters.source_filename, doc_matters.language).to!string;
  if (exists(fn_src)) {
    fn_src.copy(fn_out_filesystem);
    {
      auto zip_arc_member_file = new ArchiveMember();
      zip_arc_member_file.name = fn_out;
      auto zip_data = new OutBuffer();
      zip_data.write((fn_src).readText);
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      createZipFile!()(pth_sisupod.sisupod_filename(fn_src), zip.build());
    }
  }
}
{ /+ bundle insert files +/
  if (doc_matters.file_insert_list.length > 0) {
    foreach (insert_file; doc_matters.file_insert_list) {
      debug(sisupod) {
        writeln(
          insert_file, " -> ",
          pth_sisupod.fn_doc_insert(
            doc_matters.source_filename,
            insert_file,
            doc_matters.language
        ));
      }
      auto fn_src = insert_file;
      auto fn_out = pth_sisupod.fn_doc_insert(
        doc_matters.source_filename,
        insert_file,
        doc_matters.language
      ).to!string;
      auto fn_out_filesystem = pth_sisupod_filesystem.fn_doc_insert(
        doc_matters.source_filename,
        insert_file,
        doc_matters.language
      ).to!string;
      if (exists(fn_src)) {
        fn_src.copy(fn_out_filesystem);
        {
          auto zip_arc_member_file = new ArchiveMember();
          zip_arc_member_file.name = insert_file;
          auto zip_data = new OutBuffer();
          zip_data.write((fn_src).readText);
          zip_arc_member_file.expandedData = zip_data.toBytes();
          zip.addMember(zip_arc_member_file);
          createZipFile!()(pth_sisupod.sisupod_filename(fn_src), zip.build());
        }
      }
    }
  }
}
#+END_SRC

*** sha256 of sisupod.zip, zip debug, read zip archive

#+name: source_sisupod_copy
#+BEGIN_SRC d
if (exists(fn_sisupod)) {
  try {
    auto data = (cast(byte[]) (fn_sisupod).read);
    writefln("%-(%02x%) %s", data.sha256Of, fn_sisupod);
    debug(sisupod) {
      try {
        auto zipped = new ZipArchive((fn_sisupod).read);
        foreach (filename, member; zipped.directory) {
          auto data = zipped.expand(member);
          writeln("> ", filename, " length ", data.length); // filename == member.name
          // Use data
        }
      }
      catch (ZipException ex) {
        // Handle errors
      }
      if (doc_matters.source_filename == "en/the_wealth_of_networks.yochai_benkler.sst") {
        assert(
          ((data).sha256Of).toHexString
          == "DDE0013C13C6A4F06D4BE72087E2CDEF47697CA38A6A2D65BA7207DB6B144271",
          "\nsisupod: sha256 value for "
          ~ doc_matters.source_filename
          ~ " has changed, is now: "
          ~ ((data).sha256Of).toHexString
        );
      }
      if (doc_matters.source_filename == "en/sisu_markup_stress_test.sst") {
        assert(
          ((data).sha256Of).toHexString
          == "112C0AEDD2518A1803D91A7CF5785274A3116C0779A631782D0C0813B212C68A",
          "\nsisupod: sha256 value for "
          ~ doc_matters.source_filename
          ~ " has changed, is now: "
          ~ ((data).sha256Of).toHexString
        );
      }
    }
  }
    catch (ErrnoException ex)
  {
    // Handle errors
  }
}
#+END_SRC

** text [#C]                                                          :text:
** xml offspring (xhtml html epub)                                     :xml:
*** format xhtml objects                                           :format:
**** 0. xhtml common template                                   :template:

#+BEGIN_SRC d :tangle ../src/sdp/output_xhtmls.d
template outputXHTMLs() {
  <<output_imports>>
  mixin SiSUoutputRgxInit;
  struct outputXHTMLs {
    auto rgx = Rgx();
    <<xhtml_format_objects>>
<<xhtml_format_objects_code>>
  }
}
#+END_SRC

**** misc
***** special characters

#+name: xhtml_format_objects
#+BEGIN_SRC d
string special_characters(string _txt){
  _txt = (_txt)
    .replaceAll(rgx.xhtml_ampersand,    "&amp;")
    .replaceAll(rgx.xhtml_less_than,    "&lt;")
    .replaceAll(rgx.xhtml_greater_than, "&gt;")
    .replaceAll(rgx.xhtml_line_break,   "<br />");
  return _txt;
}
#+END_SRC

***** font_face

#+name: xhtml_format_objects
#+BEGIN_SRC d
string font_face(string _txt){
  _txt = (_txt)
    .replaceAll(rgx.inline_emphasis,    ("<emph>$1</emph>"))
    .replaceAll(rgx.inline_bold,        ("<b>$1</b>"))
    .replaceAll(rgx.inline_underscore,  ("<u>$1</u>"))
    .replaceAll(rgx.inline_italics,     ("<i>$1</i>"))
    .replaceAll(rgx.inline_superscript, ("<sup>$1</sup>"))
    .replaceAll(rgx.inline_subscript,   ("<sub>$1</sub>"))
    .replaceAll(rgx.inline_strike,      ("<del>$1</del>"))
    .replaceAll(rgx.inline_insert,      ("<ins>$1</ins>"))
    .replaceAll(rgx.inline_mono,        ("<tt>$1</tt>"))
    .replaceAll(rgx.inline_cite,        ("<cite>$1</cite>"));
  return _txt;
}
#+END_SRC

***** anchor tags

#+name: xhtml_format_objects
#+BEGIN_SRC d
string _xhtml_anchor_tags(const(string[]) anchor_tags) {
  string tags="";
  if (anchor_tags.length > 0) {
    foreach (tag; anchor_tags) {
      if (!(tag.empty)) {
        tags ~= "<a name=\"" ~ tag ~ "\"></a>";
      }
    }
  }
  return tags;
}
#+END_SRC

***** doc head & tails
****** scroll head

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto scroll_head(Me)(
  Me dochead_meta,
) {
  debug(asserts) {
    static assert(is(typeof(dochead_meta) == string[string][string]));
  }
  string o;
  o = format(q"¶<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
    <title>
      %s%s
    </title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta name="dc.title" content="Title" />
    <meta name="dc.author" content="Author" />
    <meta name="dc.publisher" content="SiSU http://www.jus.uio.no/sisu (this copy)" />
    <meta name="dc.date" content="year" />
    <meta name="dc.date.created" content="year" />
    <meta name="dc.date.issued" content="year" />
    <meta name="dc.date.available" content="year" />
    <meta name="dc.date.valid" content="year" />
    <meta name="dc.date.modified" content="year" />
    <meta name="dc.language" content="US" />
    <meta name="dc.rights" content="Copyright: Copyright (C) year holder />
    <meta name="generator" content="sdp [SiSU 7.1.8 of 2016w08/5 (2016-02-26)] (n*x and D)" />
  </meta>
  <link rel="generator" href="http://www.sisudoc.org/" />
  <link rel="shortcut icon" href="../_sisu/image/rb7.ico" />
  <link href="../../_sisu/css/html.css" rel="stylesheet" />
  <link href="../../../_sisu/css/html.css" rel="stylesheet" />
</head>
<body lang="en">
<a name="top" id="top"></a>¶",
dochead_meta["title"]["full"],
(dochead_meta["creator"]["author"].empty) ? "" : ", " ~ dochead_meta["creator"]["author"],
);
  return o;
}
#+END_SRC

****** seg head

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto seg_head(Me)(
  Me dochead_meta,
) {
  debug(asserts) {
    static assert(is(typeof(dochead_meta) == string[string][string]));
  }
  string o;
  o = format(q"¶<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
    <title>
      %s%s
    </title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta name="dc.title" content="Title" />
    <meta name="dc.author" content="Author" />
    <meta name="dc.publisher" content="SiSU http://www.jus.uio.no/sisu (this copy)" />
    <meta name="dc.date" content="year" />
    <meta name="dc.date.created" content="year" />
    <meta name="dc.date.issued" content="year" />
    <meta name="dc.date.available" content="year" />
    <meta name="dc.date.valid" content="year" />
    <meta name="dc.date.modified" content="year" />
    <meta name="dc.language" content="US" />
    <meta name="dc.rights" content="Copyright: Copyright (C) year holder" />
    <meta name="generator" content="sdp [SiSU 7.1.8 of 2016w08/5 (2016-02-26)] (n*x and D)" />
  </meta>
  <link rel="generator" href="http://www.sisudoc.org/" />
  <link rel="shortcut icon" href="../_sisu/image/rb7.ico" />
  <link href="../../_sisu/css/html.css" rel="stylesheet" />
  <link href="../../../_sisu/css/html.css" rel="stylesheet" />
</head>
<body lang="en">
<a name="top" id="top"></a>¶",
dochead_meta["title"]["full"],
(dochead_meta["creator"]["author"].empty) ? "" : ", " ~ dochead_meta["creator"]["author"],
);
  return o;
}
#+END_SRC

****** xhtml tail

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto tail() {
  string o;
  o = format(q"¶  <a name="bottom" id="bottom"></a>
  <a name="end" id="end"></a>
</div>
</body>
</html>¶");
  return o;
}
#+END_SRC

**** inline markup
***** links

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto inline_links(O)(
  auto return ref const O obj,
  string                  _txt,
  string                  _suffix    = ".html",
  string                  seg_scroll = "seg",
) {
  if (obj.inline_links) {
    if ((seg_scroll == "scroll")
    && _txt.match(rgx.mark_internal_site_lnk)) {
      _txt = (_txt).replaceAll(
        rgx.inline_seg_link,
        "$1");
    }
    _txt = (_txt).replaceAll(
      rgx.inline_link_fn_suffix,
      ("$1" ~ _suffix));
    _txt = (_txt).replaceAll(
      rgx.inline_link,
      ("<a href=\"$2\">$1</a>"));
    _txt = (_txt).replaceAll(
      rgx.mark_internal_site_lnk,
      ""
    );
  }
  debug(markup_links) {
    if (_txt.match(rgx.inline_link)) {
      writeln(__LINE__,
        " (missed) markup link identified (",
        obj.inline_links,
        "): ", obj.is_a, ": ",
        obj.text
      );
    }
  }
  debug(markup) {
    if (_txt.match(rgx.inline_link)) {
      writeln(__LINE__,
        " (missed) markup link identified (",
        obj.inline_links,
        "): ", obj.is_a, ": ",
        obj.text
      );
    }
  }
  return _txt;
}
#+END_SRC

***** notes scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto inline_notes_scroll(O)(
  auto return ref const O   obj,
  string                    _txt,
) {
  if (obj.inline_notes_reg) {
    _txt = (_txt).replaceAll(
      rgx.inline_notes_delimiter_al_regular_number_note,
      ("<a href=\"#note_$1\"><note id=\"noteref_$1\">&#160;<sup>$1</sup>&#160;</note></a>")
    );
  }
  debug(markup_endnotes) {
    if (_txt.match(rgx.inline_notes_delimiter_al_regular_number_note)) {
      writeln(__LINE__, " (missed) markup endnote: ", obj.is_a, ": ", obj.text);
    }
  }
  debug(markup) {
    if (_txt.match(rgx.inline_notes_delimiter_al_regular_number_note)) {
      writeln(__LINE__, " (missed) markup endnote: ", obj.is_a, ": ", obj.text);
    }
  }
  // if (obj.inline_notes_star) {
  //   _txt = replaceAll(
  //     _txt,
  //     rgx.inline_notes_delimiter_al_regular_number_note,
  //     ("<a href=\"#note_$1\"><note id=\"noteref_$1\">&nbsp;<sup>$1</sup>&nbsp;</note></a>")
  //   );
  // }
  return _txt;
}
#+END_SRC

***** notes seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto inline_notes_seg(O)(
  auto return ref const O     obj,
  string                      _txt,
) {
  string[] _endnotes;
  if (obj.inline_notes_reg) {
    /+ need markup for text, and separated footnote +/
    foreach(m; _txt.matchAll(rgx.inline_notes_delimiter_al_regular_number_note)) {
      _endnotes ~= format(
        "%s%s%s%s\n  %s%s%s%s%s\n  %s\n%s",
        "<p class=\"endnote\">",
        "<a href=\"#noteref_",
        m.captures[1],
        "\">",
        "<note id=\"note_",
        m.captures[1],
        "\">&#160;<sup>",
        m.captures[1],
        ".</sup></note></a>",
        m.captures[2],
        "</p>"
      );
    }
    _txt = (_txt).replaceAll(
      rgx.inline_notes_delimiter_al_regular_number_note,
      ("<a href=\"#note_$1\"><note id=\"noteref_$1\">&#160;<sup>$1</sup>&#160;</note></a>")
    );
  } else if (_txt.match(rgx.inline_notes_delimiter_al_regular_number_note)) {
    debug(markup) {
      writeln(__LINE__, " endnote: ", obj.is_a, ": ", obj.text);
    }
  }
  auto t = tuple(
    _txt,
    _endnotes,
  );
  return t;
}
#+END_SRC

***** inline markup scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto inline_markup_scroll(O)(
  auto return ref const O  obj,
  string                   _suffix = ".html",
) {
  string _txt = obj.text;
  _txt = special_characters(_txt);
  _txt = inline_links(obj, _txt, _suffix, "scroll");
  _txt = inline_notes_scroll(obj, _txt);
  return _txt;
}
#+END_SRC

***** inline markup seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto inline_markup_seg(O)(
  auto return ref const O  obj,
  string                   _suffix = ".html",
) {
  string _txt = obj.text;
  _txt = special_characters(_txt);
  _txt = inline_links(obj, _txt, _suffix, "seg");
  auto t = inline_notes_seg(obj, _txt);
  return t;
}
#+END_SRC

**** toc

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto toc(O)(
  auto return ref const O    obj,
) {
  string o;
  o = format(q"¶  <div class="substance">
  <p class="%s" indent="h%si%s">
    %s
  </p>
</div>¶",
  obj.is_a,
  obj.indent_hang,
  obj.indent_base,
  obj.text
  );
  return o;
}
#+END_SRC

**** heading
***** heading

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto heading(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string o;
  if (obj.obj_cite_number.empty) {
    o = format(q"¶<br /><hr /><br />
  <div class="substance">
    <h%s class="%s">%s
      %s
    </h%s>
  </div>¶",
      obj.heading_lev_markup,
      obj.is_a,
      tags,
      _txt,
      obj.heading_lev_markup,
    );
  } else {
    o = format(q"¶<br /><hr /><br />
  <div class="substance">
    <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
    <h%s class="%s" id="%s"><a name="%s"></a>%s
      %s
    </h%s>
  </div>¶",
    obj.obj_cite_number,
    obj.obj_cite_number,
    obj.heading_lev_markup,
    obj.is_a,
    obj.obj_cite_number,
    obj.obj_cite_number,
    tags,
    _txt,
    obj.heading_lev_markup,
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto heading_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = heading(obj, _txt);
  return o;
}
#+END_SRC

***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto heading_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = t[0];
  string[] _endnotes = t[1];
  string o = heading(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** para
***** para

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto para(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  _txt = font_face(_txt);
  string o;
  _txt = (obj.bullet) ? ("●&#160;&#160;" ~ _txt) : _txt;
  if (obj.obj_cite_number.empty) {
    o = format(q"¶  <div class="substance">
  <p class="%s" indent="h%si%s">%s
    %s
  </p>
</div>¶",
      obj.is_a,
      obj.indent_hang,
      obj.indent_base,
      tags,
      _txt
    );
  } else {
    o = format(q"¶  <div class="substance">
  <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
  <p class="%s" indent="h%si%s" id="%s">%s
    %s
  </p>
</div>¶",
      obj.obj_cite_number,
      obj.obj_cite_number,
      obj.is_a,
      obj.indent_hang,
      obj.indent_base,
      obj.obj_cite_number,
      tags,
      _txt
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto para_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = para(obj, _txt);
  return o;
}
#+END_SRC

***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto para_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = to!string(t[0]);
  string[] _endnotes = t[1];
  string o = para(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** quote

***** quote

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto quote(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  _txt = font_face(_txt);
  string o;
  if (obj.obj_cite_number.empty) {
    o = format(q"¶  <div class="substance">
  <p class="%s">
    %s
  </p>
</div>¶",
      obj.is_a,
      _txt
    );
  } else {
    o = format(q"¶  <div class="substance">
  <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
  <p class="%s" id="%s">
    %s
  </p>
</div>¶",
      obj.obj_cite_number,
      obj.obj_cite_number,
      obj.is_a,
      obj.obj_cite_number,
      _txt
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto quote_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = quote(obj, _txt);
  return o;
}
#+END_SRC
***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto quote_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = to!string(t[0]);
  string[] _endnotes = t[1];
  string o = quote(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** group

***** group

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto group(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  _txt = font_face(_txt);
  string o;
  if (obj.obj_cite_number.empty) {
    o = format(q"¶  <div class="substance">
  <p class="%s">
    %s
  </p>
</div>¶",
      obj.is_a,
      _txt
    );
  } else {
    o = format(q"¶  <div class="substance">
  <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
  <p class="%s" id="%s">
    %s
  </p>
</div>¶",
      obj.obj_cite_number,
      obj.obj_cite_number,
      obj.is_a,
      obj.obj_cite_number,
      _txt
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto group_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = group(obj, _txt);
  return o;
}
#+END_SRC
***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto group_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = to!string(t[0]);
  string[] _endnotes = t[1];
  string o = group(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** block

***** block

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto block(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  _txt = font_face(_txt);
  string o;
  if (obj.obj_cite_number.empty) {
    o = format(q"¶  <div class="substance">
  <p class="%s">
    %s
  </p>
</div>¶",
      obj.is_a,
      _txt
    );
  } else {
    o = format(q"¶  <div class="substance">
  <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
  <p class="%s" id="%s">
    %s
  </p>
</div>¶",
      obj.obj_cite_number,
      obj.obj_cite_number,
      obj.is_a,
      obj.obj_cite_number,
      _txt
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto block_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = block(obj, _txt);
  return o;
}
#+END_SRC
***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto block_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = to!string(t[0]);
  string[] _endnotes = t[1];
  string o = block(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** poem verse
***** verse

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto verse(O)(                           // using code from code block, review
  auto return ref const O    obj,
  string                     _txt,
) {
  _txt = font_face(_txt);
  _txt = (_txt)
    .replaceAll(rgx.newline, "<br />\n")
    .replaceAll(rgx.two_spaces, "&#160;" ~ "&#160;" ~ "&#160;" ~ "&#160;")
    .replaceAll(rgx.nbsp_and_space, "&#160;" ~ "&#160;")
    .replaceAll(rgx.strip_br, "");
  string o;
  if (obj.obj_cite_number.empty) {
      o = format(q"¶  <div class="substance">
        <p class="%s">
%s
      </p>
    </div>¶",
      obj.is_a,
      _txt
    );
  } else {
    o = format(q"¶  <div class="substance">
      <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
      <p class="%s" id="%s">
%s
      </p>
    </div>¶",
      obj.obj_cite_number,
      obj.obj_cite_number,
      obj.is_a,
      obj.obj_cite_number,
      _txt
    );
  }
  return o;
}
#+END_SRC

***** scroll

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto verse_scroll(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  string _txt = inline_markup_scroll(obj, _suffix); // issue
  string o = verse(obj, _txt);
  return o;
}
#+END_SRC

***** seg

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto verse_seg(O)(
  auto return ref const O    obj,
  string                     _suffix = ".html",
) {
  auto t = inline_markup_seg(obj, _suffix);
  string _txt = to!string(t[0]);
  string[] _endnotes = t[1];
  string o = verse(obj, _txt);
  auto u = tuple(
    o,
    _endnotes,
  );
  return u;
}
#+END_SRC

**** code

#+name: xhtml_format_objects_code
#+BEGIN_SRC d
    auto code(O)(
      auto return ref const O  obj,
    ) {
      string _txt = obj.text;
      _txt = (_txt)
        .replaceAll(rgx.newline, "<br />\n")
        .replaceAll(rgx.nbsp_char, "&#160;");
      string o;
      if (obj.obj_cite_number.empty) {
          o = format(q"¶  <div class="substance">
        <p class="%s">
%s
      </p>
    </div>¶",
          obj.is_a,
          _txt
        );
      } else {
        o = format(q"¶  <div class="substance">
      <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
      <p class="%s" id="%s">
%s
      </p>
    </div>¶",
          obj.obj_cite_number,
          obj.obj_cite_number,
          obj.is_a,
          obj.obj_cite_number,
          _txt
        );
      }
      return o;
    }
#+END_SRC

**** table

***** TODO tablarize

align="left|right|center"
<td align="right">$100</td>

"style=\"text-align:right\""
"style=\"text-align:left\""

"style=\"text-align:"  ~ "right\""

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto tablarize(O)(
  auto return ref const O    obj,
  string                     _txt,
) {
  string[] _table_rows = (_txt).split(rgx.table_delimiter_row);
  string[] _table_cols;
  string _table;
  string _tablenote;
  foreach(row_idx, row; _table_rows) {
    _table_cols = row.split(rgx.table_delimiter_col);
      _table ~= "<tr>";
      foreach(col_idx, cell; _table_cols) {
        if ((_table_cols.length == 1)
        && (_table_rows.length <= row_idx+2)) { // check row_idx+2 (rather than == ++row_idx)
          _tablenote ~= cell;
        } else {
          string _col_is = (row_idx == 0 && obj.table_heading) ? "th" : "td";
          string _align = ("style=\"text-align:"
          ~ ((obj.table_column_aligns[col_idx] == "l")
          ? "left\"" : "right\""));
          _table ~= "<" ~ _col_is ~ " width=\"" ~ obj.table_column_widths[col_idx].to!string ~ "%\" " ~ _align ~ ">";
          _table ~= cell;
          _table ~= "</" ~ _col_is ~ ">";
        }
      }
      _table ~= "</tr>";
    }
  auto t = tuple(
    _table,
    _tablenote,
  );
  return t;
}
#+END_SRC

***** table

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto table(O)(
  auto return ref const O    obj,
) {
  string _txt = obj.text;
  auto tags = _xhtml_anchor_tags(obj.anchor_tags);
  _txt = font_face(_txt);
  auto t = tablarize(obj, _txt);
  _txt = t[0];
  string _note = t[1];
  string o;
  o = format(q"¶  <div class="substance">
  <label class="ocn"><a href="#%s" class="lnkocn">%s</a></label>
  <p class="%s" id="%s">%s
    <table summary="normal text css" width="95%%" border="0" bgcolor="white" cellpadding="2" align="center">
      %s
    </table>
    %s
  </p>
</div>¶",
    obj.obj_cite_number,
    obj.obj_cite_number,
    obj.is_a,
    obj.obj_cite_number,
    tags,
    _txt,
    _note
  );
  return o;
}
#+END_SRC

**** endnote

#+name: xhtml_format_objects
#+BEGIN_SRC d
auto endnote(O)(
  auto return ref const O    obj,
) {
  string o;
  o = format(q"¶    <p class="%s" indent="h%si%s">
  %s
</p>¶",
    obj.is_a,
    obj.indent_hang,
    obj.indent_base,
    obj.text
  );
  return o;
}
#+END_SRC

*** _html_ [#A]                                                        :html:
**** template                                                   :template:

#+BEGIN_SRC d :tangle ../src/sdp/output_html.d
template outputHTML() {
  <<output_imports>>
  mixin outputXHTMLs;
  <<output_html>>
  <<output_html_scroll>>
  <<output_html_seg>>
  <<output_html_css>>
}
#+END_SRC

**** scroll                                                       :scroll:
***** switch (sections & objects) format html output

#+name: output_html_scroll
#+BEGIN_SRC d
void scroll(D,I)(
  auto return ref const D    doc_abstraction,
  auto return ref I          doc_matters,
) {
  mixin SiSUoutputRgxInit;
  auto xhtml_format = outputXHTMLs();
  auto rgx = Rgx();
  string[] doc_html;
  string[] doc;
  string suffix = ".html";
  foreach (part; doc_matters.keys_seq.scroll) {
    foreach (obj; doc_abstraction[part]) {
      switch (obj.use) {
      case "frontmatter":
        switch (obj.is_of) {
        case "para":
          switch (obj.is_a) {
          case "heading":
            doc_html ~= xhtml_format.heading_scroll(obj, suffix);
            break;
          case "toc":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
            }
            break;
          }
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
          }
          break;
        }
        break;
      case "body":
        switch (obj.is_of) {
        case "para":
          switch (obj.is_a) {
          case "heading":
            doc_html ~= xhtml_format.heading_scroll(obj, suffix);
            break;
          case "para":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
            }
            break;
          }
          break;
        case "block":
          switch (obj.is_a) {
          case "quote":
            doc_html ~= xhtml_format.quote_scroll(obj);
            break;
          case "group":
            doc_html ~= xhtml_format.group_scroll(obj);
            break;
          case "block":
            doc_html ~= xhtml_format.block_scroll(obj);
            break;
          case "poem":
            break;
          case "verse":
            doc_html ~= xhtml_format.verse_scroll(obj, suffix);
            break;
          case "code":
            doc_html ~= xhtml_format.code(obj);
            break;
          case "table":
            doc_html ~= xhtml_format.table(obj);
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
            }
            break;
          }
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
          }
          break;
        }
        break;
      case "backmatter":
        switch (obj.is_of) {
        case "para":
          switch (obj.is_a) {
          case "heading":
            doc_html ~= xhtml_format.heading_scroll(obj, suffix);
            break;
          case "endnote":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          case "glossary":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          case "bibliography":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          case "bookindex":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          case "blurb":
            doc_html ~= xhtml_format.para_scroll(obj, suffix);
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
            }
            break;
          }
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
          }
          break;
        }
        break;
      case "comment":
        break;
      default:
        if ((doc_matters.opt_action_bool["debug"])) {
          writeln(__FILE__, ":", __LINE__, ": ", obj.use);
          writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
          writeln(__FILE__, ":", __LINE__, ": ", obj.text);
        }
        break;
      }
    }
  }
  doc = xhtml_format.scroll_head(doc_matters.dochead_meta) ~ doc_html ~ xhtml_format.tail;
  scroll_write_output(doc_matters.source_filename, doc);
}
#+END_SRC

***** write output file

#+name: output_html_scroll
#+BEGIN_SRC d
void scroll_write_output(Fn,C)(
  Fn fn_src,
  C doc,
) {
  debug(asserts) {
    static assert(is(typeof(fn_src) == string));
    static assert(is(typeof(doc)    == string[]));
  }
  mixin SiSUpaths;
  auto pth_html = HtmlPaths();
  try {
    if (!exists(pth_html.base)) {
      pth_html.base.mkdirRecurse;
    }
    auto f = File(pth_html.fn_scroll(fn_src), "w");
    foreach (o; doc) {
      f.writeln(o);
    }
  }
  catch (ErrnoException ex) {
    // Handle error
  }
}
#+END_SRC

**** seg                                                             :seg:
***** switch (sections & objects) format html output

#+name: output_html_seg
#+BEGIN_SRC d
void seg(D,I)(
  auto return ref const D    doc_abstraction,
  auto return ref I          doc_matters,
) {
  mixin SiSUoutputRgxInit;
  auto rgx = Rgx();
  auto xhtml_format = outputXHTMLs();
  string[][string] doc_html;
  string[][string] doc_html_endnotes;
  string[] doc;
  string segment_filename;
  string[] top_level_headings = ["","","",""];
  string suffix = ".html";
  foreach (part; doc_matters.keys_seq.seg) {
    foreach (obj; doc_abstraction[part]) {
      if (obj.is_a == "heading") {                            // all headings: frontmatter, body & backmatter
        switch (obj.heading_lev_markup) {
        case 0: .. case 3:
          /+ fill buffer, and replace with new levels from 1 to 3 +/
          switch (obj.heading_lev_markup) {
          case 0:
            top_level_headings[0] = "";
            top_level_headings[1] = "";
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 1:
            top_level_headings[1] = "";
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 2:
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 3:
            top_level_headings[3] = "";
            goto default;
          default:
            auto t = xhtml_format.heading_seg(obj, suffix);
            top_level_headings[obj.heading_lev_markup] = t[0];
            break;
          }
          break;
        case 4:
          segment_filename = obj.segment_anchor_tag;
          doc_html[segment_filename] ~= xhtml_format.seg_head(doc_matters.dochead_meta);
          foreach (top_level_heading; top_level_headings) {
            doc_html[segment_filename] ~= top_level_heading;
          }
          auto t = xhtml_format.heading_seg(obj, suffix);
          doc_html[segment_filename] ~= to!string(t[0]);
          doc_html_endnotes[segment_filename] ~= t[1];
          break;
        case 5: .. case 7:
          auto t = xhtml_format.heading_seg(obj, suffix);
          doc_html[segment_filename] ~= to!string(t[0]);
          doc_html_endnotes[segment_filename] ~= t[1];
          break;
        case 8: .. case 9: // unused numbers, if remain check
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_a, ": ", obj.heading_lev_markup);
            writeln(__FILE__, ":", __LINE__, ": ", obj.text); // check
          }
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_a, ": ", obj.heading_lev_markup);
          }
          break;
        }
      } else {
        switch (obj.use) {
        case "frontmatter":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "toc":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
            }
            break;
          }
          break;
        case "body":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "para":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          case "block":
            switch (obj.is_a) {
            case "quote":
              auto t = xhtml_format.quote_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "group":
              auto t = xhtml_format.group_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "block":
              auto t = xhtml_format.block_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "poem":
              break;
            case "verse":
              auto t = xhtml_format.verse_seg(obj, suffix);
              doc_html[segment_filename] ~= to!string(t[0]);
              doc_html_endnotes[segment_filename] ~= t[1]; // work on
              break;
            case "code":
              doc_html[segment_filename] ~= xhtml_format.code(obj);
              break;
            case "table":
              doc_html[segment_filename] ~= xhtml_format.table(obj);
              doc_html_endnotes[segment_filename] ~= "";
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
            }
            break;
          }
          break;
        case "backmatter":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "endnote":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= t[0];
              break;
            case "glossary":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= t[0];
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "bibliography":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= t[0];
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "bookindex":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= t[0];
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            case "blurb":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_html[segment_filename] ~= t[0];
              doc_html_endnotes[segment_filename] ~= t[1];
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
            }
            break;
          }
          break;
        case "comment":
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.use);
          }
          break;
        }
      }
    }
  }
  seg_write_output(doc_matters, doc_html, doc_html_endnotes);
}
#+END_SRC

***** write output files

#+name: output_html_seg
#+BEGIN_SRC d
void seg_write_output(M,D,E)(
  M doc_matters,
  D doc_html,
  E doc_html_endnotes,
) {
  debug(asserts) {
    static assert(is(typeof(doc_html)      == string[][string]));
  }
  mixin SiSUoutputRgxInit;
  auto rgx = Rgx();
  mixin SiSUpaths;
  auto pth_html = HtmlPaths();
  auto xhtml_format = outputXHTMLs();
  auto m = doc_matters.source_filename.matchFirst(rgx.src_fn);
  try {
    if (!exists(pth_html.seg(doc_matters.source_filename))) {
      pth_html.seg(doc_matters.source_filename).mkdirRecurse;
    }
    foreach (seg_filename; doc_matters.segnames) {
      auto f = File(pth_html.fn_seg(doc_matters.source_filename, seg_filename), "w");
      foreach (docseg; doc_html[seg_filename]) {
        f.writeln(docseg);
      }
      foreach (docseg; doc_html_endnotes[seg_filename]) {
        f.writeln(docseg);
      }
      f.writeln(xhtml_format.tail); // needed for each lev4
    }
  }
  catch (ErrnoException ex) {
    // handle error
  }
}
#+END_SRC

**** css                                                             :css:

#+name: output_html_css
#+BEGIN_SRC d
auto html_css() {
  string css;
  css="/* SiSU css default stylesheet */
  body {
    color: black;
    background: #ffffff;
    background-color: #ffffff;
  }
  a:link {
    color: #003399;
    text-decoration: none;
  }
  a:visited {
    color: #003399;
    text-decoration: none;
  }
  a:hover {
    color: #000000;
    background-color: #f9f9aa;
  }
  a.lnkocn:link {
    color: #777777;
    text-decoration: none;
  }
  a:hover img {
    background-color: #ffffff;
  }
  a:active {
    color: #003399;
    text-decoration: underline;
  }
  div {
    margin-left: 0;
    margin-right: 0;
  }
  div.p {
    margin-left: 5%;
    margin-right: 1%;
  }
  .norm, .bold, .verse, .group, .block, .alt {
    line-height: 133%;
    margin-left: 0em;
    margin-right: 2em;
    margin-top: 12px;
    margin-bottom: 0px;
    padding-left: 0em;
    text-indent: 0em;
  }
  p, h0, h1, h2, h3, h4, h5, h6, h7 {
    display: block;
    font-family: verdana, arial, georgia, tahoma, sans-serif, helvetica, times, roman;
    font-size: 100%;
    font-weight: normal;
    line-height: 133%;
    text-align: justify;
    margin-left: 0em;
    margin-right: 2em;
    text-indent: 0mm;
    margin-top: 0.8em;
    margin-bottom: 0.8em;
  }
  /* indent */
  p.norm { }
  p.i1 {padding-left: 1em;}
  p.i2 {padding-left: 2em;}
  p.i3 {padding-left: 3em;}
  p.i4 {padding-left: 4em;}
  p.i5 {padding-left: 5em;}
  p.i6 {padding-left: 6em;}
  p.i7 {padding-left: 7em;}
  p.i8 {padding-left: 8em;}
  p.i9 {padding-left: 9em;}
  /* hanging indent */
  p[indent=\"h0i0\"] {
    padding-left: 0em;
    text-indent:  0em;
  }
  p[indent=\"h0i1\"] {
    padding-left: 1em;
    text-indent: -1em;
  }
  p[indent=\"h0i2\"] {
    padding-left: 2em;
    text-indent: -2em;
  }
  p[indent=\"h0i3\"] {
    padding-left: 3em;
    text-indent: -3em;
  }
  p[indent=\"h0i4\"] {
    padding-left: 4em;
    text-indent: -4em;
  }
  p[indent=\"h0i5\"] {
    padding-left: 5em;
    text-indent: -5em;
  }
  p[indent=\"h0i6\"] {
    padding-left: 6em;
    text-indent: -6em;
  }
  p[indent=\"h0i7\"] {
    padding-left: 7em;
    text-indent: -7em;
  }
  p[indent=\"h0i8\"] {
    padding-left: 8em;
    text-indent: -8em;
  }
  p[indent=\"h0i9\"] {
    padding-left: 9em;
    text-indent: -9em;
  }
  p[indent=\"h1i0\"] {
    padding-left: 0em;
    text-indent:  1em;
  }
  p[indent=\"h1i1\"] {
    padding-left: 1em;
    text-indent:  0em;
  }
  p[indent=\"h1i2\"] {
    padding-left: 2em;
    text-indent: -1em;
  }
  p[indent=\"h1i3\"] {
    padding-left: 3em;
    text-indent: -2em;
  }
  p[indent=\"h1i4\"] {
    padding-left: 4em;
    text-indent: -3em;
  }
  p[indent=\"h1i5\"] {
    padding-left: 5em;
    text-indent: -4em;
  }
  p[indent=\"h1i6\"] {
    padding-left: 6em;
    text-indent: -5em;
  }
  p[indent=\"h1i7\"] {
    padding-left: 7em;
    text-indent: -6em;
  }
  p[indent=\"h1i8\"] {
    padding-left: 8em;
    text-indent: -7em;
  }
  p[indent=\"h1i9\"] {
    padding-left: 9em;
    text-indent: -8em;
  }
  p[indent=\"h2i0\"] {
    padding-left: 0em;
    text-indent:  2em;
  }
  p[indent=\"h2i1\"] {
    padding-left: 1em;
    text-indent:  1em;
  }
  p[indent=\"h2i2\"] {
    padding-left: 2em;
    text-indent:  0em;
  }
  p[indent=\"h2i3\"] {
    padding-left: 3em;
    text-indent: -1em;
  }
  p[indent=\"h2i4\"] {
    padding-left: 4em;
    text-indent: -2em;
  }
  p[indent=\"h2i5\"] {
    padding-left: 5em;
    text-indent: -3em;
  }
  p[indent=\"h2i6\"] {
    padding-left: 6em;
    text-indent: -4em;
  }
  p[indent=\"h2i7\"] {
    padding-left: 7em;
    text-indent: -5em;
  }
  p[indent=\"h2i8\"] {
    padding-left: 8em;
    text-indent: -6em;
  }
  p[indent=\"h2i9\"] {
    padding-left: 9em;
    text-indent: -7em;
  }
  p[indent=\"h3i0\"] {
    padding-left: 0em;
    text-indent:  3em;
  }
  p[indent=\"h3i1\"] {
    padding-left: 1em;
    text-indent:  2em;
  }
  p[indent=\"h3i2\"] {
    padding-left: 2em;
    text-indent:  1em;
  }
  p[indent=\"h3i3\"] {
    padding-left: 3em;
    text-indent:  0em;
  }
  p[indent=\"h3i4\"] {
    padding-left: 4em;
    text-indent: -1em;
  }
  p[indent=\"h3i5\"] {
    padding-left: 5em;
    text-indent: -2em;
  }
  p[indent=\"h3i6\"] {
    padding-left: 6em;
    text-indent: -3em;
  }
  p[indent=\"h3i7\"] {
    padding-left: 7em;
    text-indent: -4em;
  }
  p[indent=\"h3i8\"] {
    padding-left: 8em;
    text-indent: -5em;
  }
  p[indent=\"h3i9\"] {
    padding-left: 9em;
    text-indent: -6em;
  }
  p[indent=\"h4i0\"] {
    padding-left: 0em;
    text-indent:  4em;
  }
  p[indent=\"h4i1\"] {
    padding-left: 1em;
    text-indent:  3em;
  }
  p[indent=\"h4i2\"] {
    padding-left: 2em;
    text-indent:  2em;
  }
  p[indent=\"h4i3\"] {
    padding-left: 3em;
    text-indent:  1em;
  }
  p[indent=\"h4i4\"] {
    padding-left: 4em;
    text-indent:  0em;
  }
  p[indent=\"h4i5\"] {
    padding-left: 5em;
    text-indent: -1em;
  }
  p[indent=\"h4i6\"] {
    padding-left: 6em;
    text-indent: -2em;
  }
  p[indent=\"h4i7\"] {
    padding-left: 7em;
    text-indent: -3em;
  }
  p[indent=\"h4i8\"] {
    padding-left: 8em;
    text-indent: -4em;
  }
  p[indent=\"h4i9\"] {
    padding-left: 9em;
    text-indent: -5em;
  }
  p[indent=\"h5i0\"] {
    padding-left: 0em;
    text-indent:  5em;
  }
  p[indent=\"h5i1\"] {
    padding-left: 1em;
    text-indent:  4em;
  }
  p[indent=\"h5i2\"] {
    padding-left: 2em;
    text-indent:  3em;
  }
  p[indent=\"h5i3\"] {
    padding-left: 3em;
    text-indent:  2em;
  }
  p[indent=\"h5i4\"] {
    padding-left: 4em;
    text-indent:  1em;
  }
  p[indent=\"h5i5\"] {
    padding-left: 5em;
    text-indent:  0em;
  }
  p[indent=\"h5i6\"] {
    padding-left: 6em;
    text-indent: -1em;
  }
  p[indent=\"h5i7\"] {
    padding-left: 7em;
    text-indent: -2em;
  }
  p[indent=\"h5i8\"] {
    padding-left: 8em;
    text-indent: -3em;
  }
  p[indent=\"h5i9\"] {
    padding-left: 9em;
    text-indent: -4em;
  }
  p[indent=\"h6i0\"] {
    padding-left: 0em;
    text-indent:  6em;
  }
  p[indent=\"h6i1\"] {
    padding-left: 1em;
    text-indent:  5em;
  }
  p[indent=\"h6i2\"] {
    padding-left: 2em;
    text-indent:  4em;
  }
  p[indent=\"h6i3\"] {
    padding-left: 3em;
    text-indent:  3em;
  }
  p[indent=\"h6i4\"] {
    padding-left: 4em;
    text-indent:  2em;
  }
  p[indent=\"h6i5\"] {
    padding-left: 5em;
    text-indent:  1em;
  }
  p[indent=\"h6i6\"] {
    padding-left: 6em;
    text-indent:  0em;
  }
  p[indent=\"h6i7\"] {
    padding-left: 7em;
    text-indent: -1em;
  }
  p[indent=\"h6i8\"] {
    padding-left: 8em;
    text-indent: -2em;
  }
  p[indent=\"h6i9\"] {
    padding-left: 9em;
    text-indent: -3em;
  }
  p[indent=\"h7i0\"] {
    padding-left: 0em;
    text-indent:  7em;
  }
  p[indent=\"h7i1\"] {
    padding-left: 1em;
    text-indent:  6em;
  }
  p[indent=\"h7i2\"] {
    padding-left: 2em;
    text-indent:  5em;
  }
  p[indent=\"h7i3\"] {
    padding-left: 3em;
    text-indent:  4em;
  }
  p[indent=\"h7i4\"] {
    padding-left: 4em;
    text-indent:  3em;
  }
  p[indent=\"h7i5\"] {
    padding-left: 5em;
    text-indent:  2em;
  }
  p[indent=\"h7i6\"] {
    padding-left: 6em;
    text-indent:  1em;
  }
  p[indent=\"h7i7\"] {
    padding-left: 7em;
    text-indent:  0em;
  }
  p[indent=\"h7i8\"] {
    padding-left: 8em;
    text-indent: -1em;
  }
  p[indent=\"h7i9\"] {
    padding-left: 9em;
    text-indent: -2em;
  }
  p[indent=\"h8i0\"] {
    padding-left: 0em;
    text-indent:  8em;
  }
  p[indent=\"h8i1\"] {
    padding-left: 1em;
    text-indent:  7em;
  }
  p[indent=\"h8i2\"] {
    padding-left: 2em;
    text-indent:  6em;
  }
  p[indent=\"h8i3\"] {
    padding-left: 3em;
    text-indent:  5em;
  }
  p[indent=\"h8i4\"] {
    padding-left: 4em;
    text-indent:  4em;
  }
  p[indent=\"h8i5\"] {
    padding-left: 5em;
    text-indent:  3em;
  }
  p[indent=\"h8i6\"] {
    padding-left: 6em;
    text-indent:  2em;
  }
  p[indent=\"h8i7\"] {
    padding-left: 7em;
    text-indent:  1em;
  }
  p[indent=\"h8i8\"] {
    padding-left: 8em;
    text-indent:  0em;
  }
  p[indent=\"h8i9\"] {
    padding-left: 9em;
    text-indent: -1em;
  }
  p[indent=\"h9i0\"] {
    padding-left: 0em;
    text-indent:  9em;
  }
  p[indent=\"h9i1\"] {
    padding-left: 1em;
    text-indent:  8em;
  }
  p[indent=\"h9i2\"] {
    padding-left: 2em;
    text-indent:  7em;
  }
  p[indent=\"h9i3\"] {
    padding-left: 3em;
    text-indent:  6em;
  }
  p[indent=\"h9i4\"] {
    padding-left: 4em;
    text-indent:  5em;
  }
  p[indent=\"h9i5\"] {
    padding-left: 5em;
    text-indent:  4em;
  }
  p[indent=\"h9i6\"] {
    padding-left: 6em;
    text-indent:  3em;
  }
  p[indent=\"h9i7\"] {
    padding-left: 7em;
    text-indent:  2em;
  }
  p[indent=\"h9i8\"] {
    padding-left: 8em;
    text-indent:  1em;
  }
  p[indent=\"h9i9\"] {
    padding-left: 9em;
    text-indent:  0em;
  }
  p.block { }
  p.group { }
  p.alt { }
  p.verse {
    margin-bottom: 6px;
  }
  p.code {
    font-family: inconsolata, andale mono, courier new, courier, monospace;
    font-size: 90%;
    text-align: left;
    background-color: #eeeeee;
  }
  p.caption {
    text-align: left;
    font-size: 80%;
    display: inline;
  }
  p.endnote {
    font-size: 96%;
    line-height: 120%;
    text-align: left;
    margin-right: 15mm;
  }
  p.endnote_indent {
    font-size: 96%;
    line-height: 120%;
    text-align: left;
    margin-left: 2em;
    margin-right: 15mm;
  }
  p.center {
    text-align: center;
  }
  p.bold {
    font-weight: bold;
  }
  p.bold_left {
    font-weight: bold;
    text-align: left;
  }
  p.centerbold {
    text-align: center;
    font-weight: bold;
  }
  p.em {
    font-weight: bold;
    font-style: normal;
    background: #fff3b6;
  }
  p.small {
    font-size: 80%;
    margin-top: 0px;
    margin-bottom: 0px;
    margin-right: 6px;
    text-align: left;
  }
  .tiny, .tiny_left, .tiny_right, .tiny_center {
    font-size: 10px;
    margin-top: 0px;
    margin-bottom: 0px;
    color: #777777;
    margin-right: 6px;
    text-align: left;
  }
  p.tiny { }
  p.tiny_left {
    margin-left: 0px;
    margin-right: 0px;
    text-align: left;
  }
  p.tiny_right {
    margin-right: 1em;
    text-align: right;
  }
  p.tiny_center {
    margin-left: 0px;
    margin-right: 0px;
    text-align: center;
  }
  p.concordance_word {
    line-height: 150%;
    font-weight: bold;
    display: inline;
    margin-top: 4px;
    margin-bottom: 1px;
  }
  p.concordance_count {
    font-size: 80%;
    color: #777777;
    display: inline;
    margin-left: 0em;
  }
  p.concordance_object {
    font-size: 80%;
    line-height: 120%;
    text-align: left;
    margin-left: 3em;
    margin-top: 1px;
    margin-bottom: 3px;
  }
  p.book_index_lev1 {
    line-height: 100%;
    margin-top: 4px;
    margin-bottom: 1px;
  }
  p.book_index_lev2 {
    line-height: 100%;
    text-align: left;
    margin-left: 3em;
    margin-top: 1px;
    margin-bottom: 3px;
  }
  tt {
    font-family: inconsolata, andale mono, courier new, courier, monospace;
    background-color: #eeeeee;
  }
  label.ocn {
    width: 2%;
    float: right;
    top: 0;
    font-size: 10px;
    margin-top: 0px;
    margin-bottom: 5px;
    color: #777777;
    margin-right: 5px;
    text-align: right;
    background-color: #ffffff;
  }
  table { }
  tr { }
  th,td {
    vertical-align: top;
    text-align: left;
  }
  th {
    font-weight: bold;
  }
  p.left,th.left,td.left {
    text-align: left;
  }
  p.small_left,th.small_left,td.small_left {
    text-align: left;
    font-size: 80%;
  }
  p.right,th.right,td.right {
    text-align: right;
  }
  ul, li {
    list-style-type: none;
    list-style: none;
    padding-left: 20px;
    display: block;
    font-family: verdana, arial, georgia, tahoma, sans-serif, helvetica, times, roman;
    font-weight: normal;
    line-height: 150%;
    text-align: left;
    text-indent: 0mm;
    margin-left: 1em;
    margin-right: 2em;
    margin-top: 3px;
    margin-bottom: 3px;
  }
  li {
    background: url(../image_sys/bullet_09.png) no-repeat 0px 6px;
  }
  ul {
  }
  h0, h1, h2, h3, h4, h5, h6, h7 {
    font-weight: bold;
    line-height: 120%;
    text-align: left;
    margin-top: 20px;
    margin-bottom: 10px;
  }
  h4.norm, h5.norm, h6.norm, h7.norm {
    margin-top: 10px;
    margin-bottom: 0px;
  }
  h0 { font-size: 125%; }
  h1 { font-size: 120%; }
  h2 { font-size: 115%; }
  h3 { font-size: 110%; }
  h4 { font-size: 105%; }
  h5 { font-size: 100%; }
  h6 { font-size: 100%; }
  h7 { font-size: 100%; }
  h1.i {margin-left: 2em;}
  h2.i {margin-left: 3em;}
  h3.i {margin-left: 4em;}
  h4.i {margin-left: 5em;}
  h5.i {margin-left: 6em;}
  h6.i {margin-left: 7em;}
  h7.i {margin-left: 8em;}
  h8.i {margin-left: 9em;}
  h9.i {margin-left: 10em;}
  .toc {
    font-weight: normal;
    margin-top: 6px;
    margin-bottom: 6px;
  }
  h0.toc {
    margin-left: 1em;
    font-size: 120%;
    line-height: 150%;
  }
  h1.toc {
    margin-left: 1em;
    font-size: 115%;
    line-height: 150%;
  }
  h2.toc {
    margin-left: 2em;
    font-size: 110%;
    line-height: 140%;
  }
  h3.toc {
    margin-left: 3em;
    font-size: 105%;
    line-height: 120%;
  }
  h4.toc {
    margin-left: 4em;
    font-size: 100%;
    line-height: 120%;
  }
  h5.toc {
    margin-left: 5em;
    font-size: 95%;
    line-height: 110%;
  }
  h6.toc {
    margin-left: 6em;
    font-size: 90%;
    line-height: 110%;
  }
  h7.toc {
    margin-left: 7em;
    font-size: 85%;
    line-height: 100%;
  }
  .subtoc {
    margin-right: 34%;
    font-weight: normal;
  }
  h5.subtoc {
    margin-left: 2em;
    font-size: 80%;
    margin-top: 2px;
    margin-bottom: 2px;
  }
  h6.subtoc {
    margin-left: 3em;
    font-size: 75%;
    margin-top: 0px;
    margin-bottom: 0px;
  }
  h7.subtoc {
    margin-left: 4em;
    font-size: 70%;
    margin-top: 0px;
    margin-bottom: 0px;
  }
  div.substance {
    width: 100%;
    background-color: #ffffff;
  }
  div.ocn {
    width: 5%;
    float: right;
    top: 0;
    background-color: #ffffff;
  }
  div.endnote {
    width: 95%;
    background-color: #fffffff;
  }
  div.toc {
    position: absolute;
    float: left;
    margin: 0;
    padding: 0;
    padding-top: 0.5em;
    border: 0;
    width: 13em;
    background-color: #eeeeee;
    margin-right:1em;
  }
  div.summary {
    margin: 0;
    padding: 0;
    border-left: 13em solid #eeeeee;
    padding-left: 1em;
    background-color: #eeeeee;
  }
  div.content, div.main_column {
    margin: 0;
    padding: 0;
    border-left: 13em solid #ffffff;
    padding-left: 1em;
    padding-right: 1em;
  }
  div.content0, div.main_column0 {
    margin: 0;
    padding: 0;
    border-left: 0% solid #ffffff;
    padding-left: 5%;
  }
  div.scroll {
    margin: 0;
    padding: 0;
    padding-left: 1em;
    padding-right: 1em;
  }
  div.content:after {
    content:' ';
    clear:both;
    display:block;
    height:0;
    overflow:hidden
  }
  div.footer {
    clear:left;
    padding: 0.5em;
    font-size: 80%;
    margin: 0;
  }
  div.toc ul {
    list-style: none;
    padding: 0;
    margin: 0;
  }
  div.toc li ul a, li ul span.currentlink
  {
    font-weight: normal;
    font-size: 90%;
    padding-left: 2em;
    background-color: #eeeeee;
  }
  div.toc a, span.currentlink{
    display:block;
    text-decoration: none;
    padding-left: 0.5em;
    color: #0000aa;
  }
  hr {
    width: 90%;
  }
  span.currentlink {
    text-decoration: none;
    background-color: #aaaaf9;
  }
  div.toc a:visited {
    color: #0000aa;
  }
  div.toc a:hover {
    color: #000000;
    background-color: #f9f9aa;
  }";
  return css;
}
auto css_write() {
  auto pth_css= "_sisu/css";
  auto pth_css_fn= pth_css ~ "/html.css";
  try {
    if (!exists(pth_css)) {
      pth_css.mkdirRecurse;
    }
    auto f = File(pth_css_fn, "w");
    f.writeln(html_css);
    // foreach (o; doc) {
    //   f.writeln(o);
    // }
  }
  catch (ErrnoException ex) {
    // Handle error
  }
}
#+END_SRC

*** _epub_ [#B]                                                        :epub:

|-----------------------+--------------------------+---------------------------+----------------------------------|
| function              | filename                 | module                    | variable                         |
|-----------------------+--------------------------+---------------------------+----------------------------------|
| identify doc filetype | mimetype                 | epub3_mimetypes           | mimetypes                        |
|-----------------------+--------------------------+---------------------------+----------------------------------|
| identify doc root     | META-INF/container.xml   | epub3_container_xml       | meta_inf_container_xml           |
|-----------------------+--------------------------+---------------------------+----------------------------------|
| doc manifest          | OEBPS/content.opf        | epub3_oebps_content       | oebps_content_opf                |
|-----------------------+--------------------------+---------------------------+----------------------------------|
| doc navigation        | OEBPS/toc_nav.xhtml      | epub3_oebps_toc_nav_xhtml | oebps_toc_nav_xhtml              |
|                       | OEBPS/toc.ncx            | epub2_oebps_toc_ncx       | oebps_toc_ncx                    |
|-----------------------+--------------------------+---------------------------+----------------------------------|
| doc contents          | OEBPS/[files ... ].xhtml | outputEPub3               | doc_epub3[seg_filename]          |
|                       |                          |                           | doc_epub3_endnotes[seg_filename] |
|-----------------------+--------------------------+---------------------------+----------------------------------|

**** template                                                   :template:

#+BEGIN_SRC d :tangle ../src/sdp/output_epub3.d
template outputEPub3() {
  <<output_imports>>
  mixin InternalMarkup;
  mixin outputXHTMLs;
  <<output_epub3_fixed>>
  <<output_epub3_constructs>>
  <<output_epub3_xhtml>>
  <<output_epub3_xhtml_seg>>
  <<output_epub3_css>>
}
#+END_SRC

**** special (epub) files                                         :format:
***** DONE static
****** _identify doc filetype_ (mimetype) [static]

- mimetype file indicating that zip file contains an EPUB

#+name: output_epub3_fixed
#+BEGIN_SRC d
string epub3_mimetypes() {
  string o;
  o = format(q"¶application/epub+zip¶") ~ "\n";
  return o;
}
#+END_SRC

****** _identify doc root_ (META-INF/container.xml) [static] rootfile: contains document root path

- identifies the root package document (so systems can find it), [unchanged from epub2]

#+name: output_epub3_fixed
#+BEGIN_SRC d
string epub3_container_xml() {
  string o;
  o = format(q"¶<?xml version='1.0' encoding='utf-8'?>¶") ~ "\n";
  o ~= format(q"¶<container version="1.0"
xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
  <rootfile full-path="OEBPS/content.opf"
    media-type="application/oebps-package+xml" />
</rootfiles>¶") ~ "\n</container>\n";
  return o;
}
#+END_SRC

***** constructs (in dir: OEBPS)
****** TODO _doc manifest_ (OEBPS/content.opf) manifest, register content: files, images etc.

- manifest, listing all resources
- provides the default reading order
- identifies the navigation document

#+name: output_epub3_constructs
#+BEGIN_SRC d
string epub3_oebps_content(D,I,P)(D doc_abstraction, I doc_matters, P parts) {
  string uuid = "18275d951861c77f78acd05672c9906924c59f18a2e0ba06dad95959693e9bd8"; // TODO sort uuid in doc_matters!
  string content = format(q"¶  <?xml version='1.0' encoding='utf-8'?>
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="EPB-UUID">
  <metadata
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dcterms="http://purl.org/dc/terms/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    unique-identifier="urn:uuid:%s" version="2.0">
    <!-- <dc:title id="title">%s</dc:title> -->
    <dc:title id="title">%s</dc:title>
    <meta refines="#title" property="title-type">main</meta>
    <dc:title id="subtitle">%s</dc:title>
    <meta refines="#subtitle" property="title-type">subtitle</meta>
    <dc:creator file-as="%s" id="aut">%s</dc:creator>
    <dc:language>%s</dc:language>
    <dc:date id="published">%s</dc:date>
    <dc:rights>Copyright: %s</dc:rights>
    <dc:identifier scheme="URI">%s</dc:identifier>
    <dc:identifier id="bookid">urn:uuid:%s</dc:identifier>
    <!-- <dc:identifier id="EPB-UUID">urn:uuid:%s</dc:identifier> -->
  </metadata>
  <manifest>
    <!-- NCX epub2 navigation -->
      <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
    <!-- CSS Style Sheets -->
      <item id="main-css" href="css/xhtml.css" media-type="text/css" />
    <!-- nav epub3 navigation -->
      <item id="nav" href="toc_nav.xhtml" media-type="application/xhtml+xml" properties="nav" />
¶",
    uuid,
    doc_matters.dochead_meta["title"]["full"],
    doc_matters.dochead_meta["title"]["main"],
    (doc_matters.dochead_meta["title"]["sub"].empty)
      ? "" : doc_matters.dochead_meta["title"]["sub"],
    (doc_matters.dochead_meta["creator"]["author"].empty)
      ? "" : doc_matters.dochead_meta["creator"]["author"],
    (doc_matters.dochead_meta["creator"]["author"].empty)
      ? "" : doc_matters.dochead_meta["creator"]["author"],
    doc_matters.language,
    (doc_matters.dochead_meta["date"]["published"].empty)
      ? "" : doc_matters.dochead_meta["date"]["published"],
    (doc_matters.dochead_meta["rights"]["copyright"].empty)
      ? "" : doc_matters.dochead_meta["rights"]["copyright"],
    uuid,
    uuid,
    uuid,
  );
  content ~= "    " ~ "<!-- Content Documents -->" ~ "\n  ";
  content ~= parts["manifest_documents"];
  // TODO sort jpg & png
  content ~= "    " ~ "<!-- Images -->" ~ "\n  ";
  foreach (image; doc_matters.image_list) {
    content ~= format(q"¶      <item id="%s" href="image/%s" media-type="image/png" />
¶",
      image,                        // strip image type, remove .png .jpg suffix, use in media-type="image/"
      image,
    );
  }
  content ~= "  " ~ "</manifest>"         ~ "\n  ";
  content ~= "  " ~ "<spine toc=\"ncx\">" ~ "\n  ";
  content ~= parts["spine"];
  content ~= "  " ~ "</spine>"            ~ "\n  ";
  content ~= "  " ~ "<guide>"             ~ "\n  ";
  content ~= parts["guide"];
  content ~= "  " ~ "</guide>"            ~ "\n  ";
  content ~= ""   ~ "</package>";
  return content;
}
#+END_SRC

****** _doc navigation epub3_ (OEBPS/toc_nav.xhtml) epub3 navigable toc using Dom structure

- toc_nav.xhtml declared as nav file in content.opf (epub3 navigation document)

#+name: output_epub3_constructs
#+BEGIN_SRC d
string epub3_oebps_toc_nav_xhtml(D,I)(D doc_abstraction, I doc_matters) {
  enum DomTags { none, open, close, close_and_open, open_still, }
  auto markup = InlineMarkup();
  string toc ="<nav epub:type=\"toc\" id=\"toc\">\n";
  foreach (sect; doc_matters.keys_seq.seg) {
    foreach (obj; doc_abstraction[sect]) {
      if (obj.is_a == "heading") {
        foreach_reverse (n; 0 .. 7) {
          string k = n.to!string;
          switch (obj.dom_collapsed[n]) {
          case DomTags.close :
            toc ~= markup.indent_by_spaces_provided((n + 1), "  ") ~ "</li>" ~ "\n";
            toc ~= markup.indent_by_spaces_provided(n, "  ") ~ "</ol>" ~ "\n";
            break;
          case DomTags.close_and_open :
            toc ~= markup.indent_by_spaces_provided((n + 1), "  ") ~ "</li>" ~ "\n";
            if  (obj.dom_markedup[n] < 4) {
              toc ~= markup.indent_by_spaces_provided((n + 1), "  ") ~ "<li>" ~ "\n"
              ~ markup.indent_by_spaces_provided((n + 2), "  ")
              ~ "<span class=\"navhd\">" ~ obj.text ~ "</span>" ~ "\n";
            } else {
              string hashtag =(obj.heading_lev_markup == 4)
              ? ""
              : ("#" ~ obj.ocn.to!string);
              toc ~= markup.indent_by_spaces_provided((n + 1), "  ") ~ "<li>" ~ "\n"
              ~ markup.indent_by_spaces_provided((n + 2), "  ")
              ~ "<a href=\"" ~ obj.segment_anchor_tag ~ ".xhtml" ~ hashtag ~ "\">"
              ~ obj.text
              ~ "</a>" ~ "\n";
            }
            break;
          case DomTags.open :
            toc ~= markup.indent_by_spaces_provided(n, "  ") ~ "<ol>" ~ "\n";
            if  (obj.dom_markedup[n] < 4) {
              toc ~= markup.indent_by_spaces_provided(n, "  ")
              ~ "<li><span class=\"navhd\">" ~ obj.text ~ "</span>" ~ "\n";
            } else {
              string hashtag =(obj.heading_lev_markup == 4)
              ? ""
              : ("#" ~ obj.ocn.to!string);
              toc ~= markup.indent_by_spaces_provided((n + 1), "  ") ~ "<li>" ~ "\n"
              ~ markup.indent_by_spaces_provided((n + 2), "  ")
              ~ "<a href=\"" ~ obj.segment_anchor_tag ~ ".xhtml" ~ hashtag ~ "\">"
              ~ obj.text
              ~ "</a>" ~ "\n";
            }
            break;
          default :
            break;
          }
        }
      }
    }
  }
  toc ~="</nav>\n";
  return toc;
}
#+END_SRC

****** TODO _doc navigation epub2_ (OEBPS/toc.ncx) navigable toc using Dom structure

- toc.ncx (epub2 navigation document)
  - (replaced in epub3 by a declared xhtml nav file, in our case toc_nav.xhtml)

#+name: output_epub3_constructs
#+BEGIN_SRC d
string epub2_oebps_toc_ncx(D,I)(D doc_abstraction, I doc_matters) {
  int counter = 0;
  string uuid = "18275d951861c77f78acd05672c9906924c59f18a2e0ba06dad95959693e9bd8"; // TODO shared elsewhere
  auto markup = InlineMarkup();
  enum DomTags { none, open, close, close_and_open, open_still, }
  string toc = format(q"¶<?xml version='1.0' encoding='utf-8'?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
  <head>
    <!-- four required metadata items (for all NCX documents,
      (including the relaxed constraints of OPS 2.0) -->
    <title>%s%s</title>
    <link href="css/xhtml.css" rel="stylesheet" type="text/css" id="main-css" />
    <meta name="dtb:uid" content="urn:uuid:%s" />
    <!-- <meta name="epub-creator" content="SiSU http://www.jus.uio.no/sisu (this copy)" /> -->
    <meta name="dtb:depth" content="%s" />
    <meta name="dtb:totalPageCount" content="0" />
    <meta name="dtb:maxPageNumber" content="0" />
  </head>
  <docTitle>
    <text>%s</text>
  </docTitle>
  <docAuthor>
    <text>%s</text>
  </docAuthor>
  <navMap>¶",
    doc_matters.dochead_meta["title"]["full"],                                                               // title
    (doc_matters.dochead_meta["creator"]["author"].empty) ? "" : " by " ~ doc_matters.dochead_meta["creator"]["author"], // author
    uuid,                                                                                        // uuid
    "3",                                                                                         // content depth
    doc_matters.dochead_meta["title"]["full"],                                                               // title
    (doc_matters.dochead_meta["creator"]["author"].empty) ? "" : doc_matters.dochead_meta["creator"]["author"],          // author
  );
  foreach (sect; doc_matters.keys_seq.seg) {
    foreach (obj; doc_abstraction[sect]) {
      if (obj.is_a == "heading") {
        foreach_reverse (k; 0 .. 7) {
          switch (obj.dom_markedup[k]) {
          case DomTags.close :
toc ~= "</navPoint>";
            break;
          case DomTags.close_and_open :
            ++counter;
toc ~= "</navPoint>";
toc ~= format(q"¶<navPoint class="chapter" id="navpoint" playOrder="%s">
<navLabel>
  <text>%s</text>
</navLabel>
<content src="%s" />¶",
counter,
obj.text,
obj.segment_anchor_tag,   // lev < 4 [no link]; lev == 4 [filename] markup.xhtml; lev > 4 [filename#ocn] (links done in segment_anchor_tag)
);
            break;
          case DomTags.open :
            ++counter;
toc ~= format(q"¶<navPoint class="chapter" id="navpoint" playOrder="%s">
<navLabel>
  <text>%s</text>
</navLabel>
<content src="%s" />¶",
counter,
obj.text,
obj.segment_anchor_tag,   // lev < 4 [no link]; lev == 4 [filename] markup.xhtml; lev > 4 [filename#ocn] (fix links in segment_anchor_tag)
);
            break;
          default :
            break;
          }
        }
      }
    }
  }
  toc ~= format(q"¶  </navMap>
</ncx>¶");
  return toc;
}
#+END_SRC

**** the document contents                                           :seg:
***** switch (sections & objects) format epub3 xhtml output

#+name: output_epub3_xhtml_seg
#+BEGIN_SRC d
void outputEPub3(D,I)(
  auto return ref const D    doc_abstraction,
  auto return ref I          doc_matters,
) {
  mixin SiSUoutputRgxInit;
  auto xhtml_format = outputXHTMLs();
  auto rgx = Rgx();
  string[][string] doc_epub3;
  string[][string] doc_epub3_endnotes;
  string[] doc;
  string segment_filename;
  string[] top_level_headings = ["","","",""];
  string[string] oepbs_content_parts;
  string suffix = ".xhtml";
  foreach (part; doc_matters.keys_seq.seg) {
    foreach (obj; doc_abstraction[part]) {
      if (obj.is_a == "heading") {
        switch (obj.heading_lev_markup) {
        case 0: .. case 3:
          /+ fill buffer, and replace with new levels from 1 to 3 +/
          switch (obj.heading_lev_markup) {
          case 0:
            top_level_headings[0] = "";
            top_level_headings[1] = "";
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 1:
            top_level_headings[1] = "";
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 2:
            top_level_headings[2] = "";
            top_level_headings[3] = "";
            goto default;
          case 3:
            top_level_headings[3] = "";
            goto default;
          default:
            auto t = xhtml_format.heading_seg(obj, suffix);
            top_level_headings[obj.heading_lev_markup] = t[0];
            break;
          }
          break;
        case 4:
          segment_filename = obj.segment_anchor_tag;
          doc_epub3[segment_filename] ~= xhtml_format.seg_head(doc_matters.dochead_meta);
          foreach (top_level_heading; top_level_headings) {
            doc_epub3[segment_filename] ~= top_level_heading;
          }
          auto t = xhtml_format.heading_seg(obj, suffix);
          doc_epub3[segment_filename] ~= t[0];
          doc_epub3_endnotes[segment_filename] ~= t[1];
          break;
        case 5: .. case 7:
          auto t = xhtml_format.heading_seg(obj, suffix);
          doc_epub3[segment_filename] ~= t[0];
          doc_epub3_endnotes[segment_filename] ~= t[1];
          break;
        case 8: .. case 9: // unused numbers, if remain check
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_a, ": ", obj.heading_lev_markup);
            writeln(__FILE__, ":", __LINE__, ": ", obj.text); // check
          }
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.is_a, ": ", obj.heading_lev_markup);
          }
          break;
        }
      } else {
        switch (obj.use) {
        case "frontmatter":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "toc":
              doc_epub3[segment_filename] ~= xhtml_format.toc(obj);
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
            }
            break;
          }
          break;
        case "body":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "para":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          case "block":
            switch (obj.is_a) {
            case "quote":
              auto t = xhtml_format.quote_seg(obj, suffix);
              doc_epub3[segment_filename] ~= to!string(t[0]);
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "group":
              auto t = xhtml_format.group_seg(obj, suffix);
              doc_epub3[segment_filename] ~= to!string(t[0]);
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "block":
              auto t = xhtml_format.block_seg(obj, suffix);
              doc_epub3[segment_filename] ~= to!string(t[0]);
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "poem":
              break;
            case "verse":
              auto t = xhtml_format.verse_seg(obj, suffix);
              doc_epub3[segment_filename] ~= to!string(t[0]);
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "code":
              doc_epub3[segment_filename] ~= xhtml_format.code(obj);
              break;
            case "table":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
            }
            break;
          }
          break;
        case "backmatter":
          switch (obj.is_of) {
          case "para":
            switch (obj.is_a) {
            case "endnote":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              break;
            case "glossary":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "bibliography":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "bookindex":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            case "blurb":
              auto t = xhtml_format.para_seg(obj, suffix);
              doc_epub3[segment_filename] ~= t[0];
              doc_epub3_endnotes[segment_filename] ~= t[1];
              break;
            default:
              if ((doc_matters.opt_action_bool["debug"])) {
                writeln(__FILE__, ":", __LINE__, ": ", obj.is_a);
              }
              break;
            }
            break;
          default:
            if ((doc_matters.opt_action_bool["debug"])) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.is_of);
            }
            break;
          }
          break;
        case "comment":
          break;
        default:
          if ((doc_matters.opt_action_bool["debug"])) {
            writeln(__FILE__, ":", __LINE__, ": ", obj.use);
          }
          break;
        }
      }
      if (obj.is_a == "heading") {
        if (obj.heading_lev_markup == 4) {
          oepbs_content_parts["manifest_documents"] ~= format(q"¶      <item id="%s.xhtml" href="%s.xhtml" media-type="application/xhtml+xml" />
¶",
            obj.segment_anchor_tag,
            obj.segment_anchor_tag,
          );
          oepbs_content_parts["spine"] ~= format(q"¶    <itemref idref="%s.xhtml" linear="yes" />
¶",
            obj.segment_anchor_tag,
          );
          oepbs_content_parts["guide"] ~= format(q"¶      <reference type="%s" href="%s" />
¶",
            obj.segment_anchor_tag,
            obj.segment_anchor_tag,
          );
        } else if (obj.heading_lev_markup > 4) {
          oepbs_content_parts["manifest_documents"] ~= format(q"¶      <item id="%s.xhtml#%s" href="%s.xhtml#%s" media-type="application/xhtml+xml" />
¶",
            obj.segment_anchor_tag,
            obj.obj_cite_number,
            obj.segment_anchor_tag,
            obj.obj_cite_number,
          );
          oepbs_content_parts["spine"] ~= format(q"¶    <itemref idref="%s.xhtml#%s" linear="yes" />
¶",
            obj.segment_anchor_tag,
            obj.obj_cite_number,
          );
          oepbs_content_parts["guide"] ~= format(q"¶      <reference type="%s#%s" href="%s#%s" />
¶",
            obj.segment_anchor_tag,
            obj.obj_cite_number,
            obj.segment_anchor_tag,
            obj.obj_cite_number,
          );
        }
      }
    }
  }
  /+ epub specific documents +/
  auto mimetypes = epub3_mimetypes;
  auto meta_inf_container_xml = epub3_container_xml;
  auto oebps_toc_ncx = epub2_oebps_toc_ncx(doc_abstraction, doc_matters);
  auto oebps_toc_nav_xhtml = epub3_oebps_toc_nav_xhtml(doc_abstraction, doc_matters);
  auto oebps_content_opf = epub3_oebps_content(doc_abstraction, doc_matters, oepbs_content_parts);
  epub3_write_output_files(
    doc_matters,
    doc_epub3,
    doc_epub3_endnotes,
    mimetypes,
    meta_inf_container_xml,
    oebps_toc_nav_xhtml,
    oebps_toc_ncx,
    oebps_content_opf,
  );
}
#+END_SRC

**** write output files

#+name: output_epub3_xhtml_seg
#+BEGIN_SRC d
void epub3_write_output_files(M,D,E,Mt,Mic,Otnx,Otn,Oc)(
  M    doc_matters,
  D    doc_epub3,
  E    doc_epub3_endnotes,
  Mt   mimetypes,
  Mic  meta_inf_container_xml,
  Otnx oebps_toc_nav_xhtml,
  Otn  oebps_toc_ncx,
  Oc   oebps_content_opf,
) {
  debug(asserts) {
    static assert(is(typeof(doc_epub3)              == string[][string]));
    static assert(is(typeof(mimetypes)              == string));
    static assert(is(typeof(meta_inf_container_xml) == string));
    static assert(is(typeof(oebps_toc_nav_xhtml)    == string));
    static assert(is(typeof(oebps_toc_ncx)          == string));
    static assert(is(typeof(oebps_content_opf)      == string));
  }
  mixin SiSUpaths;
  auto pth_epub3 = Epub3paths();
  auto xhtml_format = outputXHTMLs();
  /+ zip file +/
  auto fn_epub = pth_epub3.epub_file(doc_matters.source_filename);
  auto zip = new ZipArchive(); // ZipArchive zip = new ZipArchive();
  /+ zip archive member files +/
  try {
    if (!exists(pth_epub3.doc_meta_inf(doc_matters.source_filename))) {
      pth_epub3.doc_meta_inf(doc_matters.source_filename).mkdirRecurse;
    }
    if (!exists(pth_epub3.doc_oebps_css(doc_matters.source_filename))) {
      pth_epub3.doc_oebps_css(doc_matters.source_filename).mkdirRecurse;
    }
    if (!exists(pth_epub3.doc_oebps_image(doc_matters.source_filename))) {
      pth_epub3.doc_oebps_image(doc_matters.source_filename).mkdirRecurse;
    }
    { /+ OEBPS/[segments].xhtml (the document contents) +/
      foreach (seg_filename; doc_matters.segnames) {
        string fn = pth_epub3.fn_oebps_content_xhtml(doc_matters.source_filename, seg_filename);
        /+ add zip archive file members (with their content) +/
        auto zip_arc_member_file = new ArchiveMember();
        // add seg fn to zip archive
        zip_arc_member_file.name = fn;
        auto zip_data = new OutBuffer();
        debug(epub_output) {
          string fn_dbg = pth_epub3.dbg_fn_oebps_content_xhtml(doc_matters.source_filename, seg_filename);
          auto f = File(fn_dbg, "w");
        }
        /+ // f.writeln(seg_head); // not needed built and inserted earlier +/
        foreach (docseg; doc_epub3[seg_filename]) {
          debug(epub_output) { f.writeln(docseg); }
          zip_data.write(docseg.dup); // cast as: char[]
        }
        foreach (docseg; doc_epub3_endnotes[seg_filename]) {
          debug(epub_output) { f.writeln(docseg); }
          zip_data.write(docseg.dup); // cast as: char[]
        }
        debug(epub_output) { f.writeln(xhtml_format.tail); } // needed for each lev4
        zip_data.write(xhtml_format.tail.dup); // cast as: char[]
        zip_arc_member_file.expandedData = zip_data.toBytes();
        zip.addMember(zip_arc_member_file);
        /+ create the zip file +/
        createZipFile!()(fn_epub, zip.build());
      }
    }
    string fn;
    debug(epub_output) { string fn_dbg; }
    File f;
    { /+ mimetypes (identify zip file type) +/
      debug(epub_output) {
        fn_dbg = pth_epub3.dbg_fn_mimetypes(doc_matters.source_filename);
        File(fn_dbg, "w").writeln(mimetypes);
      }
      fn = pth_epub3.fn_mimetypes(doc_matters.source_filename);
      /+ add zip archive file members (with their content) +/
      auto zip_arc_member_file = new ArchiveMember();
      // add mimetypes to zip archive
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      zip_data.write(mimetypes.dup); // cast as: char[]
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      /+ create the zip file +/
      createZipFile!()(fn_epub, zip.build());
    }
    { /+  META-INF/container.xml (identify doc root) +/
      debug(epub_output) {
        fn_dbg = pth_epub3.dbg_fn_dmi_container_xml(doc_matters.source_filename);
        File(fn_dbg, "w").writeln(meta_inf_container_xml);
      }
      fn = pth_epub3.fn_dmi_container_xml(doc_matters.source_filename);
      /+ add zip archive file members (with their content) +/
      auto zip_arc_member_file = new ArchiveMember();
      // add META-INF/container.xml to zip archive
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      zip_data.write(meta_inf_container_xml.dup); // cast as: char[]
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      /+ create the zip file +/
      createZipFile!()(fn_epub, zip.build());
    }
    { /+ OEBPS/toc_nav.xhtml (navigation toc epub3) +/
      debug(epub_output) {
        fn_dbg = pth_epub3.dbg_fn_oebps_toc_nav_xhtml(doc_matters.source_filename);
        File(fn_dbg, "w").writeln(oebps_toc_nav_xhtml);
      }
      fn = pth_epub3.fn_oebps_toc_nav_xhtml(doc_matters.source_filename);
      /+ add zip archive file members (with their content) +/
      auto zip_arc_member_file = new ArchiveMember();
      // add OEBPS/toc_nav.xhtml to zip archive
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      zip_data.write(oebps_toc_nav_xhtml.dup); // cast as: char[]
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      /+ create the zip file +/
      createZipFile!()(fn_epub, zip.build());
    }
    { /+ TODO OEBPS/toc.ncx (navigation toc epub2) +/
      debug(epub_output) {
        fn_dbg = pth_epub3.dbg_fn_oebps_toc_ncx(doc_matters.source_filename);
        File(fn_dbg, "w").writeln(oebps_toc_ncx);
      }
      fn = pth_epub3.fn_oebps_toc_ncx(doc_matters.source_filename);
      /+ add zip archive file members (with their content) +/
      auto zip_arc_member_file = new ArchiveMember();
      // add OEBPS/toc.ncx to zip archive
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      zip_data.write(oebps_toc_ncx.dup); // cast as: char[]
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      /+ create the zip file +/
      createZipFile!()(fn_epub, zip.build());
    }
    { /+ TODO OEBPS/content.opf (doc manifest) +/
      debug(epub_output) {
        fn_dbg = pth_epub3.dbg_fn_oebps_content_opf(doc_matters.source_filename);
        File(fn_dbg, "w").writeln(oebps_content_opf);
      }
      fn = pth_epub3.fn_oebps_content_opf(doc_matters.source_filename);
      /+ add zip archive file members (with their content) +/
      auto zip_arc_member_file = new ArchiveMember();
      // add OEBPS/content.opf to zip archive
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      zip_data.write(oebps_content_opf.dup); // cast as: char[]
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      /+ create the zip file +/
      createZipFile!()(fn_epub, zip.build());
    }
    { /+ OEBPS/_sisu/image (images) +/
      foreach (image; doc_matters.image_list) {
        if (exists("_sisu/image/"~ image)) {
          ("_sisu/image/"~ image)
          .copy((pth_epub3.doc_oebps_image(doc_matters.source_filename)) ~ "/" ~ image);
        }
      }
      foreach (image; doc_matters.image_list) {
        debug(epub_images) {
          writeln(
            "_sisu/image/", image, " -> ",
            pth_epub3.doc_oebps_image(doc_matters.source_filename), "/", image
          );
        }
        auto fn_src = "_sisu/image/"~ image;
        auto fn_out =  pth_epub3.doc_oebps_image(doc_matters.source_filename).to!string ~ "/" ~ image;
        if (exists(fn_src)) {
          {
            auto zip_arc_member_file = new ArchiveMember();
            zip_arc_member_file.name = fn_out;
            auto zip_data = new OutBuffer();
            zip_data.write(cast(char[]) ((fn_src).read));
            zip_arc_member_file.expandedData = zip_data.toBytes();
            zip.addMember(zip_arc_member_file);
            createZipFile!()(fn_epub, zip.build());
          }
        }
      }
    }
  }
  catch (ErrnoException ex) {
    // Handle error
  }
#+END_SRC

*** zip debug, read zip  archive

#+name: output_epub3_xhtml_seg
#+BEGIN_SRC d
  debug(epub_archive) {
    if (exists(fn_epub)) {
      try {
        auto zipped = new ZipArchive((fn_epub).read);
        foreach (filename, member; zipped.directory) {
          auto data = zipped.expand(member);
          writeln(filename, " length ", data.length); // member.name
          // Use data
        }
      }
      catch (ZipException ex) {
        // Handle errors
      }
    }
  }
}
#+END_SRC

** pdf [#C]                                                            :pdf:
** odt                                                                 :odt:
** sqlite [#B]                                                      :sqlite:
** pgsql                                                             :pgsql: