pub(crate) mod unicode;
mod lower_case;
mod path_hierarchy;
mod shingle;
mod standard_tokenizer;
mod suggest_tokenizer;
mod token_limit;
mod word_delimiter;
pub mod numeric;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Token {
pub term: String,
pub position_increment: u32,
pub start_offset: u32,
pub end_offset: u32,
pub token_type: String,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct EndState {
pub position_increment: u32,
pub offset: u32,
}
pub(crate) struct StagedToken {
pub token: Token,
pub stop: EndState,
}
#[derive(Default)]
pub(crate) struct Staged {
pub tokens: Vec<StagedToken>,
pub end: EndState,
}
impl From<Staged> for TokenStreamResult {
fn from(staged: Staged) -> Self {
Self {
tokens: staged
.tokens
.into_iter()
.map(|staged| staged.token)
.collect(),
final_position_increment: staged.end.position_increment,
final_offset: staged.end.offset,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct TokenStreamResult {
pub tokens: Vec<Token>,
pub final_position_increment: u32,
pub final_offset: u32,
}
pub mod field_names {
pub const ANCESTORS: &str = ":ancestors";
pub const SPELLCHECK: &str = ":spellcheck";
pub const SUGGEST: &str = ":suggest";
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Chain {
Default,
OriginalTerm,
Ancestors,
Spellcheck,
Suggest,
}
const MAXIMUM_SHINGLE_SIZE: usize = 3;
pub const DEFAULT_MAXIMUM_FIELD_LENGTH: usize = 10_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AnalyzerSettings {
pub maximum_field_length: Option<usize>,
pub index_original_term: bool,
pub evaluate_path_restrictions: bool,
pub suggest_analyzed: bool,
}
impl Default for AnalyzerSettings {
fn default() -> Self {
Self {
maximum_field_length: Some(DEFAULT_MAXIMUM_FIELD_LENGTH),
index_original_term: false,
evaluate_path_restrictions: false,
suggest_analyzed: false,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Analyzer {
settings: AnalyzerSettings,
}
impl Analyzer {
#[must_use]
pub const fn new(settings: AnalyzerSettings) -> Self {
Self { settings }
}
#[must_use]
pub const fn chain_of(&self, field_name: &str) -> Chain {
if equal(field_name, field_names::SPELLCHECK) {
return Chain::Spellcheck;
}
if equal(field_name, field_names::SUGGEST) && !self.settings.suggest_analyzed {
return Chain::Suggest;
}
if equal(field_name, field_names::ANCESTORS) && self.settings.evaluate_path_restrictions {
return Chain::Ancestors;
}
if self.settings.index_original_term {
Chain::OriginalTerm
} else {
Chain::Default
}
}
const fn limit_of(&self, chain: Chain) -> Option<usize> {
match chain {
Chain::Spellcheck | Chain::Suggest => None,
_ => self.settings.maximum_field_length,
}
}
#[must_use]
pub fn tokens(&self, field_name: &str, text: &str) -> TokenStreamResult {
let chain = self.chain_of(field_name);
let units: Vec<u16> = text.encode_utf16().collect();
let produced = match chain {
Chain::Default | Chain::OriginalTerm => word_delimiter::filter(
lower_case::filter(standard_tokenizer::tokenize(&units)),
chain == Chain::OriginalTerm,
),
Chain::Ancestors => path_hierarchy::tokenize(&units),
Chain::Spellcheck => shingle::filter(
word_delimiter::filter(
lower_case::filter(standard_tokenizer::tokenize(&units)),
false,
),
MAXIMUM_SHINGLE_SIZE,
),
Chain::Suggest => suggest_tokenizer::tokenize(&units),
};
let capped = match self.limit_of(chain) {
Some(limit) => token_limit::filter(produced, limit),
None => produced,
};
capped.into()
}
}
const fn equal(left: &str, right: &str) -> bool {
let (left, right) = (left.as_bytes(), right.as_bytes());
if left.len() != right.len() {
return false;
}
let mut at = 0;
while at < left.len() {
if left[at] != right[at] {
return false;
}
at += 1;
}
true
}
pub(crate) fn code_point_at(units: &[u16], at: usize) -> (u32, usize) {
let first = units[at];
if (0xd800..0xdc00).contains(&first) && at + 1 < units.len() {
let second = units[at + 1];
if (0xdc00..0xe000).contains(&second) {
let point =
0x1_0000 + ((u32::from(first) - 0xd800) << 10) + (u32::from(second) - 0xdc00);
return (point, 2);
}
}
(u32::from(first), 1)
}
pub(crate) fn units_to_string(units: &[u16]) -> String {
String::from_utf16_lossy(units)
}