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