1use crate::ast::{CustomNode, CustomNodeWriter, HeadingType, HtmlElement, ListItem, Node};
6use crate::error::{WriteError, WriteResult};
7use crate::options::WriterOptions;
8use crate::CodeBlockType;
9use std::fmt::{self};
10
11use super::processors::{
12 BlockNodeProcessor, CustomNodeProcessor, InlineNodeProcessor, NodeProcessor,
13};
14
15#[derive(Debug)]
19pub struct CommonMarkWriter {
20 options: WriterOptions,
21 buffer: String,
22}
23
24impl CommonMarkWriter {
25 pub fn new() -> Self {
38 Self::with_options(WriterOptions::default())
39 }
40
41 pub fn with_options(options: WriterOptions) -> Self {
61 Self {
62 options,
63 buffer: String::new(),
64 }
65 }
66
67 pub(crate) fn is_strict_mode(&self) -> bool {
69 self.options.strict
70 }
71
72 fn apply_prefix(&self, content: &str, prefix: &str, first_line_prefix: Option<&str>) -> String {
84 if content.is_empty() {
85 return String::new();
86 }
87
88 let mut result = String::new();
89 let lines: Vec<&str> = content.lines().collect();
90
91 if !lines.is_empty() {
92 let actual_prefix = first_line_prefix.unwrap_or(prefix);
93 result.push_str(actual_prefix);
94 result.push_str(lines[0]);
95 }
96
97 for line in &lines[1..] {
98 result.push('\n');
99 result.push_str(prefix);
100 result.push_str(line);
101 }
102
103 result
104 }
105
106 pub fn write(&mut self, node: &Node) -> WriteResult<()> {
126 if let Node::Custom(_) = node {
127 return CustomNodeProcessor.process(self, node);
128 }
129
130 if node.is_block() {
131 BlockNodeProcessor.process(self, node)
132 } else if node.is_inline() {
133 InlineNodeProcessor.process(self, node)
134 } else {
135 Err(WriteError::UnsupportedNodeType)
137 }
138 }
139
140 #[allow(clippy::borrowed_box)]
142 pub(crate) fn write_custom_node(&mut self, node: &Box<dyn CustomNode>) -> WriteResult<()> {
143 node.write(self)
144 }
145
146 pub(crate) fn get_context_for_node(&self, node: &Node) -> String {
148 match node {
149 Node::Text(_) => "Text".to_string(),
150 Node::Emphasis(_) => "Emphasis".to_string(),
151 Node::Strong(_) => "Strong".to_string(),
152 Node::InlineCode(_) => "InlineCode".to_string(),
153 Node::Link { .. } => "Link content".to_string(),
154 Node::Image { .. } => "Image alt text".to_string(),
155 Node::HtmlElement(_) => "HtmlElement content".to_string(),
156 Node::Custom(_) => "Custom node".to_string(),
157 _ => "Unknown inline element".to_string(),
158 }
159 }
160
161 pub(crate) fn check_no_newline(&self, node: &Node, context: &str) -> WriteResult<()> {
163 if Self::node_contains_newline(node) {
164 return Err(WriteError::NewlineInInlineElement(context.to_string()));
165 }
166 Ok(())
167 }
168
169 fn node_contains_newline(node: &Node) -> bool {
171 match node {
172 Node::Text(s) | Node::InlineCode(s) => s.contains('\n'),
173 Node::Emphasis(children) | Node::Strong(children) => {
174 children.iter().any(Self::node_contains_newline)
175 }
176 Node::HtmlElement(element) => element.children.iter().any(Self::node_contains_newline),
177 Node::Link { content, .. } => content.iter().any(Self::node_contains_newline),
178 Node::Image { alt, .. } => alt.iter().any(Self::node_contains_newline),
179 Node::SoftBreak | Node::HardBreak => true,
180 Node::Custom(_) => false,
182 _ => false,
183 }
184 }
185
186 pub(crate) fn write_text_content(&mut self, content: &str) -> WriteResult<()> {
188 let escaped = content
189 .replace('\\', "\\\\")
190 .replace('*', "\\*")
191 .replace('_', "\\_")
192 .replace('[', "\\[")
193 .replace(']', "\\]")
194 .replace('<', "\\<")
195 .replace('>', "\\>")
196 .replace('`', "\\`");
197
198 self.write_str(&escaped)?;
199 Ok(())
200 }
201
202 pub(crate) fn write_code_content(&mut self, content: &str) -> WriteResult<()> {
204 self.write_char('`')?;
205 self.write_str(content)?;
206 self.write_char('`')?;
207 Ok(())
208 }
209
210 pub(crate) fn write_delimited(&mut self, content: &[Node], delimiter: &str) -> WriteResult<()> {
212 self.write_str(delimiter)?;
213
214 for node in content {
215 self.write(node)?;
216 }
217
218 self.write_str(delimiter)?;
219 Ok(())
220 }
221
222 pub(crate) fn write_document(&mut self, children: &[Node]) -> WriteResult<()> {
224 for (i, child) in children.iter().enumerate() {
225 if i > 0 {
226 self.write_str("\n")?;
227 }
228 self.write(child)?;
229 }
230 Ok(())
231 }
232
233 pub(crate) fn write_heading(
235 &mut self,
236 level: u8,
237 content: &[Node],
238 heading_type: &HeadingType,
239 ) -> WriteResult<()> {
240 if level == 0 || level > 6 {
242 return Err(WriteError::InvalidHeadingLevel(level));
243 }
244
245 match heading_type {
246 HeadingType::Atx => {
248 for _ in 0..level {
249 self.write_char('#')?;
250 }
251 self.write_char(' ')?;
252
253 for node in content {
254 self.write(node)?;
255 }
256
257 self.write_char('\n')?;
258 }
259
260 HeadingType::Setext => {
261 for node in content {
263 self.write(node)?;
264 }
265 self.write_char('\n')?;
266
267 let underline_char = if level == 1 { '=' } else { '-' };
270
271 let min_len = 3;
274
275 for _ in 0..min_len {
277 self.write_char(underline_char)?;
278 }
279
280 self.write_char('\n')?;
282 }
283 }
284
285 Ok(())
286 }
287
288 pub(crate) fn write_paragraph(&mut self, content: &[Node]) -> WriteResult<()> {
290 for node in content.iter() {
291 self.write(node)?;
292 }
293
294 Ok(())
295 }
296
297 pub(crate) fn write_blockquote(&mut self, content: &[Node]) -> WriteResult<()> {
299 let mut temp_writer = CommonMarkWriter::with_options(self.options.clone());
301
302 for (i, node) in content.iter().enumerate() {
304 if i > 0 {
305 temp_writer.write_char('\n')?;
306 }
307 temp_writer.write(node)?;
309 }
310
311 let all_content = temp_writer.into_string();
313
314 let prefix = "> ";
316 let formatted_content = self.apply_prefix(&all_content, prefix, Some(prefix));
317
318 self.buffer.push_str(&formatted_content);
320 Ok(())
321 }
322
323 pub(crate) fn write_thematic_break(&mut self) -> WriteResult<()> {
325 self.write_str("---")?;
326 Ok(())
327 }
328
329 pub(crate) fn write_code_block(
331 &mut self,
332 language: &Option<String>,
333 content: &str,
334 block_type: &CodeBlockType,
335 ) -> WriteResult<()> {
336 match block_type {
337 CodeBlockType::Indented => {
338 let indent = " ";
339 let indented_content = self.apply_prefix(content, indent, Some(indent));
340 self.buffer.push_str(&indented_content);
341 }
342 CodeBlockType::Fenced => {
343 let max_backticks = content
344 .chars()
345 .fold((0, 0), |(max, current), c| {
346 if c == '`' {
347 (max.max(current + 1), current + 1)
348 } else {
349 (max, 0)
350 }
351 })
352 .0;
353
354 let fence_len = std::cmp::max(max_backticks + 1, 3);
355 let fence = "`".repeat(fence_len);
356
357 self.write_str(&fence)?;
358 if let Some(lang) = language {
359 self.write_str(lang)?;
360 }
361 self.write_char('\n')?;
362
363 self.buffer.push_str(content);
364 if !content.ends_with('\n') {
365 self.write_char('\n')?;
366 }
367
368 self.write_str(&fence)?;
369 }
370 }
371
372 Ok(())
373 }
374
375 pub(crate) fn write_unordered_list(&mut self, items: &[ListItem]) -> WriteResult<()> {
377 for (i, item) in items.iter().enumerate() {
378 if i > 0 {
379 self.write_char('\n')?;
380 }
381 self.write_list_item(item, "- ")?;
382 }
383
384 Ok(())
385 }
386
387 pub(crate) fn write_ordered_list(&mut self, start: u32, items: &[ListItem]) -> WriteResult<()> {
389 let mut current_number = start;
391
392 for (i, item) in items.iter().enumerate() {
393 if i > 0 {
394 self.write_char('\n')?;
395 }
396
397 match item {
398 ListItem::Ordered { number, content: _ } => {
400 if let Some(custom_num) = number {
401 let prefix = format!("{}. ", custom_num);
403 self.write_list_item(item, &prefix)?;
404 current_number = custom_num + 1;
406 } else {
407 let prefix = format!("{}. ", current_number);
409 self.write_list_item(item, &prefix)?;
410 current_number += 1;
411 }
412 }
413 _ => {
415 let prefix = format!("{}. ", current_number);
416 self.write_list_item(item, &prefix)?;
417 current_number += 1;
418 }
419 }
420 }
421
422 Ok(())
423 }
424
425 fn write_list_item(&mut self, item: &ListItem, prefix: &str) -> WriteResult<()> {
427 match item {
428 ListItem::Unordered { content } => {
429 self.write_str(prefix)?;
430 self.write_list_item_content(content, prefix.len())?;
431 }
432 ListItem::Ordered { number, content } => {
433 if let Some(num) = number {
434 let custom_prefix = format!("{}. ", num);
435 self.write_str(&custom_prefix)?;
436 self.write_list_item_content(content, custom_prefix.len())?;
437 } else {
438 self.write_str(prefix)?;
439 self.write_list_item_content(content, prefix.len())?;
440 }
441 }
442 }
443
444 Ok(())
445 }
446
447 fn write_list_item_content(&mut self, content: &[Node], prefix_len: usize) -> WriteResult<()> {
449 if content.is_empty() {
450 return Ok(());
451 }
452
453 let mut temp_writer = CommonMarkWriter::with_options(self.options.clone());
454
455 for (i, node) in content.iter().enumerate() {
456 if i > 0 {
457 temp_writer.write_char('\n')?;
458 }
459
460 temp_writer.write(node)?;
461 }
462
463 let all_content = temp_writer.into_string();
464
465 let indent = " ".repeat(prefix_len);
466
467 let formatted_content = self.apply_prefix(&all_content, &indent, Some(""));
468
469 self.buffer.push_str(&formatted_content);
470
471 Ok(())
472 }
473
474 pub(crate) fn write_table(&mut self, headers: &[Node], rows: &[Vec<Node>]) -> WriteResult<()> {
476 self.write_char('|')?;
478 for header in headers {
479 self.check_no_newline(header, "Table Header")?;
480 self.write_char(' ')?;
481 self.write(header)?;
482 self.write_str(" |")?;
483 }
484 self.write_char('\n')?;
485
486 self.write_char('|')?;
488 for _ in 0..headers.len() {
489 self.write_str(" --- |")?;
490 }
491 self.write_char('\n')?;
492
493 for row in rows {
495 self.write_char('|')?;
496 for cell in row {
497 self.check_no_newline(cell, "Table Cell")?;
498 self.write_char(' ')?;
499 self.write(cell)?;
500 self.write_str(" |")?;
501 }
502 self.write_char('\n')?;
503 }
504
505 Ok(())
506 }
507
508 pub(crate) fn write_link(
510 &mut self,
511 url: &str,
512 title: &Option<String>,
513 content: &[Node],
514 ) -> WriteResult<()> {
515 for node in content {
516 self.check_no_newline(node, "Link Text")?;
517 }
518 self.write_char('[')?;
519
520 for node in content {
521 self.write(node)?;
522 }
523
524 self.write_str("](")?;
525 self.write_str(url)?;
526
527 if let Some(title_text) = title {
528 self.write_str(" \"")?;
529 self.write_str(title_text)?;
530 self.write_char('"')?;
531 }
532
533 self.write_char(')')?;
534 Ok(())
535 }
536
537 pub(crate) fn write_image(
539 &mut self,
540 url: &str,
541 title: &Option<String>,
542 alt: &[Node],
543 ) -> WriteResult<()> {
544 for node in alt {
546 self.check_no_newline(node, "Image alt text")?;
547 }
548
549 self.write_str("?;
557 self.write_str(url)?;
558
559 if let Some(title_text) = title {
560 self.write_str(" \"")?;
561 self.write_str(title_text)?;
562 self.write_char('"')?;
563 }
564
565 self.write_char(')')?;
566 Ok(())
567 }
568
569 pub(crate) fn write_soft_break(&mut self) -> WriteResult<()> {
571 self.write_char('\n')?;
572 Ok(())
573 }
574
575 pub(crate) fn write_hard_break(&mut self) -> WriteResult<()> {
577 if self.options.hard_break_spaces {
578 self.write_str(" \n")?;
579 } else {
580 self.write_str("\\\n")?;
581 }
582 Ok(())
583 }
584
585 pub(crate) fn write_html_block(&mut self, content: &str) -> WriteResult<()> {
587 self.buffer.push_str(content);
588
589 Ok(())
590 }
591
592 pub(crate) fn write_autolink(&mut self, url: &str, is_email: bool) -> WriteResult<()> {
594 if url.contains('\n') {
596 return Err(WriteError::NewlineInInlineElement(
597 "Autolink URL".to_string(),
598 ));
599 }
600
601 self.write_char('<')?;
603
604 if !is_email && !url.contains(':') {
607 self.write_str("https://")?;
609 }
610
611 self.write_str(url)?;
612 self.write_char('>')?;
613
614 Ok(())
615 }
616
617 pub(crate) fn write_link_reference_definition(
619 &mut self,
620 label: &str,
621 destination: &str,
622 title: &Option<String>,
623 ) -> WriteResult<()> {
624 self.write_char('[')?;
626 self.write_str(label)?;
627 self.write_str("]: ")?;
628 self.write_str(destination)?;
629
630 if let Some(title_text) = title {
631 self.write_str(" \"")?;
632 self.write_str(title_text)?;
633 self.write_char('"')?;
634 }
635
636 Ok(())
637 }
638
639 pub(crate) fn write_reference_link(
641 &mut self,
642 label: &str,
643 content: &[Node],
644 ) -> WriteResult<()> {
645 for node in content {
647 self.check_no_newline(node, "Reference Link Text")?;
648 }
649
650 if content.is_empty() {
653 self.write_char('[')?;
654 self.write_str(label)?;
655 self.write_char(']')?;
656 return Ok(());
657 }
658
659 let is_shortcut =
661 content.len() == 1 && matches!(&content[0], Node::Text(text) if text == label);
662
663 if is_shortcut {
664 self.write_char('[')?;
666 self.write_str(label)?;
667 self.write_char(']')?;
668 } else {
669 self.write_char('[')?;
671
672 for node in content {
673 self.write(node)?;
674 }
675
676 self.write_str("][")?;
677 self.write_str(label)?;
678 self.write_char(']')?;
679 }
680
681 Ok(())
682 }
683
684 pub(crate) fn write_html_element(&mut self, element: &HtmlElement) -> WriteResult<()> {
686 self.write_char('<')?;
687 self.write_str(&element.tag)?;
688
689 for attr in &element.attributes {
690 self.write_char(' ')?;
691 self.write_str(&attr.name)?;
692 self.write_str("=\"")?;
693 self.write_str(&attr.value)?; self.write_char('"')?;
695 }
696
697 if element.self_closing {
698 self.write_str(" />")?;
699 return Ok(());
700 }
701
702 self.write_char('>')?;
703
704 for child in &element.children {
705 self.write(child)?;
707 }
708
709 self.write_str("</")?;
710 self.write_str(&element.tag)?;
711 self.write_char('>')?;
712 Ok(())
713 }
714
715 pub fn into_string(self) -> String {
731 self.buffer
732 }
733 pub(crate) fn ensure_trailing_newline(&mut self) -> WriteResult<()> {
737 if !self.buffer.ends_with('\n') {
738 self.write_char('\n')?;
739 }
740 Ok(())
741 }
742}
743
744impl Default for CommonMarkWriter {
745 fn default() -> Self {
746 Self::new()
747 }
748}
749
750impl fmt::Display for Node {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 let mut writer = CommonMarkWriter::new();
754 match writer.write(self) {
755 Ok(_) => write!(f, "{}", writer.into_string()),
756 Err(e) => write!(f, "Error writing Node: {}", e),
757 }
758 }
759}
760
761impl CustomNodeWriter for CommonMarkWriter {
763 fn write_str(&mut self, s: &str) -> fmt::Result {
764 self.buffer.push_str(s);
765 Ok(())
766 }
767
768 fn write_char(&mut self, c: char) -> fmt::Result {
769 self.buffer.push(c);
770 Ok(())
771 }
772}