# encoding: UTF-8
module Asciidoctor
# A built-in {Converter} implementation that generates DocBook 5 output
# similar to the docbook45 backend from AsciiDoc Python, but migrated to the
# DocBook 5 specification.
class Converter::DocBook5Converter < Converter::BuiltIn
def document node
result = []
if (root_tag_name = node.doctype) == 'manpage'
root_tag_name = 'refentry'
end
result << ''
if (doctype_line = doctype_declaration root_tag_name)
result << doctype_line
end
if node.attr? 'toc'
if node.attr? 'toclevels'
result << %()
else
result << ''
end
end
if node.attr? 'sectnums'
if node.attr? 'sectnumlevels'
result << %()
else
result << ''
end
end
lang_attribute = (node.attr? 'nolang') ? nil : %( #{lang_attribute_name}="#{node.attr 'lang', 'en'}")
result << %(<#{root_tag_name}#{document_ns_attributes node}#{lang_attribute}>)
result << (document_info_element node, root_tag_name)
result << node.content if node.blocks?
unless (footer_docinfo = node.docinfo :footer).empty?
result << footer_docinfo
end
result << %(#{root_tag_name}>)
result * EOL
end
alias :embedded :content
def section node
doctype = node.document.doctype
if node.special
if (tag_name = node.sectname).start_with? 'sect'
# a normal child section of a special section
tag_name = 'section'
end
else
tag_name = doctype == 'book' && node.level <= 1 ? (node.level == 0 ? 'part' : 'chapter') : 'section'
end
if doctype == 'manpage'
if tag_name == 'section'
tag_name = 'refsection'
elsif tag_name == 'synopsis'
tag_name = 'refsynopsisdiv'
end
end
%(<#{tag_name}#{common_attributes node.id, node.role, node.reftext}>
#{node.title}
#{node.content}
#{tag_name}>)
end
def admonition node
%(<#{tag_name = node.attr 'name'}#{common_attributes node.id, node.role, node.reftext}>
#{title_tag node}#{resolve_content node}
#{tag_name}>)
end
alias :audio :skip
def colist node
result = []
result << %()
result << %(#{node.title}) if node.title?
node.items.each do |item|
result << %()
result << %(#{item.text})
result << item.content if item.blocks?
result << ''
end
result << %()
result * EOL
end
(DLIST_TAGS = {
'labeled' => {
:list => 'variablelist',
:entry => 'varlistentry',
:term => 'term',
:item => 'listitem'
},
'qanda' => {
:list => 'qandaset',
:entry => 'qandaentry',
:label => 'question',
:term => 'simpara',
:item => 'answer'
},
'glossary' => {
:list => nil,
:entry => 'glossentry',
:term => 'glossterm',
:item => 'glossdef'
}
}).default = { # default value == DLIST['labeled'], expanded for Opal
:list => 'variablelist',
:entry => 'varlistentry',
:term => 'term',
:item => 'listitem'
}
def dlist node
result = []
if node.style == 'horizontal'
result << %(<#{tag_name = node.title? ? 'table' : 'informaltable'}#{common_attributes node.id, node.role, node.reftext} tabstyle="horizontal" frame="none" colsep="0" rowsep="0">
#{title_tag node}
)
node.items.each do |terms, dd|
result << %()
[*terms].each do |dt|
result << %(#{dt.text})
end
result << %()
unless dd.nil?
result << %(#{dd.text}) if dd.text?
result << dd.content if dd.blocks?
end
result << %()
end
result << %(
#{tag_name}>)
else
tags = DLIST_TAGS[node.style]
list_tag = tags[:list]
entry_tag = tags[:entry]
label_tag = tags[:label]
term_tag = tags[:term]
item_tag = tags[:item]
if list_tag
result << %(<#{list_tag}#{common_attributes node.id, node.role, node.reftext}>)
result << %(#{node.title}) if node.title?
end
node.items.each do |terms, dd|
result << %(<#{entry_tag}>)
result << %(<#{label_tag}>) if label_tag
[*terms].each do |dt|
result << %(<#{term_tag}>#{dt.text}#{term_tag}>)
end
result << %(#{label_tag}>) if label_tag
result << %(<#{item_tag}>)
unless dd.nil?
result << %(#{dd.text}) if dd.text?
result << dd.content if dd.blocks?
end
result << %(#{item_tag}>)
result << %(#{entry_tag}>)
end
result << %(#{list_tag}>) if list_tag
end
result * EOL
end
def example node
if node.title?
%(#{node.title}
#{resolve_content node}
)
else
%(
#{resolve_content node}
)
end
end
def floating_title node
%(#{node.title})
end
def image node
width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : nil
depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : nil
# FIXME if scaledwidth is set, we should remove width & depth
# See http://www.docbook.org/tdg/en/html/imagedata.html#d0e92271 for details
swidth_attribute = (node.attr? 'scaledwidth') ? %( width="#{node.attr 'scaledwidth'}" scalefit="1") : nil
scale_attribute = (node.attr? 'scale') ? %( scale="#{node.attr 'scale'}") : nil
align_attribute = (node.attr? 'align') ? %( align="#{node.attr 'align'}") : nil
mediaobject = %(#{node.attr 'alt'})
if node.title?
%()
else
%(
#{mediaobject}
)
end
end
def listing node
informal = !node.title?
listing_attributes = (common_attributes node.id, node.role, node.reftext)
if node.style == 'source' && (node.attr? 'language')
numbering = (node.attr? 'linenums') ? 'numbered' : 'unnumbered'
listing_content = %(#{node.content})
else
listing_content = %(#{node.content})
end
if informal
listing_content
else
%(#{node.title}
#{listing_content}
)
end
end
def literal node
if node.title?
%(#{node.title}#{node.content})
else
%(#{node.content})
end
end
def stem node
if (idx = node.subs.index :specialcharacters)
node.subs.delete :specialcharacters
end
equation = node.content
node.subs.insert idx, :specialcharacters if idx
if node.style == 'asciimath'
if ((defined? ::AsciiMath) || ((defined? @asciimath_available) ? @asciimath_available :
(@asciimath_available = Helpers.require_library 'asciimath', true, :warn)))
# NOTE fop requires jeuclid to process raw mathml
equation_data = (::AsciiMath.parse equation).to_mathml 'mml:', 'xmlns:mml' => 'http://www.w3.org/1998/Math/MathML'
else
equation_data = %()
end
else
# unhandled math; pass source to alt and required mathphrase element; dblatex will process alt as LaTeX math
equation_data = %()
end
if node.title?
%(#{node.title}
#{equation_data}
)
else
# WARNING dblatex displays the element inline instead of block as documented (except w/ mathml)
%(
#{equation_data}
)
end
end
def olist node
result = []
num_attribute = node.style ? %( numeration="#{node.style}") : nil
start_attribute = (node.attr? 'start') ? %( startingnumber="#{node.attr 'start'}") : nil
result << %()
result << %(#{node.title}) if node.title?
node.items.each do |item|
result << ''
result << %(#{item.text})
result << item.content if item.blocks?
result << ''
end
result << %()
result * EOL
end
def open node
case node.style
when 'abstract'
if node.parent == node.document && node.document.attr?('doctype', 'book')
warn 'asciidoctor: WARNING: abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
''
else
%(
#{title_tag node}#{resolve_content node}
)
end
when 'partintro'
unless node.level == 0 && node.parent.context == :section && node.document.doctype == 'book'
warn 'asciidoctor: ERROR: partintro block can only be used when doctype is book and it\'s a child of a part section. Excluding block content.'
''
else
%(
#{title_tag node}#{resolve_content node}
)
end
else
node.content
end
end
def page_break node
''
end
def paragraph node
if node.title?
%(#{node.title}#{node.content})
else
%(#{node.content})
end
end
def preamble node
if node.document.doctype == 'book'
%(
#{title_tag node, false}#{node.content}
)
else
node.content
end
end
def quote node
result = []
result << %(
)
result << %(#{node.title}) if node.title?
if (node.attr? 'attribution') || (node.attr? 'citetitle')
result << ''
if node.attr? 'attribution'
result << (node.attr 'attribution')
end
if node.attr? 'citetitle'
result << %(#{node.attr 'citetitle'})
end
result << ''
end
result << (resolve_content node)
result << '
'
result * EOL
end
def thematic_break node
''
end
def sidebar node
%(
#{title_tag node}#{resolve_content node}
)
end
TABLE_PI_NAMES = ['dbhtml', 'dbfo', 'dblatex']
TABLE_SECTIONS = [:head, :foot, :body]
def table node
has_body = false
result = []
pgwide_attribute = (node.option? 'pgwide') ? ' pgwide="1"' : nil
result << %(<#{tag_name = node.title? ? 'table' : 'informaltable'}#{common_attributes node.id, node.role, node.reftext}#{pgwide_attribute} frame="#{node.attr 'frame', 'all'}" rowsep="#{['none', 'cols'].include?(node.attr 'grid') ? 0 : 1}" colsep="#{['none', 'rows'].include?(node.attr 'grid') ? 0 : 1}"#{(node.attr? 'orientation', 'landscape', nil) ? ' orient="land"' : nil}>)
if (node.option? 'unbreakable')
result << ''
elsif (node.option? 'breakable')
result << ''
end
result << %(#{node.title}) if tag_name == 'table'
col_width_key = if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
TABLE_PI_NAMES.each do |pi_name|
result << %(#{pi_name} table-width="#{width}"?>)
end
'colabswidth'
else
'colpcwidth'
end
result << %()
node.columns.each do |col|
result << %()
end
TABLE_SECTIONS.select {|tblsec| !node.rows[tblsec].empty? }.each do |tblsec|
has_body = true if tblsec == :body
result << %()
node.rows[tblsec].each do |row|
result << ''
row.each do |cell|
halign_attribute = (cell.attr? 'halign') ? %( align="#{cell.attr 'halign'}") : nil
valign_attribute = (cell.attr? 'valign') ? %( valign="#{cell.attr 'valign'}") : nil
colspan_attribute = cell.colspan ? %( namest="col_#{colnum = cell.column.attr 'colnumber'}" nameend="col_#{colnum + cell.colspan - 1}") : nil
rowspan_attribute = cell.rowspan ? %( morerows="#{cell.rowspan - 1}") : nil
# NOTE may not have whitespace (e.g., line breaks) as a direct descendant according to DocBook rules
entry_start = %()
cell_content = if tblsec == :head
cell.text
else
case cell.style
when :asciidoc
cell.content
when :verse
%(#{cell.text})
when :literal
%(#{cell.text})
when :header
cell.content.map {|text| %(#{text}) }.join
else
cell.content.map {|text| %(#{text}) }.join
end
end
entry_end = (node.document.attr? 'cellbgcolor') ? %() : ''
result << %(#{entry_start}#{cell_content}#{entry_end})
end
result << ''
end
result << %()
end
result << ''
result << %(#{tag_name}>)
warn 'asciidoctor: WARNING: tables must have at least one body row' unless has_body
result * EOL
end
alias :toc :skip
def ulist node
result = []
if node.style == 'bibliography'
result << %()
result << %(#{node.title}) if node.title?
node.items.each do |item|
result << ''
result << %(#{item.text})
result << item.content if item.blocks?
result << ''
end
result << ''
else
mark_type = (checklist = node.option? 'checklist') ? 'none' : node.style
mark_attribute = mark_type ? %( mark="#{mark_type}") : nil
result << %()
result << %(#{node.title}) if node.title?
node.items.each do |item|
text_marker = if checklist && (item.attr? 'checkbox')
(item.attr? 'checked') ? '✓ ' : '❏ '
else
nil
end
result << ''
result << %(#{text_marker}#{item.text})
result << item.content if item.blocks?
result << ''
end
result << ''
end
result * EOL
end
def verse node
result = []
result << %(
)
result << %(#{node.title}) if node.title?
if (node.attr? 'attribution') || (node.attr? 'citetitle')
result << ''
if node.attr? 'attribution'
result << (node.attr 'attribution')
end
if node.attr? 'citetitle'
result << %(#{node.attr 'citetitle'})
end
result << ''
end
result << %(#{node.content})
result << '
'
result * EOL
end
alias :video :skip
def inline_anchor node
case node.type
when :ref
%()
when :xref
if (path = node.attributes['path'])
# QUESTION should we use refid as fallback text instead? (like the html5 backend?)
%(#{node.text || path})
else
linkend = node.attributes['fragment'] || node.target
(text = node.text) ? %(#{text}) : %()
end
when :link
%(#{node.text})
when :bibref
target = node.target
%([#{target}])
else
warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
end
end
def inline_break node
%(#{node.text})
end
def inline_button node
%(#{node.text})
end
def inline_callout node
%()
end
def inline_footnote node
if node.type == :xref
%()
else
%(#{node.text})
end
end
def inline_image node
width_attribute = (node.attr? 'width') ? %( contentwidth="#{node.attr 'width'}") : nil
depth_attribute = (node.attr? 'height') ? %( contentdepth="#{node.attr 'height'}") : nil
%(#{node.attr 'alt'})
end
def inline_indexterm node
if node.type == :visible
%(#{node.text}#{node.text})
else
terms = node.attr 'terms'
result = []
if (numterms = terms.size) > 2
result << %(#{terms[0]}#{terms[1]}#{terms[2]})
end
if numterms > 1
result << %(#{terms[-2]}#{terms[-1]})
end
result << %(#{terms[-1]})
result * EOL
end
end
def inline_kbd node
if (keys = node.attr 'keys').size == 1
%(#{keys[0]})
else
%(#{keys.map {|key| "#{key}" }.join})
end
end
def inline_menu node
menu = node.attr 'menu'
if !(submenus = node.attr 'submenus').empty?
submenu_path = submenus.map {|submenu| %(#{submenu} ) }.join.chop
%(#{menu} #{submenu_path} #{node.attr 'menuitem'})
elsif (menuitem = node.attr 'menuitem')
%(#{menu}#{menuitem})
else
%(#{menu})
end
end
(QUOTE_TAGS = {
:emphasis => ['', '', true],
:strong => ['', '', true],
:monospaced => ['', '', false],
:superscript => ['', '', false],
:subscript => ['', '', false],
:double => ['“', '”', true],
:single => ['‘', '’', true],
:mark => ['', '', false]
}).default = [nil, nil, true]
def inline_quoted node
if (type = node.type) == :asciimath
if ((defined? ::AsciiMath) || ((defined? @asciimath_available) ? @asciimath_available :
(@asciimath_available = Helpers.require_library 'asciimath', true, :warn)))
# NOTE fop requires jeuclid to process raw mathml
%(#{(::AsciiMath.parse node.text).to_mathml 'mml:', 'xmlns:mml' => 'http://www.w3.org/1998/Math/MathML'})
else
%()
end
elsif type == :latexmath
# unhandled math; pass source to alt and required mathphrase element; dblatex will process alt as LaTeX math
%()
else
open, close, supports_phrase = QUOTE_TAGS[type]
text = node.text
if (role = node.role)
if supports_phrase
quoted_text = %(#{open}#{text}#{close})
else
quoted_text = %(#{open.chop} role="#{role}">#{text}#{close})
end
else
quoted_text = %(#{open}#{text}#{close})
end
node.id ? %(#{quoted_text}) : quoted_text
end
end
def author_element doc, index = nil
firstname_key = index ? %(firstname_#{index}) : 'firstname'
middlename_key = index ? %(middlename_#{index}) : 'middlename'
lastname_key = index ? %(lastname_#{index}) : 'lastname'
email_key = index ? %(email_#{index}) : 'email'
result = []
result << ''
result << ''
result << %(#{doc.attr firstname_key}) if doc.attr? firstname_key
result << %(#{doc.attr middlename_key}) if doc.attr? middlename_key
result << %(#{doc.attr lastname_key}) if doc.attr? lastname_key
result << ''
result << %(#{doc.attr email_key}) if doc.attr? email_key
result << ''
result * EOL
end
def common_attributes id, role = nil, reftext = nil
res = id ? %( xml:id="#{id}") : ''
res = %(#{res} role="#{role}") if role
res = %(#{res} xreflabel="#{reftext}") if reftext
res
end
def doctype_declaration root_tag_name
nil
end
def document_info_element doc, info_tag_prefix, use_info_tag_prefix = false
info_tag_prefix = '' unless use_info_tag_prefix
result = []
result << %(<#{info_tag_prefix}info>)
result << document_title_tags(doc.doctitle :partition => true, :use_fallback => true) unless doc.notitle
if (date = (doc.attr? 'revdate') ? (doc.attr 'revdate') : ((doc.attr? 'reproducible') ? nil : (doc.attr 'docdate')))
result << %(#{date})
end
if doc.has_header?
if doc.attr? 'author'
if (authorcount = (doc.attr 'authorcount').to_i) < 2
result << (author_element doc)
result << %(#{doc.attr 'authorinitials'}) if doc.attr? 'authorinitials'
else
result << ''
authorcount.times do |index|
result << (author_element doc, index + 1)
end
result << ''
end
end
if (doc.attr? 'revdate') && ((doc.attr? 'revnumber') || (doc.attr? 'revremark'))
result << %()
result << %(#{doc.attr 'revnumber'}) if doc.attr? 'revnumber'
result << %(#{doc.attr 'revdate'}) if doc.attr? 'revdate'
result << %(#{doc.attr 'authorinitials'}) if doc.attr? 'authorinitials'
result << %(#{doc.attr 'revremark'}) if doc.attr? 'revremark'
result << %()
end
unless (head_docinfo = doc.docinfo).empty?
result << head_docinfo
end
result << %(#{doc.attr 'orgname'}) if doc.attr? 'orgname'
end
result << %(#{info_tag_prefix}info>)
if doc.doctype == 'manpage'
result << ''
result << %(#{doc.attr 'mantitle'}) if doc.attr? 'mantitle'
result << %(#{doc.attr 'manvolnum'}) if doc.attr? 'manvolnum'
result << ''
result << ''
result << %(#{doc.attr 'manname'}) if doc.attr? 'manname'
result << %(#{doc.attr 'manpurpose'}) if doc.attr? 'manpurpose'
result << ''
end
result * EOL
end
def document_ns_attributes doc
' xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"'
end
def lang_attribute_name
'xml:lang'
end
def document_title_tags title
if title.subtitle?
%(#{title.main}#{title.subtitle})
else
%(#{title})
end
end
# FIXME this should be handled through a template mechanism
def resolve_content node
node.content_model == :compound ? node.content : %(#{node.content})
end
def title_tag node, optional = true
!optional || node.title? ? %(#{node.title}\n) : nil
end
end
end