motion-kramdown 0.5.0

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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +84 -0
  3. data/lib/kramdown/compatibility.rb +36 -0
  4. data/lib/kramdown/converter/base.rb +259 -0
  5. data/lib/kramdown/converter/html.rb +461 -0
  6. data/lib/kramdown/converter/kramdown.rb +423 -0
  7. data/lib/kramdown/converter/latex.rb +600 -0
  8. data/lib/kramdown/converter/math_engine/itex2mml.rb +39 -0
  9. data/lib/kramdown/converter/math_engine/mathjax.rb +33 -0
  10. data/lib/kramdown/converter/math_engine/ritex.rb +38 -0
  11. data/lib/kramdown/converter/pdf.rb +624 -0
  12. data/lib/kramdown/converter/remove_html_tags.rb +53 -0
  13. data/lib/kramdown/converter/syntax_highlighter/coderay.rb +78 -0
  14. data/lib/kramdown/converter/syntax_highlighter/rouge.rb +37 -0
  15. data/lib/kramdown/converter/toc.rb +69 -0
  16. data/lib/kramdown/converter.rb +69 -0
  17. data/lib/kramdown/document.rb +144 -0
  18. data/lib/kramdown/element.rb +515 -0
  19. data/lib/kramdown/error.rb +17 -0
  20. data/lib/kramdown/options.rb +584 -0
  21. data/lib/kramdown/parser/base.rb +130 -0
  22. data/lib/kramdown/parser/gfm.rb +55 -0
  23. data/lib/kramdown/parser/html.rb +575 -0
  24. data/lib/kramdown/parser/kramdown/abbreviation.rb +67 -0
  25. data/lib/kramdown/parser/kramdown/autolink.rb +37 -0
  26. data/lib/kramdown/parser/kramdown/blank_line.rb +30 -0
  27. data/lib/kramdown/parser/kramdown/block_boundary.rb +33 -0
  28. data/lib/kramdown/parser/kramdown/blockquote.rb +39 -0
  29. data/lib/kramdown/parser/kramdown/codeblock.rb +56 -0
  30. data/lib/kramdown/parser/kramdown/codespan.rb +44 -0
  31. data/lib/kramdown/parser/kramdown/emphasis.rb +61 -0
  32. data/lib/kramdown/parser/kramdown/eob.rb +26 -0
  33. data/lib/kramdown/parser/kramdown/escaped_chars.rb +25 -0
  34. data/lib/kramdown/parser/kramdown/extensions.rb +201 -0
  35. data/lib/kramdown/parser/kramdown/footnote.rb +56 -0
  36. data/lib/kramdown/parser/kramdown/header.rb +59 -0
  37. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +27 -0
  38. data/lib/kramdown/parser/kramdown/html.rb +160 -0
  39. data/lib/kramdown/parser/kramdown/html_entity.rb +33 -0
  40. data/lib/kramdown/parser/kramdown/line_break.rb +25 -0
  41. data/lib/kramdown/parser/kramdown/link.rb +139 -0
  42. data/lib/kramdown/parser/kramdown/list.rb +256 -0
  43. data/lib/kramdown/parser/kramdown/math.rb +54 -0
  44. data/lib/kramdown/parser/kramdown/paragraph.rb +54 -0
  45. data/lib/kramdown/parser/kramdown/smart_quotes.rb +174 -0
  46. data/lib/kramdown/parser/kramdown/table.rb +171 -0
  47. data/lib/kramdown/parser/kramdown/typographic_symbol.rb +44 -0
  48. data/lib/kramdown/parser/kramdown.rb +359 -0
  49. data/lib/kramdown/parser/markdown.rb +56 -0
  50. data/lib/kramdown/parser.rb +27 -0
  51. data/lib/kramdown/utils/configurable.rb +44 -0
  52. data/lib/kramdown/utils/entities.rb +347 -0
  53. data/lib/kramdown/utils/html.rb +75 -0
  54. data/lib/kramdown/utils/ordered_hash.rb +87 -0
  55. data/lib/kramdown/utils/string_scanner.rb +74 -0
  56. data/lib/kramdown/utils/unidecoder.rb +51 -0
  57. data/lib/kramdown/utils.rb +58 -0
  58. data/lib/kramdown/version.rb +15 -0
  59. data/lib/kramdown.rb +10 -0
  60. data/lib/motion-kramdown.rb +47 -0
  61. data/lib/rubymotion/encodings.rb +37 -0
  62. data/lib/rubymotion/rexml_shim.rb +25 -0
  63. data/lib/rubymotion/set.rb +1349 -0
  64. data/lib/rubymotion/version.rb +6 -0
  65. data/spec/document_tree.rb +48 -0
  66. data/spec/gfm_to_html.rb +95 -0
  67. data/spec/helpers/it_behaves_like.rb +27 -0
  68. data/spec/helpers/option_file.rb +46 -0
  69. data/spec/helpers/spec_options.rb +37 -0
  70. data/spec/helpers/tidy.rb +12 -0
  71. data/spec/html_to_html.rb +40 -0
  72. data/spec/html_to_kramdown_to_html.rb +46 -0
  73. data/spec/kramdown_to_xxx.rb +40 -0
  74. data/spec/test_location.rb +203 -0
  75. data/spec/test_string_scanner_kramdown.rb +19 -0
  76. data/spec/text_to_kramdown_to_html.rb +52 -0
  77. data/spec/text_to_latex.rb +33 -0
  78. metadata +164 -0
@@ -0,0 +1,461 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ # RM require 'rexml/parsers/baseparser'
11
+ # RM require 'kramdown/parser/html'
12
+
13
+ module Kramdown
14
+
15
+ module Converter
16
+
17
+ # Converts a Kramdown::Document to HTML.
18
+ #
19
+ # You can customize the HTML converter by sub-classing it and overriding the +convert_NAME+
20
+ # methods. Each such method takes the following parameters:
21
+ #
22
+ # [+el+] The element of type +NAME+ to be converted.
23
+ #
24
+ # [+indent+] A number representing the current amount of spaces for indent (only used for
25
+ # block-level elements).
26
+ #
27
+ # The return value of such a method has to be a string containing the element +el+ formatted as
28
+ # HTML element.
29
+ class Html < Base
30
+
31
+ include ::Kramdown::Utils::Html
32
+ include ::Kramdown::Parser::Html::Constants
33
+
34
+ # The amount of indentation used when nesting HTML tags.
35
+ attr_accessor :indent
36
+
37
+ # Initialize the HTML converter with the given Kramdown document +doc+.
38
+ def initialize(root, options)
39
+ super
40
+ @footnote_counter = @footnote_start = @options[:footnote_nr]
41
+ @footnotes = []
42
+ @footnotes_by_name = {}
43
+ @footnote_location = nil
44
+ @toc = []
45
+ @toc_code = nil
46
+ @indent = 2
47
+ @stack = []
48
+ end
49
+
50
+ # The mapping of element type to conversion method.
51
+ DISPATCHER = Hash.new {|h,k| h[k] = "convert_#{k}"}
52
+
53
+ # Dispatch the conversion of the element +el+ to a +convert_TYPE+ method using the +type+ of
54
+ # the element.
55
+ def convert(el, indent = -@indent)
56
+ send(DISPATCHER[el.type], el, indent)
57
+ end
58
+
59
+ # Return the converted content of the children of +el+ as a string. The parameter +indent+ has
60
+ # to be the amount of indentation used for the element +el+.
61
+ #
62
+ # Pushes +el+ onto the @stack before converting the child elements and pops it from the stack
63
+ # afterwards.
64
+ def inner(el, indent)
65
+ result = ''
66
+ indent += @indent
67
+ @stack.push(el)
68
+ el.children.each do |inner_el|
69
+ result << send(DISPATCHER[inner_el.type], inner_el, indent)
70
+ end
71
+ @stack.pop
72
+ result
73
+ end
74
+
75
+ def convert_blank(el, indent)
76
+ "\n"
77
+ end
78
+
79
+ def convert_text(el, indent)
80
+ escape_html(el.value, :text)
81
+ end
82
+
83
+ def convert_p(el, indent)
84
+ if el.options[:transparent]
85
+ inner(el, indent)
86
+ else
87
+ format_as_block_html(el.type, el.attr, inner(el, indent), indent)
88
+ end
89
+ end
90
+
91
+ def convert_codeblock(el, indent)
92
+ attr = el.attr.dup
93
+ lang = extract_code_language!(attr)
94
+ highlighted_code = highlight_code(el.value, lang, :block)
95
+
96
+ if highlighted_code
97
+ add_syntax_highlighter_to_class_attr(attr)
98
+ "#{' '*indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' '*indent}</div>\n"
99
+ else
100
+ result = escape_html(el.value)
101
+ result.chomp!
102
+ if el.attr['class'].to_s =~ /\bshow-whitespaces\b/
103
+ result.gsub!(/(?:(^[ \t]+)|([ \t]+$)|([ \t]+))/) do |m|
104
+ suffix = ($1 ? '-l' : ($2 ? '-r' : ''))
105
+ m.scan(/./).map do |c|
106
+ case c
107
+ when "\t" then "<span class=\"ws-tab#{suffix}\">\t</span>"
108
+ when " " then "<span class=\"ws-space#{suffix}\">&#8901;</span>"
109
+ end
110
+ end.join('')
111
+ end
112
+ end
113
+ code_attr = {}
114
+ code_attr['class'] = "language-#{lang}" if lang
115
+ "#{' '*indent}<pre#{html_attributes(attr)}><code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n"
116
+ end
117
+ end
118
+
119
+ def convert_blockquote(el, indent)
120
+ format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
121
+ end
122
+
123
+ def convert_header(el, indent)
124
+ attr = el.attr.dup
125
+ if @options[:auto_ids] && !attr['id']
126
+ attr['id'] = generate_id(el.options[:raw_text])
127
+ end
128
+ @toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
129
+ level = output_header_level(el.options[:level])
130
+ format_as_block_html("h#{level}", attr, inner(el, indent), indent)
131
+ end
132
+
133
+ def convert_hr(el, indent)
134
+ "#{' '*indent}<hr#{html_attributes(el.attr)} />\n"
135
+ end
136
+
137
+ def convert_ul(el, indent)
138
+ if !@toc_code && el.options[:ial] && (el.options[:ial][:refs] || []).include?('toc') # RM can't use 'rescue nil'
139
+ @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
140
+ @toc_code.last
141
+ elsif !@footnote_location && el.options[:ial] && (el.options[:ial][:refs] || []).include?('footnotes')
142
+ @footnote_location = (0..128).to_a.map{|a| rand(36).to_s(36)}.join
143
+ else
144
+ format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
145
+ end
146
+ end
147
+ alias :convert_ol :convert_ul
148
+
149
+ def convert_dl(el, indent)
150
+ format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
151
+ end
152
+
153
+ def convert_li(el, indent)
154
+ output = ' '*indent << "<#{el.type}" << html_attributes(el.attr) << ">"
155
+ res = inner(el, indent)
156
+ if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
157
+ output << res << (res =~ /\n\Z/ ? ' '*indent : '')
158
+ else
159
+ output << "\n" << res << ' '*indent
160
+ end
161
+ output << "</#{el.type}>\n"
162
+ end
163
+ alias :convert_dd :convert_li
164
+
165
+ def convert_dt(el, indent)
166
+ format_as_block_html(el.type, el.attr, inner(el, indent), indent)
167
+ end
168
+
169
+ def convert_html_element(el, indent)
170
+ res = inner(el, indent)
171
+ if el.options[:category] == :span
172
+ "<#{el.value}#{html_attributes(el.attr)}" << (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>")
173
+ else
174
+ output = ''
175
+ output << ' '*indent if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
176
+ output << "<#{el.value}#{html_attributes(el.attr)}"
177
+ if el.options[:is_closed] && el.options[:content_model] == :raw
178
+ output << " />"
179
+ elsif !res.empty? && el.options[:content_model] != :block
180
+ output << ">#{res}</#{el.value}>"
181
+ elsif !res.empty?
182
+ output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>"
183
+ elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
184
+ output << " />"
185
+ else
186
+ output << "></#{el.value}>"
187
+ end
188
+ output << "\n" if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
189
+ output
190
+ end
191
+ end
192
+
193
+ def convert_xml_comment(el, indent)
194
+ if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
195
+ ' '*indent << el.value << "\n"
196
+ else
197
+ el.value
198
+ end
199
+ end
200
+ alias :convert_xml_pi :convert_xml_comment
201
+
202
+ def convert_table(el, indent)
203
+ format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
204
+ end
205
+ alias :convert_thead :convert_table
206
+ alias :convert_tbody :convert_table
207
+ alias :convert_tfoot :convert_table
208
+ alias :convert_tr :convert_table
209
+
210
+ ENTITY_NBSP = ::Kramdown::Utils::Entities.entity('nbsp') # :nodoc:
211
+
212
+ def convert_td(el, indent)
213
+ res = inner(el, indent)
214
+ type = (@stack[-2].type == :thead ? :th : :td)
215
+ attr = el.attr
216
+ alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)]
217
+ if alignment != :default
218
+ attr = el.attr.dup
219
+ attr['style'] = (attr.has_key?('style') ? "#{attr['style']}; ": '') << "text-align: #{alignment}"
220
+ end
221
+ format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent)
222
+ end
223
+
224
+ def convert_comment(el, indent)
225
+ if el.options[:category] == :block
226
+ "#{' '*indent}<!-- #{el.value} -->\n"
227
+ else
228
+ "<!-- #{el.value} -->"
229
+ end
230
+ end
231
+
232
+ def convert_br(el, indent)
233
+ "<br />"
234
+ end
235
+
236
+ def convert_a(el, indent)
237
+ res = inner(el, indent)
238
+ attr = el.attr.dup
239
+ if attr['href'].start_with?('mailto:')
240
+ mail_addr = attr['href'][7..-1]
241
+ attr['href'] = obfuscate('mailto') << ":" << obfuscate(mail_addr)
242
+ res = obfuscate(res) if res == mail_addr
243
+ end
244
+ format_as_span_html(el.type, attr, res)
245
+ end
246
+
247
+ def convert_img(el, indent)
248
+ "<img#{html_attributes(el.attr)} />"
249
+ end
250
+
251
+ def convert_codespan(el, indent)
252
+ attr = el.attr.dup
253
+ lang = extract_code_language(attr)
254
+ result = highlight_code(el.value, lang, :span)
255
+ if result
256
+ add_syntax_highlighter_to_class_attr(attr)
257
+ else
258
+ result = escape_html(el.value)
259
+ end
260
+
261
+ format_as_span_html('code', attr, result)
262
+ end
263
+
264
+ def convert_footnote(el, indent)
265
+ repeat = ''
266
+ if (footnote = @footnotes_by_name[el.options[:name]])
267
+ number = footnote[2]
268
+ repeat = ":#{footnote[3] += 1}"
269
+ else
270
+ number = @footnote_counter
271
+ @footnote_counter += 1
272
+ @footnotes << [el.options[:name], el.value, number, 0]
273
+ @footnotes_by_name[el.options[:name]] = @footnotes.last
274
+ end
275
+ "<sup id=\"fnref:#{el.options[:name]}#{repeat}\"><a href=\"#fn:#{el.options[:name]}\" class=\"footnote\">#{number}</a></sup>"
276
+ end
277
+
278
+ def convert_raw(el, indent)
279
+ if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
280
+ el.value + (el.options[:category] == :block ? "\n" : '')
281
+ else
282
+ ''
283
+ end
284
+ end
285
+
286
+ def convert_em(el, indent)
287
+ format_as_span_html(el.type, el.attr, inner(el, indent))
288
+ end
289
+ alias :convert_strong :convert_em
290
+
291
+ def convert_entity(el, indent)
292
+ entity_to_str(el.value, el.options[:original])
293
+ end
294
+
295
+ TYPOGRAPHIC_SYMS = {
296
+ :mdash => [::Kramdown::Utils::Entities.entity('mdash')],
297
+ :ndash => [::Kramdown::Utils::Entities.entity('ndash')],
298
+ :hellip => [::Kramdown::Utils::Entities.entity('hellip')],
299
+ :laquo_space => [::Kramdown::Utils::Entities.entity('laquo'), ::Kramdown::Utils::Entities.entity('nbsp')],
300
+ :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
301
+ :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
302
+ :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
303
+ } # :nodoc:
304
+ def convert_typographic_sym(el, indent)
305
+ TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
306
+ end
307
+
308
+ def convert_smart_quote(el, indent)
309
+ entity_to_str(smart_quote_entity(el))
310
+ end
311
+
312
+ def convert_math(el, indent)
313
+ if (result = format_math(el, :indent => indent))
314
+ result
315
+ elsif el.options[:category] == :block
316
+ format_as_block_html('pre', el.attr, "$$\n#{el.value}\n$$", indent)
317
+ else
318
+ format_as_span_html('span', el.attr, "$#{el.value}$")
319
+ end
320
+ end
321
+
322
+ def convert_abbreviation(el, indent)
323
+ title = @root.options[:abbrev_defs][el.value]
324
+ format_as_span_html("abbr", {:title => (title.empty? ? nil : title)}, el.value)
325
+ end
326
+
327
+ def convert_root(el, indent)
328
+ result = inner(el, indent)
329
+ if @footnote_location
330
+ result.sub!(/#{@footnote_location}/, footnote_content)
331
+ else
332
+ result << footnote_content
333
+ end
334
+ if @toc_code
335
+ toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
336
+ text = if toc_tree.children.size > 0
337
+ convert(toc_tree, 0)
338
+ else
339
+ ''
340
+ end
341
+ result.sub!(/#{@toc_code.last}/, text)
342
+ end
343
+ result
344
+ end
345
+
346
+ # Format the given element as span HTML.
347
+ def format_as_span_html(name, attr, body)
348
+ "<#{name}#{html_attributes(attr)}>#{body}</#{name}>"
349
+ end
350
+
351
+ # Format the given element as block HTML.
352
+ def format_as_block_html(name, attr, body, indent)
353
+ "#{' '*indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n"
354
+ end
355
+
356
+ # Format the given element as block HTML with a newline after the start tag and indentation
357
+ # before the end tag.
358
+ def format_as_indented_block_html(name, attr, body, indent)
359
+ "#{' '*indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' '*indent}</#{name}>\n"
360
+ end
361
+
362
+ # Add the syntax highlighter name to the 'class' attribute of the given attribute hash.
363
+ def add_syntax_highlighter_to_class_attr(attr)
364
+ (attr['class'] = (attr['class'] || '') + " highlighter-#{@options[:syntax_highlighter]}").lstrip!
365
+ end
366
+
367
+ # Generate and return an element tree for the table of contents.
368
+ def generate_toc_tree(toc, type, attr)
369
+ sections = Element.new(type, nil, attr)
370
+ sections.attr['id'] ||= 'markdown-toc'
371
+ stack = []
372
+ toc.each do |level, id, children|
373
+ li = Element.new(:li, nil, nil, {:level => level})
374
+ li.children << Element.new(:p, nil, nil, {:transparent => true})
375
+ a = Element.new(:a, nil, {'href' => "##{id}"})
376
+ a.children.concat(remove_footnotes(Marshal.load(Marshal.dump(children))))
377
+ li.children.last.children << a
378
+ li.children << Element.new(type)
379
+
380
+ success = false
381
+ while !success
382
+ if stack.empty?
383
+ sections.children << li
384
+ stack << li
385
+ success = true
386
+ elsif stack.last.options[:level] < li.options[:level]
387
+ stack.last.children.last.children << li
388
+ stack << li
389
+ success = true
390
+ else
391
+ item = stack.pop
392
+ item.children.pop unless item.children.last.children.size > 0
393
+ end
394
+ end
395
+ end
396
+ while !stack.empty?
397
+ item = stack.pop
398
+ item.children.pop unless item.children.last.children.size > 0
399
+ end
400
+ sections
401
+ end
402
+
403
+ # Remove all footnotes from the given elements.
404
+ def remove_footnotes(elements)
405
+ elements.delete_if do |c|
406
+ remove_footnotes(c.children)
407
+ c.type == :footnote
408
+ end
409
+ end
410
+
411
+ # Obfuscate the +text+ by using HTML entities.
412
+ def obfuscate(text)
413
+ result = ""
414
+ text.each_byte do |b|
415
+ result << (b > 128 ? b.chr : "&#%03d;" % b)
416
+ end
417
+ result.force_encoding(text.encoding) if result.respond_to?(:force_encoding)
418
+ result
419
+ end
420
+
421
+ FOOTNOTE_BACKLINK_FMT = "%s<a href=\"#fnref:%s\" class=\"reversefootnote\">%s</a>"
422
+
423
+ # Return a HTML ordered list with the footnote content for the used footnotes.
424
+ def footnote_content
425
+ ol = Element.new(:ol)
426
+ ol.attr['start'] = @footnote_start if @footnote_start != 1
427
+ i = 0
428
+ while i < @footnotes.length
429
+ name, data, _, repeat = *@footnotes[i]
430
+ li = Element.new(:li, nil, {'id' => "fn:#{name}"})
431
+ li.children = Marshal.load(Marshal.dump(data.children))
432
+
433
+ #------------------------------------------------------------------------------
434
+ # RM Crash due to an object being autoreleased one too many times in RubyMotion.
435
+ # pre-initializing `para` before it's assigned in the `if` statement seems to
436
+ # fix it.
437
+ para = nil
438
+
439
+ if li.children.last.type == :p
440
+ para = li.children.last
441
+ insert_space = true
442
+ else
443
+ li.children << (para = Element.new(:p))
444
+ insert_space = false
445
+ end
446
+
447
+ para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [insert_space ? ' ' : '', name, "&#8617;"])
448
+ (1..repeat).each do |index|
449
+ para.children << Element.new(:raw, FOOTNOTE_BACKLINK_FMT % [" ", "#{name}:#{index}", "&#8617;<sup>#{index+1}</sup>"])
450
+ end
451
+
452
+ ol.children << Element.new(:raw, convert(li, 4))
453
+ i += 1
454
+ end
455
+ (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0))
456
+ end
457
+
458
+ end
459
+
460
+ end
461
+ end