Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions benches/encode_benchmarks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use criterion::{Criterion, criterion_group, criterion_main};
use tiny_clean::java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode};
use tiny_clean::uri_encoder::{UriEncoder, UriEncoderMode};
use tiny_clean::xml_encoder::{XmlEncoder, XmlEncoderMode};

use tiny_clean::{EncoderBuilder, UriEncoderMode, UriEncoder, XmlEncoderMode, XmlEncoder, JavaScriptEncoderMode, JavaScriptEncoder};

fn xml_encode_benches(c: &mut Criterion) {
let bench_data1 = std::fs::read_to_string("./benches/data/benchmark-data-1.txt").unwrap();
Expand Down
134 changes: 134 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use crate::java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode};
use crate::uri_encoder::{UriEncoder, UriEncoderMode};
use crate::xml_encoder::{XmlEncoder, XmlEncoderMode};

pub struct NoBuilder;
pub struct JavaScriptEncoderBuilder {
ascii_only: bool,
mode: JavaScriptEncoderMode,
}
pub struct XmlEncoderBuilder {
mode: XmlEncoderMode,
}

pub struct UriEncoderBuilder {
mode: UriEncoderMode,
}

pub struct EncoderBuilder<S> {
state: S,
}

impl EncoderBuilder<NoBuilder> {
pub fn new() -> Self {
EncoderBuilder { state: NoBuilder }
}

pub fn java_script(self) -> EncoderBuilder<JavaScriptEncoderBuilder> {
EncoderBuilder {
state: JavaScriptEncoderBuilder {
ascii_only: false,
mode: JavaScriptEncoderMode::Block,
},
}
}

pub fn xml(self) -> EncoderBuilder<XmlEncoderBuilder> {
EncoderBuilder {
state: XmlEncoderBuilder {
mode: XmlEncoderMode::All,
},
}
}

pub fn uri(self) -> EncoderBuilder<UriEncoderBuilder> {
EncoderBuilder {
state: UriEncoderBuilder {
mode: UriEncoderMode::FullUri,
},
}
}
}

impl EncoderBuilder<JavaScriptEncoderBuilder> {
pub fn with_ascii_only(self, ascii_only: bool) -> Self {
let mut state = self.state;
state.ascii_only = ascii_only;
Self { state }
}

pub fn with_encoder_mode(self, mode: JavaScriptEncoderMode) -> Self {
let mut state = self.state;
state.mode = mode;
Self { state }
}

pub fn build(self) -> JavaScriptEncoder {
JavaScriptEncoder::new(self.state.mode, self.state.ascii_only)
}
}

impl EncoderBuilder<XmlEncoderBuilder> {
pub fn with_mode(self, mode: XmlEncoderMode) -> Self {
let mut state = self.state;
state.mode = mode;
Self { state }
}

pub fn build(self) -> XmlEncoder {
XmlEncoder::new(self.state.mode)
}
}

impl EncoderBuilder<UriEncoderBuilder> {
pub fn with_mode(self, mode: UriEncoderMode) -> Self {
let mut state = self.state;
state.mode = mode;
Self { state }
}

pub fn build(self) -> UriEncoder {
UriEncoder::new(self.state.mode)
}
}

#[cfg(test)]
mod test {
use crate::builder::EncoderBuilder;
use crate::java_script_encoder::JavaScriptEncoderMode;
use crate::java_script_encoder::JavaScriptEncoderMode::Attribute;
use crate::uri_encoder::UriEncoderMode;
use crate::xml_encoder::XmlEncoderMode;

#[test]
fn test_build_java_script_encoder() {
let encoder = EncoderBuilder::new()
.java_script()
.with_encoder_mode(JavaScriptEncoderMode::Attribute)
.with_ascii_only(true);

assert_eq!(true, encoder.state.ascii_only);
assert_eq!(Attribute, encoder.state.mode);

let encoder = encoder.build();

assert_eq!(true, encoder.ascii_only);
assert_eq!(true, encoder.hex_encode_quotes);
}

#[test]
fn test_build_xml_encoder() {
let encoder = EncoderBuilder::new()
.xml()
.with_mode(XmlEncoderMode::Content);

assert_eq!(XmlEncoderMode::Content, encoder.state.mode);
}

#[test]
fn test_build_uri_encoder() {
let encoder = EncoderBuilder::new().uri().with_mode(UriEncoderMode::Component);

assert_eq!(UriEncoderMode::Component, encoder.state.mode);
}
}
26 changes: 17 additions & 9 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ pub(crate) const U_HEX: [char; 16] = [
/// - Shifts the value `1` to the left by the result of the calculation, creating a bitmask where
/// only one specific bit is set.
///
#[inline]
pub(crate) const fn char_mask(c: char) -> u32 {
#[inline(always)]
pub const fn char_mask(c: char) -> u32 {
1 << (c as u32 & 31)
}

Expand All @@ -28,8 +28,8 @@ pub(crate) const fn char_mask(c: char) -> u32 {
/// - Converts the character `c` into its Unicode scalar value (`u32`).
/// - Performs a bitwise right shift by 5 (`c as u32 >> 5`), which is equivalent to integer division by 32.
/// - Casts the resulting value to `usize` for use as an index or bucket identifier in further operations.
#[inline]
pub(crate) const fn char_bucket(c: char) -> usize {
#[inline(always)]
pub const fn char_bucket(c: char) -> usize {
(c as u32 >> 5) as usize
}

Expand All @@ -48,8 +48,8 @@ pub(crate) const fn char_bucket(c: char) -> usize {
/// value of the input character.
/// - This function uses the constants `HEX`, `HEX_SHIFT`, and `HEX_MASK` to efficiently extract and format
/// the hexadecimal digits.
#[inline]
pub(crate) fn encode_as_hex_byte(escape_char: char, output: &mut String, character: char) {
#[inline(always)]
pub fn encode_as_hex_byte(escape_char: char, output: &mut String, character: char) {
output.push(escape_char);
output.push('x');
output.push(HEX[(character as u32 >> HEX_SHIFT) as usize]);
Expand All @@ -71,8 +71,8 @@ pub(crate) fn encode_as_hex_byte(escape_char: char, output: &mut String, charact
/// hexadecimal Unicode code point of the input character.
/// - Hexadecimal digits are efficiently calculated and appended to the output using bitwise operations
/// and the `HEX` lookup table.
#[inline]
pub(crate) fn encode_as_unicode(escape_char: char, output: &mut String, character: char) {
#[inline(always)]
pub fn encode_as_unicode(escape_char: char, output: &mut String, character: char) {
output.push(escape_char);
output.push('u');
output.push(HEX[(character as u32 >> (3 * HEX_SHIFT)) as usize & HEX_MASK as usize]);
Expand All @@ -81,7 +81,15 @@ pub(crate) fn encode_as_unicode(escape_char: char, output: &mut String, characte
output.push(HEX[(character as u32 & HEX_MASK) as usize]);
}

pub(crate) fn dump_masks_to_ascii(masks: &[u32; 4]) {
#[inline(always)]
pub fn create_mask<const N: usize>(chars: [char; N]) -> u32 {
let mut mask = 0;
chars.iter().for_each(|char| mask |= char_mask(*char));
mask
}

#[cfg(debug_assertions)]
pub fn dump_masks_to_ascii(masks: &[u32; 4]) {
println!("Dumping Mask Values (0-127)");
for char in '\u{0000}'..='\u{007F}' {
let bucket = char_bucket(char);
Expand Down
15 changes: 8 additions & 7 deletions src/java_script_encoder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::common::{char_bucket, char_mask, encode_as_hex_byte, encode_as_unicode, dump_masks_to_ascii};
use crate::common::{char_bucket, char_mask, encode_as_hex_byte, encode_as_unicode};


#[derive(Debug, Clone, Copy, PartialEq)]
pub enum JavaScriptEncoderMode {
Expand All @@ -8,10 +9,11 @@ pub enum JavaScriptEncoderMode {
Attribute,
}

#[non_exhaustive]
pub struct JavaScriptEncoder {
ascii_only: bool,
valid_masks: [u32; 4],
hex_encode_quotes: bool,
pub ascii_only: bool,
pub valid_masks: [u32; 4],
pub hex_encode_quotes: bool,
}

impl JavaScriptEncoder {
Expand All @@ -36,9 +38,8 @@ impl JavaScriptEncoder {
valid_masks[1] &= !char_mask('&');
}

if cfg!(debug_assertions) {
dump_masks_to_ascii(&valid_masks);
}
#[cfg(debug_assertions)]
crate::common::dump_masks_to_ascii(&valid_masks);

let hex_encode_quotes = mode == JavaScriptEncoderMode::Attribute || mode == JavaScriptEncoderMode::Html;
JavaScriptEncoder {
Expand Down
19 changes: 15 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@

pub mod common;
pub mod uri_encoder;
pub mod java_script_encoder;
pub mod xml_encoder;
mod common;
mod uri_encoder;
mod java_script_encoder;
mod xml_encoder;
mod builder;

pub use builder::EncoderBuilder;

pub use xml_encoder::{XmlEncoderMode, XmlEncoder};
pub use java_script_encoder::{JavaScriptEncoder, JavaScriptEncoderMode};
pub use uri_encoder::{UriEncoderMode, UriEncoder};

#[cfg(debug_assertions)]
pub use common::dump_masks_to_ascii;



19 changes: 5 additions & 14 deletions src/uri_encoder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::common::{HEX_MASK, HEX_SHIFT, U_HEX, char_bucket, char_mask};
use crate::common::{HEX_MASK, HEX_SHIFT, U_HEX, char_bucket, char_mask, create_mask};

/// 0111_1111_1111 --> highest 2x utf 8 bytes
/// 0000_1000_0000 --> most sig. utf8 byte
Expand All @@ -16,6 +16,7 @@ const UTF8_4_BYTE_FIRST_MSB: u32 = 0b_0000_1111_0000;
const UTF8_SHIFT: u32 = 0b_0000_0000_0110;
const UTF8_MASK: u32 = 0b_0000_0011_1111;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum UriEncoderMode {
Component,
FullUri,
Expand Down Expand Up @@ -50,20 +51,10 @@ impl UriEncoder {
Self { valid_masks }
}
UriEncoderMode::FullUri => {
let reserved_chars1 = [
let uri_reserved_bucket1: u32 = create_mask([
'!', '#', '$', '?', '&', '(', ')', '*', '+', ',', ':', ';', '=', '/', '\'',
];
let mut uri_reserved_bucket1: u32 = 0;
for reserved in reserved_chars1 {
uri_reserved_bucket1 |= char_mask(reserved);
}

let reserved_chars2 = ['[', ']', '@'];
let mut uri_reserved_bucket2: u32 = 0;
for reserved in reserved_chars2 {
uri_reserved_bucket2 |= char_mask(reserved);
}

]);
let uri_reserved_bucket2: u32 = create_mask(['[', ']', '@']);
let valid_masks = [
0,
uri_unreserved_bucket1 | uri_reserved_bucket1,
Expand Down
33 changes: 7 additions & 26 deletions src/xml_encoder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::common::{char_bucket, char_mask};
use crate::common::{char_bucket, char_mask, create_mask};

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum XmlEncoderMode {
All,
Content,
Expand All @@ -17,11 +18,7 @@ impl XmlEncoder {
let base_mask = char_mask('\r') | char_mask('\t') | char_mask('\n');
match mode {
XmlEncoderMode::All => {
let to_be_encoded = ['&', '<', '>', '\'', '"'];
let mut to_be_encoded_mask = 0u32;
for char in to_be_encoded {
to_be_encoded_mask |= char_mask(char);
}
let to_be_encoded_mask = create_mask(['&', '<', '>', '\'', '"']);
let valid_masks = [
base_mask,
u32::MAX & !to_be_encoded_mask,
Expand All @@ -31,11 +28,7 @@ impl XmlEncoder {
Self {valid_masks}
}
XmlEncoderMode::Content => {
let to_be_encoded = ['&', '<', '>'];
let mut to_be_encoded_mask = 0u32;
for char in to_be_encoded {
to_be_encoded_mask |= char_mask(char);
}
let to_be_encoded_mask = create_mask(['&', '<', '>']);
let valid_masks = [
base_mask,
u32::MAX & !to_be_encoded_mask,
Expand All @@ -45,11 +38,7 @@ impl XmlEncoder {
Self {valid_masks}
}
XmlEncoderMode::Attribute => {
let to_be_encoded = ['&', '<', '\'', '"'];
let mut to_be_encoded_mask = 0u32;
for char in to_be_encoded {
to_be_encoded_mask |= char_mask(char);
}
let to_be_encoded_mask = create_mask(['&', '<', '\'', '"']);
let valid_masks = [
base_mask,
u32::MAX & !to_be_encoded_mask,
Expand All @@ -59,11 +48,7 @@ impl XmlEncoder {
Self {valid_masks}
}
XmlEncoderMode::SingleQuotedAttribute => {
let to_be_encoded = ['&', '<', '\''];
let mut to_be_encoded_mask = 0u32;
for char in to_be_encoded {
to_be_encoded_mask |= char_mask(char);
}
let to_be_encoded_mask = create_mask(['&', '<', '\'']);
let valid_masks = [
base_mask,
u32::MAX & !to_be_encoded_mask,
Expand All @@ -73,11 +58,7 @@ impl XmlEncoder {
Self {valid_masks}
}
XmlEncoderMode::DoubleQuotedAttribute => {
let to_be_encoded = ['&', '<', '"'];
let mut to_be_encoded_mask = 0u32;
for char in to_be_encoded {
to_be_encoded_mask |= char_mask(char);
}
let to_be_encoded_mask = create_mask(['&', '<', '"']);
let valid_masks = [
base_mask,
u32::MAX & !to_be_encoded_mask,
Expand Down
Loading