diff options
Diffstat (limited to 'comm/mailnews/base/src/ABQueryUtils.jsm')
-rw-r--r-- | comm/mailnews/base/src/ABQueryUtils.jsm | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/ABQueryUtils.jsm b/comm/mailnews/base/src/ABQueryUtils.jsm new file mode 100644 index 0000000000..4944971ddf --- /dev/null +++ b/comm/mailnews/base/src/ABQueryUtils.jsm @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains helper methods for dealing with addressbook search URIs. + */ + +const EXPORTED_SYMBOLS = [ + "getSearchTokens", + "getModelQuery", + "modelQueryHasUserValue", + "generateQueryURI", + "encodeABTermValue", +]; + +/** + * Parse the multiword search string to extract individual search terms + * (separated on the basis of spaces) or quoted exact phrases to search + * against multiple fields of the addressbook cards. + * + * @param {string} aSearchString - The full search string entered by the user. + * + * @returns {Array} Array of separated search terms from the full search string. + */ +function getSearchTokens(aSearchString) { + // Trim leading and trailing whitespace and comma(s) to prevent empty search + // words when splitting unquoted parts of search string below. + let searchString = aSearchString + .replace(/^[,\s]+/, "") + .replace(/[,\s]+$/, ""); + if (searchString == "") { + return []; + } + + let quotedTerms = []; + + // Split up multiple search words to create a *foo* and *bar* search against + // search fields, using the OR-search template from modelQuery for each word. + // If the search query has quoted terms like "foo bar", extract them as is. + let startIndex; + while ((startIndex = searchString.indexOf('"')) != -1) { + let endIndex = searchString.indexOf('"', startIndex + 1); + if (endIndex == -1) { + endIndex = searchString.length; + } + + quotedTerms.push(searchString.substring(startIndex + 1, endIndex)); + let query = searchString.substring(0, startIndex); + if (endIndex < searchString.length) { + query += searchString.substr(endIndex + 1); + } + + searchString = query.trim(); + } + + let searchWords = []; + if (searchString.length != 0) { + // Split non-quoted search terms on whitespace and comma(s): Allow flexible + // incremental searches, and prevent false negatives for |Last, First| with + // |View > Show Name As > Last, First|, where comma is not found in data. + searchWords = quotedTerms.concat(searchString.split(/[,\s]+/)); + } else { + searchWords = quotedTerms; + } + + return searchWords; +} + +/** + * For AB quicksearch or recipient autocomplete, get the normal or phonetic model + * query URL part from prefs, allowing users to customize these searches. + * + * @param {string} aBasePrefName - The full pref name of default, non-phonetic + * model query, e.g. mail.addr_book.quicksearchquery.format. If phonetic + * search is used, corresponding pref must exist: + * e.g. mail.addr_book.quicksearchquery.format.phonetic + * @returns {boolean} depending on mail.addr_book.show_phonetic_fields pref, + * the value of aBasePrefName or aBasePrefName + ".phonetic" + */ +function getModelQuery(aBasePrefName) { + let modelQuery = ""; + if ( + Services.prefs.getComplexValue( + "mail.addr_book.show_phonetic_fields", + Ci.nsIPrefLocalizedString + ).data == "true" + ) { + modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic"); + } else { + modelQuery = Services.prefs.getCharPref(aBasePrefName); + } + // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format + // todo: could this be done in a once-off migration at install time to avoid repetitive calls? + if (modelQuery.startsWith("?")) { + modelQuery = modelQuery.slice(1); + } + return modelQuery; +} + +/** + * Check if the currently used pref with the model query was customized by user. + * + * @param {string} aBasePrefName - The full pref name of default, non-phonetic + * model query, e.g. mail.addr_book.quicksearchquery.format + * If phonetic search is used, corresponding pref must exist: + * e.g. mail.addr_book.quicksearchquery.format.phonetic + * @returns {boolean} true or false + */ +function modelQueryHasUserValue(aBasePrefName) { + if ( + Services.prefs.getComplexValue( + "mail.addr_book.show_phonetic_fields", + Ci.nsIPrefLocalizedString + ).data == "true" + ) { + return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic"); + } + return Services.prefs.prefHasUserValue(aBasePrefName); +} + +/* + * Given a database model query and a list of search tokens, + * return query URI. + * + * @param aModelQuery database model query + * @param aSearchWords an array of search tokens. + * + * @return query URI. + */ +function generateQueryURI(aModelQuery, aSearchWords) { + // If there are no search tokens, we simply return an empty string. + if (!aSearchWords || aSearchWords.length == 0) { + return ""; + } + + let queryURI = ""; + aSearchWords.forEach( + searchWord => + (queryURI += aModelQuery.replace(/@V/g, encodeABTermValue(searchWord))) + ); + + // queryURI has all the (or(...)) searches, link them up with (and(...)). + queryURI = "?(and" + queryURI + ")"; + + return queryURI; +} + +/** + * Encode the string passed as value into an addressbook search term. + * The '(' and ')' characters are special for the addressbook + * search query language, but are not escaped in encodeURIComponent() + * so must be done manually on top of it. + */ +function encodeABTermValue(aString) { + return encodeURIComponent(aString) + .replace(/\(/g, "%28") + .replace(/\)/g, "%29"); +} |