/*
* 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 AddressBookAUResultParser(), new BizcardResultParser(),
new GeoResultParser(), 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";
/**
* 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((int) '\\');
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((int) 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 (rawText.charAt(i - 1) == '\\') {
// semicolon was escaped 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(new String[matches.size()]);
}
static String matchSinglePrefixedField(String prefix, String rawText,
char endChar, boolean trim) {
String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
return matches == null ? null : matches[0];
}
}