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