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 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("<", "<") )?,
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) } 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) } 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}