lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.14 vs lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.15

- old
+ new

@@ -109,16 +109,39 @@ end # See https://asciidoctor.org/docs/user-manual/#book-parts-and-chapters def get_chapter_name node if node.document.doctype != 'book' - return Asciidoctor::Document === node ? node.attr('docname') : nil + return Asciidoctor::Document === node ? node.attr('docname') || node.id : nil end return (node.id || 'preamble') if node.context == :preamble && node.level == 0 Asciidoctor::Section === node && node.level <= 1 ? node.id : nil end + def get_numbered_title node + doc_attrs = node.document.attributes + level = node.level + if node.caption + title = node.captioned_title + elsif node.respond_to?(:numbered) && node.numbered && level <= (doc_attrs['sectnumlevels'] || 3).to_i + if level < 2 && node.document.doctype == 'book' + if node.sectname == 'chapter' + title = %(#{(signifier = doc_attrs['chapter-signifier']) ? "#{signifier} " : ''}#{node.sectnum} #{node.title}) + elsif node.sectname == 'part' + title = %(#{(signifier = doc_attrs['part-signifier']) ? "#{signifier} " : ''}#{node.sectnum nil, ':'} #{node.title}) + else + title = %(#{node.sectnum} #{node.title}) + end + else + title = %(#{node.sectnum} #{node.title}) + end + else + title = node.title + end + title + end + def convert_document node @format = node.attr('ebook-format').to_sym @validate = node.attr? 'ebook-validate' @extract = node.attr? 'ebook-extract' @@ -180,10 +203,22 @@ (node.attr 'keywords', '').split(CsvDelimiterRx).each do |s| @book.metadata.add_metadata 'subject', s end + if node.attr? 'series-name' + series_name = node.attr 'series-name' + series_volume = node.attr 'series-volume', 1 + series_id = node.attr 'series-id' + + series_meta = @book.metadata.add_metadata 'meta', series_name, id: 'pub-collection', group_position: series_volume + series_meta['property'] = 'belongs-to-collection' + series_meta.refine 'dcterms:identifier', series_id unless series_id.nil? + # Calibre only understands 'series' + series_meta.refine 'collection-type', 'series' + end + add_cover_image node add_front_matter_page node if node.doctype == 'book' toc_items = [] @@ -271,11 +306,11 @@ title = %(#{doctitle.main} ) subtitle = doctitle.subtitle elsif node.title # HACK: until we get proper handling of title-only in CSS title = '' - subtitle = node.title + subtitle = get_numbered_title node else title = nil subtitle = nil end @@ -319,10 +354,13 @@ <div class="chapter-header"> #{byline}<h1 class="chapter-title">#{title}#{subtitle ? %(<small class="subtitle">#{subtitle_formatted}</small>) : ''}</h1> </div> </header>) : '' + # TODO : support writing code highlighter CSS to a separate file + linkcss = false + # 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" xml:lang="#{lang = node.document.attr 'lang', 'en'}" lang="#{lang}"> <head> <meta charset="UTF-8"/> @@ -335,16 +373,21 @@ if (navigator.userAgent.indexOf(' calibre/') >= 0) reader = { name: 'calibre-desktop' }; else if (window.parent == window || !(reader = window.parent.navigator.epubReadingSystem)) return; } document.body.setAttribute('class', reader.name.toLowerCase().replace(/ /g, '-')); }); -]]></script> -</head> +]]></script>)] + + if self.class.supports_highlighter_docinfo? && (syntax_hl = node.document.syntax_highlighter) && (syntax_hl.docinfo? :head) + lines << (syntax_hl.docinfo :head, node, linkcss: linkcss, self_closing_tag_slash: '/') + end + + lines << %(</head> <body> <section class="chapter" title="#{doctitle_sanitized.gsub '"', '&quot;'}" epub:type="chapter" id="#{docid}"> #{header} -#{content})] + #{content}) unless (fns = node.document.footnotes - @footnotes).empty? @footnotes += fns # NOTE kindlegen seems to mangle the <footer> element, so we wrap its content in a div @@ -359,12 +402,15 @@ lines << '</div> </div> </footer>' end - lines << '</section> -</body> + lines << '</section>' + + lines << (syntax_hl.docinfo :footer, node.document, linkcss: linkcss, self_closing_tag_slash: '/') if syntax_hl && (syntax_hl.docinfo? :footer) + + lines << '</body> </html>' chapter_item.add_content postprocess_xhtml lines * LF epub_properties = node.attr 'epub-properties' chapter_item.add_property 'svg' if epub_properties&.include? 'svg' @@ -378,11 +424,11 @@ def convert_section node if add_chapter(node).nil? hlevel = node.level epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : '' div_classes = [%(sect#{node.level}), node.role].compact - title = node.title + title = get_numbered_title node title_sanitized = xml_sanitize title %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}> <h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %( #{content})} </section>) @@ -425,19 +471,20 @@ #{output_content node} </div>) end def convert_paragraph node + id_attr = node.id ? %( id="#{node.id}") : '' role = node.role # stack-head is the alternative to the default, inline-head (where inline means "run-in") head_stop = node.attr 'head-stop', (role && (node.has_role? 'stack-head') ? nil : '.') head = node.title? ? %(<strong class="head">#{title = node.title}#{head_stop && title !~ TrailingPunctRx ? head_stop : ''}</strong> ) : '' if role node.set_option 'hardbreaks' if node.has_role? 'signature' - %(<p class="#{role}">#{head}#{node.content}</p>) + %(<p#{id_attr} class="#{role}">#{head}#{node.content}</p>) else - %(<p>#{head}#{node.content}</p>) + %(<p#{id_attr}>#{head}#{node.content}</p>) end end def convert_pass node content = node.content @@ -496,17 +543,33 @@ 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 + nowrap = (node.option? 'nowrap') || !(node.document.attr? 'prewrap') + if node.style == 'source' + lang = node.attr 'language' + if self.class.supports_highlighter_docinfo? && (syntax_hl = node.document.syntax_highlighter) + opts = syntax_hl.highlight? ? { + css_mode: ((doc_attrs = node.document.attributes)[%(#{syntax_hl.name}-css)] || :class).to_sym, + style: doc_attrs[%(#{syntax_hl.name}-style)], + } : {} + opts[:nowrap] = nowrap + else + pre_open = %(<pre class="highlight#{nowrap ? ' nowrap' : ''}"><code#{lang ? %( class="language-#{lang}" data-lang="#{lang}") : ''}>) + pre_close = '</code></pre>' + end + else + pre_open = %(<pre#{nowrap ? ' class="nowrap"' : ''}>) + pre_close = '</pre>' + syntax_hl = nil + end figure_classes = ['listing'] figure_classes << 'coalesce' if node.option? 'unbreakable' - pre_classes = node.style == 'source' ? ['source', %(language-#{node.attr 'language'})] : ['screen'] - title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption> -) : '' - %(<figure class="#{figure_classes * ' '}"> -#{title_div}<pre class="#{pre_classes * ' '}"><code>#{node.content}</code></pre> + title_div = node.title? ? %(<figcaption>#{get_numbered_title node}</figcaption>) : '' + %(<figure class="#{figure_classes * ' '}">#{title_div} + #{syntax_hl ? (syntax_hl.format node, lang, opts) : pre_open + (node.content || '') + pre_close} </figure>) end # TODO: implement proper stem support. See https://github.com/asciidoctor/asciidoctor-epub3/issues/10 alias convert_stem convert_listing @@ -899,11 +962,11 @@ id_attr = %( id="xref--#{ref_docname}--#{refid}") target = %(#{ref_docname}.xhtml##{refid}) end id_attr = '' unless @xrefs_seen.add? refid - text = (ref.xreftext node.attr('xrefstyle', nil, true)) + text ||= (ref.xreftext node.attr('xrefstyle', nil, true)) else logger.warn %(#{::File.basename doc.attr('docfile')}: invalid reference to unknown anchor: #{refid}) end end @@ -1264,18 +1327,18 @@ lines = [] lines << '<ol>' items.each do |item| #index = (state[:index] = (state.fetch :index, 0) + 1) if (chapter_name = get_chapter_name item).nil? - item_label = sanitize_xml item.title, :pcdata + item_label = sanitize_xml get_numbered_title(item), :pcdata item_href = %(#{state[:content_doc_href]}##{item.id}) else # NOTE we sanitize the chapter titles because we use formatting to control layout if item.context == :document item_label = sanitize_doctitle_xml item, :cdata else - item_label = sanitize_xml item.title, :cdata + item_label = sanitize_xml get_numbered_title(item), :cdata end item_href = (state[:content_doc_href] = %(#{chapter_name}.xhtml)) end lines << %(<li><a href="#{item_href}">#{item_label}</a>) if depth == 0 || (child_sections = item.sections).empty? @@ -1314,17 +1377,17 @@ state[:max_depth] = (state.fetch :max_depth, 0) + 1 items.each do |item| index = (state[:index] = (state.fetch :index, 0) + 1) item_id = %(nav_#{index}) if (chapter_name = get_chapter_name item).nil? - item_label = sanitize_xml item.title, :cdata + item_label = sanitize_xml get_numbered_title(item), :cdata item_href = %(#{state[:content_doc_href]}##{item.id}) else if item.context == :document item_label = sanitize_doctitle_xml item, :cdata else - item_label = sanitize_xml item.title, :cdata + item_label = sanitize_xml get_numbered_title(item), :cdata end item_href = (state[:content_doc_href] = %(#{chapter_name}.xhtml)) end lines << %(<navPoint id="#{item_id}" playOrder="#{index}">) lines << %(<navLabel><text>#{item_label}</text></navLabel>) @@ -1496,10 +1559,18 @@ # Handles asciidoctor 1.5.6 quirk when role can be parent def role_valid_class? role role.is_a? String end + + class << self + def supports_highlighter_docinfo? + # Asciidoctor only got pluggable syntax highlighters since 2.0: + # https://github.com/asciidoctor/asciidoctor/commit/23ddbaed6818025cbe74365fec7e8101f34eadca + Asciidoctor::Document.method_defined? :syntax_highlighter + end + end end class DocumentIdGenerator ReservedIds = %w(cover nav ncx) CharRefRx = /&(?:([a-zA-Z][a-zA-Z]+\d{0,2})|#(\d\d\d{0,4})|#x([\da-fA-F][\da-fA-F][\da-fA-F]{0,3}));/ @@ -1559,16 +1630,19 @@ end Extensions.register do if (document = @document).backend == 'epub3' document.set_attribute 'listing-caption', 'Listing' - # pygments.rb hangs on JRuby for Windows, see https://github.com/asciidoctor/asciidoctor-epub3/issues/253 - if !(::RUBY_ENGINE == 'jruby' && Gem.win_platform?) && (Gem.try_activate 'pygments.rb') - if document.set_attribute 'source-highlighter', 'pygments' - document.set_attribute 'pygments-css', 'style' - document.set_attribute 'pygments-style', 'bw' - end + + # TODO: bw theme for CodeRay + document.set_attribute 'pygments-style', 'bw' unless document.attr? 'pygments-style' + document.set_attribute 'rouge-style', 'bw' unless document.attr? 'rouge-style' + unless Converter.supports_highlighter_docinfo? + document.set_attribute 'coderay-css', 'style' + document.set_attribute 'pygments-css', 'style' + document.set_attribute 'rouge-css', 'style' end + case (ebook_format = document.attributes['ebook-format']) when 'epub3', 'kf8' # all good when 'mobi' ebook_format = document.attributes['ebook-format'] = 'kf8'