diff --git a/fuzz/test_case/src/lib.rs b/fuzz/test_case/src/lib.rs index 70f01632..98a2d96f 100644 --- a/fuzz/test_case/src/lib.rs +++ b/fuzz/test_case/src/lib.rs @@ -11,7 +11,8 @@ use std::ffi::{CStr, CString}; use encoding_rs::*; use lol_html::html_content::ContentType; -use lol_html::{comments, doc_comments, doc_text, element, text, HtmlRewriter, MemorySettings, Settings}; +use lol_html::{comments, doc_comments, doc_text, element, streaming, text}; +use lol_html::{HtmlRewriter, MemorySettings, Settings}; include!(concat!(env!("OUT_DIR"), "/bindings.rs")); @@ -111,10 +112,12 @@ fn run_rewriter_iter(data: &[u8], selector: &str, encoding: &'static Encoding) { &format!(""), ContentType::Html, ); - el.set_inner_content( - &format!(""), - ContentType::Html, - ); + + let replaced = format!(""); + el.streaming_set_inner_content(streaming!(move |sink| { + sink.write_str(&replaced, ContentType::Html); + Ok(()) + })); Ok(()) }), diff --git a/src/lib.rs b/src/lib.rs index a780160b..b7be6475 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,7 @@ pub mod errors { pub mod html_content { pub use super::rewritable_units::{ Attribute, Comment, ContentType, Doctype, DocumentEnd, Element, EndTag, StartTag, - TextChunk, UserData, + StreamingHandler, StreamingHandlerSink, TextChunk, UserData, }; pub use super::html::TextType; diff --git a/src/rewritable_units/document_end.rs b/src/rewritable_units/document_end.rs index 5d87c98b..4458157c 100644 --- a/src/rewritable_units/document_end.rs +++ b/src/rewritable_units/document_end.rs @@ -1,5 +1,4 @@ -use super::text_encoder::StreamingHandlerSink; -use super::ContentType; +use super::{ContentType, StreamingHandlerSink}; use encoding_rs::Encoding; use crate::transform_stream::OutputSink; diff --git a/src/rewritable_units/element.rs b/src/rewritable_units/element.rs index 4f65b726..ca59726e 100644 --- a/src/rewritable_units/element.rs +++ b/src/rewritable_units/element.rs @@ -1,5 +1,8 @@ use super::mutations::MutationsInner; -use super::{Attribute, AttributeNameError, ContentType, EndTag, Mutations, StartTag, StringChunk}; +use super::{ + Attribute, AttributeNameError, ContentType, EndTag, Mutations, StartTag, StreamingHandler, + StringChunk, +}; use crate::base::Bytes; use crate::rewriter::{HandlerTypes, LocalHandlerTypes}; use encoding_rs::Encoding; @@ -241,6 +244,19 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { .push_back((content, content_type).into()); } + /// Inserts content from a [`StreamingHandler`] before the element. + /// + /// Consequent calls to the method append to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_before(&mut self, string_writer: Box) { + self.start_tag + .mutations + .mutate() + .content_before + .push_back(string_writer.into()); + } + /// Inserts `content` after the element. /// /// Consequent calls to the method prepend `content` to the previously inserted content. @@ -283,6 +299,16 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { .push_front(chunk); } + /// Inserts content from a [`StreamingHandler`] after the element. + /// + /// Consequent calls to the method prepend to the previously inserted content. + /// + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_after(&mut self, string_writer: Box) { + self.after_chunk(string_writer.into()); + } + /// Prepends `content` to the element's inner content, i.e. inserts content right after /// the element's start tag. /// @@ -333,6 +359,20 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { } } + /// Prepends content from a [`StreamingHandler`] to the element's inner content, + /// i.e. inserts content right after the element's start tag. + /// + /// Consequent calls to the method prepend to the previously inserted content. + /// A call to the method doesn't make any effect if the element is an [empty element]. + /// + /// [empty element]: https://developer.mozilla.org/en-US/docs/Glossary/Empty_element + /// + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_prepend(&mut self, string_writer: Box) { + self.prepend_chunk(string_writer.into()); + } + /// Appends `content` to the element's inner content, i.e. inserts content right before /// the element's end tag. /// @@ -379,6 +419,19 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { } } + /// Appends content from a [`StreamingHandler`] to the element's inner content, + /// i.e. inserts content right before the element's end tag. + /// + /// Consequent calls to the method append to the previously inserted content. + /// A call to the method doesn't make any effect if the element is an [empty element]. + /// + /// [empty element]: https://developer.mozilla.org/en-US/docs/Glossary/Empty_element + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_append(&mut self, string_writer: Box) { + self.append_chunk(string_writer.into()); + } + /// Replaces inner content of the element with `content`. /// /// Consequent calls to the method overwrite previously inserted content. @@ -429,6 +482,19 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { } } + /// Replaces inner content of the element with content from a [`StreamingHandler`]. + /// + /// Consequent calls to the method overwrite previously inserted content. + /// A call to the method doesn't make any effect if the element is an [empty element]. + /// + /// [empty element]: https://developer.mozilla.org/en-US/docs/Glossary/Empty_element + /// + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_set_inner_content(&mut self, string_writer: Box) { + self.set_inner_content_chunk(string_writer.into()); + } + /// Replaces the element and its inner content with `content`. /// /// Consequent calls to the method overwrite previously inserted content. @@ -470,6 +536,16 @@ impl<'r, 't, H: HandlerTypes> Element<'r, 't, H> { } } + /// Replaces the element and its inner content with content from a [`StreamingHandler`]. + /// + /// Consequent calls to the method overwrite previously inserted content. + /// + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_replace(&mut self, string_writer: Box) { + self.replace_chunk(string_writer.into()); + } + /// Removes the element and its inner content. #[inline] pub fn remove(&mut self) { @@ -638,6 +714,7 @@ mod tests { use crate::rewritable_units::test_utils::*; use crate::*; use encoding_rs::{Encoding, EUC_JP, UTF_8}; + use rewritable_units::StreamingHandlerSink; fn rewrite_element( html: &[u8], @@ -660,7 +737,11 @@ mod tests { el.before("[before: should be removed]", ContentType::Text); el.after("[after: should be removed]", ContentType::Text); el.append("[append: should be removed]", ContentType::Text); - el.before("[before: should be removed]", ContentType::Text); + el.streaming_before(Box::new(|sink: &mut StreamingHandlerSink<'_>| { + sink.write_str("[before:", ContentType::Text); + sink.write_str(" should be removed]", ContentType::Text); + Ok(()) + })); Ok(()) }), ], @@ -962,7 +1043,10 @@ mod tests { encoded("
HiRemoveŴ
") { let output = rewrite_element(&html, enc, "span", |el| { - el.prepend("", ContentType::Html); + el.streaming_prepend(streaming!(|s| { + s.write_str("", ContentType::Html); + Ok(()) + })); el.append("", ContentType::Html); el.set_inner_content("", ContentType::Html); el.set_inner_content("", ContentType::Text); @@ -1096,7 +1180,17 @@ mod tests { #[test] fn self_closing_element() { let output = rewrite_element(b"Hi", UTF_8, "foo", |el| { - el.after("", ContentType::Html); + el.after("->", ContentType::Html); + el.streaming_after(streaming!(|sink| { + sink.write_str("er-", ContentType::Html); + Ok(()) + })); + el.after("t", ContentType::Html); + el.streaming_after(streaming!(|sink| { + sink.write_str("af", ContentType::Html); + Ok(()) + })); + el.after("", ContentType::Html); - c.replace("", ContentType::Text); + c.streaming_replace(streaming!(|h| { + h.write_str("", ContentType::Text); + Ok(()) + })); assert!(c.removed()); }, diff --git a/src/rewritable_units/tokens/end_tag.rs b/src/rewritable_units/tokens/end_tag.rs index 3cff346b..fbd7ee32 100644 --- a/src/rewritable_units/tokens/end_tag.rs +++ b/src/rewritable_units/tokens/end_tag.rs @@ -1,7 +1,7 @@ use super::{Mutations, Token}; use crate::base::Bytes; use crate::errors::RewritingError; -use crate::html_content::ContentType; +use crate::html_content::{ContentType, StreamingHandler}; use encoding_rs::Encoding; use std::fmt::{self, Debug}; @@ -96,6 +96,42 @@ impl<'i> EndTag<'i> { .replace((content, content_type).into()); } + /// Inserts content from a [`StreamingHandler`] before the end tag. + /// + /// Consequent calls to the method append to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + #[inline] + pub fn streaming_before(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_before + .push_back(string_writer.into()); + } + + /// Inserts content from a [`StreamingHandler`] after the end tag. + /// + /// Consequent calls to the method prepend to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + #[inline] + pub fn streaming_after(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_after + .push_front(string_writer.into()); + } + + /// Replaces the end tag with content from a [`StreamingHandler`]. + /// + /// Consequent calls to the method overwrite previous replacement content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + #[inline] + pub fn streaming_replace(&mut self, string_writer: Box) { + self.mutations.mutate().replace(string_writer.into()); + } + /// Removes the end tag. #[inline] pub fn remove(&mut self) { diff --git a/src/rewritable_units/tokens/start_tag.rs b/src/rewritable_units/tokens/start_tag.rs index e7849a55..5b637d71 100644 --- a/src/rewritable_units/tokens/start_tag.rs +++ b/src/rewritable_units/tokens/start_tag.rs @@ -3,7 +3,7 @@ use super::{Mutations, Serialize, Token}; use crate::base::Bytes; use crate::errors::RewritingError; use crate::html::Namespace; -use crate::html_content::ContentType; +use crate::html_content::{ContentType, StreamingHandler}; use encoding_rs::Encoding; use std::fmt::{self, Debug}; @@ -139,6 +139,39 @@ impl<'i> StartTag<'i> { .replace((content, content_type).into()); } + /// Inserts content from a [`StreamingHandler`] before the start tag. + /// + /// Consequent calls to the method append to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_before(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_before + .push_back(string_writer.into()); + } + + /// Inserts content from a [`StreamingHandler`] after the start tag. + /// + /// Consequent calls to the method prepend to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_after(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_after + .push_front(string_writer.into()); + } + + /// Replaces the start tag with the content from a [`StreamingHandler`]. + /// + /// Consequent calls to the method overwrite previous replacement content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_replace(&mut self, string_writer: Box) { + self.mutations.mutate().replace(string_writer.into()); + } + /// Removes the start tag. #[inline] pub fn remove(&mut self) { diff --git a/src/rewritable_units/tokens/text_chunk.rs b/src/rewritable_units/tokens/text_chunk.rs index 7a6589d0..7af680a1 100644 --- a/src/rewritable_units/tokens/text_chunk.rs +++ b/src/rewritable_units/tokens/text_chunk.rs @@ -2,7 +2,7 @@ use super::{Mutations, Token}; use crate::base::Bytes; use crate::errors::RewritingError; use crate::html::TextType; -use crate::html_content::ContentType; +use crate::html_content::{ContentType, StreamingHandler}; use encoding_rs::Encoding; use std::any::Any; use std::borrow::Cow; @@ -265,6 +265,39 @@ impl<'i> TextChunk<'i> { .replace((content, content_type).into()); } + /// Inserts content from a [`StreamingHandler`] before the text chunk. + /// + /// Consequent calls to the method append `content` to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_before(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_before + .push_back(string_writer.into()); + } + + /// Inserts content from a [`StreamingHandler`] after the text chunk. + /// + /// Consequent calls to the method prepend to the previously inserted content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_after(&mut self, string_writer: Box) { + self.mutations + .mutate() + .content_after + .push_front(string_writer.into()); + } + + /// Replaces the text chunk with the content from a [`StreamingHandler`]. + /// + /// Consequent calls to the method overwrite previous replacement content. + /// + /// Use the [`streaming!`] macro to make a `StreamingHandler` from a closure. + pub fn streaming_replace(&mut self, string_writer: Box) { + self.mutations.mutate().replace(string_writer.into()); + } + /// Removes the text chunk. #[inline] pub fn remove(&mut self) { diff --git a/src/rewriter/settings.rs b/src/rewriter/settings.rs index ea5451ca..92d1d5d2 100644 --- a/src/rewriter/settings.rs +++ b/src/rewriter/settings.rs @@ -503,6 +503,51 @@ macro_rules! comments { }}; } +/// A convenience macro to construct a `StreamingHandler` from a closure. +/// +/// For use with [`Element::streaming_replace`], etc. +/// +/// ```rust +/// use lol_html::{element, streaming, RewriteStrSettings}; +/// use lol_html::html_content::ContentType; +/// +/// RewriteStrSettings { +/// element_content_handlers: vec![ +/// element!("div", |element| { +/// element.streaming_replace(streaming!(|sink| { +/// sink.write_str("…", ContentType::Html); +/// sink.write_str("…", ContentType::Html); +/// Ok(()) +/// })); +/// Ok(()) +/// }) +/// ], +/// ..RewriteStrSettings::default() +/// }; +/// ``` + +#[macro_export(local_inner_macros)] +macro_rules! streaming { + ($closure:expr) => {{ + use ::std::error::Error; + use $crate::html_content::StreamingHandlerSink; + // Without this rust won't be able to always infer the type of the handler. + #[inline(always)] + const fn streaming_macro_type_hint( + handler_closure: StreamingHandler, + ) -> StreamingHandler + where + StreamingHandler: + FnOnce(&mut StreamingHandlerSink<'_>) -> Result<(), Box> + 'static + Send, + { + handler_closure + } + + Box::new(streaming_macro_type_hint($closure)) + as Box + }}; +} + #[doc(hidden)] #[macro_export] macro_rules! __document_content_handler {