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                vec![Self::Link(Link {
2454                    url: Url(url),
2455                    title: title.map(Title),
2456                    values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
2457                    position: position.map(|p| p.clone().into()),
2458                })]
2459            }
2460            mdast::Node::LinkReference(mdast::LinkReference {
2461                identifier,
2462                label,
2463                position,
2464                children,
2465                ..
2466            }) => {
2467                vec![Self::LinkRef(LinkRef {
2468                    ident: identifier,
2469                    values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
2470                    label,
2471                    position: position.map(|p| p.clone().into()),
2472                })]
2473            }
2474            mdast::Node::Math(mdast::Math { value, position, .. }) => {
2475                vec![Self::Math(Math {
2476                    value,
2477                    position: position.map(|p| p.clone().into()),
2478                })]
2479            }
2480            mdast::Node::FootnoteDefinition(mdast::FootnoteDefinition {
2481                identifier,
2482                position,
2483                children,
2484                ..
2485            }) => {
2486                vec![Self::Footnote(Footnote {
2487                    ident: identifier,
2488                    values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
2489                    position: position.map(|p| p.clone().into()),
2490                })]
2491            }
2492            mdast::Node::FootnoteReference(mdast::FootnoteReference {
2493                identifier,
2494                label,
2495                position,
2496                ..
2497            }) => {
2498                vec![Self::FootnoteRef(FootnoteRef {
2499                    ident: identifier,
2500                    label,
2501                    position: position.map(|p| p.clone().into()),
2502                })]
2503            }
2504            mdast::Node::MdxFlowExpression(mdx) => {
2505                vec![Self::MdxFlowExpression(MdxFlowExpression {
2506                    value: mdx.value.into(),
2507                    position: mdx.position.map(|position| position.into()),
2508                })]
2509            }
2510            mdast::Node::MdxJsxFlowElement(mdx) => {
2511                vec![Self::MdxJsxFlowElement(MdxJsxFlowElement {
2512                    children: mdx
2513                        .children
2514                        .into_iter()
2515                        .flat_map(Self::from_mdast_node)
2516                        .collect::<Vec<_>>(),
2517                    position: mdx.position.map(|p| p.clone().into()),
2518                    name: mdx.name,
2519                    attributes: mdx
2520                        .attributes
2521                        .iter()
2522                        .map(|attr| match attr {
2523                            mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
2524                                MdxAttributeContent::Expression(value.into())
2525                            }
2526                            mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
2527                                MdxAttributeContent::Property(MdxJsxAttribute {
2528                                    name: name.into(),
2529                                    value: value.as_ref().map(|value| match value {
2530                                        mdast::AttributeValue::Literal(value) => {
2531                                            MdxAttributeValue::Literal(value.into())
2532                                        }
2533                                        mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
2534                                            value,
2535                                            ..
2536                                        }) => MdxAttributeValue::Expression(value.into()),
2537                                    }),
2538                                })
2539                            }
2540                        })
2541                        .collect(),
2542                })]
2543            }
2544            mdast::Node::MdxJsxTextElement(mdx) => {
2545                vec![Self::MdxJsxTextElement(MdxJsxTextElement {
2546                    children: mdx
2547                        .children
2548                        .into_iter()
2549                        .flat_map(Self::from_mdast_node)
2550                        .collect::<Vec<_>>(),
2551                    position: mdx.position.map(|p| p.clone().into()),
2552                    name: mdx.name.map(|name| name.into()),
2553                    attributes: mdx
2554                        .attributes
2555                        .iter()
2556                        .map(|attr| match attr {
2557                            mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
2558                                MdxAttributeContent::Expression(value.into())
2559                            }
2560                            mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
2561                                MdxAttributeContent::Property(MdxJsxAttribute {
2562                                    name: name.into(),
2563                                    value: value.as_ref().map(|value| match value {
2564                                        mdast::AttributeValue::Literal(value) => {
2565                                            MdxAttributeValue::Literal(value.into())
2566                                        }
2567                                        mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
2568                                            value,
2569                                            ..
2570                                        }) => MdxAttributeValue::Expression(value.into()),
2571                                    }),
2572                                })
2573                            }
2574                        })
2575                        .collect(),
2576                })]
2577            }
2578            mdast::Node::MdxTextExpression(mdx) => {
2579                vec![Self::MdxTextExpression(MdxTextExpression {
2580                    value: mdx.value.into(),
2581                    position: mdx.position.map(|position| position.into()),
2582                })]
2583            }
2584            mdast::Node::MdxjsEsm(mdx) => {
2585                vec![Self::MdxJsEsm(MdxJsEsm {
2586                    value: mdx.value.into(),
2587                    position: mdx.position.map(|position| position.into()),
2588                })]
2589            }
2590            mdast::Node::Text(mdast::Text { position, value, .. }) => {
2591                vec![Self::Text(Text {
2592                    value,
2593                    position: position.map(|p| p.clone().into()),
2594                })]
2595            }
2596            mdast::Node::Paragraph(mdast::Paragraph { children, .. }) => {
2597                children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>()
2598            }
2599            _ => Vec::new(),
2600        }
2601    }
2602
2603    fn mdast_children_to_node(node: mdast::Node) -> Vec<Node> {
2604        node.children()
2605            .map(|children| {
2606                children
2607                    .iter()
2608                    .flat_map(|v| Self::from_mdast_node(v.clone()))
2609                    .collect::<Vec<_>>()
2610            })
2611            .unwrap_or_else(|| vec![EMPTY_NODE])
2612    }
2613
2614    fn mdast_list_items(list: &mdast::List, level: Level) -> Vec<Node> {
2615        list.children
2616            .iter()
2617            .flat_map(|n| {
2618                if let mdast::Node::ListItem(list_item) = n {
2619                    let values = Self::from_mdast_node(n.clone())
2620                        .into_iter()
2621                        .filter(|value| !matches!(value, Self::List(_)))
2622                        .collect::<Vec<_>>();
2623                    let position = if values.is_empty() {
2624                        n.position().map(|p| p.clone().into())
2625                    } else {
2626                        let first_pos = values.first().and_then(|v| v.position());
2627                        let last_pos = values.last().and_then(|v| v.position());
2628                        match (first_pos, last_pos) {
2629                            (Some(start), Some(end)) => Some(Position {
2630                                start: start.start.clone(),
2631                                end: end.end.clone(),
2632                            }),
2633                            _ => n.position().map(|p| p.clone().into()),
2634                        }
2635                    };
2636
2637                    itertools::concat(vec![
2638                        vec![Self::List(List {
2639                            level,
2640                            index: 0,
2641                            ordered: list.ordered,
2642                            checked: list_item.checked,
2643                            values,
2644                            position,
2645                        })],
2646                        list_item
2647                            .children
2648                            .iter()
2649                            .flat_map(|node| {
2650                                if let mdast::Node::List(sub_list) = node {
2651                                    Self::mdast_list_items(sub_list, level + 1)
2652                                } else if let mdast::Node::ListItem(list_item) = node {
2653                                    let values = Self::from_mdast_node(n.clone())
2654                                        .into_iter()
2655                                        .filter(|value| !matches!(value, Self::List(_)))
2656                                        .collect::<Vec<_>>();
2657                                    let position = if values.is_empty() {
2658                                        n.position().map(|p| p.clone().into())
2659                                    } else {
2660                                        let first_pos = values.first().and_then(|v| v.position());
2661                                        let last_pos = values.last().and_then(|v| v.position());
2662                                        match (first_pos, last_pos) {
2663                                            (Some(start), Some(end)) => Some(Position {
2664                                                start: start.start.clone(),
2665                                                end: end.end.clone(),
2666                                            }),
2667                                            _ => n.position().map(|p| p.clone().into()),
2668                                        }
2669                                    };
2670                                    vec![Self::List(List {
2671                                        level: level + 1,
2672                                        index: 0,
2673                                        ordered: list.ordered,
2674                                        checked: list_item.checked,
2675                                        values,
2676                                        position,
2677                                    })]
2678                                } else {
2679                                    Vec::new()
2680                                }
2681                            })
2682                            .collect(),
2683                    ])
2684                } else if let mdast::Node::List(sub_list) = n {
2685                    Self::mdast_list_items(sub_list, level + 1)
2686                } else {
2687                    Vec::new()
2688                }
2689            })
2690            .enumerate()
2691            .filter_map(|(i, node)| match node {
2692                Self::List(List {
2693                    level,
2694                    index: _,
2695                    ordered,
2696                    checked,
2697                    values,
2698                    position,
2699                }) => Some(Self::List(List {
2700                    level,
2701                    index: i,
2702                    ordered,
2703                    checked,
2704                    values,
2705                    position,
2706                })),
2707                _ => None,
2708            })
2709            .collect()
2710    }
2711
2712    fn mdx_attribute_content_to_string(attr: MdxAttributeContent) -> SmolStr {
2713        match attr {
2714            MdxAttributeContent::Expression(value) => format!("{{{}}}", value).into(),
2715            MdxAttributeContent::Property(property) => match property.value {
2716                Some(value) => match value {
2717                    MdxAttributeValue::Expression(value) => format!("{}={{{}}}", property.name, value).into(),
2718                    MdxAttributeValue::Literal(literal) => format!("{}=\"{}\"", property.name, literal).into(),
2719                },
2720                None => property.name,
2721            },
2722        }
2723    }
2724}
2725
2726pub(crate) fn values_to_string(values: &[Node], options: &RenderOptions) -> String {
2727    render_values(values, options, &ColorTheme::PLAIN)
2728}
2729
2730pub(crate) fn render_values(values: &[Node], options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
2731    let mut pre_position: Option<Position> = None;
2732    values
2733        .iter()
2734        .map(|value| {
2735            if let Some(pos) = value.position() {
2736                let new_line_count = pre_position
2737                    .as_ref()
2738                    .map(|p: &Position| pos.start.line - p.end.line)
2739                    .unwrap_or_default();
2740
2741                let space = if new_line_count > 0
2742                    && pre_position
2743                        .as_ref()
2744                        .map(|p| pos.start.line > p.end.line)
2745                        .unwrap_or_default()
2746                {
2747                    " ".repeat(pos.start.column.saturating_sub(1))
2748                } else {
2749                    "".to_string()
2750                };
2751
2752                pre_position = Some(pos);
2753
2754                if space.is_empty() {
2755                    format!(
2756                        "{}{}",
2757                        "\n".repeat(new_line_count),
2758                        value.render_with_theme(options, theme)
2759                    )
2760                } else {
2761                    format!(
2762                        "{}{}",
2763                        "\n".repeat(new_line_count),
2764                        value
2765                            .render_with_theme(options, theme)
2766                            .lines()
2767                            .map(|line| format!("{}{}", space, line))
2768                            .join("\n")
2769                    )
2770                }
2771            } else {
2772                pre_position = None;
2773                value.render_with_theme(options, theme)
2774            }
2775        })
2776        .collect::<String>()
2777}
2778
2779fn values_to_value(values: Vec<Node>) -> String {
2780    values.iter().map(|value| value.value()).collect::<String>()
2781}
2782
2783#[cfg(test)]
2784mod tests {
2785    use super::*;
2786    use rstest::rstest;
2787
2788    #[rstest]
2789    #[case::text(Node::Text(Text{value: "".to_string(), position: None}),
2790           "test".to_string(),
2791           Node::Text(Text{value: "test".to_string(), position: None }))]
2792    #[case::blockquote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }),
2793           "test".to_string(),
2794           Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }))]
2795    #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }),
2796           "test".to_string(),
2797           Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }))]
2798    #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }),
2799           "test".to_string(),
2800           Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }))]
2801    #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }),
2802           "test".to_string(),
2803           Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }))]
2804    #[case::heading(Node::Heading(Heading {depth: 1, values: vec!["test".to_string().into()], position: None }),
2805           "test".to_string(),
2806           Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None }))]
2807    #[case::link(Node::Link(Link {url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }),
2808           "test".to_string(),
2809           Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }))]
2810    #[case::image(Node::Image(Image {alt: "test".to_string(), url: "test".to_string(), title: None, position: None }),
2811           "test".to_string(),
2812           Node::Image(Image{alt: "test".to_string(), url: "test".to_string(), title: None, position: None }))]
2813    #[case::code(Node::Code(Code {value: "test".to_string(), lang: None, fence: true, meta: None, position: None }),
2814           "test".to_string(),
2815           Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None }))]
2816    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef {ident: "test".to_string(), label: None, position: None }),
2817           "test".to_string(),
2818           Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: Some("test".to_string()), position: None }))]
2819    #[case::footnote(Node::Footnote(Footnote {ident: "test".to_string(), values: Vec::new(), position: None }),
2820           "test".to_string(),
2821           Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None }))]
2822    #[case::list(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }),
2823           "test".to_string(),
2824           Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }))]
2825    #[case::list(Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }),
2826           "test".to_string(),
2827           Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }))]
2828    #[case::list(Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }),
2829           "test".to_string(),
2830           Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }))]
2831    #[case::code_inline(Node::CodeInline(CodeInline{ value: "t".into(), position: None }),
2832           "test".to_string(),
2833           Node::CodeInline(CodeInline{ value: "test".into(), position: None }))]
2834    #[case::math_inline(Node::MathInline(MathInline{ value: "t".into(), position: None }),
2835           "test".to_string(),
2836           Node::MathInline(MathInline{ value: "test".into(), position: None }))]
2837    #[case::toml(Node::Toml(Toml{ value: "t".to_string(), position: None }),
2838           "test".to_string(),
2839           Node::Toml(Toml{ value: "test".to_string(), position: None }))]
2840    #[case::yaml(Node::Yaml(Yaml{ value: "t".to_string(), position: None }),
2841           "test".to_string(),
2842           Node::Yaml(Yaml{ value: "test".to_string(), position: None }))]
2843    #[case::html(Node::Html(Html{ value: "t".to_string(), position: None }),
2844           "test".to_string(),
2845           Node::Html(Html{ value: "test".to_string(), position: None }))]
2846    #[case::table_row(Node::TableRow(TableRow{ values: vec![
2847                        Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1,  position: None}),
2848                        Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:2,  position: None})
2849                    ]
2850                    , position: None }),
2851           "test3,test4".to_string(),
2852           Node::TableRow(TableRow{ values: vec![
2853                        Node::TableCell(TableCell{values: vec!["test3".to_string().into()], row:0, column:1, position: None}),
2854                        Node::TableCell(TableCell{values: vec!["test4".to_string().into()], row:0, column:2, position: None})
2855                    ]
2856                    , position: None }))]
2857    #[case::table_cell(Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1, position: None}),
2858            "test2".to_string(),
2859            Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:1, position: None}),)]
2860    #[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}),
2861            "test2".to_string(),
2862            Node::LinkRef(LinkRef{ident: "test2".to_string(), values: vec![attr_keys::VALUE.to_string().into()], label: Some("test2".to_string()), position: None}),)]
2863    #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test1".to_string(), label: None, position: None}),
2864            "test2".to_string(),
2865            Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test2".to_string(), label: Some("test2".to_string()), position: None}),)]
2866    #[case::definition(Node::Definition(Definition{ url: Url::new(attr_keys::URL.to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),
2867            "test2".to_string(),
2868            Node::Definition(Definition{url: Url::new("test2".to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),)]
2869    #[case::break_(Node::Break(Break{ position: None}),
2870            "test".to_string(),
2871            Node::Break(Break{position: None}))]
2872    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{ position: None}),
2873            "test".to_string(),
2874            Node::HorizontalRule(HorizontalRule{position: None}))]
2875    #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
2876           "updated".to_string(),
2877           Node::MdxFlowExpression(MdxFlowExpression{value: "updated".into(), position: None}))]
2878    #[case::mdx_text_expression(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
2879           "updated".to_string(),
2880           Node::MdxTextExpression(MdxTextExpression{value: "updated".into(), position: None}))]
2881    #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}),
2882           "updated".to_string(),
2883           Node::MdxJsEsm(MdxJsEsm{value: "updated".into(), position: None}))]
2884    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
2885            name: Some("div".to_string()),
2886            attributes: Vec::new(),
2887            children: vec!["test".to_string().into()],
2888            position: None
2889        }),
2890        "updated".to_string(),
2891        Node::MdxJsxFlowElement(MdxJsxFlowElement{
2892            name: Some("div".to_string()),
2893            attributes: Vec::new(),
2894            children: vec!["updated".to_string().into()],
2895            position: None
2896        }))]
2897    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
2898            name: Some("span".into()),
2899            attributes: Vec::new(),
2900            children: vec!["test".to_string().into()],
2901            position: None
2902        }),
2903        "updated".to_string(),
2904        Node::MdxJsxTextElement(MdxJsxTextElement{
2905            name: Some("span".into()),
2906            attributes: Vec::new(),
2907            children: vec!["updated".to_string().into()],
2908            position: None
2909        }))]
2910    #[case(Node::Math(Math{ value: "x^2".to_string(), position: None }),
2911           "test".to_string(),
2912           Node::Math(Math{ value: "test".to_string(), position: None }))]
2913    fn test_with_value(#[case] node: Node, #[case] input: String, #[case] expected: Node) {
2914        assert_eq!(node.with_value(input.as_str()), expected);
2915    }
2916
2917    #[rstest]
2918    #[case(Node::Blockquote(Blockquote{values: vec![
2919        Node::Text(Text{value: "first".to_string(), position: None}),
2920        Node::Text(Text{value: "second".to_string(), position: None})
2921    ], position: None}),
2922        "new",
2923        0,
2924        Node::Blockquote(Blockquote{values: vec![
2925            Node::Text(Text{value: "new".to_string(), position: None}),
2926            Node::Text(Text{value: "second".to_string(), position: None})
2927        ], position: None}))]
2928    #[case(Node::Blockquote(Blockquote{values: vec![
2929        Node::Text(Text{value: "first".to_string(), position: None}),
2930        Node::Text(Text{value: "second".to_string(), position: None})
2931    ], position: None}),
2932        "new",
2933        1,
2934        Node::Blockquote(Blockquote{values: vec![
2935            Node::Text(Text{value: "first".to_string(), position: None}),
2936            Node::Text(Text{value: "new".to_string(), position: None})
2937        ], position: None}))]
2938    #[case(Node::Delete(Delete{values: vec![
2939        Node::Text(Text{value: "first".to_string(), position: None}),
2940        Node::Text(Text{value: "second".to_string(), position: None})
2941    ], position: None}),
2942        "new",
2943        0,
2944        Node::Delete(Delete{values: vec![
2945            Node::Text(Text{value: "new".to_string(), position: None}),
2946            Node::Text(Text{value: "second".to_string(), position: None})
2947        ], position: None}))]
2948    #[case(Node::Emphasis(Emphasis{values: vec![
2949        Node::Text(Text{value: "first".to_string(), position: None}),
2950        Node::Text(Text{value: "second".to_string(), position: None})
2951    ], position: None}),
2952        "new",
2953        1,
2954        Node::Emphasis(Emphasis{values: vec![
2955            Node::Text(Text{value: "first".to_string(), position: None}),
2956            Node::Text(Text{value: "new".to_string(), position: None})
2957        ], position: None}))]
2958    #[case(Node::Strong(Strong{values: vec![
2959        Node::Text(Text{value: "first".to_string(), position: None}),
2960        Node::Text(Text{value: "second".to_string(), position: None})
2961    ], position: None}),
2962        "new",
2963        0,
2964        Node::Strong(Strong{values: vec![
2965            Node::Text(Text{value: "new".to_string(), position: None}),
2966            Node::Text(Text{value: "second".to_string(), position: None})
2967        ], position: None}))]
2968    #[case(Node::Heading(Heading{depth: 1, values: vec![
2969        Node::Text(Text{value: "first".to_string(), position: None}),
2970        Node::Text(Text{value: "second".to_string(), position: None})
2971    ], position: None}),
2972        "new",
2973        1,
2974        Node::Heading(Heading{depth: 1, values: vec![
2975            Node::Text(Text{value: "first".to_string(), position: None}),
2976            Node::Text(Text{value: "new".to_string(), position: None})
2977        ], position: None}))]
2978    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
2979        Node::Text(Text{value: "first".to_string(), position: None}),
2980        Node::Text(Text{value: "second".to_string(), position: None})
2981    ], position: None}),
2982        "new",
2983        0,
2984        Node::List(List{index: 0, level: 0, checked: None, ordered: false,  values: vec![
2985            Node::Text(Text{value: "new".to_string(), position: None}),
2986            Node::Text(Text{value: "second".to_string(), position: None})
2987        ], position: None}))]
2988    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
2989        Node::Text(Text{value: "first".to_string(), position: None}),
2990        Node::Text(Text{value: "second".to_string(), position: None})
2991    ], position: None}),
2992        "new",
2993        1,
2994        Node::TableCell(TableCell{column: 0, row: 0, values: vec![
2995            Node::Text(Text{value: "first".to_string(), position: None}),
2996            Node::Text(Text{value: "new".to_string(), position: None})
2997        ], position: None}))]
2998    #[case(Node::Text(Text{value: "plain text".to_string(), position: None}),
2999        "new",
3000        0,
3001        Node::Text(Text{value: "plain text".to_string(), position: None}))]
3002    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
3003        "new",
3004        0,
3005        Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}))]
3006    #[case(Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3007        Node::Text(Text{value: "first".to_string(), position: None})
3008    ], position: None}),
3009        "new",
3010        0,
3011        Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3012            Node::Text(Text{value: "new".to_string(), position: None})
3013        ], position: None}))]
3014    #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3015        Node::Text(Text{value: "first".to_string(), position: None})
3016    ], position: None}),
3017        "new",
3018        2,
3019        Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3020            Node::Text(Text{value: "first".to_string(), position: None})
3021        ], position: None}))]
3022    #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3023            Node::Text(Text{value: "first".to_string(), position: None}),
3024            Node::Text(Text{value: "second".to_string(), position: None})
3025        ], label: None, position: None}), "new", 0, Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3026            Node::Text(Text{value: "new".to_string(), position: None}),
3027            Node::Text(Text{value: "second".to_string(), position: None})
3028        ], label: None, position: None}))]
3029    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3030            name: Some("div".to_string()),
3031            attributes: Vec::new(),
3032            children: vec![
3033                Node::Text(Text{value: "first".to_string(), position: None}),
3034                Node::Text(Text{value: "second".to_string(), position: None})
3035            ],
3036            position: None
3037        }),
3038        "new",
3039        0,
3040        Node::MdxJsxFlowElement(MdxJsxFlowElement{
3041            name: Some("div".to_string()),
3042            attributes: Vec::new(),
3043            children: vec![
3044                Node::Text(Text{value: "new".to_string(), position: None}),
3045                Node::Text(Text{value: "second".to_string(), position: None})
3046            ],
3047            position: None
3048        }))]
3049    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3050            name: Some("div".to_string()),
3051            attributes: Vec::new(),
3052            children: vec![
3053                Node::Text(Text{value: "first".to_string(), position: None}),
3054                Node::Text(Text{value: "second".to_string(), position: None})
3055            ],
3056            position: None
3057        }),
3058        "new",
3059        1,
3060        Node::MdxJsxFlowElement(MdxJsxFlowElement{
3061            name: Some("div".to_string()),
3062            attributes: Vec::new(),
3063            children: vec![
3064                Node::Text(Text{value: "first".to_string(), position: None}),
3065                Node::Text(Text{value: "new".to_string(), position: None})
3066            ],
3067            position: None
3068        }))]
3069    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3070            name: Some("span".into()),
3071            attributes: Vec::new(),
3072            children: vec![
3073                Node::Text(Text{value: "first".to_string(), position: None}),
3074                Node::Text(Text{value: "second".to_string(), position: None})
3075            ],
3076            position: None
3077        }),
3078        "new",
3079        0,
3080        Node::MdxJsxTextElement(MdxJsxTextElement{
3081            name: Some("span".into()),
3082            attributes: Vec::new(),
3083            children: vec![
3084                Node::Text(Text{value: "new".to_string(), position: None}),
3085                Node::Text(Text{value: "second".to_string(), position: None})
3086            ],
3087            position: None
3088        }))]
3089    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3090            name: Some("span".into()),
3091            attributes: Vec::new(),
3092            children: vec![
3093                Node::Text(Text{value: "first".to_string(), position: None}),
3094                Node::Text(Text{value: "second".to_string(), position: None})
3095            ],
3096            position: None
3097        }),
3098        "new",
3099        1,
3100        Node::MdxJsxTextElement(MdxJsxTextElement{
3101            name: Some("span".into()),
3102            attributes: Vec::new(),
3103            children: vec![
3104                Node::Text(Text{value: "first".to_string(), position: None}),
3105                Node::Text(Text{value: "new".to_string(), position: None})
3106            ],
3107            position: None
3108        }))]
3109    fn test_with_children_value(#[case] node: Node, #[case] value: &str, #[case] index: usize, #[case] expected: Node) {
3110        assert_eq!(node.with_children_value(value, index), expected);
3111    }
3112
3113    #[rstest]
3114    #[case(Node::Text(Text{value: "test".to_string(), position: None }),
3115           "test".to_string())]
3116    #[case(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
3117           "    - test".to_string())]
3118    fn test_display(#[case] node: Node, #[case] expected: String) {
3119        assert_eq!(node.to_string_with(&RenderOptions::default()), expected);
3120    }
3121
3122    #[rstest]
3123    #[case(Node::Text(Text{value: "test".to_string(), position: None}), true)]
3124    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), false)]
3125    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), false)]
3126    fn test_is_text(#[case] node: Node, #[case] expected: bool) {
3127        assert_eq!(node.is_text(), expected);
3128    }
3129
3130    #[rstest]
3131    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), true)]
3132    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3133    fn test_is_inline_code(#[case] node: Node, #[case] expected: bool) {
3134        assert_eq!(node.is_inline_code(), expected);
3135    }
3136
3137    #[rstest]
3138    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), true)]
3139    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3140    fn test_is_inline_math(#[case] node: Node, #[case] expected: bool) {
3141        assert_eq!(node.is_inline_math(), expected);
3142    }
3143
3144    #[rstest]
3145    #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), true)]
3146    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3147    fn test_is_strong(#[case] node: Node, #[case] expected: bool) {
3148        assert_eq!(node.is_strong(), expected);
3149    }
3150
3151    #[rstest]
3152    #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), true)]
3153    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3154    fn test_is_delete(#[case] node: Node, #[case] expected: bool) {
3155        assert_eq!(node.is_delete(), expected);
3156    }
3157
3158    #[rstest]
3159    #[case(Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None}), true)]
3160    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3161    fn test_is_link(#[case] node: Node, #[case] expected: bool) {
3162        assert_eq!(node.is_link(), expected);
3163    }
3164
3165    #[rstest]
3166    #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), true)]
3167    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3168    fn test_is_link_ref(#[case] node: Node, #[case] expected: bool) {
3169        assert_eq!(node.is_link_ref(), expected);
3170    }
3171
3172    #[rstest]
3173    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), true)]
3174    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3175    fn test_is_image(#[case] node: Node, #[case] expected: bool) {
3176        assert_eq!(node.is_image(), expected);
3177    }
3178
3179    #[rstest]
3180    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), true)]
3181    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3182    fn test_is_image_ref(#[case] node: Node, #[case] expected: bool) {
3183        assert_eq!(node.is_image_ref(), expected);
3184    }
3185
3186    #[rstest]
3187    #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), true, Some("rust".into()))]
3188    #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), false, Some("python".into()))]
3189    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), true, None)]
3190    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), true, None)]
3191    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3192    fn test_is_code(#[case] node: Node, #[case] expected: bool, #[case] lang: Option<SmolStr>) {
3193        assert_eq!(node.is_code(lang), expected);
3194    }
3195
3196    #[rstest]
3197    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, Some(1))]
3198    #[case(Node::Heading(Heading{depth: 2, values: vec!["test".to_string().into()], position: None}), false, Some(1))]
3199    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, None)]
3200    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3201    fn test_is_heading(#[case] node: Node, #[case] expected: bool, #[case] depth: Option<u8>) {
3202        assert_eq!(node.is_heading(depth), expected);
3203    }
3204
3205    #[rstest]
3206    #[case(Node::HorizontalRule(HorizontalRule{position: None}), true)]
3207    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3208    fn test_is_horizontal_rule(#[case] node: Node, #[case] expected: bool) {
3209        assert_eq!(node.is_horizontal_rule(), expected);
3210    }
3211
3212    #[rstest]
3213    #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), true)]
3214    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3215    fn test_is_blockquote(#[case] node: Node, #[case] expected: bool) {
3216        assert_eq!(node.is_blockquote(), expected);
3217    }
3218
3219    #[rstest]
3220    #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), true)]
3221    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3222    fn test_is_html(#[case] node: Node, #[case] expected: bool) {
3223        assert_eq!(node.is_html(), expected);
3224    }
3225
3226    #[rstest]
3227    #[case(Node::node_values(
3228           &Node::Strong(Strong{values: vec!["test".to_string().into()], position: None})),
3229           vec!["test".to_string().into()])]
3230    #[case(Node::node_values(
3231           &Node::Text(Text{value: "test".to_string(), position: None})),
3232           vec!["test".to_string().into()])]
3233    #[case(Node::node_values(
3234           &Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None})),
3235           vec!["test".to_string().into()])]
3236    #[case(Node::node_values(
3237           &Node::Delete(Delete{values: vec!["test".to_string().into()], position: None})),
3238           vec!["test".to_string().into()])]
3239    #[case(Node::node_values(
3240           &Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None})),
3241           vec!["test".to_string().into()])]
3242    #[case(Node::node_values(
3243           &Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None})),
3244           vec!["test".to_string().into()])]
3245    #[case(Node::node_values(
3246           &Node::List(List{values: vec!["test".to_string().into()], ordered: false, level: 1, checked: Some(false), index: 0, position: None})),
3247           vec!["test".to_string().into()])]
3248    fn test_node_value(#[case] actual: Vec<Node>, #[case] expected: Vec<Node>) {
3249        assert_eq!(actual, expected);
3250    }
3251
3252    #[rstest]
3253    #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None}), true)]
3254    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3255    fn test_is_footnote(#[case] node: Node, #[case] expected: bool) {
3256        assert_eq!(node.is_footnote(), expected);
3257    }
3258
3259    #[rstest]
3260    #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), true)]
3261    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3262    fn test_is_footnote_ref(#[case] node: Node, #[case] expected: bool) {
3263        assert_eq!(node.is_footnote_ref(), expected);
3264    }
3265
3266    #[rstest]
3267    #[case(Node::Math(Math{value: "x^2".to_string(), position: None}), true)]
3268    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3269    fn test_is_math(#[case] node: Node, #[case] expected: bool) {
3270        assert_eq!(node.is_math(), expected);
3271    }
3272
3273    #[rstest]
3274    #[case(Node::Break(Break{position: None}), true)]
3275    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3276    fn test_is_break(#[case] node: Node, #[case] expected: bool) {
3277        assert_eq!(node.is_break(), expected);
3278    }
3279
3280    #[rstest]
3281    #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), true)]
3282    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3283    fn test_is_yaml(#[case] node: Node, #[case] expected: bool) {
3284        assert_eq!(node.is_yaml(), expected);
3285    }
3286
3287    #[rstest]
3288    #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), true)]
3289    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3290    fn test_is_toml(#[case] node: Node, #[case] expected: bool) {
3291        assert_eq!(node.is_toml(), expected);
3292    }
3293
3294    #[rstest]
3295    #[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)]
3296    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3297    fn test_is_definition(#[case] node: Node, #[case] expected: bool) {
3298        assert_eq!(node.is_definition(), expected);
3299    }
3300
3301    #[rstest]
3302    #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), true)]
3303    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3304    fn test_is_emphasis(#[case] node: Node, #[case] expected: bool) {
3305        assert_eq!(node.is_emphasis(), expected);
3306    }
3307
3308    #[rstest]
3309    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), true)]
3310    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3311    fn test_is_mdx_flow_expression(#[case] node: Node, #[case] expected: bool) {
3312        assert_eq!(node.is_mdx_flow_expression(), expected);
3313    }
3314
3315    #[rstest]
3316    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), true)]
3317    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3318    fn test_is_mdx_text_expression(#[case] node: Node, #[case] expected: bool) {
3319        assert_eq!(node.is_mdx_text_expression(), expected);
3320    }
3321
3322    #[rstest]
3323    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
3324    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3325    fn test_is_mdx_jsx_flow_element(#[case] node: Node, #[case] expected: bool) {
3326        assert_eq!(node.is_mdx_jsx_flow_element(), expected);
3327    }
3328
3329    #[rstest]
3330    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
3331    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3332    fn test_is_mdx_jsx_text_element(#[case] node: Node, #[case] expected: bool) {
3333        assert_eq!(node.is_mdx_jsx_text_element(), expected);
3334    }
3335
3336    #[rstest]
3337    #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), true)]
3338    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3339    fn test_is_msx_js_esm(#[case] node: Node, #[case] expected: bool) {
3340        assert_eq!(node.is_mdx_js_esm(), expected);
3341    }
3342
3343    #[rstest]
3344    #[case::text(Node::Text(Text{value: "test".to_string(), position: None }), RenderOptions::default(), "test")]
3345    #[case::list(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "    - test")]
3346    #[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")]
3347    #[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")]
3348    #[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")]
3349    #[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")]
3350    #[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")]
3351    #[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|")]
3352    #[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|")]
3353    #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
3354    #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
3355    #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right, TableAlignKind::Center, TableAlignKind::None], position: None}), RenderOptions::default(), "|:---|---:|:---:|---|")]
3356    #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "> test")]
3357    #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test\ntest2".to_string().into()], position: None}), RenderOptions::default(), "> test\n> test2")]
3358    #[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```")]
3359    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), RenderOptions::default(), "```\ncode\n```")]
3360    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), RenderOptions::default(), "    code")]
3361    #[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```")]
3362    #[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")]
3363    #[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\"")]
3364    #[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]: ")]
3365    #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "~~test~~")]
3366    #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "*test*")]
3367    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![attr_keys::LABEL.to_string().into()], position: None}), RenderOptions::default(), "[^id]: label")]
3368    #[case::footnote_ref(Node::FootnoteRef(FootnoteRef{ident: attr_keys::LABEL.to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[^label]")]
3369    #[case::heading(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "# test")]
3370    #[case::heading(Node::Heading(Heading{depth: 3, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "### test")]
3371    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), RenderOptions::default(), "<div>test</div>")]
3372    #[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)")]
3373    #[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\")")]
3374    #[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]")]
3375    #[case::image_ref(Node::ImageRef(ImageRef{alt: "id".to_string(), ident: "id".to_string(), label: Some("id".to_string()), position: None}), RenderOptions::default(), "![id]")]
3376    #[case::code_inline(Node::CodeInline(CodeInline{value: "code".into(), position: None}), RenderOptions::default(), "`code`")]
3377    #[case::math_inline(Node::MathInline(MathInline{value: "x^2".into(), position: None}), RenderOptions::default(), "$x^2$")]
3378    #[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\")")]
3379    #[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]()")]
3380    #[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)")]
3381    #[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]")]
3382    #[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]")]
3383    #[case::math(Node::Math(Math{value: "x^2".to_string(), position: None}), RenderOptions::default(), "$$\nx^2\n$$")]
3384    #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "**test**")]
3385    #[case::yaml(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), RenderOptions::default(), "---\nkey: value\n---")]
3386    #[case::toml(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), RenderOptions::default(), "+++\nkey = \"value\"\n+++")]
3387    #[case::break_(Node::Break(Break{position: None}), RenderOptions::default(), "\\")]
3388    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), RenderOptions::default(), "---")]
3389    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3390        name: Some("div".to_string()),
3391        attributes: vec![
3392            MdxAttributeContent::Property(MdxJsxAttribute {
3393                name: "className".into(),
3394                value: Some(MdxAttributeValue::Literal("container".into()))
3395            })
3396        ],
3397        children: vec![
3398            "content".to_string().into()
3399        ],
3400        position: None
3401    }), RenderOptions::default(), "<div className=\"container\">content</div>")]
3402    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3403        name: Some("div".to_string()),
3404        attributes: vec![
3405            MdxAttributeContent::Property(MdxJsxAttribute {
3406                name: "className".into(),
3407                value: Some(MdxAttributeValue::Literal("container".into()))
3408            })
3409        ],
3410        children: Vec::new(),
3411        position: None
3412    }), RenderOptions::default(), "<div className=\"container\" />")]
3413    #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3414        name: Some("div".to_string()),
3415        attributes: Vec::new(),
3416        children: Vec::new(),
3417        position: None
3418    }), RenderOptions::default(), "<div />")]
3419    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3420        name: Some("span".into()),
3421        attributes: vec![
3422            MdxAttributeContent::Expression("...props".into())
3423        ],
3424        children: vec![
3425            "inline".to_string().into()
3426        ],
3427        position: None
3428    }), RenderOptions::default(), "<span {...props}>inline</span>")]
3429    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3430        name: Some("span".into()),
3431        attributes: vec![
3432            MdxAttributeContent::Expression("...props".into())
3433        ],
3434        children: vec![
3435        ],
3436        position: None
3437    }), RenderOptions::default(), "<span {...props} />")]
3438    #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3439        name: Some("span".into()),
3440        attributes: vec![
3441        ],
3442        children: vec![
3443        ],
3444        position: None
3445    }), RenderOptions::default(), "<span />")]
3446    #[case(Node::MdxTextExpression(MdxTextExpression{
3447        value: "count + 1".into(),
3448        position: None,
3449    }), RenderOptions::default(), "{count + 1}")]
3450    #[case(Node::MdxJsEsm(MdxJsEsm{
3451        value: "import React from 'react'".into(),
3452        position: None,
3453    }), RenderOptions::default(), "import React from 'react'")]
3454    #[case::fragment_empty(Node::Fragment(Fragment{values: vec![]}), RenderOptions::default(), "")]
3455    #[case::fragment_single(Node::Fragment(Fragment{values: vec![
3456        Node::Text(Text{value: "hello".to_string(), position: None})
3457    ]}), RenderOptions::default(), "hello")]
3458    #[case::fragment_multiple(Node::Fragment(Fragment{values: vec![
3459        Node::Text(Text{value: "hello".to_string(), position: None}),
3460        Node::Text(Text{value: "world".to_string(), position: None})
3461    ]}), RenderOptions::default(), "hello\nworld")]
3462    #[case::fragment_filters_empty(Node::Fragment(Fragment{values: vec![
3463        Node::Text(Text{value: "hello".to_string(), position: None}),
3464        Node::Empty,
3465        Node::Text(Text{value: "world".to_string(), position: None})
3466    ]}), RenderOptions::default(), "hello\nworld")]
3467    #[case::fragment_all_empty(Node::Fragment(Fragment{values: vec![
3468        Node::Empty,
3469        Node::Empty,
3470    ]}), RenderOptions::default(), "")]
3471    fn test_to_string_with(#[case] node: Node, #[case] options: RenderOptions, #[case] expected: &str) {
3472        assert_eq!(node.to_string_with(&options), expected);
3473    }
3474
3475    #[test]
3476    fn test_node_partial_ord() {
3477        let node1 = Node::Text(Text {
3478            value: "test1".to_string(),
3479            position: Some(Position {
3480                start: Point { line: 1, column: 1 },
3481                end: Point { line: 1, column: 5 },
3482            }),
3483        });
3484
3485        let node2 = Node::Text(Text {
3486            value: "test2".to_string(),
3487            position: Some(Position {
3488                start: Point { line: 1, column: 6 },
3489                end: Point { line: 1, column: 10 },
3490            }),
3491        });
3492
3493        let node3 = Node::Text(Text {
3494            value: "test3".to_string(),
3495            position: Some(Position {
3496                start: Point { line: 2, column: 1 },
3497                end: Point { line: 2, column: 5 },
3498            }),
3499        });
3500
3501        assert_eq!(node1.partial_cmp(&node2), Some(std::cmp::Ordering::Less));
3502        assert_eq!(node2.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3503
3504        assert_eq!(node1.partial_cmp(&node3), Some(std::cmp::Ordering::Less));
3505        assert_eq!(node3.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3506
3507        let node4 = Node::Text(Text {
3508            value: "test4".to_string(),
3509            position: None,
3510        });
3511
3512        assert_eq!(node1.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
3513        assert_eq!(node4.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
3514
3515        let node5 = Node::Text(Text {
3516            value: "test5".to_string(),
3517            position: None,
3518        });
3519
3520        assert_eq!(node4.partial_cmp(&node5), Some(std::cmp::Ordering::Equal));
3521
3522        let node6 = Node::Code(Code {
3523            value: "code".to_string(),
3524            lang: None,
3525            fence: true,
3526            meta: None,
3527            position: None,
3528        });
3529
3530        assert_eq!(node6.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
3531        assert_eq!(node4.partial_cmp(&node6), Some(std::cmp::Ordering::Greater));
3532    }
3533
3534    #[rstest]
3535    #[case(Node::Blockquote(Blockquote{values: Vec::new(), position: None}), "blockquote")]
3536    #[case(Node::Break(Break{position: None}), "break")]
3537    #[case(Node::Definition(Definition{ident: "".to_string(), url: Url::new("".to_string()), title: None, label: None, position: None}), "definition")]
3538    #[case(Node::Delete(Delete{values: Vec::new(), position: None}), "delete")]
3539    #[case(Node::Heading(Heading{depth: 1, values: Vec::new(), position: None}), "h1")]
3540    #[case(Node::Heading(Heading{depth: 2, values: Vec::new(), position: None}), "h2")]
3541    #[case(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "h3")]
3542    #[case(Node::Heading(Heading{depth: 4, values: Vec::new(), position: None}), "h4")]
3543    #[case(Node::Heading(Heading{depth: 5, values: Vec::new(), position: None}), "h5")]
3544    #[case(Node::Heading(Heading{depth: 6, values: Vec::new(), position: None}), "h6")]
3545    #[case(Node::Heading(Heading{depth: 7, values: Vec::new(), position: None}), "h")]
3546    #[case(Node::Emphasis(Emphasis{values: Vec::new(), position: None}), "emphasis")]
3547    #[case(Node::Footnote(Footnote{ident: "".to_string(), values: Vec::new(), position: None}), "footnote")]
3548    #[case(Node::FootnoteRef(FootnoteRef{ident: "".to_string(), label: None, position: None}), "footnoteref")]
3549    #[case(Node::Html(Html{value: "".to_string(), position: None}), "html")]
3550    #[case(Node::Yaml(Yaml{value: "".to_string(), position: None}), "yaml")]
3551    #[case(Node::Toml(Toml{value: "".to_string(), position: None}), "toml")]
3552    #[case(Node::Image(Image{alt: "".to_string(), url: "".to_string(), title: None, position: None}), "image")]
3553    #[case(Node::ImageRef(ImageRef{alt: "".to_string(), ident: "".to_string(), label: None, position: None}), "image_ref")]
3554    #[case(Node::CodeInline(CodeInline{value: "".into(), position: None}), "code_inline")]
3555    #[case(Node::MathInline(MathInline{value: "".into(), position: None}), "math_inline")]
3556    #[case(Node::Link(Link{url: Url::new("".to_string()), title: None, values: Vec::new(), position: None}), "link")]
3557    #[case(Node::LinkRef(LinkRef{ident: "".to_string(), values: Vec::new(), label: None, position: None}), "link_ref")]
3558    #[case(Node::Math(Math{value: "".to_string(), position: None}), "math")]
3559    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: Vec::new(), position: None}), "list")]
3560    #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "table_align")]
3561    #[case(Node::TableRow(TableRow{values: Vec::new(), position: None}), "table_row")]
3562    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}), "table_cell")]
3563    #[case(Node::Code(Code{value: "".to_string(), lang: None, fence: true, meta: None, position: None}), "code")]
3564    #[case(Node::Strong(Strong{values: Vec::new(), position: None}), "strong")]
3565    #[case(Node::HorizontalRule(HorizontalRule{position: None}), "Horizontal_rule")]
3566    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "".into(), position: None}), "mdx_flow_expression")]
3567    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_flow_element")]
3568    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_text_element")]
3569    #[case(Node::MdxTextExpression(MdxTextExpression{value: "".into(), position: None}), "mdx_text_expression")]
3570    #[case(Node::MdxJsEsm(MdxJsEsm{value: "".into(), position: None}), "mdx_js_esm")]
3571    #[case(Node::Text(Text{value: "".to_string(), position: None}), "text")]
3572    fn test_name(#[case] node: Node, #[case] expected: &str) {
3573        assert_eq!(node.name(), expected);
3574    }
3575
3576    #[rstest]
3577    #[case(Node::Text(Text{value: "test".to_string(), position: None}), "test")]
3578    #[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")]
3579    #[case(Node::Blockquote(Blockquote{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3580    #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3581    #[case(Node::Heading(Heading{depth: 1, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3582    #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3583    #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3584    #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), "test")]
3585    #[case(Node::Html(Html{value: "test".to_string(), position: None}), "test")]
3586    #[case(Node::Yaml(Yaml{value: "test".to_string(), position: None}), "test")]
3587    #[case(Node::Toml(Toml{value: "test".to_string(), position: None}), "test")]
3588    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), "test")]
3589    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), "test")]
3590    #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), "test")]
3591    #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), "test")]
3592    #[case(Node::Link(Link{url: Url::new("test".to_string()), title: None, values: Vec::new(), position: None}), "test")]
3593    #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), "test")]
3594    #[case(Node::Math(Math{value: "test".to_string(), position: None}), "test")]
3595    #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}), "test")]
3596    #[case(Node::Strong(Strong{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3597    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
3598    #[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")]
3599    #[case(Node::Break(Break{position: None}), "")]
3600    #[case(Node::HorizontalRule(HorizontalRule{position: None}), "")]
3601    #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "")]
3602    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), "test")]
3603    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), "test")]
3604    #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), "test")]
3605    #[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")]
3606    #[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)]
3607    #[case(Node::Fragment(Fragment {values: vec![Node::Text(Text{value: "test".to_string(), position: None})]}), "test")]
3608    fn test_value(#[case] node: Node, #[case] expected: &str) {
3609        assert_eq!(node.value(), expected);
3610    }
3611
3612    #[rstest]
3613    #[case(Node::Text(Text{value: "test".to_string(), position: None}), None)]
3614    #[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}}))]
3615    #[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}}))]
3616    #[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}}))]
3617    #[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}}))]
3618    #[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}}))]
3619    #[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}}))]
3620    #[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}}))]
3621    #[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}}))]
3622    #[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}}))]
3623    #[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}}))]
3624    #[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}}))]
3625    #[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}}))]
3626    #[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}}))]
3627    #[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}}))]
3628    #[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}}))]
3629    #[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}}))]
3630    #[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}}))]
3631    #[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}}))]
3632    #[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}}))]
3633    #[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}}))]
3634    #[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}}))]
3635    #[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}}))]
3636    #[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}}))]
3637    #[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}}))]
3638    #[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}}))]
3639    #[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}}))]
3640    #[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}}))]
3641    #[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}}))]
3642    #[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}}))]
3643    #[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}}))]
3644    #[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}}))]
3645    #[case(Node::Fragment(Fragment{values: Vec::new()}), None)]
3646    #[case(Node::Fragment(Fragment{values: vec![
3647        Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
3648        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
3649    ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}}))]
3650    #[case(Node::Fragment(Fragment{values: vec![
3651        Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
3652        Node::Text(Text{value: "test2".to_string(), position: None})
3653    ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
3654    #[case(Node::Fragment(Fragment{values: vec![
3655        Node::Text(Text{value: "test".to_string(), position: None}),
3656        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
3657    ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
3658    #[case(Node::Fragment(Fragment{values: vec![
3659        Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})}),
3660        Node::Text(Text{value: "test".to_string(), position: None})
3661    ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
3662    #[case(Node::Fragment(Fragment{values: vec![
3663        Node::Text(Text{value: "test".to_string(), position: None}),
3664        Node::Text(Text{value: "test2".to_string(), position: None})
3665    ]}), None)]
3666    #[case(Node::Empty, None)]
3667    fn test_position(#[case] node: Node, #[case] expected: Option<Position>) {
3668        assert_eq!(node.position(), expected);
3669    }
3670
3671    #[rstest]
3672    #[case(Node::Blockquote(Blockquote{values: vec![
3673        Node::Text(Text{value: "first".to_string(), position: None}),
3674        Node::Text(Text{value: "second".to_string(), position: None})
3675    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3676    #[case(Node::Blockquote(Blockquote{values: vec![
3677        Node::Text(Text{value: "first".to_string(), position: None}),
3678        Node::Text(Text{value: "second".to_string(), position: None})
3679    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3680    #[case(Node::Blockquote(Blockquote{values: vec![
3681        Node::Text(Text{value: "first".to_string(), position: None})
3682    ], position: None}), 1, None)]
3683    #[case(Node::Delete(Delete{values: vec![
3684        Node::Text(Text{value: "first".to_string(), position: None}),
3685        Node::Text(Text{value: "second".to_string(), position: None})
3686    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3687    #[case(Node::Emphasis(Emphasis{values: vec![
3688        Node::Text(Text{value: "first".to_string(), position: None}),
3689        Node::Text(Text{value: "second".to_string(), position: None})
3690    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3691    #[case(Node::Strong(Strong{values: vec![
3692        Node::Text(Text{value: "first".to_string(), position: None})
3693    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3694    #[case(Node::Heading(Heading{depth: 1, values: vec![
3695        Node::Text(Text{value: "first".to_string(), position: None}),
3696        Node::Text(Text{value: "second".to_string(), position: None})
3697    ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
3698    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3699        Node::Text(Text{value: "first".to_string(), position: None}),
3700        Node::Text(Text{value: "second".to_string(), position: None})
3701    ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
3702    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3703        Node::Text(Text{value: "cell content".to_string(), position: None})
3704    ], position: None}), 0, Some(Node::Text(Text{value: "cell content".to_string(), position: None})))]
3705    #[case(Node::TableRow(TableRow{values: vec![
3706        Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}),
3707        Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})
3708    ], position: None}), 1, Some(Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})))]
3709    #[case(Node::Text(Text{value: "plain text".to_string(), position: None}), 0, None)]
3710    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), 0, None)]
3711    #[case(Node::Html(Html{value: "<div>".to_string(), position: None}), 0, None)]
3712    fn test_find_at_index(#[case] node: Node, #[case] index: usize, #[case] expected: Option<Node>) {
3713        assert_eq!(node.find_at_index(index), expected);
3714    }
3715
3716    #[rstest]
3717    #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}),
3718           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3719    #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}),
3720           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3721    #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}),
3722           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3723    #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}),
3724           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3725    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
3726           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3727    #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}),
3728           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3729    #[case(Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec!["test".to_string().into()], position: None}),
3730           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3731    #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec!["test".to_string().into()], label: None, position: None}),
3732           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3733    #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec!["test".to_string().into()], position: None}),
3734           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3735    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}),
3736           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3737    #[case(Node::TableRow(TableRow{values: vec!["test".to_string().into()], position: None}),
3738           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3739    #[case(Node::Fragment(Fragment{values: vec!["test".to_string().into()]}),
3740           Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
3741    #[case(Node::Text(Text{value: "test".to_string(), position: None}),
3742           Node::Empty)]
3743    #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}),
3744           Node::Empty)]
3745    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
3746           Node::Empty)]
3747    #[case(Node::Empty, Node::Empty)]
3748    fn test_to_fragment(#[case] node: Node, #[case] expected: Node) {
3749        assert_eq!(node.to_fragment(), expected);
3750    }
3751
3752    #[rstest]
3753    #[case(
3754        &mut Node::Blockquote(Blockquote{values: vec![
3755            Node::Text(Text{value: "old".to_string(), position: None})
3756        ], position: None}),
3757        Node::Fragment(Fragment{values: vec![
3758            Node::Text(Text{value: "new".to_string(), position: None})
3759        ]}),
3760        Node::Blockquote(Blockquote{values: vec![
3761            Node::Text(Text{value: "new".to_string(), position: None})
3762        ], position: None})
3763    )]
3764    #[case(
3765        &mut Node::Delete(Delete{values: vec![
3766            Node::Text(Text{value: "old".to_string(), position: None})
3767        ], position: None}),
3768        Node::Fragment(Fragment{values: vec![
3769            Node::Text(Text{value: "new".to_string(), position: None})
3770        ]}),
3771        Node::Delete(Delete{values: vec![
3772            Node::Text(Text{value: "new".to_string(), position: None})
3773        ], position: None})
3774    )]
3775    #[case(
3776        &mut Node::Emphasis(Emphasis{values: vec![
3777            Node::Text(Text{value: "old".to_string(), position: None})
3778        ], position: None}),
3779        Node::Fragment(Fragment{values: vec![
3780            Node::Text(Text{value: "new".to_string(), position: None})
3781        ]}),
3782        Node::Emphasis(Emphasis{values: vec![
3783            Node::Text(Text{value: "new".to_string(), position: None})
3784        ], position: None})
3785    )]
3786    #[case(
3787        &mut Node::Strong(Strong{values: vec![
3788            Node::Text(Text{value: "old".to_string(), position: None})
3789        ], position: None}),
3790        Node::Fragment(Fragment{values: vec![
3791            Node::Text(Text{value: "new".to_string(), position: None})
3792        ]}),
3793        Node::Strong(Strong{values: vec![
3794            Node::Text(Text{value: "new".to_string(), position: None})
3795        ], position: None})
3796    )]
3797    #[case(
3798        &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3799            Node::Text(Text{value: "old".to_string(), position: None})
3800        ], position: None}),
3801        Node::Fragment(Fragment{values: vec![
3802            Node::Text(Text{value: "new".to_string(), position: None})
3803        ]}),
3804        Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3805            Node::Text(Text{value: "new".to_string(), position: None})
3806        ], position: None})
3807    )]
3808    #[case(
3809        &mut Node::Heading(Heading{depth: 1, values: vec![
3810            Node::Text(Text{value: "old".to_string(), position: None})
3811        ], position: None}),
3812        Node::Fragment(Fragment{values: vec![
3813            Node::Text(Text{value: "new".to_string(), position: None})
3814        ]}),
3815        Node::Heading(Heading{depth: 1, values: vec![
3816            Node::Text(Text{value: "new".to_string(), position: None})
3817        ], position: None})
3818    )]
3819    #[case(
3820        &mut Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
3821            Node::Text(Text{value: "old".to_string(), position: None})
3822        ], position: None}),
3823        Node::Fragment(Fragment{values: vec![
3824            Node::Text(Text{value: "new".to_string(), position: None})
3825        ]}),
3826        Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
3827            Node::Text(Text{value: "new".to_string(), position: None})
3828        ], position: None})
3829    )]
3830    #[case(
3831        &mut Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3832            Node::Text(Text{value: "old".to_string(), position: None})
3833        ], label: None, position: None}),
3834        Node::Fragment(Fragment{values: vec![
3835            Node::Text(Text{value: "new".to_string(), position: None})
3836        ]}),
3837        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3838            Node::Text(Text{value: "new".to_string(), position: None})
3839        ], label: None, position: None})
3840    )]
3841    #[case(
3842        &mut Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
3843            Node::Text(Text{value: "old".to_string(), position: None})
3844        ], position: None}),
3845        Node::Fragment(Fragment{values: vec![
3846            Node::Text(Text{value: "new".to_string(), position: None})
3847        ]}),
3848        Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
3849            Node::Text(Text{value: "new".to_string(), position: None})
3850        ], position: None})
3851    )]
3852    #[case(
3853        &mut Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3854            Node::Text(Text{value: "old".to_string(), position: None})
3855        ], position: None}),
3856        Node::Fragment(Fragment{values: vec![
3857            Node::Text(Text{value: "new".to_string(), position: None})
3858        ]}),
3859        Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3860            Node::Text(Text{value: "new".to_string(), position: None})
3861        ], position: None})
3862    )]
3863    #[case(
3864        &mut Node::TableRow(TableRow{values: vec![
3865            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3866                Node::Text(Text{value: "old".to_string(), position: None})
3867            ], position: None})
3868        ], position: None}),
3869        Node::Fragment(Fragment{values: vec![
3870            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3871                Node::Text(Text{value: "new".to_string(), position: None})
3872            ], position: None})
3873        ]}),
3874        Node::TableRow(TableRow{values: vec![
3875            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3876                Node::Text(Text{value: "new".to_string(), position: None})
3877            ], position: None})
3878        ], position: None})
3879    )]
3880    #[case(
3881        &mut Node::Text(Text{value: "old".to_string(), position: None}),
3882        Node::Fragment(Fragment{values: vec![
3883            Node::Text(Text{value: "new".to_string(), position: None})
3884        ]}),
3885        Node::Text(Text{value: "old".to_string(), position: None})
3886    )]
3887    #[case(
3888        &mut Node::Blockquote(Blockquote{values: vec![
3889            Node::Text(Text{value: "text1".to_string(), position: None}),
3890            Node::Text(Text{value: "text2".to_string(), position: None})
3891        ], position: None}),
3892        Node::Fragment(Fragment{values: vec![
3893            Node::Text(Text{value: "new1".to_string(), position: None}),
3894            Node::Text(Text{value: "new2".to_string(), position: None})
3895        ]}),
3896        Node::Blockquote(Blockquote{values: vec![
3897            Node::Text(Text{value: "new1".to_string(), position: None}),
3898            Node::Text(Text{value: "new2".to_string(), position: None})
3899        ], position: None})
3900    )]
3901    #[case(
3902        &mut Node::Strong(Strong{values: vec![
3903            Node::Text(Text{value: "text1".to_string(), position: None}),
3904            Node::Text(Text{value: "text2".to_string(), position: None})
3905        ], position: None}),
3906        Node::Fragment(Fragment{values: vec![
3907            Node::Empty,
3908            Node::Text(Text{value: "new2".to_string(), position: None})
3909        ]}),
3910        Node::Strong(Strong{values: vec![
3911            Node::Text(Text{value: "text1".to_string(), position: None}),
3912            Node::Text(Text{value: "new2".to_string(), position: None})
3913        ], position: None})
3914    )]
3915    #[case(
3916        &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3917            Node::Text(Text{value: "text1".to_string(), position: None}),
3918            Node::Text(Text{value: "text2".to_string(), position: None})
3919        ], position: None}),
3920        Node::Fragment(Fragment{values: vec![
3921            Node::Text(Text{value: "new1".to_string(), position: None}),
3922            Node::Fragment(Fragment{values: Vec::new()})
3923        ]}),
3924        Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3925            Node::Text(Text{value: "new1".to_string(), position: None}),
3926            Node::Text(Text{value: "text2".to_string(), position: None})
3927        ], position: None})
3928    )]
3929    fn test_apply_fragment(#[case] node: &mut Node, #[case] fragment: Node, #[case] expected: Node) {
3930        node.apply_fragment(fragment);
3931        assert_eq!(*node, expected);
3932    }
3933
3934    #[rstest]
3935    #[case(Node::Text(Text{value: "test".to_string(), position: None}),
3936       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3937       Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3938    #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
3939       Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
3940       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}})}))]
3941    #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![], position: None}),
3942       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3943       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}})}))]
3944    #[case(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
3945       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3946       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}})}))]
3947    #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3948        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3949        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}})}))]
3950    #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3951        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3952        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}})}))]
3953    #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
3954        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3955        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}})}))]
3956    #[case(Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
3957        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3958        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}})}))]
3959    #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}),
3960        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
3961        Node::Html(Html{value: "<div>test</div>".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
3962    #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}),
3963        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
3964        Node::Yaml(Yaml{value: "key: value".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
3965    #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}),
3966        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
3967        Node::Toml(Toml{value: "key = \"value\"".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
3968    #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
3969        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 12}},
3970        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}})}))]
3971    #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
3972        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3973        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}})}))]
3974    #[case(Node::CodeInline(CodeInline{value: "code".into(), position: None}),
3975        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
3976        Node::CodeInline(CodeInline{value: "code".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
3977    #[case(Node::MathInline(MathInline{value: "x^2".into(), position: None}),
3978        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
3979        Node::MathInline(MathInline{value: "x^2".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
3980    #[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}),
3981        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3982        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}})}))]
3983    #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![Node::Text(Text{value: "text".to_string(), position: None})], label: None, position: None}),
3984        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
3985        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}})}))]
3986    #[case(Node::Math(Math{value: "x^2".to_string(), position: None}),
3987        Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
3988        Node::Math(Math{value: "x^2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}})}))]
3989    #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "cell".to_string(), position: None})], position: None}),
3990        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 6}},
3991        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}})}))]
3992    #[case(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
3993        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
3994        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
3995    #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
3996        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
3997        Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
3998    #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
3999        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4000        Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4001    #[case(Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: None}),
4002        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}},
4003        Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}})}))]
4004    #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: vec![Node::Text(Text{value: "text".to_string(), position: None})], position: None}),
4005        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4006        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}})}))]
4007    #[case(Node::Break(Break{position: None}),
4008        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}},
4009        Node::Break(Break{position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}})}))]
4010    #[case(Node::Empty,
4011       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4012       Node::Empty)]
4013    #[case(Node::Fragment(Fragment{values: vec![
4014           Node::Text(Text{value: "test1".to_string(), position: None}),
4015           Node::Text(Text{value: "test2".to_string(), position: None})
4016       ]}),
4017       Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4018       Node::Fragment(Fragment{values: vec![
4019           Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}),
4020           Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4021       ]}))]
4022    #[case(Node::Blockquote(Blockquote{values: vec![
4023        Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4024        Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4025        Node::Blockquote(Blockquote{values: vec![
4026            Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4027        ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4028    #[case(Node::Heading(Heading{depth: 1, values: vec![
4029            Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4030            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4031            Node::Heading(Heading{depth: 1, values: vec![
4032                Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4033            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4034    #[case(Node::Strong(Strong{values: vec![
4035            Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4036            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4037            Node::Strong(Strong{values: vec![
4038                Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4039            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4040    #[case(Node::TableRow(TableRow{values: vec![
4041            Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4042                Node::Text(Text{value: "cell".to_string(), position: None})
4043            ], position: None})
4044        ], position: None}),
4045            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4046            Node::TableRow(TableRow{values: vec![
4047                Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4048                    Node::Text(Text{value: "cell".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4049                ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4050            ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
4051    #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4052            name: Some("div".to_string()),
4053            attributes: Vec::new(),
4054            children: vec![Node::Text(Text{value: "content".to_string(), position: None})],
4055            position: None
4056        }),
4057            Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4058            Node::MdxJsxFlowElement(MdxJsxFlowElement{
4059                name: Some("div".to_string()),
4060                attributes: Vec::new(),
4061                children: vec![Node::Text(Text{value: "content".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})})],
4062                position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})
4063            }))]
4064    fn test_set_position(#[case] mut node: Node, #[case] position: Position, #[case] expected: Node) {
4065        node.set_position(Some(position));
4066        assert_eq!(node, expected);
4067    }
4068
4069    #[rstest]
4070    #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4071    #[case(Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4072    #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4073    fn test_is_list(#[case] node: Node, #[case] expected: bool) {
4074        assert_eq!(node.is_list(), expected);
4075    }
4076
4077    #[rstest]
4078    #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::None, ..Default::default()}, "https://example.com")]
4079    #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::Angle, ..Default::default()}, "<https://example.com>")]
4080    #[case(Url::new("".to_string()), RenderOptions::default(), "")]
4081    fn test_url_to_string_with(#[case] url: Url, #[case] options: RenderOptions, #[case] expected: &str) {
4082        assert_eq!(url.to_string_with(&options), expected);
4083    }
4084
4085    #[rstest]
4086    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions::default(), "\"title\"")]
4087    #[case(Title::new(r#"title with "quotes""#.to_string()), RenderOptions::default(), r#""title with "quotes"""#)]
4088    #[case(Title::new("title with spaces".to_string()), RenderOptions::default(), "\"title with spaces\"")]
4089    #[case(Title::new("".to_string()), RenderOptions::default(), "\"\"")]
4090    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Single, ..Default::default()}, "'title'")]
4091    #[case(Title::new("title with 'quotes'".to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Double, ..Default::default()}, "\"title with 'quotes'\"")]
4092    #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Paren, ..Default::default()}, "(title)")]
4093    fn test_title_to_string_with(#[case] title: Title, #[case] options: RenderOptions, #[case] expected: &str) {
4094        assert_eq!(title.to_string_with(&options), expected);
4095    }
4096
4097    #[rstest]
4098    #[case(Node::Fragment(Fragment{values: vec![]}), true)]
4099    #[case(Node::Fragment(Fragment{values: vec![
4100        Node::Text(Text{value: "not_empty".to_string(), position: None})
4101    ]}), false)]
4102    #[case(Node::Fragment(Fragment{values: vec![
4103        Node::Fragment(Fragment{values: vec![]}),
4104        Node::Fragment(Fragment{values: vec![]})
4105    ]}), true)]
4106    #[case(Node::Fragment(Fragment{values: vec![
4107        Node::Fragment(Fragment{values: vec![]}),
4108        Node::Text(Text{value: "not_empty".to_string(), position: None})
4109    ]}), false)]
4110    #[case(Node::Text(Text{value: "not_fragment".to_string(), position: None}), false)]
4111    fn test_is_empty_fragment(#[case] node: Node, #[case] expected: bool) {
4112        assert_eq!(node.is_empty_fragment(), expected);
4113    }
4114
4115    #[rstest]
4116    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4117    #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), "unknown", None)]
4118    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("<div>test</div>".to_string())))]
4119    #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), "unknown", None)]
4120    #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("text".to_string())))]
4121    #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), "unknown", None)]
4122    #[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())))]
4123    #[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())))]
4124    #[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())))]
4125    #[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)))]
4126    #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, meta: None, fence: false, position: None}), attr_keys::FENCE, Some(AttrValue::Boolean(false)))]
4127    #[case::code_inline(Node::CodeInline(CodeInline{value: "inline".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("inline".to_string())))]
4128    #[case::math_inline(Node::MathInline(MathInline{value: "math".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4129    #[case::math(Node::Math(Math{value: "math".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4130    #[case::yaml(Node::Yaml(Yaml{value: "yaml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("yaml".to_string())))]
4131    #[case::toml(Node::Toml(Toml{value: "toml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("toml".to_string())))]
4132    #[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())))]
4133    #[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())))]
4134    #[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())))]
4135    #[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())))]
4136    #[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())))]
4137    #[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())))]
4138    #[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())))]
4139    #[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())))]
4140    #[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())))]
4141    #[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())))]
4142    #[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())))]
4143    #[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())))]
4144    #[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())))]
4145    #[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())))]
4146    #[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())))]
4147    #[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())))]
4148    #[case::heading(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "depth", Some(AttrValue::Integer(3)))]
4149    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "index", Some(AttrValue::Integer(2)))]
4150    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "level", Some(AttrValue::Integer(1)))]
4151    #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "ordered", Some(AttrValue::Boolean(true)))]
4152    #[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)))]
4153    #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "column", Some(AttrValue::Integer(1)))]
4154    #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "row", Some(AttrValue::Integer(2)))]
4155    #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}), "align", Some(AttrValue::String(":---,---:".to_string())))]
4156    #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
4157    #[case::mdx_flow_expression(Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
4158    #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "esm".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("esm".to_string())))]
4159    #[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())))]
4160    #[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())))]
4161    #[case::break_(Node::Break(Break{position: None}), attr_keys::VALUE, None)]
4162    #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), attr_keys::VALUE, None)]
4163    #[case::fragment(Node::Fragment(Fragment{values: Vec::new()}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
4164    #[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())))]
4165    #[case::heading(Node::Heading(Heading{depth: 2, values: vec![], position: None}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
4166    #[case::heading(Node::Heading(Heading{depth: 3, values: vec![
4167        Node::Text(Text{value: "first".to_string(), position: None}),
4168        Node::Text(Text{value: "second".to_string(), position: None}),
4169    ], position: None}), attr_keys::VALUE, Some(AttrValue::String("firstsecond".to_string())))]
4170    #[case(
4171        Node::List(List {
4172            index: 0,
4173            level: 1,
4174            checked: None,
4175            ordered: false,
4176            values: vec![
4177                Node::Text(Text { value: "item1".to_string(), position: None }),
4178                Node::Text(Text { value: "item2".to_string(), position: None }),
4179            ],
4180            position: None,
4181        }),
4182        attr_keys::VALUE,
4183        Some(AttrValue::String("item1item2".to_string()))
4184    )]
4185    #[case(
4186        Node::TableCell(TableCell {
4187            column: 1,
4188            row: 2,
4189            values: vec![Node::Text(Text {
4190                value: "cell_value".to_string(),
4191                position: None,
4192            })],
4193            position: None,
4194        }),
4195        attr_keys::VALUE,
4196        Some(AttrValue::String("cell_value".to_string()))
4197    )]
4198    #[case::footnote(
4199        Node::Footnote(Footnote {
4200            ident: "id".to_string(),
4201            values: vec![Node::Text(Text {
4202                value: "footnote value".to_string(),
4203                position: None,
4204            })],
4205            position: None,
4206        }),
4207        attr_keys::VALUE,
4208        Some(AttrValue::String("footnote value".to_string()))
4209    )]
4210    #[case::link(
4211        Node::Link(Link {
4212            url: Url::new("https://example.com".to_string()),
4213            title: Some(Title::new("Example".to_string())),
4214            values: vec![Node::Text(Text {
4215                value: "link text".to_string(),
4216                position: None,
4217            })],
4218            position: None,
4219        }),
4220        attr_keys::VALUE,
4221        Some(AttrValue::String("link text".to_string()))
4222    )]
4223    #[case::empty(Node::Empty, attr_keys::VALUE, None)]
4224    #[case::heading(
4225        Node::Heading(Heading {
4226            depth: 1,
4227            values: vec![
4228            Node::Text(Text {
4229                value: "child1".to_string(),
4230                position: None,
4231            }),
4232            Node::Text(Text {
4233                value: "child2".to_string(),
4234                position: None,
4235            }),
4236            ],
4237            position: None,
4238        }),
4239        attr_keys::CHILDREN,
4240        Some(AttrValue::Array(vec![
4241            Node::Text(Text {
4242            value: "child1".to_string(),
4243            position: None,
4244            }),
4245            Node::Text(Text {
4246            value: "child2".to_string(),
4247            position: None,
4248            }),
4249        ]))
4250        )]
4251    #[case::list(
4252        Node::List(List {
4253            index: 0,
4254            level: 1,
4255            checked: None,
4256            ordered: false,
4257            values: vec![
4258            Node::Text(Text {
4259                value: "item1".to_string(),
4260                position: None,
4261            }),
4262            ],
4263            position: None,
4264        }),
4265        attr_keys::CHILDREN,
4266        Some(AttrValue::Array(vec![
4267            Node::Text(Text {
4268            value: "item1".to_string(),
4269            position: None,
4270            }),
4271        ]))
4272        )]
4273    #[case::blockquote(
4274        Node::Blockquote(Blockquote {
4275            values: vec![
4276            Node::Text(Text {
4277                value: "quote".to_string(),
4278                position: None,
4279            }),
4280            ],
4281            position: None,
4282        }),
4283        attr_keys::VALUES,
4284        Some(AttrValue::Array(vec![
4285            Node::Text(Text {
4286            value: "quote".to_string(),
4287            position: None,
4288            }),
4289        ]))
4290        )]
4291    #[case::link(
4292        Node::Link(Link {
4293            url: Url::new(attr_keys::URL.to_string()),
4294            title: None,
4295            values: vec![
4296            Node::Text(Text {
4297                value: "link".to_string(),
4298                position: None,
4299            }),
4300            ],
4301            position: None,
4302        }),
4303        attr_keys::VALUES,
4304        Some(AttrValue::Array(vec![
4305            Node::Text(Text {
4306            value: "link".to_string(),
4307            position: None,
4308            }),
4309        ]))
4310        )]
4311    #[case::table_cell(
4312        Node::TableCell(TableCell {
4313            column: 0,
4314            row: 0,
4315            values: vec![
4316            Node::Text(Text {
4317                value: "cell".to_string(),
4318                position: None,
4319            }),
4320            ],
4321            position: None,
4322        }),
4323        attr_keys::CHILDREN,
4324        Some(AttrValue::Array(vec![
4325            Node::Text(Text {
4326            value: "cell".to_string(),
4327            position: None,
4328            }),
4329        ]))
4330        )]
4331    #[case::strong(
4332        Node::Strong(Strong {
4333            values: vec![
4334            Node::Text(Text {
4335                value: "bold".to_string(),
4336                position: None,
4337            }),
4338            ],
4339            position: None,
4340        }),
4341        attr_keys::CHILDREN,
4342        Some(AttrValue::Array(vec![
4343            Node::Text(Text {
4344            value: "bold".to_string(),
4345            position: None,
4346            }),
4347        ]))
4348        )]
4349    #[case::em(
4350        Node::Emphasis(Emphasis {
4351            values: vec![],
4352            position: None,
4353        }),
4354        attr_keys::CHILDREN,
4355        Some(AttrValue::Array(vec![]))
4356        )]
4357    fn test_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
4358        assert_eq!(node.attr(attr), expected);
4359    }
4360
4361    #[rstest]
4362    #[case(
4363        Node::Text(Text{value: "old".to_string(), position: None}),
4364        attr_keys::VALUE,
4365        "new",
4366        Node::Text(Text{value: "new".to_string(), position: None})
4367    )]
4368    #[case(
4369        Node::Code(Code{value: "old".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
4370        attr_keys::VALUE,
4371        "new_code",
4372        Node::Code(Code{value: "new_code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None})
4373    )]
4374    #[case(
4375        Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
4376        attr_keys::LANG,
4377        "python",
4378        Node::Code(Code{value: "code".to_string(), lang: Some("python".to_string()), fence: true, meta: None, position: None})
4379    )]
4380    #[case(
4381        Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}),
4382        attr_keys::FENCE,
4383        "true",
4384        Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None})
4385    )]
4386    #[case(
4387        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4388        attr_keys::ALT,
4389        "new_alt",
4390        Node::Image(Image{alt: "new_alt".to_string(), url: attr_keys::URL.to_string(), title: None, position: None})
4391    )]
4392    #[case(
4393        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4394        attr_keys::URL,
4395        "new_url",
4396        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "new_url".to_string(), title: None, position: None})
4397    )]
4398    #[case(
4399        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}),
4400        attr_keys::TITLE,
4401        "new_title",
4402        Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some("new_title".to_string()), position: None})
4403    )]
4404    #[case(
4405        Node::Heading(Heading{depth: 2, values: vec![], position: None}),
4406        "depth",
4407        "3",
4408        Node::Heading(Heading{depth: 3, values: vec![], position: None})
4409    )]
4410    #[case(
4411        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
4412        attr_keys::CHECKED,
4413        "false",
4414        Node::List(List{index: 1, level: 2, checked: Some(false), ordered: false, values: vec![], position: None})
4415    )]
4416    #[case(
4417        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
4418        "ordered",
4419        "true",
4420        Node::List(List{index: 1, level: 2, checked: Some(true), ordered: true, values: vec![], position: None})
4421    )]
4422    #[case(
4423        Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
4424        "column",
4425        "3",
4426        Node::TableCell(TableCell{column: 3, row: 2, values: vec![], position: None})
4427    )]
4428    #[case(
4429        Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
4430        "row",
4431        "5",
4432        Node::TableCell(TableCell{column: 1, row: 5, values: vec![], position: None})
4433    )]
4434    #[case(
4435        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4436        attr_keys::IDENT,
4437        "new_id",
4438        Node::Definition(Definition{ident: "new_id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None})
4439    )]
4440    #[case(
4441        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4442        attr_keys::URL,
4443        "new_url",
4444        Node::Definition(Definition{ident: "id".to_string(), url: Url::new("new_url".to_string()), title: None, label: None, position: None})
4445    )]
4446    #[case(
4447        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4448        attr_keys::LABEL,
4449        "new_label",
4450        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})
4451    )]
4452    #[case(
4453        Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4454        attr_keys::TITLE,
4455        "new_title",
4456        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})
4457    )]
4458    #[case(
4459        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4460        attr_keys::ALT,
4461        "new_alt",
4462        Node::ImageRef(ImageRef{alt: "new_alt".to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4463    )]
4464    #[case(
4465        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4466        attr_keys::IDENT,
4467        "new_id",
4468        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4469    )]
4470    #[case(
4471        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4472        attr_keys::LABEL,
4473        "new_label",
4474        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4475    )]
4476    #[case(
4477        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
4478        attr_keys::LABEL,
4479        "new_label",
4480        Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4481    )]
4482    #[case(
4483        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4484        attr_keys::IDENT,
4485        "new_id",
4486        Node::LinkRef(LinkRef{ident: "new_id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
4487    )]
4488    #[case(
4489        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4490        attr_keys::LABEL,
4491        "new_label",
4492        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
4493    )]
4494    #[case(
4495        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: None, position: None}),
4496        attr_keys::LABEL,
4497        "new_label",
4498        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
4499    )]
4500    #[case(
4501        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
4502        "unknown",
4503        "ignored",
4504        Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
4505    )]
4506    #[case(
4507        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4508        attr_keys::IDENT,
4509        "new_id",
4510        Node::FootnoteRef(FootnoteRef{ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4511    )]
4512    #[case(
4513        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4514        attr_keys::LABEL,
4515        "new_label",
4516        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4517    )]
4518    #[case(
4519        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: None, position: None}),
4520        attr_keys::LABEL,
4521        "new_label",
4522        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
4523    )]
4524    #[case(
4525        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4526        "unknown",
4527        "ignored",
4528        Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
4529    )]
4530    #[case(Node::Empty, attr_keys::VALUE, "ignored", Node::Empty)]
4531    #[case(
4532        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
4533        "align",
4534        "---,:---:",
4535        Node::TableAlign(TableAlign{align: vec![TableAlignKind::None, TableAlignKind::Center], position: None})
4536    )]
4537    #[case(
4538        Node::TableAlign(TableAlign{align: vec![], position: None}),
4539        "align",
4540        ":---,---:",
4541        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None})
4542    )]
4543    #[case(
4544        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None}),
4545        "unknown",
4546        "ignored",
4547        Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None})
4548    )]
4549    #[case(
4550        Node::MdxFlowExpression(MdxFlowExpression{value: "old".into(), position: None}),
4551        attr_keys::VALUE,
4552        "new_expr",
4553        Node::MdxFlowExpression(MdxFlowExpression{value: "new_expr".into(), position: None})
4554    )]
4555    #[case(
4556        Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}),
4557        "unknown",
4558        "ignored",
4559        Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None})
4560    )]
4561    #[case(
4562        Node::MdxTextExpression(MdxTextExpression{value: "old".into(), position: None}),
4563        attr_keys::VALUE,
4564        "new_expr",
4565        Node::MdxTextExpression(MdxTextExpression{value: "new_expr".into(), position: None})
4566    )]
4567    #[case(
4568        Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}),
4569        "unknown",
4570        "ignored",
4571        Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None})
4572    )]
4573    #[case(
4574        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
4575        attr_keys::VALUE,
4576        "import y",
4577        Node::MdxJsEsm(MdxJsEsm{value: "import y".into(), position: None})
4578    )]
4579    #[case(
4580        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
4581        "unknown",
4582        "ignored",
4583        Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None})
4584    )]
4585    #[case(
4586        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
4587        attr_keys::NAME,
4588        "section",
4589        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("section".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4590    )]
4591    #[case(
4592        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
4593        attr_keys::NAME,
4594        "main",
4595        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("main".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4596    )]
4597    #[case(
4598        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
4599        "unknown",
4600        "ignored",
4601        Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
4602    )]
4603    #[case(
4604        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
4605        attr_keys::NAME,
4606        "b",
4607        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("b".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4608    )]
4609    #[case(
4610        Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
4611        attr_keys::NAME,
4612        "i",
4613        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("i".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4614    )]
4615    #[case(
4616        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
4617        "unknown",
4618        "ignored",
4619        Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None})
4620    )]
4621    fn test_set_attr(#[case] mut node: Node, #[case] attr: &str, #[case] value: &str, #[case] expected: Node) {
4622        node.set_attr(attr, value);
4623        assert_eq!(node, expected);
4624    }
4625
4626    #[rstest]
4627    #[case(AttrValue::String("test".to_string()), AttrValue::String("test".to_string()), true)]
4628    #[case(AttrValue::String("test".to_string()), AttrValue::String("other".to_string()), false)]
4629    #[case(AttrValue::Integer(42), AttrValue::Integer(42), true)]
4630    #[case(AttrValue::Integer(42), AttrValue::Integer(0), false)]
4631    #[case(AttrValue::Boolean(true), AttrValue::Boolean(true), true)]
4632    #[case(AttrValue::Boolean(true), AttrValue::Boolean(false), false)]
4633    #[case(AttrValue::String("42".to_string()), AttrValue::Integer(42), false)]
4634    #[case(AttrValue::Boolean(false), AttrValue::Integer(0), false)]
4635    fn test_attr_value_eq(#[case] a: AttrValue, #[case] b: AttrValue, #[case] expected: bool) {
4636        assert_eq!(a == b, expected);
4637    }
4638
4639    #[rstest]
4640    #[case(AttrValue::String("test".to_string()), "test")]
4641    #[case(AttrValue::Integer(42), "42")]
4642    #[case(AttrValue::Boolean(true), "true")]
4643    fn test_attr_value_as_str(#[case] value: AttrValue, #[case] expected: &str) {
4644        assert_eq!(&value.as_string(), expected);
4645    }
4646
4647    #[rstest]
4648    #[case(AttrValue::Integer(42), Some(42))]
4649    #[case(AttrValue::String("42".to_string()), Some(42))]
4650    #[case(AttrValue::Boolean(false), Some(0))]
4651    fn test_attr_value_as_i64(#[case] value: AttrValue, #[case] expected: Option<i64>) {
4652        assert_eq!(value.as_i64(), expected);
4653    }
4654}