cmark_writer/writer/cmark/
writer.rs1use crate::ast::{CustomNode, Node};
4use crate::error::{WriteError, WriteResult};
5use crate::options::WriterOptions;
6use crate::writer::context::{NewlineContext, NewlineStrategy, RenderingMode};
7use ecow::EcoString;
8use std::fmt;
9
10#[derive(Debug)]
15pub struct CommonMarkWriter {
16 pub options: WriterOptions,
18 pub(super) buffer: EcoString,
20 context: NewlineContext,
22}
23
24impl CommonMarkWriter {
25 pub fn new() -> Self {
39 Self::with_options(WriterOptions::default())
40 }
41
42 pub fn with_options(options: WriterOptions) -> Self {
63 Self {
64 options,
65 buffer: EcoString::new(),
66 context: NewlineContext::block(),
67 }
68 }
69
70 pub fn with_context(options: WriterOptions, context: NewlineContext) -> Self {
72 Self {
73 options,
74 buffer: EcoString::new(),
75 context,
76 }
77 }
78
79 pub(super) fn is_strict_mode(&self) -> bool {
81 self.options.strict
82 }
83
84 pub(super) fn apply_prefix(
96 &self,
97 content: &str,
98 prefix: &str,
99 first_line_prefix: Option<&str>,
100 ) -> EcoString {
101 if content.is_empty() {
102 return EcoString::new();
103 }
104
105 let mut result = EcoString::new();
106 let lines: Vec<&str> = content.lines().collect();
107
108 if !lines.is_empty() {
109 let actual_prefix = first_line_prefix.unwrap_or(prefix);
110 result.push_str(actual_prefix);
111 result.push_str(lines[0]);
112 }
113
114 for line in &lines[1..] {
115 result.push('\n');
116 result.push_str(prefix);
117 result.push_str(line);
118 }
119
120 result
121 }
122
123 pub(super) fn write_document_children(&mut self, children: &[Node]) -> WriteResult<()> {
125 for (i, node) in children.iter().enumerate() {
126 if i > 0 {
127 self.write_node_separator(&children[i - 1], node)?;
128 }
129
130 if i == children.len() - 1 {
132 if node.is_block() {
134 self.write_node(node)?;
135 } else {
136 self.write_node_content(node)?;
138 }
139 } else {
140 self.write_node(node)?;
141 }
142 }
143 Ok(())
144 }
145
146 pub fn write_node_content(&mut self, node: &Node) -> WriteResult<()> {
149 if let Node::Custom(custom_node) = node {
151 return custom_node.render_commonmark(self);
153 }
154
155 if let Node::Document(children) = node {
157 return self.write_document_children(children);
158 }
159
160 if self.options.strict
162 && !node.is_block()
163 && !matches!(node, Node::SoftBreak | Node::HardBreak)
164 {
165 match node {
166 Node::Text(content) => {
167 if content.contains('\n') {
168 return Err(WriteError::NewlineInInlineElement("Text".into()));
169 }
170 }
171 Node::InlineCode(content) => {
172 if content.contains('\n') {
173 return Err(WriteError::NewlineInInlineElement("InlineCode".into()));
174 }
175 }
176 Node::Emphasis(children) | Node::Strong(children) => {
177 for child in children {
178 if let Node::Text(content) = child {
179 if content.contains('\n') {
180 return Err(WriteError::NewlineInInlineElement(
181 "Text in formatting".into(),
182 ));
183 }
184 }
185 }
186 }
187 _ => {}
188 }
189 }
190
191 match node {
193 Node::Heading {
195 level,
196 content,
197 heading_type,
198 } => self.write_heading(*level, content, heading_type),
199 Node::Paragraph(content) => self.write_paragraph(content),
200 Node::BlockQuote(content) => self.write_blockquote(content),
201 Node::CodeBlock {
202 language,
203 content,
204 block_type,
205 } => self.write_code_block(language, content, block_type),
206 Node::UnorderedList(items) => self.write_unordered_list(items),
207 Node::OrderedList { start, items } => self.write_ordered_list(items, *start, true), Node::ThematicBreak => self.write_thematic_break(),
209
210 Node::Text(content) => self.write_text_content(content),
212 Node::Emphasis(content) => self.write_emphasis(content),
213 Node::Strong(content) => self.write_strong(content),
214 Node::InlineCode(content) => self.write_code_content(content),
215 Node::Link {
216 url,
217 title,
218 content,
219 } => self.write_link(url, title, content),
220 Node::Image { url, title, alt } => self.write_image(url, title, alt),
221 Node::SoftBreak => self.write_soft_break(),
222 Node::HardBreak => self.write_hard_break(),
223 Node::Autolink { url, is_email } => self.write_autolink(url, *is_email),
224 Node::ReferenceLink { label, content } => self.write_reference_link(label, content),
225 Node::LinkReferenceDefinition {
226 label,
227 destination,
228 title,
229 } => self.write_link_reference_definition(label, destination, title),
230
231 Node::HtmlBlock(content) => self.write_html_block(content),
233 Node::HtmlElement(element) => self.write_html_element(element),
234
235 #[cfg(feature = "gfm")]
237 Node::Table {
238 headers,
239 alignments,
240 rows,
241 } => self.write_table_with_alignment(headers, alignments, rows),
242 #[cfg(not(feature = "gfm"))]
243 Node::Table { headers, rows, .. } => self.write_table(headers, rows),
244
245 #[cfg(feature = "gfm")]
247 Node::Strikethrough(content) => self.write_strikethrough(content),
248 #[cfg(feature = "gfm")]
249 Node::ExtendedAutolink(url) => self.write_extended_autolink(url),
250
251 Node::Custom(custom_node) => self.write_custom_node(custom_node),
253
254 _ => {
255 log::warn!("Unsupported node type encountered and skipped: {:?}", node);
256 Ok(())
257 }
258 }
259 }
260
261 #[allow(clippy::borrowed_box)]
263 pub(super) fn write_custom_node(&mut self, node: &Box<dyn CustomNode>) -> WriteResult<()> {
264 node.render_commonmark(self)
265 }
266
267 pub(super) fn check_no_newline(&self, node: &Node, context: &str) -> WriteResult<()> {
269 if Self::node_contains_newline(node) {
270 if self.is_strict_mode() {
271 return Err(WriteError::NewlineInInlineElement(
272 context.to_string().into(),
273 ));
274 } else {
275 log::warn!(
276 "Newline character found in inline element '{}', but non-strict mode allows it (output may be affected).",
277 context
278 );
279 }
280 }
281 Ok(())
282 }
283
284 pub(super) fn node_contains_newline(node: &Node) -> bool {
286 match node {
287 Node::Text(s) | Node::InlineCode(s) => s.contains('\n'),
288 Node::Emphasis(children) | Node::Strong(children) => {
289 children.iter().any(Self::node_contains_newline)
290 }
291 #[cfg(feature = "gfm")]
292 Node::Strikethrough(children) => children.iter().any(Self::node_contains_newline),
293 Node::HtmlElement(element) => element.children.iter().any(Self::node_contains_newline),
294 Node::Link { content, .. } => content.iter().any(Self::node_contains_newline),
295 Node::Image { alt, .. } => alt.iter().any(Self::node_contains_newline),
296 Node::SoftBreak | Node::HardBreak => true,
297 Node::Custom(_) => false,
299 _ => false,
300 }
301 }
302
303 pub fn into_string(self) -> EcoString {
320 self.buffer
321 }
322
323 pub fn write_str(&mut self, s: &str) -> WriteResult<()> {
327 self.buffer.push_str(s);
328 Ok(())
329 }
330
331 pub fn write_char(&mut self, c: char) -> WriteResult<()> {
335 self.buffer.push(c);
336 Ok(())
337 }
338
339 pub fn context(&self) -> &NewlineContext {
341 &self.context
342 }
343
344 pub fn current_context(&self) -> &NewlineContext {
346 &self.context
347 }
348
349 pub fn set_context(&mut self, context: NewlineContext) {
351 self.context = context;
352 }
353
354 pub fn push_context(&mut self, context: NewlineContext) {
356 self.context = context;
359 }
360
361 pub fn pop_context(&mut self) -> Option<NewlineContext> {
363 let old = std::mem::take(&mut self.context);
365 Some(old)
366 }
367
368 pub fn with_temporary_context<F, R>(&mut self, context: NewlineContext, f: F) -> WriteResult<R>
370 where
371 F: FnOnce(&mut Self) -> WriteResult<R>,
372 {
373 let original_context = std::mem::replace(&mut self.context, context);
374 let result = f(self);
375 self.context = original_context;
376 result
377 }
378
379 pub fn with_temp_context<F, R>(&mut self, context: NewlineContext, f: F) -> WriteResult<R>
381 where
382 F: FnOnce(&mut Self) -> WriteResult<R>,
383 {
384 self.with_temporary_context(context, f)
385 }
386
387 pub fn write_node(&mut self, node: &Node) -> WriteResult<()> {
389 if let Node::Document(children) = node {
391 return self.write_document_children(children);
392 }
393
394 self.context.validate_node(node)?;
396
397 let buffer_start = self.buffer.len();
399
400 self.write_node_content(node)?;
402
403 let new_content = &self.buffer[buffer_start..];
405
406 if self
408 .context
409 .should_add_trailing_newline(new_content, Some(node))
410 {
411 self.write_char('\n')?;
412 }
413
414 Ok(())
415 }
416
417 pub fn write_nodes(&mut self, nodes: &[Node]) -> WriteResult<()> {
419 for (i, node) in nodes.iter().enumerate() {
420 if i > 0 {
421 self.write_node_separator(&nodes[i - 1], node)?;
422 }
423 self.write_node(node)?;
424 }
425 Ok(())
426 }
427
428 pub fn write_nodes_with_context(&mut self, nodes: &[Node]) -> WriteResult<()> {
430 self.write_nodes(nodes)
431 }
432
433 pub fn write_content_with_context(
435 &mut self,
436 _node: &Node,
437 content_fn: impl FnOnce(&mut Self) -> WriteResult<()>,
438 ) -> WriteResult<()> {
439 content_fn(self)
440 }
441
442 fn write_node_separator(&mut self, prev_node: &Node, current_node: &Node) -> WriteResult<()> {
444 match self.context.mode {
445 RenderingMode::Block => {
446 if prev_node.is_block() && current_node.is_block() {
448 self.ensure_double_newline()?;
449 }
450 }
451 RenderingMode::InlineWithBlocks => {
452 if prev_node.is_block() || current_node.is_block() {
454 self.ensure_single_newline()?;
455 }
456 }
457 RenderingMode::PureInline => {
458 }
460 RenderingMode::TableCell => {
461 if !self.buffer.ends_with(' ') && !self.buffer.is_empty() {
463 self.write_char(' ')?;
464 }
465 }
466 RenderingMode::ListItem => {
467 if prev_node.is_block() && current_node.is_block() {
469 self.ensure_single_newline()?;
470 }
471 }
472 RenderingMode::Custom => {
473 if (prev_node.is_block() || current_node.is_block())
475 && self.context.strategy == NewlineStrategy::Always
476 {
477 self.ensure_single_newline()?;
478 }
479 }
480 }
481 Ok(())
482 }
483
484 fn ensure_single_newline(&mut self) -> WriteResult<()> {
486 if !self.buffer.ends_with('\n') {
487 self.write_char('\n')?;
488 }
489 Ok(())
490 }
491
492 fn ensure_double_newline(&mut self) -> WriteResult<()> {
494 if self.buffer.ends_with("\n\n") {
495 } else if self.buffer.ends_with('\n') {
497 self.write_char('\n')?;
498 } else {
499 self.write_str("\n\n")?;
500 }
501 Ok(())
502 }
503
504 pub(super) fn write_delimited(&mut self, content: &[Node], delimiter: &str) -> WriteResult<()> {
506 self.write_str(delimiter)?;
507
508 let original_context = std::mem::replace(&mut self.context, NewlineContext::pure_inline());
510
511 for node in content {
512 self.write_node_content(node)?;
513 }
514
515 self.context = original_context;
516 self.write_str(delimiter)?;
517 Ok(())
518 }
519}
520
521impl Default for CommonMarkWriter {
522 fn default() -> Self {
523 Self::new()
524 }
525}
526
527impl fmt::Display for Node {
529 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530 let mut writer = CommonMarkWriter::new();
531 let result = if self.is_block() {
532 writer.write_node(self)
533 } else {
534 writer.write_node_content(self)
536 };
537 match result {
538 Ok(_) => write!(f, "{}", writer.into_string()),
539 Err(e) => write!(f, "Error writing Node: {}", e),
540 }
541 }
542}