Skip to main content

base_markdown/
lib.rs

1// Released under the MIT License.
2// Copyright, 2022-2025, by Titus Wormer.
3// Copyright, 2023, by Rafael Bachmann.
4// Copyright, 2024, by Bnchi.
5// Copyright, 2026, by Samuel Williams.
6
7//! A pull parser, source-backed document AST, and shared HTML writer.
8//!
9//! Use [`to_html`] for direct rendering, [`EventParser`] for custom pull consumers, and [`Document::parse`] when inspecting or editing a syntax tree. Documents traverse the same semantic event contract as the parser. [`to_markdown`] serializes documents, and [`normalize_markdown`] rewrites source into canonical Markdown; [`render_events`] accepts parser or document events.
10//!
11//! ## Pull events and build a source-backed AST
12//!
13//! [`EventParser`] yields resolved semantic events on demand. Direct HTML rendering and the source-backed [`Document`] AST use this same contract. A document can own the input buffer, while ordinary text values retain byte ranges into it:
14//!
15//! ```
16//! use base_markdown::{render_events, CompileOptions, Document, ParseOptions};
17//!
18//! let source = String::from("# Introduction\n\nHello, *world*!");
19//! let document = Document::parse(source, &ParseOptions::default()).unwrap();
20//! let mut html = String::new();
21//! render_events(document.source(), document.events(), &CompileOptions::default(), &mut html).unwrap();
22//! assert_eq!(html, "<h1>Introduction</h1>\n<p>Hello, <em>world</em>!</p>");
23//! ```
24//!
25//! [`AstBuilder::consume`] accepts an event budget so callers can build the AST over several steps without collecting the complete stream. Parsing accepts a complete UTF-8 buffer and prepares block structure before yielding events; inline resolution is deferred until consumption. [`IncrementalParser`] accepts text in pieces, such as a streamed reply, and emits the events of each document-level block once later text can no longer change it.
26//!
27//! ## Edit and serialize a document
28//!
29//! Nodes use stable [`NodeId`] indices. Editing text with [`Document::text_mut`] copies only that value; detaching or moving a subtree retains its IDs and source metadata, and detached arena slots remain available until the document is dropped:
30//!
31//! ```
32//! use base_markdown::{to_markdown, Document, HeadingOptions, ParseOptions};
33//!
34//! let mut document = Document::parse(String::from("# Introduction\n\nHello"), &ParseOptions::default()).unwrap();
35//! let heading = document.headings(&HeadingOptions::default())[0].node;
36//! let text = document.children(heading).next().unwrap();
37//! document.text_mut(text).unwrap().push_str(" updated");
38//! assert!(to_markdown(&document).unwrap().starts_with("# Introduction updated"));
39//! ```
40//!
41//! [`Document::append_child`], [`Document::detach`], and [`Document::attach`] edit structure without rebuilding the tree, and [`Document::replace_heading_section`] copies replacement content from another document. [`Document::render_node`] and [`to_markdown_node`] render individual subtrees. [`NodeSyntax`] carries optional authored formatting, such as reference style, code info strings, and frontmatter fences, so Markdown serialization can preserve it.
42//!
43//! ## Parsing options
44//!
45//! The parser supports `CommonMark` and the GFM, frontmatter, and math constructs inherited from `markdown-rs`; see [`Constructs`] and [`Options::gfm`]. HTML output escapes raw HTML by default. MDX, JSX, and JavaScript expression parsing are not supported. Three additional [`ParseOptions`] are disabled by default, so ordinary parsing retains `CommonMark` behavior: [`ParseOptions::inline_code_info`] recognizes a language prefix before inline code, [`ParseOptions::html_block_blank_lines`] continues indented HTML blocks across blank lines, and [`ParseOptions::html_tag_namespaces`] accepts namespace-prefixed HTML tags. [`Constructs::frontmatter`] accepts language-agnostic frontmatter, and [`CodeFence`] describes authored code fences.
46#![no_std]
47#![deny(clippy::pedantic)]
48#![allow(clippy::doc_link_with_quotes)]
49#![allow(clippy::missing_panics_doc)]
50#![allow(clippy::must_use_candidate)]
51#![allow(clippy::too_many_lines)]
52#![allow(clippy::result_large_err)]
53#![doc(
54    html_logo_url = "https://raw.githubusercontent.com/wooorm/markdown-rs/8924580/media/logo-monochromatic.svg?sanitize=true"
55)]
56
57extern crate alloc;
58mod align_kind;
59mod block_parser;
60mod byte_scan;
61mod code_fence;
62mod configuration;
63mod definitions;
64mod document;
65mod event_html_renderer;
66mod event_parser;
67mod headings;
68mod incremental_parser;
69mod inline_parser;
70pub mod markdown;
71mod node_syntax;
72mod reference_kind;
73pub mod stream;
74mod util;
75mod value_builder;
76
77pub mod message; // To do: externalize.
78
79#[doc(hidden)]
80pub use util::sanitize_uri::sanitize;
81
82pub use util::line_ending::LineEnding;
83
84pub use align_kind::AlignKind;
85pub use code_fence::CodeFence;
86pub use configuration::{CompileOptions, Constructs, Options, ParseOptions};
87pub use document::{
88    AstBuilder, BuildProgress, Children, Descendants, Document, DocumentEvents,
89    Node as DocumentNode, NodeId,
90};
91pub use event_html_renderer::{render_events, EventHtmlRenderer};
92pub use event_parser::EventParser;
93pub use headings::{HeadingEntry, HeadingOptions};
94pub use incremental_parser::IncrementalParser;
95pub use markdown::to_markdown_node;
96pub use markdown::{normalize_markdown, normalize_markdown_with_options};
97pub use markdown::{to_markdown, to_markdown_with_options};
98pub use markdown::{IndentOptions, LineWrapping, Options as MarkdownOptions};
99pub use node_syntax::NodeSyntax;
100pub use reference_kind::ReferenceKind;
101
102use alloc::string::String;
103
104/// Turn markdown into HTML.
105///
106/// Compiles markdown to HTML according to `CommonMark`.
107/// Use [`to_html_with_options()`][] to configure how markdown is turned into
108/// HTML.
109///
110/// ## Examples
111///
112/// ```
113/// use base_markdown::to_html;
114///
115/// assert_eq!(to_html("# Hi Mercury!"), "<h1>Hi Mercury!</h1>");
116/// ```
117pub fn to_html(value: &str) -> String {
118    to_html_with_options(value, &Options::default())
119}
120
121/// Turn markdown into HTML, with configuration.
122///
123/// Markdown has no syntax errors, so this returns the rendered string directly.
124///
125/// ## Examples
126///
127/// ```
128/// use base_markdown::{to_html_with_options, CompileOptions, Options};
129///
130/// // Use GFM:
131/// let result = to_html_with_options("~Venus~Mars!", &Options::gfm());
132///
133/// assert_eq!(result, "<p><del>Venus</del>Mars!</p>");
134///
135/// // Live dangerously / trust the author:
136/// let result = to_html_with_options("<div>\n\n# Hi Jupiter!\n\n</div>", &Options {
137///     compile: CompileOptions {
138///       allow_dangerous_html: true,
139///       allow_dangerous_protocol: true,
140///       ..CompileOptions::default()
141///     },
142///     ..Options::default()
143/// });
144///
145/// assert_eq!(result, "<div>\n<h1>Hi Jupiter!</h1>\n</div>");
146/// ```
147pub fn to_html_with_options(value: &str, options: &Options) -> String {
148    // Skip early growth steps while bounding spare capacity for mostly invisible input.
149    let mut output = String::with_capacity(value.len().min(4096));
150    let result = EventHtmlRenderer::new(value, &options.compile, &mut output)
151        .render_markdown(&options.parse);
152    match result {
153        Ok(()) => output,
154        Err(_) => unreachable!(
155            "parser events are balanced and source-valid; writing to String cannot fail"
156        ),
157    }
158}