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