aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/sisudoc/outputs/io_out/epub3.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/sisudoc/outputs/io_out/epub3.d')
-rw-r--r--src/sisudoc/outputs/io_out/epub3.d68
1 files changed, 56 insertions, 12 deletions
diff --git a/src/sisudoc/outputs/io_out/epub3.d b/src/sisudoc/outputs/io_out/epub3.d
index b00165d..420c536 100644
--- a/src/sisudoc/outputs/io_out/epub3.d
+++ b/src/sisudoc/outputs/io_out/epub3.d
@@ -77,6 +77,45 @@ template outputEPub3() {
.replaceAll(rgx.nbsp_char, " ");
return _txt;
}
+ /+ ↓ 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;
+ }
/+ ↓ the last modified time, as EPUB3 requires it: exactly one dcterms:modified
on the package, in UTC to the second, CCYY-MM-DDThh:mm:ssZ
+/
@@ -105,11 +144,10 @@ template outputEPub3() {
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"?>
- <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">
<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>
@@ -126,6 +164,7 @@ template outputEPub3() {
<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(
@@ -145,14 +184,13 @@ template outputEPub3() {
(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 ";
@@ -251,8 +289,14 @@ template outputEPub3() {
_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=\""
@@ -575,15 +619,15 @@ template outputEPub3() {
) {
_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),
);
}
}