├── .gitignore ├── Gemfile ├── lib ├── creole │ ├── version.rb │ └── parser.rb └── creole.rb ├── CHANGES ├── .travis.yml ├── Rakefile ├── creole.gemspec ├── README.creole └── test └── parser_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | gemspec 3 | 4 | -------------------------------------------------------------------------------- /lib/creole/version.rb: -------------------------------------------------------------------------------- 1 | module Creole 2 | VERSION = '0.5.0' 3 | end 4 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.5.0 2 | 3 | * Remove methods make_*_anchor 4 | * Add method make_headline 5 | * Parse tags inside link text 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.3 5 | - 2.0.0 6 | - 2.1.0 7 | - ruby-head 8 | - jruby-18mode 9 | - jruby-19mode 10 | - rbx 11 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :test 2 | 3 | desc 'Run tests with bacon' 4 | task :test => FileList['test/*_test.rb'] do |t| 5 | sh "bacon -q -Ilib:test #{t.prerequisites.join(' ')}" 6 | end 7 | -------------------------------------------------------------------------------- /lib/creole.rb: -------------------------------------------------------------------------------- 1 | require 'creole/parser' 2 | require 'creole/version' 3 | 4 | module Creole 5 | # Convert the argument in Creole format to HTML and return the 6 | # result. Example: 7 | # 8 | # Creole.creolize("**Hello //World//**") 9 | # #=> "

Hello World

" 10 | # 11 | # This is an alias for calling Creole#parse: 12 | # Creole.new(text).to_html 13 | def self.creolize(text, options = {}) 14 | Parser.new(text, options).to_html 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /creole.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.dirname(__FILE__) + '/lib/creole/version' 3 | require 'date' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'creole' 7 | s.version = Creole::VERSION 8 | s.date = Date.today.to_s 9 | 10 | s.authors = ['Lars Christensen', 'Daniel Mendler'] 11 | s.email = ['larsch@belunktum.dk', 'mail@daniel-mendler.de'] 12 | s.summary = 'Lightweight markup language' 13 | s.description = 'Creole is a lightweight markup language (http://wikicreole.org/).' 14 | s.extra_rdoc_files = %w(README.creole) 15 | s.rubyforge_project = s.name 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 19 | s.require_paths = %w(lib) 20 | 21 | s.homepage = 'http://github.com/minad/creole' 22 | s.license = 'Ruby' 23 | 24 | s.add_development_dependency('bacon') 25 | s.add_development_dependency('rake') 26 | end 27 | -------------------------------------------------------------------------------- /README.creole: -------------------------------------------------------------------------------- 1 | = Creole 2 | 3 | Creole is a Creole-to-HTML converter for Creole, the lightweight markup 4 | language (http://wikicreole.org/). Github uses this converter to render *.creole files. 5 | 6 | Project page on github: 7 | 8 | * http://github.com/minad/creole 9 | 10 | Travis-CI: 11 | 12 | * https://travis-ci.org/minad/creole 13 | 14 | RDOC: 15 | 16 | * http://rdoc.info/projects/minad/creole 17 | 18 | == INSTALLATION 19 | 20 | {{{ 21 | gem install creole 22 | }}} 23 | 24 | == SYNOPSIS 25 | 26 | {{{ 27 | require 'creole' 28 | html = Creole.creolize('== Creole text') 29 | }}} 30 | 31 | == BUGS 32 | 33 | If you found a bug, please report it at the Creole project's tracker 34 | on GitHub: 35 | 36 | http://github.com/minad/creole/issues 37 | 38 | == AUTHORS 39 | 40 | * Lars Christensen (larsch) 41 | * Daniel Mendler (minad) 42 | 43 | == LICENSE 44 | 45 | Creole is Copyright (c) 2008 - 2013 Lars Christensen, Daniel Mendler. It is free software, and 46 | may be redistributed under the terms specified in the README file of 47 | the Ruby distribution. 48 | 49 | -------------------------------------------------------------------------------- /lib/creole/parser.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'uri' 3 | 4 | # :main: Creole 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 Creole.creolize. 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 Creole parser class. Call CreoleParser#parse to parse Creole 31 | # 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 Creole 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 CreoleParser 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 and 66 | # tags. 67 | # 68 | # Example: 69 | # 70 | # parser = CreoleParser.new("**Hello //World//**", :extensions => true) 71 | # parser.to_html 72 | # #=> "

Hello World

" 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 << '' 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 | '' << escape_html(alt) << '' 191 | else 192 | '' 193 | end 194 | end 195 | 196 | def make_headline(level, text) 197 | "" << escape_html(text) << "" 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 << '' << escape_html($2) << '' 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 << '' 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 << '' 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\{\{\{(.*?\}*)\}\}\}/ 248 | @out << '' << escape_html($1) << '' 249 | when /\A\{\{\s*(.*?)\s*(\|\s*(.*?)\s*)?\}\}/ 250 | if uri = make_image_link($1) 251 | @out << make_image(uri, $3) 252 | else 253 | @out << escape_html($&) 254 | end 255 | when /\A([[:alpha:]]|[[:digit:]])+/ 256 | @out << $& 257 | when /\A\s+/ 258 | @out << ' ' if @out[-1] != ?\s 259 | when /\A\*\*/ 260 | toggle_tag 'strong', $& 261 | when /\A\/\// 262 | toggle_tag 'em', $& 263 | when /\A\\\\/ 264 | @out << '
' 265 | else 266 | if @extensions 267 | case str 268 | when /\A__/ 269 | toggle_tag 'u', $& 270 | when /\A\-\-/ 271 | toggle_tag 'del', $& 272 | when /\A\+\+/ 273 | toggle_tag 'ins', $& 274 | when /\A\^\^/ 275 | toggle_tag 'sup', $& 276 | when /\A\~\~/ 277 | toggle_tag 'sub', $& 278 | when /\A\(R\)/i 279 | @out << '®' 280 | when /\A\(C\)/i 281 | @out << '©' 282 | when /\A~([^\s])/ 283 | @out << escape_html($1) 284 | when /./ 285 | @out << escape_html($&) 286 | end 287 | else 288 | case str 289 | when /\A~([^\s])/ 290 | @out << escape_html($1) 291 | when /./ 292 | @out << escape_html($&) 293 | end 294 | end 295 | end 296 | return $' 297 | end 298 | 299 | def parse_table_row(str) 300 | @out << '' 301 | str.scan(/\s*\|(=)?\s*((\[\[.*?\]\]|\{\{.*?\}\}|[^|~]|~.)*)(?=\||$)/) do 302 | if !$2.empty? || !$'.empty? 303 | @out << ($1 ? '' : '') 304 | parse_inline($2) if $2 305 | end_tag while @stack.last != 'table' 306 | @out << ($1 ? '' : '') 307 | end 308 | end 309 | @out << '' 310 | end 311 | 312 | def make_nowikiblock(input) 313 | input.gsub(/^ (?=\}\}\})/, '') 314 | end 315 | 316 | def ulol?(x); x == 'ul' || x == 'ol'; end 317 | 318 | def parse_block(str) 319 | until str.empty? 320 | case str 321 | when /\A\{\{\{\r?\n(.*?)\r?\n\}\}\}/m 322 | end_paragraph 323 | nowikiblock = make_nowikiblock($1) 324 | @out << '
' << escape_html(nowikiblock) << '
' 325 | when /\A\s*-{4,}\s*$/ 326 | end_paragraph 327 | @out << '
' 328 | when /\A\s*(={1,6})\s*(.*?)\s*=*\s*$(\r?\n)?/ 329 | end_paragraph 330 | level = $1.size 331 | @out << make_headline(level, $2) 332 | when /\A[ \t]*\|.*$(\r?\n)?/ 333 | if !@stack.include?('table') 334 | end_paragraph 335 | start_tag('table') 336 | end 337 | parse_table_row($&) 338 | when /\A\s*$(\r?\n)?/ 339 | end_paragraph 340 | when /\A(\s*([*#]+)\s*(.*?))$(\r?\n)?/ 341 | line, bullet, item = $1, $2, $3 342 | tag = (bullet[0,1] == '*' ? 'ul' : 'ol') 343 | if bullet[0,1] == '#' || bullet.size != 2 || @stack.find {|x| ulol?(x) } 344 | count = @stack.select { |x| ulol?(x) }.size 345 | 346 | while !@stack.empty? && count > bullet.size 347 | count -= 1 if ulol?(@stack.last) 348 | end_tag 349 | end 350 | 351 | end_tag while !@stack.empty? && @stack.last != 'li' 352 | 353 | if @stack.last == 'li' && count == bullet.size 354 | end_tag 355 | if @stack.last != tag 356 | end_tag 357 | count -= 1 358 | end 359 | end 360 | 361 | while count < bullet.size 362 | start_tag tag 363 | count += 1 364 | start_tag 'li' if count < bullet.size 365 | end 366 | 367 | @p = true 368 | start_tag('li') 369 | parse_inline(item) 370 | else 371 | start_paragraph 372 | parse_inline(line) 373 | end 374 | when /\A([ \t]*\S+.*?)$(\r?\n)?/ 375 | start_paragraph 376 | parse_inline($1) 377 | else 378 | raise "Parse error at #{str[0,30].inspect}" 379 | end 380 | #p [$&, $'] 381 | str = $' 382 | end 383 | end_paragraph 384 | @out 385 | end 386 | end 387 | end 388 | -------------------------------------------------------------------------------- /test/parser_test.rb: -------------------------------------------------------------------------------- 1 | require 'creole' 2 | 3 | class Bacon::Context 4 | def tc(html, creole, options = {}) 5 | Creole.creolize(creole, options).should.equal html 6 | end 7 | 8 | def tce(html, creole) 9 | tc(html, creole, :extensions => true) 10 | end 11 | end 12 | 13 | describe Creole::Parser do 14 | it 'should parse bold' do 15 | # Creole1.0: Bold can be used inside paragraphs 16 | tc "

This is bold

", "This **is** bold" 17 | tc "

This is bold and boldish

", "This **is** bold and **bold**ish" 18 | 19 | # Creole1.0: Bold can be used inside list items 20 | tc "", "* This is **bold**" 21 | 22 | # Creole1.0: Bold can be used inside table cells 23 | tc("
This is bold
", 24 | "|This is **bold**|") 25 | 26 | # Creole1.0: Links can appear inside bold text: 27 | tc("

A bold link: http://wikicreole.org/ nice!

", 28 | "A bold link: **http://wikicreole.org/ nice!**") 29 | 30 | # Creole1.0: Bold will end at the end of paragraph 31 | tc "

This is bold

", "This **is bold" 32 | 33 | # Creole1.0: Bold will end at the end of list items 34 | tc("", 35 | "* Item **bold\n* Item normal") 36 | 37 | # Creole1.0: Bold will end at the end of table cells 38 | tc("
Item boldAnother bold
", 39 | "|Item **bold|Another **bold") 40 | 41 | # Creole1.0: Bold should not cross paragraphs 42 | tc("

This is

bold maybe

", 43 | "This **is\n\nbold** maybe") 44 | 45 | # Creole1.0-Implied: Bold should be able to cross lines 46 | tc "

This is bold

", "This **is\nbold**" 47 | end 48 | 49 | it 'should parse italic' do 50 | # Creole1.0: Italic can be used inside paragraphs 51 | tc("

This is italic

", 52 | "This //is// italic") 53 | tc("

This is italic and italicish

", 54 | "This //is// italic and //italic//ish") 55 | 56 | # Creole1.0: Italic can be used inside list items 57 | tc "", "* This is //italic//" 58 | 59 | # Creole1.0: Italic can be used inside table cells 60 | tc("
This is italic
", 61 | "|This is //italic//|") 62 | 63 | # Creole1.0: Links can appear inside italic text: 64 | tc("

A italic link: http://wikicreole.org/ nice!

", 65 | "A italic link: //http://wikicreole.org/ nice!//") 66 | 67 | # Creole1.0: Italic will end at the end of paragraph 68 | tc "

This is italic

", "This //is italic" 69 | 70 | # Creole1.0: Italic will end at the end of list items 71 | tc("", 72 | "* Item //italic\n* Item normal") 73 | 74 | # Creole1.0: Italic will end at the end of table cells 75 | tc("
Item italicAnother italic
", 76 | "|Item //italic|Another //italic") 77 | 78 | # Creole1.0: Italic should not cross paragraphs 79 | tc("

This is

italic maybe

", 80 | "This //is\n\nitalic// maybe") 81 | 82 | # Creole1.0-Implied: Italic should be able to cross lines 83 | tc "

This is italic

", "This //is\nitalic//" 84 | end 85 | 86 | it 'should parse bold italics' do 87 | # Creole1.0: By example 88 | tc "

bold italics

", "**//bold italics//**" 89 | 90 | # Creole1.0: By example 91 | tc "

bold italics

", "//**bold italics**//" 92 | 93 | # Creole1.0: By example 94 | tc "

This is also good.

", "//This is **also** good.//" 95 | end 96 | 97 | it 'should parse headings' do 98 | # Creole1.0: Only three differed sized levels of heading are required. 99 | tc "

Heading 1

", "= Heading 1 =" 100 | tc "

Heading 2

", "== Heading 2 ==" 101 | tc "

Heading 3

", "=== Heading 3 ===" 102 | # WARNING: Optional feature, not specified in creole 1.0 103 | tc "

Heading 4

", "==== Heading 4 ====" 104 | tc "
Heading 5
", "===== Heading 5 =====" 105 | tc "
Heading 6
", "====== Heading 6 ======" 106 | 107 | # Creole1.0: Closing (right-side) equal signs are optional 108 | tc "

Heading 1

", "=Heading 1" 109 | tc "

Heading 2

", "== Heading 2" 110 | tc "

Heading 3

", " === Heading 3" 111 | 112 | # Creole1.0: Closing (right-side) equal signs don't need to be balanced and don't impact the kind of heading generated 113 | tc "

Heading 1

", "=Heading 1 ===" 114 | tc "

Heading 2

", "== Heading 2 =" 115 | tc "

Heading 3

", " === Heading 3 ===========" 116 | 117 | # Creole1.0: Whitespace is allowed before the left-side equal signs. 118 | tc "

Heading 1

", " \t= Heading 1 =" 119 | tc "

Heading 2

", " \t== Heading 2 ==" 120 | 121 | # Creole1.0: Only white-space characters are permitted after the closing equal signs. 122 | tc "

Heading 1

", " = Heading 1 = " 123 | tc "

Heading 2

", " == Heading 2 == \t " 124 | 125 | # WARNING: !!Creole1.0 doesn't specify if text after closing equal signs 126 | # !!becomes part of the heading or invalidates the entire heading. 127 | # tc "

== Heading 2 == foo

", " == Heading 2 == foo" 128 | tc "

Heading 2 == foo

", " == Heading 2 == foo" 129 | 130 | # Creole1.0-Implied: Line must start with equal sign 131 | tc "

foo = Heading 1 =

", "foo = Heading 1 =" 132 | end 133 | 134 | it 'should parse links' do 135 | # Creole1.0: Links 136 | tc "

link

", "[[link]]" 137 | 138 | # Creole1.0: Links can appear in paragraphs (i.e. inline item) 139 | tc "

Hello, world

", "Hello, [[world]]" 140 | 141 | # Creole1.0: Named links 142 | tc "

Go to my page

", "[[MyBigPage|Go to my page]]" 143 | 144 | # Creole1.0: URLs 145 | tc "

http://www.wikicreole.org/

", "[[http://www.wikicreole.org/]]" 146 | 147 | # Creole1.0: 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 "

http://www.wikicreole.org/#{esc_punct}

", "http://www.wikicreole.org/#{punct}" 152 | end 153 | # Creole1.0: Nameds URLs (by example) 154 | tc("

Visit the WikiCreole website

", 155 | "[[http://www.wikicreole.org/|Visit the WikiCreole website]]") 156 | 157 | # WRNING: Parsing markup within a link is optional 158 | tc "

Weird Stuff

", "[[Weird Stuff|**Weird** //Stuff//]]" 159 | tc("

", "[[http://example.org/|{{image.jpg}}]]") 160 | 161 | # Inside bold 162 | tc "

link

", "**[[link]]**" 163 | 164 | # Whitespace inside [[ ]] should be ignored 165 | tc("

link

", "[[ link ]]") 166 | tc("

link me

", "[[ link me ]]") 167 | tc("

dot.com

", "[[ http://dot.com/ \t| \t dot.com ]]") 168 | tc("

dot.com

", "[[ http://dot.com/ | dot.com ]]") 169 | end 170 | 171 | it 'should parse freestanding urls' do 172 | # Creole1.0: Free-standing URL's should be turned into links 173 | tc "

http://www.wikicreole.org/

", "http://www.wikicreole.org/" 174 | 175 | # URL ending in . 176 | tc "

Text http://wikicreole.org. other text

", "Text http://wikicreole.org. other text" 177 | 178 | # URL ending in ), 179 | tc "

Text (http://wikicreole.org), other text

", "Text (http://wikicreole.org), other text" 180 | 181 | # URL ending in ). 182 | tc "

Text (http://wikicreole.org). other text

", "Text (http://wikicreole.org). other text" 183 | 184 | # URL ending in ). 185 | tc "

Text (http://wikicreole.org).

", "Text (http://wikicreole.org)." 186 | 187 | # URL ending in ) 188 | tc "

Text (http://wikicreole.org)

", "Text (http://wikicreole.org)" 189 | end 190 | 191 | it 'should parse paragraphs' do 192 | # Creole1.0: One or more blank lines end paragraphs. 193 | tc "

This is my text.

This is more text.

", "This is\nmy text.\n\nThis is\nmore text." 194 | tc "

This is my text.

This is more text.

", "This is\nmy text.\n\n\nThis is\nmore text." 195 | tc "

This is my text.

This is more text.

", "This is\nmy text.\n\n\n\nThis is\nmore text." 196 | 197 | # Creole1.0: A list end paragraphs too. 198 | tc "

Hello

", "Hello\n* Item\n" 199 | 200 | # Creole1.0: A table end paragraphs too. 201 | tc "

Hello

Cell
", "Hello\n|Cell|" 202 | 203 | # Creole1.0: A nowiki end paragraphs too. 204 | tc "

Hello

nowiki
", "Hello\n{{{\nnowiki\n}}}\n" 205 | 206 | # WARNING: A heading ends a paragraph (not specced) 207 | tc "

Hello

Heading

", "Hello\n= Heading =\n" 208 | end 209 | 210 | it 'should parse linebreaks' do 211 | # Creole1.0: \\ (wiki-style) for line breaks. 212 | tc "

This is the first line,
and this is the second.

", "This is the first line,\\\\and this is the second." 213 | end 214 | 215 | it 'should parse unordered_lists' do 216 | # Creole1.0: List items begin with a * at the beginning of a line. 217 | # Creole1.0: An item ends at the next * 218 | tc "", "* Item 1\n *Item 2\n *\t\tItem 3\n" 219 | 220 | # Creole1.0: Whitespace is optional before and after the *. 221 | tc("", 222 | " * Item 1\n*Item 2\n \t*\t\tItem 3\n") 223 | 224 | # Creole1.0: A space is required if if the list element starts with bold text. 225 | tc("", "***Item 1") 226 | tc("", "* **Item 1") 227 | 228 | # Creole1.0: An item ends at blank line 229 | tc("

Par

", "* Item\n\nPar\n") 230 | 231 | # Creole1.0: An item ends at a heading 232 | tc("

Heading

", "* Item\n= Heading =\n") 233 | 234 | # Creole1.0: An item ends at a table 235 | tc("
Cell
", "* Item\n|Cell|\n") 236 | 237 | # Creole1.0: An item ends at a nowiki block 238 | tc("
Code
", "* Item\n{{{\nCode\n}}}\n") 239 | 240 | # Creole1.0: An item can span multiple lines 241 | tc("", 242 | "* The quick\nbrown fox\n\tjumps over\nlazy dog.\n*Humpty Dumpty\nsat\t\non a wall.") 243 | 244 | # Creole1.0: An item can contain line breaks 245 | tc("", 246 | "* The quick brown\\\\fox jumps over lazy dog.") 247 | 248 | # Creole1.0: Nested 249 | tc "", "* Item 1\n **Item 2\n *\t\tItem 3\n" 250 | 251 | # Creole1.0: Nested up to 5 levels 252 | tc("", 253 | "*Item 1\n**Item 2\n***Item 3\n****Item 4\n*****Item 5\n") 254 | 255 | # Creole1.0: ** immediatly following a list element will be treated as a nested unordered element. 256 | tc("", 257 | "*Hello,\nWorld!\n**Not bold\n") 258 | 259 | # Creole1.0: ** immediatly following a list element will be treated as a nested unordered element. 260 | tc("
  1. Hello, World!
", 261 | "#Hello,\nWorld!\n**Not bold\n") 262 | 263 | # Creole1.0: [...] otherwise it will be treated as the beginning of bold text. 264 | tc("

Not bold

", 265 | "*Hello,\nWorld!\n\n**Not bold\n") 266 | end 267 | 268 | it 'should parse ordered lists' do 269 | # Creole1.0: List items begin with a * at the beginning of a line. 270 | # Creole1.0: An item ends at the next * 271 | tc "
  1. Item 1
  2. Item 2
  3. Item 3
", "# Item 1\n #Item 2\n #\t\tItem 3\n" 272 | 273 | # Creole1.0: Whitespace is optional before and after the #. 274 | tc("
  1. Item 1
  2. Item 2
  3. Item 3
", 275 | " # Item 1\n#Item 2\n \t#\t\tItem 3\n") 276 | 277 | # Creole1.0: A space is required if if the list element starts with bold text. 278 | tc("
      1. Item 1
", "###Item 1") 279 | tc("
  1. Item 1
", "# **Item 1") 280 | 281 | # Creole1.0: An item ends at blank line 282 | tc("
  1. Item

Par

", "# Item\n\nPar\n") 283 | 284 | # Creole1.0: An item ends at a heading 285 | tc("
  1. Item

Heading

", "# Item\n= Heading =\n") 286 | 287 | # Creole1.0: An item ends at a table 288 | tc("
  1. Item
Cell
", "# Item\n|Cell|\n") 289 | 290 | # Creole1.0: An item ends at a nowiki block 291 | tc("
  1. Item
Code
", "# Item\n{{{\nCode\n}}}\n") 292 | 293 | # Creole1.0: An item can span multiple lines 294 | tc("
  1. The quick brown fox jumps over lazy dog.
  2. Humpty Dumpty sat on a wall.
", 295 | "# The quick\nbrown fox\n\tjumps over\nlazy dog.\n#Humpty Dumpty\nsat\t\non a wall.") 296 | 297 | # Creole1.0: An item can contain line breaks 298 | tc("
  1. The quick brown
    fox jumps over lazy dog.
", 299 | "# The quick brown\\\\fox jumps over lazy dog.") 300 | 301 | # Creole1.0: Nested 302 | tc "
  1. Item 1
    1. Item 2
  2. Item 3
", "# Item 1\n ##Item 2\n #\t\tItem 3\n" 303 | 304 | # Creole1.0: Nested up to 5 levels 305 | tc("
  1. Item 1
    1. Item 2
      1. Item 3
        1. Item 4
          1. Item 5
", 306 | "#Item 1\n##Item 2\n###Item 3\n####Item 4\n#####Item 5\n") 307 | 308 | # Creole1.0_Infered: The two-bullet rule only applies to **. 309 | tc("
    1. Item
", "##Item") 310 | end 311 | 312 | it 'should parse ordered lists #2' do 313 | tc "
  1. Item 1
  2. Item 2
  3. Item 3
", "# Item 1\n #Item 2\n #\t\tItem 3\n" 314 | # Nested 315 | tc "
  1. Item 1
    1. Item 2
  2. Item 3
", "# Item 1\n ##Item 2\n #\t\tItem 3\n" 316 | # Multiline 317 | tc "
  1. Item 1 on multiple lines
", "# Item 1\non multiple lines" 318 | end 319 | 320 | it 'should parse ambiguious mixed lists' do 321 | # ol following ul 322 | tc("
  1. oitem
", "*uitem\n#oitem\n") 323 | 324 | # ul following ol 325 | tc("
  1. uitem
", "#uitem\n*oitem\n") 326 | 327 | # 2ol following ul 328 | tc("", "*uitem\n##oitem\n") 329 | 330 | # 2ul following ol 331 | tc("
  1. uitem
", "#uitem\n**oitem\n") 332 | 333 | # 3ol following 3ul 334 | tc("", "***uitem\n###oitem\n") 335 | 336 | # 2ul following 2ol 337 | tc("
    1. uitem
", "##uitem\n**oitem\n") 338 | 339 | # ol following 2ol 340 | tc("
    1. oitem1
  1. oitem2
", "##oitem1\n#oitem2\n") 341 | # ul following 2ol 342 | tc("
    1. oitem1
", "##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("

This is what can go wrong:this should be an italic text.

", 348 | "This is what can go wrong://this should be an italic text//.") 349 | 350 | # A link inside italic text 351 | tc("

How about a link, like http://example.org, in italic text?

", 352 | "How about //a link, like http://example.org, in italic// text?") 353 | 354 | # Another test from Creole Wiki 355 | tc("

Formatted fruits, for example:apples, oranges, pears ...

", 356 | "Formatted fruits, for example://apples//, oranges, **pears** ...") 357 | end 358 | 359 | it 'should parse ambiguious bold and lists' do 360 | tc "

bold text

", "** bold text **" 361 | tc "

bold text

", " ** bold text **" 362 | end 363 | 364 | it 'should parse nowiki' do 365 | # ... works as block 366 | tc "
Hello
", "{{{\nHello\n}}}\n" 367 | 368 | # ... works inline 369 | tc "

Hello world.

", "Hello {{{world}}}." 370 | tc "

Hello world.

", "{{{Hello}}} {{{world}}}." 371 | 372 | # Creole1.0: No wiki markup is interpreted inbetween 373 | tc "
**Hello**
", "{{{\n**Hello**\n}}}\n" 374 | 375 | # Creole1.0: Leading whitespaces are not permitted 376 | tc("

{{{ Hello }}}

", " {{{\nHello\n}}}") 377 | tc("

{{{ Hello }}}

", "{{{\nHello\n }}}") 378 | 379 | # Assumed: Should preserve whitespace 380 | tc("
 \t Hello, \t \n \t World \t 
", 381 | "{{{\n \t Hello, \t \n \t World \t \n}}}\n") 382 | 383 | # In preformatted blocks ... one leading space is removed 384 | tc("
nowikiblock\n}}}
", "{{{\nnowikiblock\n }}}\n}}}\n") 385 | 386 | # In inline nowiki, any trailing closing brace is included in the span 387 | tc("

this is nowiki}

", "this is {{{nowiki}}}}") 388 | tc("

this is nowiki}}

", "this is {{{nowiki}}}}}") 389 | tc("

this is nowiki}}}

", "this is {{{nowiki}}}}}}") 390 | tc("

this is nowiki}}}}

", "this is {{{nowiki}}}}}}}") 391 | end 392 | 393 | it 'should escape html' do 394 | # Special HTML chars should be escaped 395 | tc("

<b>not bold</b>

", "not bold") 396 | 397 | # Image tags should be escape 398 | tc("

\""tag"\"/

", "{{image.jpg|\"tag\"}}") 399 | 400 | # Malicious links should not be converted. 401 | tc("

Click

", "[[javascript:alert(\"Boo!\")|Click]]") 402 | end 403 | 404 | it 'should support character escape' do 405 | tc "

** Not Bold **

", "~** Not Bold ~**" 406 | tc "

// Not Italic //

", "~// Not Italic ~//" 407 | tc "

* Not Bullet

", "~* Not Bullet" 408 | # Following char is not a blank (space or line feed) 409 | tc "

Hello ~ world

", "Hello ~ world\n" 410 | tc "

Hello ~ world

", "Hello ~\nworld\n" 411 | # Not escaping inside URLs (Creole1.0 not clear on this) 412 | tc "

http://example.org/~user/

", "http://example.org/~user/" 413 | 414 | # Escaping links 415 | tc "

http://www.wikicreole.org/

", "~http://www.wikicreole.org/" 416 | end 417 | 418 | it 'should parse horizontal rule' do 419 | # Creole: Four hyphens make a horizontal rule 420 | tc "
", "----" 421 | 422 | # Creole1.0: Whitespace around them is allowed 423 | tc "
", " ----" 424 | tc "
", "---- " 425 | tc "
", " ---- " 426 | tc "
", " \t ---- \t " 427 | 428 | # Creole1.0: Nothing else than hyphens and whitespace is "allowed" 429 | tc "

foo ----

", "foo ----\n" 430 | tc "

---- foo

", "---- foo\n" 431 | 432 | # Creole1.0: [...] no whitespace is allowed between them 433 | tc "

-- --

", " -- -- " 434 | tc "

-- --

", " --\t-- " 435 | end 436 | 437 | it 'should parse table' do 438 | tc "
Hello, World!
", "|Hello, World!|" 439 | # Multiple columns 440 | tc "
c1c2c3
", "|c1|c2|c3|" 441 | # Multiple rows 442 | tc "
c11c12
c21c22
", "|c11|c12|\n|c21|c22|\n" 443 | # End pipe is optional 444 | tc "
c1c2c3
", "|c1|c2|c3" 445 | # Empty cells 446 | tc "
c1c3
", "|c1||c3" 447 | # Escaping cell separator 448 | tc "
c1|c2c3
", "|c1~|c2|c3" 449 | # Escape in last cell + empty cell 450 | tc "
c1c2|
", "|c1|c2~|" 451 | tc "
c1c2|
", "|c1|c2~||" 452 | tc "
c1c2|
", "|c1|c2~|||" 453 | # Equal sign after pipe make a header 454 | tc "
Header
", "|=Header|" 455 | 456 | tc "
c1Link text\"Image
", "|c1|[[Link|Link text]]|{{Image|Image text}}|" 457 | end 458 | 459 | it 'should parse following table' do 460 | # table followed by heading 461 | tc("
table

heading

", "|table|\n=heading=\n") 462 | tc("
table

heading

", "|table|\n\n=heading=\n") 463 | # table followed by paragraph 464 | tc("
table

par

", "|table|\npar\n") 465 | tc("
table

par

", "|table|\n\npar\n") 466 | # table followed by unordered list 467 | tc("
table
", "|table|\n*item\n") 468 | tc("
table
", "|table|\n\n*item\n") 469 | # table followed by ordered list 470 | tc("
table
  1. item
", "|table|\n#item\n") 471 | tc("
table
  1. item
", "|table|\n\n#item\n") 472 | # table followed by horizontal rule 473 | tc("
table

", "|table|\n----\n") 474 | tc("
table

", "|table|\n\n----\n") 475 | # table followed by nowiki block 476 | tc("
table
pre
", "|table|\n{{{\npre\n}}}\n") 477 | tc("
table
pre
", "|table|\n\n{{{\npre\n}}}\n") 478 | # table followed by table 479 | tc("
table
table
", "|table|\n|table|\n") 480 | tc("
table
table
", "|table|\n\n|table|\n") 481 | end 482 | 483 | it 'should parse following heading' do 484 | # heading 485 | tc("

heading1

heading2

", "=heading1=\n=heading2\n") 486 | tc("

heading1

heading2

", "=heading1=\n\n=heading2\n") 487 | # paragraph 488 | tc("

heading

par

", "=heading=\npar\n") 489 | tc("

heading

par

", "=heading=\n\npar\n") 490 | # unordered list 491 | tc("

heading

", "=heading=\n*item\n") 492 | tc("

heading

", "=heading=\n\n*item\n") 493 | # ordered list 494 | tc("

heading

  1. item
", "=heading=\n#item\n") 495 | tc("

heading

  1. item
", "=heading=\n\n#item\n") 496 | # horizontal rule 497 | tc("

heading


", "=heading=\n----\n") 498 | tc("

heading


", "=heading=\n\n----\n") 499 | # nowiki block 500 | tc("

heading

nowiki
", "=heading=\n{{{\nnowiki\n}}}\n") 501 | tc("

heading

nowiki
", "=heading=\n\n{{{\nnowiki\n}}}\n") 502 | # table 503 | tc("

heading

table
", "=heading=\n|table|\n") 504 | tc("

heading

table
", "=heading=\n\n|table|\n") 505 | end 506 | 507 | it 'should parse following paragraph' do 508 | # heading 509 | tc("

par

heading

", "par\n=heading=") 510 | tc("

par

heading

", "par\n\n=heading=") 511 | # paragraph 512 | tc("

par par

", "par\npar\n") 513 | tc("

par

par

", "par\n\npar\n") 514 | # unordered 515 | tc("

par

", "par\n*item") 516 | tc("

par

", "par\n\n*item") 517 | # ordered 518 | tc("

par

  1. item
", "par\n#item\n") 519 | tc("

par

  1. item
", "par\n\n#item\n") 520 | # horizontal 521 | tc("

par


", "par\n----\n") 522 | tc("

par


", "par\n\n----\n") 523 | # nowiki 524 | tc("

par

nowiki
", "par\n{{{\nnowiki\n}}}\n") 525 | tc("

par

nowiki
", "par\n\n{{{\nnowiki\n}}}\n") 526 | # table 527 | tc("

par

table
", "par\n|table|\n") 528 | tc("

par

table
", "par\n\n|table|\n") 529 | end 530 | 531 | it 'should parse following unordered list' do 532 | # heading 533 | tc("

heading

", "*item\n=heading=") 534 | tc("

heading

", "*item\n\n=heading=") 535 | # paragraph 536 | tc("", "*item\npar\n") # items may span multiple lines 537 | tc("

par

", "*item\n\npar\n") 538 | # unordered 539 | tc("", "*item\n*item\n") 540 | tc("", "*item\n\n*item\n") 541 | # ordered 542 | tc("
  1. item
", "*item\n#item\n") 543 | tc("
  1. item
", "*item\n\n#item\n") 544 | # horizontal rule 545 | tc("
", "*item\n----\n") 546 | tc("
", "*item\n\n----\n") 547 | # nowiki 548 | tc("
nowiki
", "*item\n{{{\nnowiki\n}}}\n") 549 | tc("
nowiki
", "*item\n\n{{{\nnowiki\n}}}\n") 550 | # table 551 | tc("
table
", "*item\n|table|\n") 552 | tc("
table
", "*item\n\n|table|\n") 553 | end 554 | 555 | it 'should parse following ordered list' do 556 | # heading 557 | tc("
  1. item

heading

", "#item\n=heading=") 558 | tc("
  1. item

heading

", "#item\n\n=heading=") 559 | # paragraph 560 | tc("
  1. item par
", "#item\npar\n") # items may span multiple lines 561 | tc("
  1. item

par

", "#item\n\npar\n") 562 | # unordered 563 | tc("
  1. item
", "#item\n*item\n") 564 | tc("
  1. item
", "#item\n\n*item\n") 565 | # ordered 566 | tc("
  1. item
  2. item
", "#item\n#item\n") 567 | tc("
  1. item
  1. item
", "#item\n\n#item\n") 568 | # horizontal role 569 | tc("
  1. item

", "#item\n----\n") 570 | tc("
  1. item

", "#item\n\n----\n") 571 | # nowiki 572 | tc("
  1. item
nowiki
", "#item\n{{{\nnowiki\n}}}\n") 573 | tc("
  1. item
nowiki
", "#item\n\n{{{\nnowiki\n}}}\n") 574 | # table 575 | tc("
  1. item
table
", "#item\n|table|\n") 576 | tc("
  1. item
table
", "#item\n\n|table|\n") 577 | end 578 | 579 | it 'should parse following horizontal rule' do 580 | # heading 581 | tc("

heading

", "----\n=heading=") 582 | tc("

heading

", "----\n\n=heading=") 583 | # paragraph 584 | tc("

par

", "----\npar\n") 585 | tc("

par

", "----\n\npar\n") 586 | # unordered 587 | tc("
", "----\n*item") 588 | tc("
", "----\n*item") 589 | # ordered 590 | tc("
  1. item
", "----\n#item") 591 | tc("
  1. item
", "----\n#item") 592 | # horizontal 593 | tc("

", "----\n----\n") 594 | tc("

", "----\n\n----\n") 595 | # nowiki 596 | tc("
nowiki
", "----\n{{{\nnowiki\n}}}\n") 597 | tc("
nowiki
", "----\n\n{{{\nnowiki\n}}}\n") 598 | # table 599 | tc("
table
", "----\n|table|\n") 600 | tc("
table
", "----\n\n|table|\n") 601 | end 602 | 603 | it 'should parse following nowiki block' do 604 | # heading 605 | tc("
nowiki

heading

", "{{{\nnowiki\n}}}\n=heading=") 606 | tc("
nowiki

heading

", "{{{\nnowiki\n}}}\n\n=heading=") 607 | # paragraph 608 | tc("
nowiki

par

", "{{{\nnowiki\n}}}\npar") 609 | tc("
nowiki

par

", "{{{\nnowiki\n}}}\n\npar") 610 | # unordered 611 | tc("
nowiki
", "{{{\nnowiki\n}}}\n*item\n") 612 | tc("
nowiki
", "{{{\nnowiki\n}}}\n\n*item\n") 613 | # ordered 614 | tc("
nowiki
  1. item
", "{{{\nnowiki\n}}}\n#item\n") 615 | tc("
nowiki
  1. item
", "{{{\nnowiki\n}}}\n\n#item\n") 616 | # horizontal 617 | tc("
nowiki

", "{{{\nnowiki\n}}}\n----\n") 618 | tc("
nowiki

", "{{{\nnowiki\n}}}\n\n----\n") 619 | # nowiki 620 | tc("
nowiki
nowiki
", "{{{\nnowiki\n}}}\n{{{\nnowiki\n}}}\n") 621 | tc("
nowiki
nowiki
", "{{{\nnowiki\n}}}\n\n{{{\nnowiki\n}}}\n") 622 | # table 623 | tc("
nowiki
table
", "{{{\nnowiki\n}}}\n|table|\n") 624 | tc("
nowiki
table
", "{{{\nnowiki\n}}}\n\n|table|\n") 625 | end 626 | 627 | it 'should parse image' do 628 | tc("

", "{{image.jpg}}") 629 | tc("

\"tag\"/

", "{{image.jpg|tag}}") 630 | tc("

", "{{http://example.org/image.jpg}}") 631 | end 632 | 633 | it 'should parse bold combo' do 634 | tc("

bold and

table

end

", 635 | "**bold and\n|table|\nend**") 636 | end 637 | 638 | it 'should support extensions' do 639 | tc("

This is not __underlined__

", 640 | "This is not __underlined__") 641 | 642 | tce("

This is underlined

", 643 | "This is __underlined__") 644 | 645 | tce("

This is deleted

", 646 | "This is --deleted--") 647 | 648 | tce("

This is inserted

", 649 | "This is ++inserted++") 650 | 651 | tce("

This is super

", 652 | "This is ^^super^^") 653 | 654 | tce("

This is sub

", 655 | "This is ~~sub~~") 656 | 657 | tce("

®

", "(R)") 658 | tce("

®

", "(r)") 659 | tce("

©

", "(C)") 660 | tce("

©

", "(c)") 661 | end 662 | 663 | it 'should support no_escape' do 664 | tc("

a/b/c

", "[[a/b/c]]") 665 | tc("

a/b/c

", "[[a/b/c]]", :no_escape => true) 666 | end 667 | end 668 | --------------------------------------------------------------------------------