lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.7 vs lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-1.5.0.alpha.8

- old
+ new

@@ -1,6 +1,6 @@ -# encoding: UTF-8 +# encoding: utf-8 require_relative 'spine_item_processor' require_relative 'font_icon_map' module Asciidoctor module Epub3 @@ -56,12 +56,10 @@ RightAngleQuote = '&#x203a;' CalloutStartNum = %(\u2460) XmlElementRx = /<\/?.+?>/ CharEntityRx = /&#(\d{2,6});/ - NamedEntityRx = /&([A-Z]+);/ - UppercaseTagRx = /<(\/)?([A-Z]+)>/ FromHtmlSpecialCharsMap = { '&lt;' => '<', '&gt;' => '>', '&amp;' => '&' @@ -98,26 +96,21 @@ end def document node docid = node.id - if (doctitle = node.doctitle partition: true, sanitize: true, use_fallback: true).subtitle? - title = doctitle.main - title_upper = title.upcase + 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 = title_upper = '' + title = '' subtitle = doctitle.combined end - doctitle_sanitized = doctitle.combined + doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).to_s subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' ' - # FIXME use uppercase pcdata helper to make less fragile (see logic in Asciidoctor PDF) - subtitle_formatted_upper = subtitle_formatted.upcase - .gsub(UppercaseTagRx) { %(<#{$1}#{$2.downcase}>) } - .gsub(NamedEntityRx) { %(&#{$1.downcase};) } if (node.attr 'publication-type', 'book') == 'book' byline = nil else author = node.attr 'author' @@ -169,11 +162,11 @@ </head> <body> <section class="chapter" title="#{doctitle_sanitized.gsub '"', '&quot;'}" epub:type="chapter" id="#{docid}"> #{icon_css_scoped}<header> <div class="chapter-header"> -#{byline}<h1 class="chapter-title">#{title_upper}#{subtitle ? %[ <small class="subtitle">#{subtitle_formatted_upper}</small>] : nil}</h1> +#{byline}<h1 class="chapter-title">#{title}#{subtitle ? %[<small class="subtitle">#{subtitle_formatted}</small>] : ''}</h1> </div> </header> #{content})] if node.footnotes? @@ -272,10 +265,11 @@ content end end def admonition node + id_attr = node.id ? %( id="#{node.id}") : nil if node.title? title = node.title title_sanitized = xml_sanitize title title_attr = %( title="#{node.caption}: #{title_sanitized}") title_el = %(<h2>#{title}</h2> @@ -292,21 +286,22 @@ when 'note' 'note' when 'important', 'warning', 'caution' 'warning' end - %(<aside class="admonition #{type}"#{title_attr} epub:type="#{epub_type}"> + %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}"> #{title_el}<div class="content"> #{convert_content node} </div> </aside>) end def example node + id_attr = node.id ? %( id="#{node.id}") : nil title_div = node.title? ? %(<div class="example-title">#{node.title}</div> ) : nil - %(<div class="example"> + %(<div#{id_attr} class="example"> #{title_div}<div class="example-content"> #{convert_content node} </div> </div>) end @@ -342,10 +337,13 @@ 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"' + footer_content = [] if (attribution = node.attr 'attribution') footer_content << attribution end @@ -361,18 +359,21 @@ footer_tag = footer_content.empty? ? nil : %( <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 class="blockquote"> + %(<div#{id_attr}#{class_attr}> <blockquote> #{content}#{footer_tag} </blockquote> </div>) end 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 @@ -381,11 +382,11 @@ footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>) end footer_tag = footer_content.size > 0 ? %( <span class="attribution">~ #{footer_content * ', '}</span>) : nil - %(<div class="verse"> + %(<div#{id_attr}#{class_attr}> <pre>#{node.content}#{footer_tag}</pre> </div>) end def sidebar node @@ -393,13 +394,11 @@ if node.title? classes << 'titled' title = node.title title_sanitized = xml_sanitize title title_attr = %( title="#{title_sanitized}") - # FIXME use uppercase pcdata helper to make less fragile (see logic in Asciidoctor PDF) - title_upper = title.upcase.gsub(NamedEntityRx) { %(&#{$1.downcase};) } - title_el = %(<h2>#{title_upper}</h2> + title_el = %(<h2>#{title}</h2> ) else title_attr = nil title_el = nil end @@ -416,18 +415,20 @@ lines << %(<div class="content">) table_id_attr = node.id ? %( id="#{node.id}") : nil frame_class = { 'all' => 'table-framed', 'topbot' => 'table-framed-topbot', - 'sides' => 'table-framed-sides' + 'sides' => 'table-framed-sides', + 'none' => '' } grid_class = { 'all' => 'table-grid', 'rows' => 'table-grid-rows', - 'cols' => 'table-grid-cols' + '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']}) + 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 = [] @@ -623,20 +624,18 @@ 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.attr 'epub-properties') || [] - unless epub_properties.include? 'svg' - epub_properties << 'svg' - node.document.attributes['epub-properties'] = epub_properties - end + 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'}") end end @@ -655,11 +654,11 @@ # Aldiko doesn't not scale width to 100% by default img_attrs << %(width="100%") end end =end - %(<figure class="image"> + %(<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>] : nil} </figure>) @@ -690,24 +689,35 @@ id_attr = %( id="xref--#{refdoc_id}") end id_attr = nil unless @xrefs_seen.add? refid refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) } if refdoc - if (reftext = refdoc.references[:ids][refdoc_refid]) - text ||= reftext + # 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 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 = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : nil - if (reftext = doc.references[:ids][refid]) - text ||= reftext + if (refs = doc.references[:refs]) + if ::Asciidoctor::AbstractNode === (ref = refs[refid]) + xreftext = text || ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle'))) + end else - # FIXME we get false negatives for reference to bibref + 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 %(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>) when :ref @@ -755,12 +765,20 @@ 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 - class_attr = %( class="#{node.role}") if node.role? - %(<img src="#{target}" alt="#{node.attr 'alt'}"#{class_attr}/>) + 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_indexterm node node.type == :visible ? node.text : '' @@ -837,14 +855,19 @@ 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]{2,})|#(\d{2,6})|#x([a-fA-F0-9]{2,5}));/ + 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:]]+/