lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.18 vs lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.19

- old
+ new

@@ -37,13 +37,13 @@ logger.debug %(Extracted #{@format.upcase} to #{extract_dir}) end if @format == :kf8 # QUESTION shouldn't we validate this epub file too? - distill_epub_to_mobi epub_file, target, @compress, @kindlegen_path + distill_epub_to_mobi epub_file, target, @compress elsif @validate - validate_epub epub_file, @epubcheck_path + validate_epub epub_file end end CsvDelimiterRx = /\s*,\s*/ @@ -103,11 +103,11 @@ def convert node, name = nil, _opts = {} method_name = %(convert_#{name ||= node.node_name}) if respond_to? method_name send method_name, node else - logger.warn %(#{::File.basename node.attr('docfile')}: conversion missing in backend #{@backend} for #{name}) + logger.warn %(conversion missing in backend #{@backend} for #{name}) nil end end # See https://asciidoctor.org/docs/user-manual/#book-parts-and-chapters @@ -141,21 +141,24 @@ title = node.title end title end + def icon_names + @icon_names ||= [] + end + def convert_document node @format = node.attr('ebook-format').to_sym @validate = node.attr? 'ebook-validate' @extract = node.attr? 'ebook-extract' @compress = node.attr 'ebook-compress' @kindlegen_path = node.attr 'ebook-kindlegen-path' @epubcheck_path = node.attr 'ebook-epubcheck-path' @xrefs_seen = ::Set.new - @icon_names = [] - @media_files = [] + @media_files = {} @footnotes = [] @book = GEPUB::Book.new 'EPUB/package.opf' @book.epub_backward_compat = @format != :kf8 @book.language node.attr('lang', 'en'), id: 'pub-language' @@ -265,18 +268,18 @@ @book.add_item 'toc.ncx', content: toc_ncx.to_ios, id: 'ncx' docimagesdir = (node.attr 'imagesdir', '.').chomp '/' docimagesdir = (docimagesdir == '.' ? nil : %(#{docimagesdir}/)) - @media_files.each do |file| - if file[:name].start_with? %(#{docimagesdir}jacket/cover.) - logger.warn %(path is reserved for cover artwork: #{file[:name]}; skipping file found in content) + @media_files.each do |name, file| + if name.start_with? %(#{docimagesdir}jacket/cover.) + logger.warn %(path is reserved for cover artwork: #{name}; skipping file found in content) elsif file[:path].nil? || File.readable?(file[:path]) - mime_types = MIME::Types.type_for file[:name] + mime_types = MIME::Types.type_for name mime_types.delete_if {|x| x.media_type != file[:media_type] } preferred_mime_type = mime_types.empty? ? nil : mime_types[0].content_type - @book.add_item file[:name], content: file[:path], media_type: preferred_mime_type + @book.add_item name, content: file[:path], media_type: preferred_mime_type else logger.error %(#{File.basename node.attr('docfile')}: media file not found or not readable: #{file[:path]}) end end @@ -326,20 +329,20 @@ return nil if docid.nil? chapter_item = @book.add_ordered_item %(#{docid}.xhtml) doctitle = node.document.doctitle partition: true, use_fallback: true - doctitle_sanitized = sanitize_xml doctitle.combined, :cdata + chapter_title = doctitle.combined if node.context == :document && doctitle.subtitle? title = %(#{doctitle.main} ) subtitle = doctitle.subtitle elsif node.title # HACK: until we get proper handling of title-only in CSS title = '' subtitle = get_numbered_title node - doctitle_sanitized = sanitize_xml subtitle, :cdata + chapter_title = subtitle else title = nil subtitle = nil end @@ -358,14 +361,14 @@ @xrefs_seen.clear content = node.content # NOTE must run after content is resolved # TODO perhaps create dynamic CSS file? - if @icon_names.empty? + if icon_names.empty? icon_css_head = '' else - icon_defs = @icon_names.map {|name| + icon_defs = icon_names.map {|name| %(.i-#{name}::before { content: "#{FontIconMap.unicode name}"; }) } * LF icon_css_head = %(<style> #{icon_defs} </style> @@ -385,11 +388,11 @@ # NOTE kindlegen seems to mangle the <header> element, so we wrap its content in a div lines = [%(<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:mml="http://www.w3.org/1998/Math/MathML" xml:lang="#{lang = node.document.attr 'lang', 'en'}" lang="#{lang}"> <head> <meta charset="UTF-8"/> -<title>#{doctitle_sanitized}</title> +<title>#{chapter_title}</title> <link rel="stylesheet" type="text/css" href="styles/epub3.css"/> <link rel="stylesheet" type="text/css" href="styles/epub3-css3-only.css" media="(min-device-width: 0px)"/> #{icon_css_head}<script type="text/javascript"><![CDATA[ document.addEventListener('DOMContentLoaded', function(event, reader) { if (!(reader = navigator.epubReadingSystem)) { @@ -399,15 +402,17 @@ document.body.setAttribute('class', reader.name.toLowerCase().replace(/ /g, '-')); }); ]]></script>)] syntax_hl = node.document.syntax_highlighter + epub_type_attr = node.respond_to?(:section) && node.sectname != 'section' ? %( epub:type="#{node.sectname}") : '' + lines << (syntax_hl.docinfo :head, node, linkcss: linkcss, self_closing_tag_slash: '/') if syntax_hl&.docinfo? :head lines << %(</head> <body> -<section class="chapter" title="#{doctitle_sanitized}" epub:type="chapter" id="#{docid}"> +<section class="chapter" title=#{chapter_title.encode xml: :attr}#{epub_type_attr} id="#{docid}"> #{header} #{content}) unless (fns = node.document.footnotes - @footnotes).empty? @footnotes += fns @@ -444,15 +449,14 @@ end def convert_section node if add_chapter(node).nil? hlevel = node.level - epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : '' + epub_type_attr = node.sectname != 'section' ? %( epub:type="#{node.sectname}") : '' div_classes = [%(sect#{node.level}), node.role].compact title = get_numbered_title node - title_sanitized = xml_sanitize title - %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}> + %(<section class="#{div_classes * ' '}" title=#{title.encode xml: :attr}#{epub_type_attr}> <h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %( #{content})} </section>) end end @@ -531,18 +535,16 @@ end type = node.attr 'name' epub_type = case type when 'tip' - 'help' - when 'note' - 'note' - when 'important', 'warning', 'caution' - 'warning' + 'tip' + when 'important', 'warning', 'caution', 'note' + 'notice' else logger.warn %(unknown admonition type: #{type}) - 'note' + 'notice' end %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}"> #{title_el}<div class="content"> #{output_content node} </div> @@ -565,10 +567,11 @@ id_attribute = node.id ? %( id="#{node.id}") : '' %(<#{tag_name}#{id_attribute} class="#{['discrete', node.role].compact * ' '}">#{node.title}</#{tag_name}>) end def convert_listing node + id_attribute = node.id ? %( id="#{node.id}") : '' nowrap = (node.option? 'nowrap') || !(node.document.attr? 'prewrap') if node.style == 'source' lang = node.attr 'language' syntax_hl = node.document.syntax_highlighter if syntax_hl @@ -587,11 +590,11 @@ syntax_hl = nil end figure_classes = ['listing'] figure_classes << 'coalesce' if node.option? 'unbreakable' title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>) : '' - %(<figure class="#{figure_classes * ' '}">#{title_div} + %(<figure#{id_attribute} class="#{figure_classes * ' '}">#{title_div} #{syntax_hl ? (syntax_hl.format node, lang, opts) : pre_open + (node.content || '') + pre_close} </figure>) end def convert_stem node @@ -615,13 +618,17 @@ def load_asciimath Helpers.require_library('asciimath', true, :warn).nil? ? :unavailable : :loaded end - # QUESTION should we wrap the <pre> in either <div> or <figure>? def convert_literal node - %(<pre class="screen">#{node.content}</pre>) + id_attribute = node.id ? %( id="#{node.id}") : '' + title_element = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>) : '' + %(<figure#{id_attribute} class="literalblock#{prepend_space node.role}"> +#{title_element} +<div class="content"><pre class="screen">#{node.content}</pre></div> +</figure>) end def convert_page_break _node '<hr epub:type="pagebreak" class="pagebreak"/>' end @@ -710,14 +717,12 @@ table_classes << role end table_styles = [] if (autowidth = node.option? 'autowidth') && !(node.attr? 'width') table_classes << 'fit-content' - elsif (tablewidth = node.attr 'tablepcwidth') == 100 - table_classes << 'stretch' else - table_styles << %(width: #{tablewidth}%;) + table_styles << %(width: #{node.attr 'tablepcwidth'}%;) end table_class_attr = %( class="#{table_classes * ' '}") table_style_attr = !table_styles.empty? ? %( style="#{table_styles * '; '}") : '' lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>) @@ -964,10 +969,12 @@ chapter.set_attr 'epub-properties', [] unless chapter.attr? 'epub-properties' epub_properties = chapter.attr 'epub-properties' epub_properties << 'svg' unless epub_properties.include? 'svg' end + return if target.start_with? 'data:' + if Asciidoctor::Helpers.uriish? target # We need to add both local and remote media files to manifest fs_path = nil else out_dir = node.attr('outdir', nil, true) || doc_option(node.document, :to_dir) @@ -976,11 +983,11 @@ base_dir = root_document(node.document).base_dir fs_path = ::File.join base_dir, target end end # We need *both* virtual and physical image paths. Unfortunately, references[:images] only has one of them. - @media_files << { name: target, path: fs_path, media_type: media_type } + @media_files[target] ||= { path: fs_path, media_type: media_type } end def resolve_image_attrs node img_attrs = [] img_attrs << %(alt="#{node.attr 'alt'}") if node.attr? 'alt' @@ -1163,11 +1170,11 @@ end end def convert_inline_image node if node.type == 'icon' - @icon_names << (icon_name = node.target) + icon_names << (icon_name = node.target) i_classes = ['icon', %(i-#{icon_name})] i_classes << %(icon-#{node.attr 'size'}) if node.attr? 'size' i_classes << %(icon-flip-#{(node.attr 'flip')[0]}) if node.attr? 'flip' i_classes << %(icon-rotate-#{node.attr 'rotate'}) if node.attr? 'rotate' i_classes << node.role if node.role? @@ -1624,37 +1631,30 @@ .gsub(/<img([^>]+) style="width: (\d\d)%;"/, '<img\1 style="width: \2%; height: \2%;"') .gsub(/<script type="text\/javascript">.*?<\/script>\n?/m, '') .to_ios end - def get_kindlegen_command kindlegen_path - unless kindlegen_path.nil? - logger.debug %(Using ebook-kindlegen-path attribute: #{kindlegen_path}) - return [kindlegen_path] + def get_kindlegen_command + unless @kindlegen_path.nil? + logger.debug %(Using ebook-kindlegen-path attribute: #{@kindlegen_path}) + return [@kindlegen_path] end unless (result = ENV['KINDLEGEN']).nil? logger.debug %(Using KINDLEGEN env variable: #{result}) return [result] end - begin - require 'kindlegen' unless defined? ::Kindlegen - result = ::Kindlegen.command.to_s - logger.debug %(Using KindleGen from gem: #{result}) - [result] - rescue LoadError => e - logger.debug %(#{e}; Using KindleGen from PATH) - [%(kindlegen#{::Gem.win_platform? ? '.exe' : ''})] - end + logger.debug 'Using KindleGen from PATH' + [%(kindlegen#{::Gem.win_platform? ? '.exe' : ''})] end - def distill_epub_to_mobi epub_file, target, compress, kindlegen_path + def distill_epub_to_mobi epub_file, target, compress mobi_file = ::File.basename target.sub(EpubExtensionRx, '.mobi') compress_flag = KindlegenCompression[compress ? (compress.empty? ? '1' : compress.to_s) : '0'] - argv = get_kindlegen_command(kindlegen_path) + ['-dont_append_source', compress_flag, '-o', mobi_file, epub_file].compact + argv = get_kindlegen_command + ['-dont_append_source', compress_flag, '-o', mobi_file, epub_file].compact begin # This duplicates Kindlegen.run, but we want to override executable out, err, res = Open3.capture3(*argv) do |r| r.force_encoding 'UTF-8' if ::Gem.win_platform? && r.respond_to?(:force_encoding) end @@ -1675,14 +1675,14 @@ else logger.error %(KindleGen failed to write MOBI to #{output_file}) end end - def get_epubcheck_command epubcheck_path - unless epubcheck_path.nil? - logger.debug %(Using ebook-epubcheck-path attribute: #{epubcheck_path}) - return [epubcheck_path] + def get_epubcheck_command + unless @epubcheck_path.nil? + logger.debug %(Using ebook-epubcheck-path attribute: #{@epubcheck_path}) + return [@epubcheck_path] end unless (result = ENV['EPUBCHECK']).nil? logger.debug %(Using EPUBCHECK env variable: #{result}) return [result] @@ -1696,11 +1696,11 @@ logger.debug %(#{e}; Using EPUBCheck from PATH) ['epubcheck'] end end - def validate_epub epub_file, epubcheck_path - argv = get_epubcheck_command(epubcheck_path) + ['-w', epub_file] + def validate_epub epub_file + argv = get_epubcheck_command + ['-w', epub_file] begin out, err, res = Open3.capture3(*argv) rescue Errno::ENOENT => e raise 'Unable to run EPUBCheck. Either install epubcheck-ruby gem or place `epubcheck` executable on PATH or set EPUBCHECK environment variable with path to it', cause: e end