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