maruku 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/maruku +25 -0
- data/bin/marutex +29 -0
- data/docs/Makefile +25 -0
- data/docs/char_codes.xml +884 -0
- data/docs/color-package-demo.aux +1 -0
- data/docs/color-package-demo.log +127 -0
- data/docs/color-package-demo.tex +149 -0
- data/docs/index.html +74 -0
- data/docs/markdown_syntax.aux +13 -0
- data/docs/markdown_syntax.html +266 -0
- data/docs/markdown_syntax.log +287 -0
- data/docs/markdown_syntax.md +920 -0
- data/docs/markdown_syntax.out +0 -0
- data/docs/markdown_syntax.pdf +0 -0
- data/docs/markdown_syntax.tex +1203 -0
- data/docs/maruku.aux +13 -0
- data/docs/maruku.html +74 -0
- data/docs/maruku.log +294 -0
- data/docs/maruku.md +394 -0
- data/docs/maruku.out +0 -0
- data/docs/maruku.pdf +0 -0
- data/docs/maruku.tex +548 -0
- data/docs/style.css +65 -0
- data/docs/todo.md +12 -0
- data/lib/maruku.rb +20 -0
- data/lib/maruku/parse_block.rb +577 -0
- data/lib/maruku/parse_span.rb +336 -0
- data/lib/maruku/string_utils.rb +270 -0
- data/lib/maruku/structures.rb +31 -0
- data/lib/maruku/to_html.rb +430 -0
- data/lib/maruku/to_latex.rb +345 -0
- data/lib/maruku/to_latex_strings.rb +330 -0
- data/tests/abbreviations.md +11 -0
- data/tests/blank.md +4 -0
- data/tests/code.md +5 -0
- data/tests/code2.md +8 -0
- data/tests/code3.md +16 -0
- data/tests/email.md +4 -0
- data/tests/entities.md +19 -0
- data/tests/escaping.md +14 -0
- data/tests/extra_dl.md +101 -0
- data/tests/extra_header_id.md +13 -0
- data/tests/extra_table1.md +40 -0
- data/tests/footnotes.md +17 -0
- data/tests/headers.md +10 -0
- data/tests/hrule.md +10 -0
- data/tests/images.md +20 -0
- data/tests/inline_html.md +35 -0
- data/tests/links.md +31 -0
- data/tests/list1.md +4 -0
- data/tests/list2.md +5 -0
- data/tests/list3.md +8 -0
- data/tests/lists.md +32 -0
- data/tests/lists_ol.md +39 -0
- data/tests/misc_sw.md +105 -0
- data/tests/one.md +1 -0
- data/tests/paragraphs.md +13 -0
- data/tests/sss06.md +352 -0
- data/tests/test.md +4 -0
- metadata +113 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
class MDElement
|
|
3
|
+
# Allowed: :document, :paragraph, :ul, :ol, :li, :li_span, :strong, :emphasis, :link1
|
|
4
|
+
attr_accessor :node_type
|
|
5
|
+
# Children are either Strings or MDElement
|
|
6
|
+
attr_accessor :children
|
|
7
|
+
# Hash for metadata
|
|
8
|
+
# contains :id for :link1
|
|
9
|
+
# :li :want_my_paragraph
|
|
10
|
+
# :header: :level
|
|
11
|
+
# code, inline_code: :raw_code
|
|
12
|
+
attr_accessor :meta
|
|
13
|
+
# reference of containing document (document has list of ref)
|
|
14
|
+
attr_accessor :doc
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
super();
|
|
18
|
+
@children = [];
|
|
19
|
+
@node_type = :unset
|
|
20
|
+
@meta = {};
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The Maruku class holds static data for the document
|
|
26
|
+
|
|
27
|
+
class Maruku < MDElement
|
|
28
|
+
attr_accessor :refs
|
|
29
|
+
attr_accessor :footnotes
|
|
30
|
+
attr_accessor :abbreviations
|
|
31
|
+
end
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'syntax'
|
|
5
|
+
require 'syntax/convertors/html'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Maruku
|
|
9
|
+
include REXML
|
|
10
|
+
|
|
11
|
+
# Render as an HTML fragment (no head, just the content of BODY). (returns a string)
|
|
12
|
+
def to_html
|
|
13
|
+
div = Element.new 'dummy'
|
|
14
|
+
children_to_html.each do |e|
|
|
15
|
+
div << e
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# render footnotes
|
|
19
|
+
if @doc.meta[:footnotes_used]
|
|
20
|
+
div << render_footnotes
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
|
|
24
|
+
# containing code.
|
|
25
|
+
xml =""
|
|
26
|
+
div.write_children(xml,indent=-1,transitive=false,ie_hack=true)
|
|
27
|
+
xml
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Render to a complete HTML document (returns a string)
|
|
31
|
+
def to_html_document
|
|
32
|
+
doc = to_html_document_tree
|
|
33
|
+
xml = ""
|
|
34
|
+
|
|
35
|
+
# REXML Bug? if indent!=-1 whitespace is not respected for 'pre' elements
|
|
36
|
+
# containing code.
|
|
37
|
+
doc.write(xml,indent=-1,transitive=false,ie_hack=true);
|
|
38
|
+
xhtml10strict = "<?xml version='1.0'?>
|
|
39
|
+
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN'
|
|
40
|
+
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n"
|
|
41
|
+
xhtml10strict + xml
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Render to a complete HTML document (returns a REXML document tree)
|
|
45
|
+
def to_html_document_tree
|
|
46
|
+
doc = Document.new(nil,{:respect_whitespace =>:all})
|
|
47
|
+
# doc << XMLDecl.new
|
|
48
|
+
|
|
49
|
+
root = Element.new('html', doc)
|
|
50
|
+
root.add_namespace('http://www.w3.org/1999/xhtml')
|
|
51
|
+
|
|
52
|
+
lang = @meta[:lang] || 'en'
|
|
53
|
+
root.attributes['lang'] = lang
|
|
54
|
+
root.attributes['xml:lang'] = lang
|
|
55
|
+
|
|
56
|
+
head = Element.new 'head', root
|
|
57
|
+
|
|
58
|
+
# Create title element
|
|
59
|
+
doc_title = @meta[:title] || @meta[:subject] || ""
|
|
60
|
+
title = Element.new 'title'
|
|
61
|
+
title << Text.new(doc_title)
|
|
62
|
+
head << title
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
css = @meta[:css]
|
|
66
|
+
if css
|
|
67
|
+
# <link type="text/css" rel="stylesheet" href="..." />
|
|
68
|
+
link = Element.new 'link'
|
|
69
|
+
link.attributes['type'] = 'text/css'
|
|
70
|
+
link.attributes['rel'] = 'stylesheet'
|
|
71
|
+
link.attributes['href'] = css
|
|
72
|
+
head << link
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
body = Element.new 'body'
|
|
76
|
+
|
|
77
|
+
children_to_html.each do |e|
|
|
78
|
+
body << e
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# render footnotes
|
|
82
|
+
if @doc.meta[:footnotes_used]
|
|
83
|
+
body << render_footnotes
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
root << head
|
|
88
|
+
root << body
|
|
89
|
+
|
|
90
|
+
doc
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def render_footnotes
|
|
94
|
+
div = Element.new 'div'
|
|
95
|
+
div.attributes['class'] = 'footnotes'
|
|
96
|
+
div << Element.new('hr')
|
|
97
|
+
ol = Element.new 'ol'
|
|
98
|
+
@doc.meta[:footnotes_used].each_with_index do |fid, i| num = i+1
|
|
99
|
+
f = @footnotes[fid]
|
|
100
|
+
if f
|
|
101
|
+
li = f.wrap_as_element('li')
|
|
102
|
+
li.attributes['id'] = "fn:#{num}"
|
|
103
|
+
|
|
104
|
+
a = Element.new 'a'
|
|
105
|
+
a.attributes['href'] = "#fnref:#{num}"
|
|
106
|
+
a.attributes['rev'] = 'footnote'
|
|
107
|
+
a<< Text.new('↩', true, nil, true)
|
|
108
|
+
li.children.last << a
|
|
109
|
+
ol << li
|
|
110
|
+
else
|
|
111
|
+
$stderr.puts "Could not find footnote '#{fid}'"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
div << ol
|
|
115
|
+
div
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class String
|
|
120
|
+
# A string is rendered into HTML by creating
|
|
121
|
+
# a REXML::Text node. REXML takes care of all the encoding.
|
|
122
|
+
def to_html
|
|
123
|
+
REXML::Text.new(self)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class MDElement
|
|
128
|
+
|
|
129
|
+
def to_html_hrule; Element.new 'hr' end
|
|
130
|
+
def to_html_linebreak; Element.new 'br' end
|
|
131
|
+
|
|
132
|
+
# renders children as html and wraps into an element of given name
|
|
133
|
+
#
|
|
134
|
+
# Sets 'id' if meta is set
|
|
135
|
+
def wrap_as_element(name)
|
|
136
|
+
m = create_html_element name
|
|
137
|
+
children_to_html.each do |e| m << e; end
|
|
138
|
+
m
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def create_html_element(name)
|
|
142
|
+
m = Element.new name
|
|
143
|
+
if @meta[:id] then m.attributes['id'] = @meta[:id].to_s end
|
|
144
|
+
if @meta[:style] then m.attributes['style'] = @meta[:style].to_s end
|
|
145
|
+
if @meta[:class] then m.attributes['class'] = @meta[:class].to_s end
|
|
146
|
+
m
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def to_html_paragraph; wrap_as_element('p') end
|
|
150
|
+
def to_html_ul; wrap_as_element('ul') end
|
|
151
|
+
def to_html_ol; wrap_as_element('ol') end
|
|
152
|
+
def to_html_li; wrap_as_element('li') end
|
|
153
|
+
def to_html_li_span; wrap_as_element('li') end
|
|
154
|
+
def to_html_quote; wrap_as_element('blockquote') end
|
|
155
|
+
def to_html_strong; wrap_as_element('strong') end
|
|
156
|
+
def to_html_emphasis; wrap_as_element('em') end
|
|
157
|
+
def to_html_header; wrap_as_element "h#{@meta[:level]}" end
|
|
158
|
+
|
|
159
|
+
def source2html(source)
|
|
160
|
+
source = source.gsub(/&/,'&')
|
|
161
|
+
source = Text.normalize(source)
|
|
162
|
+
Text.new(source, true, nil, false )
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def to_html_code;
|
|
166
|
+
source = self.meta[:raw_code]
|
|
167
|
+
|
|
168
|
+
lang = self.meta[:lang] || @doc.meta[:code_lang]
|
|
169
|
+
|
|
170
|
+
lang = 'xml' if lang=='html'
|
|
171
|
+
use_syntax = @doc.meta[:html_use_syntax]
|
|
172
|
+
|
|
173
|
+
element =
|
|
174
|
+
if use_syntax && lang
|
|
175
|
+
convertor = Syntax::Convertors::HTML.for_syntax lang
|
|
176
|
+
html = convertor.convert( source )
|
|
177
|
+
|
|
178
|
+
show_spaces = get_setting(:code_show_spaces)
|
|
179
|
+
if show_spaces
|
|
180
|
+
s.gsub!(/\t/,'»'+' '*3)
|
|
181
|
+
s.gsub!(/ /,'¬')
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# puts "html: #{html}"
|
|
185
|
+
pre = Document.new(html, {:respect_whitespace =>:all}).root
|
|
186
|
+
pre.attributes['class'] = lang
|
|
187
|
+
# puts "After: #{pre}"
|
|
188
|
+
pre
|
|
189
|
+
else
|
|
190
|
+
pre = Element.new 'pre'
|
|
191
|
+
s = source
|
|
192
|
+
|
|
193
|
+
s = s.gsub(/&/,'&')
|
|
194
|
+
s = Text.normalize(s)
|
|
195
|
+
|
|
196
|
+
show_spaces = get_setting(:code_show_spaces)
|
|
197
|
+
if show_spaces
|
|
198
|
+
s.gsub!(/\t/,'»'+' '*3)
|
|
199
|
+
s.gsub!(/ /,'¬')
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
text = Text.new(s, true, nil, false )
|
|
203
|
+
|
|
204
|
+
pre << text
|
|
205
|
+
pre
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
color = get_setting(:code_background_color,DEFAULT_CODE_COLOR)
|
|
209
|
+
if color
|
|
210
|
+
element.attributes['style'] = "background-color: #{color};"
|
|
211
|
+
end
|
|
212
|
+
element
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def to_html_inline_code;
|
|
216
|
+
pre = Element.new 'tt'
|
|
217
|
+
source = self.meta[:raw_code]
|
|
218
|
+
pre << source2html(source)
|
|
219
|
+
|
|
220
|
+
color = get_setting(:code_background_color, DEFAULT_CODE_COLOR)
|
|
221
|
+
if color
|
|
222
|
+
pre.attributes['style'] = "background-color: #{color};"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
pre
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def to_html_immediate_link
|
|
229
|
+
a = Element.new 'a'
|
|
230
|
+
url = @meta[:url]
|
|
231
|
+
text = url
|
|
232
|
+
text = text.gsub(/^mailto:/,'') # don't show mailto
|
|
233
|
+
a << Text.new(text)
|
|
234
|
+
a.attributes['href'] = url
|
|
235
|
+
a
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def to_html_link
|
|
239
|
+
a = wrap_as_element 'a'
|
|
240
|
+
|
|
241
|
+
id = @meta[:ref_id]
|
|
242
|
+
ref = @doc.refs[id]
|
|
243
|
+
if not ref
|
|
244
|
+
$stderr.puts "Could not find id = '#{id}'"
|
|
245
|
+
else
|
|
246
|
+
url = ref[:url]
|
|
247
|
+
title = ref[:title]
|
|
248
|
+
a.attributes['href'] = url
|
|
249
|
+
a.attributes['title'] = title if title
|
|
250
|
+
end
|
|
251
|
+
a
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
##### Email address
|
|
255
|
+
|
|
256
|
+
def obfuscate(s)
|
|
257
|
+
res = ''
|
|
258
|
+
s.each_byte do |char|
|
|
259
|
+
res += "&#%03d;" % char
|
|
260
|
+
end
|
|
261
|
+
res
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def to_html_email_address
|
|
265
|
+
email = @meta[:email]
|
|
266
|
+
a = Element.new 'a'
|
|
267
|
+
#a.attributes['href'] = Text.new("mailto:"+obfuscate(email),false,nil,true)
|
|
268
|
+
#a.attributes.add Attribute.new('href',Text.new(
|
|
269
|
+
#"mailto:"+obfuscate(email),false,nil,true))
|
|
270
|
+
# Sorry, for the moment it doesn't work
|
|
271
|
+
a.attributes['href'] = "mailto:#{email}"
|
|
272
|
+
|
|
273
|
+
a << Text.new(obfuscate(email),false,nil,true)
|
|
274
|
+
a
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
##### Images
|
|
278
|
+
|
|
279
|
+
def to_html_image
|
|
280
|
+
a = Element.new 'img'
|
|
281
|
+
id = @meta[:ref_id]
|
|
282
|
+
ref = @doc.refs[id]
|
|
283
|
+
if not ref
|
|
284
|
+
$stderr.puts "Could not find id = '#{id}'"
|
|
285
|
+
else
|
|
286
|
+
url = ref[:url]
|
|
287
|
+
a.attributes['src'] = url
|
|
288
|
+
# puts ref.inspect
|
|
289
|
+
[:title, :class, :style].each do |s|
|
|
290
|
+
if ref[s] then
|
|
291
|
+
a.attributes[s.to_s] = ref[s]
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
end
|
|
296
|
+
a
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def to_html_raw_html
|
|
300
|
+
if @meta[:parsed_html]
|
|
301
|
+
return @meta[:parsed_html].root
|
|
302
|
+
else # invalid
|
|
303
|
+
raw_html = @meta[:raw_html]
|
|
304
|
+
# Creates red box with offending HTML
|
|
305
|
+
$stderr.puts "Malformed HTML: #{raw_html}"
|
|
306
|
+
div = Element.new('pre')
|
|
307
|
+
div.attributes['style'] = 'border: solid 3px red; background-color: pink'
|
|
308
|
+
div.attributes['class'] = 'markdown-html-error'
|
|
309
|
+
div << Text.new("HTML parse error: \n#{raw_html}", true)
|
|
310
|
+
return div
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def to_html_abbreviation
|
|
315
|
+
abbr = Element.new 'abbr'
|
|
316
|
+
abbr << Text.new(children[0])
|
|
317
|
+
abbr.attributes['title'] = self.meta[:title] if self.meta[:title]
|
|
318
|
+
abbr
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def to_html_footnote_reference
|
|
322
|
+
id = @meta[:footnote_id]
|
|
323
|
+
|
|
324
|
+
# save the order of used footnotes
|
|
325
|
+
order = (@doc.meta[:footnotes_used] ||= [])
|
|
326
|
+
|
|
327
|
+
# take next number
|
|
328
|
+
order << id
|
|
329
|
+
num = order.size;
|
|
330
|
+
|
|
331
|
+
sup = Element.new 'sup'
|
|
332
|
+
sup.attributes['id'] = "fnref:#{num}"
|
|
333
|
+
a = Element.new 'a'
|
|
334
|
+
a << Text.new(num.to_s)
|
|
335
|
+
a.attributes['href'] = "\#fn:#{num}"
|
|
336
|
+
a.attributes['rel'] = 'footnote'
|
|
337
|
+
sup << a
|
|
338
|
+
|
|
339
|
+
sup
|
|
340
|
+
end
|
|
341
|
+
## Definition lists ###
|
|
342
|
+
def to_html_definition_list
|
|
343
|
+
wrap_as_element('dl')
|
|
344
|
+
end
|
|
345
|
+
def to_html_definition
|
|
346
|
+
children_to_html
|
|
347
|
+
end
|
|
348
|
+
def to_html_definition_term; wrap_as_element('dt') end
|
|
349
|
+
def to_html_definition_data; wrap_as_element('dd') end
|
|
350
|
+
|
|
351
|
+
## Table ###
|
|
352
|
+
def to_html_table
|
|
353
|
+
align = @meta[:align]
|
|
354
|
+
num_columns = align.size
|
|
355
|
+
|
|
356
|
+
head = @children.slice(0, num_columns)
|
|
357
|
+
rows = []
|
|
358
|
+
i = num_columns
|
|
359
|
+
while i<@children.size
|
|
360
|
+
rows << @children.slice(i, num_columns)
|
|
361
|
+
i+=num_columns
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
table = create_html_element 'table'
|
|
365
|
+
thead = Element.new 'thead'
|
|
366
|
+
tr = Element.new 'tr'
|
|
367
|
+
array_to_html(head).each do |x| tr<<x end
|
|
368
|
+
thead << tr
|
|
369
|
+
table << thead
|
|
370
|
+
|
|
371
|
+
tbody = Element.new 'tbody'
|
|
372
|
+
rows.each do |row|
|
|
373
|
+
tr = Element.new 'tr'
|
|
374
|
+
array_to_html(row).each_with_index do |x,i|
|
|
375
|
+
x.attributes['style'] ="text-align: #{align[i].to_s};"
|
|
376
|
+
tr<<x
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
tbody << tr
|
|
380
|
+
end
|
|
381
|
+
table << tbody
|
|
382
|
+
table
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def to_html_head_cell; wrap_as_element('th') end
|
|
386
|
+
def to_html_cell; wrap_as_element('td') end
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# We only want to output the children in Maruku::to_html
|
|
392
|
+
class REXML::Element; public :write_children end
|
|
393
|
+
|
|
394
|
+
# Some utilities
|
|
395
|
+
class MDElement
|
|
396
|
+
include REXML
|
|
397
|
+
|
|
398
|
+
# Convert each child to html
|
|
399
|
+
def children_to_html
|
|
400
|
+
array_to_html(@children)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def array_to_html(array)
|
|
404
|
+
e = []
|
|
405
|
+
array.each do |c|
|
|
406
|
+
method = c.kind_of?(MDElement) ?
|
|
407
|
+
"to_html_#{c.node_type}" : "to_html"
|
|
408
|
+
|
|
409
|
+
if not c.respond_to?(method)
|
|
410
|
+
raise "Object does not answer to #{method}: #{c.class} #{c.inspect}"
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
h = c.send(method)
|
|
414
|
+
|
|
415
|
+
if h.nil?
|
|
416
|
+
raise "Nil html for #{c.inspect} created with method #{method}"
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
if h.kind_of?Array
|
|
420
|
+
e = e + h #h.each do |hh| e << hh end
|
|
421
|
+
else
|
|
422
|
+
e << h
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
e
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
|