/+
- Name: SisuDoc Spine, Doc Reform [a part of]
  - Description: documents, structuring, processing, publishing, search
    - static content generator
  - Author: Ralph Amissah
    [ralph.amissah@gmail.com]
  - Copyright: (C) 2015 - 2024 Ralph Amissah, All Rights Reserved.
  - License: AGPL 3 or later:
    Spine (SiSU), a framework for document structuring, publishing and
    search
    Copyright (C) Ralph Amissah
    This program is free software: you can redistribute it and/or modify it
    under the terms of the GNU AFERO General Public License as published by the
    Free Software Foundation, either version 3 of the License, or (at your
    option) any later version.
    This program is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    more details.
    You should have received a copy of the GNU General Public License along with
    this program. If not, see [https://www.gnu.org/licenses/].
    If you have Internet connection, the latest version of the AGPL should be
    available at these locations:
    [https://www.fsf.org/licensing/licenses/agpl.html]
    [https://www.gnu.org/licenses/agpl.html]
  - Spine (by Doc Reform, related to SiSU) uses standard:
    - docReform markup syntax
      - standard SiSU markup syntax with modified headers and minor modifications
    - docReform object numbering
      - standard SiSU object citation numbering & system
  - Homepages:
    [https://www.sisudoc.org]
    [https://www.doc-reform.org]
  - Git
    [https://git.sisudoc.org/]
+/
module sisudoc.io_out.odt;
@safe:
template formatODT() {
  import
    sisudoc.io_out,
    sisudoc.io_out.rgx,
    sisudoc.io_out.rgx_xhtml;
  import
    std.file,
    std.outbuffer,
    std.uri,
    std.zip,
    std.conv : to;
  import
    sisudoc.io_out.create_zip_file,
    sisudoc.io_out.xmls,
    sisudoc.io_out.xmls_css;
  mixin spineRgxOut;
  mixin spineRgxXHTML;
  struct formatODT {
    static auto rgx = RgxO();
    static auto rgx_xhtml = RgxXHTML();
    string _tags(O)(const O obj) {
      string _tags = "";
      if (obj.tags.anchor_tags.length > 0) {
        foreach (tag_; obj.tags.anchor_tags) {
          if (tag_.length > 0) {
            _tags ~= format(q"┃
        
        
      
    ┃",
              _special_characters(tag_, obj),
              _special_characters(tag_, obj),
            );
          }
        }
      }
      return _tags;
    }
    string _xhtml_anchor_tags(O)(O obj) {
      const(string[]) anchor_tags = obj.tags.anchor_tags;
      string tags="";
      if (anchor_tags.length > 0) {
        foreach (tag; anchor_tags) {
          if (!(tag.empty)) {
            tags ~= "";
          }
        }
      }
      return tags;
    }
    string obj_num(O)(const O obj) { // TODO
      string _on;
      _on = (obj.metainfo.object_number.empty)
      ? ""
      : (format(q"┃
      「%s」┃",
          obj.metainfo.object_number,
        ));
      return _on;
    }
    string _footnotes()(string _txt) {
      static auto rgx = RgxO();
      static auto rgx_xhtml = RgxXHTML();
      _txt = _txt.replaceAll(
        rgx.inline_notes_al_regular_number_note,
        format(q"┃
      
        %s
      
      
        
          %s
        
      
    ┃",
          "$1", "$1", "$2",
        )
      );
      return _txt;
    }
    string _bullet(O)(const O obj) {
      string _b = "";
      if (obj.attrib.bullet) {
        _b = format(q"┃● ┃",);
      }
      return _b;
    }
    string _indent(O)(string _txt, const O obj) { // TODO
      // if (obj.attrib.indent_base > 0 ||
      //   obj.attrib.indent_hang > 0
      // ) {
      if (obj.metainfo.is_a == "toc") {
        _txt = format(q"┃
    %s
      %s%s%s
    ┃",
          (obj.attrib.indent_base < 4)
          ? "\n    " : "",
          obj.attrib.indent_base,
          obj.attrib.indent_base,
          _tags(obj),
          _txt,
          obj_num(obj),
        );
      } else if (!empty(obj.metainfo.object_number)) {
        if (obj.attrib.indent_base == 0 && obj.attrib.indent_hang == 0) {
          _txt = format(q"┃
    %s
      
        
        
      
      %s%s%s
    ┃",
            _bullet(obj),
            obj.metainfo.object_number,
            obj.metainfo.object_number,
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        } else if (obj.attrib.indent_base == obj.attrib.indent_hang) {
          _txt = format(q"┃
    %s
      
        
        
      
      %s%s%s
    ┃",
            obj.attrib.indent_base,
            _bullet(obj),
            obj.metainfo.object_number,
            obj.metainfo.object_number,
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        } else {
          _txt = format(q"┃
    %s
      
        
        
      
      %s%s%s
    ┃",
            obj.attrib.indent_base,
            obj.attrib.indent_hang,
            _bullet(obj),
            obj.metainfo.object_number,
            obj.metainfo.object_number,
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        }
      } else {
        if (obj.attrib.indent_base == 0 && obj.attrib.indent_hang == 0) { /+ can omit and would explicitly set indent base and hang as 0 each below +/
          _txt = format(q"┃
    %s
      %s%s%s
    ┃",
            _bullet(obj),
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        } else if (obj.attrib.indent_base == obj.attrib.indent_hang) {
          _txt = format(q"┃
    %s
      %s%s%s
    ┃",
            obj.attrib.indent_base,
            _bullet(obj),
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        } else {
          _txt = format(q"┃
    %s
      %s%s%s
    ┃",
            _bullet(obj),
            obj.attrib.indent_base,
            obj.attrib.indent_hang,
            _tags(obj),
            _txt,
            obj_num(obj),
          );
        }
      }
      return _txt;
    }
    string _block_type_delimiters(O)(string[] _block_lines, const O obj) { // TODO
      string _block = "";
      foreach (i, _line; _block_lines) {
        _line = _footnotes(_line);
        if (i == 0) {
          _block ~= format(q"┃
    %s
        
          
          
        
      %s
    ┃",
            _bullet(obj),
            obj.metainfo.object_number,
            obj.metainfo.object_number,
            // _tags(obj),
            _line,
          );
        } else {
          _block ~= format(q"┃
    %s┃",
            _line);
        }
      }
      _block ~= format(q"┃
    
      「%s」
    
    ┃",
        obj_num(obj));
      return _block;
    }
    string _special_characters(O)(string _txt, const O obj) {
      _txt = _txt
        .replaceAll(rgx_xhtml.ampersand,    "&")
        .replaceAll(rgx_xhtml.quotation,    """)
        .replaceAll(rgx_xhtml.less_than,    "<")
        .replaceAll(rgx_xhtml.greater_than, ">")
        .replaceAll(rgx.nbsp_char,          " ");
      return _txt;
    }
    string _preserve_white_spaces(O)(string _txt, const O obj) {
      if (obj.metainfo.is_a == "code" || obj.metainfo.is_a == "verse" || obj.metainfo.is_a == "block") {
        _txt = _txt
          .replaceAll(rgx.space,      " ");
      }
      return _txt;
    }
    string _font_face(string _txt){
      _txt = _txt
        .replaceAll(rgx.inline_strike,      "$1")
        .replaceAll(rgx.inline_insert,      "$1")
        .replaceAll(rgx.inline_cite,        "$1")
        .replaceAll(rgx.inline_emphasis,    format(q"┃%s┃",       "$1"))
        .replaceAll(rgx.inline_bold,        format(q"┃%s┃",       "$1"))
        .replaceAll(rgx.inline_italics,     format(q"┃%s┃",     "$1"))
        .replaceAll(rgx.inline_underscore,  format(q"┃%s┃", "$1"))
        .replaceAll(rgx.inline_superscript, format(q"┃%s┃","$1"))
        .replaceAll(rgx.inline_subscript,   format(q"┃%s┃",  "$1"))
        .replaceAll(rgx.inline_mono,        format(q"┃%s┃",  "$1"));
      return _txt;
    }
    auto _obj_num(O)(O obj) { // NOT USED YET
      struct objNum {
        string reference() {
          return format(q"┃
        
        
      ┃",
            obj.object_number,
            obj.object_number,
          );
        }
        string display() {
          return format(q"┃
      %s%s%s
      ┃",
            on_o,
            obj.object_number,
            on_c,
          );
        }
      }
      return objNum();
    }
    string _break_page()() {
      return format(q"┃
    
    ┃",
      );
    }
    string _empty_line_break(O)(string _txt, const O obj) {
      if (obj.metainfo.is_a == "code" || obj.metainfo.is_a == "verse" || obj.metainfo.is_a == "block") {
        _txt = _txt
          .replaceAll(rgx.br_empty_line,    "
");
      }
      return _txt;
    }
    string _links(O)(string _txt, const O obj) {
      if (obj.metainfo.is_a != "code") {
        if (obj.metainfo.is_a == "toc") {
          _txt = replaceAll!(m =>
             m[1] ~ "┤"
             ~ (replaceAll!(n =>
                 n["type"] ~ n["path"] ~ (n["file"].encodeComponent)
               )((obj.stow.link[m["num"].to!ulong]).to!string, rgx.uri_identify_components))
             ~ "├"
          )(_txt, rgx.inline_link_number_only)
            .replaceAll(rgx.inline_link,
              format(q"┃%s┃",
                _special_characters("$3", obj),
                _special_characters("$1", obj)
              ));
        } else {
          _txt = replaceAll!(m =>
             m[1] ~ "┤"
             ~ (replaceAll!(n =>
                 n["type"] ~ n["path"] ~ (n["file"].encodeComponent)
               )((obj.stow.link[m["num"].to!ulong]).to!string, rgx.uri_identify_components))
             ~ "├"
          )(_txt, rgx.inline_link_number_only)
            .replaceAll(rgx.inline_link,
              format(q"┃%s┃",
                _special_characters("$2", obj),
                _special_characters("$1", obj)
              ));
        }
      }
      debug(links) {
        if (obj.text.match(rgx.inline_link_number)
          && _txt.match(rgx.inline_link_number_only)
        ) {
            writeln(">>    ", _txt);
            writeln("is_a: ", obj.metainfo.is_a);
        }
      }
      return _txt;
    }
    string _images(O)(string _txt, const O obj) {
      if (_txt.match(rgx.inline_image)) {
        _txt = _txt
          .replaceAll(rgx.inline_image,
            ("$1 $6"))
          .replaceAll(
            rgx.inline_link_empty,
            ("$1"));
      }
      return _txt;
    }
    string markup(O)(const O obj) {
      /+ markup TODO +/
      string _txt = obj.text;
      _txt = _special_characters(_txt, obj);     // TODO & why both obj & obj.text, consider also in output_xmls.org
      if (obj.metainfo.is_a == "code" || obj.metainfo.is_a == "verse" || obj.metainfo.is_a == "block") {
        _txt = replaceAll!(m => _preserve_white_spaces(m[1], obj))(_txt, rgx.spaces_keep);
      } // check that this does what you want, keep: leading spaces (indent) & more than single spaces within text
      // _txt = _preserve_white_spaces(_txt, obj);  // (obj.metainfo.is_a == "code" || obj.metainfo.is_a == "verse" || obj.metainfo.is_a == "block")
      _txt = _font_face(_txt);
      _txt = _images(_txt, obj);                  // (obj.metainfo.is_a != "code")
      _txt = _links(_txt, obj);                  // (obj.metainfo.is_a != "code")
      _txt = _empty_line_break(_txt, obj);       // (obj.metainfo.is_a == "code" || obj.metainfo.is_a == "verse" || obj.metainfo.is_a == "block")
      return _txt;
    }
    string heading(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body" || "frontmatter" || "backmatter");
      assert(obj.metainfo.is_of_section == "body" || "toc" || "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "para");
      assert(obj.metainfo.is_a          == "heading");
      string _o_txt_odt = markup(obj);
      if (obj.metainfo.dummy_heading
        && (obj.metainfo.is_a == "toc" || obj.metainfo.is_a == "heading")) {
        _o_txt_odt = "";
      } else if (obj.metainfo.is_a == "toc") {
        _o_txt_odt = format(q"┃%s
      %s%s%s
    ┃",
          _break_page,
          obj.metainfo.heading_lev_markup,
          obj.metainfo.heading_lev_markup,
          _tags(obj),
          _o_txt_odt,
          obj_num(obj),
        );
      } else {
        _o_txt_odt = _footnotes(_o_txt_odt);
        _o_txt_odt = format(q"┃%s
      
        
        
      
      %s%s%s
    ┃",
          _break_page,
          obj.metainfo.heading_lev_markup,
          obj.metainfo.heading_lev_markup,
          obj.metainfo.object_number,
          obj.metainfo.object_number,
          _tags(obj),
          _o_txt_odt,
          obj_num(obj),
        );
      }
      return _o_txt_odt;
    }
    string para(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body" || "frontmatter" || "backmatter");
      assert(obj.metainfo.is_of_section == "body" || "toc" || "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "para");
      assert(obj.metainfo.is_a          == "para" || "toc" || "endnote" || "glossary" || "bibliography" || "bookindex" || "blurb");
      string _o_txt_odt;
      if (obj.metainfo.dummy_heading
        && (obj.metainfo.is_a == "toc" || obj.metainfo.is_a == "heading")) {
        _o_txt_odt = "";
      } else {
        _o_txt_odt = markup(obj);
        _o_txt_odt = _footnotes(_o_txt_odt);
        _o_txt_odt = _indent(_o_txt_odt, obj); // final setting?
      }
      return _o_txt_odt;
    }
    string quote(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "quote");
      string _o_txt_odt = markup(obj);
      _o_txt_odt = _footnotes(_o_txt_odt); // decide
      return _o_txt_odt;
    }
    string group(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "group");
      string _o_txt_odt = markup(obj);
      /+ TODO
        - split lines
          - only double newlines (paragraph delimiter), (not line breaks, single new lines)
        - no hard space indentation
      +/
      string[] _block_lines = (_o_txt_odt).split(rgx.br_linebreaks_newlines);
      _o_txt_odt = _block_type_delimiters(_block_lines, obj);
      return _o_txt_odt;
    }
    string block(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "block");
      string _o_txt_odt = markup(obj);
      string[] _block_lines = (_o_txt_odt).split(rgx.br_linebreaks_newlines);
      _o_txt_odt = _block_type_delimiters(_block_lines, obj);
      return _o_txt_odt;
    }
    string verse(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body" || "glossary" || "bibliography" || "bookindex" || "blurb");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "verse");
      string _o_txt_odt = markup(obj);
      string[] _block_lines = (_o_txt_odt).split(rgx.br_linebreaks_newlines);
      _o_txt_odt = _block_type_delimiters(_block_lines, obj);
      return _o_txt_odt;
    }
    string code(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "code");
      string _o_txt_odt = markup(obj);
      /+ TODO
        - split lines
          - each line including empty lines
        - hard space indentation
          - "^[ ]"  
          - count number only at beginning of line and replace each
      +/
      string[] _block_lines = (_o_txt_odt).split(rgx.br_linebreaks_newlines);
      string _block = "";
      foreach (i, _line; _block_lines) {
        if (i == 1) {
          _block ~= format(q"┃
    
    
      
      
    
    %s
    ┃",
            obj.metainfo.object_number,
            obj.metainfo.object_number,
            _line,
          );
        } else {
          _block ~= format(q"┃
    %s┃",
            _line);
        }
      }
      _block ~= format(q"┃
    
      「%s」
    
    ┃",
         obj_num(obj));
      _o_txt_odt = _block;
      return _o_txt_odt;
    }
    Tuple!(string, string) tablarize(O)(
      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 ~= "";
          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 {
              _table ~= format(q"┃
    
    %s
    
    ┃",
                (row_idx == 0 && obj.table.heading) ? "Table_Heading" : "P_table_cell",
                cell,
              );
            }
          }
          _table ~= "";
        }
      Tuple!(string, string) t = tuple(
        _table,
        _tablenote,
      );
      return t;
    }
    int _table_number = 0;
    string table(O,M)(
      const        O  obj,
      const        M  doc_matters,
    ) {
      assert(obj.metainfo.is_of_part    == "body");
      assert(obj.metainfo.is_of_section == "body");
      assert(obj.metainfo.is_of_type    == "block");
      assert(obj.metainfo.is_a          == "table");
      string _o_txt_odt = markup(obj);
      Tuple!(string, string) t = tablarize(obj, _o_txt_odt);
      string _note = t[1];
      _o_txt_odt = format(q"┃
    
      
        
        
      
      
          %s
        
        
      「%s」
    ┃",
        _table_number++,
        obj.metainfo.object_number,
        obj.metainfo.object_number,
        obj.table.number_of_columns,
        t[0],
        obj.metainfo.object_number,
        // _note,
      );
      return _o_txt_odt;
    }
  }
}
template outputODT() {
  import
    sisudoc.io_out,
    sisudoc.io_out.rgx,
    sisudoc.io_out.rgx_xhtml;
  import
    std.file,
    std.outbuffer,
    std.uri,
    std.zip,
    std.conv : to;
  import
    sisudoc.io_out.create_zip_file,
    sisudoc.io_out.xmls,
    sisudoc.io_out.xmls_css;
  mixin InternalMarkup;
  mixin spineRgxOut;
  mixin spineRgxXHTML;
  static auto rgx = RgxO();
  static auto rgx_xhtml = RgxXHTML();
  // mixin outputXmlODT;
  string odt_head(I)(I doc_matters) {
    string _has_tables = format(q"┃
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
  ┃",);
    string _odt_head = format(q"┃
  
  
  
  
  
  
  
  
  
  
  
  
  
    %s
    
      
    
  
  
    
      
      
        
        
        
        
      
  ┃",
    (doc_matters.has.tables > 0) ? _has_tables : "",
  );
    return _odt_head;
  }
  string odt_body(D,I)(
    const D    doc_abstraction,
          I    doc_matters,
  ) {
    mixin formatODT;
    auto odt_format = formatODT();
    string delimit = "";
    string doc_odt = "";
    string _txt = "";
    foreach (part; doc_matters.has.keys_seq.scroll) {
      foreach (obj; doc_abstraction[part]) {
        switch (obj.metainfo.is_of_part) {
        case "frontmatter":              assert(part == "head" || "toc");
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              _txt = delimit ~ odt_format.heading(obj, doc_matters);
              goto default;
            case "toc":
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            default:
              doc_odt ~= _txt;
              _txt = "";
              break;
            }
            break;
          default: break;
          }
          break;
        case "body":                     assert(part == "body" || "head"); // surprise
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              _txt = delimit ~ odt_format.heading(obj, doc_matters);
              goto default;
            case "para":
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            default:
              doc_odt ~= _txt;
              _txt = "";
              break;
            }
            break;
          case "block":
            switch (obj.metainfo.is_a) {
            case "quote":
              _txt = odt_format.quote(obj, doc_matters);
              goto default;
            case "group":
              _txt = odt_format.group(obj, doc_matters);
              goto default;
            case "block":
              _txt = odt_format.block(obj, doc_matters);
              goto default;
            case "verse":
              _txt = odt_format.verse(obj, doc_matters);
              goto default;
            case "code":
              _txt = odt_format.code(obj, doc_matters);
              goto default;
            case "table":
              _txt = odt_format.table(obj, doc_matters);
              goto default;
            default:
              doc_odt ~= _txt;
              _txt = "";
              break;
            }
            break;
          default: break;
          }
          break;
        case "backmatter":
          assert(part == "endnotes" || "glossary" || "bibliography" || "bookindex" || "blurb" || "tail");
          switch (obj.metainfo.is_of_type) {
          case "para":
            switch (obj.metainfo.is_a) {
            case "heading":
              _txt = delimit ~ odt_format.heading(obj, doc_matters);
              goto default;
            case "endnote":              assert(part == "endnotes");
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            case "glossary":             assert(part == "glossary");
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            case "bibliography":         assert(part == "bibliography");
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            case "bookindex":            assert(part == "bookindex");
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            case "blurb":                assert(part == "blurb");
              _txt = odt_format.para(obj, doc_matters);
              goto default;
            default:
              doc_odt ~= _txt;
              _txt = "";
              break;
            }
            break;
          default: break;
          }
          break;
        case "comment":
          break;
        default:
          { /+ debug +/
            if (doc_matters.opt.action.debug_do
            && doc_matters.opt.action.vox_gt1) {
              writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_of_part);
              writeln(__FILE__, ":", __LINE__, ": ", obj.metainfo.is_a);
              writeln(__FILE__, ":", __LINE__, ": ", obj.text);
            }
          }
          break;
        }
      }
    }
    return doc_odt;
  }
  
  string odt_tail() {
    string _odt_tail = format(q"┃spine: <www.sisudoc.org> and <www.sisudoc.org>
  ┃",);
    return _odt_tail;
  }
  string content_xml(D,I)(
    const D    doc_abstraction,
          I    doc_matters,
  ) {
    string _content_xml;
    string break_line = (doc_matters.opt.action.debug_do) ? "\n" : "";
    string odt_break_page = format(q"┃┃",);
    string br_pg          = format(q"┃┃",);
    _content_xml ~= odt_head(doc_matters);
    _content_xml ~= odt_body(doc_abstraction, doc_matters);
    _content_xml ~= odt_tail;
    return _content_xml;
  }
  string manifest_xml(M)(
    auto ref              M    doc_matters,
  ) {
    string _bullet = format(q"┃┃");
    string[] _images = [ _bullet ];
    foreach (image; doc_matters.srcs.image_list) {
      _images ~= format(q"┃    ┃", image);
    }
    string _manifest_xml = format(q"┃
  
    
    
    
    %s
    
    
    
    
  
  ┃",
  _images.join("\n"),
  );
    return _manifest_xml;
  }
  void images_cp(M)(
    auto ref              M    doc_matters,
  ) {
    { /+ (copy odt images) +/
      import sisudoc.io_out.paths_output;
      auto pth_odt = spinePathsODT!()(doc_matters);
      foreach (image; doc_matters.srcs.image_list) {
        auto fn_src_in = doc_matters.src.image_dir_path ~ "/" ~ image;
        auto fn_src_out_file = pth_odt.image_dir("fs") ~ "/" ~ image;
        auto fn_src_out_zip = pth_odt.image_dir("zip") ~ "/" ~ image;
        if (exists(fn_src_in)) {
          if (doc_matters.opt.action.debug_do) {
            if (doc_matters.opt.action.debug_do) {
              fn_src_in.copy(fn_src_out_file);
            }
          }
        }
      }
    }
    // return 0;
  }
  string meta_xml(M)(
    auto ref              M    doc_matters,
  ) {
    /+ (meta_xml includes output time-stamp) +/
    string _meta_xml = format(q"┃
  
    
      %s
      %s
      %s
      en-US
    
  
  ┃",
  doc_matters.generator_program.name_and_version,
  doc_matters.generated_time,
  doc_matters.generated_time,
  );
    return _meta_xml;
  }
  void dirtree(I)(
    I   doc_matters,
  ) {
    import sisudoc.io_out.paths_output;
    auto pth_odt = spinePathsODT!()(doc_matters);
    if (doc_matters.opt.action.debug_do) { /+ (dir tree) +/
      if (!exists(pth_odt.meta_inf_dir("fs"))) {
        pth_odt.meta_inf_dir("fs").mkdirRecurse;
      }
      if (!exists(pth_odt.image_dir("fs"))) {
        pth_odt.image_dir("fs").mkdirRecurse;
      }
    }
    if (!exists(pth_odt.base_pth)) {
      pth_odt.base_pth.mkdirRecurse;
    }
    if (!exists(pth_odt.base_pth ~ "/index.html")) {
      import sisudoc.io_out.html_snippet;
      mixin htmlSnippet;
      auto f = File(pth_odt.base_pth ~"/index.html", "w");
      f.writeln(format_html_blank_page_guide_home(
        "../../css/html_scroll.css",
        "https://sisudoc.org",
        "../../index.html",
      ));
    }
    // return 0;
  }
  string mimetype() {
    string mimetype_ = format(q"┃application/vnd.oasis.opendocument.text┃");
    return mimetype_;
  }
  string manifest_rdf() {
    string _manifest_rdf = format(q"┃
  
    
      
    
    
      
    
    
      
    
    
      
    
    
      
    
  
  ┃");
    return _manifest_rdf;
  }
  string settings_xml() {
    string _settings_xml = format(q"┃
  
    
      
        0
        0
        0
        0
        true
        false
        
          
            view2
            0
            0
            0
            0
            0
            0
            0
            2
            true
            100
            false
          
        
      
      
        true
        false
        false
        true
        true
        false
        true
        false
        false
        
        false
        false
        false
        true
        true
        0
        false
        false
        false
        false
        true
        false
        false
        false
        false
        true
        true
        false
        false
        true
        false
        true
        false
        high-resolution
        1
        0
        true
        
        
        false
        true
        
        false
        true
        false
        true
        false
        true
        false
        
        true
        true
        false
        true
        true
        true
        false
        false
        
        false
        0
        false
        false
        true
        true
      
    
  
  ┃");
    return _settings_xml;
  }
  string styles_xml() {
    string _styles_xml = format(q"┃
  
    
      
      
      
      
      
    
    
      
        
        
          
        
        
      
      
        
        
      
      
        
      
      
        
      
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
        
        
        
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
        
      
      
        
      
      
      
        
      
      
        
      
      
        
      
      
        
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
        
      
      
      
      
      
      
        
      
      
        
      
      
        
      
      
      
      
      
      
      
      
      
      
    
    
      
    
  
  ┃");
    return _styles_xml;
  }
  @trusted void writeOutputODT(W,I)(
    const W    odt_content,
          I    doc_matters,
  ) {
    auto pth_odt = spinePathsODT!()(doc_matters);
    auto fn_odt = pth_odt.odt_file;
    auto zip = new ZipArchive(); // ZipArchive zip = new ZipArchive();
    void ODTzip()(string contents, string fn) {
      auto zip_arc_member_file = new ArchiveMember();
      zip_arc_member_file.name = fn;
      auto zip_data = new OutBuffer();
      (doc_matters.opt.action.debug_do)
      ? zip_data.write(contents.dup)
      : zip_data.write(contents.dup
        .replaceAll(rgx.spaces_line_start, "")
        .replaceAll(rgx.newline, "")
        .strip
      );
      zip_arc_member_file.expandedData = zip_data.toBytes();
      zip.addMember(zip_arc_member_file);
      createZipFile!()(fn_odt, zip.build());
    }
    try {
      if (!exists(pth_odt.base_pth)) { // check
        pth_odt.base_pth.mkdirRecurse;
      }
      {
        string fn;
        File f;
        { fn = pth_odt.mimetype("zip");
          ODTzip(odt_content.mimetype, fn);
        }
        { fn = pth_odt.manifest_rdf("zip");
          ODTzip(odt_content.manifest_rdf, fn);
        }
        { fn = pth_odt.settings_xml("zip");
          ODTzip(odt_content.settings_xml, fn);
        }
        { fn = pth_odt.styles_xml("zip");
          ODTzip(odt_content.styles_xml, fn);
        }
        { fn = pth_odt.content_xml("zip");
          ODTzip(odt_content.content_xml, fn);
        }
        { fn = pth_odt.manifest_xml("zip");
          ODTzip(odt_content.manifest_xml, fn);
        }
        { fn = pth_odt.meta_xml("zip");
          ODTzip(odt_content.meta_xml, fn);
        }
        { /+ (images) +/
          foreach (image; doc_matters.srcs.image_list) {
            auto fn_src = doc_matters.src.image_dir_path ~ "/" ~ image;
            auto fn_out = pth_odt.image_dir("zip") ~ "/" ~ 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)); // trusted?
                zip_arc_member_file.expandedData = zip_data.toBytes();
                zip.addMember(zip_arc_member_file);
                createZipFile!()(fn_odt, zip.build());
              }
            }
          }
        }
        if (doc_matters.opt.action.vox_gt0) {
          writeln(" ", pth_odt.odt_file);
        }
      }
      if (!exists(pth_odt.base_pth ~ "/index.html")) {
        import sisudoc.io_out.html_snippet;
        mixin htmlSnippet;
        auto f = File(pth_odt.base_pth ~"/index.html", "w");
        f.writeln(format_html_blank_page_guide_home(
          "../../css/html_scroll.css",
          (doc_matters.opt.action.webserver_url_doc_root.length > 0)
            ? doc_matters.opt.action.webserver_url_doc_root
            : doc_matters.conf_make_meta.conf.w_srv_data_root_url,
          "../../index.html",
        ));
      }
    } catch (ErrnoException ex) {
      // Handle error
    }
    if (doc_matters.opt.action.debug_do) {
      pth_odt.mimetype("fs");     /+ (mimetype) +/
      pth_odt.manifest_rdf("fs"); /+ (manifest.rdf) +/
      pth_odt.settings_xml("fs"); /+ (settings.xml) +/
      pth_odt.styles_xml("fs");   /+ (styles_xml) +/
  
      pth_odt.content_xml("fs");
      pth_odt.manifest_xml("fs");
      pth_odt.meta_xml("fs");
    }
  }
  void outputODT(D,I)(
    const    D   doc_abstraction,
             I   doc_matters,
  ) {
    struct ODT {
    /+ fixed output +/
      string mimetype;
      string manifest_rdf;
      string settings_xml;
      string styles_xml;
    /+ variable output +/
      string content_xml;    // substantive content
      string manifest_xml;   // image list changes
      string meta_xml;       // time stamp
    }
    // auto pth_odt          = spinePathsODT!()(doc_matters);
    auto odt         = ODT();
    odt.mimetype     = mimetype;
    odt.manifest_rdf = manifest_rdf;
    odt.settings_xml = settings_xml;
    odt.styles_xml   = styles_xml;
    odt.content_xml  = content_xml(doc_abstraction, doc_matters);
    odt.manifest_xml = manifest_xml(doc_matters);
    odt.meta_xml     = meta_xml(doc_matters);
    odt.writeOutputODT(doc_matters);
    dirtree(doc_matters);
    images_cp(doc_matters);                       // copy images
  }
}