Skip to main content

mq_markdown/
node.rs

1use crate::node::attr_value::{
2    AttrValue,
3    attr_keys::{self, CHILDREN},
4};
5use itertools::Itertools;
6use markdown::mdast::{self};
7use smol_str::SmolStr;
8use std::{
9    borrow::Cow,
10    fmt::{self, Display},
11};
12
13type ColorPair<'a> = (Cow<'a, str>, Cow<'a, str>);
14
15pub mod attr_value;
16
17/// Color theme for rendering markdown nodes with optional ANSI escape codes.
18///
19/// Each field is a tuple of `(prefix, suffix)` ANSI escape code strings that
20/// wrap the corresponding markdown element during colored rendering.
21#[derive(Debug, Clone, Default)]
22pub struct ColorTheme<'a> {
23    pub heading: ColorPair<'a>,
24    pub code: ColorPair<'a>,
25    pub code_inline: ColorPair<'a>,
26    pub emphasis: ColorPair<'a>,
27    pub strong: ColorPair<'a>,
28    pub link: ColorPair<'a>,
29    pub link_url: ColorPair<'a>,
30    pub image: ColorPair<'a>,
31    pub blockquote_marker: ColorPair<'a>,
32    pub delete: ColorPair<'a>,
33    pub horizontal_rule: ColorPair<'a>,
34    pub html: ColorPair<'a>,
35    pub frontmatter: ColorPair<'a>,
36    pub list_marker: ColorPair<'a>,
37    pub table_separator: ColorPair<'a>,
38    pub math: ColorPair<'a>,
39}
40
41const EMPTY: Cow<'_, str> = Cow::Borrowed("");
42#[cfg(feature = "color")]
43const RESET: Cow<'_, str> = Cow::Borrowed("\x1b[0m");
44
45impl ColorTheme<'_> {
46    pub const PLAIN: ColorTheme<'static> = ColorTheme {
47        heading: (EMPTY, EMPTY),
48        code: (EMPTY, EMPTY),
49        code_inline: (EMPTY, EMPTY),
50        emphasis: (EMPTY, EMPTY),
51        strong: (EMPTY, EMPTY),
52        link: (EMPTY, EMPTY),
53        link_url: (EMPTY, EMPTY),
54        image: (EMPTY, EMPTY),
55        blockquote_marker: (EMPTY, EMPTY),
56        delete: (EMPTY, EMPTY),
57        horizontal_rule: (EMPTY, EMPTY),
58        html: (EMPTY, EMPTY),
59        frontmatter: (EMPTY, EMPTY),
60        list_marker: (EMPTY, EMPTY),
61        table_separator: (EMPTY, EMPTY),
62        math: (EMPTY, EMPTY),
63    };
64
65    #[cfg(feature = "color")]
66    pub const COLORED: ColorTheme<'static> = ColorTheme {
67        heading: (Cow::Borrowed("\x1b[1m\x1b[36m"), RESET),
68        code: (Cow::Borrowed("\x1b[32m"), RESET),
69        code_inline: (Cow::Borrowed("\x1b[32m"), RESET),
70        emphasis: (Cow::Borrowed("\x1b[3m\x1b[33m"), RESET),
71        strong: (Cow::Borrowed("\x1b[1m"), RESET),
72        link: (Cow::Borrowed("\x1b[4m\x1b[34m"), RESET),
73        link_url: (Cow::Borrowed("\x1b[34m"), RESET),
74        image: (Cow::Borrowed("\x1b[35m"), RESET),
75        blockquote_marker: (Cow::Borrowed("\x1b[2m"), RESET),
76        delete: (Cow::Borrowed("\x1b[31m\x1b[2m"), RESET),
77        horizontal_rule: (Cow::Borrowed("\x1b[2m"), RESET),
78        html: (Cow::Borrowed("\x1b[2m"), RESET),
79        frontmatter: (Cow::Borrowed("\x1b[2m"), RESET),
80        list_marker: (Cow::Borrowed("\x1b[33m"), RESET),
81        table_separator: (Cow::Borrowed("\x1b[2m"), RESET),
82        math: (Cow::Borrowed("\x1b[32m"), RESET),
83    };
84
85    /// Creates a color theme from the `MQ_COLORS` environment variable.
86    #[cfg(feature = "color")]
87    pub fn from_env() -> ColorTheme<'static> {
88        match std::env::var("MQ_COLORS") {
89            Ok(v) if !v.is_empty() => ColorTheme::parse_colors(&v),
90            _ => Self::COLORED,
91        }
92    }
93
94    /// Parses a color configuration string into a `ColorTheme`.
95    ///
96    /// The format is `key=SGR:key=SGR:...` where each key corresponds to a
97    /// markdown element and the value is a semicolon-separated list of SGR
98    /// parameters. Unspecified keys use the default colored theme values.
99    /// Invalid entries are silently ignored.
100    #[cfg(feature = "color")]
101    pub fn parse_colors(spec: &str) -> ColorTheme<'static> {
102        let mut theme = Self::COLORED;
103
104        for entry in spec.split(':') {
105            let Some((key, sgr)) = entry.split_once('=') else {
106                continue;
107            };
108
109            if !Self::is_valid_sgr(sgr) {
110                continue;
111            }
112
113            let prefix = Cow::Owned(format!("\x1b[{}m", sgr));
114            let pair = (prefix, RESET);
115
116            match key {
117                "heading" => theme.heading = pair,
118                "code" => theme.code = pair,
119                "code_inline" => theme.code_inline = pair,
120                "emphasis" => theme.emphasis = pair,
121                "strong" => theme.strong = pair,
122                "link" => theme.link = pair,
123                "link_url" => theme.link_url = pair,
124                "image" => theme.image = pair,
125                "blockquote" => theme.blockquote_marker = pair,
126                "delete" => theme.delete = pair,
127                "hr" => theme.horizontal_rule = pair,
128                "html" => theme.html = pair,
129                "frontmatter" => theme.frontmatter = pair,
130                "list" => theme.list_marker = pair,
131                "table" => theme.table_separator = pair,
132                "math" => theme.math = pair,
133                _ => {}
134            }
135        }
136
137        theme
138    }
139
140    /// Validates that a string contains only valid SGR parameters
141    /// (semicolon-separated numbers).
142    #[cfg(feature = "color")]
143    fn is_valid_sgr(sgr: &str) -> bool {
144        !sgr.is_empty() && sgr.split(';').all(|part| part.parse::<u8>().is_ok())
145    }
146}
147
148type Level = u8;
149
150pub const EMPTY_NODE: Node = Node::Text(Text {
151    value: String::new(),
152    position: None,
153});
154
155#[derive(Debug, Clone, Default, PartialEq)]
156pub struct RenderOptions {
157    pub list_style: ListStyle,
158    pub link_url_style: UrlSurroundStyle,
159    pub link_title_style: TitleSurroundStyle,
160}
161
162#[derive(Debug, Clone, Default, PartialEq)]
163pub enum ListStyle {
164    #[default]
165    Dash,
166    Plus,
167    Star,
168}
169
170impl Display for ListStyle {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        match self {
173            ListStyle::Dash => write!(f, "-"),
174            ListStyle::Plus => write!(f, "+"),
175            ListStyle::Star => write!(f, "*"),
176        }
177    }
178}
179
180#[derive(Debug, Clone, PartialEq, Default)]
181#[cfg_attr(
182    feature = "json",
183    derive(serde::Serialize, serde::Deserialize),
184    serde(rename_all = "camelCase")
185)]
186pub struct Url(String);
187
188#[derive(Debug, Clone, PartialEq, Default)]
189pub enum UrlSurroundStyle {
190    #[default]
191    None,
192    Angle,
193}
194
195impl Url {
196    pub fn new(value: String) -> Self {
197        Self(value)
198    }
199
200    pub fn as_str(&self) -> &str {
201        &self.0
202    }
203
204    pub fn to_string_with(&self, options: &RenderOptions) -> Cow<'_, str> {
205        match options.link_url_style {
206            UrlSurroundStyle::None if self.0.is_empty() => Cow::Borrowed(""),
207            UrlSurroundStyle::None => Cow::Borrowed(&self.0),
208            UrlSurroundStyle::Angle => Cow::Owned(format!("<{}>", self.0)),
209        }
210    }
211}
212
213#[derive(Debug, Clone, PartialEq, Default)]
214pub enum TitleSurroundStyle {
215    #[default]
216    Double,
217    Single,
218    Paren,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222#[cfg_attr(
223    feature = "json",
224    derive(serde::Serialize, serde::Deserialize),
225    serde(rename_all = "camelCase")
226)]
227pub struct Title(String);
228
229impl Display for Title {
230    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231        write!(f, "{}", self.0)
232    }
233}
234
235impl Title {
236    pub fn new(value: String) -> Self {
237        Self(value)
238    }
239
240    pub fn to_value(&self) -> String {
241        self.0.clone()
242    }
243
244    pub fn to_string_with(&self, options: &RenderOptions) -> Cow<'_, str> {
245        match options.link_title_style {
246            TitleSurroundStyle::Double => Cow::Owned(format!("\"{}\"", self)),
247            TitleSurroundStyle::Single => Cow::Owned(format!("'{}'", self)),
248            TitleSurroundStyle::Paren => Cow::Owned(format!("({})", self)),
249        }
250    }
251}
252
253#[derive(Debug, Clone, PartialEq)]
254#[cfg_attr(
255    feature = "json",
256    derive(serde::Serialize, serde::Deserialize),
257    serde(rename_all = "camelCase")
258)]
259pub enum TableAlignKind {
260    Left,
261    Right,
262    Center,
263    None,
264}
265
266impl From<mdast::AlignKind> for TableAlignKind {
267    fn from(value: mdast::AlignKind) -> Self {
268        match value {
269            mdast::AlignKind::Left => Self::Left,
270            mdast::AlignKind::Right => Self::Right,
271            mdast::AlignKind::Center => Self::Center,
272            mdast::AlignKind::None => Self::None,
273        }
274    }
275}
276
277impl From<&str> for TableAlignKind {
278    fn from(value: &str) -> Self {
279        match value {
280            "left" | ":---" => Self::Left,
281            "right" | "---:" => Self::Right,
282            "center" | ":---:" => Self::Center,
283            "---" => Self::None,
284            _ => Self::None,
285        }
286    }
287}
288
289impl Display for TableAlignKind {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            TableAlignKind::Left => write!(f, ":---"),
293            TableAlignKind::Right => write!(f, "---:"),
294            TableAlignKind::Center => write!(f, ":---:"),
295            TableAlignKind::None => write!(f, "---"),
296        }
297    }
298}
299
300#[derive(Debug, Clone, PartialEq, Default)]
301#[cfg_attr(
302    feature = "json",
303    derive(serde::Serialize, serde::Deserialize),
304    serde(rename_all = "camelCase", tag = "type")
305)]
306pub struct List {
307    pub values: Vec<Node>,
308    pub index: usize,
309    pub level: Level,
310    pub ordered: bool,
311    pub checked: Option<bool>,
312    pub position: Option<Position>,
313}
314
315#[derive(Debug, Clone, PartialEq)]
316#[cfg_attr(
317    feature = "json",
318    derive(serde::Serialize, serde::Deserialize),
319    serde(rename_all = "camelCase", tag = "type")
320)]
321pub struct TableCell {
322    pub values: Vec<Node>,
323    pub column: usize,
324    pub row: usize,
325    #[cfg_attr(feature = "json", serde(skip))]
326    pub position: Option<Position>,
327}
328
329#[derive(Debug, Clone, PartialEq)]
330#[cfg_attr(
331    feature = "json",
332    derive(serde::Serialize, serde::Deserialize),
333    serde(rename_all = "camelCase", tag = "type")
334)]
335pub struct TableRow {
336    pub values: Vec<Node>,
337    #[cfg_attr(feature = "json", serde(skip))]
338    pub position: Option<Position>,
339}
340
341#[derive(Debug, Clone, PartialEq)]
342#[cfg_attr(
343    feature = "json",
344    derive(serde::Serialize, serde::Deserialize),
345    serde(rename_all = "camelCase", tag = "type")
346)]
347pub struct TableAlign {
348    pub align: Vec<TableAlignKind>,
349    #[cfg_attr(feature = "json", serde(skip))]
350    pub position: Option<Position>,
351}
352
353#[derive(Debug, Clone, PartialEq)]
354#[cfg_attr(
355    feature = "json",
356    derive(serde::Serialize, serde::Deserialize),
357    serde(rename_all = "camelCase", tag = "type")
358)]
359pub struct Fragment {
360    pub values: Vec<Node>,
361}
362
363#[derive(Debug, Clone, PartialEq, Default)]
364#[cfg_attr(
365    feature = "json",
366    derive(serde::Serialize, serde::Deserialize),
367    serde(rename_all = "camelCase", tag = "type")
368)]
369pub struct Code {
370    pub value: String,
371    pub lang: Option<String>,
372    #[cfg_attr(feature = "json", serde(skip))]
373    pub position: Option<Position>,
374    pub meta: Option<String>,
375    pub fence: bool,
376}
377
378#[derive(Debug, Clone, PartialEq)]
379#[cfg_attr(
380    feature = "json",
381    derive(serde::Serialize, serde::Deserialize),
382    serde(rename_all = "camelCase", tag = "type")
383)]
384pub struct Image {
385    pub alt: String,
386    pub url: String,
387    pub title: Option<String>,
388    #[cfg_attr(feature = "json", serde(skip))]
389    pub position: Option<Position>,
390}
391
392#[derive(Debug, Clone, PartialEq, Default)]
393#[cfg_attr(
394    feature = "json",
395    derive(serde::Serialize, serde::Deserialize),
396    serde(rename_all = "camelCase", tag = "type")
397)]
398pub struct ImageRef {
399    pub alt: String,
400    pub ident: String,
401    pub label: Option<String>,
402    #[cfg_attr(feature = "json", serde(skip))]
403    pub position: Option<Position>,
404}
405#[derive(Debug, Clone, PartialEq)]
406#[cfg_attr(
407    feature = "json",
408    derive(serde::Serialize, serde::Deserialize),
409    serde(rename_all = "camelCase", tag = "type")
410)]
411pub struct Link {
412    pub url: Url,
413    pub title: Option<Title>,
414    pub values: Vec<Node>,
415    #[cfg_attr(feature = "json", serde(skip))]
416    pub position: Option<Position>,
417}
418
419#[derive(Debug, Clone, PartialEq, Default)]
420#[cfg_attr(
421    feature = "json",
422    derive(serde::Serialize, serde::Deserialize),
423    serde(rename_all = "camelCase", tag = "type")
424)]
425pub struct FootnoteRef {
426    pub ident: String,
427    pub label: Option<String>,
428    #[cfg_attr(feature = "json", serde(skip))]
429    pub position: Option<Position>,
430}
431
432#[derive(Debug, Clone, PartialEq, Default)]
433#[cfg_attr(
434    feature = "json",
435    derive(serde::Serialize, serde::Deserialize),
436    serde(rename_all = "camelCase", tag = "type")
437)]
438pub struct Footnote {
439    pub ident: String,
440    pub values: Vec<Node>,
441    #[cfg_attr(feature = "json", serde(skip))]
442    pub position: Option<Position>,
443}
444
445#[derive(Debug, Clone, PartialEq, Default)]
446#[cfg_attr(
447    feature = "json",
448    derive(serde::Serialize, serde::Deserialize),
449    serde(rename_all = "camelCase", tag = "type")
450)]
451pub struct LinkRef {
452    pub ident: String,
453    pub label: Option<String>,
454    pub values: Vec<Node>,
455    #[cfg_attr(feature = "json", serde(skip))]
456    pub position: Option<Position>,
457}
458
459#[derive(Debug, Clone, PartialEq, Default)]
460#[cfg_attr(
461    feature = "json",
462    derive(serde::Serialize, serde::Deserialize),
463    serde(rename_all = "camelCase", tag = "type")
464)]
465pub struct Heading {
466    pub depth: u8,
467    pub values: Vec<Node>,
468    #[cfg_attr(feature = "json", serde(skip))]
469    pub position: Option<Position>,
470}
471
472#[derive(Debug, Clone, PartialEq, Default)]
473#[cfg_attr(
474    feature = "json",
475    derive(serde::Serialize, serde::Deserialize),
476    serde(rename_all = "camelCase", tag = "type")
477)]
478pub struct Definition {
479    #[cfg_attr(feature = "json", serde(skip))]
480    pub position: Option<Position>,
481    pub url: Url,
482    pub title: Option<Title>,
483    pub ident: String,
484    pub label: Option<String>,
485}
486#[derive(Debug, Clone, PartialEq)]
487#[cfg_attr(
488    feature = "json",
489    derive(serde::Serialize, serde::Deserialize),
490    serde(rename_all = "camelCase", tag = "type")
491)]
492pub struct Text {
493    pub value: String,
494    #[cfg_attr(feature = "json", serde(skip))]
495    pub position: Option<Position>,
496}
497
498#[derive(Debug, Clone, PartialEq)]
499#[cfg_attr(
500    feature = "json",
501    derive(serde::Serialize, serde::Deserialize),
502    serde(rename_all = "camelCase", tag = "type")
503)]
504pub struct Html {
505    pub value: String,
506    #[cfg_attr(feature = "json", serde(skip))]
507    pub position: Option<Position>,
508}
509
510#[derive(Debug, Clone, PartialEq)]
511#[cfg_attr(
512    feature = "json",
513    derive(serde::Serialize, serde::Deserialize),
514    serde(rename_all = "camelCase", tag = "type")
515)]
516pub struct Toml {
517    pub value: String,
518    #[cfg_attr(feature = "json", serde(skip))]
519    pub position: Option<Position>,
520}
521
522#[derive(Debug, Clone, PartialEq)]
523#[cfg_attr(
524    feature = "json",
525    derive(serde::Serialize, serde::Deserialize),
526    serde(rename_all = "camelCase", tag = "type")
527)]
528pub struct Yaml {
529    pub value: String,
530    #[cfg_attr(feature = "json", serde(skip))]
531    pub position: Option<Position>,
532}
533
534#[derive(Debug, Clone, PartialEq)]
535#[cfg_attr(
536    feature = "json",
537    derive(serde::Serialize, serde::Deserialize),
538    serde(rename_all = "camelCase", tag = "type")
539)]
540pub struct CodeInline {
541    pub value: SmolStr,
542    #[cfg_attr(feature = "json", serde(skip))]
543    pub position: Option<Position>,
544}
545
546#[derive(Debug, Clone, PartialEq)]
547#[cfg_attr(
548    feature = "json",
549    derive(serde::Serialize, serde::Deserialize),
550    serde(rename_all = "camelCase", tag = "type")
551)]
552pub struct MathInline {
553    pub value: SmolStr,
554    #[cfg_attr(feature = "json", serde(skip))]
555    pub position: Option<Position>,
556}
557
558#[derive(Debug, Clone, PartialEq)]
559#[cfg_attr(
560    feature = "json",
561    derive(serde::Serialize, serde::Deserialize),
562    serde(rename_all = "camelCase", tag = "type")
563)]
564pub struct Math {
565    pub value: String,
566    #[cfg_attr(feature = "json", serde(skip))]
567    pub position: Option<Position>,
568}
569
570#[derive(Debug, Clone, PartialEq)]
571#[cfg_attr(
572    feature = "json",
573    derive(serde::Serialize, serde::Deserialize),
574    serde(rename_all = "camelCase", tag = "type")
575)]
576pub struct MdxFlowExpression {
577    pub value: SmolStr,
578    #[cfg_attr(feature = "json", serde(skip))]
579    pub position: Option<Position>,
580}
581
582#[derive(Debug, Clone, PartialEq)]
583#[cfg_attr(
584    feature = "json",
585    derive(serde::Serialize, serde::Deserialize),
586    serde(rename_all = "camelCase", tag = "type")
587)]
588pub struct MdxJsxFlowElement {
589    pub children: Vec<Node>,
590    #[cfg_attr(feature = "json", serde(skip))]
591    pub position: Option<Position>,
592    pub name: Option<String>,
593    pub attributes: Vec<MdxAttributeContent>,
594}
595
596#[derive(Debug, Clone, PartialEq)]
597#[cfg_attr(
598    feature = "json",
599    derive(serde::Serialize, serde::Deserialize),
600    serde(rename_all = "camelCase", tag = "type")
601)]
602pub enum MdxAttributeContent {
603    Expression(SmolStr),
604    Property(MdxJsxAttribute),
605}
606
607#[derive(Debug, Clone, PartialEq)]
608#[cfg_attr(
609    feature = "json",
610    derive(serde::Serialize, serde::Deserialize),
611    serde(rename_all = "camelCase", tag = "type")
612)]
613pub struct MdxJsxAttribute {
614    pub name: SmolStr,
615    pub value: Option<MdxAttributeValue>,
616}
617
618#[derive(Debug, Clone, PartialEq)]
619#[cfg_attr(
620    feature = "json",
621    derive(serde::Serialize, serde::Deserialize),
622    serde(rename_all = "camelCase", tag = "type")
623)]
624pub enum MdxAttributeValue {
625    Expression(SmolStr),
626    Literal(SmolStr),
627}
628
629#[derive(Debug, Clone, PartialEq)]
630#[cfg_attr(
631    feature = "json",
632    derive(serde::Serialize, serde::Deserialize),
633    serde(rename_all = "camelCase", tag = "type")
634)]
635pub struct MdxJsxTextElement {
636    pub children: Vec<Node>,
637    #[cfg_attr(feature = "json", serde(skip))]
638    pub position: Option<Position>,
639    pub name: Option<SmolStr>,
640    pub attributes: Vec<MdxAttributeContent>,
641}
642
643#[derive(Debug, Clone, PartialEq)]
644#[cfg_attr(
645    feature = "json",
646    derive(serde::Serialize, serde::Deserialize),
647    serde(rename_all = "camelCase", tag = "type")
648)]
649pub struct MdxTextExpression {
650    pub value: SmolStr,
651    #[cfg_attr(feature = "json", serde(skip))]
652    pub position: Option<Position>,
653}
654
655#[derive(Debug, Clone, PartialEq)]
656#[cfg_attr(
657    feature = "json",
658    derive(serde::Serialize, serde::Deserialize),
659    serde(rename_all = "camelCase", tag = "type")
660)]
661pub struct MdxJsEsm {
662    pub value: SmolStr,
663    #[cfg_attr(feature = "json", serde(skip))]
664    pub position: Option<Position>,
665}
666
667#[derive(Debug, Clone, PartialEq)]
668#[cfg_attr(
669    feature = "json",
670    derive(serde::Serialize, serde::Deserialize),
671    serde(rename_all = "camelCase", tag = "type")
672)]
673pub struct Blockquote {
674    pub values: Vec<Node>,
675    #[cfg_attr(feature = "json", serde(skip))]
676    pub position: Option<Position>,
677}
678
679#[derive(Debug, Clone, PartialEq)]
680#[cfg_attr(
681    feature = "json",
682    derive(serde::Serialize, serde::Deserialize),
683    serde(rename_all = "camelCase", tag = "type")
684)]
685pub struct Delete {
686    pub values: Vec<Node>,
687    #[cfg_attr(feature = "json", serde(skip))]
688    pub position: Option<Position>,
689}
690
691#[derive(Debug, Clone, PartialEq)]
692#[cfg_attr(
693    feature = "json",
694    derive(serde::Serialize, serde::Deserialize),
695    serde(rename_all = "camelCase", tag = "type")
696)]
697pub struct Emphasis {
698    pub values: Vec<Node>,
699    #[cfg_attr(feature = "json", serde(skip))]
700    pub position: Option<Position>,
701}
702
703#[derive(Debug, Clone, PartialEq)]
704#[cfg_attr(
705    feature = "json",
706    derive(serde::Serialize, serde::Deserialize),
707    serde(rename_all = "camelCase", tag = "type")
708)]
709pub struct Strong {
710    pub values: Vec<Node>,
711    #[cfg_attr(feature = "json", serde(skip))]
712    pub position: Option<Position>,
713}
714
715#[derive(Debug, Clone, PartialEq)]
716#[cfg_attr(
717    feature = "json",
718    derive(serde::Serialize, serde::Deserialize),
719    serde(rename_all = "camelCase", tag = "type")
720)]
721pub struct Break {
722    #[cfg_attr(feature = "json", serde(skip))]
723    pub position: Option<Position>,
724}
725
726#[derive(Debug, Clone, PartialEq)]
727#[cfg_attr(
728    feature = "json",
729    derive(serde::Serialize, serde::Deserialize),
730    serde(rename_all = "camelCase", tag = "type")
731)]
732pub struct HorizontalRule {
733    #[cfg_attr(feature = "json", serde(skip))]
734    pub position: Option<Position>,
735}
736
737#[derive(Debug, Clone)]
738#[cfg_attr(
739    feature = "json",
740    derive(serde::Serialize, serde::Deserialize),
741    serde(rename_all = "camelCase", untagged)
742)]
743pub enum Node {
744    Blockquote(Blockquote),
745    Break(Break),
746    Definition(Definition),
747    Delete(Delete),
748    Heading(Heading),
749    Emphasis(Emphasis),
750    Footnote(Footnote),
751    FootnoteRef(FootnoteRef),
752    Html(Html),
753    Yaml(Yaml),
754    Toml(Toml),
755    Image(Image),
756    ImageRef(ImageRef),
757    CodeInline(CodeInline),
758    MathInline(MathInline),
759    Link(Link),
760    LinkRef(LinkRef),
761    Math(Math),
762    List(List),
763    TableAlign(TableAlign),
764    TableRow(TableRow),
765    TableCell(TableCell),
766    Code(Code),
767    Strong(Strong),
768    HorizontalRule(HorizontalRule),
769    MdxFlowExpression(MdxFlowExpression),
770    MdxJsxFlowElement(MdxJsxFlowElement),
771    MdxJsxTextElement(MdxJsxTextElement),
772    MdxTextExpression(MdxTextExpression),
773    MdxJsEsm(MdxJsEsm),
774    Text(Text),
775    Fragment(Fragment),
776    Empty,
777}
778
779impl PartialEq for Node {
780    fn eq(&self, other: &Self) -> bool {
781        self.to_string() == other.to_string()
782    }
783}
784
785impl PartialOrd for Node {
786    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
787        let (self_node, other_node) = (self, other);
788        let self_pos = self_node.position();
789        let other_pos = other_node.position();
790
791        match (self_pos, other_pos) {
792            (Some(self_pos), Some(other_pos)) => match self_pos.start.line.cmp(&other_pos.start.line) {
793                std::cmp::Ordering::Equal => self_pos.start.column.partial_cmp(&other_pos.start.column),
794                ordering => Some(ordering),
795            },
796            (Some(_), None) => Some(std::cmp::Ordering::Less),
797            (None, Some(_)) => Some(std::cmp::Ordering::Greater),
798            (None, None) => Some(self.name().cmp(&other.name())),
799        }
800    }
801}
802
803#[derive(Debug, Clone, PartialEq)]
804#[cfg_attr(
805    feature = "json",
806    derive(serde::Serialize, serde::Deserialize),
807    serde(rename_all = "camelCase")
808)]
809pub struct Position {
810    pub start: Point,
811    pub end: Point,
812}
813
814#[derive(Debug, Clone, PartialEq)]
815#[cfg_attr(
816    feature = "json",
817    derive(serde::Serialize, serde::Deserialize),
818    serde(rename_all = "camelCase")
819)]
820pub struct Point {
821    pub line: usize,
822    pub column: usize,
823}
824
825impl From<markdown::unist::Position> for Position {
826    fn from(value: markdown::unist::Position) -> Self {
827        Self {
828            start: Point {
829                line: value.start.line,
830                column: value.start.column,
831            },
832            end: Point {
833                line: value.end.line,
834                column: value.end.column,
835            },
836        }
837    }
838}
839
840impl From<String> for Node {
841    fn from(value: String) -> Self {
842        Self::Text(Text { value, position: None })
843    }
844}
845
846impl From<&str> for Node {
847    fn from(value: &str) -> Self {
848        Self::Text(Text {
849            value: value.to_string(),
850            position: None,
851        })
852    }
853}
854
855impl Display for Node {
856    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857        write!(f, "{}", self.to_string_with(&RenderOptions::default()))
858    }
859}
860
861impl Node {
862    pub fn map_values<E, F>(&self, f: &mut F) -> Result<Node, E>
863    where
864        E: std::error::Error,
865        F: FnMut(&Node) -> Result<Node, E>,
866    {
867        Self::_map_values(self.clone(), f)
868    }
869
870    fn _map_values<E, F>(node: Node, f: &mut F) -> Result<Node, E>
871    where
872        E: std::error::Error,
873        F: FnMut(&Node) -> Result<Node, E>,
874    {
875        match f(&node)? {
876            Node::Fragment(mut v) => {
877                let values = v
878                    .values
879                    .into_iter()
880                    .map(|node| Self::_map_values(node, f))
881                    .collect::<Result<Vec<_>, _>>();
882                match values {
883                    Ok(values) => {
884                        v.values = values;
885                        Ok(Node::Fragment(v))
886                    }
887                    Err(e) => Err(e),
888                }
889            }
890            node => Ok(node),
891        }
892    }
893
894    pub fn to_fragment(&self) -> Node {
895        match self.clone() {
896            Node::List(List { values, .. })
897            | Node::TableCell(TableCell { values, .. })
898            | Node::TableRow(TableRow { values, .. })
899            | Node::Link(Link { values, .. })
900            | Node::Footnote(Footnote { values, .. })
901            | Node::LinkRef(LinkRef { values, .. })
902            | Node::Heading(Heading { values, .. })
903            | Node::Blockquote(Blockquote { values, .. })
904            | Node::Delete(Delete { values, .. })
905            | Node::Emphasis(Emphasis { values, .. })
906            | Node::Strong(Strong { values, .. }) => Self::Fragment(Fragment { values }),
907            node @ Node::Fragment(_) => node,
908            _ => Self::Empty,
909        }
910    }
911
912    pub fn apply_fragment(&mut self, fragment: Node) {
913        Self::_apply_fragment(self, fragment)
914    }
915
916    fn _apply_fragment(node: &mut Node, fragment: Node) {
917        match node {
918            Node::List(List { values, .. })
919            | Node::TableCell(TableCell { values, .. })
920            | Node::TableRow(TableRow { values, .. })
921            | Node::Link(Link { values, .. })
922            | Node::Footnote(Footnote { values, .. })
923            | Node::LinkRef(LinkRef { values, .. })
924            | Node::Heading(Heading { values, .. })
925            | Node::Blockquote(Blockquote { values, .. })
926            | Node::Delete(Delete { values, .. })
927            | Node::Emphasis(Emphasis { values, .. })
928            | Node::Strong(Strong { values, .. }) => {
929                if let Node::Fragment(Fragment { values: new_values }) = fragment {
930                    let new_values = values
931                        .iter()
932                        .zip(new_values)
933                        .map(|(current_value, new_value)| {
934                            if new_value.is_empty() {
935                                current_value.clone()
936                            } else if new_value.is_fragment() {
937                                let mut current_value = current_value.clone();
938                                Self::_apply_fragment(&mut current_value, new_value);
939                                current_value
940                            } else {
941                                new_value
942                            }
943                        })
944                        .collect::<Vec<_>>();
945                    *values = new_values;
946                }
947            }
948            _ => {}
949        }
950    }
951
952    pub fn to_string_with(&self, options: &RenderOptions) -> String {
953        self.render_with_theme(options, &ColorTheme::PLAIN)
954    }
955
956    /// Returns a colored string representation of this node using ANSI escape codes.
957    #[cfg(feature = "color")]
958    pub fn to_colored_string_with(&self, options: &RenderOptions) -> String {
959        self.render_with_theme(options, &ColorTheme::COLORED)
960    }
961
962    pub(crate) fn render_with_theme(&self, options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
963        match self.clone() {
964            Self::List(List {
965                level,
966                checked,
967                values,
968                ordered,
969                index,
970                ..
971            }) => {
972                let marker = if ordered {
973                    format!("{}.", index + 1)
974                } else {
975                    options.list_style.to_string()
976                };
977                let (ms, me) = &theme.list_marker;
978                format!(
979                    "{}{}{}{} {}{}",
980                    "  ".repeat(level as usize),
981                    ms,
982                    marker,
983                    me,
984                    checked.map(|it| if it { "[x] " } else { "[ ] " }).unwrap_or_else(|| ""),
985                    render_values(&values, options, theme)
986                )
987            }
988            Self::TableRow(TableRow { values, .. }) => {
989                let (ts, te) = &theme.table_separator;
990                let cells = values
991                    .iter()
992                    .map(|cell| cell.render_with_theme(options, theme))
993                    .collect::<Vec<_>>()
994                    .join("|");
995                format!("{}|{}{}|", ts, te, cells)
996            }
997            Self::TableCell(TableCell { values, .. }) => render_values(&values, options, theme),
998            Self::TableAlign(TableAlign { align, .. }) => {
999                let (ts, te) = &theme.table_separator;
1000                format!("{}|{}|{}", ts, align.iter().map(|a| a.to_string()).join("|"), te)
1001            }
1002            Self::Blockquote(Blockquote { values, .. }) => {
1003                let (bs, be) = &theme.blockquote_marker;
1004                render_values(&values, options, theme)
1005                    .split('\n')
1006                    .map(|line| format!("{}> {}{}", bs, be, line))
1007                    .join("\n")
1008            }
1009            Self::Code(Code {
1010                value,
1011                lang,
1012                fence,
1013                meta,
1014                ..
1015            }) => {
1016                let (cs, ce) = &theme.code;
1017                let meta = meta.as_deref().map(|meta| format!(" {}", meta)).unwrap_or_default();
1018
1019                match lang {
1020                    Some(lang) => format!("{}```{}{}\n{}\n```{}", cs, lang, meta, value, ce),
1021                    None if fence => {
1022                        format!("{}```{}\n{}\n```{}", cs, lang.as_deref().unwrap_or(""), value, ce)
1023                    }
1024                    None => value.lines().map(|line| format!("{}    {}{}", cs, line, ce)).join("\n"),
1025                }
1026            }
1027            Self::Definition(Definition {
1028                ident,
1029                label,
1030                url,
1031                title,
1032                ..
1033            }) => {
1034                let (us, ue) = &theme.link_url;
1035                format!(
1036                    "[{}]: {}{}{}{}",
1037                    label.unwrap_or(ident),
1038                    us,
1039                    url.to_string_with(options),
1040                    ue,
1041                    title
1042                        .map(|title| format!(" {}", title.to_string_with(options)))
1043                        .unwrap_or_default()
1044                )
1045            }
1046            Self::Delete(Delete { values, .. }) => {
1047                let (ds, de) = &theme.delete;
1048                format!("{}~~{}~~{}", ds, render_values(&values, options, theme), de)
1049            }
1050            Self::Emphasis(Emphasis { values, .. }) => {
1051                let (es, ee) = &theme.emphasis;
1052                format!("{}*{}*{}", es, render_values(&values, options, theme), ee)
1053            }
1054            Self::Footnote(Footnote { values, ident, .. }) => {
1055                format!("[^{}]: {}", ident, render_values(&values, options, theme))
1056            }
1057            Self::FootnoteRef(FootnoteRef { label, .. }) => {
1058                format!("[^{}]", label.unwrap_or_default())
1059            }
1060            Self::Heading(Heading { depth, values, .. }) => {
1061                let (hs, he) = &theme.heading;
1062                format!(
1063                    "{}{} {}{}",
1064                    hs,
1065                    "#".repeat(depth as usize),
1066                    render_values(&values, options, theme),
1067                    he
1068                )
1069            }
1070            Self::Html(Html { value, .. }) => {
1071                let (hs, he) = &theme.html;
1072                format!("{}{}{}", hs, value, he)
1073            }
1074            Self::Image(Image { alt, url, title, .. }) => {
1075                let (is, ie) = &theme.image;
1076                format!(
1077                    "{}![{}]({}{}){}",
1078                    is,
1079                    alt,
1080                    url.replace(' ', "%20"),
1081                    title.map(|it| format!(" \"{}\"", it)).unwrap_or_default(),
1082                    ie
1083                )
1084            }
1085            Self::ImageRef(ImageRef { alt, ident, label, .. }) => {
1086                let (is, ie) = &theme.image;
1087                if alt == ident {
1088                    format!("{}![{}]{}", is, ident, ie)
1089                } else {
1090                    format!("{}![{}][{}]{}", is, alt, label.unwrap_or(ident), ie)
1091                }
1092            }
1093            Self::CodeInline(CodeInline { value, .. }) => {
1094                let (cs, ce) = &theme.code_inline;
1095                format!("{}`{}`{}", cs, value, ce)
1096            }
1097            Self::MathInline(MathInline { value, .. }) => {
1098                let (ms, me) = &theme.math;
1099                format!("{}${}${}", ms, value, me)
1100            }
1101            Self::Link(Link { url, title, values, .. }) => {
1102                let (ls, le) = &theme.link;
1103                format!(
1104                    "{}[{}]({}{}){}",
1105                    ls,
1106                    render_values(&values, options, theme),
1107                    url.to_string_with(options),
1108                    title
1109                        .map(|title| format!(" {}", title.to_string_with(options)))
1110                        .unwrap_or_default(),
1111                    le
1112                )
1113            }
1114            Self::LinkRef(LinkRef { values, label, .. }) => {
1115                let (ls, le) = &theme.link;
1116                let ident = render_values(&values, options, theme);
1117
1118                label
1119                    .map(|label| {
1120                        if label == ident {
1121                            format!("{}[{}]{}", ls, ident, le)
1122                        } else {
1123                            format!("{}[{}][{}]{}", ls, ident, label, le)
1124                        }
1125                    })
1126                    .unwrap_or(format!("{}[{}]{}", ls, ident, le))
1127            }
1128            Self::Math(Math { value, .. }) => {
1129                let (ms, me) = &theme.math;
1130                format!("{}$$\n{}\n$${}", ms, value, me)
1131            }
1132            Self::Text(Text { value, .. }) => value,
1133            Self::MdxFlowExpression(mdx_flow_expression) => {
1134                format!("{{{}}}", mdx_flow_expression.value)
1135            }
1136            Self::MdxJsxFlowElement(mdx_jsx_flow_element) => {
1137                let name = mdx_jsx_flow_element.name.unwrap_or_default();
1138                let attributes = if mdx_jsx_flow_element.attributes.is_empty() {
1139                    "".to_string()
1140                } else {
1141                    format!(
1142                        " {}",
1143                        mdx_jsx_flow_element
1144                            .attributes
1145                            .into_iter()
1146                            .map(Self::mdx_attribute_content_to_string)
1147                            .join(" ")
1148                    )
1149                };
1150
1151                if mdx_jsx_flow_element.children.is_empty() {
1152                    format!("<{}{} />", name, attributes,)
1153                } else {
1154                    format!(
1155                        "<{}{}>{}</{}>",
1156                        name,
1157                        attributes,
1158                        render_values(&mdx_jsx_flow_element.children, options, theme),
1159                        name
1160                    )
1161                }
1162            }
1163            Self::MdxJsxTextElement(mdx_jsx_text_element) => {
1164                let name = mdx_jsx_text_element.name.unwrap_or_default();
1165                let attributes = if mdx_jsx_text_element.attributes.is_empty() {
1166                    "".to_string()
1167                } else {
1168                    format!(
1169                        " {}",
1170                        mdx_jsx_text_element
1171                            .attributes
1172                            .into_iter()
1173                            .map(Self::mdx_attribute_content_to_string)
1174                            .join(" ")
1175                    )
1176                };
1177
1178                if mdx_jsx_text_element.children.is_empty() {
1179                    format!("<{}{} />", name, attributes,)
1180                } else {
1181                    format!(
1182                        "<{}{}>{}</{}>",
1183                        name,
1184                        attributes,
1185                        render_values(&mdx_jsx_text_element.children, options, theme),
1186                        name
1187                    )
1188                }
1189            }
1190            Self::MdxTextExpression(mdx_text_expression) => {
1191                format!("{{{}}}", mdx_text_expression.value)
1192            }
1193            Self::MdxJsEsm(mdxjs_esm) => mdxjs_esm.value.to_string(),
1194            Self::Strong(Strong { values, .. }) => {
1195                let (ss, se) = &theme.strong;
1196                format!(
1197                    "{}**{}**{}",
1198                    ss,
1199                    values
1200                        .iter()
1201                        .map(|value| value.render_with_theme(options, theme))
1202                        .collect::<String>(),
1203                    se
1204                )
1205            }
1206            Self::Yaml(Yaml { value, .. }) => {
1207                let (fs, fe) = &theme.frontmatter;
1208                format!("{}---\n{}\n---{}", fs, value, fe)
1209            }
1210            Self::Toml(Toml { value, .. }) => {
1211                let (fs, fe) = &theme.frontmatter;
1212                format!("{}+++\n{}\n+++{}", fs, value, fe)
1213            }
1214            Self::Break(_) => "\\".to_string(),
1215            Self::HorizontalRule(_) => {
1216                let (hs, he) = &theme.horizontal_rule;
1217                format!("{}---{}", hs, he)
1218            }
1219            Self::Fragment(Fragment { values }) => values
1220                .iter()
1221                .map(|value| value.render_with_theme(options, theme))
1222                .filter(|s| !s.is_empty())
1223                .join("\n"),
1224            Self::Empty => String::new(),
1225        }
1226    }
1227
1228    pub fn node_values(&self) -> Vec<Node> {
1229        match self.clone() {
1230            Self::Blockquote(v) => v.values,
1231            Self::Delete(v) => v.values,
1232            Self::Heading(h) => h.values,
1233            Self::Emphasis(v) => v.values,
1234            Self::List(l) => l.values,
1235            Self::Strong(v) => v.values,
1236            _ => vec![self.clone()],
1237        }
1238    }
1239
1240    pub fn find_at_index(&self, index: usize) -> Option<Node> {
1241        match self {
1242            Self::Blockquote(v) => v.values.get(index).cloned(),
1243            Self::Delete(v) => v.values.get(index).cloned(),
1244            Self::Emphasis(v) => v.values.get(index).cloned(),
1245            Self::Strong(v) => v.values.get(index).cloned(),
1246            Self::Heading(v) => v.values.get(index).cloned(),
1247            Self::List(v) => v.values.get(index).cloned(),
1248            Self::TableCell(v) => v.values.get(index).cloned(),
1249            Self::TableRow(v) => v.values.get(index).cloned(),
1250            _ => None,
1251        }
1252    }
1253
1254    pub fn value(&self) -> String {
1255        match self.clone() {
1256            Self::Blockquote(v) => values_to_value(v.values),
1257            Self::Definition(d) => d.url.as_str().to_string(),
1258            Self::Delete(v) => values_to_value(v.values),
1259            Self::Heading(h) => values_to_value(h.values),
1260            Self::Emphasis(v) => values_to_value(v.values),
1261            Self::Footnote(f) => values_to_value(f.values),
1262            Self::FootnoteRef(f) => f.ident,
1263            Self::Html(v) => v.value,
1264            Self::Yaml(v) => v.value,
1265            Self::Toml(v) => v.value,
1266            Self::Image(i) => i.url,
1267            Self::ImageRef(i) => i.ident,
1268            Self::CodeInline(v) => v.value.to_string(),
1269            Self::MathInline(v) => v.value.to_string(),
1270            Self::Link(l) => l.url.as_str().to_string(),
1271            Self::LinkRef(l) => l.ident,
1272            Self::Math(v) => v.value,
1273            Self::List(l) => values_to_value(l.values),
1274            Self::TableCell(c) => values_to_value(c.values),
1275            Self::TableRow(c) => values_to_value(c.values),
1276            Self::Code(c) => c.value,
1277            Self::Strong(v) => values_to_value(v.values),
1278            Self::Text(t) => t.value,
1279            Self::Break { .. } => String::new(),
1280            Self::TableAlign(_) => String::new(),
1281            Self::MdxFlowExpression(mdx) => mdx.value.to_string(),
1282            Self::MdxJsxFlowElement(mdx) => values_to_value(mdx.children),
1283            Self::MdxTextExpression(mdx) => mdx.value.to_string(),
1284            Self::MdxJsxTextElement(mdx) => values_to_value(mdx.children),
1285            Self::MdxJsEsm(mdx) => mdx.value.to_string(),
1286            Self::HorizontalRule { .. } => String::new(),
1287            Self::Fragment(v) => values_to_value(v.values),
1288            Self::Empty => String::new(),
1289        }
1290    }
1291
1292    pub fn name(&self) -> SmolStr {
1293        match self {
1294            Self::Blockquote(_) => "blockquote".into(),
1295            Self::Break { .. } => "break".into(),
1296            Self::Definition(_) => "definition".into(),
1297            Self::Delete(_) => "delete".into(),
1298            Self::Heading(Heading { depth, .. }) => match depth {
1299                1 => "h1".into(),
1300                2 => "h2".into(),
1301                3 => "h3".into(),
1302                4 => "h4".into(),
1303                5 => "h5".into(),
1304                6 => "h6".into(),
1305                _ => "h".into(),
1306            },
1307            Self::Emphasis(_) => "emphasis".into(),
1308            Self::Footnote(_) => "footnote".into(),
1309            Self::FootnoteRef(_) => "footnoteref".into(),
1310            Self::Html(_) => "html".into(),
1311            Self::Yaml(_) => "yaml".into(),
1312            Self::Toml(_) => "toml".into(),
1313            Self::Image(_) => "image".into(),
1314            Self::ImageRef(_) => "image_ref".into(),
1315            Self::CodeInline(_) => "code_inline".into(),
1316            Self::MathInline(_) => "math_inline".into(),
1317            Self::Link(_) => "link".into(),
1318            Self::LinkRef(_) => "link_ref".into(),
1319            Self::Math(_) => "math".into(),
1320            Self::List(_) => "list".into(),
1321            Self::TableAlign(_) => "table_align".into(),
1322            Self::TableRow(_) => "table_row".into(),
1323            Self::TableCell(_) => "table_cell".into(),
1324            Self::Code(_) => "code".into(),
1325            Self::Strong(_) => "strong".into(),
1326            Self::HorizontalRule { .. } => "Horizontal_rule".into(),
1327            Self::MdxFlowExpression(_) => "mdx_flow_expression".into(),
1328            Self::MdxJsxFlowElement(_) => "mdx_jsx_flow_element".into(),
1329            Self::MdxJsxTextElement(_) => "mdx_jsx_text_element".into(),
1330            Self::MdxTextExpression(_) => "mdx_text_expression".into(),
1331            Self::MdxJsEsm(_) => "mdx_js_esm".into(),
1332            Self::Text(_) => "text".into(),
1333            Self::Fragment(_) | Self::Empty => "".into(),
1334        }
1335    }
1336
1337    /// Get the children nodes of the current node.
1338    pub fn children(&self) -> Vec<Node> {
1339        match self.attr(CHILDREN) {
1340            Some(AttrValue::Array(children)) => children,
1341            _ => Vec::new(),
1342        }
1343    }
1344
1345    pub fn set_position(&mut self, pos: Option<Position>) {
1346        match self {
1347            Self::Blockquote(v) => v.position = pos,
1348            Self::Definition(d) => d.position = pos,
1349            Self::Delete(v) => v.position = pos,
1350            Self::Heading(h) => h.position = pos,
1351            Self::Emphasis(v) => v.position = pos,
1352            Self::Footnote(f) => f.position = pos,
1353            Self::FootnoteRef(f) => f.position = pos,
1354            Self::Html(v) => v.position = pos,
1355            Self::Yaml(v) => v.position = pos,
1356            Self::Toml(v) => v.position = pos,
1357            Self::Image(i) => i.position = pos,
1358            Self::ImageRef(i) => i.position = pos,
1359            Self::CodeInline(v) => v.position = pos,
1360            Self::MathInline(v) => v.position = pos,
1361            Self::Link(l) => l.position = pos,
1362            Self::LinkRef(l) => l.position = pos,
1363            Self::Math(v) => v.position = pos,
1364            Self::Code(c) => c.position = pos,
1365            Self::TableCell(c) => c.position = pos,
1366            Self::TableRow(r) => r.position = pos,
1367            Self::TableAlign(c) => c.position = pos,
1368            Self::List(l) => l.position = pos,
1369            Self::Strong(s) => s.position = pos,
1370            Self::MdxFlowExpression(m) => m.position = pos,
1371            Self::MdxTextExpression(m) => m.position = pos,
1372            Self::MdxJsEsm(m) => m.position = pos,
1373            Self::MdxJsxFlowElement(m) => m.position = pos,
1374            Self::MdxJsxTextElement(m) => m.position = pos,
1375            Self::Break(b) => b.position = pos,
1376            Self::HorizontalRule(h) => h.position = pos,
1377            Self::Text(t) => t.position = pos,
1378            Self::Fragment(_) | Self::Empty => {}
1379        }
1380    }
1381
1382    pub fn position(&self) -> Option<Position> {
1383        match self {
1384            Self::Blockquote(v) => v.position.clone(),
1385            Self::Definition(d) => d.position.clone(),
1386            Self::Delete(v) => v.position.clone(),
1387            Self::Heading(h) => h.position.clone(),
1388            Self::Emphasis(v) => v.position.clone(),
1389            Self::Footnote(f) => f.position.clone(),
1390            Self::FootnoteRef(f) => f.position.clone(),
1391            Self::Html(v) => v.position.clone(),
1392            Self::Yaml(v) => v.position.clone(),
1393            Self::Toml(v) => v.position.clone(),
1394            Self::Image(i) => i.position.clone(),
1395            Self::ImageRef(i) => i.position.clone(),
1396            Self::CodeInline(v) => v.position.clone(),
1397            Self::MathInline(v) => v.position.clone(),
1398            Self::Link(l) => l.position.clone(),
1399            Self::LinkRef(l) => l.position.clone(),
1400            Self::Math(v) => v.position.clone(),
1401            Self::Code(c) => c.position.clone(),
1402            Self::TableCell(c) => c.position.clone(),
1403            Self::TableRow(r) => r.position.clone(),
1404            Self::TableAlign(c) => c.position.clone(),
1405            Self::List(l) => l.position.clone(),
1406            Self::Strong(s) => s.position.clone(),
1407            Self::MdxFlowExpression(m) => m.position.clone(),
1408            Self::MdxTextExpression(m) => m.position.clone(),
1409            Self::MdxJsEsm(m) => m.position.clone(),
1410            Self::MdxJsxFlowElement(m) => m.position.clone(),
1411            Self::MdxJsxTextElement(m) => m.position.clone(),
1412            Self::Break(b) => b.position.clone(),
1413            Self::Text(t) => t.position.clone(),
1414            Self::HorizontalRule(h) => h.position.clone(),
1415            Self::Fragment(v) => {
1416                let positions: Vec<Position> = v.values.iter().filter_map(|node| node.position()).collect();
1417
1418                match (positions.first(), positions.last()) {
1419                    (Some(start), Some(end)) => Some(Position {
1420                        start: start.start.clone(),
1421                        end: end.end.clone(),
1422                    }),
1423                    _ => None,
1424                }
1425            }
1426            Self::Empty => None,
1427        }
1428    }
1429
1430    pub fn is_empty(&self) -> bool {
1431        matches!(self, Self::Empty)
1432    }
1433
1434    pub fn is_fragment(&self) -> bool {
1435        matches!(self, Self::Fragment(_))
1436    }
1437
1438    pub fn is_empty_fragment(&self) -> bool {
1439        if let Self::Fragment(_) = self {
1440            Self::_fragment_inner_nodes(self).is_empty()
1441        } else {
1442            false
1443        }
1444    }
1445
1446    fn _fragment_inner_nodes(node: &Node) -> Vec<Node> {
1447        if let Self::Fragment(fragment) = node {
1448            fragment.values.iter().flat_map(Self::_fragment_inner_nodes).collect()
1449        } else {
1450            vec![node.clone()]
1451        }
1452    }
1453
1454    pub fn is_inline_code(&self) -> bool {
1455        matches!(self, Self::CodeInline(_))
1456    }
1457
1458    pub fn is_inline_math(&self) -> bool {
1459        matches!(self, Self::MathInline(_))
1460    }
1461
1462    pub fn is_strong(&self) -> bool {
1463        matches!(self, Self::Strong(_))
1464    }
1465
1466    pub fn is_list(&self) -> bool {
1467        matches!(self, Self::List(_))
1468    }
1469
1470    pub fn is_emphasis(&self) -> bool {
1471        matches!(self, Self::Emphasis(_))
1472    }
1473
1474    pub fn is_delete(&self) -> bool {
1475        matches!(self, Self::Delete(_))
1476    }
1477
1478    pub fn is_link(&self) -> bool {
1479        matches!(self, Self::Link(_))
1480    }
1481
1482    pub fn is_link_ref(&self) -> bool {
1483        matches!(self, Self::LinkRef(_))
1484    }
1485
1486    pub fn is_text(&self) -> bool {
1487        matches!(self, Self::Text(_))
1488    }
1489
1490    pub fn is_image(&self) -> bool {
1491        matches!(self, Self::Image(_))
1492    }
1493
1494    pub fn is_horizontal_rule(&self) -> bool {
1495        matches!(self, Self::HorizontalRule { .. })
1496    }
1497
1498    pub fn is_blockquote(&self) -> bool {
1499        matches!(self, Self::Blockquote(_))
1500    }
1501
1502    pub fn is_html(&self) -> bool {
1503        matches!(self, Self::Html { .. })
1504    }
1505
1506    pub fn is_footnote(&self) -> bool {
1507        matches!(self, Self::Footnote(_))
1508    }
1509
1510    pub fn is_mdx_jsx_flow_element(&self) -> bool {
1511        matches!(self, Self::MdxJsxFlowElement(MdxJsxFlowElement { .. }))
1512    }
1513
1514    pub fn is_mdx_js_esm(&self) -> bool {
1515        matches!(self, Self::MdxJsEsm(MdxJsEsm { .. }))
1516    }
1517
1518    pub fn is_toml(&self) -> bool {
1519        matches!(self, Self::Toml { .. })
1520    }
1521
1522    pub fn is_yaml(&self) -> bool {
1523        matches!(self, Self::Yaml { .. })
1524    }
1525
1526    pub fn is_break(&self) -> bool {
1527        matches!(self, Self::Break { .. })
1528    }
1529
1530    pub fn is_mdx_text_expression(&self) -> bool {
1531        matches!(self, Self::MdxTextExpression(MdxTextExpression { .. }))
1532    }
1533
1534    pub fn is_footnote_ref(&self) -> bool {
1535        matches!(self, Self::FootnoteRef { .. })
1536    }
1537
1538    pub fn is_image_ref(&self) -> bool {
1539        matches!(self, Self::ImageRef(_))
1540    }
1541
1542    pub fn is_mdx_jsx_text_element(&self) -> bool {
1543        matches!(self, Self::MdxJsxTextElement(MdxJsxTextElement { .. }))
1544    }
1545
1546    pub fn is_math(&self) -> bool {
1547        matches!(self, Self::Math(_))
1548    }
1549
1550    pub fn is_mdx_flow_expression(&self) -> bool {
1551        matches!(self, Self::MdxFlowExpression(MdxFlowExpression { .. }))
1552    }
1553
1554    pub fn is_definition(&self) -> bool {
1555        matches!(self, Self::Definition(_))
1556    }
1557
1558    pub fn is_table_align(&self) -> bool {
1559        matches!(self, Self::TableAlign(_))
1560    }
1561
1562    pub fn is_code(&self, lang: Option<SmolStr>) -> bool {
1563        if let Self::Code(Code { lang: node_lang, .. }) = &self {
1564            if lang.is_none() {
1565                true
1566            } else {
1567                node_lang.clone().unwrap_or_default() == lang.unwrap_or_default()
1568            }
1569        } else {
1570            false
1571        }
1572    }
1573
1574    pub fn is_heading(&self, depth: Option<u8>) -> bool {
1575        if let Self::Heading(Heading {
1576            depth: heading_depth, ..
1577        }) = &self
1578        {
1579            depth.is_none() || *heading_depth == depth.unwrap()
1580        } else {
1581            false
1582        }
1583    }
1584
1585    pub fn with_value(&self, value: &str) -> Self {
1586        match self.clone() {
1587            Self::Blockquote(mut v) => {
1588                if let Some(node) = v.values.first() {
1589                    v.values[0] = node.with_value(value);
1590                }
1591
1592                Self::Blockquote(v)
1593            }
1594            Self::Delete(mut v) => {
1595                if let Some(node) = v.values.first() {
1596                    v.values[0] = node.with_value(value);
1597                }
1598
1599                Self::Delete(v)
1600            }
1601            Self::Emphasis(mut v) => {
1602                if let Some(node) = v.values.first() {
1603                    v.values[0] = node.with_value(value);
1604                }
1605
1606                Self::Emphasis(v)
1607            }
1608            Self::Html(mut html) => {
1609                html.value = value.to_string();
1610                Self::Html(html)
1611            }
1612            Self::Yaml(mut yaml) => {
1613                yaml.value = value.to_string();
1614                Self::Yaml(yaml)
1615            }
1616            Self::Toml(mut toml) => {
1617                toml.value = value.to_string();
1618                Self::Toml(toml)
1619            }
1620            Self::CodeInline(mut code) => {
1621                code.value = value.into();
1622                Self::CodeInline(code)
1623            }
1624            Self::MathInline(mut math) => {
1625                math.value = value.into();
1626                Self::MathInline(math)
1627            }
1628            Self::Math(mut math) => {
1629                math.value = value.to_string();
1630                Self::Math(math)
1631            }
1632            Self::List(mut v) => {
1633                if let Some(node) = v.values.first() {
1634                    v.values[0] = node.with_value(value);
1635                }
1636
1637                Self::List(v)
1638            }
1639            Self::TableCell(mut v) => {
1640                if let Some(node) = v.values.first() {
1641                    v.values[0] = node.with_value(value);
1642                }
1643
1644                Self::TableCell(v)
1645            }
1646            Self::TableRow(mut row) => {
1647                row.values = row
1648                    .values
1649                    .iter()
1650                    .zip(value.split(","))
1651                    .map(|(cell, value)| cell.with_value(value))
1652                    .collect::<Vec<_>>();
1653
1654                Self::TableRow(row)
1655            }
1656            Self::Strong(mut v) => {
1657                if let Some(node) = v.values.first() {
1658                    v.values[0] = node.with_value(value);
1659                }
1660
1661                Self::Strong(v)
1662            }
1663            Self::Code(mut code) => {
1664                code.value = value.to_string();
1665                Self::Code(code)
1666            }
1667            Self::Image(mut image) => {
1668                image.url = value.to_string();
1669                Self::Image(image)
1670            }
1671            Self::ImageRef(mut image) => {
1672                image.ident = value.to_string();
1673                image.label = Some(value.to_string());
1674                Self::ImageRef(image)
1675            }
1676            Self::Link(mut link) => {
1677                link.url = Url(value.to_string());
1678                Self::Link(link)
1679            }
1680            Self::LinkRef(mut v) => {
1681                v.label = Some(value.to_string());
1682                v.ident = value.to_string();
1683                Self::LinkRef(v)
1684            }
1685            Self::Footnote(mut footnote) => {
1686                footnote.ident = value.to_string();
1687                Self::Footnote(footnote)
1688            }
1689            Self::FootnoteRef(mut footnote) => {
1690                footnote.ident = value.to_string();
1691                footnote.label = Some(value.to_string());
1692                Self::FootnoteRef(footnote)
1693            }
1694            Self::Heading(mut v) => {
1695                if let Some(node) = v.values.first() {
1696                    v.values[0] = node.with_value(value);
1697                }
1698
1699                Self::Heading(v)
1700            }
1701            Self::Definition(mut def) => {
1702                def.url = Url(value.to_string());
1703                Self::Definition(def)
1704            }
1705            node @ Self::Break { .. } => node,
1706            node @ Self::TableAlign(_) => node,
1707            node @ Self::HorizontalRule { .. } => node,
1708            Self::Text(mut text) => {
1709                text.value = value.to_string();
1710                Self::Text(text)
1711            }
1712            Self::MdxFlowExpression(mut mdx) => {
1713                mdx.value = value.into();
1714                Self::MdxFlowExpression(mdx)
1715            }
1716            Self::MdxTextExpression(mut mdx) => {
1717                mdx.value = value.into();
1718                Self::MdxTextExpression(mdx)
1719            }
1720            Self::MdxJsEsm(mut mdx) => {
1721                mdx.value = value.into();
1722                Self::MdxJsEsm(mdx)
1723            }
1724            Self::MdxJsxFlowElement(mut mdx) => {
1725                if let Some(node) = mdx.children.first() {
1726                    mdx.children[0] = node.with_value(value);
1727                }
1728
1729                Self::MdxJsxFlowElement(MdxJsxFlowElement {
1730                    name: mdx.name,
1731                    attributes: mdx.attributes,
1732                    children: mdx.children,
1733                    ..mdx
1734                })
1735            }
1736            Self::MdxJsxTextElement(mut mdx) => {
1737                if let Some(node) = mdx.children.first() {
1738                    mdx.children[0] = node.with_value(value);
1739                }
1740
1741                Self::MdxJsxTextElement(MdxJsxTextElement {
1742                    name: mdx.name,
1743                    attributes: mdx.attributes,
1744                    children: mdx.children,
1745                    ..mdx
1746                })
1747            }
1748            node @ Self::Fragment(_) | node @ Self::Empty => node,
1749        }
1750    }
1751
1752    pub fn with_children_value(&self, value: &str, index: usize) -> Self {
1753        match self.clone() {
1754            Self::Blockquote(mut v) => {
1755                if v.values.get(index).is_some() {
1756                    v.values[index] = v.values[index].with_value(value);
1757                }
1758
1759                Self::Blockquote(v)
1760            }
1761            Self::Delete(mut v) => {
1762                if v.values.get(index).is_some() {
1763                    v.values[index] = v.values[index].with_value(value);
1764                }
1765
1766                Self::Delete(v)
1767            }
1768            Self::Emphasis(mut v) => {
1769                if v.values.get(index).is_some() {
1770                    v.values[index] = v.values[index].with_value(value);
1771                }
1772
1773                Self::Emphasis(v)
1774            }
1775            Self::List(mut v) => {
1776                if v.values.get(index).is_some() {
1777                    v.values[index] = v.values[index].with_value(value);
1778                }
1779
1780                Self::List(v)
1781            }
1782            Self::TableCell(mut v) => {
1783                if v.values.get(index).is_some() {
1784                    v.values[index] = v.values[index].with_value(value);
1785                }
1786
1787                Self::TableCell(v)
1788            }
1789            Self::Strong(mut v) => {
1790                if v.values.get(index).is_some() {
1791                    v.values[index] = v.values[index].with_value(value);
1792                }
1793
1794                Self::Strong(v)
1795            }
1796            Self::LinkRef(mut v) => {
1797                if v.values.get(index).is_some() {
1798                    v.values[index] = v.values[index].with_value(value);
1799                }
1800
1801                Self::LinkRef(v)
1802            }
1803            Self::Heading(mut v) => {
1804                if v.values.get(index).is_some() {
1805                    v.values[index] = v.values[index].with_value(value);
1806                }
1807
1808                Self::Heading(v)
1809            }
1810            Self::MdxJsxFlowElement(mut mdx) => {
1811                if let Some(node) = mdx.children.first() {
1812                    mdx.children[index] = node.with_value(value);
1813                }
1814
1815                Self::MdxJsxFlowElement(MdxJsxFlowElement {
1816                    name: mdx.name,
1817                    attributes: mdx.attributes,
1818                    children: mdx.children,
1819                    ..mdx
1820                })
1821            }
1822            Self::MdxJsxTextElement(mut mdx) => {
1823                if let Some(node) = mdx.children.first() {
1824                    mdx.children[index] = node.with_value(value);
1825                }
1826
1827                Self::MdxJsxTextElement(MdxJsxTextElement {
1828                    name: mdx.name,
1829                    attributes: mdx.attributes,
1830                    children: mdx.children,
1831                    ..mdx
1832                })
1833            }
1834            a => a,
1835        }
1836    }
1837
1838    /// Returns the value of the specified attribute, if present.
1839    pub fn attr(&self, attr: &str) -> Option<AttrValue> {
1840        match self {
1841            Node::Footnote(Footnote { ident, values, .. }) => match attr {
1842                attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
1843                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1844                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1845                _ => None,
1846            },
1847            Node::Html(Html { value, .. }) => match attr {
1848                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1849                _ => None,
1850            },
1851            Node::Text(Text { value, .. }) => match attr {
1852                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1853                _ => None,
1854            },
1855            Node::Code(Code {
1856                value,
1857                lang,
1858                meta,
1859                fence,
1860                ..
1861            }) => match attr {
1862                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1863                attr_keys::LANG => lang.clone().map(AttrValue::String),
1864                attr_keys::META => meta.clone().map(AttrValue::String),
1865                attr_keys::FENCE => Some(AttrValue::Boolean(*fence)),
1866                _ => None,
1867            },
1868            Node::CodeInline(CodeInline { value, .. }) => match attr {
1869                attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
1870                _ => None,
1871            },
1872            Node::MathInline(MathInline { value, .. }) => match attr {
1873                attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
1874                _ => None,
1875            },
1876            Node::Math(Math { value, .. }) => match attr {
1877                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1878                _ => None,
1879            },
1880            Node::Yaml(Yaml { value, .. }) => match attr {
1881                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1882                _ => None,
1883            },
1884            Node::Toml(Toml { value, .. }) => match attr {
1885                attr_keys::VALUE => Some(AttrValue::String(value.clone())),
1886                _ => None,
1887            },
1888            Node::Image(Image { alt, url, title, .. }) => match attr {
1889                attr_keys::ALT => Some(AttrValue::String(alt.clone())),
1890                attr_keys::URL => Some(AttrValue::String(url.clone())),
1891                attr_keys::TITLE => title.clone().map(AttrValue::String),
1892                _ => None,
1893            },
1894            Node::ImageRef(ImageRef { alt, ident, label, .. }) => match attr {
1895                attr_keys::ALT => Some(AttrValue::String(alt.clone())),
1896                attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
1897                attr_keys::LABEL => label.clone().map(AttrValue::String),
1898                _ => None,
1899            },
1900            Node::Link(Link { url, title, values, .. }) => match attr {
1901                attr_keys::URL => Some(AttrValue::String(url.as_str().to_string())),
1902                attr_keys::TITLE => title.as_ref().map(|t| AttrValue::String(t.to_value())),
1903                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1904                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1905                _ => None,
1906            },
1907            Node::LinkRef(LinkRef { ident, label, .. }) => match attr {
1908                attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
1909                attr_keys::LABEL => label.clone().map(AttrValue::String),
1910                _ => None,
1911            },
1912            Node::FootnoteRef(FootnoteRef { ident, label, .. }) => match attr {
1913                attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
1914                attr_keys::LABEL => label.clone().map(AttrValue::String),
1915                _ => None,
1916            },
1917            Node::Definition(Definition {
1918                ident,
1919                url,
1920                title,
1921                label,
1922                ..
1923            }) => match attr {
1924                attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
1925                attr_keys::URL => Some(AttrValue::String(url.as_str().to_string())),
1926                attr_keys::TITLE => title.as_ref().map(|t| AttrValue::String(t.to_value())),
1927                attr_keys::LABEL => label.clone().map(AttrValue::String),
1928                _ => None,
1929            },
1930            Node::Heading(Heading { depth, values, .. }) => match attr {
1931                attr_keys::DEPTH | attr_keys::LEVEL => Some(AttrValue::Integer(*depth as i64)),
1932                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1933                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1934                _ => None,
1935            },
1936            Node::List(List {
1937                index,
1938                level,
1939                ordered,
1940                checked,
1941                values,
1942                ..
1943            }) => match attr {
1944                attr_keys::INDEX => Some(AttrValue::Integer(*index as i64)),
1945                attr_keys::LEVEL => Some(AttrValue::Integer(*level as i64)),
1946                attr_keys::ORDERED => Some(AttrValue::Boolean(*ordered)),
1947                attr_keys::CHECKED => checked.map(AttrValue::Boolean),
1948                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1949                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1950                _ => None,
1951            },
1952            Node::TableCell(TableCell {
1953                column, row, values, ..
1954            }) => match attr {
1955                attr_keys::COLUMN => Some(AttrValue::Integer(*column as i64)),
1956                attr_keys::ROW => Some(AttrValue::Integer(*row as i64)),
1957                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1958                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1959                _ => None,
1960            },
1961            Node::TableAlign(TableAlign { align, .. }) => match attr {
1962                attr_keys::ALIGN => Some(AttrValue::String(
1963                    align.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(","),
1964                )),
1965                _ => None,
1966            },
1967            Node::MdxFlowExpression(MdxFlowExpression { value, .. }) => match attr {
1968                attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
1969                _ => None,
1970            },
1971            Node::MdxTextExpression(MdxTextExpression { value, .. }) => match attr {
1972                attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
1973                _ => None,
1974            },
1975            Node::MdxJsEsm(MdxJsEsm { value, .. }) => match attr {
1976                attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
1977                _ => None,
1978            },
1979            Node::MdxJsxFlowElement(MdxJsxFlowElement { name, .. }) => match attr {
1980                attr_keys::NAME => name.clone().map(AttrValue::String),
1981                _ => None,
1982            },
1983            Node::MdxJsxTextElement(MdxJsxTextElement { name, .. }) => match attr {
1984                attr_keys::NAME => name.as_ref().map(|n| AttrValue::String(n.to_string())),
1985                _ => None,
1986            },
1987            Node::Strong(Strong { values, .. })
1988            | Node::Blockquote(Blockquote { values, .. })
1989            | Node::Delete(Delete { values, .. })
1990            | Node::Emphasis(Emphasis { values, .. })
1991            | Node::TableRow(TableRow { values, .. })
1992            | Node::Fragment(Fragment { values, .. }) => match attr {
1993                attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
1994                attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
1995                _ => None,
1996            },
1997            Node::Break(_) | Node::HorizontalRule(_) | Node::Empty => None,
1998        }
1999    }
2000
2001    /// Sets the value of the specified attribute for the node, if supported.
2002    pub fn set_attr(&mut self, attr: &str, value: impl Into<AttrValue>) {
2003        let value = value.into();
2004        let value_str = value.as_string();
2005
2006        match self {
2007            Node::Footnote(f) => {
2008                if attr == attr_keys::IDENT {
2009                    f.ident = value_str;
2010                }
2011            }
2012            Node::Html(h) => {
2013                if attr == attr_keys::VALUE {
2014                    h.value = value_str;
2015                }
2016            }
2017            Node::Text(t) => {
2018                if attr == attr_keys::VALUE {
2019                    t.value = value_str;
2020                }
2021            }
2022            Node::Code(c) => match attr {
2023                attr_keys::VALUE => {
2024                    c.value = value_str;
2025                }
2026                attr_keys::LANG | "language" => {
2027                    c.lang = if value_str.is_empty() { None } else { Some(value_str) };
2028                }
2029                attr_keys::META => {
2030                    c.meta = if value_str.is_empty() { None } else { Some(value_str) };
2031                }
2032                attr_keys::FENCE => {
2033                    c.fence = match value {
2034                        AttrValue::Boolean(b) => b,
2035                        _ => value_str == "true",
2036                    };
2037                }
2038                _ => (),
2039            },
2040            Node::CodeInline(ci) => {
2041                if attr == attr_keys::VALUE {
2042                    ci.value = value_str.into();
2043                }
2044            }
2045            Node::MathInline(mi) => {
2046                if attr == attr_keys::VALUE {
2047                    mi.value = value_str.into();
2048                }
2049            }
2050            Node::Math(m) => {
2051                if attr == attr_keys::VALUE {
2052                    m.value = value_str;
2053                }
2054            }
2055            Node::Yaml(y) => {
2056                if attr == attr_keys::VALUE {
2057                    y.value = value_str;
2058                }
2059            }
2060            Node::Toml(t) => {
2061                if attr == attr_keys::VALUE {
2062                    t.value = value_str;
2063                }
2064            }
2065            Node::Image(i) => match attr {
2066                attr_keys::ALT => {
2067                    i.alt = value_str;
2068                }
2069                attr_keys::URL => {
2070                    i.url = value_str;
2071                }
2072                attr_keys::TITLE => {
2073                    i.title = if value_str.is_empty() { None } else { Some(value_str) };
2074                }
2075                _ => (),
2076            },
2077            Node::ImageRef(i) => match attr {
2078                attr_keys::ALT => {
2079                    i.alt = value_str;
2080                }
2081                attr_keys::IDENT => {
2082                    i.ident = value_str;
2083                }
2084                attr_keys::LABEL => {
2085                    i.label = if value_str.is_empty() { None } else { Some(value_str) };
2086                }
2087                _ => (),
2088            },
2089            Node::Link(l) => match attr {
2090                attr_keys::URL => {
2091                    l.url = Url::new(value_str);
2092                }
2093                attr_keys::TITLE => {
2094                    l.title = if value_str.is_empty() {
2095                        None
2096                    } else {
2097                        Some(Title::new(value_str))
2098                    };
2099                }
2100                _ => (),
2101            },
2102            Node::LinkRef(l) => match attr {
2103                attr_keys::IDENT => {
2104                    l.ident = value_str;
2105                }
2106                attr_keys::LABEL => {
2107                    l.label = if value_str.is_empty() { None } else { Some(value_str) };
2108                }
2109                _ => (),
2110            },
2111            Node::FootnoteRef(f) => match attr {
2112                attr_keys::IDENT => {
2113                    f.ident = value_str;
2114                }
2115                attr_keys::LABEL => {
2116                    f.label = if value_str.is_empty() { None } else { Some(value_str) };
2117                }
2118                _ => (),
2119            },
2120            Node::Definition(d) => match attr {
2121                attr_keys::IDENT => {
2122                    d.ident = value_str;
2123                }
2124                attr_keys::URL => {
2125                    d.url = Url::new(value_str);
2126                }
2127                attr_keys::TITLE => {
2128                    d.title = if value_str.is_empty() {
2129                        None
2130                    } else {
2131                        Some(Title::new(value_str))
2132                    };
2133                }
2134                attr_keys::LABEL => {
2135                    d.label = if value_str.is_empty() { None } else { Some(value_str) };
2136                }
2137                _ => (),
2138            },
2139            Node::Heading(h) => match attr {
2140                attr_keys::DEPTH | attr_keys::LEVEL => {
2141                    h.depth = match value {
2142                        AttrValue::Integer(i) => i as u8,
2143                        _ => value_str.parse::<u8>().unwrap_or(h.depth),
2144                    };
2145                }
2146                _ => (),
2147            },
2148            Node::List(l) => match attr {
2149                attr_keys::INDEX => {
2150                    l.index = match value {
2151                        AttrValue::Integer(i) => i as usize,
2152                        _ => value_str.parse::<usize>().unwrap_or(l.index),
2153                    };
2154                }
2155                attr_keys::LEVEL => {
2156                    l.level = match value {
2157                        AttrValue::Integer(i) => i as u8,
2158                        _ => value_str.parse::<u8>().unwrap_or(l.level),
2159                    };
2160                }
2161                attr_keys::ORDERED => {
2162                    l.ordered = match value {
2163                        AttrValue::Boolean(b) => b,
2164                        _ => value_str == "true",
2165                    };
2166                }
2167                attr_keys::CHECKED => {
2168                    l.checked = if value_str.is_empty() {
2169                        None
2170                    } else {
2171                        Some(match value {
2172                            AttrValue::Boolean(b) => b,
2173                            _ => value_str == "true",
2174                        })
2175                    };
2176                }
2177                _ => (),
2178            },
2179            Node::TableCell(c) => match attr {
2180                attr_keys::COLUMN => {
2181                    c.column = match value {
2182                        AttrValue::Integer(i) => i as usize,
2183                        _ => value_str.parse::<usize>().unwrap_or(c.column),
2184                    };
2185                }
2186                attr_keys::ROW => {
2187                    c.row = match value {
2188                        AttrValue::Integer(i) => i as usize,
2189                        _ => value_str.parse::<usize>().unwrap_or(c.row),
2190                    };
2191                }
2192                _ => (),
2193            },
2194            Node::TableAlign(th) => {
2195                if attr == attr_keys::ALIGN {
2196                    th.align = value_str.split(',').map(|s| s.trim().into()).collect();
2197                }
2198            }
2199            Node::MdxFlowExpression(m) => {
2200                if attr == attr_keys::VALUE {
2201                    m.value = value_str.into();
2202                }
2203            }
2204            Node::MdxTextExpression(m) => {
2205                if attr == attr_keys::VALUE {
2206                    m.value = value_str.into();
2207                }
2208            }
2209            Node::MdxJsEsm(m) => {
2210                if attr == attr_keys::VALUE {
2211                    m.value = value_str.into();
2212                }
2213            }
2214            Node::MdxJsxFlowElement(m) => {
2215                if attr == attr_keys::NAME {
2216                    m.name = if value_str.is_empty() { None } else { Some(value_str) };
2217                }
2218            }
2219            Node::MdxJsxTextElement(m) => {
2220                if attr == attr_keys::NAME {
2221                    m.name = if value_str.is_empty() {
2222                        None
2223                    } else {
2224                        Some(value_str.into())
2225                    };
2226                }
2227            }
2228            Node::Delete(_)
2229            | Node::Blockquote(_)
2230            | Node::Emphasis(_)
2231            | Node::Strong(_)
2232            | Node::TableRow(_)
2233            | Node::Break(_)
2234            | Node::HorizontalRule(_)
2235            | Node::Fragment(_)
2236            | Node::Empty => (),
2237        }
2238    }
2239
2240    pub(crate) fn from_mdast_node(node: mdast::Node) -> Vec<Node> {
2241        match node.clone() {
2242            mdast::Node::Root(root) => root
2243                .children
2244                .into_iter()
2245                .flat_map(Self::from_mdast_node)
2246                .collect::<Vec<_>>(),
2247            mdast::Node::ListItem(list_item) => list_item
2248                .children
2249                .into_iter()
2250                .flat_map(Self::from_mdast_node)
2251                .collect::<Vec<_>>(),
2252            mdast::Node::List(list) => Self::mdast_list_items(&list, 0),
2253            mdast::Node::Table(table) => table
2254                .children
2255                .iter()
2256                .enumerate()
2257                .flat_map(|(row, n)| {
2258                    if let mdast::Node::TableRow(table_row) = n {
2259                        itertools::concat(vec![
2260                            table_row
2261                                .children
2262                                .iter()
2263                                .enumerate()
2264                                .flat_map(|(column, node)| {
2265                                    if let mdast::Node::TableCell(_) = node {
2266                                        vec![Self::TableCell(TableCell {
2267                                            row,
2268                                            column,
2269                                            values: Self::mdast_children_to_node(node.clone()),
2270                                            position: node.position().map(|p| p.clone().into()),
2271                                        })]
2272                                    } else {
2273                                        Vec::new()
2274                                    }
2275                                })
2276                                .collect(),
2277                            if row == 0 {
2278                                vec![Self::TableAlign(TableAlign {
2279                                    align: table.align.iter().map(|a| (*a).into()).collect::<Vec<_>>(),
2280                                    position: n.position().map(|p| Position {
2281                                        start: Point {
2282                                            line: p.start.line + 1,
2283                                            column: 1,
2284                                        },
2285                                        end: Point {
2286                                            line: p.start.line + 1,
2287                                            column: 1,
2288                                        },
2289                                    }),
2290                                })]
2291                            } else {
2292                                Vec::new()
2293                            },
2294                        ])
2295                    } else {
2296                        Vec::new()
2297                    }
2298                })
2299                .collect(),
2300            mdast::Node::Code(mdast::Code {
2301                value,
2302                position,
2303                lang,
2304                meta,
2305                ..
2306            }) => match lang {
2307                Some(lang) => {
2308                    vec![Self::Code(Code {
2309                        value,
2310                        lang: Some(lang),
2311                        position: position.map(|p| p.clone().into()),
2312                        meta,
2313                        fence: true,
2314                    })]
2315                }
2316                None => {
2317                    let line_count = position
2318                        .as_ref()
2319                        .map(|p| p.end.line - p.start.line + 1)
2320                        .unwrap_or_default();
2321                    let fence = value.lines().count() != line_count;
2322
2323                    vec![Self::Code(Code {
2324                        value,
2325                        lang,
2326                        position: position.map(|p| p.clone().into()),
2327                        meta,
2328                        fence,
2329                    })]
2330                }
2331            },
2332            mdast::Node::Blockquote(mdast::Blockquote { position, .. }) => {
2333                vec![Self::Blockquote(Blockquote {
2334                    values: Self::mdast_children_to_node(node),
2335                    position: position.map(|p| p.clone().into()),
2336                })]
2337            }
2338            mdast::Node::Definition(mdast::Definition {
2339                url,
2340                title,
2341                identifier,
2342                label,
2343                position,
2344                ..
2345            }) => {
2346                vec![Self::Definition(Definition {
2347                    ident: identifier,
2348                    url: Url(url),
2349                    label,
2350                    title: title.map(Title),
2351                    position: position.map(|p| p.clone().into()),
2352                })]
2353            }
2354            mdast::Node::Heading(mdast::Heading { depth, position, .. }) => {
2355                vec![Self::Heading(Heading {
2356                    values: Self::mdast_children_to_node(node),
2357                    depth,
2358                    position: position.map(|p| p.clone().into()),
2359                })]
2360            }
2361            mdast::Node::Break(mdast::Break { position }) => {
2362                vec![Self::Break(Break {
2363                    position: position.map(|p| p.clone().into()),
2364                })]
2365            }
2366            mdast::Node::Delete(mdast::Delete { position, .. }) => {
2367                vec![Self::Delete(Delete {
2368                    values: Self::mdast_children_to_node(node),
2369                    position: position.map(|p| p.clone().into()),
2370                })]
2371            }
2372            mdast::Node::Emphasis(mdast::Emphasis { position, .. }) => {
2373                vec![Self::Emphasis(Emphasis {
2374                    values: Self::mdast_children_to_node(node),
2375                    position: position.map(|p| p.clone().into()),
2376                })]
2377            }
2378            mdast::Node::Strong(mdast::Strong { position, .. }) => {
2379                vec![Self::Strong(Strong {
2380                    values: Self::mdast_children_to_node(node),
2381                    position: position.map(|p| p.clone().into()),
2382                })]
2383            }
2384            mdast::Node::ThematicBreak(mdast::ThematicBreak { position, .. }) => {
2385                vec![Self::HorizontalRule(HorizontalRule {
2386                    position: position.map(|p| p.clone().into()),
2387                })]
2388            }
2389            mdast::Node::Html(mdast::Html { value, position }) => {
2390                vec![Self::Html(Html {
2391                    value,
2392                    position: position.map(|p| p.clone().into()),
2393                })]
2394            }
2395            mdast::Node::Yaml(mdast::Yaml { value, position }) => {
2396                vec![Self::Yaml(Yaml {
2397                    value,
2398                    position: position.map(|p| p.clone().into()),
2399                })]
2400            }
2401            mdast::Node::Toml(mdast::Toml { value, position }) => {
2402                vec![Self::Toml(Toml {
2403                    value,
2404                    position: position.map(|p| p.clone().into()),
2405                })]
2406            }
2407            mdast::Node::Image(mdast::Image {
2408                alt,
2409                url,
2410                title,
2411                position,
2412            }) => {
2413                vec![Self::Image(Image {
2414                    alt,
2415                    url,
2416                    title,
2417                    position: position.map(|p| p.clone().into()),
2418                })]
2419            }
2420            mdast::Node::ImageReference(mdast::ImageReference {
2421                alt,
2422                identifier,
2423                label,
2424                position,
2425                ..
2426            }) => {
2427                vec![Self::ImageRef(ImageRef {
2428                    alt,
2429                    ident: identifier,
2430                    label,
2431                    position: position.map(|p| p.clone().into()),
2432                })]
2433            }
2434            mdast::Node::InlineCode(mdast::InlineCode { value, position, .. }) => {
2435                vec![Self::CodeInline(CodeInline {
2436                    value: value.into(),
2437                    position: position.map(|p| p.clone().into()),
2438                })]
2439            }
2440            mdast::Node::InlineMath(mdast::InlineMath { value, position }) => {
2441                vec![Self::MathInline(MathInline {
2442                    value: value.into(),
2443                    position: position.map(|p| p.clone().into()),
2444                })]
2445            }
2446            mdast::Node::Link(mdast::Link {
2447                title,
2448                url,
2449                position,
2450                children,
2451                ..
2452            }) => {
2453                let converted: Vec<Node> = children.into_iter().flat_map(Self::from_mdast_node).collect();
2454                // Flatten nested links that arise from GFM autolink literal parsing.
2455                // When the link text is a bare URL (e.g. `[https://x](https://x)`),
2456                // markdown-rs parses the inner text as another Link node with the same
2457                // URL, which causes double-nesting on re-serialisation.  Unwrap any
2458                // such inner link whose URL matches the outer one.
2459                let values = converted
2460                    .into_iter()
2461                    .flat_map(|child| match child {
2462                        Self::Link(Link {
2463                            url: ref inner_url,
2464                            ref values,
2465                            ..
2466                        }) if inner_url.0 == url => values.clone(),
2467                        other => vec![other],
2468                    })
2469                    .collect();
2470                vec![Self::Link(Link {
2471                    url: Url(url),
2472                    title: title.map(Title),
2473                    values,
2474                    position: position.map(|p| p.clone().into()),
2475                })]
2476            }
2477            mdast::Node::LinkReference(mdast::LinkReference {
2478                identifier,
2479                label,
2480                position,
2481                children,
2482                ..
2483            }) => {
2484                vec![Self::LinkRef(LinkRef {
2485                    ident: identifier,
2486                    values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
2487                    label,
2488                    position: position.map(|p| p.clone().into()),
2489                })]
2490            }
2491            mdast::Node::Math(mdast::Math { value, position, .. }) => {
2492                vec![Self::Math(Math {
2493                    value,
2494                    position: position.map(|p| p.clone().into()),
2495                })]
2496            }
2497            mdast::Node::FootnoteDefinition(mdast::FootnoteDefinition {
2498                identifier,
2499                position,
2500                children,
2501                ..
2502            }) => {
2503                vec![Self::Footnote(Footnote {
2504                    ident: identifier,
2505                    values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
2506                    position: position.map(|p| p.clone().into()),
2507                })]
2508            }
2509            mdast::Node::FootnoteReference(mdast::FootnoteReference {
2510                identifier,
2511                label,
2512                position,
2513                ..
2514            }) => {
2515                vec![Self::FootnoteRef(FootnoteRef {
2516                    ident: identifier,
2517                    label,
2518                    position: position.map(|p| p.clone().into()),
2519                })]
2520            }
2521            mdast::Node::MdxFlowExpression(mdx) => {
2522                vec![Self::MdxFlowExpression(MdxFlowExpression {
2523                    value: mdx.value.into(),
2524                    position: mdx.position.map(|position| position.into()),
2525                })]
2526            }
2527            mdast::Node::MdxJsxFlowElement(mdx) => {
2528                vec![Self::MdxJsxFlowElement(MdxJsxFlowElement {
2529                    children: mdx
2530                        .children
2531                        .into_iter()
2532                        .flat_map(Self::from_mdast_node)
2533                        .collect::<Vec<_>>(),
2534                    position: mdx.position.map(|p| p.clone().into()),
2535                    name: mdx.name,
2536                    attributes: mdx
2537                        .attributes
2538                        .iter()
2539                        .map(|attr| match attr {
2540                            mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
2541                                MdxAttributeContent::Expression(value.into())
2542                            }
2543                            mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
2544                                MdxAttributeContent::Property(MdxJsxAttribute {
2545                                    name: name.into(),
2546                                    value: value.as_ref().map(|value| match value {
2547                                        mdast::AttributeValue::Literal(value) => {
2548                                            MdxAttributeValue::Literal(value.into())
2549                                        }
2550                                        mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
2551                                            value,
2552                                            ..
2553                                        }) => MdxAttributeValue::Expression(value.into()),
2554                                    }),
2555                                })
2556                            }
2557                        })
2558                        .collect(),
2559                })]
2560            }
2561            mdast::Node::MdxJsxTextElement(mdx) => {
2562                vec![Self::MdxJsxTextElement(MdxJsxTextElement {
2563                    children: mdx
2564                        .children
2565                        .into_iter()
2566                        .flat_map(Self::from_mdast_node)
2567                        .collect::<Vec<_>>(),
2568                    position: mdx.position.map(|p| p.clone().into()),
2569                    name: mdx.name.map(|name| name.into()),
2570                    attributes: mdx
2571                        .attributes
2572                        .iter()
2573                        .map(|attr| match attr {
2574                            mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
2575                                MdxAttributeContent::Expression(value.into())
2576                            }
2577                            mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
2578                                MdxAttributeContent::Property(MdxJsxAttribute {
2579                                    name: name.into(),
2580                                    value: value.as_ref().map(|value| match value {
2581                                        mdast::AttributeValue::Literal(value) => {
2582                                            MdxAttributeValue::Literal(value.into())
2583                                        }
2584                                        mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
2585                                            value,
2586                                            ..
2587                                        }) => MdxAttributeValue::Expression(value.into()),
2588                                    }),
2589                                })
2590                            }
2591                        })
2592                        .collect(),
2593                })]
2594            }
2595            mdast::Node::MdxTextExpression(mdx) => {
2596                vec![Self::MdxTextExpression(MdxTextExpression {
2597                    value: mdx.value.into(),
2598                    position: mdx.position.map(|position| position.into()),
2599                })]
2600            }
2601            mdast::Node::MdxjsEsm(mdx) => {
2602                vec![Self::MdxJsEsm(MdxJsEsm {
2603                    value: mdx.value.into(),
2604                    position: mdx.position.map(|position| position.into()),
2605                })]
2606            }
2607            mdast::Node::Text(mdast::Text { position, value, .. }) => {
2608                vec![Self::Text(Text {
2609                    value,
2610                    position: position.map(|p| p.clone().into()),
2611                })]
2612            }
2613            mdast::Node::Paragraph(mdast::Paragraph { children, .. }) => {
2614                children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>()
2615            }
2616            _ => Vec::new(),
2617        }
2618    }
2619
2620    fn mdast_children_to_node(node: mdast::Node) -> Vec<Node> {
2621        node.children()
2622            .map(|children| {
2623                children
2624                    .iter()
2625                    .flat_map(|v| Self::from_mdast_node(v.clone()))
2626                    .collect::<Vec<_>>()
2627            })
2628            .unwrap_or_else(|| vec![EMPTY_NODE])
2629    }
2630
2631    fn mdast_list_items(list: &mdast::List, level: Level) -> Vec<Node> {
2632        list.children
2633            .iter()
2634            .flat_map(|n| {
2635                if let mdast::Node::ListItem(list_item) = n {
2636                    let values = Self::from_mdast_node(n.clone())
2637                        .into_iter()
2638                        .filter(|value| !matches!(value, Self::List(_)))
2639                        .collect::<Vec<_>>();
2640                    let position = if values.is_empty() {
2641                        n.position().map(|p| p.clone().into())
2642                    } else {
2643                        let first_pos = values.first().and_then(|v| v.position());
2644                        let last_pos = values.last().and_then(|v| v.position());
2645                        match (first_pos, last_pos) {
2646                            (Some(start), Some(end)) => Some(Position {
2647                                start: start.start.clone(),
2648                                end: end.end.clone(),
2649                            }),
2650                            _ => n.position().map(|p| p.clone().into()),
2651                        }
2652                    };
2653
2654                    itertools::concat(vec![
2655                        vec![Self::List(List {
2656                            level,
2657                            index: 0,
2658                            ordered: list.ordered,
2659                            checked: list_item.checked,
2660                            values,
2661                            position,
2662                        })],
2663                        list_item
2664                            .children
2665                            .iter()
2666                            .flat_map(|node| {
2667                                if let mdast::Node::List(sub_list) = node {
2668                                    Self::mdast_list_items(sub_list, level + 1)
2669                                } else if let mdast::Node::ListItem(list_item) = node {
2670                                    let values = Self::from_mdast_node(n.clone())
2671                                        .into_iter()
2672                                        .filter(|value| !matches!(value, Self::List(_)))
2673                                        .collect::<Vec<_>>();
2674                                    let position = if values.is_empty() {
2675                                        n.position().map(|p| p.clone().into())
2676                                    } else {
2677                                        let first_pos = values.first().and_then(|v| v.position());
2678                                        let last_pos = values.last().and_then(|v| v.position());
2679                                        match (first_pos, last_pos) {
2680                                            (Some(start), Some(end)) => Some(Position {
2681                                                start: start.start.clone(),
2682                                                end: end.end.clone(),
2683                                            }),
2684                                            _ => n.position().map(|p| p.clone().into()),
2685                                        }
2686                                    };
2687                                    vec![Self::List(List {
2688                                        level: level + 1,
2689                                        index: 0,
2690                                        ordered: list.ordered,
2691                                        checked: list_item.checked,
2692                                        values,
2693                                        position,
2694                                    })]
2695                                } else {
2696                                    Vec::new()
2697                                }
2698                            })
2699                            .collect(),
2700                    ])
2701                } else if let mdast::Node::List(sub_list) = n {
2702                    Self::mdast_list_items(sub_list, level + 1)
2703                } else {
2704                    Vec::new()
2705                }
2706            })
2707            .enumerate()
2708            .filter_map(|(i, node)| match node {
2709                Self::List(List {
2710                    level,
2711                    index: _,
2712                    ordered,
2713                    checked,
2714                    values,
2715                    position,
2716                }) => Some(Self::List(List {
2717                    level,
2718                    index: i,
2719                    ordered,
2720                    checked,
2721                    values,
2722                    position,
2723                })),
2724                _ => None,
2725            })
2726            .collect()
2727    }
2728
2729    fn mdx_attribute_content_to_string(attr: MdxAttributeContent) -> SmolStr {
2730        match attr {
2731            MdxAttributeContent::Expression(value) => format!("{{{}}}", value).into(),
2732            MdxAttributeContent::Property(property) => match property.value {
2733                Some(value) => match value {
2734                    MdxAttributeValue::Expression(value) => format!("{}={{{}}}", property.name, value).into(),
2735                    MdxAttributeValue::Literal(literal) => format!("{}=\"{}\"", property.name, literal).into(),
2736                },
2737                None => property.name,
2738            },
2739        }
2740    }
2741}
2742
2743pub(crate) fn values_to_string(values: &[Node], options: &RenderOptions) -> String {
2744    render_values(values, options, &ColorTheme::PLAIN)
2745}
2746
2747pub(crate) fn render_values(values: &[Node], options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
2748    let mut pre_position: Option<Position> = None;
2749    values
2750        .iter()
2751        .map(|value| {
2752            if let Some(pos) = value.position() {
2753                let new_line_count = pre_position
2754                    .as_ref()
2755                    .map(|p: &Position| pos.start.line - p.end.line)
2756                    .unwrap_or_default();
2757
2758                let space = if new_line_count > 0
2759                    && pre_position
2760                        .as_ref()
2761                        .map(|p| pos.start.line > p.end.line)
2762                        .unwrap_or_default()
2763                {
2764                    " ".repeat(pos.start.column.saturating_sub(1))
2765                } else {
2766                    "".to_string()
2767                };
2768
2769                pre_position = Some(pos);
2770
2771                if space.is_empty() {
2772                    format!(
2773                        "{}{}",
2774                        "\n".repeat(new_line_count),
2775                        value.render_with_theme(options, theme)
2776                    )
2777                } else {
2778                    format!(
2779                        "{}{}",
2780                        "\n".repeat(new_line_count),
2781                        value
2782                            .render_with_theme(options, theme)
2783                            .lines()
2784                            .map(|line| format!("{}{}", space, line))
2785                            .join("\n")
2786                    )
2787                }
2788            } else {
2789                pre_position = None;
2790                value.render_with_theme(options, theme)
2791            }
2792        })
2793        .collect::<String>()
2794}
2795
2796fn values_to_value(values: Vec<Node>) -> String {
2797    values.iter().map(|value| value.value()).collect::<String>()
2798}
2799
2800#[cfg(test)]
2801mod tests {
2802    use super::*;
2803    use rstest::rstest;
2804
2805    #[rstest]
2806    #[case::text(Node::Text(Text{value: "".to_string(), position: None}),
2807           "test".to_string(),
2808           Node::Text(Text{value: "test".to_string(), position: None }))]
2809    #[case::blockquote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }),
2810           "test".to_string(),
2811           Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }))]
2812    #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }),
2813           "test".to_string(),
2814           Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }))]
2815    #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }),
2816           "test".to_string(),
2817           Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }))]
2818    #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }),
2819           "test".to_string(),
2820           Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }))]
2821    #[case::heading(Node::Heading(Heading {depth: 1, values: vec!["test".to_string().into()], position: None }),
2822           "test".to_string(),
2823           Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None }))]
2824    #[case::link(Node::Link(Link {url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }),
2825           "test".to_string(),
2826           Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }))]
2827    #[case::image(Node::Image(Image {alt: "test".to_string(), url: "test".to_string(), title: None, position: None }),
2828           "test".to_string(),
2829           Node::Image(Image{alt: "test".to_string(), url: "test".to_string(), title: None, position: None }))]
2830    #[case::code(Node::Code(Code {value: "test".to_string(), lang: None, fence: true, meta: None, position: None }),
2831           "test".to_string(),
2832           Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None }))]
2833    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef {ident: "test".to_string(), label: None, position: None }),
2834           "test".to_string(),
2835           Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: Some("test".to_string()), position: None }))]
2836    #[case::footnote(Node::Footnote(Footnote {ident: "test".to_string(), values: Vec::new(), position: None }),
2837           "test".to_string(),
2838           Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None }))]
2839    #[case::list(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }),
2840           "test".to_string(),
2841           Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }))]
2842    #[case::list(Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }),
2843           "test".to_string(),
2844           Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }))]
2845    #[case::list(Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }),
2846           "test".to_string(),
2847           Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }))]
2848    #[case::code_inline(Node::CodeInline(CodeInline{ value: "t".into(), position: None }),
2849           "test".to_string(),
2850           Node::CodeInline(CodeInline{ value: "test".into(), position: None }))]
2851    #[case::math_inline(Node::MathInline(MathInline{ value: "t".into(), position: None }),
2852           "test".to_string(),
2853           Node::MathInline(MathInline{ value: "test".into(), position: None }))]
2854    #[case::toml(Node::Toml(Toml{ value: "t".to_string(), position: None }),
2855           "test".to_string(),
2856           Node::Toml(Toml{ value: "test".to_string(), position: None }))]
2857    #[case::yaml(Node::Yaml(Yaml{ value: "t".to_string(), position: None }),
2858           "test".to_string(),
2859           Node::Yaml(Yaml{ value: "test".to_string(), position: None }))]
2860    #[case::html(Node::Html(Html{ value: "t".to_string(), position: None }),
2861           "test".to_string(),
2862           Node::Html(Html{ value: "test".to_string(), position: None }))]
2863    #[case::table_row(Node::TableRow(TableRow{ values: vec![
2864                        Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1,  position: None}),
2865                        Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:2,  position: None})
2866                    ]
2867                    , position: None }),
2868           "test3,test4".to_string(),
2869           Node::TableRow(TableRow{ values: vec![
2870                        Node::TableCell(TableCell{values: vec!["test3".to_string().into()], row:0, column:1, position: None}),
2871                        Node::TableCell(TableCell{values: vec!["test4".to_string().into()], row:0, column:2, position: None})
2872                    ]
2873                    , position: None }))]
2874    #[case::table_cell(Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1, position: None}),
2875            "test2".to_string(),
2876            Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:1, position: None}),)]
2877    #[case::link_ref(Node::LinkRef(LinkRef{ident: "test2".to_string(), values: vec![attr_keys::VALUE.to_string().into()], label: Some("test2".to_string()), position: None}),
2878            "test2".to_string(),
2879            Node::LinkRef(LinkRef{ident: "test2".to_string(), values: vec![attr_keys::VALUE.to_string().into()], label: Some("test2".to_string()), position: None}),)]
2880    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test1".to_string(), label: None, position: None}),
2881            "test2".to_string(),
2882            Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test2".to_string(), label: Some("test2".to_string()), position: None}),)]
2883    #[case::definition(Node::Definition(Definition{ url: Url::new(attr_keys::URL.to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),
2884            "test2".to_string(),
2885            Node::Definition(Definition{url: Url::new("test2".to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),)]
2886    #[case::break_(Node::Break(Break{ position: None}),
2887            "test".to_string(),
2888            Node::Break(Break{position: None}))]
2889    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{ position: None}),
2890            "test".to_string(),
2891            Node::HorizontalRule(HorizontalRule{position: None}))]
2892    #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
2893           "updated".to_string(),
2894           Node::MdxFlowExpression(MdxFlowExpression{value: "updated".into(), position: None}))]
2895    #[case::mdx_text_expression(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
2896           "updated".to_string(),
2897           Node::MdxTextExpression(MdxTextExpression{value: "updated".into(), position: None}))]
2898    #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}),
2899           "updated".to_string(),
2900           Node::MdxJsEsm(MdxJsEsm{value: "updated".into(), position: None}))]
2901    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
2902            name: Some("div".to_string()),
2903            attributes: Vec::new(),
2904            children: vec!["test".to_string().into()],
2905            position: None
2906        }),
2907        "updated".to_string(),
2908        Node::MdxJsxFlowElement(MdxJsxFlowElement{
2909            name: Some("div".to_string()),
2910            attributes: Vec::new(),
2911            children: vec!["updated".to_string().into()],
2912            position: None
2913        }))]
2914    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
2915            name: Some("span".into()),
2916            attributes: Vec::new(),
2917            children: vec!["test".to_string().into()],
2918            position: None
2919        }),
2920        "updated".to_string(),
2921        Node::MdxJsxTextElement(MdxJsxTextElement{
2922            name: Some("span".into()),
2923            attributes: Vec::new(),
2924            children: vec!["updated".to_string().into()],
2925            position: None
2926        }))]
2927    #[case(Node::Math(Math{ value: "x^2".to_string(), position: None }),
2928           "test".to_string(),
2929           Node::Math(Math{ value: "test".to_string(), position: None }))]
2930    fn test_with_value(#[case] node: Node, #[case] input: String, #[case] expected: Node) {
2931        assert_eq!(node.with_value(input.as_str()), expected);
2932    }
2933
2934    #[rstest]
2935    #[case(Node::Blockquote(Blockquote{values: vec![
2936        Node::Text(Text{value: "first".to_string(), position: None}),
2937        Node::Text(Text{value: "second".to_string(), position: None})
2938    ], position: None}),
2939        "new",
2940        0,
2941        Node::Blockquote(Blockquote{values: vec![
2942            Node::Text(Text{value: "new".to_string(), position: None}),
2943            Node::Text(Text{value: "second".to_string(), position: None})
2944        ], position: None}))]
2945    #[case(Node::Blockquote(Blockquote{values: vec![
2946        Node::Text(Text{value: "first".to_string(), position: None}),
2947        Node::Text(Text{value: "second".to_string(), position: None})
2948    ], position: None}),
2949        "new",
2950        1,
2951        Node::Blockquote(Blockquote{values: vec![
2952            Node::Text(Text{value: "first".to_string(), position: None}),
2953            Node::Text(Text{value: "new".to_string(), position: None})
2954        ], position: None}))]
2955    #[case(Node::Delete(Delete{values: vec![
2956        Node::Text(Text{value: "first".to_string(), position: None}),
2957        Node::Text(Text{value: "second".to_string(), position: None})
2958    ], position: None}),
2959        "new",
2960        0,
2961        Node::Delete(Delete{values: vec![
2962            Node::Text(Text{value: "new".to_string(), position: None}),
2963            Node::Text(Text{value: "second".to_string(), position: None})
2964        ], position: None}))]
2965    #[case(Node::Emphasis(Emphasis{values: vec![
2966        Node::Text(Text{value: "first".to_string(), position: None}),
2967        Node::Text(Text{value: "second".to_string(), position: None})
2968    ], position: None}),
2969        "new",
2970        1,
2971        Node::Emphasis(Emphasis{values: vec![
2972            Node::Text(Text{value: "first".to_string(), position: None}),
2973            Node::Text(Text{value: "new".to_string(), position: None})
2974        ], position: None}))]
2975    #[case(Node::Strong(Strong{values: vec![
2976        Node::Text(Text{value: "first".to_string(), position: None}),
2977        Node::Text(Text{value: "second".to_string(), position: None})
2978    ], position: None}),
2979        "new",
2980        0,
2981        Node::Strong(Strong{values: vec![
2982            Node::Text(Text{value: "new".to_string(), position: None}),
2983            Node::Text(Text{value: "second".to_string(), position: None})
2984        ], position: None}))]
2985    #[case(Node::Heading(Heading{depth: 1, values: vec![
2986        Node::Text(Text{value: "first".to_string(), position: None}),
2987        Node::Text(Text{value: "second".to_string(), position: None})
2988    ], position: None}),
2989        "new",
2990        1,
2991        Node::Heading(Heading{depth: 1, values: vec![
2992            Node::Text(Text{value: "first".to_string(), position: None}),
2993            Node::Text(Text{value: "new".to_string(), position: None})
2994        ], position: None}))]
2995    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
2996        Node::Text(Text{value: "first".to_string(), position: None}),
2997        Node::Text(Text{value: "second".to_string(), position: None})
2998    ], position: None}),
2999        "new",
3000        0,
3001        Node::List(List{index: 0, level: 0, checked: None, ordered: false,  values: vec![
3002            Node::Text(Text{value: "new".to_string(), position: None}),
3003            Node::Text(Text{value: "second".to_string(), position: None})
3004        ], position: None}))]
3005    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3006        Node::Text(Text{value: "first".to_string(), position: None}),
3007        Node::Text(Text{value: "second".to_string(), position: None})
3008    ], position: None}),
3009        "new",
3010        1,
3011        Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3012            Node::Text(Text{value: "first".to_string(), position: None}),
3013            Node::Text(Text{value: "new".to_string(), position: None})
3014        ], position: None}))]
3015    #[case(Node::Text(Text{value: "plain text".to_string(), position: None}),
3016        "new",
3017        0,
3018        Node::Text(Text{value: "plain text".to_string(), position: None}))]
3019    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
3020        "new",
3021        0,
3022        Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}))]
3023    #[case(Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3024        Node::Text(Text{value: "first".to_string(), position: None})
3025    ], position: None}),
3026        "new",
3027        0,
3028        Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3029            Node::Text(Text{value: "new".to_string(), position: None})
3030        ], position: None}))]
3031    #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3032        Node::Text(Text{value: "first".to_string(), position: None})
3033    ], position: None}),
3034        "new",
3035        2,
3036        Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3037            Node::Text(Text{value: "first".to_string(), position: None})
3038        ], position: None}))]
3039    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3040            Node::Text(Text{value: "first".to_string(), position: None}),
3041            Node::Text(Text{value: "second".to_string(), position: None})
3042        ], label: None, position: None}), "new", 0, Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3043            Node::Text(Text{value: "new".to_string(), position: None}),
3044            Node::Text(Text{value: "second".to_string(), position: None})
3045        ], label: None, position: None}))]
3046    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3047            name: Some("div".to_string()),
3048            attributes: Vec::new(),
3049            children: vec![
3050                Node::Text(Text{value: "first".to_string(), position: None}),
3051                Node::Text(Text{value: "second".to_string(), position: None})
3052            ],
3053            position: None
3054        }),
3055        "new",
3056        0,
3057        Node::MdxJsxFlowElement(MdxJsxFlowElement{
3058            name: Some("div".to_string()),
3059            attributes: Vec::new(),
3060            children: vec![
3061                Node::Text(Text{value: "new".to_string(), position: None}),
3062                Node::Text(Text{value: "second".to_string(), position: None})
3063            ],
3064            position: None
3065        }))]
3066    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3067            name: Some("div".to_string()),
3068            attributes: Vec::new(),
3069            children: vec![
3070                Node::Text(Text{value: "first".to_string(), position: None}),
3071                Node::Text(Text{value: "second".to_string(), position: None})
3072            ],
3073            position: None
3074        }),
3075        "new",
3076        1,
3077        Node::MdxJsxFlowElement(MdxJsxFlowElement{
3078            name: Some("div".to_string()),
3079            attributes: Vec::new(),
3080            children: vec![
3081                Node::Text(Text{value: "first".to_string(), position: None}),
3082                Node::Text(Text{value: "new".to_string(), position: None})
3083            ],
3084            position: None
3085        }))]
3086    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3087            name: Some("span".into()),
3088            attributes: Vec::new(),
3089            children: vec![
3090                Node::Text(Text{value: "first".to_string(), position: None}),
3091                Node::Text(Text{value: "second".to_string(), position: None})
3092            ],
3093            position: None
3094        }),
3095        "new",
3096        0,
3097        Node::MdxJsxTextElement(MdxJsxTextElement{
3098            name: Some("span".into()),
3099            attributes: Vec::new(),
3100            children: vec![
3101                Node::Text(Text{value: "new".to_string(), position: None}),
3102                Node::Text(Text{value: "second".to_string(), position: None})
3103            ],
3104            position: None
3105        }))]
3106    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3107            name: Some("span".into()),
3108            attributes: Vec::new(),
3109            children: vec![
3110                Node::Text(Text{value: "first".to_string(), position: None}),
3111                Node::Text(Text{value: "second".to_string(), position: None})
3112            ],
3113            position: None
3114        }),
3115        "new",
3116        1,
3117        Node::MdxJsxTextElement(MdxJsxTextElement{
3118            name: Some("span".into()),
3119            attributes: Vec::new(),
3120            children: vec![
3121                Node::Text(Text{value: "first".to_string(), position: None}),
3122                Node::Text(Text{value: "new".to_string(), position: None})
3123            ],
3124            position: None
3125        }))]
3126    fn test_with_children_value(#[case] node: Node, #[case] value: &str, #[case] index: usize, #[case] expected: Node) {
3127        assert_eq!(node.with_children_value(value, index), expected);
3128    }
3129
3130    #[rstest]
3131    #[case(Node::Text(Text{value: "test".to_string(), position: None }),
3132           "test".to_string())]
3133    #[case(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
3134           "    - test".to_string())]
3135    fn test_display(#[case] node: Node, #[case] expected: String) {
3136        assert_eq!(node.to_string_with(&RenderOptions::default()), expected);
3137    }
3138
3139    #[rstest]
3140    #[case(Node::Text(Text{value: "test".to_string(), position: None}), true)]
3141    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), false)]
3142    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), false)]
3143    fn test_is_text(#[case] node: Node, #[case] expected: bool) {
3144        assert_eq!(node.is_text(), expected);
3145    }
3146
3147    #[rstest]
3148    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), true)]
3149    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3150    fn test_is_inline_code(#[case] node: Node, #[case] expected: bool) {
3151        assert_eq!(node.is_inline_code(), expected);
3152    }
3153
3154    #[rstest]
3155    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), true)]
3156    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3157    fn test_is_inline_math(#[case] node: Node, #[case] expected: bool) {
3158        assert_eq!(node.is_inline_math(), expected);
3159    }
3160
3161    #[rstest]
3162    #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), true)]
3163    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3164    fn test_is_strong(#[case] node: Node, #[case] expected: bool) {
3165        assert_eq!(node.is_strong(), expected);
3166    }
3167
3168    #[rstest]
3169    #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), true)]
3170    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3171    fn test_is_delete(#[case] node: Node, #[case] expected: bool) {
3172        assert_eq!(node.is_delete(), expected);
3173    }
3174
3175    #[rstest]
3176    #[case(Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None}), true)]
3177    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3178    fn test_is_link(#[case] node: Node, #[case] expected: bool) {
3179        assert_eq!(node.is_link(), expected);
3180    }
3181
3182    #[rstest]
3183    #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), true)]
3184    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3185    fn test_is_link_ref(#[case] node: Node, #[case] expected: bool) {
3186        assert_eq!(node.is_link_ref(), expected);
3187    }
3188
3189    #[rstest]
3190    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), true)]
3191    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3192    fn test_is_image(#[case] node: Node, #[case] expected: bool) {
3193        assert_eq!(node.is_image(), expected);
3194    }
3195
3196    #[rstest]
3197    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), true)]
3198    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3199    fn test_is_image_ref(#[case] node: Node, #[case] expected: bool) {
3200        assert_eq!(node.is_image_ref(), expected);
3201    }
3202
3203    #[rstest]
3204    #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), true, Some("rust".into()))]
3205    #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), false, Some("python".into()))]
3206    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), true, None)]
3207    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), true, None)]
3208    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3209    fn test_is_code(#[case] node: Node, #[case] expected: bool, #[case] lang: Option<SmolStr>) {
3210        assert_eq!(node.is_code(lang), expected);
3211    }
3212
3213    #[rstest]
3214    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, Some(1))]
3215    #[case(Node::Heading(Heading{depth: 2, values: vec!["test".to_string().into()], position: None}), false, Some(1))]
3216    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, None)]
3217    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3218    fn test_is_heading(#[case] node: Node, #[case] expected: bool, #[case] depth: Option<u8>) {
3219        assert_eq!(node.is_heading(depth), expected);
3220    }
3221
3222    #[rstest]
3223    #[case(Node::HorizontalRule(HorizontalRule{position: None}), true)]
3224    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3225    fn test_is_horizontal_rule(#[case] node: Node, #[case] expected: bool) {
3226        assert_eq!(node.is_horizontal_rule(), expected);
3227    }
3228
3229    #[rstest]
3230    #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), true)]
3231    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3232    fn test_is_blockquote(#[case] node: Node, #[case] expected: bool) {
3233        assert_eq!(node.is_blockquote(), expected);
3234    }
3235
3236    #[rstest]
3237    #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), true)]
3238    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3239    fn test_is_html(#[case] node: Node, #[case] expected: bool) {
3240        assert_eq!(node.is_html(), expected);
3241    }
3242
3243    #[rstest]
3244    #[case(Node::node_values(
3245           &Node::Strong(Strong{values: vec!["test".to_string().into()], position: None})),
3246           vec!["test".to_string().into()])]
3247    #[case(Node::node_values(
3248           &Node::Text(Text{value: "test".to_string(), position: None})),
3249           vec!["test".to_string().into()])]
3250    #[case(Node::node_values(
3251           &Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None})),
3252           vec!["test".to_string().into()])]
3253    #[case(Node::node_values(
3254           &Node::Delete(Delete{values: vec!["test".to_string().into()], position: None})),
3255           vec!["test".to_string().into()])]
3256    #[case(Node::node_values(
3257           &Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None})),
3258           vec!["test".to_string().into()])]
3259    #[case(Node::node_values(
3260           &Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None})),
3261           vec!["test".to_string().into()])]
3262    #[case(Node::node_values(
3263           &Node::List(List{values: vec!["test".to_string().into()], ordered: false, level: 1, checked: Some(false), index: 0, position: None})),
3264           vec!["test".to_string().into()])]
3265    fn test_node_value(#[case] actual: Vec<Node>, #[case] expected: Vec<Node>) {
3266        assert_eq!(actual, expected);
3267    }
3268
3269    #[rstest]
3270    #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None}), true)]
3271    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3272    fn test_is_footnote(#[case] node: Node, #[case] expected: bool) {
3273        assert_eq!(node.is_footnote(), expected);
3274    }
3275
3276    #[rstest]
3277    #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), true)]
3278    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3279    fn test_is_footnote_ref(#[case] node: Node, #[case] expected: bool) {
3280        assert_eq!(node.is_footnote_ref(), expected);
3281    }
3282
3283    #[rstest]
3284    #[case(Node::Math(Math{value: "x^2".to_string(), position: None}), true)]
3285    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3286    fn test_is_math(#[case] node: Node, #[case] expected: bool) {
3287        assert_eq!(node.is_math(), expected);
3288    }
3289
3290    #[rstest]
3291    #[case(Node::Break(Break{position: None}), true)]
3292    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3293    fn test_is_break(#[case] node: Node, #[case] expected: bool) {
3294        assert_eq!(node.is_break(), expected);
3295    }
3296
3297    #[rstest]
3298    #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), true)]
3299    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3300    fn test_is_yaml(#[case] node: Node, #[case] expected: bool) {
3301        assert_eq!(node.is_yaml(), expected);
3302    }
3303
3304    #[rstest]
3305    #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), true)]
3306    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3307    fn test_is_toml(#[case] node: Node, #[case] expected: bool) {
3308        assert_eq!(node.is_toml(), expected);
3309    }
3310
3311    #[rstest]
3312    #[case(Node::Definition(Definition{ident: attr_keys::IDENT.to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}), true)]
3313    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3314    fn test_is_definition(#[case] node: Node, #[case] expected: bool) {
3315        assert_eq!(node.is_definition(), expected);
3316    }
3317
3318    #[rstest]
3319    #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), true)]
3320    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3321    fn test_is_emphasis(#[case] node: Node, #[case] expected: bool) {
3322        assert_eq!(node.is_emphasis(), expected);
3323    }
3324
3325    #[rstest]
3326    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), true)]
3327    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3328    fn test_is_mdx_flow_expression(#[case] node: Node, #[case] expected: bool) {
3329        assert_eq!(node.is_mdx_flow_expression(), expected);
3330    }
3331
3332    #[rstest]
3333    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), true)]
3334    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3335    fn test_is_mdx_text_expression(#[case] node: Node, #[case] expected: bool) {
3336        assert_eq!(node.is_mdx_text_expression(), expected);
3337    }
3338
3339    #[rstest]
3340    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
3341    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3342    fn test_is_mdx_jsx_flow_element(#[case] node: Node, #[case] expected: bool) {
3343        assert_eq!(node.is_mdx_jsx_flow_element(), expected);
3344    }
3345
3346    #[rstest]
3347    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
3348    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3349    fn test_is_mdx_jsx_text_element(#[case] node: Node, #[case] expected: bool) {
3350        assert_eq!(node.is_mdx_jsx_text_element(), expected);
3351    }
3352
3353    #[rstest]
3354    #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), true)]
3355    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3356    fn test_is_msx_js_esm(#[case] node: Node, #[case] expected: bool) {
3357        assert_eq!(node.is_mdx_js_esm(), expected);
3358    }
3359
3360    #[rstest]
3361    #[case::text(Node::Text(Text{value: "test".to_string(), position: None }), RenderOptions::default(), "test")]
3362    #[case::list(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "    - test")]
3363    #[case::list(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions { list_style: ListStyle::Plus, ..Default::default() }, "  + test")]
3364    #[case::list(Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions { list_style: ListStyle::Star, ..Default::default() }, "  * [x] test")]
3365    #[case::list(Node::List(List{index: 0, level: 1, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "  - [ ] test")]
3366    #[case::list(Node::List(List{index: 0, level: 1, checked: None, ordered: true, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "  1. test")]
3367    #[case::list(Node::List(List{index: 0, level: 1, checked: Some(false), ordered: true, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "  1. [ ] test")]
3368    #[case::table_row(Node::TableRow(TableRow{values: vec![Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None})], position: None}), RenderOptions::default(), "|test|")]
3369    #[case::table_row(Node::TableRow(TableRow{values: vec![Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None})], position: None}), RenderOptions::default(), "|test|")]
3370    #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
3371    #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
3372    #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right, TableAlignKind::Center, TableAlignKind::None], position: None}), RenderOptions::default(), "|:---|---:|:---:|---|")]
3373    #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "> test")]
3374    #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test\ntest2".to_string().into()], position: None}), RenderOptions::default(), "> test\n> test2")]
3375    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), RenderOptions::default(), "```rust\ncode\n```")]
3376    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), RenderOptions::default(), "```\ncode\n```")]
3377    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), RenderOptions::default(), "    code")]
3378    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: Some("meta".to_string()), position: None}), RenderOptions::default(), "```rust meta\ncode\n```")]
3379    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[label]: url")]
3380    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[label]: url \"title\"")]
3381    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new("".to_string()), title: None, label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[label]: ")]
3382    #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "~~test~~")]
3383    #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "*test*")]
3384    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![attr_keys::LABEL.to_string().into()], position: None}), RenderOptions::default(), "[^id]: label")]
3385    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef{ident: attr_keys::LABEL.to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[^label]")]
3386    #[case::heading(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "# test")]
3387    #[case::heading(Node::Heading(Heading{depth: 3, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "### test")]
3388    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), RenderOptions::default(), "<div>test</div>")]
3389    #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}), RenderOptions::default(), "![alt](url)")]
3390    #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "url with space".to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}), RenderOptions::default(), "![alt](url%20with%20space \"title\")")]
3391    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("id".to_string()), position: None}), RenderOptions::default(), "![alt][id]")]
3392    #[case::image_ref(Node::ImageRef(ImageRef{alt: "id".to_string(), ident: "id".to_string(), label: Some("id".to_string()), position: None}), RenderOptions::default(), "![id]")]
3393    #[case::code_inline(Node::CodeInline(CodeInline{value: "code".into(), position: None}), RenderOptions::default(), "`code`")]
3394    #[case::math_inline(Node::MathInline(MathInline{value: "x^2".into(), position: None}), RenderOptions::default(), "$x^2$")]
3395    #[case::link(Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), values: vec![attr_keys::VALUE.to_string().into()], position: None}), RenderOptions::default(), "[value](url \"title\")")]
3396    #[case::link(Node::Link(Link{url: Url::new("".to_string()), title: None, values: vec![attr_keys::VALUE.to_string().into()], position: None}), RenderOptions::default(), "[value]()")]
3397    #[case::link(Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: None, values: vec![attr_keys::VALUE.to_string().into()], position: None}), RenderOptions::default(), "[value](url)")]
3398    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec!["id".to_string().into()], label: Some("id".to_string()), position: None}), RenderOptions::default(), "[id]")]
3399    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec!["open".to_string().into()], label: Some("id".to_string()), position: None}), RenderOptions::default(), "[open][id]")]
3400    #[case::math(Node::Math(Math{value: "x^2".to_string(), position: None}), RenderOptions::default(), "$$\nx^2\n$$")]
3401    #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "**test**")]
3402    #[case::yaml(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), RenderOptions::default(), "---\nkey: value\n---")]
3403    #[case::toml(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), RenderOptions::default(), "+++\nkey = \"value\"\n+++")]
3404    #[case::break_(Node::Break(Break{position: None}), RenderOptions::default(), "\\")]
3405    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), RenderOptions::default(), "---")]
3406    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3407        name: Some("div".to_string()),
3408        attributes: vec![
3409            MdxAttributeContent::Property(MdxJsxAttribute {
3410                name: "className".into(),
3411                value: Some(MdxAttributeValue::Literal("container".into()))
3412            })
3413        ],
3414        children: vec![
3415            "content".to_string().into()
3416        ],
3417        position: None
3418    }), RenderOptions::default(), "<div className=\"container\">content</div>")]
3419    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3420        name: Some("div".to_string()),
3421        attributes: vec![
3422            MdxAttributeContent::Property(MdxJsxAttribute {
3423                name: "className".into(),
3424                value: Some(MdxAttributeValue::Literal("container".into()))
3425            })
3426        ],
3427        children: Vec::new(),
3428        position: None
3429    }), RenderOptions::default(), "<div className=\"container\" />")]
3430    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3431        name: Some("div".to_string()),
3432        attributes: Vec::new(),
3433        children: Vec::new(),
3434        position: None
3435    }), RenderOptions::default(), "<div />")]
3436    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3437        name: Some("span".into()),
3438        attributes: vec![
3439            MdxAttributeContent::Expression("...props".into())
3440        ],
3441        children: vec![
3442            "inline".to_string().into()
3443        ],
3444        position: None
3445    }), RenderOptions::default(), "<span {...props}>inline</span>")]
3446    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3447        name: Some("span".into()),
3448        attributes: vec![
3449            MdxAttributeContent::Expression("...props".into())
3450        ],
3451        children: vec![
3452        ],
3453        position: None
3454    }), RenderOptions::default(), "<span {...props} />")]
3455    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3456        name: Some("span".into()),
3457        attributes: vec![
3458        ],
3459        children: vec![
3460        ],
3461        position: None
3462    }), RenderOptions::default(), "<span />")]
3463    #[case(Node::MdxTextExpression(MdxTextExpression{
3464        value: "count + 1".into(),
3465        position: None,
3466    }), RenderOptions::default(), "{count + 1}")]
3467    #[case(Node::MdxJsEsm(MdxJsEsm{
3468        value: "import React from 'react'".into(),
3469        position: None,
3470    }), RenderOptions::default(), "import React from 'react'")]
3471    #[case::fragment_empty(Node::Fragment(Fragment{values: vec![]}), RenderOptions::default(), "")]
3472    #[case::fragment_single(Node::Fragment(Fragment{values: vec![
3473        Node::Text(Text{value: "hello".to_string(), position: None})
3474    ]}), RenderOptions::default(), "hello")]
3475    #[case::fragment_multiple(Node::Fragment(Fragment{values: vec![
3476        Node::Text(Text{value: "hello".to_string(), position: None}),
3477        Node::Text(Text{value: "world".to_string(), position: None})
3478    ]}), RenderOptions::default(), "hello\nworld")]
3479    #[case::fragment_filters_empty(Node::Fragment(Fragment{values: vec![
3480        Node::Text(Text{value: "hello".to_string(), position: None}),
3481        Node::Empty,
3482        Node::Text(Text{value: "world".to_string(), position: None})
3483    ]}), RenderOptions::default(), "hello\nworld")]
3484    #[case::fragment_all_empty(Node::Fragment(Fragment{values: vec![
3485        Node::Empty,
3486        Node::Empty,
3487    ]}), RenderOptions::default(), "")]
3488    fn test_to_string_with(#[case] node: Node, #[case] options: RenderOptions, #[case] expected: &str) {
3489        assert_eq!(node.to_string_with(&options), expected);
3490    }
3491
3492    #[test]
3493    fn test_node_partial_ord() {
3494        let node1 = Node::Text(Text {
3495            value: "test1".to_string(),
3496            position: Some(Position {
3497                start: Point { line: 1, column: 1 },
3498                end: Point { line: 1, column: 5 },
3499            }),
3500        });
3501
3502        let node2 = Node::Text(Text {
3503            value: "test2".to_string(),
3504            position: Some(Position {
3505                start: Point { line: 1, column: 6 },
3506                end: Point { line: 1, column: 10 },
3507            }),
3508        });
3509
3510        let node3 = Node::Text(Text {
3511            value: "test3".to_string(),
3512            position: Some(Position {
3513                start: Point { line: 2, column: 1 },
3514                end: Point { line: 2, column: 5 },
3515            }),
3516        });
3517
3518        assert_eq!(node1.partial_cmp(&node2), Some(std::cmp::Ordering::Less));
3519        assert_eq!(node2.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3520
3521        assert_eq!(node1.partial_cmp(&node3), Some(std::cmp::Ordering::Less));
3522        assert_eq!(node3.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3523
3524        let node4 = Node::Text(Text {
3525            value: "test4".to_string(),
3526            position: None,
3527        });
3528
3529        assert_eq!(node1.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
3530        assert_eq!(node4.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3531
3532        let node5 = Node::Text(Text {
3533            value: "test5".to_string(),
3534            position: None,
3535        });
3536
3537        assert_eq!(node4.partial_cmp(&node5), Some(std::cmp::Ordering::Equal));
3538
3539        let node6 = Node::Code(Code {
3540            value: "code".to_string(),
3541            lang: None,
3542            fence: true,
3543            meta: None,
3544            position: None,
3545        });
3546
3547        assert_eq!(node6.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
3548        assert_eq!(node4.partial_cmp(&node6), Some(std::cmp::Ordering::Greater));
3549    }
3550
3551    #[rstest]
3552    #[case(Node::Blockquote(Blockquote{values: Vec::new(), position: None}), "blockquote")]
3553    #[case(Node::Break(Break{position: None}), "break")]
3554    #[case(Node::Definition(Definition{ident: "".to_string(), url: Url::new("".to_string()), title: None, label: None, position: None}), "definition")]
3555    #[case(Node::Delete(Delete{values: Vec::new(), position: None}), "delete")]
3556    #[case(Node::Heading(Heading{depth: 1, values: Vec::new(), position: None}), "h1")]
3557    #[case(Node::Heading(Heading{depth: 2, values: Vec::new(), position: None}), "h2")]
3558    #[case(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "h3")]
3559    #[case(Node::Heading(Heading{depth: 4, values: Vec::new(), position: None}), "h4")]
3560    #[case(Node::Heading(Heading{depth: 5, values: Vec::new(), position: None}), "h5")]
3561    #[case(Node::Heading(Heading{depth: 6, values: Vec::new(), position: None}), "h6")]
3562    #[case(Node::Heading(Heading{depth: 7, values: Vec::new(), position: None}), "h")]
3563    #[case(Node::Emphasis(Emphasis{values: Vec::new(), position: None}), "emphasis")]
3564    #[case(Node::Footnote(Footnote{ident: "".to_string(), values: Vec::new(), position: None}), "footnote")]
3565    #[case(Node::FootnoteRef(FootnoteRef{ident: "".to_string(), label: None, position: None}), "footnoteref")]
3566    #[case(Node::Html(Html{value: "".to_string(), position: None}), "html")]
3567    #[case(Node::Yaml(Yaml{value: "".to_string(), position: None}), "yaml")]
3568    #[case(Node::Toml(Toml{value: "".to_string(), position: None}), "toml")]
3569    #[case(Node::Image(Image{alt: "".to_string(), url: "".to_string(), title: None, position: None}), "image")]
3570    #[case(Node::ImageRef(ImageRef{alt: "".to_string(), ident: "".to_string(), label: None, position: None}), "image_ref")]
3571    #[case(Node::CodeInline(CodeInline{value: "".into(), position: None}), "code_inline")]
3572    #[case(Node::MathInline(MathInline{value: "".into(), position: None}), "math_inline")]
3573    #[case(Node::Link(Link{url: Url::new("".to_string()), title: None, values: Vec::new(), position: None}), "link")]
3574    #[case(Node::LinkRef(LinkRef{ident: "".to_string(), values: Vec::new(), label: None, position: None}), "link_ref")]
3575    #[case(Node::Math(Math{value: "".to_string(), position: None}), "math")]
3576    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: Vec::new(), position: None}), "list")]
3577    #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "table_align")]
3578    #[case(Node::TableRow(TableRow{values: Vec::new(), position: None}), "table_row")]
3579    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}), "table_cell")]
3580    #[case(Node::Code(Code{value: "".to_string(), lang: None, fence: true, meta: None, position: None}), "code")]
3581    #[case(Node::Strong(Strong{values: Vec::new(), position: None}), "strong")]
3582    #[case(Node::HorizontalRule(HorizontalRule{position: None}), "Horizontal_rule")]
3583    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "".into(), position: None}), "mdx_flow_expression")]
3584    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_flow_element")]
3585    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_text_element")]
3586    #[case(Node::MdxTextExpression(MdxTextExpression{value: "".into(), position: None}), "mdx_text_expression")]
3587    #[case(Node::MdxJsEsm(MdxJsEsm{value: "".into(), position: None}), "mdx_js_esm")]
3588    #[case(Node::Text(Text{value: "".to_string(), position: None}), "text")]
3589    fn test_name(#[case] node: Node, #[case] expected: &str) {
3590        assert_eq!(node.name(), expected);
3591    }
3592
3593    #[rstest]
3594    #[case(Node::Text(Text{value: "test".to_string(), position: None}), "test")]
3595    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3596    #[case(Node::Blockquote(Blockquote{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3597    #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3598    #[case(Node::Heading(Heading{depth: 1, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3599    #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3600    #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3601    #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), "test")]
3602    #[case(Node::Html(Html{value: "test".to_string(), position: None}), "test")]
3603    #[case(Node::Yaml(Yaml{value: "test".to_string(), position: None}), "test")]
3604    #[case(Node::Toml(Toml{value: "test".to_string(), position: None}), "test")]
3605    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), "test")]
3606    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), "test")]
3607    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), "test")]
3608    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), "test")]
3609    #[case(Node::Link(Link{url: Url::new("test".to_string()), title: None, values: Vec::new(), position: None}), "test")]
3610    #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), "test")]
3611    #[case(Node::Math(Math{value: "test".to_string(), position: None}), "test")]
3612    #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}), "test")]
3613    #[case(Node::Strong(Strong{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3614    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3615    #[case(Node::TableRow(TableRow{values: vec![Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None})], position: None}), "test")]
3616    #[case(Node::Break(Break{position: None}), "")]
3617    #[case(Node::HorizontalRule(HorizontalRule{position: None}), "")]
3618    #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "")]
3619    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), "test")]
3620    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), "test")]
3621    #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), "test")]
3622    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some(attr_keys::NAME.to_string()), attributes: Vec::new(), children: vec![Node::Text(Text{value: "test".to_string(), position: None})],  position: None}), "test")]
3623    #[case(Node::Definition(Definition{ident: "test".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}), attr_keys::URL)]
3624    #[case(Node::Fragment(Fragment {values: vec![Node::Text(Text{value: "test".to_string(), position: None})]}), "test")]
3625    fn test_value(#[case] node: Node, #[case] expected: &str) {
3626        assert_eq!(node.value(), expected);
3627    }
3628
3629    #[rstest]
3630    #[case(Node::Text(Text{value: "test".to_string(), position: None}), None)]
3631    #[case(Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3632    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3633    #[case(Node::Blockquote(Blockquote{values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3634    #[case(Node::Delete(Delete{values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3635    #[case(Node::Heading(Heading{depth: 1, values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3636    #[case(Node::Emphasis(Emphasis{values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3637    #[case(Node::Footnote(Footnote{ident: "".to_string(), values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3638    #[case(Node::FootnoteRef(FootnoteRef{ident: "".to_string(), label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3639    #[case(Node::Html(Html{value: "".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3640    #[case(Node::Yaml(Yaml{value: "".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3641    #[case(Node::Toml(Toml{value: "".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3642    #[case(Node::Image(Image{alt: "".to_string(), url: "".to_string(), title: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3643    #[case(Node::ImageRef(ImageRef{alt: "".to_string(), ident: "".to_string(), label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3644    #[case(Node::CodeInline(CodeInline{value: "".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3645    #[case(Node::MathInline(MathInline{value: "".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3646    #[case(Node::Link(Link{url: Url("".to_string()), title: None, values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3647    #[case(Node::LinkRef(LinkRef{ident: "".to_string(), values: Vec::new(), label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3648    #[case(Node::Math(Math{value: "".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3649    #[case(Node::Code(Code{value: "".to_string(), lang: None, fence: true, meta: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3650    #[case(Node::Strong(Strong{values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3651    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3652    #[case(Node::TableRow(TableRow{values: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3653    #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3654    #[case(Node::Break(Break{position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3655    #[case(Node::HorizontalRule(HorizontalRule{position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3656    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3657    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3658    #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3659    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3660    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3661    #[case(Node::Definition(Definition{ident: "".to_string(), url: Url("".to_string()), title: None, label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3662    #[case(Node::Fragment(Fragment{values: Vec::new()}), None)]
3663    #[case(Node::Fragment(Fragment{values: vec![
3664        Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
3665        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
3666    ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}}))]
3667    #[case(Node::Fragment(Fragment{values: vec![
3668        Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
3669        Node::Text(Text{value: "test2".to_string(), position: None})
3670    ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3671    #[case(Node::Fragment(Fragment{values: vec![
3672        Node::Text(Text{value: "test".to_string(), position: None}),
3673        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
3674    ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
3675    #[case(Node::Fragment(Fragment{values: vec![
3676        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})}),
3677        Node::Text(Text{value: "test".to_string(), position: None})
3678    ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
3679    #[case(Node::Fragment(Fragment{values: vec![
3680        Node::Text(Text{value: "test".to_string(), position: None}),
3681        Node::Text(Text{value: "test2".to_string(), position: None})
3682    ]}), None)]
3683    #[case(Node::Empty, None)]
3684    fn test_position(#[case] node: Node, #[case] expected: Option<Position>) {
3685        assert_eq!(node.position(), expected);
3686    }
3687
3688    #[rstest]
3689    #[case(Node::Blockquote(Blockquote{values: vec![
3690        Node::Text(Text{value: "first".to_string(), position: None}),
3691        Node::Text(Text{value: "second".to_string(), position: None})
3692    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3693    #[case(Node::Blockquote(Blockquote{values: vec![
3694        Node::Text(Text{value: "first".to_string(), position: None}),
3695        Node::Text(Text{value: "second".to_string(), position: None})
3696    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3697    #[case(Node::Blockquote(Blockquote{values: vec![
3698        Node::Text(Text{value: "first".to_string(), position: None})
3699    ], position: None}), 1, None)]
3700    #[case(Node::Delete(Delete{values: vec![
3701        Node::Text(Text{value: "first".to_string(), position: None}),
3702        Node::Text(Text{value: "second".to_string(), position: None})
3703    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3704    #[case(Node::Emphasis(Emphasis{values: vec![
3705        Node::Text(Text{value: "first".to_string(), position: None}),
3706        Node::Text(Text{value: "second".to_string(), position: None})
3707    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3708    #[case(Node::Strong(Strong{values: vec![
3709        Node::Text(Text{value: "first".to_string(), position: None})
3710    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3711    #[case(Node::Heading(Heading{depth: 1, values: vec![
3712        Node::Text(Text{value: "first".to_string(), position: None}),
3713        Node::Text(Text{value: "second".to_string(), position: None})
3714    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3715    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3716        Node::Text(Text{value: "first".to_string(), position: None}),
3717        Node::Text(Text{value: "second".to_string(), position: None})
3718    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3719    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3720        Node::Text(Text{value: "cell content".to_string(), position: None})
3721    ], position: None}), 0, Some(Node::Text(Text{value: "cell content".to_string(), position: None})))]
3722    #[case(Node::TableRow(TableRow{values: vec![
3723        Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}),
3724        Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})
3725    ], position: None}), 1, Some(Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})))]
3726    #[case(Node::Text(Text{value: "plain text".to_string(), position: None}), 0, None)]
3727    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), 0, None)]
3728    #[case(Node::Html(Html{value: "<div>".to_string(), position: None}), 0, None)]
3729    fn test_find_at_index(#[case] node: Node, #[case] index: usize, #[case] expected: Option<Node>) {
3730        assert_eq!(node.find_at_index(index), expected);
3731    }
3732
3733    #[rstest]
3734    #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}),
3735           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3736    #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}),
3737           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3738    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}),
3739           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3740    #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}),
3741           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3742    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
3743           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3744    #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}),
3745           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3746    #[case(Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec!["test".to_string().into()], position: None}),
3747           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3748    #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec!["test".to_string().into()], label: None, position: None}),
3749           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3750    #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec!["test".to_string().into()], position: None}),
3751           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3752    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}),
3753           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3754    #[case(Node::TableRow(TableRow{values: vec!["test".to_string().into()], position: None}),
3755           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3756    #[case(Node::Fragment(Fragment{values: vec!["test".to_string().into()]}),
3757           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3758    #[case(Node::Text(Text{value: "test".to_string(), position: None}),
3759           Node::Empty)]
3760    #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}),
3761           Node::Empty)]
3762    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
3763           Node::Empty)]
3764    #[case(Node::Empty, Node::Empty)]
3765    fn test_to_fragment(#[case] node: Node, #[case] expected: Node) {
3766        assert_eq!(node.to_fragment(), expected);
3767    }
3768
3769    #[rstest]
3770    #[case(
3771        &mut Node::Blockquote(Blockquote{values: vec![
3772            Node::Text(Text{value: "old".to_string(), position: None})
3773        ], position: None}),
3774        Node::Fragment(Fragment{values: vec![
3775            Node::Text(Text{value: "new".to_string(), position: None})
3776        ]}),
3777        Node::Blockquote(Blockquote{values: vec![
3778            Node::Text(Text{value: "new".to_string(), position: None})
3779        ], position: None})
3780    )]
3781    #[case(
3782        &mut Node::Delete(Delete{values: vec![
3783            Node::Text(Text{value: "old".to_string(), position: None})
3784        ], position: None}),
3785        Node::Fragment(Fragment{values: vec![
3786            Node::Text(Text{value: "new".to_string(), position: None})
3787        ]}),
3788        Node::Delete(Delete{values: vec![
3789            Node::Text(Text{value: "new".to_string(), position: None})
3790        ], position: None})
3791    )]
3792    #[case(
3793        &mut Node::Emphasis(Emphasis{values: vec![
3794            Node::Text(Text{value: "old".to_string(), position: None})
3795        ], position: None}),
3796        Node::Fragment(Fragment{values: vec![
3797            Node::Text(Text{value: "new".to_string(), position: None})
3798        ]}),
3799        Node::Emphasis(Emphasis{values: vec![
3800            Node::Text(Text{value: "new".to_string(), position: None})
3801        ], position: None})
3802    )]
3803    #[case(
3804        &mut Node::Strong(Strong{values: vec![
3805            Node::Text(Text{value: "old".to_string(), position: None})
3806        ], position: None}),
3807        Node::Fragment(Fragment{values: vec![
3808            Node::Text(Text{value: "new".to_string(), position: None})
3809        ]}),
3810        Node::Strong(Strong{values: vec![
3811            Node::Text(Text{value: "new".to_string(), position: None})
3812        ], position: None})
3813    )]
3814    #[case(
3815        &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3816            Node::Text(Text{value: "old".to_string(), position: None})
3817        ], position: None}),
3818        Node::Fragment(Fragment{values: vec![
3819            Node::Text(Text{value: "new".to_string(), position: None})
3820        ]}),
3821        Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3822            Node::Text(Text{value: "new".to_string(), position: None})
3823        ], position: None})
3824    )]
3825    #[case(
3826        &mut Node::Heading(Heading{depth: 1, values: vec![
3827            Node::Text(Text{value: "old".to_string(), position: None})
3828        ], position: None}),
3829        Node::Fragment(Fragment{values: vec![
3830            Node::Text(Text{value: "new".to_string(), position: None})
3831        ]}),
3832        Node::Heading(Heading{depth: 1, values: vec![
3833            Node::Text(Text{value: "new".to_string(), position: None})
3834        ], position: None})
3835    )]
3836    #[case(
3837        &mut Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
3838            Node::Text(Text{value: "old".to_string(), position: None})
3839        ], position: None}),
3840        Node::Fragment(Fragment{values: vec![
3841            Node::Text(Text{value: "new".to_string(), position: None})
3842        ]}),
3843        Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
3844            Node::Text(Text{value: "new".to_string(), position: None})
3845        ], position: None})
3846    )]
3847    #[case(
3848        &mut Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3849            Node::Text(Text{value: "old".to_string(), position: None})
3850        ], label: None, position: None}),
3851        Node::Fragment(Fragment{values: vec![
3852            Node::Text(Text{value: "new".to_string(), position: None})
3853        ]}),
3854        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3855            Node::Text(Text{value: "new".to_string(), position: None})
3856        ], label: None, position: None})
3857    )]
3858    #[case(
3859        &mut Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
3860            Node::Text(Text{value: "old".to_string(), position: None})
3861        ], position: None}),
3862        Node::Fragment(Fragment{values: vec![
3863            Node::Text(Text{value: "new".to_string(), position: None})
3864        ]}),
3865        Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
3866            Node::Text(Text{value: "new".to_string(), position: None})
3867        ], position: None})
3868    )]
3869    #[case(
3870        &mut Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3871            Node::Text(Text{value: "old".to_string(), position: None})
3872        ], position: None}),
3873        Node::Fragment(Fragment{values: vec![
3874            Node::Text(Text{value: "new".to_string(), position: None})
3875        ]}),
3876        Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3877            Node::Text(Text{value: "new".to_string(), position: None})
3878        ], position: None})
3879    )]
3880    #[case(
3881        &mut Node::TableRow(TableRow{values: vec![
3882            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3883                Node::Text(Text{value: "old".to_string(), position: None})
3884            ], position: None})
3885        ], position: None}),
3886        Node::Fragment(Fragment{values: vec![
3887            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3888                Node::Text(Text{value: "new".to_string(), position: None})
3889            ], position: None})
3890        ]}),
3891        Node::TableRow(TableRow{values: vec![
3892            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3893                Node::Text(Text{value: "new".to_string(), position: None})
3894            ], position: None})
3895        ], position: None})
3896    )]
3897    #[case(
3898        &mut Node::Text(Text{value: "old".to_string(), position: None}),
3899        Node::Fragment(Fragment{values: vec![
3900            Node::Text(Text{value: "new".to_string(), position: None})
3901        ]}),
3902        Node::Text(Text{value: "old".to_string(), position: None})
3903    )]
3904    #[case(
3905        &mut Node::Blockquote(Blockquote{values: vec![
3906            Node::Text(Text{value: "text1".to_string(), position: None}),
3907            Node::Text(Text{value: "text2".to_string(), position: None})
3908        ], position: None}),
3909        Node::Fragment(Fragment{values: vec![
3910            Node::Text(Text{value: "new1".to_string(), position: None}),
3911            Node::Text(Text{value: "new2".to_string(), position: None})
3912        ]}),
3913        Node::Blockquote(Blockquote{values: vec![
3914            Node::Text(Text{value: "new1".to_string(), position: None}),
3915            Node::Text(Text{value: "new2".to_string(), position: None})
3916        ], position: None})
3917    )]
3918    #[case(
3919        &mut Node::Strong(Strong{values: vec![
3920            Node::Text(Text{value: "text1".to_string(), position: None}),
3921            Node::Text(Text{value: "text2".to_string(), position: None})
3922        ], position: None}),
3923        Node::Fragment(Fragment{values: vec![
3924            Node::Empty,
3925            Node::Text(Text{value: "new2".to_string(), position: None})
3926        ]}),
3927        Node::Strong(Strong{values: vec![
3928            Node::Text(Text{value: "text1".to_string(), position: None}),
3929            Node::Text(Text{value: "new2".to_string(), position: None})
3930        ], position: None})
3931    )]
3932    #[case(
3933        &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3934            Node::Text(Text{value: "text1".to_string(), position: None}),
3935            Node::Text(Text{value: "text2".to_string(), position: None})
3936        ], position: None}),
3937        Node::Fragment(Fragment{values: vec![
3938            Node::Text(Text{value: "new1".to_string(), position: None}),
3939            Node::Fragment(Fragment{values: Vec::new()})
3940        ]}),
3941        Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3942            Node::Text(Text{value: "new1".to_string(), position: None}),
3943            Node::Text(Text{value: "text2".to_string(), position: None})
3944        ], position: None})
3945    )]
3946    fn test_apply_fragment(#[case] node: &mut Node, #[case] fragment: Node, #[case] expected: Node) {
3947        node.apply_fragment(fragment);
3948        assert_eq!(*node, expected);
3949    }
3950
3951    #[rstest]
3952    #[case(Node::Text(Text{value: "test".to_string(), position: None}),
3953       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3954       Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3955    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
3956       Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
3957       Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}})}))]
3958    #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![], position: None}),
3959       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3960       Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3961    #[case(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
3962       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3963       Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
3964    #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3965        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3966        Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3967    #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3968        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3969        Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3970    #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3971        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3972        Node::Footnote(Footnote{ident: "id".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3973    #[case(Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
3974        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3975        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3976    #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}),
3977        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
3978        Node::Html(Html{value: "<div>test</div>".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
3979    #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}),
3980        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
3981        Node::Yaml(Yaml{value: "key: value".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
3982    #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}),
3983        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
3984        Node::Toml(Toml{value: "key = \"value\"".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
3985    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
3986        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 12}},
3987        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 12}})}))]
3988    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
3989        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3990        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
3991    #[case(Node::CodeInline(CodeInline{value: "code".into(), position: None}),
3992        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
3993        Node::CodeInline(CodeInline{value: "code".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
3994    #[case(Node::MathInline(MathInline{value: "x^2".into(), position: None}),
3995        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3996        Node::MathInline(MathInline{value: "x^2".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3997    #[case(Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: None, values: vec![Node::Text(Text{value: "text".to_string(), position: None})], position: None}),
3998        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3999        Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: None, values: vec![Node::Text(Text{value: "text".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
4000    #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![Node::Text(Text{value: "text".to_string(), position: None})], label: None, position: None}),
4001        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4002        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![Node::Text(Text{value: "text".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})], label: None, position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
4003    #[case(Node::Math(Math{value: "x^2".to_string(), position: None}),
4004        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
4005        Node::Math(Math{value: "x^2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}})}))]
4006    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "cell".to_string(), position: None})], position: None}),
4007        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 6}},
4008        Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "cell".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 6}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 6}})}))]
4009    #[case(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
4010        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
4011        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
4012    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
4013        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4014        Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4015    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
4016        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4017        Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4018    #[case(Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: None}),
4019        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}},
4020        Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}})}))]
4021    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: vec![Node::Text(Text{value: "text".to_string(), position: None})], position: None}),
4022        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4023        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: vec![Node::Text(Text{value: "text".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})})], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})}))]
4024    #[case(Node::Break(Break{position: None}),
4025        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}},
4026        Node::Break(Break{position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}})}))]
4027    #[case(Node::Empty,
4028       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4029       Node::Empty)]
4030    #[case(Node::Fragment(Fragment{values: vec![
4031           Node::Text(Text{value: "test1".to_string(), position: None}),
4032           Node::Text(Text{value: "test2".to_string(), position: None})
4033       ]}),
4034       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4035       Node::Fragment(Fragment{values: vec![
4036           Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}),
4037           Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4038       ]}))]
4039    #[case(Node::Blockquote(Blockquote{values: vec![
4040        Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4041        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4042        Node::Blockquote(Blockquote{values: vec![
4043            Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4044        ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4045    #[case(Node::Heading(Heading{depth: 1, values: vec![
4046            Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4047            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4048            Node::Heading(Heading{depth: 1, values: vec![
4049                Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4050            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4051    #[case(Node::Strong(Strong{values: vec![
4052            Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4053            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4054            Node::Strong(Strong{values: vec![
4055                Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4056            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4057    #[case(Node::TableRow(TableRow{values: vec![
4058            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4059                Node::Text(Text{value: "cell".to_string(), position: None})
4060            ], position: None})
4061        ], position: None}),
4062            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4063            Node::TableRow(TableRow{values: vec![
4064                Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4065                    Node::Text(Text{value: "cell".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4066                ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4067            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
4068    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4069            name: Some("div".to_string()),
4070            attributes: Vec::new(),
4071            children: vec![Node::Text(Text{value: "content".to_string(), position: None})],
4072            position: None
4073        }),
4074            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4075            Node::MdxJsxFlowElement(MdxJsxFlowElement{
4076                name: Some("div".to_string()),
4077                attributes: Vec::new(),
4078                children: vec![Node::Text(Text{value: "content".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})})],
4079                position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})
4080            }))]
4081    fn test_set_position(#[case] mut node: Node, #[case] position: Position, #[case] expected: Node) {
4082        node.set_position(Some(position));
4083        assert_eq!(node, expected);
4084    }
4085
4086    #[rstest]
4087    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4088    #[case(Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4089    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4090    fn test_is_list(#[case] node: Node, #[case] expected: bool) {
4091        assert_eq!(node.is_list(), expected);
4092    }
4093
4094    #[rstest]
4095    #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::None, ..Default::default()}, "https://example.com")]
4096    #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::Angle, ..Default::default()}, "<https://example.com>")]
4097    #[case(Url::new("".to_string()), RenderOptions::default(), "")]
4098    fn test_url_to_string_with(#[case] url: Url, #[case] options: RenderOptions, #[case] expected: &str) {
4099        assert_eq!(url.to_string_with(&options), expected);
4100    }
4101
4102    #[rstest]
4103    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions::default(), "\"title\"")]
4104    #[case(Title::new(r#"title with "quotes""#.to_string()), RenderOptions::default(), r#""title with "quotes"""#)]
4105    #[case(Title::new("title with spaces".to_string()), RenderOptions::default(), "\"title with spaces\"")]
4106    #[case(Title::new("".to_string()), RenderOptions::default(), "\"\"")]
4107    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Single, ..Default::default()}, "'title'")]
4108    #[case(Title::new("title with 'quotes'".to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Double, ..Default::default()}, "\"title with 'quotes'\"")]
4109    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Paren, ..Default::default()}, "(title)")]
4110    fn test_title_to_string_with(#[case] title: Title, #[case] options: RenderOptions, #[case] expected: &str) {
4111        assert_eq!(title.to_string_with(&options), expected);
4112    }
4113
4114    #[rstest]
4115    #[case(Node::Fragment(Fragment{values: vec![]}), true)]
4116    #[case(Node::Fragment(Fragment{values: vec![
4117        Node::Text(Text{value: "not_empty".to_string(), position: None})
4118    ]}), false)]
4119    #[case(Node::Fragment(Fragment{values: vec![
4120        Node::Fragment(Fragment{values: vec![]}),
4121        Node::Fragment(Fragment{values: vec![]})
4122    ]}), true)]
4123    #[case(Node::Fragment(Fragment{values: vec![
4124        Node::Fragment(Fragment{values: vec![]}),
4125        Node::Text(Text{value: "not_empty".to_string(), position: None})
4126    ]}), false)]
4127    #[case(Node::Text(Text{value: "not_fragment".to_string(), position: None}), false)]
4128    fn test_is_empty_fragment(#[case] node: Node, #[case] expected: bool) {
4129        assert_eq!(node.is_empty_fragment(), expected);
4130    }
4131
4132    #[rstest]
4133    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4134    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), "unknown", None)]
4135    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("<div>test</div>".to_string())))]
4136    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), "unknown", None)]
4137    #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("text".to_string())))]
4138    #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), "unknown", None)]
4139    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), meta: Some("meta".to_string()), fence: true, position: None}), attr_keys::VALUE, Some(AttrValue::String("code".to_string())))]
4140    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), meta: Some("meta".to_string()), fence: true, position: None}), attr_keys::LANG, Some(AttrValue::String("rust".to_string())))]
4141    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), meta: Some("meta".to_string()), fence: true, position: None}), "meta", Some(AttrValue::String("meta".to_string())))]
4142    #[case::code(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), meta: Some("meta".to_string()), fence: true, position: None}), attr_keys::FENCE, Some(AttrValue::Boolean(true)))]
4143    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, meta: None, fence: false, position: None}), attr_keys::FENCE, Some(AttrValue::Boolean(false)))]
4144    #[case::code_inline(Node::CodeInline(CodeInline{value: "inline".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("inline".to_string())))]
4145    #[case::math_inline(Node::MathInline(MathInline{value: "math".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4146    #[case::math(Node::Math(Math{value: "math".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4147    #[case::yaml(Node::Yaml(Yaml{value: "yaml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("yaml".to_string())))]
4148    #[case::toml(Node::Toml(Toml{value: "toml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("toml".to_string())))]
4149    #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}), attr_keys::ALT, Some(AttrValue::String(attr_keys::ALT.to_string())))]
4150    #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}), attr_keys::URL, Some(AttrValue::String(attr_keys::URL.to_string())))]
4151    #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}), attr_keys::TITLE, Some(AttrValue::String(attr_keys::TITLE.to_string())))]
4152    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::ALT, Some(AttrValue::String(attr_keys::ALT.to_string())))]
4153    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4154    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::LABEL, Some(AttrValue::String(attr_keys::LABEL.to_string())))]
4155    #[case::link(Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), values: Vec::new(), position: None}), attr_keys::URL, Some(AttrValue::String(attr_keys::URL.to_string())))]
4156    #[case::link(Node::Link(Link{url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), values: Vec::new(), position: None}), attr_keys::TITLE, Some(AttrValue::String(attr_keys::TITLE.to_string())))]
4157    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: Vec::new(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4158    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: Vec::new(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::LABEL, Some(AttrValue::String(attr_keys::LABEL.to_string())))]
4159    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4160    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::LABEL, Some(AttrValue::String(attr_keys::LABEL.to_string())))]
4161    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4162    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::URL, Some(AttrValue::String(attr_keys::URL.to_string())))]
4163    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::TITLE, Some(AttrValue::String(attr_keys::TITLE.to_string())))]
4164    #[case::definition(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new(attr_keys::TITLE.to_string())), label: Some(attr_keys::LABEL.to_string()), position: None}), attr_keys::LABEL, Some(AttrValue::String(attr_keys::LABEL.to_string())))]
4165    #[case::heading(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "depth", Some(AttrValue::Integer(3)))]
4166    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "index", Some(AttrValue::Integer(2)))]
4167    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "level", Some(AttrValue::Integer(1)))]
4168    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "ordered", Some(AttrValue::Boolean(true)))]
4169    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), attr_keys::CHECKED, Some(AttrValue::Boolean(true)))]
4170    #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "column", Some(AttrValue::Integer(1)))]
4171    #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "row", Some(AttrValue::Integer(2)))]
4172    #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}), "align", Some(AttrValue::String(":---,---:".to_string())))]
4173    #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
4174    #[case::mdx_flow_expression(Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
4175    #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "esm".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("esm".to_string())))]
4176    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}), attr_keys::NAME, Some(AttrValue::String("div".to_string())))]
4177    #[case::mdx_jsx_flow_element(Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}), attr_keys::NAME, Some(AttrValue::String("span".to_string())))]
4178    #[case::break_(Node::Break(Break{position: None}), attr_keys::VALUE, None)]
4179    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), attr_keys::VALUE, None)]
4180    #[case::fragment(Node::Fragment(Fragment{values: Vec::new()}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
4181    #[case::heading(Node::Heading(Heading{depth: 1, values: vec![Node::Text(Text{value: "heading text".to_string(), position: None})], position: None}), attr_keys::VALUE, Some(AttrValue::String("heading text".to_string())))]
4182    #[case::heading(Node::Heading(Heading{depth: 2, values: vec![], position: None}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
4183    #[case::heading(Node::Heading(Heading{depth: 3, values: vec![
4184        Node::Text(Text{value: "first".to_string(), position: None}),
4185        Node::Text(Text{value: "second".to_string(), position: None}),
4186    ], position: None}), attr_keys::VALUE, Some(AttrValue::String("firstsecond".to_string())))]
4187    #[case(
4188        Node::List(List {
4189            index: 0,
4190            level: 1,
4191            checked: None,
4192            ordered: false,
4193            values: vec![
4194                Node::Text(Text { value: "item1".to_string(), position: None }),
4195                Node::Text(Text { value: "item2".to_string(), position: None }),
4196            ],
4197            position: None,
4198        }),
4199        attr_keys::VALUE,
4200        Some(AttrValue::String("item1item2".to_string()))
4201    )]
4202    #[case(
4203        Node::TableCell(TableCell {
4204            column: 1,
4205            row: 2,
4206            values: vec![Node::Text(Text {
4207                value: "cell_value".to_string(),
4208                position: None,
4209            })],
4210            position: None,
4211        }),
4212        attr_keys::VALUE,
4213        Some(AttrValue::String("cell_value".to_string()))
4214    )]
4215    #[case::footnote(
4216        Node::Footnote(Footnote {
4217            ident: "id".to_string(),
4218            values: vec![Node::Text(Text {
4219                value: "footnote value".to_string(),
4220                position: None,
4221            })],
4222            position: None,
4223        }),
4224        attr_keys::VALUE,
4225        Some(AttrValue::String("footnote value".to_string()))
4226    )]
4227    #[case::link(
4228        Node::Link(Link {
4229            url: Url::new("https://example.com".to_string()),
4230            title: Some(Title::new("Example".to_string())),
4231            values: vec![Node::Text(Text {
4232                value: "link text".to_string(),
4233                position: None,
4234            })],
4235            position: None,
4236        }),
4237        attr_keys::VALUE,
4238        Some(AttrValue::String("link text".to_string()))
4239    )]
4240    #[case::empty(Node::Empty, attr_keys::VALUE, None)]
4241    #[case::heading(
4242        Node::Heading(Heading {
4243            depth: 1,
4244            values: vec![
4245            Node::Text(Text {
4246                value: "child1".to_string(),
4247                position: None,
4248            }),
4249            Node::Text(Text {
4250                value: "child2".to_string(),
4251                position: None,
4252            }),
4253            ],
4254            position: None,
4255        }),
4256        attr_keys::CHILDREN,
4257        Some(AttrValue::Array(vec![
4258            Node::Text(Text {
4259            value: "child1".to_string(),
4260            position: None,
4261            }),
4262            Node::Text(Text {
4263            value: "child2".to_string(),
4264            position: None,
4265            }),
4266        ]))
4267        )]
4268    #[case::list(
4269        Node::List(List {
4270            index: 0,
4271            level: 1,
4272            checked: None,
4273            ordered: false,
4274            values: vec![
4275            Node::Text(Text {
4276                value: "item1".to_string(),
4277                position: None,
4278            }),
4279            ],
4280            position: None,
4281        }),
4282        attr_keys::CHILDREN,
4283        Some(AttrValue::Array(vec![
4284            Node::Text(Text {
4285            value: "item1".to_string(),
4286            position: None,
4287            }),
4288        ]))
4289        )]
4290    #[case::blockquote(
4291        Node::Blockquote(Blockquote {
4292            values: vec![
4293            Node::Text(Text {
4294                value: "quote".to_string(),
4295                position: None,
4296            }),
4297            ],
4298            position: None,
4299        }),
4300        attr_keys::VALUES,
4301        Some(AttrValue::Array(vec![
4302            Node::Text(Text {
4303            value: "quote".to_string(),
4304            position: None,
4305            }),
4306        ]))
4307        )]
4308    #[case::link(
4309        Node::Link(Link {
4310            url: Url::new(attr_keys::URL.to_string()),
4311            title: None,
4312            values: vec![
4313            Node::Text(Text {
4314                value: "link".to_string(),
4315                position: None,
4316            }),
4317            ],
4318            position: None,
4319        }),
4320        attr_keys::VALUES,
4321        Some(AttrValue::Array(vec![
4322            Node::Text(Text {
4323            value: "link".to_string(),
4324            position: None,
4325            }),
4326        ]))
4327        )]
4328    #[case::table_cell(
4329        Node::TableCell(TableCell {
4330            column: 0,
4331            row: 0,
4332            values: vec![
4333            Node::Text(Text {
4334                value: "cell".to_string(),
4335                position: None,
4336            }),
4337            ],
4338            position: None,
4339        }),
4340        attr_keys::CHILDREN,
4341        Some(AttrValue::Array(vec![
4342            Node::Text(Text {
4343            value: "cell".to_string(),
4344            position: None,
4345            }),
4346        ]))
4347        )]
4348    #[case::strong(
4349        Node::Strong(Strong {
4350            values: vec![
4351            Node::Text(Text {
4352                value: "bold".to_string(),
4353                position: None,
4354            }),
4355            ],
4356            position: None,
4357        }),
4358        attr_keys::CHILDREN,
4359        Some(AttrValue::Array(vec![
4360            Node::Text(Text {
4361            value: "bold".to_string(),
4362            position: None,
4363            }),
4364        ]))
4365        )]
4366    #[case::em(
4367        Node::Emphasis(Emphasis {
4368            values: vec![],
4369            position: None,
4370        }),
4371        attr_keys::CHILDREN,
4372        Some(AttrValue::Array(vec![]))
4373        )]
4374    fn test_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
4375        assert_eq!(node.attr(attr), expected);
4376    }
4377
4378    #[rstest]
4379    #[case(
4380        Node::Text(Text{value: "old".to_string(), position: None}),
4381        attr_keys::VALUE,
4382        "new",
4383        Node::Text(Text{value: "new".to_string(), position: None})
4384    )]
4385    #[case(
4386        Node::Code(Code{value: "old".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
4387        attr_keys::VALUE,
4388        "new_code",
4389        Node::Code(Code{value: "new_code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None})
4390    )]
4391    #[case(
4392        Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
4393        attr_keys::LANG,
4394        "python",
4395        Node::Code(Code{value: "code".to_string(), lang: Some("python".to_string()), fence: true, meta: None, position: None})
4396    )]
4397    #[case(
4398        Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}),
4399        attr_keys::FENCE,
4400        "true",
4401        Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None})
4402    )]
4403    #[case(
4404        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4405        attr_keys::ALT,
4406        "new_alt",
4407        Node::Image(Image{alt: "new_alt".to_string(), url: attr_keys::URL.to_string(), title: None, position: None})
4408    )]
4409    #[case(
4410        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4411        attr_keys::URL,
4412        "new_url",
4413        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "new_url".to_string(), title: None, position: None})
4414    )]
4415    #[case(
4416        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}),
4417        attr_keys::TITLE,
4418        "new_title",
4419        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some("new_title".to_string()), position: None})
4420    )]
4421    #[case(
4422        Node::Heading(Heading{depth: 2, values: vec![], position: None}),
4423        "depth",
4424        "3",
4425        Node::Heading(Heading{depth: 3, values: vec![], position: None})
4426    )]
4427    #[case(
4428        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
4429        attr_keys::CHECKED,
4430        "false",
4431        Node::List(List{index: 1, level: 2, checked: Some(false), ordered: false, values: vec![], position: None})
4432    )]
4433    #[case(
4434        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
4435        "ordered",
4436        "true",
4437        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: true, values: vec![], position: None})
4438    )]
4439    #[case(
4440        Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
4441        "column",
4442        "3",
4443        Node::TableCell(TableCell{column: 3, row: 2, values: vec![], position: None})
4444    )]
4445    #[case(
4446        Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
4447        "row",
4448        "5",
4449        Node::TableCell(TableCell{column: 1, row: 5, values: vec![], position: None})
4450    )]
4451    #[case(
4452        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4453        attr_keys::IDENT,
4454        "new_id",
4455        Node::Definition(Definition{ident: "new_id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None})
4456    )]
4457    #[case(
4458        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4459        attr_keys::URL,
4460        "new_url",
4461        Node::Definition(Definition{ident: "id".to_string(), url: Url::new("new_url".to_string()), title: None, label: None, position: None})
4462    )]
4463    #[case(
4464        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4465        attr_keys::LABEL,
4466        "new_label",
4467        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: Some("new_label".to_string()), position: None})
4468    )]
4469    #[case(
4470        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4471        attr_keys::TITLE,
4472        "new_title",
4473        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: Some(Title::new("new_title".to_string())), label: None, position: None})
4474    )]
4475    #[case(
4476        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4477        attr_keys::ALT,
4478        "new_alt",
4479        Node::ImageRef(ImageRef{alt: "new_alt".to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4480    )]
4481    #[case(
4482        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4483        attr_keys::IDENT,
4484        "new_id",
4485        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4486    )]
4487    #[case(
4488        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4489        attr_keys::LABEL,
4490        "new_label",
4491        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4492    )]
4493    #[case(
4494        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
4495        attr_keys::LABEL,
4496        "new_label",
4497        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4498    )]
4499    #[case(
4500        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4501        attr_keys::IDENT,
4502        "new_id",
4503        Node::LinkRef(LinkRef{ident: "new_id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
4504    )]
4505    #[case(
4506        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4507        attr_keys::LABEL,
4508        "new_label",
4509        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
4510    )]
4511    #[case(
4512        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: None, position: None}),
4513        attr_keys::LABEL,
4514        "new_label",
4515        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
4516    )]
4517    #[case(
4518        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4519        "unknown",
4520        "ignored",
4521        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
4522    )]
4523    #[case(
4524        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4525        attr_keys::IDENT,
4526        "new_id",
4527        Node::FootnoteRef(FootnoteRef{ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4528    )]
4529    #[case(
4530        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4531        attr_keys::LABEL,
4532        "new_label",
4533        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4534    )]
4535    #[case(
4536        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: None, position: None}),
4537        attr_keys::LABEL,
4538        "new_label",
4539        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4540    )]
4541    #[case(
4542        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4543        "unknown",
4544        "ignored",
4545        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4546    )]
4547    #[case(Node::Empty, attr_keys::VALUE, "ignored", Node::Empty)]
4548    #[case(
4549        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
4550        "align",
4551        "---,:---:",
4552        Node::TableAlign(TableAlign{align: vec![TableAlignKind::None, TableAlignKind::Center], position: None})
4553    )]
4554    #[case(
4555        Node::TableAlign(TableAlign{align: vec![], position: None}),
4556        "align",
4557        ":---,---:",
4558        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None})
4559    )]
4560    #[case(
4561        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None}),
4562        "unknown",
4563        "ignored",
4564        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None})
4565    )]
4566    #[case(
4567        Node::MdxFlowExpression(MdxFlowExpression{value: "old".into(), position: None}),
4568        attr_keys::VALUE,
4569        "new_expr",
4570        Node::MdxFlowExpression(MdxFlowExpression{value: "new_expr".into(), position: None})
4571    )]
4572    #[case(
4573        Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}),
4574        "unknown",
4575        "ignored",
4576        Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None})
4577    )]
4578    #[case(
4579        Node::MdxTextExpression(MdxTextExpression{value: "old".into(), position: None}),
4580        attr_keys::VALUE,
4581        "new_expr",
4582        Node::MdxTextExpression(MdxTextExpression{value: "new_expr".into(), position: None})
4583    )]
4584    #[case(
4585        Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}),
4586        "unknown",
4587        "ignored",
4588        Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None})
4589    )]
4590    #[case(
4591        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
4592        attr_keys::VALUE,
4593        "import y",
4594        Node::MdxJsEsm(MdxJsEsm{value: "import y".into(), position: None})
4595    )]
4596    #[case(
4597        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
4598        "unknown",
4599        "ignored",
4600        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None})
4601    )]
4602    #[case(
4603        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
4604        attr_keys::NAME,
4605        "section",
4606        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("section".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4607    )]
4608    #[case(
4609        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
4610        attr_keys::NAME,
4611        "main",
4612        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("main".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4613    )]
4614    #[case(
4615        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
4616        "unknown",
4617        "ignored",
4618        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4619    )]
4620    #[case(
4621        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
4622        attr_keys::NAME,
4623        "b",
4624        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("b".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4625    )]
4626    #[case(
4627        Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
4628        attr_keys::NAME,
4629        "i",
4630        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("i".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4631    )]
4632    #[case(
4633        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
4634        "unknown",
4635        "ignored",
4636        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4637    )]
4638    fn test_set_attr(#[case] mut node: Node, #[case] attr: &str, #[case] value: &str, #[case] expected: Node) {
4639        node.set_attr(attr, value);
4640        assert_eq!(node, expected);
4641    }
4642
4643    #[rstest]
4644    #[case(AttrValue::String("test".to_string()), AttrValue::String("test".to_string()), true)]
4645    #[case(AttrValue::String("test".to_string()), AttrValue::String("other".to_string()), false)]
4646    #[case(AttrValue::Integer(42), AttrValue::Integer(42), true)]
4647    #[case(AttrValue::Integer(42), AttrValue::Integer(0), false)]
4648    #[case(AttrValue::Boolean(true), AttrValue::Boolean(true), true)]
4649    #[case(AttrValue::Boolean(true), AttrValue::Boolean(false), false)]
4650    #[case(AttrValue::String("42".to_string()), AttrValue::Integer(42), false)]
4651    #[case(AttrValue::Boolean(false), AttrValue::Integer(0), false)]
4652    fn test_attr_value_eq(#[case] a: AttrValue, #[case] b: AttrValue, #[case] expected: bool) {
4653        assert_eq!(a == b, expected);
4654    }
4655
4656    #[rstest]
4657    #[case(AttrValue::String("test".to_string()), "test")]
4658    #[case(AttrValue::Integer(42), "42")]
4659    #[case(AttrValue::Boolean(true), "true")]
4660    fn test_attr_value_as_str(#[case] value: AttrValue, #[case] expected: &str) {
4661        assert_eq!(&value.as_string(), expected);
4662    }
4663
4664    #[rstest]
4665    #[case(AttrValue::Integer(42), Some(42))]
4666    #[case(AttrValue::String("42".to_string()), Some(42))]
4667    #[case(AttrValue::Boolean(false), Some(0))]
4668    fn test_attr_value_as_i64(#[case] value: AttrValue, #[case] expected: Option<i64>) {
4669        assert_eq!(value.as_i64(), expected);
4670    }
4671}