/* * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // package com.google.zxing.client.result; // import com.google.zxing.RXingResult; // import java.io.UnsupportedEncodingException; // import java.net.URLDecoder; // import java.util.ArrayList; // import java.util.HashMap; // import java.util.List; // import java.util.Map; // import java.util.regex.Pattern; 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, }; /** *

Abstract class representing the result of decoding a barcode, as more than * a String -- as some type of structured data. This might be a subclass which represents * a URL, or an e-mail address. {@link #parseRXingResult(RXingResult)} will turn a raw * decoded string into the most appropriate type of structured representation.

* *

Thanks to Jeff Griffin for proposing rewrite of these classes that relies less * on exception-based mechanisms during parsing.

* * @author Sean Owen */ // pub trait RXingResultParser { // // const PARSERS: [&'static str; 20] = [ // // "BookmarkDoCoMoRXingResultParser", // // "AddressBookDoCoMoRXingResultParser", // // "EmailDoCoMoRXingResultParser", // // "AddressBookAURXingResultParser", // // "VCardRXingResultParser", // // "BizcardRXingResultParser", // // "VEventRXingResultParser", // // "EmailAddressRXingResultParser", // // "SMTPRXingResultParser", // // "TelRXingResultParser", // // "SMSMMSRXingResultParser", // // "SMSTOMMSTORXingResultParser", // // "GeoRXingResultParser", // // "WifiRXingResultParser", // // "URLTORXingResultParser", // // "URIRXingResultParser", // // "ISBNRXingResultParser", // // "ProductRXingResultParser", // // "ExpandedProductRXingResultParser", // // "VINRXingResultParser", // // ]; // /** // * Attempts to parse the raw {@link RXingResult}'s contents as a particular type // * of information (email, URL, etc.) and return a {@link ParsedRXingResult} encapsulating // * the result of parsing. // * // * @param theRXingResult the raw {@link RXingResult} to parse // * @return {@link ParsedRXingResult} encapsulating the parsing result // */ // fn parse(&self, theRXingResult: &RXingResult) -> Option; // } pub type ParserFunction = dyn Fn(&RXingResult) -> Option; static DIGITS: Lazy = Lazy::new(|| Regex::new("\\d+").unwrap()); // const DIGITS: &'static str = "\\d+"; //= Pattern.compile("\\d+"); const AMPERSAND: &str = "&"; // private static final Pattern AMPERSAND = Pattern.compile("&"); const EQUALS: &str = "="; //private static final Pattern EQUALS = Pattern.compile("="); const BYTE_ORDER_MARK: &str = "\u{feff}"; //private static final String BYTE_ORDER_MARK = "\ufeff"; // const EMPTY_STR_ARRAY: &'static str = ""; pub fn getMassagedText(result: &RXingResult) -> String { result .getText() .trim_start_matches(BYTE_ORDER_MARK) .to_owned() // if text.starts_with(BYTE_ORDER_MARK) { // text = &text[1..]; // } // text.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 Option>( the_rxing_result: &RXingResult, parser: F, ) -> Option { 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; } } // ParsedRXingResult result = parser.parse(theRXingResult); // if (result != null) { // return result; // } // } 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 { // for (String s : value) { if !s.is_empty() { if !result.is_empty() { result.push('\n'); } result.push_str(s); } } } #[inline(always)] pub fn maybeWrap(value: Option) -> Option> { 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::(); 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 ('0'..='9').contains(&c) { 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(); //&value[offset..max]; 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> { let paramStart = uri.find('?'); paramStart?; let mut result = HashMap::with_capacity(3); let paramStart = paramStart.unwrap_or(0); let sub_str = &uri[paramStart + 1..]; // This is likely ok because we're looking for a specific single byte charaacter let list = sub_str.split(AMPERSAND); for keyValue in list { appendKeyValue(keyValue, &mut result); } Some(result) } pub fn appendKeyValue(keyValue: &str, result: &mut HashMap) { let keyValueTokens = keyValue.split(EQUALS); //Self::EQUALS.split(keyValue, 2); 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); } // if keyValueTokens.len() == 2 { // let key = keyValueTokens[0]; // let value = keyValueTokens[1]; // try { // value = Self::urlDecode(value); // result.put(key, value); // } catch (IllegalArgumentException iae) { // // continue; invalid data such as an escape like %0t // } // } } pub fn urlDecode(encoded: &str) -> Result { if let Ok(decoded) = decode(encoded) { Ok(decoded.to_string()) } else { Err(Exceptions::IllegalStateException(Some(String::from( "UnsupportedEncodingException", )))) } } pub fn matchPrefixedField( prefix: &str, rawText: &str, endChar: char, trim: bool, ) -> Option> { 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 = rawText.indexOf(prefix, i); // if (i < 0) { // break; // } i += prefix.chars().count(); // Skip past this prefix we found to start let start = i; // Found the start of a match here let mut more = true; while more { if let Some(next_index) = rawText[i..].find(endChar) { i += next_index; } else { // No terminating end character? uh, done. Set i such that loop terminates and break i = rawText.chars().count(); more = false; continue; } if countPrecedingBackslashes(rawText, i) % 2 != 0 { // semicolon was escaped (odd count of preceding backslashes) so continue i += 1; } else { // found a match 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; } // i = rawText.indexOf(endChar, i); // if i < 0 { // // No terminating end character? uh, done. Set i such that loop terminates and break // i = rawText.len(); // more = false; // } else if countPrecedingBackslashes(rawText, i) % 2 != 0 { // // semicolon was escaped (odd count of preceding backslashes) so continue // i+=1; // } else { // // found a match // let element = unescapeBackslash(&rawText[start..start+i]); // if (trim) { // element = element.trim(); // } // if (!element.isEmpty()) { // matches.add(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() { // for (int i = pos - 1; i >= 0; i--) { if s.chars().nth(i).unwrap() == '\\' { count += 1; } else { break; } } count } pub fn matchSinglePrefixedField( prefix: &str, rawText: &str, endChar: char, trim: bool, ) -> Option { let matches = matchPrefixedField(prefix, rawText, endChar, trim); matches.map(|m| m[0].clone()) // return matches == null ? null : matches[0]; } pub fn match_docomo_prefixed_field(prefix: &str, raw_text: &str) -> Option> { matchPrefixedField(prefix, raw_text, ';', true) } pub fn match_single_docomo_prefixed_field( prefix: &str, raw_text: &str, trim: bool, ) -> Option { 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::>() { assert_eq!(d, result.getRawBytes()); } else { panic!("did not get vec"); } } else { panic!("did not get ParsedClientResult::Other"); } } }