lib/markdown2: introduce markdown rendering to HTML
authorAlexandre Terrasa <alexandre@moz-code.org>
Tue, 29 May 2018 23:52:35 +0000 (19:52 -0400)
committerAlexandre Terrasa <alexandre@moz-code.org>
Wed, 20 Jun 2018 23:11:18 +0000 (19:11 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

lib/markdown2/markdown2.nit [new file with mode: 0644]
lib/markdown2/markdown_html_rendering.nit [new file with mode: 0644]
lib/markdown2/tests/test_markdown_blocks.nit [new file with mode: 0644]
lib/markdown2/tests/test_markdown_headings_id.nit [new file with mode: 0644]
lib/markdown2/tests/test_markdown_inlines.nit [new file with mode: 0644]

diff --git a/lib/markdown2/markdown2.nit b/lib/markdown2/markdown2.nit
new file mode 100644 (file)
index 0000000..85da1b9
--- /dev/null
@@ -0,0 +1,27 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module markdown2
+
+import markdown_block_parsing
+import markdown_html_rendering
+
+redef class String
+       fun md_to_html2: String do
+               var parser = new MdParser
+               var doc = parser.parse(self)
+               var renderer = new HtmlRenderer
+               return renderer.render(doc)
+       end
+end
diff --git a/lib/markdown2/markdown_html_rendering.nit b/lib/markdown2/markdown_html_rendering.nit
new file mode 100644 (file)
index 0000000..b8a88e3
--- /dev/null
@@ -0,0 +1,424 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# HTML rendering of Markdown documents
+module markdown_html_rendering
+
+import markdown_rendering
+
+# Markdown document renderer to HTML
+class HtmlRenderer
+       super MdRenderer
+
+       # HTML output under construction
+       private var html: Buffer is noinit
+
+       # Render `document` as HTML
+       redef fun render(document) do
+               reset
+               enter_visit(document)
+               return html.write_to_string
+       end
+
+       redef fun visit(node) do node.render_html(self)
+
+       # Reset `headings` and internal state
+       fun reset do
+               html = new Buffer
+               if enable_heading_ids then headings.clear
+       end
+
+       # Last char visited
+       #
+       # Used to avoid double blank lines.
+       private var last_char: nullable Char = null
+
+       # Add `string` to `html`
+       private fun add(string: String) do
+               html.append(string)
+               if not html.is_empty then
+                       last_char = html.last
+               end
+       end
+
+       # Add a raw `html` string to the output
+       #
+       # Raw means that the string will not be escaped.
+       fun add_raw(html: String) do add html
+
+       # Add `text` string to the output
+       #
+       # The string will be escaped.
+       fun add_text(text: String) do add html_escape(text, true)
+
+       # Add a blank line to the output
+       fun add_line do
+               if last_char != null and last_char != '\n' then
+                       add "\n"
+               end
+       end
+
+       # Escape `string` to HTML
+       #
+       # When `keep_entities`, HTML entities will not be escaped.
+       fun html_escape(string: String, keep_entities: Bool): String do
+               var buf: nullable Buffer = null
+               for i in [0..string.length[ do
+                       var c = string.chars[i]
+                       var sub = null
+                       if c == '&' and (not keep_entities or string.search_from(re_entity, i) == null) then
+                               sub = "&amp;"
+                       else if c == '<' then
+                               sub = "&lt;"
+                       else if c == '>' then
+                               sub = "&gt;"
+                       else if c == '"' then
+                               sub = "&quot;"
+                       else
+                               if buf != null then buf.add c
+                               continue
+                       end
+                       if buf == null then
+                               buf = new Buffer
+                               for j in [0..i[ do buf.add string.chars[j]
+                       end
+                       buf.append sub
+               end
+
+               if buf == null then return string
+               return buf.to_s
+       end
+
+       # HTML entity pattern
+       private var re_entity: Regex = "^&(#x[a-f0-9]\{1,8\}|#[0-9]\{1,8\}|[a-z][a-z0-9]\{1,31\});".to_re
+
+       # Encode the `uri` string
+       fun encode_uri(uri: String): String do
+               var buf = new Buffer
+
+               var i = 0
+               while i < uri.length do
+                       var c = uri.chars[i]
+                       if (c >= '0' and c <= '9') or
+                          (c >= 'a' and c <= 'z') or
+                          (c >= 'A' and c <= 'Z') or
+                          c == ';' or c == ',' or c == '/' or c == '?' or
+                          c == ':' or c == '@' or c == '=' or c == '+' or
+                          c == '$' or c == '-' or c == '_' or c == '.' or
+                          c == '!' or c == '~' or c == '*' or c == '(' or
+                          c == ')' or c == '#' or c == '\''
+                       then
+                               buf.add c
+                       else if c == '&' then
+                               buf.append "&amp;"
+                       else if c == '%' and uri.search_from(re_uri_code, i) != null then
+                               buf.append uri.substring(i, 3)
+                               i += 2
+                       else
+                               var bytes = c.to_s.bytes
+                               for b in bytes do buf.append "%{b.to_i.to_hex}".to_upper
+                       end
+                       i += 1
+               end
+
+               return buf.to_s
+       end
+
+       # URI encode pattern
+       private var re_uri_code: Regex = "^%[a-zA-Z0-9]\{2\}".to_re
+
+       # Add `id` tags to headings
+       var enable_heading_ids = false is optional, writable
+
+       # Associate headings ids to blocks
+       var headings = new ArrayMap[String, MdHeading]
+
+       # Strip heading id
+       fun strip_id(text: String): String do
+               # strip id
+               var b = new FlatBuffer
+               for c in text do
+                       if c == ' ' then
+                               b.add '_'
+                       else
+                               if not c.is_letter and
+                                  not c.is_digit and
+                                  not allowed_id_chars.has(c) then continue
+                               b.add c
+                       end
+               end
+               var res = b.to_s
+               if res.is_empty then res = "_"
+               var key = res
+               # check for multiple id definitions
+               if headings.has_key(key) then
+                       var i = 1
+                       key = "{res}_{i}"
+                       while headings.has_key(key) do
+                               i += 1
+                               key = "{res}_{i}"
+                       end
+               end
+               return key
+       end
+
+       # Allowed characters in ids
+       var allowed_id_chars: Array[Char] = ['-', '_', ':', '.']
+end
+
+redef class MdNode
+
+       # Render `self` as HTML
+       fun render_html(v: HtmlRenderer) do visit_all(v)
+end
+
+# Blocks
+
+redef class MdBlockQuote
+       redef fun render_html(v) do
+               v.add_line
+               v.add_raw "<blockquote>"
+               v.add_line
+               visit_all(v)
+               v.add_line
+               v.add_raw "</blockquote>"
+               v.add_line
+       end
+end
+
+redef class MdCodeBlock
+       redef fun render_html(v) do
+               var info = self.info
+               v.add_line
+               v.add_raw "<pre>"
+               v.add_raw "<code"
+               if info != null and not info.is_empty then
+                       v.add_raw " class=\"language-{info.split(" ").first}\""
+               end
+               v.add_raw ">"
+               var literal = self.literal or else ""
+               var lines = literal.split("\n")
+               for i in [0..lines.length[ do
+                       var line = lines[i]
+                       v.add_raw v.html_escape(line, false)
+                       if i < lines.length - 1 then
+                               v.add_raw "\n"
+                       end
+               end
+               v.add_raw "</code>"
+               v.add_raw "</pre>"
+               v.add_line
+       end
+end
+
+redef class MdHeading
+       redef fun render_html(v) do
+               v.add_line
+               if v.enable_heading_ids then
+                       var id = self.id
+                       if id == null then
+                               id = v.strip_id(title)
+                               v.headings[id] = self
+                               self.id = id
+                       end
+                       v.add_raw "<h{level} id=\"{id}\">"
+               else
+                       v.add_raw "<h{level}>"
+               end
+               visit_all(v)
+               v.add_raw "</h{level}>"
+               v.add_line
+       end
+
+       #
+       var id: nullable String = null
+
+       #
+       fun title: String do
+               var v = new RawTextVisitor
+               return v.render(self)
+       end
+end
+
+redef class MdUnorderedList
+       redef fun render_html(v) do
+               v.add_line
+               v.add_raw "<ul>"
+               v.add_line
+               visit_all(v)
+               v.add_line
+               v.add_raw "</ul>"
+               v.add_line
+       end
+end
+
+redef class MdOrderedList
+       redef fun render_html(v) do
+               var start = self.start_number
+               v.add_line
+               v.add_raw "<ol"
+               if start != 1 then
+                       v.add_raw " start=\"{start}\""
+               end
+               v.add_raw ">"
+               v.add_line
+               visit_all(v)
+               v.add_line
+               v.add_raw "</ol>"
+               v.add_line
+       end
+end
+
+redef class MdListItem
+       redef fun render_html(v) do
+               v.add_raw "<li>"
+               visit_all(v)
+               v.add_raw "</li>"
+               v.add_line
+       end
+end
+
+redef class MdParagraph
+       redef fun render_html(v) do
+               var is_tight = is_in_tight_list
+               if not is_tight then
+                       v.add_line
+                       v.add_raw "<p>"
+               end
+               visit_all(v)
+               if not is_tight then
+                       v.add_raw "</p>"
+                       v.add_line
+               end
+       end
+end
+
+redef class MdThematicBreak
+       redef fun render_html(v) do
+               v.add_line
+               v.add_raw "<hr />"
+               v.add_line
+       end
+end
+
+redef class MdHtmlBlock
+       redef fun render_html(v) do
+               v.add_line
+               var literal = self.literal or else ""
+               var lines = literal.split("\n")
+               for i in [0..lines.length[ do
+                       var line = lines[i]
+                       if not line.trim.is_empty then
+                               v.add_raw line
+                       end
+                       if i < lines.length - 1 then
+                               v.add_raw "\n"
+                       end
+               end
+               v.add_line
+       end
+end
+
+# Inlines
+
+redef class MdHardLineBreak
+       redef fun render_html(v) do
+               v.add_raw "<br />"
+               v.add_line
+       end
+end
+
+redef class MdSoftLineBreak
+       redef fun render_html(v) do
+               v.add_raw "\n"
+       end
+end
+
+redef class MdCode
+       redef fun render_html(v) do
+               v.add_raw "<code>"
+               v.add_raw v.html_escape(literal, false)
+               v.add_raw "</code>"
+       end
+end
+
+redef class MdEmphasis
+       redef fun render_html(v) do
+               v.add_raw "<em>"
+               visit_all(v)
+               v.add_raw "</em>"
+       end
+end
+
+redef class MdStrongEmphasis
+       redef fun render_html(v) do
+               v.add_raw "<strong>"
+               visit_all(v)
+               v.add_raw "</strong>"
+       end
+end
+
+redef class MdHtmlInline
+       redef fun render_html(v) do
+               v.add_raw literal
+       end
+end
+
+redef class MdImage
+       redef fun render_html(v) do
+               var url = self.destination
+               var title = self.title
+               v.add_raw "<img"
+               v.add_raw " src=\"{v.encode_uri(url)}\""
+
+               var alt_text = self.alt_text
+               v.add_raw " alt=\"{alt_text}\""
+
+               if title != null and not title.is_empty then
+                       v.add_raw " title=\""
+                       v.add_text title
+                       v.add_raw "\""
+               end
+
+               v.add_raw " />"
+       end
+
+       private fun alt_text: String do
+               var v = new RawTextVisitor
+               return v.render(self)
+       end
+end
+
+redef class MdLink
+       redef fun render_html(v) do
+               var url = self.destination
+               var title = self.title
+               v.add_raw "<a"
+               v.add_raw " href=\"{v.encode_uri(url)}\""
+               if title != null and not title.is_empty then
+                       v.add_raw " title=\""
+                       v.add_text title
+                       v.add_raw "\""
+               end
+               v.add_raw ">"
+               visit_all(v)
+               v.add_raw "</a>"
+       end
+end
+
+redef class MdText
+       redef fun render_html(v) do
+               v.add_text literal
+       end
+end
diff --git a/lib/markdown2/tests/test_markdown_blocks.nit b/lib/markdown2/tests/test_markdown_blocks.nit
new file mode 100644 (file)
index 0000000..f81d225
--- /dev/null
@@ -0,0 +1,660 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either htmlress or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test for markdown blocks parsing
+module test_markdown_blocks is test
+
+import test_markdown
+
+class TestMarkdownBlocks
+       super TestMarkdownHtml
+       test
+
+       fun test_blocks_empty is test do
+               var md = ""
+               var html = ""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_tabs is test do
+               var md = """\tsome code\n"""
+               var html = """<pre><code>some code\n</code></pre>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_pagraph1 is test do
+               var md = "test\n"
+               var html = "<p>test</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_pagraph2 is test do
+               var md = """line1\nline2\n\nline3 line4\n\nline5\n"""
+               var html = """<p>line1\nline2</p>\n<p>line3 line4</p>\n<p>line5</p>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_pagraph3 is test do
+               var md = """
+Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """
+<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
+<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_headings_1 is test do
+               var md = """
+This is a H1
+=============
+
+This is a H2
+-------------
+"""
+               var html = """
+<h1>This is a H1</h1>
+<h2>This is a H2</h2>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_headings_2 is test do
+               var md = """
+# This is a H1
+
+## This is a H2
+###### This is a H6
+"""
+               var html = """
+<h1>This is a H1</h1>
+<h2>This is a H2</h2>
+<h6>This is a H6</h6>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_headings_3 is test do
+               var md = """
+# This is a H1 #
+
+## This is a H2 ##
+
+### This is a H3 ######
+"""
+               var html = """
+<h1>This is a H1</h1>
+<h2>This is a H2</h2>
+<h3>This is a H3</h3>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_hr1 is test do
+               var md = """
+* * *
+
+***
+
+*****
+
+- - -
+
+---------------------------------------
+"""
+               var html = "<hr />\n<hr />\n<hr />\n<hr />\n<hr />\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_bquote1 is test do
+               var md = """
+> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+>
+> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+> id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """<blockquote>
+<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
+<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.</p>
+</blockquote>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_bquote2 is test do
+               var md = """
+> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """<blockquote>
+<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
+</blockquote>
+<blockquote>
+<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.</p>
+</blockquote>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_bquote3 is test do
+               var md = """
+> This is the first level of quoting.
+>
+> > This is nested blockquote.
+>
+> Back to the first level.
+"""
+               var html = """<blockquote>
+<p>This is the first level of quoting.</p>
+<blockquote>
+<p>This is nested blockquote.</p>
+</blockquote>
+<p>Back to the first level.</p>
+</blockquote>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list1 is test do
+               var md = """
+*   Red
+*   Green
+*   Blue
+"""
+               var html = """<ul>
+<li>Red</li>
+<li>Green</li>
+<li>Blue</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list2 is test do
+               var md = """
++   Red
++   Green
++   Blue
+"""
+               var html = """<ul>
+<li>Red</li>
+<li>Green</li>
+<li>Blue</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list3 is test do
+               var md = """
+-   Red
+-   Green
+-   Blue
+"""
+               var html = """<ul>
+<li>Red</li>
+<li>Green</li>
+<li>Blue</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list4 is test do
+               var md = """
+1.  Bird
+2.  McHale
+3.  Parish
+"""
+               var html = """<ol>
+<li>Bird</li>
+<li>McHale</li>
+<li>Parish</li>
+</ol>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list5 is test do
+               var md = """
+3. Bird
+1. McHale
+8. Parish
+"""
+               var html = """<ol start=\"3\">
+<li>Bird</li>
+<li>McHale</li>
+<li>Parish</li>
+</ol>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list6 is test do
+               var md = """
+*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+    viverra nec, fringilla in, laoreet vitae, risus.
+*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+    Suspendisse id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """
+<ul>
+<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+viverra nec, fringilla in, laoreet vitae, risus.</li>
+<li>Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+Suspendisse id sem consectetuer libero luctus adipiscing.</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list7 is test do
+               var md = """
+*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+viverra nec, fringilla in, laoreet vitae, risus.
+*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+Suspendisse id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """
+<ul>
+<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+viverra nec, fringilla in, laoreet vitae, risus.</li>
+<li>Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+Suspendisse id sem consectetuer libero luctus adipiscing.</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list8 is test do
+               var md = """
+*   Bird
+
+*   Magic
+"""
+               var html = """
+<ul>
+<li>
+<p>Bird</p>
+</li>
+<li>
+<p>Magic</p>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list9 is test do
+               var md = """
+1.  This is a list item with two paragraphs. Lorem ipsum dolor
+    sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+    mi posuere lectus.
+
+    Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+    vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+    sit amet velit.
+
+2.  Suspendisse id sem consectetuer libero luctus adipiscing.
+"""
+               var html = """
+<ol>
+<li>
+<p>This is a list item with two paragraphs. Lorem ipsum dolor
+sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+mi posuere lectus.</p>
+<p>Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+sit amet velit.</p>
+</li>
+<li>
+<p>Suspendisse id sem consectetuer libero luctus adipiscing.</p>
+</li>
+</ol>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list10 is test do
+               var md = """
+*   This is a list item with two paragraphs.
+
+    This is the second paragraph in the list item. You're
+only required to indent the first line. Lorem ipsum dolor
+sit amet, consectetuer adipiscing elit.
+
+*   Another item in the same list.
+"""
+               var html = """
+<ul>
+<li>
+<p>This is a list item with two paragraphs.</p>
+<p>This is the second paragraph in the list item. You're
+only required to indent the first line. Lorem ipsum dolor
+sit amet, consectetuer adipiscing elit.</p>
+</li>
+<li>
+<p>Another item in the same list.</p>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_list_ext is test do
+               var md = """
+This is a paragraph
+* and this is a list
+"""
+               var html = """
+<p>This is a paragraph</p>
+<ul>
+<li>and this is a list</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_indented_code1 is test do
+               var md = """
+This is a normal paragraph:
+
+    This is a code block.
+"""
+               var html = """<p>This is a normal paragraph:</p>
+<pre><code>This is a code block.
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_indented_code2 is test do
+               var md = """
+Here is an example of AppleScript:
+
+    tell application "Foo"
+        beep
+    end tell
+
+    <div class="footer">
+        &copy; 2004 Foo Corporation
+    </div>
+"""
+               var html = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>tell application &quot;Foo&quot;
+    beep
+end tell
+
+&lt;div class=&quot;footer&quot;&gt;
+    &amp;copy; 2004 Foo Corporation
+&lt;/div&gt;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_indented_code3 is test do
+               var md = """
+Here is an example of AppleScript:
+
+    beep
+"""
+               var html = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>beep
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_fenced_code1 is test do
+               var md = """
+Here is an example of AppleScript:
+~~~
+tell application "Foo"
+    beep
+end tell
+
+<div class="footer">
+    &copy; 2004 Foo Corporation
+</div>
+~~~
+"""
+               var html = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>tell application &quot;Foo&quot;
+    beep
+end tell
+
+&lt;div class=&quot;footer&quot;&gt;
+    &amp;copy; 2004 Foo Corporation
+&lt;/div&gt;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_fenced_code2 is test do
+               var md = """
+Here is an example of AppleScript:
+```
+tell application "Foo"
+    beep
+end tell
+
+<div class="footer">
+    &copy; 2004 Foo Corporation
+</div>
+```
+"""
+               var html = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>tell application &quot;Foo&quot;
+    beep
+end tell
+
+&lt;div class=&quot;footer&quot;&gt;
+    &amp;copy; 2004 Foo Corporation
+&lt;/div&gt;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_fenced_code3 is test do
+               var md = """
+```nit
+print "Hello World!"
+```
+"""
+               var html = """
+<pre><code class="language-nit">print &quot;Hello World!&quot;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_fenced_code4 is test do
+               var md = """
+~~~
+print "Hello"
+~~~
+~~~
+print "World"
+~~~
+"""
+               var html = """
+<pre><code>print &quot;Hello&quot;
+</code></pre>
+<pre><code>print &quot;World&quot;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_fenced_code5 is test do
+               var md = """
+~~~
+print "Hello"
+~~~
+~~~
+print "World"
+~~~
+"""
+               var html = """
+<pre><code>print &quot;Hello&quot;
+</code></pre>
+<pre><code>print &quot;World&quot;
+</code></pre>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_nesting1 is test do
+               var md = """
+> ## This is a header.
+>
+> 1.   This is the first list item.
+> 2.   This is the second list item.
+>
+> Here's some example code:
+>
+>     return shell_exec("echo $input | $markdown_script");
+"""
+               var html = """
+<blockquote>
+<h2>This is a header.</h2>
+<ol>
+<li>This is the first list item.</li>
+<li>This is the second list item.</li>
+</ol>
+<p>Here's some example code:</p>
+<pre><code>return shell_exec(&quot;echo $input | $markdown_script&quot;);
+</code></pre>
+</blockquote>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_nesting2 is test do
+               var md = """
+*   A list item with a blockquote:
+
+    > This is a blockquote
+    > inside a list item.
+"""
+               var html = """
+<ul>
+<li>
+<p>A list item with a blockquote:</p>
+<blockquote>
+<p>This is a blockquote
+inside a list item.</p>
+</blockquote>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_nesting3 is test do
+               var md = """
+*   A list item with a code block:
+
+        <code goes here>
+"""
+               var html = """
+<ul>
+<li>
+<p>A list item with a code block:</p>
+<pre><code>&lt;code goes here&gt;
+</code></pre>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_nesting4 is test do
+               var md = """
+*      Tab
+       *       Tab
+               *       Tab
+"""
+               var html = """
+<ul>
+<li>Tab
+<ul>
+<li>Tab
+<ul>
+<li>Tab</li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_blocks_nesting5 is test do
+                       var md = """
+*      this
+
+       *       sub
+
+               that
+"""
+                       var html = """
+<ul>
+<li>
+<p>this</p>
+<ul>
+<li>
+<p>sub</p>
+<p>that</p>
+</li>
+</ul>
+</li>
+</ul>
+"""
+               assert md_to_html(md) == html
+       end
+end
diff --git a/lib/markdown2/tests/test_markdown_headings_id.nit b/lib/markdown2/tests/test_markdown_headings_id.nit
new file mode 100644 (file)
index 0000000..28f91a3
--- /dev/null
@@ -0,0 +1,61 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test for markdown headings id generation
+module test_markdown_headings_id is test
+
+import test_markdown
+
+class TestMarkdownHeadingsId
+       super TestMarkdownHtml
+       test
+
+       redef var html_renderer = new HtmlRenderer(true)
+
+       fun test_multiple_ids is test do
+               var md = """# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n"""
+               var html = """<h1 id="foo">foo</h1>\n<h2 id="foo_1">foo</h2>\n<h3 id="foo_2">foo</h3>\n<h4 id="foo_3">foo</h4>\n<h5 id="foo_4">foo</h5>\n<h6 id="foo_5">foo</h6>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_escape_ids is test do
+               var md = """# foo *bar* \\*baz\\*\n"""
+               var html = """<h1 id="foo_bar_baz">foo <em>bar</em> *baz*</h1>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_escape_ids2 is test do
+               var md = """# foo#\n"""
+               var html = """<h1 id="foo">foo#</h1>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_avoid_spaces is test do
+               var md = """#                  foo                     \n"""
+               var html = """<h1 id="foo">foo</h1>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_remove_atx_trailing is test do
+               var md = """## foo ##\n  ###   bar    ###\n"""
+               var html = """<h2 id="foo">foo</h2>\n<h3 id="bar">bar</h3>\n"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_avoid_escaped_chars is test do
+               var md = """### foo \\###\n## foo #\\##\n# foo \\#\n"""
+               var html = """<h3 id="foo_">foo ###</h3>\n<h2 id="foo__1">foo ###</h2>\n<h1 id="foo__2">foo #</h1>\n"""
+               assert md_to_html(md) == html
+       end
+end
diff --git a/lib/markdown2/tests/test_markdown_inlines.nit b/lib/markdown2/tests/test_markdown_inlines.nit
new file mode 100644 (file)
index 0000000..6a21084
--- /dev/null
@@ -0,0 +1,335 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either htmlress or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tests for markdown inline constructs
+module test_markdown_inlines is test
+
+import test_markdown
+
+class TestMarkdownInlines
+       super TestMarkdownHtml
+       test
+
+       fun test_inlines_emph1 is test do
+               var md = """
+*single asterisks*
+
+_single underscores_
+
+**double asterisks**
+
+__double underscores__
+"""
+               var html = """<p><em>single asterisks</em></p>
+<p><em>single underscores</em></p>
+<p><strong>double asterisks</strong></p>
+<p><strong>double underscores</strong></p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_emph2 is test do
+               var md = "un*frigging*believable"
+               var html = "<p>un<em>frigging</em>believable</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_emph3 is test do
+               var md = "Con _cat_ this"
+               var html = "<p>Con <em>cat</em> this</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_emph_ext is test do
+               var md = "Con_cat_this"
+               var html = "<p>Con_cat_this</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_xml1 is test do
+               var md = """
+This is a regular paragraph.
+
+<table>
+    <tr>
+        <td>Foo</td>
+    </tr>
+</table>
+
+This is another regular paragraph.
+"""
+               var html = """
+<p>This is a regular paragraph.</p>
+<table>
+    <tr>
+        <td>Foo</td>
+    </tr>
+</table>
+<p>This is another regular paragraph.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_xml2 is test do
+               var md = """
+This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.
+"""
+               var html = """
+<p>This is an image <img src="foo/bar" alt="baz"/> in a regular paragraph.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_xml3 is test do
+               var md = """
+<div style=">"/>
+"""
+               var html = """
+<div style=">"/>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_xml4 is test do
+               var md = """
+<p>This is an example of a block element that should be escaped.</p>
+<p>Idem for the second paragraph.</p>
+"""
+               assert md_to_html(md) == md
+       end
+
+       fun test_inlines_xml5 is test do
+               var md = """
+# Some more XML tests
+
+<p>This is an example of a block element that should be escaped.</p>
+<p>Idem for the second paragraph.</p>
+
+With a *md paragraph*!
+"""
+               var html = """
+<h1>Some more XML tests</h1>
+<p>This is an example of a block element that should be escaped.</p>
+<p>Idem for the second paragraph.</p>
+<p>With a <em>md paragraph</em>!</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_escape_bad_html is test do
+               var md = "-1 if < , +1 if > and 0 otherwise"
+               var html = "<p>-1 if &lt; , +1 if &gt; and 0 otherwise</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_span_code1 is test do
+               var md = "Use the `printf()` function."
+               var html = "<p>Use the <code>printf()</code> function.</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_span_code2 is test do
+               var md = "``There is a literal backtick (`) here.``"
+               var html = "<p><code>There is a literal backtick (`) here.</code></p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_span_code3 is test do
+               var md = """
+A single backtick in a code span: `` ` ``
+
+A backtick-delimited string in a code span: `` `foo` ``
+"""
+               var html = """
+<p>A single backtick in a code span: <code>`</code></p>
+<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_span_code4 is test do
+               var md = "Please don't use any `<blink>` tags."
+               var html = "<p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_span_code5 is test do
+               var md = "`&#8212;` is the decimal-encoded equivalent of `&mdash;`."
+               var html = "<p><code>&amp;#8212;</code> is the decimal-encoded equivalent of <code>&amp;mdash;</code>.</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_escape1 is test do
+               var md = "\\*this text is surrounded by literal asterisks\\*"
+               var html = "<p>*this text is surrounded by literal asterisks*</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_escape2 is test do
+               var md = "1986\\. What a great season."
+               var html = "<p>1986. What a great season.</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_escape3 is test do
+               var md = "Ben & Lux"
+               var html = "<p>Ben &amp; Lux</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link1 is test do
+               var md = """
+This is [an example](http://example.com/ "Title") inline link.
+
+[This link](http://example.net/) has no title attribute.
+"""
+               var html = """<p>This is <a href="http://example.com/" title="Title">an example</a> inline link.</p>
+<p><a href="http://example.net/">This link</a> has no title attribute.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link2 is test do
+               var md = "See my [About](/about/) page for details."
+               var html = "<p>See my <a href=\"/about/\">About</a> page for details.</p>\n"
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link3 is test do
+               var md = """
+This is [an example][id] reference-style link.
+
+Some lorem ipsum
+
+[id]: http://example.com/  "Optional Title Here"
+
+Some other lipsum
+"""
+               var html = """
+<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.</p>
+<p>Some lorem ipsum</p>
+<p>Some other lipsum</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link4 is test do
+               var md = """
+This is multiple examples: [foo][1], [bar][2], [baz][3].
+
+[1]: http://example.com/  "Optional Title Here"
+[2]: http://example.com/  'Optional Title Here'
+[3]: http://example.com/  (Optional Title Here)
+"""
+               var html = """
+<p>This is multiple examples: <a href="http://example.com/" title="Optional Title Here">foo</a>, <a href="http://example.com/" title="Optional Title Here">bar</a>, <a href="http://example.com/" title="Optional Title Here">baz</a>.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link5 is test do
+               var md = """
+This is multiple examples: [foo][a], [bar][A], [a].
+
+[a]: http://example.com/  "Optional Title Here"
+"""
+               var html = """<p>This is multiple examples: <a href="http://example.com/" title="Optional Title Here">foo</a>, <a href="http://example.com/" title="Optional Title Here">bar</a>, <a href="http://example.com/" title="Optional Title Here">a</a>.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link6 is test do
+               var md = """
+I get 10 times more traffic from [Google][] than from [Yahoo][] or [MSN][].
+
+[Google]: http://google.com/        "Google"
+[Yahoo]: http://search.yahoo.com/   "Yahoo Search"
+[MSN]: http://search.msn.com/       "MSN Search"
+"""
+               var html = """<p>I get 10 times more traffic from <a href="http://google.com/" title="Google">Google</a> than from <a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link7 is test do
+               var md = """
+Visit [Daring Fireball][] for more information.
+
+[Daring Fireball]: http://daringfireball.net/
+"""
+               var html = """<p>Visit <a href="http://daringfireball.net/">Daring Fireball</a> for more information.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link8 is test do
+               var md = """
+This one has a [line
+break].
+
+This one has a [line
+break] with a line-ending space.
+
+[line break]: /foo
+"""
+               var html = """
+<p>This one has a <a href="/foo">line
+break</a>.</p>
+<p>This one has a <a href="/foo">line
+break</a> with a line-ending space.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_link9 is test do
+               var md = """
+Foo [bar][].
+
+Foo [bar](/url/ "Title with \\"quotes\\" inside").
+
+
+[bar]: /url/ "Title with \\"quotes\\" inside"
+"""
+               var html = """
+<p>Foo <a href="/url/" title="Title with &quot;quotes&quot; inside">bar</a>.</p>
+<p>Foo <a href="/url/" title="Title with &quot;quotes&quot; inside">bar</a>.</p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_img1 is test do
+               var md = """
+![Alt text](/path/to/img.jpg)
+
+![Alt text](/path/to/img.jpg "Optional title")
+"""
+               var html = """
+<p><img src="/path/to/img.jpg" alt="Alt text" /></p>
+<p><img src="/path/to/img.jpg" alt="Alt text" title="Optional title" /></p>
+"""
+               assert md_to_html(md) == html
+       end
+
+       fun test_inlines_img2 is test do
+               var md = """
+![Alt text][id]
+
+[id]: url/to/image  "Optional title attribute"
+"""
+               var html = """
+<p><img src="url/to/image" alt="Alt text" title="Optional title attribute" /></p>
+"""
+               assert md_to_html(md) == html
+       end
+end