| /* | |
| * | |
| * Copyright 2006, The Android Open Source Project | |
| * | |
| * 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. | |
| */ | |
| // Old implementation for phone_number_compare(), which has used in cupcake, but once replaced with | |
| // the new, more strict version, and reverted again. | |
| #include <string.h> | |
| namespace android { | |
| static int MIN_MATCH = 7; | |
| /** True if c is ISO-LATIN characters 0-9 */ | |
| static bool isISODigit (char c) | |
| { | |
| return c >= '0' && c <= '9'; | |
| } | |
| /** True if c is ISO-LATIN characters 0-9, *, # , + */ | |
| static bool isNonSeparator(char c) | |
| { | |
| return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; | |
| } | |
| /** | |
| * Phone numbers are stored in "lookup" form in the database | |
| * as reversed strings to allow for caller ID lookup | |
| * | |
| * This method takes a phone number and makes a valid SQL "LIKE" | |
| * string that will match the lookup form | |
| * | |
| */ | |
| /** all of a up to len must be an international prefix or | |
| * separators/non-dialing digits | |
| */ | |
| static bool matchIntlPrefix(const char* a, int len) | |
| { | |
| /* '([^0-9*#+]\+[^0-9*#+] | [^0-9*#+]0(0|11)[^0-9*#+] )$' */ | |
| /* 0 1 2 3 45 */ | |
| int state = 0; | |
| for (int i = 0 ; i < len ; i++) { | |
| char c = a[i]; | |
| switch (state) { | |
| case 0: | |
| if (c == '+') state = 1; | |
| else if (c == '0') state = 2; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 2: | |
| if (c == '0') state = 3; | |
| else if (c == '1') state = 4; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 4: | |
| if (c == '1') state = 5; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| default: | |
| if (isNonSeparator(c)) return false; | |
| break; | |
| } | |
| } | |
| return state == 1 || state == 3 || state == 5; | |
| } | |
| /** all of 'a' up to len must match non-US trunk prefix ('0') */ | |
| static bool matchTrunkPrefix(const char* a, int len) | |
| { | |
| bool found; | |
| found = false; | |
| for (int i = 0 ; i < len ; i++) { | |
| char c = a[i]; | |
| if (c == '0' && !found) { | |
| found = true; | |
| } else if (isNonSeparator(c)) { | |
| return false; | |
| } | |
| } | |
| return found; | |
| } | |
| /** all of 'a' up to len must be a (+|00|011)country code) | |
| * We're fast and loose with the country code. Any \d{1,3} matches */ | |
| static bool matchIntlPrefixAndCC(const char* a, int len) | |
| { | |
| /* [^0-9*#+]*(\+|0(0|11)\d\d?\d? [^0-9*#+] $ */ | |
| /* 0 1 2 3 45 6 7 8 */ | |
| int state = 0; | |
| for (int i = 0 ; i < len ; i++ ) { | |
| char c = a[i]; | |
| switch (state) { | |
| case 0: | |
| if (c == '+') state = 1; | |
| else if (c == '0') state = 2; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 2: | |
| if (c == '0') state = 3; | |
| else if (c == '1') state = 4; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 4: | |
| if (c == '1') state = 5; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 1: | |
| case 3: | |
| case 5: | |
| if (isISODigit(c)) state = 6; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| case 6: | |
| case 7: | |
| if (isISODigit(c)) state++; | |
| else if (isNonSeparator(c)) return false; | |
| break; | |
| default: | |
| if (isNonSeparator(c)) return false; | |
| } | |
| } | |
| return state == 6 || state == 7 || state == 8; | |
| } | |
| /** | |
| * Compare phone numbers a and b, return true if they're identical | |
| * enough for caller ID purposes. | |
| * | |
| * - Compares from right to left | |
| * - requires minimum characters to match | |
| * - handles common trunk prefixes and international prefixes | |
| * (basically, everything except the Russian trunk prefix) | |
| * | |
| * Tolerates nulls | |
| */ | |
| bool phone_number_compare_loose_with_minmatch(const char* a, const char* b, int min_match) | |
| { | |
| int ia, ib; | |
| int matched; | |
| int numSeparatorCharsInA = 0; | |
| int numSeparatorCharsInB = 0; | |
| if (a == NULL || b == NULL) { | |
| return false; | |
| } | |
| ia = strlen(a); | |
| ib = strlen(b); | |
| if (ia == 0 || ib == 0) { | |
| return false; | |
| } | |
| // Compare from right to left | |
| ia--; | |
| ib--; | |
| matched = 0; | |
| while (ia >= 0 && ib >=0) { | |
| char ca, cb; | |
| bool skipCmp = false; | |
| ca = a[ia]; | |
| if (!isNonSeparator(ca)) { | |
| ia--; | |
| skipCmp = true; | |
| numSeparatorCharsInA++; | |
| } | |
| cb = b[ib]; | |
| if (!isNonSeparator(cb)) { | |
| ib--; | |
| skipCmp = true; | |
| numSeparatorCharsInB++; | |
| } | |
| if (!skipCmp) { | |
| if (cb != ca) { | |
| break; | |
| } | |
| ia--; ib--; matched++; | |
| } | |
| } | |
| if (matched < min_match) { | |
| const int effectiveALen = strlen(a) - numSeparatorCharsInA; | |
| const int effectiveBLen = strlen(b) - numSeparatorCharsInB; | |
| // if the number of dialable chars in a and b match, but the matched chars < min_match, | |
| // treat them as equal (i.e. 404-04 and 40404) | |
| if (effectiveALen == effectiveBLen && effectiveALen == matched) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| // At least one string has matched completely; | |
| if (matched >= min_match && (ia < 0 || ib < 0)) { | |
| return true; | |
| } | |
| /* | |
| * Now, what remains must be one of the following for a | |
| * match: | |
| * | |
| * - a '+' on one and a '00' or a '011' on the other | |
| * - a '0' on one and a (+,00)<country code> on the other | |
| * (for this, a '0' and a '00' prefix would have succeeded above) | |
| */ | |
| if (matchIntlPrefix(a, ia + 1) && matchIntlPrefix(b, ib + 1)) { | |
| return true; | |
| } | |
| if (matchTrunkPrefix(a, ia + 1) && matchIntlPrefixAndCC(b, ib + 1)) { | |
| return true; | |
| } | |
| if (matchTrunkPrefix(b, ib + 1) && matchIntlPrefixAndCC(a, ia + 1)) { | |
| return true; | |
| } | |
| /* | |
| * Last resort: if the number of unmatched characters on both sides is less than or equal | |
| * to the length of the longest country code and only one number starts with a + accept | |
| * the match. This is because some countries like France and Russia have an extra prefix | |
| * digit that is used when dialing locally in country that does not show up when you dial | |
| * the number using the country code. In France this prefix digit is used to determine | |
| * which land line carrier to route the call over. | |
| */ | |
| bool aPlusFirst = (*a == '+'); | |
| bool bPlusFirst = (*b == '+'); | |
| bool aIgnoreUnmatched = aPlusFirst && (ia - ib) >= 0 && (ia - ib) <= 1; | |
| bool bIgnoreUnmatched = bPlusFirst && (ib - ia) >= 0 && (ib - ia) <= 1; | |
| if (ia < 4 && ib < 4 && (aIgnoreUnmatched || bIgnoreUnmatched) && !(aPlusFirst && bPlusFirst)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool phone_number_compare_loose(const char* a, const char* b) | |
| { | |
| return phone_number_compare_loose_with_minmatch(a, b, MIN_MATCH); | |
| } | |
| } // namespace android |