WME Road Selector Expression Editor

(Beta) Allows easier editing of the selection criteria by converting the expressions into editable text

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

// ==UserScript==
// @name            WME Road Selector Expression Editor
// @namespace       https://gf.qytechs.cn/users/11629-TheLastTaterTot
// @version         0.3
// @description     (Beta) Allows easier editing of the selection criteria by converting the expressions into editable text
// @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==

/* global __EXPR_DEBUGINFO */
var ExprEditor = function() {
    var _rsel, rselBtns, rselCond, updateHi = false;

    //$('#cmEditExprBox').val($('#outRSExpr').html().replace(/&gt;/g, '>').replace(/&lt;/g, '<'));
    $('#cmEditExprBox').val($('#outRSExpr').val());

    $('#cmCopyToRSelExpr').click(function() {
        $('#cmEditExprBox').val($('#outRSExpr').val());
    });

    $('#cmParseRSelExpr').click(function() {
        updateHi = false;
        document.getElementById('cmParseRSelExpr').style.color = '';
        updateExpr();
    });

    $('#cmCloseExprEditor').click(function() {
        document.getElementById('cmExprEditor').remove();
    });

    document.getElementById('cmEditExprBox').addEventListener('focus', function(){
        updateHi = true;
    }, false);

    document.getElementById('cmEditExprBox').addEventListener('blur', function(){
        if (this.value !== '' && updateHi) {
            document.getElementById('cmParseRSelExpr').style.color = '#2196F3';
        }
    }, false);

    _rsel = {
        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
            }
        }
    };/*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. */

    rselBtns = {
        lfParens: function() {
            try {
                $('#btnRSLBkt').click();
            } catch (err) {}
        },
        rtParens: function() {
            try {
                $('#btnRSRBkt').click();
            } catch (err) {}
        },
        and: function() {
            try {
                $('#btnRSAnd').click()
            } catch (err) {}
        },
        or: function() {
            try {
                $('#btnRSOr').click()
            } catch (err) {}
        },
        not: function() {
            try {
                $('#btnRSNot').click()
            } catch (err) {}
        },
        clear: function() {
            try {
                $('#btnRSClear').click()
            } catch (err) {}
        }
    };

    rselCond = {
        country: {
            op: function(selText) {
                $('#opRSCountry').val(_rsel.getSelectionIndex($('#opRSCountry option'), selText));
            },
            val: function(selText) {
                $('#selRSCountry').val(_rsel.getSelectionIndex($('#selRSCountry option'), selText));
            },
            add: function() {
                $('#btnRSAddCountry').click();
            }
        },
        state: {
            op: function(selText) {
                $('#opRSState').val(_rsel.getSelectionIndex($('#opRSState option'), selText));
            },
            val: function(val) {
                $('#inRSState').val(val);
            },
            add: function() {
                $('#btnRSAddState').click();
            }
        },
        city: {
            op: function(selText) {
                $('#opRSCity').val(_rsel.getSelectionIndex($('#opRSCity option'), selText));
            },
            val: function(val) {
                $('#inRSCity').val(val);
            },
            condmod: function(val) {
                $('#selRSAltCity').val(val);
            },
            add: function() {
                $('#btnRSAddCity').click();
            }
        },
        street: {
            op: function(selText) {
                $('#opRSStreet').val(_rsel.getSelectionIndex($('#opRSStreet option'), selText));
            },
            val: function(val) {
                $('#inRSStreet').val(val);
            },
            condmod: function(val) {
                $('#selRSAlttStreet').val(val);
            },
            add: function() {
                $('#btnRSAddStreet').click();
            }
        },
        unnamed: {
            op: function(chkVal) {
                $('#cbRSNoName').prop('checked', chkVal);
            }, //checked - has no name
            op2: function(chkVal) {
                $('#cbRSAltNoName').prop('checked', chkVal);
            }, //checked - alt name
            add: function() {
                $('#btnRSAddNoName').click();
            }
        },
        road: {
            op: function(selText) {
                $('#opRSRoadType').val(_rsel.getSelectionIndex($('#opRSRoadType option'), selText));
            },
            val: function(selText) {
                $('#selRSRoadType').val(_rsel.getSelectionIndex($('#selRSRoadType option'), selText));
            },
            add: function() {
                $('#btnRSAddRoadType').click();
            }
        },
        direction: {
            op: function(selText) {
                $('#opRSDirection').val(_rsel.getSelectionIndex($('#opRSDirection option'), selText));
            },
            val: function(selText) {
                $('#selRSDirection').val(_rsel.getSelectionIndex($('#selRSDirection option'), selText));
            },
            add: function() {
                $('#btnRSAddDirection').click();
            }
        },
        elevation: {
            op: function(selText) {
                $('#opRSElevation').val(_rsel.getSelectionIndex($('#opRSElevation option'), selText));
            },
            val: function(selText) {
                $('#selRSElevation').val(_rsel.getSelectionIndex($('#selRSElevation option'), selText));
            },
            add: function() {
                $('#btnRSAddElevation').click();
            }
        },
        manlock: {
            op: function(selText) {
                $('#opRSManLock').val(_rsel.getSelectionIndex($('#opRSManLock option'), selText));
            },
            val: function(val) {
                $('#selRSManLock').val(val);
            },
            add: function() {
                $('#btnRSAddManLock').click();
            }
        },
        traflock: {
            op: function(selText) {
                $('#opRSTrLock').val(_rsel.getSelectionIndex($('#opRSTrLock option'), selText));
            },
            val: function(val) {
                $('#selRSTrLock').val(val);
            },
            add: function() {
                $('#btnRSAddTrLock').click();
            }
        },
        speed: {
            opOptNodes: $('#opRSSpeed option'),
            op: function(selText) {
                $('#opRSSpeed').val(_rsel.getSelectionIndex($('#opRSSpeed option'), selText));
            },
            val: function(val) {
                $('#inRSSpeed').val(val);
            },
            add: function() {
                $('#btnRSAddSpeed').click();
            }
        },
        closure: {
            op: function(checked) {
                $('#cbRSClsr').prop('checked', checked);
            },
            op2: function(selText) {
                $('#opRSClsrStrtEnd').val(_rsel.getSelectionIndex($('#opRSClsrStrtEnd option'), selText));
            },
            val: function(val) {
                $('#inRSClsrDays').val(val);
            },
            condmod: function(selText) {
                $('#opRSClsrBeforeAter').val(_rsel.getSelectionIndex($('#opRSClsrBeforeAter option'), selText));
            },
            add: function() {
                $('#btnRSAddClsr').click();
            }
        },
        updatedby: {
            op: function(selText) {
                $('#opRSUpdtd').val(_rsel.getSelectionIndex($('#opRSUpdtd option'), selText));
            },
            val: function(val) {
                $('#inRSUpdtd').val(val);
            },
            add: function() {
                $('#btnRSAddUpdtd').click();
            }
        },
        createdby: {
            op: function(selText) {
                $('#opRSCrtd').val(_rsel.getSelectionIndex($('#opRSCrtd option'), selText));
            },
            val: function(val) {
                $('#inRSCrtd').val(val);
            },
            add: function() {
                $('#btnRSAddCrtd').click();
            }
        },
        last: {
            op: function(selText) {
                $('#opRSLastU').val(_rsel.getSelectionIndex($('#opRSLastU option'), selText));
            },
            val: function(val) {
                $('#inRSLastU').val(val);
            },
            add: function() {
                $('#btnRSAddLastU').click();
            }
        },
        length: {
            op: function(selText) {
                $('#opRSLength').val(_rsel.getSelectionIndex($('#opRSLength option'), selText));
            },
            val: function(val) {
                $('#inRSLength').val(val);
            },
            condmod: function(selText) {
                $('#unitRSLength').val(_rsel.getSelectionIndex($('#unitRSLength option'), selText));
            },
            add: function() {
                $('#btnRSAddLength').click();
            }
        },
        id: {
            op: function(selText) {
                $('#opRSSegId').val(_rsel.getSelectionIndex($('#opRSSegId option'), selText));
            },
            val: function(val) {
                $('#inRSSegId').val(val);
            },
            add: function() {
                $('#btnRSAddSegId').click();
            }
        },
        roundabout: {
            op: function(checked) {
                $('#cbRSIsRound').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddIsRound').click();
            }
        },
        toll: {
            op: function(checked) {
                $('#cbRSIsToll').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddIsToll').click();
            }
        },
        tunnel: {
            op: function(checked) {
                $('#cbRSTunnel').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddTunnel').click();
            }
        },
        new: {
            op: function(checked) {
                $('#cbRSIsNew').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddIsNew').click();
            }
        },
        changed: {
            op: function(checked) {
                $('#cbRSIsChngd').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddIsChngd').click();
            }
        },
        screen: {
            op: function(checked) {
                $('#cbRSOnScr').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddOnScr').click();
            }
        },
        restriction: {
            op: function(checked) {
                $('#cbRSRestr').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddRestr').click();
            }
        },
        editable: {
            op: function(checked) {
                $('#cbRSEdtbl').prop('checked', checked);
            },
            add: function() {
                $('#btnRSAddEdtbl').click();
            }
        }
    };

    rselCond.autolock = rselCond.traflock;
    rselCond.lock = {
            op: rselCond.manlock.op,
            val: rselCond.manlock.val,
            add: rselCond.manlock.add,
            op2: rselCond.traflock.op,
            val2: rselCond.traflock.val,
            add2: rselCond.traflock.add
        };
    ////////////////////////////////////////////////////////////////////////////////
    // On error
    var derp = function(errdebug, err) {
        var errorMsg;
        if (err !== undefined) console.error(err);
        if (errdebug.constructor === Number) {
            switch (errdebug) {
                case 1:
                    errorMsg = 'Warning: Open and close paretheses may be unmatched.';
                    break;
                case 2:
                    errorMsg = 'Error: Try wrapping the names with quotes?';
                    break;
                case 3:
                    errorMsg = 'Error: Selection condition was not recognized.';
                    break;
                case 4:
                    errorMsg = 'Error: Presumed binary selector had no match.';
                    break;
                default: //error 101
                    errorMsg = 'Error: Unable to parse expression text.';
            }
        } else {
            errorMsg = 'Error: Unable to parse expression text.';
            console.debug(errdebug);
        }

        if (document.getElementById('cmExprError') === null) {
            $('#cmEditExprBox').css('border-color', 'red');
            $('#cmEditExprBox').after('<span id="cmExprError" style="text-align: center; color: crimson; width: 100%; font-size: 10px; float: right;">' + errorMsg +
                '</span>');

            document.getElementById('cmEditExprBox').onfocus = function() {
                $('#cmEditExprBox').css('border-color', '');
                $('#cmExprError').remove();
                document.getElementById('cmEditExprBox').onfocus = null;
            };
        }
    };

    var translateNaturalLang = function(exprFragPhrase) {
        var exprFragPhraseStr;

        exprFragPhraseStr = exprFragPhrase.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');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:is\s|are\s|was\s|were\s|has\s|have\s)?(?:less|fewer)\sthan\b|\bbefore\b/i, '<');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:is\s|are\s|was\s|were\s|has\s|have\s)?(?:greater|more)\sthan\b|\bafter\b/i, '>');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:is\s|are\s|was\s|were\s|has\s|have\s)?(?:less|fewer)\s(?:than\s)?or\sequal\sto/i, '<=');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:is\s|are\s|was\s|were\s|has\s|have\s)?(?:greater|more)\s(?:than\s)?or\sequal\sto/i, '>=');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:does|has|is|was|were|are)(?:\s?n[o']t)(?:\sequal)?(?:\sto)?\b/i, '!=');
        exprFragPhraseStr = exprFragPhraseStr.replace(/\b(?:has|have|is|was|were|are)(?:\sequal)?|={1,3}|\bin\b/i, '=');

        return exprFragPhraseStr;
    };

    ////////////////////////////////////////////////////////////////////////////////
    function updateExpr() {

       console.info('*** Begin parsing expression... ***');
        rselBtns.clear();
        $('#cmEditExprBox').css('border-color', '');
        $('#cmExprError').remove();
        document.getElementById('cmEditExprBox').onfocus = null;

        //---------------------------------------------------------------
        var addExpr = function(eb) {
            var checkKeys = false;
            Object.keys(rselCond).map(function(a, i) {
                if (a === eb.cond) checkKeys = true;
            });
            if (checkKeys) {
                try {
                    rselCond[eb.cond].op(eb.op);
                    if (eb.op2 !== null) rselCond[eb.cond].op2(eb.op2);
                    if (eb.condmod !== null) rselCond[eb.cond].condmod(eb.condmod);

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

                } catch (err) {
                    __EXPR_DEBUGINFO.eb = eb;
                    derp(__EXPR_DEBUGINFO, err);
                    return false;
                }
            } else {
                __EXPR_DEBUGINFO.eb = eb;
                derp(__EXPR_DEBUGINFO, 'Selector condition was not recognized');
                return false;
            }

            return true;
        };
        //---------------------------------------------------------------
        //parseThis = '(((Country="United States" AND State="Oregon") AND !(Prim name!="Somewhere") AND Name contains ("Somewhere")) OR (Alt name !contains ("Somewhere"))) AND Primary City ~("Some City") AND City="Big City" AND Alt. City!= "Big City" AND Prim. or Alt. City contains ("Big City") AND ((Has name OR Unnamed) AND (NO Alt. names OR Has Alt name(s))) AND Road Type = "Runway/Taxiway" AND !Roundabout AND Is NOT Toll Road AND != Unnamed & (Direction != "One way (A->B)" OR Direction = "Unknown") AND (Elevation != "Ground" AND Elevation > 1 AND Elevation <= 1) AND Is NOT Tunnel AND On Screen & OUT of Screen && Has restriction | Has NO restriction AND Has closure || Has NO closure AND Closure starts before 1 day AND Closure ends before 1 day AND Closure starts after 1 day AND Closure ends after 1 day AND Closure starts in 1 day AND Closure ends in 1 day AND Closure doesn\'t start in 1 day AND Closure doesn\'t end in 1 day';

        var parseThis = $('#cmEditExprBox').val();

        //Ensure there is a space after a comparator
        //parseThis = parseThis.replace(/([^-])([!=<>~]{1,2})([("'\w])/g,'$1$2 $3');

        //replace some multi-word conditions with unique single words - note: they cannot overlap with potential selection names
        parseThis = parseThis.replace(/\bpri?m?(?:ary|\.)?\s?(?:or|and|\|\||\||&|&&|\/)\s?alt(?:ern|s)?(?:\.)?/igm, 'any');
        parseThis = parseThis.replace(/\b((?:un)?name[ds]?)(?:[-\s]?seg[a-z]*)?\b|\b(road)(?:.?(?:type))?\b/igm, '$1$2')
        parseThis = parseThis.replace(/\b(man)[ually]*[\s-]?(lock)(?:ed|s)?\b|\b(traf)[fic]*[\s-]?(lock)(?:ed|s)?\b|\b(auto)-?(?:matic[ally]*)?[\s-]?(lock)(?:ed|s)?\b/igm,'$1$2$3$4$5$6');
        parseThis = parseThis.replace(/\b(created)[\s-]?(by)\b/igm, 'createdby');
        parseThis = parseThis.replace(/\b(?:update[ds]?|edite?d?s?)[\s-]?by\b/igm, 'updatedby');
        parseThis = parseThis.replace(/\blast(?:[\s-]?(?:update[ds]?|edite?d?s?))?\b/igm,'last');
        parseThis = parseThis.replace(/\b(?:in|on)[- ]?screen/igm, 'onscreen');  //\b(?:in|on|off|out|outside)(?: of)?[- ]?screen\b
        parseThis = parseThis.replace(/\b(?:off|out|outside)(?: of)?[- ]?screen/igm, 'offscreen');
        parseThis = parseThis.replace(/\b(?:speed[\s-]?(?:limits?)?|sls?)\b/igm, 'speed');

        //The following regex pattern is for ensuring certain phrases will be parsed out together
        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](<-|->)[ab])|\w+(\(s\))?|&&|\|\||!=|[|&<>=()!~]/gim
            ), //|"[^"]*"[^"]*"[^"]*"
            parseExprHistory = [],
            condMatches = [],
            condMatchPhrases = [],
            exprMatches = [],
            exprMatchPhrases = [],
            exprBuild, exprFragment, exprFragPhrase, exprFragPhraseStr, unwantedWordsSearch,
            e, f, b, fLength, m, mLength, //closeParens,
            success = false;

        // 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|^type|^round|^rotary|^ra\b|^toll|^speed|^dir|^elevation|^tun|^manlock|^traflock|^autolock|^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)});
                            /*for (m = unwantedWordsSearch.length; m--;) {
                                if (/\b(has|have|is|=|are|does|was|were)\b/i.test(unwantedWordsSearch[m])) {
                                    unwantedWordsSearch.splice(m,1);
                                }
                            }*/
                        }
                        if (/!|!=/.test(unwantedWordsSearch[0])) unwantedWordsSearch.splice(0,1);

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

                        //parseExprHistory.push(exprFragment);
                        parseExprHistory = parseExprHistory.concat(exprFragment, parseExprArray.slice(0, f));
                        parseExprArray = parseExprArray.slice(f);
                        e -= f;
                    } catch (err) {
                        derp('Error parsing expression at ' + exprFragment, err);
                    }
                } else {
                    parseExprHistory.push(exprFragment);
                }
            } catch (err) {
                derp('Error parsing expression at ' + exprFragment, 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) derp(1);
        //---------------------------------------------------------------

        //closeParens = 0;
        mLength = exprMatchPhrases.length;
        for (m = 0; m < mLength; m++) {
            __EXPR_DEBUGINFO = {
                m: m,
                exprMatches: exprMatches[m],
                exprMatchPhrases: exprMatchPhrases[m]
            }; //reset for error debugging

            exprFragment = exprMatches[m];
            exprFragPhrase = exprMatchPhrases[m];
            if (exprFragPhrase.constructor !== Array) exprFragPhrase = [exprFragPhrase];

            exprBuild = _rsel.getNewExprBuild();
            exprBuild.cond = exprFragment;

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

            //if (closeParens === 1) closeParens = 2;

            //============================================================
            // Where the magic happens... sort of.
            //============================================================
            switch (true) {
                case (exprFragment === '('):
                    rselBtns.lfParens();
                    break;
                case (exprFragment === ')'):
                    rselBtns.rtParens();
                    break;
                case /^(&|&&|and)$/.test(exprFragment):
                    rselBtns.and();
                    //console.info(exprFragment.toLowerCase());
                    break;
                case /^(\||\|\||or)$/.test(exprFragment):
                    //console.info(exprFragment.toLowerCase());
                    rselBtns.or();
                    break;
                case /no[\s-]alt/i.test(exprFragPhrase):
                    rselCond.unnamed.op(true);
                    rselCond.unnamed.op2(true);
                    rselCond.unnamed.add();
                    success = true;
                    break;
                case /^(!|!=|not)$/.test(exprFragment):
                    rselBtns.not();/*
                    if (exprMatches[m + 1] !== '(') {
                        rselBtns.lfParens();
                        closeParens = 1;
                    }*/
                    break;
                case /^unnamed/.test(exprBuild.cond):
                    rselCond.unnamed.op(true);
                    rselCond.unnamed.op2(false);
                    rselCond.unnamed.add();
                    success = true;
                    break;

                // SPEED LIMITS
                case /^speed.*|^sls?/.test(exprBuild.cond):
                    //exprBuild.cond = exprBuild.cond.replace(/^sls?|^speed.*/, 'speed');

                    try {
                        if (exprFragPhrase.length < 2 && /\bnot?\b|!|!=/i.test(exprFragPhrase[0])) {
                            exprBuild.op = 'none';
                        } else {
                            exprFragPhrase = exprFragPhrase.join(' ');

                            if (/\bnot?\b|!|!=/i.test(exprFragPhrase)) rselBtns.not();

                            var optionText = _rsel.getSelectOptions(rselCond.speed.opOptNodes);
                            optionText = RegExp(optionText.join('|'), 'i').exec(exprFragPhrase);
                            if (optionText) exprBuild.op = optionText[0];
                            else exprBuild.op = 'any';
                        }

                        if (exprFragPhrase.length > 1) {
                            var speedVal = exprFragPhrase.match(/(\d+)\s?mph|(\d+)\s?km/i);
                            if (speedVal && speedVal.length === 2) exprBuild.val = speedVal[1];
                        } else {
                            exprBuild.val = '';
                        }
                    } catch (err) {
                        __EXPR_DEBUGINFO.exprBuild = exprBuild;
                        derp(__EXPR_DEBUGINFO, err);
                    }
                    success = addExpr(exprBuild);
                    break;

                // BOTH LOCKS
                case /^locks?$/.test(exprBuild.cond):
                    exprFragPhraseStr = translateNaturalLang(exprFragPhrase);
                    exprBuild.op = /(?:! )?contains|[!<>=~]{1,2}/i.exec(exprFragPhraseStr)+'';
                    exprBuild.val = exprFragPhraseStr.match(/\b\d+/)+'';

                    rselCond.manlock.op(exprBuild.op);
                    rselCond.manlock.val(exprBuild.val);
                    rselCond.manlock.add();
                    rselBtn.or();
                    rselCond.traflock.op(exprBuild.op);
                    rselCond.traflock.val(exprBuild.val);
                    rselCond.traflock.add();

                // BINARY CONDITIONS:
                case exprFragPhrase.length === 0 || //suggests binary
                    /^(?:screen|roundabout|ra|toll|tun|new|changed|restr|editable)/.test(exprBuild.cond) || //binary selection conditions
                    (/^(?:street[\s-]?)?name.*|^clos.*/i.test(exprBuild.cond) && exprFragPhrase.length <= 1): //selection conditions that have both binary and multiple options

                    exprFragPhrase = exprFragPhrase.join(' ');

                    exprBuild.cond = exprBuild.cond.replace(/^(?:street[\s-]?)?name.*/, 'name');
                    exprBuild.cond = exprBuild.cond.replace(/^ra\b|^rotary|^round.*/, 'roundabout');
                    exprBuild.cond = exprBuild.cond.replace(/^toll.*/, 'toll');
                    exprBuild.cond = exprBuild.cond.replace(/^tun.*/, 'tunnel');
                    exprBuild.cond = exprBuild.cond.replace(/^restr.*/, 'restriction');

                    if (/\bnot?\b|!|!=/i.test(exprFragPhrase)) {
                        exprBuild.op = false;
                    } else {
                        exprBuild.op = true;
                    }
                    try {
                        switch (exprBuild.cond) {
                            case 'name':
                                try {
                                    if (/alt/i.test(exprFragPhrase)) {
                                        rselCond.unnamed.op(false);
                                        rselCond.unnamed.op2(true);
                                        rselCond.unnamed.add();
                                    } else {
                                        rselCond.unnamed.op(false);
                                        rselCond.unnamed.op2(false);
                                        rselCond.unnamed.add();
                                    }
                                    success = true;
                                } catch (err) { derp(__EXPR_DEBUGINFO, err) }
                                break;
                            case 'closure':
                                exprBuild.op2 = '---';
                                exprBuild.val = '';
                                success = addExpr(exprBuild);
                                break;
                            case 'onscreen':
                                exprBuild.cond = 'screen';
                                exprBuild.op = true;
                                success = addExpr(exprBuild);
                                break;
                            case 'offscreen':
                                exprBuild.cond = 'screen';
                                exprBuild.op = false;
                                success = addExpr(exprBuild);
                                break;
                            case 'roundabout':
                            case 'toll':
                            case 'tunnel':
                            case 'new':
                            case 'changed':
                            case 'restriction':
                            case 'editable':
                                success = addExpr(exprBuild);
                                break;
                        } //switch

                    } catch (err) {
                        __EXPR_DEBUGINFO.exprBuild = exprBuild;
                        derp(__EXPR_DEBUGINFO, err);
                    }
                    break;
                    //--------------------------------------------------------------------

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

                default:
                    // CONDITION NAME MATCHING (TYPE OF SELECTION)
                    try {
                        if (/^(?:name.*|str.*|cit.*)/.test(exprBuild.cond)) {
                            exprBuild.cond = exprBuild.cond.replace(/^name.*/, 'street');
                            exprBuild.cond = exprBuild.cond.replace(/^str.*/, 'street');
                            exprBuild.cond = exprBuild.cond.replace(/^cit.*/, 'city');
                            var exprStart = exprFragPhrase.slice(0, -1), //don't include last element bc it should be the name itself
                                prim, alt, any; //exprStartStr,
                            if (exprStart) {
                                //exprStartStr = exprStart.toString().toLowerCase();
                                prim = /\bpri?m?(?:ary|\.)?\b/i.test(exprStart);
                                alt = /\balt(?:ern\w*|\.)?\b/i.test(exprStart);
                                any = /\bany\b/i.test(exprStart);
                                exprFragPhrase = exprStart.filter(function(a){return !/^pr|^alt|^any/i.test(a)}).concat(exprFragPhrase.slice(-1));
                            } else {
                                prim = false;
                                alt = false;
                            }
                            if ((prim && alt) || any) exprBuild.condmod = 2;
                            else if (prim) exprBuild.condmod = 0;
                            else if (alt) exprBuild.condmod = 1;
                            else exprBuild.condmod = 0;
                        } else {
                            //exprBuild.cond = exprBuild.cond.replace(/^traf.*|^auto.*/, 'traflock');
                            //exprBuild.cond = exprBuild.cond.replace(/^man.*/, 'manlock');
                            exprBuild.cond = exprBuild.cond.replace(/^dir.*/, 'direction');
                            exprBuild.cond = exprBuild.cond.replace(/^(?:road)(?:.?(?:type))?|^type/, 'road');
                            //exprBuild.cond = exprBuild.cond.replace(/^created? by/, 'createdby');
                            //exprBuild.cond = exprBuild.cond.replace(/^(?:update[ds]?|edite?d?s?) by/, 'updatedby');
                            //exprBuild.cond = exprBuild.cond.replace(/^last (?:update[ds]?|edite?d?s?)\b|^(?:update[ds]?|edite?d?s?)\b/,'last')
                        }
                    } catch (err) {
                        __EXPR_DEBUGINFO.exprBuild = exprBuild;
                        derp(__EXPR_DEBUGINFO, err);
                    }

                    // COMPARATOR OPERATION MATCHING
                    try {
                        // Convert natural lang representation to standard comparator operations
                        exprFragPhraseStr = translateNaturalLang(exprFragPhrase);

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

                    // SELECTION VALUE MATCHING
                    try {
                        if (/^length|^last/.test(exprBuild.cond)) {
                            exprFragPhraseStr = exprFragPhraseStr.replace(/\btoday\b/,0);
                            exprFragPhraseStr = exprFragPhraseStr.replace(/\byesterday|tomorrow\b/,1);
                            exprBuild.val = exprFragPhraseStr.match(/\b\d+/)+''

                        } else {
                            exprBuild.val = exprFragPhraseStr.replace(new RegExp('^(?:\\s?' + exprBuild.op + '\\s)(.*)','i'),'$1').replace(/^\(["'](.*?)['"]\)$|^\s?["'](.*)["']$|^["'](.*)['"]$|\b(\w*?)\b/,'$1$2$3$4').replace(/(") (\w) (")/,'$1$2$3');
                            /*
                            var exprCondVal = exprFragPhrase.slice(-1)[0];
                            // Remove surrounding paretheses and quotations
                            if (/^[('"]+(.*?)['")]+$/.test(exprCondVal)) {
                                exprBuild.val = exprCondVal.replace(/^(?:\(['"\s]+(.*?)[\s'"]+\)|["']\s?(.*?)\s?['"])$/, '$1$2'); //.replace(/["()]/g,'').replace(/^'(.*)'$/,'$1');
                            } else { // if no quotes, then try to guess the boundaries...
                                try { // try to remove operator and everything before it... return stuff that comes after
                                    exprBuild.val = exprFragPhrase.join(' ').replace(new RegExp('^(?:.*\\s?' + exprBuild.op + ')\\s(\\b(?:\\w\\s?)*\\b)','i'), '$1');
                                } catch (err) {
                                    __EXPR_DEBUGINFO.exprBuild = exprBuild;
                                    derp(2,__EXPR_DEBUGINFO);
                                }
                            }*/

                            // Add flexility to value specification for direction selection
                            // Also allow user to specify 'oneway' without direction and auto add expr for both directions
                            if (/^direction/.test(exprBuild.cond)) {
                                exprBuild.val = exprBuild.val.match(/A[<>-\s]*B|B[<>-\s]*A|^one[\s-]?ways?$|unknown/i)+''; //reduce to unique key words... last option will automatically input both one ways
                                exprBuild.val = exprBuild.val.replace(/B[\s<-]*A/, 'A->B');
                                exprBuild.val = exprBuild.val.replace(/A[\s<-]*B/, 'B->A');
                                if (/^one[\s-]?ways?$/.test(exprBuild.val)) {
                                    exprBuild.val = 'A->B';
                                    exprBuild.val2 = 'B->A';
                                }
                            } else if (/^road/.test(exprBuild.cond)) {
                                exprBuild.val = exprBuild.val.replace(/^fwy?$/i,'^freeway');
                                exprBuild.val = exprBuild.val.replace(/hwy/i,''); //not important bc not a unique keyword
                                exprBuild.val = exprBuild.val.replace(/^MH$/,'^major');
                                exprBuild.val = exprBuild.val.replace(/^(mH|mh)$/,'^minor');
                                exprBuild.val = exprBuild.val.replace(/^ps$|^prim.*/i,'^primary');
                                exprBuild.val = exprBuild.val.replace(/^st.*/i,'^street');
                                exprBuild.val = exprBuild.val.replace(/^dirt.*|.*4[\s-]?x[\s-]?4.*/i,'^dirt');
                                exprBuild.val = exprBuild.val.replace(/^plr?$|^park.*/i,'^parking');
                                exprBuild.val = exprBuild.val.replace(/^pr$|^pvtr$|^pvr$|^priv.*/i,'^private');
                                exprBuild.val = exprBuild.val.replace(/^wt$|^walk.*|.*trl/i,'^walking');
                                exprBuild.val = exprBuild.val.replace(/^pb$|^ped.*|.*b(?:oard)?w(?:alk)?/i,'^pedestrian');
                                exprBuild.val = exprBuild.val.replace(/^sw$|^stair.*/i,'^stairway');
                                exprBuild.val = exprBuild.val.replace(/^rr$|^rail.*/i,'^railroad');
                                exprBuild.val = exprBuild.val.replace(/^rw$|^tw$|.*run.*|.*taxi.*/i,'^runway');
                            }
                        }
                        success = addExpr(exprBuild);

                    } catch (err) {
                        __EXPR_DEBUGINFO.exprBuild = exprBuild;
                        derp(__EXPR_DEBUGINFO, err);
                    }
            } //switch
            /*
            if (closeParens === 2) {
                rselBtns.rtParens();
                closeParens = 0;
            }*/

        } //for each condition matched
        __EXPR_DEBUGINFO.exprBuild = exprBuild;
        if (!success) {
            derp(__EXPR_DEBUGINFO);
        } else {
            parseThis = null;
            parseExprArray = null;
            parseExprHistory = null;
        }
    } //updateExpr()
}; //ExprEditor


var insertEditorIcon = function() {
    if (document.getElementById('cmBtnEdit') === null) {
        $('#RSselection>font').append('<i id="cmBtnEdit" class="fa fa-edit" style="padding-left: 5px; color: #111"></i>');
    } else {
        $('#cmBtnEdit').remove();
        $('#cmExprEditor').remove();
        $('#RSselection>font').append('<i id="cmBtnEdit" class="fa fa-edit" style="padding-left: 5px; color: #111"></i>');
    }

    document.getElementById('cmBtnEdit').addEventListener('click', function() {
        if (document.getElementById('cmExprEditor') === null) {
            $('#outRSExpr').after(
                '<div id="cmExprEditor" style="width:106%; color: #888; background-color: #F5F5F5; margin: 5px -3% 0px; padding: 0px 1% 4px; border-radius: 4px; border: 1px solid lightgray; font-size: 14px;" class="btn-group"><div id="cmCopyToRSelExpr" class="btn fa-arrow-circle-down cm-rsel-editor" style="width: 33%; font-weight: 500; padding: 0;"><span style="font-size: 11px; padding: 4px;">Copy</span></div><div id="cmParseRSelExpr" class="btn fa-arrow-circle-up cm-rsel-editor" style="width: 34%; font-weight: 500; padding: 0; border-left: 1px dotted #DDD; border-right: 1px dotted #DDD; border-top: 2px solid transparent;"><span style="font-size: 11px; padding: 4px;">Update</span></div><div id="cmCloseExprEditor" class="btn fa-times-circle cm-rsel-editor" style="width: 33%; font-weight: 500; padding: 0;"><span style="font-size: 11px; padding: 4px;">Close</span></div><textarea style="max-width: 100%; margin: 0; min-height: 75px; padding: 5px 1%;" id="cmEditExprBox" class="form-control"></textarea></div>'
            );
            ExprEditor();

        } else {
            document.getElementById('cmExprEditor').remove();
        }
    }, false);
};


/*//////////////////////////////////////////////////////////////////////////*/
var waitForWMERSel = function() {
    var waitCount = 0,
        tryInitRSelExprEditor = function() {
            try {
                if (document.getElementById('RSselection') !== null) {
                    insertEditorIcon();
                } else {
                    if (waitCount++ < 35) {
                        setTimeout(tryInitRSelExprEditor, 600);
                    } else {
                        console.error(
                            'WME RSel Expression Editor: Could not link up with WME Road Selector.');
                    }
                }
            } catch (err) {
                console.error(err)
            }
        };
    tryInitRSelExprEditor();
}

setTimeout(waitForWMERSel, 500);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址