1#[cfg(feature = "gfm")]
6use crate::ast::TableAlignment;
7use crate::ast::{
8 CodeBlockType, CustomNode, CustomNodeWriter, HeadingType, HtmlElement, ListItem, Node,
9};
10use crate::error::{WriteError, WriteResult};
11use crate::options::WriterOptions;
12use std::fmt::{self};
13
14use super::processors::{
15 BlockNodeProcessor, CustomNodeProcessor, InlineNodeProcessor, NodeProcessor,
16};
17
18#[derive(Debug)]
22pub struct CommonMarkWriter {
23 options: WriterOptions,
24 buffer: String,
25}
26
27impl CommonMarkWriter {
28 pub fn new() -> Self {
41 Self::with_options(WriterOptions::default())
42 }
43
44 pub fn with_options(options: WriterOptions) -> Self {
65 Self {
66 options,
67 buffer: String::new(),
68 }
69 }
70
71 pub(crate) fn is_strict_mode(&self) -> bool {
73 self.options.strict
74 }
75
76 fn apply_prefix(&self, content: &str, prefix: &str, first_line_prefix: Option<&str>) -> String {
88 if content.is_empty() {
89 return String::new();
90 }
91
92 let mut result = String::new();
93 let lines: Vec<&str> = content.lines().collect();
94
95 if !lines.is_empty() {
96 let actual_prefix = first_line_prefix.unwrap_or(prefix);
97 result.push_str(actual_prefix);
98 result.push_str(lines[0]);
99 }
100
101 for line in &lines[1..] {
102 result.push('\n');
103 result.push_str(prefix);
104 result.push_str(line);
105 }
106
107 result
108 }
109
110 pub fn write(&mut self, node: &Node) -> WriteResult<()> {
130 if let Node::Custom(_) = node {
131 return CustomNodeProcessor.process(self, node);
132 }
133
134 if node.is_block() {
135 BlockNodeProcessor.process(self, node)
136 } else if node.is_inline() {
137 InlineNodeProcessor.process(self, node)
138 } else {
139 Err(WriteError::UnsupportedNodeType)
141 }
142 }
143
144 #[allow(clippy::borrowed_box)]
146 pub(crate) fn write_custom_node(&mut self, node: &Box<dyn CustomNode>) -> WriteResult<()> {
147 node.write(self)
148 }
149
150 pub(crate) fn get_context_for_node(&self, node: &Node) -> String {
152 match node {
153 Node::Text(_) => "Text".to_string(),
154 Node::Emphasis(_) => "Emphasis".to_string(),
155 Node::Strong(_) => "Strong".to_string(),
156 #[cfg(feature = "gfm")]
157 Node::Strikethrough(_) => "Strikethrough".to_string(),
158 Node::InlineCode(_) => "InlineCode".to_string(),
159 Node::Link { .. } => "Link content".to_string(),
160 Node::Image { .. } => "Image alt text".to_string(),
161 Node::HtmlElement(_) => "HtmlElement content".to_string(),
162 Node::Custom(_) => "Custom node".to_string(),
163 _ => "Unknown inline element".to_string(),
164 }
165 }
166
167 pub(crate) fn check_no_newline(&self, node: &Node, context: &str) -> WriteResult<()> {
169 if Self::node_contains_newline(node) {
170 return Err(WriteError::NewlineInInlineElement(context.to_string()));
171 }
172 Ok(())
173 }
174
175 fn node_contains_newline(node: &Node) -> bool {
177 match node {
178 Node::Text(s) | Node::InlineCode(s) => s.contains('\n'),
179 Node::Emphasis(children) | Node::Strong(children) => {
180 children.iter().any(Self::node_contains_newline)
181 }
182 #[cfg(feature = "gfm")]
183 Node::Strikethrough(children) => children.iter().any(Self::node_contains_newline),
184 Node::HtmlElement(element) => element.children.iter().any(Self::node_contains_newline),
185 Node::Link { content, .. } => content.iter().any(Self::node_contains_newline),
186 Node::Image { alt, .. } => alt.iter().any(Self::node_contains_newline),
187 Node::SoftBreak | Node::HardBreak => true,
188 Node::Custom(_) => false,
190 _ => false,
191 }
192 }
193
194 pub(crate) fn write_text_content(&mut self, content: &str) -> WriteResult<()> {
196 if self.options.escape_special_chars {
197 let escaped = content
198 .replace('\\', "\\\\")
199 .replace('*', "\\*")
200 .replace('_', "\\_")
201 .replace('[', "\\[")
202 .replace(']', "\\]")
203 .replace('<', "\\<")
204 .replace('>', "\\>")
205 .replace('`', "\\`");
206
207 self.write_str(&escaped)?;
208 } else {
209 self.write_str(content)?;
210 }
211
212 Ok(())
213 }
214
215 pub(crate) fn write_code_content(&mut self, content: &str) -> WriteResult<()> {
217 self.write_char('`')?;
218 self.write_str(content)?;
219 self.write_char('`')?;
220 Ok(())
221 }
222
223 pub(crate) fn write_delimited(&mut self, content: &[Node], delimiter: &str) -> WriteResult<()> {
225 self.write_str(delimiter)?;
226
227 for node in content {
228 self.write(node)?;
229 }
230
231 self.write_str(delimiter)?;
232 Ok(())
233 }
234
235 pub(crate) fn write_document(&mut self, children: &[Node]) -> WriteResult<()> {
237 for (i, child) in children.iter().enumerate() {
238 if i > 0 {
239 self.write_str("\n")?;
240 }
241 self.write(child)?;
242 }
243 Ok(())
244 }
245
246 pub(crate) fn write_heading(
248 &mut self,
249 level: u8,
250 content: &[Node],
251 heading_type: &HeadingType,
252 ) -> WriteResult<()> {
253 if level == 0 || level > 6 {
255 return Err(WriteError::InvalidHeadingLevel(level));
256 }
257
258 match heading_type {
259 HeadingType::Atx => {
261 for _ in 0..level {
262 self.write_char('#')?;
263 }
264 self.write_char(' ')?;
265
266 for node in content {
267 self.write(node)?;
268 }
269
270 self.write_char('\n')?;
271 }
272
273 HeadingType::Setext => {
274 for node in content {
276 self.write(node)?;
277 }
278 self.write_char('\n')?;
279
280 let underline_char = if level == 1 { '=' } else { '-' };
283
284 let min_len = 3;
287
288 for _ in 0..min_len {
290 self.write_char(underline_char)?;
291 }
292
293 self.write_char('\n')?;
295 }
296 }
297
298 Ok(())
299 }
300
301 pub(crate) fn write_paragraph(&mut self, content: &[Node]) -> WriteResult<()> {
303 for node in content.iter() {
304 self.write(node)?;
305 }
306
307 Ok(())
308 }
309
310 pub(crate) fn write_blockquote(&mut self, content: &[Node]) -> WriteResult<()> {
312 let mut temp_writer = CommonMarkWriter::with_options(self.options.clone());
314
315 for (i, node) in content.iter().enumerate() {
317 if i > 0 {
318 temp_writer.write_char('\n')?;
319 }
320 temp_writer.write(node)?;
322 }
323
324 let all_content = temp_writer.into_string();
326
327 let prefix = "> ";
329 let formatted_content = self.apply_prefix(&all_content, prefix, Some(prefix));
330
331 self.buffer.push_str(&formatted_content);
333 Ok(())
334 }
335
336 pub(crate) fn write_thematic_break(&mut self) -> WriteResult<()> {
338 let char = self.options.thematic_break_char;
339 self.write_str(&format!("{}{}{}", char, char, char))?;
340 Ok(())
341 }
342
343 pub(crate) fn write_code_block(
345 &mut self,
346 language: &Option<String>,
347 content: &str,
348 block_type: &CodeBlockType,
349 ) -> WriteResult<()> {
350 match block_type {
351 CodeBlockType::Indented => {
352 let indent = " ";
353 let indented_content = self.apply_prefix(content, indent, Some(indent));
354 self.buffer.push_str(&indented_content);
355 }
356 CodeBlockType::Fenced => {
357 let max_backticks = content
358 .chars()
359 .fold((0, 0), |(max, current), c| {
360 if c == '`' {
361 (max.max(current + 1), current + 1)
362 } else {
363 (max, 0)
364 }
365 })
366 .0;
367
368 let fence_len = std::cmp::max(max_backticks + 1, 3);
369 let fence = "`".repeat(fence_len);
370
371 self.write_str(&fence)?;
372 if let Some(lang) = language {
373 self.write_str(lang)?;
374 }
375 self.write_char('\n')?;
376
377 self.buffer.push_str(content);
378 if !content.ends_with('\n') {
379 self.write_char('\n')?;
380 }
381
382 self.write_str(&fence)?;
383 }
384 }
385
386 Ok(())
387 }
388
389 pub(crate) fn write_unordered_list(&mut self, items: &[ListItem]) -> WriteResult<()> {
391 let list_marker = self.options.list_marker;
392 let prefix = format!("{} ", list_marker);
393
394 for (i, item) in items.iter().enumerate() {
395 if i > 0 {
396 self.write_char('\n')?;
397 }
398 self.write_list_item(item, &prefix)?;
399 }
400
401 Ok(())
402 }
403
404 pub(crate) fn write_ordered_list(&mut self, start: u32, items: &[ListItem]) -> WriteResult<()> {
406 let mut current_number = start;
408
409 for (i, item) in items.iter().enumerate() {
410 if i > 0 {
411 self.write_char('\n')?;
412 }
413
414 match item {
415 ListItem::Ordered { number, content: _ } => {
417 if let Some(custom_num) = number {
418 let prefix = format!("{}. ", custom_num);
420 self.write_list_item(item, &prefix)?;
421 current_number = custom_num + 1;
423 } else {
424 let prefix = format!("{}. ", current_number);
426 self.write_list_item(item, &prefix)?;
427 current_number += 1;
428 }
429 }
430 _ => {
432 let prefix = format!("{}. ", current_number);
433 self.write_list_item(item, &prefix)?;
434 current_number += 1;
435 }
436 }
437 }
438
439 Ok(())
440 }
441
442 fn write_list_item(&mut self, item: &ListItem, prefix: &str) -> WriteResult<()> {
444 match item {
445 ListItem::Unordered { content } => {
446 self.write_str(prefix)?;
447 self.write_list_item_content(content, prefix.len())?;
448 }
449 ListItem::Ordered { number, content } => {
450 if let Some(num) = number {
451 let custom_prefix = format!("{}. ", num);
452 self.write_str(&custom_prefix)?;
453 self.write_list_item_content(content, custom_prefix.len())?;
454 } else {
455 self.write_str(prefix)?;
456 self.write_list_item_content(content, prefix.len())?;
457 }
458 }
459 #[cfg(feature = "gfm")]
460 ListItem::Task { status, content } => {
461 if self.options.gfm_tasklists {
463 let checkbox = match status {
464 crate::ast::TaskListStatus::Checked => "[x] ",
465 crate::ast::TaskListStatus::Unchecked => "[ ] ",
466 };
467
468 let task_prefix = format!("{}{}", prefix, checkbox);
470 self.write_str(&task_prefix)?;
471 self.write_list_item_content(content, task_prefix.len())?;
472 } else {
473 self.write_str(prefix)?;
475 self.write_list_item_content(content, prefix.len())?;
476 }
477 }
478 }
479
480 Ok(())
481 }
482
483 fn write_list_item_content(&mut self, content: &[Node], prefix_len: usize) -> WriteResult<()> {
485 if content.is_empty() {
486 return Ok(());
487 }
488
489 let mut temp_writer = CommonMarkWriter::with_options(self.options.clone());
490
491 for (i, node) in content.iter().enumerate() {
492 if i > 0 {
493 temp_writer.write_char('\n')?;
494 }
495
496 temp_writer.write(node)?;
497 }
498
499 let all_content = temp_writer.into_string();
500
501 let indent = " ".repeat(prefix_len);
502
503 let formatted_content = self.apply_prefix(&all_content, &indent, Some(""));
504
505 self.buffer.push_str(&formatted_content);
506
507 Ok(())
508 }
509
510 pub(crate) fn write_table(&mut self, headers: &[Node], rows: &[Vec<Node>]) -> WriteResult<()> {
512 self.write_char('|')?;
514 for header in headers {
515 self.check_no_newline(header, "Table Header")?;
516 self.write_char(' ')?;
517 self.write(header)?;
518 self.write_str(" |")?;
519 }
520 self.write_char('\n')?;
521
522 self.write_char('|')?;
524 for _ in 0..headers.len() {
525 self.write_str(" --- |")?;
526 }
527 self.write_char('\n')?;
528
529 for row in rows {
531 self.write_char('|')?;
532 for cell in row {
533 self.check_no_newline(cell, "Table Cell")?;
534 self.write_char(' ')?;
535 self.write(cell)?;
536 self.write_str(" |")?;
537 }
538 self.write_char('\n')?;
539 }
540
541 Ok(())
542 }
543
544 #[cfg(feature = "gfm")]
545 pub(crate) fn write_table_with_alignment(
547 &mut self,
548 headers: &[Node],
549 alignments: &[TableAlignment],
550 rows: &[Vec<Node>],
551 ) -> WriteResult<()> {
552 if !self.options.gfm_tables {
554 return self.write_table(headers, rows);
555 }
556
557 self.write_char('|')?;
559 for header in headers {
560 self.check_no_newline(header, "Table Header")?;
561 self.write_char(' ')?;
562 self.write(header)?;
563 self.write_str(" |")?;
564 }
565 self.write_char('\n')?;
566
567 self.write_char('|')?;
570
571 for i in 0..headers.len() {
573 let alignment = if i < alignments.len() {
574 &alignments[i]
575 } else {
576 &TableAlignment::Center
577 };
578
579 match alignment {
580 TableAlignment::Left => self.write_str(" :--- |")?,
581 TableAlignment::Center => self.write_str(" :---: |")?,
582 TableAlignment::Right => self.write_str(" ---: |")?,
583 TableAlignment::None => self.write_str(" --- |")?,
584 }
585 }
586
587 self.write_char('\n')?;
588
589 for row in rows {
591 self.write_char('|')?;
592 for cell in row {
593 self.check_no_newline(cell, "Table Cell")?;
594 self.write_char(' ')?;
595 self.write(cell)?;
596 self.write_str(" |")?;
597 }
598 self.write_char('\n')?;
599 }
600
601 Ok(())
602 }
603
604 pub(crate) fn write_link(
606 &mut self,
607 url: &str,
608 title: &Option<String>,
609 content: &[Node],
610 ) -> WriteResult<()> {
611 for node in content {
612 self.check_no_newline(node, "Link Text")?;
613 }
614 self.write_char('[')?;
615
616 for node in content {
617 self.write(node)?;
618 }
619
620 self.write_str("](")?;
621 self.write_str(url)?;
622
623 if let Some(title_text) = title {
624 self.write_str(" \"")?;
625 self.write_str(title_text)?;
626 self.write_char('"')?;
627 }
628
629 self.write_char(')')?;
630 Ok(())
631 }
632
633 pub(crate) fn write_image(
635 &mut self,
636 url: &str,
637 title: &Option<String>,
638 alt: &[Node],
639 ) -> WriteResult<()> {
640 for node in alt {
642 self.check_no_newline(node, "Image alt text")?;
643 }
644
645 self.write_str("?;
653 self.write_str(url)?;
654
655 if let Some(title_text) = title {
656 self.write_str(" \"")?;
657 self.write_str(title_text)?;
658 self.write_char('"')?;
659 }
660
661 self.write_char(')')?;
662 Ok(())
663 }
664
665 pub(crate) fn write_soft_break(&mut self) -> WriteResult<()> {
667 self.write_char('\n')?;
668 Ok(())
669 }
670
671 pub(crate) fn write_hard_break(&mut self) -> WriteResult<()> {
673 if self.options.hard_break_spaces {
674 self.write_str(" \n")?;
675 } else {
676 self.write_str("\\\n")?;
677 }
678 Ok(())
679 }
680
681 pub(crate) fn write_html_block(&mut self, content: &str) -> WriteResult<()> {
683 self.buffer.push_str(content);
684
685 Ok(())
686 }
687
688 pub(crate) fn write_autolink(&mut self, url: &str, is_email: bool) -> WriteResult<()> {
690 if url.contains('\n') {
692 return Err(WriteError::NewlineInInlineElement(
693 "Autolink URL".to_string(),
694 ));
695 }
696
697 self.write_char('<')?;
699
700 if !is_email && !url.contains(':') {
703 self.write_str("https://")?;
705 }
706
707 self.write_str(url)?;
708 self.write_char('>')?;
709
710 Ok(())
711 }
712
713 #[cfg(feature = "gfm")]
715 pub(crate) fn write_extended_autolink(&mut self, url: &str) -> WriteResult<()> {
716 if !self.options.gfm_autolinks {
717 self.write_text_content(url)?;
719 return Ok(());
720 }
721
722 if url.contains('\n') {
724 return Err(WriteError::NewlineInInlineElement(
725 "Extended Autolink URL".to_string(),
726 ));
727 }
728
729 self.write_str(url)?;
731
732 Ok(())
733 }
734
735 pub(crate) fn write_link_reference_definition(
737 &mut self,
738 label: &str,
739 destination: &str,
740 title: &Option<String>,
741 ) -> WriteResult<()> {
742 self.write_char('[')?;
744 self.write_str(label)?;
745 self.write_str("]: ")?;
746 self.write_str(destination)?;
747
748 if let Some(title_text) = title {
749 self.write_str(" \"")?;
750 self.write_str(title_text)?;
751 self.write_char('"')?;
752 }
753
754 Ok(())
755 }
756
757 pub(crate) fn write_reference_link(
759 &mut self,
760 label: &str,
761 content: &[Node],
762 ) -> WriteResult<()> {
763 for node in content {
765 self.check_no_newline(node, "Reference Link Text")?;
766 }
767
768 if content.is_empty() {
771 self.write_char('[')?;
772 self.write_str(label)?;
773 self.write_char(']')?;
774 return Ok(());
775 }
776
777 let is_shortcut =
779 content.len() == 1 && matches!(&content[0], Node::Text(text) if text == label);
780
781 if is_shortcut {
782 self.write_char('[')?;
784 self.write_str(label)?;
785 self.write_char(']')?;
786 } else {
787 self.write_char('[')?;
789
790 for node in content {
791 self.write(node)?;
792 }
793
794 self.write_str("][")?;
795 self.write_str(label)?;
796 self.write_char(']')?;
797 }
798
799 Ok(())
800 }
801
802 pub(crate) fn write_html_element(&mut self, element: &HtmlElement) -> WriteResult<()> {
804 #[cfg(feature = "gfm")]
806 if self.options.enable_gfm
807 && self
808 .options
809 .gfm_disallowed_html_tags
810 .iter()
811 .any(|tag| tag.eq_ignore_ascii_case(&element.tag))
812 {
813 self.write_str("<")?;
815 self.write_str(&element.tag)?;
816
817 for attr in &element.attributes {
818 self.write_char(' ')?;
819 self.write_str(&attr.name)?;
820 self.write_str("=\"")?;
821 self.write_str(&escape_attribute_value(&attr.value))?;
822 self.write_char('"')?;
823 }
824
825 if element.self_closing {
826 self.write_str(" />")?;
827 return Ok(());
828 }
829
830 self.write_str(">")?;
831
832 for child in &element.children {
833 self.write(child)?;
834 }
835
836 self.write_str("</")?;
837 self.write_str(&element.tag)?;
838 self.write_str(">")?;
839 return Ok(());
840 }
841
842 if !is_safe_tag_name(&element.tag) {
844 return Err(WriteError::InvalidHtmlTag(element.tag.clone()));
845 }
846
847 self.write_char('<')?;
849 self.write_str(&element.tag)?;
850
851 for attr in &element.attributes {
852 if !is_safe_attribute_name(&attr.name) {
854 return Err(WriteError::InvalidHtmlAttribute(attr.name.clone()));
855 }
856
857 self.write_char(' ')?;
858 self.write_str(&attr.name)?;
859 self.write_str("=\"")?;
860 self.write_str(&escape_attribute_value(&attr.value))?;
861 self.write_char('"')?;
862 }
863
864 if element.self_closing {
865 self.write_str(" />")?;
866 return Ok(());
867 }
868
869 self.write_char('>')?;
870
871 for child in &element.children {
872 self.write(child)?;
874 }
875
876 self.write_str("</")?;
877 self.write_str(&element.tag)?;
878 self.write_char('>')?;
879 Ok(())
880 }
881
882 pub fn into_string(self) -> String {
898 self.buffer
899 }
900 pub(crate) fn ensure_trailing_newline(&mut self) -> WriteResult<()> {
904 if !self.buffer.ends_with('\n') {
905 self.write_char('\n')?;
906 }
907 Ok(())
908 }
909
910 pub(crate) fn write_emphasis(&mut self, content: &[Node]) -> WriteResult<()> {
912 let delimiter = self.options.emphasis_char.to_string();
913 self.write_delimited(content, &delimiter)
914 }
915
916 pub(crate) fn write_strong(&mut self, content: &[Node]) -> WriteResult<()> {
918 let char = self.options.strong_char;
919 let delimiter = format!("{}{}", char, char);
920 self.write_delimited(content, &delimiter)
921 }
922
923 #[cfg(feature = "gfm")]
925 pub(crate) fn write_strikethrough(&mut self, content: &[Node]) -> WriteResult<()> {
926 if !self.options.enable_gfm || !self.options.gfm_strikethrough {
927 for node in content.iter() {
929 self.write(node)?;
930 }
931 return Ok(());
932 }
933
934 self.write_delimited(content, "~~")
936 }
937}
938
939impl Default for CommonMarkWriter {
940 fn default() -> Self {
941 Self::new()
942 }
943}
944
945impl fmt::Display for Node {
947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948 let mut writer = CommonMarkWriter::new();
949 match writer.write(self) {
950 Ok(_) => write!(f, "{}", writer.into_string()),
951 Err(e) => write!(f, "Error writing Node: {}", e),
952 }
953 }
954}
955
956impl CustomNodeWriter for CommonMarkWriter {
958 fn write_str(&mut self, s: &str) -> fmt::Result {
959 self.buffer.push_str(s);
960 Ok(())
961 }
962
963 fn write_char(&mut self, c: char) -> fmt::Result {
964 self.buffer.push(c);
965 Ok(())
966 }
967}
968fn is_safe_tag_name(tag: &str) -> bool {
974 !tag.is_empty()
975 && tag
976 .chars()
977 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':' || c == '-')
978}
979
980fn is_safe_attribute_name(name: &str) -> bool {
984 !name.is_empty()
985 && name
986 .chars()
987 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':' || c == '-' || c == '.')
988}
989
990fn escape_attribute_value(value: &str) -> String {
994 value
995 .replace('&', "&")
996 .replace('"', """)
997 .replace('\'', "'")
998 .replace('<', "<")
999 .replace('>', ">")
1000}