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