Skip to main content

dioxus_mdx/parser/
mod.rs

1//! MDX documentation parser for extracting frontmatter and components.
2//!
3//! This module provides functionality to parse MDX (Markdown with JSX) content,
4//! extracting YAML frontmatter and converting custom components like Cards, Tabs,
5//! Steps, and Callouts into an intermediate representation for rendering.
6
7mod accordion;
8mod callout;
9mod card;
10mod code_group;
11mod content;
12mod fields;
13mod frontmatter;
14mod heading;
15mod openapi_parser;
16mod openapi_tag;
17mod openapi_types;
18mod steps;
19mod tabs;
20mod types;
21mod update;
22mod utils;
23
24pub use content::{get_raw_markdown, parse_mdx};
25pub use frontmatter::extract_frontmatter;
26pub use heading::strip_leading_h1;
27pub use openapi_parser::{OpenApiError, parse_openapi};
28pub use openapi_types::*;
29pub use types::*;
30
31/// Parse a complete MDX document, extracting frontmatter and content.
32///
33/// This is the main entry point for parsing MDX content. It extracts
34/// YAML frontmatter from the beginning of the document and parses the
35/// remaining content into a tree of `DocNode` elements.
36pub fn parse_document(content: &str) -> ParsedDoc {
37    let (frontmatter, remaining) = extract_frontmatter(content);
38    // Consumer layouts render the frontmatter title in their own <h1>; drop a
39    // duplicate body H1 so the page emits exactly one.
40    let body = strip_leading_h1(remaining);
41    // Frontmatter is already gone; parse_mdx would strip a second time and eat
42    // the body up to the next `---`.
43    let nodes = content::parse_body(body);
44    let raw_markdown = get_raw_markdown(&nodes);
45
46    ParsedDoc {
47        frontmatter,
48        content: nodes,
49        raw_markdown,
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn parse_document_strips_duplicate_atx_h1() {
59        let content = "---\ntitle: Hello\n---\n\n# Hello\n\nbody text\n";
60        let doc = parse_document(content);
61        assert_eq!(doc.frontmatter.title, "Hello");
62        assert!(
63            !doc.raw_markdown.contains("# Hello"),
64            "expected leading H1 to be stripped, got: {:?}",
65            doc.raw_markdown
66        );
67        assert!(doc.raw_markdown.contains("body text"));
68    }
69
70    #[test]
71    fn parse_document_leaves_body_without_leading_h1_untouched() {
72        let content = "---\ntitle: Hello\n---\n\nintro paragraph\n\n## Subheading\n";
73        let doc = parse_document(content);
74        assert!(doc.raw_markdown.contains("intro paragraph"));
75        assert!(doc.raw_markdown.contains("## Subheading"));
76    }
77
78    #[test]
79    fn parse_document_preserves_h1_appearing_mid_document() {
80        let content = "---\ntitle: Hello\n---\n\nintro\n\n# Later heading\n\nmore body\n";
81        let doc = parse_document(content);
82        assert!(doc.raw_markdown.contains("intro"));
83        assert!(
84            doc.raw_markdown.contains("# Later heading"),
85            "mid-document H1 should survive, got: {:?}",
86            doc.raw_markdown
87        );
88    }
89
90    #[test]
91    fn parse_document_keeps_body_starting_with_thematic_break() {
92        // parse_mdx would strip frontmatter a second time here and swallow
93        // everything up to the next `---`.
94        let content = "---\ntitle: T\n---\n\n---\n\nfirst para\n\n---\n\nsecond para\n";
95        let doc = parse_document(content);
96        assert_eq!(doc.frontmatter.title, "T");
97        assert!(
98            doc.raw_markdown.contains("first para"),
99            "body before the second rule was eaten: {:?}",
100            doc.raw_markdown
101        );
102        assert!(doc.raw_markdown.contains("second para"));
103    }
104}