diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/addrbook/modules/QueryStringToExpression.jsm | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/addrbook/modules/QueryStringToExpression.jsm')
-rw-r--r-- | comm/mailnews/addrbook/modules/QueryStringToExpression.jsm | 186 |
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; + }, +}; |