aboutsummaryrefslogtreecommitdiffhomepage
path: root/org/out_xmls.org
diff options
context:
space:
mode:
Diffstat (limited to 'org/out_xmls.org')
-rw-r--r--org/out_xmls.org173
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