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 '"', '"'}" 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'