use std::collections::HashMap;
use regex::Regex;
use urlencoding::decode;
use once_cell::sync::Lazy;
use crate::{common::Result, exceptions::Exceptions, RXingResult};
use super::{
AddressBookAUResultParser, AddressBookDoCoMoResultParser, BizcardResultParser,
BookmarkDoCoMoResultParser, EmailAddressResultParser, EmailDoCoMoResultParser,
ExpandedProductResultParser, GeoResultParser, ISBNResultParser, ParsedClientResult,
ProductResultParser, SMSMMSResultParser, SMSTOMMSTOResultParser, SMTPResultParser,
TelResultParser, TextParsedRXingResult, URIResultParser, URLTOResultParser, VCardResultParser,
VEventResultParser, VINResultParser, WifiResultParser,
};
pub type ParserFunction = dyn Fn(&RXingResult) -> Option<ParsedClientResult>;
static DIGITS: Lazy<Regex> = Lazy::new(|| Regex::new("\\d+").unwrap());
const AMPERSAND: &str = "&"; const EQUALS: &str = "="; const BYTE_ORDER_MARK: &str = "\u{feff}";
pub fn getMassagedText(result: &RXingResult) -> String {
result
.getText()
.trim_start_matches(BYTE_ORDER_MARK)
.to_owned()
}
pub fn parse_result_with_parsers(
the_rxing_result: &RXingResult,
parsers: &[&ParserFunction],
) -> ParsedClientResult {
for parser in parsers {
let result = parser(the_rxing_result);
if let Some(res) = result {
return res;
}
}
parseRXingResult(the_rxing_result)
}
pub fn parse_result_with_parser<F: Fn(&RXingResult) -> Option<ParsedClientResult>>(
the_rxing_result: &RXingResult,
parser: F,
) -> Option<ParsedClientResult> {
parser(the_rxing_result)
}
pub fn parseRXingResult(the_rxing_result: &RXingResult) -> ParsedClientResult {
let PARSERS: [&ParserFunction; 20] = [
&BookmarkDoCoMoResultParser::parse,
&AddressBookDoCoMoResultParser::parse,
&EmailDoCoMoResultParser::parse,
&AddressBookAUResultParser::parse,
&VCardResultParser::parse,
&BizcardResultParser::parse,
&VEventResultParser::parse,
&EmailAddressResultParser::parse,
&SMTPResultParser::parse,
&TelResultParser::parse,
&SMSMMSResultParser::parse,
&SMSTOMMSTOResultParser::parse,
&GeoResultParser::parse,
&WifiResultParser::parse,
&URLTOResultParser::parse,
&URIResultParser::parse,
&ISBNResultParser::parse,
&ProductResultParser::parse,
&ExpandedProductResultParser::parse,
&VINResultParser::parse,
];
for parser in PARSERS {
let result = parser(the_rxing_result);
if let Some(res) = result {
return res;
}
}
ParsedClientResult::TextResult(TextParsedRXingResult::new(
the_rxing_result.getText().to_owned(),
String::default(),
))
}
pub fn maybe_append_string(value: &str, result: &mut String) {
if !value.is_empty() {
if !result.is_empty() {
result.push('\n');
}
result.push_str(value);
}
}
pub fn maybe_append_multiple(value: &[String], result: &mut String) {
for s in value {
if !s.is_empty() {
if !result.is_empty() {
result.push('\n');
}
result.push_str(s);
}
}
}
#[inline(always)]
pub fn maybeWrap(value: Option<String>) -> Option<Vec<String>> {
if value.is_none() {
None
} else {
Some(vec![value.unwrap()])
}
}
pub fn unescapeBackslash(escaped: &str) -> String {
let backslash = escaped.find('\\');
if backslash.is_none() {
return escaped.to_owned();
}
let max = escaped.chars().count();
let backslash = backslash.unwrap_or(0);
let mut unescaped = escaped.chars().take(backslash).collect::<String>();
unescaped.reserve(max - 1);
let mut nextIsEscaped = false;
for c in escaped.chars().skip(backslash) {
if nextIsEscaped || c != '\\' {
unescaped.push(c);
nextIsEscaped = false;
} else {
nextIsEscaped = true;
}
}
unescaped
}
pub fn parseHexDigit(c: char) -> i32 {
if c.is_ascii_digit() {
return (c as u8 - b'0') as i32;
}
if ('a'..='f').contains(&c) {
return 10 + (c as u8 - b'a') as i32;
}
if ('A'..='F').contains(&c) {
return 10 + (c as u8 - b'A') as i32;
}
-1
}
#[inline(always)]
pub fn isStringOfDigits(value: &str, length: usize) -> bool {
!value.is_empty() && length > 0 && length == value.len() && DIGITS.is_match(value)
}
pub fn isSubstringOfDigits(value: &str, offset: usize, length: usize) -> bool {
if value.is_empty() || length == 0 {
return false;
}
let max = offset + length;
let sub_seq: String = value.chars().skip(offset).take(length).collect();
let is_a_match = if let Some(mtch) = DIGITS.find(&sub_seq) {
mtch.start() == 0 && mtch.end() == sub_seq.chars().count()
} else {
false
};
value.len() >= max && is_a_match
}
pub fn parseNameValuePairs(uri: &str) -> Option<HashMap<String, String>> {
let paramStart = uri.find('?');
paramStart?;
let mut result = HashMap::with_capacity(3);
let paramStart = paramStart.unwrap_or(0);
let sub_str = &uri[paramStart + 1..]; let list = sub_str.split(AMPERSAND);
for keyValue in list {
appendKeyValue(keyValue, &mut result);
}
Some(result)
}
pub fn appendKeyValue(keyValue: &str, result: &mut HashMap<String, String>) {
let keyValueTokens = keyValue.split(EQUALS);
let kvp: Vec<&str> = keyValueTokens.take(2).collect();
if let [key, value] = kvp[..] {
let p_value = urlDecode(value).unwrap_or_else(|_| String::default());
result.insert(key.to_owned(), p_value);
}
}
pub fn urlDecode(encoded: &str) -> Result<String> {
if let Ok(decoded) = decode(encoded) {
Ok(decoded.to_string())
} else {
Err(Exceptions::illegal_state_with(
"UnsupportedEncodingException",
))
}
}
pub fn matchPrefixedField(
prefix: &str,
rawText: &str,
endChar: char,
trim: bool,
) -> Option<Vec<String>> {
let mut matches = Vec::new();
let mut i = 0;
let max = rawText.len();
while i < max {
i = if let Some(loc) = rawText[i..].find(prefix) {
loc + i
} else {
break;
};
i += prefix.chars().count(); let start = i; let mut more = true;
while more {
if let Some(next_index) = rawText[i..].find(endChar) {
i += next_index;
} else {
i = rawText.chars().count();
more = false;
continue;
}
if countPrecedingBackslashes(rawText, i) % 2 != 0 {
i += 1;
} else {
let mut element = unescapeBackslash(&rawText[start..i]);
if trim {
element = element.trim().to_owned();
}
if !element.is_empty() {
matches.push(element);
}
i += 1;
more = false;
}
}
}
if matches.is_empty() {
return None;
}
Some(matches)
}
pub fn countPrecedingBackslashes(s: &str, pos: usize) -> u32 {
let mut count = 0;
for i in (0..pos).rev() {
if s.chars().nth(i).unwrap() == '\\' {
count += 1;
} else {
break;
}
}
count
}
pub fn matchSinglePrefixedField(
prefix: &str,
rawText: &str,
endChar: char,
trim: bool,
) -> Option<String> {
let matches = matchPrefixedField(prefix, rawText, endChar, trim);
matches.map(|m| m[0].clone())
}
pub fn match_docomo_prefixed_field(prefix: &str, raw_text: &str) -> Option<Vec<String>> {
matchPrefixedField(prefix, raw_text, ';', true)
}
pub fn match_single_docomo_prefixed_field(
prefix: &str,
raw_text: &str,
trim: bool,
) -> Option<String> {
matchSinglePrefixedField(prefix, raw_text, ';', trim)
}
#[cfg(test)]
mod tests {
use crate::{
client::result::{
OtherParsedResult, ParsedClientResult, ParsedRXingResult, TextParsedRXingResult,
},
RXingResult,
};
use super::parse_result_with_parser;
#[test]
fn test_single_parser() {
let result: RXingResult = RXingResult::new(
"text",
vec![12, 23, 54, 23],
Vec::new(),
crate::BarcodeFormat::EAN_13,
);
let p_res = parse_result_with_parser(&result, |_| {
Some(ParsedClientResult::TextResult(TextParsedRXingResult::new(
String::from("parsed with parser"),
String::from("en/us"),
)))
})
.unwrap();
assert_eq!(p_res.to_string(), "parsed with parser");
}
#[test]
fn test_other_parser() {
let result: RXingResult = RXingResult::new(
"text",
vec![12, 23, 54, 23],
Vec::new(),
crate::BarcodeFormat::EAN_13,
);
let p_res = parse_result_with_parser(&result, |v| {
Some(ParsedClientResult::Other(OtherParsedResult::new(Box::new(
v.getRawBytes().clone(),
))))
})
.unwrap();
assert_eq!(p_res.getDisplayRXingResult(), "Any { .. }");
if let ParsedClientResult::Other(opr) = p_res {
if let Some(d) = opr.get_data().downcast_ref::<Vec<u8>>() {
assert_eq!(d, result.getRawBytes());
} else {
panic!("did not get vec<u8>");
}
} else {
panic!("did not get ParsedClientResult::Other");
}
}
}