diff --git a/android-m3/src/com/google/zxing/client/android/ResultHandler.java b/android-m3/src/com/google/zxing/client/android/ResultHandler.java index 3c5de418f..4c0ad9a2a 100755 --- a/android-m3/src/com/google/zxing/client/android/ResultHandler.java +++ b/android-m3/src/com/google/zxing/client/android/ResultHandler.java @@ -22,7 +22,19 @@ import android.provider.Contacts; import android.util.Log; import android.view.View; import android.widget.Button; -import com.google.zxing.client.result.*; +import com.google.zxing.client.result.AddressBookAUParsedResult; +import com.google.zxing.client.result.AddressBookDoCoMoParsedResult; +import com.google.zxing.client.result.BookmarkDoCoMoParsedResult; +import com.google.zxing.client.result.EmailAddressParsedResult; +import com.google.zxing.client.result.EmailDoCoMoParsedResult; +import com.google.zxing.client.result.GeoParsedResult; +import com.google.zxing.client.result.ParsedReaderResult; +import com.google.zxing.client.result.ParsedReaderResultType; +import com.google.zxing.client.result.SMSParsedResult; +import com.google.zxing.client.result.TelParsedResult; +import com.google.zxing.client.result.UPCParsedResult; +import com.google.zxing.client.result.URIParsedResult; +import com.google.zxing.client.result.URLTOParsedResult; import java.net.URISyntaxException; @@ -89,6 +101,12 @@ final class ResultHandler implements Button.OnClickListener { intent = new Intent(Intent.SENDTO_ACTION, new ContentURI("mailto:" + emailResult.getEmailAddress())); } catch (URISyntaxException e) { } + } else if (type.equals(ParsedReaderResultType.SMS)) { + SMSParsedResult smsResult = (SMSParsedResult) result; + try { + intent = new Intent(Intent.SENDTO_ACTION, new ContentURI(smsResult.getSMSURI())); + } catch (URISyntaxException e) { + } } else if (type.equals(ParsedReaderResultType.TEL)) { TelParsedResult telResult = (TelParsedResult) result; try { diff --git a/android/src/com/google/zxing/client/android/ResultHandler.java b/android/src/com/google/zxing/client/android/ResultHandler.java index 6f9a3ab46..bf39dcd4a 100755 --- a/android/src/com/google/zxing/client/android/ResultHandler.java +++ b/android/src/com/google/zxing/client/android/ResultHandler.java @@ -19,10 +19,21 @@ package com.google.zxing.client.android; import android.content.Intent; import android.net.Uri; import android.provider.Contacts; -import android.util.Log; import android.view.View; import android.widget.Button; -import com.google.zxing.client.result.*; +import com.google.zxing.client.result.AddressBookAUParsedResult; +import com.google.zxing.client.result.AddressBookDoCoMoParsedResult; +import com.google.zxing.client.result.BookmarkDoCoMoParsedResult; +import com.google.zxing.client.result.EmailAddressParsedResult; +import com.google.zxing.client.result.EmailDoCoMoParsedResult; +import com.google.zxing.client.result.GeoParsedResult; +import com.google.zxing.client.result.ParsedReaderResult; +import com.google.zxing.client.result.ParsedReaderResultType; +import com.google.zxing.client.result.SMSParsedResult; +import com.google.zxing.client.result.TelParsedResult; +import com.google.zxing.client.result.UPCParsedResult; +import com.google.zxing.client.result.URIParsedResult; +import com.google.zxing.client.result.URLTOParsedResult; /** * Handles the result of barcode decoding in the context of the Android platform, @@ -31,7 +42,7 @@ import com.google.zxing.client.result.*; * @author srowen@google.com (Sean Owen) * @author dswitkin@google.com (Daniel Switkin) */ -final class ResultHandler implements Button.OnClickListener { +final class ResultHandler implements Button.OnClickListenear { private static final String TAG = "ResultHandler"; @@ -75,6 +86,9 @@ final class ResultHandler implements Button.OnClickListener { } else if (type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) { EmailAddressParsedResult emailResult = (EmailAddressParsedResult) result; intent = new Intent(Intent.SENDTO_ACTION, Uri.parse("mailto:" + emailResult.getEmailAddress())); + } else if (type.equals(ParsedReaderResultType.SMS)) { + SMSParsedResult smsResult = (SMSParsedResult) result; + intent = new Intent(Intent.SENDTO_ACTION, Uri.parse(smsResult.getSMSURI())); } else if (type.equals(ParsedReaderResultType.TEL)) { TelParsedResult telResult = (TelParsedResult) result; intent = new Intent(Intent.DIAL_ACTION, Uri.parse("tel:" + telResult.getNumber())); diff --git a/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java b/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java index acb5e0cd4..ffeca1cc0 100644 --- a/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java +++ b/core/src/com/google/zxing/client/result/AbstractDoCoMoParsedResult.java @@ -93,13 +93,6 @@ abstract class AbstractDoCoMoParsedResult extends ParsedReaderResult { return matches == null ? null : matches[0]; } - static void maybeAppend(String value, StringBuffer result) { - if (value != null) { - result.append('\n'); - result.append(value); - } - } - static void maybeAppend(String[] value, StringBuffer result) { if (value != null) { for (int i = 0; i < value.length; i++) { diff --git a/core/src/com/google/zxing/client/result/GeoParsedResult.java b/core/src/com/google/zxing/client/result/GeoParsedResult.java index 1dee6c803..09cdd362f 100644 --- a/core/src/com/google/zxing/client/result/GeoParsedResult.java +++ b/core/src/com/google/zxing/client/result/GeoParsedResult.java @@ -48,27 +48,28 @@ public final class GeoParsedResult extends ParsedReaderResult { } // Drop geo, query portion int queryStart = rawText.indexOf('?', 4); + String geoURIWithoutQuery; if (queryStart < 0) { - rawText = rawText.substring(4); + geoURIWithoutQuery = rawText.substring(4); } else { - rawText = rawText.substring(4, queryStart); + geoURIWithoutQuery = rawText.substring(4, queryStart); } - int latitudeEnd = rawText.indexOf(','); + int latitudeEnd = geoURIWithoutQuery.indexOf(','); if (latitudeEnd < 0) { return null; } - float latitude = Float.parseFloat(rawText.substring(0, latitudeEnd)); - int longitudeEnd = rawText.indexOf(',', latitudeEnd + 1); + float latitude = Float.parseFloat(geoURIWithoutQuery.substring(0, latitudeEnd)); + int longitudeEnd = geoURIWithoutQuery.indexOf(',', latitudeEnd + 1); float longitude; float altitude; // in meters if (longitudeEnd < 0) { - longitude = Float.parseFloat(rawText.substring(latitudeEnd + 1)); + longitude = Float.parseFloat(geoURIWithoutQuery.substring(latitudeEnd + 1)); altitude = 0.0f; } else { - longitude = Float.parseFloat(rawText.substring(latitudeEnd + 1, longitudeEnd)); - altitude = Float.parseFloat(rawText.substring(longitudeEnd + 1)); + longitude = Float.parseFloat(geoURIWithoutQuery.substring(latitudeEnd + 1, longitudeEnd)); + altitude = Float.parseFloat(geoURIWithoutQuery.substring(longitudeEnd + 1)); } - return new GeoParsedResult("geo:" + rawText, latitude, longitude, altitude); + return new GeoParsedResult(rawText, latitude, longitude, altitude); } public String getGeoURI() { diff --git a/core/src/com/google/zxing/client/result/ParsedReaderResult.java b/core/src/com/google/zxing/client/result/ParsedReaderResult.java index f0c490913..970fb5e13 100644 --- a/core/src/com/google/zxing/client/result/ParsedReaderResult.java +++ b/core/src/com/google/zxing/client/result/ParsedReaderResult.java @@ -60,6 +60,8 @@ public abstract class ParsedReaderResult { return result; } else if ((result = TelParsedResult.parse(theResult)) != null) { return result; + } else if ((result = SMSParsedResult.parse(theResult)) != null) { + return result; } else if ((result = GeoParsedResult.parse(theResult)) != null) { return result; } else if ((result = URLTOParsedResult.parse(theResult)) != null) { @@ -82,7 +84,14 @@ public abstract class ParsedReaderResult { return getDisplayResult(); } - static String unescapeBackslash(String escaped) { + protected static void maybeAppend(String value, StringBuffer result) { + if (value != null) { + result.append('\n'); + result.append(value); + } + } + + protected static String unescapeBackslash(String escaped) { if (escaped != null) { int backslash = escaped.indexOf((int) '\\'); if (backslash >= 0) { diff --git a/core/src/com/google/zxing/client/result/ParsedReaderResultType.java b/core/src/com/google/zxing/client/result/ParsedReaderResultType.java index 344fe4c09..815f4cf58 100644 --- a/core/src/com/google/zxing/client/result/ParsedReaderResultType.java +++ b/core/src/com/google/zxing/client/result/ParsedReaderResultType.java @@ -36,10 +36,21 @@ public final class ParsedReaderResultType { public static final ParsedReaderResultType ANDROID_INTENT = new ParsedReaderResultType("ANDROID_INTENT"); public static final ParsedReaderResultType GEO = new ParsedReaderResultType("GEO"); public static final ParsedReaderResultType TEL = new ParsedReaderResultType("TEL"); - // TODO later, add the NDEF types to those actually processed by the clients + public static final ParsedReaderResultType SMS = new ParsedReaderResultType("SMS"); + + // "optional" types public static final ParsedReaderResultType NDEF_TEXT = new ParsedReaderResultType("NDEF_TEXT"); public static final ParsedReaderResultType NDEF_URI = new ParsedReaderResultType("NDEF_URI"); public static final ParsedReaderResultType NDEF_SMART_POSTER = new ParsedReaderResultType("NDEF_SMART_POSTER"); + public static final ParsedReaderResultType MOBILETAG_TEL = new ParsedReaderResultType("MOBILETAG_TEL"); + public static final ParsedReaderResultType MOBILETAG_SMS = new ParsedReaderResultType("MOBILETAG_SMS"); + public static final ParsedReaderResultType MOBILETAG_MMS = new ParsedReaderResultType("MOBILETAG_MMS"); + public static final ParsedReaderResultType MOBILETAG_SIMPLE_WEB = new ParsedReaderResultType("MOBILETAG_SIMPLE_WEB"); + public static final ParsedReaderResultType MOBILETAG_SIMPLE_CONTACT = + new ParsedReaderResultType("MOBILETAG_SIMPLE_CONTACT"); + public static final ParsedReaderResultType MOBILETAG_SIMPLE_CALENDAR = + new ParsedReaderResultType("MOBILETAG_SIMPLE_CALENDAR"); + public static final ParsedReaderResultType MOBILETAG_RICH_WEB = new ParsedReaderResultType("MOBILETAG_RICH_WEB"); private final String name; diff --git a/core/src/com/google/zxing/client/result/SMSParsedResult.java b/core/src/com/google/zxing/client/result/SMSParsedResult.java new file mode 100644 index 000000000..65066da1c --- /dev/null +++ b/core/src/com/google/zxing/client/result/SMSParsedResult.java @@ -0,0 +1,88 @@ +/* + * Copyright 2008 Google Inc. + * + * 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; + +/** + * Represents a "sms:" URI result, which specifies a number to SMS and optional + * "via" number. See + * the IETF draft on this. + * + * @author srowen@google.com (Sean Owen) + */ +public final class SMSParsedResult extends ParsedReaderResult { + + private final String smsURI; + private final String number; + private final String via; + + private SMSParsedResult(String smsURI, String number, String via) { + super(ParsedReaderResultType.SMS); + this.smsURI = smsURI; + this.number = number; + this.via = via; + } + + public static SMSParsedResult parse(Result result) { + String rawText = result.getText(); + if (rawText == null || !rawText.startsWith("sms:")) { + return null; + } + // Drop sms, query portion + int queryStart = rawText.indexOf('?', 4); + String smsURIWithoutQuery; + if (queryStart < 0) { + smsURIWithoutQuery = rawText.substring(4); + } else { + smsURIWithoutQuery = rawText.substring(4, queryStart); + } + int numberEnd = smsURIWithoutQuery.indexOf(';'); + String number; + String via; + if (numberEnd < 0) { + number = smsURIWithoutQuery; + via = null; + } else { + number = smsURIWithoutQuery.substring(0, numberEnd); + String maybeVia = smsURIWithoutQuery.substring(numberEnd + 1); + if (maybeVia.startsWith("via=")) { + via = maybeVia.substring(4); + } else { + via = null; + } + } + return new SMSParsedResult(rawText, number, via); + } + + public String getSMSURI() { + return smsURI; + } + + public String getNumber() { + return number; + } + + public String getVia() { + return via; + } + + public String getDisplayResult() { + return number; + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/AbstractMobileTagParsedResult.java b/core/src/com/google/zxing/client/result/optional/AbstractMobileTagParsedResult.java new file mode 100644 index 000000000..704f50f93 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/AbstractMobileTagParsedResult.java @@ -0,0 +1,87 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.client.result.ParsedReaderResult; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *
Superclass for classes encapsulating reader results encoded according + * to the MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +abstract class AbstractMobileTagParsedResult extends ParsedReaderResult { + + public static final int ACTION_DO = 1; + public static final int ACTION_EDIT = 2; + public static final int ACTION_SAVE = 4; + + AbstractMobileTagParsedResult(ParsedReaderResultType type) { + super(type); + } + + static String[] matchDelimitedFields(String rawText, int maxItems) { + String[] result = new String[maxItems]; + int item = 0; + int i = 0; + int max = rawText.length(); + while (item < maxItems && i < max) { + int start = i; // Found the start of a match here + boolean done = false; + while (!done) { + i = rawText.indexOf((int) '|', i); + if (i < 0) { + // No terminating end character? done. Set i such that loop terminates and break + i = rawText.length(); + done = true; + } else if (rawText.charAt(i - 1) == '\\') { + // semicolon was escaped so continue + i++; + } else { + // found a match + if (start != i) { + result[item] = unescapeBackslash(rawText.substring(start, i)); + } + item++; + i++; + done = true; + } + } + } + if (item < maxItems) { + return null; + } + return result; + } + + static boolean isDigits(String s, int expectedLength) { + if (s == null) { + return true; + } + if (s.length() != expectedLength) { + return false; + } + for (int i = 0; i < expectedLength; i++) { + if (!Character.isDigit(s.charAt(i))) { + return false; + } + } + return true; + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/AbstractNDEFParsedResult.java b/core/src/com/google/zxing/client/result/optional/AbstractNDEFParsedResult.java new file mode 100644 index 000000000..c3df02ffa --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/AbstractNDEFParsedResult.java @@ -0,0 +1,50 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.client.result.ParsedReaderResult; +import com.google.zxing.client.result.ParsedReaderResultType; + +import java.io.UnsupportedEncodingException; + +/** + *Superclass for classes encapsulating results in the NDEF format. + * See http://www.nfc-forum.org/specs/.
+ * + *This code supports a limited subset of NDEF messages, ones that are plausibly + * useful in 2D barcode formats. This generally includes 1-record messages, no chunking, + * "short record" syntax, no ID field.
+ * + * @author srowen@google.com (Sean Owen) + */ +abstract class AbstractNDEFParsedResult extends ParsedReaderResult { + + AbstractNDEFParsedResult(ParsedReaderResultType type) { + super(type); + } + + static String bytesToString(byte[] bytes, int offset, int length, String encoding) { + try { + return new String(bytes, offset, length, encoding); + } catch (UnsupportedEncodingException uee) { + // This should only be used when 'encoding' is an encoding that must necessarily + // be supported by the JVM, like UTF-8 + throw new RuntimeException("Platform does not support required encoding: " + uee); + } + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagMMSParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagMMSParsedResult.java new file mode 100644 index 000000000..72d0e5638 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagMMSParsedResult.java @@ -0,0 +1,91 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "MMS" result encoded according to section 4.7 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagMMSParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "05"; + + private final String to; + private final String subject; + private final String body; + private final String title; + + private MobileTagMMSParsedResult(String to, String subject, String body, String title) { + super(ParsedReaderResultType.MOBILETAG_MMS); + this.to = to; + this.subject = subject; + this.body = body; + this.title = title; + } + + public static MobileTagMMSParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 4); + if (matches == null) { + return null; + } + String to = matches[0]; + String subject = matches[1]; + String body = matches[2]; + String title = matches[3]; + + return new MobileTagMMSParsedResult(to, subject, body, title); + } + + public String getTo() { + return to; + } + + public String getSubject() { + return subject; + } + + public String getBody() { + return body; + } + + public String getTitle() { + return title; + } + + public String getDisplayResult() { + StringBuffer result = new StringBuffer(to); + maybeAppend(subject, result); + maybeAppend(title, result); + maybeAppend(body, result); + return result.toString(); + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagRichWebParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagRichWebParsedResult.java new file mode 100644 index 000000000..335a1528c --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagRichWebParsedResult.java @@ -0,0 +1,97 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "rich web" result encoded according to section 5 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagRichWebParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "54"; + + private static final int DEFAULT_ACTION = ACTION_DO; + // Example: "http://www.tagserver.com/script.asp?id=" + private static final String TAGSERVER_URI_PREFIX = System.getProperty("zxing.mobiletag.tagserver"); + + private final String id; + private final int action; + + private MobileTagRichWebParsedResult(String id, int action) { + super(ParsedReaderResultType.MOBILETAG_RICH_WEB); + this.id = id; + this.action = action; + } + + public static MobileTagRichWebParsedResult parse(Result result) { + if (TAGSERVER_URI_PREFIX == null) { + return null; + } + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + int length = rawText.length(); + if (!isDigits(rawText, length)) { + return null; + } + int action; + String id; + if (length == 15) { + action = DEFAULT_ACTION; + id = rawText.substring(0, 2) + action + rawText.substring(2); + } else if (length == 16) { + action = rawText.charAt(2) - '0'; + id = rawText; + } else { + return null; + } + + return new MobileTagRichWebParsedResult(id, action); + } + + public static String getTagserverURIPrefix() { + return TAGSERVER_URI_PREFIX; + } + + public String getId() { + return id; + } + + public int getAction() { + return action; + } + + public String getTagserverURI() { + return TAGSERVER_URI_PREFIX + id; + } + + public String getDisplayResult() { + return id; + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagSMSParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagSMSParsedResult.java new file mode 100644 index 000000000..72768a4f3 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagSMSParsedResult.java @@ -0,0 +1,83 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "SMS" result encoded according to section 4.6 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagSMSParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "03"; + + private final String to; + private final String body; + private final String title; + + private MobileTagSMSParsedResult(String to, String body, String title) { + super(ParsedReaderResultType.MOBILETAG_SMS); + this.to = to; + this.body = body; + this.title = title; + } + + public static MobileTagSMSParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 3); + if (matches == null) { + return null; + } + String to = matches[0]; + String body = matches[1]; + String title = matches[2]; + + return new MobileTagSMSParsedResult(to, body, title); + } + + public String getTo() { + return to; + } + + public String getBody() { + return body; + } + + public String getTitle() { + return title; + } + + public String getDisplayResult() { + StringBuffer result = new StringBuffer(to); + maybeAppend(title, result); + maybeAppend(body, result); + return result.toString(); + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagSimpleCalendarParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleCalendarParsedResult.java new file mode 100644 index 000000000..d75e87b4d --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleCalendarParsedResult.java @@ -0,0 +1,131 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "simple calendar" result encoded according to section 4.9 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagSimpleCalendarParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "07"; + + private final String summary; + private final String start; + private final String end; + private final String location; + private final String attendee; + private final String title; + + private MobileTagSimpleCalendarParsedResult(String summary, + String start, + String end, + String location, + String attendee, + String title) { + super(ParsedReaderResultType.MOBILETAG_SIMPLE_CALENDAR); + this.summary = summary; + this.start = start; + this.end = end; + this.location = location; + this.attendee = attendee; + this.title = title; + } + + public static MobileTagSimpleCalendarParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 6); + if (matches == null || !isDigits(matches[1], 10) || !isDigits(matches[2], 10)) { + return null; + } + String summary = matches[0]; + String start = expandDateString(matches[1]); + String end = expandDateString(matches[2]); + String location = matches[3]; + String attendee = matches[4]; + String title = matches[5]; + + return new MobileTagSimpleCalendarParsedResult(summary, start, end, location, attendee, title); + } + + public String getSummary() { + return summary; + } + + /** + *We would return the start and end date as a {@link java.util.Date} except that this code
+ * needs to work under JavaME / MIDP and there is no date parsing library available there, such
+ * as java.text.SimpleDateFormat
.
However we do translate the date from its encoded format of, say, "0602212156" to its full + * text representation of "20060221T215600Z", per the specification.
+ */ + public String getStart() { + return start; + } + + /** + * @see #getStart() + */ + public String getEnd() { + return end; + } + + public String getLocation() { + return location; + } + + public String getAttendee() { + return attendee; + } + + public String getTitle() { + return title; + } + + public String getDisplayResult() { + StringBuffer result = new StringBuffer(summary); + maybeAppend(start, result); + maybeAppend(end, result); + maybeAppend(location, result); + maybeAppend(attendee, result); + maybeAppend(title, result); + return result.toString(); + } + + private static String expandDateString(String date) { + if (date == null) { + return null; + } + // Input is of form YYMMddHHmmss, and needs to be YYYYMMdd'T'HHmmss'Z' + return "20" + date.substring(0, 6) + 'T' + date.substring(6) + "00Z"; + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactParsedResult.java new file mode 100644 index 000000000..84c6c8546 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleContactParsedResult.java @@ -0,0 +1,148 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "simple contact" result encoded according to section 4.8 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagSimpleContactParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "02"; + + private final String fullName; + private final String telephoneCell; + private final String telephone; + private final String email1; + private final String email2; + private final String address; + private final String org; + private final String birthday; + private final String title; + + private MobileTagSimpleContactParsedResult(String fullName, + String telephoneCell, + String telephone, + String email1, + String email2, + String address, + String org, + String birthday, + String title) { + super(ParsedReaderResultType.MOBILETAG_SIMPLE_CONTACT); + this.fullName = fullName; + this.telephoneCell = telephoneCell; + this.telephone = telephone; + this.email1 = email1; + this.email2 = email2; + this.address = address; + this.org = org; + this.birthday = birthday; + this.title = title; + } + + public static MobileTagSimpleContactParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 9); + if (matches == null || !isDigits(matches[7], 8)) { + return null; + } + String fullName = matches[0]; + String telephoneCell = matches[1]; + String telephone = matches[2]; + String email1 = matches[3]; + String email2 = matches[4]; + String address = matches[5]; + String org = matches[6]; + String birthday = matches[7]; + String title = matches[8]; + + return new MobileTagSimpleContactParsedResult(fullName, + telephoneCell, + telephone, + email1, + email2, + address, + org, + birthday, + title); + } + + + public String getFullName() { + return fullName; + } + + public String getTelephoneCell() { + return telephoneCell; + } + + public String getTelephone() { + return telephone; + } + + public String getEmail1() { + return email1; + } + + public String getEmail2() { + return email2; + } + + public String getAddress() { + return address; + } + + public String getOrg() { + return org; + } + + public String getBirthday() { + return birthday; + } + + public String getTitle() { + return title; + } + + public String getDisplayResult() { + StringBuffer result = new StringBuffer(fullName); + maybeAppend(telephoneCell, result); + maybeAppend(telephone, result); + maybeAppend(email1, result); + maybeAppend(email2, result); + maybeAppend(address, result); + maybeAppend(org, result); + maybeAppend(birthday, result); + maybeAppend(title, result); + return result.toString(); + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagSimpleWebParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleWebParsedResult.java new file mode 100644 index 000000000..7df36a306 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagSimpleWebParsedResult.java @@ -0,0 +1,96 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "simple web" result encoded according to section 4.11 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagSimpleWebParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "04"; + private static final String[] URI_PREFIXES = { + null, + "http://", + "http://www.", + "https://", + "https://www.", + "rtsp://", + }; + + private final String title; + private final String uri; + + private MobileTagSimpleWebParsedResult(String title, String uri) { + super(ParsedReaderResultType.MOBILETAG_SIMPLE_WEB); + this.title = title; + this.uri = uri; + } + + public static MobileTagSimpleWebParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 2); + if (matches == null) { + return null; + } + String uri = matches[0]; + String title = matches[1]; + + char maybePrefixChar = uri.charAt(2); + if (maybePrefixChar >= '0' && maybePrefixChar <= '9') { + int prefixIndex = maybePrefixChar - '0'; + // Note that '0' is reserved + if (prefixIndex >= 1 && prefixIndex < URI_PREFIXES.length) { + uri = URI_PREFIXES[prefixIndex] + uri.substring(1); + } else { + uri = uri.substring(1); + } + } + + return new MobileTagSimpleWebParsedResult(title, uri); + } + + public String getTitle() { + return title; + } + + public String getURI() { + return uri; + } + + public String getDisplayResult() { + if (title == null) { + return uri; + } else { + return title + '\n' + uri; + } + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/optional/MobileTagTelParsedResult.java b/core/src/com/google/zxing/client/result/optional/MobileTagTelParsedResult.java new file mode 100644 index 000000000..8adc3fc88 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/MobileTagTelParsedResult.java @@ -0,0 +1,77 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *Represents a "TEL" result encoded according to section 4.4 of the + * MobileTag Reader International Specification.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class MobileTagTelParsedResult extends AbstractMobileTagParsedResult { + + public static final String SERVICE_TYPE = "01"; + + private final String number; + private final String title; + + private MobileTagTelParsedResult(String number, String title) { + super(ParsedReaderResultType.MOBILETAG_TEL); + this.number = number; + this.title = title; + } + + public static MobileTagTelParsedResult parse(Result result) { + if (!result.getBarcodeFormat().equals(BarcodeFormat.DATAMATRIX)) { + return null; + } + String rawText = result.getText(); + if (!rawText.startsWith(SERVICE_TYPE)) { + return null; + } + + String[] matches = matchDelimitedFields(rawText.substring(2), 2); + if (matches == null) { + return null; + } + String number = matches[0]; + String title = matches[1]; + + return new MobileTagTelParsedResult(number, title); + } + + public String getNumber() { + return number; + } + + public String getTitle() { + return title; + } + + public String getDisplayResult() { + if (title == null) { + return number; + } else { + return title + '\n' + number; + } + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/NDEFRecord.java b/core/src/com/google/zxing/client/result/optional/NDEFRecord.java similarity index 98% rename from core/src/com/google/zxing/client/result/NDEFRecord.java rename to core/src/com/google/zxing/client/result/optional/NDEFRecord.java index 59797c23c..2acd6b256 100644 --- a/core/src/com/google/zxing/client/result/NDEFRecord.java +++ b/core/src/com/google/zxing/client/result/optional/NDEFRecord.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.zxing.client.result; +package com.google.zxing.client.result.optional; /** *Represents a record in an NDEF message. This class only supports certain types diff --git a/core/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java b/core/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java new file mode 100644 index 000000000..380f193aa --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java @@ -0,0 +1,113 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + *
Recognizes an NDEF message that encodes information according to the + * "Smart Poster Record Type Definition" specification.
+ * + *This actually only supports some parts of the Smart Poster format: title, + * URI, and action records. Icon records are not supported because the size + * of these records are infeasibly large for barcodes. Size and type records + * are not supported. Multiple titles are not supported.
+ * + * @author srowen@google.com (Sean Owen) + */ +public final class NDEFSmartPosterParsedResult extends AbstractNDEFParsedResult { + + public static final int ACTION_UNSPECIFIED = -1; + public static final int ACTION_DO = 0; + public static final int ACTION_SAVE = 1; + public static final int ACTION_OPEN = 2; + + private String title; + private String uri; + private int action; + + private NDEFSmartPosterParsedResult() { + super(ParsedReaderResultType.NDEF_SMART_POSTER); + action = ACTION_UNSPECIFIED; + } + + public static NDEFSmartPosterParsedResult parse(Result result) { + byte[] bytes = result.getRawBytes(); + if (bytes == null) { + return null; + } + NDEFRecord headerRecord = NDEFRecord.readRecord(bytes, 0); + // Yes, header record starts and ends a message + if (headerRecord == null || !headerRecord.isMessageBegin() || !headerRecord.isMessageEnd()) { + return null; + } + if (!headerRecord.getType().equals(NDEFRecord.SMART_POSTER_WELL_KNOWN_TYPE)) { + return null; + } + + int offset = 0; + int recordNumber = 0; + NDEFRecord ndefRecord = null; + byte[] payload = headerRecord.getPayload(); + NDEFSmartPosterParsedResult smartPosterParsedResult = new NDEFSmartPosterParsedResult(); + + while (offset < payload.length && (ndefRecord = NDEFRecord.readRecord(payload, offset)) != null) { + if (recordNumber == 0 && !ndefRecord.isMessageBegin()) { + return null; + } + String type = ndefRecord.getType(); + if (NDEFRecord.TEXT_WELL_KNOWN_TYPE.equals(type)) { + String[] languageText = NDEFTextParsedResult.decodeTextPayload(ndefRecord.getPayload()); + smartPosterParsedResult.title = languageText[1]; + } else if (NDEFRecord.URI_WELL_KNOWN_TYPE.equals(type)) { + smartPosterParsedResult.uri = NDEFURIParsedResult.decodeURIPayload(ndefRecord.getPayload()); + } else if (NDEFRecord.ACTION_WELL_KNOWN_TYPE.equals(type)) { + smartPosterParsedResult.action = ndefRecord.getPayload()[0]; + } + recordNumber++; + offset += ndefRecord.getTotalRecordLength(); + } + + if (recordNumber == 0 || (ndefRecord != null && !ndefRecord.isMessageEnd())) { + return null; + } + + return smartPosterParsedResult; + } + + public String getTitle() { + return title; + } + + public String getURI() { + return uri; + } + + public int getAction() { + return action; + } + + public String getDisplayResult() { + if (title == null) { + return uri; + } else { + return title + '\n' + uri; + } + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/client/result/NDEFTextParsedResult.java b/core/src/com/google/zxing/client/result/optional/NDEFTextParsedResult.java similarity index 95% rename from core/src/com/google/zxing/client/result/NDEFTextParsedResult.java rename to core/src/com/google/zxing/client/result/optional/NDEFTextParsedResult.java index bec1fdd7f..fc646e447 100644 --- a/core/src/com/google/zxing/client/result/NDEFTextParsedResult.java +++ b/core/src/com/google/zxing/client/result/optional/NDEFTextParsedResult.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.google.zxing.client.result; +package com.google.zxing.client.result.optional; import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; /** * Recognizes an NDEF message that encodes text according to the @@ -26,7 +27,6 @@ import com.google.zxing.Result; */ public final class NDEFTextParsedResult extends AbstractNDEFParsedResult { - private final String language; private final String text; diff --git a/core/src/com/google/zxing/client/result/optional/NDEFURIParsedResult.java b/core/src/com/google/zxing/client/result/optional/NDEFURIParsedResult.java new file mode 100644 index 000000000..2b98f6d02 --- /dev/null +++ b/core/src/com/google/zxing/client/result/optional/NDEFURIParsedResult.java @@ -0,0 +1,110 @@ +/* + * Copyright 2008 Google Inc. + * + * 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.optional; + +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedReaderResultType; + +/** + * Recognizes an NDEF message that encodes a URI according to the + * "URI Record Type Definition" specification. + * + * @author srowen@google.com (Sean Owen) + */ +public final class NDEFURIParsedResult extends AbstractNDEFParsedResult { + + private static final String[] URI_PREFIXES = { + null, + "http://www.", + "https://www.", + "http://", + "https://", + "tel:", + "mailto:", + "ftp://anonymous:anonymous@", + "ftp://ftp.", + "ftps://", + "sftp://", + "smb://", + "nfs://", + "ftp://", + "dav://", + "news:", + "telnet://", + "imap:", + "rtsp://", + "urn:", + "pop:", + "sip:", + "sips:", + "tftp:", + "btspp://", + "btl2cap://", + "btgoep://", + "tcpobex://", + "irdaobex://", + "file://", + "urn:epc:id:", + "urn:epc:tag:", + "urn:epc:pat:", + "urn:epc:raw:", + "urn:epc:", + "urn:nfc:", + }; + + private final String uri; + + private NDEFURIParsedResult(String uri) { + super(ParsedReaderResultType.NDEF_URI); + this.uri = uri; + } + + public static NDEFURIParsedResult parse(Result result) { + byte[] bytes = result.getRawBytes(); + if (bytes == null) { + return null; + } + NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0); + if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) { + return null; + } + if (!ndefRecord.getType().equals(NDEFRecord.URI_WELL_KNOWN_TYPE)) { + return null; + } + String fullURI = decodeURIPayload(ndefRecord.getPayload()); + return new NDEFURIParsedResult(fullURI); + } + + static String decodeURIPayload(byte[] payload) { + int identifierCode = payload[0] & 0xFF; + String prefix = null; + if (identifierCode < URI_PREFIXES.length) { + prefix = URI_PREFIXES[identifierCode]; + } + String restOfURI = bytesToString(payload, 1, payload.length - 1, "UTF-8"); + return prefix == null ? restOfURI : prefix + restOfURI; + } + + public String getURI() { + return uri; + } + + public String getDisplayResult() { + return uri; + } + +} \ No newline at end of file diff --git a/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java b/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java index 101026278..fd4754e73 100644 --- a/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java +++ b/javame/src/com/google/zxing/client/j2me/ZXingMIDlet.java @@ -22,6 +22,7 @@ import com.google.zxing.client.result.EmailAddressParsedResult; import com.google.zxing.client.result.EmailDoCoMoParsedResult; import com.google.zxing.client.result.ParsedReaderResult; import com.google.zxing.client.result.ParsedReaderResultType; +import com.google.zxing.client.result.SMSParsedResult; import com.google.zxing.client.result.TelParsedResult; import com.google.zxing.client.result.UPCParsedResult; import com.google.zxing.client.result.URIParsedResult; @@ -209,9 +210,9 @@ public final class ZXingMIDlet extends MIDlet { } else if (type.equals(ParsedReaderResultType.EMAIL_ADDRESS)) { String email = ((EmailAddressParsedResult) result).getEmailAddress(); showOpenURL("Compose E-mail?", email, "mailto:" + email); - //} else if (type.equals(ParsedReaderResultType.GEO)) { - // GeoParsedResult geoResult = (GeoParsedResult) result; - // showOpenURL("Open In Google Maps?", geoResult.getDisplayResult(), geoResult.getGoogleMapsURI()); + } else if (type.equals(ParsedReaderResultType.SMS)) { + SMSParsedResult smsResult = (SMSParsedResult) result; + showOpenURL("Compose SMS?", smsResult.getNumber(), smsResult.getSMSURI()); } else if (type.equals(ParsedReaderResultType.UPC)) { String upc = ((UPCParsedResult) result).getUPC(); String uri = "http://www.upcdatabase.com/item.asp?upc=" + upc;