From 0d8c3d2efa2d2f2051f8755dddd11e52ab777936 Mon Sep 17 00:00:00 2001 From: Lars Christensen Date: Sat, 9 Aug 2008 17:20:37 +0200 Subject: [PATCH] Initial version added to git --- .gitignore | 1 + Manifest.txt | 7 + README.txt | 28 ++ Rakefile | 9 + lib/creole.rb | 350 +++++++++++++++++++++++++ test/test_creole.rb | 15 ++ test/testcases.rb | 626 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1036 insertions(+) create mode 100644 .gitignore create mode 100644 Manifest.txt create mode 100644 README.txt create mode 100644 Rakefile create mode 100644 lib/creole.rb create mode 100644 test/test_creole.rb create mode 100644 test/testcases.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e23635 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +doc diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..d03e988 --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,7 @@ +History.txt +Manifest.txt +README.txt +Rakefile +lib/creole.rb +test/test_creole.rb +test/testcases.rb diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..a3be2c5 --- /dev/null +++ b/README.txt @@ -0,0 +1,28 @@ += Creole + +* http://creole.rubyforge.org/ +* http://rubyforge.org/projects/creole/ + +== DESCRIPTION: + +Creole is a Creole-to-HTML converter for Creole, the lightwight markup +language (http://wikicreole.org/). + +== SYNOPSIS: + + gem 'creole' + require 'creole' + html = Creole.creolize( ... ) + +== BUGS: + +If you found a bug, please report it at the Creole project's tracker +on RubyForge: + +http://rubyforge.org/tracker/?group_id=6344 + +== LICENSE: + +RDoc is Copyright (c) 2008 Lars Christensen. It is free software, and +may be redistributed under the terms specified in the README file of +the Ruby distribution. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..5c6b77e --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'hoe' + +$:.unshift 'lib' +require 'creole' + +Hoe.new "creole", Creole::VERSION do |creole| + creole.developer 'Lars Christensen', 'larsch@belunktum.dk' +end + diff --git a/lib/creole.rb b/lib/creole.rb new file mode 100644 index 0000000..6ae1570 --- /dev/null +++ b/lib/creole.rb @@ -0,0 +1,350 @@ +require 'cgi' +require 'uri' + +# :main: Creole + +# The Creole parses and translates Creole formatted text into +# XHTML. Creole is a lightwight markup syntax similar to what many +# WikiWikiWebs use. Example syntax: +# +# = Heading 1 = +# == Heading 2 == +# === Heading 3 === +# **Bold text** +# //Italic text// +# [[Links]] +# |=Table|=Heading| +# |Table |Cells | +# {{image.png}} +# +# The simplest interface is Creole.creolize. The default handling of +# links allow explicit local links using the [[link]] syntax. External +# links will only be allowed if specified using http(s) and ftp(s) +# schemes. If special link handling is needed, such as inter-wiki or +# hierachical local links, you must inherit Creole::CreoleParser and +# override make_link. + +module Creole + + VERSION = "0.1" + + # CreoleParseError is raised when the Creole parser encounters + # something unexpected. This is generally now thrown unless there is + # a bug in the parser. + class CreoleParseError < Exception; end + + # Convert the argument in Creole format to HTML and return the + # result. Example: + # + # Creole.creolize("**Hello //World//**") + # #=> "

Hello World

" + # + # This is an alias for calling CreoleParser#parse: + # CreoleParser.new.parse(creole) + def self.creolize(creole) + CreoleParser.new.parse(creole) + end + + # Main Creole parser class. Call CreoleParser#parse to parse Creole + # formatted text. + # + # This class is not reentrant. A separate instance is needed for + # each thread that needs to convert Creole to HTML. + # + # Inherit this to provide custom handling of links. The overrideable + # methods are: make_link + class CreoleParser + + # Create a new CreoleParser instance. + def initialize + @base = nil + @allowed_schemes = [ 'http', 'https', 'ftp', 'ftps' ] + @uri_scheme_re = @allowed_schemes.join('|') + @link_re = /\b[A-Z][a-z]*([A-Z][a-z]*)+\b/ + end + + # Parse and convert the argument in Creole text to HTML and return + # the result. The resulting HTML does not contain and + # tags. + # + # Example: + # + # parser = CreoleParser.new + # parser.parse("**Hello //World//**") + # #=> "

Hello World

" + def parse(string) + @out = "" + @strong = false + @p = false + @stack = [] + parse_block(string) + return @out + end + + # Escape any characters with special meaning in HTML using HTML + # entities. + private + def escape_html(string) + CGI::escapeHTML(string) + end + + # Escape any characters with special meaning in URLs using URL + # encoding. + private + def escape_url(string) + CGI::escape(string) + end + + private + def toggle_tag(tag, match) + if @stack.include?(tag) + if @stack.last == tag + @stack.pop + @out << '' + else + @out << escape_html(match) + end + else + @stack.push(tag) + @out << '<' << tag << '>' + end + end + + def end_paragraph + while tag = @stack.pop + @out << "" + end + @p = false + end + + def start_paragraph + if not @p + end_paragraph + @out << '

' + @stack.push('p') + @p = true + else + @out << ' ' unless @out[-1,1] == ' ' + end + end + + # Translate an explicit local link to a desired URL that is + # properly URL-escaped. The default behaviour is to convert local + # links directly, escaping any characters that have special + # meaning in URLs. Relative URLs in local links are not handled. + # + # Examples: + # + # make_link("LocalLink") #=> "LocalLink" + # make_link("/Foo/Bar") #=> "%2FFoo%2FBar" + # + # Must ensure that the result is properly URL-escaped. The caller + # will handle HTML escaping as necessary. HTML links will not be + # inserted if the function returns nil. + # + # Example custom behaviour: + # + # make_link("LocalLink") #=> "/LocalLink" + # make_link("Wikipedia:Bread") #=> "http://en.wikipedia.org/wiki/Bread" + private + def make_link(link) #:doc: + escape_url(link) + end + + # Sanatize a direct url (e.g. http://wikipedia.org/). The default + # behaviour returns the original link as-is. + # + # Must ensure that the result is properly URL-escaped. The caller + # will handle HTML escaping as necessary. Links will not be + # converted to HTML links if the function returns link. + # + # Custom versions of this function in inherited classes can + # implement specific link handling behaviour, such as redirection + # to intermediate pages (for example, for notifing the user that + # he is leaving the site). + private + def make_direct_link(url) #:doc: + return url + end + + # Sanatize and prefix image URLs. When images are encountered in + # Creole text, this function is called to obtain the actual URL of + # the image. The default behaviour is to return the image link + # as-is. No image tags are inserted if the function returns nil. + # + # Custom version of the method can be used to sanatize URLs + # (e.g. remove query-parts), inhibit off-site images, or add a + # base URL, for example: + # + # def make_image_link(url) + # URI.join("http://mywiki.org/images/", url) + # end + private + def make_image_link(url) #:doc: + return url + end + + private + def make_explicit_link(link) + begin + uri = URI.parse(link) + if uri.scheme and @allowed_schemes.include?(uri.scheme) + return uri.to_s + end + rescue URI::InvalidURIError + end + return make_link(link) + end + + def parse_inline(str) + until str.empty? + case str + when /\A\r?\n/ + return + when /\A(\~)?((https?|ftps?):\/\/\S+?)(?=([,.?!:;"'])?(\s|$))/ + if $1 + @out << escape_html($2) + else + if uri = make_direct_link($2) + @out << '' << escape_html($2) << '' + else + @out << escape_html($&) + end + end + when /\A\[\[\s*([^|]*?)\s*(\|\s*(.*?))?\s*\]\]/m + link = $1 + if uri = make_explicit_link(link) + @out << '' << escape_html($3 || link) << '' + else + @out << escape_html($&) + end + when @link_re + if uri = make_explicit_link($&) + @out << '' << escape_html($&) << '' + else + @out << escape_html($&) + end + when /\A[^\/\\*\s{}~]+/ + @out << escape_html($&) + when /\A\{\{\{(.*)\}\}\}/ + @out << '' << escape_html($1) << '' + when /\A\{\{\s*(.*?)\s*(\|\s*(.*?)\s*)?\}\}/ # (|\s*(.*?)\s*)?*\}\}/ + if uri = make_image_link($1) + if $3 + @out << '' << escape_html($3) << '' + else + @out << '' + end + else + @out << escape_html($&) + end + when /\A~([^\s])/ + @out << escape_html($1) + when /\A[ \t]+/ + @out << ' ' unless @out[-1,1] == ' ' + when /\A\*\*/ + toggle_tag 'strong', $& + when /\A\/\// + toggle_tag 'em', $& + when /\A\\\\/ + @out << '
' + when /./ + @out << escape_html($&) + else + raise CreoleParseError, "Parse error at #{str[0,30].inspect}" + end + # p [$&, $'] + str = $' + end + end + + def parse_table_row(str) + @out << '' + str.scan(/\s*\|(=)?\s*(([^|~]|~.)*)(?=\||$)/) { + unless $2.empty? and $'.empty? + @out << ($1 ? '' : '') + parse_inline($2) if $2 + until @stack.last == 'table' + @out << '' + end + @out << ($1 ? '' : '') + end + } + @out << '' + end + + def make_nowikiblock(input) + input.gsub(/^ (?=\}\}\})/, '') + end + + def ulol(x); x=='ul'||x=='ol'; end + + def parse_block(str) + until str.empty? + case str + when /\A\{\{\{\r?\n(.*?)\r?\n\}\}\}/m + end_paragraph + nowikiblock = make_nowikiblock($1) + @out << '

' << escape_html(nowikiblock) << '
' + when /\A\s*-{4,}\s*$/ + end_paragraph + @out << '
' + when /\A\s*(={1,6})\s*(.*?)\s*=*\s*$(\r?\n)?/ + end_paragraph + level = $1.size + @out << "" << escape_html($2) << "" + when /\A[ \t]*\|.*$(\r?\n)?/ + unless @stack.include?('table') + end_paragraph + @stack.push('table') + @out << '' + end + parse_table_row($&) + when /\A\s*$(\r?\n)?/ + end_paragraph + when /\A(\s*([*#]+)\s*(.*?))$(\r?\n)?/ + line, bullet, item = $1, $2, $3 + tag = (bullet[0,1] == '*' ? 'ul' : 'ol') + listre = /\A[ou]l\z/ + if bullet[0,1] == '#' or bullet.size != 2 or @stack.find { |x| x=='ol' || x == 'ul' } + ulcount = @stack.inject(0) { |a,b| a + (ulol(b) ? 1 : 0) } + while ulcount > bullet.size or not (@stack.empty? or ulol(@stack.last)) + @out << '' + ulcount -= 1 if ulol(@stack.pop) + end + + if ulcount == bullet.size and @stack.last != tag + @out << '' + @stack.pop + ulcount -= 1 + end + + while ulcount < bullet.size + @out << '<' << tag << '>' + @stack.push tag + ulcount += 1 + end + @p = true + @out << '
  • ' + @stack.push('li') + parse_inline(item) + else + start_paragraph + parse_inline(line) + end + when /\A([ \t]*\S+.*?)$(\r?\n)?/ + start_paragraph + parse_inline($1) + else + raise CreoleParseError, "Parse error at #{str[0,30].inspect}" + end + #p [$&, $'] + str = $' + end + end_paragraph + return @out + end + + end # class CreoleParser + +end # module Creole diff --git a/test/test_creole.rb b/test/test_creole.rb new file mode 100644 index 0000000..933d069 --- /dev/null +++ b/test/test_creole.rb @@ -0,0 +1,15 @@ +require 'test/unit' +require 'creole' +require 'cgi' +require 'testcases' + +$strict = false + +class TestC < Test::Unit::TestCase + include TestCases + + def tc(html, creole) + output = Creole.creolize(creole) + assert html === output, "Parsing: #{creole.inspect}\nExpected: #{html.inspect}\n Was: #{output.inspect}" + end +end diff --git a/test/testcases.rb b/test/testcases.rb new file mode 100644 index 0000000..5ee2f18 --- /dev/null +++ b/test/testcases.rb @@ -0,0 +1,626 @@ +require 'cgi' + +# Test cases for the creole converter. These are included into the +# unittests, but is implemented as a separated module to be able to +# import them into demo wikis. +# +# Each test case should simply call the 'tc' method with the expected +# HTML and input Creole formatted text as arguments. The expected HTML +# should contain minimal amount of whitespace (only the absolutely +# required). + +module TestCases + def escape_html(html) + CGI::escapeHTML(html) + end + + def test_bold + # Creole1.0: Bold can be used inside paragraphs + tc "

    This is bold

    ", "This **is** bold" + tc "

    This is bold and boldish

    ", "This **is** bold and **bold**ish" + + # Creole1.0: Bold can be used inside list items + tc "", "* This is **bold**" + + # Creole1.0: Bold can be used inside table cells + tc("
  • This is bold
    ", + "|This is **bold**|") + + # Creole1.0: Links can appear inside bold text: + tc("

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

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

    This is bold

    ", "This **is bold" + + # Creole1.0: Bold will end at the end of list items + tc("", + "* Item **bold\n* Item normal") + + # Creole1.0: Bold will end at the end of table cells + tc("
    Item boldAnother bold
    ", + "|Item **bold|Another **bold") + + # Creole1.0: Bold should not cross paragraphs + tc("

    This is

    bold maybe

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

    This is bold

    ", "This **is\nbold**" + end + + def test_italic + # Creole1.0: Italic can be used inside paragraphs + tc("

    This is italic

    ", + "This //is// italic") + tc("

    This is italic and italicish

    ", + "This //is// italic and //italic//ish") + + # Creole1.0: Italic can be used inside list items + tc "", "* This is //italic//" + + # Creole1.0: Italic can be used inside table cells + tc("
    This is italic
    ", + "|This is //italic//|") + + # Creole1.0: Links can appear inside italic text: + tc("

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

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

    This is italic

    ", "This //is italic" + + # Creole1.0: Italic will end at the end of list items + tc("", + "* Item //italic\n* Item normal") + + # Creole1.0: Italic will end at the end of table cells + tc("
    Item italicAnother italic
    ", + "|Item //italic|Another //italic") + + # Creole1.0: Italic should not cross paragraphs + tc("

    This is

    italic maybe

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

    This is italic

    ", "This //is\nitalic//" + end + + def test_bold_italics + # Creole1.0: By example + tc "

    bold italics

    ", "**//bold italics//**" + + # Creole1.0: By example + tc "

    bold italics

    ", "//**bold italics**//" + + # Creole1.0: By example + tc "

    This is also good.

    ", "//This is **also** good.//" + end + + def test_headings + # Creole1.0: Only three differed sized levels of heading are required. + tc "

    Heading 1

    ", "= Heading 1 =" + tc "

    Heading 2

    ", "== Heading 2 ==" + tc "

    Heading 3

    ", "=== Heading 3 ===" + unless $strict + tc "

    Heading 4

    ", "==== Heading 4 ====" + tc "
    Heading 5
    ", "===== Heading 5 =====" + tc "
    Heading 6
    ", "====== Heading 6 ======" + end + + # Creole1.0: Closing (right-side) equal signs are optional + tc "

    Heading 1

    ", "=Heading 1" + tc "

    Heading 2

    ", "== Heading 2" + tc "

    Heading 3

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

    Heading 1

    ", "=Heading 1 ===" + tc "

    Heading 2

    ", "== Heading 2 =" + tc "

    Heading 3

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

    Heading 1

    ", " \t= Heading 1 =" + tc "

    Heading 2

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

    Heading 1

    ", " = Heading 1 = " + tc "

    Heading 2

    ", " == Heading 2 == \t " + + # !!Creole1.0 doesn't specify if text after closing equal signs + # !!becomes part of the heading or invalidates the entire heading. + # tc "

    == Heading 2 == foo

    ", " == Heading 2 == foo" + unless $strict + tc "

    Heading 2 == foo

    ", " == Heading 2 == foo" + end + + # Creole1.0-Implied: Line must start with equal sign + tc "

    foo = Heading 1 =

    ", "foo = Heading 1 =" + end + + def test_links + # Creole1.0: Links + tc "

    link

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

    Hello, world

    ", "Hello, [[world]]" + + # Creole1.0: Named links + tc "

    Go to my page

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

    http://www.wikicreole.org/

    ", "[[http://www.wikicreole.org/]]" + + # Creole1.0: Free-standing URL's should be turned into links + tc "

    http://www.wikicreole.org/

    ", "http://www.wikicreole.org/" + + # Creole1.0: Single punctuation characters at the end of URLs + # should not be considered a part of the URL. + [',','.','?','!',':',';','\'','"'].each { |punct| + esc_punct = escape_html(punct) + tc "

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

    ", "http://www.wikicreole.org/#{punct}" + } + # Creole1.0: Nameds URLs (by example) + tc("

    Visit the WikiCreole website

    ", + "[[http://www.wikicreole.org/|Visit the WikiCreole website]]") + + unless $strict + # Parsing markup within a link is optional + tc "

    **Weird** //Stuff//

    ", "[[Weird Stuff|**Weird** //Stuff//]]" + end + + # Inside bold + tc "

    link

    ", "**[[link]]**" + + # Whitespace inside [[ ]] should be ignored + tc("

    link

    ", "[[ link ]]") + tc("

    link me

    ", "[[ link me ]]") + tc("

    dot.com

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

    dot.com

    ", "[[ http://dot.com/ | dot.com ]]") + end + + def test_paragraph + # Creole1.0: One or more blank lines end paragraphs. + tc "

    This is my text.

    This is more text.

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

    This is my text.

    This is more text.

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

    This is my text.

    This is more text.

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

    Hello

    ", "Hello\n* Item\n" + + # Creole1.0: A table end paragraphs too. + tc "

    Hello

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

    Hello

    nowiki
    ", "Hello\n{{{\nnowiki\n}}}\n" + + unless $strict + # A heading ends a paragraph (not specced) + tc "

    Hello

    Heading

    ", "Hello\n= Heading =\n" + end + end + + def test_linebreak + # Creole1.0: \\ (wiki-style) for line breaks. + tc "

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

    ", "This is the first line,\\\\and this is the second." + end + + def test_unordered_lists + # Creole1.0: List items begin with a * at the beginning of a line. + # Creole1.0: An item ends at the next * + tc "", "* Item 1\n *Item 2\n *\t\tItem 3\n" + + # Creole1.0: Whitespace is optional before and after the *. + tc("", + " * Item 1\n*Item 2\n \t*\t\tItem 3\n") + + # Creole1.0: A space is required if if the list element starts with bold text. + tc("", "***Item 1") + tc("", "* **Item 1") + + # Creole1.0: An item ends at blank line + tc("

    Par

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

    Heading

    ", "* Item\n= Heading =\n") + + # Creole1.0: An item ends at a table + tc("
    Cell
    ", "* Item\n|Cell|\n") + + # Creole1.0: An item ends at a nowiki block + tc("
    Code
    ", "* Item\n{{{\nCode\n}}}\n") + + # Creole1.0: An item can span multiple lines + tc("", + "* The quick\nbrown fox\n\tjumps over\nlazy dog.\n*Humpty Dumpty\nsat\t\non a wall.") + + # Creole1.0: An item can contain line breaks + tc("", + "* The quick brown\\\\fox jumps over lazy dog.") + + # Creole1.0: Nested + tc "", "* Item 1\n **Item 2\n *\t\tItem 3\n" + + # Creole1.0: Nested up to 5 levels + tc("", + "*Item 1\n**Item 2\n***Item 3\n****Item 4\n*****Item 5\n") + + # Creole1.0: ** immediatly following a list element will be treated as a nested unordered element. + tc("", + "*Hello,\nWorld!\n**Not bold\n") + + # Creole1.0: ** immediatly following a list element will be treated as a nested unordered element. + tc("
    1. Hello, World!
    ", + "#Hello,\nWorld!\n**Not bold\n") + + # Creole1.0: [...] otherwise it will be treated as the beginning of bold text. + tc("

    Not bold

    ", + "*Hello,\nWorld!\n\n**Not bold\n") + end + + def test_ordered_lists + # Creole1.0: List items begin with a * at the beginning of a line. + # Creole1.0: An item ends at the next * + tc "
    1. Item 1
    2. Item 2
    3. Item 3
    ", "# Item 1\n #Item 2\n #\t\tItem 3\n" + + # Creole1.0: Whitespace is optional before and after the #. + tc("
    1. Item 1
    2. Item 2
    3. Item 3
    ", + " # Item 1\n#Item 2\n \t#\t\tItem 3\n") + + # Creole1.0: A space is required if if the list element starts with bold text. + tc("
        1. Item 1
    ", "###Item 1") + tc("
    1. Item 1
    ", "# **Item 1") + + # Creole1.0: An item ends at blank line + tc("
    1. Item

    Par

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

    Heading

    ", "# Item\n= Heading =\n") + + # Creole1.0: An item ends at a table + tc("
    1. Item
    Cell
    ", "# Item\n|Cell|\n") + + # Creole1.0: An item ends at a nowiki block + tc("
    1. Item
    Code
    ", "# Item\n{{{\nCode\n}}}\n") + + # Creole1.0: An item can span multiple lines + tc("
    1. The quick brown fox jumps over lazy dog.
    2. Humpty Dumpty sat on a wall.
    ", + "# The quick\nbrown fox\n\tjumps over\nlazy dog.\n#Humpty Dumpty\nsat\t\non a wall.") + + # Creole1.0: An item can contain line breaks + tc("
    1. The quick brown
      fox jumps over lazy dog.
    ", + "# The quick brown\\\\fox jumps over lazy dog.") + + # Creole1.0: Nested + tc "
    1. Item 1
      1. Item 2
    2. Item 3
    ", "# Item 1\n ##Item 2\n #\t\tItem 3\n" + + # Creole1.0: Nested up to 5 levels + tc("
    1. Item 1
      1. Item 2
        1. Item 3
          1. Item 4
            1. Item 5
    ", + "#Item 1\n##Item 2\n###Item 3\n####Item 4\n#####Item 5\n") + + # Creole1.0_Infered: The two-bullet rule only applies to **. + tc("
      1. Item
    ", "##Item") + end + + def test_ordered_lists2 + tc "
    1. Item 1
    2. Item 2
    3. Item 3
    ", "# Item 1\n #Item 2\n #\t\tItem 3\n" + # Nested + tc "
    1. Item 1
      1. Item 2
    2. Item 3
    ", "# Item 1\n ##Item 2\n #\t\tItem 3\n" + # Multiline + tc "
    1. Item 1 on multiple lines
    ", "# Item 1\non multiple lines" + end + + def test_ambiguity_mixed_lists + # ol following ul + tc("
    1. oitem
    ", "*uitem\n#oitem\n") + + # ul following ol + tc("
    1. uitem
    ", "#uitem\n*oitem\n") + + # 2ol following ul + tc("", "*uitem\n##oitem\n") + + # 2ul following ol + tc("
    1. uitem
    ", "#uitem\n**oitem\n") + + # 3ol following 3ul + tc("", "***uitem\n###oitem\n") + + # 2ul following 2ol + tc("
      1. uitem
    ", "##uitem\n**oitem\n") + + # ol following 2ol + tc("
      1. oitem1
    1. oitem2
    ", "##oitem1\n#oitem2\n") + # ul following 2ol + tc("
      1. oitem1
    ", "##oitem1\n*oitem2\n") + end + + def test_ambiguity_italics_and_url + # Uncommon URL schemes should not be parsed as URLs + tc("

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

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

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

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

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

    ", + "Formatted fruits, for example://apples//, oranges, **pears** ...") + end + + def test_ambiguity_bold_and_lists + tc "

    bold text

    ", "** bold text **" + tc "

    bold text

    ", " ** bold text **" + end + + def test_nowiki + # ... works as block + tc "
    Hello
    ", "{{{\nHello\n}}}\n" + + # ... works inline + tc "

    Hello world.

    ", "Hello {{{world}}}." + + # Creole1.0: No wiki markup is interpreted inbetween + tc "
    **Hello**
    ", "{{{\n**Hello**\n}}}\n" + + # Creole1.0: Leading whitespaces are not permitted + tc("

    {{{ Hello }}}

    ", " {{{\nHello\n}}}") + tc("

    {{{ Hello }}}

    ", "{{{\nHello\n }}}") + + # Assumed: Should preserve whitespace + tc("
     \t Hello, \t \n \t World \t 
    ", + "{{{\n \t Hello, \t \n \t World \t \n}}}\n") + + # In preformatted blocks ... one leading space is removed + tc("
    nowikiblock\n}}}
    ", "{{{\nnowikiblock\n }}}\n}}}\n") + + # In inline nowiki, any trailing closing brace is included in the span + tc("

    this is nowiki}

    ", "this is {{{nowiki}}}}") + tc("

    this is nowiki}}

    ", "this is {{{nowiki}}}}}") + tc("

    this is nowiki}}}

    ", "this is {{{nowiki}}}}}}") + tc("

    this is nowiki}}}}

    ", "this is {{{nowiki}}}}}}}") + end + + def test_html_escaping + # Special HTML chars should be escaped + tc("

    <b>not bold</b>

    ", "not bold") + + # Image tags should be escape + tc("

    \""tag"\"/

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

    Click

    ", "[[javascript:alert(\"Boo!\")|Click]]") + end + + def test_escape + tc "

    ** Not Bold **

    ", "~** Not Bold ~**" + tc "

    // Not Italic //

    ", "~// Not Italic ~//" + tc "

    * Not Bullet

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

    Hello ~ world

    ", "Hello ~ world\n" + tc "

    Hello ~ world

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

    http://example.org/~user/

    ", "http://example.org/~user/" + + # Escaping links + tc "

    http://www.wikicreole.org/

    ", "~http://www.wikicreole.org/" + end + + def test_horizontal_rule + # Creole: Four hyphens make a horizontal rule + tc "
    ", "----" + + # Creole1.0: Whitespace around them is allowed + tc "
    ", " ----" + tc "
    ", "---- " + tc "
    ", " ---- " + tc "
    ", " \t ---- \t " + + # Creole1.0: Nothing else than hyphens and whitespace is "allowed" + tc "

    foo ----

    ", "foo ----\n" + tc "

    ---- foo

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

    -- --

    ", " -- -- " + tc "

    -- --

    ", " --\t-- " + end + + def test_table + tc "
    Hello, World!
    ", "|Hello, World!|" + # Multiple columns + tc "
    c1c2c3
    ", "|c1|c2|c3|" + # Multiple rows + tc "
    c11c12
    c21c22
    ", "|c11|c12|\n|c21|c22|\n" + # End pipe is optional + tc "
    c1c2c3
    ", "|c1|c2|c3" + # Empty cells + tc "
    c1c3
    ", "|c1||c3" + # Escaping cell separator + tc "
    c1|c2c3
    ", "|c1~|c2|c3" + # Escape in last cell + empty cell + tc "
    c1c2|
    ", "|c1|c2~|" + tc "
    c1c2|
    ", "|c1|c2~||" + tc "
    c1c2|
    ", "|c1|c2~|||" + # Equal sign after pipe make a header + tc "
    Header
    ", "|=Header|" + end + + def test_following_table + # table followed by heading + tc("
    table

    heading

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

    heading

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

    par

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

    par

    ", "|table|\n\npar\n") + # table followed by unordered list + tc("
    table
    ", "|table|\n*item\n") + tc("
    table
    ", "|table|\n\n*item\n") + # table followed by ordered list + tc("
    table
    1. item
    ", "|table|\n#item\n") + tc("
    table
    1. item
    ", "|table|\n\n#item\n") + # table followed by horizontal rule + tc("
    table

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

    ", "|table|\n\n----\n") + # table followed by nowiki block + tc("
    table
    pre
    ", "|table|\n{{{\npre\n}}}\n") + tc("
    table
    pre
    ", "|table|\n\n{{{\npre\n}}}\n") + # table followed by table + tc("
    table
    table
    ", "|table|\n|table|\n") + tc("
    table
    table
    ", "|table|\n\n|table|\n") + end + + def test_following_heading + # heading + tc("

    heading1

    heading2

    ", "=heading1=\n=heading2\n") + tc("

    heading1

    heading2

    ", "=heading1=\n\n=heading2\n") + # paragraph + tc("

    heading

    par

    ", "=heading=\npar\n") + tc("

    heading

    par

    ", "=heading=\n\npar\n") + # unordered list + tc("

    heading

    ", "=heading=\n*item\n") + tc("

    heading

    ", "=heading=\n\n*item\n") + # ordered list + tc("

    heading

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

    heading

    1. item
    ", "=heading=\n\n#item\n") + # horizontal rule + tc("

    heading


    ", "=heading=\n----\n") + tc("

    heading


    ", "=heading=\n\n----\n") + # nowiki block + tc("

    heading

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

    heading

    nowiki
    ", "=heading=\n\n{{{\nnowiki\n}}}\n") + # table + tc("

    heading

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

    heading

    table
    ", "=heading=\n\n|table|\n") + end + + def test_following_paragraph + # heading + tc("

    par

    heading

    ", "par\n=heading=") + tc("

    par

    heading

    ", "par\n\n=heading=") + # paragraph + tc("

    par par

    ", "par\npar\n") + tc("

    par

    par

    ", "par\n\npar\n") + # unordered + tc("

    par

    ", "par\n*item") + tc("

    par

    ", "par\n\n*item") + # ordered + tc("

    par

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

    par

    1. item
    ", "par\n\n#item\n") + # horizontal + tc("

    par


    ", "par\n----\n") + tc("

    par


    ", "par\n\n----\n") + # nowiki + tc("

    par

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

    par

    nowiki
    ", "par\n\n{{{\nnowiki\n}}}\n") + # table + tc("

    par

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

    par

    table
    ", "par\n\n|table|\n") + end + + def test_following_unordered_list + # heading + tc("

    heading

    ", "*item\n=heading=") + tc("

    heading

    ", "*item\n\n=heading=") + # paragraph + tc("", "*item\npar\n") # items may span multiple lines + tc("

    par

    ", "*item\n\npar\n") + # unordered + tc("", "*item\n*item\n") + tc("", "*item\n\n*item\n") + # ordered + tc("
    1. item
    ", "*item\n#item\n") + tc("
    1. item
    ", "*item\n\n#item\n") + # horizontal rule + tc("
    ", "*item\n----\n") + tc("
    ", "*item\n\n----\n") + # nowiki + tc("
    nowiki
    ", "*item\n{{{\nnowiki\n}}}\n") + tc("
    nowiki
    ", "*item\n\n{{{\nnowiki\n}}}\n") + # table + tc("
    table
    ", "*item\n|table|\n") + tc("
    table
    ", "*item\n\n|table|\n") + end + + def test_following_ordered_list + # heading + tc("
    1. item

    heading

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

    heading

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

    par

    ", "#item\n\npar\n") + # unordered + tc("
    1. item
    ", "#item\n*item\n") + tc("
    1. item
    ", "#item\n\n*item\n") + # ordered + tc("
    1. item
    2. item
    ", "#item\n#item\n") + tc("
    1. item
    1. item
    ", "#item\n\n#item\n") + # horizontal role + tc("
    1. item

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

    ", "#item\n\n----\n") + # nowiki + tc("
    1. item
    nowiki
    ", "#item\n{{{\nnowiki\n}}}\n") + tc("
    1. item
    nowiki
    ", "#item\n\n{{{\nnowiki\n}}}\n") + # table + tc("
    1. item
    table
    ", "#item\n|table|\n") + tc("
    1. item
    table
    ", "#item\n\n|table|\n") + end + + def test_following_horizontal_rule + # heading + tc("

    heading

    ", "----\n=heading=") + tc("

    heading

    ", "----\n\n=heading=") + # paragraph + tc("

    par

    ", "----\npar\n") + tc("

    par

    ", "----\n\npar\n") + # unordered + tc("
    ", "----\n*item") + tc("
    ", "----\n*item") + # ordered + tc("
    1. item
    ", "----\n#item") + tc("
    1. item
    ", "----\n#item") + # horizontal + tc("

    ", "----\n----\n") + tc("

    ", "----\n\n----\n") + # nowiki + tc("
    nowiki
    ", "----\n{{{\nnowiki\n}}}\n") + tc("
    nowiki
    ", "----\n\n{{{\nnowiki\n}}}\n") + # table + tc("
    table
    ", "----\n|table|\n") + tc("
    table
    ", "----\n\n|table|\n") + end + + def test_following_nowiki_block + # heading + tc("
    nowiki

    heading

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

    heading

    ", "{{{\nnowiki\n}}}\n\n=heading=") + # paragraph + tc("
    nowiki

    par

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

    par

    ", "{{{\nnowiki\n}}}\n\npar") + # unordered + tc("
    nowiki
    ", "{{{\nnowiki\n}}}\n*item\n") + tc("
    nowiki
    ", "{{{\nnowiki\n}}}\n\n*item\n") + # ordered + tc("
    nowiki
    1. item
    ", "{{{\nnowiki\n}}}\n#item\n") + tc("
    nowiki
    1. item
    ", "{{{\nnowiki\n}}}\n\n#item\n") + # horizontal + tc("
    nowiki

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

    ", "{{{\nnowiki\n}}}\n\n----\n") + # nowiki + tc("
    nowiki
    nowiki
    ", "{{{\nnowiki\n}}}\n{{{\nnowiki\n}}}\n") + tc("
    nowiki
    nowiki
    ", "{{{\nnowiki\n}}}\n\n{{{\nnowiki\n}}}\n") + # table + tc("
    nowiki
    table
    ", "{{{\nnowiki\n}}}\n|table|\n") + tc("
    nowiki
    table
    ", "{{{\nnowiki\n}}}\n\n|table|\n") + end + + def test_image + tc("

    ", "{{image.jpg}}") + tc("

    \"tag\"/

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

    ", "{{http://example.org/image.jpg}}") + end + + def test_bold_combo + tc("

    bold and

    table

    end

    ", + "**bold and\n|table|\nend**") + end +end