move client/result

This commit is contained in:
Henry Schimke
2022-08-13 15:13:03 -05:00
parent 357a6a953d
commit 68e7a5f396
75 changed files with 3425 additions and 3935 deletions

View File

@@ -0,0 +1,39 @@
/*
* 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;
/**
* <p>See
* <a href="http://www.nttdocomo.co.jp/english/service/imode/make/content/barcode/about/s2.html">
* DoCoMo's documentation</a> about the result types represented by subclasses of this class.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
abstract class AbstractDoCoMoResultParser extends ResultParser {
static String[] matchDoCoMoPrefixedField(String prefix, String rawText) {
return matchPrefixedField(prefix, rawText, ';', true);
}
static String matchSingleDoCoMoPrefixedField(String prefix, String rawText, boolean trim) {
return matchSinglePrefixedField(prefix, rawText, ';', trim);
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2008 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.Result;
import java.util.ArrayList;
import java.util.List;
/**
* Implements KDDI AU's address book format. See
* <a href="http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html">
* http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html</a>.
* (Thanks to Yuzo for translating!)
*
* @author Sean Owen
*/
public final class AddressBookAUResultParser extends ResultParser {
@Override
public AddressBookParsedResult parse(Result result) {
String rawText = getMassagedText(result);
// MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
if (!rawText.contains("MEMORY") || !rawText.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.
String name = matchSinglePrefixedField("NAME1:", rawText, '\r', true);
String pronunciation = matchSinglePrefixedField("NAME2:", rawText, '\r', true);
String[] phoneNumbers = matchMultipleValuePrefix("TEL", rawText);
String[] emails = matchMultipleValuePrefix("MAIL", rawText);
String note = matchSinglePrefixedField("MEMORY:", rawText, '\r', false);
String address = matchSinglePrefixedField("ADD:", rawText, '\r', true);
String[] addresses = address == null ? null : new String[] {address};
return new AddressBookParsedResult(maybeWrap(name),
null,
pronunciation,
phoneNumbers,
null,
emails,
null,
null,
note,
addresses,
null,
null,
null,
null,
null,
null);
}
private static String[] matchMultipleValuePrefix(String prefix, String rawText) {
List<String> values = null;
// For now, always 3, and always trim
for (int i = 1; i <= 3; i++) {
String value = matchSinglePrefixedField(prefix + i + ':', rawText, '\r', true);
if (value == null) {
break;
}
if (values == null) {
values = new ArrayList<>(3); // lazy init
}
values.add(value);
}
if (values == null) {
return null;
}
return values.toArray(EMPTY_STR_ARRAY);
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.Result;
/**
* 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
*/
public final class AddressBookDoCoMoResultParser extends AbstractDoCoMoResultParser {
@Override
public AddressBookParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("MECARD:")) {
return null;
}
String[] rawName = matchDoCoMoPrefixedField("N:", rawText);
if (rawName == null) {
return null;
}
String name = parseName(rawName[0]);
String pronunciation = matchSingleDoCoMoPrefixedField("SOUND:", rawText, true);
String[] phoneNumbers = matchDoCoMoPrefixedField("TEL:", rawText);
String[] emails = matchDoCoMoPrefixedField("EMAIL:", rawText);
String note = matchSingleDoCoMoPrefixedField("NOTE:", rawText, false);
String[] addresses = matchDoCoMoPrefixedField("ADR:", rawText);
String birthday = matchSingleDoCoMoPrefixedField("BDAY:", rawText, true);
if (!isStringOfDigits(birthday, 8)) {
// No reason to throw out the whole card because the birthday is formatted wrong.
birthday = null;
}
String[] urls = matchDoCoMoPrefixedField("URL:", rawText);
// 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.
String org = matchSingleDoCoMoPrefixedField("ORG:", rawText, true);
return new AddressBookParsedResult(maybeWrap(name),
null,
pronunciation,
phoneNumbers,
null,
emails,
null,
null,
note,
addresses,
null,
org,
birthday,
null,
urls,
null);
}
private static String parseName(String name) {
int comma = name.indexOf(',');
if (comma >= 0) {
// Format may be last,first; switch it around
return name.substring(comma + 1) + ' ' + name.substring(0, comma);
}
return name;
}
}

View File

@@ -0,0 +1,220 @@
/*
* 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;
/**
* Represents a parsed result that encodes contact information, like that in an address book
* entry.
*
* @author Sean Owen
*/
public final class AddressBookParsedResult extends ParsedResult {
private final String[] names;
private final String[] nicknames;
private final String pronunciation;
private final String[] phoneNumbers;
private final String[] phoneTypes;
private final String[] emails;
private final String[] emailTypes;
private final String instantMessenger;
private final String note;
private final String[] addresses;
private final String[] addressTypes;
private final String org;
private final String birthday;
private final String title;
private final String[] urls;
private final String[] geo;
public AddressBookParsedResult(String[] names,
String[] phoneNumbers,
String[] phoneTypes,
String[] emails,
String[] emailTypes,
String[] addresses,
String[] addressTypes) {
this(names,
null,
null,
phoneNumbers,
phoneTypes,
emails,
emailTypes,
null,
null,
addresses,
addressTypes,
null,
null,
null,
null,
null);
}
public AddressBookParsedResult(String[] names,
String[] nicknames,
String pronunciation,
String[] phoneNumbers,
String[] phoneTypes,
String[] emails,
String[] emailTypes,
String instantMessenger,
String note,
String[] addresses,
String[] addressTypes,
String org,
String birthday,
String title,
String[] urls,
String[] geo) {
super(ParsedResultType.ADDRESSBOOK);
if (phoneNumbers != null && phoneTypes != null && phoneNumbers.length != phoneTypes.length) {
throw new IllegalArgumentException("Phone numbers and types lengths differ");
}
if (emails != null && emailTypes != null && emails.length != emailTypes.length) {
throw new IllegalArgumentException("Emails and types lengths differ");
}
if (addresses != null && addressTypes != null && addresses.length != addressTypes.length) {
throw new IllegalArgumentException("Addresses and types lengths differ");
}
this.names = names;
this.nicknames = nicknames;
this.pronunciation = pronunciation;
this.phoneNumbers = phoneNumbers;
this.phoneTypes = phoneTypes;
this.emails = emails;
this.emailTypes = emailTypes;
this.instantMessenger = instantMessenger;
this.note = note;
this.addresses = addresses;
this.addressTypes = addressTypes;
this.org = org;
this.birthday = birthday;
this.title = title;
this.urls = urls;
this.geo = geo;
}
public String[] getNames() {
return names;
}
public String[] getNicknames() {
return 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.
*/
public String getPronunciation() {
return pronunciation;
}
public String[] getPhoneNumbers() {
return phoneNumbers;
}
/**
* @return optional descriptions of the type of each phone number. It could be like "HOME", but,
* there is no guaranteed or standard format.
*/
public String[] getPhoneTypes() {
return phoneTypes;
}
public String[] getEmails() {
return emails;
}
/**
* @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
* there is no guaranteed or standard format.
*/
public String[] getEmailTypes() {
return emailTypes;
}
public String getInstantMessenger() {
return instantMessenger;
}
public String getNote() {
return note;
}
public String[] getAddresses() {
return addresses;
}
/**
* @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
* there is no guaranteed or standard format.
*/
public String[] getAddressTypes() {
return addressTypes;
}
public String getTitle() {
return title;
}
public String getOrg() {
return org;
}
public String[] getURLs() {
return urls;
}
/**
* @return birthday formatted as yyyyMMdd (e.g. 19780917)
*/
public String getBirthday() {
return birthday;
}
/**
* @return a location as a latitude/longitude pair
*/
public String[] getGeo() {
return geo;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(100);
maybeAppend(names, result);
maybeAppend(nicknames, result);
maybeAppend(pronunciation, result);
maybeAppend(title, result);
maybeAppend(org, result);
maybeAppend(addresses, result);
maybeAppend(phoneNumbers, result);
maybeAppend(emails, result);
maybeAppend(instantMessenger, result);
maybeAppend(urls, result);
maybeAppend(birthday, result);
maybeAppend(geo, result);
maybeAppend(note, result);
return result.toString();
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2008 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.Result;
import java.util.ArrayList;
import java.util.List;
/**
* 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
*/
public final class BizcardResultParser extends AbstractDoCoMoResultParser {
// 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
@Override
public AddressBookParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("BIZCARD:")) {
return null;
}
String firstName = matchSingleDoCoMoPrefixedField("N:", rawText, true);
String lastName = matchSingleDoCoMoPrefixedField("X:", rawText, true);
String fullName = buildName(firstName, lastName);
String title = matchSingleDoCoMoPrefixedField("T:", rawText, true);
String org = matchSingleDoCoMoPrefixedField("C:", rawText, true);
String[] addresses = matchDoCoMoPrefixedField("A:", rawText);
String phoneNumber1 = matchSingleDoCoMoPrefixedField("B:", rawText, true);
String phoneNumber2 = matchSingleDoCoMoPrefixedField("M:", rawText, true);
String phoneNumber3 = matchSingleDoCoMoPrefixedField("F:", rawText, true);
String email = matchSingleDoCoMoPrefixedField("E:", rawText, true);
return new AddressBookParsedResult(maybeWrap(fullName),
null,
null,
buildPhoneNumbers(phoneNumber1, phoneNumber2, phoneNumber3),
null,
maybeWrap(email),
null,
null,
null,
addresses,
null,
org,
null,
title,
null,
null);
}
private static String[] buildPhoneNumbers(String number1,
String number2,
String number3) {
List<String> numbers = new ArrayList<>(3);
if (number1 != null) {
numbers.add(number1);
}
if (number2 != null) {
numbers.add(number2);
}
if (number3 != null) {
numbers.add(number3);
}
int size = numbers.size();
if (size == 0) {
return null;
}
return numbers.toArray(new String[size]);
}
private static String buildName(String firstName, String lastName) {
if (firstName == null) {
return lastName;
} else {
return lastName == null ? firstName : firstName + ' ' + lastName;
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.Result;
/**
* @author Sean Owen
*/
public final class BookmarkDoCoMoResultParser extends AbstractDoCoMoResultParser {
@Override
public URIParsedResult parse(Result result) {
String rawText = result.getText();
if (!rawText.startsWith("MEBKM:")) {
return null;
}
String title = matchSingleDoCoMoPrefixedField("TITLE:", rawText, true);
String[] rawUri = matchDoCoMoPrefixedField("URL:", rawText);
if (rawUri == null) {
return null;
}
String uri = rawUri[0];
return URIResultParser.isBasicallyValidURI(uri) ? new URIParsedResult(uri, title) : null;
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2008 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 java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a parsed result that encodes a calendar event at a certain time, optionally
* with attendees and a location.
*
* @author Sean Owen
*/
public final class CalendarParsedResult extends ParsedResult {
private static final Pattern RFC2445_DURATION =
Pattern.compile("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?");
private static final long[] RFC2445_DURATION_FIELD_UNITS = {
7 * 24 * 60 * 60 * 1000L, // 1 week
24 * 60 * 60 * 1000L, // 1 day
60 * 60 * 1000L, // 1 hour
60 * 1000L, // 1 minute
1000L, // 1 second
};
private static final Pattern DATE_TIME = Pattern.compile("[0-9]{8}(T[0-9]{6}Z?)?");
private final String summary;
private final long start;
private final boolean startAllDay;
private final long end;
private final boolean endAllDay;
private final String location;
private final String organizer;
private final String[] attendees;
private final String description;
private final double latitude;
private final double longitude;
public CalendarParsedResult(String summary,
String startString,
String endString,
String durationString,
String location,
String organizer,
String[] attendees,
String description,
double latitude,
double longitude) {
super(ParsedResultType.CALENDAR);
this.summary = summary;
try {
this.start = parseDate(startString);
} catch (ParseException pe) {
throw new IllegalArgumentException(pe.toString());
}
if (endString == null) {
long durationMS = parseDurationMS(durationString);
end = durationMS < 0L ? -1L : start + durationMS;
} else {
try {
this.end = parseDate(endString);
} catch (ParseException pe) {
throw new IllegalArgumentException(pe.toString());
}
}
this.startAllDay = startString.length() == 8;
this.endAllDay = endString != null && endString.length() == 8;
this.location = location;
this.organizer = organizer;
this.attendees = attendees;
this.description = description;
this.latitude = latitude;
this.longitude = longitude;
}
public String getSummary() {
return summary;
}
/**
* @return start time
* @deprecated use {@link #getStartTimestamp()}
*/
@Deprecated
public Date getStart() {
return new Date(start);
}
/**
* @return start time
* @see #getEndTimestamp()
*/
public long getStartTimestamp() {
return start;
}
/**
* @return true if start time was specified as a whole day
*/
public boolean isStartAllDay() {
return startAllDay;
}
/**
* @return event end {@link Date}, or {@code null} if event has no duration
* @deprecated use {@link #getEndTimestamp()}
*/
@Deprecated
public Date getEnd() {
return end < 0L ? null : new Date(end);
}
/**
* @return event end {@link Date}, or -1 if event has no duration
* @see #getStartTimestamp()
*/
public long getEndTimestamp() {
return end;
}
/**
* @return true if end time was specified as a whole day
*/
public boolean isEndAllDay() {
return endAllDay;
}
public String getLocation() {
return location;
}
public String getOrganizer() {
return organizer;
}
public String[] getAttendees() {
return attendees;
}
public String getDescription() {
return description;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(100);
maybeAppend(summary, result);
maybeAppend(format(startAllDay, start), result);
maybeAppend(format(endAllDay, end), result);
maybeAppend(location, result);
maybeAppend(organizer, result);
maybeAppend(attendees, result);
maybeAppend(description, result);
return result.toString();
}
/**
* 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
*/
private static long parseDate(String when) throws ParseException {
if (!DATE_TIME.matcher(when).matches()) {
throw new ParseException(when, 0);
}
if (when.length() == 8) {
// Show only year/month/day
DateFormat format = new SimpleDateFormat("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.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.parse(when).getTime();
}
// The when string can be local time, or UTC if it ends with a Z
if (when.length() == 16 && when.charAt(15) == 'Z') {
long milliseconds = parseDateTimeString(when.substring(0, 15));
Calendar calendar = new GregorianCalendar();
// 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.setTime(new Date(milliseconds));
return milliseconds + calendar.get(Calendar.DST_OFFSET);
}
return parseDateTimeString(when);
}
private static String format(boolean allDay, long date) {
if (date < 0L) {
return null;
}
DateFormat format = allDay
? DateFormat.getDateInstance(DateFormat.MEDIUM)
: DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
return format.format(date);
}
private static long parseDurationMS(CharSequence durationString) {
if (durationString == null) {
return -1L;
}
Matcher m = RFC2445_DURATION.matcher(durationString);
if (!m.matches()) {
return -1L;
}
long durationMS = 0L;
for (int i = 0; i < RFC2445_DURATION_FIELD_UNITS.length; i++) {
String fieldValue = m.group(i + 1);
if (fieldValue != null) {
durationMS += RFC2445_DURATION_FIELD_UNITS[i] * Integer.parseInt(fieldValue);
}
}
return durationMS;
}
private static long parseDateTimeString(String dateTimeString) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
return format.parse(dateTimeString).getTime();
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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;
/**
* Represents a parsed result that encodes an email message including recipients, subject
* and body text.
*
* @author Sean Owen
*/
public final class EmailAddressParsedResult extends ParsedResult {
private final String[] tos;
private final String[] ccs;
private final String[] bccs;
private final String subject;
private final String body;
EmailAddressParsedResult(String to) {
this(new String[] {to}, null, null, null, null);
}
EmailAddressParsedResult(String[] tos,
String[] ccs,
String[] bccs,
String subject,
String body) {
super(ParsedResultType.EMAIL_ADDRESS);
this.tos = tos;
this.ccs = ccs;
this.bccs = bccs;
this.subject = subject;
this.body = body;
}
/**
* @return first elements of {@link #getTos()} or {@code null} if none
* @deprecated use {@link #getTos()}
*/
@Deprecated
public String getEmailAddress() {
return tos == null || tos.length == 0 ? null : tos[0];
}
public String[] getTos() {
return tos;
}
public String[] getCCs() {
return ccs;
}
public String[] getBCCs() {
return bccs;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
/**
* @return "mailto:"
* @deprecated without replacement
*/
@Deprecated
public String getMailtoURI() {
return "mailto:";
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(30);
maybeAppend(tos, result);
maybeAppend(ccs, result);
maybeAppend(bccs, result);
maybeAppend(subject, result);
maybeAppend(body, result);
return result.toString();
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.Result;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 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
*/
public final class EmailAddressResultParser extends ResultParser {
private static final Pattern COMMA = Pattern.compile(",");
@Override
public EmailAddressParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (rawText.startsWith("mailto:") || rawText.startsWith("MAILTO:")) {
// If it starts with mailto:, assume it is definitely trying to be an email address
String hostEmail = rawText.substring(7);
int queryStart = hostEmail.indexOf('?');
if (queryStart >= 0) {
hostEmail = hostEmail.substring(0, queryStart);
}
try {
hostEmail = urlDecode(hostEmail);
} catch (IllegalArgumentException iae) {
return null;
}
String[] tos = null;
if (!hostEmail.isEmpty()) {
tos = COMMA.split(hostEmail);
}
Map<String,String> nameValues = parseNameValuePairs(rawText);
String[] ccs = null;
String[] bccs = null;
String subject = null;
String body = null;
if (nameValues != null) {
if (tos == null) {
String tosString = nameValues.get("to");
if (tosString != null) {
tos = COMMA.split(tosString);
}
}
String ccString = nameValues.get("cc");
if (ccString != null) {
ccs = COMMA.split(ccString);
}
String bccString = nameValues.get("bcc");
if (bccString != null) {
bccs = COMMA.split(bccString);
}
subject = nameValues.get("subject");
body = nameValues.get("body");
}
return new EmailAddressParsedResult(tos, ccs, bccs, subject, body);
} else {
if (!EmailDoCoMoResultParser.isBasicallyValidEmailAddress(rawText)) {
return null;
}
return new EmailAddressParsedResult(rawText);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.Result;
import java.util.regex.Pattern;
/**
* Implements the "MATMSG" email message entry format.
*
* Supported keys: TO, SUB, BODY
*
* @author Sean Owen
*/
public final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser {
private static final Pattern ATEXT_ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+");
@Override
public EmailAddressParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("MATMSG:")) {
return null;
}
String[] tos = matchDoCoMoPrefixedField("TO:", rawText);
if (tos == null) {
return null;
}
for (String to : tos) {
if (!isBasicallyValidEmailAddress(to)) {
return null;
}
}
String subject = matchSingleDoCoMoPrefixedField("SUB:", rawText, false);
String body = matchSingleDoCoMoPrefixedField("BODY:", rawText, false);
return new EmailAddressParsedResult(tos, null, null, subject, 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.
*/
static boolean isBasicallyValidEmailAddress(String email) {
return email != null && ATEXT_ALPHANUMERIC.matcher(email).matches() && email.indexOf('@') >= 0;
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2010 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.
*/
/*
* These authors would like to acknowledge the Spanish Ministry of Industry,
* Tourism and Trade, for the support in the project TSI020301-2008-2
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
* Mobile Dynamic Environments", led by Treelogic
* ( http://www.treelogic.com/ ):
*
* http://www.piramidepse.com/
*/
package com.google.zxing.client.result;
import java.util.Map;
import java.util.Objects;
/**
* 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.
*/
public final class ExpandedProductParsedResult extends ParsedResult {
public static final String KILOGRAM = "KG";
public static final String POUND = "LB";
private final String rawText;
private final String productID;
private final String sscc;
private final String lotNumber;
private final String productionDate;
private final String packagingDate;
private final String bestBeforeDate;
private final String expirationDate;
private final String weight;
private final String weightType;
private final String weightIncrement;
private final String price;
private final String priceIncrement;
private final String priceCurrency;
// For AIS that not exist in this object
private final Map<String,String> uncommonAIs;
public ExpandedProductParsedResult(String rawText,
String productID,
String sscc,
String lotNumber,
String productionDate,
String packagingDate,
String bestBeforeDate,
String expirationDate,
String weight,
String weightType,
String weightIncrement,
String price,
String priceIncrement,
String priceCurrency,
Map<String,String> uncommonAIs) {
super(ParsedResultType.PRODUCT);
this.rawText = rawText;
this.productID = productID;
this.sscc = sscc;
this.lotNumber = lotNumber;
this.productionDate = productionDate;
this.packagingDate = packagingDate;
this.bestBeforeDate = bestBeforeDate;
this.expirationDate = expirationDate;
this.weight = weight;
this.weightType = weightType;
this.weightIncrement = weightIncrement;
this.price = price;
this.priceIncrement = priceIncrement;
this.priceCurrency = priceCurrency;
this.uncommonAIs = uncommonAIs;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ExpandedProductParsedResult)) {
return false;
}
ExpandedProductParsedResult other = (ExpandedProductParsedResult) o;
return Objects.equals(productID, other.productID)
&& Objects.equals(sscc, other.sscc)
&& Objects.equals(lotNumber, other.lotNumber)
&& Objects.equals(productionDate, other.productionDate)
&& Objects.equals(bestBeforeDate, other.bestBeforeDate)
&& Objects.equals(expirationDate, other.expirationDate)
&& Objects.equals(weight, other.weight)
&& Objects.equals(weightType, other.weightType)
&& Objects.equals(weightIncrement, other.weightIncrement)
&& Objects.equals(price, other.price)
&& Objects.equals(priceIncrement, other.priceIncrement)
&& Objects.equals(priceCurrency, other.priceCurrency)
&& Objects.equals(uncommonAIs, other.uncommonAIs);
}
@Override
public int hashCode() {
int hash = Objects.hashCode(productID);
hash ^= Objects.hashCode(sscc);
hash ^= Objects.hashCode(lotNumber);
hash ^= Objects.hashCode(productionDate);
hash ^= Objects.hashCode(bestBeforeDate);
hash ^= Objects.hashCode(expirationDate);
hash ^= Objects.hashCode(weight);
hash ^= Objects.hashCode(weightType);
hash ^= Objects.hashCode(weightIncrement);
hash ^= Objects.hashCode(price);
hash ^= Objects.hashCode(priceIncrement);
hash ^= Objects.hashCode(priceCurrency);
hash ^= Objects.hashCode(uncommonAIs);
return hash;
}
public String getRawText() {
return rawText;
}
public String getProductID() {
return productID;
}
public String getSscc() {
return sscc;
}
public String getLotNumber() {
return lotNumber;
}
public String getProductionDate() {
return productionDate;
}
public String getPackagingDate() {
return packagingDate;
}
public String getBestBeforeDate() {
return bestBeforeDate;
}
public String getExpirationDate() {
return expirationDate;
}
public String getWeight() {
return weight;
}
public String getWeightType() {
return weightType;
}
public String getWeightIncrement() {
return weightIncrement;
}
public String getPrice() {
return price;
}
public String getPriceIncrement() {
return priceIncrement;
}
public String getPriceCurrency() {
return priceCurrency;
}
public Map<String,String> getUncommonAIs() {
return uncommonAIs;
}
@Override
public String getDisplayResult() {
return String.valueOf(rawText);
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) 2010 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.
*/
/*
* These authors would like to acknowledge the Spanish Ministry of Industry,
* Tourism and Trade, for the support in the project TSI020301-2008-2
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
* Mobile Dynamic Environments", led by Treelogic
* ( http://www.treelogic.com/ ):
*
* http://www.piramidepse.com/
*/
package com.google.zxing.client.result;
import java.util.HashMap;
import java.util.Map;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
/**
* 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.
*/
public final class ExpandedProductResultParser extends ResultParser {
@Override
public ExpandedProductParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (format != BarcodeFormat.RSS_EXPANDED) {
// ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode
return null;
}
String rawText = getMassagedText(result);
String productID = null;
String sscc = null;
String lotNumber = null;
String productionDate = null;
String packagingDate = null;
String bestBeforeDate = null;
String expirationDate = null;
String weight = null;
String weightType = null;
String weightIncrement = null;
String price = null;
String priceIncrement = null;
String priceCurrency = null;
Map<String,String> uncommonAIs = new HashMap<>();
int i = 0;
while (i < rawText.length()) {
String ai = findAIvalue(i, rawText);
if (ai == null) {
// Error. Code doesn't match with RSS expanded pattern
// ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
return null;
}
i += ai.length() + 2;
String value = findValue(i, rawText);
i += value.length();
switch (ai) {
case "00":
sscc = value;
break;
case "01":
productID = value;
break;
case "10":
lotNumber = value;
break;
case "11":
productionDate = value;
break;
case "13":
packagingDate = value;
break;
case "15":
bestBeforeDate = value;
break;
case "17":
expirationDate = value;
break;
case "3100":
case "3101":
case "3102":
case "3103":
case "3104":
case "3105":
case "3106":
case "3107":
case "3108":
case "3109":
weight = value;
weightType = ExpandedProductParsedResult.KILOGRAM;
weightIncrement = ai.substring(3);
break;
case "3200":
case "3201":
case "3202":
case "3203":
case "3204":
case "3205":
case "3206":
case "3207":
case "3208":
case "3209":
weight = value;
weightType = ExpandedProductParsedResult.POUND;
weightIncrement = ai.substring(3);
break;
case "3920":
case "3921":
case "3922":
case "3923":
price = value;
priceIncrement = ai.substring(3);
break;
case "3930":
case "3931":
case "3932":
case "3933":
if (value.length() < 4) {
// The value must have more of 3 symbols (3 for currency and
// 1 at least for the price)
// ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
return null;
}
price = value.substring(3);
priceCurrency = value.substring(0, 3);
priceIncrement = ai.substring(3);
break;
default:
// No match with common AIs
uncommonAIs.put(ai, value);
break;
}
}
return new ExpandedProductParsedResult(rawText,
productID,
sscc,
lotNumber,
productionDate,
packagingDate,
bestBeforeDate,
expirationDate,
weight,
weightType,
weightIncrement,
price,
priceIncrement,
priceCurrency,
uncommonAIs);
}
private static String findAIvalue(int i, String rawText) {
char c = rawText.charAt(i);
// First character must be a open parenthesis.If not, ERROR
if (c != '(') {
return null;
}
CharSequence rawTextAux = rawText.substring(i + 1);
StringBuilder buf = new StringBuilder();
for (int index = 0; index < rawTextAux.length(); index++) {
char currentChar = rawTextAux.charAt(index);
if (currentChar == ')') {
return buf.toString();
}
if (currentChar < '0' || currentChar > '9') {
return null;
}
buf.append(currentChar);
}
return buf.toString();
}
private static String findValue(int i, String rawText) {
StringBuilder buf = new StringBuilder();
String rawTextAux = rawText.substring(i);
for (int index = 0; index < rawTextAux.length(); index++) {
char c = rawTextAux.charAt(index);
if (c == '(') {
// We look for a new AI. If it doesn't exist (ERROR), we continue
// with the iteration
if (findAIvalue(index, rawTextAux) != null) {
break;
}
buf.append('(');
} else {
buf.append(c);
}
}
return buf.toString();
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2008 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;
/**
* Represents a parsed result that encodes a geographic coordinate, with latitude,
* longitude and altitude.
*
* @author Sean Owen
*/
public final class GeoParsedResult extends ParsedResult {
private final double latitude;
private final double longitude;
private final double altitude;
private final String query;
GeoParsedResult(double latitude, double longitude, double altitude, String query) {
super(ParsedResultType.GEO);
this.latitude = latitude;
this.longitude = longitude;
this.altitude = altitude;
this.query = query;
}
public String getGeoURI() {
StringBuilder result = new StringBuilder();
result.append("geo:");
result.append(latitude);
result.append(',');
result.append(longitude);
if (altitude > 0) {
result.append(',');
result.append(altitude);
}
if (query != null) {
result.append('?');
result.append(query);
}
return result.toString();
}
/**
* @return latitude in degrees
*/
public double getLatitude() {
return latitude;
}
/**
* @return longitude in degrees
*/
public double getLongitude() {
return longitude;
}
/**
* @return altitude in meters. If not specified, in the geo URI, returns 0.0
*/
public double getAltitude() {
return altitude;
}
/**
* @return query string associated with geo URI or null if none exists
*/
public String getQuery() {
return query;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(20);
result.append(latitude);
result.append(", ");
result.append(longitude);
if (altitude > 0.0) {
result.append(", ");
result.append(altitude);
result.append('m');
}
if (query != null) {
result.append(" (");
result.append(query);
result.append(')');
}
return result.toString();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2008 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.Result;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 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
* <a href="http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00">
* http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00</a>.
*
* @author Sean Owen
*/
public final class GeoResultParser extends ResultParser {
private static final Pattern GEO_URL_PATTERN =
Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
@Override
public GeoParsedResult parse(Result result) {
CharSequence rawText = getMassagedText(result);
Matcher matcher = GEO_URL_PATTERN.matcher(rawText);
if (!matcher.matches()) {
return null;
}
String query = matcher.group(4);
double latitude;
double longitude;
double altitude;
try {
latitude = Double.parseDouble(matcher.group(1));
if (latitude > 90.0 || latitude < -90.0) {
return null;
}
longitude = Double.parseDouble(matcher.group(2));
if (longitude > 180.0 || longitude < -180.0) {
return null;
}
if (matcher.group(3) == null) {
altitude = 0.0;
} else {
altitude = Double.parseDouble(matcher.group(3));
if (altitude < 0.0) {
return null;
}
}
} catch (NumberFormatException ignored) {
return null;
}
return new GeoParsedResult(latitude, longitude, altitude, query);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2008 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;
/**
* Represents a parsed result that encodes a product ISBN number.
*
* @author jbreiden@google.com (Jeff Breidenbach)
*/
public final class ISBNParsedResult extends ParsedResult {
private final String isbn;
ISBNParsedResult(String isbn) {
super(ParsedResultType.ISBN);
this.isbn = isbn;
}
public String getISBN() {
return isbn;
}
@Override
public String getDisplayResult() {
return isbn;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2008 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.BarcodeFormat;
import com.google.zxing.Result;
/**
* Parses strings of digits that represent a ISBN.
*
* @author jbreiden@google.com (Jeff Breidenbach)
*/
public final class ISBNResultParser extends ResultParser {
/**
* See <a href="http://www.bisg.org/isbn-13/for.dummies.html">ISBN-13 For Dummies</a>
*/
@Override
public ISBNParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (format != BarcodeFormat.EAN_13) {
return null;
}
String rawText = getMassagedText(result);
int length = rawText.length();
if (length != 13) {
return null;
}
if (!rawText.startsWith("978") && !rawText.startsWith("979")) {
return null;
}
return new ISBNParsedResult(rawText);
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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;
/**
* <p>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.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
public abstract class ParsedResult {
private final ParsedResultType type;
protected ParsedResult(ParsedResultType type) {
this.type = type;
}
public final ParsedResultType getType() {
return type;
}
public abstract String getDisplayResult();
@Override
public final String toString() {
return getDisplayResult();
}
public static void maybeAppend(String value, StringBuilder result) {
if (value != null && !value.isEmpty()) {
// Don't add a newline before the first value
if (result.length() > 0) {
result.append('\n');
}
result.append(value);
}
}
public static void maybeAppend(String[] values, StringBuilder result) {
if (values != null) {
for (String value : values) {
maybeAppend(value, result);
}
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2010 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;
/**
* Represents the type of data encoded by a barcode -- from plain text, to a
* URI, to an e-mail address, etc.
*
* @author Sean Owen
*/
public enum ParsedResultType {
ADDRESSBOOK,
EMAIL_ADDRESS,
PRODUCT,
URI,
TEXT,
GEO,
TEL,
SMS,
CALENDAR,
WIFI,
ISBN,
VIN,
}

View File

@@ -0,0 +1,52 @@
/*
* 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;
/**
* Represents a parsed result that encodes a product by an identifier of some kind.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ProductParsedResult extends ParsedResult {
private final String productID;
private final String normalizedProductID;
ProductParsedResult(String productID) {
this(productID, productID);
}
ProductParsedResult(String productID, String normalizedProductID) {
super(ParsedResultType.PRODUCT);
this.productID = productID;
this.normalizedProductID = normalizedProductID;
}
public String getProductID() {
return productID;
}
public String getNormalizedProductID() {
return normalizedProductID;
}
@Override
public String getDisplayResult() {
return productID;
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.BarcodeFormat;
import com.google.zxing.Result;
import com.google.zxing.oned.UPCEReader;
/**
* Parses strings of digits that represent a UPC code.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ProductResultParser extends ResultParser {
// Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes.
@Override
public ProductParsedResult parse(Result result) {
BarcodeFormat format = result.getBarcodeFormat();
if (!(format == BarcodeFormat.UPC_A || format == BarcodeFormat.UPC_E ||
format == BarcodeFormat.EAN_8 || format == BarcodeFormat.EAN_13)) {
return null;
}
String rawText = getMassagedText(result);
if (!isStringOfDigits(rawText, rawText.length())) {
return null;
}
// Not actually checking the checksum again here
String normalizedProductID;
// Expand UPC-E for purposes of searching
if (format == BarcodeFormat.UPC_E && rawText.length() == 8) {
normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText);
} else {
normalizedProductID = rawText;
}
return new ProductParsedResult(rawText, normalizedProductID);
}
}

View File

@@ -0,0 +1,261 @@
/*
* 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.Result;
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;
/**
* <p>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.</p>
*
* <p>Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
* on exception-based mechanisms during parsing.</p>
*
* @author Sean Owen
*/
public abstract class ResultParser {
private static final ResultParser[] PARSERS = {
new 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(),
};
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 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
*/
public abstract ParsedResult parse(Result theResult);
protected static String getMassagedText(Result result) {
String text = result.getText();
if (text.startsWith(BYTE_ORDER_MARK)) {
text = text.substring(1);
}
return text;
}
public static ParsedResult parseResult(Result theResult) {
for (ResultParser parser : PARSERS) {
ParsedResult result = parser.parse(theResult);
if (result != null) {
return result;
}
}
return new TextParsedResult(theResult.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<String,String> parseNameValuePairs(String uri) {
int paramStart = uri.indexOf('?');
if (paramStart < 0) {
return null;
}
Map<String,String> 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<String,String> 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<String> 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];
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2008 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.Result;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* <p>Parses an "sms:" URI result, which specifies a number to SMS.
* See <a href="http://tools.ietf.org/html/rfc5724"> RFC 5724</a> on this.</p>
*
* <p>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.</p>
*
* <p>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.</p>
*
* @author Sean Owen
*/
public final class SMSMMSResultParser extends ResultParser {
@Override
public SMSParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) {
return null;
}
// Check up front if this is a URI syntax string with query arguments
Map<String,String> nameValuePairs = parseNameValuePairs(rawText);
String subject = null;
String body = null;
boolean querySyntax = false;
if (nameValuePairs != null && !nameValuePairs.isEmpty()) {
subject = nameValuePairs.get("subject");
body = nameValuePairs.get("body");
querySyntax = true;
}
// Drop sms, query portion
int queryStart = rawText.indexOf('?', 4);
String smsURIWithoutQuery;
// If it's not query syntax, the question mark is part of the subject or message
if (queryStart < 0 || !querySyntax) {
smsURIWithoutQuery = rawText.substring(4);
} else {
smsURIWithoutQuery = rawText.substring(4, queryStart);
}
int lastComma = -1;
int comma;
List<String> numbers = new ArrayList<>(1);
List<String> vias = new ArrayList<>(1);
while ((comma = smsURIWithoutQuery.indexOf(',', lastComma + 1)) > lastComma) {
String numberPart = smsURIWithoutQuery.substring(lastComma + 1, comma);
addNumberVia(numbers, vias, numberPart);
lastComma = comma;
}
addNumberVia(numbers, vias, smsURIWithoutQuery.substring(lastComma + 1));
return new SMSParsedResult(numbers.toArray(EMPTY_STR_ARRAY),
vias.toArray(EMPTY_STR_ARRAY),
subject,
body);
}
private static void addNumberVia(Collection<String> numbers,
Collection<String> vias,
String numberPart) {
int numberEnd = numberPart.indexOf(';');
if (numberEnd < 0) {
numbers.add(numberPart);
vias.add(null);
} else {
numbers.add(numberPart.substring(0, numberEnd));
String maybeVia = numberPart.substring(numberEnd + 1);
String via;
if (maybeVia.startsWith("via=")) {
via = maybeVia.substring(4);
} else {
via = null;
}
vias.add(via);
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2008 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;
/**
* Represents a parsed result that encodes an SMS message, including recipients, subject
* and body text.
*
* @author Sean Owen
*/
public final class SMSParsedResult extends ParsedResult {
private final String[] numbers;
private final String[] vias;
private final String subject;
private final String body;
public SMSParsedResult(String number,
String via,
String subject,
String body) {
super(ParsedResultType.SMS);
this.numbers = new String[] {number};
this.vias = new String[] {via};
this.subject = subject;
this.body = body;
}
public SMSParsedResult(String[] numbers,
String[] vias,
String subject,
String body) {
super(ParsedResultType.SMS);
this.numbers = numbers;
this.vias = vias;
this.subject = subject;
this.body = body;
}
public String getSMSURI() {
StringBuilder result = new StringBuilder();
result.append("sms:");
boolean first = true;
for (int i = 0; i < numbers.length; i++) {
if (first) {
first = false;
} else {
result.append(',');
}
result.append(numbers[i]);
if (vias != null && vias[i] != null) {
result.append(";via=");
result.append(vias[i]);
}
}
boolean hasBody = body != null;
boolean hasSubject = subject != null;
if (hasBody || hasSubject) {
result.append('?');
if (hasBody) {
result.append("body=");
result.append(body);
}
if (hasSubject) {
if (hasBody) {
result.append('&');
}
result.append("subject=");
result.append(subject);
}
}
return result.toString();
}
public String[] getNumbers() {
return numbers;
}
public String[] getVias() {
return vias;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(100);
maybeAppend(numbers, result);
maybeAppend(subject, result);
maybeAppend(body, result);
return result.toString();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2008 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.Result;
/**
* <p>Parses an "smsto:" URI result, whose format is not standardized but appears to be like:
* {@code smsto:number(:body)}.</p>
*
* <p>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.</p>
*
* @author Sean Owen
*/
public final class SMSTOMMSTOResultParser extends ResultParser {
@Override
public SMSParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) {
return null;
}
// Thanks to dominik.wild for suggesting this enhancement to support
// smsto:number:body URIs
String number = rawText.substring(6);
String body = null;
int bodyStart = number.indexOf(':');
if (bodyStart >= 0) {
body = number.substring(bodyStart + 1);
number = number.substring(0, bodyStart);
}
return new SMSParsedResult(number, null, null, body);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2010 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.Result;
/**
* <p>Parses an "smtp:" URI result, whose format is not standardized but appears to be like:
* {@code smtp[:subject[:body]]}.</p>
*
* @author Sean Owen
*/
public final class SMTPResultParser extends ResultParser {
@Override
public EmailAddressParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!(rawText.startsWith("smtp:") || rawText.startsWith("SMTP:"))) {
return null;
}
String emailAddress = rawText.substring(5);
String subject = null;
String body = null;
int colon = emailAddress.indexOf(':');
if (colon >= 0) {
subject = emailAddress.substring(colon + 1);
emailAddress = emailAddress.substring(0, colon);
colon = subject.indexOf(':');
if (colon >= 0) {
body = subject.substring(colon + 1);
subject = subject.substring(0, colon);
}
}
return new EmailAddressParsedResult(new String[] {emailAddress},
null,
null,
subject,
body);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2008 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;
/**
* Represents a parsed result that encodes a telephone number.
*
* @author Sean Owen
*/
public final class TelParsedResult extends ParsedResult {
private final String number;
private final String telURI;
private final String title;
public TelParsedResult(String number, String telURI, String title) {
super(ParsedResultType.TEL);
this.number = number;
this.telURI = telURI;
this.title = title;
}
public String getNumber() {
return number;
}
public String getTelURI() {
return telURI;
}
public String getTitle() {
return title;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(20);
maybeAppend(number, result);
maybeAppend(title, result);
return result.toString();
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2008 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.Result;
/**
* Parses a "tel:" URI result, which specifies a phone number.
*
* @author Sean Owen
*/
public final class TelResultParser extends ResultParser {
@Override
public TelParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("tel:") && !rawText.startsWith("TEL:")) {
return null;
}
// Normalize "TEL:" to "tel:"
String telURI = rawText.startsWith("TEL:") ? "tel:" + rawText.substring(4) : rawText;
// Drop tel, query portion
int queryStart = rawText.indexOf('?', 4);
String number = queryStart < 0 ? rawText.substring(4) : rawText.substring(4, queryStart);
return new TelParsedResult(number, telURI, null);
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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;
/**
* A simple result type encapsulating a string that has no further
* interpretation.
*
* @author Sean Owen
*/
public final class TextParsedResult extends ParsedResult {
private final String text;
private final String language;
public TextParsedResult(String text, String language) {
super(ParsedResultType.TEXT);
this.text = text;
this.language = language;
}
public String getText() {
return text;
}
public String getLanguage() {
return language;
}
@Override
public String getDisplayResult() {
return text;
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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;
/**
* A simple result type encapsulating a URI that has no further interpretation.
*
* @author Sean Owen
*/
public final class URIParsedResult extends ParsedResult {
private final String uri;
private final String title;
public URIParsedResult(String uri, String title) {
super(ParsedResultType.URI);
this.uri = massageURI(uri);
this.title = title;
}
public String getURI() {
return uri;
}
public String getTitle() {
return 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)}
*/
@Deprecated
public boolean isPossiblyMaliciousURI() {
return URIResultParser.isPossiblyMaliciousURI(uri);
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(30);
maybeAppend(title, result);
maybeAppend(uri, result);
return result.toString();
}
/**
* Transforms a string that represents a URI into something more proper, by adding or canonicalizing
* the protocol.
*/
private static String massageURI(String uri) {
uri = uri.trim();
int protocolEnd = uri.indexOf(':');
if (protocolEnd < 0 || isColonFollowedByPortNumber(uri, protocolEnd)) {
// 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 = "http://" + uri;
}
return uri;
}
private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) {
int start = protocolEnd + 1;
int nextSlash = uri.indexOf('/', start);
if (nextSlash < 0) {
nextSlash = uri.length();
}
return ResultParser.isSubstringOfDigits(uri, start, nextSlash - start);
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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.Result;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Tries to parse results that are a URI of some kind.
*
* @author Sean Owen
*/
public final class URIResultParser extends ResultParser {
private static final Pattern ALLOWED_URI_CHARS_PATTERN =
Pattern.compile("[-._~:/?#\\[\\]@!$&'()*+,;=%A-Za-z0-9]+");
private static final Pattern USER_IN_HOST = Pattern.compile(":/*([^/@]+)@[^/]+");
// See http://www.ietf.org/rfc/rfc2396.txt
private static final Pattern URL_WITH_PROTOCOL_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-.]+:");
private static final Pattern URL_WITHOUT_PROTOCOL_PATTERN = Pattern.compile(
"([a-zA-Z0-9\\-]+\\.){1,6}[a-zA-Z]{2,}" + // host name elements; allow up to say 6 domain elements
"(:\\d{1,5})?" + // maybe port
"(/|\\?|$)"); // query, path or nothing
@Override
public URIParsedResult parse(Result result) {
String rawText = getMassagedText(result);
// We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun
// Assume anything starting this way really means to be a URI
if (rawText.startsWith("URL:") || rawText.startsWith("URI:")) {
return new URIParsedResult(rawText.substring(4).trim(), null);
}
rawText = rawText.trim();
if (!isBasicallyValidURI(rawText) || isPossiblyMaliciousURI(rawText)) {
return null;
}
return new URIParsedResult(rawText, 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.
*/
static boolean isPossiblyMaliciousURI(String uri) {
return !ALLOWED_URI_CHARS_PATTERN.matcher(uri).matches() || USER_IN_HOST.matcher(uri).find();
}
static boolean isBasicallyValidURI(String uri) {
if (uri.contains(" ")) {
// Quick hack check for a common case
return false;
}
Matcher m = 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;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.Result;
/**
* 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
*/
public final class URLTOResultParser extends ResultParser {
@Override
public URIParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:")) {
return null;
}
int titleEnd = rawText.indexOf(':', 6);
if (titleEnd < 0) {
return null;
}
String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd);
String uri = rawText.substring(titleEnd + 1);
return new URIParsedResult(uri, title);
}
}

View File

@@ -0,0 +1,374 @@
/*
* Copyright 2008 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.Result;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 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
*/
public final class VCardResultParser extends ResultParser {
private static final Pattern BEGIN_VCARD = Pattern.compile("BEGIN:VCARD", Pattern.CASE_INSENSITIVE);
private static final Pattern VCARD_LIKE_DATE = Pattern.compile("\\d{4}-?\\d{2}-?\\d{2}");
private static final Pattern CR_LF_SPACE_TAB = Pattern.compile("\r\n[ \t]");
private static final Pattern NEWLINE_ESCAPE = Pattern.compile("\\\\[nN]");
private static final Pattern VCARD_ESCAPES = Pattern.compile("\\\\([,;\\\\])");
private static final Pattern EQUALS = Pattern.compile("=");
private static final Pattern SEMICOLON = Pattern.compile(";");
private static final Pattern UNESCAPED_SEMICOLONS = Pattern.compile("(?<!\\\\);+");
private static final Pattern COMMA = Pattern.compile(",");
private static final Pattern SEMICOLON_OR_COMMA = Pattern.compile("[;,]");
@Override
public AddressBookParsedResult parse(Result result) {
// 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.
String rawText = getMassagedText(result);
Matcher m = BEGIN_VCARD.matcher(rawText);
if (!m.find() || m.start() != 0) {
return null;
}
List<List<String>> names = matchVCardPrefixedField("FN", rawText, true, false);
if (names == null) {
// If no display names found, look for regular name fields and format them
names = matchVCardPrefixedField("N", rawText, true, false);
formatNames(names);
}
List<String> nicknameString = matchSingleVCardPrefixedField("NICKNAME", rawText, true, false);
String[] nicknames = nicknameString == null ? null : COMMA.split(nicknameString.get(0));
List<List<String>> phoneNumbers = matchVCardPrefixedField("TEL", rawText, true, false);
List<List<String>> emails = matchVCardPrefixedField("EMAIL", rawText, true, false);
List<String> note = matchSingleVCardPrefixedField("NOTE", rawText, false, false);
List<List<String>> addresses = matchVCardPrefixedField("ADR", rawText, true, true);
List<String> org = matchSingleVCardPrefixedField("ORG", rawText, true, true);
List<String> birthday = matchSingleVCardPrefixedField("BDAY", rawText, true, false);
if (birthday != null && !isLikeVCardDate(birthday.get(0))) {
birthday = null;
}
List<String> title = matchSingleVCardPrefixedField("TITLE", rawText, true, false);
List<List<String>> urls = matchVCardPrefixedField("URL", rawText, true, false);
List<String> instantMessenger = matchSingleVCardPrefixedField("IMPP", rawText, true, false);
List<String> geoString = matchSingleVCardPrefixedField("GEO", rawText, true, false);
String[] geo = geoString == null ? null : SEMICOLON_OR_COMMA.split(geoString.get(0));
if (geo != null && geo.length != 2) {
geo = null;
}
return new AddressBookParsedResult(toPrimaryValues(names),
nicknames,
null,
toPrimaryValues(phoneNumbers),
toTypes(phoneNumbers),
toPrimaryValues(emails),
toTypes(emails),
toPrimaryValue(instantMessenger),
toPrimaryValue(note),
toPrimaryValues(addresses),
toTypes(addresses),
toPrimaryValue(org),
toPrimaryValue(birthday),
toPrimaryValue(title),
toPrimaryValues(urls),
geo);
}
static List<List<String>> matchVCardPrefixedField(CharSequence prefix,
String rawText,
boolean trim,
boolean parseFieldDivider) {
List<List<String>> matches = null;
int i = 0;
int max = rawText.length();
while (i < max) {
// At start or after newline, match prefix, followed by optional metadata
// (led by ;) ultimately ending in colon
Matcher matcher = Pattern.compile("(?:^|\n)" + prefix + "(?:;([^:]*))?:",
Pattern.CASE_INSENSITIVE).matcher(rawText);
if (i > 0) {
i--; // Find from i-1 not i since looking at the preceding character
}
if (!matcher.find(i)) {
break;
}
i = matcher.end(0); // group 0 = whole pattern; end(0) is past final colon
String metadataString = matcher.group(1); // group 1 = metadata substring
List<String> metadata = null;
boolean quotedPrintable = false;
String quotedPrintableCharset = null;
String valueType = null;
if (metadataString != null) {
for (String metadatum : SEMICOLON.split(metadataString)) {
if (metadata == null) {
metadata = new ArrayList<>(1);
}
metadata.add(metadatum);
String[] metadatumTokens = EQUALS.split(metadatum, 2);
if (metadatumTokens.length > 1) {
String key = metadatumTokens[0];
String value = metadatumTokens[1];
if ("ENCODING".equalsIgnoreCase(key) && "QUOTED-PRINTABLE".equalsIgnoreCase(value)) {
quotedPrintable = true;
} else if ("CHARSET".equalsIgnoreCase(key)) {
quotedPrintableCharset = value;
} else if ("VALUE".equalsIgnoreCase(key)) {
valueType = value;
}
}
}
}
int matchStart = i; // Found the start of a match here
while ((i = rawText.indexOf('\n', i)) >= 0) { // Really, end in \r\n
if (i < rawText.length() - 1 && // But if followed by tab or space,
(rawText.charAt(i + 1) == ' ' || // this is only a continuation
rawText.charAt(i + 1) == '\t')) {
i += 2; // Skip \n and continutation whitespace
} else if (quotedPrintable && // If preceded by = in quoted printable
((i >= 1 && rawText.charAt(i - 1) == '=') || // this is a continuation
(i >= 2 && rawText.charAt(i - 2) == '='))) {
i++; // Skip \n
} else {
break;
}
}
if (i < 0) {
// No terminating end character? uh, done. Set i such that loop terminates and break
i = max;
} else if (i > matchStart) {
// found a match
if (matches == null) {
matches = new ArrayList<>(1); // lazy init
}
if (i >= 1 && rawText.charAt(i - 1) == '\r') {
i--; // Back up over \r, which really should be there
}
String element = rawText.substring(matchStart, i);
if (trim) {
element = element.trim();
}
if (quotedPrintable) {
element = decodeQuotedPrintable(element, quotedPrintableCharset);
if (parseFieldDivider) {
element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
}
} else {
if (parseFieldDivider) {
element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
}
element = CR_LF_SPACE_TAB.matcher(element).replaceAll("");
element = NEWLINE_ESCAPE.matcher(element).replaceAll("\n");
element = VCARD_ESCAPES.matcher(element).replaceAll("$1");
}
// Only handle VALUE=uri specially
if ("uri".equals(valueType)) {
// Don't actually support dereferencing URIs, but use scheme-specific part not URI
// as value, to support tel: and mailto:
try {
element = URI.create(element).getSchemeSpecificPart();
} catch (IllegalArgumentException iae) {
// ignore
}
}
if (metadata == null) {
List<String> match = new ArrayList<>(1);
match.add(element);
matches.add(match);
} else {
metadata.add(0, element);
matches.add(metadata);
}
i++;
} else {
i++;
}
}
return matches;
}
private static String decodeQuotedPrintable(CharSequence value, String charset) {
int length = value.length();
StringBuilder result = new StringBuilder(length);
ByteArrayOutputStream fragmentBuffer = new ByteArrayOutputStream();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
switch (c) {
case '\r':
case '\n':
break;
case '=':
if (i < length - 2) {
char nextChar = value.charAt(i + 1);
if (nextChar != '\r' && nextChar != '\n') {
char nextNextChar = value.charAt(i + 2);
int firstDigit = parseHexDigit(nextChar);
int secondDigit = parseHexDigit(nextNextChar);
if (firstDigit >= 0 && secondDigit >= 0) {
fragmentBuffer.write((firstDigit << 4) + secondDigit);
} // else ignore it, assume it was incorrectly encoded
i += 2;
}
}
break;
default:
maybeAppendFragment(fragmentBuffer, charset, result);
result.append(c);
}
}
maybeAppendFragment(fragmentBuffer, charset, result);
return result.toString();
}
private static void maybeAppendFragment(ByteArrayOutputStream fragmentBuffer,
String charset,
StringBuilder result) {
if (fragmentBuffer.size() > 0) {
byte[] fragmentBytes = fragmentBuffer.toByteArray();
String fragment;
if (charset == null) {
fragment = new String(fragmentBytes, StandardCharsets.UTF_8);
} else {
try {
fragment = new String(fragmentBytes, charset);
} catch (UnsupportedEncodingException e) {
fragment = new String(fragmentBytes, StandardCharsets.UTF_8);
}
}
fragmentBuffer.reset();
result.append(fragment);
}
}
static List<String> matchSingleVCardPrefixedField(CharSequence prefix,
String rawText,
boolean trim,
boolean parseFieldDivider) {
List<List<String>> values = matchVCardPrefixedField(prefix, rawText, trim, parseFieldDivider);
return values == null || values.isEmpty() ? null : values.get(0);
}
private static String toPrimaryValue(List<String> list) {
return list == null || list.isEmpty() ? null : list.get(0);
}
private static String[] toPrimaryValues(Collection<List<String>> lists) {
if (lists == null || lists.isEmpty()) {
return null;
}
List<String> result = new ArrayList<>(lists.size());
for (List<String> list : lists) {
String value = list.get(0);
if (value != null && !value.isEmpty()) {
result.add(value);
}
}
return result.toArray(EMPTY_STR_ARRAY);
}
private static String[] toTypes(Collection<List<String>> lists) {
if (lists == null || lists.isEmpty()) {
return null;
}
List<String> result = new ArrayList<>(lists.size());
for (List<String> list : lists) {
String value = list.get(0);
if (value != null && !value.isEmpty()) {
String type = null;
for (int i = 1; i < list.size(); i++) {
String metadatum = list.get(i);
int equals = metadatum.indexOf('=');
if (equals < 0) {
// take the whole thing as a usable label
type = metadatum;
break;
}
if ("TYPE".equalsIgnoreCase(metadatum.substring(0, equals))) {
type = metadatum.substring(equals + 1);
break;
}
}
result.add(type);
}
}
return result.toArray(EMPTY_STR_ARRAY);
}
private static boolean isLikeVCardDate(CharSequence value) {
return value == null || VCARD_LIKE_DATE.matcher(value).matches();
}
/**
* 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
*/
private static void formatNames(Iterable<List<String>> names) {
if (names != null) {
for (List<String> list : names) {
String name = list.get(0);
String[] components = new String[5];
int start = 0;
int end;
int componentIndex = 0;
while (componentIndex < components.length - 1 && (end = name.indexOf(';', start)) >= 0) {
components[componentIndex] = name.substring(start, end);
componentIndex++;
start = end + 1;
}
components[componentIndex] = name.substring(start);
StringBuilder newName = new StringBuilder(100);
maybeAppendComponent(components, 3, newName);
maybeAppendComponent(components, 1, newName);
maybeAppendComponent(components, 2, newName);
maybeAppendComponent(components, 0, newName);
maybeAppendComponent(components, 4, newName);
list.set(0, newName.toString().trim());
}
}
}
private static void maybeAppendComponent(String[] components, int i, StringBuilder newName) {
if (components[i] != null && !components[i].isEmpty()) {
if (newName.length() > 0) {
newName.append(' ');
}
newName.append(components[i]);
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2008 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.Result;
import java.util.List;
/**
* 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
*/
public final class VEventResultParser extends ResultParser {
@Override
public CalendarParsedResult parse(Result result) {
String rawText = getMassagedText(result);
int vEventStart = rawText.indexOf("BEGIN:VEVENT");
if (vEventStart < 0) {
return null;
}
String summary = matchSingleVCardPrefixedField("SUMMARY", rawText);
String start = matchSingleVCardPrefixedField("DTSTART", rawText);
if (start == null) {
return null;
}
String end = matchSingleVCardPrefixedField("DTEND", rawText);
String duration = matchSingleVCardPrefixedField("DURATION", rawText);
String location = matchSingleVCardPrefixedField("LOCATION", rawText);
String organizer = stripMailto(matchSingleVCardPrefixedField("ORGANIZER", rawText));
String[] attendees = matchVCardPrefixedField("ATTENDEE", rawText);
if (attendees != null) {
for (int i = 0; i < attendees.length; i++) {
attendees[i] = stripMailto(attendees[i]);
}
}
String description = matchSingleVCardPrefixedField("DESCRIPTION", rawText);
String geoString = matchSingleVCardPrefixedField("GEO", rawText);
double latitude;
double longitude;
if (geoString == null) {
latitude = Double.NaN;
longitude = Double.NaN;
} else {
int semicolon = geoString.indexOf(';');
if (semicolon < 0) {
return null;
}
try {
latitude = Double.parseDouble(geoString.substring(0, semicolon));
longitude = Double.parseDouble(geoString.substring(semicolon + 1));
} catch (NumberFormatException ignored) {
return null;
}
}
try {
return new CalendarParsedResult(summary,
start,
end,
duration,
location,
organizer,
attendees,
description,
latitude,
longitude);
} catch (IllegalArgumentException ignored) {
return null;
}
}
private static String matchSingleVCardPrefixedField(CharSequence prefix,
String rawText) {
List<String> values = VCardResultParser.matchSingleVCardPrefixedField(prefix, rawText, true, false);
return values == null || values.isEmpty() ? null : values.get(0);
}
private static String[] matchVCardPrefixedField(CharSequence prefix, String rawText) {
List<List<String>> values = VCardResultParser.matchVCardPrefixedField(prefix, rawText, true, false);
if (values == null || values.isEmpty()) {
return null;
}
int size = values.size();
String[] result = new String[size];
for (int i = 0; i < size; i++) {
result[i] = values.get(i).get(0);
}
return result;
}
private static String stripMailto(String s) {
if (s != null && (s.startsWith("mailto:") || s.startsWith("MAILTO:"))) {
s = s.substring(7);
}
return s;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2014 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;
/**
* Represents a parsed result that encodes a Vehicle Identification Number (VIN).
*/
public final class VINParsedResult extends ParsedResult {
private final String vin;
private final String worldManufacturerID;
private final String vehicleDescriptorSection;
private final String vehicleIdentifierSection;
private final String countryCode;
private final String vehicleAttributes;
private final int modelYear;
private final char plantCode;
private final String sequentialNumber;
public VINParsedResult(String vin,
String worldManufacturerID,
String vehicleDescriptorSection,
String vehicleIdentifierSection,
String countryCode,
String vehicleAttributes,
int modelYear,
char plantCode,
String sequentialNumber) {
super(ParsedResultType.VIN);
this.vin = vin;
this.worldManufacturerID = worldManufacturerID;
this.vehicleDescriptorSection = vehicleDescriptorSection;
this.vehicleIdentifierSection = vehicleIdentifierSection;
this.countryCode = countryCode;
this.vehicleAttributes = vehicleAttributes;
this.modelYear = modelYear;
this.plantCode = plantCode;
this.sequentialNumber = sequentialNumber;
}
public String getVIN() {
return vin;
}
public String getWorldManufacturerID() {
return worldManufacturerID;
}
public String getVehicleDescriptorSection() {
return vehicleDescriptorSection;
}
public String getVehicleIdentifierSection() {
return vehicleIdentifierSection;
}
public String getCountryCode() {
return countryCode;
}
public String getVehicleAttributes() {
return vehicleAttributes;
}
public int getModelYear() {
return modelYear;
}
public char getPlantCode() {
return plantCode;
}
public String getSequentialNumber() {
return sequentialNumber;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(50);
result.append(worldManufacturerID).append(' ');
result.append(vehicleDescriptorSection).append(' ');
result.append(vehicleIdentifierSection).append('\n');
if (countryCode != null) {
result.append(countryCode).append(' ');
}
result.append(modelYear).append(' ');
result.append(plantCode).append(' ');
result.append(sequentialNumber).append('\n');
return result.toString();
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2014 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.BarcodeFormat;
import com.google.zxing.Result;
import java.util.regex.Pattern;
/**
* Detects a result that is likely a vehicle identification number.
*
* @author Sean Owen
*/
public final class VINResultParser extends ResultParser {
private static final Pattern IOQ = Pattern.compile("[IOQ]");
private static final Pattern AZ09 = Pattern.compile("[A-Z0-9]{17}");
@Override
public VINParsedResult parse(Result result) {
if (result.getBarcodeFormat() != BarcodeFormat.CODE_39) {
return null;
}
String rawText = result.getText();
rawText = IOQ.matcher(rawText).replaceAll("").trim();
if (!AZ09.matcher(rawText).matches()) {
return null;
}
try {
if (!checkChecksum(rawText)) {
return null;
}
String wmi = rawText.substring(0, 3);
return new VINParsedResult(rawText,
wmi,
rawText.substring(3, 9),
rawText.substring(9, 17),
countryCode(wmi),
rawText.substring(3, 8),
modelYear(rawText.charAt(9)),
rawText.charAt(10),
rawText.substring(11));
} catch (IllegalArgumentException iae) {
return null;
}
}
private static boolean checkChecksum(CharSequence vin) {
int sum = 0;
for (int i = 0; i < vin.length(); i++) {
sum += vinPositionWeight(i + 1) * vinCharValue(vin.charAt(i));
}
char checkChar = vin.charAt(8);
char expectedCheckChar = checkChar(sum % 11);
return checkChar == expectedCheckChar;
}
private static int vinCharValue(char c) {
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';
}
throw new IllegalArgumentException();
}
private static int vinPositionWeight(int position) {
if (position >= 1 && position <= 7) {
return 9 - position;
}
if (position == 8) {
return 10;
}
if (position == 9) {
return 0;
}
if (position >= 10 && position <= 17) {
return 19 - position;
}
throw new IllegalArgumentException();
}
private static char checkChar(int remainder) {
if (remainder < 10) {
return (char) ('0' + remainder);
}
if (remainder == 10) {
return 'X';
}
throw new IllegalArgumentException();
}
private static int modelYear(char c) {
if (c >= 'E' && c <= 'H') {
return (c - 'E') + 1984;
}
if (c >= 'J' && c <= 'N') {
return (c - 'J') + 1988;
}
if (c == 'P') {
return 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;
}
throw new IllegalArgumentException();
}
private static String countryCode(CharSequence wmi) {
char c1 = wmi.charAt(0);
char c2 = wmi.charAt(1);
switch (c1) {
case '1':
case '4':
case '5':
return "US";
case '2':
return "CA";
case '3':
if (c2 >= 'A' && c2 <= 'W') {
return "MX";
}
break;
case '9':
if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) {
return "BR";
}
break;
case 'J':
if (c2 >= 'A' && c2 <= 'T') {
return "JP";
}
break;
case 'K':
if (c2 >= 'L' && c2 <= 'R') {
return "KO";
}
break;
case 'L':
return "CN";
case 'M':
if (c2 >= 'A' && c2 <= 'E') {
return "IN";
}
break;
case 'S':
if (c2 >= 'A' && c2 <= 'M') {
return "UK";
}
if (c2 >= 'N' && c2 <= 'T') {
return "DE";
}
break;
case 'V':
if (c2 >= 'F' && c2 <= 'R') {
return "FR";
}
if (c2 >= 'S' && c2 <= 'W') {
return "ES";
}
break;
case 'W':
return "DE";
case 'X':
if (c2 == '0' || (c2 >= '3' && c2 <= '9')) {
return "RU";
}
break;
case 'Z':
if (c2 >= 'A' && c2 <= 'R') {
return "IT";
}
break;
}
return null;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2010 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;
/**
* Represents a parsed result that encodes wifi network information, like SSID and password.
*
* @author Vikram Aggarwal
*/
public final class WifiParsedResult extends ParsedResult {
private final String ssid;
private final String networkEncryption;
private final String password;
private final boolean hidden;
private final String identity;
private final String anonymousIdentity;
private final String eapMethod;
private final String phase2Method;
public WifiParsedResult(String networkEncryption, String ssid, String password) {
this(networkEncryption, ssid, password, false);
}
public WifiParsedResult(String networkEncryption, String ssid, String password, boolean hidden) {
this(networkEncryption, ssid, password, hidden, null, null, null, null);
}
public WifiParsedResult(String networkEncryption,
String ssid,
String password,
boolean hidden,
String identity,
String anonymousIdentity,
String eapMethod,
String phase2Method) {
super(ParsedResultType.WIFI);
this.ssid = ssid;
this.networkEncryption = networkEncryption;
this.password = password;
this.hidden = hidden;
this.identity = identity;
this.anonymousIdentity = anonymousIdentity;
this.eapMethod = eapMethod;
this.phase2Method = phase2Method;
}
public String getSsid() {
return ssid;
}
public String getNetworkEncryption() {
return networkEncryption;
}
public String getPassword() {
return password;
}
public boolean isHidden() {
return hidden;
}
public String getIdentity() {
return identity;
}
public String getAnonymousIdentity() {
return anonymousIdentity;
}
public String getEapMethod() {
return eapMethod;
}
public String getPhase2Method() {
return phase2Method;
}
@Override
public String getDisplayResult() {
StringBuilder result = new StringBuilder(80);
maybeAppend(ssid, result);
maybeAppend(networkEncryption, result);
maybeAppend(password, result);
maybeAppend(Boolean.toString(hidden), result);
return result.toString();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2010 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.Result;
@SuppressWarnings("checkstyle:lineLength")
/**
* <p>Parses a WIFI configuration string. Strings will be of the form:</p>
*
* <p>{@code WIFI:T:[network type];S:[network SSID];P:[network password];H:[hidden?];;}</p>
*
* <p>For WPA2 enterprise (EAP), strings will be of the form:</p>
*
* <p>{@code WIFI:T:WPA2-EAP;S:[network SSID];H:[hidden?];E:[EAP method];PH2:[Phase 2 method];A:[anonymous identity];I:[username];P:[password];;}</p>
*
* <p>"EAP method" can e.g. be "TTLS" or "PWD" or one of the other fields in <a href="https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Eap.html">WifiEnterpriseConfig.Eap</a> and "Phase 2 method" can e.g. be "MSCHAPV2" or any of the other fields in <a href="https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Phase2.html">WifiEnterpriseConfig.Phase2</a></p>
*
* <p>The fields can appear in any order. Only "S:" is required.</p>
*
* @author Vikram Aggarwal
* @author Sean Owen
* @author Steffen Kieß
*/
public final class WifiResultParser extends ResultParser {
@Override
public WifiParsedResult parse(Result result) {
String rawText = getMassagedText(result);
if (!rawText.startsWith("WIFI:")) {
return null;
}
rawText = rawText.substring("WIFI:".length());
String ssid = matchSinglePrefixedField("S:", rawText, ';', false);
if (ssid == null || ssid.isEmpty()) {
return null;
}
String pass = matchSinglePrefixedField("P:", rawText, ';', false);
String type = matchSinglePrefixedField("T:", rawText, ';', false);
if (type == null) {
type = "nopass";
}
// 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':
boolean hidden = false;
String phase2Method = matchSinglePrefixedField("PH2:", rawText, ';', false);
String hValue = matchSinglePrefixedField("H:", rawText, ';', false);
if (hValue != null) {
// If PH2 was specified separately, or if the value is clearly boolean, interpret it as 'hidden'
if (phase2Method != null || "true".equalsIgnoreCase(hValue) || "false".equalsIgnoreCase(hValue)) {
hidden = Boolean.parseBoolean(hValue);
} else {
phase2Method = hValue;
}
}
String identity = matchSinglePrefixedField("I:", rawText, ';', false);
String anonymousIdentity = matchSinglePrefixedField("A:", rawText, ';', false);
String eapMethod = matchSinglePrefixedField("E:", rawText, ';', false);
return new WifiParsedResult(type, ssid, pass, hidden, identity, anonymousIdentity, eapMethod, phase2Method);
}
}