facet_html/
lib.rs

1#![deny(unsafe_code)]
2#![deny(missing_docs, rustdoc::broken_intra_doc_links)]
3
4//! HTML parser and serializer implementing the facet format architecture.
5//!
6//! This crate provides:
7//! - **Parsing**: WHATWG-compliant HTML tokenization via html5gum
8//! - **Serialization**: Configurable HTML output (minified or pretty-printed)
9//!
10//! # Attributes
11//!
12//! After importing `use facet_html as html;`, you can use these attributes:
13//!
14//! - `#[facet(html::element)]` - Marks a field as a single HTML child element
15//! - `#[facet(html::elements)]` - Marks a field as collecting multiple HTML child elements  
16//! - `#[facet(html::attribute)]` - Marks a field as an HTML attribute (on the element tag)
17//! - `#[facet(html::text)]` - Marks a field as the text content of the element
18//!
19//! # Parsing Example
20//!
21//! ```rust
22//! use facet::Facet;
23//! use facet_format::FormatDeserializer;
24//! use facet_html::HtmlParser;
25//!
26//! #[derive(Debug, Facet, PartialEq)]
27//! struct Document {
28//!     #[facet(default)]
29//!     head: Option<Head>,
30//!     #[facet(default)]
31//!     body: Option<Body>,
32//! }
33//!
34//! #[derive(Debug, Facet, PartialEq)]
35//! struct Head {
36//!     #[facet(default)]
37//!     title: Option<String>,
38//! }
39//!
40//! #[derive(Debug, Facet, PartialEq)]
41//! struct Body {
42//!     #[facet(default)]
43//!     text: String,
44//! }
45//! ```
46//!
47//! # Serialization Example
48//!
49//! ```rust
50//! use facet::Facet;
51//! use facet_xml as xml;
52//! use facet_html::{to_string, to_string_pretty};
53//!
54//! #[derive(Debug, Facet)]
55//! #[facet(rename = "div")]
56//! struct MyDiv {
57//!     #[facet(xml::attribute, default)]
58//!     class: Option<String>,
59//!     #[facet(xml::text, default)]
60//!     content: String,
61//! }
62//!
63//! let div = MyDiv {
64//!     class: Some("container".into()),
65//!     content: "Hello!".into(),
66//! };
67//!
68//! // Minified output (default)
69//! let html = to_string(&div).unwrap();
70//!
71//! // Pretty-printed output
72//! let html_pretty = to_string_pretty(&div).unwrap();
73//! ```
74//!
75//! # Pre-defined HTML Element Types
76//!
77//! This crate provides typed definitions for all standard HTML5 elements in the
78//! [`elements`] module. You can use these to deserialize HTML into strongly-typed
79//! Rust structures:
80//!
81//! ```rust
82//! use facet_html::elements::{Html, Div, P, A};
83//! ```
84
85pub mod elements;
86mod parser;
87mod serializer;
88
89pub use parser::{HtmlError, HtmlParser};
90pub use serializer::{
91    HtmlSerializeError, HtmlSerializer, SerializeOptions, to_string, to_string_pretty,
92    to_string_with_options, to_vec, to_vec_with_options,
93};
94
95// HTML extension attributes for use with #[facet(html::attr)] syntax.
96//
97// After importing `use facet_format_html as html;`, users can write:
98//   #[facet(html::element)]
99//   #[facet(html::elements)]
100//   #[facet(html::attribute)]
101//   #[facet(html::text)]
102
103// Generate HTML attribute grammar using the grammar DSL.
104// This generates:
105// - `Attr` enum with all HTML attribute variants
106// - `__attr!` macro that dispatches to attribute handlers and returns ExtensionAttr
107// - `__parse_attr!` macro for parsing (internal use)
108facet::define_attr_grammar! {
109    ns "html";
110    crate_path ::facet_html;
111
112    /// HTML attribute types for field and container configuration.
113    pub enum Attr {
114        /// Marks a field as a single HTML child element
115        Element,
116        /// Marks a field as collecting multiple HTML child elements
117        Elements,
118        /// Marks a field as an HTML attribute (on the element tag)
119        Attribute,
120        /// Marks a field as the text content of the element
121        Text,
122    }
123}
124
125/// Deserialize an HTML document from a string.
126///
127/// # Example
128///
129/// ```rust
130/// use facet::Facet;
131/// use facet_html as html;
132///
133/// #[derive(Debug, Facet)]
134/// struct Div {
135///     #[facet(html::text, default)]
136///     text: String,
137/// }
138///
139/// let doc: Div = facet_html::from_str("<div>hello</div>").unwrap();
140/// assert_eq!(doc.text, "hello");
141/// ```
142pub fn from_str<'de, T: facet_core::Facet<'de>>(
143    s: &'de str,
144) -> Result<T, facet_format::DeserializeError<HtmlError>> {
145    let parser = HtmlParser::new(s.as_bytes());
146    let mut deserializer = facet_format::FormatDeserializer::new(parser);
147    deserializer.deserialize()
148}
149
150/// Deserialize an HTML document from bytes.
151pub fn from_slice<'de, T: facet_core::Facet<'de>>(
152    bytes: &'de [u8],
153) -> Result<T, facet_format::DeserializeError<HtmlError>> {
154    let parser = HtmlParser::new(bytes);
155    let mut deserializer = facet_format::FormatDeserializer::new(parser);
156    deserializer.deserialize()
157}