maruku 0.6.0 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (290) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE.txt +20 -0
  3. data/bin/maruku +153 -152
  4. data/bin/marutex +2 -29
  5. data/data/entities.xml +261 -0
  6. data/docs/markdown_syntax.md +9 -21
  7. data/docs/math.md +14 -18
  8. data/lib/maruku.rb +65 -78
  9. data/lib/maruku/attributes.rb +109 -214
  10. data/lib/maruku/defaults.rb +45 -67
  11. data/lib/maruku/document.rb +44 -0
  12. data/lib/maruku/element.rb +138 -0
  13. data/lib/maruku/errors.rb +80 -0
  14. data/lib/maruku/ext/div.rb +105 -113
  15. data/lib/maruku/ext/fenced_code.rb +97 -0
  16. data/lib/maruku/ext/math.rb +22 -26
  17. data/lib/maruku/ext/math/elements.rb +20 -26
  18. data/lib/maruku/ext/math/mathml_engines/blahtex.rb +92 -104
  19. data/lib/maruku/ext/math/mathml_engines/itex2mml.rb +33 -26
  20. data/lib/maruku/ext/math/mathml_engines/none.rb +11 -19
  21. data/lib/maruku/ext/math/mathml_engines/ritex.rb +2 -4
  22. data/lib/maruku/ext/math/parsing.rb +121 -115
  23. data/lib/maruku/ext/math/to_html.rb +202 -187
  24. data/lib/maruku/ext/math/to_latex.rb +34 -21
  25. data/lib/maruku/helpers.rb +158 -257
  26. data/lib/maruku/html.rb +251 -0
  27. data/lib/maruku/input/charsource.rb +272 -319
  28. data/lib/maruku/input/extensions.rb +62 -63
  29. data/lib/maruku/input/html_helper.rb +233 -189
  30. data/lib/maruku/input/linesource.rb +90 -110
  31. data/lib/maruku/input/mdline.rb +131 -0
  32. data/lib/maruku/input/parse_block.rb +736 -613
  33. data/lib/maruku/input/parse_doc.rb +145 -217
  34. data/lib/maruku/input/parse_span.rb +740 -0
  35. data/lib/maruku/inspect_element.rb +60 -0
  36. data/lib/maruku/maruku.rb +14 -30
  37. data/lib/maruku/output/entity_table.rb +37 -0
  38. data/lib/maruku/output/s5/fancy.rb +462 -462
  39. data/lib/maruku/output/s5/to_s5.rb +115 -135
  40. data/lib/maruku/output/to_html.rb +907 -983
  41. data/lib/maruku/output/to_latex.rb +571 -563
  42. data/lib/maruku/output/to_markdown.rb +207 -162
  43. data/lib/maruku/output/to_s.rb +10 -52
  44. data/lib/maruku/string_utils.rb +129 -179
  45. data/lib/maruku/toc.rb +185 -196
  46. data/lib/maruku/version.rb +33 -38
  47. data/spec/block_docs/abbrev.md +776 -0
  48. data/{tests/unittest → spec/block_docs}/abbreviations.md +11 -20
  49. data/spec/block_docs/abbreviations2.md +27 -0
  50. data/{tests/unittest → spec/block_docs}/alt.md +2 -14
  51. data/{tests/unittest/pending → spec/block_docs}/amps.md +1 -13
  52. data/spec/block_docs/attribute_sanitize.md +22 -0
  53. data/{tests/unittest → spec/block_docs}/attributes/att2.md +0 -12
  54. data/{tests/unittest → spec/block_docs}/attributes/att3.md +2 -14
  55. data/{tests/unittest → spec/block_docs}/attributes/attributes.md +12 -16
  56. data/{tests/unittest → spec/block_docs}/attributes/circular.md +0 -12
  57. data/{tests/unittest → spec/block_docs}/attributes/default.md +1 -13
  58. data/spec/block_docs/atx_headers.md +22 -0
  59. data/spec/block_docs/auto_cdata.md +48 -0
  60. data/spec/block_docs/bad_cites.md +30 -0
  61. data/spec/block_docs/bad_divrefs.md +30 -0
  62. data/{tests/unittest → spec/block_docs}/blank.md +0 -12
  63. data/{tests/unittest → spec/block_docs}/blanks_in_code.md +16 -15
  64. data/spec/block_docs/block_quotes.md +66 -0
  65. data/{tests/unittest/loss.md → spec/block_docs/bug_def.md} +6 -18
  66. data/{tests/unittest → spec/block_docs}/bug_table.md +7 -19
  67. data/spec/block_docs/cites.md +37 -0
  68. data/{tests/unittest → spec/block_docs}/code.md +7 -14
  69. data/{tests/unittest → spec/block_docs}/code2.md +4 -14
  70. data/{tests/unittest → spec/block_docs}/code3.md +12 -16
  71. data/spec/block_docs/code4.md +79 -0
  72. data/{tests/unittest → spec/block_docs}/data_loss.md +2 -14
  73. data/spec/block_docs/div_without_newline.md +16 -0
  74. data/{tests/unittest → spec/block_docs}/divs/div1.md +0 -12
  75. data/{tests/unittest → spec/block_docs}/divs/div2.md +0 -12
  76. data/{tests/unittest → spec/block_docs}/divs/div3_nest.md +3 -15
  77. data/{tests/unittest → spec/block_docs}/easy.md +1 -13
  78. data/spec/block_docs/email.md +29 -0
  79. data/spec/block_docs/empty_cells.md +31 -0
  80. data/{tests/unittest → spec/block_docs}/encoding/iso-8859-1.md +1 -14
  81. data/{tests/unittest → spec/block_docs}/encoding/utf-8.md +0 -12
  82. data/{tests/unittest → spec/block_docs}/entities.md +33 -41
  83. data/{tests/unittest/notyet → spec/block_docs}/escape.md +2 -14
  84. data/{tests/unittest → spec/block_docs}/escaping.md +11 -22
  85. data/{tests/unittest → spec/block_docs}/extra_dl.md +2 -13
  86. data/{tests/unittest → spec/block_docs}/extra_header_id.md +14 -20
  87. data/{tests/unittest → spec/block_docs}/extra_table1.md +9 -21
  88. data/spec/block_docs/fenced_code_blocks.md +58 -0
  89. data/spec/block_docs/fenced_code_blocks_highlighted.md +17 -0
  90. data/{tests/unittest → spec/block_docs}/footnotes.md +12 -24
  91. data/spec/block_docs/footnotes2.md +82 -0
  92. data/spec/block_docs/hard.md +25 -0
  93. data/spec/block_docs/header_after_par.md +62 -0
  94. data/{tests/unittest → spec/block_docs}/headers.md +10 -18
  95. data/{tests/unittest → spec/block_docs}/hex_entities.md +7 -18
  96. data/{tests/unittest → spec/block_docs}/hrule.md +5 -12
  97. data/{tests/unittest → spec/block_docs}/html3.md +1 -13
  98. data/{tests/unittest → spec/block_docs}/html4.md +2 -14
  99. data/{tests/unittest → spec/block_docs}/html5.md +2 -14
  100. data/spec/block_docs/html_block_in_para.md +22 -0
  101. data/spec/block_docs/html_inline.md +25 -0
  102. data/spec/block_docs/html_trailing.md +31 -0
  103. data/spec/block_docs/ie.md +62 -0
  104. data/spec/block_docs/iframe.md +29 -0
  105. data/spec/block_docs/ignore_bad_header.md +9 -0
  106. data/{tests/unittest → spec/block_docs}/images.md +22 -28
  107. data/{tests/unittest → spec/block_docs}/images2.md +7 -17
  108. data/{tests/unittest → spec/block_docs}/inline_html.md +37 -67
  109. data/{tests/unittest → spec/block_docs}/inline_html2.md +1 -13
  110. data/spec/block_docs/inline_html_beginning.md +10 -0
  111. data/spec/block_docs/issue106.md +78 -0
  112. data/spec/block_docs/issue115.md +20 -0
  113. data/spec/block_docs/issue117.md +13 -0
  114. data/spec/block_docs/issue120.md +48 -0
  115. data/spec/block_docs/issue123.md +11 -0
  116. data/spec/block_docs/issue124.md +16 -0
  117. data/spec/block_docs/issue126.md +9 -0
  118. data/spec/block_docs/issue130.md +11 -0
  119. data/spec/block_docs/issue20.md +9 -0
  120. data/spec/block_docs/issue26.md +22 -0
  121. data/spec/block_docs/issue29.md +9 -0
  122. data/spec/block_docs/issue30.md +30 -0
  123. data/spec/block_docs/issue31.md +25 -0
  124. data/spec/block_docs/issue40.md +52 -0
  125. data/spec/block_docs/issue64.md +55 -0
  126. data/spec/block_docs/issue67.md +19 -0
  127. data/spec/block_docs/issue70.md +11 -0
  128. data/spec/block_docs/issue72.md +17 -0
  129. data/spec/block_docs/issue74.md +38 -0
  130. data/spec/block_docs/issue79.md +15 -0
  131. data/spec/block_docs/issue83.md +13 -0
  132. data/spec/block_docs/issue85.md +25 -0
  133. data/spec/block_docs/issue88.md +19 -0
  134. data/spec/block_docs/issue89.md +12 -0
  135. data/spec/block_docs/issue90.md +38 -0
  136. data/{tests/unittest/pending → spec/block_docs}/link.md +21 -18
  137. data/{tests/unittest → spec/block_docs}/links.md +33 -32
  138. data/spec/block_docs/links2.md +21 -0
  139. data/{tests/unittest → spec/block_docs}/list1.md +0 -12
  140. data/{tests/unittest → spec/block_docs}/list12.md +2 -14
  141. data/{tests/unittest → spec/block_docs}/list2.md +2 -14
  142. data/spec/block_docs/list_multipara.md +42 -0
  143. data/{tests/unittest → spec/block_docs}/lists.md +28 -29
  144. data/{tests/unittest → spec/block_docs}/lists10.md +2 -14
  145. data/spec/block_docs/lists11.md +23 -0
  146. data/spec/block_docs/lists12.md +43 -0
  147. data/spec/block_docs/lists13.md +55 -0
  148. data/spec/block_docs/lists14.md +61 -0
  149. data/spec/block_docs/lists15.md +36 -0
  150. data/spec/block_docs/lists6.md +88 -0
  151. data/spec/block_docs/lists7b.md +58 -0
  152. data/spec/block_docs/lists9.md +53 -0
  153. data/{tests/unittest → spec/block_docs}/lists_after_paragraph.md +19 -25
  154. data/spec/block_docs/lists_blank.md +35 -0
  155. data/{tests/unittest/list3.md → spec/block_docs/lists_blockquote_code.md} +2 -14
  156. data/{tests/unittest/list4.md → spec/block_docs/lists_need_blank_line.md} +44 -29
  157. data/spec/block_docs/lists_nested.md +44 -0
  158. data/spec/block_docs/lists_nested_blankline.md +34 -0
  159. data/spec/block_docs/lists_nested_deep.md +43 -0
  160. data/spec/block_docs/lists_ol.md +129 -0
  161. data/spec/block_docs/lists_ol2.md +147 -0
  162. data/spec/block_docs/lists_paraindent.md +42 -0
  163. data/spec/block_docs/lists_tab.md +54 -0
  164. data/spec/block_docs/loss.md +17 -0
  165. data/spec/block_docs/math-blahtex/equations.md +29 -0
  166. data/spec/block_docs/math-blahtex/inline.md +48 -0
  167. data/spec/block_docs/math-blahtex/math2.md +52 -0
  168. data/spec/block_docs/math-blahtex/table.md +25 -0
  169. data/spec/block_docs/math/embedded_invalid_svg.md +108 -0
  170. data/spec/block_docs/math/embedded_svg.md +136 -0
  171. data/spec/block_docs/math/equations.md +49 -0
  172. data/spec/block_docs/math/inline.md +46 -0
  173. data/spec/block_docs/math/math2.md +53 -0
  174. data/{tests/unittest → spec/block_docs}/math/notmath.md +0 -12
  175. data/spec/block_docs/math/raw_mathml.md +87 -0
  176. data/spec/block_docs/math/spaces_after_inline_math.md +17 -0
  177. data/spec/block_docs/math/table.md +25 -0
  178. data/{tests/unittest → spec/block_docs}/math/table2.md +11 -23
  179. data/{tests/unittest → spec/block_docs}/misc_sw.md +184 -121
  180. data/{tests/unittest → spec/block_docs}/olist.md +6 -18
  181. data/{tests/unittest → spec/block_docs}/one.md +0 -12
  182. data/{tests/unittest → spec/block_docs}/paragraph.md +0 -12
  183. data/{tests/unittest → spec/block_docs}/paragraph_rules/dont_merge_ref.md +4 -12
  184. data/{tests/unittest → spec/block_docs}/paragraph_rules/tab_is_blank.md +0 -12
  185. data/{tests/unittest → spec/block_docs}/paragraphs.md +1 -13
  186. data/{tests/unittest → spec/block_docs}/recover/recover_links.md +4 -16
  187. data/{tests/unittest/pending/ref.md → spec/block_docs/ref_with_period.md} +7 -16
  188. data/spec/block_docs/ref_with_title.md +22 -0
  189. data/{tests/unittest → spec/block_docs}/references/long_example.md +16 -23
  190. data/{tests/unittest → spec/block_docs}/references/spaces_and_numbers.md +0 -12
  191. data/{tests/unittest → spec/block_docs}/smartypants.md +24 -31
  192. data/{tests/unittest → spec/block_docs}/syntax_hl.md +13 -17
  193. data/{tests/unittest → spec/block_docs}/table_attributes.md +6 -20
  194. data/spec/block_docs/table_colspan.md +41 -0
  195. data/spec/block_docs/tables.md +47 -0
  196. data/spec/block_docs/tables2.md +74 -0
  197. data/{tests/unittest → spec/block_docs}/test.md +1 -13
  198. data/{tests/unittest/notyet → spec/block_docs}/ticks.md +1 -13
  199. data/spec/block_docs/toc.md +87 -0
  200. data/{tests/unittest/notyet → spec/block_docs}/triggering.md +14 -25
  201. data/{tests/unittest → spec/block_docs}/underscore_in_words.md +0 -12
  202. data/{tests/unittest → spec/block_docs}/wrapping.md +4 -16
  203. data/spec/block_docs/xml.md +33 -0
  204. data/spec/block_docs/xml3.md +24 -0
  205. data/spec/block_docs/xml_comments.md +32 -0
  206. data/{tests/unittest → spec/block_docs}/xml_instruction.md +9 -20
  207. data/spec/block_spec.rb +110 -0
  208. data/spec/cli_spec.rb +8 -0
  209. data/spec/span_spec.rb +263 -0
  210. data/spec/spec_helper.rb +3 -0
  211. data/spec/to_html_utf8_spec.rb +13 -0
  212. metadata +218 -202
  213. data/Rakefile +0 -73
  214. data/bin/marudown +0 -29
  215. data/bin/marutest +0 -345
  216. data/docs/changelog.md +0 -334
  217. data/lib/maruku/errors_management.rb +0 -92
  218. data/lib/maruku/ext/math/latex_fix.rb +0 -12
  219. data/lib/maruku/input/parse_span_better.rb +0 -746
  220. data/lib/maruku/input/rubypants.rb +0 -225
  221. data/lib/maruku/input/type_detection.rb +0 -147
  222. data/lib/maruku/output/to_latex_entities.rb +0 -367
  223. data/lib/maruku/output/to_latex_strings.rb +0 -64
  224. data/lib/maruku/structures.rb +0 -167
  225. data/lib/maruku/structures_inspect.rb +0 -87
  226. data/lib/maruku/structures_iterators.rb +0 -61
  227. data/lib/maruku/tests/benchmark.rb +0 -82
  228. data/lib/maruku/tests/new_parser.rb +0 -373
  229. data/lib/maruku/tests/tests.rb +0 -136
  230. data/lib/maruku/usage/example1.rb +0 -33
  231. data/maruku_gem.rb +0 -33
  232. data/tests/bugs/code_in_links.md +0 -101
  233. data/tests/bugs/complex_escaping.md +0 -38
  234. data/tests/math/syntax.md +0 -46
  235. data/tests/math_usage/document.md +0 -13
  236. data/tests/others/abbreviations.md +0 -11
  237. data/tests/others/blank.md +0 -4
  238. data/tests/others/code.md +0 -5
  239. data/tests/others/code2.md +0 -8
  240. data/tests/others/code3.md +0 -16
  241. data/tests/others/email.md +0 -4
  242. data/tests/others/entities.md +0 -19
  243. data/tests/others/escaping.md +0 -16
  244. data/tests/others/extra_dl.md +0 -101
  245. data/tests/others/extra_header_id.md +0 -13
  246. data/tests/others/extra_table1.md +0 -40
  247. data/tests/others/footnotes.md +0 -17
  248. data/tests/others/headers.md +0 -10
  249. data/tests/others/hrule.md +0 -10
  250. data/tests/others/images.md +0 -20
  251. data/tests/others/inline_html.md +0 -42
  252. data/tests/others/links.md +0 -38
  253. data/tests/others/list1.md +0 -4
  254. data/tests/others/list2.md +0 -5
  255. data/tests/others/list3.md +0 -8
  256. data/tests/others/lists.md +0 -32
  257. data/tests/others/lists_after_paragraph.md +0 -44
  258. data/tests/others/lists_ol.md +0 -39
  259. data/tests/others/misc_sw.md +0 -105
  260. data/tests/others/one.md +0 -1
  261. data/tests/others/paragraphs.md +0 -13
  262. data/tests/others/sss06.md +0 -352
  263. data/tests/others/test.md +0 -4
  264. data/tests/s5/s5profiling.md +0 -48
  265. data/tests/unittest/bug_def.md +0 -28
  266. data/tests/unittest/email.md +0 -32
  267. data/tests/unittest/hang.md +0 -29
  268. data/tests/unittest/html2.md +0 -34
  269. data/tests/unittest/ie.md +0 -61
  270. data/tests/unittest/links2.md +0 -34
  271. data/tests/unittest/lists11.md +0 -28
  272. data/tests/unittest/lists6.md +0 -53
  273. data/tests/unittest/lists9.md +0 -76
  274. data/tests/unittest/lists_ol.md +0 -274
  275. data/tests/unittest/math/equations.md +0 -86
  276. data/tests/unittest/math/inline.md +0 -58
  277. data/tests/unittest/math/math2.md +0 -57
  278. data/tests/unittest/math/table.md +0 -37
  279. data/tests/unittest/notyet/header_after_par.md +0 -70
  280. data/tests/unittest/pending/empty_cells.md +0 -49
  281. data/tests/unittest/red_tests/abbrev.md +0 -1388
  282. data/tests/unittest/red_tests/lists7.md +0 -68
  283. data/tests/unittest/red_tests/lists7b.md +0 -128
  284. data/tests/unittest/red_tests/lists8.md +0 -76
  285. data/tests/unittest/red_tests/xml.md +0 -70
  286. data/tests/unittest/xml2.md +0 -31
  287. data/tests/unittest/xml3.md +0 -38
  288. data/tests/utf8-files/simple.md +0 -1
  289. data/unit_test_block.sh +0 -5
  290. data/unit_test_span.sh +0 -3
@@ -1,111 +1,91 @@
1
- #--
2
- # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
3
- #
4
- # This file is part of Maruku.
5
- #
6
- # Maruku is free software; you can redistribute it and/or modify
7
- # it under the terms of the GNU General Public License as published by
8
- # the Free Software Foundation; either version 2 of the License, or
9
- # (at your option) any later version.
10
- #
11
- # Maruku is distributed in the hope that it will be useful,
12
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- # GNU General Public License for more details.
15
- #
16
- # You should have received a copy of the GNU General Public License
17
- # along with Maruku; if not, write to the Free Software
18
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
- #++
20
-
21
-
22
- module MaRuKu; module In; module Markdown; module BlockLevelParser
23
-
24
- # This represents a source of lines that can be consumed.
25
- #
26
- # It is the twin of CharSource.
27
- #
28
-
29
- class LineSource
30
- include MaRuKu::Strings
31
- attr_reader :parent
32
-
33
- def initialize(lines, parent=nil, parent_offset=nil)
34
- raise "NIL lines? " if not lines
35
- @lines = lines
36
- @lines_index = 0
37
- @parent = parent
38
- @parent_offset = parent_offset
39
- end
40
-
41
- def cur_line() @lines[@lines_index] end
42
- def next_line() @lines[@lines_index+1] end
43
-
44
- def shift_line()
45
- raise "Over the rainbow" if @lines_index >= @lines.size
46
- l = @lines[@lines_index]
47
- @lines_index += 1
48
- return l
49
- end
50
-
51
- def ignore_line
52
- raise "Over the rainbow" if @lines_index >= @lines.size
53
- @lines_index += 1
54
- end
55
-
56
- def describe
57
- s = "At line #{original_line_number(@lines_index)}\n"
58
-
59
- context = 3 # lines
60
- from = [@lines_index-context, 0].max
61
- to = [@lines_index+context, @lines.size-1].min
62
-
63
- for i in from..to
64
- prefix = (i == @lines_index) ? '--> ' : ' ';
65
- l = @lines[i]
66
- s += "%10s %4s|%s" %
67
- [@lines[i].md_type.to_s, prefix, l]
68
-
69
- s += "|\n"
70
- end
71
-
72
- # if @parent
73
- # s << "Parent context is: \n"
74
- # s << add_tabs(@parent.describe,1,'|')
75
- # end
76
- s
77
- end
78
-
79
- def original_line_number(index)
80
- if @parent
81
- return index + @parent.original_line_number(@parent_offset)
82
- else
83
- 1 + index
84
- end
85
- end
86
-
87
- def cur_index
88
- @lines_index
89
- end
90
-
91
- # Returns the type of next line as a string
92
- # breaks at first :definition
93
- def tell_me_the_future
94
- s = ""; num_e = 0;
95
- for i in @lines_index..@lines.size-1
96
- c = case @lines[i].md_type
97
- when :text; "t"
98
- when :empty; num_e+=1; "e"
99
- when :definition; "d"
100
- else "o"
101
- end
102
- s += c
103
- break if c == "d" or num_e>1
104
- end
105
- s
106
- end
107
-
108
- end # linesource
109
-
110
- end end end end # block
1
+ module MaRuKu::In::Markdown::BlockLevelParser
2
+
3
+ # This represents a source of lines that can be consumed.
4
+ #
5
+ # It is the twin of CharSource.
6
+ #
7
+
8
+ class LineSource
9
+ attr_reader :parent
10
+
11
+ def initialize(lines, parent=nil, parent_offset=nil)
12
+ raise "NIL lines? " unless lines
13
+ @lines = lines.map {|l| l.kind_of?(MaRuKu::MDLine) ? l : MaRuKu::MDLine.new(l) }
14
+ @lines_index = 0
15
+ @parent = parent
16
+ @parent_offset = parent_offset
17
+ end
18
+
19
+ def cur_line
20
+ @lines[@lines_index]
21
+ end
22
+
23
+ def next_line
24
+ @lines[@lines_index + 1]
25
+ end
26
+
27
+ def shift_line
28
+ raise "Over the rainbow" if @lines_index >= @lines.size
29
+ l = @lines[@lines_index]
30
+ @lines_index += 1
31
+ l
32
+ end
33
+
34
+ def ignore_line
35
+ raise "Over the rainbow" if @lines_index >= @lines.size
36
+ @lines_index += 1
37
+ end
38
+
39
+ def describe
40
+ s = "At line #{original_line_number(@lines_index)}\n"
41
+
42
+ context = 3 # lines
43
+ from = [@lines_index - context, 0].max
44
+ to = [@lines_index + context, @lines.size - 1].min
45
+
46
+ from.upto(to) do |i|
47
+ prefix = (i == @lines_index) ? '--> ' : ' ';
48
+ l = @lines[i]
49
+ s += "%10s %4s|%s" %
50
+ [@lines[i].md_type.to_s, prefix, l]
51
+
52
+ s += "|\n"
53
+ end
54
+
55
+ s
56
+ end
57
+
58
+ def original_line_number(index)
59
+ if @parent
60
+ index + @parent.original_line_number(@parent_offset)
61
+ else
62
+ 1 + index
63
+ end
64
+ end
65
+
66
+ def cur_index
67
+ @lines_index
68
+ end
69
+
70
+ # Returns the type of next line as a string
71
+ # breaks at first :definition
72
+ def tell_me_the_future
73
+ s = ""
74
+ num_e = 0
75
+
76
+ @lines_index.upto(@lines.size - 1) do |i|
77
+ c = case @lines[i].md_type
78
+ when :text; "t"
79
+ when :empty; num_e += 1; "e"
80
+ when :definition; "d"
81
+ else "o"
82
+ end
83
+ s << c
84
+ break if c == "d" or num_e > 1
85
+ end
86
+ s
87
+ end
88
+
89
+ end # linesource
90
+ end
111
91
 
@@ -0,0 +1,131 @@
1
+ # This code does the classification of lines for block-level parsing.
2
+ module MaRuKu
3
+
4
+ # Represents a single line in a Markdown source file, as produced by
5
+ # LineSource.
6
+ class MDLine < String
7
+ def md_type
8
+ @md_type ||= line_md_type
9
+ end
10
+
11
+ # Returns the number of leading spaces on this string,
12
+ # considering that a tab counts as {MaRuKu::Strings::TAB_SIZE} spaces.
13
+ #
14
+ # @param s [String]
15
+ # @return [Fixnum]
16
+ def number_of_leading_spaces
17
+ if self =~ /\A\s+/
18
+ spaces = $&
19
+ spaces.count(" ") + spaces.count("\t") * MaRuKu::Strings::TAB_SIZE
20
+ else
21
+ 0
22
+ end
23
+ end
24
+
25
+ def gsub!(*args)
26
+ # Any in-place-modification method should reset the md_type
27
+ @md_type = nil
28
+ super
29
+ end
30
+
31
+ private
32
+
33
+ def line_md_type
34
+ # The order of evaluation is important (:text is a catch-all)
35
+ return :text if self =~ /\A[a-zA-Z]/
36
+ return :empty if self =~ /\A\s*\z/
37
+ return :footnote_text if self =~ FootnoteText
38
+ return :ref_definition if self =~ LinkRegex || self =~ IncompleteLink
39
+ return :abbreviation if self =~ Abbreviation
40
+ return :definition if self =~ Definition
41
+ # I had a bug with emails and urls at the beginning of the
42
+ # line that were mistaken for raw_html
43
+ return :text if self =~ /\A[ ]{0,3}#{EMailAddress}/
44
+ return :text if self =~ /\A[ ]{0,3}<\w+:\/\//
45
+ # raw html is like PHP Markdown Extra: at most three spaces before
46
+ return :xml_instr if self =~ /\A\s*<\?/
47
+ return :raw_html if self =~ %r{\A[ ]{0,3}</?\s*\w+}
48
+ return :raw_html if self =~ /\A[ ]{0,3}<\!\-\-/
49
+ return :header1 if self =~ /\A(=)+/
50
+ return :header2 if self =~ /\A([-\s])+\z/
51
+ return :header3 if self =~ /\A(#)+\s*\S+/
52
+ # at least three asterisks/hyphens/underscores on a line, and only whitespace
53
+ return :hrule if self =~ /\A(\s*[\*\-_]\s*){3,}\z/
54
+ return :ulist if self =~ /\A[ ]{0,3}([\*\-\+])\s+.*/
55
+ return :olist if self =~ /\A[ ]{0,3}\d+\.\s+.*/
56
+ return :code if number_of_leading_spaces >= 4
57
+ return :quote if self =~ /\A>/
58
+ return :ald if self =~ AttributeDefinitionList
59
+ return :ial if self =~ InlineAttributeList
60
+ return :text # else, it's just text
61
+ end
62
+ end
63
+
64
+ # MacRuby has trouble with commented regexes, so just put the expanded form
65
+ # in a comment.
66
+
67
+ # $1 = id $2 = attribute list
68
+ AttributeDefinitionList = /\A\s{0,3}\{([\w\s]+)\}:\s*(.*?)\s*\z/
69
+ #
70
+ InlineAttributeList = /\A\s{0,3}\{([:#\.].*?)\}\s*\z/
71
+ # Example:
72
+ # ^:blah blah
73
+ # ^: blah blah
74
+ # ^ : blah blah
75
+ Definition = /\A[ ]{0,3}:\s*(\S.*)\z/
76
+ # %r{
77
+ # ^ # begin of line
78
+ # [ ]{0,3} # up to 3 spaces
79
+ # : # colon
80
+ # \s* # whitespace
81
+ # (\S.*) # the text = $1
82
+ # $ # end of line
83
+ # }x
84
+
85
+ # Example:
86
+ # *[HTML]: Hyper Text Markup Language
87
+ Abbreviation = /\A[ ]{0,3}\*\[([^\]]+)\]:\s*(\S.*\S)*\s*\z/
88
+ # %r{
89
+ # ^ # begin of line
90
+ # [ ]{0,3} # up to 3 spaces
91
+ # \* # one asterisk
92
+ # \[ # opening bracket
93
+ # ([^\]]+) # any non-closing bracket: id = $1
94
+ # \] # closing bracket
95
+ # : # colon
96
+ # \s* # whitespace
97
+ # (\S.*\S)* # definition=$2
98
+ # \s* # strip this whitespace
99
+ # $ # end of line
100
+ # }x
101
+
102
+ FootnoteText = /\A[ ]{0,3}\[(\^.+)\]:\s*(\S.*)?\z/
103
+ # %r{
104
+ # ^ # begin of line
105
+ # [ ]{0,3} # up to 3 spaces
106
+ # \[(\^.+)\]: # id = $1 (including '^')
107
+ # \s*(\S.*)?$ # text = $2 (not obb.)
108
+ # }x
109
+
110
+ # This regex is taken from BlueCloth sources
111
+ # Link defs are in the form: ^[id]: \n? url "optional title"
112
+ LinkRegex = /\A[ ]{0,3}\[([^\[\]]+)\]:[ ]*<?([^>\s]+)>?[ ]*(?:(?:(?:"([^"]+)")|(?:'([^']+)')|(?:\(([^\(\)]+)\)))\s*(.+)?)?/
113
+ #%r{
114
+ # ^[ ]{0,3}\[([^\[\]]+)\]: # id = $1
115
+ # [ ]*
116
+ # <?([^>\s]+)>? # url = $2
117
+ # [ ]*
118
+ # (?: # Titles are delimited by "quotes" or (parens).
119
+ # (?:(?:"([^"]+)")|(?:'([^']+)')|(?:\(([^\(\)]+)\))) # title = $3, $4, or $5
120
+ # \s*(.+)? # stuff = $6
121
+ # )? # title is optional
122
+ #}x
123
+
124
+ IncompleteLink = /\A[ ]{0,3}\[([^\[\]]+?)\]:\s*\z/
125
+
126
+ # Table syntax: http://michelf.ca/projects/php-markdown/extra/#table
127
+ # | -------------:| ------------------------------ |
128
+ TableSeparator = /\A(?>\|?\s*\:?\-+\:?\s*\|?)+?\z/
129
+
130
+ EMailAddress = /<([^:@>]+?@[^:@>]+?)>/
131
+ end
@@ -1,615 +1,738 @@
1
- #--
2
- # Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
3
- #
4
- # This file is part of Maruku.
5
- #
6
- # Maruku is free software; you can redistribute it and/or modify
7
- # it under the terms of the GNU General Public License as published by
8
- # the Free Software Foundation; either version 2 of the License, or
9
- # (at your option) any later version.
10
- #
11
- # Maruku is distributed in the hope that it will be useful,
12
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- # GNU General Public License for more details.
15
- #
16
- # You should have received a copy of the GNU General Public License
17
- # along with Maruku; if not, write to the Free Software
18
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
- #++
20
-
21
-
22
1
  module MaRuKu; module In; module Markdown; module BlockLevelParser
23
2
 
24
- include Helpers
25
- include MaRuKu::Strings
26
- include MaRuKu::In::Markdown::SpanLevelParser
27
-
28
- class BlockContext < Array
29
- def describe
30
- n = 5
31
- desc = size > n ? self[-n,n] : self
32
- "Last #{n} elements: "+
33
- desc.map{|x| "\n -" + x.inspect}.join
34
- end
35
- end
36
-
37
- # Splits the string and calls parse_lines_as_markdown
38
- def parse_text_as_markdown(text)
39
- lines = split_lines(text)
40
- src = LineSource.new(lines)
41
- return parse_blocks(src)
42
- end
43
-
44
- # Input is a LineSource
45
- def parse_blocks(src)
46
- output = BlockContext.new
47
-
48
- # run state machine
49
- while src.cur_line
50
-
51
- next if check_block_extensions(src, output, src.cur_line)
52
-
53
- # Prints detected type (useful for debugging)
54
- # puts "#{src.cur_line.md_type}|#{src.cur_line}"
55
- case src.cur_line.md_type
56
- when :empty;
57
- output.push :empty
58
- src.ignore_line
59
- when :ial
60
- m = InlineAttributeList.match src.shift_line
61
- content = m[1] || ""
62
- # puts "Content: #{content.inspect}"
63
- src2 = CharSource.new(content, src)
64
- interpret_extension(src2, output, [nil])
65
- when :ald
66
- output.push read_ald(src)
67
- when :text
68
- # paragraph, or table, or definition list
69
- read_text_material(src, output)
70
- when :header2, :hrule
71
- # hrule
72
- src.shift_line
73
- output.push md_hrule()
74
- when :header3
75
- output.push read_header3(src)
76
- when :ulist, :olist
77
- list_type = src.cur_line.md_type == :ulist ? :ul : :ol
78
- li = read_list_item(src)
79
- # append to current list if we have one
80
- if output.last.kind_of?(MDElement) &&
81
- output.last.node_type == list_type then
82
- output.last.children << li
83
- else
84
- output.push md_el(list_type, [li])
85
- end
86
- when :quote; output.push read_quote(src)
87
- when :code; e = read_code(src); output << e if e
88
- when :raw_html; e = read_raw_html(src); output << e if e
89
-
90
- when :footnote_text; output.push read_footnote_text(src)
91
- when :ref_definition;
92
- if src.parent && (src.cur_index == 0)
93
- read_text_material(src, output)
94
- else
95
- read_ref_definition(src, output)
96
- end
97
- when :abbreviation; output.push read_abbreviation(src)
98
- when :xml_instr; read_xml_instruction(src, output)
99
- when :metadata;
100
- maruku_error "Please use the new meta-data syntax: \n"+
101
- " http://maruku.rubyforge.org/proposal.html\n", src
102
- src.ignore_line
103
- else # warn if we forgot something
104
- md_type = src.cur_line.md_type
105
- line = src.cur_line
106
- maruku_error "Ignoring line '#{line}' type = #{md_type}", src
107
- src.shift_line
108
- end
109
- end
110
-
111
- merge_ial(output, src, output)
112
- output.delete_if {|x| x.kind_of?(MDElement) &&
113
- x.node_type == :ial}
114
-
115
- # get rid of empty line markers
116
- output.delete_if {|x| x == :empty}
117
- # See for each list if we can omit the paragraphs and use li_span
118
- # TODO: do this after
119
- output.each do |c|
120
- # Remove paragraphs that we can get rid of
121
- if [:ul,:ol].include? c.node_type
122
- if c.children.all? {|li| !li.want_my_paragraph} then
123
- c.children.each do |d|
124
- d.node_type = :li_span
125
- d.children = d.children[0].children
126
- end
127
- end
128
- end
129
- if c.node_type == :definition_list
130
- if c.children.all?{|defi| !defi.want_my_paragraph} then
131
- c.children.each do |definition|
132
- definition.definitions.each do |dd|
133
- dd.children = dd.children[0].children
134
- end
135
- end
136
- end
137
- end
138
- end
139
-
140
- output
141
- end
142
-
143
- def read_text_material(src, output)
144
- if src.cur_line =~ MightBeTableHeader and
145
- (src.next_line && src.next_line =~ TableSeparator)
146
- output.push read_table(src)
147
- elsif [:header1,:header2].include? src.next_line.md_type
148
- output.push read_header12(src)
149
- elsif eventually_comes_a_def_list(src)
150
- definition = read_definition(src)
151
- if output.last.kind_of?(MDElement) &&
152
- output.last.node_type == :definition_list then
153
- output.last.children << definition
154
- else
155
- output.push md_el(:definition_list, [definition])
156
- end
157
- else # Start of a paragraph
158
- output.push read_paragraph(src)
159
- end
160
- end
161
-
162
-
163
- def read_ald(src)
164
- if (l=src.shift_line) =~ AttributeDefinitionList
165
- id = $1; al=$2;
166
- al = read_attribute_list(CharSource.new(al,src), context=nil, break_on=[nil])
167
- self.ald[id] = al;
168
- return md_ald(id, al)
169
- else
170
- maruku_error "Bug Bug:\n#{l.inspect}"
171
- return nil
172
- end
173
- end
174
-
175
- # reads a header (with ----- or ========)
176
- def read_header12(src)
177
- line = src.shift_line.strip
178
- al = nil
179
- # Check if there is an IAL
180
- if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
181
- line = $1.strip
182
- ial = $2
183
- al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
184
- end
185
- text = parse_lines_as_span [ line ]
186
- level = src.cur_line.md_type == :header2 ? 2 : 1;
187
- src.shift_line
188
- return md_header(level, text, al)
189
- end
190
-
191
- # reads a header like '#### header ####'
192
- def read_header3(src)
193
- line = src.shift_line.strip
194
- al = nil
195
- # Check if there is an IAL
196
- if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
197
- line = $1.strip
198
- ial = $2
199
- al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
200
- end
201
- level = num_leading_hashes(line)
202
- text = parse_lines_as_span [strip_hashes(line)]
203
- return md_header(level, text, al)
204
- end
205
-
206
- def read_xml_instruction(src, output)
207
- m = /^\s*<\?((\w+)\s*)?(.*)$/.match src.shift_line
208
- raise "BugBug" if not m
209
- target = m[2] || ''
210
- code = m[3]
211
- until code =~ /\?>/
212
- code += "\n"+src.shift_line
213
- end
214
- if not code =~ (/\?>\s*$/)
215
- garbage = (/\?>(.*)$/.match(code))[1]
216
- maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n"+
217
- add_tabs(code, 1, '|'), src
218
- end
219
- code.gsub!(/\?>\s*$/, '')
220
-
221
- if target == 'mrk' && MaRuKu::Globals[:unsafe_features]
222
- result = safe_execute_code(self, code)
223
- if result
224
- if result.kind_of? String
225
- raise "Not expected"
226
- else
227
- output.push(*result)
228
- end
229
- end
230
- else
231
- output.push md_xml_instr(target, code)
232
- end
233
- end
234
-
235
- def read_raw_html(src)
236
- h = HTMLHelper.new
237
- begin
238
- h.eat_this(l=src.shift_line)
239
- # puts "\nBLOCK:\nhtml -> #{l.inspect}"
240
- while src.cur_line and not h.is_finished?
241
- l=src.shift_line
242
- # puts "html -> #{l.inspect}"
243
- h.eat_this "\n"+l
244
- end
245
- rescue Exception => e
246
- ex = e.inspect + e.backtrace.join("\n")
247
- maruku_error "Bad block-level HTML:\n#{add_tabs(ex,1,'|')}\n", src
248
- end
249
- if not (h.rest =~ /^\s*$/)
250
- maruku_error "Could you please format this better?\n"+
251
- "I see that #{h.rest.inspect} is left after the raw HTML.", src
252
- end
253
- raw_html = h.stuff_you_read
254
-
255
- return md_html(raw_html)
256
- end
257
-
258
- def read_paragraph(src)
259
- lines = [src.shift_line]
260
- while src.cur_line
261
- # :olist does not break
262
- case t = src.cur_line.md_type
263
- when :quote,:header3,:empty,:ref_definition,:ial #,:xml_instr,:raw_html
264
- break
265
- when :olist,:ulist
266
- break if src.next_line.md_type == t
267
- end
268
- break if src.cur_line.strip.size == 0
269
- break if [:header1,:header2].include? src.next_line.md_type
270
- break if any_matching_block_extension?(src.cur_line)
271
-
272
- lines << src.shift_line
273
- end
274
- # dbg_describe_ary(lines, 'PAR')
275
- children = parse_lines_as_span(lines, src)
276
-
277
- return md_par(children)
278
- end
279
-
280
- # Reads one list item, either ordered or unordered.
281
- def read_list_item(src)
282
- parent_offset = src.cur_index
283
-
284
- item_type = src.cur_line.md_type
285
- first = src.shift_line
286
-
287
- indentation = spaces_before_first_char(first)
288
- break_list = [:ulist, :olist, :ial]
289
- # Ugly things going on inside `read_indented_content`
290
- lines, want_my_paragraph =
291
- read_indented_content(src,indentation, break_list, item_type)
292
-
293
- # add first line
294
- # Strip first '*', '-', '+' from first line
295
- stripped = first[indentation, first.size-1]
296
- lines.unshift stripped
297
-
298
- # dbg_describe_ary(lines, 'LIST ITEM ')
299
-
300
- src2 = LineSource.new(lines, src, parent_offset)
301
- children = parse_blocks(src2)
302
- with_par = want_my_paragraph || (children.size>1)
303
-
304
- return md_li(children, with_par)
305
- end
306
-
307
- def read_abbreviation(src)
308
- if not (l=src.shift_line) =~ Abbreviation
309
- maruku_error "Bug: it's Andrea's fault. Tell him.\n#{l.inspect}"
310
- end
311
-
312
- abbr = $1
313
- desc = $2
314
-
315
- if (not abbr) or (abbr.size==0)
316
- maruku_error "Bad abbrev. abbr=#{abbr.inspect} desc=#{desc.inspect}"
317
- end
318
-
319
- self.abbreviations[abbr] = desc
320
-
321
- return md_abbr_def(abbr, desc)
322
- end
323
-
324
- def read_footnote_text(src)
325
- parent_offset = src.cur_index
326
-
327
- first = src.shift_line
328
-
329
- if not first =~ FootnoteText
330
- maruku_error "Bug (it's Andrea's fault)"
331
- end
332
-
333
- id = $1
334
- text = $2
335
-
336
- # Ugly things going on inside `read_indented_content`
337
- indentation = 4 #first.size-text.size
338
-
339
- # puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
340
-
341
- break_list = [:footnote_text, :ref_definition, :definition, :abbreviation]
342
- item_type = :footnote_text
343
- lines, want_my_paragraph =
344
- read_indented_content(src,indentation, break_list, item_type)
345
-
346
- # add first line
347
- if text && text.strip != "" then lines.unshift text end
348
-
349
- # dbg_describe_ary(lines, 'FOOTNOTE')
350
- src2 = LineSource.new(lines, src, parent_offset)
351
- children = parse_blocks(src2)
352
-
353
- e = md_footnote(id, children)
354
- self.footnotes[id] = e
355
- return e
356
- end
357
-
358
-
359
- # This is the only ugly function in the code base.
360
- # It is used to read list items, descriptions, footnote text
361
- def read_indented_content(src, indentation, break_list, item_type)
362
- lines =[]
363
- # collect all indented lines
364
- saw_empty = false; saw_anything_after = false
365
- while src.cur_line
366
- # puts "Reading indent = #{indentation} #{src.cur_line.inspect}"
367
- #puts "#{src.cur_line.md_type} #{src.cur_line.inspect}"
368
- if src.cur_line.md_type == :empty
369
- saw_empty = true
370
- lines << src.shift_line
371
- next
372
- end
373
-
374
- # after a white line
375
- if saw_empty
376
- # we expect things to be properly aligned
377
- if (ns=number_of_leading_spaces(src.cur_line)) < indentation
378
- #puts "breaking for spaces, only #{ns}: #{src.cur_line}"
379
- break
380
- end
381
- saw_anything_after = true
382
- else
383
- # if src.cur_line[0] != ?\
384
- break if break_list.include? src.cur_line.md_type
385
- # end
386
- # break if src.cur_line.md_type != :text
387
- end
388
-
389
-
390
- stripped = strip_indent(src.shift_line, indentation)
391
- lines << stripped
392
-
393
- #puts "Accepted as #{stripped.inspect}"
394
-
395
- # You are only required to indent the first line of
396
- # a child paragraph.
397
- if stripped.md_type == :text
398
- while src.cur_line && (src.cur_line.md_type == :text)
399
- lines << strip_indent(src.shift_line, indentation)
400
- end
401
- end
402
- end
403
-
404
- want_my_paragraph = saw_anything_after ||
405
- (saw_empty && (src.cur_line && (src.cur_line.md_type == item_type)))
406
-
407
- # dbg_describe_ary(lines, 'LI')
408
- # create a new context
409
-
410
- while lines.last && (lines.last.md_type == :empty)
411
- lines.pop
412
- end
413
-
414
- return lines, want_my_paragraph
415
- end
416
-
417
-
418
- def read_quote(src)
419
- parent_offset = src.cur_index
420
-
421
- lines = []
422
- # collect all indented lines
423
- while src.cur_line && src.cur_line.md_type == :quote
424
- lines << unquote(src.shift_line)
425
- end
426
- # dbg_describe_ary(lines, 'QUOTE')
427
-
428
- src2 = LineSource.new(lines, src, parent_offset)
429
- children = parse_blocks(src2)
430
- return md_quote(children)
431
- end
432
-
433
- def read_code(src)
434
- # collect all indented lines
435
- lines = []
436
- while src.cur_line && ([:code, :empty].include? src.cur_line.md_type)
437
- lines << strip_indent(src.shift_line, 4)
438
- end
439
-
440
- #while lines.last && (lines.last.md_type == :empty )
441
- while lines.last && lines.last.strip.size == 0
442
- lines.pop
443
- end
444
-
445
- while lines.first && lines.first.strip.size == 0
446
- lines.shift
447
- end
448
-
449
- return nil if lines.empty?
450
-
451
- source = lines.join("\n")
452
-
453
- # dbg_describe_ary(lines, 'CODE')
454
-
455
- return md_codeblock(source)
456
- end
457
-
458
- # Reads a series of metadata lines with empty lines in between
459
- def read_metadata(src)
460
- hash = {}
461
- while src.cur_line
462
- case src.cur_line.md_type
463
- when :empty; src.shift_line
464
- when :metadata; hash.merge! parse_metadata(src.shift_line)
465
- else break
466
- end
467
- end
468
- hash
469
- end
470
-
471
-
472
- def read_ref_definition(src, out)
473
- line = src.shift_line
474
-
475
-
476
- # if link is incomplete, shift next line
477
- if src.cur_line && !([:footnote_text, :ref_definition, :definition, :abbreviation].include? src.cur_line.md_type) &&
478
- ([1,2,3].include? number_of_leading_spaces(src.cur_line) )
479
- line += " "+ src.shift_line
480
- end
481
-
482
- # puts "total= #{line}"
483
-
484
- match = LinkRegex.match(line)
485
- if not match
486
- maruku_error "Link does not respect format: '#{line}'"
487
- return
488
- end
489
-
490
- id = match[1]; url = match[2]; title = match[3];
491
- id = sanitize_ref_id(id)
492
-
493
- hash = self.refs[id] = {:url=>url,:title=>title}
494
-
495
- stuff=match[4]
496
-
497
- if stuff
498
- stuff.split.each do |couple|
499
- # puts "found #{couple}"
500
- k, v = couple.split('=')
501
- v ||= ""
502
- if v[0,1]=='"' then v = v[1, v.size-2] end
503
- # puts "key:_#{k}_ value=_#{v}_"
504
- hash[k.to_sym] = v
505
- end
506
- end
507
- # puts hash.inspect
508
-
509
- out.push md_ref_def(id, url, meta={:title=>title})
510
- end
511
-
512
- def split_cells(s)
513
- # s.strip.split('|').select{|x|x.strip.size>0}.map{|x|x.strip}
514
- # changed to allow empty cells
515
- s.strip.split('|').select{|x|x.size>0}.map{|x|x.strip}
516
- end
517
-
518
- def read_table(src)
519
- head = split_cells(src.shift_line).map{|s| md_el(:head_cell, parse_lines_as_span([s])) }
520
-
521
- separator=split_cells(src.shift_line)
522
-
523
- align = separator.map { |s| s =~ Sep
524
- if $1 and $2 then :center elsif $2 then :right else :left end }
525
-
526
- num_columns = align.size
527
-
528
- if head.size != num_columns
529
- maruku_error "Table head does not have #{num_columns} columns: \n#{head.inspect}"
530
- tell_user "I will ignore this table."
531
- # XXX try to recover
532
- return md_br()
533
- end
534
-
535
- rows = []
536
-
537
- while src.cur_line && src.cur_line =~ /\|/
538
- row = split_cells(src.shift_line).map{|s|
539
- md_el(:cell, parse_lines_as_span([s]))}
540
- if head.size != num_columns
541
- maruku_error "Row does not have #{num_columns} columns: \n#{row.inspect}"
542
- tell_user "I will ignore this table."
543
- # XXX try to recover
544
- return md_br()
545
- end
546
- rows << row
547
- end
548
-
549
- children = (head+rows).flatten
550
- return md_el(:table, children, {:align => align})
551
- end
552
-
553
- # If current line is text, a definition list is coming
554
- # if 1) text,empty,[text,empty]*,definition
555
-
556
- def eventually_comes_a_def_list(src)
557
- future = src.tell_me_the_future
558
- ok = future =~ %r{^t+e?d}x
559
- # puts "future: #{future} - #{ok}"
560
- ok
561
- end
562
-
563
-
564
- def read_definition(src)
565
- # Read one or more terms
566
- terms = []
567
- while src.cur_line && src.cur_line.md_type == :text
568
- terms << md_el(:definition_term, parse_lines_as_span([src.shift_line]))
569
- end
570
- # dbg_describe_ary(terms, 'DT')
571
-
572
- want_my_paragraph = false
573
-
574
- raise "Chunky Bacon!" if not src.cur_line
575
-
576
- # one optional empty
577
- if src.cur_line.md_type == :empty
578
- want_my_paragraph = true
579
- src.shift_line
580
- end
581
-
582
- raise "Chunky Bacon!" if src.cur_line.md_type != :definition
583
-
584
- # Read one or more definitions
585
- definitions = []
586
- while src.cur_line && src.cur_line.md_type == :definition
587
- parent_offset = src.cur_index
588
-
589
- first = src.shift_line
590
- first =~ Definition
591
- first = $1
592
-
593
- # I know, it's ugly!!!
594
-
595
- lines, w_m_p =
596
- read_indented_content(src,4, [:definition], :definition)
597
- want_my_paragraph ||= w_m_p
598
-
599
- lines.unshift first
600
-
601
- # dbg_describe_ary(lines, 'DD')
602
- src2 = LineSource.new(lines, src, parent_offset)
603
- children = parse_blocks(src2)
604
- definitions << md_el(:definition_data, children)
605
- end
606
-
607
- return md_el(:definition, terms+definitions, {
608
- :terms => terms,
609
- :definitions => definitions,
610
- :want_my_paragraph => want_my_paragraph})
611
- end
612
- end # BlockLevelParser
613
- end # MaRuKu
614
- end
615
- end
3
+ include Helpers
4
+ include MaRuKu::Strings
5
+ include MaRuKu::In::Markdown::SpanLevelParser
6
+
7
+ class BlockContext < Array
8
+ def describe
9
+ n = 5
10
+ desc = size > n ? self[-n, n] : self
11
+ "Last #{n} elements: " +
12
+ desc.map {|x| "\n -" + x.inspect }.join
13
+ end
14
+ end
15
+
16
+ # Splits the string and calls parse_lines_as_markdown
17
+ def parse_text_as_markdown(text)
18
+ lines = split_lines(text)
19
+ src = LineSource.new(lines)
20
+ parse_blocks(src)
21
+ end
22
+
23
+ # Input is a LineSource
24
+ def parse_blocks(src)
25
+ output = BlockContext.new
26
+
27
+ # run state machine
28
+ while src.cur_line
29
+ next if check_block_extensions(src, output, src.cur_line)
30
+
31
+ md_type = src.cur_line.md_type
32
+
33
+ # Prints detected type (useful for debugging)
34
+ #puts "parse_blocks #{md_type}|#{src.cur_line}"
35
+ case md_type
36
+ when :empty
37
+ output << :empty
38
+ src.ignore_line
39
+ when :ial
40
+ m = InlineAttributeList.match src.shift_line
41
+ content = m[1] || ""
42
+ src2 = CharSource.new(content, src)
43
+ interpret_extension(src2, output)
44
+ when :ald
45
+ output << read_ald(src)
46
+ when :text
47
+ # paragraph, or table, or definition list
48
+ read_text_material(src, output)
49
+ when :header2, :hrule
50
+ # hrule
51
+ src.shift_line
52
+ output << md_hrule
53
+ when :header3
54
+ output << read_header3(src)
55
+ when :ulist, :olist
56
+ list_type = (md_type == :ulist) ? :ul : :ol
57
+ li = read_list_item(src)
58
+ # append to current list if we have one
59
+ if output.last.kind_of?(MDElement) &&
60
+ output.last.node_type == list_type then
61
+ output.last.children << li
62
+ else
63
+ output << md_el(list_type, li)
64
+ end
65
+ when :quote
66
+ output << read_quote(src)
67
+ when :code
68
+ e = read_code(src)
69
+ output << e if e
70
+ when :raw_html
71
+ # More extra hacky stuff - if there's more than just HTML, we either wrap it
72
+ # in a paragraph or break it up depending on whether it's an inline element or not
73
+ e = read_raw_html(src)
74
+ unless e.empty?
75
+ if e.first.parsed_html &&
76
+ (first_node_name = e.first.parsed_html.first_node_name) &&
77
+ HTML_INLINE_ELEMS.include?(first_node_name) &&
78
+ !%w(svg math).include?(first_node_name)
79
+ content = [e.first]
80
+ if e.size > 1
81
+ content.concat(e[1].children)
82
+ end
83
+ output << md_par(content)
84
+ else
85
+ output.concat(e)
86
+ end
87
+ end
88
+ when :footnote_text
89
+ output << read_footnote_text(src)
90
+ when :ref_definition
91
+ if src.parent && src.cur_index == 0
92
+ read_text_material(src, output)
93
+ else
94
+ read_ref_definition(src, output)
95
+ end
96
+ when :abbreviation
97
+ output << read_abbreviation(src)
98
+ when :xml_instr
99
+ read_xml_instruction(src, output)
100
+ else # unhandled line type at this level
101
+ # Just treat it as raw text
102
+ read_text_material(src, output)
103
+ end
104
+ end
105
+
106
+ merge_ial(output, src, output)
107
+ output.delete_if do |x|
108
+ # Strip out IAL
109
+ (x.kind_of?(MDElement) && x.node_type == :ial) ||
110
+ # get rid of empty line markers
111
+ x == :empty
112
+ end
113
+
114
+ # See for each list if we can omit the paragraphs
115
+ # TODO: do this after
116
+ output.each do |c|
117
+ # Remove paragraphs that we can get rid of
118
+ if [:ul, :ol].include?(c.node_type) && c.children.none?(&:want_my_paragraph)
119
+ c.children.each do |d|
120
+ if d.children.first && d.children.first.node_type == :paragraph
121
+ d.children = d.children.first.children + d.children[1..-1]
122
+ end
123
+ end
124
+ elsif c.node_type == :definition_list && c.children.none?(&:want_my_paragraph)
125
+ c.children.each do |definition|
126
+ definition.definitions.each do |dd|
127
+ if dd.children.first.node_type == :paragraph
128
+ dd.children = dd.children.first.children + dd.children[1..-1]
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ output
136
+ end
137
+
138
+ def read_text_material(src, output)
139
+ if src.cur_line.include?('|') && # if contains a pipe, it could be a table header
140
+ src.next_line &&
141
+ src.next_line.rstrip =~ TableSeparator
142
+ output << read_table(src)
143
+ elsif src.next_line && [:header1, :header2].include?(src.next_line.md_type)
144
+ output << read_header12(src)
145
+ elsif eventually_comes_a_def_list(src)
146
+ definition = read_definition(src)
147
+ if output.last.kind_of?(MDElement) &&
148
+ output.last.node_type == :definition_list then
149
+ output.last.children << definition
150
+ else
151
+ output << md_el(:definition_list, definition)
152
+ end
153
+ else # Start of a paragraph
154
+ output.concat read_paragraph(src)
155
+ end
156
+ end
157
+
158
+ def read_ald(src)
159
+ if (l = src.shift_line) =~ AttributeDefinitionList
160
+ id = $1
161
+ al = read_attribute_list(CharSource.new($2, src))
162
+ self.ald[id] = al;
163
+ md_ald(id, al)
164
+ else
165
+ maruku_error "Bug Bug:\n#{l.inspect}"
166
+ nil
167
+ end
168
+ end
169
+
170
+ # reads a header (with ----- or ========)
171
+ def read_header12(src)
172
+ line = src.shift_line.strip
173
+ al = nil
174
+ # Check if there is an IAL
175
+ if new_meta_data? and line =~ /^(.*?)\{(.*?)\}\s*$/
176
+ line = $1.strip
177
+ ial = $2
178
+ al = read_attribute_list(CharSource.new(ial, src))
179
+ end
180
+ text = parse_span line
181
+ if text.empty?
182
+ text = "{#{ial}}"
183
+ al = nil
184
+ end
185
+ level = src.cur_line.md_type == :header2 ? 2 : 1;
186
+ src.shift_line
187
+ md_header(level, text, al)
188
+ end
189
+
190
+ # reads a header like '#### header ####'
191
+ def read_header3(src)
192
+ line = src.shift_line.strip
193
+ al = nil
194
+ # Check if there is an IAL
195
+ if new_meta_data? and line =~ /^(.*?)\{(.*?)\}\s*$/
196
+ line = $1.strip
197
+ ial = $2
198
+ al = read_attribute_list(CharSource.new(ial, src))
199
+ end
200
+ level = line[/^#+/].size
201
+ if level > 6
202
+ text = parse_span line
203
+ return md_par(text, al)
204
+ end
205
+ text = parse_span line.gsub(/\A#+|#+\z/, '')
206
+ if text.empty?
207
+ text = "{#{ial}}"
208
+ al = nil
209
+ end
210
+ md_header(level, text, al)
211
+ end
212
+
213
+ def read_xml_instruction(src, output)
214
+ m = /^\s*<\?((\w+)\s*)?(.*)$/.match src.shift_line
215
+ raise "BugBug" unless m
216
+ target = m[2] || ''
217
+ code = m[3]
218
+ until code.include?('?>')
219
+ code << "\n" << src.shift_line
220
+ end
221
+ unless code =~ /\?>\s*$/
222
+ garbage = (/\?>(.*)$/.match(code))[1]
223
+ maruku_error "Trailing garbage on last line: #{garbage.inspect}:\n" +
224
+ code.gsub(/^/, '|'), src
225
+ end
226
+ code.gsub!(/\?>\s*$/, '')
227
+
228
+ if target == 'mrk' && MaRuKu::Globals[:unsafe_features]
229
+ result = safe_execute_code(self, code)
230
+ if result
231
+ if result.kind_of? String
232
+ raise "Not expected"
233
+ else
234
+ output.push(*result)
235
+ end
236
+ end
237
+ else
238
+ output << md_xml_instr(target, code)
239
+ end
240
+ end
241
+
242
+ def read_raw_html(src)
243
+ extra_line = nil
244
+ h = HTMLHelper.new
245
+ begin
246
+ l = src.shift_line
247
+ h.eat_this(l)
248
+ # puts "\nBLOCK:\nhtml -> #{l.inspect}"
249
+ while src.cur_line && !h.is_finished?
250
+ l = src.shift_line
251
+ # puts "html -> #{l.inspect}"
252
+ h.eat_this "\n" + l
253
+ end
254
+ rescue => e
255
+ maruku_error "Bad block-level HTML:\n#{e.inspect.gsub(/^/, '|')}\n", src
256
+ end
257
+ unless h.rest =~ /^\s*$/
258
+ extra_line = h.rest
259
+ end
260
+ raw_html = h.stuff_you_read
261
+
262
+ is_inline = HTML_INLINE_ELEMS.include?(h.first_tag)
263
+
264
+ if extra_line
265
+ remainder = is_inline ? parse_span(extra_line) : parse_text_as_markdown(extra_line)
266
+ if extra_line.start_with?(' ')
267
+ remainder[0] = ' ' + remainder[0] if remainder[0].is_a?(String)
268
+ end
269
+ is_inline ? [md_html(raw_html), md_par(remainder)] : [md_html(raw_html)] + remainder
270
+ else
271
+ [md_html(raw_html)]
272
+ end
273
+ end
274
+
275
+ def read_paragraph(src)
276
+ lines = [src.shift_line]
277
+ while src.cur_line
278
+ # :olist does not break
279
+ case t = src.cur_line.md_type
280
+ when :quote, :header3, :empty, :ref_definition, :ial, :xml_instr
281
+ break
282
+ end
283
+ break if src.cur_line.strip.empty?
284
+ break if src.next_line && [:header1, :header2].include?(src.next_line.md_type)
285
+ break if any_matching_block_extension?(src.cur_line)
286
+
287
+ lines << src.shift_line
288
+ end
289
+ children = parse_span(lines, src)
290
+
291
+ pick_apart_non_inline_html(children)
292
+ end
293
+
294
+ # If there are non-inline HTML tags in the paragraph, break them out into
295
+ # their own elements and make paragraphs out of everything else.
296
+ def pick_apart_non_inline_html(children)
297
+ output = []
298
+ para_children = []
299
+
300
+ children.each do |child|
301
+ if element_is_non_inline_html?(child)
302
+ unless para_children.empty?
303
+ # Fix up paragraphs before non-inline elements having an extra space
304
+ last_child = para_children.last
305
+ if last_child.is_a?(String) && !last_child.empty?
306
+ last_child.replace last_child[0..-2]
307
+ end
308
+
309
+ output << md_par(para_children)
310
+ para_children = []
311
+ end
312
+ output << child
313
+ else
314
+ para_children << child
315
+ end
316
+ end
317
+
318
+ unless para_children.empty?
319
+ output << md_par(para_children)
320
+ end
321
+
322
+ output
323
+ end
324
+
325
+ # Is the given element an HTML element whose root is not an inline element?
326
+ def element_is_non_inline_html?(elem)
327
+ if elem.is_a?(MDElement) && elem.node_type == :raw_html && elem.parsed_html
328
+ first_node_name = elem.parsed_html.first_node_name
329
+ first_node_name && !HTML_INLINE_ELEMS.include?(elem.parsed_html.first_node_name)
330
+ else
331
+ false
332
+ end
333
+ end
334
+
335
+ # Reads one list item, either ordered or unordered.
336
+ def read_list_item(src)
337
+ parent_offset = src.cur_index
338
+
339
+ item_type = src.cur_line.md_type
340
+ first = src.shift_line
341
+
342
+ indentation, ial = spaces_before_first_char(first)
343
+ al = read_attribute_list(CharSource.new(ial, src)) if ial
344
+ ial_offset = ial ? ial.length + 3 : 0
345
+ lines, want_my_paragraph = read_indented_content(src, indentation, [], item_type, ial_offset)
346
+
347
+ # in case there is a second line and this line starts a new list, format it.
348
+ if !lines.empty? && [:ulist, :olist].include?(MaRuKu::MDLine.new(lines.first).md_type)
349
+ lines.unshift ""
350
+ end
351
+
352
+ # add first line
353
+ # Strip first '*', '-', '+' from first line
354
+ first_changed = first.gsub(/([^\t]*)(\t)/) { $1 + " " * (TAB_SIZE - $1.length % TAB_SIZE) }
355
+ stripped = first_changed[indentation, first_changed.size - 1]
356
+ lines.unshift stripped
357
+ src2 = LineSource.new(lines, src, parent_offset)
358
+ children = parse_blocks(src2)
359
+
360
+ md_li(children, want_my_paragraph, al)
361
+ end
362
+
363
+ def read_abbreviation(src)
364
+ unless (l = src.shift_line) =~ Abbreviation
365
+ maruku_error "Bug: it's Andrea's fault. Tell him.\n#{l.inspect}"
366
+ end
367
+
368
+ abbr = $1
369
+ desc = $2
370
+
371
+ if !abbr || abbr.empty?
372
+ maruku_error "Bad abbrev. abbr=#{abbr.inspect} desc=#{desc.inspect}"
373
+ end
374
+
375
+ self.abbreviations[abbr] = desc
376
+
377
+ md_abbr_def(abbr, desc)
378
+ end
379
+
380
+ def read_footnote_text(src)
381
+ parent_offset = src.cur_index
382
+
383
+ first = src.shift_line
384
+
385
+ unless first =~ FootnoteText
386
+ maruku_error "Bug (it's Andrea's fault)"
387
+ end
388
+
389
+ id = $1
390
+ text = $2 || ''
391
+
392
+ indentation = 4 #first.size-text.size
393
+
394
+ # puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
395
+
396
+ break_list = [:footnote_text, :ref_definition, :definition, :abbreviation]
397
+ item_type = :footnote_text
398
+ lines, _ = read_indented_content(src, indentation, break_list, item_type)
399
+
400
+ # add first line
401
+ lines.unshift text unless text.strip.empty?
402
+
403
+ src2 = LineSource.new(lines, src, parent_offset)
404
+ children = parse_blocks(src2)
405
+
406
+ e = md_footnote(id, children)
407
+ self.footnotes[id] = e
408
+ e
409
+ end
410
+
411
+
412
+ # This is the only ugly function in the code base.
413
+ # It is used to read list items, descriptions, footnote text
414
+ def read_indented_content(src, indentation, break_list, item_type, ial_offset=0)
415
+ lines = []
416
+ # collect all indented lines
417
+ saw_empty = false
418
+ saw_anything_after = false
419
+ break_list = Array(break_list)
420
+ len = indentation - ial_offset
421
+
422
+ while src.cur_line
423
+ num_leading_spaces = src.cur_line.number_of_leading_spaces
424
+ break if num_leading_spaces < len && ![:text, :empty, :code].include?(src.cur_line.md_type)
425
+
426
+ line = strip_indent(src.cur_line, indentation)
427
+ md_type = line.md_type
428
+
429
+ if md_type == :empty
430
+ saw_empty = true
431
+ lines << line
432
+ src.shift_line
433
+ next
434
+ end
435
+
436
+ # Unquestioningly grab anything that's deeper-indented
437
+ if md_type != :code && num_leading_spaces > len
438
+ lines << line
439
+ src.shift_line
440
+ next
441
+ end
442
+
443
+ # after a white line
444
+ if saw_empty
445
+ # we expect things to be properly aligned
446
+ break if num_leading_spaces < len
447
+ saw_anything_after = true
448
+ else
449
+ break if break_list.include?(md_type)
450
+ end
451
+
452
+ if md_type == :code && num_leading_spaces > len+6
453
+ lines << strip_indent(src.cur_line, num_leading_spaces-4)
454
+ src.shift_line
455
+ next
456
+ end
457
+
458
+ lines << line
459
+ src.shift_line
460
+
461
+ # You are only required to indent the first line of
462
+ # a child paragraph.
463
+ if md_type == :text
464
+ while src.cur_line && src.cur_line.md_type == :text
465
+ lines << strip_indent(src.shift_line, indentation)
466
+ end
467
+ end
468
+ end
469
+
470
+ # TODO fix this
471
+ want_my_paragraph = saw_anything_after ||
472
+ (saw_empty && src.cur_line && src.cur_line.md_type == item_type)
473
+
474
+ # create a new context
475
+
476
+ while lines.last && lines.last.md_type == :empty
477
+ lines.pop
478
+ end
479
+
480
+ return lines, want_my_paragraph
481
+ end
482
+
483
+
484
+ def read_quote(src)
485
+ parent_offset = src.cur_index
486
+
487
+ lines = []
488
+ # collect all indented lines
489
+ while src.cur_line && ( [:text, :quote].include?(src.cur_line.md_type) or
490
+ src.cur_line.md_type == :empty && ( src.next_line && src.next_line.md_type == :quote ) )
491
+ lines << unquote(src.shift_line)
492
+ end
493
+
494
+ src2 = LineSource.new(lines, src, parent_offset)
495
+ children = parse_blocks(src2)
496
+ md_quote(children)
497
+ end
498
+
499
+ def read_code(src)
500
+ # collect all indented lines
501
+ lines = []
502
+ while src.cur_line && [:code, :empty].include?(src.cur_line.md_type)
503
+ lines << strip_indent(src.shift_line, 4)
504
+ end
505
+
506
+ #while lines.last && (lines.last.md_type == :empty )
507
+ while lines.last && lines.last.strip.size == 0
508
+ lines.pop
509
+ end
510
+
511
+ while lines.first && lines.first.strip.size == 0
512
+ lines.shift
513
+ end
514
+
515
+ return nil if lines.empty?
516
+
517
+ source = lines.join("\n")
518
+
519
+ md_codeblock(source)
520
+ end
521
+
522
+ def read_ref_definition(src, out)
523
+ line = src.shift_line
524
+
525
+ # if link is incomplete, shift next line
526
+ if src.cur_line &&
527
+ ![:footnote_text, :ref_definition, :definition, :abbreviation].include?(src.cur_line.md_type) &&
528
+ (1..3).include?(src.cur_line.number_of_leading_spaces)
529
+ line << " " << src.shift_line
530
+ end
531
+
532
+ match = LinkRegex.match(line)
533
+ unless match
534
+ maruku_error "Link does not respect format: '#{line}'" and return
535
+ end
536
+
537
+ id = match[1]
538
+ url = match[2]
539
+ title = match[3] || match[4] || match[5]
540
+ id = sanitize_ref_id(id)
541
+
542
+ hash = self.refs[id] = {
543
+ :url => url,
544
+ :title => title
545
+ }
546
+
547
+ stuff = (match[6] || '')
548
+ stuff.split.each do |couple|
549
+ k, v = couple.split('=')
550
+ v ||= ""
551
+ v = v[1..-2] if v.start_with?('"') # strip quotes
552
+ hash[k.to_sym] = v
553
+ end
554
+
555
+ out << md_ref_def(id, url, :title => title)
556
+ end
557
+
558
+ def split_cells(s, allowBlank = false)
559
+ if allowBlank
560
+ if /^[|].*[|]$/ =~ s # handle the simple and decorated table cases
561
+ s.split('|', -1)[1..-2] # allow blank cells, but only keep the inner elements of the cells
562
+ elsif /^.*[|]$/ =~ s
563
+ s.split('|', -1)[0..-2] # allow blank cells, but only keep the inner elements of the cells
564
+ else
565
+ s.split('|', -1)
566
+ end
567
+ else
568
+ s.split('|').reject(&:empty?).map(&:strip)
569
+ end
570
+ end
571
+
572
+ def read_table(src)
573
+ head = split_cells(src.shift_line).map do |s|
574
+ md_el(:head_cell, parse_span(s))
575
+ end
576
+
577
+ separator = split_cells(src.shift_line)
578
+
579
+ align = separator.map do |s|
580
+ # ex: :-------------------:
581
+ # If the separator starts and ends with a colon,
582
+ # center the cell. If it's on the right, right-align,
583
+ # otherwise left-align.
584
+ starts = s.start_with? ':'
585
+ ends = s.end_with? ':'
586
+ if s.empty? # blank
587
+ nil
588
+ elsif starts && ends
589
+ :center
590
+ elsif ends
591
+ :right
592
+ else
593
+ :left
594
+ end
595
+ end
596
+
597
+ align.pop if align[-1].nil? # trailing blank
598
+ num_columns = align.size
599
+
600
+ head.pop if head.size == num_columns + 1 && head[-1].al.size == 0 # trailing blank
601
+
602
+ if head.size != num_columns
603
+ maruku_error "Table head does not have #{num_columns} columns: \n#{head.inspect}"
604
+ tell_user "I will ignore this table."
605
+ # XXX try to recover
606
+ return md_br
607
+ end
608
+
609
+ rows = []
610
+ while src.cur_line && src.cur_line.include?('|')
611
+ row = []
612
+ colCount = 0
613
+ colspan = 1
614
+ currElem = nil
615
+ currIdx = 0
616
+ split_cells(src.shift_line, true).map do |s|
617
+ if s.empty?
618
+ # empty cells increase the colspan of the previous cell
619
+ found = false
620
+ colspan += 1
621
+ al = (currElem &&currElem.al) || AttributeList.new
622
+ if al.size > 0
623
+ elem = find_colspan(al)
624
+ if elem != nil
625
+ elem[1] = colspan.to_s
626
+ found = true
627
+ end
628
+ end
629
+ al.push(["colspan", colspan.to_s]) unless found # also handles the case of and empty attribute list
630
+ else
631
+ colspan = 1
632
+ row[currIdx] = md_el(:cell, parse_span(s))
633
+ currElem = row[currIdx]
634
+ currIdx += 1
635
+ end
636
+ end
637
+
638
+ #
639
+ # sanity check - make sure the current row has the right number of columns (including spans)
640
+ # If not, dump the table and return a break
641
+ #
642
+ num_columns = count_columns(row)
643
+ if num_columns == head.size + 1 && row[-1].al.size == 0 #trailing blank cell
644
+ row.pop
645
+ num_columns -= 1
646
+ end
647
+ if head.size != num_columns
648
+ maruku_error "Row does not have #{head.size} columns: \n#{row.inspect} - #{num_columns}"
649
+ tell_user "I will ignore this table."
650
+ # XXX need to recover
651
+ return md_br
652
+ end
653
+ rows << row
654
+ end
655
+ rows.unshift(head) # put the header row on the processed table
656
+ md_el(:table, rows, { :align => align })
657
+ end
658
+
659
+ #
660
+ # count the actual number of elements in a row taking into account colspans
661
+ #
662
+ def count_columns(row)
663
+ colCount = 0
664
+
665
+ row.each do |cell|
666
+ if cell.al && cell.al.size > 0
667
+ al = find_colspan(cell.al)
668
+ if al != nil
669
+ colCount += al[1].to_i
670
+ else
671
+ colCount += 1
672
+ end
673
+ else
674
+ colCount += 1
675
+ end
676
+ end
677
+
678
+ colCount
679
+ end
680
+
681
+ #
682
+ # Search an attribute list looking for a colspan
683
+ #
684
+ def find_colspan(al)
685
+ al.find {|alElem| alElem[0] == "colspan" }
686
+ end
687
+
688
+ # If current line is text, a definition list is coming
689
+ # if 1) text,empty,[text,empty]*,definition
690
+ def eventually_comes_a_def_list(src)
691
+ src.tell_me_the_future =~ %r{^t+e?d}x
692
+ end
693
+
694
+ def read_definition(src)
695
+ # Read one or more terms
696
+ terms = []
697
+ while src.cur_line && src.cur_line.md_type == :text
698
+ terms << md_el(:definition_term, parse_span(src.shift_line))
699
+ end
700
+
701
+ want_my_paragraph = false
702
+
703
+ raise "Chunky Bacon!" unless src.cur_line
704
+
705
+ # one optional empty
706
+ if src.cur_line.md_type == :empty
707
+ want_my_paragraph = true
708
+ src.shift_line
709
+ end
710
+
711
+ raise "Chunky Bacon!" unless src.cur_line.md_type == :definition
712
+
713
+ # Read one or more definitions
714
+ definitions = []
715
+ while src.cur_line && src.cur_line.md_type == :definition
716
+ parent_offset = src.cur_index
717
+
718
+ first = src.shift_line
719
+ first =~ Definition
720
+ first = $1
721
+
722
+ lines, w_m_p = read_indented_content(src, 4, :definition, :definition)
723
+ want_my_paragraph ||= w_m_p
724
+
725
+ lines.unshift first
726
+
727
+ src2 = LineSource.new(lines, src, parent_offset)
728
+ children = parse_blocks(src2)
729
+ definitions << md_el(:definition_data, children)
730
+ end
731
+
732
+ md_el(:definition, terms + definitions, {
733
+ :terms => terms,
734
+ :definitions => definitions,
735
+ :want_my_paragraph => want_my_paragraph
736
+ })
737
+ end
738
+ end end end end