at main 652 lines 24 kB view raw
1// use std::{string::String, vec::Vec}; 2// #[cfg(all(feature = "std", not(feature = "hashbrown")))] 3// use std::collections::HashMap; 4 5// #[cfg(feature = "hashbrown")] 6// use hashbrown::HashMap; 7// 8use std::collections::HashMap; 9//#[cfg(feature = "std")] 10use markdown_weaver_escape::IoWriter; 11use markdown_weaver_escape::{ 12 FmtWriter, StrWrite, escape_href, escape_html, escape_html_body_text, 13}; 14 15use markdown_weaver::{ 16 Alignment, BlockQuoteKind, CodeBlockKind, CowStr, Event, Event::*, LinkType, Tag, TagEnd, 17}; 18 19pub enum TableState { 20 Head, 21 Body, 22} 23 24struct HtmlWriter<'a, I, W> { 25 /// Iterator supplying events. 26 iter: I, 27 28 /// Writer to write to. 29 writer: W, 30 31 /// Whether or not the last write wrote a newline. 32 end_newline: bool, 33 34 /// Whether if inside a metadata block (text should not be written) 35 in_non_writing_block: bool, 36 37 table_state: TableState, 38 table_alignments: Vec<Alignment>, 39 table_cell_index: usize, 40 numbers: HashMap<CowStr<'a>, usize>, 41 /// Buffered paragraph opening tag (without closing `>`) for dir attribute emission 42 pending_paragraph_open: Option<String>, 43} 44 45impl<'a, I, W> HtmlWriter<'a, I, W> 46where 47 I: Iterator<Item = Event<'a>>, 48 W: StrWrite, 49{ 50 fn new(iter: I, writer: W) -> Self { 51 Self { 52 iter, 53 writer, 54 end_newline: true, 55 in_non_writing_block: false, 56 table_state: TableState::Head, 57 table_alignments: vec![], 58 table_cell_index: 0, 59 numbers: HashMap::new(), 60 pending_paragraph_open: None, 61 } 62 } 63 64 /// Writes a new line. 65 #[inline] 66 fn write_newline(&mut self) -> Result<(), W::Error> { 67 self.end_newline = true; 68 self.writer.write_str("\n") 69 } 70 71 /// Writes a buffer, and tracks whether or not a newline was written. 72 #[inline] 73 fn write(&mut self, s: &str) -> Result<(), W::Error> { 74 self.writer.write_str(s)?; 75 76 if !s.is_empty() { 77 self.end_newline = s.ends_with('\n'); 78 } 79 Ok(()) 80 } 81 82 fn run(mut self) -> Result<(), W::Error> { 83 while let Some(event) = self.iter.next() { 84 match event { 85 Start(tag) => { 86 self.start_tag(tag)?; 87 } 88 End(tag) => { 89 self.end_tag(tag)?; 90 } 91 Text(text) => { 92 if !self.in_non_writing_block { 93 // Flush pending paragraph with dir attribute if needed 94 if let Some(opening) = self.pending_paragraph_open.take() { 95 if let Some(dir) = crate::utils::detect_text_direction(&text) { 96 self.write(&opening)?; 97 self.write(" dir=\"")?; 98 self.write(dir)?; 99 self.write("\">")?; 100 } else { 101 self.write(&opening)?; 102 self.write(">")?; 103 } 104 } 105 escape_html_body_text(&mut self.writer, &text)?; 106 self.end_newline = text.ends_with('\n'); 107 } 108 } 109 Code(text) => { 110 self.write("<code>")?; 111 escape_html_body_text(&mut self.writer, &text)?; 112 self.write("</code>")?; 113 } 114 InlineMath(text) => match crate::math::render_math(&text, false) { 115 crate::math::MathResult::Success(mathml) => { 116 self.write(r#"<span class="math math-inline">"#)?; 117 self.write(&mathml)?; 118 self.write("</span>")?; 119 } 120 crate::math::MathResult::Error { html, .. } => { 121 self.write(&html)?; 122 } 123 }, 124 DisplayMath(text) => match crate::math::render_math(&text, true) { 125 crate::math::MathResult::Success(mathml) => { 126 self.write(r#"<span class="math math-display">"#)?; 127 self.write(&mathml)?; 128 self.write("</span>")?; 129 } 130 crate::math::MathResult::Error { html, .. } => { 131 self.write(&html)?; 132 } 133 }, 134 Html(html) | InlineHtml(html) => { 135 self.write(&html)?; 136 } 137 SoftBreak => { 138 self.write_newline()?; 139 } 140 HardBreak => { 141 self.write("<br />\n")?; 142 } 143 Rule => { 144 if self.end_newline { 145 self.write("<hr />\n")?; 146 } else { 147 self.write("\n<hr />\n")?; 148 } 149 } 150 FootnoteReference(name) => { 151 let len = self.numbers.len() + 1; 152 self.write("<sup class=\"footnote-reference\"><a href=\"#")?; 153 escape_html(&mut self.writer, &name)?; 154 self.write("\">")?; 155 let number = *self.numbers.entry(name).or_insert(len); 156 write!(&mut self.writer, "{}", number)?; 157 self.write("</a></sup>")?; 158 } 159 TaskListMarker(true) => { 160 self.write("<input disabled=\"\" type=\"checkbox\" checked=\"\" aria-label=\"Completed task\"/>\n")?; 161 } 162 TaskListMarker(false) => { 163 self.write("<input disabled=\"\" type=\"checkbox\" aria-label=\"Incomplete task\"/>\n")?; 164 } 165 WeaverBlock(_text) => {} 166 } 167 } 168 Ok(()) 169 } 170 171 /// Writes the start of an HTML tag. 172 fn start_tag(&mut self, tag: Tag<'a>) -> Result<(), W::Error> { 173 match tag { 174 Tag::HtmlBlock => Ok(()), 175 Tag::Paragraph(_) => { 176 // Buffer paragraph opening for dir attribute detection 177 let opening = if self.end_newline { 178 String::from("<p") 179 } else { 180 String::from("\n<p") 181 }; 182 self.pending_paragraph_open = Some(opening); 183 Ok(()) 184 } 185 Tag::Heading { 186 level, 187 id, 188 classes, 189 attrs, 190 } => { 191 if self.end_newline { 192 self.write("<")?; 193 } else { 194 self.write("\n<")?; 195 } 196 write!(&mut self.writer, "{}", level)?; 197 if let Some(id) = id { 198 self.write(" id=\"")?; 199 escape_html(&mut self.writer, &id)?; 200 self.write("\"")?; 201 } 202 let mut classes = classes.iter(); 203 if let Some(class) = classes.next() { 204 self.write(" class=\"")?; 205 escape_html(&mut self.writer, class)?; 206 for class in classes { 207 self.write(" ")?; 208 escape_html(&mut self.writer, class)?; 209 } 210 self.write("\"")?; 211 } 212 for (attr, value) in attrs { 213 self.write(" ")?; 214 escape_html(&mut self.writer, &attr)?; 215 if let Some(val) = value { 216 self.write("=\"")?; 217 escape_html(&mut self.writer, &val)?; 218 self.write("\"")?; 219 } else { 220 self.write("=\"\"")?; 221 } 222 } 223 self.write(">") 224 } 225 Tag::Table(alignments) => { 226 self.table_alignments = alignments; 227 self.write("<table>") 228 } 229 Tag::TableHead => { 230 self.table_state = TableState::Head; 231 self.table_cell_index = 0; 232 self.write("<thead><tr>") 233 } 234 Tag::TableRow => { 235 self.table_cell_index = 0; 236 self.write("<tr>") 237 } 238 Tag::TableCell => { 239 match self.table_state { 240 TableState::Head => { 241 self.write("<th")?; 242 } 243 TableState::Body => { 244 self.write("<td")?; 245 } 246 } 247 match self.table_alignments.get(self.table_cell_index) { 248 Some(&Alignment::Left) => self.write(" style=\"text-align: left\">"), 249 Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"), 250 Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"), 251 _ => self.write(">"), 252 } 253 } 254 Tag::BlockQuote(kind) => { 255 let class_str = match kind { 256 None => "", 257 Some(kind) => match kind { 258 BlockQuoteKind::Note => " class=\"markdown-alert-note\"", 259 BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"", 260 BlockQuoteKind::Important => " class=\"markdown-alert-important\"", 261 BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"", 262 BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"", 263 }, 264 }; 265 if self.end_newline { 266 self.write(&format!("<blockquote{}>\n", class_str)) 267 } else { 268 self.write(&format!("\n<blockquote{}>\n", class_str)) 269 } 270 } 271 Tag::CodeBlock(info) => { 272 if !self.end_newline { 273 self.write_newline()?; 274 } 275 match info { 276 CodeBlockKind::Fenced(info) => { 277 let lang = info.split(' ').next().unwrap(); 278 if lang.is_empty() { 279 self.write("<pre><code>") 280 } else { 281 self.write("<pre><code class=\"language-")?; 282 escape_html(&mut self.writer, lang)?; 283 self.write("\">") 284 } 285 } 286 CodeBlockKind::Indented => self.write("<pre><code>"), 287 } 288 } 289 Tag::List(Some(1)) => { 290 if self.end_newline { 291 self.write("<ol>\n") 292 } else { 293 self.write("\n<ol>\n") 294 } 295 } 296 Tag::List(Some(start)) => { 297 if self.end_newline { 298 self.write("<ol start=\"")?; 299 } else { 300 self.write("\n<ol start=\"")?; 301 } 302 write!(&mut self.writer, "{}", start)?; 303 self.write("\">\n") 304 } 305 Tag::List(None) => { 306 if self.end_newline { 307 self.write("<ul>\n") 308 } else { 309 self.write("\n<ul>\n") 310 } 311 } 312 Tag::Item => { 313 if self.end_newline { 314 self.write("<li>") 315 } else { 316 self.write("\n<li>") 317 } 318 } 319 Tag::DefinitionList => { 320 if self.end_newline { 321 self.write("<dl>\n") 322 } else { 323 self.write("\n<dl>\n") 324 } 325 } 326 Tag::DefinitionListTitle => { 327 if self.end_newline { 328 self.write("<dt>") 329 } else { 330 self.write("\n<dt>") 331 } 332 } 333 Tag::DefinitionListDefinition => { 334 if self.end_newline { 335 self.write("<dd>") 336 } else { 337 self.write("\n<dd>") 338 } 339 } 340 Tag::Subscript => self.write("<sub>"), 341 Tag::Superscript => self.write("<sup>"), 342 Tag::Emphasis => self.write("<em>"), 343 Tag::Strong => self.write("<strong>"), 344 Tag::Strikethrough => self.write("<del>"), 345 Tag::Link { 346 link_type: LinkType::Email, 347 dest_url, 348 title, 349 id: _, 350 } => { 351 self.write("<a href=\"mailto:")?; 352 escape_href(&mut self.writer, &dest_url)?; 353 if !title.is_empty() { 354 self.write("\" title=\"")?; 355 escape_html(&mut self.writer, &title)?; 356 } 357 self.write("\">") 358 } 359 Tag::Link { 360 link_type: _, 361 dest_url, 362 title, 363 id: _, 364 } => { 365 self.write("<a href=\"")?; 366 escape_href(&mut self.writer, &dest_url)?; 367 if !title.is_empty() { 368 self.write("\" title=\"")?; 369 escape_html(&mut self.writer, &title)?; 370 } 371 self.write("\">") 372 } 373 Tag::Image { 374 link_type: _, 375 dest_url, 376 title, 377 id: _, 378 attrs, 379 } => { 380 self.write("<img src=\"")?; 381 escape_href(&mut self.writer, &dest_url)?; 382 if let Some(attrs) = attrs { 383 if !attrs.classes.is_empty() { 384 self.write("\" class=\"")?; 385 for class in &attrs.classes { 386 escape_html(&mut self.writer, class)?; 387 self.write(" ")?; 388 } 389 self.write("\" ")?; 390 } else { 391 self.write("\" ")?; 392 } 393 if !attrs.attrs.is_empty() { 394 for (attr, value) in &attrs.attrs { 395 escape_html(&mut self.writer, attr)?; 396 self.write("=\"")?; 397 escape_html(&mut self.writer, value)?; 398 self.write("\" ")?; 399 } 400 } 401 } else { 402 self.write("\" ")?; 403 } 404 self.write("alt=\"")?; 405 self.raw_text()?; 406 if !title.is_empty() { 407 self.write("\" title=\"")?; 408 escape_html(&mut self.writer, &title)?; 409 } 410 self.write("\" />") 411 } 412 Tag::Embed { 413 embed_type: _, 414 dest_url, 415 title, 416 id, 417 attrs, 418 } => { 419 // rewrite this to work correctly 420 self.write("<iframe src=\"")?; 421 escape_href(&mut self.writer, &dest_url)?; 422 self.write("\" title=\"")?; 423 escape_html(&mut self.writer, &title)?; 424 if !id.is_empty() { 425 self.write("\" id=\"")?; 426 escape_html(&mut self.writer, &id)?; 427 self.write("\"")?; 428 } 429 if let Some(attrs) = attrs { 430 self.write(" ")?; 431 if !attrs.classes.is_empty() { 432 self.write("class=\"")?; 433 for class in &attrs.classes { 434 escape_html(&mut self.writer, class)?; 435 self.write(" ")?; 436 } 437 self.write("\" ")?; 438 } 439 if !attrs.attrs.is_empty() { 440 for (attr, value) in &attrs.attrs { 441 escape_html(&mut self.writer, attr)?; 442 self.write("=\"")?; 443 escape_html(&mut self.writer, value)?; 444 self.write("\" ")?; 445 } 446 } 447 } 448 self.write("/>") 449 } 450 Tag::WeaverBlock(_, _attrs) => { 451 println!("Weaver block"); 452 self.in_non_writing_block = true; 453 Ok(()) 454 } 455 Tag::FootnoteDefinition(name) => { 456 if self.end_newline { 457 self.write("<div class=\"footnote-definition\" id=\"")?; 458 } else { 459 self.write("\n<div class=\"footnote-definition\" id=\"")?; 460 } 461 escape_html(&mut self.writer, &name)?; 462 self.write("\"><sup class=\"footnote-definition-label\">")?; 463 let len = self.numbers.len() + 1; 464 let number = *self.numbers.entry(name).or_insert(len); 465 write!(&mut self.writer, "{}", number)?; 466 self.write("</sup>") 467 } 468 Tag::MetadataBlock(_) => { 469 self.in_non_writing_block = true; 470 Ok(()) 471 } 472 } 473 } 474 475 fn end_tag(&mut self, tag: TagEnd) -> Result<(), W::Error> { 476 match tag { 477 TagEnd::HtmlBlock => {} 478 TagEnd::Paragraph(_) => { 479 // Flush any pending paragraph open (for empty paragraphs) 480 if let Some(opening) = self.pending_paragraph_open.take() { 481 self.write(&opening)?; 482 self.write(">")?; 483 } 484 self.write("</p>\n")?; 485 } 486 TagEnd::Heading(level) => { 487 self.write("</")?; 488 write!(&mut self.writer, "{}", level)?; 489 self.write(">\n")?; 490 } 491 TagEnd::Table => { 492 self.write("</tbody></table>\n")?; 493 } 494 TagEnd::TableHead => { 495 self.write("</tr></thead><tbody>\n")?; 496 self.table_state = TableState::Body; 497 } 498 TagEnd::TableRow => { 499 self.write("</tr>\n")?; 500 } 501 TagEnd::TableCell => { 502 match self.table_state { 503 TableState::Head => { 504 self.write("</th>")?; 505 } 506 TableState::Body => { 507 self.write("</td>")?; 508 } 509 } 510 self.table_cell_index += 1; 511 } 512 TagEnd::BlockQuote(_) => { 513 self.write("</blockquote>\n")?; 514 } 515 TagEnd::CodeBlock => { 516 self.write("</code></pre>\n")?; 517 } 518 TagEnd::List(true) => { 519 self.write("</ol>\n")?; 520 } 521 TagEnd::List(false) => { 522 self.write("</ul>\n")?; 523 } 524 TagEnd::Item => { 525 self.write("</li>\n")?; 526 } 527 TagEnd::DefinitionList => { 528 self.write("</dl>\n")?; 529 } 530 TagEnd::DefinitionListTitle => { 531 self.write("</dt>\n")?; 532 } 533 TagEnd::DefinitionListDefinition => { 534 self.write("</dd>\n")?; 535 } 536 TagEnd::Emphasis => { 537 self.write("</em>")?; 538 } 539 TagEnd::Superscript => { 540 self.write("</sup>")?; 541 } 542 TagEnd::Subscript => { 543 self.write("</sub>")?; 544 } 545 TagEnd::Strong => { 546 self.write("</strong>")?; 547 } 548 TagEnd::Strikethrough => { 549 self.write("</del>")?; 550 } 551 TagEnd::Link => { 552 self.write("</a>")?; 553 } 554 TagEnd::Image => (), // shouldn't happen, handled in start 555 TagEnd::Embed => (), // shouldn't happen, handled in start 556 TagEnd::WeaverBlock(_) => { 557 self.in_non_writing_block = false; 558 } 559 TagEnd::FootnoteDefinition => { 560 self.write("</div>\n")?; 561 } 562 TagEnd::MetadataBlock(_) => { 563 self.in_non_writing_block = false; 564 } 565 } 566 Ok(()) 567 } 568 569 // run raw text, consuming end tag 570 fn raw_text(&mut self) -> Result<(), W::Error> { 571 let mut nest = 0; 572 while let Some(event) = self.iter.next() { 573 match event { 574 Start(_) => nest += 1, 575 End(_) => { 576 if nest == 0 { 577 break; 578 } 579 nest -= 1; 580 } 581 Html(_) => {} 582 InlineHtml(text) | Code(text) | Text(text) => { 583 // Don't use escape_html_body_text here. 584 // The output of this function is used in the `alt` attribute. 585 escape_html(&mut self.writer, &text)?; 586 self.end_newline = text.ends_with('\n'); 587 } 588 InlineMath(text) => { 589 self.write("$")?; 590 escape_html(&mut self.writer, &text)?; 591 self.write("$")?; 592 } 593 DisplayMath(text) => { 594 self.write("$$")?; 595 escape_html(&mut self.writer, &text)?; 596 self.write("$$")?; 597 } 598 SoftBreak | HardBreak | Rule => { 599 self.write(" ")?; 600 } 601 FootnoteReference(name) => { 602 let len = self.numbers.len() + 1; 603 let number = *self.numbers.entry(name).or_insert(len); 604 write!(&mut self.writer, "[{}]", number)?; 605 } 606 TaskListMarker(true) => self.write("[x]")?, 607 TaskListMarker(false) => self.write("[ ]")?, 608 WeaverBlock(_) => { 609 println!("Weaver block internal"); 610 } 611 } 612 } 613 Ok(()) 614 } 615} 616 617/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and 618/// push it to a `String`. 619pub fn push_html<'a, I>(s: &mut String, iter: I) 620where 621 I: Iterator<Item = Event<'a>>, 622{ 623 write_html_fmt(s, iter).unwrap() 624} 625 626/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and 627/// write it out to an I/O stream. 628/// 629/// **Note**: using this function with an unbuffered writer like a file or socket 630/// will result in poor performance. Wrap these in a 631/// [`BufWriter`](https://doc.rust-lang.org/std/io/struct.BufWriter.html) to 632/// prevent unnecessary slowdowns. 633 634//#[cfg(feature = "std")] 635pub fn write_html_io<'a, I, W>(writer: W, iter: I) -> std::io::Result<()> 636where 637 I: Iterator<Item = Event<'a>>, 638 W: std::io::Write, 639{ 640 HtmlWriter::new(iter, IoWriter(writer)).run() 641} 642 643/// Iterate over an `Iterator` of `Event`s, generate HTML for each `Event`, and 644/// write it into Unicode-accepting buffer or stream. 645 646pub fn write_html_fmt<'a, I, W>(writer: W, iter: I) -> core::fmt::Result 647where 648 I: Iterator<Item = Event<'a>>, 649 W: core::fmt::Write, 650{ 651 HtmlWriter::new(iter, FmtWriter(writer)).run() 652}