asciidoctor-moodle 1.0.0.dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.adoc +412 -0
- data/asciidoctor-moodle.gemspec +36 -0
- data/lib/asciidoctor-moodle/cloze_mc/extension.rb +134 -0
- data/lib/asciidoctor-moodle/converter.rb +793 -0
- data/lib/asciidoctor-moodle/drag_drop_text and missing_words/extension.rb +162 -0
- data/lib/asciidoctor-moodle/extensions.rb +34 -0
- data/lib/asciidoctor-moodle/gap/extension.rb +93 -0
- data/lib/asciidoctor-moodle/matching/extension.rb +160 -0
- data/lib/asciidoctor-moodle/multiple_choice/extension.rb +175 -0
- data/lib/asciidoctor-moodle/numerical/extension.rb +163 -0
- data/lib/asciidoctor-moodle/ordering/extension.rb +113 -0
- data/lib/asciidoctor-moodle/question/extension.rb +113 -0
- data/lib/asciidoctor-moodle/question.rb +6 -0
- data/lib/asciidoctor-moodle/short/extension.rb +102 -0
- data/lib/asciidoctor-moodle/true_false/extension.rb +97 -0
- data/lib/asciidoctor-moodle/version.rb +6 -0
- data/lib/asciidoctor-moodle.rb +6 -0
- metadata +105 -0
@@ -0,0 +1,793 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'asciidoctor' unless defined? ::Asciidoctor::VERSION ##Rajout
|
3
|
+
module Asciidoctor
|
4
|
+
ROOT_TAG_NAME = 'quiz'
|
5
|
+
# A built-in {Converter} implementation that generates DocBook 5 output. The output is inspired by the output produced
|
6
|
+
# by the docbook45 backend from AsciiDoc.py, except it has been migrated to the DocBook 5 specification.
|
7
|
+
class Converter::MoodleConverter < Converter::Base
|
8
|
+
register_for 'moodle'
|
9
|
+
|
10
|
+
# default represents variablelist
|
11
|
+
(DLIST_TAGS = {
|
12
|
+
'qanda' => { list: 'qandaset', entry: 'qandaentry', label: 'question', term: 'simpara', item: 'answer' },
|
13
|
+
'glossary' => { list: nil, entry: 'glossentry', term: 'glossterm', item: 'glossdef' },
|
14
|
+
}).default = { list: 'variablelist', entry: 'varlistentry', term: 'term', item: 'listitem' }
|
15
|
+
|
16
|
+
(QUOTE_TAGS = {
|
17
|
+
monospaced: ['<literal>', '</literal>'],
|
18
|
+
emphasis: ['<em>', '</em>', true],
|
19
|
+
strong: ['<strong>','</strong>', true],
|
20
|
+
double: ['<quote role="double">', '</quote>', true],
|
21
|
+
single: ['<quote role="single">', '</quote>', true],
|
22
|
+
mark: ['<emphasis role="marked">', '</emphasis>'],
|
23
|
+
superscript: ['<superscript>', '</superscript>'],
|
24
|
+
subscript: ['<subscript>', '</subscript>'],
|
25
|
+
}).default = ['', '', true]
|
26
|
+
|
27
|
+
MANPAGE_SECTION_TAGS = { 'section' => 'refsection', 'synopsis' => 'refsynopsisdiv' }
|
28
|
+
TABLE_PI_NAMES = %w(dbhtml dbfo dblatex)
|
29
|
+
|
30
|
+
CopyrightRx = /^(#{CC_ANY}+?)(?: ((?:\d{4}-)?\d{4}))?$/
|
31
|
+
ImageMacroRx = /^image::?(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
|
32
|
+
|
33
|
+
def initialize backend, opts = {}
|
34
|
+
@backend = backend
|
35
|
+
init_backend_traits basebackend: 'docbook', filetype: 'xml', outfilesuffix: '.xml', supports_templates: true
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def convert_document node
|
40
|
+
result = ['<?xml version="1.0" encoding="UTF-8"?> <quiz>']
|
41
|
+
|
42
|
+
|
43
|
+
result << (node.blocks.map {|block| block.convert }.compact.join LF) if node.blocks?
|
44
|
+
#restore_abstract abstract if abstract
|
45
|
+
unless (docinfo_content = node.docinfo :footer).empty?
|
46
|
+
result << docinfo_content
|
47
|
+
end
|
48
|
+
id, node.id = node.id, nil unless id
|
49
|
+
|
50
|
+
|
51
|
+
result = result.join LF
|
52
|
+
result << '</quiz>'
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def convert_embedded node
|
58
|
+
# NOTE in DocBook 5, the root abstract must be in the info tag and is thus not part of the body
|
59
|
+
if @backend == 'docbook5' && (abstract = find_root_abstract node)
|
60
|
+
abstract = extract_abstract node, abstract
|
61
|
+
end
|
62
|
+
result = node.blocks.map {|block| block.convert }.compact.join LF
|
63
|
+
restore_abstract abstract if abstract
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def convert_section node
|
68
|
+
if node.document.doctype == 'manpage'
|
69
|
+
tag_name = MANPAGE_SECTION_TAGS[tag_name = node.sectname] || tag_name
|
70
|
+
else
|
71
|
+
tag_name = node.sectname
|
72
|
+
end
|
73
|
+
title_el = node.special && ((node.option? 'notitle') || (node.option? 'untitled')) ? '' : %(<title>#{node.title}</title>\n)
|
74
|
+
%(<#{tag_name}#{common_attributes node.id, node.role, node.reftext}>
|
75
|
+
#{title_el}#{node.content}
|
76
|
+
</#{tag_name}>)
|
77
|
+
end
|
78
|
+
|
79
|
+
def convert_admonition node
|
80
|
+
%(<#{tag_name = node.attr 'name'}#{common_attributes node.id, node.role, node.reftext}>
|
81
|
+
#{title_tag node}#{enclose_content node}
|
82
|
+
</#{tag_name}>)
|
83
|
+
end
|
84
|
+
|
85
|
+
alias convert_audio skip
|
86
|
+
|
87
|
+
def convert_colist node
|
88
|
+
result = []
|
89
|
+
result << %(<calloutlist#{common_attributes node.id, node.role, node.reftext}>)
|
90
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
91
|
+
node.items.each do |item|
|
92
|
+
result << %(<callout arearefs="#{item.attr 'coids'}">)
|
93
|
+
result << %(<para>#{item.text}</para>)
|
94
|
+
result << item.content if item.blocks?
|
95
|
+
result << '</callout>'
|
96
|
+
end
|
97
|
+
result << %(</calloutlist>)
|
98
|
+
result.join LF
|
99
|
+
end
|
100
|
+
|
101
|
+
def convert_dlist node
|
102
|
+
result = []
|
103
|
+
if node.style == 'horizontal'
|
104
|
+
result << %(<#{tag_name = node.title? ? 'table' : 'informaltable'}#{common_attributes node.id, node.role, node.reftext} tabstyle="horizontal" frame="none" colsep="0" rowsep="0">
|
105
|
+
#{title_tag node}<tgroup cols="2">
|
106
|
+
<colspec colwidth="#{node.attr 'labelwidth', 15}*"/>
|
107
|
+
<colspec colwidth="#{node.attr 'itemwidth', 85}*"/>
|
108
|
+
<tbody valign="top">)
|
109
|
+
node.items.each do |terms, dd|
|
110
|
+
result << %(<row>
|
111
|
+
<entry>)
|
112
|
+
terms.each {|dt| result << %(<simpara>#{dt.text}</simpara>) }
|
113
|
+
result << %(</entry>
|
114
|
+
<entry>)
|
115
|
+
if dd
|
116
|
+
result << %(<simpara>#{dd.text}</simpara>) if dd.text?
|
117
|
+
result << dd.content if dd.blocks?
|
118
|
+
end
|
119
|
+
result << %(</entry>
|
120
|
+
</row>)
|
121
|
+
end
|
122
|
+
result << %(</tbody>
|
123
|
+
</tgroup>
|
124
|
+
</#{tag_name}>)
|
125
|
+
else
|
126
|
+
tags = DLIST_TAGS[node.style]
|
127
|
+
list_tag = tags[:list]
|
128
|
+
entry_tag = tags[:entry]
|
129
|
+
label_tag = tags[:label]
|
130
|
+
term_tag = tags[:term]
|
131
|
+
item_tag = tags[:item]
|
132
|
+
if list_tag
|
133
|
+
result << %(<#{list_tag}#{common_attributes node.id, node.role, node.reftext}>)
|
134
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
135
|
+
end
|
136
|
+
|
137
|
+
node.items.each do |terms, dd|
|
138
|
+
result << %(<#{entry_tag}>)
|
139
|
+
result << %(<#{label_tag}>) if label_tag
|
140
|
+
terms.each {|dt| result << %(<#{term_tag}>#{dt.text}</#{term_tag}>) }
|
141
|
+
result << %(</#{label_tag}>) if label_tag
|
142
|
+
result << %(<#{item_tag}>)
|
143
|
+
if dd
|
144
|
+
result << %(<simpara>#{dd.text}</simpara>) if dd.text?
|
145
|
+
result << dd.content if dd.blocks?
|
146
|
+
end
|
147
|
+
result << %(</#{item_tag}>)
|
148
|
+
result << %(</#{entry_tag}>)
|
149
|
+
end
|
150
|
+
|
151
|
+
result << %(</#{list_tag}>) if list_tag
|
152
|
+
end
|
153
|
+
|
154
|
+
result.join LF
|
155
|
+
end
|
156
|
+
|
157
|
+
def convert_example node
|
158
|
+
if node.title?
|
159
|
+
%(<example#{common_attributes node.id, node.role, node.reftext}>
|
160
|
+
<title>#{node.title}</title>
|
161
|
+
#{enclose_content node}
|
162
|
+
</example>)
|
163
|
+
else
|
164
|
+
%(<informalexample#{common_attributes node.id, node.role, node.reftext}>
|
165
|
+
#{enclose_content node}
|
166
|
+
</informalexample>)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def convert_floating_title node
|
171
|
+
%(<bridgehead#{common_attributes node.id, node.role, node.reftext} renderas="sect#{node.level}">#{node.title}</bridgehead>)
|
172
|
+
end
|
173
|
+
|
174
|
+
def convert_image node
|
175
|
+
align_attribute = (node.attr? 'align') ? %( align="#{node.attr 'align'}") : ''
|
176
|
+
|
177
|
+
mediaobject = %(<mediaobject>
|
178
|
+
<imageobject>
|
179
|
+
<imagedata fileref="#{node.image_uri node.attr 'target'}"#{image_size_attributes node.attributes}#{align_attribute}/>
|
180
|
+
</imageobject>
|
181
|
+
<textobject><phrase>#{node.alt}</phrase></textobject>
|
182
|
+
</mediaobject>)
|
183
|
+
|
184
|
+
if node.title?
|
185
|
+
%(<figure#{common_attributes node.id, node.role, node.reftext}>
|
186
|
+
<title>#{node.title}</title>
|
187
|
+
#{mediaobject}
|
188
|
+
</figure>)
|
189
|
+
else
|
190
|
+
%(<informalfigure#{common_attributes node.id, node.role, node.reftext}>
|
191
|
+
#{mediaobject}
|
192
|
+
</informalfigure>)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def convert_listing node
|
197
|
+
informal = !node.title?
|
198
|
+
common_attrs = common_attributes node.id, node.role, node.reftext
|
199
|
+
if node.style == 'source'
|
200
|
+
attrs = node.attributes
|
201
|
+
if node.option? 'linenums'
|
202
|
+
numbering_attrs = (attrs.key? 'start') ? %( linenumbering="numbered" startinglinenumber="#{attrs['start'].to_i}") : ' linenumbering="numbered"'
|
203
|
+
else
|
204
|
+
numbering_attrs = ' linenumbering="unnumbered"'
|
205
|
+
end
|
206
|
+
if attrs.key? 'language'
|
207
|
+
wrapped_content = %(<programlisting#{informal ? common_attrs : ''} language="#{attrs['language']}"#{numbering_attrs}>#{node.content}</programlisting>)
|
208
|
+
else
|
209
|
+
wrapped_content = %(<screen#{informal ? common_attrs : ''}#{numbering_attrs}>#{node.content}</screen>)
|
210
|
+
end
|
211
|
+
else
|
212
|
+
wrapped_content = %(<screen#{informal ? common_attrs : ''}>#{node.content}</screen>)
|
213
|
+
end
|
214
|
+
informal ? wrapped_content : %(<formalpara#{common_attrs}>
|
215
|
+
<title>#{node.title}</title>
|
216
|
+
<para>
|
217
|
+
#{wrapped_content}
|
218
|
+
</para>
|
219
|
+
</formalpara>)
|
220
|
+
end
|
221
|
+
|
222
|
+
def convert_literal node
|
223
|
+
if node.title?
|
224
|
+
%(#{node.content}) #J'ai effacé toutes les balises ici pour avoir juste le contenu
|
225
|
+
else
|
226
|
+
%(#{node.content}) #J'ai effacé toutes les balises ici pour avoir juste le contenu
|
227
|
+
end #On rentre ici
|
228
|
+
end
|
229
|
+
|
230
|
+
alias convert_pass content_only
|
231
|
+
|
232
|
+
def convert_stem node
|
233
|
+
if (idx = node.subs.index :specialcharacters)
|
234
|
+
node.subs.delete_at idx
|
235
|
+
equation = node.content
|
236
|
+
idx > 0 ? (node.subs.insert idx, :specialcharacters) : (node.subs.unshift :specialcharacters)
|
237
|
+
else
|
238
|
+
equation = node.content
|
239
|
+
end
|
240
|
+
if node.style == 'asciimath'
|
241
|
+
# NOTE fop requires jeuclid to process mathml markup
|
242
|
+
equation_data = asciimath_available? ? ((::AsciiMath.parse equation).to_mathml 'mml:', 'xmlns:mml' => 'http://www.w3.org/1998/Math/MathML') : %(<mathphrase><![CDATA[#{equation}]]></mathphrase>)
|
243
|
+
else
|
244
|
+
# unhandled math; pass source to alt and required mathphrase element; dblatex will process alt as LaTeX math
|
245
|
+
equation_data = %(<alt><![CDATA[#{equation}]]></alt>
|
246
|
+
<mathphrase><![CDATA[#{equation}]]></mathphrase>)
|
247
|
+
end
|
248
|
+
if node.title?
|
249
|
+
%(<equation#{common_attributes node.id, node.role, node.reftext}>
|
250
|
+
<title>#{node.title}</title>
|
251
|
+
#{equation_data}
|
252
|
+
</equation>)
|
253
|
+
else
|
254
|
+
# WARNING dblatex displays the <informalequation> element inline instead of block as documented (except w/ mathml)
|
255
|
+
%(<informalequation#{common_attributes node.id, node.role, node.reftext}>
|
256
|
+
#{equation_data}
|
257
|
+
</informalequation>)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def convert_olist node
|
262
|
+
puts('#convert_olist')
|
263
|
+
|
264
|
+
result = []
|
265
|
+
num_attribute = node.style ? %( numeration="#{node.style}") : ''
|
266
|
+
start_attribute = (node.attr? 'start') ? %( startingnumber="#{node.attr 'start'}") : ''
|
267
|
+
result << %(<orderedlist#{common_attributes node.id, node.role, node.reftext}#{num_attribute}#{start_attribute}>)
|
268
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
269
|
+
node.items.each do |item|
|
270
|
+
result << %(<listitem#{common_attributes item.id, item.role}>)
|
271
|
+
result << %(<simpara>#{item.text}</simpara>)
|
272
|
+
result << item.content if item.blocks?
|
273
|
+
result << '</listitem>'
|
274
|
+
end
|
275
|
+
result << %(</orderedlist>)
|
276
|
+
result.join LF
|
277
|
+
end
|
278
|
+
|
279
|
+
def convert_open node
|
280
|
+
case node.style
|
281
|
+
when 'abstract'
|
282
|
+
if (parent = node.parent) == node.document && node.document.doctype == 'book'
|
283
|
+
logger.warn 'abstract block cannot be used in a document without a doctitle when doctype is book. Excluding block content.'
|
284
|
+
''
|
285
|
+
else
|
286
|
+
result = %(<abstract>
|
287
|
+
#{title_tag node}#{enclose_content node}
|
288
|
+
</abstract>)
|
289
|
+
if @backend == 'docbook5' && !(node.option? 'root') && (parent.context == :open ? parent.style == 'partintro' : parent.context == :section && parent.sectname == 'partintro') && node == parent.blocks[0]
|
290
|
+
result = %(<info>\n#{result}\n</info>)
|
291
|
+
end
|
292
|
+
result
|
293
|
+
end
|
294
|
+
when 'partintro'
|
295
|
+
if node.level == 0 && node.parent.context == :section && node.document.doctype == 'book'
|
296
|
+
%(<partintro#{common_attributes node.id, node.role, node.reftext}>
|
297
|
+
#{title_tag node}#{enclose_content node}
|
298
|
+
</partintro>)
|
299
|
+
else
|
300
|
+
logger.error 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
|
301
|
+
''
|
302
|
+
end
|
303
|
+
else #Par défaut
|
304
|
+
reftext = node.reftext if (id = node.id)
|
305
|
+
role = node.role
|
306
|
+
if node.title?
|
307
|
+
%(<formalpara#{common_attributes id, role, reftext}>
|
308
|
+
<title>#{node.title}</title>
|
309
|
+
<para>#{content_spacer = node.content_model == :compound ? LF : ''}#{node.content}#{content_spacer}</para>
|
310
|
+
</formalpara>)
|
311
|
+
elsif id || role
|
312
|
+
if node.content_model == :compound
|
313
|
+
%(#{common_attributes id, role, reftext}
|
314
|
+
#{node.content}
|
315
|
+
) #ici lesparagraphes
|
316
|
+
else
|
317
|
+
%(<simpara#{common_attributes id, role, reftext}>#{node.content}</simpara>)
|
318
|
+
end
|
319
|
+
else
|
320
|
+
enclose_content node
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def convert_page_break node
|
326
|
+
'<simpara><?asciidoc-pagebreak?></simpara>'
|
327
|
+
end
|
328
|
+
|
329
|
+
def convert_paragraph node
|
330
|
+
if node.title?
|
331
|
+
%(<formalpara#{common_attributes node.id, node.role, node.reftext}>
|
332
|
+
<title>#{node.title}</title>
|
333
|
+
<para>#{node.content}</para>
|
334
|
+
</formalpara>)
|
335
|
+
else
|
336
|
+
%(#{node.content}) #Rajout : plutot effacelment ici les simpra de paragraphe
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def convert_preamble node
|
341
|
+
if node.document.doctype == 'book'
|
342
|
+
%(<preface#{common_attributes node.id, node.role, node.reftext}>
|
343
|
+
#{title_tag node, false}#{node.content}
|
344
|
+
</preface>)
|
345
|
+
else
|
346
|
+
node.content
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def convert_quote node
|
351
|
+
blockquote_tag(node, (node.has_role? 'epigraph') && 'epigraph') { enclose_content node }
|
352
|
+
end
|
353
|
+
|
354
|
+
def convert_thematic_break node
|
355
|
+
'<simpara><?asciidoc-hr?></simpara>'
|
356
|
+
end
|
357
|
+
|
358
|
+
def convert_sidebar node
|
359
|
+
%(<sidebar#{common_attributes node.id, node.role, node.reftext}>
|
360
|
+
#{title_tag node}#{enclose_content node}
|
361
|
+
</sidebar>)
|
362
|
+
end
|
363
|
+
|
364
|
+
def convert_table node
|
365
|
+
has_body = false
|
366
|
+
result = []
|
367
|
+
pgwide_attribute = (node.option? 'pgwide') ? ' pgwide="1"' : ''
|
368
|
+
frame = 'topbot' if (frame = node.attr 'frame', 'all', 'table-frame') == 'ends'
|
369
|
+
grid = node.attr 'grid', nil, 'table-grid'
|
370
|
+
result << %(<#{tag_name = node.title? ? 'table' : 'informaltable'}#{common_attributes node.id, node.role, node.reftext}#{pgwide_attribute} frame="#{frame}" rowsep="#{(%w(none cols).include? grid) ? 0 : 1}" colsep="#{(%w(none rows).include? grid) ? 0 : 1}"#{(node.attr? 'orientation', 'landscape', 'table-orientation') ? ' orient="land"' : ''}>)
|
371
|
+
if node.option? 'unbreakable'
|
372
|
+
result << '<?dbfo keep-together="always"?>'
|
373
|
+
elsif node.option? 'breakable'
|
374
|
+
result << '<?dbfo keep-together="auto"?>'
|
375
|
+
end
|
376
|
+
result << %(<title>#{node.title}</title>) if tag_name == 'table'
|
377
|
+
if (width = (node.attr? 'width') ? (node.attr 'width') : nil)
|
378
|
+
TABLE_PI_NAMES.each do |pi_name|
|
379
|
+
result << %(<?#{pi_name} table-width="#{width}"?>)
|
380
|
+
end
|
381
|
+
col_width_key = 'colabswidth'
|
382
|
+
else
|
383
|
+
col_width_key = 'colpcwidth'
|
384
|
+
end
|
385
|
+
result << %(<tgroup cols="#{node.attr 'colcount'}">)
|
386
|
+
node.columns.each do |col|
|
387
|
+
result << %(<colspec colname="col_#{col.attr 'colnumber'}" colwidth="#{col.attr col_width_key}*"/>)
|
388
|
+
end
|
389
|
+
node.rows.to_h.each do |tsec, rows|
|
390
|
+
next if rows.empty?
|
391
|
+
has_body = true if tsec == :body
|
392
|
+
result << %(<t#{tsec}>)
|
393
|
+
rows.each do |row|
|
394
|
+
result << '<row>'
|
395
|
+
row.each do |cell|
|
396
|
+
colspan_attribute = cell.colspan ? %( namest="col_#{colnum = cell.column.attr 'colnumber'}" nameend="col_#{colnum + cell.colspan - 1}") : ''
|
397
|
+
rowspan_attribute = cell.rowspan ? %( morerows="#{cell.rowspan - 1}") : ''
|
398
|
+
# NOTE <entry> may not have whitespace (e.g., line breaks) as a direct descendant according to DocBook rules
|
399
|
+
entry_start = %(<entry align="#{cell.attr 'halign'}" valign="#{cell.attr 'valign'}"#{colspan_attribute}#{rowspan_attribute}>)
|
400
|
+
if tsec == :head
|
401
|
+
cell_content = cell.text
|
402
|
+
else
|
403
|
+
case cell.style
|
404
|
+
when :asciidoc
|
405
|
+
cell_content = cell.content
|
406
|
+
when :literal
|
407
|
+
cell_content = %(<literallayout class="monospaced">#{cell.text}</literallayout>)
|
408
|
+
when :header
|
409
|
+
cell_content = (cell_content = cell.content).empty? ? '' : %(<simpara><emphasis role="strong">#{cell_content.join '</emphasis></simpara><simpara><emphasis role="strong">'}</emphasis></simpara>)
|
410
|
+
else
|
411
|
+
cell_content = (cell_content = cell.content).empty? ? '' : %(<simpara>#{cell_content.join '</simpara><simpara>'}</simpara>)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
entry_end = (node.document.attr? 'cellbgcolor') ? %(<?dbfo bgcolor="#{node.document.attr 'cellbgcolor'}"?></entry>) : '</entry>'
|
415
|
+
result << %(#{entry_start}#{cell_content}#{entry_end})
|
416
|
+
end
|
417
|
+
result << '</row>'
|
418
|
+
end
|
419
|
+
result << %(</t#{tsec}>)
|
420
|
+
end
|
421
|
+
result << '</tgroup>'
|
422
|
+
result << %(</#{tag_name}>)
|
423
|
+
|
424
|
+
logger.warn 'tables must have at least one body row' unless has_body
|
425
|
+
result.join LF
|
426
|
+
end
|
427
|
+
|
428
|
+
alias convert_toc skip
|
429
|
+
|
430
|
+
def convert_ulist node
|
431
|
+
puts('#convert_ulist')
|
432
|
+
result = []
|
433
|
+
if node.style == 'bibliography'
|
434
|
+
result << %(<bibliodiv#{common_attributes node.id, node.role, node.reftext}>)
|
435
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
436
|
+
node.items.each do |item|
|
437
|
+
result << '<bibliomixed>'
|
438
|
+
result << %(<bibliomisc>#{item.text}</bibliomisc>)
|
439
|
+
result << item.content if item.blocks?
|
440
|
+
result << '</bibliomixed>'
|
441
|
+
end
|
442
|
+
result << '</bibliodiv>'
|
443
|
+
else
|
444
|
+
mark_type = (checklist = node.option? 'checklist') ? 'none' : node.style
|
445
|
+
mark_attribute = mark_type ? %( mark="#{mark_type}") : ''
|
446
|
+
result << %(<itemizedlist#{common_attributes node.id, node.role, node.reftext}#{mark_attribute}>)
|
447
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
448
|
+
node.items.each do |item|
|
449
|
+
text_marker = (item.attr? 'checked') ? '✓ ' : '❏ ' if checklist && (item.attr? 'checkbox')
|
450
|
+
result << %(<listitem#{common_attributes item.id, item.role}>)
|
451
|
+
result << %(<simpara>#{text_marker || ''}#{item.text}</simpara>)
|
452
|
+
result << item.content if item.blocks?
|
453
|
+
result << '</listitem>'
|
454
|
+
end
|
455
|
+
result << '</itemizedlist>'
|
456
|
+
end
|
457
|
+
result.join LF
|
458
|
+
end
|
459
|
+
|
460
|
+
def convert_verse node
|
461
|
+
blockquote_tag(node, (node.has_role? 'epigraph') && 'epigraph') { %(<literallayout>#{node.content}</literallayout>) }
|
462
|
+
end
|
463
|
+
|
464
|
+
alias convert_video skip
|
465
|
+
|
466
|
+
def convert_inline_anchor node
|
467
|
+
case node.type
|
468
|
+
when :ref
|
469
|
+
%(<anchor#{common_attributes((id = node.id), nil, node.reftext || %([#{id}]))}/>)
|
470
|
+
when :xref
|
471
|
+
if (path = node.attributes['path'])
|
472
|
+
%(<link xl:href="#{node.target}">#{node.text || path}</link>)
|
473
|
+
else
|
474
|
+
if (linkend = node.attributes['refid']).nil_or_empty?
|
475
|
+
root_doc = get_root_document node
|
476
|
+
# Q: should we warn instead of generating a document ID on demand?
|
477
|
+
linkend = (root_doc.id ||= generate_document_id root_doc)
|
478
|
+
end
|
479
|
+
# NOTE the xref tag in DocBook does not support explicit link text, so the link tag must be used instead
|
480
|
+
# The section at http://www.sagehill.net/docbookxsl/CrossRefs.html#IdrefLinks gives an explanation for this choice
|
481
|
+
# "link - a cross reference where you supply the text of the reference as the content of the link element."
|
482
|
+
(text = node.text) ? %(<link linkend="#{linkend}">#{text}</link>) : %(<xref linkend="#{linkend}"/>)
|
483
|
+
end
|
484
|
+
when :link
|
485
|
+
%(<link xl:href="#{node.target}">#{node.text}</link>)
|
486
|
+
when :bibref
|
487
|
+
%(<anchor#{common_attributes node.id, nil, (text = "[#{node.reftext || node.id}]")}/>#{text})
|
488
|
+
else
|
489
|
+
logger.warn %(unknown anchor type: #{node.type.inspect})
|
490
|
+
nil
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def convert_inline_break node
|
495
|
+
%(#{node.text}<?asciidoc-br?>)
|
496
|
+
end
|
497
|
+
|
498
|
+
def convert_inline_button node
|
499
|
+
%(<guibutton>#{node.text}</guibutton>)
|
500
|
+
end
|
501
|
+
|
502
|
+
def convert_inline_callout node
|
503
|
+
%(<co#{common_attributes node.id}/>)
|
504
|
+
end
|
505
|
+
|
506
|
+
def convert_inline_footnote node
|
507
|
+
if node.type == :xref
|
508
|
+
%(<footnoteref linkend="#{node.target}"/>)
|
509
|
+
else
|
510
|
+
%(<footnote#{common_attributes node.id}><simpara>#{node.text}</simpara></footnote>)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def convert_inline_image node
|
515
|
+
%(<inlinemediaobject#{common_attributes nil, node.role}>
|
516
|
+
<imageobject>
|
517
|
+
<imagedata fileref="#{node.type == 'icon' ? (node.icon_uri node.target) : (node.image_uri node.target)}"#{image_size_attributes node.attributes}/>
|
518
|
+
</imageobject>
|
519
|
+
<textobject><phrase>#{node.alt}</phrase></textobject>
|
520
|
+
</inlinemediaobject>)
|
521
|
+
end
|
522
|
+
|
523
|
+
def convert_inline_indexterm node
|
524
|
+
if (see = node.attr 'see')
|
525
|
+
rel = %(\n<see>#{see}</see>)
|
526
|
+
elsif (see_also_list = node.attr 'see-also')
|
527
|
+
rel = see_also_list.map {|see_also| %(\n<seealso>#{see_also}</seealso>) }.join
|
528
|
+
else
|
529
|
+
rel = ''
|
530
|
+
end
|
531
|
+
if node.type == :visible
|
532
|
+
%(<indexterm>
|
533
|
+
<primary>#{node.text}</primary>#{rel}
|
534
|
+
</indexterm>#{node.text})
|
535
|
+
elsif (numterms = (terms = node.attr 'terms').size) > 2
|
536
|
+
%(<indexterm>
|
537
|
+
<primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary><tertiary>#{terms[2]}</tertiary>#{rel}
|
538
|
+
</indexterm>#{(node.document.option? 'indexterm-promotion') ? %(
|
539
|
+
<indexterm>
|
540
|
+
<primary>#{terms[1]}</primary><secondary>#{terms[2]}</secondary>
|
541
|
+
</indexterm>
|
542
|
+
<indexterm>
|
543
|
+
<primary>#{terms[2]}</primary>
|
544
|
+
</indexterm>) : ''})
|
545
|
+
elsif numterms > 1
|
546
|
+
%(<indexterm>
|
547
|
+
<primary>#{terms[0]}</primary><secondary>#{terms[1]}</secondary>#{rel}
|
548
|
+
</indexterm>#{(node.document.option? 'indexterm-promotion') ? %(
|
549
|
+
<indexterm>
|
550
|
+
<primary>#{terms[1]}</primary>
|
551
|
+
</indexterm>) : ''})
|
552
|
+
else
|
553
|
+
%(<indexterm>
|
554
|
+
<primary>#{terms[0]}</primary>#{rel}
|
555
|
+
</indexterm>)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def convert_inline_kbd node
|
560
|
+
if (keys = node.attr 'keys').size == 1
|
561
|
+
%(<keycap>#{keys[0]}</keycap>)
|
562
|
+
else
|
563
|
+
%(<keycombo><keycap>#{keys.join '</keycap><keycap>'}</keycap></keycombo>)
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
def convert_inline_menu node
|
568
|
+
menu = node.attr 'menu'
|
569
|
+
if (submenus = node.attr 'submenus').empty?
|
570
|
+
if (menuitem = node.attr 'menuitem')
|
571
|
+
%(<menuchoice><guimenu>#{menu}</guimenu> <guimenuitem>#{menuitem}</guimenuitem></menuchoice>)
|
572
|
+
else
|
573
|
+
%(<guimenu>#{menu}</guimenu>)
|
574
|
+
end
|
575
|
+
else
|
576
|
+
%(<menuchoice><guimenu>#{menu}</guimenu> <guisubmenu>#{submenus.join '</guisubmenu> <guisubmenu>'}</guisubmenu> <guimenuitem>#{node.attr 'menuitem'}</guimenuitem></menuchoice>)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
def convert_inline_quoted node
|
581
|
+
if (type = node.type) == :asciimath
|
582
|
+
# NOTE fop requires jeuclid to process mathml markup
|
583
|
+
asciimath_available? ? %(<inlineequation>#{(::AsciiMath.parse node.text).to_mathml 'mml:', 'xmlns:mml' => 'http://www.w3.org/1998/Math/MathML'}</inlineequation>) : %(<inlineequation><mathphrase><![CDATA[#{node.text}]]></mathphrase></inlineequation>)
|
584
|
+
elsif type == :latexmath
|
585
|
+
# unhandled math; pass source to alt and required mathphrase element; dblatex will process alt as LaTeX math
|
586
|
+
%(<inlineequation><alt><![CDATA[#{equation = node.text}]]></alt><mathphrase><![CDATA[#{equation}]]></mathphrase></inlineequation>)
|
587
|
+
else
|
588
|
+
open, close, supports_phrase = QUOTE_TAGS[type]
|
589
|
+
text = node.text
|
590
|
+
if node.role
|
591
|
+
if supports_phrase
|
592
|
+
quoted_text = %(#{open}<phrase role="#{node.role}">#{text}</phrase>#{close})
|
593
|
+
else
|
594
|
+
quoted_text = %(#{open.chop} role="#{node.role}">#{text}#{close})
|
595
|
+
end
|
596
|
+
else
|
597
|
+
quoted_text = %(#{open}#{text}#{close})
|
598
|
+
end
|
599
|
+
|
600
|
+
node.id ? %(<anchor#{common_attributes node.id, nil, text}/>#{quoted_text}) : quoted_text
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
private
|
605
|
+
|
606
|
+
def common_attributes id, role = nil, reftext = nil
|
607
|
+
|
608
|
+
attrs = ''
|
609
|
+
|
610
|
+
end
|
611
|
+
|
612
|
+
def image_size_attributes attributes
|
613
|
+
|
614
|
+
if attributes.key? 'scaledwidth'
|
615
|
+
%( width="#{attributes['scaledwidth']}")
|
616
|
+
elsif attributes.key? 'scale'
|
617
|
+
|
618
|
+
%( scale="#{attributes['scale']}")
|
619
|
+
else
|
620
|
+
width_attribute = (attributes.key? 'width') ? %( contentwidth="#{attributes['width']}") : ''
|
621
|
+
depth_attribute = (attributes.key? 'height') ? %( contentdepth="#{attributes['height']}") : ''
|
622
|
+
%(#{width_attribute}#{depth_attribute})
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
def author_tag doc, author
|
627
|
+
result = []
|
628
|
+
result << '<author>'
|
629
|
+
result << '<personname>'
|
630
|
+
result << %(<firstname>#{doc.sub_replacements author.firstname}</firstname>) if author.firstname
|
631
|
+
result << %(<othername>#{doc.sub_replacements author.middlename}</othername>) if author.middlename
|
632
|
+
result << %(<surname>#{doc.sub_replacements author.lastname}</surname>) if author.lastname
|
633
|
+
result << '</personname>'
|
634
|
+
result << %(<email>#{author.email}</email>) if author.email
|
635
|
+
result << '</author>'
|
636
|
+
result.join LF
|
637
|
+
end
|
638
|
+
|
639
|
+
def document_info_tag doc, abstract
|
640
|
+
result = ['<info>']
|
641
|
+
unless doc.notitle
|
642
|
+
if (title = doc.doctitle partition: true, use_fallback: true).subtitle?
|
643
|
+
result << %(<title>#{title.main}</title>
|
644
|
+
<subtitle>#{title.subtitle}</subtitle>)
|
645
|
+
else
|
646
|
+
result << %(<title>#{title}</title>)
|
647
|
+
end
|
648
|
+
end
|
649
|
+
if (date = (doc.attr? 'revdate') ? (doc.attr 'revdate') : ((doc.attr? 'reproducible') ? nil : (doc.attr 'docdate')))
|
650
|
+
result << %(<date>#{date}</date>)
|
651
|
+
end
|
652
|
+
if doc.attr? 'copyright'
|
653
|
+
CopyrightRx =~ (doc.attr 'copyright')
|
654
|
+
result << '<copyright>'
|
655
|
+
result << %(<holder>#{$1}</holder>)
|
656
|
+
result << %(<year>#{$2}</year>) if $2
|
657
|
+
result << '</copyright>'
|
658
|
+
end
|
659
|
+
if doc.header?
|
660
|
+
unless (authors = doc.authors).empty?
|
661
|
+
if authors.size > 1
|
662
|
+
result << '<authorgroup>'
|
663
|
+
authors.each {|author| result << (author_tag doc, author) }
|
664
|
+
result << '</authorgroup>'
|
665
|
+
else
|
666
|
+
result << (author_tag doc, (author = authors[0]))
|
667
|
+
result << %(<authorinitials>#{author.initials}</authorinitials>) if author.initials
|
668
|
+
end
|
669
|
+
end
|
670
|
+
if (doc.attr? 'revdate') && ((doc.attr? 'revnumber') || (doc.attr? 'revremark'))
|
671
|
+
result << %(<revhistory>
|
672
|
+
<revision>)
|
673
|
+
result << %(<revnumber>#{doc.attr 'revnumber'}</revnumber>) if doc.attr? 'revnumber'
|
674
|
+
result << %(<date>#{doc.attr 'revdate'}</date>) if doc.attr? 'revdate'
|
675
|
+
result << %(<authorinitials>#{doc.attr 'authorinitials'}</authorinitials>) if doc.attr? 'authorinitials'
|
676
|
+
result << %(<revremark>#{doc.attr 'revremark'}</revremark>) if doc.attr? 'revremark'
|
677
|
+
result << %(</revision>
|
678
|
+
</revhistory>)
|
679
|
+
end
|
680
|
+
if (doc.attr? 'front-cover-image') || (doc.attr? 'back-cover-image')
|
681
|
+
if (back_cover_tag = cover_tag doc, 'back')
|
682
|
+
result << (cover_tag doc, 'front', true)
|
683
|
+
result << back_cover_tag
|
684
|
+
elsif (front_cover_tag = cover_tag doc, 'front')
|
685
|
+
result << front_cover_tag
|
686
|
+
end
|
687
|
+
end
|
688
|
+
result << %(<orgname>#{doc.attr 'orgname'}</orgname>) if doc.attr? 'orgname'
|
689
|
+
unless (docinfo_content = doc.docinfo).empty?
|
690
|
+
result << docinfo_content
|
691
|
+
end
|
692
|
+
end
|
693
|
+
if abstract
|
694
|
+
abstract.set_option 'root'
|
695
|
+
result << (convert abstract, abstract.node_name)
|
696
|
+
abstract.remove_attr 'root-option'
|
697
|
+
end
|
698
|
+
result << '</info>'
|
699
|
+
|
700
|
+
result.join LF
|
701
|
+
end
|
702
|
+
|
703
|
+
def find_root_abstract doc
|
704
|
+
return unless doc.blocks?
|
705
|
+
if (first_block = doc.blocks[0]).context == :preamble
|
706
|
+
return unless (first_block = first_block.blocks[0])
|
707
|
+
elsif first_block.context == :section
|
708
|
+
return first_block if first_block.sectname == 'abstract'
|
709
|
+
return unless first_block.sectname == 'preface' && (first_block = first_block.blocks[0])
|
710
|
+
end
|
711
|
+
return first_block if first_block.style == 'abstract' && first_block.context == :open
|
712
|
+
end
|
713
|
+
|
714
|
+
def extract_abstract document, abstract
|
715
|
+
parent = abstract.parent
|
716
|
+
parent = parent.parent while parent != document && parent.blocks.length == 1
|
717
|
+
parent.blocks.delete_at 0
|
718
|
+
end
|
719
|
+
|
720
|
+
def restore_abstract abstract
|
721
|
+
abstract.parent.blocks.insert 0, abstract
|
722
|
+
end
|
723
|
+
|
724
|
+
def get_root_document node
|
725
|
+
while (node = node.document).nested?
|
726
|
+
node = node.parent_document
|
727
|
+
end
|
728
|
+
node
|
729
|
+
end
|
730
|
+
|
731
|
+
def generate_document_id doc
|
732
|
+
%(__#{doc.doctype}-root__)
|
733
|
+
end
|
734
|
+
|
735
|
+
# FIXME this should be handled through a template mechanism
|
736
|
+
def enclose_content node
|
737
|
+
node.content_model == :compound ? node.content : %(<simpara>#{node.content}</simpara>)
|
738
|
+
end
|
739
|
+
|
740
|
+
def title_tag node, optional = true
|
741
|
+
!optional || node.title? ? %(<title>#{node.title}</title>\n) : ''
|
742
|
+
end
|
743
|
+
|
744
|
+
def cover_tag doc, face, use_placeholder = false
|
745
|
+
if (cover_image = doc.attr %(#{face}-cover-image))
|
746
|
+
if (cover_image.include? ':') && ImageMacroRx =~ cover_image
|
747
|
+
target, attrlist = $1, $2
|
748
|
+
cover_image = doc.image_uri target
|
749
|
+
# NOTE scalefit="1" is the default for a cover image
|
750
|
+
size_attrs = image_size_attributes (AttributeList.new attrlist).parse %w(alt width height) if attrlist
|
751
|
+
else
|
752
|
+
size_attrs = ''
|
753
|
+
end
|
754
|
+
%(<cover role="#{face}">
|
755
|
+
<mediaobject>
|
756
|
+
<imageobject>
|
757
|
+
<imagedata fileref="#{cover_image}"#{size_attrs}/>
|
758
|
+
</imageobject>
|
759
|
+
</mediaobject>
|
760
|
+
</cover>)
|
761
|
+
elsif use_placeholder
|
762
|
+
%(<cover role="#{face}"/>)
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
def blockquote_tag node, tag_name = nil
|
767
|
+
if tag_name
|
768
|
+
start_tag, end_tag = %(<#{tag_name}), %(</#{tag_name}>)
|
769
|
+
else
|
770
|
+
start_tag, end_tag = '<blockquote', '</blockquote>'
|
771
|
+
end
|
772
|
+
result = [%(#{start_tag}#{common_attributes node.id, node.role, node.reftext}>)]
|
773
|
+
result << %(<title>#{node.title}</title>) if node.title?
|
774
|
+
if (node.attr? 'attribution') || (node.attr? 'citetitle')
|
775
|
+
result << '<attribution>'
|
776
|
+
result << (node.attr 'attribution') if node.attr? 'attribution'
|
777
|
+
result << %(<citetitle>#{node.attr 'citetitle'}</citetitle>) if node.attr? 'citetitle'
|
778
|
+
result << '</attribution>'
|
779
|
+
end
|
780
|
+
result << yield
|
781
|
+
result << end_tag
|
782
|
+
result.join LF
|
783
|
+
end
|
784
|
+
|
785
|
+
def asciimath_available?
|
786
|
+
(@asciimath_status ||= load_asciimath) == :loaded
|
787
|
+
end
|
788
|
+
|
789
|
+
def load_asciimath
|
790
|
+
(defined? ::AsciiMath.parse) ? :loaded : (Helpers.require_library 'asciimath', true, :warn).nil? ? :unavailable : :loaded
|
791
|
+
end
|
792
|
+
end
|
793
|
+
end
|