use std::borrow::Cow;
use crate::model::NormalizerSpec;
use crate::trie::DoubleArray;
pub const SPACE_SYMBOL: &str = "\u{2581}";
struct Charsmap {
trie: DoubleArray,
normalized: Vec<u8>,
}
impl Charsmap {
fn decode(blob: &[u8]) -> Option<Charsmap> {
if blob.len() <= 4 {
return None;
}
let size = u32::from_le_bytes([blob[0], blob[1], blob[2], blob[3]]) as usize;
if size >= blob.len() - 4 || size % 4 != 0 {
return None;
}
let trie = DoubleArray::from_trie_bytes(&blob[4..4 + size]);
let normalized = blob[4 + size..].to_vec();
Some(Charsmap { trie, normalized })
}
fn normalize_prefix<'a>(&'a self, input: &'a [u8]) -> (Cow<'a, [u8]>, usize) {
let mut longest_len = 0usize;
let mut longest_val = 0usize;
for (val, len) in self.trie.common_prefix_search(input) {
if longest_len == 0 || len > longest_len {
longest_len = len;
longest_val = val as usize;
}
}
if longest_len == 0 || longest_len > input.len() || longest_val >= self.normalized.len() {
let len = utf8_len(input[0]).min(input.len());
(Cow::Borrowed(&input[..len]), len)
} else {
let start = longest_val;
let end = self.normalized[start..]
.iter()
.position(|&b| b == 0)
.map(|p| start + p)
.unwrap_or(self.normalized.len());
(Cow::Borrowed(&self.normalized[start..end]), longest_len)
}
}
}
pub struct Normalizer {
add_dummy_prefix: bool,
remove_extra_whitespaces: bool,
escape_whitespaces: bool,
treat_whitespace_as_suffix: bool,
charsmap: Option<Charsmap>,
}
impl Normalizer {
pub fn new(spec: &NormalizerSpec, treat_whitespace_as_suffix: bool) -> Normalizer {
let charsmap = if spec.precompiled_charsmap.is_empty() {
None
} else {
Charsmap::decode(&spec.precompiled_charsmap)
};
Normalizer {
add_dummy_prefix: spec.add_dummy_prefix,
remove_extra_whitespaces: spec.remove_extra_whitespaces,
escape_whitespaces: spec.escape_whitespaces,
treat_whitespace_as_suffix,
charsmap,
}
}
fn normalize_prefix<'a>(&'a self, input: &'a [u8]) -> (Cow<'a, [u8]>, usize) {
match &self.charsmap {
Some(cm) => cm.normalize_prefix(input),
None => {
let len = utf8_len(input[0]).min(input.len());
(Cow::Borrowed(&input[..len]), len)
}
}
}
pub fn normalize(&self, text: &str) -> String {
let input = text.as_bytes();
let space: &[u8] = if self.escape_whitespaces {
SPACE_SYMBOL.as_bytes()
} else {
b" "
};
let mut out: Vec<u8> = Vec::with_capacity(input.len() * 2);
if !self.treat_whitespace_as_suffix && self.add_dummy_prefix {
out.extend_from_slice(space);
}
let mut is_prev_space = self.remove_extra_whitespaces;
let mut pos = 0;
while pos < input.len() {
let (sp_cow, consumed) = self.normalize_prefix(&input[pos..]);
let mut sp: &[u8] = sp_cow.as_ref();
while is_prev_space && sp.first() == Some(&b' ') {
sp = &sp[1..];
}
if !sp.is_empty() {
for &b in sp {
if b == b' ' {
out.extend_from_slice(space);
} else {
out.push(b);
}
}
is_prev_space = sp.last() == Some(&b' ');
}
pos += consumed.max(1);
if !self.remove_extra_whitespaces {
is_prev_space = false;
}
}
if self.remove_extra_whitespaces {
while out.ends_with(space) {
out.truncate(out.len() - space.len());
}
}
if self.treat_whitespace_as_suffix && self.add_dummy_prefix {
out.extend_from_slice(space);
}
String::from_utf8(out).unwrap_or_default()
}
}
fn utf8_len(b: u8) -> usize {
match b {
0x00..=0x7f => 1,
0xc0..=0xdf => 2,
0xe0..=0xef => 3,
0xf0..=0xf7 => 4,
_ => 1,
}
}