1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#![allow(clippy::unneeded_field_pattern)]
#![doc(html_root_url = "https://docs.rs/lignin-html/0.0.5")]
#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]

#[cfg(doctest)]
pub mod readme {
	doc_comment::doctest!("../README.md");
}

use core::fmt::{Error as fmtError, Write};
pub use lignin;
use lignin::{
	bumpalo::Bump,
	remnants::RemnantSite as rRemnantSite,
	Attribute, Element as lElement,
	Node::{self, Comment, Element, Multi, Ref, RemnantSite, Text},
};
use std::error::Error as stdError;
use v_htmlescape::escape;

#[allow(clippy::missing_errors_doc)]
pub fn render<'a>(w: &mut impl Write, vdom: &'a Node<'a>, bump: &'a Bump) -> Result<(), Error<'a>> {
	match vdom {
		&Comment(comment) => {
			write!(w, "<!--{}-->", escape(comment))?;
		}

		&Element(lElement {
			name: element_name,
			ref attributes,
			ref content,
			event_bindings: _,
		}) => {
			write!(w, "<{}", validate_element_name(element_name)?)?;
			for Attribute {
				name: attribute_name,
				value,
			} in &**attributes
			{
				write!(
					w,
					" {}=\"{}\"",
					validate_attribute_name(attribute_name)?,
					escape_attribute_value(value),
				)?;
			}
			w.write_char('>')?;
			for node in *content {
				render(w, node, bump)?;
			}
			//TODO: Fill out the blacklist here.
			if !["BR"].contains(element_name) {
				write!(w, "</{}>", element_name)?;
			}
		}

		Ref(target) => render(w, target, bump)?,
		Multi(nodes) => {
			for node in *nodes {
				render(w, node, bump)?;
			}
		}
		Text(text) => write!(
			w,
			"{}",
			text.replace("<", "&lt;") //TODO: Check if this is enough.
		)?,
		RemnantSite(rRemnantSite { content, .. }) => {
			render(w, content, bump)?;
		}
		other => return Err(Error::Unsupported(other)),
	};
	Ok(())
}

#[non_exhaustive]
pub enum Error<'a> {
	Unsupported(&'a Node<'a>),
	InvalidAttributeName,
	InvalidElementName,
	Format(fmtError),
	Other(Box<dyn stdError>),
}

impl<'a> From<fmtError> for Error<'a> {
	fn from(fmtError: fmtError) -> Self {
		Self::Format(fmtError)
	}
}

impl<'a> From<Box<dyn stdError>> for Error<'a> {
	fn from(std_error: Box<dyn stdError>) -> Self {
		Self::Other(std_error)
	}
}

fn validate_element_name(value: &str) -> Result<&str, Error> {
	if value.contains(|c| c == '>') {
		Err(Error::InvalidElementName) //TODO
	} else {
		Ok(value)
	}
}

fn validate_attribute_name(value: &str) -> Result<&str, Error> {
	if value.contains(|c| c == '>') {
		Err(Error::InvalidAttributeName) //TODO
	} else {
		Ok(value)
	}
}

fn escape_attribute_value(value: &str) -> String {
	if value.contains('"') {
		todo!("Attribute value escapes")
	}
	value.to_owned()
}