motion-kramdown 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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