More refactoring of parsed results / result parsers; added basic vCard support
[zxing.git] / core / src / com / google / zxing / client / result / VCardResultParser.java
1 /*
2  * Copyright 2008 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.client.result;
18
19 import com.google.zxing.Result;
20
21 import java.util.Vector;
22
23 /**
24  * Parses contact information formatted according to the VCard (2.1) format. This is not a complete
25  * implementation but should parse information as commonly encoded in 2D barcodes.
26  *
27  * @author srowen@google.com (Sean Owen)
28  */
29 public final class VCardResultParser extends ResultParser {
30
31   private VCardResultParser() {
32   }
33
34   public static AddressBookParsedResult parse(Result result) {
35     String rawText = result.getText();
36     if (rawText == null || !rawText.startsWith("BEGIN:VCARD") || !rawText.endsWith("END:VCARD")) {
37       return null;
38     }
39     String[] names = matchVCardPrefixedField("FN", rawText);
40     if (names == null) {
41       // If no display names found, look for regular name fields and format them
42       names = matchVCardPrefixedField("N", rawText);
43       formatNames(names);
44     }
45     String[] phoneNumbers = matchVCardPrefixedField("TEL", rawText);
46     String[] emails = matchVCardPrefixedField("EMAIL", rawText);
47     String note = matchSingleVCardPrefixedField("NOTE", rawText);
48     String address = matchSingleVCardPrefixedField("ADR", rawText);
49     address = formatAddress(address);
50     String org = matchSingleVCardPrefixedField("ORG", rawText);
51     String birthday = matchSingleVCardPrefixedField("BDAY", rawText);
52     if (!isStringOfDigits(birthday, 8)) {
53       return null;
54     }
55     String title = matchSingleVCardPrefixedField("TITLE", rawText);
56     return new AddressBookParsedResult(names, phoneNumbers, emails, note, address, org, birthday, title); 
57   }
58
59   private static String[] matchVCardPrefixedField(String prefix, String rawText) {
60     Vector matches = null;
61     int i = 0;
62     int max = rawText.length();
63     while (i < max) {
64       i = rawText.indexOf(prefix, i);
65       if (i < 0) {
66         break;
67       }
68       if (rawText.charAt(i - 1) != '\n') {
69         // then this didn't start a new token, we matched in the middle of something
70         i++;
71         continue;
72       }
73       i += prefix.length(); // Skip past this prefix we found to start
74       if (rawText.charAt(i) != ':' && rawText.charAt(i) != ';') {
75         continue;
76       }
77       while (rawText.charAt(i) != ':') { // Skip until a colon
78         i++;
79       }
80       i++; // skip colon
81       int start = i; // Found the start of a match here
82       boolean done = false;
83       while (!done) {
84         i = rawText.indexOf((int) '\n', i); // Really, ends in \r\n
85         if (i < 0) {
86           // No terminating end character? uh, done. Set i such that loop terminates and break
87           i = rawText.length();
88           done = true;
89         } else {
90           // found a match
91           if (matches == null) {
92             matches = new Vector(3); // lazy init
93           }
94           matches.addElement(rawText.substring(start, i - 1)); // i - 1 to strip off the \r too
95           i++;
96           done = true;
97         }
98       }
99     }
100     if (matches == null || matches.isEmpty()) {
101       return null;
102     }
103     return toStringArray(matches);
104   }
105
106   private static String matchSingleVCardPrefixedField(String prefix, String rawText) {
107     String[] values = matchVCardPrefixedField(prefix, rawText);
108     return values == null ? null : values[0];
109   }
110
111   private static String formatAddress(String address) {
112     int length = address.length();
113     StringBuffer newAddress = new StringBuffer(length);
114     for (int j = 0; j < length; j++) {
115       char c = address.charAt(j);
116       if (c == ';') {
117         newAddress.append(' ');
118       } else {
119         newAddress.append(c);
120       }
121     }
122     return newAddress.toString().trim();
123   }
124
125   /**
126    * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like
127    * "Reverend John Q. Public III".
128    *
129    * @param names name values to format, in place
130    */
131   private static void formatNames(String[] names) {
132     for (int i = 0; i < names.length; i++) {
133       String name = names[i];
134       String[] components = new String[5];
135       int start = 0;
136       int end;
137       int componentIndex = 0;
138       while ((end = name.indexOf(';', start)) > 0) {
139         components[componentIndex] = name.substring(start, end);
140         componentIndex++;
141         start = end + 1;
142       }
143       components[componentIndex] = name.substring(start);      
144       StringBuffer newName = new StringBuffer();
145       maybeAppendComponent(components, 3, newName);
146       maybeAppendComponent(components, 1, newName);
147       maybeAppendComponent(components, 2, newName);
148       maybeAppendComponent(components, 0, newName);
149       maybeAppendComponent(components, 4, newName);
150       names[i] = newName.toString().trim();
151     }
152   }
153
154   private static void maybeAppendComponent(String[] components, int i, StringBuffer newName) {
155     if (components[i] != null) {
156       newName.append(' ');
157       newName.append(components[i]);
158     }
159   }
160
161 }