Greasy Fork

rsel-exprparser-basic

Parses RSel-specific expression text and rebuilds it in the UI.

目前为 2016-03-07 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.icu/scripts/17641/111915/rsel-exprparser-basic.js

// ==UserScript==
// @name            rsel-exprparser-basic
// @namespace       https://greasyfork.org/users/11629-TheLastTaterTot
// @version         0.3.5
// @description     Parses RSel-specific expression text and rebuilds it in the UI.
// @author          TheLastTaterTot
// @include         https://editor-beta.waze.com/*editor/*
// @include         https://www.waze.com/*editor/*
// @exclude         https://www.waze.com/*user/editor/*
// @grant           none
// @run-at          document-end
// ==/UserScript==

// Main usage: RSelExprParser.updateExpression(<rsel expression text>)

var RSelExprParser = {
    version: '0.3.4',
    new__EXPR_DEBUGINFO: function(m, exprWord, exprPhrase) {
        return {
            m: m,
            exprMatches: exprWord,
            exprMatchPhrases: exprPhrase,
            exprBuild: {},
            err: null ,
            errorMsg: null
        };
    },
    _getSelectionIndex: function(selector, selText) {
        return selector.map(function(i) {
            if (new RegExp(selText,'i').test(this.innerText))
                return this.value
        }).get(0);
    },
    _getSelectOptions: function(selector) {
        var opts = [];
        selector.map(function(i, a) {
            opts.push(a.innerText.toLowerCase());
        });
        return opts;
    },
    _getNewExprBuild: function() {
        return {
            cond: null ,
            op: null ,
            op2: null ,
            val: null ,
            val2: null ,
            condmod: null ,
            errorCode: 0

        }
    },
    getCurrentExprText: function(){
        return document.getElementById('outRSExpr').value;
    },
    /*Using RSel DOM elements rather than requesting dev to provide direct modifiction of RSel's expr object.
        This is so the RSel dev can feel free to significantly change his object storage structure if needed. */
    rselButtons: {
        lfParens: function() {
            try {
                document.getElementById('btnRSLBkt').click();
            } catch (err) {}
        },
        rtParens: function() {
            try {
                document.getElementById('btnRSRBkt').click();
            } catch (err) {}
        },
        and: function() {
            try {
                document.getElementById('btnRSAnd').click()
            } catch (err) {}
        },
        or: function() {
            try {
                document.getElementById('btnRSOr').click()
            } catch (err) {}
        },
        not: function() {
            try {
                document.getElementById('btnRSNot').click()
            } catch (err) {}
        },
        clear: function() {
            try {
                document.getElementById('btnRSClear').click()
            } catch (err) {}
        }
    },
    rselConditions: {
        country: {
            op: function(selText) {
                document.getElementById('opRSCountry').value = RSelExprParser._getSelectionIndex($('#opRSCountry option'), selText);
            },
            val: function(selText) {
                document.getElementById('selRSCountry').value = RSelExprParser._getSelectionIndex($('#selRSCountry option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddCountry').click();
            }
        },
        state: {
            op: function(selText) {
                document.getElementById('opRSState').value = RSelExprParser._getSelectionIndex($('#opRSState option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSState').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddState').click();
            }
        },
        city: {
            op: function(selText) {
                document.getElementById('opRSCity').value = RSelExprParser._getSelectionIndex($('#opRSCity option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSCity').value = val;
            },
            condmod: function(val) {
                document.getElementById('selRSAltCity').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddCity').click();
            }
        },
        street: {
            op: function(selText) {
                document.getElementById('opRSStreet').value = RSelExprParser._getSelectionIndex($('#opRSStreet option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSStreet').value = val;
            },
            condmod: function(val) {
                document.getElementById('selRSAlttStreet').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddStreet').click();
            }
        },
        unnamed: {
            op: function(checked) {
                document.getElementById('cbRSNoName').checked = checked;
            },
            //checked - has no name
            op2: function(checked) {
                document.getElementById('cbRSAltNoName').checked = checked;
            },
            //checked - alt name
            add: function() {
                document.getElementById('btnRSAddNoName').click();
            }
        },
        road: {
            op: function(selText) {
                document.getElementById('opRSRoadType').value = RSelExprParser._getSelectionIndex($('#opRSRoadType option'), selText);
            },
            val: function(selText) {
                document.getElementById('selRSRoadType').value = RSelExprParser._getSelectionIndex($('#selRSRoadType option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddRoadType').click();
            }
        },
        direction: {
            op: function(selText) {
                document.getElementById('opRSDirection').value = RSelExprParser._getSelectionIndex($('#opRSDirection option'), selText);
            },
            val: function(selText) {
                document.getElementById('selRSDirection').value = RSelExprParser._getSelectionIndex($('#selRSDirection option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddDirection').click();
            }
        },
        elevation: {
            op: function(selText) {
                document.getElementById('opRSElevation').value = RSelExprParser._getSelectionIndex($('#opRSElevation option'), selText);
            },
            val: function(selText) {
                document.getElementById('selRSElevation').value = RSelExprParser._getSelectionIndex($('#selRSElevation option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddElevation').click();
            }
        },
        manlock: {
            op: function(selText) {
                document.getElementById('opRSManLock').value = RSelExprParser._getSelectionIndex($('#opRSManLock option'), selText);
            },
            val: function(val) {
                document.getElementById('selRSManLock').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddManLock').click();
            }
        },
        traflock: {
            op: function(selText) {
                document.getElementById('opRSTrLock').value = RSelExprParser._getSelectionIndex($('#opRSTrLock option'), selText);
            },
            val: function(val) {
                document.getElementById('selRSTrLock').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddTrLock').click();
            }
        },
        speed: {
            opOptNodes: $('#opRSSpeed option'),
            op: function(selText) {
                document.getElementById('opRSSpeed').value = RSelExprParser._getSelectionIndex($('#opRSSpeed option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSSpeed').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddSpeed').click();
            }
        },
        closure: {
            op: function(checked) {
                document.getElementById('cbRSClsr').checked = checked;
            },
            op2: function(selText) {
                document.getElementById('opRSClsrStrtEnd').value = RSelExprParser._getSelectionIndex($('#opRSClsrStrtEnd option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSClsrDays').value = val;
            },
            condmod: function(selText) {
                document.getElementById('opRSClsrBeforeAter').value = RSelExprParser._getSelectionIndex($('#opRSClsrBeforeAter option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddClsr').click();
            }
        },
        updatedby: {
            op: function(selText) {
                document.getElementById('opRSUpdtd').value = RSelExprParser._getSelectionIndex($('#opRSUpdtd option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSUpdtd').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddUpdtd').click();
            }
        },
        createdby: {
            op: function(selText) {
                document.getElementById('opRSCrtd').value = RSelExprParser._getSelectionIndex($('#opRSCrtd option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSCrtd').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddCrtd').click();
            }
        },
        last: {
            op: function(selText) {
                document.getElementById('opRSLastU').value = RSelExprParser._getSelectionIndex($('#opRSLastU option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSLastU').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddLastU').click();
            }
        },
        length: {
            op: function(selText) {
                document.getElementById('opRSLength').value = RSelExprParser._getSelectionIndex($('#opRSLength option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSLength').value = val;
            },
            condmod: function(selText) {
                document.getElementById('unitRSLength').value = RSelExprParser._getSelectionIndex($('#unitRSLength option'), selText);
            },
            add: function() {
                document.getElementById('btnRSAddLength').click();
            }
        },
        id: {
            op: function(selText) {
                document.getElementById('opRSSegId').value = RSelExprParser._getSelectionIndex($('#opRSSegId option'), selText);
            },
            val: function(val) {
                document.getElementById('inRSSegId').value = val;
            },
            add: function() {
                document.getElementById('btnRSAddSegId').click();
            }
        },
        roundabout: {
            op: function(checked) {
                document.getElementById('cbRSIsRound').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddIsRound').click();
            }
        },
        toll: {
            op: function(checked) {
                document.getElementById('cbRSIsToll').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddIsToll').click();
            }
        },
        tunnel: {
            op: function(checked) {
                document.getElementById('cbRSTunnel').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddTunnel').click();
            }
        },
        new: {
            op: function(checked) {
                document.getElementById('cbRSIsNew').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddIsNew').click();
            }
        },
        changed: {
            op: function(checked) {
                document.getElementById('cbRSIsChngd').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddIsChngd').click();
            }
        },
        screen: {
            op: function(checked) {
                document.getElementById('cbRSOnScr').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddOnScr').click();
            }
        },
        restriction: {
            op: function(checked) {
                document.getElementById('cbRSRestr').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddRestr').click();
            }
        },
        editable: {
            op: function(checked) {
                document.getElementById('cbRSEdtbl').checked = checked;
            },
            add: function() {
                document.getElementById('btnRSAddEdtbl').click();
            }
        }
    },
    addExpr: function(eb) {
        var checkKeys = false;
        Object.keys(this.rselConditions).map(function(a, i) {
            if (a === eb.cond)
                checkKeys = true;
        });
        if (checkKeys) {
            try {
                this.rselConditions[eb.cond].op(eb.op);
                if (eb.op2 !== null )
                    this.rselConditions[eb.cond].op2(eb.op2);
                if (eb.condmod !== null )
                    this.rselConditions[eb.cond].condmod(eb.condmod);

                if (eb.val2 === null ) {
                    if (eb.val !== null )
                        this.rselConditions[eb.cond].val(eb.val);
                    this.rselConditions[eb.cond].add();
                } else {
                    this.rselButtons.lfParens();
                    this.rselConditions[eb.cond].val(eb.val);
                    this.rselConditions[eb.cond].add();
                    this.rselButtons.or();
                    this.rselConditions[eb.cond].val(eb.val2);
                    this.rselConditions[eb.cond].add();
                    this.rselButtons.rtParens();
                }

            } catch (err) {
                return {
                    errorCode: 101,
                    errorMsg: 'Error: Unable to parse expression text.',
                    err: err
                };
            }
        } else {
            return {
                errorCode: 3,
                errorMsg: 'Selection condition was not recognized'
            };
            //
        }
        return {
            errorCode: 0
        };
    },
    //=============================================================================
    parseExpr: function(parseThis) {
        //---------------------------------------------------------------
        parseThis = parseThis.replace(/\bpri?m?(?:ary|\.)?\s?(?:or)\s?alt(?:ern|s)?(?:\.)?/ig, 'any');
        parseThis = parseThis.replace(/\b((?:un)?name[ds]?)\b|\b(road) type\b|\b(last) update\b|\b(speed) limits?\b/ig, '$1$2$3$4')
        parseThis = parseThis.replace(/\b(man)ual (lock)s?\b|\b(traf)[fic]* (lock)s?\b/ig, '$1$2$3$4');
        parseThis = parseThis.replace(/\b(created|updated)\s(by)\b/ig, '$1$2');
        parseThis = parseThis.replace(/\bon screen/ig, 'onscreen');
        //\b(?:in|on|off|out|outside)(?: of)?[- ]?screen\b
        parseThis = parseThis.replace(/\b(?:off|out)(?: of)?[- ]?screen/ig, 'offscreen');

        var parseExprArray = parseThis.match(
        /(\(['"].*?['"]\)|".*?"|'.*?')|\bno[\s-]alt|\b(?:street[\s-]?)?name\(s\)|\bstreet(?:\snames?)\b|\btoll(?:[-\s]?ro?a?d)?\b|\bdoes(?:\s?n[o']t)\b|(?:!\s?)?contains?\b|!=|>=|<=|[ab][<->]{2}[ab]|\w+(\(s\))?|&&|\|\||!=|[|&<>=()!~]/gi
        ),
            parseExprHistory = [],
            condMatches = [],
            condMatchPhrases = [],
            exprMatches = [],
            exprMatchPhrases = [],
            exprFragment, unwantedWordsSearch,
            e, f, b, fLength;

        // The following parses the expression text into unique chunks within separate array elements
        e = parseExprArray.length;
        while (e-- > 0) {
            try {
                exprFragment = parseExprArray.shift();
                //console.info(exprFragment);

                // Find operators that join individual expressions (AND|OR|!|parenthesis)
                if (/^(?:and|or|&&|\|\||!=|[=&|()!])$/i.test(exprFragment)) {
                    exprMatches.push(exprFragment.toLowerCase());
                    exprMatchPhrases.push(exprFragment.toLowerCase());
                }

                // Identify elements that contain selection condition names
                if (
                /^country|^state|^city|^street|^(?:un|street[\s-]?)?name|^road|^round|^toll|^speed|^dir|^elevation|^tun|^manlock|^traflock|^speed|^new|^changed|screen$|^restrict|^clos|^createdby|^last|^updatedby|^length|^id|^editable/i
                .test(exprFragment)) {
                    condMatches.push(exprFragment.toLowerCase());
                    // lists specific selection conditions
                    exprMatches.push(exprFragment.toLowerCase());
                    //same as condMatches, but includes operations as separate array elements

                    try {
                        //search phrase fowards
                        fLength = parseExprArray.length;
                        f = 0;
                        while (!(/^(and|or|&&|\|\||[&|)])$/i.test(parseExprArray[f])) && (++f < fLength)) {}
                        //search phrase backwards
                        b = parseExprHistory.length;
                        while (!(/^(and|or|&&|\|\||[&|(])$/i.test(parseExprHistory[b - 1])) && (--b > 0)) {}

                        condMatchPhrases.push(parseExprHistory.slice(b).concat(exprFragment, parseExprArray.slice(0, f)));
                        //list specific selection conditions and its criteria

                        unwantedWordsSearch = parseExprHistory.slice(b);
                        if (unwantedWordsSearch && unwantedWordsSearch.length) {
                            unwantedWordsSearch = unwantedWordsSearch.filter(function(a) {
                                return !/\b(has|have|is|=|are|does|was|were)\b/i.test(a)
                            });
                        }
                        if (/!|!=/.test(unwantedWordsSearch[0]))
                            unwantedWordsSearch.splice(0, 1);

                        exprMatchPhrases.push(unwantedWordsSearch.concat(parseExprArray.slice(0, f)));
                        //excludes the match cond

                        parseExprHistory = parseExprHistory.concat(exprFragment, parseExprArray.slice(0, f));
                        parseExprArray = parseExprArray.slice(f);
                        e -= f;
                    } catch (err) {
                        return {
                            errorCode: 101,
                            errorMsg: 'Error parsing expression at ' + exprFragment,
                            err: err
                        };
                    }
                } else {
                    parseExprHistory.push(exprFragment);
                }
            } catch (err) {
                return {
                    errorCode: 101,
                    errdebug: 'Error parsing expression at ' + exprFragment,
                    err: err
                };
            }
        }
        //while


        //---------------------------------------------------------------
        // Quick crude check for unmatched parentheses
        var nOpenParens = exprMatches.toString().match(/\(/g),
        nCloseParens = exprMatches.toString().match(/\)/g);
        if (!nOpenParens) nOpenParens = [];
        if (!nCloseParens) nCloseParens = [];
        if (nOpenParens.length !== nCloseParens.length)
            return {
                errorCode: 1,
                errorMsg: 'Warning: Open and close paretheses may be unmatched.'
            };

        //---------------------------------------------------------------

        return {
            errorCode: 0,
            exprMatches: exprMatches,
            exprMatchPhrases: exprMatchPhrases,
            condMatches: condMatches,
            condMatchPhrases: condMatchPhrases
        };
    },
    buildExpr: function(exprWord, exprPhrase) {

        var exprBuild = RSelExprParser._getNewExprBuild();
        exprBuild.cond = exprWord;

        //if (m===10) debugger;

        //============================================================
        // Where the magic happens... sort of.
        //============================================================
        switch (true) {
        case exprWord === '(':
            this.rselButtons.lfParens();
            return false;
        case exprWord === ')':
            this.rselButtons.rtParens();
            return false;
        case 'and' === exprWord:
            this.rselButtons.and();
            return false;
        case 'or' === exprWord:
            this.rselButtons.or();
            return false;
        case /no alt/i.test(exprPhrase):
            exprBuild.cond = 'unnamed';
            exprBuild.op = true;
            exprBuild.op2 = true;
            return exprBuild;
        case '!' === exprWord:
            this.rselButtons.not();
            return false;
        case /^unnamed/.test(exprBuild.cond):
            exprBuild.cond = 'unnamed';
            exprBuild.op = true;
            exprBuild.op2 = false;
            return exprBuild;

            // SPEED LIMITS
        case 'speed' === exprBuild.cond:
            try {
                if (exprPhrase.length < 2 && /\bnot?\b|!|!=/i.test(exprPhrase[0])) {
                    exprBuild.op = 'none';
                } else {
                    exprPhrase = exprPhrase.join(' ');

                    if (/\bnot?\b|!|!=/i.test(exprPhrase)) {
                        RSelExprParser.rselButtons.not();
                    }

                    var optionText = RSelExprParser._getSelectOptions(RSelExprParser.rselConditions.speed.opOptNodes);
                    optionText = new RegExp(optionText.join('|'), 'i')
                    optionText = optionText.exec(exprPhrase);

                    if (optionText) {
                        exprBuild.op = optionText+'';
                    } else {
                        exprBuild.op = 'any';
                    }
                }

                if (exprPhrase) {
                    var speedVal = exprPhrase.match(/(\d+)\s?mph|(\d+)\s?km/i);
                    if (speedVal && speedVal.length === 2) {
                        exprBuild.val = speedVal[1];
                    }
                }
            } catch (err) {
                exprBuild.errorCode = 101;
                exprBuild.err = err;
                return exprBuild;
            }
            return exprBuild;

            // BINARY CONDITIONS:
        case exprPhrase.length === 0 || //suggests binary
        /^(screen|roundabout|toll|tun|new|changed|restrict|editable)/.test(exprBuild.cond) || //binary selection conditions
        (/^name.*|^closure/i.test(exprBuild.cond) && exprPhrase.length <= 1):
            //selection conditions that have both binary and multiple options

            exprPhrase = exprPhrase.join(' ');

            exprBuild.cond = exprBuild.cond.replace(/^name.*/, 'name');
            exprBuild.cond = exprBuild.cond.replace(/^toll\s.*/, 'toll');

            if (/\bnot?\b|!|!=/i.test(exprPhrase)) {
                exprBuild.op = false;
            } else {
                exprBuild.op = true;
            }
            switch (exprBuild.cond) {
            case 'name':
                try {
                    if (/alt/i.test(exprPhrase)) {
                        exprBuild.cond = 'unnamed';
                        exprBuild.op = false;
                        exprBuild.op2 = true;
                    } else {
                        exprBuild.cond = 'unnamed';
                        exprBuild.op = false;
                        exprBuild.op2 = false;
                    }
                    return exprBuild;
                } catch (err) {
                    exprBuild.errorCode = 101;
                    exprBuild.err = err;
                    return exprBuild;
                }
            case 'closure':
                exprBuild.op2 = '---';
                return exprBuild;
            case 'onscreen':
                exprBuild.cond = 'screen';
                exprBuild.op = true;
                return exprBuild;
            case 'offscreen':
                exprBuild.cond = 'screen';
                exprBuild.op = false;
                return exprBuild;
            case 'roundabout':
            case 'toll':
            case 'tunnel':
            case 'new':
            case 'changed':
            case 'restriction':
            case 'editable':
                return exprBuild;
            default:
                exprBuild.errorCode = 101;
                exprBuild.errorMsg = 'Error: Presumed binary selector had no match.';
                return exprBuild;
            }
            //switch

            //--------------------------------------------------------------------

        case /^closure/.test(exprBuild.cond):
            try {
                exprPhrase = exprPhrase.join().toLowerCase();
                exprBuild.op = !(/does\s?n['o]t|!|!=/.test(exprPhrase));
                //checkbox
                exprBuild.op2 = /start|end/.exec(exprPhrase) + 's';
                //starts/ends
                exprBuild.condmod = /before|after|\bin\b/.exec(exprPhrase) + '';
                //in/before/after
                if (!exprBuild.condmod)
                    exprBuild.condmod = 'in';
                exprBuild.val = /\d+/.exec(exprPhrase) + '';
                //days ago
            } catch (err) {
                exprBuild.errorCode = 101;
                exprBuild.err = err;
                return exprBuild;
            }
            return exprBuild;

        default:
            // CONDITION NAME MATCHING (TYPE OF SELECTION)
            try {
                if (/^(str.*|cit.*)/.test(exprBuild.cond)) {
                    exprBuild.cond = exprBuild.cond.replace(/^str.*/, 'street');
                    exprBuild.cond = exprBuild.cond.replace(/^cit.*/, 'city');
                    var exprStart = exprPhrase.slice(0, -1), //don't include last element bc it should be the name itself
                    prim, alt;
                    if (exprStart) {
                        //exprStart = exprStart.toString().toLowerCase();
                        prim = /\bprim?(?:ary|\.)?\b/i.test(exprStart);
                        alt = /\balt(?:ern\w*|\.)?\b/i.test(exprStart);
                        exprPhrase = exprStart.filter(function(a) {
                            return !/^pri|^alt/i.test(a)
                        }).concat(exprPhrase.slice(-1));
                    } else {
                        prim = false;
                        alt = false;
                    }
                    if (prim && alt)
                        exprBuild.condmod = 2;
                    else if (prim)
                        exprBuild.condmod = 0;
                    else if (alt)
                        exprBuild.condmod = 1;
                    else
                        exprBuild.condmod = 0;
                }
            } catch (err) {
                exprBuild.errorCode = 101;
                exprBuild.err = err;
                return exprBuild;
            }

            // COMPARATOR OPERATION MATCHING
            try {
                // Convert natural lang representation to standard comparator operations
                var exprPhraseStr = exprPhrase.join(' ').replace(/\bcontains?/i, 'contains').replace(/(?:\bdo(?:es)?\s?n[o']t\s|!\s?)(contains)/i, '! $1');
                //.replace(/\b(?:do(?:es)?\s?n[o']t\s|!\s?)contains?/i, '!^').replace(/\bcontains?/i,'\u220b');

                // Comparator operations with standard representation
                exprBuild.op = /(?:! )?contains|[!<>=~]{1,2}/i.exec(exprPhraseStr) + '';

            } catch (err) {
                exprBuild.errorCode = 101;
                exprBuild.err = err;
                return exprBuild;
            }

            // SELECTION VALUE MATCHING
            try {
                if (/^length|^last/.test(exprBuild.cond)) {
                    exprBuild.val = exprPhraseStr.match(/\b\d+/) + ''
                } else {
                    try {
                        // The following line is kind of elaborate bc it needed to grab text between parens/quotes while keeping the inner quotes
                        exprBuild.val = exprPhraseStr.replace(new RegExp('^(?:\\s?' + exprBuild.op + '\\s)(.*)','i'), '$1').replace(/^\(["'](.*?)['"]\)$|^\s?["'](.*)["']$|^["'](.*)['"]$|\b(\w*?)\b/, '$1$2$3$4').replace(/(") (\w) (")/, '$1$2$3');

                    } catch (err) {
                        exprBuild.errorCode = 2;
                        exprBuild.err = err;
                        return exprBuild;
                    }

                    if (/^direction/.test(exprBuild.cond)) {
                        exprBuild.val = exprBuild.val.match(/A[<>-\s]*B|B[<>-\s]*A|unknown/i) + '';
                        //reduce to unique key words...
                    }
                }

                return exprBuild;

            } catch (err) {
                exprBuild.errorCode = 101;
                exprBuild.err = err;
                return exprBuild;
            }
        }
        //switch
    },
    //parseExpr()
    updateExpression: function(parseThis) {
        this.rselButtons.clear();
        if (parseThis) {
            //console.info('*** Begin parsing expression... ***');


            var parsed = this.parseExpr(parseThis);

            if (parsed && !parsed.errorCode) {
                var exprMatches = parsed.exprMatches,
                exprMatchPhrases = parsed.exprMatchPhrases,
                exprFragment, exprFragPhrase, mLength, m, __EXPR_DEBUGINFO;

                mLength = exprMatchPhrases.length;
                for (m = 0; m < mLength; m++) {
                    __EXPR_DEBUGINFO = this.new__EXPR_DEBUGINFO(m, exprMatches[m], exprMatchPhrases[m]);

                    //if (m > 3) debugger;

                    exprFragment = exprMatches[m];
                    exprFragPhrase = exprMatchPhrases[m];

                    if (exprFragPhrase.constructor !== Array) exprFragPhrase = [exprFragPhrase];

                    var exprBuild = this.buildExpr(exprFragment, exprFragPhrase);

                    if (exprBuild && !exprBuild.errorCode) {
                        __EXPR_DEBUGINFO.errorStatus = this.addExpr(exprBuild);

                        if (__EXPR_DEBUGINFO.errorStatus && __EXPR_DEBUGINFO.errorStatus.errorCode) {
                            console.warn('updateExpression() may have partly failed. Check results.');
                            __EXPR_DEBUGINFO.exprBuild = exprBuild;
                            console.debug(__EXPR_DEBUGINFO);
                            return false;
                        }
                    } else if (exprBuild && exprBuild.errorCode) {
                        console.warn('updateExpression() may have partly failed. Check results.');
                        __EXPR_DEBUGINFO.exprBuild = exprBuild;
                        console.debug(__EXPR_DEBUGINFO);
                        return false;
                    }
                } //for each condition matched

                return this.getCurrentExprText();
            } else {
                console.debug(parsed);
                return false;
            }
        } else {
            return null;
        }
    }
};