1use crate::ctype::{isalpha, isdigit, ispunct, isspace};
2use crate::nodes::{
3 AstNode, ListDelimType, ListType, NodeAlert, NodeCodeBlock, NodeHeading, NodeHtmlBlock,
4 NodeLink, NodeMath, NodeTable, NodeValue, NodeWikiLink,
5};
6use crate::nodes::{NodeList, TableAlignment};
7#[cfg(feature = "shortcodes")]
8use crate::parser::shortcodes::NodeShortCode;
9use crate::parser::{Options, WikiLinksMode};
10use crate::scanners;
11use crate::strings::trim_start_match;
12use crate::{nodes, Plugins};
13
14use std::cmp::max;
15use std::io::{self, Write};
16
17pub fn format_document<'a>(
19 root: &'a AstNode<'a>,
20 options: &Options,
21 output: &mut dyn Write,
22) -> io::Result<()> {
23 #[cfg(debug_assertions)]
27 root.validate().unwrap_or_else(|e| {
28 panic!("The document to format is ill-formed: {:?}", e);
29 });
30
31 format_document_with_plugins(root, options, output, &Plugins::default())
32}
33
34pub fn format_document_with_plugins<'a>(
36 root: &'a AstNode<'a>,
37 options: &Options,
38 output: &mut dyn Write,
39 _plugins: &Plugins,
40) -> io::Result<()> {
41 let mut f = CommonMarkFormatter::new(root, options);
42 f.format(root);
43 if !f.v.is_empty() && f.v[f.v.len() - 1] != b'\n' {
44 f.v.push(b'\n');
45 }
46 output.write_all(&f.v)?;
47 Ok(())
48}
49
50struct CommonMarkFormatter<'a, 'o, 'c> {
51 node: &'a AstNode<'a>,
52 options: &'o Options<'c>,
53 v: Vec<u8>,
54 prefix: Vec<u8>,
55 column: usize,
56 need_cr: u8,
57 last_breakable: usize,
58 begin_line: bool,
59 begin_content: bool,
60 no_linebreaks: bool,
61 in_tight_list_item: bool,
62 custom_escape: Option<fn(&'a AstNode<'a>, u8) -> bool>,
63 footnote_ix: u32,
64 ol_stack: Vec<usize>,
65}
66
67#[derive(PartialEq, Clone, Copy)]
68enum Escaping {
69 Literal,
70 Normal,
71 Url,
72 Title,
73}
74
75impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
76 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
77 self.output(buf, false, Escaping::Literal);
78 Ok(buf.len())
79 }
80
81 fn flush(&mut self) -> std::io::Result<()> {
82 Ok(())
83 }
84}
85
86impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
87 fn new(node: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
88 CommonMarkFormatter {
89 node,
90 options,
91 v: vec![],
92 prefix: vec![],
93 column: 0,
94 need_cr: 0,
95 last_breakable: 0,
96 begin_line: true,
97 begin_content: true,
98 no_linebreaks: false,
99 in_tight_list_item: false,
100 custom_escape: None,
101 footnote_ix: 0,
102 ol_stack: vec![],
103 }
104 }
105
106 fn output(&mut self, buf: &[u8], wrap: bool, escaping: Escaping) {
107 let wrap = wrap && !self.no_linebreaks;
108
109 if self.in_tight_list_item && self.need_cr > 1 {
110 self.need_cr = 1;
111 }
112
113 let mut k = self.v.len() as i32 - 1;
114 while self.need_cr > 0 {
115 if k < 0 || self.v[k as usize] == b'\n' {
116 k -= 1;
117 } else {
118 self.v.push(b'\n');
119 if self.need_cr > 1 {
120 self.v.extend(&self.prefix);
121 }
122 }
123 self.column = 0;
124 self.last_breakable = 0;
125 self.begin_line = true;
126 self.begin_content = true;
127 self.need_cr -= 1;
128 }
129
130 let mut i = 0;
131 while i < buf.len() {
132 if self.begin_line {
133 self.v.extend(&self.prefix);
134 self.column = self.prefix.len();
135 }
136
137 if self.custom_escape.is_some() && self.custom_escape.unwrap()(self.node, buf[i]) {
138 self.v.push(b'\\');
139 }
140
141 let nextc = buf.get(i + 1);
142 if buf[i] == b' ' && wrap {
143 if !self.begin_line {
144 let last_nonspace = self.v.len();
145 self.v.push(b' ');
146 self.column += 1;
147 self.begin_line = false;
148 self.begin_content = false;
149 while buf.get(i + 1) == Some(&(b' ')) {
150 i += 1;
151 }
152 if !buf.get(i + 1).map_or(false, |&c| isdigit(c)) {
153 self.last_breakable = last_nonspace;
154 }
155 }
156 } else if escaping == Escaping::Literal {
157 if buf[i] == b'\n' {
158 self.v.push(b'\n');
159 self.column = 0;
160 self.begin_line = true;
161 self.begin_content = true;
162 self.last_breakable = 0;
163 } else {
164 self.v.push(buf[i]);
165 self.column += 1;
166 self.begin_line = false;
167 self.begin_content = self.begin_content && isdigit(buf[i]);
168 }
169 } else {
170 self.outc(buf[i], escaping, nextc);
171 self.begin_line = false;
172 self.begin_content = self.begin_content && isdigit(buf[i]);
173 }
174
175 if self.options.render.width > 0
176 && self.column > self.options.render.width
177 && !self.begin_line
178 && self.last_breakable > 0
179 {
180 let remainder = self.v[self.last_breakable + 1..].to_vec();
181 self.v.truncate(self.last_breakable);
182 self.v.push(b'\n');
183 self.v.extend(&self.prefix);
184 self.v.extend(&remainder);
185 self.column = self.prefix.len() + remainder.len();
186 self.last_breakable = 0;
187 self.begin_line = false;
188 self.begin_content = false;
189 }
190
191 i += 1;
192 }
193 }
194
195 fn outc(&mut self, c: u8, escaping: Escaping, nextc: Option<&u8>) {
196 let follows_digit = !self.v.is_empty() && isdigit(self.v[self.v.len() - 1]);
197
198 let nextc = nextc.map_or(0, |&c| c);
199
200 let needs_escaping = c < 0x80
201 && escaping != Escaping::Literal
202 && ((escaping == Escaping::Normal
203 && (c < 0x20
204 || c == b'*'
205 || c == b'_'
206 || c == b'['
207 || c == b']'
208 || c == b'#'
209 || c == b'<'
210 || c == b'>'
211 || c == b'\\'
212 || c == b'`'
213 || c == b'!'
214 || (c == b'&' && isalpha(nextc))
215 || (c == b'!' && nextc == 0x5b)
216 || (self.begin_content
217 && (c == b'-' || c == b'+' || c == b'=')
218 && !follows_digit)
219 || (self.begin_content
220 && (c == b'.' || c == b')')
221 && follows_digit
222 && (nextc == 0 || isspace(nextc)))))
223 || (escaping == Escaping::Url
224 && (c == b'`'
225 || c == b'<'
226 || c == b'>'
227 || isspace(c)
228 || c == b'\\'
229 || c == b')'
230 || c == b'('))
231 || (escaping == Escaping::Title
232 && (c == b'`' || c == b'<' || c == b'>' || c == b'"' || c == b'\\')));
233
234 if needs_escaping {
235 if escaping == Escaping::Url && isspace(c) {
236 write!(self.v, "%{:2X}", c).unwrap();
237 self.column += 3;
238 } else if ispunct(c) {
239 write!(self.v, "\\{}", c as char).unwrap();
240 self.column += 2;
241 } else {
242 let s = format!("&#{};", c);
243 self.write_all(s.as_bytes()).unwrap();
244 self.column += s.len();
245 }
246 } else {
247 self.v.push(c);
248 self.column += 1;
249 }
250 }
251
252 fn cr(&mut self) {
253 self.need_cr = max(self.need_cr, 1);
254 }
255
256 fn blankline(&mut self) {
257 self.need_cr = max(self.need_cr, 2);
258 }
259
260 fn format(&mut self, node: &'a AstNode<'a>) {
261 enum Phase {
262 Pre,
263 Post,
264 }
265 let mut stack = vec![(node, Phase::Pre)];
266
267 while let Some((node, phase)) = stack.pop() {
268 match phase {
269 Phase::Pre => {
270 if self.format_node(node, true) {
271 stack.push((node, Phase::Post));
272 for ch in node.reverse_children() {
273 stack.push((ch, Phase::Pre));
274 }
275 }
276 }
277 Phase::Post => {
278 self.format_node(node, false);
279 }
280 }
281 }
282 }
283
284 fn get_in_tight_list_item(&self, node: &'a AstNode<'a>) -> bool {
285 let tmp = match nodes::containing_block(node) {
286 Some(tmp) => tmp,
287 None => return false,
288 };
289
290 match tmp.data.borrow().value {
291 NodeValue::Item(..) | NodeValue::TaskItem(..) => {
292 if let NodeValue::List(ref nl) = tmp.parent().unwrap().data.borrow().value {
293 return nl.tight;
294 }
295 return false;
296 }
297 _ => {}
298 }
299
300 let parent = match tmp.parent() {
301 Some(parent) => parent,
302 None => return false,
303 };
304
305 match parent.data.borrow().value {
306 NodeValue::Item(..) | NodeValue::TaskItem(..) => {
307 if let NodeValue::List(ref nl) = parent.parent().unwrap().data.borrow().value {
308 return nl.tight;
309 }
310 }
311 _ => {}
312 }
313
314 false
315 }
316
317 fn format_node(&mut self, node: &'a AstNode<'a>, entering: bool) -> bool {
318 self.node = node;
319 let allow_wrap = self.options.render.width > 0 && !self.options.render.hardbreaks;
320
321 let parent_node = node.parent();
322 if entering {
323 if parent_node.is_some()
324 && matches!(
325 parent_node.unwrap().data.borrow().value,
326 NodeValue::Item(..) | NodeValue::TaskItem(..)
327 )
328 {
329 self.in_tight_list_item = self.get_in_tight_list_item(node);
330 }
331 } else if matches!(node.data.borrow().value, NodeValue::List(..)) {
332 self.in_tight_list_item = parent_node.is_some()
333 && matches!(
334 parent_node.unwrap().data.borrow().value,
335 NodeValue::Item(..) | NodeValue::TaskItem(..)
336 )
337 && self.get_in_tight_list_item(node);
338 }
339 let next_is_block = node
340 .next_sibling()
341 .map_or(true, |next| next.data.borrow().value.block());
342
343 match node.data.borrow().value {
344 NodeValue::Document => (),
345 NodeValue::FrontMatter(ref fm) => self.format_front_matter(fm.as_bytes(), entering),
346 NodeValue::BlockQuote => self.format_block_quote(entering),
347 NodeValue::List(..) => self.format_list(node, entering),
348 NodeValue::Item(..) => self.format_item(node, entering),
349 NodeValue::DescriptionList => (),
350 NodeValue::DescriptionItem(..) => (),
351 NodeValue::DescriptionTerm => (),
352 NodeValue::DescriptionDetails => self.format_description_details(entering),
353 NodeValue::Heading(ref nch) => self.format_heading(nch, entering),
354 NodeValue::CodeBlock(ref ncb) => self.format_code_block(node, ncb, entering),
355 NodeValue::HtmlBlock(ref nhb) => self.format_html_block(nhb, entering),
356 NodeValue::ThematicBreak => self.format_thematic_break(entering),
357 NodeValue::Paragraph => self.format_paragraph(entering),
358 NodeValue::Text(ref literal) => {
359 self.format_text(literal.as_bytes(), allow_wrap, entering)
360 }
361 NodeValue::LineBreak => self.format_line_break(entering, next_is_block),
362 NodeValue::SoftBreak => self.format_soft_break(allow_wrap, entering),
363 NodeValue::Code(ref code) => {
364 self.format_code(code.literal.as_bytes(), allow_wrap, entering)
365 }
366 NodeValue::HtmlInline(ref literal) => {
367 self.format_html_inline(literal.as_bytes(), entering)
368 }
369 NodeValue::Raw(ref literal) => self.format_raw(literal.as_bytes(), entering),
370 NodeValue::Strong => {
371 if parent_node.is_none()
372 || !matches!(parent_node.unwrap().data.borrow().value, NodeValue::Strong)
373 {
374 self.format_strong();
375 }
376 }
377 NodeValue::Emph => self.format_emph(node),
378 NodeValue::TaskItem(symbol) => self.format_task_item(symbol, node, entering),
379 NodeValue::Strikethrough => self.format_strikethrough(),
380 NodeValue::Superscript => self.format_superscript(),
381 NodeValue::Link(ref nl) => return self.format_link(node, nl, entering),
382 NodeValue::Image(ref nl) => self.format_image(nl, allow_wrap, entering),
383 #[cfg(feature = "shortcodes")]
384 NodeValue::ShortCode(ref ne) => self.format_shortcode(ne, entering),
385 NodeValue::Table(..) => self.format_table(entering),
386 NodeValue::TableRow(..) => self.format_table_row(entering),
387 NodeValue::TableCell => self.format_table_cell(node, entering),
388 NodeValue::FootnoteDefinition(ref nfd) => {
389 self.format_footnote_definition(&nfd.name, entering)
390 }
391 NodeValue::FootnoteReference(ref nfr) => {
392 self.format_footnote_reference(nfr.name.as_bytes(), entering)
393 }
394 NodeValue::MultilineBlockQuote(..) => self.format_block_quote(entering),
395 NodeValue::Escaped => {
396 }
398 NodeValue::Math(ref math) => self.format_math(math, allow_wrap, entering),
399 NodeValue::WikiLink(ref nl) => return self.format_wikilink(nl, entering),
400 NodeValue::Underline => self.format_underline(),
401 NodeValue::Subscript => self.format_subscript(),
402 NodeValue::SpoileredText => self.format_spoiler(),
403 NodeValue::EscapedTag(ref net) => self.format_escaped_tag(net),
404 NodeValue::Alert(ref alert) => self.format_alert(alert, entering),
405 };
406 true
407 }
408
409 fn format_front_matter(&mut self, front_matter: &[u8], entering: bool) {
410 if entering {
411 self.output(front_matter, false, Escaping::Literal);
412 }
413 }
414
415 fn format_block_quote(&mut self, entering: bool) {
416 if entering {
417 write!(self, "> ").unwrap();
418 self.begin_content = true;
419 write!(self.prefix, "> ").unwrap();
420 } else {
421 let new_len = self.prefix.len() - 2;
422 self.prefix.truncate(new_len);
423 self.blankline();
424 }
425 }
426
427 fn format_list(&mut self, node: &'a AstNode<'a>, entering: bool) {
428 let ol_start = match node.data.borrow().value {
429 NodeValue::List(NodeList {
430 list_type: ListType::Ordered,
431 start,
432 ..
433 }) => Some(start),
434 _ => None,
435 };
436
437 if entering {
438 if let Some(start) = ol_start {
439 self.ol_stack.push(start);
440 }
441 } else {
442 if ol_start.is_some() {
443 self.ol_stack.pop();
444 }
445
446 if match node.next_sibling() {
447 Some(next_sibling) => matches!(
448 next_sibling.data.borrow().value,
449 NodeValue::CodeBlock(..) | NodeValue::List(..)
450 ),
451 _ => false,
452 } {
453 self.cr();
454 write!(self, "<!-- end list -->").unwrap();
455 self.blankline();
456 }
457 }
458 }
459
460 fn format_item(&mut self, node: &'a AstNode<'a>, entering: bool) {
461 let parent = match node.parent().unwrap().data.borrow().value {
462 NodeValue::List(ref nl) => *nl,
463 _ => unreachable!(),
464 };
465
466 let mut listmarker = vec![];
467
468 let marker_width = if parent.list_type == ListType::Bullet {
469 2
470 } else {
471 let list_number = if let Some(last_stack) = self.ol_stack.last_mut() {
472 let list_number = *last_stack;
473 if entering {
474 *last_stack += 1;
475 };
476 list_number
477 } else {
478 match node.data.borrow().value {
479 NodeValue::Item(ref ni) => ni.start,
480 NodeValue::TaskItem(_) => parent.start,
481 _ => unreachable!(),
482 }
483 };
484 let list_delim = parent.delimiter;
485 write!(
486 listmarker,
487 "{}{} ",
488 list_number,
489 if list_delim == ListDelimType::Paren {
490 ")"
491 } else {
492 "."
493 }
494 )
495 .unwrap();
496 let mut current_len = listmarker.len();
497
498 while current_len < self.options.render.ol_width {
499 write!(listmarker, " ").unwrap();
500 current_len += 1;
501 }
502
503 listmarker.len()
504 };
505
506 if entering {
507 if parent.list_type == ListType::Bullet {
508 let bullet = char::from(self.options.render.list_style as u8);
509 write!(self, "{} ", bullet).unwrap();
510 } else {
511 self.write_all(&listmarker).unwrap();
512 }
513 self.begin_content = true;
514 for _ in 0..marker_width {
515 write!(self.prefix, " ").unwrap();
516 }
517 } else {
518 let new_len = if self.prefix.len() > marker_width {
519 self.prefix.len() - marker_width
520 } else {
521 0
522 };
523 self.prefix.truncate(new_len);
524 self.cr();
525 }
526 }
527
528 fn format_description_details(&mut self, entering: bool) {
529 if entering {
530 write!(self, ": ").unwrap()
531 }
532 }
533
534 fn format_heading(&mut self, nch: &NodeHeading, entering: bool) {
535 if entering {
536 for _ in 0..nch.level {
537 write!(self, "#").unwrap();
538 }
539 write!(self, " ").unwrap();
540 self.begin_content = true;
541 self.no_linebreaks = true;
542 } else {
543 self.no_linebreaks = false;
544 self.blankline();
545 }
546 }
547
548 fn format_code_block(&mut self, node: &'a AstNode<'a>, ncb: &NodeCodeBlock, entering: bool) {
549 if entering {
550 let first_in_list_item = node.previous_sibling().is_none()
551 && match node.parent() {
552 Some(parent) => {
553 matches!(
554 parent.data.borrow().value,
555 NodeValue::Item(..) | NodeValue::TaskItem(..)
556 )
557 }
558 _ => false,
559 };
560
561 if !first_in_list_item {
562 self.blankline();
563 }
564
565 let info = ncb.info.as_bytes();
566 let literal = ncb.literal.as_bytes();
567
568 #[allow(clippy::len_zero)]
569 if !(info.len() > 0
570 || literal.len() <= 2
571 || isspace(literal[0])
572 || first_in_list_item
573 || self.options.render.prefer_fenced
574 || isspace(literal[literal.len() - 1]) && isspace(literal[literal.len() - 2]))
575 {
576 write!(self, " ").unwrap();
577 write!(self.prefix, " ").unwrap();
578 self.write_all(literal).unwrap();
579 let new_len = self.prefix.len() - 4;
580 self.prefix.truncate(new_len);
581 } else {
582 let fence_char = if info.contains(&b'`') { b'~' } else { b'`' };
583 let numticks = max(3, longest_char_sequence(literal, fence_char) + 1);
584 for _ in 0..numticks {
585 write!(self, "{}", fence_char as char).unwrap();
586 }
587 if !info.is_empty() {
588 write!(self, " ").unwrap();
589 self.write_all(info).unwrap();
590 }
591 self.cr();
592 self.write_all(literal).unwrap();
593 self.cr();
594 for _ in 0..numticks {
595 write!(self, "{}", fence_char as char).unwrap();
596 }
597 }
598 self.blankline();
599 }
600 }
601
602 fn format_html_block(&mut self, nhb: &NodeHtmlBlock, entering: bool) {
603 if entering {
604 self.blankline();
605 self.write_all(nhb.literal.as_bytes()).unwrap();
606 self.blankline();
607 }
608 }
609
610 fn format_thematic_break(&mut self, entering: bool) {
611 if entering {
612 self.blankline();
613 write!(self, "-----").unwrap();
614 self.blankline();
615 }
616 }
617
618 fn format_paragraph(&mut self, entering: bool) {
619 if !entering {
620 self.blankline();
621 }
622 }
623
624 fn format_text(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
625 if entering {
626 self.output(literal, allow_wrap, Escaping::Normal);
627 }
628 }
629
630 fn format_line_break(&mut self, entering: bool, next_is_block: bool) {
631 if entering {
632 if !self.options.render.hardbreaks && !next_is_block {
633 write!(self, "\\").unwrap();
638 }
639 self.cr();
640 }
641 }
642
643 fn format_soft_break(&mut self, allow_wrap: bool, entering: bool) {
644 if entering {
645 if !self.no_linebreaks
646 && self.options.render.width == 0
647 && !self.options.render.hardbreaks
648 {
649 self.cr();
650 } else if self.options.render.hardbreaks {
651 self.output(&[b'\n'], allow_wrap, Escaping::Literal);
652 } else {
653 self.output(&[b' '], allow_wrap, Escaping::Literal);
654 }
655 }
656 }
657
658 fn format_code(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
659 if entering {
660 let numticks = shortest_unused_sequence(literal, b'`');
661 for _ in 0..numticks {
662 write!(self, "`").unwrap();
663 }
664
665 let all_space = literal
666 .iter()
667 .all(|&c| c == b' ' || c == b'\r' || c == b'\n');
668 let has_edge_space = literal[0] == b' ' || literal[literal.len() - 1] == b' ';
669 let has_edge_backtick = literal[0] == b'`' || literal[literal.len() - 1] == b'`';
670
671 let pad = literal.is_empty() || has_edge_backtick || (!all_space && has_edge_space);
672 if pad {
673 write!(self, " ").unwrap();
674 }
675 self.output(literal, allow_wrap, Escaping::Literal);
676 if pad {
677 write!(self, " ").unwrap();
678 }
679 for _ in 0..numticks {
680 write!(self, "`").unwrap();
681 }
682 }
683 }
684
685 fn format_html_inline(&mut self, literal: &[u8], entering: bool) {
686 if entering {
687 self.write_all(literal).unwrap();
688 }
689 }
690
691 fn format_raw(&mut self, literal: &[u8], entering: bool) {
692 if entering {
693 self.write_all(literal).unwrap();
694 }
695 }
696
697 fn format_strong(&mut self) {
698 write!(self, "**").unwrap();
699 }
700
701 fn format_emph(&mut self, node: &'a AstNode<'a>) {
702 let emph_delim = if match node.parent() {
703 Some(parent) => matches!(parent.data.borrow().value, NodeValue::Emph),
704 _ => false,
705 } && node.next_sibling().is_none()
706 && node.previous_sibling().is_none()
707 {
708 b'_'
709 } else {
710 b'*'
711 };
712
713 self.write_all(&[emph_delim]).unwrap();
714 }
715
716 fn format_task_item(&mut self, symbol: Option<char>, node: &'a AstNode<'a>, entering: bool) {
717 self.format_item(node, entering);
718 if entering {
719 write!(self, "[{}] ", symbol.unwrap_or(' ')).unwrap();
720 }
721 }
722
723 fn format_strikethrough(&mut self) {
724 write!(self, "~~").unwrap();
725 }
726
727 fn format_superscript(&mut self) {
728 write!(self, "^").unwrap();
729 }
730
731 fn format_underline(&mut self) {
732 write!(self, "__").unwrap();
733 }
734
735 fn format_subscript(&mut self) {
736 write!(self, "~").unwrap();
737 }
738
739 fn format_spoiler(&mut self) {
740 write!(self, "||").unwrap();
741 }
742
743 fn format_escaped_tag(&mut self, net: &String) {
744 self.output(net.as_bytes(), false, Escaping::Literal);
745 }
746
747 fn format_link(&mut self, node: &'a AstNode<'a>, nl: &NodeLink, entering: bool) -> bool {
748 if is_autolink(node, nl) {
749 if entering {
750 write!(self, "<{}>", trim_start_match(&nl.url, "mailto:")).unwrap();
751 return false;
752 }
753 } else if entering {
754 write!(self, "[").unwrap();
755 } else {
756 write!(self, "](").unwrap();
757 self.output(nl.url.as_bytes(), false, Escaping::Url);
758 if !nl.title.is_empty() {
759 write!(self, " \"").unwrap();
760 self.output(nl.title.as_bytes(), false, Escaping::Title);
761 write!(self, "\"").unwrap();
762 }
763 write!(self, ")").unwrap();
764 }
765
766 true
767 }
768
769 fn format_wikilink(&mut self, nl: &NodeWikiLink, entering: bool) -> bool {
770 if entering {
771 write!(self, "[[").unwrap();
772 if self.options.extension.wikilinks() == Some(WikiLinksMode::UrlFirst) {
773 self.output(nl.url.as_bytes(), false, Escaping::Url);
774 write!(self, "|").unwrap();
775 }
776 } else {
777 if self.options.extension.wikilinks() == Some(WikiLinksMode::TitleFirst) {
778 write!(self, "|").unwrap();
779 self.output(nl.url.as_bytes(), false, Escaping::Url);
780 }
781 write!(self, "]]").unwrap();
782 }
783
784 true
785 }
786
787 fn format_image(&mut self, nl: &NodeLink, allow_wrap: bool, entering: bool) {
788 if entering {
789 write!(self, ".unwrap();
792 self.output(nl.url.as_bytes(), false, Escaping::Url);
793 if !nl.title.is_empty() {
794 self.output(&[b' ', b'"'], allow_wrap, Escaping::Literal);
795 self.output(nl.title.as_bytes(), false, Escaping::Title);
796 write!(self, "\"").unwrap();
797 }
798 write!(self, ")").unwrap();
799 }
800 }
801
802 #[cfg(feature = "shortcodes")]
803 fn format_shortcode(&mut self, ne: &NodeShortCode, entering: bool) {
804 if entering {
805 write!(self, ":").unwrap();
806 self.output(ne.code.as_bytes(), false, Escaping::Literal);
807 write!(self, ":").unwrap();
808 }
809 }
810
811 fn format_table(&mut self, entering: bool) {
812 if entering {
813 self.custom_escape = Some(table_escape);
814 } else {
815 self.custom_escape = None;
816 }
817 self.blankline();
818 }
819
820 fn format_table_row(&mut self, entering: bool) {
821 if entering {
822 self.cr();
823 write!(self, "|").unwrap();
824 }
825 }
826
827 fn format_table_cell(&mut self, node: &'a AstNode<'a>, entering: bool) {
828 if entering {
829 write!(self, " ").unwrap();
830 } else {
831 write!(self, " |").unwrap();
832
833 let row = &node.parent().unwrap().data.borrow().value;
834 let in_header = match *row {
835 NodeValue::TableRow(header) => header,
836 _ => panic!(),
837 };
838
839 if in_header && node.next_sibling().is_none() {
840 let table = &node.parent().unwrap().parent().unwrap().data.borrow().value;
841 let alignments = match *table {
842 NodeValue::Table(NodeTable { ref alignments, .. }) => alignments,
843 _ => panic!(),
844 };
845
846 self.cr();
847 write!(self, "|").unwrap();
848 for a in alignments {
849 write!(
850 self,
851 " {} |",
852 match *a {
853 TableAlignment::Left => ":--",
854 TableAlignment::Center => ":-:",
855 TableAlignment::Right => "--:",
856 TableAlignment::None => "---",
857 }
858 )
859 .unwrap();
860 }
861 self.cr();
862 }
863 }
864 }
865 fn format_footnote_definition(&mut self, name: &str, entering: bool) {
866 if entering {
867 self.footnote_ix += 1;
868 writeln!(self, "[^{}]:", name).unwrap();
869 write!(self.prefix, " ").unwrap();
870 } else {
871 let new_len = self.prefix.len() - 4;
872 self.prefix.truncate(new_len);
873 }
874 }
875
876 fn format_footnote_reference(&mut self, r: &[u8], entering: bool) {
877 if entering {
878 self.write_all(b"[^").unwrap();
879 self.write_all(r).unwrap();
880 self.write_all(b"]").unwrap();
881 }
882 }
883
884 fn format_math(&mut self, math: &NodeMath, allow_wrap: bool, entering: bool) {
885 if entering {
886 let literal = math.literal.as_bytes();
887 let start_fence = if math.dollar_math {
888 if math.display_math {
889 "$$"
890 } else {
891 "$"
892 }
893 } else {
894 "$`"
895 };
896
897 let end_fence = if start_fence == "$`" {
898 "`$"
899 } else {
900 start_fence
901 };
902
903 self.output(start_fence.as_bytes(), false, Escaping::Literal);
904 self.output(literal, allow_wrap, Escaping::Literal);
905 self.output(end_fence.as_bytes(), false, Escaping::Literal);
906 }
907 }
908
909 fn format_alert(&mut self, alert: &NodeAlert, entering: bool) {
910 if entering {
911 write!(
912 self,
913 "> [!{}]",
914 alert.alert_type.default_title().to_uppercase()
915 )
916 .unwrap();
917 if alert.title.is_some() {
918 let title = alert.title.as_ref().unwrap();
919 write!(self, " {}", title).unwrap();
920 }
921 writeln!(self).unwrap();
922 write!(self, "> ").unwrap();
923 self.begin_content = true;
924 write!(self.prefix, "> ").unwrap();
925 } else {
926 let new_len = self.prefix.len() - 2;
927 self.prefix.truncate(new_len);
928 self.blankline();
929 }
930 }
931}
932
933fn longest_char_sequence(literal: &[u8], ch: u8) -> usize {
934 let mut longest = 0;
935 let mut current = 0;
936 for c in literal {
937 if *c == ch {
938 current += 1;
939 } else {
940 if current > longest {
941 longest = current;
942 }
943 current = 0;
944 }
945 }
946 if current > longest {
947 longest = current;
948 }
949 longest
950}
951
952fn shortest_unused_sequence(literal: &[u8], f: u8) -> usize {
953 let mut used = 1;
954 let mut current = 0;
955 for c in literal {
956 if *c == f {
957 current += 1;
958 } else {
959 if current > 0 {
960 used |= 1 << current;
961 }
962 current = 0;
963 }
964 }
965
966 if current > 0 {
967 used |= 1 << current;
968 }
969
970 let mut i = 0;
971 while used & 1 != 0 {
972 used >>= 1;
973 i += 1;
974 }
975 i
976}
977
978fn is_autolink<'a>(node: &'a AstNode<'a>, nl: &NodeLink) -> bool {
979 if nl.url.is_empty() || scanners::scheme(nl.url.as_bytes()).is_none() {
980 return false;
981 }
982
983 if !nl.title.is_empty() {
984 return false;
985 }
986
987 let link_text = match node.first_child() {
988 None => return false,
989 Some(child) => match child.data.borrow().value {
990 NodeValue::Text(ref t) => t.clone(),
991 _ => return false,
992 },
993 };
994
995 trim_start_match(&nl.url, "mailto:") == link_text
996}
997
998fn table_escape<'a>(node: &'a AstNode<'a>, c: u8) -> bool {
999 match node.data.borrow().value {
1000 NodeValue::Table(..) | NodeValue::TableRow(..) | NodeValue::TableCell => false,
1001 _ => c == b'|',
1002 }
1003}