trac-wiki 0.0.1
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/Gemfile +2 -0
- data/README +40 -0
- data/Rakefile +6 -0
- data/lib/trac_wiki.rb +16 -0
- data/lib/trac_wiki/parser.rb +402 -0
- data/lib/trac_wiki/version.rb +3 -0
- data/test/parser_test.rb +665 -0
- data/trac-wiki.gemspec +24 -0
- metadata +88 -0
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
= TracWiki =
|
2
|
+
|
3
|
+
TracWiki is a TracWiki-to-HTML converter for Trac wiki, http://trac.edgewall.org/wiki/WikiFormatting.
|
4
|
+
|
5
|
+
Project page on github:
|
6
|
+
|
7
|
+
* http://github.com/vitstradal/trac-wiki
|
8
|
+
|
9
|
+
== INSTALLATION ==
|
10
|
+
|
11
|
+
{{{
|
12
|
+
gem install trac-wiki
|
13
|
+
}}}
|
14
|
+
|
15
|
+
== SYNOPSIS ==
|
16
|
+
|
17
|
+
{{{
|
18
|
+
require 'trac_wiki'
|
19
|
+
html = TracWiki.render('== TracWik text ==')
|
20
|
+
}}}
|
21
|
+
|
22
|
+
== BUGS ==
|
23
|
+
|
24
|
+
If you found a bug, please report it at the TracWiki project's tracker
|
25
|
+
on GitHub:
|
26
|
+
|
27
|
+
http://github.com/vitstradal/trac-wiki/issues
|
28
|
+
|
29
|
+
== AUTHORS ==
|
30
|
+
|
31
|
+
* Vitas Stradal
|
32
|
+
Based on Creole:
|
33
|
+
|
34
|
+
* Lars Christensen (larsch)
|
35
|
+
* Daniel Mendler (minad)
|
36
|
+
|
37
|
+
== LICENSE ==
|
38
|
+
|
39
|
+
|
40
|
+
GPL
|
data/Rakefile
ADDED
data/lib/trac_wiki.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'trac_wiki/parser'
|
2
|
+
require 'trac_wiki/version'
|
3
|
+
|
4
|
+
module TracWiki
|
5
|
+
# Convert the argument in Trac format to HTML and return the
|
6
|
+
# result. Example:
|
7
|
+
#
|
8
|
+
# TracWiki.creolize("**Hello ''World''**")
|
9
|
+
# #=> "<p><strong>Hello <em>World</em></strong></p>"
|
10
|
+
#
|
11
|
+
# This is an alias for calling Creole#parse:
|
12
|
+
# TracWiki.new(text).to_html
|
13
|
+
def self.render(text, options = {})
|
14
|
+
Parser.new(text, options).to_html
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
# :main: TracWiki
|
5
|
+
|
6
|
+
# The Creole parses and translates Creole formatted text into
|
7
|
+
# XHTML. Creole is a lightweight markup syntax similar to what many
|
8
|
+
# WikiWikiWebs use. Example syntax:
|
9
|
+
#
|
10
|
+
# = Heading 1 =
|
11
|
+
# == Heading 2 ==
|
12
|
+
# === Heading 3 ===
|
13
|
+
# **Bold text**
|
14
|
+
# //Italic text//
|
15
|
+
# [[Links]]
|
16
|
+
# |=Table|=Heading|
|
17
|
+
# |Table |Cells |
|
18
|
+
# {{image.png}}
|
19
|
+
#
|
20
|
+
# The simplest interface is TracWiki.render. The default handling of
|
21
|
+
# links allow explicit local links using the [[link]] syntax. External
|
22
|
+
# links will only be allowed if specified using http(s) and ftp(s)
|
23
|
+
# schemes. If special link handling is needed, such as inter-wiki or
|
24
|
+
# hierachical local links, you must inherit Creole::CreoleParser and
|
25
|
+
# override make_local_link.
|
26
|
+
#
|
27
|
+
# You can customize the created image markup by overriding
|
28
|
+
# make_image.
|
29
|
+
|
30
|
+
# Main TracWiki parser class. Call TracWikiParser#parse to parse
|
31
|
+
# TracWiki formatted text.
|
32
|
+
#
|
33
|
+
# This class is not reentrant. A separate instance is needed for
|
34
|
+
# each thread that needs to convert Creole to HTML.
|
35
|
+
#
|
36
|
+
# Inherit this to provide custom handling of links. The overrideable
|
37
|
+
# methods are: make_local_link
|
38
|
+
module TracWiki
|
39
|
+
class Parser
|
40
|
+
|
41
|
+
# Allowed url schemes
|
42
|
+
# Examples: http https ftp ftps
|
43
|
+
attr_accessor :allowed_schemes
|
44
|
+
|
45
|
+
# Non-standard wiki text extensions enabled?
|
46
|
+
# E.g. underlined, deleted text etc
|
47
|
+
attr_writer :extensions
|
48
|
+
def extensions?; @extensions; end
|
49
|
+
|
50
|
+
# Disable url escaping for local links
|
51
|
+
# Escaping: [[/Test]] --> %2FTest
|
52
|
+
# No escaping: [[/Test]] --> Test
|
53
|
+
attr_writer :no_escape
|
54
|
+
def no_escape?; @no_escape; end
|
55
|
+
|
56
|
+
# Create a new Parser instance.
|
57
|
+
def initialize(text, options = {})
|
58
|
+
@allowed_schemes = %w(http https ftp ftps)
|
59
|
+
@text = text
|
60
|
+
@extensions = @no_escape = nil
|
61
|
+
options.each_pair {|k,v| send("#{k}=", v) }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Convert CCreole text to HTML and return
|
65
|
+
# the result. The resulting HTML does not contain <html> and
|
66
|
+
# <body> tags.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# parser = CreoleParser.new("**Hello //World//**", :extensions => true)
|
71
|
+
# parser.to_html
|
72
|
+
# #=> "<p><strong>Hello <em>World</em></strong></p>"
|
73
|
+
def to_html
|
74
|
+
@out = ''
|
75
|
+
@p = false
|
76
|
+
@stack = []
|
77
|
+
parse_block(@text)
|
78
|
+
@out
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
# Escape any characters with special meaning in HTML using HTML
|
84
|
+
# entities.
|
85
|
+
def escape_html(string)
|
86
|
+
CGI::escapeHTML(string)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Escape any characters with special meaning in URLs using URL
|
90
|
+
# encoding.
|
91
|
+
def escape_url(string)
|
92
|
+
CGI::escape(string)
|
93
|
+
end
|
94
|
+
|
95
|
+
def start_tag(tag)
|
96
|
+
@stack.push(tag)
|
97
|
+
@out << '<' << tag << '>'
|
98
|
+
end
|
99
|
+
|
100
|
+
def end_tag
|
101
|
+
@out << '</' << @stack.pop << '>'
|
102
|
+
end
|
103
|
+
|
104
|
+
def toggle_tag(tag, match)
|
105
|
+
if @stack.include?(tag)
|
106
|
+
if @stack.last == tag
|
107
|
+
end_tag
|
108
|
+
else
|
109
|
+
@out << escape_html(match)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
start_tag(tag)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def end_paragraph
|
117
|
+
end_tag while !@stack.empty?
|
118
|
+
@p = false
|
119
|
+
end
|
120
|
+
|
121
|
+
def start_paragraph
|
122
|
+
if @p
|
123
|
+
@out << ' ' if @out[-1] != ?\s
|
124
|
+
else
|
125
|
+
end_paragraph
|
126
|
+
start_tag('p')
|
127
|
+
@p = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Translate an explicit local link to a desired URL that is
|
132
|
+
# properly URL-escaped. The default behaviour is to convert local
|
133
|
+
# links directly, escaping any characters that have special
|
134
|
+
# meaning in URLs. Relative URLs in local links are not handled.
|
135
|
+
#
|
136
|
+
# Examples:
|
137
|
+
#
|
138
|
+
# make_local_link("LocalLink") #=> "LocalLink"
|
139
|
+
# make_local_link("/Foo/Bar") #=> "%2FFoo%2FBar"
|
140
|
+
#
|
141
|
+
# Must ensure that the result is properly URL-escaped. The caller
|
142
|
+
# will handle HTML escaping as necessary. HTML links will not be
|
143
|
+
# inserted if the function returns nil.
|
144
|
+
#
|
145
|
+
# Example custom behaviour:
|
146
|
+
#
|
147
|
+
# make_local_link("LocalLink") #=> "/LocalLink"
|
148
|
+
# make_local_link("Wikipedia:Bread") #=> "http://en.wikipedia.org/wiki/Bread"
|
149
|
+
def make_local_link(link) #:doc:
|
150
|
+
no_escape? ? link : escape_url(link)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Sanatize a direct url (e.g. http://wikipedia.org/). The default
|
154
|
+
# behaviour returns the original link as-is.
|
155
|
+
#
|
156
|
+
# Must ensure that the result is properly URL-escaped. The caller
|
157
|
+
# will handle HTML escaping as necessary. Links will not be
|
158
|
+
# converted to HTML links if the function returns link.
|
159
|
+
#
|
160
|
+
# Custom versions of this function in inherited classes can
|
161
|
+
# implement specific link handling behaviour, such as redirection
|
162
|
+
# to intermediate pages (for example, for notifing the user that
|
163
|
+
# he is leaving the site).
|
164
|
+
def make_direct_link(url) #:doc:
|
165
|
+
url
|
166
|
+
end
|
167
|
+
|
168
|
+
# Sanatize and prefix image URLs. When images are encountered in
|
169
|
+
# Creole text, this function is called to obtain the actual URL of
|
170
|
+
# the image. The default behaviour is to return the image link
|
171
|
+
# as-is. No image tags are inserted if the function returns nil.
|
172
|
+
#
|
173
|
+
# Custom version of the method can be used to sanatize URLs
|
174
|
+
# (e.g. remove query-parts), inhibit off-site images, or add a
|
175
|
+
# base URL, for example:
|
176
|
+
#
|
177
|
+
# def make_image_link(url)
|
178
|
+
# URI.join("http://mywiki.org/images/", url)
|
179
|
+
# end
|
180
|
+
def make_image_link(url) #:doc:
|
181
|
+
url
|
182
|
+
end
|
183
|
+
|
184
|
+
# Create image markup. This
|
185
|
+
# method can be overridden to generate custom
|
186
|
+
# markup, for example to add html additional attributes or
|
187
|
+
# to put divs around the imgs.
|
188
|
+
def make_image(uri, alt)
|
189
|
+
if alt
|
190
|
+
'<img src="' << escape_html(uri) << '" alt="' << escape_html(alt) << '"/>'
|
191
|
+
else
|
192
|
+
'<img src="' << escape_html(uri) << '"/>'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def make_headline(level, text)
|
197
|
+
"<h#{level}>" << escape_html(text) << "</h#{level}>"
|
198
|
+
end
|
199
|
+
|
200
|
+
def make_explicit_link(link)
|
201
|
+
begin
|
202
|
+
uri = URI.parse(link)
|
203
|
+
return uri.to_s if uri.scheme && @allowed_schemes.include?(uri.scheme)
|
204
|
+
rescue URI::InvalidURIError
|
205
|
+
end
|
206
|
+
make_local_link(link)
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_inline(str)
|
210
|
+
until str.empty?
|
211
|
+
case str
|
212
|
+
when /\A(\~)?((https?|ftps?):\/\/\S+?)(?=([\,.?!:;"'\)]+)?(\s|$))/
|
213
|
+
str = $'
|
214
|
+
if $1
|
215
|
+
@out << escape_html($2)
|
216
|
+
else
|
217
|
+
if uri = make_direct_link($2)
|
218
|
+
@out << '<a href="' << escape_html(uri) << '">' << escape_html($2) << '</a>'
|
219
|
+
else
|
220
|
+
@out << escape_html($&)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
when /\A\[\[\s*([^|]*?)\s*(\|\s*(.*?))?\s*\]\]/m
|
224
|
+
str = $'
|
225
|
+
link, content = $1, $3
|
226
|
+
if uri = make_explicit_link(link)
|
227
|
+
@out << '<a href="' << escape_html(uri) << '">'
|
228
|
+
if content
|
229
|
+
until content.empty?
|
230
|
+
content = parse_inline_tag(content)
|
231
|
+
end
|
232
|
+
else
|
233
|
+
@out << escape_html(link)
|
234
|
+
end
|
235
|
+
@out << '</a>'
|
236
|
+
else
|
237
|
+
@out << escape_html($&)
|
238
|
+
end
|
239
|
+
else
|
240
|
+
str = parse_inline_tag(str)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def parse_inline_tag(str)
|
246
|
+
case str
|
247
|
+
when /\A\{\{\{(.*?\}*)\}\}\}/ # inline pre (tt)
|
248
|
+
@out << '<tt>' << escape_html($1) << '</tt>'
|
249
|
+
when /\A`(.*?)`/ # inline pre (tt)
|
250
|
+
@out << '<tt>' << escape_html($1) << '</tt>'
|
251
|
+
when /\A\{\{\s*(.*?)\s*(\|\s*(.*?)\s*)?\}\}/
|
252
|
+
if uri = make_image_link($1)
|
253
|
+
@out << make_image(uri, $3)
|
254
|
+
else
|
255
|
+
@out << escape_html($&)
|
256
|
+
end # link
|
257
|
+
when /\A([:alpha:]|[:digit:])+/
|
258
|
+
@out << $& # word
|
259
|
+
when /\A\s+/
|
260
|
+
@out << ' ' if @out[-1] != ?\s # spaces
|
261
|
+
when /\A\*\*/
|
262
|
+
toggle_tag 'strong', $& # bold
|
263
|
+
when /\A''/
|
264
|
+
toggle_tag 'em', $& # italic
|
265
|
+
when /\A\\\\/
|
266
|
+
@out << '<br/>' # newline
|
267
|
+
when /\A__/
|
268
|
+
toggle_tag 'u', $& # underline
|
269
|
+
when /\A~~/
|
270
|
+
toggle_tag 'del', $& # delete
|
271
|
+
# when /\A\+\+/
|
272
|
+
# toggle_tag 'ins', $& # insert
|
273
|
+
when /\A\^\^/
|
274
|
+
toggle_tag 'sup', $& # ^{}
|
275
|
+
when /\A,,/
|
276
|
+
toggle_tag 'sub', $& # _{}
|
277
|
+
when /\A\(R\)/i
|
278
|
+
@out << '®' # (R)
|
279
|
+
when /\A\(C\)/i
|
280
|
+
@out << '©' # (C)
|
281
|
+
when /\A!([^\s])/
|
282
|
+
@out << escape_html($1) # !neco
|
283
|
+
when /./
|
284
|
+
@out << escape_html($&) # ordinal char
|
285
|
+
end
|
286
|
+
return $'
|
287
|
+
end
|
288
|
+
|
289
|
+
def parse_table_row(str)
|
290
|
+
@out << '<tr>'
|
291
|
+
#str.scan(/\s*\|\|(=)?\s*((\[\[.*?\]\]|\{\{.*?\}\}|[^|~]|~.)*)(?=\||$)/) do
|
292
|
+
str.scan(/\s*\|\|(=)?(\s*)(.*?)(?==?\|\||$)/) do
|
293
|
+
if !$3.empty? || !$'.empty?
|
294
|
+
tag = $1 ? 'th' : 'td'
|
295
|
+
le = $2.size
|
296
|
+
txt = $3
|
297
|
+
style =''
|
298
|
+
if txt =~ /\S(\s*)$/
|
299
|
+
ri = $1.size
|
300
|
+
# style = " style='text-align:left'" if le == 0
|
301
|
+
style = " style='text-align:right'" if ri == 0 && le >= 1
|
302
|
+
style = " style='text-align:center'" if le >= 2 && ri >= 2
|
303
|
+
#print "le#{le} ri#{ri} st:#{style}\n"
|
304
|
+
end
|
305
|
+
@out << ('<' + tag + style + '>' )
|
306
|
+
parse_inline(txt.strip) if txt
|
307
|
+
end_tag while @stack.last != 'table'
|
308
|
+
@out << ('</' + tag + '>')
|
309
|
+
end
|
310
|
+
end
|
311
|
+
@out << '</tr>'
|
312
|
+
end
|
313
|
+
|
314
|
+
def make_nowikiblock(input)
|
315
|
+
input.gsub(/^ (?=\}\}\})/, '')
|
316
|
+
end
|
317
|
+
|
318
|
+
def ulol?(x); x == 'ul' || x == 'ol'; end
|
319
|
+
|
320
|
+
def parse_block(str)
|
321
|
+
until str.empty?
|
322
|
+
case str
|
323
|
+
|
324
|
+
# pre {{{ ... }}}
|
325
|
+
when /\A\{\{\{\r?\n(.*?)\r?\n\}\}\}/m
|
326
|
+
end_paragraph
|
327
|
+
nowikiblock = make_nowikiblock($1)
|
328
|
+
@out << '<pre>' << escape_html(nowikiblock) << '</pre>'
|
329
|
+
|
330
|
+
# horizontal rule
|
331
|
+
when /\A\s*-{4,}\s*$/
|
332
|
+
end_paragraph
|
333
|
+
@out << '<hr/>'
|
334
|
+
|
335
|
+
# heading == Wiki Ruless ==
|
336
|
+
when /\A\s*(={1,6})\s*(.*?)\s*=*\s*$(\r?\n)?/
|
337
|
+
end_paragraph
|
338
|
+
level = $1.size
|
339
|
+
@out << make_headline(level, $2)
|
340
|
+
|
341
|
+
# table row
|
342
|
+
when /\A[ \t]*\|\|.*$(\r?\n)?/
|
343
|
+
if !@stack.include?('table')
|
344
|
+
end_paragraph
|
345
|
+
start_tag('table')
|
346
|
+
end
|
347
|
+
parse_table_row($&)
|
348
|
+
|
349
|
+
# empty line
|
350
|
+
when /\A\s*$(\r?\n)?/
|
351
|
+
end_paragraph
|
352
|
+
|
353
|
+
# li
|
354
|
+
when /\A(\s*([*#]+)\s*(.*?))$(\r?\n)?/
|
355
|
+
line, bullet, item = $1, $2, $3
|
356
|
+
tag = (bullet[0,1] == '*' ? 'ul' : 'ol')
|
357
|
+
if bullet[0,1] == '#' || bullet.size != 2 || @stack.find {|x| ulol?(x) }
|
358
|
+
count = @stack.select { |x| ulol?(x) }.size
|
359
|
+
|
360
|
+
while !@stack.empty? && count > bullet.size
|
361
|
+
count -= 1 if ulol?(@stack.last)
|
362
|
+
end_tag
|
363
|
+
end
|
364
|
+
|
365
|
+
end_tag while !@stack.empty? && @stack.last != 'li'
|
366
|
+
|
367
|
+
if @stack.last == 'li' && count == bullet.size
|
368
|
+
end_tag
|
369
|
+
if @stack.last != tag
|
370
|
+
end_tag
|
371
|
+
count -= 1
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
while count < bullet.size
|
376
|
+
start_tag tag
|
377
|
+
count += 1
|
378
|
+
start_tag 'li' if count < bullet.size
|
379
|
+
end
|
380
|
+
|
381
|
+
@p = true
|
382
|
+
start_tag('li')
|
383
|
+
parse_inline(item)
|
384
|
+
else
|
385
|
+
start_paragraph
|
386
|
+
parse_inline(line)
|
387
|
+
end
|
388
|
+
|
389
|
+
# ordinary line
|
390
|
+
when /\A([ \t]*\S+.*?)$(\r?\n)?/
|
391
|
+
start_paragraph
|
392
|
+
parse_inline($1)
|
393
|
+
else
|
394
|
+
raise "Parse error at #{str[0,30].inspect}"
|
395
|
+
end
|
396
|
+
str = $'
|
397
|
+
end
|
398
|
+
end_paragraph
|
399
|
+
@out
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
data/test/parser_test.rb
ADDED
@@ -0,0 +1,665 @@
|
|
1
|
+
require 'trac_wiki'
|
2
|
+
|
3
|
+
class Bacon::Context
|
4
|
+
def tc(html, wiki, options = {})
|
5
|
+
TracWiki.render(wiki, options).should.equal html
|
6
|
+
end
|
7
|
+
|
8
|
+
def tce(html, wiki)
|
9
|
+
tc(html, wiki, :extensions => true)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe TracWiki::Parser do
|
14
|
+
it 'should parse bold' do
|
15
|
+
# Bold can be used inside paragraphs
|
16
|
+
tc "<p>This <strong>is</strong> bold</p>", "This **is** bold"
|
17
|
+
tc "<p>This <strong>is</strong> bold and <strong>bold</strong>ish</p>", "This **is** bold and **bold**ish"
|
18
|
+
|
19
|
+
# Bold can be used inside list items
|
20
|
+
tc "<ul><li>This is <strong>bold</strong></li></ul>", "* This is **bold**"
|
21
|
+
|
22
|
+
# Bold can be used inside table cells
|
23
|
+
tc("<table><tr><td>This is <strong>bold</strong></td></tr></table>",
|
24
|
+
"||This is **bold**||")
|
25
|
+
|
26
|
+
# Links can appear inside bold text:
|
27
|
+
tc("<p>A bold link: <strong><a href=\"http://example.org/\">http://example.org/</a> nice! </strong></p>",
|
28
|
+
"A bold link: **http://example.org/ nice! **")
|
29
|
+
|
30
|
+
# Bold will end at the end of paragraph
|
31
|
+
tc "<p>This <strong>is bold</strong></p>", "This **is bold"
|
32
|
+
|
33
|
+
# Bold will end at the end of list items
|
34
|
+
tc("<ul><li>Item <strong>bold</strong></li><li>Item normal</li></ul>",
|
35
|
+
"* Item **bold\n* Item normal")
|
36
|
+
|
37
|
+
# Bold will end at the end of table cells
|
38
|
+
tc("<table><tr><td>Item <strong>bold</strong></td><td>Another <strong>bold</strong></td></tr></table>",
|
39
|
+
"||Item **bold||Another **bold||")
|
40
|
+
|
41
|
+
# Bold should not cross paragraphs
|
42
|
+
tc("<p>This <strong>is</strong></p><p>bold<strong> maybe</strong></p>",
|
43
|
+
"This **is\n\nbold** maybe")
|
44
|
+
|
45
|
+
# Bold should be able to cross lines
|
46
|
+
tc "<p>This <strong>is bold</strong></p>", "This **is\nbold**"
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should parse italic' do
|
50
|
+
# Italic can be used inside paragraphs
|
51
|
+
tc("<p>This <em>is</em> italic</p>",
|
52
|
+
"This ''is'' italic")
|
53
|
+
tc("<p>This <em>is</em> italic and <em>italic</em>ish</p>",
|
54
|
+
"This ''is'' italic and ''italic''ish")
|
55
|
+
|
56
|
+
# Italic can be used inside list items
|
57
|
+
tc "<ul><li>This is <em>italic</em></li></ul>", "* This is ''italic''"
|
58
|
+
|
59
|
+
# Italic can be used inside table cells
|
60
|
+
tc("<table><tr><td>This is <em>italic</em></td></tr></table>",
|
61
|
+
"||This is ''italic''||")
|
62
|
+
|
63
|
+
# Links can appear inside italic text:
|
64
|
+
tc("<p>A italic link: <em><a href=\"http://example.org/\">http://example.org/</a> nice! </em></p>",
|
65
|
+
"A italic link: ''http://example.org/ nice! ''")
|
66
|
+
|
67
|
+
# Italic will end at the end of paragraph
|
68
|
+
tc "<p>This <em>is italic</em></p>", "This ''is italic"
|
69
|
+
|
70
|
+
# Italic will end at the end of list items
|
71
|
+
tc("<ul><li>Item <em>italic</em></li><li>Item normal</li></ul>",
|
72
|
+
"* Item ''italic\n* Item normal")
|
73
|
+
|
74
|
+
# Italic will end at the end of table cells
|
75
|
+
tc("<table><tr><td>Item <em>italic</em></td><td>Another <em>italic</em></td></tr></table>",
|
76
|
+
"||Item ''italic||Another ''italic")
|
77
|
+
|
78
|
+
# Italic should not cross paragraphs
|
79
|
+
tc("<p>This <em>is</em></p><p>italic<em> maybe</em></p>",
|
80
|
+
"This ''is\n\nitalic'' maybe")
|
81
|
+
|
82
|
+
# Italic should be able to cross lines
|
83
|
+
tc "<p>This <em>is italic</em></p>", "This ''is\nitalic''"
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should parse bold italics' do
|
87
|
+
# By example
|
88
|
+
tc "<p><strong><em>bold italics</em></strong></p>", "**''bold italics''**"
|
89
|
+
|
90
|
+
# By example
|
91
|
+
tc "<p><em><strong>bold italics</strong></em></p>", "''**bold italics**''"
|
92
|
+
|
93
|
+
# By example
|
94
|
+
tc "<p><em>This is <strong>also</strong> good.</em></p>", "''This is **also** good.''"
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should parse headings' do
|
98
|
+
# Only three differed sized levels of heading are required.
|
99
|
+
tc "<h1>Heading 1</h1>", "= Heading 1 ="
|
100
|
+
tc "<h2>Heading 2</h2>", "== Heading 2 =="
|
101
|
+
tc "<h3>Heading 3</h3>", "=== Heading 3 ==="
|
102
|
+
# WARNING: Optional feature, not specified in
|
103
|
+
tc "<h4>Heading 4</h4>", "==== Heading 4 ===="
|
104
|
+
tc "<h5>Heading 5</h5>", "===== Heading 5 ====="
|
105
|
+
tc "<h6>Heading 6</h6>", "====== Heading 6 ======"
|
106
|
+
|
107
|
+
# Closing (right-side) equal signs are optional
|
108
|
+
tc "<h1>Heading 1</h1>", "=Heading 1"
|
109
|
+
tc "<h2>Heading 2</h2>", "== Heading 2"
|
110
|
+
tc "<h3>Heading 3</h3>", " === Heading 3"
|
111
|
+
|
112
|
+
# Closing (right-side) equal signs don't need to be balanced and don't impact the kind of heading generated
|
113
|
+
tc "<h1>Heading 1</h1>", "=Heading 1 ==="
|
114
|
+
tc "<h2>Heading 2</h2>", "== Heading 2 ="
|
115
|
+
tc "<h3>Heading 3</h3>", " === Heading 3 ==========="
|
116
|
+
|
117
|
+
# Whitespace is allowed before the left-side equal signs.
|
118
|
+
tc "<h1>Heading 1</h1>", " \t= Heading 1 ="
|
119
|
+
tc "<h2>Heading 2</h2>", " \t== Heading 2 =="
|
120
|
+
|
121
|
+
# Only white-space characters are permitted after the closing equal signs.
|
122
|
+
tc "<h1>Heading 1</h1>", " = Heading 1 = "
|
123
|
+
tc "<h2>Heading 2</h2>", " == Heading 2 == \t "
|
124
|
+
|
125
|
+
# WARNING: !! XXX doesn't specify if text after closing equal signs
|
126
|
+
# !!becomes part of the heading or invalidates the entire heading.
|
127
|
+
# tc "<p> == Heading 2 == foo</p>", " == Heading 2 == foo"
|
128
|
+
tc "<h2>Heading 2 == foo</h2>", " == Heading 2 == foo"
|
129
|
+
|
130
|
+
# Line must start with equal sign
|
131
|
+
tc "<p>foo = Heading 1 =</p>", "foo = Heading 1 ="
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should parse links' do
|
135
|
+
# Links
|
136
|
+
tc "<p><a href=\"link\">link</a></p>", "[[link]]"
|
137
|
+
|
138
|
+
# Links can appear in paragraphs (i.e. inline item)
|
139
|
+
tc "<p>Hello, <a href=\"world\">world</a></p>", "Hello, [[world]]"
|
140
|
+
|
141
|
+
# Named links
|
142
|
+
tc "<p><a href=\"MyBigPage\">Go to my page</a></p>", "[[MyBigPage|Go to my page]]"
|
143
|
+
|
144
|
+
# URLs
|
145
|
+
tc "<p><a href=\"http://www.example.org/\">http://www.example.org/</a></p>", "[[http://www.example.org/]]"
|
146
|
+
|
147
|
+
# Single punctuation characters at the end of URLs
|
148
|
+
# should not be considered a part of the URL.
|
149
|
+
[',','.','?','!',':',';','\'','"'].each do |punct|
|
150
|
+
esc_punct = CGI::escapeHTML(punct)
|
151
|
+
tc "<p><a href=\"http://www.example.org/\">http://www.example.org/</a>#{esc_punct}</p>", "http://www.example.org/#{punct}"
|
152
|
+
end
|
153
|
+
# Nameds URLs (by example)
|
154
|
+
tc("<p><a href=\"http://www.example.org/\">Visit the Example website</a></p>",
|
155
|
+
"[[http://www.example.org/|Visit the Example website]]")
|
156
|
+
|
157
|
+
# WRNING: Parsing markup within a link is optional
|
158
|
+
tc "<p><a href=\"Weird+Stuff\"><strong>Weird</strong> <em>Stuff</em></a></p>", "[[Weird Stuff|**Weird** ''Stuff'']]"
|
159
|
+
tc("<p><a href=\"http://example.org/\"><img src=\"image.jpg\"/></a></p>", "[[http://example.org/|{{image.jpg}}]]")
|
160
|
+
|
161
|
+
# Inside bold
|
162
|
+
tc "<p><strong><a href=\"link\">link</a></strong></p>", "**[[link]]**"
|
163
|
+
|
164
|
+
# Whitespace inside [[ ]] should be ignored
|
165
|
+
tc("<p><a href=\"link\">link</a></p>", "[[ link ]]")
|
166
|
+
tc("<p><a href=\"link+me\">link me</a></p>", "[[ link me ]]")
|
167
|
+
tc("<p><a href=\"http://dot.com/\">dot.com</a></p>", "[[ http://dot.com/ \t| \t dot.com ]]")
|
168
|
+
tc("<p><a href=\"http://dot.com/\">dot.com</a></p>", "[[ http://dot.com/ | dot.com ]]")
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should parse freestanding urls' do
|
172
|
+
# Free-standing URL's should be turned into links
|
173
|
+
tc "<p><a href=\"http://www.example.org/\">http://www.example.org/</a></p>", "http://www.example.org/"
|
174
|
+
|
175
|
+
# URL ending in .
|
176
|
+
tc "<p>Text <a href=\"http://example.org\">http://example.org</a>. other text</p>", "Text http://example.org. other text"
|
177
|
+
|
178
|
+
# URL ending in ),
|
179
|
+
tc "<p>Text (<a href=\"http://example.org\">http://example.org</a>), other text</p>", "Text (http://example.org), other text"
|
180
|
+
|
181
|
+
# URL ending in ).
|
182
|
+
tc "<p>Text (<a href=\"http://example.org\">http://example.org</a>). other text</p>", "Text (http://example.org). other text"
|
183
|
+
|
184
|
+
# URL ending in ).
|
185
|
+
tc "<p>Text (<a href=\"http://example.org\">http://example.org</a>).</p>", "Text (http://example.org)."
|
186
|
+
|
187
|
+
# URL ending in )
|
188
|
+
tc "<p>Text (<a href=\"http://example.org\">http://example.org</a>)</p>", "Text (http://example.org)"
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should parse paragraphs' do
|
192
|
+
# One or more blank lines end paragraphs.
|
193
|
+
tc "<p>This is my text.</p><p>This is more text.</p>", "This is\nmy text.\n\nThis is\nmore text."
|
194
|
+
tc "<p>This is my text.</p><p>This is more text.</p>", "This is\nmy text.\n\n\nThis is\nmore text."
|
195
|
+
tc "<p>This is my text.</p><p>This is more text.</p>", "This is\nmy text.\n\n\n\nThis is\nmore text."
|
196
|
+
|
197
|
+
# A list end paragraphs too.
|
198
|
+
tc "<p>Hello</p><ul><li>Item</li></ul>", "Hello\n* Item\n"
|
199
|
+
|
200
|
+
# A table end paragraphs too.
|
201
|
+
tc "<p>Hello</p><table><tr><td>Cell</td></tr></table>", "Hello\n||Cell||"
|
202
|
+
|
203
|
+
# A nowiki end paragraphs too.
|
204
|
+
tc "<p>Hello</p><pre>nowiki</pre>", "Hello\n{{{\nnowiki\n}}}\n"
|
205
|
+
|
206
|
+
# WARNING: A heading ends a paragraph (not specced)
|
207
|
+
tc "<p>Hello</p><h1>Heading</h1>", "Hello\n= Heading =\n"
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should parse linebreaks' do
|
211
|
+
# \\ (wiki-style) for line breaks.
|
212
|
+
tc "<p>This is the first line,<br/>and this is the second.</p>", "This is the first line,\\\\and this is the second."
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should parse unordered_lists' do
|
216
|
+
# List items begin with a * at the beginning of a line.
|
217
|
+
# An item ends at the next *
|
218
|
+
tc "<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>", "* Item 1\n *Item 2\n *\t\tItem 3\n"
|
219
|
+
|
220
|
+
# Whitespace is optional before and after the *.
|
221
|
+
tc("<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>",
|
222
|
+
" * Item 1\n*Item 2\n \t*\t\tItem 3\n")
|
223
|
+
|
224
|
+
# A space is required if if the list element starts with bold text.
|
225
|
+
tc("<ul><li><ul><li><ul><li>Item 1</li></ul></li></ul></li></ul>", "***Item 1")
|
226
|
+
tc("<ul><li><strong>Item 1</strong></li></ul>", "* **Item 1")
|
227
|
+
|
228
|
+
# An item ends at blank line
|
229
|
+
tc("<ul><li>Item</li></ul><p>Par</p>", "* Item\n\nPar\n")
|
230
|
+
|
231
|
+
# An item ends at a heading
|
232
|
+
tc("<ul><li>Item</li></ul><h1>Heading</h1>", "* Item\n= Heading =\n")
|
233
|
+
|
234
|
+
# An item ends at a table
|
235
|
+
tc("<ul><li>Item</li></ul><table><tr><td>Cell</td></tr></table>", "* Item\n||Cell||\n")
|
236
|
+
|
237
|
+
# An item ends at a nowiki block
|
238
|
+
tc("<ul><li>Item</li></ul><pre>Code</pre>", "* Item\n{{{\nCode\n}}}\n")
|
239
|
+
|
240
|
+
# An item can span multiple lines
|
241
|
+
tc("<ul><li>The quick brown fox jumps over lazy dog.</li><li>Humpty Dumpty sat on a wall.</li></ul>",
|
242
|
+
"* The quick\nbrown fox\n\tjumps over\nlazy dog.\n*Humpty Dumpty\nsat\t\non a wall.")
|
243
|
+
|
244
|
+
# An item can contain line breaks
|
245
|
+
tc("<ul><li>The quick brown<br/>fox jumps over lazy dog.</li></ul>",
|
246
|
+
"* The quick brown\\\\fox jumps over lazy dog.")
|
247
|
+
|
248
|
+
# Nested
|
249
|
+
tc "<ul><li>Item 1<ul><li>Item 2</li></ul></li><li>Item 3</li></ul>", "* Item 1\n **Item 2\n *\t\tItem 3\n"
|
250
|
+
|
251
|
+
# Nested up to 5 levels
|
252
|
+
tc("<ul><li>Item 1<ul><li>Item 2<ul><li>Item 3<ul><li>Item 4<ul><li>Item 5</li></ul></li></ul></li></ul></li></ul></li></ul>",
|
253
|
+
"*Item 1\n**Item 2\n***Item 3\n****Item 4\n*****Item 5\n")
|
254
|
+
|
255
|
+
# ** immediatly following a list element will be treated as a nested unordered element.
|
256
|
+
tc("<ul><li>Hello, World!<ul><li>Not bold</li></ul></li></ul>",
|
257
|
+
"*Hello,\nWorld!\n**Not bold\n")
|
258
|
+
|
259
|
+
# ** immediatly following a list element will be treated as a nested unordered element.
|
260
|
+
tc("<ol><li>Hello, World!<ul><li>Not bold</li></ul></li></ol>",
|
261
|
+
"#Hello,\nWorld!\n**Not bold\n")
|
262
|
+
|
263
|
+
# [...] otherwise it will be treated as the beginning of bold text.
|
264
|
+
tc("<ul><li>Hello, World!</li></ul><p><strong>Not bold</strong></p>",
|
265
|
+
"*Hello,\nWorld!\n\n**Not bold\n")
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'should parse ordered lists' do
|
269
|
+
# List items begin with a * at the beginning of a line.
|
270
|
+
# An item ends at the next *
|
271
|
+
tc "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol>", "# Item 1\n #Item 2\n #\t\tItem 3\n"
|
272
|
+
|
273
|
+
# Whitespace is optional before and after the #.
|
274
|
+
tc("<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol>",
|
275
|
+
" # Item 1\n#Item 2\n \t#\t\tItem 3\n")
|
276
|
+
|
277
|
+
# A space is required if if the list element starts with bold text.
|
278
|
+
tc("<ol><li><ol><li><ol><li>Item 1</li></ol></li></ol></li></ol>", "###Item 1")
|
279
|
+
tc("<ol><li><strong>Item 1</strong></li></ol>", "# **Item 1")
|
280
|
+
|
281
|
+
# An item ends at blank line
|
282
|
+
tc("<ol><li>Item</li></ol><p>Par</p>", "# Item\n\nPar\n")
|
283
|
+
|
284
|
+
# An item ends at a heading
|
285
|
+
tc("<ol><li>Item</li></ol><h1>Heading</h1>", "# Item\n= Heading =\n")
|
286
|
+
|
287
|
+
# An item ends at a table
|
288
|
+
tc("<ol><li>Item</li></ol><table><tr><td>Cell</td></tr></table>", "# Item\n||Cell||\n")
|
289
|
+
|
290
|
+
# An item ends at a nowiki block
|
291
|
+
tc("<ol><li>Item</li></ol><pre>Code</pre>", "# Item\n{{{\nCode\n}}}\n")
|
292
|
+
|
293
|
+
# An item can span multiple lines
|
294
|
+
tc("<ol><li>The quick brown fox jumps over lazy dog.</li><li>Humpty Dumpty sat on a wall.</li></ol>",
|
295
|
+
"# The quick\nbrown fox\n\tjumps over\nlazy dog.\n#Humpty Dumpty\nsat\t\non a wall.")
|
296
|
+
|
297
|
+
# An item can contain line breaks
|
298
|
+
tc("<ol><li>The quick brown<br/>fox jumps over lazy dog.</li></ol>",
|
299
|
+
"# The quick brown\\\\fox jumps over lazy dog.")
|
300
|
+
|
301
|
+
# Nested
|
302
|
+
tc "<ol><li>Item 1<ol><li>Item 2</li></ol></li><li>Item 3</li></ol>", "# Item 1\n ##Item 2\n #\t\tItem 3\n"
|
303
|
+
|
304
|
+
# Nested up to 5 levels
|
305
|
+
tc("<ol><li>Item 1<ol><li>Item 2<ol><li>Item 3<ol><li>Item 4<ol><li>Item 5</li></ol></li></ol></li></ol></li></ol></li></ol>",
|
306
|
+
"#Item 1\n##Item 2\n###Item 3\n####Item 4\n#####Item 5\n")
|
307
|
+
|
308
|
+
# The two-bullet rule only applies to **.
|
309
|
+
tc("<ol><li><ol><li>Item</li></ol></li></ol>", "##Item")
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'should parse ordered lists #2' do
|
313
|
+
tc "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol>", "# Item 1\n #Item 2\n #\t\tItem 3\n"
|
314
|
+
# Nested
|
315
|
+
tc "<ol><li>Item 1<ol><li>Item 2</li></ol></li><li>Item 3</li></ol>", "# Item 1\n ##Item 2\n #\t\tItem 3\n"
|
316
|
+
# Multiline
|
317
|
+
tc "<ol><li>Item 1 on multiple lines</li></ol>", "# Item 1\non multiple lines"
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should parse ambiguious mixed lists' do
|
321
|
+
# ol following ul
|
322
|
+
tc("<ul><li>uitem</li></ul><ol><li>oitem</li></ol>", "*uitem\n#oitem\n")
|
323
|
+
|
324
|
+
# ul following ol
|
325
|
+
tc("<ol><li>uitem</li></ol><ul><li>oitem</li></ul>", "#uitem\n*oitem\n")
|
326
|
+
|
327
|
+
# 2ol following ul
|
328
|
+
tc("<ul><li>uitem<ol><li>oitem</li></ol></li></ul>", "*uitem\n##oitem\n")
|
329
|
+
|
330
|
+
# 2ul following ol
|
331
|
+
tc("<ol><li>uitem<ul><li>oitem</li></ul></li></ol>", "#uitem\n**oitem\n")
|
332
|
+
|
333
|
+
# 3ol following 3ul
|
334
|
+
tc("<ul><li><ul><li><ul><li>uitem</li></ul><ol><li>oitem</li></ol></li></ul></li></ul>", "***uitem\n###oitem\n")
|
335
|
+
|
336
|
+
# 2ul following 2ol
|
337
|
+
tc("<ol><li><ol><li>uitem</li></ol><ul><li>oitem</li></ul></li></ol>", "##uitem\n**oitem\n")
|
338
|
+
|
339
|
+
# ol following 2ol
|
340
|
+
tc("<ol><li><ol><li>oitem1</li></ol></li><li>oitem2</li></ol>", "##oitem1\n#oitem2\n")
|
341
|
+
# ul following 2ol
|
342
|
+
tc("<ol><li><ol><li>oitem1</li></ol></li></ol><ul><li>oitem2</li></ul>", "##oitem1\n*oitem2\n")
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'should parse ambiguious italics and url' do
|
346
|
+
# Uncommon URL schemes should not be parsed as URLs
|
347
|
+
tc("<p>This is what can go wrong:<em>this should be an italic text</em>.</p>",
|
348
|
+
"This is what can go wrong:''this should be an italic text''.")
|
349
|
+
|
350
|
+
# A link inside italic text
|
351
|
+
tc("<p>How about <em>a link, like <a href=\"http://example.org\">http://example.org</a>, in italic</em> text?</p>",
|
352
|
+
"How about ''a link, like http://example.org, in italic'' text?")
|
353
|
+
|
354
|
+
# Another test
|
355
|
+
tc("<p>Formatted fruits, for example:<em>apples</em>, oranges, <strong>pears</strong> ...</p>",
|
356
|
+
"Formatted fruits, for example:''apples'', oranges, **pears** ...")
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'should parse ambiguious bold and lists' do
|
360
|
+
tc "<p><strong> bold text </strong></p>", "** bold text **"
|
361
|
+
tc "<p> <strong> bold text </strong></p>", " ** bold text **"
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'should parse nowiki' do
|
365
|
+
# ... works as block
|
366
|
+
tc "<pre>Hello</pre>", "{{{\nHello\n}}}\n"
|
367
|
+
|
368
|
+
# ... works inline
|
369
|
+
tc "<p>Hello <tt>world</tt>.</p>", "Hello {{{world}}}."
|
370
|
+
tc "<p><tt>Hello</tt> <tt>world</tt>.</p>", "{{{Hello}}} {{{world}}}."
|
371
|
+
|
372
|
+
# No wiki markup is interpreted inbetween
|
373
|
+
tc "<pre>**Hello**</pre>", "{{{\n**Hello**\n}}}\n"
|
374
|
+
|
375
|
+
# Leading whitespaces are not permitted
|
376
|
+
tc("<p> {{{ Hello }}}</p>", " {{{\nHello\n}}}")
|
377
|
+
tc("<p>{{{ Hello }}}</p>", "{{{\nHello\n }}}")
|
378
|
+
|
379
|
+
# Assumed: Should preserve whitespace
|
380
|
+
tc("<pre> \t Hello, \t \n \t World \t </pre>",
|
381
|
+
"{{{\n \t Hello, \t \n \t World \t \n}}}\n")
|
382
|
+
|
383
|
+
# In preformatted blocks ... one leading space is removed
|
384
|
+
tc("<pre>nowikiblock\n}}}</pre>", "{{{\nnowikiblock\n }}}\n}}}\n")
|
385
|
+
|
386
|
+
# In inline nowiki, any trailing closing brace is included in the span
|
387
|
+
tc("<p>this is <tt>nowiki}</tt></p>", "this is {{{nowiki}}}}")
|
388
|
+
tc("<p>this is <tt>nowiki}}</tt></p>", "this is {{{nowiki}}}}}")
|
389
|
+
tc("<p>this is <tt>nowiki}}}</tt></p>", "this is {{{nowiki}}}}}}")
|
390
|
+
tc("<p>this is <tt>nowiki}}}}</tt></p>", "this is {{{nowiki}}}}}}}")
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'should escape html' do
|
394
|
+
# Special HTML chars should be escaped
|
395
|
+
tc("<p><b>not bold</b></p>", "<b>not bold</b>")
|
396
|
+
|
397
|
+
# Image tags should be escape
|
398
|
+
tc("<p><img src=\"image.jpg\" alt=\""tag"\"/></p>", "{{image.jpg|\"tag\"}}")
|
399
|
+
|
400
|
+
# Malicious links should not be converted.
|
401
|
+
tc("<p><a href=\"javascript%3Aalert%28%22Boo%21%22%29\">Click</a></p>", "[[javascript:alert(\"Boo!\")|Click]]")
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'should support character escape' do
|
405
|
+
tc "<p>** Not Bold **</p>", "!** Not Bold !**"
|
406
|
+
tc "<p>// Not Italic //</p>", "!// Not Italic !//"
|
407
|
+
tc "<p>* Not Bullet</p>", "!* Not Bullet"
|
408
|
+
# Following char is not a blank (space or line feed)
|
409
|
+
tc "<p>Hello ~ world</p>", "Hello ~ world\n"
|
410
|
+
tc "<p>Hello ~ world</p>", "Hello ~\nworld\n"
|
411
|
+
# Not escaping inside URLs
|
412
|
+
tc "<p><a href=\"http://example.org/~user/\">http://example.org/~user/</a></p>", "http://example.org/~user/"
|
413
|
+
|
414
|
+
# Escaping links
|
415
|
+
tc "<p>http://www.example.org/</p>", "~http://www.example.org/"
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'should parse horizontal rule' do
|
419
|
+
# Four hyphens make a horizontal rule
|
420
|
+
tc "<hr/>", "----"
|
421
|
+
|
422
|
+
# Whitespace around them is allowed
|
423
|
+
tc "<hr/>", " ----"
|
424
|
+
tc "<hr/>", "---- "
|
425
|
+
tc "<hr/>", " ---- "
|
426
|
+
tc "<hr/>", " \t ---- \t "
|
427
|
+
|
428
|
+
# Nothing else than hyphens and whitespace is "allowed"
|
429
|
+
tc "<p>foo ----</p>", "foo ----\n"
|
430
|
+
tc "<p>---- foo</p>", "---- foo\n"
|
431
|
+
|
432
|
+
# [...] no whitespace is allowed between them
|
433
|
+
tc "<p> -- -- </p>", " -- -- "
|
434
|
+
tc "<p> -- -- </p>", " --\t-- "
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'should parse table' do
|
438
|
+
tc "<table><tr><td>Hello, World!</td></tr></table>", "||Hello, World!||"
|
439
|
+
tc "<table><tr><td style='text-align:right'>Hello, Right World!</td></tr></table>", "|| Hello, Right World!||"
|
440
|
+
tc "<table><tr><th style='text-align:right'>Hello, Right World!</th></tr></table>", "||= Hello, Right World!=||"
|
441
|
+
tc "<table><tr><td style='text-align:center'>Hello, Centered World!</td></tr></table>", "|| Hello, Centered World! ||"
|
442
|
+
tc "<table><tr><th style='text-align:center'>Hello, Centered World!</th></tr></table>", "||= Hello, Centered World! =||"
|
443
|
+
# Multiple columns
|
444
|
+
tc "<table><tr><td>c1</td><td>c2</td><td>c3</td></tr></table>", "||c1||c2||c3||"
|
445
|
+
# Multiple rows
|
446
|
+
tc "<table><tr><td>c11</td><td>c12</td></tr><tr><td>c21</td><td>c22</td></tr></table>", "||c11||c12||\n||c21||c22||\n"
|
447
|
+
# End pipe is optional
|
448
|
+
tc "<table><tr><td>c1</td><td>c2</td><td>c3</td></tr></table>", "||c1||c2||c3"
|
449
|
+
# Empty cells
|
450
|
+
tc "<table><tr><td>c1</td><td></td><td>c2</td></tr></table>", "||c1|| ||c2"
|
451
|
+
# Escaping cell separator
|
452
|
+
tc "<table><tr><td>c1|c2</td><td>c3</td></tr></table>", "||c1!|c2||c3"
|
453
|
+
# Escape in last cell + empty cell
|
454
|
+
tc "<table><tr><td>c1</td><td>c2|</td></tr></table>", "||c1||c2!|"
|
455
|
+
tc "<table><tr><td>c1</td><td>c2|</td></tr></table>", "||c1||c2!|"
|
456
|
+
tc "<table><tr><td>c1</td><td>c2|</td><td></td></tr></table>", "||c1||c2| || ||"
|
457
|
+
# Equal sign after pipe make a header
|
458
|
+
tc "<table><tr><th>Header</th></tr></table>", "||=Header=||"
|
459
|
+
|
460
|
+
tc "<table><tr><td>c1</td><td><a href=\"Link\">Link text</a></td><td><img src=\"Image\" alt=\"Image text\"/></td></tr></table>", "||c1||[[Link|Link text]]||{{Image|Image text}}||"
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'should parse following table' do
|
464
|
+
# table followed by heading
|
465
|
+
tc("<table><tr><td>table</td></tr></table><h1>heading</h1>", "||table||\n=heading=\n")
|
466
|
+
tc("<table><tr><td>table</td></tr></table><h1>heading</h1>", "||table||\n\n=heading=\n")
|
467
|
+
# table followed by paragraph
|
468
|
+
tc("<table><tr><td>table</td></tr></table><p>par</p>", "||table||\npar\n")
|
469
|
+
tc("<table><tr><td>table</td></tr></table><p>par</p>", "||table||\n\npar\n")
|
470
|
+
# table followed by unordered list
|
471
|
+
tc("<table><tr><td>table</td></tr></table><ul><li>item</li></ul>", "||table||\n*item\n")
|
472
|
+
tc("<table><tr><td>table</td></tr></table><ul><li>item</li></ul>", "||table||\n\n*item\n")
|
473
|
+
# table followed by ordered list
|
474
|
+
tc("<table><tr><td>table</td></tr></table><ol><li>item</li></ol>", "||table||\n#item\n")
|
475
|
+
tc("<table><tr><td>table</td></tr></table><ol><li>item</li></ol>", "||table||\n\n#item\n")
|
476
|
+
# table followed by horizontal rule
|
477
|
+
tc("<table><tr><td>table</td></tr></table><hr/>", "||table||\n----\n")
|
478
|
+
tc("<table><tr><td>table</td></tr></table><hr/>", "||table||\n\n----\n")
|
479
|
+
# table followed by nowiki block
|
480
|
+
tc("<table><tr><td>table</td></tr></table><pre>pre</pre>", "||table||\n{{{\npre\n}}}\n")
|
481
|
+
tc("<table><tr><td>table</td></tr></table><pre>pre</pre>", "||table||\n\n{{{\npre\n}}}\n")
|
482
|
+
# table followed by table
|
483
|
+
tc("<table><tr><td>table</td></tr><tr><td>table</td></tr></table>", "||table||\n||table||\n")
|
484
|
+
tc("<table><tr><td>table</td></tr></table><table><tr><td>table</td></tr></table>", "||table||\n\n||table||\n")
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'should parse following heading' do
|
488
|
+
# heading
|
489
|
+
tc("<h1>heading1</h1><h1>heading2</h1>", "=heading1=\n=heading2\n")
|
490
|
+
tc("<h1>heading1</h1><h1>heading2</h1>", "=heading1=\n\n=heading2\n")
|
491
|
+
# paragraph
|
492
|
+
tc("<h1>heading</h1><p>par</p>", "=heading=\npar\n")
|
493
|
+
tc("<h1>heading</h1><p>par</p>", "=heading=\n\npar\n")
|
494
|
+
# unordered list
|
495
|
+
tc("<h1>heading</h1><ul><li>item</li></ul>", "=heading=\n*item\n")
|
496
|
+
tc("<h1>heading</h1><ul><li>item</li></ul>", "=heading=\n\n*item\n")
|
497
|
+
# ordered list
|
498
|
+
tc("<h1>heading</h1><ol><li>item</li></ol>", "=heading=\n#item\n")
|
499
|
+
tc("<h1>heading</h1><ol><li>item</li></ol>", "=heading=\n\n#item\n")
|
500
|
+
# horizontal rule
|
501
|
+
tc("<h1>heading</h1><hr/>", "=heading=\n----\n")
|
502
|
+
tc("<h1>heading</h1><hr/>", "=heading=\n\n----\n")
|
503
|
+
# nowiki block
|
504
|
+
tc("<h1>heading</h1><pre>nowiki</pre>", "=heading=\n{{{\nnowiki\n}}}\n")
|
505
|
+
tc("<h1>heading</h1><pre>nowiki</pre>", "=heading=\n\n{{{\nnowiki\n}}}\n")
|
506
|
+
# table
|
507
|
+
tc("<h1>heading</h1><table><tr><td>table</td></tr></table>", "=heading=\n||table||\n")
|
508
|
+
tc("<h1>heading</h1><table><tr><td>table</td></tr></table>", "=heading=\n\n||table||\n")
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'should parse following paragraph' do
|
512
|
+
# heading
|
513
|
+
tc("<p>par</p><h1>heading</h1>", "par\n=heading=")
|
514
|
+
tc("<p>par</p><h1>heading</h1>", "par\n\n=heading=")
|
515
|
+
# paragraph
|
516
|
+
tc("<p>par par</p>", "par\npar\n")
|
517
|
+
tc("<p>par</p><p>par</p>", "par\n\npar\n")
|
518
|
+
# unordered
|
519
|
+
tc("<p>par</p><ul><li>item</li></ul>", "par\n*item")
|
520
|
+
tc("<p>par</p><ul><li>item</li></ul>", "par\n\n*item")
|
521
|
+
# ordered
|
522
|
+
tc("<p>par</p><ol><li>item</li></ol>", "par\n#item\n")
|
523
|
+
tc("<p>par</p><ol><li>item</li></ol>", "par\n\n#item\n")
|
524
|
+
# horizontal
|
525
|
+
tc("<p>par</p><hr/>", "par\n----\n")
|
526
|
+
tc("<p>par</p><hr/>", "par\n\n----\n")
|
527
|
+
# nowiki
|
528
|
+
tc("<p>par</p><pre>nowiki</pre>", "par\n{{{\nnowiki\n}}}\n")
|
529
|
+
tc("<p>par</p><pre>nowiki</pre>", "par\n\n{{{\nnowiki\n}}}\n")
|
530
|
+
# table
|
531
|
+
tc("<p>par</p><table><tr><td>table</td></tr></table>", "par\n||table||\n")
|
532
|
+
tc("<p>par</p><table><tr><td>table</td></tr></table>", "par\n\n||table||\n")
|
533
|
+
end
|
534
|
+
|
535
|
+
it 'should parse following unordered list' do
|
536
|
+
# heading
|
537
|
+
tc("<ul><li>item</li></ul><h1>heading</h1>", "*item\n=heading=")
|
538
|
+
tc("<ul><li>item</li></ul><h1>heading</h1>", "*item\n\n=heading=")
|
539
|
+
# paragraph
|
540
|
+
tc("<ul><li>item par</li></ul>", "*item\npar\n") # items may span multiple lines
|
541
|
+
tc("<ul><li>item</li></ul><p>par</p>", "*item\n\npar\n")
|
542
|
+
# unordered
|
543
|
+
tc("<ul><li>item</li><li>item</li></ul>", "*item\n*item\n")
|
544
|
+
tc("<ul><li>item</li></ul><ul><li>item</li></ul>", "*item\n\n*item\n")
|
545
|
+
# ordered
|
546
|
+
tc("<ul><li>item</li></ul><ol><li>item</li></ol>", "*item\n#item\n")
|
547
|
+
tc("<ul><li>item</li></ul><ol><li>item</li></ol>", "*item\n\n#item\n")
|
548
|
+
# horizontal rule
|
549
|
+
tc("<ul><li>item</li></ul><hr/>", "*item\n----\n")
|
550
|
+
tc("<ul><li>item</li></ul><hr/>", "*item\n\n----\n")
|
551
|
+
# nowiki
|
552
|
+
tc("<ul><li>item</li></ul><pre>nowiki</pre>", "*item\n{{{\nnowiki\n}}}\n")
|
553
|
+
tc("<ul><li>item</li></ul><pre>nowiki</pre>", "*item\n\n{{{\nnowiki\n}}}\n")
|
554
|
+
# table
|
555
|
+
tc("<ul><li>item</li></ul><table><tr><td>table</td></tr></table>", "*item\n||table||\n")
|
556
|
+
tc("<ul><li>item</li></ul><table><tr><td>table</td></tr></table>", "*item\n\n||table||\n")
|
557
|
+
end
|
558
|
+
|
559
|
+
it 'should parse following ordered list' do
|
560
|
+
# heading
|
561
|
+
tc("<ol><li>item</li></ol><h1>heading</h1>", "#item\n=heading=")
|
562
|
+
tc("<ol><li>item</li></ol><h1>heading</h1>", "#item\n\n=heading=")
|
563
|
+
# paragraph
|
564
|
+
tc("<ol><li>item par</li></ol>", "#item\npar\n") # items may span multiple lines
|
565
|
+
tc("<ol><li>item</li></ol><p>par</p>", "#item\n\npar\n")
|
566
|
+
# unordered
|
567
|
+
tc("<ol><li>item</li></ol><ul><li>item</li></ul>", "#item\n*item\n")
|
568
|
+
tc("<ol><li>item</li></ol><ul><li>item</li></ul>", "#item\n\n*item\n")
|
569
|
+
# ordered
|
570
|
+
tc("<ol><li>item</li><li>item</li></ol>", "#item\n#item\n")
|
571
|
+
tc("<ol><li>item</li></ol><ol><li>item</li></ol>", "#item\n\n#item\n")
|
572
|
+
# horizontal role
|
573
|
+
tc("<ol><li>item</li></ol><hr/>", "#item\n----\n")
|
574
|
+
tc("<ol><li>item</li></ol><hr/>", "#item\n\n----\n")
|
575
|
+
# nowiki
|
576
|
+
tc("<ol><li>item</li></ol><pre>nowiki</pre>", "#item\n{{{\nnowiki\n}}}\n")
|
577
|
+
tc("<ol><li>item</li></ol><pre>nowiki</pre>", "#item\n\n{{{\nnowiki\n}}}\n")
|
578
|
+
# table
|
579
|
+
tc("<ol><li>item</li></ol><table><tr><td>table</td></tr></table>", "#item\n||table||\n")
|
580
|
+
tc("<ol><li>item</li></ol><table><tr><td>table</td></tr></table>", "#item\n\n||table||\n")
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'should parse following horizontal rule' do
|
584
|
+
# heading
|
585
|
+
tc("<hr/><h1>heading</h1>", "----\n=heading=")
|
586
|
+
tc("<hr/><h1>heading</h1>", "----\n\n=heading=")
|
587
|
+
# paragraph
|
588
|
+
tc("<hr/><p>par</p>", "----\npar\n")
|
589
|
+
tc("<hr/><p>par</p>", "----\n\npar\n")
|
590
|
+
# unordered
|
591
|
+
tc("<hr/><ul><li>item</li></ul>", "----\n*item")
|
592
|
+
tc("<hr/><ul><li>item</li></ul>", "----\n*item")
|
593
|
+
# ordered
|
594
|
+
tc("<hr/><ol><li>item</li></ol>", "----\n#item")
|
595
|
+
tc("<hr/><ol><li>item</li></ol>", "----\n#item")
|
596
|
+
# horizontal
|
597
|
+
tc("<hr/><hr/>", "----\n----\n")
|
598
|
+
tc("<hr/><hr/>", "----\n\n----\n")
|
599
|
+
# nowiki
|
600
|
+
tc("<hr/><pre>nowiki</pre>", "----\n{{{\nnowiki\n}}}\n")
|
601
|
+
tc("<hr/><pre>nowiki</pre>", "----\n\n{{{\nnowiki\n}}}\n")
|
602
|
+
# table
|
603
|
+
tc("<hr/><table><tr><td>table</td></tr></table>", "----\n||table||\n")
|
604
|
+
tc("<hr/><table><tr><td>table</td></tr></table>", "----\n\n||table||\n")
|
605
|
+
end
|
606
|
+
|
607
|
+
it 'should parse following nowiki block' do
|
608
|
+
# heading
|
609
|
+
tc("<pre>nowiki</pre><h1>heading</h1>", "{{{\nnowiki\n}}}\n=heading=")
|
610
|
+
tc("<pre>nowiki</pre><h1>heading</h1>", "{{{\nnowiki\n}}}\n\n=heading=")
|
611
|
+
# paragraph
|
612
|
+
tc("<pre>nowiki</pre><p>par</p>", "{{{\nnowiki\n}}}\npar")
|
613
|
+
tc("<pre>nowiki</pre><p>par</p>", "{{{\nnowiki\n}}}\n\npar")
|
614
|
+
# unordered
|
615
|
+
tc("<pre>nowiki</pre><ul><li>item</li></ul>", "{{{\nnowiki\n}}}\n*item\n")
|
616
|
+
tc("<pre>nowiki</pre><ul><li>item</li></ul>", "{{{\nnowiki\n}}}\n\n*item\n")
|
617
|
+
# ordered
|
618
|
+
tc("<pre>nowiki</pre><ol><li>item</li></ol>", "{{{\nnowiki\n}}}\n#item\n")
|
619
|
+
tc("<pre>nowiki</pre><ol><li>item</li></ol>", "{{{\nnowiki\n}}}\n\n#item\n")
|
620
|
+
# horizontal
|
621
|
+
tc("<pre>nowiki</pre><hr/>", "{{{\nnowiki\n}}}\n----\n")
|
622
|
+
tc("<pre>nowiki</pre><hr/>", "{{{\nnowiki\n}}}\n\n----\n")
|
623
|
+
# nowiki
|
624
|
+
tc("<pre>nowiki</pre><pre>nowiki</pre>", "{{{\nnowiki\n}}}\n{{{\nnowiki\n}}}\n")
|
625
|
+
tc("<pre>nowiki</pre><pre>nowiki</pre>", "{{{\nnowiki\n}}}\n\n{{{\nnowiki\n}}}\n")
|
626
|
+
# table
|
627
|
+
tc("<pre>nowiki</pre><table><tr><td>table</td></tr></table>", "{{{\nnowiki\n}}}\n||table||\n")
|
628
|
+
tc("<pre>nowiki</pre><table><tr><td>table</td></tr></table>", "{{{\nnowiki\n}}}\n\n||table||\n")
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'should parse image' do
|
632
|
+
tc("<p><img src=\"image.jpg\"/></p>", "{{image.jpg}}")
|
633
|
+
tc("<p><img src=\"image.jpg\" alt=\"tag\"/></p>", "{{image.jpg|tag}}")
|
634
|
+
tc("<p><img src=\"http://example.org/image.jpg\"/></p>", "{{http://example.org/image.jpg}}")
|
635
|
+
end
|
636
|
+
|
637
|
+
it 'should parse bold combo' do
|
638
|
+
tc("<p><strong>bold and</strong></p><table><tr><td>table</td></tr></table><p>end<strong></strong></p>",
|
639
|
+
"**bold and\n||table||\nend**")
|
640
|
+
end
|
641
|
+
|
642
|
+
it 'should support extensions' do
|
643
|
+
tce("<p>This is <u>underlined</u></p>",
|
644
|
+
"This is __underlined__")
|
645
|
+
|
646
|
+
tce("<p>This is <del>deleted</del></p>",
|
647
|
+
"This is ~~deleted~~")
|
648
|
+
|
649
|
+
tce("<p>This is <sup>super</sup></p>",
|
650
|
+
"This is ^^super^^")
|
651
|
+
|
652
|
+
tce("<p>This is <sub>sub</sub></p>",
|
653
|
+
"This is ,,sub,,")
|
654
|
+
|
655
|
+
tce("<p>®</p>", "(R)")
|
656
|
+
tce("<p>®</p>", "(r)")
|
657
|
+
tce("<p>©</p>", "(C)")
|
658
|
+
tce("<p>©</p>", "(c)")
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'should support no_escape' do
|
662
|
+
tc("<p><a href=\"a%2Fb%2Fc\">a/b/c</a></p>", "[[a/b/c]]")
|
663
|
+
tc("<p><a href=\"a/b/c\">a/b/c</a></p>", "[[a/b/c]]", :no_escape => true)
|
664
|
+
end
|
665
|
+
end
|
data/trac-wiki.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/trac_wiki/version'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'trac-wiki'
|
6
|
+
s.version = TracWiki::VERSION
|
7
|
+
s.date = Date.today.to_s
|
8
|
+
|
9
|
+
s.authors = ['Vitas Stradal']
|
10
|
+
s.email = ['vitas@matfyz.cz' ]
|
11
|
+
s.summary = 'Trac Wiki markup language'
|
12
|
+
s.description = 'TracWiki markup language render (http://trac.edgewall.org/wiki/WikiFormatting ).'
|
13
|
+
s.extra_rdoc_files = %w(README)
|
14
|
+
s.rubyforge_project = s.name
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = %w(lib)
|
19
|
+
|
20
|
+
s.homepage = 'http://github.com/vitstradal/trac-wiki'
|
21
|
+
|
22
|
+
s.add_development_dependency('bacon')
|
23
|
+
s.add_development_dependency('rake')
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trac-wiki
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Vitas Stradal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bacon
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: TracWiki markup language render (http://trac.edgewall.org/wiki/WikiFormatting
|
47
|
+
).
|
48
|
+
email:
|
49
|
+
- vitas@matfyz.cz
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- README
|
54
|
+
files:
|
55
|
+
- Gemfile
|
56
|
+
- README
|
57
|
+
- Rakefile
|
58
|
+
- lib/trac_wiki.rb
|
59
|
+
- lib/trac_wiki/parser.rb
|
60
|
+
- lib/trac_wiki/version.rb
|
61
|
+
- test/parser_test.rb
|
62
|
+
- trac-wiki.gemspec
|
63
|
+
homepage: http://github.com/vitstradal/trac-wiki
|
64
|
+
licenses: []
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project: trac-wiki
|
83
|
+
rubygems_version: 1.8.23
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Trac Wiki markup language
|
87
|
+
test_files: []
|
88
|
+
has_rdoc:
|