#![allow(unused_variables)]
use crate::cd::{encoding_languages, mb_encoding_languages};
use crate::consts::{IANA_SUPPORTED_ALIASES, TOO_BIG_SEQUENCE};
use crate::utils::{decode, iana_name, is_multi_byte_encoding, range_scan, round_float};
use clap::Parser;
use encoding::DecoderTrap;
use ordered_float::OrderedFloat;
use serde::Serialize;
use std::cmp::Ordering;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::hash::Hash;
use std::ops::Index;
use std::path::PathBuf;
use std::time::Duration;
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Language {
English,
German,
French,
Dutch,
Italian,
Polish,
Spanish,
Russian,
Japanese,
Portuguese,
Swedish,
Chinese,
Ukrainian,
Norwegian,
Finnish,
Vietnamese,
Czech,
Hungarian,
Korean,
Indonesian,
Turkish,
Romanian,
Farsi,
Arabic,
Danish,
Serbian,
Lithuanian,
Slovene,
Slovak,
Hebrew,
Bulgarian,
Croatian,
Hindi,
Estonian,
Thai,
Greek,
Tamil,
Kazakh,
Unknown,
}
impl Display for Language {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct CoherenceMatch {
pub language: &'static Language,
pub score: f32,
}
pub type CoherenceMatches = Vec<CoherenceMatch>;
#[derive(Clone)]
pub struct CharsetMatch {
payload: Vec<u8>,
encoding: String,
mean_mess_ratio: f32,
coherence_matches: CoherenceMatches,
has_sig_or_bom: bool,
fingerprint: String,
submatch: Vec<CharsetMatch>,
decoded_payload: Option<String>,
}
impl Display for CharsetMatch {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ({})", self.payload, self.encoding)
}
}
impl Debug for CharsetMatch {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?} ({})", self.payload, self.encoding)
}
}
impl PartialEq<Self> for CharsetMatch {
fn eq(&self, other: &Self) -> bool {
self.encoding == other.encoding && self.fingerprint == other.fingerprint
}
}
impl PartialOrd<Self> for CharsetMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let mess_difference = (self.mean_mess_ratio - other.mean_mess_ratio).abs();
let coherence_a = self.coherence();
let coherence_b = other.coherence();
let coherence_difference = (coherence_a - coherence_b).abs();
if mess_difference < 0.01 && coherence_difference > 0.02 {
if mess_difference == 0.0 && coherence_difference == 0.0 {
return other
.multi_byte_usage()
.partial_cmp(&self.multi_byte_usage());
}
return coherence_b.partial_cmp(&coherence_a);
}
self.mean_mess_ratio.partial_cmp(&other.mean_mess_ratio)
}
}
impl CharsetMatch {
pub fn new(
payload: &[u8],
encoding: &str,
mean_mess_ratio: f32,
has_sig_or_bom: bool,
coherence_matches: &CoherenceMatches,
decoded_payload: Option<&str>,
) -> Self {
let mut obj = CharsetMatch {
payload: Vec::from(payload),
encoding: String::from(encoding),
mean_mess_ratio,
coherence_matches: coherence_matches.clone(),
has_sig_or_bom,
submatch: vec![],
decoded_payload: decoded_payload.map(String::from),
fingerprint: String::new(),
};
if obj.decoded_payload.is_none() {
if let Ok(res) = decode(
&obj.payload,
obj.encoding.as_str(),
DecoderTrap::Strict,
false,
true,
) {
obj.decoded_payload =
Some(res.strip_prefix('\u{feff}').unwrap_or(&res).to_string());
}
}
if obj.decoded_payload.is_some() {
obj.fingerprint = format!(
"{:?}",
blake3::hash(
obj.decoded_payload
.as_ref()
.unwrap_or(&String::new())
.as_bytes()
)
);
}
obj
}
pub fn add_submatch(&mut self, submatch: CharsetMatch) {
self.submatch.push(submatch.clone());
}
pub fn alphabets(&self) -> Vec<String> {
todo!();
}
pub fn encoding_aliases(&self) -> Vec<&'static str> {
if let Some(res) = IANA_SUPPORTED_ALIASES.get(&self.encoding.as_str()) {
return res.clone();
}
vec![]
}
pub fn bom(&self) -> bool {
self.has_sig_or_bom
}
pub fn byte_order_mark(&self) -> bool {
self.has_sig_or_bom
}
pub fn encoding(&self) -> &str {
&self.encoding
}
pub fn chaos(&self) -> f32 {
self.mean_mess_ratio
}
pub fn most_probably_language(&self) -> &'static Language {
if self.coherence_matches.is_empty() {
if self.suitable_encodings().contains(&String::from("ascii")) {
return &Language::English;
}
let languages = if is_multi_byte_encoding(&self.encoding) {
mb_encoding_languages(&self.encoding)
} else {
encoding_languages(self.encoding.clone())
};
if languages.is_empty() || languages.contains(&&Language::Unknown) {
return &Language::Unknown;
}
return languages.first().unwrap();
}
self.coherence_matches
.first()
.map(|lang| lang.language)
.unwrap()
}
pub fn languages(&self) -> Vec<&'static Language> {
self.coherence_matches
.iter()
.map(|cm| cm.language)
.collect()
}
pub fn has_submatch(&self) -> bool {
!self.submatch.is_empty()
}
pub fn submatch(&self) -> &Vec<CharsetMatch> {
&self.submatch
}
pub fn multi_byte_usage(&self) -> f32 {
1.0 - (self
.decoded_payload()
.unwrap_or(String::new().as_ref())
.chars()
.count() as f32)
/ (self.payload.len() as f32)
}
pub fn raw(&self) -> &Vec<u8> {
&self.payload
}
pub fn chaos_percents(&self) -> f32 {
round_float(self.chaos() * 100.0, 3)
}
pub fn coherence_percents(&self) -> f32 {
round_float(self.coherence() * 100.0, 3)
}
pub fn coherence(&self) -> f32 {
if self.coherence_matches.is_empty() {
return 0.0;
}
self.coherence_matches
.first()
.map(|lang| lang.score)
.unwrap()
}
pub fn decoded_payload(&self) -> Option<&str> {
self.decoded_payload.as_deref()
}
pub fn fingerprint(&self) -> &str {
&self.fingerprint
}
pub fn suitable_encodings(&self) -> Vec<String> {
let mut result: Vec<String> = self.submatch.iter().map(|s| s.encoding.clone()).collect();
result.insert(0, self.encoding.clone());
result
}
pub fn unicode_ranges(&self) -> Vec<String> {
let mut ranges: Vec<String> = range_scan(self.decoded_payload().unwrap_or(""))
.iter()
.cloned()
.collect();
ranges.sort();
ranges
}
}
#[derive(Debug)]
pub struct CharsetMatches {
items: Vec<CharsetMatch>,
}
pub struct CharsetMatchesIterMut<'a> {
items: std::slice::IterMut<'a, CharsetMatch>,
}
pub struct CharsetMatchesIter<'a> {
items: std::slice::Iter<'a, CharsetMatch>,
}
impl CharsetMatches {
pub fn new(items: Option<Vec<CharsetMatch>>) -> Self {
let mut items = items.unwrap_or(vec![]);
CharsetMatches::resort(&mut items);
CharsetMatches { items }
}
pub fn append(&mut self, item: CharsetMatch) {
if item.payload.len() <= *TOO_BIG_SEQUENCE {
for m in self.items.iter_mut() {
if m.fingerprint() == item.fingerprint()
&& m.mean_mess_ratio == item.mean_mess_ratio
{
m.add_submatch(item.clone());
return;
}
}
}
self.items.push(item);
CharsetMatches::resort(&mut self.items);
}
pub fn get_best(&self) -> Option<&CharsetMatch> {
if self.items.is_empty() {
return None;
}
self.items.first()
}
pub fn get_by_encoding(&self, encoding: &str) -> Option<&CharsetMatch> {
let encoding = iana_name(encoding)?;
self.items
.iter()
.find(|&i| i.suitable_encodings().contains(&encoding.to_string()))
}
fn resort(items: &mut [CharsetMatch]) {
items.sort_by(|a, b| a.partial_cmp(b).unwrap());
}
pub fn iter_mut(&mut self) -> CharsetMatchesIterMut {
CharsetMatchesIterMut {
items: self.items.iter_mut(),
}
}
pub fn iter(&self) -> CharsetMatchesIter {
CharsetMatchesIter {
items: self.items.iter(),
}
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Index<usize> for CharsetMatches {
type Output = CharsetMatch;
fn index(&self, index: usize) -> &Self::Output {
&self.items[index]
}
}
impl<'a> Iterator for CharsetMatchesIterMut<'a> {
type Item = &'a mut CharsetMatch;
fn next(&mut self) -> Option<Self::Item> {
self.items.next()
}
}
impl<'a> Iterator for CharsetMatchesIter<'a> {
type Item = &'a CharsetMatch;
fn next(&mut self) -> Option<Self::Item> {
self.items.next()
}
}
#[derive(Clone)]
pub struct NormalizerSettings {
pub steps: usize,
pub chunk_size: usize,
pub threshold: OrderedFloat<f32>,
pub include_encodings: Vec<String>,
pub exclude_encodings: Vec<String>,
pub preemptive_behaviour: bool,
pub language_threshold: OrderedFloat<f32>,
pub enable_fallback: bool,
}
impl Default for NormalizerSettings {
fn default() -> Self {
NormalizerSettings {
steps: 5,
chunk_size: 512,
threshold: OrderedFloat(0.2),
include_encodings: vec![],
exclude_encodings: vec![],
preemptive_behaviour: true,
language_threshold: OrderedFloat(0.1),
enable_fallback: true,
}
}
}
#[derive(Parser, Debug)]
#[command(name = "Performance check for charset-normalizer-rs vs chardet vs chardetng")]
#[command(author, version, about, long_about = None)]
pub struct PerformanceArgs {
#[arg(short, long, default_value_t = 1)]
pub size_increase: u8,
}
pub struct PerformanceResult {
pub duration: Duration,
pub correct: bool,
}
#[derive(Parser, Debug)]
#[command(
name = "The Real First Universal Charset Detector. Discover originating encoding used on text file. Normalize text to unicode."
)]
#[command(author, version, about, long_about = None)]
pub struct CLINormalizerArgs {
#[arg(required = true, action = clap::ArgAction::Append)]
pub files: Vec<PathBuf>,
#[arg(short = 'v', long = "verbose", default_value_t = false)]
pub verbose: bool,
#[arg(short = 'a', long = "with-alternative", default_value_t = false)]
pub alternatives: bool,
#[arg(short, long, default_value_t = false)]
pub normalize: bool,
#[arg(short, long, default_value_t = false)]
pub minimal: bool,
#[arg(short, long, default_value_t = false)]
pub replace: bool,
#[arg(short, long, default_value_t = false)]
pub force: bool,
#[arg(short, long, default_value_t = 0.2)]
pub threshold: f32,
}
#[derive(Default, Debug, Serialize)]
pub struct CLINormalizerResult {
pub path: PathBuf,
pub encoding: Option<String>,
pub encoding_aliases: Vec<String>,
pub alternative_encodings: Vec<String>,
pub language: String,
pub alphabets: Vec<String>,
pub has_sig_or_bom: bool,
pub chaos: f32,
pub coherence: f32,
pub unicode_path: Option<PathBuf>,
pub is_preferred: bool,
}