HTML

facet-html parses and serializes HTML documents using Facet. Define your document structure with #[facet(html::element)] for child elements, #[facet(html::attribute)] for tag attributes, and #[facet(html::text)] for text content.

Parsing HTML

Simple Document

Parse a basic HTML document with head, body, and nested elements.

Target Type
/// A simple page structure with head and body.
#[derive(Facet)]
struct SimplePage {
    #[facet(html::element)]
    head: Option<SimpleHead>,

#[facet(html::element)] body: Option<SimpleBody>, }
#[derive(Facet)] struct SimpleBody { #[facet(html::attribute)] class: Option<String>,
#[facet(html::elements)] children: Vec<BodyElement>, }
/// Elements that can appear in the body. #[derive(Facet)] #[repr(u8)] enum BodyElement { H1(Heading),
P(Paragraph),
Div(DivElement), }
#[derive(Facet)] struct DivElement { #[facet(html::attribute)] id: Option<String>,
#[facet(html::attribute)] class: Option<String>,
#[facet(html::text)] content: String, }
#[derive(Facet)] struct Paragraph { #[facet(html::attribute)] class: Option<String>,
#[facet(html::text)] text: String, }
#[derive(Facet)] struct Heading { #[facet(html::attribute)] id: Option<String>,
#[facet(html::text)] text: String, }
#[derive(Facet)] struct SimpleHead { #[facet(html::element)] title: Option<SimpleTitle>, }
#[derive(Facet)] struct SimpleTitle { #[facet(html::text)] text: String, }

HTML Input

html
<html>
    <head><title>My Page</title></head>
    <body class="main">
        <h1 id="header">Welcome</h1>
        <p>Hello, world!</p>
    </body>
</html>

Success

SimplePage {
  headOption::Some(SimpleHead {
    titleOption::Some(SimpleTitle {
      text"My Page",
    }),
  }),
  bodyOption::Some(SimpleBody {
    classOption::Some("main"),
    childrenVec<BodyElement>[],
  }),
}

Nested Elements

Parse nested HTML elements into an enum-based content model.

Target Type
/// A simple page structure with head and body.
#[derive(Facet)]
struct SimplePage {
    #[facet(html::element)]
    head: Option<SimpleHead>,

#[facet(html::element)] body: Option<SimpleBody>, }
#[derive(Facet)] struct SimpleBody { #[facet(html::attribute)] class: Option<String>,
#[facet(html::elements)] children: Vec<BodyElement>, }
/// Elements that can appear in the body. #[derive(Facet)] #[repr(u8)] enum BodyElement { H1(Heading),
P(Paragraph),
Div(DivElement), }
#[derive(Facet)] struct DivElement { #[facet(html::attribute)] id: Option<String>,
#[facet(html::attribute)] class: Option<String>,
#[facet(html::text)] content: String, }
#[derive(Facet)] struct Paragraph { #[facet(html::attribute)] class: Option<String>,
#[facet(html::text)] text: String, }
#[derive(Facet)] struct Heading { #[facet(html::attribute)] id: Option<String>,
#[facet(html::text)] text: String, }
#[derive(Facet)] struct SimpleHead { #[facet(html::element)] title: Option<SimpleTitle>, }
#[derive(Facet)] struct SimpleTitle { #[facet(html::text)] text: String, }

HTML Input

html
<html>
    <body>
        <div id="container" class="wrapper">
            <h1>Title</h1>
            <p class="intro">Introduction paragraph.</p>
            <div class="content">Main content here.</div>
        </div>
    </body>
</html>

Success

SimplePage {
  headOption::None,
  bodyOption::Some(SimpleBody {
    classOption::None,
    childrenVec<BodyElement>[],
  }),
}

Form Elements

Parse HTML form elements with their attributes.

Target Type
/// A form element with various input types.
#[derive(Facet)]
struct ContactForm {
    #[facet(html::attribute)]
    action: Option<String>,

#[facet(html::attribute)] method: Option<String>,
#[facet(html::elements)] inputs: Vec<FormInput>, }
#[derive(Facet)] struct FormInput { #[facet(html::attribute)] input_type: Option<String>,
#[facet(html::attribute)] name: Option<String>,
#[facet(html::attribute)] placeholder: Option<String>,
#[facet(html::attribute)] required: Option<String>, }

HTML Input

html
<form action="/submit" method="post">
    <input type="text" name="username" placeholder="Username" required />
    <input type="email" name="email" placeholder="Email" />
    <input type="submit" name="submit" />
</form>

Success

ContactForm {
  actionOption::Some("/submit"),
  methodOption::Some("post"),
  inputsVec<FormInput> [
    FormInput {
      input_typeOption::Some("text"),
      nameOption::Some("username"),
      placeholderOption::Some("Username"),
      requiredOption::Some(""),
    },
    FormInput {
      input_typeOption::Some("email"),
      nameOption::Some("email"),
      placeholderOption::Some("Email"),
      requiredOption::None,
    },
    FormInput {
      input_typeOption::Some("submit"),
      nameOption::Some("submit"),
      placeholderOption::None,
      requiredOption::None,
    },
  ],
}

Serialization

Minified Output

Serialize to compact HTML without extra whitespace.

Target Type
#[derive(Facet)]
struct DivElement {
    #[facet(html::attribute)]
    id: Option<String>,

#[facet(html::attribute)] class: Option<String>,
#[facet(html::text)] content: String, }

HTML Output

html
<divElement id="main" class="container">Hello!</divElement>

Pretty-Printed Output

Serialize with indentation for readability.

Target Type
/// A form element with various input types.
#[derive(Facet)]
struct ContactForm {
    #[facet(html::attribute)]
    action: Option<String>,

#[facet(html::attribute)] method: Option<String>,
#[facet(html::elements)] inputs: Vec<FormInput>, }
#[derive(Facet)] struct FormInput { #[facet(html::attribute)] input_type: Option<String>,
#[facet(html::attribute)] name: Option<String>,
#[facet(html::attribute)] placeholder: Option<String>,
#[facet(html::attribute)] required: Option<String>, }

HTML Output

html
<form action="/api/contact" method="post"><input type="text" name="name" placeholder="Your name" required><input type="email" name="email" placeholder="your@email.com"></form>

Advanced Features

Extra Attributes (data-, aria-)

Unknown attributes like data-* and aria-* are captured in the extra field via #[facet(flatten)].

Target Type
/// A div that captures extra attributes (data-*, aria-*, etc.)
#[derive(Facet)]
struct DivWithExtras {
    #[facet(html::attribute)]
    id: Option<String>,

#[facet(html::attribute)] class: Option<String>,
/// Captures data-*, aria-*, and other unknown attributes extra: BTreeMap<String, String>,
#[facet(html::text)] content: String, }

HTML Input

html
<div id="widget" class="card" data-user-id="123" data-theme="dark" aria-label="User Card">Content</div>

Success

DivWithExtras {
  idOption::Some("widget"),
  classOption::Some("card"),
  extraBTreeMap<String, String> [
    "aria-label" => "User Card",
    "data-theme" => "dark",
    "data-user-id" => "123",
  ],
  content"Content",
}