module doc_reform.io_out.odt; template formatODT() { import doc_reform.io_out; import std.digest.sha, std.file, std.outbuffer, std.uri, std.zip, std.conv : to; import doc_reform.io_out.create_zip_file, doc_reform.io_out.xmls, doc_reform.io_out.xmls_css; mixin spineOutputRgxInit; struct formatODT { static auto rgx = Rgx(); string _tags(O)(const O obj) @safe { 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) @safe { 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) @safe { // TODO string _on; _on = (obj.metainfo.object_number.empty) ? "" : (format(q"┃ 「%s」┃", obj.metainfo.object_number, )); return _on; } string _footnotes()(string _txt) @safe { static auto rgx = Rgx(); _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) @safe { string _b = ""; if (obj.attrib.bullet) { _b = format(q"┃● ┃",); } return _b; } string _indent(O)(string _txt, const O obj) @safe { // 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) @safe { // 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) @safe { _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) @safe { 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) @safe { // NOT USED YET struct objNum { string reference() @safe { return format(q"┃ ┃", obj.object_number, obj.object_number, ); } string display() @safe { return format(q"┃ %s%s%s ┃", on_o, obj.object_number, on_c, ); } } return objNum(); } string _break_page()() @safe { return format(q"┃ ┃", ); } string _empty_line_break(O)(string _txt, const O obj) @safe { 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) @safe { 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) @safe { 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) @safe { /+ 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, ) @safe { 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, ) @safe { 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" || "endnotes" || "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, ) @safe { 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, ) @safe { 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_newlines_linebreaks); _o_txt_odt = _block_type_delimiters(_block_lines, obj); return _o_txt_odt; } string block(O,M)( const O obj, const M doc_matters, ) @safe { 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_newlines_linebreaks); _o_txt_odt = _block_type_delimiters(_block_lines, obj); return _o_txt_odt; } string verse(O,M)( const O obj, const M doc_matters, ) @safe { 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_newlines_linebreaks); _o_txt_odt = _block_type_delimiters(_block_lines, obj); return _o_txt_odt; } string code(O,M)( const O obj, const M doc_matters, ) @safe { 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_newlines_linebreaks); 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; } auto tablarize(O)( const O obj, string _txt, ) @safe { 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 ~= ""; } auto t = tuple( _table, _tablenote, ); return t; } int _table_number = 0; string table(O,M)( const O obj, const M doc_matters, ) @safe { 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); auto 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 doc_reform.io_out; import std.digest.sha, std.file, std.outbuffer, std.uri, std.zip, std.conv : to; import doc_reform.io_out.create_zip_file, doc_reform.io_out.xmls, doc_reform.io_out.xmls_css; mixin InternalMarkup; mixin spineOutputRgxInit; auto rgx = Rgx(); // mixin outputXmlODT; string odt_head(I)(I doc_matters) @safe { 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, ) @safe { 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.verbose) { 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() @safe { string _odt_tail = format(q"┃spine: <www.doc_reform.org> and <www.sisudoc.org> ┃",); return _odt_tail; } string content_xml(D,I)( const D doc_abstraction, I doc_matters, ) @safe { 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, ) @safe { 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, ) @safe { { /+ (copy odt images) +/ 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, ) @safe { /+ (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, ) { 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; } // return 0; } string mimetype() @safe { string mimetype_ = format(q"┃application/vnd.oasis.opendocument.text┃"); return mimetype_; } string manifest_rdf() @safe { string _manifest_rdf = format(q"┃ ┃"); return _manifest_rdf; } string settings_xml() @safe { 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() @safe { string _styles_xml = format(q"┃ ┃"); return _styles_xml; } 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)); zip_arc_member_file.expandedData = zip_data.toBytes(); zip.addMember(zip_arc_member_file); createZipFile!()(fn_odt, zip.build()); } } } } if (!(doc_matters.opt.action.quiet)) { writeln(" ", pth_odt.odt_file); } } 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 } }