kramdown 0.14.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kramdown might be problematic. Click here for more details.

Files changed (323) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTERS +63 -1
  3. data/COPYING +17 -11
  4. data/README.md +35 -14
  5. data/VERSION +1 -1
  6. data/bin/kramdown +92 -40
  7. data/data/kramdown/document.html +4 -0
  8. data/data/kramdown/document.latex +7 -0
  9. data/lib/kramdown.rb +3 -16
  10. data/lib/kramdown/converter.rb +42 -16
  11. data/lib/kramdown/converter/base.rb +102 -38
  12. data/lib/kramdown/converter/hash_ast.rb +38 -0
  13. data/lib/kramdown/converter/html.rb +232 -141
  14. data/lib/kramdown/converter/kramdown.rb +122 -104
  15. data/lib/kramdown/converter/latex.rb +95 -78
  16. data/lib/kramdown/converter/man.rb +300 -0
  17. data/lib/kramdown/converter/math_engine/mathjax.rb +32 -0
  18. data/lib/kramdown/converter/remove_html_tags.rb +8 -17
  19. data/lib/kramdown/converter/syntax_highlighter.rb +56 -0
  20. data/lib/kramdown/converter/syntax_highlighter/minted.rb +35 -0
  21. data/lib/kramdown/converter/syntax_highlighter/rouge.rb +85 -0
  22. data/lib/kramdown/converter/toc.rb +7 -20
  23. data/lib/kramdown/document.rb +30 -37
  24. data/lib/kramdown/element.rb +54 -27
  25. data/lib/kramdown/error.rb +3 -16
  26. data/lib/kramdown/options.rb +392 -247
  27. data/lib/kramdown/parser.rb +3 -16
  28. data/lib/kramdown/parser/base.rb +28 -33
  29. data/lib/kramdown/parser/html.rb +151 -119
  30. data/lib/kramdown/parser/kramdown.rb +87 -50
  31. data/lib/kramdown/parser/kramdown/abbreviation.rb +33 -27
  32. data/lib/kramdown/parser/kramdown/autolink.rb +7 -25
  33. data/lib/kramdown/parser/kramdown/blank_line.rb +6 -19
  34. data/lib/kramdown/parser/kramdown/block_boundary.rb +6 -18
  35. data/lib/kramdown/parser/kramdown/blockquote.rb +6 -19
  36. data/lib/kramdown/parser/kramdown/codeblock.rb +15 -24
  37. data/lib/kramdown/parser/kramdown/codespan.rb +20 -22
  38. data/lib/kramdown/parser/kramdown/emphasis.rb +15 -24
  39. data/lib/kramdown/parser/kramdown/eob.rb +3 -16
  40. data/lib/kramdown/parser/kramdown/escaped_chars.rb +3 -16
  41. data/lib/kramdown/parser/kramdown/extensions.rb +66 -56
  42. data/lib/kramdown/parser/kramdown/footnote.rb +21 -31
  43. data/lib/kramdown/parser/kramdown/header.rb +37 -37
  44. data/lib/kramdown/parser/kramdown/horizontal_rule.rb +5 -17
  45. data/lib/kramdown/parser/kramdown/html.rb +47 -56
  46. data/lib/kramdown/parser/kramdown/html_entity.rb +9 -19
  47. data/lib/kramdown/parser/kramdown/line_break.rb +4 -17
  48. data/lib/kramdown/parser/kramdown/link.rb +39 -38
  49. data/lib/kramdown/parser/kramdown/list.rb +124 -82
  50. data/lib/kramdown/parser/kramdown/math.rb +12 -24
  51. data/lib/kramdown/parser/kramdown/paragraph.rb +23 -24
  52. data/lib/kramdown/parser/kramdown/smart_quotes.rb +26 -66
  53. data/lib/kramdown/parser/kramdown/table.rb +41 -48
  54. data/lib/kramdown/parser/kramdown/typographic_symbol.rb +14 -22
  55. data/lib/kramdown/parser/markdown.rb +11 -23
  56. data/lib/kramdown/utils.rb +21 -18
  57. data/lib/kramdown/utils/configurable.rb +45 -0
  58. data/lib/kramdown/utils/entities.rb +287 -292
  59. data/lib/kramdown/utils/html.rb +27 -30
  60. data/lib/kramdown/utils/lru_cache.rb +41 -0
  61. data/lib/kramdown/utils/string_scanner.rb +81 -0
  62. data/lib/kramdown/utils/unidecoder.rb +50 -0
  63. data/lib/kramdown/version.rb +4 -17
  64. data/man/man1/kramdown.1 +340 -347
  65. data/test/run_tests.rb +7 -20
  66. data/test/test_files.rb +188 -100
  67. data/test/test_location.rb +216 -0
  68. data/test/test_string_scanner_kramdown.rb +27 -0
  69. data/test/testcases/block/03_paragraph/indented.html.gfm +18 -0
  70. data/test/testcases/block/03_paragraph/line_break_last_line.html +9 -0
  71. data/test/testcases/block/03_paragraph/line_break_last_line.text +9 -0
  72. data/test/testcases/block/03_paragraph/standalone_image.html +8 -0
  73. data/test/testcases/block/03_paragraph/standalone_image.text +6 -0
  74. data/test/testcases/block/03_paragraph/with_html_to_native.html +1 -0
  75. data/test/testcases/block/03_paragraph/with_html_to_native.options +1 -0
  76. data/test/testcases/block/03_paragraph/with_html_to_native.text +1 -0
  77. data/test/testcases/block/04_header/atx_header.html +15 -1
  78. data/test/testcases/block/04_header/atx_header.text +14 -1
  79. data/test/testcases/block/04_header/setext_header.html +3 -1
  80. data/test/testcases/block/04_header/setext_header.text +4 -1
  81. data/test/testcases/block/04_header/with_auto_id_stripping.html +1 -0
  82. data/test/testcases/block/04_header/with_auto_id_stripping.options +1 -0
  83. data/test/testcases/block/04_header/with_auto_id_stripping.text +1 -0
  84. data/test/testcases/block/04_header/with_auto_ids.html +2 -0
  85. data/test/testcases/block/04_header/with_auto_ids.options +1 -0
  86. data/test/testcases/block/04_header/with_auto_ids.text +2 -0
  87. data/test/testcases/block/06_codeblock/guess_lang_css_class.html +15 -0
  88. data/test/testcases/block/06_codeblock/guess_lang_css_class.options +2 -0
  89. data/test/testcases/block/06_codeblock/guess_lang_css_class.text +13 -0
  90. data/test/testcases/block/06_codeblock/highlighting-minted-with-opts.latex +9 -0
  91. data/test/testcases/block/06_codeblock/highlighting-minted-with-opts.options +4 -0
  92. data/test/testcases/block/06_codeblock/highlighting-minted-with-opts.text +5 -0
  93. data/test/testcases/block/06_codeblock/highlighting-minted.latex +8 -0
  94. data/test/testcases/block/06_codeblock/highlighting-minted.options +3 -0
  95. data/test/testcases/block/06_codeblock/highlighting-minted.text +4 -0
  96. data/test/testcases/block/06_codeblock/highlighting-opts.html +6 -0
  97. data/test/testcases/block/06_codeblock/highlighting-opts.options +7 -0
  98. data/test/testcases/block/06_codeblock/highlighting-opts.text +4 -0
  99. data/test/testcases/block/06_codeblock/highlighting.html +5 -6
  100. data/test/testcases/block/06_codeblock/issue_gh45.html +164 -0
  101. data/test/testcases/block/06_codeblock/issue_gh45.test +188 -0
  102. data/test/testcases/block/06_codeblock/rouge/disabled.html +2 -0
  103. data/test/testcases/block/06_codeblock/rouge/disabled.options +4 -0
  104. data/test/testcases/block/06_codeblock/rouge/disabled.text +1 -0
  105. data/test/testcases/block/06_codeblock/rouge/multiple.html +11 -0
  106. data/test/testcases/block/06_codeblock/rouge/multiple.options +4 -0
  107. data/test/testcases/block/06_codeblock/rouge/multiple.text +11 -0
  108. data/test/testcases/block/06_codeblock/rouge/simple.html +10 -0
  109. data/test/testcases/block/06_codeblock/rouge/simple.options +3 -0
  110. data/test/testcases/block/06_codeblock/rouge/simple.text +9 -0
  111. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block.options +1 -1
  112. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_any_char.html +8 -0
  113. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_any_char.options +2 -0
  114. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_any_char.text +11 -0
  115. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_name_with_dash.html +3 -0
  116. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_name_with_dash.options +2 -0
  117. data/test/testcases/block/06_codeblock/with_lang_in_fenced_block_name_with_dash.text +4 -0
  118. data/test/testcases/block/07_horizontal_rule/error.html +2 -2
  119. data/test/testcases/block/07_horizontal_rule/normal.html +2 -0
  120. data/test/testcases/block/07_horizontal_rule/normal.text +3 -0
  121. data/test/testcases/block/08_list/brackets_in_item.latex +3 -0
  122. data/test/testcases/block/08_list/brackets_in_item.text +1 -0
  123. data/test/testcases/block/08_list/lazy_and_nested.html +9 -0
  124. data/test/testcases/block/08_list/lazy_and_nested.text +4 -0
  125. data/test/testcases/block/09_html/html5_attributes.html +2 -0
  126. data/test/testcases/block/09_html/html5_attributes.text +2 -0
  127. data/test/testcases/block/09_html/html_after_block.html +7 -0
  128. data/test/testcases/block/09_html/html_after_block.text +5 -0
  129. data/test/testcases/block/09_html/html_to_native/table_simple.html +13 -0
  130. data/test/testcases/block/09_html/html_to_native/table_simple.text +15 -0
  131. data/test/testcases/block/09_html/html_to_native/typography.html +1 -1
  132. data/test/testcases/block/09_html/not_parsed.html +1 -1
  133. data/test/testcases/block/09_html/processing_instruction.html +5 -6
  134. data/test/testcases/block/09_html/simple.html +1 -5
  135. data/test/testcases/block/09_html/simple.text +1 -5
  136. data/test/testcases/block/09_html/standalone_image_in_div.htmlinput +7 -0
  137. data/test/testcases/block/09_html/standalone_image_in_div.text +8 -0
  138. data/test/testcases/block/09_html/textarea.html +8 -0
  139. data/test/testcases/block/09_html/textarea.text +8 -0
  140. data/test/testcases/block/09_html/xml.html +8 -0
  141. data/test/testcases/block/09_html/xml.text +7 -0
  142. data/test/testcases/block/11_ial/simple.html +5 -1
  143. data/test/testcases/block/11_ial/simple.text +8 -1
  144. data/test/testcases/block/12_extension/options.html +4 -4
  145. data/test/testcases/block/12_extension/options.text +2 -0
  146. data/test/testcases/block/12_extension/options2.html +4 -4
  147. data/test/testcases/block/12_extension/options3.html +7 -6
  148. data/test/testcases/block/12_extension/options3.text +2 -2
  149. data/test/testcases/block/13_definition_list/auto_ids.html +15 -0
  150. data/test/testcases/block/13_definition_list/auto_ids.text +18 -0
  151. data/test/testcases/block/13_definition_list/item_ial.html +5 -0
  152. data/test/testcases/block/13_definition_list/item_ial.text +8 -0
  153. data/test/testcases/block/14_table/empty_tag_in_cell.html +8 -0
  154. data/test/testcases/block/14_table/empty_tag_in_cell.options +1 -0
  155. data/test/testcases/block/14_table/empty_tag_in_cell.text +1 -0
  156. data/test/testcases/block/14_table/errors.html +4 -0
  157. data/test/testcases/block/14_table/errors.text +4 -0
  158. data/test/testcases/block/14_table/header.html +21 -0
  159. data/test/testcases/block/14_table/header.text +7 -0
  160. data/test/testcases/block/14_table/simple.html +22 -7
  161. data/test/testcases/block/14_table/simple.text +4 -0
  162. data/test/testcases/block/14_table/table_with_footnote.html +4 -4
  163. data/test/testcases/block/15_math/gh_128.html +1 -0
  164. data/test/testcases/block/15_math/gh_128.text +1 -0
  165. data/test/testcases/block/15_math/no_engine.html +3 -0
  166. data/test/testcases/block/15_math/no_engine.options +1 -0
  167. data/test/testcases/block/15_math/no_engine.text +2 -0
  168. data/test/testcases/block/15_math/normal.html +17 -14
  169. data/test/testcases/block/15_math/normal.text +2 -0
  170. data/test/testcases/block/16_toc/toc_exclude.html +7 -7
  171. data/test/testcases/block/16_toc/toc_levels.html +5 -5
  172. data/test/testcases/block/16_toc/toc_levels.text +1 -1
  173. data/test/testcases/block/16_toc/toc_with_footnotes.html +5 -5
  174. data/test/testcases/block/16_toc/toc_with_links.html +8 -0
  175. data/test/testcases/block/16_toc/toc_with_links.options +2 -0
  176. data/test/testcases/block/16_toc/toc_with_links.text +8 -0
  177. data/test/testcases/cjk-line-break.html +4 -0
  178. data/test/testcases/cjk-line-break.options +1 -0
  179. data/test/testcases/cjk-line-break.text +12 -0
  180. data/test/testcases/man/example.man +123 -0
  181. data/test/testcases/man/example.text +85 -0
  182. data/test/testcases/man/heading-name-dash-description.man +4 -0
  183. data/test/testcases/man/heading-name-dash-description.text +1 -0
  184. data/test/testcases/man/heading-name-description.man +4 -0
  185. data/test/testcases/man/heading-name-description.text +2 -0
  186. data/test/testcases/man/heading-name-section-description.man +4 -0
  187. data/test/testcases/man/heading-name-section-description.text +1 -0
  188. data/test/testcases/man/heading-name-section.man +2 -0
  189. data/test/testcases/man/heading-name-section.text +1 -0
  190. data/test/testcases/man/heading-name.man +2 -0
  191. data/test/testcases/man/heading-name.text +1 -0
  192. data/test/testcases/man/sections.man +4 -0
  193. data/test/testcases/man/sections.text +11 -0
  194. data/test/testcases/man/text-escaping.man +8 -0
  195. data/test/testcases/man/text-escaping.text +7 -0
  196. data/test/testcases/span/01_link/empty.html +1 -1
  197. data/test/testcases/span/01_link/empty_title.htmlinput +3 -0
  198. data/test/testcases/span/01_link/empty_title.text +7 -0
  199. data/test/testcases/span/01_link/imagelinks.html +1 -0
  200. data/test/testcases/span/01_link/imagelinks.text +2 -0
  201. data/test/testcases/span/01_link/inline.html +1 -1
  202. data/test/testcases/span/01_link/latex_escaping.latex +6 -0
  203. data/test/testcases/span/01_link/latex_escaping.text +5 -0
  204. data/test/testcases/span/01_link/link_defs.html +1 -1
  205. data/test/testcases/span/01_link/link_defs.text +2 -1
  206. data/test/testcases/span/01_link/link_defs_with_ial.html +4 -0
  207. data/test/testcases/span/01_link/link_defs_with_ial.text +16 -0
  208. data/test/testcases/span/01_link/reference.html +3 -3
  209. data/test/testcases/span/02_emphasis/nesting.html +3 -0
  210. data/test/testcases/span/02_emphasis/nesting.text +4 -1
  211. data/test/testcases/span/02_emphasis/normal.html +19 -0
  212. data/test/testcases/span/02_emphasis/normal.options +1 -0
  213. data/test/testcases/span/02_emphasis/normal.text +17 -0
  214. data/test/testcases/span/03_codespan/highlighting-minted.latex +2 -0
  215. data/test/testcases/span/03_codespan/highlighting-minted.options +1 -0
  216. data/test/testcases/span/03_codespan/highlighting-minted.text +1 -0
  217. data/test/testcases/span/03_codespan/highlighting.html +1 -1
  218. data/test/testcases/span/03_codespan/normal-css-class.html +1 -0
  219. data/test/testcases/span/03_codespan/normal-css-class.options +2 -0
  220. data/test/testcases/span/03_codespan/normal-css-class.text +1 -0
  221. data/test/testcases/span/03_codespan/rouge/disabled.html +1 -0
  222. data/test/testcases/span/03_codespan/rouge/disabled.options +4 -0
  223. data/test/testcases/span/03_codespan/rouge/disabled.text +1 -0
  224. data/test/testcases/span/03_codespan/rouge/simple.html +1 -0
  225. data/test/testcases/span/03_codespan/rouge/simple.options +1 -0
  226. data/test/testcases/span/03_codespan/rouge/simple.text +1 -0
  227. data/test/testcases/span/04_footnote/backlink_inline.html +79 -0
  228. data/test/testcases/span/04_footnote/backlink_inline.options +1 -0
  229. data/test/testcases/span/04_footnote/backlink_inline.text +38 -0
  230. data/test/testcases/span/04_footnote/backlink_text.html +9 -0
  231. data/test/testcases/span/04_footnote/backlink_text.options +1 -0
  232. data/test/testcases/span/04_footnote/backlink_text.text +3 -0
  233. data/test/testcases/span/04_footnote/definitions.latex +2 -2
  234. data/test/testcases/span/04_footnote/footnote_nr.html +6 -6
  235. data/test/testcases/span/04_footnote/footnote_prefix.html +12 -0
  236. data/test/testcases/span/04_footnote/footnote_prefix.options +1 -0
  237. data/test/testcases/span/04_footnote/footnote_prefix.text +4 -0
  238. data/test/testcases/span/04_footnote/inside_footnote.html +17 -0
  239. data/test/testcases/span/04_footnote/inside_footnote.text +9 -0
  240. data/test/testcases/span/04_footnote/markers.html +16 -16
  241. data/test/testcases/span/04_footnote/markers.latex +3 -3
  242. data/test/testcases/span/04_footnote/markers.options +2 -0
  243. data/test/testcases/span/04_footnote/markers.text +2 -1
  244. data/test/testcases/span/04_footnote/placement.html +11 -0
  245. data/test/testcases/span/04_footnote/placement.options +1 -0
  246. data/test/testcases/span/04_footnote/placement.text +8 -0
  247. data/test/testcases/span/04_footnote/regexp_problem.html +14 -0
  248. data/test/testcases/span/04_footnote/regexp_problem.options +2 -0
  249. data/test/testcases/span/04_footnote/regexp_problem.text +52 -0
  250. data/test/testcases/span/04_footnote/without_backlink.html +9 -0
  251. data/test/testcases/span/04_footnote/without_backlink.options +1 -0
  252. data/test/testcases/span/04_footnote/without_backlink.text +3 -0
  253. data/test/testcases/span/05_html/button.html +7 -0
  254. data/test/testcases/span/05_html/button.text +7 -0
  255. data/test/testcases/span/05_html/mark_element.html +3 -0
  256. data/test/testcases/span/05_html/mark_element.text +3 -0
  257. data/test/testcases/span/05_html/normal.html +10 -1
  258. data/test/testcases/span/05_html/normal.text +9 -0
  259. data/test/testcases/span/05_html/raw_span_elements.html +2 -0
  260. data/test/testcases/span/05_html/raw_span_elements.text +2 -0
  261. data/test/testcases/span/05_html/xml.html +5 -0
  262. data/test/testcases/span/05_html/xml.text +5 -0
  263. data/test/testcases/span/abbreviations/abbrev.html +14 -1
  264. data/test/testcases/span/abbreviations/abbrev.text +18 -2
  265. data/test/testcases/span/abbreviations/in_footnote.html +9 -0
  266. data/test/testcases/span/abbreviations/in_footnote.text +5 -0
  267. data/test/testcases/span/autolinks/url_links.html +5 -4
  268. data/test/testcases/span/autolinks/url_links.text +1 -0
  269. data/test/testcases/span/line_breaks/normal.html +2 -2
  270. data/test/testcases/span/line_breaks/normal.latex +2 -2
  271. data/test/testcases/span/math/no_engine.html +1 -0
  272. data/test/testcases/span/math/no_engine.options +1 -0
  273. data/test/testcases/span/math/no_engine.text +1 -0
  274. data/test/testcases/span/math/normal.html +4 -3
  275. data/test/testcases/span/math/normal.text +2 -1
  276. data/test/testcases/span/text_substitutions/entities_as_char.html +1 -1
  277. data/test/testcases/span/text_substitutions/entities_as_char.options +1 -0
  278. data/test/testcases/span/text_substitutions/entities_as_char.text +1 -1
  279. data/test/testcases/span/text_substitutions/typography.html +22 -0
  280. data/test/testcases/span/text_substitutions/typography.text +22 -0
  281. data/test/testcases/span/text_substitutions/typography_subst.html +3 -0
  282. data/test/testcases/span/text_substitutions/typography_subst.latex +4 -0
  283. data/test/testcases/span/text_substitutions/typography_subst.options +8 -0
  284. data/test/testcases/span/text_substitutions/typography_subst.text +3 -0
  285. metadata +218 -67
  286. data/ChangeLog +0 -7436
  287. data/GPL +0 -674
  288. data/Rakefile +0 -306
  289. data/benchmark/benchmark.rb +0 -36
  290. data/benchmark/benchmark.sh +0 -74
  291. data/benchmark/generate_data.rb +0 -119
  292. data/benchmark/mdbasics.text +0 -306
  293. data/benchmark/mdsyntax.text +0 -888
  294. data/benchmark/testing.sh +0 -9
  295. data/benchmark/timing.sh +0 -10
  296. data/doc/bg.png +0 -0
  297. data/doc/default.scss.css +0 -181
  298. data/doc/default.template +0 -68
  299. data/doc/design.scss.css +0 -441
  300. data/doc/documentation.page +0 -84
  301. data/doc/documentation.template +0 -20
  302. data/doc/index.page +0 -94
  303. data/doc/installation.page +0 -88
  304. data/doc/links.markdown +0 -6
  305. data/doc/metainfo +0 -3
  306. data/doc/news.feed +0 -10
  307. data/doc/news.page +0 -29
  308. data/doc/options.page +0 -10
  309. data/doc/quickref.page +0 -598
  310. data/doc/sidebar.template +0 -21
  311. data/doc/syntax.page +0 -1700
  312. data/doc/tests.page +0 -91
  313. data/doc/virtual +0 -2
  314. data/lib/kramdown/compatibility.rb +0 -49
  315. data/lib/kramdown/utils/ordered_hash.rb +0 -100
  316. data/setup.rb +0 -1585
  317. data/test/testcases/block/07_horizontal_rule/error.html.19 +0 -7
  318. data/test/testcases/block/09_html/html_to_native/typography.html.19 +0 -1
  319. data/test/testcases/block/09_html/simple.html.19 +0 -64
  320. data/test/testcases/block/14_table/simple.html.19 +0 -177
  321. data/test/testcases/span/01_link/inline.html.19 +0 -46
  322. data/test/testcases/span/01_link/reference.html.19 +0 -37
  323. data/test/testcases/span/text_substitutions/entities_as_char.html.19 +0 -1
@@ -1,26 +1,15 @@
1
- # -*- coding: utf-8 -*-
1
+ # -*- coding: utf-8; frozen_string_literal: true -*-
2
2
  #
3
3
  #--
4
- # Copyright (C) 2009-2012 Thomas Leitner <t_leitner@gmx.at>
4
+ # Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
5
5
  #
6
- # This file is part of kramdown.
7
- #
8
- # kramdown is free software: you can redistribute it and/or modify
9
- # it under the terms of the GNU General Public License as published by
10
- # the Free Software Foundation, either version 3 of the License, or
11
- # (at your option) any later version.
12
- #
13
- # This program is distributed in the hope that it will be useful,
14
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- # GNU General Public License for more details.
17
- #
18
- # You should have received a copy of the GNU General Public License
19
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
6
+ # This file is part of kramdown which is licensed under the MIT.
20
7
  #++
21
8
  #
22
9
 
23
10
  require 'erb'
11
+ require 'kramdown/utils'
12
+ require 'kramdown/document'
24
13
 
25
14
  module Kramdown
26
15
 
@@ -71,47 +60,84 @@ module Kramdown
71
60
  end
72
61
  private_class_method(:new, :allocate)
73
62
 
63
+ # Returns whether the template should be applied before the conversion of the tree.
64
+ #
65
+ # Defaults to false.
66
+ def apply_template_before?
67
+ false
68
+ end
69
+
70
+ # Returns whether the template should be applied after the conversion of the tree.
71
+ #
72
+ # Defaults to true.
73
+ def apply_template_after?
74
+ true
75
+ end
76
+
74
77
  # Convert the element tree +tree+ and return the resulting conversion object (normally a
75
78
  # string) and an array with warning messages. The parameter +options+ specifies the conversion
76
79
  # options that should be used.
77
80
  #
78
81
  # Initializes a new instance of the calling class and then calls the #convert method with
79
- # +tree+ as parameter. If the +template+ option is specified and non-empty, the result is
80
- # rendered into the specified template. The template resolution is done in the following way:
82
+ # +tree+ as parameter.
83
+ #
84
+ # If the +template+ option is specified and non-empty, the template is evaluate with ERB
85
+ # before and/or after the tree conversion depending on the result of #apply_template_before?
86
+ # and #apply_template_after?. If the template is evaluated before, an empty string is used for
87
+ # the body; if evaluated after, the result is used as body. See ::apply_template.
88
+ #
89
+ # The template resolution is done in the following way (for the converter ConverterName):
81
90
  #
82
91
  # 1. Look in the current working directory for the template.
83
92
  #
84
- # 2. Append +.convertername+ (e.g. +.html+) to the template name and look for the resulting
85
- # file in the current working directory.
93
+ # 2. Append +.converter_name+ (e.g. +.html+) to the template name and look for the resulting
94
+ # file in the current working directory (the form +.convertername+ is deprecated).
95
+ #
96
+ # 3. Append +.converter_name+ to the template name and look for it in the kramdown data
97
+ # directory (the form +.convertername+ is deprecated).
86
98
  #
87
- # 3. Append +.convertername+ to the template name and look for it in the kramdown data
88
- # directory.
99
+ # 4. Check if the template name starts with 'string://' and if so, strip this prefix away and
100
+ # use the rest as template.
89
101
  def self.convert(tree, options = {})
90
102
  converter = new(tree, ::Kramdown::Options.merge(options.merge(tree.options[:options] || {})))
103
+
104
+ if !converter.options[:template].empty? && converter.apply_template_before?
105
+ apply_template(converter, '')
106
+ end
91
107
  result = converter.convert(tree)
92
- result = apply_template(converter, result) if !converter.options[:template].empty?
108
+ if result.respond_to?(:encode!) && result.encoding != Encoding::BINARY
109
+ result.encode!(tree.options[:encoding] ||
110
+ (raise ::Kramdown::Error, "Missing encoding option on root element"))
111
+ end
112
+ if !converter.options[:template].empty? && converter.apply_template_after?
113
+ result = apply_template(converter, result)
114
+ end
115
+
93
116
  [result, converter.warnings]
94
117
  end
95
118
 
96
119
  # Convert the element +el+ and return the resulting object.
97
120
  #
98
121
  # This is the only method that has to be implemented by sub-classes!
99
- def convert(el)
122
+ def convert(_el)
100
123
  raise NotImplementedError
101
124
  end
102
125
 
103
126
  # Apply the +template+ using +body+ as the body string.
127
+ #
128
+ # The template is evaluated using ERB and the body is available in the @body instance variable
129
+ # and the converter object in the @converter instance variable.
104
130
  def self.apply_template(converter, body) # :nodoc:
105
131
  erb = ERB.new(get_template(converter.options[:template]))
106
132
  obj = Object.new
107
133
  obj.instance_variable_set(:@converter, converter)
108
134
  obj.instance_variable_set(:@body, body)
109
- erb.result(obj.instance_eval{binding})
135
+ erb.result(obj.instance_eval { binding })
110
136
  end
111
137
 
112
138
  # Return the template specified by +template+.
113
139
  def self.get_template(template) # :nodoc:
114
- format_ext = '.' + self.name.split(/::/).last.downcase
140
+ format_ext = '.' + ::Kramdown::Utils.snake_case(self.name.split(/::/).last)
115
141
  shipped = File.join(::Kramdown.data_dir, template + format_ext)
116
142
  if File.exist?(template)
117
143
  File.read(template)
@@ -119,6 +145,8 @@ module Kramdown
119
145
  File.read(template + format_ext)
120
146
  elsif File.exist?(shipped)
121
147
  File.read(shipped)
148
+ elsif template.start_with?('string://')
149
+ template.sub(/\Astring:\/\//, '')
122
150
  else
123
151
  raise "The specified template file #{template} does not exist"
124
152
  end
@@ -132,7 +160,7 @@ module Kramdown
132
160
  # Return +true+ if the header element +el+ should be used for the table of contents (as
133
161
  # specified by the +toc_levels+ option).
134
162
  def in_toc?(el)
135
- @options[:toc_levels].include?(el.options[:level]) and ((not el.attr['class'].split.include?('no_toc')) rescue true)
163
+ @options[:toc_levels].include?(el.options[:level]) && (el.attr['class'] || '') !~ /\bno_toc\b/
136
164
  end
137
165
 
138
166
  # Return the output header level given a level.
@@ -144,8 +172,8 @@ module Kramdown
144
172
 
145
173
  # Extract the code block/span language from the attributes.
146
174
  def extract_code_language(attr)
147
- if attr['class'] && attr['class'] =~ /\blanguage-\w+\b/
148
- attr['class'].scan(/\blanguage-(\w+)\b/).first.first
175
+ if attr['class'] && attr['class'] =~ /\blanguage-\S+/
176
+ attr['class'].scan(/\blanguage-(\S+)/).first.first
149
177
  end
150
178
  end
151
179
 
@@ -154,31 +182,67 @@ module Kramdown
154
182
  # *Warning*: This version will modify the given attributes if a language is present.
155
183
  def extract_code_language!(attr)
156
184
  lang = extract_code_language(attr)
157
- attr['class'] = attr['class'].sub(/\blanguage-\w+\b/, '').strip if lang
185
+ attr['class'] = attr['class'].sub(/\blanguage-\S+/, '').strip if lang
158
186
  attr.delete('class') if lang && attr['class'].empty?
159
187
  lang
160
188
  end
161
189
 
190
+ # Highlight the given +text+ in the language +lang+ with the syntax highlighter configured
191
+ # through the option 'syntax_highlighter'.
192
+ def highlight_code(text, lang, type, opts = {})
193
+ return nil unless @options[:syntax_highlighter]
194
+
195
+ highlighter = ::Kramdown::Converter.syntax_highlighter(@options[:syntax_highlighter])
196
+ if highlighter
197
+ highlighter.call(self, text, lang, type, opts)
198
+ else
199
+ warning("The configured syntax highlighter #{@options[:syntax_highlighter]} is not available.")
200
+ nil
201
+ end
202
+ end
203
+
204
+ # Format the given math element with the math engine configured through the option
205
+ # 'math_engine'.
206
+ def format_math(el, opts = {})
207
+ return nil unless @options[:math_engine]
208
+
209
+ engine = ::Kramdown::Converter.math_engine(@options[:math_engine])
210
+ if engine
211
+ engine.call(self, el, opts)
212
+ else
213
+ warning("The configured math engine #{@options[:math_engine]} is not available.")
214
+ nil
215
+ end
216
+ end
217
+
162
218
  # Generate an unique alpha-numeric ID from the the string +str+ for use as a header ID.
163
219
  #
164
220
  # Uses the option +auto_id_prefix+: the value of this option is prepended to every generated
165
221
  # ID.
166
222
  def generate_id(str)
167
- gen_id = str.gsub(/^[^a-zA-Z]+/, '')
168
- gen_id.tr!('^a-zA-Z0-9 -', '')
169
- gen_id.tr!(' ', '-')
170
- gen_id.downcase!
171
- gen_id = 'section' if gen_id.length == 0
223
+ str = ::Kramdown::Utils::Unidecoder.decode(str) if @options[:transliterated_header_ids]
224
+ gen_id = basic_generate_id(str)
225
+ gen_id = 'section' if gen_id.empty?
172
226
  @used_ids ||= {}
173
- if @used_ids.has_key?(gen_id)
174
- gen_id += '-' << (@used_ids[gen_id] += 1).to_s
227
+ if @used_ids.key?(gen_id)
228
+ gen_id += "-#{@used_ids[gen_id] += 1}"
175
229
  else
176
230
  @used_ids[gen_id] = 0
177
231
  end
178
232
  @options[:auto_id_prefix] + gen_id
179
233
  end
180
234
 
181
- SMART_QUOTE_INDICES = {:lsquo => 0, :rsquo => 1, :ldquo => 2, :rdquo => 3} # :nodoc:
235
+ # The basic version of the ID generator, without any special provisions for empty or unique
236
+ # IDs.
237
+ def basic_generate_id(str)
238
+ gen_id = str.gsub(/^[^a-zA-Z]+/, '')
239
+ gen_id.tr!('^a-zA-Z0-9 -', '')
240
+ gen_id.tr!(' ', '-')
241
+ gen_id.downcase!
242
+ gen_id
243
+ end
244
+
245
+ SMART_QUOTE_INDICES = {lsquo: 0, rsquo: 1, ldquo: 2, rdquo: 3} # :nodoc:
182
246
 
183
247
  # Return the entity that represents the given smart_quote element.
184
248
  def smart_quote_entity(el)
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
5
+ #
6
+ # This file is part of kramdown which is licensed under the MIT.
7
+ #++
8
+ #
9
+
10
+ require 'kramdown/parser'
11
+ require 'kramdown/converter'
12
+ require 'kramdown/utils'
13
+
14
+ module Kramdown
15
+
16
+ module Converter
17
+
18
+ # Converts a Kramdown::Document to a nested hash for further processing or debug output.
19
+ class HashAST < Base
20
+
21
+ def convert(el)
22
+ hash = {type: el.type}
23
+ hash[:attr] = el.attr unless el.attr.empty?
24
+ hash[:value] = el.value unless el.value.nil?
25
+ hash[:options] = el.options unless el.options.empty?
26
+ unless el.children.empty?
27
+ hash[:children] = []
28
+ el.children.each {|child| hash[:children] << convert(child) }
29
+ end
30
+ hash
31
+ end
32
+
33
+ end
34
+
35
+ HashAst = HashAST
36
+
37
+ end
38
+ end
@@ -1,27 +1,15 @@
1
- # -*- coding: utf-8 -*-
1
+ # -*- coding: utf-8; frozen_string_literal: true -*-
2
2
  #
3
3
  #--
4
- # Copyright (C) 2009-2012 Thomas Leitner <t_leitner@gmx.at>
4
+ # Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
5
5
  #
6
- # This file is part of kramdown.
7
- #
8
- # kramdown is free software: you can redistribute it and/or modify
9
- # it under the terms of the GNU General Public License as published by
10
- # the Free Software Foundation, either version 3 of the License, or
11
- # (at your option) any later version.
12
- #
13
- # This program is distributed in the hope that it will be useful,
14
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- # GNU General Public License for more details.
17
- #
18
- # You should have received a copy of the GNU General Public License
19
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
6
+ # This file is part of kramdown which is licensed under the MIT.
20
7
  #++
21
8
  #
22
9
 
23
- require 'rexml/parsers/baseparser'
24
- require 'kramdown/parser/html'
10
+ require 'kramdown/parser'
11
+ require 'kramdown/converter'
12
+ require 'kramdown/utils'
25
13
 
26
14
  module Kramdown
27
15
 
@@ -41,15 +29,6 @@ module Kramdown
41
29
  # HTML element.
42
30
  class Html < Base
43
31
 
44
- begin
45
- require 'coderay'
46
-
47
- # Highlighting via coderay is available if this constant is +true+.
48
- HIGHLIGHTING_AVAILABLE = true
49
- rescue LoadError
50
- HIGHLIGHTING_AVAILABLE = false # :nodoc:
51
- end
52
-
53
32
  include ::Kramdown::Utils::Html
54
33
  include ::Kramdown::Parser::Html::Constants
55
34
 
@@ -61,20 +40,22 @@ module Kramdown
61
40
  super
62
41
  @footnote_counter = @footnote_start = @options[:footnote_nr]
63
42
  @footnotes = []
43
+ @footnotes_by_name = {}
44
+ @footnote_location = nil
64
45
  @toc = []
65
46
  @toc_code = nil
66
47
  @indent = 2
67
48
  @stack = []
68
- @coderay_enabled = @options[:enable_coderay] && HIGHLIGHTING_AVAILABLE
69
- end
70
49
 
71
- # The mapping of element type to conversion method.
72
- DISPATCHER = Hash.new {|h,k| h[k] = "convert_#{k}"}
50
+ # stash string representation of symbol to avoid allocations from multiple interpolations.
51
+ @highlighter_class = " highlighter-#{options[:syntax_highlighter]}"
52
+ @dispatcher = Hash.new {|h, k| h[k] = :"convert_#{k}" }
53
+ end
73
54
 
74
55
  # Dispatch the conversion of the element +el+ to a +convert_TYPE+ method using the +type+ of
75
56
  # the element.
76
57
  def convert(el, indent = -@indent)
77
- send(DISPATCHER[el.type], el, indent)
58
+ send(@dispatcher[el.type], el, indent)
78
59
  end
79
60
 
80
61
  # Return the converted content of the children of +el+ as a string. The parameter +indent+ has
@@ -83,42 +64,57 @@ module Kramdown
83
64
  # Pushes +el+ onto the @stack before converting the child elements and pops it from the stack
84
65
  # afterwards.
85
66
  def inner(el, indent)
86
- result = ''
67
+ result = +''
87
68
  indent += @indent
88
69
  @stack.push(el)
89
70
  el.children.each do |inner_el|
90
- result << send(DISPATCHER[inner_el.type], inner_el, indent)
71
+ result << send(@dispatcher[inner_el.type], inner_el, indent)
91
72
  end
92
73
  @stack.pop
93
74
  result
94
75
  end
95
76
 
96
- def convert_blank(el, indent)
77
+ def convert_blank(_el, _indent)
97
78
  "\n"
98
79
  end
99
80
 
100
- def convert_text(el, indent)
101
- escape_html(el.value, :text)
81
+ def convert_text(el, _indent)
82
+ escaped = escape_html(el.value, :text)
83
+ @options[:remove_line_breaks_for_cjk] ? fix_cjk_line_break(escaped) : escaped
102
84
  end
103
85
 
104
86
  def convert_p(el, indent)
105
87
  if el.options[:transparent]
106
88
  inner(el, indent)
89
+ elsif el.children.size == 1 && el.children.first.type == :img &&
90
+ el.children.first.options[:ial]&.[](:refs)&.include?('standalone')
91
+ convert_standalone_image(el.children.first, indent)
107
92
  else
108
- format_as_block_html(el.type, el.attr, inner(el, indent), indent)
93
+ format_as_block_html("p", el.attr, inner(el, indent), indent)
109
94
  end
110
95
  end
111
96
 
97
+ # Helper method used by +convert_p+ to convert a paragraph that only contains a single :img
98
+ # element.
99
+ def convert_standalone_image(el, indent)
100
+ attr = el.attr.dup
101
+ figure_attr = {}
102
+ figure_attr['class'] = attr.delete('class') if attr.key?('class')
103
+ figure_attr['id'] = attr.delete('id') if attr.key?('id')
104
+ body = "#{' ' * (indent + @indent)}<img#{html_attributes(attr)} />\n" \
105
+ "#{' ' * (indent + @indent)}<figcaption>#{attr['alt']}</figcaption>\n"
106
+ format_as_indented_block_html("figure", figure_attr, body, indent)
107
+ end
108
+
112
109
  def convert_codeblock(el, indent)
113
110
  attr = el.attr.dup
114
111
  lang = extract_code_language!(attr)
115
- if @coderay_enabled && (lang || @options[:coderay_default_lang])
116
- opts = {:wrap => @options[:coderay_wrap], :line_numbers => @options[:coderay_line_numbers],
117
- :line_number_start => @options[:coderay_line_number_start], :tab_width => @options[:coderay_tab_width],
118
- :bold_every => @options[:coderay_bold_every], :css => @options[:coderay_css]}
119
- lang = (lang || @options[:coderay_default_lang]).to_sym
120
- result = CodeRay.scan(el.value, lang).html(opts).chomp << "\n"
121
- "#{' '*indent}<div#{html_attributes(attr)}>#{result}#{' '*indent}</div>\n"
112
+ hl_opts = {}
113
+ highlighted_code = highlight_code(el.value, el.options[:lang] || lang, :block, hl_opts)
114
+
115
+ if highlighted_code
116
+ add_syntax_highlighter_to_class_attr(attr, lang || hl_opts[:default_lang])
117
+ "#{' ' * indent}<div#{html_attributes(attr)}>#{highlighted_code}#{' ' * indent}</div>\n"
122
118
  else
123
119
  result = escape_html(el.value)
124
120
  result.chomp!
@@ -135,12 +131,13 @@ module Kramdown
135
131
  end
136
132
  code_attr = {}
137
133
  code_attr['class'] = "language-#{lang}" if lang
138
- "#{' '*indent}<pre#{html_attributes(attr)}><code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n"
134
+ "#{' ' * indent}<pre#{html_attributes(attr)}>" \
135
+ "<code#{html_attributes(code_attr)}>#{result}\n</code></pre>\n"
139
136
  end
140
137
  end
141
138
 
142
139
  def convert_blockquote(el, indent)
143
- format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
140
+ format_as_indented_block_html("blockquote", el.attr, inner(el, indent), indent)
144
141
  end
145
142
 
146
143
  def convert_header(el, indent)
@@ -154,50 +151,68 @@ module Kramdown
154
151
  end
155
152
 
156
153
  def convert_hr(el, indent)
157
- "#{' '*indent}<hr />\n"
154
+ "#{' ' * indent}<hr#{html_attributes(el.attr)} />\n"
158
155
  end
159
156
 
157
+ ZERO_TO_ONETWENTYEIGHT = (0..128).to_a.freeze
158
+ private_constant :ZERO_TO_ONETWENTYEIGHT
159
+
160
160
  def convert_ul(el, indent)
161
- if !@toc_code && (el.options[:ial][:refs].include?('toc') rescue nil) && (el.type == :ul || el.type == :ol)
162
- @toc_code = [el.type, el.attr, (0..128).to_a.map{|a| rand(36).to_s(36)}.join]
161
+ if !@toc_code && el.options.dig(:ial, :refs)&.include?('toc')
162
+ @toc_code = [el.type, el.attr, ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join]
163
163
  @toc_code.last
164
+ elsif !@footnote_location && el.options.dig(:ial, :refs)&.include?('footnotes')
165
+ @footnote_location = ZERO_TO_ONETWENTYEIGHT.map { rand(36).to_s(36) }.join
164
166
  else
165
167
  format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
166
168
  end
167
169
  end
168
- alias :convert_ol :convert_ul
169
- alias :convert_dl :convert_ul
170
+ alias convert_ol convert_ul
171
+
172
+ def convert_dl(el, indent)
173
+ format_as_indented_block_html("dl", el.attr, inner(el, indent), indent)
174
+ end
170
175
 
171
176
  def convert_li(el, indent)
172
- output = ' '*indent << "<#{el.type}" << html_attributes(el.attr) << ">"
177
+ output = ' ' * indent << "<#{el.type}" << html_attributes(el.attr) << ">"
173
178
  res = inner(el, indent)
174
179
  if el.children.empty? || (el.children.first.type == :p && el.children.first.options[:transparent])
175
- output << res << (res =~ /\n\Z/ ? ' '*indent : '')
180
+ output << res << (res =~ /\n\Z/ ? ' ' * indent : '')
176
181
  else
177
- output << "\n" << res << ' '*indent
182
+ output << "\n" << res << ' ' * indent
178
183
  end
179
184
  output << "</#{el.type}>\n"
180
185
  end
181
- alias :convert_dd :convert_li
186
+ alias convert_dd convert_li
182
187
 
183
188
  def convert_dt(el, indent)
184
- format_as_block_html(el.type, el.attr, inner(el, indent), indent)
189
+ attr = el.attr.dup
190
+ @stack.last.options[:ial][:refs].each do |ref|
191
+ if ref =~ /\Aauto_ids(?:-([\w-]+))?/
192
+ attr['id'] = "#{$1}#{basic_generate_id(el.options[:raw_text])}".lstrip
193
+ break
194
+ end
195
+ end if !attr['id'] && @stack.last.options[:ial] && @stack.last.options[:ial][:refs]
196
+ format_as_block_html("dt", attr, inner(el, indent), indent)
185
197
  end
186
198
 
187
199
  def convert_html_element(el, indent)
188
200
  res = inner(el, indent)
189
201
  if el.options[:category] == :span
190
- "<#{el.value}#{html_attributes(el.attr)}" << (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>")
202
+ "<#{el.value}#{html_attributes(el.attr)}" + \
203
+ (res.empty? && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value) ? " />" : ">#{res}</#{el.value}>")
191
204
  else
192
- output = ''
193
- output << ' '*indent if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
205
+ output = +''
206
+ if @stack.last.type != :html_element || @stack.last.options[:content_model] != :raw
207
+ output << ' ' * indent
208
+ end
194
209
  output << "<#{el.value}#{html_attributes(el.attr)}"
195
210
  if el.options[:is_closed] && el.options[:content_model] == :raw
196
211
  output << " />"
197
212
  elsif !res.empty? && el.options[:content_model] != :block
198
213
  output << ">#{res}</#{el.value}>"
199
214
  elsif !res.empty?
200
- output << ">\n#{res.chomp}\n" << ' '*indent << "</#{el.value}>"
215
+ output << ">\n#{res.chomp}\n" << ' ' * indent << "</#{el.value}>"
201
216
  elsif HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
202
217
  output << " />"
203
218
  else
@@ -209,21 +224,22 @@ module Kramdown
209
224
  end
210
225
 
211
226
  def convert_xml_comment(el, indent)
212
- if el.options[:category] == :block && (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
213
- ' '*indent << el.value << "\n"
227
+ if el.options[:category] == :block &&
228
+ (@stack.last.type != :html_element || @stack.last.options[:content_model] != :raw)
229
+ ' ' * indent << el.value << "\n"
214
230
  else
215
231
  el.value
216
232
  end
217
233
  end
218
- alias :convert_xml_pi :convert_xml_comment
234
+ alias convert_xml_pi convert_xml_comment
219
235
 
220
236
  def convert_table(el, indent)
221
237
  format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
222
238
  end
223
- alias :convert_thead :convert_table
224
- alias :convert_tbody :convert_table
225
- alias :convert_tfoot :convert_table
226
- alias :convert_tr :convert_table
239
+ alias convert_thead convert_table
240
+ alias convert_tbody convert_table
241
+ alias convert_tfoot convert_table
242
+ alias convert_tr convert_table
227
243
 
228
244
  ENTITY_NBSP = ::Kramdown::Utils::Entities.entity('nbsp') # :nodoc:
229
245
 
@@ -234,56 +250,63 @@ module Kramdown
234
250
  alignment = @stack[-3].options[:alignment][@stack.last.children.index(el)]
235
251
  if alignment != :default
236
252
  attr = el.attr.dup
237
- attr['style'] = (attr.has_key?('style') ? "#{attr['style']}; ": '') << "text-align: #{alignment}"
253
+ attr['style'] = (attr.key?('style') ? "#{attr['style']}; " : '') + "text-align: #{alignment}"
238
254
  end
239
255
  format_as_block_html(type, attr, res.empty? ? entity_to_str(ENTITY_NBSP) : res, indent)
240
256
  end
241
257
 
242
258
  def convert_comment(el, indent)
243
259
  if el.options[:category] == :block
244
- "#{' '*indent}<!-- #{el.value} -->\n"
260
+ "#{' ' * indent}<!-- #{el.value} -->\n"
245
261
  else
246
262
  "<!-- #{el.value} -->"
247
263
  end
248
264
  end
249
265
 
250
- def convert_br(el, indent)
266
+ def convert_br(_el, _indent)
251
267
  "<br />"
252
268
  end
253
269
 
254
270
  def convert_a(el, indent)
255
- res = inner(el, indent)
256
- attr = el.attr.dup
257
- if attr['href'] =~ /^mailto:/
258
- mail_addr = attr['href'].sub(/^mailto:/, '')
259
- attr['href'] = obfuscate('mailto') << ":" << obfuscate(mail_addr)
260
- res = obfuscate(res) if res == mail_addr
261
- end
262
- format_as_span_html(el.type, attr, res)
271
+ format_as_span_html("a", el.attr, inner(el, indent))
263
272
  end
264
273
 
265
- def convert_img(el, indent)
274
+ def convert_img(el, _indent)
266
275
  "<img#{html_attributes(el.attr)} />"
267
276
  end
268
277
 
269
- def convert_codespan(el, indent)
270
- lang = extract_code_language(el.attr)
271
- result = if @coderay_enabled && lang
272
- CodeRay.scan(el.value, lang.to_sym).html(:wrap => :span, :css => @options[:coderay_css]).chomp
273
- else
274
- escape_html(el.value)
275
- end
276
- format_as_span_html('code', el.attr, result)
278
+ def convert_codespan(el, _indent)
279
+ attr = el.attr.dup
280
+ lang = extract_code_language(attr)
281
+ hl_opts = {}
282
+ result = highlight_code(el.value, lang, :span, hl_opts)
283
+ if result
284
+ add_syntax_highlighter_to_class_attr(attr, hl_opts[:default_lang])
285
+ else
286
+ result = escape_html(el.value)
287
+ end
288
+
289
+ format_as_span_html('code', attr, result)
277
290
  end
278
291
 
279
- def convert_footnote(el, indent)
280
- number = @footnote_counter
281
- @footnote_counter += 1
282
- @footnotes << [el.options[:name], el.value]
283
- "<sup id=\"fnref:#{el.options[:name]}\"><a href=\"#fn:#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
292
+ def convert_footnote(el, _indent)
293
+ repeat = ''
294
+ name = @options[:footnote_prefix] + el.options[:name]
295
+ if (footnote = @footnotes_by_name[name])
296
+ number = footnote[2]
297
+ repeat = ":#{footnote[3] += 1}"
298
+ else
299
+ number = @footnote_counter
300
+ @footnote_counter += 1
301
+ @footnotes << [name, el.value, number, 0]
302
+ @footnotes_by_name[name] = @footnotes.last
303
+ end
304
+ "<sup id=\"fnref:#{name}#{repeat}\" role=\"doc-noteref\">" \
305
+ "<a href=\"#fn:#{name}\" class=\"footnote\">" \
306
+ "#{number}</a></sup>"
284
307
  end
285
308
 
286
- def convert_raw(el, indent)
309
+ def convert_raw(el, _indent)
287
310
  if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('html')
288
311
  el.value + (el.options[:category] == :block ? "\n" : '')
289
312
  else
@@ -294,56 +317,71 @@ module Kramdown
294
317
  def convert_em(el, indent)
295
318
  format_as_span_html(el.type, el.attr, inner(el, indent))
296
319
  end
297
- alias :convert_strong :convert_em
320
+ alias convert_strong convert_em
298
321
 
299
- def convert_entity(el, indent)
322
+ def convert_entity(el, _indent)
300
323
  entity_to_str(el.value, el.options[:original])
301
324
  end
302
325
 
303
326
  TYPOGRAPHIC_SYMS = {
304
- :mdash => [::Kramdown::Utils::Entities.entity('mdash')],
305
- :ndash => [::Kramdown::Utils::Entities.entity('ndash')],
306
- :hellip => [::Kramdown::Utils::Entities.entity('hellip')],
307
- :laquo_space => [::Kramdown::Utils::Entities.entity('laquo'), ::Kramdown::Utils::Entities.entity('nbsp')],
308
- :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
309
- :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
310
- :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
327
+ mdash: [::Kramdown::Utils::Entities.entity('mdash')],
328
+ ndash: [::Kramdown::Utils::Entities.entity('ndash')],
329
+ hellip: [::Kramdown::Utils::Entities.entity('hellip')],
330
+ laquo_space: [::Kramdown::Utils::Entities.entity('laquo'),
331
+ ::Kramdown::Utils::Entities.entity('nbsp')],
332
+ raquo_space: [::Kramdown::Utils::Entities.entity('nbsp'),
333
+ ::Kramdown::Utils::Entities.entity('raquo')],
334
+ laquo: [::Kramdown::Utils::Entities.entity('laquo')],
335
+ raquo: [::Kramdown::Utils::Entities.entity('raquo')],
311
336
  } # :nodoc:
312
- def convert_typographic_sym(el, indent)
313
- TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
337
+ def convert_typographic_sym(el, _indent)
338
+ if (result = @options[:typographic_symbols][el.value])
339
+ escape_html(result, :text)
340
+ else
341
+ TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e) }.join('')
342
+ end
314
343
  end
315
344
 
316
- def convert_smart_quote(el, indent)
345
+ def convert_smart_quote(el, _indent)
317
346
  entity_to_str(smart_quote_entity(el))
318
347
  end
319
348
 
320
349
  def convert_math(el, indent)
321
- block = (el.options[:category] == :block)
322
- value = (el.value =~ /<|&/ ? "% <![CDATA[\n#{el.value} %]]>" : el.value)
323
- type = {:type => "math/tex#{block ? '; mode=display' : ''}"}
324
- if block
325
- format_as_block_html('script', type, value, indent)
350
+ if (result = format_math(el, indent: indent))
351
+ result
326
352
  else
327
- format_as_span_html('script', type, value)
353
+ attr = el.attr.dup
354
+ attr['class'] = "#{attr['class']} kdmath".lstrip
355
+ if el.options[:category] == :block
356
+ format_as_block_html('div', attr, "$$\n#{el.value}\n$$", indent)
357
+ else
358
+ format_as_span_html('span', attr, "$#{el.value}$")
359
+ end
328
360
  end
329
361
  end
330
362
 
331
- def convert_abbreviation(el, indent)
363
+ def convert_abbreviation(el, _indent)
332
364
  title = @root.options[:abbrev_defs][el.value]
333
- format_as_span_html("abbr", {:title => (title.empty? ? nil : title)}, el.value)
365
+ attr = @root.options[:abbrev_attr][el.value].dup
366
+ attr['title'] = title unless title.empty?
367
+ format_as_span_html("abbr", attr, el.value)
334
368
  end
335
369
 
336
370
  def convert_root(el, indent)
337
371
  result = inner(el, indent)
338
- result << footnote_content
372
+ if @footnote_location
373
+ result.sub!(/#{@footnote_location}/, footnote_content.gsub(/\\/, "\\\\\\\\"))
374
+ else
375
+ result << footnote_content
376
+ end
339
377
  if @toc_code
340
378
  toc_tree = generate_toc_tree(@toc, @toc_code[0], @toc_code[1] || {})
341
- text = if toc_tree.children.size > 0
379
+ text = if !toc_tree.children.empty?
342
380
  convert(toc_tree, 0)
343
381
  else
344
382
  ''
345
383
  end
346
- result.sub!(/#{@toc_code.last}/, text)
384
+ result.sub!(/#{@toc_code.last}/, text.gsub(/\\/, "\\\\\\\\"))
347
385
  end
348
386
  result
349
387
  end
@@ -355,30 +393,39 @@ module Kramdown
355
393
 
356
394
  # Format the given element as block HTML.
357
395
  def format_as_block_html(name, attr, body, indent)
358
- "#{' '*indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n"
396
+ "#{' ' * indent}<#{name}#{html_attributes(attr)}>#{body}</#{name}>\n"
359
397
  end
360
398
 
361
399
  # Format the given element as block HTML with a newline after the start tag and indentation
362
400
  # before the end tag.
363
401
  def format_as_indented_block_html(name, attr, body, indent)
364
- "#{' '*indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' '*indent}</#{name}>\n"
402
+ "#{' ' * indent}<#{name}#{html_attributes(attr)}>\n#{body}#{' ' * indent}</#{name}>\n"
403
+ end
404
+
405
+ # Add the syntax highlighter name to the 'class' attribute of the given attribute hash. And
406
+ # overwrites or add a "language-LANG" part using the +lang+ parameter if +lang+ is not nil.
407
+ def add_syntax_highlighter_to_class_attr(attr, lang = nil)
408
+ (attr['class'] = (attr['class'] || '') + @highlighter_class).lstrip!
409
+ attr['class'].sub!(/\blanguage-\S+|(^)/) { "language-#{lang}#{$1 ? ' ' : ''}" } if lang
365
410
  end
366
411
 
367
412
  # Generate and return an element tree for the table of contents.
368
413
  def generate_toc_tree(toc, type, attr)
369
- sections = Element.new(type, nil, attr)
414
+ sections = Element.new(type, nil, attr.dup)
370
415
  sections.attr['id'] ||= 'markdown-toc'
371
416
  stack = []
372
417
  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))))
418
+ li = Element.new(:li, nil, nil, level: level)
419
+ li.children << Element.new(:p, nil, nil, transparent: true)
420
+ a = Element.new(:a, nil)
421
+ a.attr['href'] = "##{id}"
422
+ a.attr['id'] = "#{sections.attr['id']}-#{id}"
423
+ a.children.concat(fix_for_toc_entry(Marshal.load(Marshal.dump(children))))
377
424
  li.children.last.children << a
378
425
  li.children << Element.new(type)
379
426
 
380
427
  success = false
381
- while !success
428
+ until success
382
429
  if stack.empty?
383
430
  sections.children << li
384
431
  stack << li
@@ -389,17 +436,32 @@ module Kramdown
389
436
  success = true
390
437
  else
391
438
  item = stack.pop
392
- item.children.pop unless item.children.last.children.size > 0
439
+ item.children.pop if item.children.last.children.empty?
393
440
  end
394
441
  end
395
442
  end
396
- while !stack.empty?
443
+ until stack.empty?
397
444
  item = stack.pop
398
- item.children.pop unless item.children.last.children.size > 0
445
+ item.children.pop if item.children.last.children.empty?
399
446
  end
400
447
  sections
401
448
  end
402
449
 
450
+ # Fixes the elements for use in a TOC entry.
451
+ def fix_for_toc_entry(elements)
452
+ remove_footnotes(elements)
453
+ unwrap_links(elements)
454
+ elements
455
+ end
456
+
457
+ # Remove all link elements by unwrapping them.
458
+ def unwrap_links(elements)
459
+ elements.map! do |c|
460
+ unwrap_links(c.children)
461
+ c.type == :a ? c.children : c
462
+ end.flatten!
463
+ end
464
+
403
465
  # Remove all footnotes from the given elements.
404
466
  def remove_footnotes(elements)
405
467
  elements.delete_if do |c|
@@ -410,32 +472,61 @@ module Kramdown
410
472
 
411
473
  # Obfuscate the +text+ by using HTML entities.
412
474
  def obfuscate(text)
413
- result = ""
475
+ result = +''
414
476
  text.each_byte do |b|
415
- result << (b > 128 ? b.chr : "&#%03d;" % b)
477
+ result << (b > 128 ? b.chr : sprintf("&#%03d;", b))
416
478
  end
417
- result.force_encoding(text.encoding) if result.respond_to?(:force_encoding)
479
+ result.force_encoding(text.encoding)
418
480
  result
419
481
  end
420
482
 
421
- # Return a HTML ordered list with the footnote content for the used footnotes.
483
+ FOOTNOTE_BACKLINK_FMT = "%s<a href=\"#fnref:%s\" class=\"reversefootnote\" role=\"doc-backlink\">%s</a>"
484
+
485
+ # Return an HTML ordered list with the footnote content for the used footnotes.
422
486
  def footnote_content
423
487
  ol = Element.new(:ol)
424
488
  ol.attr['start'] = @footnote_start if @footnote_start != 1
425
- @footnotes.each do |name, data|
426
- li = Element.new(:li, nil, {'id' => "fn:#{name}"})
489
+ i = 0
490
+ backlink_text = escape_html(@options[:footnote_backlink], :text)
491
+ while i < @footnotes.length
492
+ name, data, _, repeat = *@footnotes[i]
493
+ li = Element.new(:li, nil, 'id' => "fn:#{name}", 'role' => 'doc-endnote')
427
494
  li.children = Marshal.load(Marshal.dump(data.children))
428
- ol.children << li
429
495
 
430
- ref = Element.new(:raw, "<a href=\"#fnref:#{name}\" rel=\"reference\">&#8617;</a>")
431
- if li.children.last.type == :p
432
- para = li.children.last
433
- else
496
+ para = nil
497
+ if li.children.last.type == :p || @options[:footnote_backlink_inline]
498
+ parent = li
499
+ while !parent.children.empty? && ![:p, :header].include?(parent.children.last.type)
500
+ parent = parent.children.last
501
+ end
502
+ para = parent.children.last
503
+ insert_space = true
504
+ end
505
+
506
+ unless para
434
507
  li.children << (para = Element.new(:p))
508
+ insert_space = false
435
509
  end
436
- para.children << ref
510
+
511
+ unless @options[:footnote_backlink].empty?
512
+ nbsp = entity_to_str(ENTITY_NBSP)
513
+ value = sprintf(FOOTNOTE_BACKLINK_FMT, (insert_space ? nbsp : ''), name, backlink_text)
514
+ para.children << Element.new(:raw, value)
515
+ (1..repeat).each do |index|
516
+ value = sprintf(FOOTNOTE_BACKLINK_FMT, nbsp, "#{name}:#{index}",
517
+ "#{backlink_text}<sup>#{index + 1}</sup>")
518
+ para.children << Element.new(:raw, value)
519
+ end
520
+ end
521
+
522
+ ol.children << Element.new(:raw, convert(li, 4))
523
+ i += 1
524
+ end
525
+ if ol.children.empty?
526
+ ''
527
+ else
528
+ format_as_indented_block_html('div', {class: "footnotes", role: "doc-endnotes"}, convert(ol, 2), 0)
437
529
  end
438
- (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0))
439
530
  end
440
531
 
441
532
  end