1 // Copyright 2015 Google Inc. All rights reserved.
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to deal
5 // in the Software without restriction, including without limitation the rights
6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 // copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 //! HTML renderer that takes an iterator of events as input.
24 use std
::collections
::HashMap
;
27 use parse
::{Event, Tag}
;
28 use parse
::Event
::{Start, End, Text, Html, InlineHtml, SoftBreak, HardBreak, FootnoteReference}
;
30 use escape
::{escape_html, escape_href}
;
40 table_state
: TableState
,
41 table_alignments
: Vec
<Alignment
>,
42 table_cell_index
: usize,
45 impl<'a
, 'b
, I
: Iterator
<Item
=Event
<'a
>>> Ctx
<'b
, I
> {
46 fn fresh_line(&mut self) {
47 if !(self.buf
.is_empty() || self.buf
.ends_with('
\n'
)) {
52 pub fn run(&mut self) {
53 let mut numbers
= HashMap
::new();
54 while let Some(event
) = self.iter
.next() {
56 Start(tag
) => self.start_tag(tag
, &mut numbers
),
57 End(tag
) => self.end_tag(tag
),
58 Text(text
) => escape_html(self.buf
, &text
, false),
60 InlineHtml(html
) => self.buf
.push_str(&html
),
61 SoftBreak
=> self.buf
.push('
\n'
),
62 HardBreak
=> self.buf
.push_str("<br />\n"),
63 FootnoteReference(name
) => {
64 let len
= numbers
.len() + 1;
65 self.buf
.push_str("<sup class=\"footnote-reference\"><a href=\"#");
66 escape_html(self.buf
, &*name
, false);
67 self.buf
.push_str("\">");
68 let number
= numbers
.entry(name
).or_insert(len
);
69 self.buf
.push_str(&*format
!("{}", number
));
70 self.buf
.push_str("</a></sup>");
76 fn start_tag(&mut self, tag
: Tag
<'a
>, numbers
: &mut HashMap
<Cow
<'a
, str>, usize>) {
80 self.buf
.push_str("<p>");
84 self.buf
.push_str("<hr />\n")
86 Tag
::Header(level
) => {
88 self.buf
.push_str("<h");
89 self.buf
.push((b'
0'
+ level
as u8) as char);
92 Tag
::Table(alignments
) => {
93 self.table_alignments
= alignments
;
94 self.buf
.push_str("<table>");
97 self.table_state
= TableState
::Head
;
98 self.buf
.push_str("<thead><tr>");
101 self.table_cell_index
= 0;
102 self.buf
.push_str("<tr>");
105 match self.table_state
{
106 TableState
::Head
=> self.buf
.push_str("<th"),
107 TableState
::Body
=> self.buf
.push_str("<td"),
109 match self.table_alignments
.get(self.table_cell_index
) {
110 Some(&Alignment
::Left
) => self.buf
.push_str(" align=\"left\""),
111 Some(&Alignment
::Center
) => self.buf
.push_str(" align=\"center\""),
112 Some(&Alignment
::Right
) => self.buf
.push_str(" align=\"right\""),
115 self.buf
.push_str(">");
119 self.buf
.push_str("<blockquote>\n");
121 Tag
::CodeBlock(info
) => {
123 let lang
= info
.split(' '
).next().unwrap();
125 self.buf
.push_str("<pre><code>");
127 self.buf
.push_str("<pre><code class=\"language-");
128 escape_html(self.buf
, lang
, false);
129 self.buf
.push_str("\">");
132 Tag
::List(Some(1)) => {
134 self.buf
.push_str("<ol>\n");
136 Tag
::List(Some(start
)) => {
138 let _
= write
!(self.buf
, "<ol start=\"{}\">\n", start
);
142 self.buf
.push_str("<ul>\n");
146 self.buf
.push_str("<li>");
148 Tag
::Emphasis
=> self.buf
.push_str("<em>"),
149 Tag
::Strong
=> self.buf
.push_str("<strong>"),
150 Tag
::Code
=> self.buf
.push_str("<code>"),
151 Tag
::Link(dest
, title
) => {
152 self.buf
.push_str("<a href=\"");
153 escape_href(self.buf
, &dest
);
154 if !title
.is_empty() {
155 self.buf
.push_str("\" title=\"");
156 escape_html(self.buf
, &title
, false);
158 self.buf
.push_str("\">");
160 Tag
::Image(dest
, title
) => {
161 self.buf
.push_str("<img src=\"");
162 escape_href(self.buf
, &dest
);
163 self.buf
.push_str("\" alt=\"");
164 self.raw_text(numbers
);
165 if !title
.is_empty() {
166 self.buf
.push_str("\" title=\"");
167 escape_html(self.buf
, &title
, false);
169 self.buf
.push_str("\" />")
171 Tag
::FootnoteDefinition(name
) => {
173 let len
= numbers
.len() + 1;
174 self.buf
.push_str("<div class=\"footnote-definition\" id=\"");
175 escape_html(self.buf
, &*name
, false);
176 self.buf
.push_str("\"><sup class=\"footnote-definition-label\">");
177 let number
= numbers
.entry(name
).or_insert(len
);
178 self.buf
.push_str(&*format
!("{}", number
));
179 self.buf
.push_str("</sup>");
184 fn end_tag(&mut self, tag
: Tag
) {
186 Tag
::Paragraph
=> self.buf
.push_str("</p>\n"),
188 Tag
::Header(level
) => {
189 self.buf
.push_str("</h");
190 self.buf
.push((b'
0'
+ level
as u8) as char);
191 self.buf
.push_str(">\n");
194 self.buf
.push_str("</tbody></table>\n");
197 self.buf
.push_str("</tr></thead><tbody>\n");
198 self.table_state
= TableState
::Body
;
201 self.buf
.push_str("</tr>\n");
204 match self.table_state
{
205 TableState
::Head
=> self.buf
.push_str("</th>"),
206 TableState
::Body
=> self.buf
.push_str("</td>"),
208 self.table_cell_index
+= 1;
210 Tag
::BlockQuote
=> self.buf
.push_str("</blockquote>\n"),
211 Tag
::CodeBlock(_
) => self.buf
.push_str("</code></pre>\n"),
212 Tag
::List(Some(_
)) => self.buf
.push_str("</ol>\n"),
213 Tag
::List(None
) => self.buf
.push_str("</ul>\n"),
214 Tag
::Item
=> self.buf
.push_str("</li>\n"),
215 Tag
::Emphasis
=> self.buf
.push_str("</em>"),
216 Tag
::Strong
=> self.buf
.push_str("</strong>"),
217 Tag
::Code
=> self.buf
.push_str("</code>"),
218 Tag
::Link(_
, _
) => self.buf
.push_str("</a>"),
219 Tag
::Image(_
, _
) => (), // shouldn't happen, handled in start
220 Tag
::FootnoteDefinition(_
) => self.buf
.push_str("</div>\n"),
224 // run raw text, consuming end tag
225 fn raw_text
<'c
>(&mut self, numbers
: &'c
mut HashMap
<Cow
<'a
, str>, usize>) {
227 while let Some(event
) = self.iter
.next() {
229 Start(_
) => nest
+= 1,
231 if nest
== 0 { break; }
234 Text(text
) => escape_html(self.buf
, &text
, false),
236 InlineHtml(html
) => escape_html(self.buf
, &html
, false),
237 SoftBreak
| HardBreak
=> self.buf
.push(' '
),
238 FootnoteReference(name
) => {
239 let len
= numbers
.len() + 1;
240 let number
= numbers
.entry(name
).or_insert(len
);
241 self.buf
.push_str(&*format
!("[{}]", number
));
248 pub fn push_html
<'a
, I
: Iterator
<Item
=Event
<'a
>>>(buf
: &mut String
, iter
: I
) {
252 table_state
: TableState
::Head
,
253 table_alignments
: vec
![],