1use crate::ctype::{isalpha, isdigit, ispunct, isspace};
2use crate::nodes::{
3 AstNode, ListDelimType, ListType, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink,
4 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 };
405 true
406 }
407
408 fn format_front_matter(&mut self, front_matter: &[u8], entering: bool) {
409 if entering {
410 self.output(front_matter, false, Escaping::Literal);
411 }
412 }
413
414 fn format_block_quote(&mut self, entering: bool) {
415 if entering {
416 write!(self, "> ").unwrap();
417 self.begin_content = true;
418 write!(self.prefix, "> ").unwrap();
419 } else {
420 let new_len = self.prefix.len() - 2;
421 self.prefix.truncate(new_len);
422 self.blankline();
423 }
424 }
425
426 fn format_list(&mut self, node: &'a AstNode<'a>, entering: bool) {
427 let ol_start = match node.data.borrow().value {
428 NodeValue::List(NodeList {
429 list_type: ListType::Ordered,
430 start,
431 ..
432 }) => Some(start),
433 _ => None,
434 };
435
436 if entering {
437 if let Some(start) = ol_start {
438 self.ol_stack.push(start);
439 }
440 } else {
441 if ol_start.is_some() {
442 self.ol_stack.pop();
443 }
444
445 if match node.next_sibling() {
446 Some(next_sibling) => matches!(
447 next_sibling.data.borrow().value,
448 NodeValue::CodeBlock(..) | NodeValue::List(..)
449 ),
450 _ => false,
451 } {
452 self.cr();
453 write!(self, "<!-- end list -->").unwrap();
454 self.blankline();
455 }
456 }
457 }
458
459 fn format_item(&mut self, node: &'a AstNode<'a>, entering: bool) {
460 let parent = match node.parent().unwrap().data.borrow().value {
461 NodeValue::List(ref nl) => *nl,
462 _ => unreachable!(),
463 };
464
465 let mut listmarker = vec![];
466
467 let marker_width = if parent.list_type == ListType::Bullet {
468 2
469 } else {
470 let list_number = if let Some(last_stack) = self.ol_stack.last_mut() {
471 let list_number = *last_stack;
472 if entering {
473 *last_stack += 1;
474 };
475 list_number
476 } else {
477 match node.data.borrow().value {
478 NodeValue::Item(ref ni) => ni.start,
479 NodeValue::TaskItem(_) => parent.start,
480 _ => unreachable!(),
481 }
482 };
483 let list_delim = parent.delimiter;
484 write!(
485 listmarker,
486 "{}{} ",
487 list_number,
488 if list_delim == ListDelimType::Paren {
489 ")"
490 } else {
491 "."
492 }
493 )
494 .unwrap();
495 let mut current_len = listmarker.len();
496
497 while current_len < self.options.render.ol_width {
498 write!(listmarker, " ").unwrap();
499 current_len += 1;
500 }
501
502 listmarker.len()
503 };
504
505 if entering {
506 if parent.list_type == ListType::Bullet {
507 let bullet = char::from(self.options.render.list_style as u8);
508 write!(self, "{} ", bullet).unwrap();
509 } else {
510 self.write_all(&listmarker).unwrap();
511 }
512 self.begin_content = true;
513 for _ in 0..marker_width {
514 write!(self.prefix, " ").unwrap();
515 }
516 } else {
517 let new_len = if self.prefix.len() > marker_width {
518 self.prefix.len() - marker_width
519 } else {
520 0
521 };
522 self.prefix.truncate(new_len);
523 self.cr();
524 }
525 }
526
527 fn format_description_details(&mut self, entering: bool) {
528 if entering {
529 write!(self, ": ").unwrap()
530 }
531 }
532
533 fn format_heading(&mut self, nch: &NodeHeading, entering: bool) {
534 if entering {
535 for _ in 0..nch.level {
536 write!(self, "#").unwrap();
537 }
538 write!(self, " ").unwrap();
539 self.begin_content = true;
540 self.no_linebreaks = true;
541 } else {
542 self.no_linebreaks = false;
543 self.blankline();
544 }
545 }
546
547 fn format_code_block(&mut self, node: &'a AstNode<'a>, ncb: &NodeCodeBlock, entering: bool) {
548 if entering {
549 let first_in_list_item = node.previous_sibling().is_none()
550 && match node.parent() {
551 Some(parent) => {
552 matches!(
553 parent.data.borrow().value,
554 NodeValue::Item(..) | NodeValue::TaskItem(..)
555 )
556 }
557 _ => false,
558 };
559
560 if !first_in_list_item {
561 self.blankline();
562 }
563
564 let info = ncb.info.as_bytes();
565 let literal = ncb.literal.as_bytes();
566
567 #[allow(clippy::len_zero)]
568 if !(info.len() > 0
569 || literal.len() <= 2
570 || isspace(literal[0])
571 || first_in_list_item
572 || self.options.render.prefer_fenced
573 || isspace(literal[literal.len() - 1]) && isspace(literal[literal.len() - 2]))
574 {
575 write!(self, " ").unwrap();
576 write!(self.prefix, " ").unwrap();
577 self.write_all(literal).unwrap();
578 let new_len = self.prefix.len() - 4;
579 self.prefix.truncate(new_len);
580 } else {
581 let fence_char = if info.contains(&b'`') { b'~' } else { b'`' };
582 let numticks = max(3, longest_char_sequence(literal, fence_char) + 1);
583 for _ in 0..numticks {
584 write!(self, "{}", fence_char as char).unwrap();
585 }
586 if !info.is_empty() {
587 write!(self, " ").unwrap();
588 self.write_all(info).unwrap();
589 }
590 self.cr();
591 self.write_all(literal).unwrap();
592 self.cr();
593 for _ in 0..numticks {
594 write!(self, "{}", fence_char as char).unwrap();
595 }
596 }
597 self.blankline();
598 }
599 }
600
601 fn format_html_block(&mut self, nhb: &NodeHtmlBlock, entering: bool) {
602 if entering {
603 self.blankline();
604 self.write_all(nhb.literal.as_bytes()).unwrap();
605 self.blankline();
606 }
607 }
608
609 fn format_thematic_break(&mut self, entering: bool) {
610 if entering {
611 self.blankline();
612 write!(self, "-----").unwrap();
613 self.blankline();
614 }
615 }
616
617 fn format_paragraph(&mut self, entering: bool) {
618 if !entering {
619 self.blankline();
620 }
621 }
622
623 fn format_text(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
624 if entering {
625 self.output(literal, allow_wrap, Escaping::Normal);
626 }
627 }
628
629 fn format_line_break(&mut self, entering: bool, next_is_block: bool) {
630 if entering {
631 if !self.options.render.hardbreaks && !next_is_block {
632 write!(self, "\\").unwrap();
637 }
638 self.cr();
639 }
640 }
641
642 fn format_soft_break(&mut self, allow_wrap: bool, entering: bool) {
643 if entering {
644 if !self.no_linebreaks
645 && self.options.render.width == 0
646 && !self.options.render.hardbreaks
647 {
648 self.cr();
649 } else if self.options.render.hardbreaks {
650 self.output(&[b'\n'], allow_wrap, Escaping::Literal);
651 } else {
652 self.output(&[b' '], allow_wrap, Escaping::Literal);
653 }
654 }
655 }
656
657 fn format_code(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
658 if entering {
659 let numticks = shortest_unused_sequence(literal, b'`');
660 for _ in 0..numticks {
661 write!(self, "`").unwrap();
662 }
663
664 let all_space = literal
665 .iter()
666 .all(|&c| c == b' ' || c == b'\r' || c == b'\n');
667 let has_edge_space = literal[0] == b' ' || literal[literal.len() - 1] == b' ';
668 let has_edge_backtick = literal[0] == b'`' || literal[literal.len() - 1] == b'`';
669
670 let pad = literal.is_empty() || has_edge_backtick || (!all_space && has_edge_space);
671 if pad {
672 write!(self, " ").unwrap();
673 }
674 self.output(literal, allow_wrap, Escaping::Literal);
675 if pad {
676 write!(self, " ").unwrap();
677 }
678 for _ in 0..numticks {
679 write!(self, "`").unwrap();
680 }
681 }
682 }
683
684 fn format_html_inline(&mut self, literal: &[u8], entering: bool) {
685 if entering {
686 self.write_all(literal).unwrap();
687 }
688 }
689
690 fn format_raw(&mut self, literal: &[u8], entering: bool) {
691 if entering {
692 self.write_all(literal).unwrap();
693 }
694 }
695
696 fn format_strong(&mut self) {
697 write!(self, "**").unwrap();
698 }
699
700 fn format_emph(&mut self, node: &'a AstNode<'a>) {
701 let emph_delim = if match node.parent() {
702 Some(parent) => matches!(parent.data.borrow().value, NodeValue::Emph),
703 _ => false,
704 } && node.next_sibling().is_none()
705 && node.previous_sibling().is_none()
706 {
707 b'_'
708 } else {
709 b'*'
710 };
711
712 self.write_all(&[emph_delim]).unwrap();
713 }
714
715 fn format_task_item(&mut self, symbol: Option<char>, node: &'a AstNode<'a>, entering: bool) {
716 self.format_item(node, entering);
717 if entering {
718 write!(self, "[{}] ", symbol.unwrap_or(' ')).unwrap();
719 }
720 }
721
722 fn format_strikethrough(&mut self) {
723 write!(self, "~~").unwrap();
724 }
725
726 fn format_superscript(&mut self) {
727 write!(self, "^").unwrap();
728 }
729
730 fn format_underline(&mut self) {
731 write!(self, "__").unwrap();
732 }
733
734 fn format_subscript(&mut self) {
735 write!(self, "~").unwrap();
736 }
737
738 fn format_spoiler(&mut self) {
739 write!(self, "||").unwrap();
740 }
741
742 fn format_escaped_tag(&mut self, net: &String) {
743 self.output(net.as_bytes(), false, Escaping::Literal);
744 }
745
746 fn format_link(&mut self, node: &'a AstNode<'a>, nl: &NodeLink, entering: bool) -> bool {
747 if is_autolink(node, nl) {
748 if entering {
749 write!(self, "<{}>", trim_start_match(&nl.url, "mailto:")).unwrap();
750 return false;
751 }
752 } else if entering {
753 write!(self, "[").unwrap();
754 } else {
755 write!(self, "](").unwrap();
756 self.output(nl.url.as_bytes(), false, Escaping::Url);
757 if !nl.title.is_empty() {
758 write!(self, " \"").unwrap();
759 self.output(nl.title.as_bytes(), false, Escaping::Title);
760 write!(self, "\"").unwrap();
761 }
762 write!(self, ")").unwrap();
763 }
764
765 true
766 }
767
768 fn format_wikilink(&mut self, nl: &NodeWikiLink, entering: bool) -> bool {
769 if entering {
770 write!(self, "[[").unwrap();
771 if self.options.extension.wikilinks() == Some(WikiLinksMode::UrlFirst) {
772 self.output(nl.url.as_bytes(), false, Escaping::Url);
773 write!(self, "|").unwrap();
774 }
775 } else {
776 if self.options.extension.wikilinks() == Some(WikiLinksMode::TitleFirst) {
777 write!(self, "|").unwrap();
778 self.output(nl.url.as_bytes(), false, Escaping::Url);
779 }
780 write!(self, "]]").unwrap();
781 }
782
783 true
784 }
785
786 fn format_image(&mut self, nl: &NodeLink, allow_wrap: bool, entering: bool) {
787 if entering {
788 write!(self, ".unwrap();
791 self.output(nl.url.as_bytes(), false, Escaping::Url);
792 if !nl.title.is_empty() {
793 self.output(&[b' ', b'"'], allow_wrap, Escaping::Literal);
794 self.output(nl.title.as_bytes(), false, Escaping::Title);
795 write!(self, "\"").unwrap();
796 }
797 write!(self, ")").unwrap();
798 }
799 }
800
801 #[cfg(feature = "shortcodes")]
802 fn format_shortcode(&mut self, ne: &NodeShortCode, entering: bool) {
803 if entering {
804 write!(self, ":").unwrap();
805 self.output(ne.code.as_bytes(), false, Escaping::Literal);
806 write!(self, ":").unwrap();
807 }
808 }
809
810 fn format_table(&mut self, entering: bool) {
811 if entering {
812 self.custom_escape = Some(table_escape);
813 } else {
814 self.custom_escape = None;
815 }
816 self.blankline();
817 }
818
819 fn format_table_row(&mut self, entering: bool) {
820 if entering {
821 self.cr();
822 write!(self, "|").unwrap();
823 }
824 }
825
826 fn format_table_cell(&mut self, node: &'a AstNode<'a>, entering: bool) {
827 if entering {
828 write!(self, " ").unwrap();
829 } else {
830 write!(self, " |").unwrap();
831
832 let row = &node.parent().unwrap().data.borrow().value;
833 let in_header = match *row {
834 NodeValue::TableRow(header) => header,
835 _ => panic!(),
836 };
837
838 if in_header && node.next_sibling().is_none() {
839 let table = &node.parent().unwrap().parent().unwrap().data.borrow().value;
840 let alignments = match *table {
841 NodeValue::Table(NodeTable { ref alignments, .. }) => alignments,
842 _ => panic!(),
843 };
844
845 self.cr();
846 write!(self, "|").unwrap();
847 for a in alignments {
848 write!(
849 self,
850 " {} |",
851 match *a {
852 TableAlignment::Left => ":--",
853 TableAlignment::Center => ":-:",
854 TableAlignment::Right => "--:",
855 TableAlignment::None => "---",
856 }
857 )
858 .unwrap();
859 }
860 self.cr();
861 }
862 }
863 }
864 fn format_footnote_definition(&mut self, name: &str, entering: bool) {
865 if entering {
866 self.footnote_ix += 1;
867 writeln!(self, "[^{}]:", name).unwrap();
868 write!(self.prefix, " ").unwrap();
869 } else {
870 let new_len = self.prefix.len() - 4;
871 self.prefix.truncate(new_len);
872 }
873 }
874
875 fn format_footnote_reference(&mut self, r: &[u8], entering: bool) {
876 if entering {
877 self.write_all(b"[^").unwrap();
878 self.write_all(r).unwrap();
879 self.write_all(b"]").unwrap();
880 }
881 }
882
883 fn format_math(&mut self, math: &NodeMath, allow_wrap: bool, entering: bool) {
884 if entering {
885 let literal = math.literal.as_bytes();
886 let start_fence = if math.dollar_math {
887 if math.display_math {
888 "$$"
889 } else {
890 "$"
891 }
892 } else {
893 "$`"
894 };
895
896 let end_fence = if start_fence == "$`" {
897 "`$"
898 } else {
899 start_fence
900 };
901
902 self.output(start_fence.as_bytes(), false, Escaping::Literal);
903 self.output(literal, allow_wrap, Escaping::Literal);
904 self.output(end_fence.as_bytes(), false, Escaping::Literal);
905 }
906 }
907}
908
909fn longest_char_sequence(literal: &[u8], ch: u8) -> usize {
910 let mut longest = 0;
911 let mut current = 0;
912 for c in literal {
913 if *c == ch {
914 current += 1;
915 } else {
916 if current > longest {
917 longest = current;
918 }
919 current = 0;
920 }
921 }
922 if current > longest {
923 longest = current;
924 }
925 longest
926}
927
928fn shortest_unused_sequence(literal: &[u8], f: u8) -> usize {
929 let mut used = 1;
930 let mut current = 0;
931 for c in literal {
932 if *c == f {
933 current += 1;
934 } else {
935 if current > 0 {
936 used |= 1 << current;
937 }
938 current = 0;
939 }
940 }
941
942 if current > 0 {
943 used |= 1 << current;
944 }
945
946 let mut i = 0;
947 while used & 1 != 0 {
948 used >>= 1;
949 i += 1;
950 }
951 i
952}
953
954fn is_autolink<'a>(node: &'a AstNode<'a>, nl: &NodeLink) -> bool {
955 if nl.url.is_empty() || scanners::scheme(nl.url.as_bytes()).is_none() {
956 return false;
957 }
958
959 if !nl.title.is_empty() {
960 return false;
961 }
962
963 let link_text = match node.first_child() {
964 None => return false,
965 Some(child) => match child.data.borrow().value {
966 NodeValue::Text(ref t) => t.clone(),
967 _ => return false,
968 },
969 };
970
971 trim_start_match(&nl.url, "mailto:") == link_text
972}
973
974fn table_escape<'a>(node: &'a AstNode<'a>, c: u8) -> bool {
975 match node.data.borrow().value {
976 NodeValue::Table(..) | NodeValue::TableRow(..) | NodeValue::TableCell => false,
977 _ => c == b'|',
978 }
979}