patcito-maruku 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/AUTHORS +23 -0
  2. data/LICENSE +340 -0
  3. data/README.md +73 -0
  4. data/bin/maruku +196 -0
  5. data/bin/marutex +4 -0
  6. data/data/entities.xml +261 -0
  7. data/docs/changelog.md +334 -0
  8. data/docs/div_syntax.md +36 -0
  9. data/docs/entity_test.md +23 -0
  10. data/docs/markdown_syntax.md +899 -0
  11. data/docs/maruku.md +346 -0
  12. data/docs/math.md +194 -0
  13. data/docs/other_stuff.md +51 -0
  14. data/docs/proposal.md +309 -0
  15. data/docs/website/src/bluecloth.md +25 -0
  16. data/docs/website/src/download.md +31 -0
  17. data/docs/website/src/maruku.md +261 -0
  18. data/docs/website/src/proposal.md +271 -0
  19. data/lib/maruku.rb +132 -0
  20. data/lib/maruku/attributes.rb +138 -0
  21. data/lib/maruku/defaults.rb +69 -0
  22. data/lib/maruku/errors.rb +89 -0
  23. data/lib/maruku/ext/div.rb +121 -0
  24. data/lib/maruku/ext/fenced_code.rb +78 -0
  25. data/lib/maruku/ext/math.rb +37 -0
  26. data/lib/maruku/ext/math/elements.rb +21 -0
  27. data/lib/maruku/ext/math/latex_fix.rb +12 -0
  28. data/lib/maruku/ext/math/mathml_engines/blahtex.rb +93 -0
  29. data/lib/maruku/ext/math/mathml_engines/itex2mml.rb +39 -0
  30. data/lib/maruku/ext/math/mathml_engines/none.rb +21 -0
  31. data/lib/maruku/ext/math/mathml_engines/ritex.rb +24 -0
  32. data/lib/maruku/ext/math/parsing.rb +125 -0
  33. data/lib/maruku/ext/math/to_html.rb +237 -0
  34. data/lib/maruku/ext/math/to_latex.rb +36 -0
  35. data/lib/maruku/ext/yaml.rb +43 -0
  36. data/lib/maruku/helpers.rb +214 -0
  37. data/lib/maruku/input/charsource.rb +326 -0
  38. data/lib/maruku/input/extensions.rb +69 -0
  39. data/lib/maruku/input/html_helper.rb +189 -0
  40. data/lib/maruku/input/linesource.rb +111 -0
  41. data/lib/maruku/input/parse_block.rb +608 -0
  42. data/lib/maruku/input/parse_doc.rb +240 -0
  43. data/lib/maruku/input/parse_span_better.rb +746 -0
  44. data/lib/maruku/input/rubypants.rb +225 -0
  45. data/lib/maruku/input/type_detection.rb +147 -0
  46. data/lib/maruku/input_textile2/t2_parser.rb +163 -0
  47. data/lib/maruku/maruku.rb +31 -0
  48. data/lib/maruku/output/s5/fancy.rb +756 -0
  49. data/lib/maruku/output/s5/to_s5.rb +138 -0
  50. data/lib/maruku/output/to_html.rb +994 -0
  51. data/lib/maruku/output/to_latex.rb +580 -0
  52. data/lib/maruku/output/to_latex_entities.rb +101 -0
  53. data/lib/maruku/output/to_latex_strings.rb +64 -0
  54. data/lib/maruku/output/to_markdown.rb +164 -0
  55. data/lib/maruku/output/to_s.rb +54 -0
  56. data/lib/maruku/string_utils.rb +185 -0
  57. data/lib/maruku/structures.rb +143 -0
  58. data/lib/maruku/structures_inspect.rb +51 -0
  59. data/lib/maruku/structures_iterators.rb +48 -0
  60. data/lib/maruku/textile2.rb +1 -0
  61. data/lib/maruku/toc.rb +214 -0
  62. data/lib/maruku/usage/example1.rb +33 -0
  63. data/lib/maruku/version +0 -0
  64. data/lib/maruku/version.rb +54 -0
  65. data/spec/block_docs/abbreviations.md +52 -0
  66. data/spec/block_docs/alt.md +17 -0
  67. data/spec/block_docs/attributes/att2.md +20 -0
  68. data/spec/block_docs/attributes/att3.md +28 -0
  69. data/spec/block_docs/attributes/attributes.md +57 -0
  70. data/spec/block_docs/attributes/circular.md +26 -0
  71. data/spec/block_docs/attributes/default.md +22 -0
  72. data/spec/block_docs/blank.md +24 -0
  73. data/spec/block_docs/blanks_in_code.md +75 -0
  74. data/spec/block_docs/bug_def.md +16 -0
  75. data/spec/block_docs/bug_table.md +46 -0
  76. data/spec/block_docs/code.md +34 -0
  77. data/spec/block_docs/code2.md +28 -0
  78. data/spec/block_docs/code3.md +71 -0
  79. data/spec/block_docs/data_loss.md +25 -0
  80. data/spec/block_docs/divs/div1.md +167 -0
  81. data/spec/block_docs/divs/div2.md +21 -0
  82. data/spec/block_docs/divs/div3_nest.md +45 -0
  83. data/spec/block_docs/easy.md +15 -0
  84. data/spec/block_docs/email.md +20 -0
  85. data/spec/block_docs/encoding/iso-8859-1.md +23 -0
  86. data/spec/block_docs/encoding/utf-8.md +18 -0
  87. data/spec/block_docs/entities.md +94 -0
  88. data/spec/block_docs/escaping.md +67 -0
  89. data/spec/block_docs/extra_dl.md +52 -0
  90. data/spec/block_docs/extra_header_id.md +63 -0
  91. data/spec/block_docs/extra_table1.md +37 -0
  92. data/spec/block_docs/footnotes.md +97 -0
  93. data/spec/block_docs/headers.md +37 -0
  94. data/spec/block_docs/hex_entities.md +37 -0
  95. data/spec/block_docs/hrule.md +39 -0
  96. data/spec/block_docs/html2.md +22 -0
  97. data/spec/block_docs/html3.md +31 -0
  98. data/spec/block_docs/html4.md +25 -0
  99. data/spec/block_docs/html5.md +23 -0
  100. data/spec/block_docs/ie.md +49 -0
  101. data/spec/block_docs/images.md +90 -0
  102. data/spec/block_docs/images2.md +31 -0
  103. data/spec/block_docs/inline_html.md +152 -0
  104. data/spec/block_docs/inline_html2.md +21 -0
  105. data/spec/block_docs/links.md +152 -0
  106. data/spec/block_docs/links2.md +22 -0
  107. data/spec/block_docs/list1.md +46 -0
  108. data/spec/block_docs/list12.md +28 -0
  109. data/spec/block_docs/list2.md +56 -0
  110. data/spec/block_docs/list3.md +64 -0
  111. data/spec/block_docs/list4.md +89 -0
  112. data/spec/block_docs/lists.md +192 -0
  113. data/spec/block_docs/lists10.md +34 -0
  114. data/spec/block_docs/lists11.md +23 -0
  115. data/spec/block_docs/lists6.md +41 -0
  116. data/spec/block_docs/lists9.md +64 -0
  117. data/spec/block_docs/lists_after_paragraph.md +208 -0
  118. data/spec/block_docs/lists_ol.md +262 -0
  119. data/spec/block_docs/loss.md +16 -0
  120. data/spec/block_docs/math/equations.md +45 -0
  121. data/spec/block_docs/math/inline.md +46 -0
  122. data/spec/block_docs/math/math2.md +45 -0
  123. data/spec/block_docs/math/notmath.md +25 -0
  124. data/spec/block_docs/math/table.md +25 -0
  125. data/spec/block_docs/math/table2.md +42 -0
  126. data/spec/block_docs/misc_sw.md +525 -0
  127. data/spec/block_docs/notyet/escape.md +21 -0
  128. data/spec/block_docs/notyet/header_after_par.md +58 -0
  129. data/spec/block_docs/notyet/ticks.md +18 -0
  130. data/spec/block_docs/notyet/triggering.md +157 -0
  131. data/spec/block_docs/olist.md +45 -0
  132. data/spec/block_docs/one.md +15 -0
  133. data/spec/block_docs/paragraph.md +16 -0
  134. data/spec/block_docs/paragraph_rules/dont_merge_ref.md +42 -0
  135. data/spec/block_docs/paragraph_rules/tab_is_blank.md +24 -0
  136. data/spec/block_docs/paragraphs.md +46 -0
  137. data/spec/block_docs/pending/amps.md +15 -0
  138. data/spec/block_docs/pending/empty_cells.md +37 -0
  139. data/spec/block_docs/pending/link.md +72 -0
  140. data/spec/block_docs/pending/ref.md +21 -0
  141. data/spec/block_docs/recover/recover_links.md +15 -0
  142. data/spec/block_docs/red_tests/abbrev.md +679 -0
  143. data/spec/block_docs/red_tests/lists7.md +32 -0
  144. data/spec/block_docs/red_tests/lists7b.md +65 -0
  145. data/spec/block_docs/red_tests/lists8.md +42 -0
  146. data/spec/block_docs/red_tests/ref.md +23 -0
  147. data/spec/block_docs/red_tests/xml.md +35 -0
  148. data/spec/block_docs/references/long_example.md +71 -0
  149. data/spec/block_docs/references/spaces_and_numbers.md +15 -0
  150. data/spec/block_docs/smartypants.md +114 -0
  151. data/spec/block_docs/syntax_hl.md +52 -0
  152. data/spec/block_docs/table_attributes.md +34 -0
  153. data/spec/block_docs/test.md +19 -0
  154. data/spec/block_docs/underscore_in_words.md +15 -0
  155. data/spec/block_docs/wrapping.md +67 -0
  156. data/spec/block_docs/xml2.md +19 -0
  157. data/spec/block_docs/xml3.md +26 -0
  158. data/spec/block_docs/xml_instruction.md +52 -0
  159. data/spec/block_spec.rb +49 -0
  160. data/spec/span_spec.rb +254 -0
  161. data/spec/spec_helper.rb +6 -0
  162. metadata +247 -0
@@ -0,0 +1,138 @@
1
+ # This module groups all functions related to HTML export.
2
+ module MaRuKu
3
+
4
+ begin
5
+ require 'rexml/formatters/pretty'
6
+ require 'rexml/formatters/default'
7
+ $rexml_new_version = true
8
+ rescue LoadError
9
+ $rexml_new_version = false
10
+ end
11
+
12
+ class MDDocument
13
+
14
+ def s5_theme
15
+ html_escape(self.attributes[:slide_theme] || "default")
16
+ end
17
+
18
+ def html_escape(string)
19
+ string.gsub( /&/, "&" ).
20
+ gsub( /</, "&lt;" ).
21
+ gsub( />/, "&gt;" ).
22
+ gsub( /'/, "&#39;" ).
23
+ gsub( /"/, "&quot;" )
24
+ end
25
+
26
+ # Render as an HTML fragment (no head, just the content of BODY). (returns a string)
27
+ def to_s5(context={})
28
+ indent = context[:indent] || -1
29
+ ie_hack = !context[:ie_hack].kind_of?(FalseClass)
30
+ content_only = !context[:content_only].kind_of?(FalseClass)
31
+
32
+ doc = Document.new(nil,{:respect_whitespace =>:all})
33
+
34
+ if content_only
35
+ body = Element.new('div', doc)
36
+ else
37
+ html = Element.new('html', doc)
38
+ html.add_namespace('http://www.w3.org/1999/xhtml')
39
+ html.add_namespace('svg', "http://www.w3.org/2000/svg" )
40
+
41
+ head = Element.new('head', html)
42
+ me = Element.new 'meta', head
43
+ me.attributes['http-equiv'] = 'Content-type'
44
+ me.attributes['content'] = 'text/html;charset=utf-8'
45
+
46
+ # Create title element
47
+ doc_title = self.attributes[:title] || self.attributes[:subject] || ""
48
+ title = Element.new 'title', head
49
+ title << Text.new(doc_title)
50
+ body = Element.new('body', html)
51
+
52
+ end
53
+
54
+ slide_header = self.attributes[:slide_header]
55
+ slide_footer = self.attributes[:slide_footer]
56
+ slide_subfooter = self.attributes[:slide_subfooter]
57
+ slide_topleft = self.attributes[:slide_topleft]
58
+ slide_topright = self.attributes[:slide_topright]
59
+ slide_bottomleft = self.attributes[:slide_bottomleft]
60
+ slide_bottomright = self.attributes[:slide_bottomright]
61
+
62
+ dummy_layout_slide =
63
+ "
64
+ <div class='layout'>
65
+ <div id='controls'> </div>
66
+ <div id='currentSlide'> </div>
67
+ <div id='header'> #{slide_header}</div>
68
+ <div id='footer'>
69
+ <h1>#{slide_footer}</h1>
70
+ <h2>#{slide_subfooter}</h2>
71
+ </div>
72
+ <div class='topleft'> #{slide_topleft}</div>
73
+ <div class='topright'> #{slide_topright}</div>
74
+ <div class='bottomleft'> #{slide_bottomleft}</div>
75
+ <div class='bottomright'> #{slide_bottomright}</div>
76
+ </div>
77
+ "
78
+ body.add_element Document.new(dummy_layout_slide, {:respect_whitespace =>:all}).root
79
+
80
+ presentation = Element.new 'div', body
81
+ presentation.attributes['class'] = 'presentation'
82
+
83
+ first_slide="
84
+ <div class='slide'>
85
+ <h1> #{self.attributes[:title] ||context[:title]}</h1>
86
+ <h2> #{self.attributes[:subtitle] ||context[:subtitle]}</h2>
87
+ <h3> #{self.attributes[:author] ||context[:author]}</h3>
88
+ <h4> #{self.attributes[:company] ||context[:company]}</h4>
89
+ </div>
90
+ "
91
+ presentation.add_element Document.new(first_slide).root
92
+
93
+ slide_num = 0
94
+ self.toc.section_children.each do |slide|
95
+ slide_num += 1
96
+ @doc.attributes[:doc_prefix] = "s#{slide_num}"
97
+
98
+ puts "Slide #{slide_num}: " + slide.header_element.to_s
99
+ div = Element.new('div', presentation)
100
+ div.attributes['class'] = 'slide'
101
+
102
+ h1 = Element.new 'h1', div
103
+ slide.header_element.children_to_html.each do |e| h1 << e; end
104
+
105
+ array_to_html(slide.immediate_children).each do |e| div << e end
106
+
107
+ # render footnotes
108
+ if @doc.footnotes_order.size > 0
109
+ div << render_footnotes
110
+ @doc.footnotes_order = []
111
+ end
112
+ end
113
+
114
+ xml = ""
115
+ if (content_only)
116
+ if $rexml_new_version
117
+ formatter = REXML::Formatters::Default.new(ie_hack)
118
+ formatter.write(body, xml)
119
+ else
120
+ body.write(xml,indent,transitive=true,ie_hack);
121
+ end
122
+ else
123
+ doc2 = Document.new("<div>"+S5_external+"</div>",{:respect_whitespace =>:all})
124
+ doc2.root.children.each{ |child| head << child }
125
+
126
+ add_css_to(head)
127
+
128
+ # REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
129
+ # containing code.
130
+ html.write(xml,indent,transitive=true,ie_hack);
131
+ Xhtml11_mathml2_svg11 + xml
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+
138
+ end
@@ -0,0 +1,994 @@
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
+ require 'rexml/document'
22
+
23
+ begin
24
+ require 'rexml/formatters/pretty'
25
+ require 'rexml/formatters/default'
26
+ $rexml_new_version = true
27
+ rescue LoadError
28
+ $rexml_new_version = false
29
+ end
30
+
31
+ class String
32
+ # A string is rendered into HTML by creating
33
+ # a REXML::Text node. REXML takes care of all the encoding.
34
+ def to_html
35
+ REXML::Text.new(self)
36
+ end
37
+ end
38
+
39
+
40
+ # This module groups all functions related to HTML export.
41
+ module MaRuKu; module Out; module HTML
42
+ include REXML
43
+
44
+ # Render as an HTML fragment (no head, just the content of BODY). (returns a string)
45
+ def to_html(context={})
46
+ indent = context[:indent] || -1
47
+ ie_hack = context[:ie_hack] || true
48
+
49
+ div = Element.new 'dummy'
50
+ children_to_html.each do |e|
51
+ div << e
52
+ end
53
+
54
+ # render footnotes
55
+ if @doc.footnotes_order.size > 0
56
+ div << render_footnotes
57
+ end
58
+
59
+ doc = Document.new(nil,{:respect_whitespace =>:all})
60
+ doc << div
61
+
62
+ # REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
63
+ # containing code.
64
+ xml =""
65
+
66
+ if $rexml_new_version
67
+ formatter = if indent > -1
68
+ REXML::Formatters::Pretty.new( indent, ie_hack )
69
+ else
70
+ REXML::Formatters::Default.new( ie_hack )
71
+ end
72
+ formatter.write( div, xml)
73
+ else
74
+ div.write(xml,indent,transitive=true,ie_hack)
75
+ end
76
+
77
+ xml.gsub!(/\A<dummy>\s*/,'')
78
+ xml.gsub!(/\s*<\/dummy>\Z/,'')
79
+ xml.gsub!(/\A<dummy\s*\/>/,'')
80
+ xml
81
+ end
82
+
83
+ # Render to a complete HTML document (returns a string)
84
+ def to_html_document(context={})
85
+ indent = context[:indent] || -1
86
+ ie_hack = context[:ie_hack] ||true
87
+ doc = to_html_document_tree
88
+ xml = ""
89
+
90
+ # REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
91
+ # containing code.
92
+ doc.write(xml,indent,transitive=true,ie_hack);
93
+
94
+ Xhtml11_mathml2_svg11 + xml
95
+ end
96
+
97
+
98
+ Xhtml10strict =
99
+ "<?xml version='1.0' encoding='utf-8'?>
100
+ <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'
101
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n"
102
+
103
+ Xhtml11strict_mathml2 = '<?xml version="1.0" encoding="utf-8"?>
104
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN"
105
+ "http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd" [
106
+ <!ENTITY mathml "http://www.w3.org/1998/Math/MathML">
107
+ ]>
108
+ '
109
+
110
+ Xhtml11_mathml2_svg11 =
111
+ '<?xml version="1.0" encoding="utf-8"?>
112
+ <!DOCTYPE html PUBLIC
113
+ "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
114
+ "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">
115
+ '
116
+
117
+
118
+ def xml_newline() Text.new("\n") end
119
+
120
+
121
+ =begin maruku_doc
122
+ Attribute: title
123
+ Scope: document
124
+
125
+ Sets the title of the document.
126
+ If a title is not specified, the first header will be used.
127
+
128
+ These should be equivalent:
129
+
130
+ Title: my document
131
+
132
+ Content
133
+
134
+ and
135
+
136
+ my document
137
+ ===========
138
+
139
+ Content
140
+
141
+ In both cases, the title is set to "my document".
142
+ =end
143
+
144
+ =begin maruku_doc
145
+ Attribute: doc_prefix
146
+ Scope: document
147
+
148
+ String to disambiguate footnote links.
149
+ =end
150
+
151
+
152
+ =begin maruku_doc
153
+ Attribute: subject
154
+ Scope: document
155
+
156
+ Synonim for `title`.
157
+ =end
158
+
159
+
160
+ # Render to an HTML fragment (returns a REXML document tree)
161
+ def to_html_tree
162
+ div = Element.new 'div'
163
+ div.attributes['class'] = 'maruku_wrapper_div'
164
+ children_to_html.each do |e|
165
+ div << e
166
+ end
167
+
168
+ # render footnotes
169
+ if @doc.footnotes_order.size > 0
170
+ div << render_footnotes
171
+ end
172
+
173
+ doc = Document.new(nil,{:respect_whitespace =>:all})
174
+ doc << div
175
+ end
176
+
177
+ =begin maruku_doc
178
+ Attribute: css
179
+ Scope: document
180
+ Output: HTML
181
+ Summary: Activates CSS stylesheets for HTML.
182
+
183
+ `css` should be a space-separated list of urls.
184
+
185
+ Example:
186
+
187
+ CSS: style.css math.css
188
+
189
+ =end
190
+
191
+ METAS = %w{description keywords author revised}
192
+
193
+ # Render to a complete HTML document (returns a REXML document tree)
194
+ def to_html_document_tree
195
+ doc = Document.new(nil,{:respect_whitespace =>:all})
196
+ # doc << XMLDecl.new
197
+
198
+ root = Element.new('html', doc)
199
+ root.add_namespace('http://www.w3.org/1999/xhtml')
200
+ root.add_namespace('svg', "http://www.w3.org/2000/svg" )
201
+ lang = self.attributes[:lang] || 'en'
202
+ root.attributes['xml:lang'] = lang
203
+
204
+ root << xml_newline
205
+ head = Element.new 'head', root
206
+
207
+ #<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
208
+ me = Element.new 'meta', head
209
+ me.attributes['http-equiv'] = 'Content-type'
210
+ # me.attributes['content'] = 'text/html;charset=utf-8'
211
+ me.attributes['content'] = 'application/xhtml+xml;charset=utf-8'
212
+
213
+ METAS.each do |m|
214
+ if value = self.attributes[m.to_sym]
215
+ meta = Element.new 'meta', head
216
+ meta.attributes['name'] = m
217
+ meta.attributes['content'] = value.to_s
218
+ end
219
+ end
220
+
221
+
222
+ self.attributes.each do |k,v|
223
+ if k.to_s =~ /\Ameta-(.*)\Z/
224
+ meta = Element.new 'meta', head
225
+ meta.attributes['name'] = $1
226
+ meta.attributes['content'] = v.to_s
227
+ end
228
+ end
229
+
230
+
231
+
232
+ # Create title element
233
+ doc_title = self.attributes[:title] || self.attributes[:subject] || ""
234
+ title = Element.new 'title', head
235
+ title << Text.new(doc_title)
236
+
237
+ add_css_to(head)
238
+
239
+
240
+ root << xml_newline
241
+
242
+ body = Element.new 'body'
243
+
244
+ children_to_html.each do |e|
245
+ body << e
246
+ end
247
+
248
+ # render footnotes
249
+ if @doc.footnotes_order.size > 0
250
+ body << render_footnotes
251
+ end
252
+
253
+ # When we are rendering a whole document, we add a signature
254
+ # at the bottom.
255
+ if get_setting(:maruku_signature)
256
+ body << maruku_html_signature
257
+ end
258
+
259
+ root << body
260
+
261
+ doc
262
+ end
263
+
264
+ def add_css_to(head)
265
+ if css_list = self.attributes[:css]
266
+ css_list.split.each do |css|
267
+ # <link type="text/css" rel="stylesheet" href="..." />
268
+ link = Element.new 'link'
269
+ link.attributes['type'] = 'text/css'
270
+ link.attributes['rel'] = 'stylesheet'
271
+ link.attributes['href'] = css
272
+ head << link
273
+ head << xml_newline
274
+ end
275
+ end
276
+ end
277
+
278
+ # returns "st","nd","rd" or "th" as appropriate
279
+ def day_suffix(day)
280
+ s = {
281
+ 1 => 'st',
282
+ 2 => 'nd',
283
+ 3 => 'rd',
284
+ 21 => 'st',
285
+ 22 => 'nd',
286
+ 23 => 'rd',
287
+ 31 => 'st'
288
+ }
289
+ return s[day] || 'th';
290
+ end
291
+
292
+ # formats a nice date
293
+ def nice_date
294
+ t = Time.now
295
+ t.strftime(" at %H:%M on ")+
296
+ t.strftime("%A, %B %d")+
297
+ day_suffix(t.day)+
298
+ t.strftime(", %Y")
299
+ end
300
+
301
+ def maruku_html_signature
302
+ div = Element.new 'div'
303
+ div.attributes['class'] = 'maruku_signature'
304
+ Element.new 'hr', div
305
+ span = Element.new 'span', div
306
+ span.attributes['style'] = 'font-size: small; font-style: italic'
307
+ span << Text.new('Created by ')
308
+ a = Element.new('a', span)
309
+ a.attributes['href'] = 'http://maruku.rubyforge.org'
310
+ a.attributes['title'] = 'Maruku: a Markdown-superset interpreter for Ruby'
311
+ a << Text.new('Maruku')
312
+ span << Text.new(nice_date+".")
313
+ div
314
+ end
315
+
316
+ def render_footnotes()
317
+ div = Element.new 'div'
318
+ div.attributes['class'] = 'footnotes'
319
+ div << Element.new('hr')
320
+ ol = Element.new 'ol'
321
+ @doc.footnotes_order.each_with_index do |fid, i| num = i+1
322
+ f = self.footnotes[fid]
323
+ if f
324
+ li = f.wrap_as_element('li')
325
+ li.attributes['id'] = "#{get_setting(:doc_prefix)}fn:#{num}"
326
+
327
+ a = Element.new 'a'
328
+ a.attributes['href'] = "\##{get_setting(:doc_prefix)}fnref:#{num}"
329
+ a.attributes['rev'] = 'footnote'
330
+ a<< Text.new('&#8617;', true, nil, true)
331
+ li.insert_after(li.children.last, a)
332
+ ol << li
333
+ else
334
+ maruku_error "Could not find footnote id '#{fid}' among ["+
335
+ self.footnotes.keys.map{|s|"'"+s+"'"}.join(', ')+"]."
336
+ end
337
+ end
338
+ div << ol
339
+ div
340
+ end
341
+
342
+
343
+ def to_html_hrule; create_html_element 'hr' end
344
+ def to_html_linebreak; Element.new 'br' end
345
+
346
+ # renders children as html and wraps into an element of given name
347
+ #
348
+ # Sets 'id' if meta is set
349
+ def wrap_as_element(name, attributes_to_copy=[])
350
+ m = create_html_element(name, attributes_to_copy)
351
+ children_to_html.each do |e| m << e; end
352
+
353
+ # m << Comment.new( "{"+self.al.to_md+"}") if not self.al.empty?
354
+ # m << Comment.new( @attributes.inspect) if not @attributes.empty?
355
+ m
356
+ end
357
+
358
+ =begin maruku_doc
359
+ Attribute: id
360
+ Scope: element
361
+ Output: LaTeX, HTML
362
+
363
+ It is copied as a standard HTML attribute.
364
+
365
+ Moreover, it used as a label name for hyperlinks in both HTML and
366
+ in PDF.
367
+
368
+ =end
369
+
370
+ =begin maruku_doc
371
+ Attribute: class
372
+ Scope: element
373
+ Output: HTML
374
+
375
+ It is copied as a standard HTML attribute.
376
+ =end
377
+
378
+ =begin maruku_doc
379
+ Attribute: style
380
+ Scope: element
381
+ Output: HTML
382
+
383
+ It is copied as a standard HTML attribute.
384
+ =end
385
+
386
+
387
+
388
+
389
+
390
+ HTML4Attributes = {}
391
+
392
+ coreattrs = [:id, :class, :style, :title, :accesskey, :contenteditable, :dir, :draggable, :spellcheck, :tabindex]
393
+ i18n = [:lang, 'xml:lang'.to_sym]
394
+ events = [
395
+ :onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover,
396
+ :onmousemove, :onmouseout,
397
+ :onkeypress, :onkeydown, :onkeyup]
398
+ attrs = coreattrs + i18n + events
399
+ cellhalign = [:align, :char, :charoff]
400
+ cellvalign = [:valign]
401
+ [
402
+ ['body', attrs + [:onload, :onunload]],
403
+ ['address', attrs],
404
+ ['div', attrs],
405
+ ['a', attrs+[:charset, :type, :name, :rel, :rev, :accesskey, :shape, :coords, :tabindex,
406
+ :onfocus,:onblur]],
407
+ ['img', attrs + [:longdesc, :name, :height, :width, :alt] ],
408
+ ['p', attrs],
409
+ [['h1','h2','h3','h4','h5','h6'], attrs],
410
+ [['pre'], attrs],
411
+ [['q', 'blockquote'], attrs+[:cite]],
412
+ [['ins','del'], attrs+[:cite,:datetime]],
413
+ [['ol'], attrs+[:reversed, :start]],
414
+ [['ul'], attrs],
415
+ [['li'], attrs+[:value]],
416
+ ['table',attrs+[:summary, :width, :frame, :rules, :border, :cellspacing, :cellpadding]],
417
+ ['caption',attrs],
418
+ [['colgroup','col'],attrs+[:span, :width]+cellhalign+cellvalign],
419
+ [['thead','tbody','tfoot'], attrs+cellhalign+cellvalign],
420
+ [['td','td','th'], attrs+[:abbr, :axis, :headers, :scope, :rowspan, :colspan, :cellvalign, :cellhalign]],
421
+
422
+ # altri
423
+ [['em','code','strong','hr','span','dl','dd','dt'], attrs]
424
+ ].each do |el, a| [*el].each do |e| HTML4Attributes[e] = a end end
425
+
426
+
427
+ def create_html_element(name, attributes_to_copy=[])
428
+ m = Element.new name
429
+ if atts = HTML4Attributes[name] then
430
+ atts.each do |att|
431
+ if v = @attributes[att] then
432
+ m.attributes[att.to_s] = v.to_s
433
+ end
434
+ end
435
+ else
436
+ # puts "not atts for #{name.inspect}"
437
+ end
438
+ m
439
+ end
440
+
441
+
442
+ def to_html_ul
443
+ if @attributes[:toc]
444
+ # render toc
445
+ html_toc = @doc.toc.to_html
446
+ return html_toc
447
+ else
448
+ add_ws wrap_as_element('ul')
449
+ end
450
+ end
451
+
452
+
453
+ def to_html_paragraph; add_ws wrap_as_element('p') end
454
+ def to_html_ol; add_ws wrap_as_element('ol') end
455
+ def to_html_li; add_ws wrap_as_element('li') end
456
+ def to_html_li_span; add_ws wrap_as_element('li') end
457
+ def to_html_quote; add_ws wrap_as_element('blockquote') end
458
+ def to_html_strong; wrap_as_element('strong') end
459
+ def to_html_emphasis; wrap_as_element('em') end
460
+
461
+ =begin maruku_doc
462
+ Attribute: use_numbered_headers
463
+ Scope: document
464
+ Summary: Activates the numbering of headers.
465
+
466
+ If `true`, section headers will be numbered.
467
+
468
+ In LaTeX export, the numbering of headers is managed
469
+ by Maruku, to have the same results in both HTML and LaTeX.
470
+ =end
471
+
472
+ # nil if not applicable, else string
473
+ def section_number
474
+ return nil if not get_setting(:use_numbered_headers)
475
+
476
+ n = @attributes[:section_number]
477
+ if n && (not n.empty?)
478
+ n.join('.')+". "
479
+ else
480
+ nil
481
+ end
482
+ end
483
+
484
+ # nil if not applicable, else SPAN element
485
+ def render_section_number
486
+ # if we are bound to a section, add section number
487
+ if num = section_number
488
+ span = Element.new 'span'
489
+ span.attributes['class'] = 'maruku_section_number'
490
+ span << Text.new(section_number)
491
+ span
492
+ else
493
+ nil
494
+ end
495
+ end
496
+
497
+ def to_html_header
498
+ element_name = "h#{self.level}"
499
+ h = wrap_as_element element_name
500
+
501
+ if span = render_section_number
502
+ h.insert_before(h.children.first, span)
503
+ end
504
+ add_ws h
505
+ end
506
+
507
+ def source2html(source)
508
+ # source = source.gsub(/&/,'&amp;')
509
+ source = Text.normalize(source)
510
+ source = source.gsub(/\&apos;/,'&#39;') # IE bug
511
+ source = source.gsub(/'/,'&#39;') # IE bug
512
+ Text.new(source, true, nil, true )
513
+ end
514
+
515
+ =begin maruku_doc
516
+ Attribute: html_use_syntax
517
+ Scope: global, document, element
518
+ Output: HTML
519
+ Summary: Enables the use of the `syntax` package.
520
+ Related: lang, code_lang
521
+ Default: <?mrk md_code(Globals[:html_use_syntax].to_s) ?>
522
+
523
+ If true, the `syntax` package is used. It supports the `ruby` and `xml`
524
+ languages. Remember to set the `lang` attribute of the code block.
525
+
526
+ Examples:
527
+
528
+ require 'maruku'
529
+ {:lang=ruby html_use_syntax=true}
530
+
531
+ and
532
+
533
+ <div style="text-align:center">Div</div>
534
+ {:lang=html html_use_syntax=true}
535
+
536
+ produces:
537
+
538
+ require 'maruku'
539
+ {:lang=ruby html_use_syntax=true}
540
+
541
+ and
542
+
543
+ <div style="text-align:center">Div</div>
544
+ {:lang=html html_use_syntax=true}
545
+
546
+ =end
547
+
548
+ $syntax_loaded = false
549
+ def to_html_code;
550
+ source = self.raw_code
551
+
552
+ lang = self.attributes[:lang] || @doc.attributes[:code_lang]
553
+
554
+ lang = 'xml' if lang=='html'
555
+ lang = 'css21' if lang == 'css'
556
+
557
+ use_syntax = get_setting :html_use_syntax
558
+
559
+ element =
560
+ if use_syntax && lang
561
+ begin
562
+ if not $syntax_loaded
563
+ require 'rubygems'
564
+ require 'syntax'
565
+ require 'syntax/convertors/html'
566
+ $syntax_loaded = true
567
+ end
568
+ convertor = Syntax::Convertors::HTML.for_syntax lang
569
+
570
+ # eliminate trailing newlines otherwise Syntax crashes
571
+ source = source.gsub(/\n*\Z/,'')
572
+
573
+ html = convertor.convert( source )
574
+ html = html.gsub(/\&apos;/,'&#39;') # IE bug
575
+ html = html.gsub(/'/,'&#39;') # IE bug
576
+ # html = html.gsub(/&/,'&amp;')
577
+
578
+ code = Document.new(html, {:respect_whitespace =>:all}).root
579
+ code.name = 'code'
580
+ code.attributes['lang'] = lang
581
+
582
+ pre = Element.new 'pre'
583
+ pre.attributes['class'] = lang
584
+ pre << code
585
+ pre
586
+ rescue LoadError => e
587
+ maruku_error "Could not load package 'syntax'.\n"+
588
+ "Please install it, for example using 'gem install syntax'."
589
+ to_html_code_using_pre(source)
590
+ rescue Object => e
591
+ maruku_error"Error while using the syntax library for code:\n#{source.inspect}"+
592
+ "Lang is #{lang} object is: \n"+
593
+ self.inspect +
594
+ "\nException: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
595
+
596
+ tell_user("Using normal PRE because the syntax library did not work.")
597
+ to_html_code_using_pre(source)
598
+ end
599
+ else
600
+ to_html_code_using_pre(source)
601
+ end
602
+
603
+ color = get_setting(:code_background_color)
604
+ if color != Globals[:code_background_color]
605
+ element.attributes['style'] = "background-color: #{color};"
606
+ end
607
+ add_ws element
608
+ end
609
+
610
+ =begin maruku_doc
611
+ Attribute: code_background_color
612
+ Scope: global, document, element
613
+ Summary: Background color for code blocks.
614
+
615
+ The format is either a named color (`green`, `red`) or a CSS color
616
+ of the form `#ff00ff`.
617
+
618
+ * for **HTML output**, the value is put straight in the `background-color` CSS
619
+ property of the block.
620
+
621
+ * for **LaTeX output**, if it is a named color, it must be a color accepted
622
+ by the LaTeX `color` packages. If it is of the form `#ff00ff`, Maruku
623
+ defines a color using the `\color[rgb]{r,g,b}` macro.
624
+
625
+ For example, for `#0000ff`, the macro is called as: `\color[rgb]{0,0,1}`.
626
+
627
+ =end
628
+
629
+
630
+ def to_html_code_using_pre(source)
631
+ pre = create_html_element 'pre'
632
+ code = Element.new 'code', pre
633
+ s = source
634
+
635
+ # s = s.gsub(/&/,'&amp;')
636
+ s = Text.normalize(s)
637
+ s = s.gsub(/\&apos;/,'&#39;') # IE bug
638
+ s = s.gsub(/'/,'&#39;') # IE bug
639
+
640
+ if get_setting(:code_show_spaces)
641
+ # 187 = raquo
642
+ # 160 = nbsp
643
+ # 172 = not
644
+ s.gsub!(/\t/,'&#187;'+'&#160;'*3)
645
+ s.gsub!(/ /,'&#172;')
646
+ end
647
+
648
+ text = Text.new(s, respect_ws=true, parent=nil, raw=true )
649
+
650
+ if lang = self.attributes[:lang]
651
+ code.attributes['lang'] = lang
652
+ code.attributes['class'] = lang
653
+ end
654
+ code << text
655
+ pre
656
+ end
657
+
658
+ def to_html_inline_code;
659
+ pre = create_html_element 'code'
660
+ source = self.raw_code
661
+ pre << source2html(source)
662
+
663
+ color = get_setting(:code_background_color)
664
+ if color != Globals[:code_background_color]
665
+ pre.attributes['style'] = "background-color: #{color};"+(pre.attributes['style']||"")
666
+ end
667
+
668
+ pre
669
+ end
670
+
671
+ def add_class_to(el, cl)
672
+ el.attributes['class'] =
673
+ if already = el.attributes['class']
674
+ already + " " + cl
675
+ else
676
+ cl
677
+ end
678
+ end
679
+
680
+ def add_class_to_link(a)
681
+ return # not ready yet
682
+
683
+ # url = a.attributes['href']
684
+ # return if not url
685
+ #
686
+ # if url =~ /^#/
687
+ # add_class_to(a, 'maruku-link-samedoc')
688
+ # elsif url =~ /^http:/
689
+ # add_class_to(a, 'maruku-link-external')
690
+ # else
691
+ # add_class_to(a, 'maruku-link-local')
692
+ # end
693
+ #
694
+ # puts a.attributes['class']
695
+ end
696
+
697
+
698
+ def to_html_immediate_link
699
+ a = create_html_element 'a'
700
+ url = self.url
701
+ text = url.gsub(/^mailto:/,'') # don't show mailto
702
+ a << Text.new(text)
703
+ a.attributes['href'] = url
704
+ add_class_to_link(a)
705
+ a
706
+ end
707
+
708
+ def to_html_link
709
+ a = wrap_as_element 'a'
710
+ id = self.ref_id
711
+
712
+ if ref = @doc.refs[id]
713
+ url = ref[:url]
714
+ title = ref[:title]
715
+ a.attributes['href'] = url if url
716
+ a.attributes['title'] = title if title
717
+ else
718
+ maruku_error "Could not find ref_id = #{id.inspect} for #{self.inspect}\n"+
719
+ "Available refs are #{@doc.refs.keys.inspect}"
720
+ tell_user "Not creating a link for ref_id = #{id.inspect}."
721
+ return wrap_as_element('span')
722
+ end
723
+
724
+ # add_class_to_link(a)
725
+ return a
726
+ end
727
+
728
+ def to_html_im_link
729
+ if url = self.url
730
+ title = self.title
731
+ a = wrap_as_element 'a'
732
+ a.attributes['href'] = url
733
+ a.attributes['title'] = title if title
734
+ return a
735
+ else
736
+ maruku_error"Could not find url in #{self.inspect}"
737
+ tell_user "Not creating a link for ref_id = #{id.inspect}."
738
+ return wrap_as_element('span')
739
+ end
740
+ end
741
+
742
+ def add_ws(e)
743
+ [Text.new("\n"), e, Text.new("\n")]
744
+ end
745
+ ##### Email address
746
+
747
+ def obfuscate(s)
748
+ res = ''
749
+ s.each_byte do |char|
750
+ res += "&#%03d;" % char
751
+ end
752
+ res
753
+ end
754
+
755
+ def to_html_email_address
756
+ email = self.email
757
+ a = create_html_element 'a'
758
+ #a.attributes['href'] = Text.new("mailto:"+obfuscate(email),false,nil,true)
759
+ #a.attributes.add Attribute.new('href',Text.new(
760
+ #"mailto:"+obfuscate(email),false,nil,true))
761
+ # Sorry, for the moment it doesn't work
762
+ a.attributes['href'] = "mailto:#{email}"
763
+
764
+ a << Text.new(obfuscate(email),false,nil,true)
765
+ a
766
+ end
767
+
768
+ ##### Images
769
+
770
+ def to_html_image
771
+ a = create_html_element 'img'
772
+ id = self.ref_id
773
+ if ref = @doc.refs[id]
774
+ url = ref[:url]
775
+ title = ref[:title]
776
+ a.attributes['src'] = url.to_s
777
+ a.attributes['alt'] = children_to_s
778
+ else
779
+ maruku_error"Could not find id = #{id.inspect} for\n #{self.inspect}"
780
+ tell_user "Could not create image with ref_id = #{id.inspect};"+
781
+ " Using SPAN element as replacement."
782
+ return wrap_as_element('span')
783
+ end
784
+ return a
785
+ end
786
+
787
+ def to_html_im_image
788
+ if not url = self.url
789
+ maruku_error "Image with no url: #{self.inspect}"
790
+ tell_user "Could not create image with ref_id = #{id.inspect};"+
791
+ " Using SPAN element as replacement."
792
+ return wrap_as_element('span')
793
+ end
794
+ title = self.title
795
+ a = create_html_element 'img'
796
+ a.attributes['src'] = url.to_s
797
+ a.attributes['alt'] = children_to_s
798
+ return a
799
+ end
800
+
801
+ =begin maruku_doc
802
+ Attribute: filter_html
803
+ Scope: document
804
+
805
+ If true, raw HTML is discarded from the output.
806
+
807
+ =end
808
+
809
+ def to_html_raw_html
810
+ return [] if get_setting(:filter_html)
811
+
812
+ raw_html = self.raw_html
813
+ if rexml_doc = @parsed_html
814
+ root = rexml_doc.root
815
+ if root.nil?
816
+ s = "Bug in REXML: root() of Document is nil: \n#{rexml_doc.inspect}\n"+
817
+ "Raw HTML:\n#{raw_html.inspect}"
818
+ maruku_error s
819
+ tell_user 'The REXML version you have has a bug, omitting HTML'
820
+ div = Element.new 'div'
821
+ #div << Text.new(s)
822
+ return div
823
+ end
824
+
825
+ # copies the @children array (FIXME is it deep?)
826
+ elements = root.to_a
827
+ return elements
828
+ else # invalid
829
+ # Creates red box with offending HTML
830
+ tell_user "Wrapping bad html in a PRE with class 'markdown-html-error'\n"+
831
+ raw_html.gsub(/^/, '|')
832
+ pre = Element.new('pre')
833
+ pre.attributes['style'] = 'border: solid 3px red; background-color: pink'
834
+ pre.attributes['class'] = 'markdown-html-error'
835
+ pre << Text.new("REXML could not parse this XML/HTML: \n#{raw_html}", true)
836
+ return pre
837
+ end
838
+ end
839
+
840
+ def to_html_abbr
841
+ abbr = Element.new 'abbr'
842
+ abbr << Text.new(children[0])
843
+ abbr.attributes['title'] = self.title if self.title
844
+ abbr
845
+ end
846
+
847
+ def to_html_footnote_reference
848
+ id = self.footnote_id
849
+
850
+ # save the order of used footnotes
851
+ order = @doc.footnotes_order
852
+
853
+ if order.include? id
854
+ # footnote has already been used
855
+ return []
856
+ end
857
+
858
+ if not @doc.footnotes[id]
859
+ return []
860
+ end
861
+
862
+ # take next number
863
+ order << id
864
+
865
+ #num = order.size;
866
+ num = order.index(id) + 1
867
+
868
+ sup = Element.new 'sup'
869
+ sup.attributes['id'] = "#{get_setting(:doc_prefix)}fnref:#{num}"
870
+ a = Element.new 'a'
871
+ a << Text.new(num.to_s)
872
+ a.attributes['href'] = "\##{get_setting(:doc_prefix)}fn:#{num}"
873
+ a.attributes['rel'] = 'footnote'
874
+ sup << a
875
+
876
+ sup
877
+ end
878
+
879
+ ## Definition lists ###
880
+ def to_html_definition_list() add_ws wrap_as_element('dl') end
881
+ def to_html_definition() children_to_html end
882
+ def to_html_definition_term() add_ws wrap_as_element('dt') end
883
+ def to_html_definition_data() add_ws wrap_as_element('dd') end
884
+
885
+ # FIXME: Ugly code
886
+ def to_html_table
887
+ align = self.align
888
+ num_columns = align.size
889
+
890
+ head = @children.slice(0, num_columns)
891
+ rows = []
892
+ i = num_columns
893
+ while i<@children.size
894
+ rows << @children.slice(i, num_columns)
895
+ i += num_columns
896
+ end
897
+
898
+ table = create_html_element 'table'
899
+ thead = Element.new 'thead'
900
+ tr = Element.new 'tr'
901
+ array_to_html(head).each do |x| tr<<x end
902
+ thead << tr
903
+ table << thead
904
+
905
+ tbody = Element.new 'tbody'
906
+ rows.each do |row|
907
+ tr = Element.new 'tr'
908
+ array_to_html(row).each_with_index do |x,i|
909
+ x.attributes['style'] ="text-align: #{align[i].to_s};"
910
+ tr<<x
911
+ end
912
+
913
+ tbody << tr << Text.new("\n")
914
+ end
915
+ table << tbody
916
+ table
917
+ end
918
+
919
+ def to_html_head_cell; wrap_as_element('th') end
920
+ def to_html_cell
921
+ if @attributes[:scope]
922
+ wrap_as_element('th', [:scope])
923
+ else
924
+ wrap_as_element('td')
925
+ end
926
+ end
927
+
928
+ def to_html_entity
929
+ MaRuKu::Out::Latex.need_entity_table
930
+
931
+ entity_name = self.entity_name
932
+
933
+ if (e = MaRuKu::Out::Latex::ENTITY_TABLE[entity_name]) && e.html_num
934
+ entity_name = e.html_num
935
+ end
936
+
937
+ # Fix for Internet Explorer
938
+ if entity_name == 'apos'
939
+ entity_name = 39
940
+ end
941
+
942
+
943
+ if entity_name.kind_of? Fixnum
944
+ # Entity.new(entity_name)
945
+ Text.new('&#%d;' % [entity_name], false, nil, true)
946
+ else
947
+ Text.new('&%s;' % [entity_name], false, nil, true)
948
+ end
949
+ end
950
+
951
+ def to_html_xml_instr
952
+ target = self.target || ''
953
+ code = self.code || ''
954
+ REXML::Instruction.new(target, code)
955
+ end
956
+
957
+ # Convert each child to html
958
+ def children_to_html
959
+ array_to_html(@children)
960
+ end
961
+
962
+ def array_to_html(array)
963
+ e = []
964
+ array.each do |c|
965
+ method = c.kind_of?(MDElement) ?
966
+ "to_html_#{c.node_type}" : "to_html"
967
+
968
+ if not c.respond_to?(method)
969
+ #raise "Object does not answer to #{method}: #{c.class} #{c.inspect}"
970
+ next
971
+ end
972
+
973
+ h = c.send(method)
974
+
975
+ if h.nil?
976
+ raise "Nil html created by method #{method}:\n#{h.inspect}\n"+
977
+ " for object #{c.inspect[0,300]}"
978
+ end
979
+
980
+ if h.kind_of?Array
981
+ e = e + h #h.each do |hh| e << hh end
982
+ else
983
+ e << h
984
+ end
985
+ end
986
+ e
987
+ end
988
+
989
+ def to_html_ref_definition; [] end
990
+ def to_latex_ref_definition; [] end
991
+
992
+ end # HTML
993
+ end # out
994
+ end # MaRuKu