lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.9 vs lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.10
- old
+ new
@@ -1,646 +1,637 @@
-# encoding: utf-8
+# frozen_string_literal: true
+
require_relative 'spine_item_processor'
require_relative 'font_icon_map'
module Asciidoctor
-module Epub3
+ module Epub3
+ # Public: The main converter for the epub3 backend that handles packaging the
+ # EPUB3 or KF8 publication file.
+ class Converter
+ include ::Asciidoctor::Converter
+ include ::Asciidoctor::Writer
-# Public: The main converter for the epub3 backend that handles packaging the
-# EPUB3 or KF8 publication file.
-class Converter
- include ::Asciidoctor::Converter
- include ::Asciidoctor::Writer
+ register_for 'epub3'
- register_for 'epub3'
+ def initialize backend, opts
+ super
+ basebackend 'html'
+ outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi
+ htmlsyntax 'xml'
+ @validate = false
+ @extract = false
+ end
- def initialize backend, opts
- super
- basebackend 'html'
- outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi
- htmlsyntax 'xml'
- @validate = false
- @extract = false
- end
+ def convert node, name = nil
+ if (name ||= node.node_name) == 'document'
+ @validate = node.attr? 'ebook-validate'
+ @extract = node.attr? 'ebook-extract'
+ @compress = node.attr 'ebook-compress'
+ Packager.new node, (node.references[:spine_items] || [node]), node.attributes['ebook-format'].to_sym
+ # converting an element from the spine document, such as an inline node in the doctitle
+ elsif name.start_with? 'inline_'
+ (@content_converter ||= ::Asciidoctor::Converter::Factory.default.create 'epub3-xhtml5').convert node, name
+ else
+ raise ::ArgumentError, %(Encountered unexpected node in epub3 package converter: #{name})
+ end
+ end
- def convert node, name = nil
- if (name ||= node.node_name) == 'document'
- @validate = node.attr? 'ebook-validate'
- @extract = node.attr? 'ebook-extract'
- @compress = node.attr 'ebook-compress'
- Packager.new node, (node.references[:spine_items] || [node]), node.attributes['ebook-format'].to_sym
- # converting an element from the spine document, such as an inline node in the doctitle
- elsif name.start_with? 'inline_'
- (@content_converter ||= ::Asciidoctor::Converter::Factory.default.create('epub3-xhtml5')).convert node, name
- else
- raise ::ArgumentError, %(Encountered unexpected node in epub3 package converter: #{name})
+ # FIXME: we have to package in write because we don't have access to target before this point
+ def write packager, target
+ packager.package validate: @validate, extract: @extract, compress: @compress, target: target
+ nil
+ end
end
- end
- # FIXME we have to package in write because we don't have access to target before this point
- def write packager, target
- packager.package validate: @validate, extract: @extract, compress: @compress, target: target
- nil
- end
-end
+ # Public: The converter for the epub3 backend that converts the individual
+ # content documents in an EPUB3 publication.
+ class ContentConverter
+ include ::Asciidoctor::Converter
-# Public: The converter for the epub3 backend that converts the individual
-# content documents in an EPUB3 publication.
-class ContentConverter
- include ::Asciidoctor::Converter
+ register_for 'epub3-xhtml5'
- register_for 'epub3-xhtml5'
+ LF = ?\n
+ NoBreakSpace = ' '
+ ThinNoBreakSpace = ' '
+ RightAngleQuote = '›'
+ CalloutStartNum = %(\u2460)
- LF = ?\n
- NoBreakSpace = ' '
- ThinNoBreakSpace = ' '
- RightAngleQuote = '›'
- CalloutStartNum = %(\u2460)
+ CharEntityRx = /&#(\d{2,6});/
+ XmlElementRx = /<\/?.+?>/
+ TrailingPunctRx = /[[:punct:]]$/
- CharEntityRx = /&#(\d{2,6});/
- XmlElementRx = /<\/?.+?>/
- TrailingPunctRx = /[[:punct:]]$/
+ FromHtmlSpecialCharsMap = {
+ '<' => '<',
+ '>' => '>',
+ '&' => '&',
+ }
- FromHtmlSpecialCharsMap = {
- '<' => '<',
- '>' => '>',
- '&' => '&'
- }
+ FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/
- FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/
+ ToHtmlSpecialCharsMap = {
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ }
- ToHtmlSpecialCharsMap = {
- '&' => '&',
- '<' => '<',
- '>' => '>'
- }
+ ToHtmlSpecialCharsRx = /[#{ToHtmlSpecialCharsMap.keys.join}]/
- ToHtmlSpecialCharsRx = /[#{ToHtmlSpecialCharsMap.keys.join}]/
+ OpenParagraphTagRx = /^<p>/
+ CloseParagraphTagRx = /<\/p>$/
- OpenParagraphTagRx = /^<p>/
- CloseParagraphTagRx = /<\/p>$/
+ def initialize backend, opts
+ super
+ basebackend 'html'
+ outfilesuffix '.xhtml'
+ htmlsyntax 'xml'
+ @xrefs_seen = ::Set.new
+ @icon_names = []
+ end
- def initialize backend, opts
- super
- basebackend 'html'
- outfilesuffix '.xhtml'
- htmlsyntax 'xml'
- @xrefs_seen = ::Set.new
- @icon_names = []
- end
+ def convert node, name = nil
+ if respond_to? name ||= node.node_name
+ send name, node
+ else
+ warn %(asciidoctor: WARNING: conversion missing in epub3 backend for #{name})
+ end
+ end
- def convert node, name = nil
- if respond_to?(name ||= node.node_name)
- send name, node
- else
- warn %(asciidoctor: WARNING: conversion missing in epub3 backend for #{name})
- end
- end
+ def document node
+ docid = node.id
+ pubtype = node.attr 'publication-type', 'book'
- def document node
- docid = node.id
- pubtype = node.attr 'publication-type', 'book'
+ if (doctitle = node.doctitle partition: true, use_fallback: true).subtitle?
+ title = %(#{doctitle.main} )
+ subtitle = doctitle.subtitle
+ else
+ # HACK: until we get proper handling of title-only in CSS
+ title = ''
+ subtitle = doctitle.combined
+ end
- if (doctitle = node.doctitle partition: true, use_fallback: true).subtitle?
- title = %(#{doctitle.main} )
- subtitle = doctitle.subtitle
- else
- # HACK until we get proper handling of title-only in CSS
- title = ''
- subtitle = doctitle.combined
- end
+ doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).to_s
+ subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' '
- doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).to_s
- subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' '
+ if pubtype == 'book'
+ byline = ''
+ else
+ author = node.attr 'author'
+ username = node.attr 'username', 'default'
+ imagesdir = (node.references[:spine].attr 'imagesdir', '.').chomp '/'
+ imagesdir = imagesdir == '.' ? '' : %(#{imagesdir}/)
+ byline = %(<p class="byline"><img src="#{imagesdir}avatars/#{username}.jpg"/> <b class="author">#{author}</b></p>#{LF})
+ end
- if pubtype == 'book'
- byline = ''
- else
- author = node.attr 'author'
- username = node.attr 'username', 'default'
- imagesdir = (node.references[:spine].attr 'imagesdir', '.').chomp '/'
- imagesdir = imagesdir == '.' ? '' : %(#{imagesdir}/)
- byline = %(<p class="byline"><img src="#{imagesdir}avatars/#{username}.jpg"/> <b class="author">#{author}</b></p>#{LF})
- end
+ mark_last_paragraph node unless pubtype == 'book'
+ content = node.content
- mark_last_paragraph node unless pubtype == 'book'
- content = node.content
-
- # NOTE must run after content is resolved
- # TODO perhaps create dynamic CSS file?
- if @icon_names.empty?
- icon_css_head = icon_css_scoped = ''
- else
- icon_defs = @icon_names.map {|name|
- %(.i-#{name}::before { content: "#{FontIconMap[name.tr('-', '_').to_sym]}"; })
- } * LF
- icon_css_head = %(<style>
+ # NOTE must run after content is resolved
+ # TODO perhaps create dynamic CSS file?
+ if @icon_names.empty?
+ icon_css_head = icon_css_scoped = ''
+ else
+ icon_defs = @icon_names.map {|name|
+ %(.i-#{name}::before { content: "#{FontIconMap[name.tr('-', '_').to_sym]}"; })
+ } * LF
+ icon_css_head = %(<style>
#{icon_defs}
</style>
)
- # NOTE Namo Pubtree requires icon CSS to be repeated inside <body> (or in a linked stylesheet); wrap in div to hide from Aldiko
- icon_css_scoped = (node.attr? 'ebook-format', 'kf8') ? '' : %(<div style="display: none" aria-hidden="true"><style scoped="scoped">
+ # NOTE Namo Pubtree requires icon CSS to be repeated inside <body> (or in a linked stylesheet); wrap in div to hide from Aldiko
+ icon_css_scoped = (node.attr? 'ebook-format', 'kf8') ? '' : %(<div style="display: none" aria-hidden="true"><style scoped="scoped">
#{icon_defs}
</style></div>
)
- end
+ end
- # 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.attr 'lang', 'en')}" lang="#{lang}">
+ # 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.attr 'lang', 'en'}" lang="#{lang}">
<head>
<meta charset="UTF-8"/>
<title>#{doctitle_sanitized}</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">
+#{icon_css_head}<script type="text/javascript"><![CDATA[
document.addEventListener('DOMContentLoaded', function(event, reader) {
if (!(reader = navigator.epubReadingSystem)) {
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>
+]]></script>
</head>
<body>
<section class="chapter" title="#{doctitle_sanitized.gsub '"', '"'}" epub:type="chapter" id="#{docid}">
#{icon_css_scoped}<header>
<div class="chapter-header">
-#{byline}<h1 class="chapter-title">#{title}#{subtitle ? %[<small class="subtitle">#{subtitle_formatted}</small>] : ''}</h1>
+#{byline}<h1 class="chapter-title">#{title}#{subtitle ? %(<small class="subtitle">#{subtitle_formatted}</small>) : ''}</h1>
</div>
</header>
#{content})]
- if node.footnotes?
- # NOTE kindlegen seems to mangle the <footer> element, so we wrap its content in a div
- lines << '<footer>
+ if node.footnotes?
+ # NOTE kindlegen seems to mangle the <footer> element, so we wrap its content in a div
+ lines << '<footer>
<div class="chapter-footer">
<div class="footnotes">'
- node.footnotes.each do |footnote|
- lines << %(<aside id="note-#{footnote.index}" epub:type="footnote">
+ node.footnotes.each do |footnote|
+ lines << %(<aside id="note-#{footnote.index}" epub:type="footnote">
<p><sup class="noteref"><a href="#noteref-#{footnote.index}">#{footnote.index}</a></sup> #{footnote.text}</p>
</aside>)
- end
- lines << '</div>
+ end
+ lines << '</div>
</div>
</footer>'
- end
+ end
- lines << '</section>
+ lines << '</section>
</body>
</html>'
- lines * LF
- end
+ lines * LF
+ end
- # NOTE embedded is used for AsciiDoc table cell content
- def embedded node
- node.content
- end
+ # NOTE embedded is used for AsciiDoc table cell content
+ def embedded node
+ node.content
+ end
- def section node
- hlevel = node.level + 1
- epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : ''
- div_classes = [%(sect#{node.level}), node.role].compact
- title = node.title
- title_sanitized = xml_sanitize title
- if node.document.header? || node.level != 1 || node != node.document.first_section
- %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}>
-<h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %[
-#{content}]}
+ def section node
+ hlevel = node.level + 1
+ epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : ''
+ div_classes = [%(sect#{node.level}), node.role].compact
+ title = node.title
+ title_sanitized = xml_sanitize title
+ if node.document.header? || node.level != 1 || node != node.document.first_section
+ %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}>
+<h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %(
+ #{content})}
</section>)
- else
- # document has no level-0 heading and this heading serves as the document title
- node.content
- end
- end
+ else
+ # document has no level-0 heading and this heading serves as the document title
+ node.content
+ end
+ end
- # TODO support use of quote block as abstract
- def preamble node
- if (first_block = node.blocks[0]) && first_block.style == 'abstract'
- abstract first_block
- # REVIEW should we treat the preamble as an abstract in general?
- elsif first_block && node.blocks.size == 1
- abstract first_block
- else
- node.content
- end
- end
+ # TODO: support use of quote block as abstract
+ def preamble node
+ if (first_block = node.blocks[0]) && first_block.style == 'abstract'
+ abstract first_block
+ # REVIEW: should we treat the preamble as an abstract in general?
+ elsif first_block && node.blocks.size == 1
+ abstract first_block
+ else
+ node.content
+ end
+ end
- def open node
- id_attr = node.id ? %( id="#{node.id}") : nil
- class_attr = node.role ? %( class="#{node.role}") : nil
- if id_attr || class_attr
- %(<div#{id_attr}#{class_attr}>
+ def open node
+ id_attr = node.id ? %( id="#{node.id}") : nil
+ class_attr = node.role ? %( class="#{node.role}") : nil
+ if id_attr || class_attr
+ %(<div#{id_attr}#{class_attr}>
#{convert_content node}
</div>)
- else
- convert_content node
- end
- end
+ else
+ convert_content node
+ end
+ end
- def abstract node
- %(<div class="abstract" epub:type="preamble">
+ def abstract node
+ %(<div class="abstract" epub:type="preamble">
#{convert_content node}
</div>)
- end
+ end
- def paragraph node
- 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>)
- else
- %(<p>#{head}#{node.content}</p>)
- end
- end
+ def paragraph node
+ 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>)
+ else
+ %(<p>#{head}#{node.content}</p>)
+ end
+ end
- def pass node
- content = node.content
- if content == '<?hard-pagebreak?>'
- '<hr epub:type="pagebreak" class="pagebreak"/>'
- else
- content
- end
- end
+ def pass node
+ content = node.content
+ if content == '<?hard-pagebreak?>'
+ '<hr epub:type="pagebreak" class="pagebreak"/>'
+ else
+ content
+ end
+ end
- def admonition node
- id_attr = node.id ? %( id="#{node.id}") : ''
- if node.title?
- title = node.title
- title_sanitized = xml_sanitize title
- title_attr = %( title="#{node.caption}: #{title_sanitized}")
- title_el = %(<h2>#{title}</h2>
+ def admonition node
+ id_attr = node.id ? %( id="#{node.id}") : ''
+ if node.title?
+ title = node.title
+ title_sanitized = xml_sanitize title
+ title_attr = %( title="#{node.caption}: #{title_sanitized}")
+ title_el = %(<h2>#{title}</h2>
)
- else
- title_attr = %( title="#{node.caption}")
- title_el = ''
- end
+ else
+ title_attr = %( title="#{node.caption}")
+ title_el = ''
+ end
- type = node.attr 'name'
- epub_type = case type
- when 'tip'
- 'help'
- when 'note'
- 'note'
- when 'important', 'warning', 'caution'
- 'warning'
- end
- %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}">
+ type = node.attr 'name'
+ epub_type = case type
+ when 'tip'
+ 'help'
+ when 'note'
+ 'note'
+ when 'important', 'warning', 'caution'
+ 'warning'
+ end
+ %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}">
#{title_el}<div class="content">
#{convert_content node}
</div>
</aside>)
- end
+ end
- def example node
- id_attr = node.id ? %( id="#{node.id}") : ''
- title_div = node.title? ? %(<div class="example-title">#{node.title}</div>
+ def example node
+ id_attr = node.id ? %( id="#{node.id}") : ''
+ title_div = node.title? ? %(<div class="example-title">#{node.title}</div>
) : ''
- %(<div#{id_attr} class="example">
+ %(<div#{id_attr} class="example">
#{title_div}<div class="example-content">
#{convert_content node}
</div>
</div>)
- end
+ end
- def floating_title node
- tag_name = %(h#{node.level + 1})
- id_attribute = node.id ? %( id="#{node.id}") : ''
- %(<#{tag_name}#{id_attribute} class="#{['discrete', node.role].compact * ' '}">#{node.title}</#{tag_name}>)
- end
+ def floating_title node
+ tag_name = %(h#{node.level + 1})
+ id_attribute = node.id ? %( id="#{node.id}") : ''
+ %(<#{tag_name}#{id_attribute} class="#{['discrete', node.role].compact * ' '}">#{node.title}</#{tag_name}>)
+ end
- def listing node
- 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>
+ def listing node
+ 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>
) : ''
- # patches conums to fix extra or missing leading space
- # TODO remove patch once upgrading to Asciidoctor 1.5.6
- %(<figure class="#{figure_classes * ' '}">
+ # patches conums to fix extra or missing leading space
+ # TODO remove patch once upgrading to Asciidoctor 1.5.6
+ %(<figure class="#{figure_classes * ' '}">
#{title_div}<pre class="#{pre_classes * ' '}"><code>#{(node.content || '').gsub(/(?<! )<i class="conum"| +<i class="conum"/, ' <i class="conum"')}</code></pre>
</figure>)
- end
+ end
- # QUESTION should we wrap the <pre> in either <div> or <figure>?
- def literal node
- %(<pre class="screen">#{node.content}</pre>)
- end
+ # QUESTION should we wrap the <pre> in either <div> or <figure>?
+ def literal node
+ %(<pre class="screen">#{node.content}</pre>)
+ end
- def page_break node
- '<hr epub:type="pagebreak" class="pagebreak"/>'
- end
+ def page_break _node
+ '<hr epub:type="pagebreak" class="pagebreak"/>'
+ end
- def thematic_break node
- '<hr class="thematicbreak"/>'
- end
+ def thematic_break _node
+ '<hr class="thematicbreak"/>'
+ end
- def quote node
- id_attr = %( id="#{node.id}") if node.id
- class_attr = (role = node.role) ? %( class="blockquote #{role}") : ' class="blockquote"'
+ def quote node
+ id_attr = %( id="#{node.id}") if node.id
+ class_attr = (role = node.role) ? %( class="blockquote #{role}") : ' class="blockquote"'
- footer_content = []
- if (attribution = node.attr 'attribution')
- footer_content << attribution
- end
+ footer_content = []
+ if (attribution = node.attr 'attribution')
+ footer_content << attribution
+ end
- if (citetitle = node.attr 'citetitle')
- citetitle_sanitized = xml_sanitize citetitle
- footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
- end
+ if (citetitle = node.attr 'citetitle')
+ citetitle_sanitized = xml_sanitize citetitle
+ footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
+ end
- if node.title?
- footer_content << %(<span class="context">#{node.title}</span>)
- end
+ footer_content << %(<span class="context">#{node.title}</span>) if node.title?
- footer_tag = footer_content.empty? ? '' : %(
+ footer_tag = footer_content.empty? ? '' : %(
<footer>~ #{footer_content * ' '}</footer>)
- content = (convert_content node).strip.
- sub(OpenParagraphTagRx, '<p><span class="open-quote">“</span>').
- sub(CloseParagraphTagRx, '<span class="close-quote">”</span></p>')
- %(<div#{id_attr}#{class_attr}>
+ content = ((convert_content node).strip.sub OpenParagraphTagRx, '<p><span class="open-quote">“</span>').sub CloseParagraphTagRx, '<span class="close-quote">”</span></p>'
+ %(<div#{id_attr}#{class_attr}>
<blockquote>
#{content}#{footer_tag}
</blockquote>
</div>)
- end
+ end
- def verse node
- id_attr = %( id="#{node.id}") if node.id
- class_attr = (role = node.role) ? %( class="verse #{role}") : ' class="verse"'
+ def verse node
+ id_attr = %( id="#{node.id}") if node.id
+ class_attr = (role = node.role) ? %( class="verse #{role}") : ' class="verse"'
- footer_content = []
- if (attribution = node.attr 'attribution')
- footer_content << attribution
- end
+ footer_content = []
+ if (attribution = node.attr 'attribution')
+ footer_content << attribution
+ end
- if (citetitle = node.attr 'citetitle')
- citetitle_sanitized = xml_sanitize citetitle
- footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
- end
+ if (citetitle = node.attr 'citetitle')
+ citetitle_sanitized = xml_sanitize citetitle
+ footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
+ end
- footer_tag = footer_content.size > 0 ? %(
+ footer_tag = !footer_content.empty? ? %(
<span class="attribution">~ #{footer_content * ', '}</span>) : ''
- %(<div#{id_attr}#{class_attr}>
+ %(<div#{id_attr}#{class_attr}>
<pre>#{node.content}#{footer_tag}</pre>
</div>)
- end
+ end
- def sidebar node
- classes = ['sidebar']
- if node.title?
- classes << 'titled'
- title = node.title
- title_sanitized = xml_sanitize title
- title_attr = %( title="#{title_sanitized}")
- title_el = %(<h2>#{title}</h2>
+ def sidebar node
+ classes = ['sidebar']
+ if node.title?
+ classes << 'titled'
+ title = node.title
+ title_sanitized = xml_sanitize title
+ title_attr = %( title="#{title_sanitized}")
+ title_el = %(<h2>#{title}</h2>
)
- else
- title_attr = title_el = ''
- end
+ else
+ title_attr = title_el = ''
+ end
- %(<aside class="#{classes * ' '}"#{title_attr} epub:type="sidebar">
+ %(<aside class="#{classes * ' '}"#{title_attr} epub:type="sidebar">
#{title_el}<div class="content">
#{convert_content node}
</div>
</aside>)
- end
+ end
- def table node
- lines = [%(<div class="table">)]
- lines << %(<div class="content">)
- table_id_attr = node.id ? %( id="#{node.id}") : ''
- frame_class = {
- 'all' => 'table-framed',
- 'topbot' => 'table-framed-topbot',
- 'sides' => 'table-framed-sides',
- 'none' => ''
- }
- grid_class = {
- 'all' => 'table-grid',
- 'rows' => 'table-grid-rows',
- 'cols' => 'table-grid-cols',
- 'none' => ''
- }
- table_classes = %W(table #{frame_class[node.attr 'frame'] || frame_class['topbot']} #{grid_class[node.attr 'grid'] || grid_class['rows']})
- if (role = node.role)
- table_classes << role
- end
- table_class_attr = %( class="#{table_classes * ' '}")
- table_styles = []
- unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
- table_styles << %(width: #{node.attr 'tablepcwidth'}%)
- end
- table_style_attr = table_styles.size > 0 ? %( style="#{table_styles * '; '}") : ''
-
- lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>)
- lines << %(<caption>#{node.captioned_title}</caption>) if node.title?
- if (node.attr 'rowcount') > 0
- lines << '<colgroup>'
- #if node.option? 'autowidth'
- tag = %(<col/>)
- node.columns.size.times do
- lines << tag
+ def table node
+ lines = [%(<div class="table">)]
+ lines << %(<div class="content">)
+ table_id_attr = node.id ? %( id="#{node.id}") : ''
+ frame_class = {
+ 'all' => 'table-framed',
+ 'topbot' => 'table-framed-topbot',
+ 'sides' => 'table-framed-sides',
+ 'none' => '',
+ }
+ grid_class = {
+ 'all' => 'table-grid',
+ 'rows' => 'table-grid-rows',
+ 'cols' => 'table-grid-cols',
+ 'none' => '',
+ }
+ table_classes = %W[table #{frame_class[node.attr 'frame'] || frame_class['topbot']} #{grid_class[node.attr 'grid'] || grid_class['rows']}]
+ if (role = node.role)
+ table_classes << role
end
- #else
- # node.columns.each do |col|
- # lines << %(<col style="width: #{col.attr 'colpcwidth'}%"/>)
- # end
- #end
- lines << '</colgroup>'
- [:head, :foot, :body].select {|tsec| !node.rows[tsec].empty? }.each do |tsec|
- lines << %(<t#{tsec}>)
- node.rows[tsec].each do |row|
- lines << '<tr>'
- row.each do |cell|
- if tsec == :head
- cell_content = cell.text
- else
- case cell.style
- when :asciidoc
- cell_content = %(<div class="embed">#{cell.content}</div>)
- when :verse
- cell_content = %(<div class="verse">#{cell.text}</div>)
- when :literal
- cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
- else
- cell_content = ''
- cell.content.each do |text|
- cell_content = %(#{cell_content}<p>#{text}</p>)
+ table_class_attr = %( class="#{table_classes * ' '}")
+ table_styles = []
+ table_styles << %(width: #{node.attr 'tablepcwidth'}%) unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
+ table_style_attr = !table_styles.empty? ? %( style="#{table_styles * '; '}") : ''
+
+ lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>)
+ lines << %(<caption>#{node.captioned_title}</caption>) if node.title?
+ if (node.attr 'rowcount') > 0
+ lines << '<colgroup>'
+ #if node.option? 'autowidth'
+ tag = %(<col/>)
+ node.columns.size.times do
+ lines << tag
+ end
+ #else
+ # node.columns.each do |col|
+ # lines << %(<col style="width: #{col.attr 'colpcwidth'}%"/>)
+ # end
+ #end
+ lines << '</colgroup>'
+ [:head, :foot, :body].reject {|tsec| node.rows[tsec].empty? }.each do |tsec|
+ lines << %(<t#{tsec}>)
+ node.rows[tsec].each do |row|
+ lines << '<tr>'
+ row.each do |cell|
+ if tsec == :head
+ cell_content = cell.text
+ else
+ case cell.style
+ when :asciidoc
+ cell_content = %(<div class="embed">#{cell.content}</div>)
+ when :verse
+ cell_content = %(<div class="verse">#{cell.text}</div>)
+ when :literal
+ cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
+ else
+ cell_content = ''
+ cell.content.each do |text|
+ cell_content = %(#{cell_content}<p>#{text}</p>)
+ end
+ end
end
+
+ cell_tag_name = tsec == :head || cell.style == :header ? 'th' : 'td'
+ cell_classes = []
+ if (halign = cell.attr 'halign') && halign != 'left'
+ cell_classes << 'halign-left'
+ end
+ if (halign = cell.attr 'valign') && halign != 'top'
+ cell_classes << 'valign-top'
+ end
+ cell_class_attr = !cell_classes.empty? ? %( class="#{cell_classes * ' '}") : ''
+ cell_colspan_attr = cell.colspan ? %( colspan="#{cell.colspan}") : ''
+ cell_rowspan_attr = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
+ cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}") : ''
+ lines << %(<#{cell_tag_name}#{cell_class_attr}#{cell_colspan_attr}#{cell_rowspan_attr}#{cell_style_attr}>#{cell_content}</#{cell_tag_name}>)
end
+ lines << '</tr>'
end
-
- cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
- cell_classes = []
- if (halign = cell.attr 'halign') && halign != 'left'
- cell_classes << 'halign-left'
- end
- if (halign = cell.attr 'valign') && halign != 'top'
- cell_classes << 'valign-top'
- end
- cell_class_attr = cell_classes.size > 0 ? %( class="#{cell_classes * ' '}") : ''
- cell_colspan_attr = cell.colspan ? %( colspan="#{cell.colspan}") : ''
- cell_rowspan_attr = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
- cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}") : ''
- lines << %(<#{cell_tag_name}#{cell_class_attr}#{cell_colspan_attr}#{cell_rowspan_attr}#{cell_style_attr}>#{cell_content}</#{cell_tag_name}>)
+ lines << %(</t#{tsec}>)
end
- lines << '</tr>'
end
- lines << %(</t#{tsec}>)
- end
- end
- lines << '</table>
+ lines << '</table>
</div>
</div>'
- lines * LF
- end
+ lines * LF
+ end
- def colist node
- lines = ['<div class="callout-list">
+ def colist node
+ lines = ['<div class="callout-list">
<ol>']
- num = CalloutStartNum
- node.items.each_with_index do |item, i|
- lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>)
- num = num.next
- end
- lines << '</ol>
+ num = CalloutStartNum
+ node.items.each_with_index do |item, i|
+ lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>)
+ num = num.next
+ end
+ lines << '</ol>
</div>'
- end
+ end
- # TODO add complex class if list has nested blocks
- def dlist node
- lines = []
- case (style = node.style)
- when 'itemized', 'ordered'
- list_tag_name = (style == 'itemized' ? 'ul' : 'ol')
- role = node.role
- subject_stop = node.attr 'subject-stop', (role && (node.has_role? 'stack') ? nil : ':')
- # QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
- div_classes = [%(#{style}-list), role].compact
- list_class_attr = (node.option? 'brief') ? ' class="brief"' : ''
- lines << %(<div class="#{div_classes * ' '}">
+ # TODO: add complex class if list has nested blocks
+ def dlist node
+ lines = []
+ case (style = node.style)
+ when 'itemized', 'ordered'
+ list_tag_name = style == 'itemized' ? 'ul' : 'ol'
+ role = node.role
+ subject_stop = node.attr 'subject-stop', (role && (node.has_role? 'stack') ? nil : ':')
+ # QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
+ div_classes = [%(#{style}-list), role].compact
+ list_class_attr = (node.option? 'brief') ? ' class="brief"' : ''
+ lines << %(<div class="#{div_classes * ' '}">
<#{list_tag_name}#{list_class_attr}#{list_tag_name == 'ol' && (node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
- node.items.each do |subjects, dd|
- # consists of one term (a subject) and supporting content
- subject = [*subjects].first.text
- subject_plain = xml_sanitize subject, :plain
- subject_element = %(<strong class="subject">#{subject}#{subject_stop && subject_plain !~ TrailingPunctRx ? subject_stop : ''}</strong>)
- lines << '<li>'
- if dd
- # NOTE: must wrap remaining text in a span to help webkit justify the text properly
- lines << %(<span class="principal">#{subject_element}#{dd.text? ? %[ <span class="supporting">#{dd.text}</span>] : ''}</span>)
- lines << dd.content if dd.blocks?
- else
- lines << %(<span class="principal">#{subject_element}</span>)
- end
- lines << '</li>'
- end
- lines << %(</#{list_tag_name}>
+ node.items.each do |subjects, dd|
+ # consists of one term (a subject) and supporting content
+ subject = [*subjects].first.text
+ subject_plain = xml_sanitize subject, :plain
+ subject_element = %(<strong class="subject">#{subject}#{subject_stop && subject_plain !~ TrailingPunctRx ? subject_stop : ''}</strong>)
+ lines << '<li>'
+ if dd
+ # NOTE: must wrap remaining text in a span to help webkit justify the text properly
+ lines << %(<span class="principal">#{subject_element}#{dd.text? ? %( <span class="supporting">#{dd.text}</span>) : ''}</span>)
+ lines << dd.content if dd.blocks?
+ else
+ lines << %(<span class="principal">#{subject_element}</span>)
+ end
+ lines << '</li>'
+ end
+ lines << %(</#{list_tag_name}>
</div>)
- else
- lines << '<div class="description-list">
+ else
+ lines << '<div class="description-list">
<dl>'
- node.items.each do |terms, dd|
- [*terms].each do |dt|
- lines << %(<dt>
+ node.items.each do |terms, dd|
+ [*terms].each do |dt|
+ lines << %(<dt>
<span class="term">#{dt.text}</span>
</dt>)
- end
- if dd
- lines << '<dd>'
- if dd.blocks?
- lines << %(<span class="principal">#{dd.text}</span>) if dd.text?
- lines << dd.content
- else
- lines << %(<span class="principal">#{dd.text}</span>)
+ end
+ next unless dd
+ lines << '<dd>'
+ if dd.blocks?
+ lines << %(<span class="principal">#{dd.text}</span>) if dd.text?
+ lines << dd.content
+ else
+ lines << %(<span class="principal">#{dd.text}</span>)
+ end
+ lines << '</dd>'
end
- lines << '</dd>'
+ lines << '</dl>
+</div>'
end
+ lines * LF
end
- lines << '</dl>
-</div>'
- end
- lines * LF
- end
- def olist node
- complex = false
- div_classes = ['ordered-list', node.style, node.role].compact
- ol_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
- ol_class_attr = ol_classes.empty? ? '' : %( class="#{ol_classes * ' '}")
- ol_start_attr = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
- id_attribute = node.id ? %( id="#{node.id}") : ''
- lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
- lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
- lines << %(<ol#{ol_class_attr}#{ol_start_attr}#{(node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
- node.items.each do |item|
- lines << %(<li>
+ def olist node
+ complex = false
+ div_classes = ['ordered-list', node.style, node.role].compact
+ ol_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
+ ol_class_attr = ol_classes.empty? ? '' : %( class="#{ol_classes * ' '}")
+ ol_start_attr = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
+ id_attribute = node.id ? %( id="#{node.id}") : ''
+ lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
+ lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
+ lines << %(<ol#{ol_class_attr}#{ol_start_attr}#{(node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
+ node.items.each do |item|
+ lines << %(<li>
<span class="principal">#{item.text}</span>)
- if item.blocks?
- lines << item.content
- complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
- end
- lines << '</li>'
- end
- if complex
- div_classes << 'complex'
- lines[0] = %(<div class="#{div_classes * ' '}">)
- end
- lines << '</ol>
+ if item.blocks?
+ lines << item.content
+ complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
+ end
+ lines << '</li>'
+ end
+ if complex
+ div_classes << 'complex'
+ lines[0] = %(<div class="#{div_classes * ' '}">)
+ end
+ lines << '</ol>
</div>'
- lines * LF
- end
+ lines * LF
+ end
- def ulist node
- complex = false
- div_classes = ['itemized-list', node.style, node.role].compact
- ul_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
- ul_class_attr = ul_classes.empty? ? '' : %( class="#{ul_classes * ' '}")
- id_attribute = node.id ? %( id="#{node.id}") : ''
- lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
- lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
- lines << %(<ul#{ul_class_attr}>)
- node.items.each do |item|
- lines << %(<li>
+ def ulist node
+ complex = false
+ div_classes = ['itemized-list', node.style, node.role].compact
+ ul_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
+ ul_class_attr = ul_classes.empty? ? '' : %( class="#{ul_classes * ' '}")
+ id_attribute = node.id ? %( id="#{node.id}") : ''
+ lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
+ lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
+ lines << %(<ul#{ul_class_attr}>)
+ node.items.each do |item|
+ lines << %(<li>
<span class="principal">#{item.text}</span>)
- if item.blocks?
- lines << item.content
- complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
- end
- lines << '</li>'
- end
- if complex
- div_classes << 'complex'
- lines[0] = %(<div class="#{div_classes * ' '}">)
- end
- lines << '</ul>
+ if item.blocks?
+ lines << item.content
+ complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
+ end
+ lines << '</li>'
+ end
+ if complex
+ div_classes << 'complex'
+ lines[0] = %(<div class="#{div_classes * ' '}">)
+ end
+ lines << '</ul>
</div>'
- lines * LF
- end
-
- def image node
- target = node.attr 'target'
- type = (::File.extname target)[1..-1]
- id_attr = node.id ? %( id="#{node.id}") : ''
- img_attrs = [%(alt="#{node.attr 'alt'}")]
- case type
- when 'svg'
- img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
- # TODO make this a convenience method on document
- epub_properties = (node.document.attributes['epub-properties'] ||= [])
- epub_properties << 'svg' unless epub_properties.include? 'svg'
- else
- if node.attr? 'scaledwidth'
- img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
+ lines * LF
end
- end
+
+ def image node
+ target = node.attr 'target'
+ type = (::File.extname target)[1..-1]
+ id_attr = node.id ? %( id="#{node.id}") : ''
+ img_attrs = [%(alt="#{node.attr 'alt'}")]
+ case type
+ when 'svg'
+ img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
+ # TODO: make this a convenience method on document
+ epub_properties = (node.document.attributes['epub-properties'] ||= [])
+ epub_properties << 'svg' unless epub_properties.include? 'svg'
+ else
+ img_attrs << %(style="width: #{node.attr 'scaledwidth'}") if node.attr? 'scaledwidth'
+ end
=begin
# NOTE to set actual width and height, use CSS width and height
if type == 'svg'
if node.attr? 'scaledwidth'
img_attrs << %(width="#{node.attr 'scaledwidth'}")
@@ -654,305 +645,302 @@
# Aldiko doesn't not scale width to 100% by default
img_attrs << %(width="100%")
end
end
=end
- %(<figure#{id_attr} class="image#{prepend_space node.role}">
+ %(<figure#{id_attr} class="image#{prepend_space node.role}">
<div class="content">
<img src="#{node.image_uri node.attr('target')}" #{img_attrs * ' '}/>
-</div>#{node.title? ? %[
-<figcaption>#{node.captioned_title}</figcaption>] : ''}
+</div>#{node.title? ? %(
+<figcaption>#{node.captioned_title}</figcaption>) : ''}
</figure>)
- end
+ end
- def inline_anchor node
- target = node.target
- case node.type
- when :xref # TODO would be helpful to know what type the target is (e.g., bibref)
- doc, refid, text, path = node.document, ((node.attr 'refid') || target), node.text, (node.attr 'path')
- # NOTE if path is non-nil, we have an inter-document xref
- # QUESTION should we drop the id attribute for an inter-document xref?
- if path
- # ex. chapter-id#section-id
- if node.attr 'fragment'
- refdoc_id, refdoc_refid = refid.split '#', 2
- if refdoc_id == refdoc_refid
- target = target[0...(target.index '#')]
- id_attr = %( id="xref--#{refdoc_id}")
+ def inline_anchor node
+ target = node.target
+ case node.type
+ when :xref # TODO: would be helpful to know what type the target is (e.g., bibref)
+ doc, refid, text, path = node.document, ((node.attr 'refid') || target), node.text, (node.attr 'path')
+ # NOTE if path is non-nil, we have an inter-document xref
+ # QUESTION should we drop the id attribute for an inter-document xref?
+ if path
+ # ex. chapter-id#section-id
+ if node.attr 'fragment'
+ refdoc_id, refdoc_refid = refid.split '#', 2
+ if refdoc_id == refdoc_refid
+ target = target[0...(target.index '#')]
+ id_attr = %( id="xref--#{refdoc_id}")
+ else
+ id_attr = %( id="xref--#{refdoc_id}--#{refdoc_refid}")
+ end
+ # ex. chapter-id#
+ else
+ refdoc_id = refdoc_refid = refid
+ # inflate key to spine item root (e.g., transform chapter-id to chapter-id#chapter-id)
+ refid = %(#{refid}##{refid})
+ id_attr = %( id="xref--#{refdoc_id}")
+ end
+ id_attr = '' unless @xrefs_seen.add? refid
+ refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) }
+ if refdoc
+ if (refs = refdoc.references[:refs]) && ::Asciidoctor::AbstractNode === (ref = refs[refdoc_refid])
+ text ||= ::Asciidoctor::Document === ref ? ((ref.attr 'docreftext') || ref.doctitle) : ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
+ elsif (xreftext = refdoc.references[:ids][refdoc_refid])
+ text ||= xreftext
+ else
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to unknown anchor in #{refdoc_id} chapter: #{refdoc_refid})
+ end
+ else
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to anchor in unknown chapter: #{refdoc_id})
+ end
else
- id_attr = %( id="xref--#{refdoc_id}--#{refdoc_refid}")
+ id_attr = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : ''
+ if (refs = doc.references[:refs])
+ if ::Asciidoctor::AbstractNode === (ref = refs[refid])
+ xreftext = text || ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
+ end
+ else
+ xreftext = doc.references[:ids][refid]
+ end
+
+ if xreftext
+ text ||= xreftext
+ else
+ # FIXME: we get false negatives for reference to bibref when using Asciidoctor < 1.5.6
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to unknown local anchor (or valid bibref): #{refid})
+ end
end
- # ex. chapter-id#
- else
- refdoc_id = refdoc_refid = refid
- # inflate key to spine item root (e.g., transform chapter-id to chapter-id#chapter-id)
- refid = %(#{refid}##{refid})
- id_attr = %( id="xref--#{refdoc_id}")
- end
- id_attr = '' unless @xrefs_seen.add? refid
- refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) }
- if refdoc
- # QUESTION should we invoke xreftext for references in other documents?
- if (refs = refdoc.references[:refs]) && ::Asciidoctor::Document === (ref = refs[refdoc_refid])
- text ||= (ref.attr 'docreftext') || ref.doctitle
- elsif (xreftext = refdoc.references[:ids][refdoc_refid])
- text ||= xreftext
+ %(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>)
+ when :ref
+ %(<a id="#{target}"></a>)
+ when :link
+ %(<a href="#{target}" class="link">#{node.text}</a>)
+ when :bibref
+ if @xrefs_seen.include? target
+ %(<a id="#{target}" href="#xref-#{target}">[#{target}]</a>)
else
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown anchor in #{refdoc_id} chapter: #{refdoc_refid})
+ %(<a id="#{target}"></a>[#{target}])
end
- else
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to anchor in unknown chapter: #{refdoc_id})
end
- else
- id_attr = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : ''
- if (refs = doc.references[:refs])
- if ::Asciidoctor::AbstractNode === (ref = refs[refid])
- xreftext = text || ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
- end
- else
- xreftext = doc.references[:ids][refid]
- end
+ end
- if xreftext
- text ||= xreftext
- else
- # FIXME we get false negatives for reference to bibref when using Asciidoctor < 1.5.6
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown local anchor (or valid bibref): #{refid})
- end
+ def inline_break node
+ %(#{node.text}<br/>)
end
- %(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>)
- when :ref
- %(<a id="#{target}"></a>)
- when :link
- %(<a href="#{target}" class="link">#{node.text}</a>)
- when :bibref
- if @xrefs_seen.include? target
- %(<a id="#{target}" href="#xref-#{target}">[#{target}]</a>)
- else
- %(<a id="#{target}"></a>[#{target}])
+
+ def inline_button node
+ %(<b class="button">[<span class="label">#{node.text}</span>]</b>)
end
- end
- end
- def inline_break node
- %(#{node.text}<br/>)
- end
+ def inline_callout node
+ num = CalloutStartNum
+ int_num = node.text.to_i
+ (int_num - 1).times { num = num.next }
+ %(<i class="conum" data-value="#{int_num}">#{num}</i>)
+ end
- def inline_button node
- %(<b class="button">[<span class="label">#{node.text}</span>]</b>)
- end
+ def inline_footnote node
+ if (index = node.attr 'index')
+ %(<sup class="noteref">[<a id="noteref-#{index}" href="#note-#{index}" epub:type="noteref">#{index}</a>]</sup>)
+ elsif node.type == :xref
+ %(<mark class="noteref" title="Unresolved note reference">#{node.text}</mark>)
+ end
+ end
- def inline_callout node
- num = CalloutStartNum
- int_num = node.text.to_i
- (int_num - 1).times { num = num.next }
- %(<i class="conum" data-value="#{int_num}">#{num}</i>)
- end
+ def inline_image node
+ if node.type == 'icon'
+ @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?
+ %(<i class="#{i_classes * ' '}"></i>)
+ else
+ target = node.image_uri node.target
+ img_attrs = [%(alt="#{node.attr 'alt'}"), %(class="inline#{node.role? ? " #{node.role}" : ''}")]
+ if target.end_with? '.svg'
+ img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
+ # TODO: make this a convenience method on document
+ epub_properties = (node.document.attributes['epub-properties'] ||= [])
+ epub_properties << 'svg' unless epub_properties.include? 'svg'
+ elsif node.attr? 'scaledwidth'
+ img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
+ end
+ %(<img src="#{target}" #{img_attrs * ' '}/>)
+ end
+ end
- def inline_footnote node
- if (index = node.attr 'index')
- %(<sup class="noteref">[<a id="noteref-#{index}" href="#note-#{index}" epub:type="noteref">#{index}</a>]</sup>)
- elsif node.type == :xref
- %(<mark class="noteref" title="Unresolved note reference">#{node.text}</mark>)
- end
- end
+ def inline_indexterm node
+ node.type == :visible ? node.text : ''
+ end
- def inline_image node
- if node.type == 'icon'
- @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?
- %(<i class="#{i_classes * ' '}"></i>)
- else
- target = node.image_uri node.target
- img_attrs = [%(alt="#{node.attr 'alt'}"), %(class="inline#{node.role? ? " #{node.role}" : ''}")]
- if target.end_with? '.svg'
- img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
- # TODO make this a convenience method on document
- epub_properties = (node.document.attributes['epub-properties'] ||= [])
- epub_properties << 'svg' unless epub_properties.include? 'svg'
- elsif node.attr? 'scaledwidth'
- img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
+ def inline_kbd node
+ if (keys = node.attr 'keys').size == 1
+ %(<kbd>#{keys[0]}</kbd>)
+ else
+ key_combo = keys.map {|key| %(<kbd>#{key}</kbd>) }.join '+'
+ %(<span class="keyseq">#{key_combo}</span>)
+ end
end
- %(<img src="#{target}" #{img_attrs * ' '}/>)
- end
- end
- def inline_indexterm node
- node.type == :visible ? node.text : ''
- end
+ def inline_menu node
+ menu = node.attr 'menu'
+ # NOTE we swap right angle quote with chevron right from FontAwesome using CSS
+ caret = %(#{NoBreakSpace}<span class="caret">#{RightAngleQuote}</span> )
+ if !(submenus = node.attr 'submenus').empty?
+ submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>#{caret}) }.join.chop
+ %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}#{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
+ elsif (menuitem = node.attr 'menuitem')
+ %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}<span class="menuitem">#{menuitem}</span></span>)
+ else
+ %(<span class="menu">#{menu}</span>)
+ end
+ end
- def inline_kbd node
- if (keys = node.attr 'keys').size == 1
- %(<kbd>#{keys[0]}</kbd>)
- else
- key_combo = keys.map {|key| %(<kbd>#{key}</kbd>) }.join '+'
- %(<span class="keyseq">#{key_combo}</span>)
- end
- end
+ def inline_quoted node
+ case node.type
+ when :strong
+ %(<strong>#{node.text}</strong>)
+ when :emphasis
+ %(<em>#{node.text}</em>)
+ when :monospaced
+ %(<code class="literal">#{node.text}</code>)
+ when :double
+ #%(“#{node.text}”)
+ %(“#{node.text}”)
+ when :single
+ #%(‘#{node.text}’)
+ %(‘#{node.text}’)
+ when :superscript
+ %(<sup>#{node.text}</sup>)
+ when :subscript
+ %(<sub>#{node.text}</sub>)
+ else
+ node.text
+ end
+ end
- def inline_menu node
- menu = node.attr 'menu'
- # NOTE we swap right angle quote with chevron right from FontAwesome using CSS
- caret = %(#{NoBreakSpace}<span class="caret">#{RightAngleQuote}</span> )
- if !(submenus = node.attr 'submenus').empty?
- submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>#{caret}) }.join.chop
- %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}#{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
- elsif (menuitem = node.attr 'menuitem')
- %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}<span class="menuitem">#{menuitem}</span></span>)
- else
- %(<span class="menu">#{menu}</span>)
- end
- end
+ def convert_content node
+ node.content_model == :simple ? %(<p>#{node.content}</p>) : node.content
+ end
- def inline_quoted node
- case node.type
- when :strong
- %(<strong>#{node.text}</strong>)
- when :emphasis
- %(<em>#{node.text}</em>)
- when :monospaced
- %(<code class="literal">#{node.text}</code>)
- when :double
- #%(“#{node.text}”)
- %(“#{node.text}”)
- when :single
- #%(‘#{node.text}’)
- %(‘#{node.text}’)
- when :superscript
- %(<sup>#{node.text}</sup>)
- when :subscript
- %(<sub>#{node.text}</sub>)
- else
- node.text
- end
- end
+ # FIXME: merge into with xml_sanitize helper
+ def xml_sanitize value, target = :attribute
+ sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').strip.tr_s(' ', ' ') : value
+ if target == :plain && (sanitized.include? ';')
+ sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack 'U*' } if sanitized.include? '&#'
+ sanitized = sanitized.gsub FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap
+ elsif target == :attribute
+ sanitized = sanitized.gsub '"', '"' if sanitized.include? '"'
+ end
+ sanitized
+ end
- def convert_content node
- node.content_model == :simple ? %(<p>#{node.content}</p>) : node.content
- end
+ # TODO: make check for last content paragraph a feature of Asciidoctor
+ def mark_last_paragraph root
+ return unless (last_block = root.blocks[-1])
+ last_block = last_block.blocks[-1] while last_block.context == :section && last_block.blocks?
+ if last_block.context == :paragraph
+ last_block.attributes['role'] = last_block.role? ? %(#{last_block.role} last) : 'last'
+ end
+ nil
+ end
- # FIXME merge into with xml_sanitize helper
- def xml_sanitize value, target = :attribute
- sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').strip.tr_s(' ', ' ') : value
- if target == :plain && (sanitized.include? ';')
- sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack 'U*' } if sanitized.include? '&#'
- sanitized = sanitized.gsub(FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap)
- elsif target == :attribute
- sanitized = sanitized.gsub '"', '"' if sanitized.include? '"'
+ # Prepend a space to the value if it's non-nil, otherwise return empty string.
+ def prepend_space value
+ value ? %( #{value}) : ''
+ end
end
- sanitized
- end
- # TODO make check for last content paragraph a feature of Asciidoctor
- def mark_last_paragraph root
- return unless (last_block = root.blocks[-1])
- while last_block.context == :section && last_block.blocks?
- last_block = last_block.blocks[-1]
- end
- if last_block.context == :paragraph
- last_block.attributes['role'] = last_block.role? ? %(#{last_block.role} last) : 'last'
- end
- nil
- end
-
- # Prepend a space to the value if it's non-nil, otherwise return empty string.
- def prepend_space value
- value ? %( #{value}) : ''
- 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}));/
- if defined? __dir__
- InvalidIdCharsRx = /[^\p{Word}]+/
- LeadingDigitRx = /^\p{Nd}/
- else
- InvalidIdCharsRx = /[^[:word:]]+/
- LeadingDigitRx = /^[[:digit:]]/
- end
- class << self
- def generate_id doc, pre = nil, sep = nil
- synthetic = false
- unless (id = doc.id)
- # NOTE we assume pre is a valid ID prefix and that pre and sep only contain valid ID chars
- pre ||= '_'
- sep = sep ? sep.chr : '_'
- if doc.header?
- id = doc.doctitle sanitize: true
- id = id.gsub CharRefRx do
- $1 ? ($1 == 'amp' ? 'and' : sep) : ((d = $2 ? $2.to_i : $3.hex) == 8217 ? '' : ([d].pack 'U*'))
- end if id.include? '&'
- id = id.downcase.gsub InvalidIdCharsRx, sep
- if id.empty?
- id, synthetic = nil, true
- else
- unless sep.empty?
- if (id = id.tr_s sep, sep).end_with? sep
- if id == sep
- id, synthetic = nil, true
- else
- id = (id.start_with? sep) ? id[1..-2] : id.chop
+ 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}));/
+ if defined? __dir__
+ InvalidIdCharsRx = /[^\p{Word}]+/
+ LeadingDigitRx = /^\p{Nd}/
+ else
+ InvalidIdCharsRx = /[^[:word:]]+/
+ LeadingDigitRx = /^[[:digit:]]/
+ end
+ class << self
+ def generate_id doc, pre = nil, sep = nil
+ synthetic = false
+ unless (id = doc.id)
+ # NOTE we assume pre is a valid ID prefix and that pre and sep only contain valid ID chars
+ pre ||= '_'
+ sep = sep ? sep.chr : '_'
+ if doc.header?
+ id = doc.doctitle sanitize: true
+ id = id.gsub CharRefRx do
+ $1 ? ($1 == 'amp' ? 'and' : sep) : ((d = $2 ? $2.to_i : $3.hex) == 8217 ? '' : ([d].pack 'U*'))
+ end if id.include? '&'
+ id = id.downcase.gsub InvalidIdCharsRx, sep
+ if id.empty?
+ id, synthetic = nil, true
+ else
+ unless sep.empty?
+ if (id = id.tr_s sep, sep).end_with? sep
+ if id == sep
+ id, synthetic = nil, true
+ else
+ id = (id.start_with? sep) ? id[1..-2] : id.chop
+ end
+ elsif id.start_with? sep
+ id = id[1..-1]
+ end
end
- elsif id.start_with? sep
- id = id[1..-1]
+ unless synthetic
+ if pre.empty?
+ id = %(_#{id}) if LeadingDigitRx =~ id
+ elsif !(id.start_with? pre)
+ id = %(#{pre}#{id})
+ end
+ end
end
+ elsif (first_section = doc.first_section)
+ id = first_section.id
+ else
+ synthetic = true
end
- unless synthetic
- if pre.empty?
- id = %(_#{id}) if LeadingDigitRx =~ id
- elsif !(id.start_with? pre)
- id = %(#{pre}#{id})
- end
- end
+ id = %(#{pre}document#{sep}#{doc.object_id}) if synthetic
end
- elsif (first_section = doc.first_section)
- id = first_section.id
- else
- synthetic = true
+ warn %(asciidoctor: ERROR: chapter uses a reserved ID: #{id}) if !synthetic && (ReservedIds.include? id)
+ id
end
- id = %(#{pre}document#{sep}#{doc.object_id}) if synthetic
end
- warn %(asciidoctor: ERROR: chapter uses a reserved ID: #{id}) if !synthetic && (ReservedIds.include? id)
- id
end
- end
-end
-require_relative 'packager'
+ require_relative 'packager'
-Extensions.register do
- if (document = @document).backend == 'epub3'
- document.attributes['spine'] = ''
- document.set_attribute 'listing-caption', 'Listing'
- if !(defined? ::AsciidoctorJ) && (::Gem::try_activate 'pygments.rb')
- if document.set_attribute 'source-highlighter', 'pygments'
- document.set_attribute 'pygments-css', 'style'
- document.set_attribute 'pygments-style', 'bw'
+ Extensions.register do
+ if (document = @document).backend == 'epub3'
+ document.attributes['spine'] = ''
+ document.set_attribute 'listing-caption', 'Listing'
+ if !(defined? ::AsciidoctorJ) && (::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
+ end
+ case (ebook_format = document.attributes['ebook-format'])
+ when 'epub3', 'kf8'
+ # all good
+ when 'mobi'
+ ebook_format = document.attributes['ebook-format'] = 'kf8'
+ else
+ # QUESTION should we display a warning?
+ ebook_format = document.attributes['ebook-format'] = 'epub3'
+ end
+ document.attributes[%(ebook-format-#{ebook_format})] = ''
+ # Only fire SpineItemProcessor for top-level include directives
+ include_processor SpineItemProcessor.new(document)
+ treeprocessor do
+ process do |doc|
+ doc.id = DocumentIdGenerator.generate_id doc, (doc.attr 'idprefix'), (doc.attr 'idseparator')
+ nil
+ end
+ end
end
end
- case (ebook_format = document.attributes['ebook-format'])
- when 'epub3', 'kf8'
- # all good
- when 'mobi'
- ebook_format = document.attributes['ebook-format'] = 'kf8'
- else
- # QUESTION should we display a warning?
- ebook_format = document.attributes['ebook-format'] = 'epub3'
- end
- document.attributes[%(ebook-format-#{ebook_format})] = ''
- # Only fire SpineItemProcessor for top-level include directives
- include_processor SpineItemProcessor.new(document)
- treeprocessor do
- process do |doc|
- doc.id = DocumentIdGenerator.generate_id doc, (doc.attr 'idprefix'), (doc.attr 'idseparator')
- nil
- end
- end
end
-end
-end
end