lignin_html/
lib.rs

1#![allow(clippy::unneeded_field_pattern)]
2#![doc(html_root_url = "https://docs.rs/lignin-html/0.0.5")]
3#![forbid(unsafe_code)]
4#![warn(clippy::pedantic)]
5
6#[cfg(doctest)]
7pub mod readme {
8	doc_comment::doctest!("../README.md");
9}
10
11use core::fmt::{Error as fmtError, Write};
12pub use lignin;
13use lignin::{
14	bumpalo::Bump,
15	remnants::RemnantSite as rRemnantSite,
16	Attribute, Element as lElement,
17	Node::{self, Comment, Element, Multi, Ref, RemnantSite, Text},
18};
19use std::error::Error as stdError;
20use v_htmlescape::escape;
21
22#[allow(clippy::missing_errors_doc)]
23pub fn render<'a>(w: &mut impl Write, vdom: &'a Node<'a>, bump: &'a Bump) -> Result<(), Error<'a>> {
24	match vdom {
25		&Comment(comment) => {
26			write!(w, "<!--{}-->", escape(comment))?;
27		}
28
29		&Element(lElement {
30			name: element_name,
31			ref attributes,
32			ref content,
33			event_bindings: _,
34		}) => {
35			write!(w, "<{}", validate_element_name(element_name)?)?;
36			for Attribute {
37				name: attribute_name,
38				value,
39			} in &**attributes
40			{
41				write!(
42					w,
43					" {}=\"{}\"",
44					validate_attribute_name(attribute_name)?,
45					escape_attribute_value(value),
46				)?;
47			}
48			w.write_char('>')?;
49			for node in *content {
50				render(w, node, bump)?;
51			}
52			//TODO: Fill out the blacklist here.
53			if !["BR"].contains(element_name) {
54				write!(w, "</{}>", element_name)?;
55			}
56		}
57
58		Ref(target) => render(w, target, bump)?,
59		Multi(nodes) => {
60			for node in *nodes {
61				render(w, node, bump)?;
62			}
63		}
64		Text(text) => write!(
65			w,
66			"{}",
67			text.replace("<", "&lt;") //TODO: Check if this is enough.
68		)?,
69		RemnantSite(rRemnantSite { content, .. }) => {
70			render(w, content, bump)?;
71		}
72		other => return Err(Error::Unsupported(other)),
73	};
74	Ok(())
75}
76
77#[non_exhaustive]
78pub enum Error<'a> {
79	Unsupported(&'a Node<'a>),
80	InvalidAttributeName,
81	InvalidElementName,
82	Format(fmtError),
83	Other(Box<dyn stdError>),
84}
85
86impl<'a> From<fmtError> for Error<'a> {
87	fn from(fmtError: fmtError) -> Self {
88		Self::Format(fmtError)
89	}
90}
91
92impl<'a> From<Box<dyn stdError>> for Error<'a> {
93	fn from(std_error: Box<dyn stdError>) -> Self {
94		Self::Other(std_error)
95	}
96}
97
98fn validate_element_name(value: &str) -> Result<&str, Error> {
99	if value.contains(|c| c == '>') {
100		Err(Error::InvalidElementName) //TODO
101	} else {
102		Ok(value)
103	}
104}
105
106fn validate_attribute_name(value: &str) -> Result<&str, Error> {
107	if value.contains(|c| c == '>') {
108		Err(Error::InvalidAttributeName) //TODO
109	} else {
110		Ok(value)
111	}
112}
113
114fn escape_attribute_value(value: &str) -> String {
115	if value.contains('"') {
116		todo!("Attribute value escapes")
117	}
118	value.to_owned()
119}