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