diff --git a/Cargo.toml b/Cargo.toml index a399e55..49ee2f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,10 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -regex = "1" +regex = "1.6" encoding = "0.2" rand = "0.8.5" +urlencoding = "2.1.2" image = {version = "0.24.3", optional = true} imageproc = {version = "0.23.0", optional = true} diff --git a/src/client/mod.rs b/src/client/mod.rs new file mode 100644 index 0000000..a1b8547 --- /dev/null +++ b/src/client/mod.rs @@ -0,0 +1 @@ +mod result; \ No newline at end of file diff --git a/src/client/result/ParsedResult.java b/src/client/result/ParsedResult.rs similarity index 60% rename from src/client/result/ParsedResult.java rename to src/client/result/ParsedResult.rs index 6f56dfb..97477d1 100644 --- a/src/client/result/ParsedResult.java +++ b/src/client/result/ParsedResult.rs @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.google.zxing.client.result; +// package com.google.zxing.client.result; + +use super::ParsedRXingResultType; /** *

Abstract class representing the result of decoding a barcode, as more than @@ -27,39 +29,33 @@ package com.google.zxing.client.result; * * @author Sean Owen */ -public abstract class ParsedRXingResult { +pub trait ParsedRXingResult { - private final ParsedRXingResultType type; + // private final ParsedRXingResultType type; - protected ParsedRXingResult(ParsedRXingResultType type) { - this.type = type; - } + // protected ParsedRXingResult(ParsedRXingResultType type) { + // this.type = type; + // } - public final ParsedRXingResultType getType() { - return type; - } + fn getType(&self) -> ParsedRXingResultType; - public abstract String getDisplayRXingResult(); + fn getDisplayRXingResult(&self) -> String; - @Override - public final String toString() { - return getDisplayRXingResult(); - } - - public static void maybeAppend(String value, StringBuilder result) { - if (value != null && !value.isEmpty()) { + fn maybe_append(&self, value :&str, result:&mut String) { + if !value.is_empty() { // Don't add a newline before the first value - if (result.length() > 0) { - result.append('\n'); + if result.len() > 0 { + result.push('\n'); } - result.append(value); + result.push_str(value); } } - public static void maybeAppend(String[] values, StringBuilder result) { - if (values != null) { - for (String value : values) { - maybeAppend(value, result); + fn maybe_append_multiple(&self, values:&[&str], result:&mut String) { + if !values.is_empty() { + for value in values { + // for (String value : values) { + self.maybe_append(value, result); } } } diff --git a/src/client/result/ParsedResultType.java b/src/client/result/ParsedResultType.rs similarity index 91% rename from src/client/result/ParsedResultType.java rename to src/client/result/ParsedResultType.rs index 7fba320..53c9f87 100644 --- a/src/client/result/ParsedResultType.java +++ b/src/client/result/ParsedResultType.rs @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.zxing.client.result; +// package com.google.zxing.client.result; /** * Represents the type of data encoded by a barcode -- from plain text, to a @@ -22,7 +22,7 @@ package com.google.zxing.client.result; * * @author Sean Owen */ -public enum ParsedRXingResultType { +pub enum ParsedRXingResultType { ADDRESSBOOK, EMAIL_ADDRESS, diff --git a/src/client/result/ResultParser.java b/src/client/result/ResultParser.java deleted file mode 100644 index d755d0c..0000000 --- a/src/client/result/ResultParser.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 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; - -/** - *

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 - */ -public abstract class RXingResultParser { - - private static final RXingResultParser[] PARSERS = { - new BookmarkDoCoMoRXingResultParser(), - new AddressBookDoCoMoRXingResultParser(), - new EmailDoCoMoRXingResultParser(), - new AddressBookAURXingResultParser(), - new VCardRXingResultParser(), - new BizcardRXingResultParser(), - new VEventRXingResultParser(), - new EmailAddressRXingResultParser(), - new SMTPRXingResultParser(), - new TelRXingResultParser(), - new SMSMMSRXingResultParser(), - new SMSTOMMSTORXingResultParser(), - new GeoRXingResultParser(), - new WifiRXingResultParser(), - new URLTORXingResultParser(), - new URIRXingResultParser(), - new ISBNRXingResultParser(), - new ProductRXingResultParser(), - new ExpandedProductRXingResultParser(), - new VINRXingResultParser(), - }; - - private static final Pattern DIGITS = Pattern.compile("\\d+"); - private static final Pattern AMPERSAND = Pattern.compile("&"); - private static final Pattern EQUALS = Pattern.compile("="); - private static final String BYTE_ORDER_MARK = "\ufeff"; - - static final String[] EMPTY_STR_ARRAY = new String[0]; - - /** - * 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 - */ - public abstract ParsedRXingResult parse(RXingResult theRXingResult); - - protected static String getMassagedText(RXingResult result) { - String text = result.getText(); - if (text.startsWith(BYTE_ORDER_MARK)) { - text = text.substring(1); - } - return text; - } - - public static ParsedRXingResult parseRXingResult(RXingResult theRXingResult) { - for (RXingResultParser parser : PARSERS) { - ParsedRXingResult result = parser.parse(theRXingResult); - if (result != null) { - return result; - } - } - return new TextParsedRXingResult(theRXingResult.getText(), null); - } - - protected static void maybeAppend(String value, StringBuilder result) { - if (value != null) { - result.append('\n'); - result.append(value); - } - } - - protected static void maybeAppend(String[] value, StringBuilder result) { - if (value != null) { - for (String s : value) { - result.append('\n'); - result.append(s); - } - } - } - - protected static String[] maybeWrap(String value) { - return value == null ? null : new String[] { value }; - } - - protected static String unescapeBackslash(String escaped) { - int backslash = escaped.indexOf('\\'); - if (backslash < 0) { - return escaped; - } - int max = escaped.length(); - StringBuilder unescaped = new StringBuilder(max - 1); - unescaped.append(escaped.toCharArray(), 0, backslash); - boolean nextIsEscaped = false; - for (int i = backslash; i < max; i++) { - char c = escaped.charAt(i); - if (nextIsEscaped || c != '\\') { - unescaped.append(c); - nextIsEscaped = false; - } else { - nextIsEscaped = true; - } - } - return unescaped.toString(); - } - - protected static int parseHexDigit(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'a' && c <= 'f') { - return 10 + (c - 'a'); - } - if (c >= 'A' && c <= 'F') { - return 10 + (c - 'A'); - } - return -1; - } - - protected static boolean isStringOfDigits(CharSequence value, int length) { - return value != null && length > 0 && length == value.length() && DIGITS.matcher(value).matches(); - } - - protected static boolean isSubstringOfDigits(CharSequence value, int offset, int length) { - if (value == null || length <= 0) { - return false; - } - int max = offset + length; - return value.length() >= max && DIGITS.matcher(value.subSequence(offset, max)).matches(); - } - - static Map parseNameValuePairs(String uri) { - int paramStart = uri.indexOf('?'); - if (paramStart < 0) { - return null; - } - Map result = new HashMap<>(3); - for (String keyValue : AMPERSAND.split(uri.substring(paramStart + 1))) { - appendKeyValue(keyValue, result); - } - return result; - } - - private static void appendKeyValue(CharSequence keyValue, Map result) { - String[] keyValueTokens = EQUALS.split(keyValue, 2); - if (keyValueTokens.length == 2) { - String key = keyValueTokens[0]; - String value = keyValueTokens[1]; - try { - value = urlDecode(value); - result.put(key, value); - } catch (IllegalArgumentException iae) { - // continue; invalid data such as an escape like %0t - } - } - } - - static String urlDecode(String encoded) { - try { - return URLDecoder.decode(encoded, "UTF-8"); - } catch (UnsupportedEncodingException uee) { - throw new IllegalStateException(uee); // can't happen - } - } - - static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) { - List matches = null; - int i = 0; - int max = rawText.length(); - while (i < max) { - i = rawText.indexOf(prefix, i); - if (i < 0) { - break; - } - i += prefix.length(); // Skip past this prefix we found to start - int start = i; // Found the start of a match here - boolean more = true; - while (more) { - i = rawText.indexOf(endChar, i); - if (i < 0) { - // No terminating end character? uh, done. Set i such that loop terminates and break - i = rawText.length(); - more = false; - } else if (countPrecedingBackslashes(rawText, i) % 2 != 0) { - // semicolon was escaped (odd count of preceding backslashes) so continue - i++; - } else { - // found a match - if (matches == null) { - matches = new ArrayList<>(3); // lazy init - } - String element = unescapeBackslash(rawText.substring(start, i)); - if (trim) { - element = element.trim(); - } - if (!element.isEmpty()) { - matches.add(element); - } - i++; - more = false; - } - } - } - if (matches == null || matches.isEmpty()) { - return null; - } - return matches.toArray(EMPTY_STR_ARRAY); - } - - private static int countPrecedingBackslashes(CharSequence s, int pos) { - int count = 0; - for (int i = pos - 1; i >= 0; i--) { - if (s.charAt(i) == '\\') { - count++; - } else { - break; - } - } - return count; - } - - static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) { - String[] matches = matchPrefixedField(prefix, rawText, endChar, trim); - return matches == null ? null : matches[0]; - } - -} diff --git a/src/client/result/ResultParser.rs b/src/client/result/ResultParser.rs new file mode 100644 index 0000000..6b5bea2 --- /dev/null +++ b/src/client/result/ResultParser.rs @@ -0,0 +1,345 @@ +/* + * 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 crate::{exceptions::Exceptions, RXingResult}; + +use super::ParsedRXingResult; + +/** + *

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 { + // private static final RXingResultParser[] PARSERS = { + // new BookmarkDoCoMoRXingResultParser(), + // new AddressBookDoCoMoRXingResultParser(), + // new EmailDoCoMoRXingResultParser(), + // new AddressBookAURXingResultParser(), + // new VCardRXingResultParser(), + // new BizcardRXingResultParser(), + // new VEventRXingResultParser(), + // new EmailAddressRXingResultParser(), + // new SMTPRXingResultParser(), + // new TelRXingResultParser(), + // new SMSMMSRXingResultParser(), + // new SMSTOMMSTORXingResultParser(), + // new GeoRXingResultParser(), + // new WifiRXingResultParser(), + // new URLTORXingResultParser(), + // new URIRXingResultParser(), + // new ISBNRXingResultParser(), + // new ProductRXingResultParser(), + // new ExpandedProductRXingResultParser(), + // new VINRXingResultParser(), + // }; + + const DIGITS: &'static str = "\\d+"; //= Pattern.compile("\\d+"); + const AMPERSAND: &'static str = "&"; // private static final Pattern AMPERSAND = Pattern.compile("&"); + const EQUALS: &'static str = "="; //private static final Pattern EQUALS = Pattern.compile("="); + const BYTE_ORDER_MARK: &'static str = "\u{feff}"; //private static final String BYTE_ORDER_MARK = "\ufeff"; + + const EMPTY_STR_ARRAY: &'static str = ""; + + 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) -> Self; + + fn getMassagedText(result: &RXingResult) -> String { + let mut text = result.getText().clone(); + if text.starts_with(Self::BYTE_ORDER_MARK) { + text = text[1..].to_owned(); + } + return text; + } + + fn parseRXingResult(theRXingResult: &RXingResult) -> Box { + for parser in Self::PARSERS { + // for (RXingResultParser parser : PARSERS) { + todo!("implement multiple parsers"); + } + // ParsedRXingResult result = parser.parse(theRXingResult); + // if (result != null) { + // return result; + // } + // } + // return new TextParsedRXingResult(theRXingResult.getText(), null); + unimplemented!(); + } + + fn maybe_ppend_string(value: &str, result: &mut String) { + if !value.is_empty() { + result.push('\n'); + result.push_str(value); + } + } + + fn maybe_append_multiple(value: &[&str], result: &mut String) { + if !value.is_empty() { + for s in value { + // for (String s : value) { + result.push('\n'); + result.push_str(s); + } + } + } + + fn maybeWrap(value: Option<&str>) -> Option> { + if value.is_none() { + None + } else { + Some(vec![value.unwrap().to_owned()]) + } + } + + fn unescapeBackslash(escaped: &str) -> String { + let backslash = escaped.find('\\'); + if backslash.is_none() { + return escaped.to_owned(); + } + let max = escaped.len(); + let mut unescaped = String::with_capacity(max - 1); + let backslash = backslash.unwrap_or(0); + unescaped.push_str(&escaped[0..backslash]); + let mut nextIsEscaped = false; + for i in backslash..max { + // for (int i = backslash; i < max; i++) { + let c = escaped.chars().nth(i).unwrap(); + if nextIsEscaped || c != '\\' { + unescaped.push(c); + nextIsEscaped = false; + } else { + nextIsEscaped = true; + } + } + + unescaped + } + + fn parseHexDigit(c: char) -> i32 { + if c >= '0' && c <= '9' { + return (c as u8 - '0' as u8) as i32; + } + if c >= 'a' && c <= 'f' { + return 10 + (c as u8 - 'a' as u8) as i32; + } + if c >= 'A' && c <= 'F' { + return 10 + (c as u8 - 'A' as u8) as i32; + } + return -1; + } + + fn isStringOfDigits(value: &str, length: usize) -> bool { + let matcher = Regex::new(Self::DIGITS).unwrap(); + !value.is_empty() && length > 0 && length == value.len() && matcher.is_match(value) + } + + fn isSubstringOfDigits(value: &str, offset: i32, length: usize) -> bool { + if value.is_empty() || length <= 0 { + return false; + } + let max = offset as usize + length; + + let matcher = Regex::new(Self::DIGITS).unwrap(); + let sub_seq = &value[offset as usize..offset as usize + max]; + + value.len() >= max && matcher.is_match(sub_seq) + } + + fn parseNameValuePairs(uri: &str) -> Option> { + let paramStart = uri.find('?'); + if paramStart.is_none() { + return None; + } + let mut result = HashMap::with_capacity(3); + let paramStart = paramStart.unwrap_or(0); + + let sub_str = &uri[paramStart + 1..]; + let list = sub_str.split(Self::AMPERSAND); + for keyValue in list { + Self::appendKeyValue(keyValue, &mut result); + } + + // for keyValue in Self::AMPERSAND.split(uri[paramStart + 1..]) { + // // for (String keyValue : AMPERSAND.split(uri.substring(paramStart + 1))) { + // Self::appendKeyValue(keyValue, &mut result); + // } + Some(result) + } + + fn appendKeyValue(keyValue: &str, result: &mut HashMap) { + let keyValueTokens = keyValue.split(Self::EQUALS); //Self::EQUALS.split(keyValue, 2); + + let kvp: Vec<&str> = keyValueTokens.take(2).collect(); + if let [key, value] = kvp[..] { + let p_value = Self::urlDecode(value).unwrap_or("".to_owned()); + 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 + // } + // } + } + + fn urlDecode(encoded: &str) -> Result { + if let Ok(decoded) = decode(encoded) { + Ok(decoded.to_string()) + } else { + Err(Exceptions::IllegalStateException( + "UnsupportedEncodingException".to_owned(), + )) + } + } + + fn matchPrefixedField( + prefix: &str, + rawText: &str, + endChar: char, + trim: bool, + ) -> Option> { + let mut matches: Vec = Vec::new(); + let mut i = 0; + let max = rawText.len(); + while i < max { + i = if let Some(loc) = rawText[i..].find(prefix) { + loc //rawText.indexOf(prefix, i); + } else { + break; + }; + // if i < 0 { + // break; + // } + i += prefix.len(); // Skip past this prefix we found to start + let start = i; // Found the start of a match here + let mut more = true; + while more { + let i = if let Some(loc) = rawText[i..].find(endChar) { + if Self::countPrecedingBackslashes(rawText, i) % 2 != 0 { + // semicolon was escaped (odd count of preceding backslashes) so continue + i + 1 + } else { + // found a match + let mut element = Self::unescapeBackslash(&rawText[start..i + start]); + if trim { + element = element.to_string().trim().to_owned(); + } + if !element.is_empty() { + matches.push(element); + } + more = false; + i + 1 + } + } else { + // No terminating end character? uh, done. Set i such that loop terminates and break + //i = rawText.len(); + more = false; + rawText.len() + }; + } + } + if matches.is_empty() { + return None; + } + Some(matches) + } + + fn countPrecedingBackslashes(s: &str, pos: usize) -> u32 { + let mut count = 0; + for i in (0..pos - 1).rev() { + // for (int i = pos - 1; i >= 0; i--) { + if s.chars().nth(i).unwrap() == '\\' { + count += 1; + } else { + break; + } + } + return count; + } + + fn matchSinglePrefixedField( + prefix: &str, + rawText: &str, + endChar: char, + trim: bool, + ) -> Option { + let matches = Self::matchPrefixedField(prefix, rawText, endChar, trim); + if let Some(m) = matches { + Some(m[0].clone()) + } else { + None + } + // return matches == null ? null : matches[0]; + } +} diff --git a/src/client/result/mod.rs b/src/client/result/mod.rs new file mode 100644 index 0000000..b35a02f --- /dev/null +++ b/src/client/result/mod.rs @@ -0,0 +1,7 @@ +mod ParsedResult; +pub use ParsedResult::*; +mod ResultParser; + +mod ParsedResultType; +pub use ParsedResultType::*; +pub use ResultParser::*; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 54f096f..55d7ccd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod common; mod exceptions; +mod client; #[cfg(feature="image")] mod BufferedImageLuminanceSource;