1use core::borrow::Borrow;
8use alloc::format;
9use alloc::rc::Rc;
10use alloc::string::String;
11use alloc::string::ToString;
12use alloc::vec::Vec;
13use alloc::collections::btree_map::BTreeMap;
14use regex::Regex;
15use serde_json::from_str;
16use crate::session::Session;
17use crate::tokinizer::RuleItemList;
18use crate::tokinizer::RuleType;
19use crate::types::CurrencyInfo;
20use crate::types::TimeOffset;
21use crate::tokinizer::Tokinizer;
22use crate::tokinizer::TokenInfo;
23use crate::tokinizer::RULE_FUNCTIONS;
24use crate::constants::*;
25
26pub type LanguageData<T> = BTreeMap<String, T>;
27pub type CurrencyData<T> = BTreeMap<Rc<CurrencyInfo>, T>;
28
29#[derive(Default)]
30#[derive(Clone)]
31#[derive(Debug)]
32#[derive(PartialEq)]
33pub struct MoneyConfig {
34 pub remove_fract_if_zero: bool,
35 pub use_fract_rounding: bool
36}
37
38#[derive(Default)]
39#[derive(Clone)]
40#[derive(Debug)]
41#[derive(PartialEq)]
42pub struct NumberConfig {
43 pub decimal_digits: u8,
44 pub remove_fract_if_zero: bool,
45 pub use_fract_rounding: bool
46}
47
48#[derive(Default)]
49#[derive(Clone)]
50#[derive(Debug)]
51#[derive(PartialEq)]
52pub struct DynamicType {
53 pub group_name: String,
54 pub index: usize,
55 pub format: String,
56 pub parse: Vec<Vec<Rc<TokenInfo>>>,
57 pub upgrade_code: String,
58 pub downgrade_code: String,
59 pub names:Vec<String>,
60 pub decimal_digits: Option<u8>,
61 pub use_fract_rounding: Option<bool>,
62 pub remove_fract_if_zero: Option<bool>
63}
64
65impl DynamicType {
66 pub fn new(group_name: String, index: usize, format: String, parse: Vec<Vec<Rc<TokenInfo>>>, upgrade_code: String, downgrade_code: String, names:Vec<String>, decimal_digits: Option<u8>, use_fract_rounding: Option<bool>, remove_fract_if_zero: Option<bool>) -> Self {
67 DynamicType {
68 group_name,
69 index,
70 format,
71 parse,
72 upgrade_code,
73 downgrade_code,
74 names,
75 decimal_digits,
76 use_fract_rounding,
77 remove_fract_if_zero
78 }
79 }
80}
81
82pub struct SmartCalcConfig {
83 pub(crate) json_data: JsonConstant,
84 pub(crate) format: LanguageData<JsonFormat>,
85 pub(crate) currency: LanguageData<Rc<CurrencyInfo>>,
86 pub(crate) currency_alias: LanguageData<Rc<CurrencyInfo>>,
87 pub(crate) timezones: BTreeMap<String, i32>,
88 pub(crate) currency_rate: CurrencyData<f64>,
89 pub(crate) token_parse_regex: LanguageData<Vec<Regex>>,
90 pub(crate) word_group: LanguageData<BTreeMap<String, Vec<String>>>,
91 pub(crate) constant_pair: LanguageData<BTreeMap<String, ConstantType>>,
92 pub(crate) language_alias_regex: LanguageData<Vec<(Regex, String)>>,
93 pub(crate) alias_regex: Vec<(Regex, String)>,
94 pub(crate) rule: LanguageData<RuleItemList>,
95 pub(crate) types: BTreeMap<String, BTreeMap<usize, Rc<DynamicType>>>,
96 pub(crate) type_conversion: Vec<JsonTypeConversion>,
97 pub(crate) month_regex: LanguageData<MonthItemList>,
98 pub(crate) money_config: MoneyConfig,
99 pub(crate) number_config: NumberConfig,
100 pub(crate) percentage_config: NumberConfig,
101 pub(crate) decimal_seperator: String,
102 pub(crate) thousand_separator: String,
103 pub(crate) timezone: String,
104 pub(crate) timezone_offset: i32
105}
106
107impl Default for SmartCalcConfig {
108 fn default() -> Self {
109 SmartCalcConfig::load_from_json(JSON_DATA)
110 }
111}
112
113impl SmartCalcConfig {
114 pub fn get_time_offset(&self) -> TimeOffset {
115 TimeOffset {
116 name: self.timezone.to_string(),
117 offset: self.timezone_offset
118 }
119 }
120
121 pub fn get_currency<T: Borrow<String>>(&self, currency: T) -> Option<Rc<CurrencyInfo>> {
122 self.currency
123 .get(currency.borrow())
124 .cloned()
125 }
126
127 pub fn load_from_json(json_data: &str) -> Self {
128 let mut config = SmartCalcConfig {
129 json_data: match from_str(json_data) {
130 Ok(data) => data,
131 Err(error) => panic!("JSON parse error: {}", error)
132 },
133 format: LanguageData::new(),
134 currency: LanguageData::new(),
135 currency_alias: LanguageData::new(),
136 timezones: BTreeMap::new(),
137 currency_rate: CurrencyData::new(),
138 token_parse_regex: LanguageData::new(),
139 word_group: LanguageData::new(),
140 constant_pair: LanguageData::new(),
141 language_alias_regex: LanguageData::new(),
142 rule: LanguageData::new(),
143 types: BTreeMap::new(),
144 type_conversion: Vec::new(),
145 month_regex: LanguageData::new(),
146 alias_regex: Vec::new(),
147 decimal_seperator: ",".to_string(),
148 thousand_separator: ".".to_string(),
149 timezone: "UTC".to_string(),
150 timezone_offset: 0,
151 money_config: MoneyConfig {
152 remove_fract_if_zero: false,
153 use_fract_rounding: true
154 },
155 number_config: NumberConfig {
156 decimal_digits: 2,
157 remove_fract_if_zero: true,
158 use_fract_rounding: true
159 },
160 percentage_config: NumberConfig {
161 decimal_digits: 2,
162 remove_fract_if_zero: true,
163 use_fract_rounding: true
164 },
165 };
166
167 for (name, currency) in config.json_data.currencies.iter() {
168 config.currency.insert(name.to_lowercase(), currency.clone());
169 }
170
171 for (timezone, offset) in config.json_data.timezones.iter() {
172 config.timezones.insert(timezone.clone(), *offset);
173 }
174
175 for (from, to) in config.json_data.alias.iter() {
176 match Regex::new(&format!(r"\b{}\b", from)) {
177 Ok(re) => config.alias_regex.push((re, to.to_string())),
178 Err(error) => log::error!("Alias parser error ({}) {}", from, error)
179 }
180 }
181
182 for (language, language_object) in config.json_data.languages.iter() {
183 let mut language_clone = language_object.format.clone();
184 language_clone.language = language.to_string();
185 config.format.insert(language.to_string(), language_clone);
186 }
187
188 for (key, value) in config.json_data.currency_alias.iter() {
189 match config.get_currency(value) {
190 Some(currency) => { config.currency_alias.insert(key.to_string(), currency.clone()); },
191 None => log::warn!("'{}' currency not found at alias", value)
192 };
193 }
194
195 for (key, value) in config.json_data.currency_rates.iter() {
196 match config.get_currency(key) {
197 Some(currency) => { config.currency_rate.insert(currency.clone(), *value); },
198 None => log::warn!("'{}' currency not found at rate", key)
199 };
200 }
201
202 for (language, language_constant) in config.json_data.languages.iter() {
203 let mut language_aliases = Vec::new();
204 for (alias, target_name) in language_constant.alias.iter() {
205
206 match Regex::new(&format!(r"\b{}\b", alias)) {
207 Ok(re) => language_aliases.push((re, target_name.to_string())),
208 Err(error) => log::error!("Alias parser error ({}) {}", alias, error)
209 }
210
211 }
212
213 config.language_alias_regex.insert(language.to_string(), language_aliases);
214 }
215
216 for (parse_type, items) in &config.json_data.parse {
217 let mut patterns = Vec::new();
218 for pattern in items {
219 match Regex::new(pattern) {
220 Ok(re) => patterns.push(re),
221 Err(error) => log::error!("Token parse regex error ({}) {}", pattern, error)
222 }
223 }
224
225 config.token_parse_regex.insert(parse_type.to_string(), patterns);
226 }
227
228 for (language, language_constant) in config.json_data.languages.iter() {
229 let mut language_group = Vec::new();
230 let mut month_list = Vec::with_capacity(12);
231 for i in 0..12 {
232 month_list.push(MonthInfo {
233 short: String::new(),
234 long: String::new(),
235 month: i + 1
236 });
237 }
238
239 for (month_name, month_number) in &language_constant.long_months {
240 match month_list.get_mut((*month_number - 1) as usize) {
241 Some(month_object) => month_object.long = month_name.to_string(),
242 None => log::warn!("Month not fetched. {}", month_number)
243 };
244 }
245
246 for (month_name, month_number) in &language_constant.short_months {
247 match month_list.get_mut((*month_number - 1) as usize) {
248 Some(month_object) => month_object.short = month_name.to_string(),
249 None => log::warn!("Month not fetched. {}", month_number)
250 };
251 }
252
253 for month in month_list.iter() {
254 let pattern = &format!(r"\b{}\b|\b{}\b", month.long, month.short);
255 match Regex::new(pattern) {
256 Ok(re) => language_group.push((re, month.clone())),
257 Err(error) => log::error!("Month parser error ({}) {}", month.long, error)
258 }
259 }
260
261 config.month_regex.insert(language.to_string(), language_group);
262 }
263
264 for (language, language_constant) in config.json_data.languages.iter() {
265 let mut word_groups = BTreeMap::new();
266 for (word_group_name, word_group_items) in language_constant.word_group.iter() {
267 let mut patterns = Vec::new();
268
269 for pattern in word_group_items {
270 patterns.push(pattern.to_string());
271 }
272
273 word_groups.insert(word_group_name.to_string(), patterns);
274 }
275
276 config.word_group.insert(language.to_string(), word_groups);
277 }
278
279 for (language, language_constant) in config.json_data.languages.iter() {
280 let mut constants = BTreeMap::new();
281 for (alias_name, constant_type) in language_constant.constant_pair.iter() {
282
283 match ConstantType::from_u8(*constant_type) {
284 Some(const_type) => {
285 constants.insert(alias_name.to_string(), const_type);
286 },
287 _ => log::error!("Constant type not parsed. {}", constant_type)
288 };
289 }
290
291 config.constant_pair.insert(language.to_string(), constants);
292 }
293
294 for (language, language_constant) in config.json_data.languages.iter() {
295 let mut language_rules = Vec::new();
296 for (rule_name, rule) in language_constant.rules.iter() {
297 if let Some(function_ref) = RULE_FUNCTIONS.get(rule_name) {
298 let mut function_items = Vec::new();
299
300 for rule_item in &rule.rules {
301 let mut session = Session::new();
302 session.set_language(language.to_string());
303 session.set_text(rule_item.to_string());
304 function_items.push(Tokinizer::token_infos(&config, &session));
305 }
306
307 language_rules.push(RuleType::Internal {
308 function_name: rule_name.to_string(),
309 function: *function_ref,
310 tokens_list: function_items
311 });
312 }
313 else {
314 log::warn!("Function not found : {}", rule_name);
315 }
316 }
317
318 config.rule.insert(language.to_string(), language_rules);
319 }
320
321 for dynamic_type in config.json_data.types.iter() {
322 let mut dynamic_type_holder = BTreeMap::new();
323
324 for type_item in dynamic_type.items.iter() {
325
326 if type_item.upgrade_code.is_none() || type_item.downgrade_code.is_none() {
327 log::warn!("Dynamic type {}:{} has missing calculation code. Please check upgrade_code and downgrade_code fields", dynamic_type.name, type_item.index);
328 continue;
329 }
330
331 let upgrade_code = type_item.upgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
332 let downgrade_code = type_item.downgrade_code.as_ref().map_or(String::new(), |item| item.to_string());
333
334 let mut token_info = DynamicType {
335 group_name: dynamic_type.name.to_string(),
336 index: type_item.index,
337 format: type_item.format.to_string(),
338 parse: Vec::new(),
339 upgrade_code,
340 downgrade_code,
341 names: type_item.names.clone(),
342 decimal_digits: type_item.decimal_digits,
343 use_fract_rounding: type_item.use_fract_rounding,
344 remove_fract_if_zero: type_item.remove_fract_if_zero
345 };
346
347 for type_parse_item in type_item.parse.iter() {
348 let mut session = Session::new();
349 session.set_language("en".to_string());
350 session.set_text(type_parse_item.to_string());
351
352 let tokens = Tokinizer::token_infos(&config, &session);
353 token_info.parse.push(tokens);
354 }
355
356 dynamic_type_holder.insert(token_info.index, Rc::new(token_info));
357 }
358
359 config.types.insert(dynamic_type.name.to_string(), dynamic_type_holder);
360 }
361
362 for type_conversion in config.json_data.type_conversion.iter() {
363 let source = config.types.get(&type_conversion.source.name);
364 let target = config.types.get(&type_conversion.target.name);
365
366 let mut source_found = false;
367 let mut target_found = false;
368
369 if let Some(source) = source {
370 source_found = source.contains_key(&type_conversion.source.index)
371
372 }
373
374 if let Some(target) = target {
375 target_found = target.contains_key(&type_conversion.target.index)
376
377 }
378
379 if !source_found {
380 log::warn!("{} type not defined", type_conversion.source.name);
381 }
382
383 if !target_found {
384 log::warn!("{} type not defined", type_conversion.target.name);
385 }
386
387 if source_found && target_found {
388 config.type_conversion.push(type_conversion.clone());
389 }
390 }
391
392 config
393 }
394}