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