use crate::XRingResult; use regex::Regex; // ParsedResultType.java /** * Represents the type& of data encoded by a barco&de -- from plain text, to a * URI, to an e-mail address, etc. * * @author Sean Owen */ pub enum ParsedResultType { ADDRESSBOOK, EMAIL_ADDRESS, PRODUCT, URI, TEXT, GEO, TEL, SMS, CALENDAR, WIFI, ISBN, VIN, } // ParsedResult.java /** *

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 ResultParser#parseResult(com.google.zxing.Result)} 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 */ trait ParsedResult { pub fn new(parsed_type: &ParsedResultType) -> Self; /*{ let .type = type; }*/ pub fn get_type(&self) -> ParsedResultType; /* { return self.type; }*/ pub fn get_display_result(&self) -> String; pub fn to_string(&self) -> String { return self.get_display_result(); } pub fn maybe_append(value: &String, result: &String) { if value != null && !value.is_empty() { // Don't add a newline before the first value if result.length() > 0 { result.append('\n'); } result.append(&value); } } pub fn maybe_append(values: &Vec, result: &String) { if values != null { for value in values { ::maybe_append(&value, &result); } } } } // ResultParser.java /** *

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 #parseResult(Result)} 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 */ const PARSERS: vec![Vec; 20] = vec![ BookmarkDoCoMoResultParser::new(), AddressBookDoCoMoResultParser::new(), EmailDoCoMoResultParser::new(), AddressBookAUResultParser::new(), VCardResultParser::new(), BizcardResultParser::new(), VEventResultParser::new(), EmailAddressResultParser::new(), SMTPResultParser::new(), TelResultParser::new(), SMSMMSResultParser::new(), SMSTOMMSTOResultParser::new(), GeoResultParser::new(), WifiResultParser::new(), URLTOResultParser::new(), URIResultParser::new(), ISBNResultParser::new(), ProductResultParser::new(), ExpandedProductResultParser::new(), VINResultParser::new(), ]; const DIGITS: Regex = Regex::new("\\d+"); const AMPERSAND: Regex = Regex::new("&"); const EQUALS: Regex = Regex::new("="); const BYTE_ORDER_MARK: &'static str = ""; const EMPTY_STR_ARRAY: [Option; 0] = [None; 0]; trait ResultParser { /** * Attempts to parse the raw {@link Result}'s contents as a particular type * of information (email, URL, etc.) and return a {@link ParsedResult} encapsulating * the result of parsing. * * @param theResult the raw {@link Result} to parse * @return {@link ParsedResult} encapsulating the parsing result */ pub fn parse(&self, the_result: &Result) -> ParsedResult; pub fn get_massaged_text(result: &Result) -> String { let mut text: String = result.get_text(); if text.starts_with(&BYTE_ORDER_MARK) { text = text.substring(1); } return text; } pub fn parse_result(the_result: &Result) -> ParsedResult { for parser in PARSERS { let result: ParsedResult = parser.parse(the_result); if result != null { return result; } } return TextParsedResult::new(&the_result.get_text(), null); } pub fn maybe_append(value: &String, result: &String) { if value != null { result.append('\n'); result.append(&value); } } pub fn maybe_append(value: &Vec, result: &String) { if value != null { for s in value { result.append('\n'); result.append(&s); } } } pub fn maybe_wrap(value: &String) -> Vec { return if value == null { null } else { vec![value] }; } pub fn unescape_backslash(escaped: &String) -> String { let backslash: i32 = escaped.index_of('\\'); if backslash < 0 { return escaped; } let max: i32 = escaped.length(); let unescaped: String = String::new(max - 1); unescaped.append(&escaped.to_char_array(), 0, backslash); let next_is_escaped: bool = false; { let mut i: i32 = backslash; while i < max { { let c: char = escaped.char_at(i); if next_is_escaped || c != '\\' { unescaped.append(c); next_is_escaped = false; } else { next_is_escaped = true; } } i += 1; } } return unescaped.to_string(); } pub fn parse_hex_digit(c: char) -> i32 { 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; } pub fn is_string_of_digits(value: &String, length: i32) -> bool { return value != null && length > 0 && length == value.length() && DIGITS.is_match(&value); } pub fn is_substring_of_digits(value: &String, offset: i32, length: i32) -> bool { if value == null || length <= 0 { return false; } let max: i32 = offset + length; return value.length() >= max && DIGITS.is_match(&value.sub_sequence(offset, max)); } fn parse_name_value_pairs(uri: &String) -> Map { let param_start: i32 = uri.index_of('?'); if param_start < 0 { return null; } let result: Map = HashMap::new(3); for key_value in AMPERSAND::split(&uri.substring(param_start + 1)) { ::append_key_value(&key_value, &result); } return result; } fn append_key_value(key_value: &CharSequence, result: &HasMap) { let key_value_tokens: Vec = EQUALS::split(&key_value, 2); if key_value_tokens.len() == 2 { let key: String = key_value_tokens[0]; let mut value: String = key_value_tokens[1]; value = ::url_decode(&value); result.put(&key, &value); /*let tryResult1 = 0; 'try1: loop { { value = ::url_decode(&value); result.put(&key, &value); } break 'try1 } match tryResult1 { catch ( iae: &IllegalArgumentException) { } 0 => break }*/ } } fn url_decode(encoded: &String) -> Result { return URLDecoder::decode(&encoded, "UTF-8"); /*let tryResult1 = 0; 'try1: loop { { return URLDecoder::decode(&encoded, "UTF-8"); } break 'try1 } match tryResult1 { catch ( uee: &UnsupportedEncodingException) { throw IllegalStateException::new(&uee); } 0 => break } */ } fn match_prefixed_field( prefix: &String, raw_text: &String, end_char: char, trim: bool, ) -> Vec { let mut matches: List = null; let mut i: i32 = 0; let max: i32 = raw_text.length(); while i < max { i = raw_text.index_of(&prefix, i); if i < 0 { break; } // Skip past this prefix we found to start i += prefix.length(); // Found the start of a match here let start: i32 = i; let mut more: bool = true; while more { i = raw_text.index_of(end_char, i); if i < 0 { // No terminating end character? uh, done. Set i such that loop terminates and break i = raw_text.length(); more = false; } else if ::count_preceding_backslashes(&raw_text, i) % 2 != 0 { // semicolon was escaped (odd count of preceding backslashes) so continue i += 1; } else { // found a match if matches == null { // lazy init matches = Vec::new(3); } let mut element: String = ::unescape_backslash(&raw_text.substring(start, i)); if trim { element = element.trim(); } if !element.is_empty() { matches.add(&element); } i += 1; more = false; } } } if matches == null || matches.is_empty() { return null; } return matches.to_array(&EMPTY_STR_ARRAY); } fn count_preceding_backslashes(s: &CharSequence, pos: i32) -> i32 { let mut count: i32 = 0; { let mut i: i32 = pos - 1; while i >= 0 { { if s.char_at(i) == '\\' { count += 1; } else { break; } } i -= 1; } } return count; } fn match_single_prefixed_field( prefix: &String, raw_text: &String, end_char: char, trim: bool, ) -> String { let matches: Vec = ::match_prefixed_field(&prefix, &raw_text, end_char, trim); return if matches == null { null } else { matches[0] }; } } // AbstractDoCoMoResultParser.java /** *

See * * DoCoMo's documentation about the result types represented by subclasses of this class.

* *

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

* * @author Sean Owen */ trait AbstractDoCoMoResultParser: ResultParser { fn match_do_co_mo_prefixed_field(prefix: &String, raw_text: &String) -> Vec { return match_prefixed_field(&prefix, &raw_text, ';', true); } fn match_single_do_co_mo_prefixed_field( prefix: &String, raw_text: &String, trim: bool, ) -> String { return match_single_prefixed_field(&prefix, &raw_text, ';', trim); } } // AddressBookAUResultParser.java /** * Implements KDDI AU's address book format. See * * http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html. * (Thanks to Yuzo for translating!) * * @author Sean Owen */ pub struct AddressBookAUResultParser; impl ResultParser for AddressBookAUResultParser {} impl AddressBookAUResultParser { pub fn parse(&self, result: &Result) -> AddressBookParsedResult { let raw_text: String = get_massaged_text(result); // MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF if !raw_text.contains("MEMORY") || !raw_text.contains("\r\n") { return null; } // NAME1 and NAME2 have specific uses, namely written name and pronunciation, respectively. // Therefore we treat them specially instead of as an array of names. let name: String = match_single_prefixed_field("NAME1:", &raw_text, '\r', true); let pronunciation: String = match_single_prefixed_field("NAME2:", &raw_text, '\r', true); let phone_numbers: Vec = ::match_multiple_value_prefix("TEL", &raw_text); let emails: Vec = ::match_multiple_value_prefix("MAIL", &raw_text); let note: String = match_single_prefixed_field("MEMORY:", &raw_text, '\r', false); let address: String = match_single_prefixed_field("ADD:", &raw_text, '\r', true); let addresses: Vec = if address == null { null } else { vec![address] }; return AddressBookParsedResult::new( &maybe_wrap(&name), null, Some(&pronunciation), &phone_numbers, null, &emails, null, null, Some(¬e), &addresses, null, null, null, null, null, null, ); } fn match_multiple_value_prefix(prefix: &String, raw_text: &String) -> Vec { let mut values: Vec = null; // For now, always 3, and always trim { let mut i: i32 = 1; while i <= 3 { { let value: String = match_single_prefixed_field( format!("{}{}:", prefix, i), &raw_text, '\r', true, ); if value == null { break; } if values == null { // lazy init values = Vec::new(); } values.add(&value); } i += 1; } } if values == null { return null; } return values.to_array(EMPTY_STR_ARRAY); } } // AddressBookDoCoMoResultParser.java /** * Implements the "MECARD" address book entry format. * * Supported keys: N, SOUND, TEL, EMAIL, NOTE, ADR, BDAY, URL, plus ORG * Unsupported keys: TEL-AV, NICKNAME * * Except for TEL, multiple values for keys are also not supported; * the first one found takes precedence. * * Our understanding of the MECARD format is based on this document: * * http://www.mobicode.org.tw/files/OMIA%20Mobile%20Bar%20Code%20Standard%20v3.2.1.doc * * @author Sean Owen */ pub struct AddressBookDoCoMoResultParser; impl AbstractDoCoMoResultParser for AddressBookDoCoMoResultParser {} impl AddressBookDoCoMoResultParser { pub fn parse(&self, result: &Result) -> AddressBookParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("MECARD:") { return null; } let raw_name: Vec = match_do_co_mo_prefixed_field("N:", &raw_text); if raw_name == null { return null; } let name: String = ::parse_name(raw_name[0]); let pronunciation: String = match_single_do_co_mo_prefixed_field("SOUND:", &raw_text, true); let phone_numbers: Vec = match_do_co_mo_prefixed_field("TEL:", &raw_text); let emails: Vec = match_do_co_mo_prefixed_field("EMAIL:", &raw_text); let note: String = match_single_do_co_mo_prefixed_field("NOTE:", &raw_text, false); let addresses: Vec = match_do_co_mo_prefixed_field("ADR:", &raw_text); let mut birthday: String = match_single_do_co_mo_prefixed_field("BDAY:", &raw_text, true); if !is_string_of_digits(&birthday, 8) { // No reason to throw out the whole card because the birthday is formatted wrong. birthday = null; } let urls: Vec = match_do_co_mo_prefixed_field("URL:", &raw_text); // Although ORG may not be strictly legal in MECARD, it does exist in VCARD and we might as well // honor it when found in the wild. let org: String = match_single_do_co_mo_prefixed_field("ORG:", &raw_text, true); return AddressBookParsedResult::new( &maybe_wrap(&name), null, Some(&pronunciation), &phone_numbers, null, &emails, null, null, Some(¬e), &addresses, null, Some(&org), Some(&birthday), null, Some(&urls), null, ); } fn parse_name(name: &String) -> String { let comma: i32 = name.index_of(','); if comma >= 0 { // Format may be last,first; switch it around return name.substring(comma + 1) + ' ' + name.substring(0, comma); } return name; } } // AddressBookParsedResult.java /** * Represents a parsed result that encodes contact information, like that in an address book * entry. * * @author Sean Owen */ pub struct AddressBookParsedResult { //super: ParsedResult; names: Vec, nicknames: Vec, pronunciation: String, phone_numbers: Vec, phone_types: Vec, emails: Vec, email_types: Vec, instant_messenger: String, note: String, addresses: Vec, address_types: Vec, org: String, birthday: String, title: String, urls: Vec, geo: Vec, } impl ParsedResult for AddressBookParsedResult {} impl AddressBookParsedResult { pub fn new( names: &Vec, nicknames: Option<&Vec>, pronunciation: Option<&String>, phone_numbers: &Vec, phone_types: &Vec, emails: &Vec, email_types: &Vec, instant_messenger: Option<&String>, note: Option<&String>, addresses: &Vec, address_types: &Vec, org: Option<&String>, birthday: Option<&String>, title: Option<&String>, urls: Option<&Vec>, geo: Option<&Vec>, ) -> Result { if phone_numbers != null && phone_types != null && phone_numbers.len() != phone_types.len() { return Err(IllegalArgumentException::new( "Phone numbers and types lengths differ", )); } if emails != null && email_types != null && emails.len() != email_types.len() { return Err(IllegalArgumentException::new( "Emails and types lengths differ", )); } if addresses != null && address_types != null && addresses.len() != address_types.len() { return Err(IllegalArgumentException::new( "Addresses and types lengths differ", )); } Ok(Self { names: names, nicknames: nicknames, pronunciation: pronunciation, phone_numbers: phone_numbers, phone_types: phone_types, emails: emails, email_types: email_types, instant_messenger: instant_messenger, note: note, addresses: addresses, address_types: address_types, org: org, birthday: birthday, title: title, urls: urls, geo: geo, }) } pub fn get_names(&self) -> Vec { return self.names; } pub fn get_nicknames(&self) -> Vec { return self.nicknames; } /** * In Japanese, the name is written in kanji, which can have multiple readings. Therefore a hint * is often provided, called furigana, which spells the name phonetically. * * @return The pronunciation of the getNames() field, often in hiragana or katakana. */ pub fn get_pronunciation(&self) -> String { return self.pronunciation; } pub fn get_phone_numbers(&self) -> Vec { return self.phone_numbers; } /** * @return optional descriptions of the type of each phone number. It could be like "HOME", but, * there is no guaranteed or standard format. */ pub fn get_phone_types(&self) -> Vec { return self.phone_types; } pub fn get_emails(&self) -> Vec { return self.emails; } /** * @return optional descriptions of the type of each e-mail. It could be like "WORK", but, * there is no guaranteed or standard format. */ pub fn get_email_types(&self) -> Vec { return self.email_types; } pub fn get_instant_messenger(&self) -> String { return self.instant_messenger; } pub fn get_note(&self) -> String { return self.note; } pub fn get_addresses(&self) -> Vec { return self.addresses; } /** * @return optional descriptions of the type of each e-mail. It could be like "WORK", but, * there is no guaranteed or standard format. */ pub fn get_address_types(&self) -> Vec { return self.address_types; } pub fn get_title(&self) -> String { return self.title; } pub fn get_org(&self) -> String { return self.org; } pub fn get_u_r_ls(&self) -> Vec { return self.urls; } /** * @return birthday formatted as yyyyMMdd (e.g. 19780917) */ pub fn get_birthday(&self) -> String { return self.birthday; } /** * @return a location as a latitude/longitude pair */ pub fn get_geo(&self) -> Vec { return self.geo; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.names, &result); maybe_append(&self.nicknames, &result); maybe_append(&self.pronunciation, &result); maybe_append(&self.title, &result); maybe_append(&self.org, &result); maybe_append(&self.addresses, &result); maybe_append(&self.phone_numbers, &result); maybe_append(&self.emails, &result); maybe_append(&self.instant_messenger, &result); maybe_append(&self.urls, &result); maybe_append(&self.birthday, &result); maybe_append(&self.geo, &result); maybe_append(&self.note, &result); return result.to_string(); } } // BizcardResultParser.java /** * Implements the "BIZCARD" address book entry format, though this has been * largely reverse-engineered from examples observed in the wild -- still * looking for a definitive reference. * * @author Sean Owen */ pub struct BizcardResultParser; impl AbstractDoCoMoResultParser for BizcardResultParser {} impl BizcardResultParser { // Yes, we extend AbstractDoCoMoResultParser since the format is very much // like the DoCoMo MECARD format, but this is not technically one of // DoCoMo's proposed formats pub fn parse(&self, result: &Result) -> AddressBookParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("BIZCARD:") { return null; } let first_name: String = match_single_do_co_mo_prefixed_field("N:", &raw_text, true); let last_name: String = match_single_do_co_mo_prefixed_field("X:", &raw_text, true); let full_name: String = ::build_name(&first_name, &last_name); let title: String = match_single_do_co_mo_prefixed_field("T:", &raw_text, true); let org: String = match_single_do_co_mo_prefixed_field("C:", &raw_text, true); let addresses: Vec = match_do_co_mo_prefixed_field("A:", &raw_text); let phone_number1: String = match_single_do_co_mo_prefixed_field("B:", &raw_text, true); let phone_number2: String = match_single_do_co_mo_prefixed_field("M:", &raw_text, true); let phone_number3: String = match_single_do_co_mo_prefixed_field("F:", &raw_text, true); let email: String = match_single_do_co_mo_prefixed_field("E:", &raw_text, true); return AddressBookParsedResult::new( &maybe_wrap(&full_name), null, null, &::build_phone_numbers(&phone_number1, &phone_number2, &phone_number3), null, &maybe_wrap(&email), null, null, null, &addresses, null, Some(&org), null, Some(&title), null, null, ); } fn build_phone_numbers(number1: &String, number2: &String, number3: &String) -> Vec { let numbers: List = Vec::new(); if number1 != null { numbers.add(&number1); } if number2 != null { numbers.add(&number2); } if number3 != null { numbers.add(&number3); } let size: i32 = numbers.size(); if size == 0 { return null; } return numbers.to_array([None; size]); } fn build_name(first_name: &String, last_name: &String) -> String { if first_name == null { return last_name; } else { return if last_name == null { first_name } else { format!("{} {}", first_name, last_name) }; } } } // BookmarkDoCoMoResultParser.java /** * @author Sean Owen */ pub struct BookmarkDoCoMoResultParser { //super: AbstractDoCoMoResultParser; } impl AbstractDoCoMoResultParser for BookmarkDoCoMoResultParser {} impl BookmarkDoCoMoResultParser { pub fn parse(&self, result: &Result) -> URIParsedResult { let raw_text: String = result.get_text(); if !raw_text.starts_with("MEBKM:") { return null; } let title: String = match_single_do_co_mo_prefixed_field("TITLE:", &raw_text, true); let raw_uri: Vec = match_do_co_mo_prefixed_field("URL:", &raw_text); if raw_uri == null { return null; } let uri: String = raw_uri[0]; return if URIResultParser::is_basically_valid_u_r_i(&uri) { URIParsedResult::new(&uri, &title) } else { null }; } } // CalendarParsedResult.java /** * Represents a parsed result that encodes a calendar event at a certain time, optionally * with attendees and a location. * * @author Sean Owen */ const RFC2445_DURATION: Pattern = Pattern::compile("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?"); const RFC2445_DURATION_FIELD_UNITS: vec![Vec; 5] = vec![ // 1 week 7 * 24 * 60 * 60 * 1000, // 1 day 24 * 60 * 60 * 1000, // 1 hour 60 * 60 * 1000, // 1 minute 60 * 1000, // 1 second 1000, ]; const DATE_TIME: Pattern = Pattern::compile("[0-9]{8}(T[0-9]{6}Z?)?"); pub struct CalendarParsedResult { //super: ParsedResult; summary: String, start: i64, start_all_day: bool, end: i64, end_all_day: bool, location: String, organizer: String, attendees: Vec, description: String, latitude: f64, longitude: f64, } impl ParsedResult for CalendarParsedResult {} impl CalendarParsedResult { pub fn new( summary: &String, start_string: &String, end_string: &String, duration_string: &String, location: &String, organizer: &String, attendees: &Vec, description: &String, latitude: f64, longitude: f64, ) -> Result { let new_cpr: Self; new_cpr.summary = summary; let strt = CalendarParsedResult::parse_date(&start_string); if strt.is_err() { return Err(IllegalArgumentException::new(&pe.to_string())); } new_cpr.start = strt.unwrap(); if end_string == null { let duration_m_s: i64 = ::parse_duration_m_s(&duration_string); new_cpr.end = if duration_m_s < 0 { -1 } else { start + duration_m_s }; } else { let en = Self::parse_date(&end_string); if en.is_err() { return Err(IllegalArgumentException::new(&pe.to_string())); } new_cpr.end = ::parse_date(&end_string); } new_cpr.startAllDay = start_string.length() == 8; new_cpr.endAllDay = end_string != null && end_string.length() == 8; new_cpr.location = location; new_cpr.organizer = organizer; new_cpr.attendees = attendees; new_cpr.description = description; new_cpr.latitude = latitude; new_cpr.longitude = longitude; Ok(new_cpr) } pub fn get_summary(&self) -> String { return self.summary; } /** * @return start time * @deprecated use {@link #getStartTimestamp()} */ pub fn get_start(&self) -> Date { return Date::new(self.start); } /** * @return start time * @see #getEndTimestamp() */ pub fn get_start_timestamp(&self) -> i64 { return self.start; } /** * @return true if start time was specified as a whole day */ pub fn is_start_all_day(&self) -> bool { return self.start_all_day; } /** * @return event end {@link Date}, or {@code null} if event has no duration * @deprecated use {@link #getEndTimestamp()} */ pub fn get_end(&self) -> Date { return if self.end < 0 { null } else { Date::new(self.end) }; } /** * @return event end {@link Date}, or -1 if event has no duration * @see #getStartTimestamp() */ pub fn get_end_timestamp(&self) -> i64 { return self.end; } /** * @return true if end time was specified as a whole day */ pub fn is_end_all_day(&self) -> bool { return self.end_all_day; } pub fn get_location(&self) -> String { return self.location; } pub fn get_organizer(&self) -> String { return self.organizer; } pub fn get_attendees(&self) -> Vec { return self.attendees; } pub fn get_description(&self) -> String { return self.description; } pub fn get_latitude(&self) -> f64 { return self.latitude; } pub fn get_longitude(&self) -> f64 { return self.longitude; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.summary, &result); maybe_append(&::format(self.start_all_day, self.start), &result); maybe_append(&::format(self.end_all_day, self.end), &result); maybe_append(&self.location, &result); maybe_append(&self.organizer, &result); maybe_append(&self.attendees, &result); maybe_append(&self.description, &result); return result.to_string(); } /** * Parses a string as a date. RFC 2445 allows the start and end fields to be of type DATE (e.g. 20081021) * or DATE-TIME (e.g. 20081021T123000 for local time, or 20081021T123000Z for UTC). * * @param when The string to parse * @throws ParseException if not able to parse as a date */ fn parse_date(when: &String) -> Result { if !DATE_TIME::is_match(&when) { return Err(ParseException::new(&when, 0)); } if when.length() == 8 { // Show only year/month/day let format: DateFormat = SimpleDateFormat::new("yyyyMMdd", Locale::ENGLISH); // For dates without a time, for purposes of interacting with Android, the resulting timestamp // needs to be midnight of that day in GMT. See: // http://code.google.com/p/android/issues/detail?id=8330 format.set_time_zone(&TimeZone::get_time_zone("GMT")); return Ok(format.parse(&when).get_time()); } // The when string can be local time, or UTC if it ends with a Z if when.length() == 16 && when.char_at(15) == 'Z' { let mut milliseconds: i64 = ::parse_date_time_string(&when.substring(0, 15)); let calendar: Calendar = GregorianCalendar::new(); // Account for time zone difference milliseconds += calendar.get(Calendar::ZONE_OFFSET); // Might need to correct for daylight savings time, but use target time since // now might be in DST but not then, or vice versa calendar.set_time(Date::new(milliseconds)); return Ok(milliseconds + calendar.get(Calendar::DST_OFFSET)); } return Ok(::parse_date_time_string(&when)); } fn format(all_day: bool, date: i64) -> String { if date < 0 { return null; } let format: DateFormat = if all_day { DateFormat::get_date_instance(DateFormat::MEDIUM) } else { DateFormat::get_date_time_instance(DateFormat::MEDIUM, DateFormat::MEDIUM) }; return format.format(date); } fn parse_duration_m_s(duration_string: &CharSequence) -> i64 { if duration_string == null { return -1; } let m: Matcher = RFC2445_DURATION::matcher(&duration_string); if !m.matches() { return -1; } let duration_m_s: i64 = 0; { let mut i: i32 = 0; while i < RFC2445_DURATION_FIELD_UNITS.len() { { let field_value: String = m.group(i + 1); if field_value != null { duration_m_s += RFC2445_DURATION_FIELD_UNITS[i] * Integer::parse_int(&field_value); } } i += 1; } } return duration_m_s; } fn parse_date_time_string(date_time_string: &String) -> Result { let format: DateFormat = SimpleDateFormat::new("yyyyMMdd'T'HHmmss", Locale::ENGLISH); return Ok(format.parse(&date_time_string).get_time()); } } // EmailAddressParsedResult.java /** * Represents a parsed result that encodes an email message including recipients, subject * and body text. * * @author Sean Owen */ pub struct EmailAddressParsedResult { //super: ParsedResult; tos: Vec, ccs: Vec, bccs: Vec, subject: String, body: String, } impl ParsedResult for EmailAddressParsedResult {} impl EmailAddressParsedResult { fn new( tos: &Vec, ccs: Option<&Vec>, bccs: Option<&Vec>, subject: Option<&String>, body: Option<&String>, ) -> Self { //super(ParsedResultType::EMAIL_ADDRESS); Self { tos: tos, ccs: ccs, bccs: bccs, subject: subject, body: body, } } /** * @return first elements of {@link #getTos()} or {@code null} if none * @deprecated use {@link #getTos()} */ pub fn get_email_address(&self) -> String { return if self.tos == null || self.tos.len() == 0 { null } else { self.tos[0] }; } pub fn get_tos(&self) -> Vec { return self.tos; } pub fn get_c_cs(&self) -> Vec { return self.ccs; } pub fn get_b_c_cs(&self) -> Vec { return self.bccs; } pub fn get_subject(&self) -> String { return self.subject; } pub fn get_body(&self) -> String { return self.body; } /** * @return "mailto:" * @deprecated without replacement */ pub fn get_mailto_u_r_i(&self) -> &'static str { return "mailto:"; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.tos, &result); maybe_append(&self.ccs, &result); maybe_append(&self.bccs, &result); maybe_append(&self.subject, &result); maybe_append(&self.body, &result); return result.to_string(); } } // EmailAddressResultParser.java /** * Represents a result that encodes an e-mail address, either as a plain address * like "joe@example.org" or a mailto: URL like "mailto:joe@example.org". * * @author Sean Owen */ const COMMA: Regex = Regex::new(","); pub struct EmailAddressResultParser { //super: ResultParser; } impl ResultParser for EmailAddressResultParser {} impl EmailAddressResultParser { pub fn parse(&self, result: &Result) -> EmailAddressParsedResult { let raw_text: String = get_massaged_text(result); if raw_text.starts_with("mailto:") || raw_text.starts_with("MAILTO:") { // If it starts with mailto:, assume it is definitely trying to be an email address let host_email: String = raw_text.substring(7); let query_start: i32 = host_email.index_of('?'); if query_start >= 0 { host_email = host_email.substring(0, query_start); } let ud = url_decode(&host_email); if ud.is_err() { return None; } host_email = ud; let mut tos: Vec = null; if !host_email.is_empty() { tos = COMMA::split(&host_email); } let name_values: Map = parse_name_value_pairs(&raw_text); let mut ccs: Vec = null; let mut bccs: Vec = null; let mut subject: String = null; let mut body: String = null; if name_values != null { if tos == null { let tos_string: String = name_values.get("to"); if tos_string != null { tos = COMMA::split(&tos_string); } } let cc_string: String = name_values.get("cc"); if cc_string != null { ccs = COMMA::split(&cc_string); } let bcc_string: String = name_values.get("bcc"); if bcc_string != null { bccs = COMMA::split(&bcc_string); } subject = name_values.get("subject"); body = name_values.get("body"); } return EmailAddressParsedResult::new( &tos, Some(&ccs), Some(&bccs), Some(&subject), Some(&body), ); } else { if !EmailDoCoMoResultParser::is_basically_valid_email_address(&raw_text) { return null; } return EmailAddressParsedResult::new(&raw_text, None, None, None, None); } } } // EmailDoCoMoResultParser.java /** * Implements the "MATMSG" email message entry format. * * Supported keys: TO, SUB, BODY * * @author Sean Owen */ const ATEXT_ALPHANUMERIC: Regex = Regex::new("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+"); pub struct EmailDoCoMoResultParser { //super: AbstractDoCoMoResultParser; } impl AbstractDoCoMoResultParser for EmailDoCoMoResultParser {} impl EmailDoCoMoResultParser { pub fn parse(&self, result: &Result) -> EmailAddressParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("MATMSG:") { return null; } let tos: Vec = match_do_co_mo_prefixed_field("TO:", &raw_text); if tos == null { return null; } for to in tos { if !::is_basically_valid_email_address(&to) { return null; } } let subject: String = match_single_do_co_mo_prefixed_field("SUB:", &raw_text, false); let body: String = match_single_do_co_mo_prefixed_field("BODY:", &raw_text, false); return EmailAddressParsedResult::new(&tos, null, null, Some(&subject), Some(&body)); } /** * This implements only the most basic checking for an email address's validity -- that it contains * an '@' and contains no characters disallowed by RFC 2822. This is an overly lenient definition of * validity. We want to generally be lenient here since this class is only intended to encapsulate what's * in a barcode, not "judge" it. */ fn is_basically_valid_email_address(email: &String) -> bool { return email != null && ATEXT_ALPHANUMERIC::is_match(&email) && email.index_of('@') >= 0; } } // ExpandedProductParsedResult.java /** * Represents a parsed result that encodes extended product information as encoded * by the RSS format, like weight, price, dates, etc. * * @author Antonio Manuel Benjumea Conde, Servinform, S.A. * @author Agustín Delgado, Servinform, S.A. */ const KILOGRAM: &'static str = "KG"; const POUND: &'static str = "LB"; #[Derive(Eq, PartialEq, Hash)] pub struct ExpandedProductParsedResult { //super: ParsedResult; raw_text: String, product_i_d: String, sscc: String, lot_number: String, production_date: String, packaging_date: String, best_before_date: String, expiration_date: String, weight: String, weight_type: String, weight_increment: String, price: String, price_increment: String, price_currency: String, // For AIS that not exist in this object uncommon_a_is: Map, } impl ParsedResult for ExpandedProductParsedResult {} impl ExpandedProductParsedResult { pub fn new( raw_text: &String, product_i_d: &String, sscc: &String, lot_number: &String, production_date: &String, packaging_date: &String, best_before_date: &String, expiration_date: &String, weight: &String, weight_type: &String, weight_increment: &String, price: &String, price_increment: &String, price_currency: &String, uncommon_a_is: &Map, ) -> Self { let new_eppr: Self; new_eppr.rawText = raw_text; new_eppr.productID = product_i_d; new_eppr.sscc = sscc; new_eppr.lotNumber = lot_number; new_eppr.productionDate = production_date; new_eppr.packagingDate = packaging_date; new_eppr.bestBeforeDate = best_before_date; new_eppr.expirationDate = expiration_date; new_eppr.weight = weight; new_eppr.weightType = weight_type; new_eppr.weightIncrement = weight_increment; new_eppr.price = price; new_eppr.priceIncrement = price_increment; new_eppr.priceCurrency = price_currency; new_eppr.uncommonAIs = uncommon_a_is; new_eppr } pub fn get_raw_text(&self) -> String { return self.raw_text; } pub fn get_product_i_d(&self) -> String { return self.product_i_d; } pub fn get_sscc(&self) -> String { return self.sscc; } pub fn get_lot_number(&self) -> String { return self.lot_number; } pub fn get_production_date(&self) -> String { return self.production_date; } pub fn get_packaging_date(&self) -> String { return self.packaging_date; } pub fn get_best_before_date(&self) -> String { return self.best_before_date; } pub fn get_expiration_date(&self) -> String { return self.expiration_date; } pub fn get_weight(&self) -> String { return self.weight; } pub fn get_weight_type(&self) -> String { return self.weight_type; } pub fn get_weight_increment(&self) -> String { return self.weight_increment; } pub fn get_price(&self) -> String { return self.price; } pub fn get_price_increment(&self) -> String { return self.price_increment; } pub fn get_price_currency(&self) -> String { return self.price_currency; } pub fn get_uncommon_a_is(&self) -> Map { return self.uncommon_a_is; } pub fn get_display_result(&self) -> String { return String::value_of(&self.raw_text); } } // ExpandedProductResultParser.java /** * Parses strings of digits that represent a RSS Extended code. * * @author Antonio Manuel Benjumea Conde, Servinform, S.A. * @author Agustín Delgado, Servinform, S.A. */ pub struct ExpandedProductResultParser { //super: ResultParser; } impl ResultParser for ExpandedProductResultParser {} impl ExpandedProductResultParser { pub fn parse(&self, result: &Result) -> ExpandedProductParsedResult { let format: BarcodeFormat = result.get_barcode_format(); if format != BarcodeFormat::RSS_EXPANDED { // ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode return null; } let raw_text: String = get_massaged_text(result); let product_i_d: String = null; let mut sscc: String = null; let lot_number: String = null; let production_date: String = null; let packaging_date: String = null; let best_before_date: String = null; let expiration_date: String = null; let mut weight: String = null; let weight_type: String = null; let weight_increment: String = null; let mut price: String = null; let price_increment: String = null; let price_currency: String = null; let uncommon_a_is: Map = HashMap::new(); let mut i: i32 = 0; while i < raw_text.length() { let ai: String = ::find_a_ivalue(i, &raw_text); if ai == null { // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern return null; } i += ai.length() + 2; let value: String = ::find_value(i, &raw_text); i += value.length(); match ai.as_str() { "00" => { sscc = value; break; } "01" => { product_i_d = value; break; } "10" => { lot_number = value; break; } "11" => { production_date = value; break; } "13" => { packaging_date = value; break; } "15" => { best_before_date = value; break; } "17" => { expiration_date = value; break; } "3100" => {} "3101" => {} "3102" => {} "3103" => {} "3104" => {} "3105" => {} "3106" => {} "3107" => {} "3108" => {} "3109" => { weight = value; weight_type = ExpandedProductParsedResult::KILOGRAM; weight_increment = ai.substring(3); break; } "3200" => {} "3201" => {} "3202" => {} "3203" => {} "3204" => {} "3205" => {} "3206" => {} "3207" => {} "3208" => {} "3209" => { weight = value; weight_type = ExpandedProductParsedResult::POUND; weight_increment = ai.substring(3); break; } "3920" => {} "3921" => {} "3922" => {} "3923" => { price = value; price_increment = ai.substring(3); break; } "3930" => {} "3931" => {} "3932" => {} "3933" => { if value.length() < 4 { // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern return null; } price = value.substring(3); price_currency = value.substring(0, 3); price_increment = ai.substring(3); break; } _ => { // No match with common AIs uncommon_a_is.put(&ai, &value); break; } } } return ExpandedProductParsedResult::new( &raw_text, &product_i_d, &sscc, &lot_number, &production_date, &packaging_date, &best_before_date, &expiration_date, &weight, &weight_type, &weight_increment, &price, &price_increment, &price_currency, &uncommon_a_is, ); } fn find_a_ivalue(i: i32, raw_text: &String) -> String { let c: char = raw_text.char_at(i); // First character must be a open parenthesis.If not, ERROR if c != '(' { return null; } let raw_text_aux: CharSequence = raw_text.substring(i + 1); let buf: String = String::new(); { let mut index: i32 = 0; while index < raw_text_aux.length() { { let current_char: char = raw_text_aux.char_at(index); if current_char == ')' { return buf.to_string(); } if current_char < '0' || current_char > '9' { return null; } buf.append(current_char); } index += 1; } } return buf.to_string(); } fn find_value(i: i32, raw_text: &String) -> String { let buf: String = String::new(); let raw_text_aux: String = raw_text.substring(i); { let mut index: i32 = 0; while index < raw_text_aux.length() { { let c: char = raw_text_aux.char_at(index); if c == '(' { // with the iteration if ::find_a_ivalue(index, &raw_text_aux) != null { break; } buf.append('('); } else { buf.append(c); } } index += 1; } } return buf.to_string(); } } // GeoResultParser.java /** * Parses a "geo:" URI result, which specifies a location on the surface of * the Earth as well as an optional altitude above the surface. See * * http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00. * * @author Sean Owen */ const GEO_URL_PATTERN: Regex = Regex::new("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?"); pub struct GeoResultParser { //super: ResultParser; } impl ResultParser for GeoResultParser {} impl GeoResultParser { pub fn parse(&self, result: &Result) -> GeoParsedResult { let raw_text: CharSequence = get_massaged_text(result); let matcher: Matcher = GEO_URL_PATTERN::matcher(&raw_text); if !matcher.matches() { return null; } let query: String = matcher.group(4); let mut latitude: f64; let mut longitude: f64; let mut altitude: f64; let tryResult1 = 0; latitude = Double::parse_double(&matcher.group(1)); if latitude > 90.0 || latitude < -90.0 { return null; } longitude = Double::parse_double(&matcher.group(2)); if longitude > 180.0 || longitude < -180.0 { return null; } if matcher.group(3) == null { altitude = 0.0; } else { altitude = Double::parse_double(&matcher.group(3)); if altitude < 0.0 { return null; } } return GeoParsedResult::new(latitude, longitude, altitude, &query); } } // GeoParsedResult.java /** * Represents a parsed result that encodes a geographic coordinate, with latitude, * longitude and altitude. * * @author Sean Owen */ pub struct GeoParsedResult { //super: ParsedResult; latitude: f64, longitude: f64, altitude: f64, query: String, } impl ParsedResult for GeoParsedResult {} impl GeoParsedResult { fn new(latitude: f64, longitude: f64, altitude: f64, query: &String) -> Self { Self { latitude, longitude, altitude, query, } } pub fn get_geo_u_r_i(&self) -> String { let result: String = String::new(); result.append("geo:"); result.append(self.latitude); result.append(','); result.append(self.longitude); if self.altitude > 0.0 { result.append(','); result.append(self.altitude); } if self.query != null { result.append('?'); result.append(&self.query); } return result.to_string(); } /** * @return latitude in degrees */ pub fn get_latitude(&self) -> f64 { return self.latitude; } /** * @return longitude in degrees */ pub fn get_longitude(&self) -> f64 { return self.longitude; } /** * @return altitude in meters. If not specified, in the geo URI, returns 0.0 */ pub fn get_altitude(&self) -> f64 { return self.altitude; } /** * @return query string associated with geo URI or null if none exists */ pub fn get_query(&self) -> String { return self.query; } pub fn get_display_result(&self) -> String { let result: String = String::new(); result.append(self.latitude); result.append(", "); result.append(self.longitude); if self.altitude > 0.0 { result.append(", "); result.append(self.altitude); result.append('m'); } if self.query != null { result.append(" ("); result.append(&self.query); result.append(')'); } return result.to_string(); } } // ProductParsedResult.java /** * Represents a parsed result that encodes a product by an identifier of some kind. * * @author dswitkin@google.com (Daniel Switkin) */ pub struct ProductParsedResult { // super: ParsedResult; product_i_d: String, normalized_product_i_d: String, } impl ParsedResult for ProductParsedResult {} impl ProductParsedResult { fn new(product_i_d: &String, normalized_product_i_d: Option<&String>) -> Self { Self { product_i_d: product_i_d, normalized_product_i_d: normalized_product_i_d.unwrap_or(product_i_d), } } pub fn get_product_i_d(&self) -> String { return self.product_i_d; } pub fn get_normalized_product_i_d(&self) -> String { return self.normalized_product_i_d; } pub fn get_display_result(&self) -> String { return self.product_i_d; } } // ProductResultParser.java /** * Parses strings of digits that represent a UPC code. * * @author dswitkin@google.com (Daniel Switkin) */ pub struct ProductResultParser { //super: ResultParser; } impl ResultParser for ProductResultParser {} impl ProductResultParser { // Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes. pub fn parse(&self, result: &Result) -> ProductParsedResult { let format: BarcodeFormat = result.get_barcode_format(); if !(format == BarcodeFormat::UPC_A || format == BarcodeFormat::UPC_E || format == BarcodeFormat::EAN_8 || format == BarcodeFormat::EAN_13) { return null; } let raw_text: String = get_massaged_text(result); if !is_string_of_digits(&raw_text, &raw_text.length()) { return null; } // Not actually checking the checksum again here let normalized_product_i_d: String; // Expand UPC-E for purposes of searching if format == BarcodeFormat::UPC_E && raw_text.length() == 8 { normalized_product_i_d = UPCEReader::convert_u_p_c_eto_u_p_c_a(&raw_text); } else { normalized_product_i_d = raw_text; } return ProductParsedResult::new(&raw_text, Some(&normalized_product_i_d)); } } // SMSMMSResultParser.java /** *

Parses an "sms:" URI result, which specifies a number to SMS. * See RFC 5724 on this.

* *

This class supports "via" syntax for numbers, which is not part of the spec. * For example "+12125551212;via=+12124440101" may appear as a number. * It also supports a "subject" query parameter, which is not mentioned in the spec. * These are included since they were mentioned in earlier IETF drafts and might be * used.

* *

This actually also parses URIs starting with "mms:" and treats them all the same way, * and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.

* * @author Sean Owen */ pub struct SMSMMSResultParser { //super: ResultParser; } impl ResultParser for SMSMMSResultParser {} impl SMSMMSResultParser { pub fn parse(&self, result: &Result) -> SMSParsedResult { let raw_text: String = get_massaged_text(result); if !(raw_text.starts_with("sms:") || raw_text.starts_with("SMS:") || raw_text.starts_with("mms:") || raw_text.starts_with("MMS:")) { return null; } // Check up front if this is a URI syntax string with query arguments let name_value_pairs: Map = parse_name_value_pairs(&raw_text); let mut subject: String = null; let mut body: String = null; let query_syntax: bool = false; if name_value_pairs != null && !name_value_pairs.is_empty() { subject = name_value_pairs.get("subject"); body = name_value_pairs.get("body"); query_syntax = true; } // Drop sms, query portion let query_start: i32 = raw_text.index_of('?', 4); let sms_u_r_i_without_query: String; // If it's not query syntax, the question mark is part of the subject or message if query_start < 0 || !query_syntax { sms_u_r_i_without_query = raw_text.substring(4); } else { sms_u_r_i_without_query = raw_text.substring(4, query_start); } let last_comma: i32 = -1; let mut comma: i32; let numbers: List = Vec::new(); let vias: List = Vec::new(); while (comma = sms_u_r_i_without_query.index_of(',', last_comma + 1)) > last_comma { let number_part: String = sms_u_r_i_without_query.substring(last_comma + 1, comma); ::add_number_via(&numbers, &vias, &number_part); last_comma = comma; } ::add_number_via( &numbers, &vias, &sms_u_r_i_without_query.substring(last_comma + 1), ); return SMSParsedResult::new( &numbers.to_array(EMPTY_STR_ARRAY), &vias.to_array(EMPTY_STR_ARRAY), &subject, &body, ); } fn add_number_via( numbers: &Collection, vias: &Collection, number_part: &String, ) { let number_end: i32 = number_part.index_of(';'); if number_end < 0 { numbers.add(&number_part); vias.add(null); } else { numbers.add(&number_part.substring(0, number_end)); let maybe_via: String = number_part.substring(number_end + 1); let mut via: String; if maybe_via.starts_with("via=") { via = maybe_via.substring(4); } else { via = null; } vias.add(&via); } } } // SMSParsedResult.java /** * Represents a parsed result that encodes an SMS message, including recipients, subject * and body text. * * @author Sean Owen */ pub struct SMSParsedResult { //super: ParsedResult; numbers: Vec, vias: Vec, subject: String, body: String, } impl ParsedResult for SMSParsedResult {} impl SMSParsedResult { pub fn new(numbers: &Vec, vias: &Vec, subject: &String, body: &String) -> Self { Self { numbers: numbers, vias: vias, subject: subject, body: body, } } pub fn get_s_m_s_u_r_i(&self) -> String { let result: String = String::new(); result.append("sms:"); let mut first: bool = true; { let mut i: i32 = 0; while i < self.numbers.len() { { if first { first = false; } else { result.append(','); } result.append(self.numbers[i]); if self.vias != null && self.vias[i] != null { result.append(";via="); result.append(self.vias[i]); } } i += 1; } } let has_body: bool = self.body != null; let has_subject: bool = self.subject != null; if has_body || has_subject { result.append('?'); if has_body { result.append("body="); result.append(&self.body); } if has_subject { if has_body { result.append('&'); } result.append("subject="); result.append(&self.subject); } } return result.to_string(); } pub fn get_numbers(&self) -> Vec { return self.numbers; } pub fn get_vias(&self) -> Vec { return self.vias; } pub fn get_subject(&self) -> String { return self.subject; } pub fn get_body(&self) -> String { return self.body; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.numbers, &result); maybe_append(&self.subject, &result); maybe_append(&self.body, &result); return result.to_string(); } } // SMSTOMMSTOResultParser.java /** *

Parses an "smsto:" URI result, whose format is not standardized but appears to be like: * {@code smsto:number(:body)}.

* *

This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI * for purposes of forwarding to the platform.

* * @author Sean Owen */ pub struct SMSTOMMSTOResultParser { //super: ResultParser; } impl ResultParser for SMSTOMMSTOResultParser {} impl SMSTOMMSTOResultParser { pub fn parse(&self, result: &Result) -> SMSParsedResult { let raw_text: String = get_massaged_text(result); if !(raw_text.starts_with("smsto:") || raw_text.starts_with("SMSTO:") || raw_text.starts_with("mmsto:") || raw_text.starts_with("MMSTO:")) { return null; } // Thanks to dominik.wild for suggesting this enhancement to support // smsto:number:body URIs let mut number: String = raw_text.substring(6); let mut body: String = null; let body_start: i32 = number.index_of(':'); if body_start >= 0 { body = number.substring(body_start + 1); number = number.substring(0, body_start); } return SMSParsedResult::new(&number, null, null, &body); } } // SMTPResultParser.java /** *

Parses an "smtp:" URI result, whose format is not standardized but appears to be like: * {@code smtp[:subject[:body]]}.

* * @author Sean Owen */ pub struct SMTPResultParser { //super: ResultParser; } impl ResultParser for SMTPResultParser {} impl SMTPResultParser { pub fn parse(&self, result: &Result) -> EmailAddressParsedResult { let raw_text: String = get_massaged_text(result); if !(raw_text.starts_with("smtp:") || raw_text.starts_with("SMTP:")) { return null; } let email_address: String = raw_text.substring(5); let mut subject: String = null; let mut body: String = null; let mut colon: i32 = email_address.index_of(':'); if colon >= 0 { subject = email_address.substring(colon + 1); email_address = email_address.substring(0, colon); colon = subject.index_of(':'); if colon >= 0 { body = subject.substring(colon + 1); subject = subject.substring(0, colon); } } return EmailAddressParsedResult::new( &vec![email_address], null, null, Some(&subject), Some(&body), ); } } // TelParsedResult.java /** * Represents a parsed result that encodes a telephone number. * * @author Sean Owen */ pub struct TelParsedResult { //super: ParsedResult; number: String, tel_u_r_i: String, title: String, } impl ParsedResult for TelParsedResult {} impl TelParsedResult { pub fn new(number: &String, tel_u_r_i: &String, title: &String) -> Self { Self { number: number, tel_u_r_i: tel_u_r_i, title: title, } } pub fn get_number(&self) -> String { return self.number; } pub fn get_tel_u_r_i(&self) -> String { return self.tel_u_r_i; } pub fn get_title(&self) -> String { return self.title; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.number, &result); maybe_append(&self.title, &result); return result.to_string(); } } // TelResultParser.java /** * Parses a "tel:" URI result, which specifies a phone number. * * @author Sean Owen */ pub struct TelResultParser { //super: ResultParser; } impl ResultParser for TelResultParser {} impl TelResultParser { pub fn parse(&self, result: &Result) -> TelParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("tel:") && !raw_text.starts_with("TEL:") { return null; } // Normalize "TEL:" to "tel:" let tel_u_r_i: String = if raw_text.starts_with("TEL:") { format!("tel:{}", raw_text.substring(4)) } else { raw_text }; // Drop tel, query portion let query_start: i32 = raw_text.index_of('?', 4); let number: String = if query_start < 0 { raw_text.substring(4) } else { raw_text.substring(4, query_start) }; return TelParsedResult::new(&number, &tel_u_r_i, null); } } // TextParsedResult.java /** * A simple result type encapsulating a string that has no further * interpretation. * * @author Sean Owen */ pub struct TextParsedResult { //super: ParsedResult; text: String, language: String, } impl ParsedResult for TextParsedResult {} impl TextParsedResult { pub fn new(text: &String, language: &String) -> Self { Self { text: text, language: language, } } pub fn get_text(&self) -> String { return self.text; } pub fn get_language(&self) -> String { return self.language; } pub fn get_display_result(&self) -> String { return self.text; } } // URIParsedResult.java /** * A simple result type encapsulating a URI that has no further interpretation. * * @author Sean Owen */ pub struct URIParsedResult { //super: ParsedResult; uri: String, title: String, } impl ParsedResult for URIParsedResult {} impl URIParsedResult { pub fn new(uri: &String, title: &String) -> Self { Self { uri: ::massage_u_r_i(&uri), title: title, } } pub fn get_u_r_i(&self) -> String { return self.uri; } pub fn get_title(&self) -> String { return self.title; } /** * @return true if the URI contains suspicious patterns that may suggest it intends to * mislead the user about its true nature * @deprecated see {@link URIResultParser#isPossiblyMaliciousURI(String)} */ pub fn is_possibly_malicious_u_r_i(&self) -> bool { return URIResultParser::is_possibly_malicious_u_r_i(&self.uri); } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.title, &result); maybe_append(&self.uri, &result); return result.to_string(); } /** * Transforms a string that represents a URI into something more proper, by adding or canonicalizing * the protocol. */ fn massage_u_r_i(uri: &String) -> String { uri = uri.trim(); let protocol_end: i32 = uri.index_of(':'); if protocol_end < 0 || ::is_colon_followed_by_port_number(&uri, protocol_end) { // No protocol, or found a colon, but it looks like it is after the host, so the protocol is still missing, // so assume http uri = &format!("http://{}", uri); } return uri; } fn is_colon_followed_by_port_number(uri: &String, protocol_end: i32) -> bool { let start: i32 = protocol_end + 1; let next_slash: i32 = uri.index_of('/', start); if next_slash < 0 { next_slash = uri.length(); } return ResultParser::is_substring_of_digits(&uri, start, next_slash - start); } } // URIResultParser.java /** * Tries to parse results that are a URI of some kind. * * @author Sean Owen */ const ALLOWED_URI_CHARS_PATTERN: Regext = Regex::new("[-._~:/?#\\[\\]@!$&'()*+,;=%A-Za-z0-9]+"); const USER_IN_HOST: Regext = Regex::new(":/*([^/@]+)@[^/]+"); // See http://www.ietf.org/rfc/rfc2396.txt const URL_WITH_PROTOCOL_PATTERN: Regext = Regex::new("[a-zA-Z][a-zA-Z0-9+-.]+:"); const URL_WITHOUT_PROTOCOL_PATTERN: Regext = Regex::new(&format!( "([a-zA-Z0-9\\-]+\\.){1,6}[a-zA-Z]{2,}(:\\d{1,5})?(/|\\?|$)" )); pub struct URIResultParser { //super: ResultParser; } impl ResultParser for URIResultParser {} impl URIResultParser { pub fn parse(&self, result: &Result) -> URIParsedResult { let raw_text: String = get_massaged_text(result); // Assume anything starting this way really means to be a URI if raw_text.starts_with("URL:") || raw_text.starts_with("URI:") { return URIParsedResult::new(&raw_text.substring(4).trim(), null); } raw_text = raw_text.trim().to_owned(); if !::is_basically_valid_u_r_i(&raw_text) || ::is_possibly_malicious_u_r_i(&raw_text) { return null; } return URIParsedResult::new(&raw_text, null); } /** * @return true if the URI contains suspicious patterns that may suggest it intends to * mislead the user about its true nature. At the moment this looks for the presence * of user/password syntax in the host/authority portion of a URI which may be used * in attempts to make the URI's host appear to be other than it is. Example: * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear * to connect to yourbank.com at first glance. */ fn is_possibly_malicious_u_r_i(uri: &String) -> bool { return !ALLOWED_URI_CHARS_PATTERN.is_match(&uri) || USER_IN_HOST.find(&uri); } fn is_basically_valid_u_r_i(uri: &String) -> bool { if uri.contains(" ") { // Quick hack check for a common case return false; } let mut m: Matcher = URL_WITH_PROTOCOL_PATTERN::matcher(&uri); if m.find() && m.start() == 0 { // match at start only return true; } m = URL_WITHOUT_PROTOCOL_PATTERN::matcher(&uri); return m.find() && m.start() == 0; } } // URLTOResultParser.java /** * Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]". * This seems to be used sometimes, but I am not able to find documentation * on its origin or official format? * * @author Sean Owen */ pub struct URLTOResultParser { //super: ResultParser; } impl ResultParser for URLTOResultParser {} impl URLTOResultParser { pub fn parse(&self, result: &Result) -> URIParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("urlto:") && !raw_text.starts_with("URLTO:") { return null; } let title_end: i32 = raw_text.index_of(':', 6); if title_end < 0 { return null; } let title: String = if title_end <= 6 { null } else { raw_text.substring(6, title_end) }; let uri: String = raw_text.substring(title_end + 1); return URIParsedResult::new(&uri, &title); } } // VCardResultParser.java /** * Parses contact information formatted according to the VCard (2.1) format. This is not a complete * implementation but should parse information as commonly encoded in 2D barcodes. * * @author Sean Owen */ const BEGIN_VCARD: Regex = Regex::new("BEGIN:VCARD"); const VCARD_LIKE_DATE: Regex = Regex::new("\\d{4}-?\\d{2}-?\\d{2}"); const CR_LF_SPACE_TAB: Regex = Regex::new("\r\n[ \t]"); const NEWLINE_ESCAPE: Regex = Regex::new("\\\\[nN]"); const VCARD_ESCAPES: Regex = Regex::new("\\\\([,;\\\\])"); const EQUALS: Regex = Regex::new("="); const SEMICOLON: Regex = Regex::new(";"); const UNESCAPED_SEMICOLONS: Regex = Regex::new("(? AddressBookParsedResult { // Although we should insist on the raw text ending with "END:VCARD", there's no reason // to throw out everything else we parsed just because this was omitted. In fact, Eclair // is doing just that, and we can't parse its contacts without this leniency. let raw_text: String = get_massaged_text(result); let m: Matcher = BEGIN_VCARD::matcher(&raw_text); if !m.find() || m.start() != 0 { return null; } let mut names: List> = ::match_v_card_prefixed_field("FN", &raw_text, true, false); if names == null { // If no display names found, look for regular name fields and format them names = ::match_v_card_prefixed_field("N", &raw_text, true, false); ::format_names(&names); } let nickname_string: List = ::match_single_v_card_prefixed_field("NICKNAME", &raw_text, true, false); let nicknames: Vec = if nickname_string == null { null } else { COMMA::split(&nickname_string.get(0)) }; let phone_numbers: List> = ::match_v_card_prefixed_field("TEL", &raw_text, true, false); let emails: List> = ::match_v_card_prefixed_field("EMAIL", &raw_text, true, false); let note: List = ::match_single_v_card_prefixed_field("NOTE", &raw_text, false, false); let addresses: List> = ::match_v_card_prefixed_field("ADR", &raw_text, true, true); let org: List = ::match_single_v_card_prefixed_field("ORG", &raw_text, true, true); let mut birthday: List = ::match_single_v_card_prefixed_field("BDAY", &raw_text, true, false); if birthday != null && !::is_like_v_card_date(&birthday.get(0)) { birthday = null; } let title: List = ::match_single_v_card_prefixed_field("TITLE", &raw_text, true, false); let urls: List> = ::match_v_card_prefixed_field("URL", &raw_text, true, false); let instant_messenger: List = ::match_single_v_card_prefixed_field("IMPP", &raw_text, true, false); let geo_string: List = ::match_single_v_card_prefixed_field("GEO", &raw_text, true, false); let mut geo: Vec = if geo_string == null { null } else { SEMICOLON_OR_COMMA::split(&geo_string.get(0)) }; if geo != null && geo.len() != 2 { geo = null; } return AddressBookParsedResult::new( &::to_primary_values(&names), Some(&nicknames), null, &::to_primary_values(&phone_numbers), &::to_types(&phone_numbers), &::to_primary_values(&emails), &::to_types(&emails), Some(&::to_primary_value(&instant_messenger)), Some(&::to_primary_value(¬e)), &::to_primary_values(&addresses), &::to_types(&addresses), Some(&::to_primary_value(&org)), Some(&::to_primary_value(&birthday)), Some(&::to_primary_value(&title)), Some(&::to_primary_values(&urls)), Some(&geo), ); } fn match_v_card_prefixed_field( prefix: &CharSequence, raw_text: &String, trim: bool, parse_field_divider: bool, ) -> List> { let mut matches: List> = null; let mut i: i32 = 0; let max: i32 = raw_text.length(); while i < max { // At start or after newline, match prefix, followed by optional metadata // (led by ;) ultimately ending in colon let matcher: Regex = Regex::new(&format!("(?:^|\n){}(?:;([^:]*))?:", prefix)); if i > 0 { // Find from i-1 not i since looking at the preceding character i -= 1; } // ::matcher(&raw_text); FEELS WRONG todo!("feels wrong"); if !matcher.find(i) { break; } // group 0 = whole pattern; end(0) is past final colon i = matcher.end(0); // group 1 = metadata substring let metadata_string: String = matcher.group(1); let mut metadata: List = null; let quoted_printable: bool = false; let quoted_printable_charset: String = null; let value_type: String = null; if metadata_string != null { for metadatum in SEMICOLON::split(&metadata_string) { if metadata == null { metadata = Vec::new(); } metadata.add(&metadatum); let metadatum_tokens: Vec = EQUALS::split(&metadatum, 2); if metadatum_tokens.len() > 1 { let key: String = metadatum_tokens[0]; let value: String = metadatum_tokens[1]; if "ENCODING".equals_ignore_case(&key) && "QUOTED-PRINTABLE".equals_ignore_case(&value) { quoted_printable = true; } else if "CHARSET".equals_ignore_case(&key) { quoted_printable_charset = value; } else if "VALUE".equals_ignore_case(&key) { value_type = value; } } } } // Found the start of a match here let match_start: i32 = i; while (i = raw_text.index_of('\n', i)) >= 0 { // Really, end in \r\n if // But if followed by tab or space, i < raw_text.length() - 1 && ( // this is only a continuation raw_text.char_at(i + 1) == ' ' || raw_text.char_at(i + 1) == '\t' ) { // Skip \n and continutation whitespace i += 2; } else if // If preceded by = in quoted printable quoted_printable && ( // this is a continuation (i >= 1 && raw_text.char_at(i - 1) == '=') || (i >= 2 && raw_text.char_at(i - 2) == '=') ) { // Skip \n i += 1; } else { break; } } if i < 0 { // No terminating end character? uh, done. Set i such that loop terminates and break i = max; } else if i > match_start { // found a match if matches == null { // lazy init matches = Vec::new(); } if i >= 1 && raw_text.char_at(i - 1) == '\r' { // Back up over \r, which really should be there i -= 1; } let mut element: String = raw_text.substring(match_start, i); if trim { element = element.trim().to_owned(); } if quoted_printable { element = ::decode_quoted_printable(&element, "ed_printable_charset); if parse_field_divider { element = UNESCAPED_SEMICOLONS .replace_all(&element, "\n") .trim() .to_owned(); } } else { if parse_field_divider { element = UNESCAPED_SEMICOLONS::matcher(&element, "\n").trim(); } element = CR_LF_SPACE_TAB.replace_all(&element, ""); element = NEWLINE_ESCAPE.replace_all(&element, "\n"); element = VCARD_ESCAPES.replace_all(&element, "$1"); } // Only handle VALUE=uri specially if "uri".equals(&value_type) { // as value, to support tel: and mailto: panic!("i don't know what this does, and i don't know how to fix it"); todo!("i don't know what this does, and i don't know how to fix it") //element = URI::create(&element)::get_scheme_specific_part(); } if metadata == null { let _match: List = Vec::new(); _match.add(&element); matches.add(&_match); } else { metadata.add(0, &element); matches.add(&metadata); } i += 1; } else { i += 1; } } return matches; } fn decode_quoted_printable(value: &CharSequence, charset: &String) -> String { let length: i32 = value.length(); let result: String = String::new(); let fragment_buffer: ByteArrayOutputStream = ByteArrayOutputStream::new(); { let mut i: i32 = 0; while i < length { { let c: char = value.char_at(i); match c { '\r' => {} '\n' => { break; } '=' => { if i < length - 2 { let next_char: char = value.char_at(i + 1); if next_char != '\r' && next_char != '\n' { let next_next_char: char = value.char_at(i + 2); let first_digit: i32 = parse_hex_digit(next_char); let second_digit: i32 = parse_hex_digit(next_next_char); if first_digit >= 0 && second_digit >= 0 { fragment_buffer.write((first_digit << 4) + second_digit); } // else ignore it, assume it was incorrectly encoded i += 2; } } break; } _ => { ::maybe_append_fragment(&fragment_buffer, &charset, &result); result.append(c); } } } i += 1; } } ::maybe_append_fragment(&fragment_buffer, &charset, &result); return result.to_string(); } fn maybe_append_fragment( fragment_buffer: &ByteArrayOutputStream, charset: &String, result: &String, ) { if fragment_buffer.size() > 0 { let fragment_bytes: Vec = fragment_buffer.to_byte_array(); let mut fragment: String; if charset == null { fragment = String::from(fragment_bytes); } else { fragment = String::from(fragment_bytes); } fragment_buffer.reset(); result.append(&fragment); } } fn match_single_v_card_prefixed_field( prefix: &CharSequence, raw_text: &String, trim: bool, parse_field_divider: bool, ) -> List { let values: List> = ::match_v_card_prefixed_field(&prefix, &raw_text, trim, parse_field_divider); return if values == null || values.is_empty() { null } else { values.get(0) }; } fn to_primary_value(list: &List) -> String { return if list == null || list.is_empty() { null } else { list.get(0) }; } fn to_primary_values(lists: &Collection>) -> Vec { if lists == null || lists.is_empty() { return null; } let result: List = Vec::new(); for list in lists { let value: String = list.get(0); if value != null && !value.is_empty() { result.add(&value); } } return result.to_array(EMPTY_STR_ARRAY); } fn to_types(lists: &Collection>) -> Vec { if lists == null || lists.is_empty() { return null; } let result: List = Vec::new(); for list in lists { let value: String = list.get(0); if value != null && !value.is_empty() { let mut _type: String = null; { let mut i: i32 = 1; while i < list.size() { { let metadatum: String = list.get(i); let equals: i32 = metadatum.index_of('='); if equals < 0 { // take the whole thing as a usable label _type = metadatum; break; } if "TYPE".equals_ignore_case(&metadatum.substring(0, equals)) { _type = metadatum.substring(equals + 1); break; } } i += 1; } } result.add(&_type); } } return result.to_array(EMPTY_STR_ARRAY); } fn is_like_v_card_date(value: &CharSequence) -> bool { return value == null || VCARD_LIKE_DATE.is_match(&value); } /** * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like * "Reverend John Q. Public III". * * @param names name values to format, in place */ fn format_names(names: &Iterable>) { if names != null { for list in names { let name: String = list.get(0); let mut components: [Option; 5] = [None; 5]; let mut start: i32 = 0; let mut end: i32; let component_index: i32 = 0; while component_index < components.len() - 1 && (end = name.index_of(';', start)) >= 0 { components[component_index] = name.substring(start, end); component_index += 1; start = end + 1; } components[component_index] = name.substring(start); let new_name: String = String::new(); ::maybe_append_component(&components, 3, &new_name); ::maybe_append_component(&components, 1, &new_name); ::maybe_append_component(&components, 2, &new_name); ::maybe_append_component(&components, 0, &new_name); ::maybe_append_component(&components, 4, &new_name); list.set(0, &new_name.to_string().trim()); } } } fn maybe_append_component(components: &Vec, i: i32, new_name: &String) { if components[i] != null && !components[i].is_empty() { if new_name.length() > 0 { new_name.append(' '); } new_name.append(components[i]); } } } // VEventResultParser.java /** * Partially implements the iCalendar format's "VEVENT" format for specifying a * calendar event. See RFC 2445. This supports SUMMARY, LOCATION, GEO, DTSTART and DTEND fields. * * @author Sean Owen */ pub struct VEventResultParser { //super: ResultParser; } impl ResultParser for VEventResultParser {} impl VEventResultParser { pub fn parse(&self, result: &Result) -> CalendarParsedResult { let raw_text: String = get_massaged_text(result); let v_event_start: i32 = raw_text.index_of("BEGIN:VEVENT"); if v_event_start < 0 { return null; } let summary: String = ::match_single_v_card_prefixed_field("SUMMARY", &raw_text); let start: String = ::match_single_v_card_prefixed_field("DTSTART", &raw_text); if start == null { return null; } let end: String = ::match_single_v_card_prefixed_field("DTEND", &raw_text); let duration: String = ::match_single_v_card_prefixed_field("DURATION", &raw_text); let location: String = ::match_single_v_card_prefixed_field("LOCATION", &raw_text); let organizer: String = ::strip_mailto(&::match_single_v_card_prefixed_field( "ORGANIZER", &raw_text, )); let mut attendees: Vec = ::match_v_card_prefixed_field("ATTENDEE", &raw_text); if attendees != null { { let mut i: i32 = 0; while i < attendees.len() { { attendees[i] = ::strip_mailto(attendees[i]); } i += 1; } } } let description: String = ::match_single_v_card_prefixed_field("DESCRIPTION", &raw_text); let geo_string: String = ::match_single_v_card_prefixed_field("GEO", &raw_text); let mut latitude: f64; let mut longitude: f64; if geo_string == null { latitude = Double::NaN; longitude = Double::NaN; } else { let semicolon: i32 = geo_string.index_of(';'); if semicolon < 0 { return null; } latitude = Double::parse_double(&geo_string.substring(0, semicolon)); longitude = Double::parse_double(&geo_string.substring(semicolon + 1)); } return CalendarParsedResult::new( &summary, &start, &end, &duration, &location, &organizer, &attendees, &description, latitude, longitude, ); } fn match_single_v_card_prefixed_field(prefix: &CharSequence, raw_text: &String) -> String { let values: List = VCardResultParser::match_single_v_card_prefixed_field(&prefix, &raw_text, true, false); return if values == null || values.is_empty() { null } else { values.get(0) }; } fn match_v_card_prefixed_field(prefix: &CharSequence, raw_text: &String) -> Vec { let values: List> = VCardResultParser::match_v_card_prefixed_field(&prefix, &raw_text, true, false); if values == null || values.is_empty() { return null; } let size: i32 = values.size(); let mut result: [Option; size] = [None; size]; { let mut i: i32 = 0; while i < size { { result[i] = values.get(i).get(0); } i += 1; } } return result; } fn strip_mailto(s: &String) -> String { if s != null && (s.starts_with("mailto:") || s.starts_with("MAILTO:")) { s = s.substring(7); } return s; } } // VINParsedResult.java /** * Represents a parsed result that encodes a Vehicle Identification Number (VIN). */ pub struct VINParsedResult { //super: ParsedResult; vin: String, world_manufacturer_i_d: String, vehicle_descriptor_section: String, vehicle_identifier_section: String, country_code: String, vehicle_attributes: String, model_year: i32, plant_code: char, sequential_number: String, } impl ParsedResult for VINParsedResult {} impl VINParsedResult { pub fn new( vin: &String, world_manufacturer_i_d: &String, vehicle_descriptor_section: &String, vehicle_identifier_section: &String, country_code: &String, vehicle_attributes: &String, model_year: i32, plant_code: char, sequential_number: &String, ) -> Self { Self { vin: vin, world_manufacturer_i_d: world_manufacturer_i_d, vehicle_descriptor_section: vehicle_descriptor_section, vehicle_identifier_section: vehicle_identifier_section, country_code: country_code, vehicle_attributes: vehicle_attributes, model_year, plant_code, sequential_number: sequential_number, } } pub fn get_v_i_n(&self) -> String { return self.vin; } pub fn get_world_manufacturer_i_d(&self) -> String { return self.world_manufacturer_i_d; } pub fn get_vehicle_descriptor_section(&self) -> String { return self.vehicle_descriptor_section; } pub fn get_vehicle_identifier_section(&self) -> String { return self.vehicle_identifier_section; } pub fn get_country_code(&self) -> String { return self.country_code; } pub fn get_vehicle_attributes(&self) -> String { return self.vehicle_attributes; } pub fn get_model_year(&self) -> i32 { return self.model_year; } pub fn get_plant_code(&self) -> char { return self.plant_code; } pub fn get_sequential_number(&self) -> String { return self.sequential_number; } pub fn get_display_result(&self) -> String { let result: String = String::new(); result.append(&self.world_manufacturer_i_d).append(' '); result.append(&self.vehicle_descriptor_section).append(' '); result.append(&self.vehicle_identifier_section).append('\n'); if self.country_code != null { result.append(&self.country_code).append(' '); } result.append(self.model_year).append(' '); result.append(self.plant_code).append(' '); result.append(&self.sequential_number).append('\n'); return result.to_string(); } } // VINResultParser.java /** * Detects a result that is likely a vehicle identification number. * * @author Sean Owen */ const IOQ: Regex = Regex::new("[IOQ]"); const AZ09: Regex = Regex::new("[A-Z0-9]{17}"); pub struct VINResultParser { //super: ResultParser; } impl ResultParser for VINResultParser {} impl VINResultParser { pub fn parse(&self, result: &Result) -> VINParsedResult { if result.get_barcode_format() != BarcodeFormat::CODE_39 { return null; } let raw_text: String = result.get_text(); raw_text = IOQ.replace_all(&raw_text, "").trim().to_owned(); if !AZ09::is_match(&raw_text) { return null; } if !::check_checksum(&raw_text) { return null; } let wmi: String = raw_text.substring(0, 3); return VINParsedResult::new( &raw_text, &wmi, &raw_text.substring(3, 9), &raw_text.substring(9, 17), &::country_code(&wmi), &raw_text.substring(3, 8), &::model_year(&raw_text.char_at(9)), &raw_text.char_at(10), &raw_text.substring(11), ); } fn check_checksum(vin: &CharSequence) -> bool { let mut sum: i32 = 0; { let mut i: i32 = 0; while i < vin.length() { { sum += ::vin_position_weight(i + 1) * ::vin_char_value(&vin.char_at(i)); } i += 1; } } let check_char: char = vin.char_at(8); let expected_check_char: char = self.check_char(sum % 11); return check_char == expected_check_char; } fn vin_char_value(c: char) -> Result { if c >= 'A' && c <= 'I' { return (c - 'A') + 1; } if c >= 'J' && c <= 'R' { return (c - 'J') + 1; } if c >= 'S' && c <= 'Z' { return (c - 'S') + 2; } if c >= '0' && c <= '9' { return c - '0'; } Err(IllegalArgumentException::new()) } fn vin_position_weight(position: i32) -> Result { if position >= 1 && position <= 7 { return Ok(9 - position); } if position == 8 { return Ok(10); } if position == 9 { return Ok(0); } if position >= 10 && position <= 17 { return Ok(19 - position); } Err(IllegalArgumentException::new()) } fn check_char(remainder: i32) -> Result { if remainder < 10 { return Ok(('0' + remainder) as char); } if remainder == 10 { return Ok('X'); } Err(IllegalArgumentException::new()) } fn model_year(c: char) -> Result { if c >= 'E' && c <= 'H' { return (c - 'E') + 1984; } if c >= 'J' && c <= 'N' { return (c - 'J') + 1988; } if c == 'P' { return Ok(1993); } if c >= 'R' && c <= 'T' { return (c - 'R') + 1994; } if c >= 'V' && c <= 'Y' { return (c - 'V') + 1997; } if c >= '1' && c <= '9' { return (c - '1') + 2001; } if c >= 'A' && c <= 'D' { return (c - 'A') + 2010; } Err(IllegalArgumentException::new()) } fn country_code(wmi: &CharSequence) -> &'static str { let c1: char = wmi.char_at(0); let c2: char = wmi.char_at(1); match c1 { '1' => {} '4' => {} '5' => { return "US"; } '2' => { return "CA"; } '3' => { if c2 >= 'A' && c2 <= 'W' { return "MX"; } } '9' => { if (c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9') { return "BR"; } } 'J' => { if c2 >= 'A' && c2 <= 'T' { return "JP"; } } 'K' => { if c2 >= 'L' && c2 <= 'R' { return "KO"; } } 'L' => { return "CN"; } 'M' => { if c2 >= 'A' && c2 <= 'E' { return "IN"; } } 'S' => { if c2 >= 'A' && c2 <= 'M' { return "UK"; } if c2 >= 'N' && c2 <= 'T' { return "DE"; } } 'V' => { if c2 >= 'F' && c2 <= 'R' { return "FR"; } if c2 >= 'S' && c2 <= 'W' { return "ES"; } } 'W' => { return "DE"; } 'X' => { if c2 == '0' || (c2 >= '3' && c2 <= '9') { return "RU"; } } 'Z' => { if c2 >= 'A' && c2 <= 'R' { return "IT"; } } } return null; } } // WifiParsedResult.java /** * Represents a parsed result that encodes wifi network information, like SSID and password. * * @author Vikram Aggarwal */ pub struct WifiParsedResult { //super: ParsedResult; ssid: String, network_encryption: String, password: String, hidden: bool, identity: String, anonymous_identity: String, eap_method: String, phase2_method: String, } impl ParsedResult for WifiParsedResult {} impl WifiParsedResult { pub fn new( network_encryption: &String, ssid: &String, password: &String, hidden: bool, identity: &String, anonymous_identity: &String, eap_method: &String, phase2_method: &String, ) -> Self { Self { ssid: ssid, network_encryption: network_encryption, password: password, hidden, identity: identity, anonymous_identity: anonymous_identity, eap_method: eap_method, phase2_method: phase2_method, } } pub fn get_ssid(&self) -> String { return self.ssid; } pub fn get_network_encryption(&self) -> String { return self.network_encryption; } pub fn get_password(&self) -> String { return self.password; } pub fn is_hidden(&self) -> bool { return self.hidden; } pub fn get_identity(&self) -> String { return self.identity; } pub fn get_anonymous_identity(&self) -> String { return self.anonymous_identity; } pub fn get_eap_method(&self) -> String { return self.eap_method; } pub fn get_phase2_method(&self) -> String { return self.phase2_method; } pub fn get_display_result(&self) -> String { let result: String = String::new(); maybe_append(&self.ssid, &result); maybe_append(&self.network_encryption, &result); maybe_append(&self.password, &result); maybe_append(&Boolean::to_string(self.hidden), &result); return result.to_string(); } } // WifiResultParser.java pub struct WifiResultParser { //super: ResultParser; } impl ResultParser for WifiResultParser {} impl WifiResultParser { pub fn parse(&self, result: &Result) -> WifiParsedResult { let raw_text: String = get_massaged_text(result); if !raw_text.starts_with("WIFI:") { return null; } raw_text = raw_text.substring(&"WIFI:".length()); let ssid: String = match_single_prefixed_field("S:", &raw_text, ';', false); if ssid == null || ssid.is_empty() { return null; } let pass: String = match_single_prefixed_field("P:", &raw_text, ';', false); let mut _type: String = match_single_prefixed_field("T:", &raw_text, ';', false); if _type == null { _type = "nopass".to_owned(); } // Unfortunately, in the past, H: was not just used for boolean 'hidden', but 'phase 2 method'. // To try to retain backwards compatibility, we set one or the other based on whether the string // is 'true' or 'false': let mut hidden: bool = false; let phase2_method: String = match_single_prefixed_field("PH2:", &raw_text, ';', false); let h_value: String = match_single_prefixed_field("H:", &raw_text, ';', false); if h_value != null { // If PH2 was specified separately, or if the value is clearly boolean, interpret it as 'hidden' if phase2_method != null || "true".equals_ignore_case(&h_value) || "false".equals_ignore_case(&h_value) { hidden = Boolean::parse_boolean(&h_value); } else { phase2_method = h_value; } } let identity: String = match_single_prefixed_field("I:", &raw_text, ';', false); let anonymous_identity: String = match_single_prefixed_field("A:", &raw_text, ';', false); let eap_method: String = match_single_prefixed_field("E:", &raw_text, ';', false); return WifiParsedResult::new( &_type, &ssid, &pass, hidden, &identity, &anonymous_identity, &eap_method, &phase2_method, ); } } // ISBNParsedResult.java /** * Represents a parsed result that encodes a product ISBN number. * * @author jbreiden@google.com (Jeff Breidenbach) */ pub struct ISBNParsedResult { //super: ParsedResult; isbn: String, } impl ParsedResult for ISBNParsedResult {} impl ISBNParsedResult { fn new(isbn: &String) -> Self { Self { isbn } } pub fn get_i_s_b_n(&self) -> String { return self.isbn; } pub fn get_display_result(&self) -> String { return self.isbn; } } // ISBNResultParser.java /** * Parses strings of digits that represent a ISBN. * * @author jbreiden@google.com (Jeff Breidenbach) */ pub struct ISBNResultParser { //super: ResultParser; } impl ResultParser for ISBNResultParser {} impl ISBNResultParser { /** * See ISBN-13 For Dummies */ pub fn parse(&self, result: &Result) -> ISBNParsedResult { let format: BarcodeFormat = result.get_barcode_format(); if format != BarcodeFormat::EAN_13 { return null; } let raw_text: String = get_massaged_text(result); let length: i32 = raw_text.length(); if length != 13 { return null; } if !raw_text.starts_with("978") && !raw_text.starts_with("979") { return null; } return ISBNParsedResult::new(&raw_text); } }