1use crate::node::attr_value::{
2 AttrValue,
3 attr_keys::{self, CHILDREN},
4};
5use itertools::Itertools;
6use markdown::mdast::{self};
7use smol_str::SmolStr;
8use std::{
9 borrow::Cow,
10 fmt::{self, Display},
11};
12
13type ColorPair<'a> = (Cow<'a, str>, Cow<'a, str>);
14
15pub mod attr_value;
16
17#[derive(Debug, Clone, Default)]
22pub struct ColorTheme<'a> {
23 pub heading: ColorPair<'a>,
24 pub code: ColorPair<'a>,
25 pub code_inline: ColorPair<'a>,
26 pub emphasis: ColorPair<'a>,
27 pub strong: ColorPair<'a>,
28 pub link: ColorPair<'a>,
29 pub link_url: ColorPair<'a>,
30 pub image: ColorPair<'a>,
31 pub blockquote_marker: ColorPair<'a>,
32 pub delete: ColorPair<'a>,
33 pub horizontal_rule: ColorPair<'a>,
34 pub html: ColorPair<'a>,
35 pub frontmatter: ColorPair<'a>,
36 pub list_marker: ColorPair<'a>,
37 pub table_separator: ColorPair<'a>,
38 pub math: ColorPair<'a>,
39}
40
41const EMPTY: Cow<'_, str> = Cow::Borrowed("");
42#[cfg(feature = "color")]
43const RESET: Cow<'_, str> = Cow::Borrowed("\x1b[0m");
44
45impl ColorTheme<'_> {
46 pub const PLAIN: ColorTheme<'static> = ColorTheme {
47 heading: (EMPTY, EMPTY),
48 code: (EMPTY, EMPTY),
49 code_inline: (EMPTY, EMPTY),
50 emphasis: (EMPTY, EMPTY),
51 strong: (EMPTY, EMPTY),
52 link: (EMPTY, EMPTY),
53 link_url: (EMPTY, EMPTY),
54 image: (EMPTY, EMPTY),
55 blockquote_marker: (EMPTY, EMPTY),
56 delete: (EMPTY, EMPTY),
57 horizontal_rule: (EMPTY, EMPTY),
58 html: (EMPTY, EMPTY),
59 frontmatter: (EMPTY, EMPTY),
60 list_marker: (EMPTY, EMPTY),
61 table_separator: (EMPTY, EMPTY),
62 math: (EMPTY, EMPTY),
63 };
64
65 #[cfg(feature = "color")]
66 pub const COLORED: ColorTheme<'static> = ColorTheme {
67 heading: (Cow::Borrowed("\x1b[1m\x1b[36m"), RESET),
68 code: (Cow::Borrowed("\x1b[32m"), RESET),
69 code_inline: (Cow::Borrowed("\x1b[32m"), RESET),
70 emphasis: (Cow::Borrowed("\x1b[3m\x1b[33m"), RESET),
71 strong: (Cow::Borrowed("\x1b[1m"), RESET),
72 link: (Cow::Borrowed("\x1b[4m\x1b[34m"), RESET),
73 link_url: (Cow::Borrowed("\x1b[34m"), RESET),
74 image: (Cow::Borrowed("\x1b[35m"), RESET),
75 blockquote_marker: (Cow::Borrowed("\x1b[2m"), RESET),
76 delete: (Cow::Borrowed("\x1b[31m\x1b[2m"), RESET),
77 horizontal_rule: (Cow::Borrowed("\x1b[2m"), RESET),
78 html: (Cow::Borrowed("\x1b[2m"), RESET),
79 frontmatter: (Cow::Borrowed("\x1b[2m"), RESET),
80 list_marker: (Cow::Borrowed("\x1b[33m"), RESET),
81 table_separator: (Cow::Borrowed("\x1b[2m"), RESET),
82 math: (Cow::Borrowed("\x1b[32m"), RESET),
83 };
84
85 #[cfg(feature = "color")]
87 pub fn from_env() -> ColorTheme<'static> {
88 match std::env::var("MQ_COLORS") {
89 Ok(v) if !v.is_empty() => ColorTheme::parse_colors(&v),
90 _ => Self::COLORED,
91 }
92 }
93
94 #[cfg(feature = "color")]
101 pub fn parse_colors(spec: &str) -> ColorTheme<'static> {
102 let mut theme = Self::COLORED;
103
104 for entry in spec.split(':') {
105 let Some((key, sgr)) = entry.split_once('=') else {
106 continue;
107 };
108
109 if !Self::is_valid_sgr(sgr) {
110 continue;
111 }
112
113 let prefix = Cow::Owned(format!("\x1b[{}m", sgr));
114 let pair = (prefix, RESET);
115
116 match key {
117 "heading" => theme.heading = pair,
118 "code" => theme.code = pair,
119 "code_inline" => theme.code_inline = pair,
120 "emphasis" => theme.emphasis = pair,
121 "strong" => theme.strong = pair,
122 "link" => theme.link = pair,
123 "link_url" => theme.link_url = pair,
124 "image" => theme.image = pair,
125 "blockquote" => theme.blockquote_marker = pair,
126 "delete" => theme.delete = pair,
127 "hr" => theme.horizontal_rule = pair,
128 "html" => theme.html = pair,
129 "frontmatter" => theme.frontmatter = pair,
130 "list" => theme.list_marker = pair,
131 "table" => theme.table_separator = pair,
132 "math" => theme.math = pair,
133 _ => {}
134 }
135 }
136
137 theme
138 }
139
140 #[cfg(feature = "color")]
143 fn is_valid_sgr(sgr: &str) -> bool {
144 !sgr.is_empty() && sgr.split(';').all(|part| part.parse::<u8>().is_ok())
145 }
146}
147
148type Level = u8;
149
150pub const EMPTY_NODE: Node = Node::Text(Text {
151 value: String::new(),
152 position: None,
153});
154
155#[derive(Debug, Clone, Default, PartialEq)]
156pub struct RenderOptions {
157 pub list_style: ListStyle,
158 pub link_url_style: UrlSurroundStyle,
159 pub link_title_style: TitleSurroundStyle,
160}
161
162#[derive(Debug, Clone, Default, PartialEq)]
163pub enum ListStyle {
164 #[default]
165 Dash,
166 Plus,
167 Star,
168}
169
170impl Display for ListStyle {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 ListStyle::Dash => write!(f, "-"),
174 ListStyle::Plus => write!(f, "+"),
175 ListStyle::Star => write!(f, "*"),
176 }
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Default)]
181#[cfg_attr(
182 feature = "json",
183 derive(serde::Serialize, serde::Deserialize),
184 serde(rename_all = "camelCase")
185)]
186pub struct Url(String);
187
188#[derive(Debug, Clone, PartialEq, Default)]
189pub enum UrlSurroundStyle {
190 #[default]
191 None,
192 Angle,
193}
194
195impl Url {
196 pub fn new(value: String) -> Self {
197 Self(value)
198 }
199
200 pub fn as_str(&self) -> &str {
201 &self.0
202 }
203
204 pub fn to_string_with(&self, options: &RenderOptions) -> Cow<'_, str> {
205 match options.link_url_style {
206 UrlSurroundStyle::None if self.0.is_empty() => Cow::Borrowed(""),
207 UrlSurroundStyle::None => Cow::Borrowed(&self.0),
208 UrlSurroundStyle::Angle => Cow::Owned(format!("<{}>", self.0)),
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Default)]
214pub enum TitleSurroundStyle {
215 #[default]
216 Double,
217 Single,
218 Paren,
219}
220
221#[derive(Debug, Clone, PartialEq)]
222#[cfg_attr(
223 feature = "json",
224 derive(serde::Serialize, serde::Deserialize),
225 serde(rename_all = "camelCase")
226)]
227pub struct Title(String);
228
229impl Display for Title {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 write!(f, "{}", self.0)
232 }
233}
234
235impl Title {
236 pub fn new(value: String) -> Self {
237 Self(value)
238 }
239
240 pub fn to_value(&self) -> String {
241 self.0.clone()
242 }
243
244 pub fn to_string_with(&self, options: &RenderOptions) -> Cow<'_, str> {
245 match options.link_title_style {
246 TitleSurroundStyle::Double => Cow::Owned(format!("\"{}\"", self)),
247 TitleSurroundStyle::Single => Cow::Owned(format!("'{}'", self)),
248 TitleSurroundStyle::Paren => Cow::Owned(format!("({})", self)),
249 }
250 }
251}
252
253#[derive(Debug, Clone, PartialEq)]
254#[cfg_attr(
255 feature = "json",
256 derive(serde::Serialize, serde::Deserialize),
257 serde(rename_all = "camelCase")
258)]
259pub enum TableAlignKind {
260 Left,
261 Right,
262 Center,
263 None,
264}
265
266impl From<mdast::AlignKind> for TableAlignKind {
267 fn from(value: mdast::AlignKind) -> Self {
268 match value {
269 mdast::AlignKind::Left => Self::Left,
270 mdast::AlignKind::Right => Self::Right,
271 mdast::AlignKind::Center => Self::Center,
272 mdast::AlignKind::None => Self::None,
273 }
274 }
275}
276
277impl From<&str> for TableAlignKind {
278 fn from(value: &str) -> Self {
279 match value {
280 "left" | ":---" => Self::Left,
281 "right" | "---:" => Self::Right,
282 "center" | ":---:" => Self::Center,
283 "---" => Self::None,
284 _ => Self::None,
285 }
286 }
287}
288
289impl Display for TableAlignKind {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 match self {
292 TableAlignKind::Left => write!(f, ":---"),
293 TableAlignKind::Right => write!(f, "---:"),
294 TableAlignKind::Center => write!(f, ":---:"),
295 TableAlignKind::None => write!(f, "---"),
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Default)]
301#[cfg_attr(
302 feature = "json",
303 derive(serde::Serialize, serde::Deserialize),
304 serde(rename_all = "camelCase", tag = "type")
305)]
306pub struct List {
307 pub values: Vec<Node>,
308 pub index: usize,
309 pub level: Level,
310 pub ordered: bool,
311 pub checked: Option<bool>,
312 pub position: Option<Position>,
313}
314
315#[derive(Debug, Clone, PartialEq)]
316#[cfg_attr(
317 feature = "json",
318 derive(serde::Serialize, serde::Deserialize),
319 serde(rename_all = "camelCase", tag = "type")
320)]
321pub struct TableCell {
322 pub values: Vec<Node>,
323 pub column: usize,
324 pub row: usize,
325 pub position: Option<Position>,
326}
327
328#[derive(Debug, Clone, PartialEq)]
329#[cfg_attr(
330 feature = "json",
331 derive(serde::Serialize, serde::Deserialize),
332 serde(rename_all = "camelCase", tag = "type")
333)]
334pub struct TableRow {
335 pub values: Vec<Node>,
336 pub position: Option<Position>,
337}
338
339#[derive(Debug, Clone, PartialEq)]
340#[cfg_attr(
341 feature = "json",
342 derive(serde::Serialize, serde::Deserialize),
343 serde(rename_all = "camelCase", tag = "type")
344)]
345pub struct TableAlign {
346 pub align: Vec<TableAlignKind>,
347 pub position: Option<Position>,
348}
349
350#[derive(Debug, Clone, PartialEq)]
351#[cfg_attr(
352 feature = "json",
353 derive(serde::Serialize, serde::Deserialize),
354 serde(rename_all = "camelCase", tag = "type")
355)]
356pub struct Fragment {
357 pub values: Vec<Node>,
358}
359
360#[derive(Debug, Clone, PartialEq, Default)]
361#[cfg_attr(
362 feature = "json",
363 derive(serde::Serialize, serde::Deserialize),
364 serde(rename_all = "camelCase", tag = "type")
365)]
366pub struct Code {
367 pub value: String,
368 pub lang: Option<String>,
369 pub position: Option<Position>,
370 pub meta: Option<String>,
371 pub fence: bool,
372}
373
374#[derive(Debug, Clone, PartialEq)]
375#[cfg_attr(
376 feature = "json",
377 derive(serde::Serialize, serde::Deserialize),
378 serde(rename_all = "camelCase", tag = "type")
379)]
380pub struct Image {
381 pub alt: String,
382 pub url: String,
383 pub title: Option<String>,
384 pub position: Option<Position>,
385}
386
387#[derive(Debug, Clone, PartialEq, Default)]
388#[cfg_attr(
389 feature = "json",
390 derive(serde::Serialize, serde::Deserialize),
391 serde(rename_all = "camelCase", tag = "type")
392)]
393pub struct ImageRef {
394 pub alt: String,
395 pub ident: String,
396 pub label: Option<String>,
397 pub position: Option<Position>,
398}
399#[derive(Debug, Clone, PartialEq)]
400#[cfg_attr(
401 feature = "json",
402 derive(serde::Serialize, serde::Deserialize),
403 serde(rename_all = "camelCase", tag = "type")
404)]
405pub struct Link {
406 pub url: Url,
407 pub title: Option<Title>,
408 pub values: Vec<Node>,
409 pub position: Option<Position>,
410}
411
412#[cfg(feature = "callout")]
414#[derive(Debug, Clone, PartialEq, Default)]
415#[cfg_attr(
416 feature = "json",
417 derive(serde::Serialize, serde::Deserialize),
418 serde(rename_all = "camelCase", tag = "type")
419)]
420pub struct Callout {
421 pub kind: String,
423 pub title: Option<String>,
425 pub values: Vec<Node>,
427 pub position: Option<Position>,
428}
429
430#[cfg(feature = "embed")]
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 Embed {
439 pub target: String,
441 pub display: Option<String>,
443 pub position: Option<Position>,
444}
445
446#[cfg(feature = "wikilink")]
448#[derive(Debug, Clone, PartialEq, Default)]
449#[cfg_attr(
450 feature = "json",
451 derive(serde::Serialize, serde::Deserialize),
452 serde(rename_all = "camelCase", tag = "type")
453)]
454pub struct WikiLink {
455 pub target: String,
457 pub text: Option<String>,
459 pub position: Option<Position>,
460}
461
462#[derive(Debug, Clone, PartialEq, Default)]
463#[cfg_attr(
464 feature = "json",
465 derive(serde::Serialize, serde::Deserialize),
466 serde(rename_all = "camelCase", tag = "type")
467)]
468pub struct FootnoteRef {
469 pub ident: String,
470 pub label: Option<String>,
471 pub position: Option<Position>,
472}
473
474#[derive(Debug, Clone, PartialEq, Default)]
475#[cfg_attr(
476 feature = "json",
477 derive(serde::Serialize, serde::Deserialize),
478 serde(rename_all = "camelCase", tag = "type")
479)]
480pub struct Footnote {
481 pub ident: String,
482 pub values: Vec<Node>,
483 pub position: Option<Position>,
484}
485
486#[derive(Debug, Clone, PartialEq, Default)]
487#[cfg_attr(
488 feature = "json",
489 derive(serde::Serialize, serde::Deserialize),
490 serde(rename_all = "camelCase", tag = "type")
491)]
492pub struct LinkRef {
493 pub ident: String,
494 pub label: Option<String>,
495 pub values: Vec<Node>,
496 pub position: Option<Position>,
497}
498
499#[derive(Debug, Clone, PartialEq, Default)]
500#[cfg_attr(
501 feature = "json",
502 derive(serde::Serialize, serde::Deserialize),
503 serde(rename_all = "camelCase", tag = "type")
504)]
505pub struct Heading {
506 pub depth: u8,
507 pub values: Vec<Node>,
508 pub position: Option<Position>,
509}
510
511#[derive(Debug, Clone, PartialEq, Default)]
512#[cfg_attr(
513 feature = "json",
514 derive(serde::Serialize, serde::Deserialize),
515 serde(rename_all = "camelCase", tag = "type")
516)]
517pub struct Definition {
518 pub position: Option<Position>,
519 pub url: Url,
520 pub title: Option<Title>,
521 pub ident: String,
522 pub label: Option<String>,
523}
524#[derive(Debug, Clone, PartialEq)]
525#[cfg_attr(
526 feature = "json",
527 derive(serde::Serialize, serde::Deserialize),
528 serde(rename_all = "camelCase", tag = "type")
529)]
530pub struct Text {
531 pub value: String,
532 pub position: Option<Position>,
533}
534
535#[derive(Debug, Clone, PartialEq)]
536#[cfg_attr(
537 feature = "json",
538 derive(serde::Serialize, serde::Deserialize),
539 serde(rename_all = "camelCase", tag = "type")
540)]
541pub struct Html {
542 pub value: String,
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 Toml {
553 pub value: String,
554 pub position: Option<Position>,
555}
556
557#[derive(Debug, Clone, PartialEq)]
558#[cfg_attr(
559 feature = "json",
560 derive(serde::Serialize, serde::Deserialize),
561 serde(rename_all = "camelCase", tag = "type")
562)]
563pub struct Yaml {
564 pub value: String,
565 pub position: Option<Position>,
566}
567
568#[derive(Debug, Clone, PartialEq)]
569#[cfg_attr(
570 feature = "json",
571 derive(serde::Serialize, serde::Deserialize),
572 serde(rename_all = "camelCase", tag = "type")
573)]
574pub struct CodeInline {
575 pub value: SmolStr,
576 pub position: Option<Position>,
577}
578
579#[derive(Debug, Clone, PartialEq)]
580#[cfg_attr(
581 feature = "json",
582 derive(serde::Serialize, serde::Deserialize),
583 serde(rename_all = "camelCase", tag = "type")
584)]
585pub struct MathInline {
586 pub value: SmolStr,
587 pub position: Option<Position>,
588}
589
590#[derive(Debug, Clone, PartialEq)]
591#[cfg_attr(
592 feature = "json",
593 derive(serde::Serialize, serde::Deserialize),
594 serde(rename_all = "camelCase", tag = "type")
595)]
596pub struct Math {
597 pub value: String,
598 pub position: Option<Position>,
599}
600
601#[derive(Debug, Clone, PartialEq)]
602#[cfg_attr(
603 feature = "json",
604 derive(serde::Serialize, serde::Deserialize),
605 serde(rename_all = "camelCase", tag = "type")
606)]
607pub struct MdxFlowExpression {
608 pub value: SmolStr,
609 pub position: Option<Position>,
610}
611
612#[derive(Debug, Clone, PartialEq)]
613#[cfg_attr(
614 feature = "json",
615 derive(serde::Serialize, serde::Deserialize),
616 serde(rename_all = "camelCase", tag = "type")
617)]
618pub struct MdxJsxFlowElement {
619 pub children: Vec<Node>,
620 pub position: Option<Position>,
621 pub name: Option<String>,
622 pub attributes: Vec<MdxAttributeContent>,
623}
624
625#[derive(Debug, Clone, PartialEq)]
626#[cfg_attr(
627 feature = "json",
628 derive(serde::Serialize, serde::Deserialize),
629 serde(rename_all = "camelCase", tag = "type")
630)]
631pub enum MdxAttributeContent {
632 Expression(SmolStr),
633 Property(MdxJsxAttribute),
634}
635
636#[derive(Debug, Clone, PartialEq)]
637#[cfg_attr(
638 feature = "json",
639 derive(serde::Serialize, serde::Deserialize),
640 serde(rename_all = "camelCase", tag = "type")
641)]
642pub struct MdxJsxAttribute {
643 pub name: SmolStr,
644 pub value: Option<MdxAttributeValue>,
645}
646
647#[derive(Debug, Clone, PartialEq)]
648#[cfg_attr(
649 feature = "json",
650 derive(serde::Serialize, serde::Deserialize),
651 serde(rename_all = "camelCase", tag = "type")
652)]
653pub enum MdxAttributeValue {
654 Expression(SmolStr),
655 Literal(SmolStr),
656}
657
658#[derive(Debug, Clone, PartialEq)]
659#[cfg_attr(
660 feature = "json",
661 derive(serde::Serialize, serde::Deserialize),
662 serde(rename_all = "camelCase", tag = "type")
663)]
664pub struct MdxJsxTextElement {
665 pub children: Vec<Node>,
666 pub position: Option<Position>,
667 pub name: Option<SmolStr>,
668 pub attributes: Vec<MdxAttributeContent>,
669}
670
671#[derive(Debug, Clone, PartialEq)]
672#[cfg_attr(
673 feature = "json",
674 derive(serde::Serialize, serde::Deserialize),
675 serde(rename_all = "camelCase", tag = "type")
676)]
677pub struct MdxTextExpression {
678 pub value: SmolStr,
679 pub position: Option<Position>,
680}
681
682#[derive(Debug, Clone, PartialEq)]
683#[cfg_attr(
684 feature = "json",
685 derive(serde::Serialize, serde::Deserialize),
686 serde(rename_all = "camelCase", tag = "type")
687)]
688pub struct MdxJsEsm {
689 pub value: SmolStr,
690 pub position: Option<Position>,
691}
692
693#[derive(Debug, Clone, PartialEq)]
694#[cfg_attr(
695 feature = "json",
696 derive(serde::Serialize, serde::Deserialize),
697 serde(rename_all = "camelCase", tag = "type")
698)]
699pub struct Blockquote {
700 pub values: Vec<Node>,
701 pub position: Option<Position>,
702}
703
704#[derive(Debug, Clone, PartialEq)]
705#[cfg_attr(
706 feature = "json",
707 derive(serde::Serialize, serde::Deserialize),
708 serde(rename_all = "camelCase", tag = "type")
709)]
710pub struct Delete {
711 pub values: Vec<Node>,
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 Emphasis {
722 pub values: Vec<Node>,
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 Strong {
733 pub values: Vec<Node>,
734 pub position: Option<Position>,
735}
736
737#[derive(Debug, Clone, PartialEq)]
738#[cfg_attr(
739 feature = "json",
740 derive(serde::Serialize, serde::Deserialize),
741 serde(rename_all = "camelCase", tag = "type")
742)]
743pub struct Break {
744 pub position: Option<Position>,
745}
746
747#[derive(Debug, Clone, PartialEq)]
748#[cfg_attr(
749 feature = "json",
750 derive(serde::Serialize, serde::Deserialize),
751 serde(rename_all = "camelCase", tag = "type")
752)]
753pub struct HorizontalRule {
754 pub position: Option<Position>,
755}
756
757#[derive(Debug, Clone, Default)]
758#[cfg_attr(
759 feature = "json",
760 derive(serde::Serialize, serde::Deserialize),
761 serde(rename_all = "camelCase", untagged)
762)]
763pub enum Node {
764 Blockquote(Blockquote),
765 Break(Break),
766 #[cfg(feature = "callout")]
767 Callout(Callout),
768 #[cfg(feature = "embed")]
769 Embed(Embed),
770 Definition(Definition),
771 Delete(Delete),
772 Heading(Heading),
773 Emphasis(Emphasis),
774 Footnote(Footnote),
775 FootnoteRef(FootnoteRef),
776 Html(Html),
777 Yaml(Yaml),
778 Toml(Toml),
779 Image(Image),
780 ImageRef(ImageRef),
781 CodeInline(CodeInline),
782 MathInline(MathInline),
783 Link(Link),
784 LinkRef(LinkRef),
785 #[cfg(feature = "wikilink")]
786 WikiLink(WikiLink),
787 Math(Math),
788 List(List),
789 TableAlign(TableAlign),
790 TableRow(TableRow),
791 TableCell(TableCell),
792 Code(Code),
793 Strong(Strong),
794 HorizontalRule(HorizontalRule),
795 MdxFlowExpression(MdxFlowExpression),
796 MdxJsxFlowElement(MdxJsxFlowElement),
797 MdxJsxTextElement(MdxJsxTextElement),
798 MdxTextExpression(MdxTextExpression),
799 MdxJsEsm(MdxJsEsm),
800 Text(Text),
801 Fragment(Fragment),
802 #[default]
803 Empty,
804}
805
806impl PartialEq for Node {
807 fn eq(&self, other: &Self) -> bool {
808 self.to_string() == other.to_string()
809 }
810}
811
812impl PartialOrd for Node {
813 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
814 let (self_node, other_node) = (self, other);
815 let self_pos = self_node.position();
816 let other_pos = other_node.position();
817
818 match (self_pos, other_pos) {
819 (Some(self_pos), Some(other_pos)) => match self_pos.start.line.cmp(&other_pos.start.line) {
820 std::cmp::Ordering::Equal => self_pos.start.column.partial_cmp(&other_pos.start.column),
821 ordering => Some(ordering),
822 },
823 (Some(_), None) => Some(std::cmp::Ordering::Less),
824 (None, Some(_)) => Some(std::cmp::Ordering::Greater),
825 (None, None) => Some(self.name().cmp(&other.name())),
826 }
827 }
828}
829
830#[derive(Debug, Clone, PartialEq)]
831#[cfg_attr(
832 feature = "json",
833 derive(serde::Serialize, serde::Deserialize),
834 serde(rename_all = "camelCase")
835)]
836pub struct Position {
837 pub start: Point,
838 pub end: Point,
839}
840
841#[derive(Debug, Clone, PartialEq)]
842#[cfg_attr(
843 feature = "json",
844 derive(serde::Serialize, serde::Deserialize),
845 serde(rename_all = "camelCase")
846)]
847pub struct Point {
848 pub line: usize,
849 pub column: usize,
850}
851
852impl From<markdown::unist::Position> for Position {
853 fn from(value: markdown::unist::Position) -> Self {
854 Self {
855 start: Point {
856 line: value.start.line,
857 column: value.start.column,
858 },
859 end: Point {
860 line: value.end.line,
861 column: value.end.column,
862 },
863 }
864 }
865}
866
867impl From<String> for Node {
868 fn from(value: String) -> Self {
869 Self::Text(Text { value, position: None })
870 }
871}
872
873impl From<&str> for Node {
874 fn from(value: &str) -> Self {
875 Self::Text(Text {
876 value: value.to_string(),
877 position: None,
878 })
879 }
880}
881
882impl Display for Node {
883 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
884 write!(f, "{}", self.to_string_with(&RenderOptions::default()))
885 }
886}
887
888impl Node {
889 pub fn map_values<E, F>(&self, f: &mut F) -> Result<Node, E>
890 where
891 E: std::error::Error,
892 F: FnMut(&Node) -> Result<Node, E>,
893 {
894 Self::_map_values(self.clone(), f)
895 }
896
897 fn _map_values<E, F>(node: Node, f: &mut F) -> Result<Node, E>
898 where
899 E: std::error::Error,
900 F: FnMut(&Node) -> Result<Node, E>,
901 {
902 match f(&node)? {
903 Node::Fragment(mut v) => {
904 let values = v
905 .values
906 .into_iter()
907 .map(|node| Self::_map_values(node, f))
908 .collect::<Result<Vec<_>, _>>();
909 match values {
910 Ok(values) => {
911 v.values = values;
912 Ok(Node::Fragment(v))
913 }
914 Err(e) => Err(e),
915 }
916 }
917 node => Ok(node),
918 }
919 }
920
921 pub fn to_fragment(&self) -> Node {
922 match self.clone() {
923 Node::List(List { values, .. })
924 | Node::TableCell(TableCell { values, .. })
925 | Node::TableRow(TableRow { values, .. })
926 | Node::Link(Link { values, .. })
927 | Node::Footnote(Footnote { values, .. })
928 | Node::LinkRef(LinkRef { values, .. })
929 | Node::Heading(Heading { values, .. })
930 | Node::Blockquote(Blockquote { values, .. })
931 | Node::Delete(Delete { values, .. })
932 | Node::Emphasis(Emphasis { values, .. })
933 | Node::Strong(Strong { values, .. }) => Self::Fragment(Fragment { values }),
934 #[cfg(feature = "callout")]
935 Node::Callout(Callout { values, .. }) => Self::Fragment(Fragment { values }),
936 node @ Node::Fragment(_) => node,
937 _ => Self::Empty,
938 }
939 }
940
941 pub fn apply_fragment(&mut self, fragment: Node) {
942 Self::_apply_fragment(self, fragment)
943 }
944
945 fn _apply_fragment(node: &mut Node, fragment: Node) {
946 match node {
947 Node::List(List { values, .. })
948 | Node::TableCell(TableCell { values, .. })
949 | Node::TableRow(TableRow { values, .. })
950 | Node::Link(Link { values, .. })
951 | Node::Footnote(Footnote { values, .. })
952 | Node::LinkRef(LinkRef { values, .. })
953 | Node::Heading(Heading { values, .. })
954 | Node::Blockquote(Blockquote { values, .. })
955 | Node::Delete(Delete { values, .. })
956 | Node::Emphasis(Emphasis { values, .. })
957 | Node::Strong(Strong { values, .. }) => {
958 if let Node::Fragment(Fragment { values: new_values }) = fragment {
959 let new_values = values
960 .iter()
961 .zip(new_values)
962 .map(|(current_value, new_value)| {
963 if new_value.is_empty() {
964 current_value.clone()
965 } else if new_value.is_fragment() {
966 let mut current_value = current_value.clone();
967 Self::_apply_fragment(&mut current_value, new_value);
968 current_value
969 } else {
970 new_value
971 }
972 })
973 .collect::<Vec<_>>();
974 *values = new_values;
975 }
976 }
977 #[cfg(feature = "callout")]
978 Node::Callout(Callout { values, .. }) => {
979 if let Node::Fragment(Fragment { values: new_values }) = fragment {
980 let new_values = values
981 .iter()
982 .zip(new_values)
983 .map(|(current_value, new_value)| {
984 if new_value.is_empty() {
985 current_value.clone()
986 } else if new_value.is_fragment() {
987 let mut current_value = current_value.clone();
988 Self::_apply_fragment(&mut current_value, new_value);
989 current_value
990 } else {
991 new_value
992 }
993 })
994 .collect::<Vec<_>>();
995 *values = new_values;
996 }
997 }
998 _ => {}
999 }
1000 }
1001
1002 pub fn to_string_with(&self, options: &RenderOptions) -> String {
1003 self.render_with_theme(options, &ColorTheme::PLAIN)
1004 }
1005
1006 #[cfg(feature = "color")]
1008 pub fn to_colored_string_with(&self, options: &RenderOptions) -> String {
1009 self.render_with_theme(options, &ColorTheme::COLORED)
1010 }
1011
1012 pub(crate) fn render_with_theme(&self, options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
1013 match self.clone() {
1014 Self::List(List {
1015 level,
1016 checked,
1017 values,
1018 ordered,
1019 index,
1020 ..
1021 }) => {
1022 let marker = if ordered {
1023 format!("{}.", index + 1)
1024 } else {
1025 options.list_style.to_string()
1026 };
1027 let (ms, me) = &theme.list_marker;
1028 format!(
1029 "{}{}{}{} {}{}",
1030 " ".repeat(level as usize),
1031 ms,
1032 marker,
1033 me,
1034 checked.map(|it| if it { "[x] " } else { "[ ] " }).unwrap_or_else(|| ""),
1035 render_values(&values, options, theme)
1036 )
1037 }
1038 Self::TableRow(TableRow { values, .. }) => {
1039 let (ts, te) = &theme.table_separator;
1040 let cells = values
1041 .iter()
1042 .map(|cell| cell.render_with_theme(options, theme))
1043 .collect::<Vec<_>>()
1044 .join("|");
1045 format!("{}|{}{}|", ts, te, cells)
1046 }
1047 Self::TableCell(TableCell { values, .. }) => render_values(&values, options, theme),
1048 Self::TableAlign(TableAlign { align, .. }) => {
1049 let (ts, te) = &theme.table_separator;
1050 format!("{}|{}|{}", ts, align.iter().map(|a| a.to_string()).join("|"), te)
1051 }
1052 Self::Blockquote(Blockquote { values, .. }) => {
1053 let (bs, be) = &theme.blockquote_marker;
1054 render_values_block(&values, options, theme)
1055 .split('\n')
1056 .map(|line| format!("{}> {}{}", bs, be, line))
1057 .join("\n")
1058 }
1059 #[cfg(feature = "callout")]
1060 Self::Callout(Callout {
1061 kind, title, values, ..
1062 }) => {
1063 let (bs, be) = &theme.blockquote_marker;
1064 let header = match title.as_deref() {
1065 Some(t) if !t.is_empty() => format!("[!{}] {}", kind, t),
1066 _ => format!("[!{}]", kind),
1067 };
1068 let header_line = format!("{}> {}{}", bs, be, header);
1069 if values.is_empty() {
1070 return header_line;
1071 }
1072 let body = render_values_block(&values, options, theme);
1073 if body.trim().is_empty() {
1074 header_line
1075 } else {
1076 let body_lines = body
1077 .split('\n')
1078 .map(|line| format!("{}> {}{}", bs, be, line))
1079 .join("\n");
1080 format!("{}\n{}", header_line, body_lines)
1081 }
1082 }
1083 #[cfg(feature = "embed")]
1084 Self::Embed(Embed { target, display, .. }) => match display.as_deref() {
1085 Some(d) if !d.is_empty() => format!("![[{}|{}]]", target, d),
1086 _ => format!("![[{}]]", target),
1087 },
1088 Self::Code(Code {
1089 value,
1090 lang,
1091 fence,
1092 meta,
1093 ..
1094 }) => {
1095 let (cs, ce) = &theme.code;
1096 let meta = meta.as_deref().map(|meta| format!(" {}", meta)).unwrap_or_default();
1097
1098 match lang {
1099 Some(lang) => format!("{}```{}{}\n{}\n```{}", cs, lang, meta, value, ce),
1100 None if fence => {
1101 format!("{}```{}\n{}\n```{}", cs, lang.as_deref().unwrap_or(""), value, ce)
1102 }
1103 None => value.lines().map(|line| format!("{} {}{}", cs, line, ce)).join("\n"),
1104 }
1105 }
1106 Self::Definition(Definition {
1107 ident,
1108 label,
1109 url,
1110 title,
1111 ..
1112 }) => {
1113 let (us, ue) = &theme.link_url;
1114 format!(
1115 "[{}]: {}{}{}{}",
1116 label.unwrap_or(ident),
1117 us,
1118 url.to_string_with(options),
1119 ue,
1120 title
1121 .map(|title| format!(" {}", title.to_string_with(options)))
1122 .unwrap_or_default()
1123 )
1124 }
1125 Self::Delete(Delete { values, .. }) => {
1126 let (ds, de) = &theme.delete;
1127 format!("{}~~{}~~{}", ds, render_values(&values, options, theme), de)
1128 }
1129 Self::Emphasis(Emphasis { values, .. }) => {
1130 let (es, ee) = &theme.emphasis;
1131 format!("{}*{}*{}", es, render_values(&values, options, theme), ee)
1132 }
1133 Self::Footnote(Footnote { values, ident, .. }) => {
1134 format!("[^{}]: {}", ident, render_values(&values, options, theme))
1135 }
1136 Self::FootnoteRef(FootnoteRef { label, .. }) => {
1137 format!("[^{}]", label.unwrap_or_default())
1138 }
1139 Self::Heading(Heading { depth, values, .. }) => {
1140 let (hs, he) = &theme.heading;
1141 format!(
1142 "{}{} {}{}",
1143 hs,
1144 "#".repeat(depth as usize),
1145 render_values(&values, options, theme),
1146 he
1147 )
1148 }
1149 Self::Html(Html { value, .. }) => {
1150 let (hs, he) = &theme.html;
1151 format!("{}{}{}", hs, value, he)
1152 }
1153 Self::Image(Image { alt, url, title, .. }) => {
1154 let (is, ie) = &theme.image;
1155 format!(
1156 "{}{}",
1157 is,
1158 alt,
1159 url.replace(' ', "%20"),
1160 title.map(|it| format!(" \"{}\"", it)).unwrap_or_default(),
1161 ie
1162 )
1163 }
1164 Self::ImageRef(ImageRef { alt, ident, label, .. }) => {
1165 let (is, ie) = &theme.image;
1166 if alt == ident {
1167 format!("{}![{}]{}", is, ident, ie)
1168 } else {
1169 format!("{}![{}][{}]{}", is, alt, label.unwrap_or(ident), ie)
1170 }
1171 }
1172 Self::CodeInline(CodeInline { value, .. }) => {
1173 let (cs, ce) = &theme.code_inline;
1174 format!("{}`{}`{}", cs, value, ce)
1175 }
1176 Self::MathInline(MathInline { value, .. }) => {
1177 let (ms, me) = &theme.math;
1178 format!("{}${}${}", ms, value, me)
1179 }
1180 Self::Link(Link { url, title, values, .. }) => {
1181 let (ls, le) = &theme.link;
1182 format!(
1183 "{}[{}]({}{}){}",
1184 ls,
1185 render_values(&values, options, theme),
1186 url.to_string_with(options),
1187 title
1188 .map(|title| format!(" {}", title.to_string_with(options)))
1189 .unwrap_or_default(),
1190 le
1191 )
1192 }
1193 #[cfg(feature = "wikilink")]
1194 Self::WikiLink(WikiLink { target, text, .. }) => {
1195 let (ls, le) = &theme.link;
1196 match text.as_deref() {
1197 Some(t) if t != target.as_str() => format!("{}[[{}|{}]]{}", ls, target, t, le),
1198 _ => format!("{}[[{}]]{}", ls, target, le),
1199 }
1200 }
1201 Self::LinkRef(LinkRef { values, label, .. }) => {
1202 let (ls, le) = &theme.link;
1203 let ident = render_values(&values, options, theme);
1204
1205 label
1206 .map(|label| {
1207 if label == ident {
1208 format!("{}[{}]{}", ls, ident, le)
1209 } else {
1210 format!("{}[{}][{}]{}", ls, ident, label, le)
1211 }
1212 })
1213 .unwrap_or(format!("{}[{}]{}", ls, ident, le))
1214 }
1215 Self::Math(Math { value, .. }) => {
1216 let (ms, me) = &theme.math;
1217 format!("{}$$\n{}\n$${}", ms, value, me)
1218 }
1219 Self::Text(Text { value, .. }) => value,
1220 Self::MdxFlowExpression(mdx_flow_expression) => {
1221 format!("{{{}}}", mdx_flow_expression.value)
1222 }
1223 Self::MdxJsxFlowElement(mdx_jsx_flow_element) => {
1224 let name = mdx_jsx_flow_element.name.unwrap_or_default();
1225 let attributes = if mdx_jsx_flow_element.attributes.is_empty() {
1226 "".to_string()
1227 } else {
1228 format!(
1229 " {}",
1230 mdx_jsx_flow_element
1231 .attributes
1232 .into_iter()
1233 .map(Self::mdx_attribute_content_to_string)
1234 .join(" ")
1235 )
1236 };
1237
1238 if mdx_jsx_flow_element.children.is_empty() {
1239 format!("<{}{} />", name, attributes,)
1240 } else {
1241 format!(
1242 "<{}{}>{}</{}>",
1243 name,
1244 attributes,
1245 render_values(&mdx_jsx_flow_element.children, options, theme),
1246 name
1247 )
1248 }
1249 }
1250 Self::MdxJsxTextElement(mdx_jsx_text_element) => {
1251 let name = mdx_jsx_text_element.name.unwrap_or_default();
1252 let attributes = if mdx_jsx_text_element.attributes.is_empty() {
1253 "".to_string()
1254 } else {
1255 format!(
1256 " {}",
1257 mdx_jsx_text_element
1258 .attributes
1259 .into_iter()
1260 .map(Self::mdx_attribute_content_to_string)
1261 .join(" ")
1262 )
1263 };
1264
1265 if mdx_jsx_text_element.children.is_empty() {
1266 format!("<{}{} />", name, attributes,)
1267 } else {
1268 format!(
1269 "<{}{}>{}</{}>",
1270 name,
1271 attributes,
1272 render_values(&mdx_jsx_text_element.children, options, theme),
1273 name
1274 )
1275 }
1276 }
1277 Self::MdxTextExpression(mdx_text_expression) => {
1278 format!("{{{}}}", mdx_text_expression.value)
1279 }
1280 Self::MdxJsEsm(mdxjs_esm) => mdxjs_esm.value.to_string(),
1281 Self::Strong(Strong { values, .. }) => {
1282 let (ss, se) = &theme.strong;
1283 format!(
1284 "{}**{}**{}",
1285 ss,
1286 values
1287 .iter()
1288 .map(|value| value.render_with_theme(options, theme))
1289 .collect::<String>(),
1290 se
1291 )
1292 }
1293 Self::Yaml(Yaml { value, .. }) => {
1294 let (fs, fe) = &theme.frontmatter;
1295 format!("{}---\n{}\n---{}", fs, value, fe)
1296 }
1297 Self::Toml(Toml { value, .. }) => {
1298 let (fs, fe) = &theme.frontmatter;
1299 format!("{}+++\n{}\n+++{}", fs, value, fe)
1300 }
1301 Self::Break(_) => "\\".to_string(),
1302 Self::HorizontalRule(_) => {
1303 let (hs, he) = &theme.horizontal_rule;
1304 format!("{}---{}", hs, he)
1305 }
1306 Self::Fragment(Fragment { values }) => values
1307 .iter()
1308 .map(|value| value.render_with_theme(options, theme))
1309 .filter(|s| !s.is_empty())
1310 .join("\n"),
1311 Self::Empty => String::new(),
1312 }
1313 }
1314
1315 pub fn node_values(&self) -> Vec<Node> {
1316 match self.clone() {
1317 Self::Blockquote(v) => v.values,
1318 Self::Delete(v) => v.values,
1319 Self::Heading(h) => h.values,
1320 Self::Emphasis(v) => v.values,
1321 Self::List(l) => l.values,
1322 Self::Strong(v) => v.values,
1323 #[cfg(feature = "callout")]
1324 Self::Callout(v) => v.values,
1325 _ => vec![self.clone()],
1326 }
1327 }
1328
1329 pub fn find_at_index(&self, index: usize) -> Option<Node> {
1330 match self {
1331 Self::Blockquote(v) => v.values.get(index).cloned(),
1332 Self::Delete(v) => v.values.get(index).cloned(),
1333 Self::Emphasis(v) => v.values.get(index).cloned(),
1334 Self::Strong(v) => v.values.get(index).cloned(),
1335 Self::Heading(v) => v.values.get(index).cloned(),
1336 Self::List(v) => v.values.get(index).cloned(),
1337 Self::TableCell(v) => v.values.get(index).cloned(),
1338 Self::TableRow(v) => v.values.get(index).cloned(),
1339 #[cfg(feature = "callout")]
1340 Self::Callout(v) => v.values.get(index).cloned(),
1341 _ => None,
1342 }
1343 }
1344
1345 pub fn value(&self) -> String {
1346 match self.clone() {
1347 Self::Blockquote(v) => values_to_value(v.values),
1348 Self::Definition(d) => d.url.as_str().to_string(),
1349 Self::Delete(v) => values_to_value(v.values),
1350 Self::Heading(h) => values_to_value(h.values),
1351 Self::Emphasis(v) => values_to_value(v.values),
1352 Self::Footnote(f) => values_to_value(f.values),
1353 Self::FootnoteRef(f) => f.ident,
1354 Self::Html(v) => v.value,
1355 Self::Yaml(v) => v.value,
1356 Self::Toml(v) => v.value,
1357 Self::Image(i) => i.url,
1358 Self::ImageRef(i) => i.ident,
1359 Self::CodeInline(v) => v.value.to_string(),
1360 Self::MathInline(v) => v.value.to_string(),
1361 Self::Link(l) => l.url.as_str().to_string(),
1362 Self::LinkRef(l) => l.ident,
1363 #[cfg(feature = "wikilink")]
1364 Self::WikiLink(w) => w.text.unwrap_or(w.target),
1365 #[cfg(feature = "callout")]
1366 Self::Callout(v) => values_to_value(v.values),
1367 #[cfg(feature = "embed")]
1368 Self::Embed(e) => e.display.unwrap_or(e.target),
1369 Self::Math(v) => v.value,
1370 Self::List(l) => values_to_value(l.values),
1371 Self::TableCell(c) => values_to_value(c.values),
1372 Self::TableRow(c) => values_to_value(c.values),
1373 Self::Code(c) => c.value,
1374 Self::Strong(v) => values_to_value(v.values),
1375 Self::Text(t) => t.value,
1376 Self::Break { .. } => String::new(),
1377 Self::TableAlign(_) => String::new(),
1378 Self::MdxFlowExpression(mdx) => mdx.value.to_string(),
1379 Self::MdxJsxFlowElement(mdx) => values_to_value(mdx.children),
1380 Self::MdxTextExpression(mdx) => mdx.value.to_string(),
1381 Self::MdxJsxTextElement(mdx) => values_to_value(mdx.children),
1382 Self::MdxJsEsm(mdx) => mdx.value.to_string(),
1383 Self::HorizontalRule { .. } => String::new(),
1384 Self::Fragment(v) => values_to_value(v.values),
1385 Self::Empty => String::new(),
1386 }
1387 }
1388
1389 pub fn name(&self) -> SmolStr {
1390 match self {
1391 Self::Blockquote(_) => "blockquote".into(),
1392 Self::Break { .. } => "break".into(),
1393 Self::Definition(_) => "definition".into(),
1394 Self::Delete(_) => "delete".into(),
1395 Self::Heading(Heading { depth, .. }) => match depth {
1396 1 => "h1".into(),
1397 2 => "h2".into(),
1398 3 => "h3".into(),
1399 4 => "h4".into(),
1400 5 => "h5".into(),
1401 6 => "h6".into(),
1402 _ => "h".into(),
1403 },
1404 Self::Emphasis(_) => "emphasis".into(),
1405 Self::Footnote(_) => "footnote".into(),
1406 Self::FootnoteRef(_) => "footnoteref".into(),
1407 Self::Html(_) => "html".into(),
1408 Self::Yaml(_) => "yaml".into(),
1409 Self::Toml(_) => "toml".into(),
1410 Self::Image(_) => "image".into(),
1411 Self::ImageRef(_) => "image_ref".into(),
1412 Self::CodeInline(_) => "code_inline".into(),
1413 Self::MathInline(_) => "math_inline".into(),
1414 Self::Link(_) => "link".into(),
1415 Self::LinkRef(_) => "link_ref".into(),
1416 #[cfg(feature = "wikilink")]
1417 Self::WikiLink(_) => "wikilink".into(),
1418 #[cfg(feature = "callout")]
1419 Self::Callout(_) => "callout".into(),
1420 #[cfg(feature = "embed")]
1421 Self::Embed(_) => "embed".into(),
1422 Self::Math(_) => "math".into(),
1423 Self::List(_) => "list".into(),
1424 Self::TableAlign(_) => "table_align".into(),
1425 Self::TableRow(_) => "table_row".into(),
1426 Self::TableCell(_) => "table_cell".into(),
1427 Self::Code(_) => "code".into(),
1428 Self::Strong(_) => "strong".into(),
1429 Self::HorizontalRule { .. } => "Horizontal_rule".into(),
1430 Self::MdxFlowExpression(_) => "mdx_flow_expression".into(),
1431 Self::MdxJsxFlowElement(_) => "mdx_jsx_flow_element".into(),
1432 Self::MdxJsxTextElement(_) => "mdx_jsx_text_element".into(),
1433 Self::MdxTextExpression(_) => "mdx_text_expression".into(),
1434 Self::MdxJsEsm(_) => "mdx_js_esm".into(),
1435 Self::Text(_) => "text".into(),
1436 Self::Fragment(_) | Self::Empty => "".into(),
1437 }
1438 }
1439
1440 pub fn children(&self) -> Vec<Node> {
1442 match self.attr(CHILDREN) {
1443 Some(AttrValue::Array(children)) => children,
1444 _ => Vec::new(),
1445 }
1446 }
1447
1448 pub fn set_position(&mut self, pos: Option<Position>) {
1449 match self {
1450 Self::Blockquote(v) => v.position = pos,
1451 Self::Definition(d) => d.position = pos,
1452 Self::Delete(v) => v.position = pos,
1453 Self::Heading(h) => h.position = pos,
1454 Self::Emphasis(v) => v.position = pos,
1455 Self::Footnote(f) => f.position = pos,
1456 Self::FootnoteRef(f) => f.position = pos,
1457 Self::Html(v) => v.position = pos,
1458 Self::Yaml(v) => v.position = pos,
1459 Self::Toml(v) => v.position = pos,
1460 Self::Image(i) => i.position = pos,
1461 Self::ImageRef(i) => i.position = pos,
1462 Self::CodeInline(v) => v.position = pos,
1463 Self::MathInline(v) => v.position = pos,
1464 Self::Link(l) => l.position = pos,
1465 Self::LinkRef(l) => l.position = pos,
1466 #[cfg(feature = "wikilink")]
1467 Self::WikiLink(w) => w.position = pos,
1468 #[cfg(feature = "callout")]
1469 Self::Callout(c) => c.position = pos,
1470 #[cfg(feature = "embed")]
1471 Self::Embed(e) => e.position = pos,
1472 Self::Math(v) => v.position = pos,
1473 Self::Code(c) => c.position = pos,
1474 Self::TableCell(c) => c.position = pos,
1475 Self::TableRow(r) => r.position = pos,
1476 Self::TableAlign(c) => c.position = pos,
1477 Self::List(l) => l.position = pos,
1478 Self::Strong(s) => s.position = pos,
1479 Self::MdxFlowExpression(m) => m.position = pos,
1480 Self::MdxTextExpression(m) => m.position = pos,
1481 Self::MdxJsEsm(m) => m.position = pos,
1482 Self::MdxJsxFlowElement(m) => m.position = pos,
1483 Self::MdxJsxTextElement(m) => m.position = pos,
1484 Self::Break(b) => b.position = pos,
1485 Self::HorizontalRule(h) => h.position = pos,
1486 Self::Text(t) => t.position = pos,
1487 Self::Fragment(_) | Self::Empty => {}
1488 }
1489 }
1490
1491 pub fn position(&self) -> Option<Position> {
1492 match self {
1493 Self::Blockquote(v) => v.position.clone(),
1494 Self::Definition(d) => d.position.clone(),
1495 Self::Delete(v) => v.position.clone(),
1496 Self::Heading(h) => h.position.clone(),
1497 Self::Emphasis(v) => v.position.clone(),
1498 Self::Footnote(f) => f.position.clone(),
1499 Self::FootnoteRef(f) => f.position.clone(),
1500 Self::Html(v) => v.position.clone(),
1501 Self::Yaml(v) => v.position.clone(),
1502 Self::Toml(v) => v.position.clone(),
1503 Self::Image(i) => i.position.clone(),
1504 Self::ImageRef(i) => i.position.clone(),
1505 Self::CodeInline(v) => v.position.clone(),
1506 Self::MathInline(v) => v.position.clone(),
1507 Self::Link(l) => l.position.clone(),
1508 Self::LinkRef(l) => l.position.clone(),
1509 #[cfg(feature = "wikilink")]
1510 Self::WikiLink(w) => w.position.clone(),
1511 #[cfg(feature = "callout")]
1512 Self::Callout(c) => c.position.clone(),
1513 #[cfg(feature = "embed")]
1514 Self::Embed(e) => e.position.clone(),
1515 Self::Math(v) => v.position.clone(),
1516 Self::Code(c) => c.position.clone(),
1517 Self::TableCell(c) => c.position.clone(),
1518 Self::TableRow(r) => r.position.clone(),
1519 Self::TableAlign(c) => c.position.clone(),
1520 Self::List(l) => l.position.clone(),
1521 Self::Strong(s) => s.position.clone(),
1522 Self::MdxFlowExpression(m) => m.position.clone(),
1523 Self::MdxTextExpression(m) => m.position.clone(),
1524 Self::MdxJsEsm(m) => m.position.clone(),
1525 Self::MdxJsxFlowElement(m) => m.position.clone(),
1526 Self::MdxJsxTextElement(m) => m.position.clone(),
1527 Self::Break(b) => b.position.clone(),
1528 Self::Text(t) => t.position.clone(),
1529 Self::HorizontalRule(h) => h.position.clone(),
1530 Self::Fragment(v) => {
1531 let positions: Vec<Position> = v.values.iter().filter_map(|node| node.position()).collect();
1532
1533 match (positions.first(), positions.last()) {
1534 (Some(start), Some(end)) => Some(Position {
1535 start: start.start.clone(),
1536 end: end.end.clone(),
1537 }),
1538 _ => None,
1539 }
1540 }
1541 Self::Empty => None,
1542 }
1543 }
1544
1545 pub fn is_empty(&self) -> bool {
1546 matches!(self, Self::Empty)
1547 }
1548
1549 pub fn is_fragment(&self) -> bool {
1550 matches!(self, Self::Fragment(_))
1551 }
1552
1553 pub fn is_empty_fragment(&self) -> bool {
1554 if let Self::Fragment(_) = self {
1555 Self::_fragment_inner_nodes(self).is_empty()
1556 } else {
1557 false
1558 }
1559 }
1560
1561 fn _fragment_inner_nodes(node: &Node) -> Vec<Node> {
1562 if let Self::Fragment(fragment) = node {
1563 fragment.values.iter().flat_map(Self::_fragment_inner_nodes).collect()
1564 } else if node.is_empty() {
1565 Vec::new()
1566 } else {
1567 vec![node.clone()]
1568 }
1569 }
1570
1571 pub fn is_inline_code(&self) -> bool {
1572 matches!(self, Self::CodeInline(_))
1573 }
1574
1575 pub fn is_inline_math(&self) -> bool {
1576 matches!(self, Self::MathInline(_))
1577 }
1578
1579 pub fn is_strong(&self) -> bool {
1580 matches!(self, Self::Strong(_))
1581 }
1582
1583 pub fn is_list(&self) -> bool {
1584 matches!(self, Self::List(_))
1585 }
1586
1587 pub fn is_emphasis(&self) -> bool {
1588 matches!(self, Self::Emphasis(_))
1589 }
1590
1591 pub fn is_delete(&self) -> bool {
1592 matches!(self, Self::Delete(_))
1593 }
1594
1595 pub fn is_link(&self) -> bool {
1596 #[cfg(feature = "wikilink")]
1597 {
1598 matches!(self, Self::Link(_) | Self::WikiLink(_))
1599 }
1600 #[cfg(not(feature = "wikilink"))]
1601 {
1602 matches!(self, Self::Link(_))
1603 }
1604 }
1605
1606 pub fn is_link_ref(&self) -> bool {
1607 matches!(self, Self::LinkRef(_))
1608 }
1609
1610 #[cfg(feature = "wikilink")]
1612 pub fn is_wikilink(&self) -> bool {
1613 matches!(self, Self::WikiLink(_))
1614 }
1615
1616 #[cfg(feature = "callout")]
1618 pub fn is_callout(&self) -> bool {
1619 matches!(self, Self::Callout(_))
1620 }
1621
1622 #[cfg(feature = "embed")]
1624 pub fn is_embed(&self) -> bool {
1625 matches!(self, Self::Embed(_))
1626 }
1627
1628 pub fn is_text(&self) -> bool {
1629 matches!(self, Self::Text(_))
1630 }
1631
1632 pub fn is_image(&self) -> bool {
1633 matches!(self, Self::Image(_))
1634 }
1635
1636 pub fn is_horizontal_rule(&self) -> bool {
1637 matches!(self, Self::HorizontalRule { .. })
1638 }
1639
1640 pub fn is_blockquote(&self) -> bool {
1641 matches!(self, Self::Blockquote(_))
1642 }
1643
1644 pub fn is_html(&self) -> bool {
1645 matches!(self, Self::Html { .. })
1646 }
1647
1648 pub fn is_footnote(&self) -> bool {
1649 matches!(self, Self::Footnote(_))
1650 }
1651
1652 pub fn is_mdx_jsx_flow_element(&self) -> bool {
1653 matches!(self, Self::MdxJsxFlowElement(MdxJsxFlowElement { .. }))
1654 }
1655
1656 pub fn is_mdx_js_esm(&self) -> bool {
1657 matches!(self, Self::MdxJsEsm(MdxJsEsm { .. }))
1658 }
1659
1660 pub fn is_toml(&self) -> bool {
1661 matches!(self, Self::Toml { .. })
1662 }
1663
1664 pub fn is_yaml(&self) -> bool {
1665 matches!(self, Self::Yaml { .. })
1666 }
1667
1668 pub fn is_break(&self) -> bool {
1669 matches!(self, Self::Break { .. })
1670 }
1671
1672 pub fn is_mdx_text_expression(&self) -> bool {
1673 matches!(self, Self::MdxTextExpression(MdxTextExpression { .. }))
1674 }
1675
1676 pub fn is_footnote_ref(&self) -> bool {
1677 matches!(self, Self::FootnoteRef { .. })
1678 }
1679
1680 pub fn is_image_ref(&self) -> bool {
1681 matches!(self, Self::ImageRef(_))
1682 }
1683
1684 pub fn is_mdx_jsx_text_element(&self) -> bool {
1685 matches!(self, Self::MdxJsxTextElement(MdxJsxTextElement { .. }))
1686 }
1687
1688 pub fn is_math(&self) -> bool {
1689 matches!(self, Self::Math(_))
1690 }
1691
1692 pub fn is_mdx_flow_expression(&self) -> bool {
1693 matches!(self, Self::MdxFlowExpression(MdxFlowExpression { .. }))
1694 }
1695
1696 pub fn is_definition(&self) -> bool {
1697 matches!(self, Self::Definition(_))
1698 }
1699
1700 pub fn is_table_align(&self) -> bool {
1701 matches!(self, Self::TableAlign(_))
1702 }
1703
1704 pub fn is_code(&self, lang: Option<SmolStr>) -> bool {
1705 if let Self::Code(Code { lang: node_lang, .. }) = &self {
1706 if lang.is_none() {
1707 true
1708 } else {
1709 node_lang.clone().unwrap_or_default() == lang.unwrap_or_default()
1710 }
1711 } else {
1712 false
1713 }
1714 }
1715
1716 pub fn is_heading(&self, depth: Option<u8>) -> bool {
1717 if let Self::Heading(Heading {
1718 depth: heading_depth, ..
1719 }) = &self
1720 {
1721 depth.is_none() || *heading_depth == depth.unwrap()
1722 } else {
1723 false
1724 }
1725 }
1726
1727 pub fn with_value(&self, value: &str) -> Self {
1728 match self.clone() {
1729 Self::Blockquote(mut v) => {
1730 if let Some(node) = v.values.first() {
1731 v.values[0] = node.with_value(value);
1732 }
1733
1734 Self::Blockquote(v)
1735 }
1736 Self::Delete(mut v) => {
1737 if let Some(node) = v.values.first() {
1738 v.values[0] = node.with_value(value);
1739 }
1740
1741 Self::Delete(v)
1742 }
1743 Self::Emphasis(mut v) => {
1744 if let Some(node) = v.values.first() {
1745 v.values[0] = node.with_value(value);
1746 }
1747
1748 Self::Emphasis(v)
1749 }
1750 Self::Html(mut html) => {
1751 html.value = value.to_string();
1752 Self::Html(html)
1753 }
1754 Self::Yaml(mut yaml) => {
1755 yaml.value = value.to_string();
1756 Self::Yaml(yaml)
1757 }
1758 Self::Toml(mut toml) => {
1759 toml.value = value.to_string();
1760 Self::Toml(toml)
1761 }
1762 Self::CodeInline(mut code) => {
1763 code.value = value.into();
1764 Self::CodeInline(code)
1765 }
1766 Self::MathInline(mut math) => {
1767 math.value = value.into();
1768 Self::MathInline(math)
1769 }
1770 Self::Math(mut math) => {
1771 math.value = value.to_string();
1772 Self::Math(math)
1773 }
1774 Self::List(mut v) => {
1775 if let Some(node) = v.values.first() {
1776 v.values[0] = node.with_value(value);
1777 }
1778
1779 Self::List(v)
1780 }
1781 Self::TableCell(mut v) => {
1782 if let Some(node) = v.values.first() {
1783 v.values[0] = node.with_value(value);
1784 }
1785
1786 Self::TableCell(v)
1787 }
1788 Self::TableRow(mut row) => {
1789 row.values = row
1790 .values
1791 .iter()
1792 .zip(value.split(","))
1793 .map(|(cell, value)| cell.with_value(value))
1794 .collect::<Vec<_>>();
1795
1796 Self::TableRow(row)
1797 }
1798 Self::Strong(mut v) => {
1799 if let Some(node) = v.values.first() {
1800 v.values[0] = node.with_value(value);
1801 }
1802
1803 Self::Strong(v)
1804 }
1805 Self::Code(mut code) => {
1806 code.value = value.to_string();
1807 Self::Code(code)
1808 }
1809 Self::Image(mut image) => {
1810 image.url = value.to_string();
1811 Self::Image(image)
1812 }
1813 Self::ImageRef(mut image) => {
1814 image.ident = value.to_string();
1815 image.label = Some(value.to_string());
1816 Self::ImageRef(image)
1817 }
1818 Self::Link(mut link) => {
1819 link.url = Url(value.to_string());
1820 Self::Link(link)
1821 }
1822 Self::LinkRef(mut v) => {
1823 v.label = Some(value.to_string());
1824 v.ident = value.to_string();
1825 Self::LinkRef(v)
1826 }
1827 Self::Footnote(mut footnote) => {
1828 footnote.ident = value.to_string();
1829 Self::Footnote(footnote)
1830 }
1831 Self::FootnoteRef(mut footnote) => {
1832 footnote.ident = value.to_string();
1833 footnote.label = Some(value.to_string());
1834 Self::FootnoteRef(footnote)
1835 }
1836 Self::Heading(mut v) => {
1837 if let Some(node) = v.values.first() {
1838 v.values[0] = node.with_value(value);
1839 }
1840
1841 Self::Heading(v)
1842 }
1843 Self::Definition(mut def) => {
1844 def.url = Url(value.to_string());
1845 Self::Definition(def)
1846 }
1847 node @ Self::Break { .. } => node,
1848 node @ Self::TableAlign(_) => node,
1849 node @ Self::HorizontalRule { .. } => node,
1850 Self::Text(mut text) => {
1851 text.value = value.to_string();
1852 Self::Text(text)
1853 }
1854 Self::MdxFlowExpression(mut mdx) => {
1855 mdx.value = value.into();
1856 Self::MdxFlowExpression(mdx)
1857 }
1858 Self::MdxTextExpression(mut mdx) => {
1859 mdx.value = value.into();
1860 Self::MdxTextExpression(mdx)
1861 }
1862 Self::MdxJsEsm(mut mdx) => {
1863 mdx.value = value.into();
1864 Self::MdxJsEsm(mdx)
1865 }
1866 Self::MdxJsxFlowElement(mut mdx) => {
1867 if let Some(node) = mdx.children.first() {
1868 mdx.children[0] = node.with_value(value);
1869 }
1870
1871 Self::MdxJsxFlowElement(MdxJsxFlowElement {
1872 name: mdx.name,
1873 attributes: mdx.attributes,
1874 children: mdx.children,
1875 ..mdx
1876 })
1877 }
1878 Self::MdxJsxTextElement(mut mdx) => {
1879 if let Some(node) = mdx.children.first() {
1880 mdx.children[0] = node.with_value(value);
1881 }
1882
1883 Self::MdxJsxTextElement(MdxJsxTextElement {
1884 name: mdx.name,
1885 attributes: mdx.attributes,
1886 children: mdx.children,
1887 ..mdx
1888 })
1889 }
1890 node @ Self::Fragment(_) | node @ Self::Empty => node,
1891 #[cfg(feature = "wikilink")]
1892 Self::WikiLink(mut w) => {
1893 if w.text.is_some() {
1894 w.text = Some(value.to_string());
1895 } else {
1896 w.target = value.to_string();
1897 }
1898 Self::WikiLink(w)
1899 }
1900 #[cfg(feature = "callout")]
1901 Self::Callout(mut c) => {
1902 if let Some(node) = c.values.first() {
1903 c.values[0] = node.with_value(value);
1904 }
1905 Self::Callout(c)
1906 }
1907 #[cfg(feature = "embed")]
1908 Self::Embed(mut e) => {
1909 if e.display.is_some() {
1910 e.display = Some(value.to_string());
1911 } else {
1912 e.target = value.to_string();
1913 }
1914 Self::Embed(e)
1915 }
1916 }
1917 }
1918
1919 pub fn with_children_value(&self, value: &str, index: usize) -> Self {
1920 match self.clone() {
1921 Self::Blockquote(mut v) => {
1922 if v.values.get(index).is_some() {
1923 v.values[index] = v.values[index].with_value(value);
1924 }
1925
1926 Self::Blockquote(v)
1927 }
1928 Self::Delete(mut v) => {
1929 if v.values.get(index).is_some() {
1930 v.values[index] = v.values[index].with_value(value);
1931 }
1932
1933 Self::Delete(v)
1934 }
1935 Self::Emphasis(mut v) => {
1936 if v.values.get(index).is_some() {
1937 v.values[index] = v.values[index].with_value(value);
1938 }
1939
1940 Self::Emphasis(v)
1941 }
1942 Self::List(mut v) => {
1943 if v.values.get(index).is_some() {
1944 v.values[index] = v.values[index].with_value(value);
1945 }
1946
1947 Self::List(v)
1948 }
1949 Self::TableCell(mut v) => {
1950 if v.values.get(index).is_some() {
1951 v.values[index] = v.values[index].with_value(value);
1952 }
1953
1954 Self::TableCell(v)
1955 }
1956 Self::Strong(mut v) => {
1957 if v.values.get(index).is_some() {
1958 v.values[index] = v.values[index].with_value(value);
1959 }
1960
1961 Self::Strong(v)
1962 }
1963 Self::LinkRef(mut v) => {
1964 if v.values.get(index).is_some() {
1965 v.values[index] = v.values[index].with_value(value);
1966 }
1967
1968 Self::LinkRef(v)
1969 }
1970 Self::Heading(mut v) => {
1971 if v.values.get(index).is_some() {
1972 v.values[index] = v.values[index].with_value(value);
1973 }
1974
1975 Self::Heading(v)
1976 }
1977 Self::MdxJsxFlowElement(mut mdx) => {
1978 if let Some(node) = mdx.children.first() {
1979 mdx.children[index] = node.with_value(value);
1980 }
1981
1982 Self::MdxJsxFlowElement(MdxJsxFlowElement {
1983 name: mdx.name,
1984 attributes: mdx.attributes,
1985 children: mdx.children,
1986 ..mdx
1987 })
1988 }
1989 Self::MdxJsxTextElement(mut mdx) => {
1990 if let Some(node) = mdx.children.first() {
1991 mdx.children[index] = node.with_value(value);
1992 }
1993
1994 Self::MdxJsxTextElement(MdxJsxTextElement {
1995 name: mdx.name,
1996 attributes: mdx.attributes,
1997 children: mdx.children,
1998 ..mdx
1999 })
2000 }
2001 #[cfg(feature = "wikilink")]
2002 a @ Self::WikiLink(_) => a,
2003 #[cfg(feature = "callout")]
2004 Self::Callout(mut c) => {
2005 if c.values.get(index).is_some() {
2006 c.values[index] = c.values[index].with_value(value);
2007 }
2008 Self::Callout(c)
2009 }
2010 #[cfg(feature = "embed")]
2011 a @ Self::Embed(_) => a,
2012 a => a,
2013 }
2014 }
2015
2016 pub fn attr(&self, attr: &str) -> Option<AttrValue> {
2018 match self {
2019 Node::Footnote(Footnote { ident, values, .. }) => match attr {
2020 attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
2021 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2022 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2023 _ => None,
2024 },
2025 Node::Html(Html { value, .. }) => match attr {
2026 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2027 _ => None,
2028 },
2029 Node::Text(Text { value, .. }) => match attr {
2030 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2031 _ => None,
2032 },
2033 Node::Code(Code {
2034 value,
2035 lang,
2036 meta,
2037 fence,
2038 ..
2039 }) => match attr {
2040 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2041 attr_keys::LANG => lang.clone().map(AttrValue::String),
2042 attr_keys::META => meta.clone().map(AttrValue::String),
2043 attr_keys::FENCE => Some(AttrValue::Boolean(*fence)),
2044 _ => None,
2045 },
2046 Node::CodeInline(CodeInline { value, .. }) => match attr {
2047 attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
2048 _ => None,
2049 },
2050 Node::MathInline(MathInline { value, .. }) => match attr {
2051 attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
2052 _ => None,
2053 },
2054 Node::Math(Math { value, .. }) => match attr {
2055 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2056 _ => None,
2057 },
2058 Node::Yaml(Yaml { value, .. }) => match attr {
2059 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2060 _ => None,
2061 },
2062 Node::Toml(Toml { value, .. }) => match attr {
2063 attr_keys::VALUE => Some(AttrValue::String(value.clone())),
2064 _ => None,
2065 },
2066 Node::Image(Image { alt, url, title, .. }) => match attr {
2067 attr_keys::ALT => Some(AttrValue::String(alt.clone())),
2068 attr_keys::URL => Some(AttrValue::String(url.clone())),
2069 attr_keys::TITLE => title.clone().map(AttrValue::String),
2070 _ => None,
2071 },
2072 Node::ImageRef(ImageRef { alt, ident, label, .. }) => match attr {
2073 attr_keys::ALT => Some(AttrValue::String(alt.clone())),
2074 attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
2075 attr_keys::LABEL => label.clone().map(AttrValue::String),
2076 _ => None,
2077 },
2078 Node::Link(Link { url, title, values, .. }) => match attr {
2079 attr_keys::URL => Some(AttrValue::String(url.as_str().to_string())),
2080 attr_keys::TITLE => title.as_ref().map(|t| AttrValue::String(t.to_value())),
2081 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2082 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2083 _ => None,
2084 },
2085 #[cfg(feature = "wikilink")]
2086 Node::WikiLink(WikiLink { target, text, .. }) => match attr {
2087 attr_keys::URL => Some(AttrValue::String(target.clone())),
2088 attr_keys::VALUE => Some(AttrValue::String(text.clone().unwrap_or_else(|| target.clone()))),
2089 _ => None,
2090 },
2091 #[cfg(feature = "callout")]
2092 Node::Callout(Callout {
2093 kind, title, values, ..
2094 }) => match attr {
2095 attr_keys::KIND => Some(AttrValue::String(kind.clone())),
2096 attr_keys::TITLE => title.clone().map(AttrValue::String),
2097 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2098 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2099 _ => None,
2100 },
2101 #[cfg(feature = "embed")]
2102 Node::Embed(Embed { target, display, .. }) => match attr {
2103 attr_keys::URL => Some(AttrValue::String(target.clone())),
2104 attr_keys::VALUE => Some(AttrValue::String(display.clone().unwrap_or_else(|| target.clone()))),
2105 _ => None,
2106 },
2107 Node::LinkRef(LinkRef { ident, label, .. }) => match attr {
2108 attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
2109 attr_keys::LABEL => label.clone().map(AttrValue::String),
2110 _ => None,
2111 },
2112 Node::FootnoteRef(FootnoteRef { ident, label, .. }) => match attr {
2113 attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
2114 attr_keys::LABEL => label.clone().map(AttrValue::String),
2115 _ => None,
2116 },
2117 Node::Definition(Definition {
2118 ident,
2119 url,
2120 title,
2121 label,
2122 ..
2123 }) => match attr {
2124 attr_keys::IDENT => Some(AttrValue::String(ident.clone())),
2125 attr_keys::URL => Some(AttrValue::String(url.as_str().to_string())),
2126 attr_keys::TITLE => title.as_ref().map(|t| AttrValue::String(t.to_value())),
2127 attr_keys::LABEL => label.clone().map(AttrValue::String),
2128 _ => None,
2129 },
2130 Node::Heading(Heading { depth, values, .. }) => match attr {
2131 attr_keys::DEPTH | attr_keys::LEVEL => Some(AttrValue::Integer(*depth as i64)),
2132 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2133 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2134 _ => None,
2135 },
2136 Node::List(List {
2137 index,
2138 level,
2139 ordered,
2140 checked,
2141 values,
2142 ..
2143 }) => match attr {
2144 attr_keys::INDEX => Some(AttrValue::Integer(*index as i64)),
2145 attr_keys::LEVEL => Some(AttrValue::Integer(*level as i64)),
2146 attr_keys::ORDERED => Some(AttrValue::Boolean(*ordered)),
2147 attr_keys::CHECKED => checked.map(AttrValue::Boolean),
2148 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2149 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2150 _ => None,
2151 },
2152 Node::TableCell(TableCell {
2153 column, row, values, ..
2154 }) => match attr {
2155 attr_keys::COLUMN => Some(AttrValue::Integer(*column as i64)),
2156 attr_keys::ROW => Some(AttrValue::Integer(*row as i64)),
2157 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2158 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2159 _ => None,
2160 },
2161 Node::TableAlign(TableAlign { align, .. }) => match attr {
2162 attr_keys::ALIGN => Some(AttrValue::String(
2163 align.iter().map(|a| a.to_string()).collect::<Vec<_>>().join(","),
2164 )),
2165 _ => None,
2166 },
2167 Node::MdxFlowExpression(MdxFlowExpression { value, .. }) => match attr {
2168 attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
2169 _ => None,
2170 },
2171 Node::MdxTextExpression(MdxTextExpression { value, .. }) => match attr {
2172 attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
2173 _ => None,
2174 },
2175 Node::MdxJsEsm(MdxJsEsm { value, .. }) => match attr {
2176 attr_keys::VALUE => Some(AttrValue::String(value.to_string())),
2177 _ => None,
2178 },
2179 Node::MdxJsxFlowElement(MdxJsxFlowElement { name, .. }) => match attr {
2180 attr_keys::NAME => name.clone().map(AttrValue::String),
2181 _ => None,
2182 },
2183 Node::MdxJsxTextElement(MdxJsxTextElement { name, .. }) => match attr {
2184 attr_keys::NAME => name.as_ref().map(|n| AttrValue::String(n.to_string())),
2185 _ => None,
2186 },
2187 Node::Strong(Strong { values, .. })
2188 | Node::Blockquote(Blockquote { values, .. })
2189 | Node::Delete(Delete { values, .. })
2190 | Node::Emphasis(Emphasis { values, .. })
2191 | Node::TableRow(TableRow { values, .. })
2192 | Node::Fragment(Fragment { values, .. }) => match attr {
2193 attr_keys::VALUE => Some(AttrValue::String(values_to_string(values, &RenderOptions::default()))),
2194 attr_keys::VALUES | attr_keys::CHILDREN => Some(AttrValue::Array(values.clone())),
2195 _ => None,
2196 },
2197 Node::Break(_) | Node::HorizontalRule(_) | Node::Empty => None,
2198 }
2199 }
2200
2201 pub fn set_attr(&mut self, attr: &str, value: impl Into<AttrValue>) {
2203 let value = value.into();
2204 let value_str = value.as_string();
2205
2206 match self {
2207 Node::Footnote(f) => {
2208 if attr == attr_keys::IDENT {
2209 f.ident = value_str;
2210 }
2211 }
2212 Node::Html(h) => {
2213 if attr == attr_keys::VALUE {
2214 h.value = value_str;
2215 }
2216 }
2217 Node::Text(t) => {
2218 if attr == attr_keys::VALUE {
2219 t.value = value_str;
2220 }
2221 }
2222 Node::Code(c) => match attr {
2223 attr_keys::VALUE => {
2224 c.value = value_str;
2225 }
2226 attr_keys::LANG | "language" => {
2227 c.lang = if value_str.is_empty() { None } else { Some(value_str) };
2228 }
2229 attr_keys::META => {
2230 c.meta = if value_str.is_empty() { None } else { Some(value_str) };
2231 }
2232 attr_keys::FENCE => {
2233 c.fence = match value {
2234 AttrValue::Boolean(b) => b,
2235 _ => value_str == "true",
2236 };
2237 }
2238 _ => (),
2239 },
2240 Node::CodeInline(ci) => {
2241 if attr == attr_keys::VALUE {
2242 ci.value = value_str.into();
2243 }
2244 }
2245 Node::MathInline(mi) => {
2246 if attr == attr_keys::VALUE {
2247 mi.value = value_str.into();
2248 }
2249 }
2250 Node::Math(m) => {
2251 if attr == attr_keys::VALUE {
2252 m.value = value_str;
2253 }
2254 }
2255 Node::Yaml(y) => {
2256 if attr == attr_keys::VALUE {
2257 y.value = value_str;
2258 }
2259 }
2260 Node::Toml(t) => {
2261 if attr == attr_keys::VALUE {
2262 t.value = value_str;
2263 }
2264 }
2265 Node::Image(i) => match attr {
2266 attr_keys::ALT => {
2267 i.alt = value_str;
2268 }
2269 attr_keys::URL => {
2270 i.url = value_str;
2271 }
2272 attr_keys::TITLE => {
2273 i.title = if value_str.is_empty() { None } else { Some(value_str) };
2274 }
2275 _ => (),
2276 },
2277 Node::ImageRef(i) => match attr {
2278 attr_keys::ALT => {
2279 i.alt = value_str;
2280 }
2281 attr_keys::IDENT => {
2282 i.ident = value_str;
2283 }
2284 attr_keys::LABEL => {
2285 i.label = if value_str.is_empty() { None } else { Some(value_str) };
2286 }
2287 _ => (),
2288 },
2289 Node::Link(l) => match attr {
2290 attr_keys::URL => {
2291 l.url = Url::new(value_str);
2292 }
2293 attr_keys::TITLE => {
2294 l.title = if value_str.is_empty() {
2295 None
2296 } else {
2297 Some(Title::new(value_str))
2298 };
2299 }
2300 _ => (),
2301 },
2302 Node::LinkRef(l) => match attr {
2303 attr_keys::IDENT => {
2304 l.ident = value_str;
2305 }
2306 attr_keys::LABEL => {
2307 l.label = if value_str.is_empty() { None } else { Some(value_str) };
2308 }
2309 _ => (),
2310 },
2311 Node::FootnoteRef(f) => match attr {
2312 attr_keys::IDENT => {
2313 f.ident = value_str;
2314 }
2315 attr_keys::LABEL => {
2316 f.label = if value_str.is_empty() { None } else { Some(value_str) };
2317 }
2318 _ => (),
2319 },
2320 Node::Definition(d) => match attr {
2321 attr_keys::IDENT => {
2322 d.ident = value_str;
2323 }
2324 attr_keys::URL => {
2325 d.url = Url::new(value_str);
2326 }
2327 attr_keys::TITLE => {
2328 d.title = if value_str.is_empty() {
2329 None
2330 } else {
2331 Some(Title::new(value_str))
2332 };
2333 }
2334 attr_keys::LABEL => {
2335 d.label = if value_str.is_empty() { None } else { Some(value_str) };
2336 }
2337 _ => (),
2338 },
2339 Node::Heading(h) => match attr {
2340 attr_keys::DEPTH | attr_keys::LEVEL => {
2341 h.depth = match value {
2342 AttrValue::Integer(i) => i as u8,
2343 _ => value_str.parse::<u8>().unwrap_or(h.depth),
2344 };
2345 }
2346 _ => (),
2347 },
2348 Node::List(l) => match attr {
2349 attr_keys::INDEX => {
2350 l.index = match value {
2351 AttrValue::Integer(i) => i as usize,
2352 _ => value_str.parse::<usize>().unwrap_or(l.index),
2353 };
2354 }
2355 attr_keys::LEVEL => {
2356 l.level = match value {
2357 AttrValue::Integer(i) => i as u8,
2358 _ => value_str.parse::<u8>().unwrap_or(l.level),
2359 };
2360 }
2361 attr_keys::ORDERED => {
2362 l.ordered = match value {
2363 AttrValue::Boolean(b) => b,
2364 _ => value_str == "true",
2365 };
2366 }
2367 attr_keys::CHECKED => {
2368 l.checked = if value_str.is_empty() {
2369 None
2370 } else {
2371 Some(match value {
2372 AttrValue::Boolean(b) => b,
2373 _ => value_str == "true",
2374 })
2375 };
2376 }
2377 _ => (),
2378 },
2379 Node::TableCell(c) => match attr {
2380 attr_keys::COLUMN => {
2381 c.column = match value {
2382 AttrValue::Integer(i) => i as usize,
2383 _ => value_str.parse::<usize>().unwrap_or(c.column),
2384 };
2385 }
2386 attr_keys::ROW => {
2387 c.row = match value {
2388 AttrValue::Integer(i) => i as usize,
2389 _ => value_str.parse::<usize>().unwrap_or(c.row),
2390 };
2391 }
2392 _ => (),
2393 },
2394 Node::TableAlign(th) => {
2395 if attr == attr_keys::ALIGN {
2396 th.align = value_str.split(',').map(|s| s.trim().into()).collect();
2397 }
2398 }
2399 Node::MdxFlowExpression(m) => {
2400 if attr == attr_keys::VALUE {
2401 m.value = value_str.into();
2402 }
2403 }
2404 Node::MdxTextExpression(m) => {
2405 if attr == attr_keys::VALUE {
2406 m.value = value_str.into();
2407 }
2408 }
2409 Node::MdxJsEsm(m) => {
2410 if attr == attr_keys::VALUE {
2411 m.value = value_str.into();
2412 }
2413 }
2414 Node::MdxJsxFlowElement(m) => {
2415 if attr == attr_keys::NAME {
2416 m.name = if value_str.is_empty() { None } else { Some(value_str) };
2417 }
2418 }
2419 Node::MdxJsxTextElement(m) => {
2420 if attr == attr_keys::NAME {
2421 m.name = if value_str.is_empty() {
2422 None
2423 } else {
2424 Some(value_str.into())
2425 };
2426 }
2427 }
2428 Node::Delete(_)
2429 | Node::Blockquote(_)
2430 | Node::Emphasis(_)
2431 | Node::Strong(_)
2432 | Node::TableRow(_)
2433 | Node::Break(_)
2434 | Node::HorizontalRule(_)
2435 | Node::Fragment(_)
2436 | Node::Empty => (),
2437 #[cfg(feature = "wikilink")]
2438 Node::WikiLink(w) => match attr {
2439 attr_keys::URL => w.target = value_str,
2440 attr_keys::VALUE => w.text = if value_str.is_empty() { None } else { Some(value_str) },
2441 _ => (),
2442 },
2443 #[cfg(feature = "callout")]
2444 Node::Callout(c) => match attr {
2445 attr_keys::KIND => c.kind = value_str,
2446 attr_keys::TITLE => c.title = if value_str.is_empty() { None } else { Some(value_str) },
2447 _ => (),
2448 },
2449 #[cfg(feature = "embed")]
2450 Node::Embed(e) => match attr {
2451 attr_keys::URL => e.target = value_str,
2452 attr_keys::VALUE => e.display = if value_str.is_empty() { None } else { Some(value_str) },
2453 _ => (),
2454 },
2455 }
2456 }
2457
2458 #[cfg(feature = "callout")]
2463 fn try_parse_callout(values: Vec<Node>, position: Option<Position>) -> Node {
2464 if !matches!(values.first(), Some(Node::Text(t)) if t.value.starts_with("[!")) {
2466 return Node::Blockquote(Blockquote { values, position });
2467 }
2468
2469 let mut iter = values.into_iter();
2470 let first_text = match iter.next().unwrap() {
2472 Node::Text(t) => t.value,
2473 _ => unreachable!(),
2474 };
2475
2476 let Some(rest) = first_text.strip_prefix("[!") else {
2477 unreachable!()
2478 };
2479
2480 let Some(bracket_end) = rest.find(']') else {
2481 let mut values = vec![Node::Text(Text {
2483 value: first_text,
2484 position: None,
2485 })];
2486 values.extend(iter);
2487 return Node::Blockquote(Blockquote { values, position });
2488 };
2489
2490 let kind = rest[..bracket_end].to_string();
2491 let after_bracket = &rest[bracket_end + 1..];
2492
2493 let (title, remaining_body) = if let Some(nl) = after_bracket.find('\n') {
2494 let t = after_bracket[..nl].trim();
2495 (
2496 if t.is_empty() { None } else { Some(t.to_string()) },
2497 after_bracket[nl + 1..].to_string(),
2498 )
2499 } else {
2500 let t = after_bracket.trim();
2501 (if t.is_empty() { None } else { Some(t.to_string()) }, String::new())
2502 };
2503
2504 let body = if !remaining_body.trim().is_empty() {
2505 let rest_nodes: Vec<Node> = iter.collect();
2506 let mut b = Vec::with_capacity(rest_nodes.len() + 1);
2507 b.push(Node::Text(Text {
2508 value: remaining_body,
2509 position: None,
2510 }));
2511 b.extend(rest_nodes);
2512 b
2513 } else {
2514 iter.collect()
2515 };
2516
2517 Node::Callout(Callout {
2518 kind,
2519 title,
2520 values: body,
2521 position,
2522 })
2523 }
2524
2525 #[cfg(any(feature = "embed", feature = "wikilink"))]
2528 fn values_contain_links(values: &[Node]) -> bool {
2529 values.iter().any(|n| match n {
2530 Node::Text(t) => t.value.contains("[["),
2531 Node::Heading(h) => Self::values_contain_links(&h.values),
2532 Node::Blockquote(b) => Self::values_contain_links(&b.values),
2533 #[cfg(feature = "callout")]
2534 Node::Callout(c) => Self::values_contain_links(&c.values),
2535 Node::List(l) => Self::values_contain_links(&l.values),
2536 Node::Strong(s) => Self::values_contain_links(&s.values),
2537 Node::Emphasis(e) => Self::values_contain_links(&e.values),
2538 Node::Delete(d) => Self::values_contain_links(&d.values),
2539 Node::TableCell(tc) => Self::values_contain_links(&tc.values),
2540 Node::TableRow(tr) => Self::values_contain_links(&tr.values),
2541 Node::Footnote(f) => Self::values_contain_links(&f.values),
2542 _ => false,
2543 })
2544 }
2545
2546 #[cfg(any(feature = "embed", feature = "wikilink"))]
2549 fn expand_with(nodes: Vec<Node>, parse_into: fn(&str, Option<Position>, &mut Vec<Node>)) -> Vec<Node> {
2550 let mut result = Vec::with_capacity(nodes.len());
2551 for node in nodes {
2552 Self::expand_with_into(node, &mut result, parse_into);
2553 }
2554 result
2555 }
2556
2557 #[cfg(any(feature = "embed", feature = "wikilink"))]
2558 fn expand_with_into(node: Node, out: &mut Vec<Node>, parse_into: fn(&str, Option<Position>, &mut Vec<Node>)) {
2559 match node {
2560 Node::Text(Text { ref value, .. }) if !value.contains("[[") => out.push(node),
2561 Node::Text(Text { value, position }) => parse_into(&value, position, out),
2562 Node::Heading(mut h) => {
2563 if Self::values_contain_links(&h.values) {
2564 h.values = Self::expand_with(h.values, parse_into);
2565 }
2566 out.push(Node::Heading(h));
2567 }
2568 Node::Blockquote(mut b) => {
2569 if Self::values_contain_links(&b.values) {
2570 b.values = Self::expand_with(b.values, parse_into);
2571 }
2572 out.push(Node::Blockquote(b));
2573 }
2574 #[cfg(feature = "callout")]
2575 Node::Callout(mut c) => {
2576 if Self::values_contain_links(&c.values) {
2577 c.values = Self::expand_with(c.values, parse_into);
2578 }
2579 out.push(Node::Callout(c));
2580 }
2581 Node::List(mut l) => {
2582 if Self::values_contain_links(&l.values) {
2583 l.values = Self::expand_with(l.values, parse_into);
2584 }
2585 out.push(Node::List(l));
2586 }
2587 Node::Strong(mut s) => {
2588 if Self::values_contain_links(&s.values) {
2589 s.values = Self::expand_with(s.values, parse_into);
2590 }
2591 out.push(Node::Strong(s));
2592 }
2593 Node::Emphasis(mut e) => {
2594 if Self::values_contain_links(&e.values) {
2595 e.values = Self::expand_with(e.values, parse_into);
2596 }
2597 out.push(Node::Emphasis(e));
2598 }
2599 Node::Delete(mut d) => {
2600 if Self::values_contain_links(&d.values) {
2601 d.values = Self::expand_with(d.values, parse_into);
2602 }
2603 out.push(Node::Delete(d));
2604 }
2605 Node::TableCell(mut tc) => {
2606 if Self::values_contain_links(&tc.values) {
2607 tc.values = Self::expand_with(tc.values, parse_into);
2608 }
2609 out.push(Node::TableCell(tc));
2610 }
2611 Node::TableRow(mut tr) => {
2612 if Self::values_contain_links(&tr.values) {
2613 tr.values = Self::expand_with(tr.values, parse_into);
2614 }
2615 out.push(Node::TableRow(tr));
2616 }
2617 Node::Footnote(mut f) => {
2618 if Self::values_contain_links(&f.values) {
2619 f.values = Self::expand_with(f.values, parse_into);
2620 }
2621 out.push(Node::Footnote(f));
2622 }
2623 other => out.push(other),
2624 }
2625 }
2626
2627 #[cfg(feature = "embed")]
2630 pub fn expand_embeds(nodes: Vec<Node>) -> Vec<Node> {
2631 Self::expand_with(nodes, Self::parse_embeds_into)
2632 }
2633
2634 #[cfg(feature = "embed")]
2637 fn parse_embeds_into(text: &str, position: Option<Position>, out: &mut Vec<Node>) {
2638 let mut last_end = 0;
2639 let mut search_from = 0;
2640 let mut found_any = false;
2641
2642 while let Some(bang_rel) = text[search_from..].find("![[") {
2643 let bang = search_from + bang_rel;
2644 let inner_start = bang + 3;
2645
2646 let Some(close_rel) = text[inner_start..].find("]]") else {
2647 search_from = bang + 1;
2648 continue;
2649 };
2650 let close = inner_start + close_rel;
2651 let content = &text[inner_start..close];
2652
2653 if content.contains('[') || content.contains(']') {
2654 search_from = bang + 1;
2655 continue;
2656 }
2657
2658 found_any = true;
2659 if last_end < bang {
2660 out.push(Node::Text(Text {
2661 value: text[last_end..bang].to_string(),
2662 position: position.clone(),
2663 }));
2664 }
2665 let (target, display) = if let Some(pipe) = content.find('|') {
2666 (
2667 content[..pipe].trim().to_string(),
2668 Some(content[pipe + 1..].trim().to_string()),
2669 )
2670 } else {
2671 (content.trim().to_string(), None)
2672 };
2673 out.push(Node::Embed(Embed {
2674 target,
2675 display,
2676 position: position.clone(),
2677 }));
2678 last_end = close + 2;
2679 search_from = close + 2;
2680 }
2681
2682 if !found_any {
2683 out.push(Node::Text(Text {
2684 value: text.to_string(),
2685 position,
2686 }));
2687 return;
2688 }
2689
2690 if last_end < text.len() {
2691 out.push(Node::Text(Text {
2692 value: text[last_end..].to_string(),
2693 position,
2694 }));
2695 }
2696 }
2697
2698 #[cfg(feature = "wikilink")]
2701 pub fn expand_wikilinks(nodes: Vec<Node>) -> Vec<Node> {
2702 Self::expand_with(nodes, Self::parse_wikilinks_into)
2703 }
2704
2705 #[cfg(feature = "wikilink")]
2708 fn parse_wikilinks_into(text: &str, position: Option<Position>, out: &mut Vec<Node>) {
2709 let mut last_end = 0;
2710 let mut search_from = 0;
2711 let mut found_any = false;
2712
2713 while let Some(open_rel) = text[search_from..].find("[[") {
2714 let open = search_from + open_rel;
2715 let inner_start = open + 2;
2716
2717 let Some(close_rel) = text[inner_start..].find("]]") else {
2718 break;
2719 };
2720 let close = inner_start + close_rel;
2721 let content = &text[inner_start..close];
2722
2723 if content.contains('[') || content.contains(']') {
2724 search_from = open + 1;
2726 continue;
2727 }
2728
2729 found_any = true;
2730 if last_end < open {
2731 out.push(Node::Text(Text {
2732 value: text[last_end..open].to_string(),
2733 position: position.clone(),
2734 }));
2735 }
2736 let (target, text_part) = if let Some(pipe) = content.find('|') {
2737 (content[..pipe].trim(), Some(content[pipe + 1..].trim()))
2738 } else {
2739 (content.trim(), None)
2740 };
2741 out.push(Node::WikiLink(WikiLink {
2742 target: target.to_string(),
2743 text: text_part.map(|t| t.to_string()),
2744 position: position.clone(),
2745 }));
2746 last_end = close + 2;
2747 search_from = close + 2;
2748 }
2749
2750 if !found_any {
2751 out.push(Node::Text(Text {
2752 value: text.to_string(),
2753 position,
2754 }));
2755 return;
2756 }
2757
2758 if last_end < text.len() {
2759 out.push(Node::Text(Text {
2760 value: text[last_end..].to_string(),
2761 position,
2762 }));
2763 }
2764 }
2765
2766 #[cfg(all(feature = "wikilink", test))]
2769 fn parse_wikilinks_in_text(text: &str, position: Option<Position>) -> Vec<Node> {
2770 let mut result = Vec::new();
2771 Self::parse_wikilinks_into(text, position, &mut result);
2772 result
2773 }
2774
2775 #[cfg(all(feature = "embed", feature = "wikilink"))]
2780 pub fn expand_inline_links(nodes: Vec<Node>) -> Vec<Node> {
2781 Self::expand_with(nodes, Self::parse_inline_links_into)
2782 }
2783
2784 #[cfg(all(feature = "embed", feature = "wikilink"))]
2788 fn parse_inline_links_into(text: &str, position: Option<Position>, out: &mut Vec<Node>) {
2789 let mut last_end = 0;
2790 let mut search_from = 0;
2791 let mut found_any = false;
2792
2793 while let Some(open_rel) = text[search_from..].find("[[") {
2794 let open = search_from + open_rel;
2795 let inner_start = open + 2;
2796
2797 let Some(close_rel) = text[inner_start..].find("]]") else {
2798 break;
2799 };
2800 let close = inner_start + close_rel;
2801 let content = &text[inner_start..close];
2802
2803 if content.contains('[') || content.contains(']') {
2804 search_from = open + 1;
2805 continue;
2806 }
2807
2808 let is_embed = text.as_bytes()[..open].last() == Some(&b'!');
2809 let token_start = if is_embed { open - 1 } else { open };
2810
2811 found_any = true;
2812 if last_end < token_start {
2813 out.push(Node::Text(Text {
2814 value: text[last_end..token_start].to_string(),
2815 position: position.clone(),
2816 }));
2817 }
2818
2819 let (target, opt_part) = if let Some(pipe) = content.find('|') {
2820 (content[..pipe].trim(), Some(content[pipe + 1..].trim()))
2821 } else {
2822 (content.trim(), None)
2823 };
2824
2825 if is_embed {
2826 out.push(Node::Embed(Embed {
2827 target: target.to_string(),
2828 display: opt_part.map(|s| s.to_string()),
2829 position: position.clone(),
2830 }));
2831 } else {
2832 out.push(Node::WikiLink(WikiLink {
2833 target: target.to_string(),
2834 text: opt_part.map(|s| s.to_string()),
2835 position: position.clone(),
2836 }));
2837 }
2838
2839 last_end = close + 2;
2840 search_from = close + 2;
2841 }
2842
2843 if !found_any {
2844 out.push(Node::Text(Text {
2845 value: text.to_string(),
2846 position,
2847 }));
2848 return;
2849 }
2850
2851 if last_end < text.len() {
2852 out.push(Node::Text(Text {
2853 value: text[last_end..].to_string(),
2854 position,
2855 }));
2856 }
2857 }
2858
2859 pub(crate) fn from_mdast_node(node: mdast::Node) -> Vec<Node> {
2860 match node.clone() {
2861 mdast::Node::Root(root) => root
2862 .children
2863 .into_iter()
2864 .flat_map(Self::from_mdast_node)
2865 .collect::<Vec<_>>(),
2866 mdast::Node::ListItem(list_item) => list_item
2867 .children
2868 .into_iter()
2869 .flat_map(Self::from_mdast_node)
2870 .collect::<Vec<_>>(),
2871 mdast::Node::List(list) => Self::mdast_list_items(&list, 0),
2872 mdast::Node::Table(table) => table
2873 .children
2874 .iter()
2875 .enumerate()
2876 .flat_map(|(row, n)| {
2877 if let mdast::Node::TableRow(table_row) = n {
2878 itertools::concat(vec![
2879 table_row
2880 .children
2881 .iter()
2882 .enumerate()
2883 .flat_map(|(column, node)| {
2884 if let mdast::Node::TableCell(_) = node {
2885 vec![Self::TableCell(TableCell {
2886 row,
2887 column,
2888 values: Self::mdast_children_to_node(node.clone()),
2889 position: node.position().map(|p| p.clone().into()),
2890 })]
2891 } else {
2892 Vec::new()
2893 }
2894 })
2895 .collect(),
2896 if row == 0 {
2897 vec![Self::TableAlign(TableAlign {
2898 align: table.align.iter().map(|a| (*a).into()).collect::<Vec<_>>(),
2899 position: n.position().map(|p| Position {
2900 start: Point {
2901 line: p.start.line + 1,
2902 column: 1,
2903 },
2904 end: Point {
2905 line: p.start.line + 1,
2906 column: 1,
2907 },
2908 }),
2909 })]
2910 } else {
2911 Vec::new()
2912 },
2913 ])
2914 } else {
2915 Vec::new()
2916 }
2917 })
2918 .collect(),
2919 mdast::Node::Code(mdast::Code {
2920 value,
2921 position,
2922 lang,
2923 meta,
2924 ..
2925 }) => match lang {
2926 Some(lang) => {
2927 vec![Self::Code(Code {
2928 value,
2929 lang: Some(lang),
2930 position: position.map(|p| p.clone().into()),
2931 meta,
2932 fence: true,
2933 })]
2934 }
2935 None => {
2936 let line_count = position
2937 .as_ref()
2938 .map(|p| p.end.line - p.start.line + 1)
2939 .unwrap_or_default();
2940 let fence = value.lines().count() != line_count;
2941
2942 vec![Self::Code(Code {
2943 value,
2944 lang,
2945 position: position.map(|p| p.clone().into()),
2946 meta,
2947 fence,
2948 })]
2949 }
2950 },
2951 mdast::Node::Blockquote(mdast::Blockquote { position, .. }) => {
2952 let values = Self::mdast_children_to_node(node);
2953 let pos = position.map(|p| p.clone().into());
2954 #[cfg(feature = "callout")]
2955 {
2956 vec![Self::try_parse_callout(values, pos)]
2957 }
2958 #[cfg(not(feature = "callout"))]
2959 {
2960 vec![Self::Blockquote(Blockquote { values, position: pos })]
2961 }
2962 }
2963 mdast::Node::Definition(mdast::Definition {
2964 url,
2965 title,
2966 identifier,
2967 label,
2968 position,
2969 ..
2970 }) => {
2971 vec![Self::Definition(Definition {
2972 ident: identifier,
2973 url: Url(url),
2974 label,
2975 title: title.map(Title),
2976 position: position.map(|p| p.clone().into()),
2977 })]
2978 }
2979 mdast::Node::Heading(mdast::Heading { depth, position, .. }) => {
2980 vec![Self::Heading(Heading {
2981 values: Self::mdast_children_to_node(node),
2982 depth,
2983 position: position.map(|p| p.clone().into()),
2984 })]
2985 }
2986 mdast::Node::Break(mdast::Break { position }) => {
2987 vec![Self::Break(Break {
2988 position: position.map(|p| p.clone().into()),
2989 })]
2990 }
2991 mdast::Node::Delete(mdast::Delete { position, .. }) => {
2992 vec![Self::Delete(Delete {
2993 values: Self::mdast_children_to_node(node),
2994 position: position.map(|p| p.clone().into()),
2995 })]
2996 }
2997 mdast::Node::Emphasis(mdast::Emphasis { position, .. }) => {
2998 vec![Self::Emphasis(Emphasis {
2999 values: Self::mdast_children_to_node(node),
3000 position: position.map(|p| p.clone().into()),
3001 })]
3002 }
3003 mdast::Node::Strong(mdast::Strong { position, .. }) => {
3004 vec![Self::Strong(Strong {
3005 values: Self::mdast_children_to_node(node),
3006 position: position.map(|p| p.clone().into()),
3007 })]
3008 }
3009 mdast::Node::ThematicBreak(mdast::ThematicBreak { position, .. }) => {
3010 vec![Self::HorizontalRule(HorizontalRule {
3011 position: position.map(|p| p.clone().into()),
3012 })]
3013 }
3014 mdast::Node::Html(mdast::Html { value, position }) => {
3015 vec![Self::Html(Html {
3016 value,
3017 position: position.map(|p| p.clone().into()),
3018 })]
3019 }
3020 mdast::Node::Yaml(mdast::Yaml { value, position }) => {
3021 vec![Self::Yaml(Yaml {
3022 value,
3023 position: position.map(|p| p.clone().into()),
3024 })]
3025 }
3026 mdast::Node::Toml(mdast::Toml { value, position }) => {
3027 vec![Self::Toml(Toml {
3028 value,
3029 position: position.map(|p| p.clone().into()),
3030 })]
3031 }
3032 mdast::Node::Image(mdast::Image {
3033 alt,
3034 url,
3035 title,
3036 position,
3037 }) => {
3038 vec![Self::Image(Image {
3039 alt,
3040 url,
3041 title,
3042 position: position.map(|p| p.clone().into()),
3043 })]
3044 }
3045 mdast::Node::ImageReference(mdast::ImageReference {
3046 alt,
3047 identifier,
3048 label,
3049 position,
3050 ..
3051 }) => {
3052 vec![Self::ImageRef(ImageRef {
3053 alt,
3054 ident: identifier,
3055 label,
3056 position: position.map(|p| p.clone().into()),
3057 })]
3058 }
3059 mdast::Node::InlineCode(mdast::InlineCode { value, position, .. }) => {
3060 vec![Self::CodeInline(CodeInline {
3061 value: value.into(),
3062 position: position.map(|p| p.clone().into()),
3063 })]
3064 }
3065 mdast::Node::InlineMath(mdast::InlineMath { value, position }) => {
3066 vec![Self::MathInline(MathInline {
3067 value: value.into(),
3068 position: position.map(|p| p.clone().into()),
3069 })]
3070 }
3071 mdast::Node::Link(mdast::Link {
3072 title,
3073 url,
3074 position,
3075 children,
3076 ..
3077 }) => {
3078 let converted: Vec<Node> = children.into_iter().flat_map(Self::from_mdast_node).collect();
3079 let values = converted
3085 .into_iter()
3086 .flat_map(|child| match child {
3087 Self::Link(Link {
3088 url: ref inner_url,
3089 ref values,
3090 ..
3091 }) if inner_url.0 == url => values.clone(),
3092 other => vec![other],
3093 })
3094 .collect();
3095 vec![Self::Link(Link {
3096 url: Url(url),
3097 title: title.map(Title),
3098 values,
3099 position: position.map(|p| p.clone().into()),
3100 })]
3101 }
3102 mdast::Node::LinkReference(mdast::LinkReference {
3103 identifier,
3104 label,
3105 position,
3106 children,
3107 ..
3108 }) => {
3109 vec![Self::LinkRef(LinkRef {
3110 ident: identifier,
3111 values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
3112 label,
3113 position: position.map(|p| p.clone().into()),
3114 })]
3115 }
3116 mdast::Node::Math(mdast::Math { value, position, .. }) => {
3117 vec![Self::Math(Math {
3118 value,
3119 position: position.map(|p| p.clone().into()),
3120 })]
3121 }
3122 mdast::Node::FootnoteDefinition(mdast::FootnoteDefinition {
3123 identifier,
3124 position,
3125 children,
3126 ..
3127 }) => {
3128 vec![Self::Footnote(Footnote {
3129 ident: identifier,
3130 values: children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>(),
3131 position: position.map(|p| p.clone().into()),
3132 })]
3133 }
3134 mdast::Node::FootnoteReference(mdast::FootnoteReference {
3135 identifier,
3136 label,
3137 position,
3138 ..
3139 }) => {
3140 vec![Self::FootnoteRef(FootnoteRef {
3141 ident: identifier,
3142 label,
3143 position: position.map(|p| p.clone().into()),
3144 })]
3145 }
3146 mdast::Node::MdxFlowExpression(mdx) => {
3147 vec![Self::MdxFlowExpression(MdxFlowExpression {
3148 value: mdx.value.into(),
3149 position: mdx.position.map(|position| position.into()),
3150 })]
3151 }
3152 mdast::Node::MdxJsxFlowElement(mdx) => {
3153 vec![Self::MdxJsxFlowElement(MdxJsxFlowElement {
3154 children: mdx
3155 .children
3156 .into_iter()
3157 .flat_map(Self::from_mdast_node)
3158 .collect::<Vec<_>>(),
3159 position: mdx.position.map(|p| p.clone().into()),
3160 name: mdx.name,
3161 attributes: mdx
3162 .attributes
3163 .iter()
3164 .map(|attr| match attr {
3165 mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
3166 MdxAttributeContent::Expression(value.into())
3167 }
3168 mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
3169 MdxAttributeContent::Property(MdxJsxAttribute {
3170 name: name.into(),
3171 value: value.as_ref().map(|value| match value {
3172 mdast::AttributeValue::Literal(value) => {
3173 MdxAttributeValue::Literal(value.into())
3174 }
3175 mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
3176 value,
3177 ..
3178 }) => MdxAttributeValue::Expression(value.into()),
3179 }),
3180 })
3181 }
3182 })
3183 .collect(),
3184 })]
3185 }
3186 mdast::Node::MdxJsxTextElement(mdx) => {
3187 vec![Self::MdxJsxTextElement(MdxJsxTextElement {
3188 children: mdx
3189 .children
3190 .into_iter()
3191 .flat_map(Self::from_mdast_node)
3192 .collect::<Vec<_>>(),
3193 position: mdx.position.map(|p| p.clone().into()),
3194 name: mdx.name.map(|name| name.into()),
3195 attributes: mdx
3196 .attributes
3197 .iter()
3198 .map(|attr| match attr {
3199 mdast::AttributeContent::Expression(mdast::MdxJsxExpressionAttribute { value, .. }) => {
3200 MdxAttributeContent::Expression(value.into())
3201 }
3202 mdast::AttributeContent::Property(mdast::MdxJsxAttribute { value, name, .. }) => {
3203 MdxAttributeContent::Property(MdxJsxAttribute {
3204 name: name.into(),
3205 value: value.as_ref().map(|value| match value {
3206 mdast::AttributeValue::Literal(value) => {
3207 MdxAttributeValue::Literal(value.into())
3208 }
3209 mdast::AttributeValue::Expression(mdast::AttributeValueExpression {
3210 value,
3211 ..
3212 }) => MdxAttributeValue::Expression(value.into()),
3213 }),
3214 })
3215 }
3216 })
3217 .collect(),
3218 })]
3219 }
3220 mdast::Node::MdxTextExpression(mdx) => {
3221 vec![Self::MdxTextExpression(MdxTextExpression {
3222 value: mdx.value.into(),
3223 position: mdx.position.map(|position| position.into()),
3224 })]
3225 }
3226 mdast::Node::MdxjsEsm(mdx) => {
3227 vec![Self::MdxJsEsm(MdxJsEsm {
3228 value: mdx.value.into(),
3229 position: mdx.position.map(|position| position.into()),
3230 })]
3231 }
3232 mdast::Node::Text(mdast::Text { position, value, .. }) => {
3233 vec![Self::Text(Text {
3234 value,
3235 position: position.map(|p| p.clone().into()),
3236 })]
3237 }
3238 mdast::Node::Paragraph(mdast::Paragraph { children, .. }) => {
3239 children.into_iter().flat_map(Self::from_mdast_node).collect::<Vec<_>>()
3240 }
3241 _ => Vec::new(),
3242 }
3243 }
3244
3245 fn mdast_children_to_node(node: mdast::Node) -> Vec<Node> {
3246 node.children()
3247 .map(|children| {
3248 children
3249 .iter()
3250 .flat_map(|v| Self::from_mdast_node(v.clone()))
3251 .collect::<Vec<_>>()
3252 })
3253 .unwrap_or_else(|| vec![EMPTY_NODE])
3254 }
3255
3256 fn mdast_list_items(list: &mdast::List, level: Level) -> Vec<Node> {
3257 list.children
3258 .iter()
3259 .flat_map(|n| {
3260 if let mdast::Node::ListItem(list_item) = n {
3261 let values = Self::from_mdast_node(n.clone())
3262 .into_iter()
3263 .filter(|value| !matches!(value, Self::List(_)))
3264 .collect::<Vec<_>>();
3265 let position = if values.is_empty() {
3266 n.position().map(|p| p.clone().into())
3267 } else {
3268 let first_pos = values.first().and_then(|v| v.position());
3269 let last_pos = values.last().and_then(|v| v.position());
3270 match (first_pos, last_pos) {
3271 (Some(start), Some(end)) => Some(Position {
3272 start: start.start.clone(),
3273 end: end.end.clone(),
3274 }),
3275 _ => n.position().map(|p| p.clone().into()),
3276 }
3277 };
3278
3279 itertools::concat(vec![
3280 vec![Self::List(List {
3281 level,
3282 index: 0,
3283 ordered: list.ordered,
3284 checked: list_item.checked,
3285 values,
3286 position,
3287 })],
3288 list_item
3289 .children
3290 .iter()
3291 .flat_map(|node| {
3292 if let mdast::Node::List(sub_list) = node {
3293 Self::mdast_list_items(sub_list, level + 1)
3294 } else if let mdast::Node::ListItem(list_item) = node {
3295 let values = Self::from_mdast_node(n.clone())
3296 .into_iter()
3297 .filter(|value| !matches!(value, Self::List(_)))
3298 .collect::<Vec<_>>();
3299 let position = if values.is_empty() {
3300 n.position().map(|p| p.clone().into())
3301 } else {
3302 let first_pos = values.first().and_then(|v| v.position());
3303 let last_pos = values.last().and_then(|v| v.position());
3304 match (first_pos, last_pos) {
3305 (Some(start), Some(end)) => Some(Position {
3306 start: start.start.clone(),
3307 end: end.end.clone(),
3308 }),
3309 _ => n.position().map(|p| p.clone().into()),
3310 }
3311 };
3312 vec![Self::List(List {
3313 level: level + 1,
3314 index: 0,
3315 ordered: list.ordered,
3316 checked: list_item.checked,
3317 values,
3318 position,
3319 })]
3320 } else {
3321 Vec::new()
3322 }
3323 })
3324 .collect(),
3325 ])
3326 } else if let mdast::Node::List(sub_list) = n {
3327 Self::mdast_list_items(sub_list, level + 1)
3328 } else {
3329 Vec::new()
3330 }
3331 })
3332 .enumerate()
3333 .filter_map(|(i, node)| match node {
3334 Self::List(List {
3335 level,
3336 index: _,
3337 ordered,
3338 checked,
3339 values,
3340 position,
3341 }) => Some(Self::List(List {
3342 level,
3343 index: i,
3344 ordered,
3345 checked,
3346 values,
3347 position,
3348 })),
3349 _ => None,
3350 })
3351 .collect()
3352 }
3353
3354 fn mdx_attribute_content_to_string(attr: MdxAttributeContent) -> SmolStr {
3355 match attr {
3356 MdxAttributeContent::Expression(value) => format!("{{{}}}", value).into(),
3357 MdxAttributeContent::Property(property) => match property.value {
3358 Some(value) => match value {
3359 MdxAttributeValue::Expression(value) => format!("{}={{{}}}", property.name, value).into(),
3360 MdxAttributeValue::Literal(literal) => format!("{}=\"{}\"", property.name, literal).into(),
3361 },
3362 None => property.name,
3363 },
3364 }
3365 }
3366}
3367
3368pub(crate) fn values_to_string(values: &[Node], options: &RenderOptions) -> String {
3369 render_values(values, options, &ColorTheme::PLAIN)
3370}
3371
3372pub(crate) fn render_values(values: &[Node], options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
3373 let mut pre_position: Option<Position> = None;
3374 values
3375 .iter()
3376 .map(|value| {
3377 if let Some(pos) = value.position() {
3378 let new_line_count = pre_position
3379 .as_ref()
3380 .map(|p: &Position| pos.start.line - p.end.line)
3381 .unwrap_or_default();
3382
3383 let space = if new_line_count > 0
3384 && pre_position
3385 .as_ref()
3386 .map(|p| pos.start.line > p.end.line)
3387 .unwrap_or_default()
3388 {
3389 " ".repeat(pos.start.column.saturating_sub(1))
3390 } else {
3391 "".to_string()
3392 };
3393
3394 pre_position = Some(pos);
3395
3396 if space.is_empty() {
3397 format!(
3398 "{}{}",
3399 "\n".repeat(new_line_count),
3400 value.render_with_theme(options, theme)
3401 )
3402 } else {
3403 format!(
3404 "{}{}",
3405 "\n".repeat(new_line_count),
3406 value
3407 .render_with_theme(options, theme)
3408 .lines()
3409 .map(|line| format!("{}{}", space, line))
3410 .join("\n")
3411 )
3412 }
3413 } else {
3414 pre_position = None;
3415 value.render_with_theme(options, theme)
3416 }
3417 })
3418 .collect::<String>()
3419}
3420
3421pub(crate) fn render_values_block(values: &[Node], options: &RenderOptions, theme: &ColorTheme<'_>) -> String {
3427 let mut pre_position: Option<Position> = None;
3428 values
3429 .iter()
3430 .map(|value| {
3431 if let Some(pos) = value.position() {
3432 let new_line_count = pre_position
3433 .as_ref()
3434 .map(|p: &Position| pos.start.line - p.end.line)
3435 .unwrap_or_default();
3436 pre_position = Some(pos);
3437 format!(
3438 "{}{}",
3439 "\n".repeat(new_line_count),
3440 value.render_with_theme(options, theme)
3441 )
3442 } else {
3443 pre_position = None;
3444 value.render_with_theme(options, theme)
3445 }
3446 })
3447 .collect::<String>()
3448}
3449
3450fn values_to_value(values: Vec<Node>) -> String {
3451 values.iter().map(|value| value.value()).collect::<String>()
3452}
3453
3454#[cfg(test)]
3455mod tests {
3456 use super::*;
3457 use rstest::rstest;
3458
3459 #[rstest]
3460 #[case::text(Node::Text(Text{value: "".to_string(), position: None}),
3461 "test".to_string(),
3462 Node::Text(Text{value: "test".to_string(), position: None }))]
3463 #[case::blockquote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }),
3464 "test".to_string(),
3465 Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None }))]
3466 #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }),
3467 "test".to_string(),
3468 Node::Delete(Delete{values: vec!["test".to_string().into()], position: None }))]
3469 #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }),
3470 "test".to_string(),
3471 Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None }))]
3472 #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }),
3473 "test".to_string(),
3474 Node::Strong(Strong{values: vec!["test".to_string().into()], position: None }))]
3475 #[case::heading(Node::Heading(Heading {depth: 1, values: vec!["test".to_string().into()], position: None }),
3476 "test".to_string(),
3477 Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None }))]
3478 #[case::link(Node::Link(Link {url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }),
3479 "test".to_string(),
3480 Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None }))]
3481 #[case::image(Node::Image(Image {alt: "test".to_string(), url: "test".to_string(), title: None, position: None }),
3482 "test".to_string(),
3483 Node::Image(Image{alt: "test".to_string(), url: "test".to_string(), title: None, position: None }))]
3484 #[case::code(Node::Code(Code {value: "test".to_string(), lang: None, fence: true, meta: None, position: None }),
3485 "test".to_string(),
3486 Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None }))]
3487 #[case::footnote_ref(Node::FootnoteRef(FootnoteRef {ident: "test".to_string(), label: None, position: None }),
3488 "test".to_string(),
3489 Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: Some("test".to_string()), position: None }))]
3490 #[case::footnote(Node::Footnote(Footnote {ident: "test".to_string(), values: Vec::new(), position: None }),
3491 "test".to_string(),
3492 Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None }))]
3493 #[case::list(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }),
3494 "test".to_string(),
3495 Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None }))]
3496 #[case::list(Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }),
3497 "test".to_string(),
3498 Node::List(List{index: 1, level: 1, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None }))]
3499 #[case::list(Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }),
3500 "test".to_string(),
3501 Node::List(List{index: 2, level: 2, checked: Some(false), ordered: false, values: vec!["test".to_string().into()], position: None }))]
3502 #[case::code_inline(Node::CodeInline(CodeInline{ value: "t".into(), position: None }),
3503 "test".to_string(),
3504 Node::CodeInline(CodeInline{ value: "test".into(), position: None }))]
3505 #[case::math_inline(Node::MathInline(MathInline{ value: "t".into(), position: None }),
3506 "test".to_string(),
3507 Node::MathInline(MathInline{ value: "test".into(), position: None }))]
3508 #[case::toml(Node::Toml(Toml{ value: "t".to_string(), position: None }),
3509 "test".to_string(),
3510 Node::Toml(Toml{ value: "test".to_string(), position: None }))]
3511 #[case::yaml(Node::Yaml(Yaml{ value: "t".to_string(), position: None }),
3512 "test".to_string(),
3513 Node::Yaml(Yaml{ value: "test".to_string(), position: None }))]
3514 #[case::html(Node::Html(Html{ value: "t".to_string(), position: None }),
3515 "test".to_string(),
3516 Node::Html(Html{ value: "test".to_string(), position: None }))]
3517 #[case::table_row(Node::TableRow(TableRow{ values: vec![
3518 Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1, position: None}),
3519 Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:2, position: None})
3520 ]
3521 , position: None }),
3522 "test3,test4".to_string(),
3523 Node::TableRow(TableRow{ values: vec![
3524 Node::TableCell(TableCell{values: vec!["test3".to_string().into()], row:0, column:1, position: None}),
3525 Node::TableCell(TableCell{values: vec!["test4".to_string().into()], row:0, column:2, position: None})
3526 ]
3527 , position: None }))]
3528 #[case::table_cell(Node::TableCell(TableCell{values: vec!["test1".to_string().into()], row:0, column:1, position: None}),
3529 "test2".to_string(),
3530 Node::TableCell(TableCell{values: vec!["test2".to_string().into()], row:0, column:1, position: None}),)]
3531 #[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}),
3532 "test2".to_string(),
3533 Node::LinkRef(LinkRef{ident: "test2".to_string(), values: vec![attr_keys::VALUE.to_string().into()], label: Some("test2".to_string()), position: None}),)]
3534 #[case::image_ref(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test1".to_string(), label: None, position: None}),
3535 "test2".to_string(),
3536 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test2".to_string(), label: Some("test2".to_string()), position: None}),)]
3537 #[case::definition(Node::Definition(Definition{ url: Url::new(attr_keys::URL.to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),
3538 "test2".to_string(),
3539 Node::Definition(Definition{url: Url::new("test2".to_string()), title: None, ident: "test1".to_string(), label: None, position: None}),)]
3540 #[case::break_(Node::Break(Break{ position: None}),
3541 "test".to_string(),
3542 Node::Break(Break{position: None}))]
3543 #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{ position: None}),
3544 "test".to_string(),
3545 Node::HorizontalRule(HorizontalRule{position: None}))]
3546 #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
3547 "updated".to_string(),
3548 Node::MdxFlowExpression(MdxFlowExpression{value: "updated".into(), position: None}))]
3549 #[case::mdx_text_expression(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
3550 "updated".to_string(),
3551 Node::MdxTextExpression(MdxTextExpression{value: "updated".into(), position: None}))]
3552 #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}),
3553 "updated".to_string(),
3554 Node::MdxJsEsm(MdxJsEsm{value: "updated".into(), position: None}))]
3555 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3556 name: Some("div".to_string()),
3557 attributes: Vec::new(),
3558 children: vec!["test".to_string().into()],
3559 position: None
3560 }),
3561 "updated".to_string(),
3562 Node::MdxJsxFlowElement(MdxJsxFlowElement{
3563 name: Some("div".to_string()),
3564 attributes: Vec::new(),
3565 children: vec!["updated".to_string().into()],
3566 position: None
3567 }))]
3568 #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
3569 name: Some("span".into()),
3570 attributes: Vec::new(),
3571 children: vec!["test".to_string().into()],
3572 position: None
3573 }),
3574 "updated".to_string(),
3575 Node::MdxJsxTextElement(MdxJsxTextElement{
3576 name: Some("span".into()),
3577 attributes: Vec::new(),
3578 children: vec!["updated".to_string().into()],
3579 position: None
3580 }))]
3581 #[case(Node::Math(Math{ value: "x^2".to_string(), position: None }),
3582 "test".to_string(),
3583 Node::Math(Math{ value: "test".to_string(), position: None }))]
3584 fn test_with_value(#[case] node: Node, #[case] input: String, #[case] expected: Node) {
3585 assert_eq!(node.with_value(input.as_str()), expected);
3586 }
3587
3588 #[rstest]
3589 #[case(Node::Blockquote(Blockquote{values: vec![
3590 Node::Text(Text{value: "first".to_string(), position: None}),
3591 Node::Text(Text{value: "second".to_string(), position: None})
3592 ], position: None}),
3593 "new",
3594 0,
3595 Node::Blockquote(Blockquote{values: vec![
3596 Node::Text(Text{value: "new".to_string(), position: None}),
3597 Node::Text(Text{value: "second".to_string(), position: None})
3598 ], position: None}))]
3599 #[case(Node::Blockquote(Blockquote{values: vec![
3600 Node::Text(Text{value: "first".to_string(), position: None}),
3601 Node::Text(Text{value: "second".to_string(), position: None})
3602 ], position: None}),
3603 "new",
3604 1,
3605 Node::Blockquote(Blockquote{values: vec![
3606 Node::Text(Text{value: "first".to_string(), position: None}),
3607 Node::Text(Text{value: "new".to_string(), position: None})
3608 ], position: None}))]
3609 #[case(Node::Delete(Delete{values: vec![
3610 Node::Text(Text{value: "first".to_string(), position: None}),
3611 Node::Text(Text{value: "second".to_string(), position: None})
3612 ], position: None}),
3613 "new",
3614 0,
3615 Node::Delete(Delete{values: vec![
3616 Node::Text(Text{value: "new".to_string(), position: None}),
3617 Node::Text(Text{value: "second".to_string(), position: None})
3618 ], position: None}))]
3619 #[case(Node::Emphasis(Emphasis{values: vec![
3620 Node::Text(Text{value: "first".to_string(), position: None}),
3621 Node::Text(Text{value: "second".to_string(), position: None})
3622 ], position: None}),
3623 "new",
3624 1,
3625 Node::Emphasis(Emphasis{values: vec![
3626 Node::Text(Text{value: "first".to_string(), position: None}),
3627 Node::Text(Text{value: "new".to_string(), position: None})
3628 ], position: None}))]
3629 #[case(Node::Strong(Strong{values: vec![
3630 Node::Text(Text{value: "first".to_string(), position: None}),
3631 Node::Text(Text{value: "second".to_string(), position: None})
3632 ], position: None}),
3633 "new",
3634 0,
3635 Node::Strong(Strong{values: vec![
3636 Node::Text(Text{value: "new".to_string(), position: None}),
3637 Node::Text(Text{value: "second".to_string(), position: None})
3638 ], position: None}))]
3639 #[case(Node::Heading(Heading{depth: 1, values: vec![
3640 Node::Text(Text{value: "first".to_string(), position: None}),
3641 Node::Text(Text{value: "second".to_string(), position: None})
3642 ], position: None}),
3643 "new",
3644 1,
3645 Node::Heading(Heading{depth: 1, values: vec![
3646 Node::Text(Text{value: "first".to_string(), position: None}),
3647 Node::Text(Text{value: "new".to_string(), position: None})
3648 ], position: None}))]
3649 #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3650 Node::Text(Text{value: "first".to_string(), position: None}),
3651 Node::Text(Text{value: "second".to_string(), position: None})
3652 ], position: None}),
3653 "new",
3654 0,
3655 Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
3656 Node::Text(Text{value: "new".to_string(), position: None}),
3657 Node::Text(Text{value: "second".to_string(), position: None})
3658 ], position: None}))]
3659 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3660 Node::Text(Text{value: "first".to_string(), position: None}),
3661 Node::Text(Text{value: "second".to_string(), position: None})
3662 ], position: None}),
3663 "new",
3664 1,
3665 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
3666 Node::Text(Text{value: "first".to_string(), position: None}),
3667 Node::Text(Text{value: "new".to_string(), position: None})
3668 ], position: None}))]
3669 #[case(Node::Text(Text{value: "plain text".to_string(), position: None}),
3670 "new",
3671 0,
3672 Node::Text(Text{value: "plain text".to_string(), position: None}))]
3673 #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
3674 "new",
3675 0,
3676 Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}))]
3677 #[case(Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3678 Node::Text(Text{value: "first".to_string(), position: None})
3679 ], position: None}),
3680 "new",
3681 0,
3682 Node::List(List{index: 0, level: 1, checked: Some(true), ordered: false, values: vec![
3683 Node::Text(Text{value: "new".to_string(), position: None})
3684 ], position: None}))]
3685 #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3686 Node::Text(Text{value: "first".to_string(), position: None})
3687 ], position: None}),
3688 "new",
3689 2,
3690 Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![
3691 Node::Text(Text{value: "first".to_string(), position: None})
3692 ], position: None}))]
3693 #[case::link_ref(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3694 Node::Text(Text{value: "first".to_string(), position: None}),
3695 Node::Text(Text{value: "second".to_string(), position: None})
3696 ], label: None, position: None}), "new", 0, Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
3697 Node::Text(Text{value: "new".to_string(), position: None}),
3698 Node::Text(Text{value: "second".to_string(), position: None})
3699 ], label: None, position: None}))]
3700 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3701 name: Some("div".to_string()),
3702 attributes: Vec::new(),
3703 children: vec![
3704 Node::Text(Text{value: "first".to_string(), position: None}),
3705 Node::Text(Text{value: "second".to_string(), position: None})
3706 ],
3707 position: None
3708 }),
3709 "new",
3710 0,
3711 Node::MdxJsxFlowElement(MdxJsxFlowElement{
3712 name: Some("div".to_string()),
3713 attributes: Vec::new(),
3714 children: vec![
3715 Node::Text(Text{value: "new".to_string(), position: None}),
3716 Node::Text(Text{value: "second".to_string(), position: None})
3717 ],
3718 position: None
3719 }))]
3720 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
3721 name: Some("div".to_string()),
3722 attributes: Vec::new(),
3723 children: vec![
3724 Node::Text(Text{value: "first".to_string(), position: None}),
3725 Node::Text(Text{value: "second".to_string(), position: None})
3726 ],
3727 position: None
3728 }),
3729 "new",
3730 1,
3731 Node::MdxJsxFlowElement(MdxJsxFlowElement{
3732 name: Some("div".to_string()),
3733 attributes: Vec::new(),
3734 children: vec![
3735 Node::Text(Text{value: "first".to_string(), position: None}),
3736 Node::Text(Text{value: "new".to_string(), position: None})
3737 ],
3738 position: None
3739 }))]
3740 #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3741 name: Some("span".into()),
3742 attributes: Vec::new(),
3743 children: vec![
3744 Node::Text(Text{value: "first".to_string(), position: None}),
3745 Node::Text(Text{value: "second".to_string(), position: None})
3746 ],
3747 position: None
3748 }),
3749 "new",
3750 0,
3751 Node::MdxJsxTextElement(MdxJsxTextElement{
3752 name: Some("span".into()),
3753 attributes: Vec::new(),
3754 children: vec![
3755 Node::Text(Text{value: "new".to_string(), position: None}),
3756 Node::Text(Text{value: "second".to_string(), position: None})
3757 ],
3758 position: None
3759 }))]
3760 #[case(Node::MdxJsxTextElement(MdxJsxTextElement{
3761 name: Some("span".into()),
3762 attributes: Vec::new(),
3763 children: vec![
3764 Node::Text(Text{value: "first".to_string(), position: None}),
3765 Node::Text(Text{value: "second".to_string(), position: None})
3766 ],
3767 position: None
3768 }),
3769 "new",
3770 1,
3771 Node::MdxJsxTextElement(MdxJsxTextElement{
3772 name: Some("span".into()),
3773 attributes: Vec::new(),
3774 children: vec![
3775 Node::Text(Text{value: "first".to_string(), position: None}),
3776 Node::Text(Text{value: "new".to_string(), position: None})
3777 ],
3778 position: None
3779 }))]
3780 fn test_with_children_value(#[case] node: Node, #[case] value: &str, #[case] index: usize, #[case] expected: Node) {
3781 assert_eq!(node.with_children_value(value, index), expected);
3782 }
3783
3784 #[rstest]
3785 #[case(Node::Text(Text{value: "test".to_string(), position: None }),
3786 "test".to_string())]
3787 #[case(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
3788 " - test".to_string())]
3789 fn test_display(#[case] node: Node, #[case] expected: String) {
3790 assert_eq!(node.to_string_with(&RenderOptions::default()), expected);
3791 }
3792
3793 #[rstest]
3794 #[case(Node::Text(Text{value: "test".to_string(), position: None}), true)]
3795 #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), false)]
3796 #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), false)]
3797 fn test_is_text(#[case] node: Node, #[case] expected: bool) {
3798 assert_eq!(node.is_text(), expected);
3799 }
3800
3801 #[rstest]
3802 #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), true)]
3803 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3804 fn test_is_inline_code(#[case] node: Node, #[case] expected: bool) {
3805 assert_eq!(node.is_inline_code(), expected);
3806 }
3807
3808 #[rstest]
3809 #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), true)]
3810 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3811 fn test_is_inline_math(#[case] node: Node, #[case] expected: bool) {
3812 assert_eq!(node.is_inline_math(), expected);
3813 }
3814
3815 #[rstest]
3816 #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), true)]
3817 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3818 fn test_is_strong(#[case] node: Node, #[case] expected: bool) {
3819 assert_eq!(node.is_strong(), expected);
3820 }
3821
3822 #[rstest]
3823 #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), true)]
3824 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3825 fn test_is_delete(#[case] node: Node, #[case] expected: bool) {
3826 assert_eq!(node.is_delete(), expected);
3827 }
3828
3829 #[rstest]
3830 #[case(Node::Link(Link{url: Url::new("test".to_string()), values: Vec::new(), title: None, position: None}), true)]
3831 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3832 fn test_is_link(#[case] node: Node, #[case] expected: bool) {
3833 assert_eq!(node.is_link(), expected);
3834 }
3835
3836 #[rstest]
3837 #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), true)]
3838 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3839 fn test_is_link_ref(#[case] node: Node, #[case] expected: bool) {
3840 assert_eq!(node.is_link_ref(), expected);
3841 }
3842
3843 #[rstest]
3844 #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), true)]
3845 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3846 fn test_is_image(#[case] node: Node, #[case] expected: bool) {
3847 assert_eq!(node.is_image(), expected);
3848 }
3849
3850 #[rstest]
3851 #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), true)]
3852 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3853 fn test_is_image_ref(#[case] node: Node, #[case] expected: bool) {
3854 assert_eq!(node.is_image_ref(), expected);
3855 }
3856
3857 #[rstest]
3858 #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), true, Some("rust".into()))]
3859 #[case(Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}), false, Some("python".into()))]
3860 #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), true, None)]
3861 #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), true, None)]
3862 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3863 fn test_is_code(#[case] node: Node, #[case] expected: bool, #[case] lang: Option<SmolStr>) {
3864 assert_eq!(node.is_code(lang), expected);
3865 }
3866
3867 #[rstest]
3868 #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, Some(1))]
3869 #[case(Node::Heading(Heading{depth: 2, values: vec!["test".to_string().into()], position: None}), false, Some(1))]
3870 #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), true, None)]
3871 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false, None)]
3872 fn test_is_heading(#[case] node: Node, #[case] expected: bool, #[case] depth: Option<u8>) {
3873 assert_eq!(node.is_heading(depth), expected);
3874 }
3875
3876 #[rstest]
3877 #[case(Node::HorizontalRule(HorizontalRule{position: None}), true)]
3878 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3879 fn test_is_horizontal_rule(#[case] node: Node, #[case] expected: bool) {
3880 assert_eq!(node.is_horizontal_rule(), expected);
3881 }
3882
3883 #[rstest]
3884 #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), true)]
3885 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3886 fn test_is_blockquote(#[case] node: Node, #[case] expected: bool) {
3887 assert_eq!(node.is_blockquote(), expected);
3888 }
3889
3890 #[cfg(feature = "wikilink")]
3891 #[rstest]
3892 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), true)]
3893 #[case(Node::WikiLink(WikiLink{target: "Three laws of motion".to_string(), text: Some("Newton".to_string()), position: None}), true)]
3894 #[case(Node::Link(Link{url: Url::new("https://example.com".to_string()), values: Vec::new(), title: None, position: None}), false)]
3895 #[case(Node::Link(Link{url: Url::new("relative.md".to_string()), values: Vec::new(), title: None, position: None}), false)]
3896 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3897 #[case(Node::Text(Text{value: "[[target]]".to_string(), position: None}), false)]
3898 fn test_is_wikilink(#[case] node: Node, #[case] expected: bool) {
3899 assert_eq!(node.is_wikilink(), expected);
3900 }
3901
3902 #[cfg(feature = "wikilink")]
3903 #[rstest]
3904 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), true)]
3905 #[case(Node::WikiLink(WikiLink{target: "Three laws of motion".to_string(), text: Some("Newton".to_string()), position: None}), true)]
3906 #[case(Node::Link(Link{url: Url::new("https://example.com".to_string()), values: Vec::new(), title: None, position: None}), true)]
3907 #[case(Node::Link(Link{url: Url::new("relative.md".to_string()), values: Vec::new(), title: None, position: None}), true)]
3908 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
3909 fn test_is_link_includes_wikilink(#[case] node: Node, #[case] expected: bool) {
3910 assert_eq!(node.is_link(), expected);
3911 }
3912
3913 #[cfg(feature = "wikilink")]
3914 #[rstest]
3915 #[case("plain text", vec![Node::Text(Text{value: "plain text".to_string(), position: None})])]
3917 #[case("[[target]]", vec![Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None})])]
3919 #[case("[[target|display]]", vec![Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("display".to_string()), position: None})])]
3921 #[case("[[Three laws of motion]]", vec![Node::WikiLink(WikiLink{target: "Three laws of motion".to_string(), text: None, position: None})])]
3923 #[case("[[target.md]]", vec![Node::WikiLink(WikiLink{target: "target.md".to_string(), text: None, position: None})])]
3925 #[case("[[target]] after", vec![
3927 Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}),
3928 Node::Text(Text{value: " after".to_string(), position: None}),
3929 ])]
3930 #[case("before [[target]]", vec![
3932 Node::Text(Text{value: "before ".to_string(), position: None}),
3933 Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}),
3934 ])]
3935 #[case("before [[target]] after", vec![
3937 Node::Text(Text{value: "before ".to_string(), position: None}),
3938 Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}),
3939 Node::Text(Text{value: " after".to_string(), position: None}),
3940 ])]
3941 #[case("[[a]] and [[b]]", vec![
3943 Node::WikiLink(WikiLink{target: "a".to_string(), text: None, position: None}),
3944 Node::Text(Text{value: " and ".to_string(), position: None}),
3945 Node::WikiLink(WikiLink{target: "b".to_string(), text: None, position: None}),
3946 ])]
3947 #[case("[[unclosed", vec![Node::Text(Text{value: "[[unclosed".to_string(), position: None})])]
3949 #[case("[[in[ner]]]", vec![Node::Text(Text{value: "[[in[ner]]]".to_string(), position: None})])]
3951 #[case("日本語 [[ターゲット]] テキスト", vec![
3953 Node::Text(Text{value: "日本語 ".to_string(), position: None}),
3954 Node::WikiLink(WikiLink{target: "ターゲット".to_string(), text: None, position: None}),
3955 Node::Text(Text{value: " テキスト".to_string(), position: None}),
3956 ])]
3957 #[case("[[ページ|表示名]]", vec![
3959 Node::WikiLink(WikiLink{target: "ページ".to_string(), text: Some("表示名".to_string()), position: None}),
3960 ])]
3961 fn test_parse_wikilinks_in_text(#[case] input: &str, #[case] expected: Vec<Node>) {
3962 let result = Node::parse_wikilinks_in_text(input, None);
3963 assert_eq!(result, expected);
3964 }
3965
3966 #[cfg(feature = "wikilink")]
3967 #[rstest]
3968 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), "url", Some(AttrValue::String("target".to_string())))]
3969 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), "value", Some(AttrValue::String("target".to_string())))]
3970 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("display".to_string()), position: None}), "url", Some(AttrValue::String("target".to_string())))]
3971 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("display".to_string()), position: None}), "value", Some(AttrValue::String("display".to_string())))]
3972 #[case(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), "title", None)]
3973 fn test_wikilink_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
3974 assert_eq!(node.attr(attr), expected);
3975 }
3976
3977 #[cfg(feature = "wikilink")]
3978 #[rstest]
3979 #[case(
3981 Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}),
3982 "new-target",
3983 Node::WikiLink(WikiLink{target: "new-target".to_string(), text: None, position: None})
3984 )]
3985 #[case(
3987 Node::WikiLink(WikiLink{target: "page".to_string(), text: Some("Display Text".to_string()), position: None}),
3988 "DISPLAY TEXT",
3989 Node::WikiLink(WikiLink{target: "page".to_string(), text: Some("DISPLAY TEXT".to_string()), position: None})
3990 )]
3991 fn test_wikilink_with_value(#[case] node: Node, #[case] value: &str, #[case] expected: Node) {
3992 assert_eq!(node.with_value(value), expected);
3993 }
3994
3995 #[cfg(feature = "wikilink")]
3996 #[rstest]
3997 #[case(
3999 Node::Footnote(Footnote{ident: "1".to_string(), values: vec![Node::Text(Text{value: "plain text".to_string(), position: None})], position: None}),
4000 vec![Node::Footnote(Footnote{ident: "1".to_string(), values: vec![Node::Text(Text{value: "plain text".to_string(), position: None})], position: None})]
4001 )]
4002 #[case(
4004 Node::Footnote(Footnote{ident: "1".to_string(), values: vec![Node::Text(Text{value: "[[target]]".to_string(), position: None})], position: None}),
4005 vec![Node::Footnote(Footnote{ident: "1".to_string(), values: vec![Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None})], position: None})]
4006 )]
4007 #[case(
4009 Node::Footnote(Footnote{ident: "2".to_string(), values: vec![Node::Text(Text{value: "[[target|display]]".to_string(), position: None})], position: None}),
4010 vec![Node::Footnote(Footnote{ident: "2".to_string(), values: vec![Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("display".to_string()), position: None})], position: None})]
4011 )]
4012 #[case(
4014 Node::Footnote(Footnote{ident: "3".to_string(), values: vec![Node::Text(Text{value: "see [[note]]".to_string(), position: None})], position: None}),
4015 vec![Node::Footnote(Footnote{ident: "3".to_string(), values: vec![
4016 Node::Text(Text{value: "see ".to_string(), position: None}),
4017 Node::WikiLink(WikiLink{target: "note".to_string(), text: None, position: None}),
4018 ], position: None})]
4019 )]
4020 fn test_expand_wikilinks_footnote(#[case] input: Node, #[case] expected: Vec<Node>) {
4021 let result = Node::expand_wikilinks(vec![input]);
4022 assert_eq!(result, expected);
4023 }
4024
4025 #[cfg(all(feature = "embed", feature = "wikilink"))]
4026 #[rstest]
4027 #[case("![[note.md]]", vec![
4029 Node::Embed(Embed{target: "note.md".to_string(), display: None, position: None}),
4030 ])]
4031 #[case("[[target]]", vec![
4033 Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}),
4034 ])]
4035 #[case("![[embed]] and [[link]]", vec![
4037 Node::Embed(Embed{target: "embed".to_string(), display: None, position: None}),
4038 Node::Text(Text{value: " and ".to_string(), position: None}),
4039 Node::WikiLink(WikiLink{target: "link".to_string(), text: None, position: None}),
4040 ])]
4041 #[case("[[link]] and ![[embed]]", vec![
4043 Node::WikiLink(WikiLink{target: "link".to_string(), text: None, position: None}),
4044 Node::Text(Text{value: " and ".to_string(), position: None}),
4045 Node::Embed(Embed{target: "embed".to_string(), display: None, position: None}),
4046 ])]
4047 #[case("![[image.png|400]]", vec![
4049 Node::Embed(Embed{target: "image.png".to_string(), display: Some("400".to_string()), position: None}),
4050 ])]
4051 #[case("[[page|display]]", vec![
4053 Node::WikiLink(WikiLink{target: "page".to_string(), text: Some("display".to_string()), position: None}),
4054 ])]
4055 #[case("before ![[note]] after [[link]] end", vec![
4057 Node::Text(Text{value: "before ".to_string(), position: None}),
4058 Node::Embed(Embed{target: "note".to_string(), display: None, position: None}),
4059 Node::Text(Text{value: " after ".to_string(), position: None}),
4060 Node::WikiLink(WikiLink{target: "link".to_string(), text: None, position: None}),
4061 Node::Text(Text{value: " end".to_string(), position: None}),
4062 ])]
4063 #[case("just plain text", vec![
4065 Node::Text(Text{value: "just plain text".to_string(), position: None}),
4066 ])]
4067 #[case("日本語 ![[ファイル]] と [[ページ]]", vec![
4069 Node::Text(Text{value: "日本語 ".to_string(), position: None}),
4070 Node::Embed(Embed{target: "ファイル".to_string(), display: None, position: None}),
4071 Node::Text(Text{value: " と ".to_string(), position: None}),
4072 Node::WikiLink(WikiLink{target: "ページ".to_string(), text: None, position: None}),
4073 ])]
4074 fn test_parse_inline_links_into(#[case] input: &str, #[case] expected: Vec<Node>) {
4075 let mut result = Vec::new();
4076 Node::parse_inline_links_into(input, None, &mut result);
4077 assert_eq!(result, expected);
4078 }
4079
4080 #[rstest]
4081 #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), true)]
4082 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4083 fn test_is_html(#[case] node: Node, #[case] expected: bool) {
4084 assert_eq!(node.is_html(), expected);
4085 }
4086
4087 #[rstest]
4088 #[case(Node::node_values(
4089 &Node::Strong(Strong{values: vec!["test".to_string().into()], position: None})),
4090 vec!["test".to_string().into()])]
4091 #[case(Node::node_values(
4092 &Node::Text(Text{value: "test".to_string(), position: None})),
4093 vec!["test".to_string().into()])]
4094 #[case(Node::node_values(
4095 &Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None})),
4096 vec!["test".to_string().into()])]
4097 #[case(Node::node_values(
4098 &Node::Delete(Delete{values: vec!["test".to_string().into()], position: None})),
4099 vec!["test".to_string().into()])]
4100 #[case(Node::node_values(
4101 &Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None})),
4102 vec!["test".to_string().into()])]
4103 #[case(Node::node_values(
4104 &Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None})),
4105 vec!["test".to_string().into()])]
4106 #[case(Node::node_values(
4107 &Node::List(List{values: vec!["test".to_string().into()], ordered: false, level: 1, checked: Some(false), index: 0, position: None})),
4108 vec!["test".to_string().into()])]
4109 fn test_node_value(#[case] actual: Vec<Node>, #[case] expected: Vec<Node>) {
4110 assert_eq!(actual, expected);
4111 }
4112
4113 #[rstest]
4114 #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: Vec::new(), position: None}), true)]
4115 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4116 fn test_is_footnote(#[case] node: Node, #[case] expected: bool) {
4117 assert_eq!(node.is_footnote(), expected);
4118 }
4119
4120 #[rstest]
4121 #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), true)]
4122 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4123 fn test_is_footnote_ref(#[case] node: Node, #[case] expected: bool) {
4124 assert_eq!(node.is_footnote_ref(), expected);
4125 }
4126
4127 #[rstest]
4128 #[case(Node::Math(Math{value: "x^2".to_string(), position: None}), true)]
4129 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4130 fn test_is_math(#[case] node: Node, #[case] expected: bool) {
4131 assert_eq!(node.is_math(), expected);
4132 }
4133
4134 #[rstest]
4135 #[case(Node::Break(Break{position: None}), true)]
4136 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4137 fn test_is_break(#[case] node: Node, #[case] expected: bool) {
4138 assert_eq!(node.is_break(), expected);
4139 }
4140
4141 #[rstest]
4142 #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), true)]
4143 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4144 fn test_is_yaml(#[case] node: Node, #[case] expected: bool) {
4145 assert_eq!(node.is_yaml(), expected);
4146 }
4147
4148 #[rstest]
4149 #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), true)]
4150 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4151 fn test_is_toml(#[case] node: Node, #[case] expected: bool) {
4152 assert_eq!(node.is_toml(), expected);
4153 }
4154
4155 #[rstest]
4156 #[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)]
4157 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4158 fn test_is_definition(#[case] node: Node, #[case] expected: bool) {
4159 assert_eq!(node.is_definition(), expected);
4160 }
4161
4162 #[rstest]
4163 #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), true)]
4164 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4165 fn test_is_emphasis(#[case] node: Node, #[case] expected: bool) {
4166 assert_eq!(node.is_emphasis(), expected);
4167 }
4168
4169 #[rstest]
4170 #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), true)]
4171 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4172 fn test_is_mdx_flow_expression(#[case] node: Node, #[case] expected: bool) {
4173 assert_eq!(node.is_mdx_flow_expression(), expected);
4174 }
4175
4176 #[rstest]
4177 #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), true)]
4178 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4179 fn test_is_mdx_text_expression(#[case] node: Node, #[case] expected: bool) {
4180 assert_eq!(node.is_mdx_text_expression(), expected);
4181 }
4182
4183 #[rstest]
4184 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
4185 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4186 fn test_is_mdx_jsx_flow_element(#[case] node: Node, #[case] expected: bool) {
4187 assert_eq!(node.is_mdx_jsx_flow_element(), expected);
4188 }
4189
4190 #[rstest]
4191 #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), true)]
4192 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4193 fn test_is_mdx_jsx_text_element(#[case] node: Node, #[case] expected: bool) {
4194 assert_eq!(node.is_mdx_jsx_text_element(), expected);
4195 }
4196
4197 #[rstest]
4198 #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), true)]
4199 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4200 fn test_is_msx_js_esm(#[case] node: Node, #[case] expected: bool) {
4201 assert_eq!(node.is_mdx_js_esm(), expected);
4202 }
4203
4204 #[rstest]
4205 #[case::text(Node::Text(Text{value: "test".to_string(), position: None }), RenderOptions::default(), "test")]
4206 #[case::list(Node::List(List{index: 0, level: 2, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), " - test")]
4207 #[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")]
4208 #[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")]
4209 #[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")]
4210 #[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")]
4211 #[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")]
4212 #[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|")]
4213 #[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|")]
4214 #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
4215 #[case::table_cell(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "test")]
4216 #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right, TableAlignKind::Center, TableAlignKind::None], position: None}), RenderOptions::default(), "|:---|---:|:---:|---|")]
4217 #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "> test")]
4218 #[case::block_quote(Node::Blockquote(Blockquote{values: vec!["test\ntest2".to_string().into()], position: None}), RenderOptions::default(), "> test\n> test2")]
4219 #[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```")]
4220 #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), RenderOptions::default(), "```\ncode\n```")]
4221 #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}), RenderOptions::default(), " code")]
4222 #[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```")]
4223 #[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")]
4224 #[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\"")]
4225 #[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]: ")]
4226 #[case::delete(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "~~test~~")]
4227 #[case::emphasis(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "*test*")]
4228 #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![attr_keys::LABEL.to_string().into()], position: None}), RenderOptions::default(), "[^id]: label")]
4229 #[case::footnote_ref(Node::FootnoteRef(FootnoteRef{ident: attr_keys::LABEL.to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}), RenderOptions::default(), "[^label]")]
4230 #[case::heading(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "# test")]
4231 #[case::heading(Node::Heading(Heading{depth: 3, values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "### test")]
4232 #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), RenderOptions::default(), "<div>test</div>")]
4233 #[case::image(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}), RenderOptions::default(), "")]
4234 #[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(), "")]
4235 #[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]")]
4236 #[case::image_ref(Node::ImageRef(ImageRef{alt: "id".to_string(), ident: "id".to_string(), label: Some("id".to_string()), position: None}), RenderOptions::default(), "![id]")]
4237 #[case::code_inline(Node::CodeInline(CodeInline{value: "code".into(), position: None}), RenderOptions::default(), "`code`")]
4238 #[case::math_inline(Node::MathInline(MathInline{value: "x^2".into(), position: None}), RenderOptions::default(), "$x^2$")]
4239 #[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\")")]
4240 #[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]()")]
4241 #[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)")]
4242 #[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]")]
4243 #[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]")]
4244 #[case::math(Node::Math(Math{value: "x^2".to_string(), position: None}), RenderOptions::default(), "$$\nx^2\n$$")]
4245 #[case::strong(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}), RenderOptions::default(), "**test**")]
4246 #[case::yaml(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}), RenderOptions::default(), "---\nkey: value\n---")]
4247 #[case::toml(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}), RenderOptions::default(), "+++\nkey = \"value\"\n+++")]
4248 #[case::break_(Node::Break(Break{position: None}), RenderOptions::default(), "\\")]
4249 #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), RenderOptions::default(), "---")]
4250 #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4251 name: Some("div".to_string()),
4252 attributes: vec![
4253 MdxAttributeContent::Property(MdxJsxAttribute {
4254 name: "className".into(),
4255 value: Some(MdxAttributeValue::Literal("container".into()))
4256 })
4257 ],
4258 children: vec![
4259 "content".to_string().into()
4260 ],
4261 position: None
4262 }), RenderOptions::default(), "<div className=\"container\">content</div>")]
4263 #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4264 name: Some("div".to_string()),
4265 attributes: vec![
4266 MdxAttributeContent::Property(MdxJsxAttribute {
4267 name: "className".into(),
4268 value: Some(MdxAttributeValue::Literal("container".into()))
4269 })
4270 ],
4271 children: Vec::new(),
4272 position: None
4273 }), RenderOptions::default(), "<div className=\"container\" />")]
4274 #[case::mdx_jsx_flow_element(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4275 name: Some("div".to_string()),
4276 attributes: Vec::new(),
4277 children: Vec::new(),
4278 position: None
4279 }), RenderOptions::default(), "<div />")]
4280 #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
4281 name: Some("span".into()),
4282 attributes: vec![
4283 MdxAttributeContent::Expression("...props".into())
4284 ],
4285 children: vec![
4286 "inline".to_string().into()
4287 ],
4288 position: None
4289 }), RenderOptions::default(), "<span {...props}>inline</span>")]
4290 #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
4291 name: Some("span".into()),
4292 attributes: vec![
4293 MdxAttributeContent::Expression("...props".into())
4294 ],
4295 children: vec![
4296 ],
4297 position: None
4298 }), RenderOptions::default(), "<span {...props} />")]
4299 #[case::mdx_jsx_text_element(Node::MdxJsxTextElement(MdxJsxTextElement{
4300 name: Some("span".into()),
4301 attributes: vec![
4302 ],
4303 children: vec![
4304 ],
4305 position: None
4306 }), RenderOptions::default(), "<span />")]
4307 #[case(Node::MdxTextExpression(MdxTextExpression{
4308 value: "count + 1".into(),
4309 position: None,
4310 }), RenderOptions::default(), "{count + 1}")]
4311 #[case(Node::MdxJsEsm(MdxJsEsm{
4312 value: "import React from 'react'".into(),
4313 position: None,
4314 }), RenderOptions::default(), "import React from 'react'")]
4315 #[case::fragment_empty(Node::Fragment(Fragment{values: vec![]}), RenderOptions::default(), "")]
4316 #[case::fragment_single(Node::Fragment(Fragment{values: vec![
4317 Node::Text(Text{value: "hello".to_string(), position: None})
4318 ]}), RenderOptions::default(), "hello")]
4319 #[case::fragment_multiple(Node::Fragment(Fragment{values: vec![
4320 Node::Text(Text{value: "hello".to_string(), position: None}),
4321 Node::Text(Text{value: "world".to_string(), position: None})
4322 ]}), RenderOptions::default(), "hello\nworld")]
4323 #[case::fragment_filters_empty(Node::Fragment(Fragment{values: vec![
4324 Node::Text(Text{value: "hello".to_string(), position: None}),
4325 Node::Empty,
4326 Node::Text(Text{value: "world".to_string(), position: None})
4327 ]}), RenderOptions::default(), "hello\nworld")]
4328 #[case::fragment_all_empty(Node::Fragment(Fragment{values: vec![
4329 Node::Empty,
4330 Node::Empty,
4331 ]}), RenderOptions::default(), "")]
4332 #[cfg_attr(feature = "wikilink", case::wikilink(Node::WikiLink(WikiLink{target: "target".to_string(), text: None, position: None}), RenderOptions::default(), "[[target]]"))]
4333 #[cfg_attr(feature = "wikilink", case::wikilink_with_text(Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("display text".to_string()), position: None}), RenderOptions::default(), "[[target|display text]]"))]
4334 #[cfg_attr(feature = "wikilink", case::wikilink_same_text(Node::WikiLink(WikiLink{target: "target".to_string(), text: Some("target".to_string()), position: None}), RenderOptions::default(), "[[target]]"))]
4335 fn test_to_string_with(#[case] node: Node, #[case] options: RenderOptions, #[case] expected: &str) {
4336 assert_eq!(node.to_string_with(&options), expected);
4337 }
4338
4339 #[test]
4340 fn test_node_partial_ord() {
4341 let node1 = Node::Text(Text {
4342 value: "test1".to_string(),
4343 position: Some(Position {
4344 start: Point { line: 1, column: 1 },
4345 end: Point { line: 1, column: 5 },
4346 }),
4347 });
4348
4349 let node2 = Node::Text(Text {
4350 value: "test2".to_string(),
4351 position: Some(Position {
4352 start: Point { line: 1, column: 6 },
4353 end: Point { line: 1, column: 10 },
4354 }),
4355 });
4356
4357 let node3 = Node::Text(Text {
4358 value: "test3".to_string(),
4359 position: Some(Position {
4360 start: Point { line: 2, column: 1 },
4361 end: Point { line: 2, column: 5 },
4362 }),
4363 });
4364
4365 assert_eq!(node1.partial_cmp(&node2), Some(std::cmp::Ordering::Less));
4366 assert_eq!(node2.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
4367
4368 assert_eq!(node1.partial_cmp(&node3), Some(std::cmp::Ordering::Less));
4369 assert_eq!(node3.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
4370
4371 let node4 = Node::Text(Text {
4372 value: "test4".to_string(),
4373 position: None,
4374 });
4375
4376 assert_eq!(node1.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
4377 assert_eq!(node4.partial_cmp(&node1), Some(std::cmp::Ordering::Greater));
4378
4379 let node5 = Node::Text(Text {
4380 value: "test5".to_string(),
4381 position: None,
4382 });
4383
4384 assert_eq!(node4.partial_cmp(&node5), Some(std::cmp::Ordering::Equal));
4385
4386 let node6 = Node::Code(Code {
4387 value: "code".to_string(),
4388 lang: None,
4389 fence: true,
4390 meta: None,
4391 position: None,
4392 });
4393
4394 assert_eq!(node6.partial_cmp(&node4), Some(std::cmp::Ordering::Less));
4395 assert_eq!(node4.partial_cmp(&node6), Some(std::cmp::Ordering::Greater));
4396 }
4397
4398 #[rstest]
4399 #[case(Node::Blockquote(Blockquote{values: Vec::new(), position: None}), "blockquote")]
4400 #[case(Node::Break(Break{position: None}), "break")]
4401 #[case(Node::Definition(Definition{ident: "".to_string(), url: Url::new("".to_string()), title: None, label: None, position: None}), "definition")]
4402 #[case(Node::Delete(Delete{values: Vec::new(), position: None}), "delete")]
4403 #[case(Node::Heading(Heading{depth: 1, values: Vec::new(), position: None}), "h1")]
4404 #[case(Node::Heading(Heading{depth: 2, values: Vec::new(), position: None}), "h2")]
4405 #[case(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "h3")]
4406 #[case(Node::Heading(Heading{depth: 4, values: Vec::new(), position: None}), "h4")]
4407 #[case(Node::Heading(Heading{depth: 5, values: Vec::new(), position: None}), "h5")]
4408 #[case(Node::Heading(Heading{depth: 6, values: Vec::new(), position: None}), "h6")]
4409 #[case(Node::Heading(Heading{depth: 7, values: Vec::new(), position: None}), "h")]
4410 #[case(Node::Emphasis(Emphasis{values: Vec::new(), position: None}), "emphasis")]
4411 #[case(Node::Footnote(Footnote{ident: "".to_string(), values: Vec::new(), position: None}), "footnote")]
4412 #[case(Node::FootnoteRef(FootnoteRef{ident: "".to_string(), label: None, position: None}), "footnoteref")]
4413 #[case(Node::Html(Html{value: "".to_string(), position: None}), "html")]
4414 #[case(Node::Yaml(Yaml{value: "".to_string(), position: None}), "yaml")]
4415 #[case(Node::Toml(Toml{value: "".to_string(), position: None}), "toml")]
4416 #[case(Node::Image(Image{alt: "".to_string(), url: "".to_string(), title: None, position: None}), "image")]
4417 #[case(Node::ImageRef(ImageRef{alt: "".to_string(), ident: "".to_string(), label: None, position: None}), "image_ref")]
4418 #[case(Node::CodeInline(CodeInline{value: "".into(), position: None}), "code_inline")]
4419 #[case(Node::MathInline(MathInline{value: "".into(), position: None}), "math_inline")]
4420 #[case(Node::Link(Link{url: Url::new("".to_string()), title: None, values: Vec::new(), position: None}), "link")]
4421 #[case(Node::LinkRef(LinkRef{ident: "".to_string(), values: Vec::new(), label: None, position: None}), "link_ref")]
4422 #[case(Node::Math(Math{value: "".to_string(), position: None}), "math")]
4423 #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: Vec::new(), position: None}), "list")]
4424 #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "table_align")]
4425 #[case(Node::TableRow(TableRow{values: Vec::new(), position: None}), "table_row")]
4426 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}), "table_cell")]
4427 #[case(Node::Code(Code{value: "".to_string(), lang: None, fence: true, meta: None, position: None}), "code")]
4428 #[case(Node::Strong(Strong{values: Vec::new(), position: None}), "strong")]
4429 #[case(Node::HorizontalRule(HorizontalRule{position: None}), "Horizontal_rule")]
4430 #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "".into(), position: None}), "mdx_flow_expression")]
4431 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_flow_element")]
4432 #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}), "mdx_jsx_text_element")]
4433 #[case(Node::MdxTextExpression(MdxTextExpression{value: "".into(), position: None}), "mdx_text_expression")]
4434 #[case(Node::MdxJsEsm(MdxJsEsm{value: "".into(), position: None}), "mdx_js_esm")]
4435 #[case(Node::Text(Text{value: "".to_string(), position: None}), "text")]
4436 fn test_name(#[case] node: Node, #[case] expected: &str) {
4437 assert_eq!(node.name(), expected);
4438 }
4439
4440 #[rstest]
4441 #[case(Node::Text(Text{value: "test".to_string(), position: None}), "test")]
4442 #[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")]
4443 #[case(Node::Blockquote(Blockquote{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4444 #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4445 #[case(Node::Heading(Heading{depth: 1, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4446 #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4447 #[case(Node::Footnote(Footnote{ident: "test".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4448 #[case(Node::FootnoteRef(FootnoteRef{ident: "test".to_string(), label: None, position: None}), "test")]
4449 #[case(Node::Html(Html{value: "test".to_string(), position: None}), "test")]
4450 #[case(Node::Yaml(Yaml{value: "test".to_string(), position: None}), "test")]
4451 #[case(Node::Toml(Toml{value: "test".to_string(), position: None}), "test")]
4452 #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "test".to_string(), title: None, position: None}), "test")]
4453 #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "test".to_string(), label: None, position: None}), "test")]
4454 #[case(Node::CodeInline(CodeInline{value: "test".into(), position: None}), "test")]
4455 #[case(Node::MathInline(MathInline{value: "test".into(), position: None}), "test")]
4456 #[case(Node::Link(Link{url: Url::new("test".to_string()), title: None, values: Vec::new(), position: None}), "test")]
4457 #[case(Node::LinkRef(LinkRef{ident: "test".to_string(), values: Vec::new(), label: None, position: None}), "test")]
4458 #[case(Node::Math(Math{value: "test".to_string(), position: None}), "test")]
4459 #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}), "test")]
4460 #[case(Node::Strong(Strong{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4461 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}), "test")]
4462 #[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")]
4463 #[case(Node::Break(Break{position: None}), "")]
4464 #[case(Node::HorizontalRule(HorizontalRule{position: None}), "")]
4465 #[case(Node::TableAlign(TableAlign{align: Vec::new(), position: None}), "")]
4466 #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}), "test")]
4467 #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}), "test")]
4468 #[case(Node::MdxJsEsm(MdxJsEsm{value: "test".into(), position: None}), "test")]
4469 #[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")]
4470 #[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)]
4471 #[case(Node::Fragment(Fragment {values: vec![Node::Text(Text{value: "test".to_string(), position: None})]}), "test")]
4472 fn test_value(#[case] node: Node, #[case] expected: &str) {
4473 assert_eq!(node.value(), expected);
4474 }
4475
4476 #[rstest]
4477 #[case(Node::Text(Text{value: "test".to_string(), position: None}), None)]
4478 #[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}}))]
4479 #[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}}))]
4480 #[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}}))]
4481 #[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}}))]
4482 #[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}}))]
4483 #[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}}))]
4484 #[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}}))]
4485 #[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}}))]
4486 #[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}}))]
4487 #[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}}))]
4488 #[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}}))]
4489 #[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}}))]
4490 #[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}}))]
4491 #[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}}))]
4492 #[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}}))]
4493 #[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}}))]
4494 #[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}}))]
4495 #[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}}))]
4496 #[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}}))]
4497 #[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}}))]
4498 #[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}}))]
4499 #[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}}))]
4500 #[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}}))]
4501 #[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}}))]
4502 #[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}}))]
4503 #[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}}))]
4504 #[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}}))]
4505 #[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}}))]
4506 #[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}}))]
4507 #[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}}))]
4508 #[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}}))]
4509 #[case(Node::Fragment(Fragment{values: Vec::new()}), None)]
4510 #[case(Node::Fragment(Fragment{values: vec![
4511 Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
4512 Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
4513 ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}}))]
4514 #[case(Node::Fragment(Fragment{values: vec![
4515 Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}),
4516 Node::Text(Text{value: "test2".to_string(), position: None})
4517 ]}), Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}}))]
4518 #[case(Node::Fragment(Fragment{values: vec![
4519 Node::Text(Text{value: "test".to_string(), position: None}),
4520 Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})})
4521 ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
4522 #[case(Node::Fragment(Fragment{values: vec![
4523 Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}})}),
4524 Node::Text(Text{value: "test".to_string(), position: None})
4525 ]}), Some(Position{start: Point{line: 1, column: 6}, end: Point{line: 1, column: 10}}))]
4526 #[case(Node::Fragment(Fragment{values: vec![
4527 Node::Text(Text{value: "test".to_string(), position: None}),
4528 Node::Text(Text{value: "test2".to_string(), position: None})
4529 ]}), None)]
4530 #[case(Node::Empty, None)]
4531 fn test_position(#[case] node: Node, #[case] expected: Option<Position>) {
4532 assert_eq!(node.position(), expected);
4533 }
4534
4535 #[rstest]
4536 #[case(Node::Blockquote(Blockquote{values: vec![
4537 Node::Text(Text{value: "first".to_string(), position: None}),
4538 Node::Text(Text{value: "second".to_string(), position: None})
4539 ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
4540 #[case(Node::Blockquote(Blockquote{values: vec![
4541 Node::Text(Text{value: "first".to_string(), position: None}),
4542 Node::Text(Text{value: "second".to_string(), position: None})
4543 ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
4544 #[case(Node::Blockquote(Blockquote{values: vec![
4545 Node::Text(Text{value: "first".to_string(), position: None})
4546 ], position: None}), 1, None)]
4547 #[case(Node::Delete(Delete{values: vec![
4548 Node::Text(Text{value: "first".to_string(), position: None}),
4549 Node::Text(Text{value: "second".to_string(), position: None})
4550 ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
4551 #[case(Node::Emphasis(Emphasis{values: vec![
4552 Node::Text(Text{value: "first".to_string(), position: None}),
4553 Node::Text(Text{value: "second".to_string(), position: None})
4554 ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
4555 #[case(Node::Strong(Strong{values: vec![
4556 Node::Text(Text{value: "first".to_string(), position: None})
4557 ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
4558 #[case(Node::Heading(Heading{depth: 1, values: vec![
4559 Node::Text(Text{value: "first".to_string(), position: None}),
4560 Node::Text(Text{value: "second".to_string(), position: None})
4561 ], position: None}), 0, Some(Node::Text(Text{value: "first".to_string(), position: None})))]
4562 #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
4563 Node::Text(Text{value: "first".to_string(), position: None}),
4564 Node::Text(Text{value: "second".to_string(), position: None})
4565 ], position: None}), 1, Some(Node::Text(Text{value: "second".to_string(), position: None})))]
4566 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4567 Node::Text(Text{value: "cell content".to_string(), position: None})
4568 ], position: None}), 0, Some(Node::Text(Text{value: "cell content".to_string(), position: None})))]
4569 #[case(Node::TableRow(TableRow{values: vec![
4570 Node::TableCell(TableCell{column: 0, row: 0, values: Vec::new(), position: None}),
4571 Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})
4572 ], position: None}), 1, Some(Node::TableCell(TableCell{column: 1, row: 0, values: Vec::new(), position: None})))]
4573 #[case(Node::Text(Text{value: "plain text".to_string(), position: None}), 0, None)]
4574 #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}), 0, None)]
4575 #[case(Node::Html(Html{value: "<div>".to_string(), position: None}), 0, None)]
4576 fn test_find_at_index(#[case] node: Node, #[case] index: usize, #[case] expected: Option<Node>) {
4577 assert_eq!(node.find_at_index(index), expected);
4578 }
4579
4580 #[rstest]
4581 #[case(Node::Blockquote(Blockquote{values: vec!["test".to_string().into()], position: None}),
4582 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4583 #[case(Node::Delete(Delete{values: vec!["test".to_string().into()], position: None}),
4584 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4585 #[case(Node::Heading(Heading{depth: 1, values: vec!["test".to_string().into()], position: None}),
4586 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4587 #[case(Node::Emphasis(Emphasis{values: vec!["test".to_string().into()], position: None}),
4588 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4589 #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}),
4590 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4591 #[case(Node::Strong(Strong{values: vec!["test".to_string().into()], position: None}),
4592 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4593 #[case(Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec!["test".to_string().into()], position: None}),
4594 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4595 #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec!["test".to_string().into()], label: None, position: None}),
4596 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4597 #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec!["test".to_string().into()], position: None}),
4598 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4599 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec!["test".to_string().into()], position: None}),
4600 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4601 #[case(Node::TableRow(TableRow{values: vec!["test".to_string().into()], position: None}),
4602 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4603 #[case(Node::Fragment(Fragment{values: vec!["test".to_string().into()]}),
4604 Node::Fragment(Fragment{values: vec!["test".to_string().into()]}))]
4605 #[case(Node::Text(Text{value: "test".to_string(), position: None}),
4606 Node::Empty)]
4607 #[case(Node::Code(Code{value: "test".to_string(), lang: None, fence: true, meta: None, position: None}),
4608 Node::Empty)]
4609 #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4610 Node::Empty)]
4611 #[case(Node::Empty, Node::Empty)]
4612 fn test_to_fragment(#[case] node: Node, #[case] expected: Node) {
4613 assert_eq!(node.to_fragment(), expected);
4614 }
4615
4616 #[rstest]
4617 #[case(
4618 &mut Node::Blockquote(Blockquote{values: vec![
4619 Node::Text(Text{value: "old".to_string(), position: None})
4620 ], position: None}),
4621 Node::Fragment(Fragment{values: vec![
4622 Node::Text(Text{value: "new".to_string(), position: None})
4623 ]}),
4624 Node::Blockquote(Blockquote{values: vec![
4625 Node::Text(Text{value: "new".to_string(), position: None})
4626 ], position: None})
4627 )]
4628 #[case(
4629 &mut Node::Delete(Delete{values: vec![
4630 Node::Text(Text{value: "old".to_string(), position: None})
4631 ], position: None}),
4632 Node::Fragment(Fragment{values: vec![
4633 Node::Text(Text{value: "new".to_string(), position: None})
4634 ]}),
4635 Node::Delete(Delete{values: vec![
4636 Node::Text(Text{value: "new".to_string(), position: None})
4637 ], position: None})
4638 )]
4639 #[case(
4640 &mut Node::Emphasis(Emphasis{values: vec![
4641 Node::Text(Text{value: "old".to_string(), position: None})
4642 ], position: None}),
4643 Node::Fragment(Fragment{values: vec![
4644 Node::Text(Text{value: "new".to_string(), position: None})
4645 ]}),
4646 Node::Emphasis(Emphasis{values: vec![
4647 Node::Text(Text{value: "new".to_string(), position: None})
4648 ], position: None})
4649 )]
4650 #[case(
4651 &mut Node::Strong(Strong{values: vec![
4652 Node::Text(Text{value: "old".to_string(), position: None})
4653 ], position: None}),
4654 Node::Fragment(Fragment{values: vec![
4655 Node::Text(Text{value: "new".to_string(), position: None})
4656 ]}),
4657 Node::Strong(Strong{values: vec![
4658 Node::Text(Text{value: "new".to_string(), position: None})
4659 ], position: None})
4660 )]
4661 #[case(
4662 &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
4663 Node::Text(Text{value: "old".to_string(), position: None})
4664 ], position: None}),
4665 Node::Fragment(Fragment{values: vec![
4666 Node::Text(Text{value: "new".to_string(), position: None})
4667 ]}),
4668 Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
4669 Node::Text(Text{value: "new".to_string(), position: None})
4670 ], position: None})
4671 )]
4672 #[case(
4673 &mut Node::Heading(Heading{depth: 1, values: vec![
4674 Node::Text(Text{value: "old".to_string(), position: None})
4675 ], position: None}),
4676 Node::Fragment(Fragment{values: vec![
4677 Node::Text(Text{value: "new".to_string(), position: None})
4678 ]}),
4679 Node::Heading(Heading{depth: 1, values: vec![
4680 Node::Text(Text{value: "new".to_string(), position: None})
4681 ], position: None})
4682 )]
4683 #[case(
4684 &mut Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
4685 Node::Text(Text{value: "old".to_string(), position: None})
4686 ], position: None}),
4687 Node::Fragment(Fragment{values: vec![
4688 Node::Text(Text{value: "new".to_string(), position: None})
4689 ]}),
4690 Node::Link(Link{url: Url(attr_keys::URL.to_string()), title: None, values: vec![
4691 Node::Text(Text{value: "new".to_string(), position: None})
4692 ], position: None})
4693 )]
4694 #[case(
4695 &mut Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
4696 Node::Text(Text{value: "old".to_string(), position: None})
4697 ], label: None, position: None}),
4698 Node::Fragment(Fragment{values: vec![
4699 Node::Text(Text{value: "new".to_string(), position: None})
4700 ]}),
4701 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![
4702 Node::Text(Text{value: "new".to_string(), position: None})
4703 ], label: None, position: None})
4704 )]
4705 #[case(
4706 &mut Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
4707 Node::Text(Text{value: "old".to_string(), position: None})
4708 ], position: None}),
4709 Node::Fragment(Fragment{values: vec![
4710 Node::Text(Text{value: "new".to_string(), position: None})
4711 ]}),
4712 Node::Footnote(Footnote{ident: "id".to_string(), values: vec![
4713 Node::Text(Text{value: "new".to_string(), position: None})
4714 ], position: None})
4715 )]
4716 #[case(
4717 &mut Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4718 Node::Text(Text{value: "old".to_string(), position: None})
4719 ], position: None}),
4720 Node::Fragment(Fragment{values: vec![
4721 Node::Text(Text{value: "new".to_string(), position: None})
4722 ]}),
4723 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4724 Node::Text(Text{value: "new".to_string(), position: None})
4725 ], position: None})
4726 )]
4727 #[case(
4728 &mut Node::TableRow(TableRow{values: vec![
4729 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4730 Node::Text(Text{value: "old".to_string(), position: None})
4731 ], position: None})
4732 ], position: None}),
4733 Node::Fragment(Fragment{values: vec![
4734 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4735 Node::Text(Text{value: "new".to_string(), position: None})
4736 ], position: None})
4737 ]}),
4738 Node::TableRow(TableRow{values: vec![
4739 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4740 Node::Text(Text{value: "new".to_string(), position: None})
4741 ], position: None})
4742 ], position: None})
4743 )]
4744 #[case(
4745 &mut Node::Text(Text{value: "old".to_string(), position: None}),
4746 Node::Fragment(Fragment{values: vec![
4747 Node::Text(Text{value: "new".to_string(), position: None})
4748 ]}),
4749 Node::Text(Text{value: "old".to_string(), position: None})
4750 )]
4751 #[case(
4752 &mut Node::Blockquote(Blockquote{values: vec![
4753 Node::Text(Text{value: "text1".to_string(), position: None}),
4754 Node::Text(Text{value: "text2".to_string(), position: None})
4755 ], position: None}),
4756 Node::Fragment(Fragment{values: vec![
4757 Node::Text(Text{value: "new1".to_string(), position: None}),
4758 Node::Text(Text{value: "new2".to_string(), position: None})
4759 ]}),
4760 Node::Blockquote(Blockquote{values: vec![
4761 Node::Text(Text{value: "new1".to_string(), position: None}),
4762 Node::Text(Text{value: "new2".to_string(), position: None})
4763 ], position: None})
4764 )]
4765 #[case(
4766 &mut Node::Strong(Strong{values: vec![
4767 Node::Text(Text{value: "text1".to_string(), position: None}),
4768 Node::Text(Text{value: "text2".to_string(), position: None})
4769 ], position: None}),
4770 Node::Fragment(Fragment{values: vec![
4771 Node::Empty,
4772 Node::Text(Text{value: "new2".to_string(), position: None})
4773 ]}),
4774 Node::Strong(Strong{values: vec![
4775 Node::Text(Text{value: "text1".to_string(), position: None}),
4776 Node::Text(Text{value: "new2".to_string(), position: None})
4777 ], position: None})
4778 )]
4779 #[case(
4780 &mut Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
4781 Node::Text(Text{value: "text1".to_string(), position: None}),
4782 Node::Text(Text{value: "text2".to_string(), position: None})
4783 ], position: None}),
4784 Node::Fragment(Fragment{values: vec![
4785 Node::Text(Text{value: "new1".to_string(), position: None}),
4786 Node::Fragment(Fragment{values: Vec::new()})
4787 ]}),
4788 Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec![
4789 Node::Text(Text{value: "new1".to_string(), position: None}),
4790 Node::Text(Text{value: "text2".to_string(), position: None})
4791 ], position: None})
4792 )]
4793 fn test_apply_fragment(#[case] node: &mut Node, #[case] fragment: Node, #[case] expected: Node) {
4794 node.apply_fragment(fragment);
4795 assert_eq!(*node, expected);
4796 }
4797
4798 #[rstest]
4799 #[case(Node::Text(Text{value: "test".to_string(), position: None}),
4800 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4801 Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4802 #[case(Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None}),
4803 Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
4804 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}})}))]
4805 #[case(Node::List(List{index: 0, level: 1, checked: None, ordered: false, values: vec![], position: None}),
4806 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4807 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}})}))]
4808 #[case(Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
4809 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4810 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}})}))]
4811 #[case(Node::Delete(Delete{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4812 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4813 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}})}))]
4814 #[case(Node::Emphasis(Emphasis{values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4815 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4816 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}})}))]
4817 #[case(Node::Footnote(Footnote{ident: "id".to_string(), values: vec![Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4818 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4819 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}})}))]
4820 #[case(Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
4821 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4822 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}})}))]
4823 #[case(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}),
4824 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
4825 Node::Html(Html{value: "<div>test</div>".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
4826 #[case(Node::Yaml(Yaml{value: "key: value".to_string(), position: None}),
4827 Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
4828 Node::Yaml(Yaml{value: "key: value".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
4829 #[case(Node::Toml(Toml{value: "key = \"value\"".to_string(), position: None}),
4830 Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}},
4831 Node::Toml(Toml{value: "key = \"value\"".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 4}})}))]
4832 #[case(Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
4833 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 12}},
4834 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}})}))]
4835 #[case(Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
4836 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4837 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}})}))]
4838 #[case(Node::CodeInline(CodeInline{value: "code".into(), position: None}),
4839 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4840 Node::CodeInline(CodeInline{value: "code".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4841 #[case(Node::MathInline(MathInline{value: "x^2".into(), position: None}),
4842 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4843 Node::MathInline(MathInline{value: "x^2".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4844 #[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}),
4845 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4846 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}})}))]
4847 #[case(Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![Node::Text(Text{value: "text".to_string(), position: None})], label: None, position: None}),
4848 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4849 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}})}))]
4850 #[case(Node::Math(Math{value: "x^2".to_string(), position: None}),
4851 Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}},
4852 Node::Math(Math{value: "x^2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 3, column: 3}})}))]
4853 #[case(Node::TableCell(TableCell{column: 0, row: 0, values: vec![Node::Text(Text{value: "cell".to_string(), position: None})], position: None}),
4854 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 6}},
4855 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}})}))]
4856 #[case(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
4857 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}},
4858 Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 15}})}))]
4859 #[case(Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: None}),
4860 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4861 Node::MdxFlowExpression(MdxFlowExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4862 #[case(Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: None}),
4863 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}},
4864 Node::MdxTextExpression(MdxTextExpression{value: "test".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 7}})}))]
4865 #[case(Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: None}),
4866 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}},
4867 Node::MdxJsEsm(MdxJsEsm{value: "import React from 'react'".into(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 25}})}))]
4868 #[case(Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: vec![Node::Text(Text{value: "text".to_string(), position: None})], position: None}),
4869 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4870 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}})}))]
4871 #[case(Node::Break(Break{position: None}),
4872 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}},
4873 Node::Break(Break{position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 2}})}))]
4874 #[case(Node::Empty,
4875 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4876 Node::Empty)]
4877 #[case(Node::Fragment(Fragment{values: vec![
4878 Node::Text(Text{value: "test1".to_string(), position: None}),
4879 Node::Text(Text{value: "test2".to_string(), position: None})
4880 ]}),
4881 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4882 Node::Fragment(Fragment{values: vec![
4883 Node::Text(Text{value: "test1".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}),
4884 Node::Text(Text{value: "test2".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4885 ]}))]
4886 #[case(Node::Blockquote(Blockquote{values: vec![
4887 Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4888 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4889 Node::Blockquote(Blockquote{values: vec![
4890 Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4891 ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4892 #[case(Node::Heading(Heading{depth: 1, values: vec![
4893 Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4894 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4895 Node::Heading(Heading{depth: 1, values: vec![
4896 Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4897 ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4898 #[case(Node::Strong(Strong{values: vec![
4899 Node::Text(Text{value: "test".to_string(), position: None})], position: None}),
4900 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}},
4901 Node::Strong(Strong{values: vec![
4902 Node::Text(Text{value: "test".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})})
4903 ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 5}})}))]
4904 #[case(Node::TableRow(TableRow{values: vec![
4905 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4906 Node::Text(Text{value: "cell".to_string(), position: None})
4907 ], position: None})
4908 ], position: None}),
4909 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}},
4910 Node::TableRow(TableRow{values: vec![
4911 Node::TableCell(TableCell{column: 0, row: 0, values: vec![
4912 Node::Text(Text{value: "cell".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4913 ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})})
4914 ], position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 10}})}))]
4915 #[case(Node::MdxJsxFlowElement(MdxJsxFlowElement{
4916 name: Some("div".to_string()),
4917 attributes: Vec::new(),
4918 children: vec![Node::Text(Text{value: "content".to_string(), position: None})],
4919 position: None
4920 }),
4921 Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}},
4922 Node::MdxJsxFlowElement(MdxJsxFlowElement{
4923 name: Some("div".to_string()),
4924 attributes: Vec::new(),
4925 children: vec![Node::Text(Text{value: "content".to_string(), position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})})],
4926 position: Some(Position{start: Point{line: 1, column: 1}, end: Point{line: 1, column: 20}})
4927 }))]
4928 fn test_set_position(#[case] mut node: Node, #[case] position: Position, #[case] expected: Node) {
4929 node.set_position(Some(position));
4930 assert_eq!(node, expected);
4931 }
4932
4933 #[rstest]
4934 #[case(Node::List(List{index: 0, level: 0, checked: None, ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4935 #[case(Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec!["test".to_string().into()], position: None}), true)]
4936 #[case(Node::Text(Text{value: "test".to_string(), position: None}), false)]
4937 fn test_is_list(#[case] node: Node, #[case] expected: bool) {
4938 assert_eq!(node.is_list(), expected);
4939 }
4940
4941 #[rstest]
4942 #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::None, ..Default::default()}, "https://example.com")]
4943 #[case(Url::new("https://example.com".to_string()), RenderOptions{link_url_style: UrlSurroundStyle::Angle, ..Default::default()}, "<https://example.com>")]
4944 #[case(Url::new("".to_string()), RenderOptions::default(), "")]
4945 fn test_url_to_string_with(#[case] url: Url, #[case] options: RenderOptions, #[case] expected: &str) {
4946 assert_eq!(url.to_string_with(&options), expected);
4947 }
4948
4949 #[rstest]
4950 #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions::default(), "\"title\"")]
4951 #[case(Title::new(r#"title with "quotes""#.to_string()), RenderOptions::default(), r#""title with "quotes"""#)]
4952 #[case(Title::new("title with spaces".to_string()), RenderOptions::default(), "\"title with spaces\"")]
4953 #[case(Title::new("".to_string()), RenderOptions::default(), "\"\"")]
4954 #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Single, ..Default::default()}, "'title'")]
4955 #[case(Title::new("title with 'quotes'".to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Double, ..Default::default()}, "\"title with 'quotes'\"")]
4956 #[case(Title::new(attr_keys::TITLE.to_string()), RenderOptions{link_title_style: TitleSurroundStyle::Paren, ..Default::default()}, "(title)")]
4957 fn test_title_to_string_with(#[case] title: Title, #[case] options: RenderOptions, #[case] expected: &str) {
4958 assert_eq!(title.to_string_with(&options), expected);
4959 }
4960
4961 #[rstest]
4962 #[case(Node::Fragment(Fragment{values: vec![]}), true)]
4963 #[case(Node::Fragment(Fragment{values: vec![
4964 Node::Text(Text{value: "not_empty".to_string(), position: None})
4965 ]}), false)]
4966 #[case(Node::Fragment(Fragment{values: vec![
4967 Node::Fragment(Fragment{values: vec![]}),
4968 Node::Fragment(Fragment{values: vec![]})
4969 ]}), true)]
4970 #[case(Node::Fragment(Fragment{values: vec![
4971 Node::Fragment(Fragment{values: vec![]}),
4972 Node::Text(Text{value: "not_empty".to_string(), position: None})
4973 ]}), false)]
4974 #[case(Node::Text(Text{value: "not_fragment".to_string(), position: None}), false)]
4975 fn test_is_empty_fragment(#[case] node: Node, #[case] expected: bool) {
4976 assert_eq!(node.is_empty_fragment(), expected);
4977 }
4978
4979 #[rstest]
4980 #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), attr_keys::IDENT, Some(AttrValue::String("id".to_string())))]
4981 #[case::footnote(Node::Footnote(Footnote{ident: "id".to_string(), values: Vec::new(), position: None}), "unknown", None)]
4982 #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("<div>test</div>".to_string())))]
4983 #[case::html(Node::Html(Html{value: "<div>test</div>".to_string(), position: None}), "unknown", None)]
4984 #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("text".to_string())))]
4985 #[case::text(Node::Text(Text{value: "text".to_string(), position: None}), "unknown", None)]
4986 #[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())))]
4987 #[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())))]
4988 #[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())))]
4989 #[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)))]
4990 #[case::code(Node::Code(Code{value: "code".to_string(), lang: None, meta: None, fence: false, position: None}), attr_keys::FENCE, Some(AttrValue::Boolean(false)))]
4991 #[case::code_inline(Node::CodeInline(CodeInline{value: "inline".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("inline".to_string())))]
4992 #[case::math_inline(Node::MathInline(MathInline{value: "math".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4993 #[case::math(Node::Math(Math{value: "math".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("math".to_string())))]
4994 #[case::yaml(Node::Yaml(Yaml{value: "yaml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("yaml".to_string())))]
4995 #[case::toml(Node::Toml(Toml{value: "toml".to_string(), position: None}), attr_keys::VALUE, Some(AttrValue::String("toml".to_string())))]
4996 #[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())))]
4997 #[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())))]
4998 #[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())))]
4999 #[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())))]
5000 #[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())))]
5001 #[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())))]
5002 #[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())))]
5003 #[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())))]
5004 #[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())))]
5005 #[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())))]
5006 #[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())))]
5007 #[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())))]
5008 #[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())))]
5009 #[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())))]
5010 #[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())))]
5011 #[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())))]
5012 #[case::heading(Node::Heading(Heading{depth: 3, values: Vec::new(), position: None}), "depth", Some(AttrValue::Integer(3)))]
5013 #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "index", Some(AttrValue::Integer(2)))]
5014 #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "level", Some(AttrValue::Integer(1)))]
5015 #[case::list(Node::List(List{index: 2, level: 1, checked: Some(true), ordered: true, values: Vec::new(), position: None}), "ordered", Some(AttrValue::Boolean(true)))]
5016 #[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)))]
5017 #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "column", Some(AttrValue::Integer(1)))]
5018 #[case::table_cell(Node::TableCell(TableCell{column: 1, row: 2, values: Vec::new(), position: None}), "row", Some(AttrValue::Integer(2)))]
5019 #[case::table_align(Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}), "align", Some(AttrValue::String(":---,---:".to_string())))]
5020 #[case::mdx_flow_expression(Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
5021 #[case::mdx_flow_expression(Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("expr".to_string())))]
5022 #[case::mdx_js_esm(Node::MdxJsEsm(MdxJsEsm{value: "esm".into(), position: None}), attr_keys::VALUE, Some(AttrValue::String("esm".to_string())))]
5023 #[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())))]
5024 #[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())))]
5025 #[case::break_(Node::Break(Break{position: None}), attr_keys::VALUE, None)]
5026 #[case::horizontal_rule(Node::HorizontalRule(HorizontalRule{position: None}), attr_keys::VALUE, None)]
5027 #[case::fragment(Node::Fragment(Fragment{values: Vec::new()}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
5028 #[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())))]
5029 #[case::heading(Node::Heading(Heading{depth: 2, values: vec![], position: None}), attr_keys::VALUE, Some(AttrValue::String("".to_string())))]
5030 #[case::heading(Node::Heading(Heading{depth: 3, values: vec![
5031 Node::Text(Text{value: "first".to_string(), position: None}),
5032 Node::Text(Text{value: "second".to_string(), position: None}),
5033 ], position: None}), attr_keys::VALUE, Some(AttrValue::String("firstsecond".to_string())))]
5034 #[case(
5035 Node::List(List {
5036 index: 0,
5037 level: 1,
5038 checked: None,
5039 ordered: false,
5040 values: vec![
5041 Node::Text(Text { value: "item1".to_string(), position: None }),
5042 Node::Text(Text { value: "item2".to_string(), position: None }),
5043 ],
5044 position: None,
5045 }),
5046 attr_keys::VALUE,
5047 Some(AttrValue::String("item1item2".to_string()))
5048 )]
5049 #[case(
5050 Node::TableCell(TableCell {
5051 column: 1,
5052 row: 2,
5053 values: vec![Node::Text(Text {
5054 value: "cell_value".to_string(),
5055 position: None,
5056 })],
5057 position: None,
5058 }),
5059 attr_keys::VALUE,
5060 Some(AttrValue::String("cell_value".to_string()))
5061 )]
5062 #[case::footnote(
5063 Node::Footnote(Footnote {
5064 ident: "id".to_string(),
5065 values: vec![Node::Text(Text {
5066 value: "footnote value".to_string(),
5067 position: None,
5068 })],
5069 position: None,
5070 }),
5071 attr_keys::VALUE,
5072 Some(AttrValue::String("footnote value".to_string()))
5073 )]
5074 #[case::link(
5075 Node::Link(Link {
5076 url: Url::new("https://example.com".to_string()),
5077 title: Some(Title::new("Example".to_string())),
5078 values: vec![Node::Text(Text {
5079 value: "link text".to_string(),
5080 position: None,
5081 })],
5082 position: None,
5083 }),
5084 attr_keys::VALUE,
5085 Some(AttrValue::String("link text".to_string()))
5086 )]
5087 #[case::empty(Node::Empty, attr_keys::VALUE, None)]
5088 #[case::heading(
5089 Node::Heading(Heading {
5090 depth: 1,
5091 values: vec![
5092 Node::Text(Text {
5093 value: "child1".to_string(),
5094 position: None,
5095 }),
5096 Node::Text(Text {
5097 value: "child2".to_string(),
5098 position: None,
5099 }),
5100 ],
5101 position: None,
5102 }),
5103 attr_keys::CHILDREN,
5104 Some(AttrValue::Array(vec![
5105 Node::Text(Text {
5106 value: "child1".to_string(),
5107 position: None,
5108 }),
5109 Node::Text(Text {
5110 value: "child2".to_string(),
5111 position: None,
5112 }),
5113 ]))
5114 )]
5115 #[case::list(
5116 Node::List(List {
5117 index: 0,
5118 level: 1,
5119 checked: None,
5120 ordered: false,
5121 values: vec![
5122 Node::Text(Text {
5123 value: "item1".to_string(),
5124 position: None,
5125 }),
5126 ],
5127 position: None,
5128 }),
5129 attr_keys::CHILDREN,
5130 Some(AttrValue::Array(vec![
5131 Node::Text(Text {
5132 value: "item1".to_string(),
5133 position: None,
5134 }),
5135 ]))
5136 )]
5137 #[case::blockquote(
5138 Node::Blockquote(Blockquote {
5139 values: vec![
5140 Node::Text(Text {
5141 value: "quote".to_string(),
5142 position: None,
5143 }),
5144 ],
5145 position: None,
5146 }),
5147 attr_keys::VALUES,
5148 Some(AttrValue::Array(vec![
5149 Node::Text(Text {
5150 value: "quote".to_string(),
5151 position: None,
5152 }),
5153 ]))
5154 )]
5155 #[case::link(
5156 Node::Link(Link {
5157 url: Url::new(attr_keys::URL.to_string()),
5158 title: None,
5159 values: vec![
5160 Node::Text(Text {
5161 value: "link".to_string(),
5162 position: None,
5163 }),
5164 ],
5165 position: None,
5166 }),
5167 attr_keys::VALUES,
5168 Some(AttrValue::Array(vec![
5169 Node::Text(Text {
5170 value: "link".to_string(),
5171 position: None,
5172 }),
5173 ]))
5174 )]
5175 #[case::table_cell(
5176 Node::TableCell(TableCell {
5177 column: 0,
5178 row: 0,
5179 values: vec![
5180 Node::Text(Text {
5181 value: "cell".to_string(),
5182 position: None,
5183 }),
5184 ],
5185 position: None,
5186 }),
5187 attr_keys::CHILDREN,
5188 Some(AttrValue::Array(vec![
5189 Node::Text(Text {
5190 value: "cell".to_string(),
5191 position: None,
5192 }),
5193 ]))
5194 )]
5195 #[case::strong(
5196 Node::Strong(Strong {
5197 values: vec![
5198 Node::Text(Text {
5199 value: "bold".to_string(),
5200 position: None,
5201 }),
5202 ],
5203 position: None,
5204 }),
5205 attr_keys::CHILDREN,
5206 Some(AttrValue::Array(vec![
5207 Node::Text(Text {
5208 value: "bold".to_string(),
5209 position: None,
5210 }),
5211 ]))
5212 )]
5213 #[case::em(
5214 Node::Emphasis(Emphasis {
5215 values: vec![],
5216 position: None,
5217 }),
5218 attr_keys::CHILDREN,
5219 Some(AttrValue::Array(vec![]))
5220 )]
5221 fn test_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
5222 assert_eq!(node.attr(attr), expected);
5223 }
5224
5225 #[rstest]
5226 #[case(
5227 Node::Text(Text{value: "old".to_string(), position: None}),
5228 attr_keys::VALUE,
5229 "new",
5230 Node::Text(Text{value: "new".to_string(), position: None})
5231 )]
5232 #[case(
5233 Node::Code(Code{value: "old".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
5234 attr_keys::VALUE,
5235 "new_code",
5236 Node::Code(Code{value: "new_code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None})
5237 )]
5238 #[case(
5239 Node::Code(Code{value: "code".to_string(), lang: Some("rust".to_string()), fence: true, meta: None, position: None}),
5240 attr_keys::LANG,
5241 "python",
5242 Node::Code(Code{value: "code".to_string(), lang: Some("python".to_string()), fence: true, meta: None, position: None})
5243 )]
5244 #[case(
5245 Node::Code(Code{value: "code".to_string(), lang: None, fence: false, meta: None, position: None}),
5246 attr_keys::FENCE,
5247 "true",
5248 Node::Code(Code{value: "code".to_string(), lang: None, fence: true, meta: None, position: None})
5249 )]
5250 #[case(
5251 Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
5252 attr_keys::ALT,
5253 "new_alt",
5254 Node::Image(Image{alt: "new_alt".to_string(), url: attr_keys::URL.to_string(), title: None, position: None})
5255 )]
5256 #[case(
5257 Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: None, position: None}),
5258 attr_keys::URL,
5259 "new_url",
5260 Node::Image(Image{alt: attr_keys::ALT.to_string(), url: "new_url".to_string(), title: None, position: None})
5261 )]
5262 #[case(
5263 Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some(attr_keys::TITLE.to_string()), position: None}),
5264 attr_keys::TITLE,
5265 "new_title",
5266 Node::Image(Image{alt: attr_keys::ALT.to_string(), url: attr_keys::URL.to_string(), title: Some("new_title".to_string()), position: None})
5267 )]
5268 #[case(
5269 Node::Heading(Heading{depth: 2, values: vec![], position: None}),
5270 "depth",
5271 "3",
5272 Node::Heading(Heading{depth: 3, values: vec![], position: None})
5273 )]
5274 #[case(
5275 Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
5276 attr_keys::CHECKED,
5277 "false",
5278 Node::List(List{index: 1, level: 2, checked: Some(false), ordered: false, values: vec![], position: None})
5279 )]
5280 #[case(
5281 Node::List(List{index: 1, level: 2, checked: Some(true), ordered: false, values: vec![], position: None}),
5282 "ordered",
5283 "true",
5284 Node::List(List{index: 1, level: 2, checked: Some(true), ordered: true, values: vec![], position: None})
5285 )]
5286 #[case(
5287 Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
5288 "column",
5289 "3",
5290 Node::TableCell(TableCell{column: 3, row: 2, values: vec![], position: None})
5291 )]
5292 #[case(
5293 Node::TableCell(TableCell{column: 1, row: 2, values: vec![], position: None}),
5294 "row",
5295 "5",
5296 Node::TableCell(TableCell{column: 1, row: 5, values: vec![], position: None})
5297 )]
5298 #[case(
5299 Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
5300 attr_keys::IDENT,
5301 "new_id",
5302 Node::Definition(Definition{ident: "new_id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None})
5303 )]
5304 #[case(
5305 Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
5306 attr_keys::URL,
5307 "new_url",
5308 Node::Definition(Definition{ident: "id".to_string(), url: Url::new("new_url".to_string()), title: None, label: None, position: None})
5309 )]
5310 #[case(
5311 Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
5312 attr_keys::LABEL,
5313 "new_label",
5314 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})
5315 )]
5316 #[case(
5317 Node::Definition(Definition{ident: "id".to_string(), url: Url::new(attr_keys::URL.to_string()), title: None, label: None, position: None}),
5318 attr_keys::TITLE,
5319 "new_title",
5320 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})
5321 )]
5322 #[case(
5323 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5324 attr_keys::ALT,
5325 "new_alt",
5326 Node::ImageRef(ImageRef{alt: "new_alt".to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
5327 )]
5328 #[case(
5329 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5330 attr_keys::IDENT,
5331 "new_id",
5332 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
5333 )]
5334 #[case(
5335 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5336 attr_keys::LABEL,
5337 "new_label",
5338 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
5339 )]
5340 #[case(
5341 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: None, position: None}),
5342 attr_keys::LABEL,
5343 "new_label",
5344 Node::ImageRef(ImageRef{alt: attr_keys::ALT.to_string(), ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
5345 )]
5346 #[case(
5347 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
5348 attr_keys::IDENT,
5349 "new_id",
5350 Node::LinkRef(LinkRef{ident: "new_id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
5351 )]
5352 #[case(
5353 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
5354 attr_keys::LABEL,
5355 "new_label",
5356 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
5357 )]
5358 #[case(
5359 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: None, position: None}),
5360 attr_keys::LABEL,
5361 "new_label",
5362 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some("new_label".to_string()), position: None})
5363 )]
5364 #[case(
5365 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None}),
5366 "unknown",
5367 "ignored",
5368 Node::LinkRef(LinkRef{ident: "id".to_string(), values: vec![], label: Some(attr_keys::LABEL.to_string()), position: None})
5369 )]
5370 #[case(
5371 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5372 attr_keys::IDENT,
5373 "new_id",
5374 Node::FootnoteRef(FootnoteRef{ident: "new_id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
5375 )]
5376 #[case(
5377 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5378 attr_keys::LABEL,
5379 "new_label",
5380 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
5381 )]
5382 #[case(
5383 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: None, position: None}),
5384 attr_keys::LABEL,
5385 "new_label",
5386 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some("new_label".to_string()), position: None})
5387 )]
5388 #[case(
5389 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None}),
5390 "unknown",
5391 "ignored",
5392 Node::FootnoteRef(FootnoteRef{ident: "id".to_string(), label: Some(attr_keys::LABEL.to_string()), position: None})
5393 )]
5394 #[case(Node::Empty, attr_keys::VALUE, "ignored", Node::Empty)]
5395 #[case(
5396 Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None}),
5397 "align",
5398 "---,:---:",
5399 Node::TableAlign(TableAlign{align: vec![TableAlignKind::None, TableAlignKind::Center], position: None})
5400 )]
5401 #[case(
5402 Node::TableAlign(TableAlign{align: vec![], position: None}),
5403 "align",
5404 ":---,---:",
5405 Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left, TableAlignKind::Right], position: None})
5406 )]
5407 #[case(
5408 Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None}),
5409 "unknown",
5410 "ignored",
5411 Node::TableAlign(TableAlign{align: vec![TableAlignKind::Left], position: None})
5412 )]
5413 #[case(
5414 Node::MdxFlowExpression(MdxFlowExpression{value: "old".into(), position: None}),
5415 attr_keys::VALUE,
5416 "new_expr",
5417 Node::MdxFlowExpression(MdxFlowExpression{value: "new_expr".into(), position: None})
5418 )]
5419 #[case(
5420 Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None}),
5421 "unknown",
5422 "ignored",
5423 Node::MdxFlowExpression(MdxFlowExpression{value: "expr".into(), position: None})
5424 )]
5425 #[case(
5426 Node::MdxTextExpression(MdxTextExpression{value: "old".into(), position: None}),
5427 attr_keys::VALUE,
5428 "new_expr",
5429 Node::MdxTextExpression(MdxTextExpression{value: "new_expr".into(), position: None})
5430 )]
5431 #[case(
5432 Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None}),
5433 "unknown",
5434 "ignored",
5435 Node::MdxTextExpression(MdxTextExpression{value: "expr".into(), position: None})
5436 )]
5437 #[case(
5438 Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
5439 attr_keys::VALUE,
5440 "import y",
5441 Node::MdxJsEsm(MdxJsEsm{value: "import y".into(), position: None})
5442 )]
5443 #[case(
5444 Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None}),
5445 "unknown",
5446 "ignored",
5447 Node::MdxJsEsm(MdxJsEsm{value: "import x".into(), position: None})
5448 )]
5449 #[case(
5450 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
5451 attr_keys::NAME,
5452 "section",
5453 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("section".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
5454 )]
5455 #[case(
5456 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
5457 attr_keys::NAME,
5458 "main",
5459 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("main".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
5460 )]
5461 #[case(
5462 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None}),
5463 "unknown",
5464 "ignored",
5465 Node::MdxJsxFlowElement(MdxJsxFlowElement{name: Some("div".to_string()), attributes: Vec::new(), children: Vec::new(), position: None})
5466 )]
5467 #[case(
5468 Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
5469 attr_keys::NAME,
5470 "b",
5471 Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("b".into()), attributes: Vec::new(), children: Vec::new(), position: None})
5472 )]
5473 #[case(
5474 Node::MdxJsxTextElement(MdxJsxTextElement{name: None, attributes: Vec::new(), children: Vec::new(), position: None}),
5475 attr_keys::NAME,
5476 "i",
5477 Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("i".into()), attributes: Vec::new(), children: Vec::new(), position: None})
5478 )]
5479 #[case(
5480 Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None}),
5481 "unknown",
5482 "ignored",
5483 Node::MdxJsxTextElement(MdxJsxTextElement{name: Some("span".into()), attributes: Vec::new(), children: Vec::new(), position: None})
5484 )]
5485 fn test_set_attr(#[case] mut node: Node, #[case] attr: &str, #[case] value: &str, #[case] expected: Node) {
5486 node.set_attr(attr, value);
5487 assert_eq!(node, expected);
5488 }
5489
5490 #[rstest]
5491 #[case(AttrValue::String("test".to_string()), AttrValue::String("test".to_string()), true)]
5492 #[case(AttrValue::String("test".to_string()), AttrValue::String("other".to_string()), false)]
5493 #[case(AttrValue::Integer(42), AttrValue::Integer(42), true)]
5494 #[case(AttrValue::Integer(42), AttrValue::Integer(0), false)]
5495 #[case(AttrValue::Boolean(true), AttrValue::Boolean(true), true)]
5496 #[case(AttrValue::Boolean(true), AttrValue::Boolean(false), false)]
5497 #[case(AttrValue::String("42".to_string()), AttrValue::Integer(42), false)]
5498 #[case(AttrValue::Boolean(false), AttrValue::Integer(0), false)]
5499 fn test_attr_value_eq(#[case] a: AttrValue, #[case] b: AttrValue, #[case] expected: bool) {
5500 assert_eq!(a == b, expected);
5501 }
5502
5503 #[rstest]
5504 #[case(AttrValue::String("test".to_string()), "test")]
5505 #[case(AttrValue::Integer(42), "42")]
5506 #[case(AttrValue::Boolean(true), "true")]
5507 fn test_attr_value_as_str(#[case] value: AttrValue, #[case] expected: &str) {
5508 assert_eq!(&value.as_string(), expected);
5509 }
5510
5511 #[rstest]
5512 #[case(AttrValue::Integer(42), Some(42))]
5513 #[case(AttrValue::String("42".to_string()), Some(42))]
5514 #[case(AttrValue::Boolean(false), Some(0))]
5515 fn test_attr_value_as_i64(#[case] value: AttrValue, #[case] expected: Option<i64>) {
5516 assert_eq!(value.as_i64(), expected);
5517 }
5518
5519 #[cfg(feature = "callout")]
5522 #[rstest]
5523 #[case(
5525 vec![Node::Text(Text { value: "[!NOTE]\nbody text".to_string(), position: None })],
5526 Node::Callout(Callout {
5527 kind: "NOTE".to_string(),
5528 title: None,
5529 values: vec![Node::Text(Text { value: "body text".to_string(), position: None })],
5530 position: None,
5531 })
5532 )]
5533 #[case(
5535 vec![Node::Text(Text { value: "[!WARNING] My Title\nbody".to_string(), position: None })],
5536 Node::Callout(Callout {
5537 kind: "WARNING".to_string(),
5538 title: Some("My Title".to_string()),
5539 values: vec![Node::Text(Text { value: "body".to_string(), position: None })],
5540 position: None,
5541 })
5542 )]
5543 #[case(
5545 vec![Node::Text(Text { value: "[!TIP]".to_string(), position: None })],
5546 Node::Callout(Callout { kind: "TIP".to_string(), title: None, values: vec![], position: None })
5547 )]
5548 #[case(
5550 vec![Node::Text(Text { value: "[!note]\ncontent".to_string(), position: None })],
5551 Node::Callout(Callout {
5552 kind: "note".to_string(),
5553 title: None,
5554 values: vec![Node::Text(Text { value: "content".to_string(), position: None })],
5555 position: None,
5556 })
5557 )]
5558 #[case(
5560 vec![Node::Text(Text { value: "plain quote".to_string(), position: None })],
5561 Node::Blockquote(Blockquote {
5562 values: vec![Node::Text(Text { value: "plain quote".to_string(), position: None })],
5563 position: None,
5564 })
5565 )]
5566 #[case(
5568 vec![Node::Text(Text { value: "[!UNCLOSED".to_string(), position: None })],
5569 Node::Blockquote(Blockquote {
5570 values: vec![Node::Text(Text { value: "[!UNCLOSED".to_string(), position: None })],
5571 position: None,
5572 })
5573 )]
5574 #[case(
5576 vec![Node::Code(Code { value: "code".to_string(), lang: None, fence: true, meta: None, position: None })],
5577 Node::Blockquote(Blockquote {
5578 values: vec![Node::Code(Code { value: "code".to_string(), lang: None, fence: true, meta: None, position: None })],
5579 position: None,
5580 })
5581 )]
5582 #[case(
5584 vec![
5585 Node::Text(Text { value: "[!NOTE]".to_string(), position: None }),
5586 Node::Text(Text { value: "second paragraph".to_string(), position: None }),
5587 ],
5588 Node::Callout(Callout {
5589 kind: "NOTE".to_string(),
5590 title: None,
5591 values: vec![Node::Text(Text { value: "second paragraph".to_string(), position: None })],
5592 position: None,
5593 })
5594 )]
5595 fn test_try_parse_callout(#[case] values: Vec<Node>, #[case] expected: Node) {
5596 let result = Node::try_parse_callout(values, None);
5597 assert_eq!(result, expected);
5598 }
5599
5600 #[cfg(feature = "callout")]
5601 #[rstest]
5602 #[case(
5604 Node::Callout(Callout { kind: "NOTE".to_string(), title: None,
5605 values: vec![Node::Text(Text { value: "content".to_string(), position: None })],
5606 position: None }),
5607 "> [!NOTE]\n> content"
5608 )]
5609 #[case(
5611 Node::Callout(Callout { kind: "WARNING".to_string(), title: Some("Heads up".to_string()),
5612 values: vec![Node::Text(Text { value: "watch out".to_string(), position: None })],
5613 position: None }),
5614 "> [!WARNING] Heads up\n> watch out"
5615 )]
5616 #[case(
5618 Node::Callout(Callout { kind: "TIP".to_string(), title: None, values: vec![], position: None }),
5619 "> [!TIP]"
5620 )]
5621 #[case(
5623 Node::Callout(Callout { kind: "INFO".to_string(), title: None,
5624 values: vec![Node::Text(Text { value: "line one\nline two".to_string(), position: None })],
5625 position: None }),
5626 "> [!INFO]\n> line one\n> line two"
5627 )]
5628 #[case(
5630 Node::Callout(Callout { kind: "INFO".to_string(), title: None,
5631 values: vec![
5632 Node::Text(Text { value: "part a".to_string(), position: None }),
5633 Node::Text(Text { value: "part b".to_string(), position: None }),
5634 ],
5635 position: None }),
5636 "> [!INFO]\n> part apart b"
5637 )]
5638 fn test_callout_render(#[case] node: Node, #[case] expected: &str) {
5639 assert_eq!(node.to_string_with(&RenderOptions::default()), expected);
5640 }
5641
5642 #[cfg(feature = "callout")]
5643 #[rstest]
5644 #[case(Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }), "kind", Some(AttrValue::String("NOTE".to_string())))]
5645 #[case(Node::Callout(Callout { kind: "WARNING".to_string(), title: Some("Title".to_string()), values: vec![], position: None }), "title", Some(AttrValue::String("Title".to_string())))]
5646 #[case(Node::Callout(Callout { kind: "TIP".to_string(), title: None, values: vec![], position: None }), "title", None)]
5647 #[case(Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![Node::Text(Text { value: "body".to_string(), position: None })], position: None }), "value", Some(AttrValue::String("body".to_string())))]
5648 #[case(Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![Node::Text(Text { value: "body".to_string(), position: None })], position: None }), "children", Some(AttrValue::Array(vec![Node::Text(Text { value: "body".to_string(), position: None })])))]
5649 fn test_callout_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
5650 assert_eq!(node.attr(attr), expected);
5651 }
5652
5653 #[cfg(feature = "callout")]
5654 #[rstest]
5655 #[case(
5656 Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }),
5657 "kind", "WARNING",
5658 Node::Callout(Callout { kind: "WARNING".to_string(), title: None, values: vec![], position: None })
5659 )]
5660 #[case(
5662 Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }),
5663 "kind", "tip",
5664 Node::Callout(Callout { kind: "tip".to_string(), title: None, values: vec![], position: None })
5665 )]
5666 #[case(
5667 Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }),
5668 "title", "My title",
5669 Node::Callout(Callout { kind: "NOTE".to_string(), title: Some("My title".to_string()), values: vec![], position: None })
5670 )]
5671 #[case(
5673 Node::Callout(Callout { kind: "NOTE".to_string(), title: Some("old".to_string()), values: vec![], position: None }),
5674 "title", "",
5675 Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None })
5676 )]
5677 fn test_callout_set_attr(#[case] mut node: Node, #[case] attr: &str, #[case] value: &str, #[case] expected: Node) {
5678 node.set_attr(attr, value);
5679 assert_eq!(node, expected);
5680 }
5681
5682 #[cfg(feature = "callout")]
5683 #[rstest]
5684 #[case(
5686 Node::Callout(Callout { kind: "NOTE".to_string(), title: None,
5687 values: vec![Node::Text(Text { value: "old".to_string(), position: None })],
5688 position: None }),
5689 "new",
5690 Node::Callout(Callout { kind: "NOTE".to_string(), title: None,
5691 values: vec![Node::Text(Text { value: "new".to_string(), position: None })],
5692 position: None })
5693 )]
5694 fn test_callout_with_value(#[case] node: Node, #[case] value: &str, #[case] expected: Node) {
5695 assert_eq!(node.with_value(value), expected);
5696 }
5697
5698 #[cfg(feature = "callout")]
5699 #[rstest]
5700 #[case(Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }), "callout")]
5701 fn test_callout_name(#[case] node: Node, #[case] expected: &str) {
5702 assert_eq!(node.name(), expected);
5703 }
5704
5705 #[cfg(feature = "callout")]
5706 #[rstest]
5707 #[case(
5708 Node::Callout(Callout { kind: "NOTE".to_string(), title: None,
5709 values: vec![Node::Text(Text { value: "body".to_string(), position: None })],
5710 position: None }),
5711 "body"
5712 )]
5713 fn test_callout_value(#[case] node: Node, #[case] expected: &str) {
5714 assert_eq!(node.value(), expected);
5715 }
5716
5717 #[cfg(feature = "callout")]
5718 #[rstest]
5719 #[case(Node::Callout(Callout { kind: "NOTE".to_string(), title: None, values: vec![], position: None }), true)]
5720 #[case(Node::Blockquote(Blockquote { values: vec![], position: None }), false)]
5721 #[case(Node::Text(Text { value: "test".to_string(), position: None }), false)]
5722 fn test_is_callout(#[case] node: Node, #[case] expected: bool) {
5723 assert_eq!(node.is_callout(), expected);
5724 }
5725
5726 #[cfg(feature = "callout")]
5727 #[test]
5728 fn test_callout_end_to_end() {
5729 use crate::Markdown;
5730 let plain = Markdown::from_markdown_str("> just a quote").unwrap();
5732 assert!(plain.nodes[0].is_blockquote());
5733
5734 let note = Markdown::from_markdown_str("> [!NOTE]\n> body").unwrap();
5736 assert!(note.nodes[0].is_callout());
5737 assert_eq!(note.nodes[0].attr("kind"), Some(AttrValue::String("NOTE".to_string())));
5738
5739 let warn = Markdown::from_markdown_str("> [!WARNING] Watch out\n> content").unwrap();
5741 assert!(warn.nodes[0].is_callout());
5742 assert_eq!(
5743 warn.nodes[0].attr("title"),
5744 Some(AttrValue::String("Watch out".to_string()))
5745 );
5746 }
5747
5748 #[cfg(feature = "embed")]
5751 #[rstest]
5752 #[case("![[target]]", vec![Node::Embed(Embed { target: "target".to_string(), display: None, position: None })])]
5754 #[case("![[image.png|400]]", vec![Node::Embed(Embed { target: "image.png".to_string(), display: Some("400".to_string()), position: None })])]
5756 #[case("![[note#Heading]]", vec![Node::Embed(Embed { target: "note#Heading".to_string(), display: None, position: None })])]
5758 #[case("![[note]] rest", vec![
5760 Node::Embed(Embed { target: "note".to_string(), display: None, position: None }),
5761 Node::Text(Text { value: " rest".to_string(), position: None }),
5762 ])]
5763 #[case("before ![[note]] after", vec![
5765 Node::Text(Text { value: "before ".to_string(), position: None }),
5766 Node::Embed(Embed { target: "note".to_string(), display: None, position: None }),
5767 Node::Text(Text { value: " after".to_string(), position: None }),
5768 ])]
5769 #[case("![[a]] and ![[b]]", vec![
5771 Node::Embed(Embed { target: "a".to_string(), display: None, position: None }),
5772 Node::Text(Text { value: " and ".to_string(), position: None }),
5773 Node::Embed(Embed { target: "b".to_string(), display: None, position: None }),
5774 ])]
5775 #[case("![[ノート]]", vec![Node::Embed(Embed { target: "ノート".to_string(), display: None, position: None })])]
5777 #[case("plain text", vec![Node::Text(Text { value: "plain text".to_string(), position: None })])]
5779 #[case("![[unclosed", vec![Node::Text(Text { value: "![[unclosed".to_string(), position: None })])]
5781 #[case("! not an embed", vec![Node::Text(Text { value: "! not an embed".to_string(), position: None })])]
5783 fn test_parse_embeds_in_text(#[case] input: &str, #[case] expected: Vec<Node>) {
5784 let mut result = Vec::new();
5785 Node::parse_embeds_into(input, None, &mut result);
5786 assert_eq!(result, expected);
5787 }
5788
5789 #[cfg(feature = "embed")]
5790 #[rstest]
5791 #[case(
5793 Node::Embed(Embed { target: "note.md".to_string(), display: None, position: None }),
5794 "![[note.md]]"
5795 )]
5796 #[case(
5798 Node::Embed(Embed { target: "image.png".to_string(), display: Some("400".to_string()), position: None }),
5799 "![[image.png|400]]"
5800 )]
5801 #[case(
5803 Node::Embed(Embed { target: "note#Intro".to_string(), display: None, position: None }),
5804 "![[note#Intro]]"
5805 )]
5806 fn test_embed_render(#[case] node: Node, #[case] expected: &str) {
5807 assert_eq!(node.to_string_with(&RenderOptions::default()), expected);
5808 }
5809
5810 #[cfg(feature = "embed")]
5811 #[rstest]
5812 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), "url", Some(AttrValue::String("note".to_string())))]
5813 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), "value", Some(AttrValue::String("note".to_string())))]
5815 #[case(Node::Embed(Embed { target: "note".to_string(), display: Some("400".to_string()), position: None }), "url", Some(AttrValue::String("note".to_string())))]
5817 #[case(Node::Embed(Embed { target: "note".to_string(), display: Some("400".to_string()), position: None }), "value", Some(AttrValue::String("400".to_string())))]
5819 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), "unknown", None)]
5821 fn test_embed_attr(#[case] node: Node, #[case] attr: &str, #[case] expected: Option<AttrValue>) {
5822 assert_eq!(node.attr(attr), expected);
5823 }
5824
5825 #[cfg(feature = "embed")]
5826 #[rstest]
5827 #[case(
5829 Node::Embed(Embed { target: "old".to_string(), display: None, position: None }),
5830 "url", "new",
5831 Node::Embed(Embed { target: "new".to_string(), display: None, position: None })
5832 )]
5833 #[case(
5835 Node::Embed(Embed { target: "note".to_string(), display: None, position: None }),
5836 "value", "800",
5837 Node::Embed(Embed { target: "note".to_string(), display: Some("800".to_string()), position: None })
5838 )]
5839 #[case(
5841 Node::Embed(Embed { target: "note".to_string(), display: Some("400".to_string()), position: None }),
5842 "value", "",
5843 Node::Embed(Embed { target: "note".to_string(), display: None, position: None })
5844 )]
5845 fn test_embed_set_attr(#[case] mut node: Node, #[case] attr: &str, #[case] value: &str, #[case] expected: Node) {
5846 node.set_attr(attr, value);
5847 assert_eq!(node, expected);
5848 }
5849
5850 #[cfg(feature = "embed")]
5851 #[rstest]
5852 #[case(
5854 Node::Embed(Embed { target: "old".to_string(), display: None, position: None }),
5855 "new",
5856 Node::Embed(Embed { target: "new".to_string(), display: None, position: None })
5857 )]
5858 #[case(
5860 Node::Embed(Embed { target: "note".to_string(), display: Some("400".to_string()), position: None }),
5861 "800",
5862 Node::Embed(Embed { target: "note".to_string(), display: Some("800".to_string()), position: None })
5863 )]
5864 fn test_embed_with_value(#[case] node: Node, #[case] value: &str, #[case] expected: Node) {
5865 assert_eq!(node.with_value(value), expected);
5866 }
5867
5868 #[cfg(feature = "embed")]
5869 #[rstest]
5870 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), "embed")]
5871 fn test_embed_name(#[case] node: Node, #[case] expected: &str) {
5872 assert_eq!(node.name(), expected);
5873 }
5874
5875 #[cfg(feature = "embed")]
5876 #[rstest]
5877 #[case(Node::Embed(Embed { target: "note".to_string(), display: Some("400".to_string()), position: None }), "400")]
5879 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), "note")]
5881 fn test_embed_value(#[case] node: Node, #[case] expected: &str) {
5882 assert_eq!(node.value(), expected);
5883 }
5884
5885 #[cfg(feature = "embed")]
5886 #[rstest]
5887 #[case(Node::Embed(Embed { target: "note".to_string(), display: None, position: None }), true)]
5888 #[case(Node::Text(Text { value: "test".to_string(), position: None }), false)]
5889 #[case(Node::Image(Image { alt: "".to_string(), url: "img.png".to_string(), title: None, position: None }), false)]
5890 fn test_is_embed(#[case] node: Node, #[case] expected: bool) {
5891 assert_eq!(node.is_embed(), expected);
5892 }
5893
5894 #[cfg(feature = "embed")]
5895 #[test]
5896 fn test_embed_end_to_end() {
5897 use crate::Markdown;
5898 let md = Markdown::from_markdown_str("See ![[note.md]] for details.").unwrap();
5899 let embed = md.nodes.iter().find(|n| n.is_embed());
5900 assert!(embed.is_some());
5901 assert_eq!(
5902 embed.unwrap().attr("url"),
5903 Some(AttrValue::String("note.md".to_string()))
5904 );
5905 }
5906
5907 #[cfg(all(feature = "callout", feature = "wikilink"))]
5908 #[rstest]
5909 #[case("> [!NOTE]\n> See [[note]]", 1, "> [!NOTE]\n> See [[note]]\n")]
5911 fn test_callout_with_wikilink(#[case] input: &str, #[case] expected_nodes: usize, #[case] expected_output: &str) {
5912 use crate::Markdown;
5913 let md = Markdown::from_markdown_str(input).unwrap();
5914 assert_eq!(md.nodes.len(), expected_nodes);
5915 assert_eq!(md.to_string(), expected_output);
5916 }
5917
5918 #[cfg(feature = "callout")]
5919 #[rstest]
5920 #[case("> [!NOTE]\n> **important**", 1, "> [!NOTE]\n> **important**\n")]
5922 #[case("> [!NOTE]\n> Use `code`", 1, "> [!NOTE]\n> Use `code`\n")]
5924 #[case(
5926 "> [!NOTE]\n> first\n\n> [!WARNING]\n> second",
5927 2,
5928 "> [!NOTE]\n> first\n\n> [!WARNING]\n> second\n"
5929 )]
5930 #[case("# Title\n\n> [!NOTE]\n> body", 2, "# Title\n\n> [!NOTE]\n> body\n")]
5932 #[case("> [!NOTE]\n> - item 1\n> - item 2", 1, "> [!NOTE]\n> - item 1\n> - item 2\n")]
5934 fn test_callout_combined_with_other_nodes(
5935 #[case] input: &str,
5936 #[case] expected_nodes: usize,
5937 #[case] expected_output: &str,
5938 ) {
5939 use crate::Markdown;
5940 let md = Markdown::from_markdown_str(input).unwrap();
5941 assert_eq!(md.nodes.len(), expected_nodes);
5942 assert_eq!(md.to_string(), expected_output);
5943 }
5944
5945 #[cfg(feature = "embed")]
5946 #[rstest]
5947 #[case("- ![[note.md]]", 1, "- ![[note.md]]\n")]
5949 #[case("# See ![[note]]", 1, "# See ![[note]]\n")]
5951 #[case("**![[note]]**", 1, "**![[note]]**\n")]
5953 fn test_embed_combined_with_other_nodes(
5954 #[case] input: &str,
5955 #[case] expected_nodes: usize,
5956 #[case] expected_output: &str,
5957 ) {
5958 use crate::Markdown;
5959 let md = Markdown::from_markdown_str(input).unwrap();
5960 assert_eq!(md.nodes.len(), expected_nodes);
5961 assert_eq!(md.to_string(), expected_output);
5962 }
5963
5964 #[cfg(all(feature = "callout", feature = "embed"))]
5965 #[rstest]
5966 #[case("> [!NOTE]\n> [[link]]\n\n![[embed]]", 2, "> [!NOTE]\n> [[link]]\n\n![[embed]]\n")]
5968 fn test_callout_and_embed_together(
5969 #[case] input: &str,
5970 #[case] expected_nodes: usize,
5971 #[case] expected_output: &str,
5972 ) {
5973 use crate::Markdown;
5974 let md = Markdown::from_markdown_str(input).unwrap();
5975 assert_eq!(md.nodes.len(), expected_nodes);
5976 assert_eq!(md.to_string(), expected_output);
5977 }
5978
5979 #[cfg(all(feature = "embed", feature = "wikilink"))]
5980 #[test]
5981 fn test_embed_does_not_conflict_with_wikilink() {
5982 use crate::Markdown;
5983 let md = Markdown::from_markdown_str("![[embed]] and [[link]]").unwrap();
5985 let embed = md.nodes.iter().find(|n| n.is_embed());
5986 let wikilink = md.nodes.iter().find(|n| n.is_wikilink());
5987 assert!(embed.is_some(), "expected Embed node");
5988 assert!(wikilink.is_some(), "expected WikiLink node");
5989 }
5990}