use html5ever::{ Attribute, ParseOpts, QualName, parse_document, tendril::{Tendril, TendrilSink, fmt::UTF8}, }; use markup5ever_rcdom::{Node, NodeData, RcDom}; use std::{cell::RefCell, io, rc::Rc, str}; #[derive(Default)] pub(crate) struct MinifierOptions { pub omit_doctype: bool, pub preserve_comments: bool, pub collapse_whitespace: bool, } pub(crate) struct Minifier<'a, W: io::Write> { w: &'a mut W, options: MinifierOptions, preceding_whitespace: bool, } impl<'a, W> Minifier<'a, W> where W: io::Write, { /// Creates a new `Minifier` instance. #[inline] pub fn new(w: &'a mut W, options: MinifierOptions) -> Self { Self { w, options, preceding_whitespace: false, } } /// Minifies the given reader input. /// /// # Errors /// /// Will return `Err` if unable to write to the output writer. #[inline] pub fn minify(&mut self, mut r: &mut R) -> io::Result<()> { let dom = parse_document(RcDom::default(), ParseOpts::default()) .from_utf8() .read_from(&mut r)?; if !self.options.omit_doctype { self.w.write_all(b"")?; } self.minify_node(&None, &dom.document) } fn minify_node<'b>(&mut self, ctx: &'b Option, node: &'b Node) -> io::Result<()> { match &node.data { NodeData::Text { contents } => { // Check if whitespace collapsing disabled let contents = contents.borrow(); let contents = contents.as_ref(); if !self.options.collapse_whitespace { return self.w.write_all(contents.as_bytes()); } // Check if parent is whitespace preserving element or contains code ( ", "", true, false, ), ( " ", "", true, false, ), ("

A", "

A", true, false), ("

A", "

A", true, false), // Retain whitespace, whitespace before

( "

A ", "

A ", false, false, ), // Retain whitespace, touching

("

A", "

A", false, false), // Comments ignored ("

A", "

A", false, false), // Comments preserved ( "

A", "

A", false, true, ), // Retain end tag if touching inline element ( "

Some text

", "

Some text

", false, false, ), ] { let mut w = Vec::new(); let mut minifier = Minifier::new( &mut w, MinifierOptions { omit_doctype: true, preserve_comments, collapse_whitespace, }, ); minifier.minify(&mut input.as_bytes()).unwrap(); let s = str::from_utf8(&w).unwrap(); assert_eq!(expected, s); } } }