trac-wiki 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|