teacat_lib 0.5.1

Tools for working with TeaCat files
Documentation
use std::fmt::Write;

use crate::{Moo, parser::MarkupType, prelude::*};

/// HTML elements that must use XML-style `<tag />` syntax instead of the standard `<tag></tag>` syntax.
///
/// List aquired from here: <https://developer.mozilla.org/en-US/docs/Glossary/Void_element>
pub const VOID_ELEMENTS: [&str; 14] = [
	"area",
	"base",
	"br",
	"col",
	"embed",
	"hr",
	"img",
	"input",
	"link",
	"meta",
	"param",
	"source",
	"track",
	"wbr",
];

/// Renders an [`ExpandedAst`] into an HTML string.
pub struct HtmlRenderer;

impl Renderer<String> for HtmlRenderer {
	fn render(ast: TeaCatDom) -> String {
		format!("<!DOCTYPE html><html>{}</html>", render_ast(ast))
	}
}

fn render_ast(ast: TeaCatDom) -> String {
	let mut rendered = String::new();

	for node in ast.0 {
		rendered.push_str(&match node {
			DomNode::Tag(tag) => render_tag(tag),
			DomNode::ContentBlock(ast) => render_ast(ast),
			DomNode::Word(word) => render_word(&word),
			DomNode::Space => " ".into(),
			DomNode::Markup { of, inner } => render_markup(of, inner),
		});
	}

	rendered
}

fn render_markup(of: MarkupType, inner: TeaCatDom) -> String {
	let rendered = render_ast(inner);
	let tag_name = match of {
		MarkupType::Bold => "b",
		MarkupType::Italic => "i",
		MarkupType::Strikethrough => "s",
		MarkupType::Underline => "u",
		MarkupType::Header(n) => &format!("h{n}"),
	};

	format!("<{tag_name}>{rendered}</{tag_name}>")
}

fn render_tag(tag: DomTag) -> String {
	if VOID_ELEMENTS.contains(&&*tag.name) {
		format!("<{}{} />", tag.name, render_attrs(tag.attrs))
	} else {
		format!(
			"<{}{}>{}</{}>",
			tag.name,
			render_attrs(tag.attrs),
			render_ast(tag.content),
			tag.name
		)
	}
}

fn render_word(text: &Moo) -> String {
	html_escape::encode_safe(text).into()
}

fn render_attrs(attrs: Attributes) -> String {
	let mut rendered = String::new();

	for (key, val) in attrs.0 {
		write!(&mut rendered, " {key}=\"{val}\"").expect("Writing should be successful!");
	}

	rendered
}