kramdown-asciidoc 1.0.0.alpha.4 → 1.0.0.alpha.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +15 -0
- data/README.adoc +1 -1
- data/lib/kramdown-asciidoc.rb +1 -0
- data/lib/kramdown-asciidoc/converter.rb +297 -266
- data/lib/kramdown-asciidoc/version.rb +1 -1
- data/lib/kramdown-asciidoc/writer.rb +114 -0
- data/spec/scenarios/blockquote/deep-nested.adoc +3 -0
- data/spec/scenarios/codespan/constrained-triple.adoc +1 -0
- data/spec/scenarios/codespan/constrained-triple.md +1 -0
- data/spec/scenarios/dl/compound.adoc +11 -0
- data/spec/scenarios/{dlist → dl}/compound.md +0 -0
- data/spec/scenarios/dl/nested-mixed.adoc +11 -0
- data/spec/scenarios/{dlist → dl}/nested-mixed.md +0 -0
- data/spec/scenarios/dl/nested.adoc +15 -0
- data/spec/scenarios/{dlist → dl}/nested.md +0 -0
- data/spec/scenarios/dl/simple.adoc +9 -0
- data/spec/scenarios/dl/simple.md +10 -0
- data/spec/scenarios/entity/reverse.adoc +2 -0
- data/spec/scenarios/entity/reverse.md +2 -0
- data/spec/scenarios/p/admonition/in-blockquote.adoc +1 -0
- data/spec/scenarios/p/admonition/in-blockquote.md +1 -0
- data/spec/scenarios/p/admonition/in-compound-blockquote.adoc +5 -0
- data/spec/scenarios/p/admonition/in-compound-blockquote.md +3 -0
- data/spec/scenarios/p/admonition/plain.adoc +2 -0
- data/spec/scenarios/p/admonition/plain.md +2 -0
- data/spec/scenarios/strong/menu.adoc +1 -1
- data/spec/scenarios/table/alignment.adoc +7 -0
- data/spec/scenarios/table/alignment.md +4 -0
- data/spec/scenarios/text/nbsp.adoc +1 -0
- data/spec/scenarios/text/nbsp.md +1 -0
- data/spec/scenarios/xml_comment/line-adjacent-to-text.adoc +2 -0
- data/spec/scenarios/xml_comment/line-adjacent-to-text.md +1 -0
- metadata +39 -18
- data/spec/scenarios/dlist/compound.adoc +0 -13
- data/spec/scenarios/dlist/nested-mixed.adoc +0 -11
- data/spec/scenarios/dlist/nested.adoc +0 -23
- data/spec/scenarios/dlist/simple.adoc +0 -5
- data/spec/scenarios/dlist/simple.md +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 047f74e6cf78d83f0044f72410f8df7e5338800cf1f5796de4c919a0ff79ed36
|
4
|
+
data.tar.gz: ab08640754fff574b51a5ef5f0c400b493ff17f31d54163a33f4af5e115a64ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3170271705bb355ad77d7aa262724f07e789947b4eaf4e0c3ee09fbf48409cce910c445011f825be92c53f90e30b17d9d2562e9c627a4b0b86ca764d05748331
|
7
|
+
data.tar.gz: 6412723030a019d5c039206c5f4ada159b4c6c75f728648a7161ad989cb9990c938e56be6e48986b5c28e80cbff738b522d8c82248de1a6b7564eca4307e9652
|
data/CHANGELOG.adoc
CHANGED
@@ -5,6 +5,21 @@
|
|
5
5
|
This document provides a high-level view of the changes to {project-name} by release.
|
6
6
|
For a detailed view of what has changed, refer to the {uri-repo}/commits/master[commit history] on GitHub.
|
7
7
|
|
8
|
+
== 1.0.0.alpha.5 (2018-06-19) - @mojavelinux
|
9
|
+
|
10
|
+
=== Added
|
11
|
+
|
12
|
+
* recognize Hint as admonition label; map to TIP
|
13
|
+
* replace no-break space with \{nbsp}
|
14
|
+
|
15
|
+
=== Changed
|
16
|
+
|
17
|
+
* rewrite converter to use a structured writer
|
18
|
+
* remove blockquote enclosure around simple admonition block
|
19
|
+
* revert \& back to &
|
20
|
+
* use separate list level for dl
|
21
|
+
* fold description list item to one line if primary text is a single line
|
22
|
+
|
8
23
|
== 1.0.0.alpha.4 (2018-06-12) - @mojavelinux
|
9
24
|
|
10
25
|
=== Added
|
data/README.adoc
CHANGED
data/lib/kramdown-asciidoc.rb
CHANGED
@@ -10,15 +10,6 @@ module Kramdown; module AsciiDoc
|
|
10
10
|
TocDirectiveTip = '<!-- TOC '
|
11
11
|
TocDirectiveRx = /^<!-- TOC .*<!-- \/TOC -->/m
|
12
12
|
|
13
|
-
def self.replace_toc source, attributes
|
14
|
-
if source.include? TocDirectiveTip
|
15
|
-
attributes['toc'] = 'macro'
|
16
|
-
source.gsub TocDirectiveRx, 'toc::[]'
|
17
|
-
else
|
18
|
-
source
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
13
|
# TODO return original source if YAML can't be parsed
|
23
14
|
def self.extract_front_matter source, attributes
|
24
15
|
if (line_i = (lines = source.each_line).first) && line_i.chomp == '---'
|
@@ -42,12 +33,23 @@ module Kramdown; module AsciiDoc
|
|
42
33
|
end
|
43
34
|
end
|
44
35
|
|
36
|
+
def self.replace_toc source, attributes
|
37
|
+
if source.include? TocDirectiveTip
|
38
|
+
attributes['toc'] = 'macro'
|
39
|
+
source.gsub TocDirectiveRx, 'toc::[]'
|
40
|
+
else
|
41
|
+
source
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
45
|
class Converter < ::Kramdown::Converter::Base
|
46
|
-
RESOLVE_ENTITY_TABLE = { 60 => '<', 62 => '>', 124 => '|' }
|
47
|
-
ADMON_LABELS = %w(Note Tip Caution Warning Important Attention).map {|l| [l, l] }.to_h
|
46
|
+
RESOLVE_ENTITY_TABLE = { 38 => '&', 60 => '<', 62 => '>', 124 => '|' }
|
47
|
+
ADMON_LABELS = %w(Note Tip Caution Warning Important Attention Hint).map {|l| [l, l] }.to_h
|
48
48
|
ADMON_MARKERS = ADMON_LABELS.map {|l, _| %(#{l}: ) }
|
49
|
+
ADMON_MARKERS_ASCIIDOC = %w(NOTE TIP CAUTION WARNING IMPORTANT).map {|l| %(#{l}: ) }
|
49
50
|
ADMON_FORMATTED_MARKERS = ADMON_LABELS.map {|l, _| [%(#{l}:), l] }.to_h
|
50
|
-
ADMON_TYPE_MAP = ADMON_LABELS.map {|l, _| [l, l.upcase] }.to_h.merge 'Attention' => 'IMPORTANT'
|
51
|
+
ADMON_TYPE_MAP = ADMON_LABELS.map {|l, _| [l, l.upcase] }.to_h.merge 'Attention' => 'IMPORTANT', 'Hint' => 'TIP'
|
52
|
+
BLOCK_TYPES = [:p, :blockquote, :codeblock]
|
51
53
|
DLIST_MARKERS = %w(:: ;; ::: ::::)
|
52
54
|
# FIXME here we reverse the smart quotes; add option to allow them (needs to be handled carefully)
|
53
55
|
SMART_QUOTE_ENTITY_TO_MARKUP = { ldquo: ?", rdquo: ?", lsquo: ?', rsquo: ?' }
|
@@ -77,12 +79,13 @@ module Kramdown; module AsciiDoc
|
|
77
79
|
right: '>',
|
78
80
|
}
|
79
81
|
|
80
|
-
|
82
|
+
NON_DEFAULT_TABLE_ALIGNMENTS = [:center, :right]
|
83
|
+
|
81
84
|
CommentPrefixRx = /^ *! ?/m
|
82
85
|
CssPropDelimRx = /\s*;\s*/
|
83
86
|
MenuRefRx = /^([\p{Word}&].*?)\s>\s([\p{Word}&].*(?:\s>\s|$))+/
|
84
87
|
ReplaceableTextRx = /[-=]>|<[-=]|\.\.\./
|
85
|
-
|
88
|
+
SmartApostropheRx = /\b’\b/
|
86
89
|
TrailingSpaceRx = / +$/
|
87
90
|
TypographicSymbolRx = /[“”‘’—–…]/
|
88
91
|
XmlCommentRx = /\A<!--(.*)-->\Z/m
|
@@ -90,14 +93,12 @@ module Kramdown; module AsciiDoc
|
|
90
93
|
VoidElement = Element.new nil
|
91
94
|
|
92
95
|
LF = ?\n
|
93
|
-
LFx2 = LF * 2
|
94
96
|
|
95
97
|
def initialize root, opts
|
96
98
|
super
|
97
|
-
@header = []
|
98
99
|
@attributes = opts[:attributes] || {}
|
99
100
|
@imagesdir = (@attributes.delete 'implicit-imagesdir') || @attributes['imagesdir']
|
100
|
-
@
|
101
|
+
@current_heading_level = nil
|
101
102
|
end
|
102
103
|
|
103
104
|
def convert el, opts = {}
|
@@ -105,29 +106,27 @@ module Kramdown; module AsciiDoc
|
|
105
106
|
end
|
106
107
|
|
107
108
|
def convert_root el, opts
|
108
|
-
|
109
|
-
|
109
|
+
writer = Writer.new
|
110
|
+
el = extract_prologue el, (opts.merge writer: writer)
|
111
|
+
traverse el, (opts.merge writer: writer)
|
110
112
|
if (fallback_doctitle = @attributes.delete 'title')
|
111
|
-
|
113
|
+
writer.doctitle ||= fallback_doctitle
|
112
114
|
end
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
def convert_blank el, opts
|
118
|
-
nil
|
115
|
+
writer.add_attributes @attributes unless @attributes.empty?
|
116
|
+
writer.to_s.gsub TrailingSpaceRx, ''
|
119
117
|
end
|
120
118
|
|
121
119
|
def convert_heading el, opts
|
122
|
-
|
123
|
-
style = []
|
120
|
+
(writer = opts[:writer]).start_block
|
124
121
|
level = el.options[:level]
|
125
|
-
|
122
|
+
style = []
|
123
|
+
# Q: should writer track last heading level?
|
124
|
+
if (discrete = @current_heading_level && level > @current_heading_level + 1)
|
126
125
|
# TODO make block title promotion an option (allow certain levels and titles)
|
127
|
-
#if ((raw_text = el.options[:raw_text]) == 'Example' || raw_text == 'Examples') &&
|
128
126
|
if level == 5 && (next_2_siblings = (siblings = opts[:parent].children).slice (siblings.index el) + 1, 2) &&
|
129
127
|
next_2_siblings.any? {|sibling| sibling.type == :codeblock }
|
130
|
-
|
128
|
+
writer.add_line %(.#{compose_text el, strip: true})
|
129
|
+
return
|
131
130
|
end
|
132
131
|
style << 'discrete'
|
133
132
|
end
|
@@ -139,39 +138,36 @@ module Kramdown; module AsciiDoc
|
|
139
138
|
elsif (role = el.attr['class'])
|
140
139
|
style << %(.#{role.tr ' ', '.'})
|
141
140
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
if level == 1 &&
|
146
|
-
|
141
|
+
lines = []
|
142
|
+
lines << %([#{style.join}]) unless style.empty?
|
143
|
+
lines << %(#{'=' * level} #{compose_text el, strip: true})
|
144
|
+
if level == 1 && writer.empty? && @current_heading_level != 1
|
145
|
+
writer.header.push(*lines)
|
147
146
|
nil
|
148
147
|
else
|
149
148
|
@attributes['doctype'] = 'book' if level == 1
|
150
|
-
|
149
|
+
writer.add_lines lines
|
151
150
|
end
|
151
|
+
@current_heading_level = level unless discrete
|
152
|
+
nil
|
152
153
|
end
|
153
154
|
|
154
155
|
# Kramdown incorrectly uses the term header for headings
|
155
156
|
alias convert_header convert_heading
|
156
157
|
|
158
|
+
def convert_blank el, opts
|
159
|
+
end
|
160
|
+
|
157
161
|
def convert_p el, opts
|
158
|
-
|
159
|
-
# NOTE :prev option not set indicates primary text; convert_li appends LF
|
160
|
-
return inner el, opts unless opts[:prev]
|
161
|
-
parent.options[:compound] = true
|
162
|
-
opts[:result].pop unless opts[:result][-1]
|
163
|
-
prefix, suffix = %(#{LF}+#{LF}), ''
|
164
|
-
else
|
165
|
-
prefix, suffix = '', LFx2
|
166
|
-
end
|
162
|
+
(writer = opts[:writer]).start_block
|
167
163
|
if (children = el.children).empty?
|
168
|
-
|
164
|
+
lines = ['{blank}']
|
169
165
|
# NOTE detect plain admonition marker (e.g, Note: ...)
|
166
|
+
# TODO these conditionals could be optimized
|
170
167
|
elsif (child_i = children[0]).type == :text && (child_i_text = child_i.value).start_with?(*ADMON_MARKERS)
|
171
168
|
marker, child_i_text = child_i_text.split ': ', 2
|
172
|
-
|
173
|
-
|
174
|
-
contents = inner el, opts
|
169
|
+
children = [(clone child_i, value: %(#{ADMON_TYPE_MAP[marker]}: #{child_i_text}))] + (children.drop 1)
|
170
|
+
lines = compose_text children, parent: el, strip: true, split: true
|
175
171
|
# NOTE detect formatted admonition marker (e.g., *Note:* ...)
|
176
172
|
elsif (child_i.type == :strong || child_i.type == :em) &&
|
177
173
|
(marker_el = child_i.children[0]) && ((marker = ADMON_FORMATTED_MARKERS[marker_el.value]) ||
|
@@ -179,329 +175,345 @@ module Kramdown; module AsciiDoc
|
|
179
175
|
((child_ii_text = child_ii.value).start_with? ': ')))
|
180
176
|
children = children.drop 1
|
181
177
|
children[0] = clone child_ii, value: (child_ii_text.slice 1, child_ii_text.length) if child_ii
|
182
|
-
|
183
|
-
|
178
|
+
# Q: should we only rstrip?
|
179
|
+
lines = compose_text children, parent: el, strip: true, split: true
|
180
|
+
lines.unshift %(#{ADMON_TYPE_MAP[marker]}: #{lines.shift})
|
184
181
|
else
|
185
|
-
|
182
|
+
lines = compose_text el, strip: true, split: true
|
186
183
|
end
|
187
|
-
|
184
|
+
writer.add_lines lines
|
188
185
|
end
|
189
186
|
|
190
|
-
#
|
187
|
+
# Q: should we delete blank line between blocks in a nested conversation?
|
188
|
+
# TODO use shorthand for blockquote when contents is paragraph only; or always?
|
191
189
|
def convert_blockquote el, opts
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
(writer = opts[:writer]).start_block
|
191
|
+
traverse el, (opts.merge writer: (block_writer = Writer.new), blockquote_depth: (depth = opts[:blockquote_depth] || 0) + 1)
|
192
|
+
contents = block_writer.body
|
193
|
+
if contents[0].start_with?(*ADMON_MARKERS_ASCIIDOC) && !(contents.include? '')
|
194
|
+
writer.add_lines contents
|
197
195
|
else
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
contents = lines.join LF
|
210
|
-
end
|
211
|
-
result << boundary
|
212
|
-
result << contents
|
213
|
-
result << boundary
|
214
|
-
result.unshift list_continuation if list_continuation
|
215
|
-
%(#{result.join LF}#{suffix})
|
196
|
+
if contents.size > 1 && (contents[-1].start_with? '-- ')
|
197
|
+
attribution = (attribution_line = contents.pop).slice 3, attribution_line.length
|
198
|
+
writer.add_line %([,#{attribution}])
|
199
|
+
contents.pop while contents.size > 0 && contents[-1].empty?
|
200
|
+
end
|
201
|
+
# Q: should writer handle delimited block nesting?
|
202
|
+
delimiter = '____' + (depth > 0 ? '__' * depth : '')
|
203
|
+
writer.start_delimited_block delimiter
|
204
|
+
writer.add_lines contents
|
205
|
+
writer.end_delimited_block
|
206
|
+
end
|
216
207
|
end
|
217
208
|
|
209
|
+
# TODO match logic from ditarx
|
218
210
|
def convert_codeblock el, opts
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
opts[:result] << current_line.chomp
|
224
|
-
end unless opts[:result].empty?
|
225
|
-
list_continuation = %(#{LF}+)
|
226
|
-
suffix = ''
|
227
|
-
else
|
228
|
-
suffix = LFx2
|
211
|
+
writer = opts[:writer]
|
212
|
+
# NOTE hack to down-convert level-5 heading to block title
|
213
|
+
if (current_line = writer.current_line) && (!(current_line.start_with? '.') || (current_line.start_with? '. '))
|
214
|
+
writer.start_block
|
229
215
|
end
|
230
|
-
|
216
|
+
lines = el.value.rstrip.split LF
|
231
217
|
if (lang = el.attr['class'])
|
232
218
|
# NOTE Kramdown always prefixes class with language-
|
233
219
|
# TODO remap lang if requested
|
234
|
-
|
235
|
-
elsif (prompt =
|
236
|
-
|
220
|
+
writer.add_line %([source,#{lang = lang.slice 9, lang.length}])
|
221
|
+
elsif (prompt = lines[0].start_with? '$ ')
|
222
|
+
writer.add_line %([source,#{lang = 'console'}]) if lines.include? ''
|
237
223
|
end
|
238
224
|
if lang || (el.options[:fenced] && !prompt)
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
elsif !prompt && (
|
243
|
-
|
244
|
-
|
245
|
-
|
225
|
+
writer.add_line '----'
|
226
|
+
writer.add_lines lines
|
227
|
+
writer.add_line '----'
|
228
|
+
elsif !prompt && (lines.include? '')
|
229
|
+
writer.add_line '....'
|
230
|
+
writer.add_lines lines
|
231
|
+
writer.add_line '....'
|
246
232
|
else
|
247
|
-
|
248
|
-
|
233
|
+
# NOTE clear the list continuation (is the condition necessary?)
|
234
|
+
writer.clear_line if writer.current_line == '+'
|
235
|
+
writer.add_line lines.map {|l| %( #{l}) }
|
249
236
|
end
|
250
|
-
result.unshift list_continuation if list_continuation
|
251
|
-
%(#{result.join LF}#{suffix})
|
252
237
|
end
|
253
238
|
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
239
|
+
def convert_img el, opts
|
240
|
+
if !(parent = opts[:parent]) || parent.type == :p && parent.children.size == 1
|
241
|
+
style = []
|
242
|
+
if (id = el.attr['id'])
|
243
|
+
style << %(##{id})
|
244
|
+
end
|
245
|
+
if (role = el.attr['class'])
|
246
|
+
style << %(.#{role.tr ' ', '.'})
|
247
|
+
end
|
248
|
+
block_attributes_line = %([#{style.join}]) unless style.empty?
|
249
|
+
block = true
|
250
|
+
end
|
251
|
+
macro_attrs = [nil]
|
252
|
+
if (alt_text = el.attr['alt'])
|
253
|
+
macro_attrs[0] = alt_text unless alt_text.empty?
|
254
|
+
end
|
255
|
+
if (width = el.attr['width'])
|
256
|
+
macro_attrs << width
|
257
|
+
elsif (css = el.attr['style']) && (width_css = (css.split CssPropDelimRx).find {|p| p.start_with? 'width:' })
|
258
|
+
width = (width_css.slice (width_css.index ':') + 1, width_css.length).strip
|
259
|
+
width = width.to_f.round unless width.end_with? '%'
|
260
|
+
macro_attrs << width
|
261
|
+
end
|
262
|
+
if macro_attrs.size == 1 && (alt_text = macro_attrs.pop)
|
263
|
+
macro_attrs << alt_text
|
264
|
+
end
|
265
|
+
if (url = opts[:url])
|
266
|
+
macro_attrs << %(link=#{url})
|
267
|
+
end
|
268
|
+
src = el.attr['src']
|
269
|
+
if (imagesdir = @imagesdir) && (src.start_with? %(#{imagesdir}/))
|
270
|
+
src = src.slice imagesdir.length + 1, src.length
|
262
271
|
end
|
263
|
-
|
264
|
-
if
|
265
|
-
|
266
|
-
|
272
|
+
writer = opts[:writer]
|
273
|
+
if block
|
274
|
+
writer.start_block
|
275
|
+
writer.add_line block_attributes_line if block_attributes_line
|
276
|
+
writer.add_line %(image::#{src}[#{macro_attrs.join ','}])
|
267
277
|
else
|
268
|
-
|
269
|
-
opts[:list_level] -= 1
|
278
|
+
writer.append %(image:#{src}[#{macro_attrs.join ','}])
|
270
279
|
end
|
271
|
-
|
280
|
+
end
|
281
|
+
|
282
|
+
def convert_ul el, opts
|
283
|
+
nested = (parent = opts[:parent]) && (parent.type == :li || parent.type == :dd)
|
284
|
+
(writer = opts[:writer]).start_list nested && parent.type != :dd && !parent.options[:compound]
|
285
|
+
level_opt = el.type == :dl ? :dlist_level : :list_level
|
286
|
+
level = opts[level_opt] ? (opts[level_opt] += 1) : (opts[level_opt] = 1)
|
287
|
+
traverse el, opts
|
288
|
+
opts.delete level_opt if (opts[level_opt] -= 1) < 1
|
289
|
+
writer.end_list nested
|
272
290
|
end
|
273
291
|
|
274
292
|
alias convert_ol convert_ul
|
275
293
|
alias convert_dl convert_ul
|
276
294
|
|
277
295
|
def convert_li el, opts
|
278
|
-
|
296
|
+
writer = opts[:writer]
|
297
|
+
writer.add_blank_line if (prev = opts[:prev]) && prev.options[:compound]
|
279
298
|
marker = opts[:parent].type == :ol ? '.' : '*'
|
280
299
|
indent = (level = opts[:list_level]) - 1
|
281
|
-
|
300
|
+
primary, remaining = [(children = el.children.dup).shift, children]
|
301
|
+
lines = compose_text [primary], parent: el, strip: true, split: true
|
302
|
+
lines.unshift %(#{indent > 0 ? ' ' * indent : ''}#{marker * level} #{lines.shift})
|
303
|
+
writer.add_lines lines
|
304
|
+
unless remaining.empty?
|
305
|
+
next_node = remaining.find {|n| n.type != :blank }
|
306
|
+
el.options[:compound] = true if next_node && (BLOCK_TYPES.include? next_node.type)
|
307
|
+
traverse remaining, (opts.merge parent: el)
|
308
|
+
end
|
282
309
|
end
|
283
310
|
|
284
311
|
def convert_dt el, opts
|
285
|
-
|
286
|
-
marker = DLIST_MARKERS[opts[:
|
287
|
-
|
312
|
+
term = compose_text el, strip: true
|
313
|
+
marker = DLIST_MARKERS[opts[:dlist_level] - 1]
|
314
|
+
#opts[:writer].add_blank_line if (prev = opts[:prev]) && prev.options[:compound]
|
315
|
+
opts[:writer].add_blank_line if opts[:prev]
|
316
|
+
opts[:writer].add_line %(#{term}#{marker})
|
288
317
|
end
|
289
318
|
|
290
319
|
def convert_dd el, opts
|
291
|
-
|
320
|
+
primary, remaining = [(children = el.children.dup).shift, children]
|
321
|
+
primary_lines = compose_text [primary], parent: el, strip: true, split: true
|
322
|
+
if primary_lines.size == 1
|
323
|
+
opts[:writer].append %( #{primary_lines[0]})
|
324
|
+
else
|
325
|
+
el.options[:compound] = true
|
326
|
+
opts[:writer].add_lines primary_lines
|
327
|
+
end
|
328
|
+
unless remaining.empty?
|
329
|
+
next_node = remaining.find {|n| n.type != :blank }
|
330
|
+
el.options[:compound] = true if next_node && (BLOCK_TYPES.include? next_node.type)
|
331
|
+
traverse remaining, (opts.merge parent: el)
|
332
|
+
end
|
292
333
|
end
|
293
334
|
|
294
335
|
def convert_table el, opts
|
295
336
|
head = nil
|
296
337
|
cols = (alignments = el.options[:alignment]).size
|
297
|
-
if alignments.any? {|align|
|
338
|
+
if alignments.any? {|align| NON_DEFAULT_TABLE_ALIGNMENTS.include? align }
|
298
339
|
colspecs = alignments.map {|align| TABLE_ALIGNMENTS[align] }.join ','
|
299
340
|
colspecs = %("#{colspecs}") if cols > 1
|
300
341
|
end
|
301
|
-
|
342
|
+
table_buffer = ['|===']
|
302
343
|
el.children.each do |container|
|
303
344
|
container.children.each do |row|
|
304
|
-
|
345
|
+
row_buffer = []
|
305
346
|
row.children.each do |cell|
|
306
|
-
|
347
|
+
# TODO if using sentence-per-line, append to row_buffer as separate lines
|
348
|
+
cell_contents = compose_text cell
|
307
349
|
cell_contents = cell_contents.gsub '|', '\|' if cell_contents.include? '|'
|
308
|
-
|
350
|
+
row_buffer << %(| #{cell_contents})
|
309
351
|
end
|
310
352
|
if container.type == :thead
|
311
353
|
head = true
|
312
|
-
|
354
|
+
row_buffer = [row_buffer * ' ', '']
|
313
355
|
elsif cols > 1
|
314
|
-
|
356
|
+
row_buffer << ''
|
315
357
|
end
|
316
|
-
|
358
|
+
table_buffer.concat row_buffer
|
317
359
|
end
|
318
360
|
end
|
361
|
+
table_buffer.pop if table_buffer[-1] == ''
|
362
|
+
table_buffer << '|==='
|
363
|
+
(writer = opts[:writer]).start_block
|
319
364
|
if colspecs
|
320
|
-
|
365
|
+
writer.add_line %([cols=#{colspecs}])
|
321
366
|
elsif !head && cols > 1
|
322
|
-
|
367
|
+
writer.add_line %([cols=#{cols}*])
|
323
368
|
end
|
324
|
-
|
325
|
-
table_buf << '|==='
|
326
|
-
%(#{table_buf * LF}#{LFx2})
|
369
|
+
opts[:writer].add_lines table_buffer
|
327
370
|
end
|
328
371
|
|
329
372
|
def convert_hr el, opts
|
330
|
-
|
373
|
+
(writer = opts[:writer]).start_block
|
374
|
+
writer.add_line '\'\'\''
|
331
375
|
end
|
332
376
|
|
333
|
-
def
|
334
|
-
if (
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
377
|
+
def convert_a el, opts
|
378
|
+
if (url = el.attr['href']).start_with? '#'
|
379
|
+
opts[:writer].append %(<<#{url.slice 1, url.length},#{compose_text el, strip: true}>>)
|
380
|
+
elsif url.start_with? 'https://', 'http://'
|
381
|
+
if (children = el.children).size == 1 && (child_i = el.children[0]).type == :img
|
382
|
+
convert_img child_i, parent: opts[:parent], index: 0, url: url, writer: opts[:writer]
|
383
|
+
else
|
384
|
+
bare = ((text = compose_text el, strip: true).chomp '/') == (url.chomp '/')
|
385
|
+
url = url.gsub '__', '%5F%5F' if (url.include? '__')
|
386
|
+
opts[:writer].append bare ? url : %(#{url}[#{text.gsub ']', '\]'}])
|
387
|
+
end
|
388
|
+
elsif url.end_with? '.md'
|
389
|
+
opts[:writer].append %(xref:#{url.slice 0, url.length - 3}.adoc[#{(compose_text el, strip: true).gsub ']', '\]'}])
|
342
390
|
else
|
343
|
-
|
391
|
+
opts[:writer].append %(link:#{url}[#{(compose_text el, strip: true).gsub ']', '\]'}])
|
344
392
|
end
|
345
|
-
end
|
393
|
+
end
|
346
394
|
|
347
395
|
def convert_codespan el, opts
|
348
|
-
(val = el.value) =~ ReplaceableTextRx ? %(`+#{val}+`) : %(`#{val}`)
|
396
|
+
opts[:writer].append (val = el.value) =~ ReplaceableTextRx ? %(`+#{val}+`) : %(`#{val}`)
|
349
397
|
end
|
350
398
|
|
351
399
|
def convert_em el, opts
|
352
|
-
%(_#{
|
400
|
+
opts[:writer].append %(_#{compose_text el}_)
|
353
401
|
end
|
354
402
|
|
355
403
|
def convert_strong el, opts
|
356
|
-
|
357
|
-
if (
|
404
|
+
text = compose_text el
|
405
|
+
if (text.include? ' > ') && MenuRefRx =~ text
|
358
406
|
@attributes['experimental'] = ''
|
359
|
-
%(menu:#{$1}[#{$2}])
|
407
|
+
opts[:writer].append %(menu:#{$1}[#{$2}])
|
360
408
|
else
|
361
|
-
%(*#{
|
409
|
+
opts[:writer].append %(*#{text}*)
|
362
410
|
end
|
363
411
|
end
|
364
412
|
|
413
|
+
def convert_text el, opts
|
414
|
+
if (text = el.value).include? '++'
|
415
|
+
@attributes['pp'] = '{plus}{plus}'
|
416
|
+
text = text.gsub '++', '{pp}'
|
417
|
+
end
|
418
|
+
# Q: should we replace with single space instead?
|
419
|
+
text = text.gsub ' ', '{nbsp}' if text.include? ' '
|
420
|
+
text = text.gsub '^', '{caret}' if (text.include? '^') && text != '^'
|
421
|
+
text = text.gsub '<=', '\<=' if text.include? '<='
|
422
|
+
unless text.ascii_only?
|
423
|
+
text = (text.gsub SmartApostropheRx, ?').gsub TypographicSymbolRx, TYPOGRAPHIC_SYMBOL_TO_MARKUP
|
424
|
+
end
|
425
|
+
opts[:writer].append text
|
426
|
+
end
|
427
|
+
|
365
428
|
# NOTE this logic assumes the :hard_wrap option is disabled in the parser
|
366
429
|
def convert_br el, opts
|
367
|
-
|
368
|
-
|
430
|
+
writer = opts[:writer]
|
431
|
+
if writer.empty?
|
432
|
+
writer.append '{blank} +'
|
369
433
|
else
|
370
|
-
|
434
|
+
writer.append %(#{(writer.current_line.end_with? ' ') ? '' : ' '}+)
|
371
435
|
end
|
372
436
|
if el.options[:html_tag]
|
373
437
|
siblings = opts[:parent].children
|
374
|
-
|
375
|
-
|
376
|
-
|
438
|
+
unless (next_el = siblings[(siblings.index el) + 1] || VoidElement).type == :text && (next_el.value.start_with? LF)
|
439
|
+
writer.add_blank_line
|
440
|
+
end
|
377
441
|
end
|
378
|
-
%(#{prefix}+#{suffix})
|
379
|
-
end
|
380
|
-
|
381
|
-
def convert_smart_quote el, opts
|
382
|
-
SMART_QUOTE_ENTITY_TO_MARKUP[el.value]
|
383
442
|
end
|
384
443
|
|
385
444
|
def convert_entity el, opts
|
386
|
-
RESOLVE_ENTITY_TABLE[el.value.code_point] || el.options[:original]
|
445
|
+
opts[:writer].append RESOLVE_ENTITY_TABLE[el.value.code_point] || el.options[:original]
|
387
446
|
end
|
388
447
|
|
389
|
-
def
|
390
|
-
|
391
|
-
%(<<#{url.slice 1, url.length},#{inner el, opts}>>)
|
392
|
-
elsif url.start_with? 'https://', 'http://'
|
393
|
-
if (children = el.children).size == 1 && (child_i = el.children[0]).type == :img
|
394
|
-
convert_img child_i, parent: opts[:parent], index: 0, url: url
|
395
|
-
else
|
396
|
-
bare = ((contents = inner el, opts).chomp '/') == (url.chomp '/')
|
397
|
-
url = url.gsub '__', '%5F%5F' if (url.include? '__')
|
398
|
-
bare ? url : %(#{url}[#{contents.gsub ']', '\]'}])
|
399
|
-
end
|
400
|
-
elsif url.end_with? '.md'
|
401
|
-
%(xref:#{url.slice 0, url.length - 3}.adoc[#{(inner el, opts).gsub ']', '\]'}])
|
402
|
-
else
|
403
|
-
%(link:#{url}[#{(inner el, opts).gsub ']', '\]'}])
|
404
|
-
end
|
405
|
-
end
|
406
|
-
|
407
|
-
def convert_img el, opts
|
408
|
-
if !(parent = opts[:parent]) || parent.type == :p && parent.children.size == 1
|
409
|
-
style = []
|
410
|
-
if (id = el.attr['id'])
|
411
|
-
style << %(##{id})
|
412
|
-
end
|
413
|
-
if (role = el.attr['class'])
|
414
|
-
style << %(.#{role.tr ' ', '.'})
|
415
|
-
end
|
416
|
-
macro_prefix = style.empty? ? 'image::' : ([%([#{style.join}]), 'image::'].join LF)
|
417
|
-
else
|
418
|
-
macro_prefix = 'image:'
|
419
|
-
end
|
420
|
-
macro_attrs = [nil]
|
421
|
-
if (alt_text = el.attr['alt'])
|
422
|
-
macro_attrs[0] = alt_text unless alt_text.empty?
|
423
|
-
end
|
424
|
-
if (width = el.attr['width'])
|
425
|
-
macro_attrs << width
|
426
|
-
elsif (css = el.attr['style']) && (width_css = (css.split CssPropDelimRx).find {|p| p.start_with? 'width:' })
|
427
|
-
width = (width_css.slice (width_css.index ':') + 1, width_css.length).strip
|
428
|
-
width = width.to_f.round unless width.end_with? '%'
|
429
|
-
macro_attrs << width
|
430
|
-
end
|
431
|
-
if macro_attrs.size == 1 && (alt_text = macro_attrs.pop)
|
432
|
-
macro_attrs << alt_text
|
433
|
-
end
|
434
|
-
if (url = opts[:url])
|
435
|
-
macro_attrs << %(link=#{url})
|
436
|
-
end
|
437
|
-
src = el.attr['src']
|
438
|
-
if (imagesdir = @imagesdir) && (src.start_with? %(#{imagesdir}/))
|
439
|
-
src = src.slice imagesdir.length + 1, src.length
|
440
|
-
end
|
441
|
-
%(#{macro_prefix}#{src}[#{macro_attrs.join ','}])
|
448
|
+
def convert_smart_quote el, opts
|
449
|
+
opts[:writer].append SMART_QUOTE_ENTITY_TO_MARKUP[el.value]
|
442
450
|
end
|
443
451
|
|
444
452
|
# NOTE leave enabled so we can down-convert mdash to --
|
445
453
|
def convert_typographic_sym el, opts
|
446
|
-
TYPOGRAPHIC_ENTITY_TO_MARKUP[el.value]
|
454
|
+
opts[:writer].append TYPOGRAPHIC_ENTITY_TO_MARKUP[el.value]
|
447
455
|
end
|
448
456
|
|
449
457
|
def convert_html_element el, opts
|
450
|
-
if (
|
451
|
-
(child_i_i = child_i.children[0])
|
458
|
+
if (tag = el.value) == 'div' && (child_i = el.children[0]) && child_i.options[:transparent] && (child_i_i = child_i.children[0])
|
452
459
|
if child_i_i.type == :img
|
453
|
-
|
454
|
-
|
455
|
-
|
460
|
+
convert_img child_i_i, (opts.merge parent: child_i, index: 0) if child_i.children.size == 1
|
461
|
+
return
|
462
|
+
elsif child_i_i.value == 'span' && ((role = el.attr['class'] || '').start_with? 'note') && child_i_i.attr['class'] == 'notetitle'
|
456
463
|
marker = ADMON_FORMATTED_MARKERS[(child_i_i.children[0] || VoidElement).value] || 'Note'
|
457
|
-
|
458
|
-
|
464
|
+
lines = compose_text (child_i.children.drop 1), parent: child_i, strip: true, split: true
|
465
|
+
lines.unshift %(#{ADMON_TYPE_MAP[marker]}: #{lines.shift})
|
466
|
+
opts[:writer].start_block
|
467
|
+
opts[:writer].add_lines lines
|
468
|
+
return
|
459
469
|
end
|
460
470
|
end
|
461
|
-
|
471
|
+
|
472
|
+
contents = compose_text el, (opts.merge strip: el.options[:category] == :block)
|
462
473
|
attrs = (attrs = el.attr).empty? ? '' : attrs.map {|k, v| %( #{k}="#{v}") }.join
|
463
|
-
case
|
474
|
+
case tag
|
464
475
|
when 'del'
|
465
|
-
%([.line-through]##{contents}#)
|
476
|
+
opts[:writer].append %([.line-through]##{contents}#)
|
466
477
|
when 'sup'
|
467
|
-
%(^#{contents}^)
|
478
|
+
opts[:writer].append %(^#{contents}^)
|
468
479
|
when 'sub'
|
469
|
-
%(~#{contents}~)
|
480
|
+
opts[:writer].append %(~#{contents}~)
|
470
481
|
else
|
471
|
-
%(+++<#{
|
482
|
+
opts[:writer].append %(+++<#{tag}#{attrs}>+++#{contents}+++</#{tag}>+++)
|
472
483
|
end
|
473
484
|
end
|
474
485
|
|
475
486
|
def convert_xml_comment el, opts
|
487
|
+
writer = opts[:writer]
|
476
488
|
XmlCommentRx =~ el.value
|
477
|
-
|
489
|
+
lines = (($1.include? ' !') ? ($1.gsub CommentPrefixRx, '').strip : $1.strip).split LF
|
478
490
|
#siblings = (parent = opts[:parent]) ? parent.children : []
|
479
491
|
if (el.options[:category] == :block)# || (!opts[:result][-1] && siblings[-1] == el)
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
492
|
+
writer.start_block
|
493
|
+
if lines.empty?
|
494
|
+
writer.add_line '//'
|
495
|
+
# Q: should we only use block form if empty line is present?
|
496
|
+
elsif lines.size > 1
|
497
|
+
writer.add_line '////'
|
498
|
+
writer.add_lines lines
|
499
|
+
writer.add_line '////'
|
484
500
|
else
|
485
|
-
%(// #{
|
501
|
+
writer.add_line %(// #{lines[0]})
|
486
502
|
end
|
487
503
|
else
|
488
|
-
if (current_line =
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
prefix = LF
|
493
|
-
opts[:result][-1] = (current_line = current_line.rstrip) if current_line.end_with? ' '
|
494
|
-
end
|
495
|
-
else
|
496
|
-
prefix = ''
|
504
|
+
if (current_line = writer.current_line) && !(current_line.end_with? LF)
|
505
|
+
start_new_line = true
|
506
|
+
# FIXME cleaner API here (writer#strip_line?)
|
507
|
+
writer.current_line.rstrip! if current_line.end_with? ' '
|
497
508
|
end
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
%(#{prefix}#{comment_text.gsub StartOfLinesRx, '// '}#{suffix})
|
509
|
+
lines = lines.map {|l| %(// #{l}) }
|
510
|
+
if start_new_line
|
511
|
+
writer.add_lines lines
|
502
512
|
else
|
503
|
-
|
513
|
+
writer.append lines.shift
|
514
|
+
writer.add_lines lines unless lines.empty?
|
504
515
|
end
|
516
|
+
writer.add_blank_line
|
505
517
|
end
|
506
518
|
end
|
507
519
|
|
@@ -509,22 +521,12 @@ module Kramdown; module AsciiDoc
|
|
509
521
|
if (child_i = (children = el.children)[0] || VoidElement).type == :xml_comment
|
510
522
|
(prologue_el = el.dup).children = children.take_while {|child| child.type == :xml_comment || child.type == :blank }
|
511
523
|
(el = el.dup).children = children.drop prologue_el.children.size
|
512
|
-
|
524
|
+
traverse prologue_el, (opts.merge writer: (prologue_writer = Writer.new))
|
525
|
+
opts[:writer].header.push(*prologue_writer.body)
|
513
526
|
end
|
514
527
|
el
|
515
528
|
end
|
516
529
|
|
517
|
-
def inner el, opts
|
518
|
-
rstrip = opts.delete :rstrip
|
519
|
-
result = []
|
520
|
-
prev = nil
|
521
|
-
el.children.each_with_index do |child, idx|
|
522
|
-
result << (send %(convert_#{child.type}), child, (opts.merge parent: el, index: idx, result: result, prev: prev))
|
523
|
-
prev = child
|
524
|
-
end
|
525
|
-
rstrip ? result.join.rstrip : result.join
|
526
|
-
end
|
527
|
-
|
528
530
|
def clone el, properties
|
529
531
|
el = el.dup
|
530
532
|
properties.each do |name, value|
|
@@ -532,6 +534,35 @@ module Kramdown; module AsciiDoc
|
|
532
534
|
end
|
533
535
|
el
|
534
536
|
end
|
537
|
+
|
538
|
+
def traverse el, opts = {}
|
539
|
+
prev = nil
|
540
|
+
if ::Array === el
|
541
|
+
nodes = el
|
542
|
+
parent = opts[:parent]
|
543
|
+
else
|
544
|
+
nodes = (parent = el).children
|
545
|
+
end
|
546
|
+
nodes.each_with_index do |child, idx|
|
547
|
+
convert child, (opts.merge parent: parent, index: idx, prev: prev)
|
548
|
+
prev = child
|
549
|
+
end
|
550
|
+
nil
|
551
|
+
end
|
552
|
+
|
553
|
+
# Q: should we support rstrip in addition to strip?
|
554
|
+
# TODO add escaping of closing square bracket
|
555
|
+
# TODO reflow text
|
556
|
+
def compose_text el, opts = {}
|
557
|
+
strip = opts.delete :strip
|
558
|
+
split = opts.delete :split
|
559
|
+
# Q: do we want to merge or just start fresh?
|
560
|
+
traverse el, (opts.merge writer: (span_writer = Writer.new))
|
561
|
+
# NOTE there should only ever be one line
|
562
|
+
text = span_writer.body.join LF
|
563
|
+
text = text.strip if strip
|
564
|
+
split ? (text.split LF) : text
|
565
|
+
end
|
535
566
|
end
|
536
567
|
end; end
|
537
568
|
|