summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/modules/QueryStringToExpression.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/addrbook/modules/QueryStringToExpression.jsm')
-rw-r--r--comm/mailnews/addrbook/modules/QueryStringToExpression.jsm186
1 files changed, 186 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/modules/QueryStringToExpression.jsm b/comm/mailnews/addrbook/modules/QueryStringToExpression.jsm
new file mode 100644
index 0000000000..0129d2e3d3
--- /dev/null
+++ b/comm/mailnews/addrbook/modules/QueryStringToExpression.jsm
@@ -0,0 +1,186 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["QueryStringToExpression"];
+
+/**
+ * A module to parse a query string to a nsIAbBooleanExpression. A valid query
+ * string is in this form:
+ *
+ * (OP1(FIELD1,COND1,VALUE1)..(FIELDn,CONDn,VALUEn)(BOOL2(FIELD1,COND1,VALUE1)..)..)
+ *
+ * OPn A boolean operator joining subsequent terms delimited by ().
+ *
+ * @see {nsIAbBooleanOperationTypes}.
+ * FIELDn An addressbook card data field.
+ * CONDn A condition to compare FIELDn with VALUEn.
+ * @see {nsIAbBooleanConditionTypes}.
+ * VALUEn The value to be matched in the FIELDn via the CONDn.
+ * The value must be URL encoded by the caller, if it contains any
+ * special characters including '(' and ')'.
+ */
+var QueryStringToExpression = {
+ /**
+ * Convert a query string to a nsIAbBooleanExpression.
+ *
+ * @param {string} qs - The query string to convert.
+ * @returns {nsIAbBooleanExpression}
+ */
+ convert(qs) {
+ let tokens = this.parse(qs);
+
+ // An array of nsIAbBooleanExpression, the first element is the root exp,
+ // the last element is the current operating exp.
+ let stack = [];
+ for (let { type, depth, value } of tokens) {
+ while (depth < stack.length) {
+ // We are done with the current exp, go one level up.
+ stack.pop();
+ }
+ if (type == "op") {
+ if (depth == stack.length) {
+ // We are done with the current exp, go one level up.
+ stack.pop();
+ }
+ // Found a new exp, go one level down.
+ let parent = stack.slice(-1)[0];
+ let exp = this.createBooleanExpression(value);
+ stack.push(exp);
+ if (parent) {
+ parent.expressions = [...parent.expressions, exp];
+ }
+ } else if (type == "field") {
+ // Add a new nsIAbBooleanConditionString to the current exp.
+ let condition = this.createBooleanConditionString(...value);
+ let exp = stack.slice(-1)[0];
+ exp.expressions = [...exp.expressions, condition];
+ }
+ }
+
+ return stack[0];
+ },
+
+ /**
+ * Parse a query string to an array of tokens.
+ *
+ * @param {string} qs - The query string to parse.
+ * @param {number} depth - The depth of a token.
+ * @param {object[]} tokens - The tokens to return.
+ * @param {"op"|"field"} tokens[].type - The token type.
+ * @param {number} tokens[].depth - The token depth.
+ * @param {string|string[]} tokens[].value - The token value.
+ */
+ parse(qs, depth = 0, tokens = []) {
+ if (qs[0] == "?") {
+ qs = qs.slice(1);
+ }
+ while (qs[0] == ")" && depth > 0) {
+ depth--;
+ qs = qs.slice(1);
+ }
+ if (qs.length == 0) {
+ // End of input.
+ return tokens;
+ }
+ if (qs[0] != "(") {
+ throw Components.Exception(
+ `Invalid query string: ${qs}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ qs = qs.slice(1);
+ let nextOpen = qs.indexOf("(");
+ let nextClose = qs.indexOf(")");
+
+ if (nextOpen != -1 && nextOpen < nextClose) {
+ // Case: "OP("
+ depth++;
+ tokens.push({
+ type: "op",
+ depth,
+ value: qs.slice(0, nextOpen),
+ });
+ this.parse(qs.slice(nextOpen), depth, tokens);
+ } else if (nextClose != -1) {
+ // Case: "FIELD, COND, VALUE)"
+ tokens.push({
+ type: "field",
+ depth,
+ value: qs.slice(0, nextClose).split(","),
+ });
+ this.parse(qs.slice(nextClose + 1), depth, tokens);
+ }
+ return tokens;
+ },
+
+ /**
+ * Create a nsIAbBooleanExpression from a string.
+ *
+ * @param {string} operation - The operation string.
+ * @returns {nsIAbBooleanExpression}
+ */
+ createBooleanExpression(operation) {
+ let op = {
+ and: Ci.nsIAbBooleanOperationTypes.AND,
+ or: Ci.nsIAbBooleanOperationTypes.OR,
+ not: Ci.nsIAbBooleanOperationTypes.NOT,
+ }[operation];
+ if (op == undefined) {
+ throw Components.Exception(
+ `Invalid operation: ${operation}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ let exp = Cc["@mozilla.org/boolean-expression/n-peer;1"].createInstance(
+ Ci.nsIAbBooleanExpression
+ );
+ exp.operation = op;
+ return exp;
+ },
+
+ /**
+ * Create a nsIAbBooleanConditionString.
+ *
+ * @param {string} name - The field name.
+ * @param {nsIAbBooleanConditionTypes} condition - The condition.
+ * @param {string} value - The value string.
+ * @returns {nsIAbBooleanConditionString}
+ */
+ createBooleanConditionString(name, condition, value) {
+ value = decodeURIComponent(value);
+ let cond = {
+ "=": Ci.nsIAbBooleanConditionTypes.Is,
+ "!=": Ci.nsIAbBooleanConditionTypes.IsNot,
+ lt: Ci.nsIAbBooleanConditionTypes.LessThan,
+ gt: Ci.nsIAbBooleanConditionTypes.GreaterThan,
+ bw: Ci.nsIAbBooleanConditionTypes.BeginsWith,
+ ew: Ci.nsIAbBooleanConditionTypes.EndsWith,
+ c: Ci.nsIAbBooleanConditionTypes.Contains,
+ "!c": Ci.nsIAbBooleanConditionTypes.DoesNotContain,
+ "~=": Ci.nsIAbBooleanConditionTypes.SoundsLike,
+ regex: Ci.nsIAbBooleanConditionTypes.RegExp,
+ ex: Ci.nsIAbBooleanConditionTypes.Exists,
+ "!ex": Ci.nsIAbBooleanConditionTypes.DoesNotExist,
+ }[condition];
+ if (name == "" || condition == "" || value == "" || cond == undefined) {
+ throw Components.Exception(
+ `Failed to create condition string from name=${name}, condition=${condition}, value=${value}, cond=${cond}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ let cs = Cc[
+ "@mozilla.org/boolean-expression/condition-string;1"
+ ].createInstance(Ci.nsIAbBooleanConditionString);
+ cs.condition = cond;
+
+ try {
+ cs.name = Services.textToSubURI.unEscapeAndConvert("UTF-8", name);
+ cs.value = Services.textToSubURI.unEscapeAndConvert("UTF-8", value);
+ } catch (e) {
+ cs.name = name;
+ cs.value = value;
+ }
+ return cs;
+ },
+};