diff options
Diffstat (limited to 'org/out_xmls.org')
| -rw-r--r-- | org/out_xmls.org | 173 |
1 files changed, 133 insertions, 40 deletions
diff --git a/org/out_xmls.org b/org/out_xmls.org index 33dfa5d..4d1c4a7 100644 --- a/org/out_xmls.org +++ b/org/out_xmls.org @@ -41,6 +41,7 @@ template outputXHTMLs() { <<xhtml_format_objects_special_characters_date>> <<xhtml_format_objects_breaks_indents_bullets>> <<xhtml_format_objects_font_face>> + <<xhtml_format_objects_xml_heading_element>> <<xhtml_format_objects_xml_anchor_tags>> <<xhtml_format_objects_header_metadata>> <<xhtml_format_objects_site_info_button>> @@ -301,6 +302,23 @@ string font_face(string _txt) { **** anchor tags +#+NAME: xhtml_format_objects_xml_heading_element +#+BEGIN_SRC d +/+ ↓ the heading element for a markup level. + Markup goes to level 7; HTML stops at h6, and an <h7> is simply + not an element, so the deepest three markup levels share h6 and + are told apart by the "heading" class and their ocn. Level 0 is + the document title, which is h1 in a segment of its own. ++/ +int _heading_element_level(O)(O obj, string _xml_type) { + int _lev = obj.metainfo.heading_lev_markup.to!int; + if (_xml_type == "epub" && _lev == 0) { return 1; } + return (_lev > 6) ? 6 : _lev; +} +#+END_SRC + +**** anchor tags + #+NAME: xhtml_format_objects_xml_anchor_tags #+BEGIN_SRC d string _xhtml_anchor_tags(O)(O obj) { @@ -639,13 +657,13 @@ string tail(M)(M doc_matters) { string o; o = format(q"┃<hr /> <div class="rights"> - <p class="small" id="copyright"> + <p class="small" id="doc_footer_copyright"> <b>Copyright:</b> %s </p> </div> %s <div class="rights"> - <p class="small" id="rights"> + <p class="small" id="doc_footer_rights"> %s </p> </div> @@ -737,7 +755,9 @@ string inline_links(O,M)( if (_xml_type == "seg" || _xml_type == "epub") { seg_lvs = (_xml_type == "epub") ? "seg_lv1to4" : "seg_lv4"; foreach (m; _txt.match(rgx.inline_link_hash)) { - if (m.captures["hash"] in doc_matters.has.tag_associations) { + if ((m.captures["hash"] in doc_matters.has.tag_associations) + && (seg_lvs in doc_matters.has.tag_associations[(m.captures["hash"])]) + ) { if ( m.captures["hash"] == doc_matters.has.tag_associations[(m.captures["hash"])][seg_lvs] @@ -1132,15 +1152,13 @@ string heading(O,M)( (doc_matters.opt.action.ocn_off) ? "" : ((obj.metainfo.object_number.empty) ? "" : obj.metainfo.identifier), - ((_xml_type == "epub" && obj.metainfo.heading_lev_markup == 0) ? 1 - : obj.metainfo.heading_lev_markup), + _heading_element_level(obj, _xml_type), obj.metainfo.is_a, obj.metainfo.identifier, tags, heading_lev_anchor_tag, _txt, - ((_xml_type == "epub" && obj.metainfo.heading_lev_markup == 0) ? 1 - : obj.metainfo.heading_lev_markup), + _heading_element_level(obj, _xml_type), ); } else { o ~= format(q"┃%s @@ -1150,14 +1168,12 @@ string heading(O,M)( </h%s> </div>┃", _horizontal_rule, - ((_xml_type == "epub" && obj.metainfo.heading_lev_markup == 0) ? 1 - : obj.metainfo.heading_lev_markup), + _heading_element_level(obj, _xml_type), obj.metainfo.is_a, tags, heading_lev_anchor_tag, _txt, - ((_xml_type == "epub" && obj.metainfo.heading_lev_markup == 0) ? 1 - : obj.metainfo.heading_lev_markup), + _heading_element_level(obj, _xml_type), ); } return o; @@ -1706,6 +1722,17 @@ align="left|right|center" #+NAME: xhtml_format_objects_tablarize #+BEGIN_SRC d +/+ ↓ column widths as a <colgroup>, not a width attribute on every cell +/ +string _table_colgroup(O)(const O obj) { + if (obj.table.column_widths.length == 0) { return ""; } + string _cols; + foreach(w; obj.table.column_widths) { + _cols ~= (w > 0) + ? "<col style=\"width:" ~ w.to!string ~ "%\" />" + : "<col />"; + } + return "<colgroup>" ~ _cols ~ "</colgroup>"; +} Tuple!(string, string) tablarize(O)( string _txt, const O obj, @@ -1714,27 +1741,42 @@ Tuple!(string, string) tablarize(O)( string[] _table_cols; string _table; string _tablenote; + bool _in_head = false; + bool _in_body = false; foreach(row_idx, row; _table_rows) { _table_cols = row.split(rgx.table_delimiter_col); + if ((_table_cols.length == 1) + && (_table_rows.length <= row_idx+2)) { + _tablenote ~= _table_cols[0]; + continue; + } + /+ ↓ a heading row goes in <thead>, the rest in <tbody> +/ + if (row_idx == 0 && obj.table.heading) { + _table ~= "<thead>"; + _in_head = true; + } else if (!_in_body) { + if (_in_head) { _table ~= "</thead>"; _in_head = false; } + _table ~= "<tbody>"; + _in_body = true; + } _table ~= "<tr>"; foreach(col_idx, cell; _table_cols) { - if ((_table_cols.length == 1) - && (_table_rows.length <= row_idx+2)) { - _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 ~ ">"; - } + bool _is_th = (row_idx == 0 && obj.table.heading); + string _col_is = _is_th ? "th" : "td"; + string _class = (col_idx < obj.table.column_aligns.length + && obj.table.column_aligns[col_idx] == "l") ? "left" : "right"; + _table ~= "<" ~ _col_is + ~ (_is_th ? " scope=\"col\"" : "") + ~ " class=\"" ~ _class ~ "\">"; + _table ~= cell; + _table ~= "</" ~ _col_is ~ ">"; } _table ~= "</tr>"; } + if (_in_head) { _table ~= "</thead>"; } + else if (_in_body) { _table ~= "</tbody>"; } Tuple!(string, string) t = tuple( - _table, + (_table_colgroup(obj) ~ _table), _tablenote, ); return t; @@ -1762,12 +1804,12 @@ string table(O,M)( 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" cellpadding="2" align="center"> + <div class="%s" id="%s">%s + <table class="table"> %s </table> %s - </p> + </div> </div>┃", obj.metainfo.object_number, (doc_matters.opt.action.ocn_off) ? "" : ((obj.metainfo.object_number.empty) ? "" : obj.metainfo.identifier), @@ -1775,7 +1817,7 @@ string table(O,M)( obj.metainfo.object_number, tags, _txt, - _note + (_note.strip.empty) ? "" : ("<p class=\"tablenote\">" ~ _note.strip ~ "</p>") ); return o; } @@ -2507,6 +2549,7 @@ template outputEPub3() { static auto rgx = RgxO(); static auto rgx_xhtml = RgxXHTML(); <<xhtml_format_objects_special_characters_text>> + <<output_epub3_manifest_id>> <<output_epub3_last_modified_time>> <<output_epub3_fixed_mimetypes>> <<output_epub3_fixed_container_xml>> @@ -2528,6 +2571,51 @@ template outputEPub3() { #+END_SRC ** special (epub) files :format: + + +#+NAME: output_epub3_manifest_id +#+BEGIN_SRC d +/+ ↓ a manifest item id, which has to be an XML name. + An XML name may not begin with a digit, and a file quite happily + may: "2bits_02_01-100.png" gave id="2bits_02_01-100", which is not + a name. A prefix settles the first character, and anything else a + filename allows and a name does not becomes an underscore. +/ +string _manifest_id(string _prefix, string _name) { + string _id; + foreach (c; _name) { + _id ~= ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.') + ? c : '_'; + } + return _prefix ~ "_" ~ _id; +} +/+ ↓ the media type of an image, which is not its file extension. + There is no "image/jpg"; a manifest that claims one is declaring a + foreign resource, and EPUB then demands a fallback for it. The + core image types are these, and anything else genuinely is + foreign. +/ +string _image_media_type(string _ext) { + switch (_ext.toLower) { + case "jpg": case "jpeg": return "image/jpeg"; + case "png": return "image/png"; + case "gif": return "image/gif"; + case "svg": case "svgz": return "image/svg+xml"; + case "webp": return "image/webp"; + default: return "image/" ~ _ext.toLower; + } +} +/+ ↓ the publication identifier, as a real RFC 4122 UUID. + Version 5, name based: the name is the document's own uid, so the + identifier is stable across rebuilds and distinct per document, + which is what dc:identifier is for. A random v4 would be neither. + The namespace is v5 of "sisudoc.org" under the DNS namespace. +/ +string _epub_publication_uuid(string _doc_uid) { + import std.uuid : sha1UUID, dnsNamespace; + auto _ns = sha1UUID("sisudoc.org", dnsNamespace); + return sha1UUID(_doc_uid, _ns).toString; +} +#+END_SRC + *** time #+NAME: output_epub3_last_modified_time @@ -2590,7 +2678,7 @@ xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> #+NAME: epub3_package_version #+BEGIN_SRC d -<package version="3.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" prefix="rendition: http://www.idpf.org/vocab/rendition/#"> +<package version="3.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" xml:lang="%s"> #+END_SRC #+NAME: output_epub3_constructs_oebps_content @@ -2599,11 +2687,10 @@ xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> string epub3_oebps_content(D,P)(D doc, P parts) { auto xhtml_format = outputXHTMLs(); auto pth_epub3 = spinePathsEPUB!()(doc.matters.output_path, doc.matters.src.language); - string _uuid = "18275d951861c77f78acd05672c9906924c59f18a2e0ba06dad95959693e9bd8"; // TODO sort uuid in doc.matters! + string _uuid = _epub_publication_uuid(doc.matters.src.doc_uid_out); string content = format(q"┃<?xml version="1.0" encoding="utf-8"?> <<epub3_package_version>> <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/"> <dc:identifier id="bookid">urn:uuid:%s</dc:identifier> @@ -2620,6 +2707,7 @@ string epub3_oebps_content(D,P)(D doc, P parts) { <item id="css" href="%s" media-type="text/css"/> <item id="nav" href="toc_nav.xhtml" media-type="application/xhtml+xml" properties="nav" /> ┃", + doc.matters.src.language, _uuid, xhtml_format.special_characters_plain(doc.matters.conf_make_meta.meta.title_main), (doc.matters.conf_make_meta.meta.title_sub.empty) ? "" : format( @@ -2639,14 +2727,13 @@ string epub3_oebps_content(D,P)(D doc, P parts) { (pth_epub3.fn_oebps_css).chompPrefix("OEBPS/"), ); content ~= parts["manifest_documents"]; - // TODO sort jpg & png foreach (image; doc.matters.srcs.image_list) { - content ~= format(q"┃ <item id="%s" href="%s/%s" media-type="image/%s" /> + content ~= format(q"┃ <item id="%s" href="%s/%s" media-type="%s" /> ┃", - image.baseName.stripExtension, + _manifest_id("img", image.baseName.stripExtension), (pth_epub3.doc_oebps_image).chompPrefix("OEBPS/"), image, - image.extension.chompPrefix("."), + _image_media_type(image.extension.chompPrefix(".")), ); } content ~= " " ~ "</manifest>" ~ "\n "; @@ -2753,8 +2840,14 @@ string epub3_oebps_toc_nav_xhtml(D)(D doc) { _new_title_set = true; } else { _new_title_set = false; + /+ ↓ a fragment only where there is one to reach. + A heading of level 4 or less is the top of its own + file, so the file is the target. A heading below + that sits inside a file and carries id="<ocn>", so + that is the fragment. An object with no ocn has no + id, and "#0" was pointing at nothing. +/ string _hashtag = ""; - if ((obj.metainfo.heading_lev_markup <= 4) && (obj.metainfo.ocn == 0)) { + if ((obj.metainfo.heading_lev_markup > 4) && (obj.metainfo.ocn > 0)) { _hashtag = "#" ~ obj.metainfo.ocn.to!string; } string _href = "<a href=\"" @@ -3132,15 +3225,15 @@ if (obj.metainfo.is_a == "heading" ) { _segment_files_seen[obj.tags.segment_anchor_tag_epub] = true; oepbs_content_parts["manifest_documents"] ~= - format(q"┃<item id="%s.xhtml" href="%s.xhtml" media-type="application/xhtml+xml" /> + format(q"┃<item id="%s" href="%s.xhtml" media-type="application/xhtml+xml" /> ┃", - obj.tags.segment_anchor_tag_epub, + _manifest_id("seg", obj.tags.segment_anchor_tag_epub), obj.tags.segment_anchor_tag_epub, ); oepbs_content_parts["spine"] ~= - format(q"┃<itemref idref="%s.xhtml" linear="yes" /> + format(q"┃<itemref idref="%s" linear="yes" /> ┃", - obj.tags.segment_anchor_tag_epub, + _manifest_id("seg", obj.tags.segment_anchor_tag_epub), ); #+END_SRC |
