# frozen_string_literal: true
require 'asciidoctor'
require 'asciidoctor/converter'
require 'fb2rb'
require 'mime/types'
module Asciidoctor
module FB2
DATA_DIR = File.expand_path(File.join(__dir__, '..', 'data'))
# Converts AsciiDoc documents to FB2 e-book formats
class Converter < Asciidoctor::Converter::Base # rubocop:disable Metrics/ClassLength
include ::Asciidoctor::Writer
CSV_DELIMITER_REGEX = /\s*,\s*/.freeze
IMAGE_ATTRIBUTE_VALUE_RX = /^image:{1,2}(.*?)\[(.*?)\]$/.freeze
register_for 'fb2'
# @return [FB2rb::Book]
attr_reader(:book)
def initialize(backend, opts = {})
super
outfilesuffix '.fb2.zip'
end
# @param node [Asciidoctor::Document]
def convert_document(node) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
@book = FB2rb::Book.new
@book.add_stylesheet('text/css', File.join(DATA_DIR, 'fb2.css'))
document_info = @book.description.document_info
title_info = @book.description.title_info
title_info.book_title = node.doctitle
title_info.lang = node.attr('lang', 'en')
(node.attr 'keywords', '').split(CSV_DELIMITER_REGEX).each do |s|
title_info.keywords << s
end
(node.attr 'genres', '').split(CSV_DELIMITER_REGEX).each do |s|
title_info.genres << s
end
node.authors.each do |author|
title_info.authors << FB2rb::Author.new(
author.firstname,
author.middlename,
author.lastname,
nil,
[],
author.email.nil? ? [] : [author.email]
)
end
if node.attr? 'series-name'
series_name = node.attr 'series-name'
series_volume = node.attr 'series-volume', 1
title_info.sequences << FB2rb::Sequence.new(series_name, series_volume)
end
date = node.attr('revdate') || node.attr('docdate')
fb2date = FB2rb::FB2Date.new(date, Date.parse(date))
title_info.date = document_info.date = fb2date
unless (cover_image = node.attr('front-cover-image')).nil?
cover_image = Regexp.last_match(1) if cover_image =~ IMAGE_ATTRIBUTE_VALUE_RX
cover_image_path = node.image_uri(cover_image)
register_binary(node, cover_image_path, 'image')
title_info.coverpage = FB2rb::Coverpage.new([%(##{cover_image_path})])
end
document_info.id = node.attr('uuid', '')
document_info.version = node.attr('revnumber')
document_info.program_used = %(Asciidoctor FB2 #{VERSION} using Asciidoctor #{node.attr('asciidoctor-version')})
publisher = node.attr('publisher')
document_info.publishers << publisher if publisher
body = %( #{node.doctitle} #{footnote.index} #{footnote.text} #{node.title}
', node.content, '
' ] lines << '#{node.content}
#{author_tag} ) end # @param node [Asciidoctor::Block] def convert_verse(node) body = node.content&.split("\n\n")&.map do |stanza| %(#{line}
', '
'],
emphasis: ['', '
'],
latexmath: ['', '
']
}).default = ['', '']
# @param node [Asciidoctor::Inline]
def convert_inline_quoted(node)
open, close = QUOTE_TAGS[node.type]
%(#{open}#{node.text}#{close})
end
# @param node [Asciidoctor::Inline]
def convert_inline_menu(node)
caret = ' › '
menu = node.attr('menu')
menuitem = node.attr('menuitem')
submenus = node.attr('submenus') * %(#{caret})
result = %(#{menu})
result += %(#{caret}#{submenus}) unless submenus.nil_or_empty?
result += %(#{caret}#{menuitem}) unless menuitem.nil_or_empty?
result
end
# @param node [Asciidoctor::Inline]
def convert_inline_break(node)
node.text
end
# @param node [Asciidoctor::Inline]
def convert_inline_button(node)
%([#{node.text}])
end
# @param node [Asciidoctor::Inline]
def convert_inline_kbd(node)
%(#{node.attr('keys') * '+'})
end
# @param node [Asciidoctor::Inline]
def convert_inline_anchor(node) # rubocop:disable Metrics/MethodLength
case node.type
when :xref
%(#{node.text})
when :link
%(#{node.text})
when :ref
%()
when :bibref
unless (reftext = node.reftext)
reftext = %([#{node.id}])
end
%(#{reftext})
else
logger.warn %(unknown anchor type: #{node.type.inspect})
nil
end
end
# @param node [Asciidoctor::Inline]
def convert_inline_footnote(node)
index = node.attr('index')
%([#{index}])
end
# @param node [Asciidoctor::Inline]
def convert_inline_image(node)
image_attrs = register_binary(node, node.image_uri(node.target), 'image')
%(#{node.title || node.caption}: #{node.content}
)] lines << '#{node.title}
) %(#{title_tag} #{node.content}) end # @param node [Asciidoctor::List] def convert_ulist(node) lines = [] @stack ||= [] node.items.each do |item| @stack << '•' lines << %(#{@stack * ' '} #{item.text}
) lines << %(#{item.content}
) if item.blocks? @stack.pop end lines << '#{@stack * ' '} #{item.text}
) lines << %(#{item.content}
) if item.blocks? @stack.pop end lines << ''
first_term = true
terms.each do |dt|
lines << %( ' lines << '' if node.option?('strong') lines << dt.text lines << '' if node.option?('strong') lines << ' ' first_term = false end lines << ' | '
lines << ''
if dd
lines << %( #{dd.text} ) if dd.text? lines << dd.content if dd.blocks? end lines << ' | '
lines << '