1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # HTML rendering of Markdown documents
16 module markdown_html_rendering
18 import markdown_rendering
19 import markdown_github
20 import markdown_wikilinks
22 # Markdown document renderer to HTML
26 # HTML output under construction
27 private var html
: Buffer is noinit
29 # Render `document` as HTML
30 redef fun render
(document
) do
33 return html
.write_to_string
36 redef fun visit
(node
) do node
.render_html
(self)
38 # Reset `headings` and internal state
41 if enable_heading_ids
then headings
.clear
46 # Used to avoid double blank lines.
47 private var last_char
: nullable Char = null
49 # Add `string` to `html`
50 private fun add
(string
: String) do
52 if not html
.is_empty
then
57 # Add a raw `html` string to the output
59 # Raw means that the string will not be escaped.
60 fun add_raw
(html
: String) do add html
62 # Add `text` string to the output
64 # The string will be escaped.
65 fun add_text
(text
: String) do add html_escape
(text
, true)
67 # Add a blank line to the output
69 if last_char
!= null and last_char
!= '\n' then
74 # Escape `string` to HTML
76 # When `keep_entities`, HTML entities will not be escaped.
77 fun html_escape
(string
: String, keep_entities
: Bool): String do
78 var buf
: nullable Buffer = null
79 for i
in [0..string
.length
[ do
80 var c
= string
.chars
[i
]
82 if c
== '&' and (not keep_entities
or string
.search_from
(re_entity
, i
) == null) then
91 if buf
!= null then buf
.add c
96 for j
in [0..i
[ do buf
.add string
.chars
[j
]
101 if buf
== null then return string
105 # HTML entity pattern
106 private var re_entity
: Regex = "^&(#x[a-f0-9]\{1,8\}|#[0-9]\{1,8\}|[a-z][a-z0-9]\{1,31\});".to_re
108 # Encode the `uri` string
109 fun encode_uri
(uri
: String): String do
113 while i
< uri
.length
do
115 if (c
>= '0' and c
<= '9') or
116 (c
>= 'a' and c
<= 'z') or
117 (c
>= 'A' and c
<= 'Z') or
118 c
== ';' or c
== ',' or c
== '/' or c
== '?' or
119 c
== ':' or c
== '@' or c
== '=' or c
== '+' or
120 c
== '$' or c
== '-' or c
== '_' or c
== '.' or
121 c
== '!' or c
== '~' or c
== '*' or c
== '(' or
122 c
== ')' or c
== '#' or c
== '\''
125 else if c == '&' then
127 else if c == '%' and uri.search_from(re_uri_code, i) != null then
128 buf.append uri.substring(i, 3)
131 var bytes = c.to_s.bytes
132 for b in bytes do buf.append "%{b.to_i.to_hex}".to_upper
141 private var re_uri_code: Regex = "^%[a-zA-Z0-9]\{2\}".to_re
143 # Add `id` tags to headings
144 var enable_heading_ids = false is optional, writable
146 # Associate headings ids to blocks
147 var headings = new ArrayMap[String, MdHeading]
150 fun strip_id(text: String): String do
152 var b = new FlatBuffer
157 if not c.is_letter and
159 not allowed_id_chars.has(c) then continue
164 if res.is_empty then res = "_"
166 # check for multiple id definitions
167 if headings.has_key(key) then
170 while headings.has_key(key) do
178 # Allowed characters in ids
179 var allowed_id_chars: Array[Char] = ['-', '_
', ':', '.']
184 # Render `self` as HTML
185 fun render_html(v: HtmlRenderer) do visit_all(v)
190 redef class MdBlockQuote
191 redef fun render_html(v) do
193 v.add_raw "<blockquote>"
197 v.add_raw "</blockquote>"
202 redef class MdCodeBlock
203 redef fun render_html(v) do
208 if info != null and not info.is_empty then
209 v.add_raw " class=\"language-{info.split(" ").first}\""
212 var literal = self.literal or else ""
213 var lines = literal.split("\n")
214 for i in [0..lines.length[ do
216 v.add_raw v.html_escape(line, false)
217 if i < lines.length - 1 then
227 redef class MdHeading
228 redef fun render_html(v) do
230 if v.enable_heading_ids then
233 id = v.strip_id(title)
234 v.headings[id] = self
237 v.add_raw "<h{level} id=\"{id}\">"
239 v.add_raw "<h{level}>"
242 v.add_raw "</h{level}>"
247 var id: nullable String = null
251 var v = new RawTextVisitor
252 return v.render(self)
256 redef class MdUnorderedList
257 redef fun render_html(v) do
268 redef class MdOrderedList
269 redef fun render_html(v) do
270 var start = self.start_number
274 v.add_raw " start=\"{start}\""
285 redef class MdListItem
286 redef fun render_html(v) do
294 redef class MdParagraph
295 redef fun render_html(v) do
296 var is_tight = is_in_tight_list
309 redef class MdThematicBreak
310 redef fun render_html(v) do
317 redef class MdHtmlBlock
318 redef fun render_html(v) do
320 var literal = self.literal or else ""
321 var lines = literal.split("\n")
322 for i in [0..lines.length[ do
324 if not line.trim.is_empty then
327 if i < lines.length - 1 then
337 redef class MdHardLineBreak
338 redef fun render_html(v) do
344 redef class MdSoftLineBreak
345 redef fun render_html(v) do
351 redef fun render_html(v) do
353 v.add_raw v.html_escape(literal, false)
358 redef class MdEmphasis
359 redef fun render_html(v) do
366 redef class MdStrongEmphasis
367 redef fun render_html(v) do
370 v.add_raw "</strong>"
374 redef class MdHtmlInline
375 redef fun render_html(v) do
381 redef fun render_html(v) do
382 var url = self.destination
383 var title = self.title
385 v.add_raw " src=\"{v.encode_uri(url)}\""
387 var alt_text = self.alt_text
388 v.add_raw " alt=\"{alt_text}\""
390 if title != null and not title.is_empty then
391 v.add_raw " title=\""
399 private fun alt_text: String do
400 var v = new RawTextVisitor
401 return v.render(self)
406 redef fun render_html(v) do
407 var url = self.destination
408 var title = self.title
410 v.add_raw " href=\"{v.encode_uri(url)}\""
411 if title != null and not title.is_empty then
412 v.add_raw " title=\""
423 redef fun render_html(v) do
431 redef fun render_html(v) do
439 redef fun render_html(v) do
448 redef class MdWikilink
450 # Dummy rendering of wikilinks
452 # Clients should redefine this.
453 redef fun render_html(v) do
454 v.add_raw "<wiki link=\"{v.encode_uri(link)}\">"