1use crate::ast::{Alignment, HtmlElement, ListItem, Node};
7use crate::error::{WriteError, WriteResult};
8use crate::options::WriterOptions;
9use std::{
10 cmp::max,
11 fmt::{self},
12};
13
14#[derive(Debug)]
18pub struct CommonMarkWriter {
19 options: WriterOptions,
20 buffer: String,
21 indent_level: usize,
23}
24
25trait NodeProcessor {
27 fn process(&self, writer: &mut CommonMarkWriter, node: &Node) -> WriteResult<()>;
29}
30
31struct BlockNodeProcessor;
33
34struct InlineNodeProcessor;
36
37impl NodeProcessor for BlockNodeProcessor {
38 fn process(&self, writer: &mut CommonMarkWriter, node: &Node) -> WriteResult<()> {
39 match node {
40 Node::Document(children) => writer.write_document(children),
41 Node::Heading { level, content } => writer.write_heading(*level, content),
42 Node::Paragraph(content) => writer.write_paragraph(content),
43 Node::BlockQuote(content) => writer.write_blockquote(content),
44 Node::CodeBlock { language, content } => writer.write_code_block(language, content),
45 Node::UnorderedList(items) => writer.write_unordered_list(items),
46 Node::OrderedList { start, items } => writer.write_ordered_list(*start, items),
47 Node::ThematicBreak => writer.write_thematic_break(),
48 Node::Table {
49 headers,
50 rows,
51 alignments,
52 } => writer.write_table(headers, rows, alignments),
53 Node::HtmlBlock(content) => writer.write_html_block(content),
54 _ => Err(WriteError::UnsupportedNodeType),
55 }
56 }
57}
58
59impl NodeProcessor for InlineNodeProcessor {
60 fn process(&self, writer: &mut CommonMarkWriter, node: &Node) -> WriteResult<()> {
61 if writer.options.strict && !matches!(node, Node::SoftBreak | Node::HardBreak) {
63 let context = writer.get_context_for_node(node);
64 writer.check_no_newline(node, &context)?;
65 }
66
67 match node {
68 Node::Text(content) => writer.write_text_content(content),
69 Node::Emphasis(content) => writer.write_delimited(content, "*"),
70 Node::Strong(content) => writer.write_delimited(content, "**"),
71 Node::Strike(content) => writer.write_delimited(content, "~~"),
72 Node::InlineCode(content) => writer.write_code_content(content),
73 Node::Link {
74 url,
75 title,
76 content,
77 } => writer.write_link(url, title, content),
78 Node::Image { url, title, alt } => writer.write_image(url, title, alt),
79 Node::HtmlElement(element) => writer.write_html_element(element),
80 Node::InlineContainer(content) => writer.write_inline_container(content),
81 Node::SoftBreak => writer.write_soft_break(),
82 Node::HardBreak => writer.write_hard_break(),
83 _ => Err(WriteError::UnsupportedNodeType),
84 }
85 }
86}
87
88impl CommonMarkWriter {
89 pub fn new() -> Self {
102 Self::with_options(WriterOptions::default())
103 }
104
105 pub fn with_options(options: WriterOptions) -> Self {
125 Self {
126 options,
127 buffer: String::new(),
128 indent_level: 0,
129 }
130 }
131
132 pub fn write(&mut self, node: &Node) -> WriteResult<()> {
152 if node.is_block() {
154 BlockNodeProcessor.process(self, node)
155 } else if node.is_inline() {
156 InlineNodeProcessor.process(self, node)
157 } else {
158 Err(WriteError::UnsupportedNodeType)
160 }
161 }
162
163 fn get_context_for_node(&self, node: &Node) -> String {
165 match node {
166 Node::Text(_) => "Text".to_string(),
167 Node::Emphasis(_) => "Emphasis".to_string(),
168 Node::Strong(_) => "Strong".to_string(),
169 Node::Strike(_) => "Strike".to_string(),
170 Node::InlineCode(_) => "InlineCode".to_string(),
171 Node::Link { .. } => "Link content".to_string(),
172 Node::Image { .. } => "Image alt text".to_string(),
173 Node::HtmlElement(_) => "HtmlElement content".to_string(),
174 Node::InlineContainer(_) => "InlineContainer".to_string(),
175 _ => "Unknown inline element".to_string(),
176 }
177 }
178
179 fn write_text_content(&mut self, content: &str) -> WriteResult<()> {
181 let escaped = content
182 .replace('\\', "\\\\")
183 .replace('*', "\\*")
184 .replace('_', "\\_")
185 .replace('[', "\\[")
186 .replace(']', "\\]")
187 .replace('<', "\\<")
188 .replace('>', "\\>")
189 .replace('`', "\\`");
190
191 self.write_str(&escaped)?;
192 Ok(())
193 }
194
195 fn write_code_content(&mut self, content: &str) -> WriteResult<()> {
197 self.write_char('`')?;
198 self.write_str(content)?;
199 self.write_char('`')?;
200 Ok(())
201 }
202
203 fn write_delimited(&mut self, content: &[Node], delimiter: &str) -> WriteResult<()> {
205 self.write_str(delimiter)?;
206
207 for node in content {
208 self.write(node)?;
209 }
210
211 self.write_str(delimiter)?;
212 Ok(())
213 }
214
215 fn write_document(&mut self, children: &[Node]) -> WriteResult<()> {
217 for (i, child) in children.iter().enumerate() {
218 self.write(child)?;
219 if i < children.len() - 1 {
220 self.write_str("\n\n")?;
221 }
222 }
223 Ok(())
224 }
225
226 fn write_heading(&mut self, level: u8, content: &[Node]) -> WriteResult<()> {
228 if !(1..=6).contains(&level) {
229 return Err(WriteError::InvalidHeadingLevel(level));
230 }
231
232 for _ in 0..level {
233 self.write_char('#')?;
234 }
235 self.write_char(' ')?;
236
237 for node in content.iter() {
238 self.write(node)?;
239 }
240
241 Ok(())
242 }
243
244 fn write_paragraph(&mut self, content: &[Node]) -> WriteResult<()> {
246 for node in content.iter() {
247 self.write(node)?;
248 }
249 Ok(())
250 }
251
252 fn write_blockquote(&mut self, content: &[Node]) -> WriteResult<()> {
254 self.indent_level += 1;
255
256 for (i, node) in content.iter().enumerate() {
257 self.write_str("> ")?;
258 self.write(node)?;
259 if i < content.len() - 1 {
260 self.write_str("\n> \n")?;
261 }
262 }
263
264 self.indent_level -= 1;
265 Ok(())
266 }
267
268 fn write_code_block(&mut self, language: &Option<String>, content: &str) -> WriteResult<()> {
270 let max_backticks = content
271 .chars()
272 .fold((0, 0), |(max, current), c| {
273 if c == '`' {
274 (max.max(current + 1), current + 1)
275 } else {
276 (max, 0)
277 }
278 })
279 .0;
280
281 let fence_len = max(max_backticks + 1, 3);
282 let fence = "`".repeat(fence_len);
283
284 self.write_str(&fence)?;
285 if let Some(lang) = language {
286 self.write_str(lang)?;
287 }
288 self.write_char('\n')?;
289 self.write_str(content)?;
290
291 if !content.ends_with('\n') {
293 self.write_char('\n')?;
294 }
295
296 self.write_str(&fence)?;
297 Ok(())
298 }
299
300 fn write_unordered_list(&mut self, items: &[ListItem]) -> WriteResult<()> {
302 for (i, item) in items.iter().enumerate() {
303 self.write_list_item(item, "- ")?;
304 if i < items.len() - 1 {
305 self.write_char('\n')?;
306 }
307 }
308 Ok(())
309 }
310
311 fn write_ordered_list(&mut self, start: u32, items: &[ListItem]) -> WriteResult<()> {
313 let mut current_number = start;
315
316 for (i, item) in items.iter().enumerate() {
317 match item {
318 ListItem::Ordered { number, content: _ } => {
320 if let Some(custom_num) = number {
321 let prefix = format!("{}. ", custom_num);
323 self.write_list_item(item, &prefix)?;
324 current_number = custom_num + 1;
326 } else {
327 let prefix = format!("{}. ", current_number);
329 self.write_list_item(item, &prefix)?;
330 current_number += 1;
331 }
332 }
333 _ => {
335 let prefix = format!("{}. ", current_number);
336 self.write_list_item(item, &prefix)?;
337 current_number += 1;
338 }
339 }
340
341 if i < items.len() - 1 {
342 self.write_char('\n')?;
343 }
344 }
345 Ok(())
346 }
347
348 fn write_list_item(&mut self, item: &ListItem, prefix: &str) -> WriteResult<()> {
350 for _ in 0..(self.indent_level * self.options.indent_spaces) {
352 self.write_char(' ')?;
353 }
354
355 match item {
357 ListItem::Unordered { content } => {
358 self.write_str(prefix)?;
359 self.write_list_item_content(content, prefix, false)?;
360 }
361 ListItem::Ordered { number, content } => {
362 if let Some(num) = number {
364 let custom_prefix = format!("{}. ", num);
366 self.write_str(&custom_prefix)?;
367 self.write_list_item_content(content, &custom_prefix, false)?;
368 } else {
369 self.write_str(prefix)?;
371 self.write_list_item_content(content, prefix, false)?;
372 }
373 }
374 }
375
376 Ok(())
377 }
378
379 fn write_list_item_content(
381 &mut self,
382 content: &[Node],
383 prefix: &str,
384 is_task: bool,
385 ) -> WriteResult<()> {
386 self.indent_level += 1;
387
388 for (i, node) in content.iter().enumerate() {
389 let is_list = matches!(node, Node::OrderedList { .. } | Node::UnorderedList(..));
390
391 if is_list {
393 if i > 0 {
394 self.write_char('\n')?;
395 }
396 self.write(node)?;
397 continue;
398 }
399
400 if i > 0 {
401 self.write_char('\n')?;
402 let prefix_length = prefix.len() + if is_task { 4 } else { 0 };
404 for _ in 0..(self.indent_level * self.options.indent_spaces) + prefix_length {
405 self.write_char(' ')?;
406 }
407 }
408
409 self.write(node)?;
410 }
411
412 self.indent_level -= 1;
413 Ok(())
414 }
415
416 fn write_thematic_break(&mut self) -> WriteResult<()> {
418 self.write_str("---")?;
419 Ok(())
420 }
421
422 fn check_no_newline(&self, node: &Node, context: &str) -> WriteResult<()> {
424 if Self::node_contains_newline(node) {
425 return Err(WriteError::NewlineInInlineElement(context.to_string()));
426 }
427 Ok(())
428 }
429
430 fn node_contains_newline(node: &Node) -> bool {
432 match node {
433 Node::Text(s) | Node::InlineCode(s) => s.contains('\n'),
434 Node::Emphasis(children)
435 | Node::Strong(children)
436 | Node::Strike(children)
437 | Node::InlineContainer(children) => children.iter().any(Self::node_contains_newline),
438 Node::HtmlElement(element) => element.children.iter().any(Self::node_contains_newline),
439 Node::Link { content, .. } => content.iter().any(Self::node_contains_newline),
440 Node::Image { alt, .. } => alt.iter().any(Self::node_contains_newline),
441 Node::SoftBreak | Node::HardBreak => true,
442 _ => false,
443 }
444 }
445
446 fn write_table(
448 &mut self,
449 headers: &[Node],
450 rows: &[Vec<Node>],
451 alignments: &[Alignment],
452 ) -> WriteResult<()> {
453 self.write_char('|')?;
455 for header in headers {
456 self.check_no_newline(header, "Table Header")?;
457 self.write_char(' ')?;
458 self.write(header)?;
459 self.write_str(" |")?;
460 }
461 self.write_char('\n')?;
462
463 self.write_char('|')?;
465 for alignment in alignments {
466 match alignment {
467 Alignment::None => self.write_str(" --- |")?,
468 Alignment::Left => self.write_str(" :--- |")?,
469 Alignment::Center => self.write_str(" :---: |")?,
470 Alignment::Right => self.write_str(" ---: |")?,
471 }
472 }
473 self.write_char('\n')?;
474
475 for row in rows {
477 self.write_char('|')?;
478 for cell in row {
479 self.check_no_newline(cell, "Table Cell")?;
480 self.write_char(' ')?;
481 self.write(cell)?;
482 self.write_str(" |")?;
483 }
484 self.write_char('\n')?;
485 }
486
487 Ok(())
488 }
489
490 fn write_link(
492 &mut self,
493 url: &str,
494 title: &Option<String>,
495 content: &[Node],
496 ) -> WriteResult<()> {
497 for node in content {
498 self.check_no_newline(node, "Link Text")?;
499 }
500 self.write_char('[')?;
501
502 for node in content {
503 self.write(node)?;
504 }
505
506 self.write_str("](")?;
507 self.write_str(url)?;
508
509 if let Some(title_text) = title {
510 self.write_str(" \"")?;
511 self.write_str(title_text)?;
512 self.write_char('"')?;
513 }
514
515 self.write_char(')')?;
516 Ok(())
517 }
518
519 fn write_image(&mut self, url: &str, title: &Option<String>, alt: &[Node]) -> WriteResult<()> {
521 for node in alt {
523 self.check_no_newline(node, "Image alt text")?;
524 }
525
526 self.write_str("?;
534 self.write_str(url)?;
535
536 if let Some(title_text) = title {
537 self.write_str(" \"")?;
538 self.write_str(title_text)?;
539 self.write_char('"')?;
540 }
541
542 self.write_char(')')?;
543 Ok(())
544 }
545
546 fn write_soft_break(&mut self) -> WriteResult<()> {
548 self.write_char('\n')?;
549 Ok(())
550 }
551
552 fn write_hard_break(&mut self) -> WriteResult<()> {
554 if self.options.hard_break_spaces {
555 self.write_str(" \n")?;
556 } else {
557 self.write_str("\\\n")?;
558 }
559 Ok(())
560 }
561
562 pub fn into_string(self) -> String {
578 self.buffer
579 }
580
581 fn write_char(&mut self, c: char) -> fmt::Result {
583 self.buffer.push(c);
584 Ok(())
585 }
586
587 fn write_str(&mut self, s: &str) -> fmt::Result {
589 self.buffer.push_str(s);
590 Ok(())
591 }
592
593 fn write_html_block(&mut self, content: &str) -> WriteResult<()> {
595 self.write_str(content)?;
596 Ok(())
597 }
598
599 fn write_html_element(&mut self, element: &HtmlElement) -> WriteResult<()> {
601 self.write_char('<')?;
602 self.write_str(&element.tag)?;
603
604 for attr in &element.attributes {
605 self.write_char(' ')?;
606 self.write_str(&attr.name)?;
607 self.write_str("=\"")?;
608 self.write_str(&attr.value)?; self.write_char('"')?;
610 }
611
612 if element.self_closing {
613 self.write_str(" />")?;
614 return Ok(());
615 }
616
617 self.write_char('>')?;
618
619 for child in &element.children {
620 self.write(child)?;
622 }
623
624 self.write_str("</")?;
625 self.write_str(&element.tag)?;
626 self.write_char('>')?;
627 Ok(())
628 }
629
630 fn write_inline_container(&mut self, content: &[Node]) -> WriteResult<()> {
632 for node in content {
633 self.write(node)?;
634 }
635 Ok(())
636 }
637}
638
639impl Default for CommonMarkWriter {
640 fn default() -> Self {
641 Self::new()
642 }
643}
644
645impl fmt::Display for Node {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 let mut writer = CommonMarkWriter::new();
649 match writer.write(self) {
650 Ok(_) => write!(f, "{}", writer.into_string()),
651 Err(e) => write!(f, "Error writing Node: {}", e),
652 }
653 }
654}