From: srowen Date: Sat, 24 Apr 2010 09:28:10 +0000 (+0000) Subject: Add ENCODING=QUOTED-PRINTABLE support X-Git-Url: http://git.rot13.org/?p=zxing.git;a=commitdiff_plain;h=7b6cd7e45e9d84810d896b7aadc4539d49d3095d Add ENCODING=QUOTED-PRINTABLE support git-svn-id: http://zxing.googlecode.com/svn/trunk@1317 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- diff --git a/core/src/com/google/zxing/client/result/VCardResultParser.java b/core/src/com/google/zxing/client/result/VCardResultParser.java index b43aa4f8..4e32ba83 100644 --- a/core/src/com/google/zxing/client/result/VCardResultParser.java +++ b/core/src/com/google/zxing/client/result/VCardResultParser.java @@ -69,11 +69,14 @@ final class VCardResultParser extends ResultParser { Vector matches = null; int i = 0; int max = rawText.length(); + while (i < max) { + i = rawText.indexOf(prefix, i); if (i < 0) { break; } + if (i > 0 && rawText.charAt(i - 1) != '\n') { // then this didn't start a new token, we matched in the middle of something i++; @@ -83,39 +86,81 @@ final class VCardResultParser extends ResultParser { if (rawText.charAt(i) != ':' && rawText.charAt(i) != ';') { continue; } + + int metadataStart = i; while (rawText.charAt(i) != ':') { // Skip until a colon i++; } + + boolean quotedPrintable = false; + if (i > metadataStart) { + // There was something after the tag, before colon + int j = metadataStart+1; + while (j <= i) { + if (rawText.charAt(j) == ';' || rawText.charAt(j) == ':') { + String metadata = rawText.substring(metadataStart+1, j); + int equals = metadata.indexOf('='); + if (equals >= 0) { + String key = metadata.substring(0, equals); + String value = metadata.substring(equals+1); + if (key.equalsIgnoreCase("ENCODING")) { + if (value.equalsIgnoreCase("QUOTED-PRINTABLE")) { + quotedPrintable = true; + } + } + } + metadataStart = j; + } + j++; + } + } + i++; // skip colon - int start = i; // Found the start of a match here - while ((i = rawText.indexOf((int) '\n', i)) >= 0 && // Really, ends in \r\n - 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; + + int matchStart = i; // Found the start of a match here + + while ((i = rawText.indexOf((int) '\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 + (rawText.charAt(i-1) == '=' || // this is a continuation + 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 > start) { + } else if (i > matchStart) { // found a match if (matches == null) { - matches = new Vector(3); // lazy init + matches = new Vector(1); // lazy init } if (rawText.charAt(i-1) == '\r') { i--; // Back up over \r, which really should be there } - String element = rawText.substring(start, i); + String element = rawText.substring(matchStart, i); if (trim) { element = element.trim(); } - element = stripContinuationCRLF(element); + if (quotedPrintable) { + element = decodeQuotedPrintable(element); + } else { + element = stripContinuationCRLF(element); + } matches.addElement(element); i++; } else { i++; } + } + if (matches == null || matches.isEmpty()) { return null; } @@ -146,6 +191,50 @@ final class VCardResultParser extends ResultParser { return result.toString(); } + private static String decodeQuotedPrintable(String value) { + int length = value.length(); + StringBuffer result = new StringBuffer(length); + 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') { + // Ignore, it's just a continuation symbol + } else { + char nextNextChar = value.charAt(i+2); + try { + int encodedChar = 16 * toHexValue(nextChar) + toHexValue(nextNextChar); + result.append((char) encodedChar); + } catch (IllegalArgumentException iae) { + // continue, assume it was incorrectly encoded + } + i += 2; + } + } + break; + default: + result.append(c); + } + } + return result.toString(); + } + + private static int toHexValue(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + throw new IllegalArgumentException(); + } + static String matchSingleVCardPrefixedField(String prefix, String rawText, boolean trim) { String[] values = matchVCardPrefixedField(prefix, rawText, trim); return values == null ? null : values[0]; diff --git a/core/test/src/com/google/zxing/client/result/AddressBookParsedResultTestCase.java b/core/test/src/com/google/zxing/client/result/AddressBookParsedResultTestCase.java index 68548440..601708d8 100644 --- a/core/test/src/com/google/zxing/client/result/AddressBookParsedResultTestCase.java +++ b/core/test/src/com/google/zxing/client/result/AddressBookParsedResultTestCase.java @@ -60,6 +60,15 @@ public final class AddressBookParsedResultTestCase extends TestCase { new String[] {"5555555555" }, "Company", null, null, "This is the memo."); } + public void testQuotedPrintable() { + doTest("BEGIN:VCARD\r\nADR;HOME;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;;" + + "=35=38=20=4C=79=6E=62=72=6F=6F=6B=0D=0A=43=\r\n" + + "=4F=20=36=39=39=\r\n" + + "=32=36;;;\r\nEND:VCARD", + null, null, null, new String[] {"58 Lynbrook\r\nCO 69926"}, + null, null, null, null, null, null); + } + private static void doTest(String contents, String title, String[] names,