// ==UserScript== // @name Magic™Editor // @author Cameron Bernhardt (AstroCB) // @developer Jonathan Todd (jt0dd) // @developer sathyabhat // @contributor Unihedron // @contributor Tiny Giant // @contributor Mogsdad // @contributor Makyen // @contributor VLAZ // @grant none // @license MIT // @namespace http://github.com/SO-Close-Vote-Reviewers/UserScripts/Magic™Editor // @version 1.8.0 // @description Fix common grammar/usage annoyances on Stack Exchange posts with a click. Forked from https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit // @include /^https?:\/\/([\w-]*\.)*((stackoverflow|stackexchange|serverfault|superuser|askubuntu|stackapps)\.com|mathoverflow.net)\/(c\/[^\/]*\/)?(questions|posts|review|tools)\/(?!tagged\/|new\/).*/ // @exclude *://chat.stackoverflow.com/* // @exclude *://chat.stackexchange.com/* // @exclude *://chat.*.stackexchange.com/* // @exclude *://api.*.stackexchange.com/* // @exclude *://data.stackexchange.com/* // ==/UserScript== /* globals StackExchange, $ */ /* eslint-disable no-multi-spaces, no-sequences */ (function() { "use strict"; function extendEditor(root) { var App = {}; // Place edit items here App.items = {}; App.originals = {}; // Place selected jQuery items here App.selections = {}; // Place "global" app data here App.globals = {}; // Place "const" app data here App.consts = {}; // Place "helper" functions here App.funcs = {}; // True to display counts and / or rule names in Edit Summary App.globals.showCounts = false; App.globals.showRules = false; App.globals.root = root; App.globals.reasons = {}; App.globals.placeHolders = { //The text here is staticly used in some edit RegExp to prevent substitution of placeholders. // See: // badphrases which relies on "_xPlacexHolderx" starting a placeholder. "auto": "_xPlacexHolderxAutoxInsertxTextxPlacexHolderx_", "quote": "_xPlacexHolderxBlockxQuotexPlacexHolderx_", "backtickCode": "_xPlacexHolderxCodexPreserveBlockxPlacexHolderx_", "block": "_xPlacexHolderxCodexBlockxPlacexHolderx_", "blockStart": "_xPlacexHolderxCodexBlockxStartxPlacexHolderx_", "lsec": "_xPlacexHolderxLinkxSectionxPlacexHolderx_", "links": "_xPlacexHolderxLinkxPlacexHolderx_", "preBlock": "_xPlacexHolderxPrexBlockxPlacexHolderx_", "codeTag": "_xPlacexHolderxCodexTagxPlacexHolderx_", "tags": "_xPlacexHolderxTagxPlacexHolderx_", "dashes": "_xPlacexHolderxDashesxPlacexHolderx_" }; App.globals.replacedStrings = {}; App.globals.replacedStringsOriginal = {}; App.globals.placeHolderChecks = {}; App.globals.placeHolderKeys = Object.keys(App.globals.placeHolders); App.globals.checks = { //automatically inserted text // https://regex101.com/r/cI6oK2/1 "auto": /[^]*\<\!\-\- End of automatically inserted text \-\-\>/g, //blockquotes // https://regex101.com/r/fU5lE6/1 "quote": /^\>(?:(?!\n\n)[^])+/gm, //code surrounded by backticks // https://regex101.com/r/8tZD3i/2 "backtickCode": /(?:(?:^(`{3,})[^]+?\1)|(`+)(?:\\`|[^`](?!\n\n))+\2)/gm, //code blocks and multiline inline code. // https://regex101.com/r/eC7mF7/4 "block": /(?:(?:^[ \t]*(?:[\r\n]|\r\n))?`[^`]+`|(?:^[ \t]*(?:[\r\n]|\r\n))^(?:(?:[ ]{4}|[ ]{0,3}\t).+(?:[\r\n]?(?!\n\S)(?:[ \t]+\n)*)+)+)/gm, //code blocks at the start of the post. // https://regex101.com/r/vu7fBd/1 "blockStart": /(?:^(?:(?:[ ]{4}|[ ]{0,3}\t).+(?:[\r\n]?(?!\n\S)(?:[ ]+\n)*)+)+)/g, //link-sections // Testing of this and the "links" RegExp were done within the same regex101.com "regex". // The prior version of this was https://regex101.com/r/tZ4eY3/7 it was saved and became version 21. // It was then forked into it's own regex: // https://regex101.com/r/C7nXfd/2 "lsec": /(?:^ *(?:[\r\n]|\r\n))?(?: {2}(?:\[\d\]): \w*:+\/\/.*\n*)+/gm, //links and pathnames // See comment above the "lsec" RegExp regarding testing sharing the same "regex" on regex101.com // https://regex101.com/r/tZ4eY3/22 "links": /!?\[[^\]\n]+\](?:\([^\)\n]+\)|\[[^\]\n]+\])(?:\](?:\([^\)\n]+\)|\[[^\]\n]+\]))?|(?:\/\w+\/|.:\\|\w*:\/\/|\.+\/[./\w\d]+|(?:\w+\.\w+){2,})[./\w\d:/?#\[\]@!$&'()*+,;=\-~%]*/gi, // ' fix syntax highlighting in code editor //
 blocks
            //        https://regex101.com/r/KFvgol/1
            "preBlock": /]*?|)>[\W\w]*?<\/pre>/gi,
            // blocks
            //        https://regex101.com/r/waCxWR/1
            "codeTag":  /]*?|)>[\W\w]*?<\/code>/gi,
            //        https://regex101.com/r/bF0iQ0/2   tags and html comments
            "tags":   /\<[\/a-z]+\>|\<\!\-\-[^>]+\-\-\>|\[tag:[\w.-]+\]/gi,
            "dashes":   /^(\s*--+\s*?)$/gim
        };
        //Make a shallow copy of the App.globals.checks Object
        App.globals.checksr = (function(objIn){
            var objOut = {};
            var keys = Object.keys(objIn);
            for(var i = keys.length-1; i >= 0; --i) objOut[keys[i]] = objIn[keys[i]];
            return objOut;
        })(App.globals.checks);

        // Assign modules here
        App.pipeMods = {};

        // Define order in which mods affect  here
        App.globals.order = ["omit", "codefix", "inlineImages", "edit", "diff", "replace", "output"];

        // Define reason constant strings
        App.consts.reasons = {
            legalSO:       "'Stack Overflow' is the legal name",
            legalSE:       "'Stack Exchange' is the legal name",
            tagTitle:      "removed tags from title",
            trademark:     "trademark capitalization",
            acronym:       "acronym capitalization",
            spelling:      "spelling",
            grammar:       "grammar",
            noise:         "noise reduction",
            punctuation:   "punctuation",
            layout:        "layout",
            silent:        "",                              // Unreported / uncounted
            titleSaysAll:  "replicated title in body",
            inlineImage:   "inline image"
        };


        // Get the original post tags
        App.globals.taglist = [];
        $('a.post-tag').each( function(){
            var newtag = $(this).text();
            if (App.globals.taglist.indexOf(newtag) === -1) {
                App.globals.taglist.push(newtag);
            }
        });

        // Define edit rules
        // See https://regex101.com/r/fC3bY5/2 for a basic RegExp that excludes matches in filenames, paths, library names, etc.
        // The following properties are available for each edit rule:
        //    expr:         RegExp                          Used as the argument for the String methods .match() and the first argument for .replace().
        //    replacement:  String or function              Used as the second argument for the String method .replace(). e.g. "$1 want".
        //    reason:       String                          Should be one of the constants defined as a reason. e.g.:  App.consts.reasons.grammar
        //    rerun:        String or Array of String       The keys of rules which will be re-run if there are any changes made by this current rule. "rerrun" is executed before "runAfter"
        //    runBefore:    String or Array of String       The keys of rules which will be run, perhaps re-run, immediately before this key.
        //    runAfter:     String or Array of String       The keys of rules which will be run, perhaps re-run, immediately after this key.
        //    notAlone:     truthy (Boolean)                If evaluates to true, then the rule is only run when specified in a "rerun", "runBefore", or "runAfter"
        //    titleOnly:    truthy (Boolean)                If evaluates to true, then the rule is only applied to titles.
        //    bodyOnly:     truthy (Boolean)                If evaluates to true, then the rule is only applied to bodies.
        //    debug:        truthy (Boolean)                If evaluates to true, then debug output is logged to the console for this rule.
        //  WARNING: rerun, runBefore, and runAfter can result in an infinite loop.
        App.edits = {
            // Handle all-caps posts first
            noneedtoyell: {
                expr: /^((?=.*[A-Z])[^a-z]*)$/g,
                replacement: function(input) {
                    return input.trim().substr(0, 1).toUpperCase() + input.trim().substr(1).toLowerCase();
                },
                reason: App.consts.reasons.grammar
            },
            // Remove tags from title
            taglist: {  // https://regex101.com/r/wH4oA3/25
                // WARNING: the expression from regex101 must have backslashes escaped here - wbn to automate this...
                expr: new RegExp(
                    "(?:^(?:[(]?(?:_xTagsx_)(?!\\.\\w)(?:and|[ ,.&+/-])*)+[:. \\)-]*|\\b(?:[:. \\(-]|in|with|using|by|for|from)*(?:(?:_xTagsx_)(?:and|[ ,&+/)-])*)+([?.! ]*)$)"
                        .replace(/_xTagsx_/g,App.globals.taglist.map(escapeTag).join("|")),
                    //Consider escaping character classes:
                    //.replace(/\\(?=[bsSdDwW])/g,"\\"), // https://regex101.com/r/pY1hI2/1 - WBN to figure this out.
                    'gi'
                ),
                replacement: "$1",
                debug: false,
                titleOnly: true,
                reason: App.consts.reasons.tagTitle
            },
            so: {
                expr: /\bstack\s*overflow\b/gi,
                replacement: "Stack Overflow",
                reason: App.consts.reasons.legalSO
            },
            se: {
                expr: /\bstack\s*exchange\b/gi,
                replacement: "Stack Exchange",
                reason: App.consts.reasons.legalSE
            },
            expansionSO: {
                expr: /([^\b\w.]|^)SO\b/g,
                replacement: "$1Stack Overflow",
                reason: App.consts.reasons.legalSO
            },
            expansionSE: {
                expr: /([^\b\w.]|^)SE\b/g,
                replacement: "$1Stack Exchange",
                reason: App.consts.reasons.legalSE
            },
            /*
            ** Trademark names
            **/
            jsfiddle: {
                expr: /\bjs ?fiddle\b/gi,
                replacement: "JSFiddle",
                reason: App.consts.reasons.trademark
            },
            meteor: {  // must appear before "javascript"
                expr: /([^\b\w.]|^)meteor(?: *(js))?\b(?![.-]\w)/gi,
                replacement: function (str,pre,uppercase) {
                    var fixed = pre + "Meteor" + (uppercase ? uppercase.toUpperCase() : '');
                    return fixed;
                },
                reason: App.consts.reasons.trademark
            },
            knockout_js: {  // must appear before "javascript"
                expr: /\bknockout[. ]?js\b/gi,
                replacement: "Knockout.js",
                reason: App.consts.reasons.trademark
            },
            script: {  // Spelling rule out-of-order, must run before javascript & google_apps_script
                expr: /(s)c[ri]+pt?(ing|s)?\b/gi,
                replacement: "$1cript$2",
                reason: App.consts.reasons.spelling
            },
            javascript: {
                expr: /([^\b\w.]|^)(java?scr?ipt?|js|java(?:[^\w.]|_)?script?)\b/gi,
                replacement: "$1JavaScript",
                reason: App.consts.reasons.trademark
            },
            jquery: {
                expr: /\bjque?rr?y\b(?![.-]\w)/gi,  // jqury, jquerry, jqurry... ~600 spelling mistakes
                replacement: "jQuery",
                reason: App.consts.reasons.trademark
            },
            angularjs: {
                expr: /\bangularjs\b(?![.-]\w)/gi, //Updated as Angular and AngularJS are two different things.
                replacement: "AngularJS",
                reason: App.consts.reasons.trademark
            },
            angularcli: {
                expr: /\bangular\W{0,2}cli\b(?![.-]\w)/gi,
                replacement: "Angular CLI",
                reason: App.consts.reasons.trademark
            },
            angular: {
                expr: /\bangular\b(?![.-]\w)/gi,
                replacement: "Angular",
                reason: App.consts.reasons.trademark
            },
            php: {
                expr: /(?:[^\b\w.]|^)php[\d]?\b(?![.-]\w)/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            c: {
                expr: /(?:[^\b\w.]|^)c\b(?:#|\+\+)?/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            java: {
                expr: /([^\b\w.]|^)java\b(?![.-]\w)/gi,
                replacement: "$1Java",
                reason: App.consts.reasons.trademark
            },
            sqlite: {
                expr: /\bsql*\W?l*ite(\s*[0-9]*)\b/gi,
                replacement: "SQLite$1",
                reason: App.consts.reasons.trademark
            },
            android: {
                expr: /\band(?:roi|ori)d\b(?![.-]\w)/gi,
                replacement: "Android",
                reason: App.consts.reasons.trademark
            },
            oracle: {
                expr: /\boracle\b/gi,
                replacement: "Oracle",
                reason: App.consts.reasons.trademark
            },
            windows: {
                // https://regex101.com/r/jF9zK1/8
                expr: /\b(?:win(?=(?:\s+(?:2k|[0-9.]+|ce|me|nt|xp|vista|server)))|windows)(?:\s+(2k|[0-9.]+|ce|me|nt|xp|vista|server))?\b/gi,
                replacement: function(match, ver) {
                    ver = !ver ? '' : ' ' + ver
                        .replace(/ce/i, 'CE')
                        .replace(/me/i, 'ME')
                        .replace(/nt/i, 'NT')
                        .replace(/xp/i, 'XP')
                        .replace(/2k/i, '2000')
                        .replace(/vista/i, 'Vista')
                        .replace(/server/i, 'Server');
                    return 'Windows' + ver;
                },
                reason: App.consts.reasons.trademark
            },
            unix: {
                expr: /\bunix\b/gi,
                replacement: "Unix",
                reason: App.consts.reasons.trademark
            },
            linux: {
                expr: /\blinux\b/gi,
                replacement: "Linux",
                reason: App.consts.reasons.trademark
            },
            wordpress: {
                expr: /\bword ?press\b/gi,
                replacement: "WordPress",
                reason: App.consts.reasons.trademark
            },
            mysql: {
                expr: /\bmysql\b/gi,
                replacement: "MySQL",
                reason: App.consts.reasons.trademark
            },
            nodejs: {
                expr: /\bnode\.?js\b/gi,
                replacement: "Node.js",
                reason: App.consts.reasons.trademark
            },
            apache: {
                expr: /\bapache([\d])?\b(?![.-]\w)/gi,
                replacement: "Apache$1",
                reason: App.consts.reasons.trademark
            },
            git: {
                expr: /([^\b\w.]|^)git\b/gi,
                replacement: "$1Git",
                reason: App.consts.reasons.trademark
            },
            github: {
                expr: /\bgithub\b/gi,
                replacement: "GitHub",
                reason: App.consts.reasons.trademark
            },
            facebook: {  // https://regex101.com/r/rO1tH4/2
                expr: /\bf(?:a[cs]e?)?be?o+k?(s)?/gi,
                replacement: function(str,s) {
                    return "Facebook" + (s ? "'s" : "");
                },
                reason: App.consts.reasons.trademark
            },
            python: {
                //Given that "python" is a real word, this isn't something we necessarily should be capitalizing all the time.
                //However, on SO it's far more likely to be the programming language than a snake.
                expr: /\bpython\b/gi,
                replacement: "Python",
                reason: App.consts.reasons.trademark
            },
            ios: {
                expr: /\bios\b/gi,
                replacement: "iOS",
                reason: App.consts.reasons.trademark
            },
            iosnum: {
                expr: /\bios([0-9])\b/gi,
                replacement: "iOS $1",
                reason: App.consts.reasons.trademark
            },
            ubuntu: {  // https://regex101.com/r/sT8wV5/2
                expr: /\b[uoa]+n?b[uoa]*[tn][oua]*[tnu][oua]*\b/gi,
                replacement: "Ubuntu",
                reason: App.consts.reasons.trademark
            },
            vbnet: {  // https://regex101.com/r/bB9pP3/8
                expr: /(?:vb\.net|\bvb|(?:[^\b\w.]|^)\.net)\b(?:\s*[0-9]+)?\s*(?:framework|core)?/gi,
                replacement: function(str) {
                    return str.replace(/([^.])vb/i, '$1VB')
                        .replace(/([^.])asp/i, '$1ASP')
                        .replace(/net/i, 'NET')
                        .replace(/framework/i, 'Framework')
                        .replace(/core/i, 'Core');
                },
                reason: App.consts.reasons.trademark
            },
            vba_related: {
                expr: /(?:[^\b\w.]|^)(?:vba|vbs|vbc|evb|vbo|vbp|vbide)\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            vbscript: {
                expr: /\bvbscript/gi,
                replacement: "VBScript",
                reason: App.consts.reasons.trademark
            },
            excel: {
                expr: /\bexcel\b(?!\-|\.\w)/gi,
                replacement: "Excel",
                reason: App.consts.reasons.trademark
            },
            regex: {
                expr: /\b(r)egg?([ea]*)x(p)?\b/gi,
                replacement: function (match, p1, p2, p3) {
                    //If this is JavaScript related, then use RegExp
                    const isRegExp = ['javascript', 'jquery', 'reactjs', 'nodejs'].some(function(testTag) {
                        return App.globals.taglist.indexOf(testTag) > -1;
                    });
                    let result = `${(isRegExp ? 'R' : p1)}eg`;
                    if ((p2 && p2 === p2.toUpperCase()) || isRegExp) {
                        result += 'E';
                    } else {
                        result += 'e';
                    }
                    result += `x${(isRegExp ? 'p' : (p3 || ''))}`;
                    return result;
                },
                reason: App.consts.reasons.trademark
            },
            postgresql: {
                expr: /\bpost?gres*(q?l|s)?\b/gi,
                replacement: "PostgreSQL",
                reason: App.consts.reasons.trademark
            },
            paypal: {
                expr: /\bpaypal\b/gi,
                replacement: "PayPal",
                reason: App.consts.reasons.trademark
            },
            tomcat: {
                expr: /\btomcat([0-9.]*)/gi,
                replacement: "Tomcat$1",
                reason: App.consts.reasons.trademark
            },
            netbeans: {
                expr: /\b(?:netbean?|net-bean|net bean|netbeen)s?\b/gi,
                replacement: "NetBeans",
                reason: App.consts.reasons.trademark
            },
            nginx: {
                expr: /\bnginx\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            firefox: {
                expr: /\bfire?fox\b/gi,
                replacement: "Firefox",
                reason: App.consts.reasons.trademark
            },
            safari: {
                expr: /\bsafari\b/gi,
                replacement: "Safari",
                reason: App.consts.reasons.trademark
            },
            chrome: {
                expr: /\bchrome\b(?![-.]\w)/gi, //Don't match chrome.* namespace and chrome-* schemes
                replacement: "Chrome",
                reason: App.consts.reasons.trademark
            },
            gnu: {
                expr: /\bgnu\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            gcc: {
                expr: /(?:[^\b\w.]|^)gcc\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            maven: {
                expr: /\bmaven\b/gi,
                replacement: "Maven",
                reason: App.consts.reasons.trademark
            },
            youtube: {
                expr: /\byoutube\b/gi,
                replacement: "YouTube",
                reason: App.consts.reasons.trademark
            },
            amazon: {
                // https://regex101.com/r/dR0pJ7/1
                expr: /\b(amazon(?: )?(?:redshift|web services|cloudfront|console)?)((?: )?(?:ec2|aws|s3|rds|sqs|iam|elb|emr|vpc))?\b/gi,
                replacement: function(str,titlecase,uppercase) {
                    var fixed = toTitleCase(titlecase) + (uppercase ? uppercase.toUpperCase() : '');
                    return fixed;
                },
                reason: App.consts.reasons.trademark
            },
            zend: {
                expr: /\bzend((?: )?(?:framework|studio|guard))?\b/gi,
                //replacement: toTitleCase(),  // Doesn't work like built-in toUpperCase, returns 'undefined'. Load order?
                replacement: function(str) {
                    return toTitleCase(str);
                },
                reason: App.consts.reasons.trademark
            },
            twitter: {
                expr: /\btwitter\b(?![.-]\w)/gi,
                replacement: "Twitter",
                reason: App.consts.reasons.trademark
            },
            bootstrap: {     // "bootstrap" is also a general computing term, so expect some false positives
                expr: /\bbootst?r?ap\b/gi,
                replacement: "Bootstrap",
                reason: App.consts.reasons.trademark
            },
            apple: {
                expr: /\bapple\b/g,
                replacement: "Apple",
                reason: App.consts.reasons.trademark
            },
            iphone: {
                expr: /\biph?one?\b/gi,
                replacement: "iPhone",
                reason: App.consts.reasons.trademark
            },
            google: {  // https://regex101.com/r/qW8fI8/4
                expr: /\bgo+(?:g+le?|lge?|gl?el)(e[drs]*|ing)\b/gi,
                replacement: "Googl$1",
                reason: App.consts.reasons.trademark
            },
            google_verbed: {
                expr: /\bgoogl(?:ed|ing|er)\b/gi,
                replacement: function(str) {
                    return toTitleCase(str);
                },
                reason: App.consts.reasons.trademark
            },
            spreadsheet: {  // https://regex101.com/r/oK4uW3/1 - must appear before google_things
                expr: /\b(s)[pr]+[ea]+dsh?e+t(?:ing)?(s)?\b/gi,
                replacement: "$1preadsheet$2",
                reason: App.consts.reasons.spelling
            },
            google_things: { // https://regex101.com/r/iS5fO1/1
                expr: /\bgoogle\b[ \t-]*(?:maps?|sheets?|docs?|drive|sites?|forms?|documents?|spreadsheets?|images?|presentations?|play)?\b/gi,
                replacement: function(str) {
                    return toTitleCase(str);
                },
                reason: App.consts.reasons.trademark
            },
            google_apps_script: { //Not in google_things due to possible missing 's' on Apps.
                expr: /\bgoogle[- ]?(?:apps?)?[- ]?script(ing|s)?\b/gi,
                replacement: "Google Apps Script$1",
                reason: App.consts.reasons.trademark
            },
            google_app_engine: { //Not in google_things due to possible 's' on App.
                expr: /\bgoogle[- ]?(?:apps?)?[- ]?engine(s)?\b/gi,
                replacement: "Google App Engine$1",
                reason: App.consts.reasons.trademark
            },
            google_analytics: { //Not in google_things due to possible missing 's' on analytics.
                expr: /\bgoogle[- ]?analytics?\b/gi,
                replacement: "Google Analytics",
            },
            bluetooth: {
                expr: /\bbl(?:ue|oo)too?th?\b/gi,
                replacement: "Bluetooth",
                reason: App.consts.reasons.trademark
            },
            lenovo: {
                expr: /\bleno?vo\b/gi,
                replacement: "Lenovo",
                reason: App.consts.reasons.trademark
            },
            matlab: {
                expr: /([^\b\w.]|^)math?lab\b/gi,
                replacement: "$1MATLAB",
                reason: App.consts.reasons.trademark
            },
            internet: {
                expr: /\binternet\b/g,
                replacement: "Internet",
                reason: App.consts.reasons.trademark
            },
            oauth: {  // https://regex101.com/r/sA2cQ5/1
                expr: /\boauth(?:(?: )*(\d)(?!\.\d)|(?: )*([\d.]+))?\b/gi,
                replacement: "OAuth$1 $2",
                reason: App.consts.reasons.trademark
            },
            web_services: {
                expr: /\bweb services\b/g,
                replacement: "Web services",
                reason: App.consts.reasons.trademark
            },
            opencv: {
                expr: /\bopencv\b/gi,
                replacement: "OpenCV",
                reason: App.consts.reasons.trademark
            },
            ruby: {
                expr: /\bruby\b/g,
                replacement: "Ruby",
                reason: App.consts.reasons.trademark
            },
            rails: {
                expr: /\brails\b/g,
                replacement: "Rails",
                reason: App.consts.reasons.trademark
            },
            grails: {
                expr: /\bgrails\b/g,
                replacement: "Grails",
                reason: App.consts.reasons.trademark
            },
            subversion: {
                expr: /\bsubvers[io]*n\b/g,
                replacement: "Subversion",
                reason: App.consts.reasons.trademark
            },
            javafx: {
                expr: /\bjavafx\b/gi,
                replacement: "JavaFX",
                reason: App.consts.reasons.trademark
            },
            delphi: {
                expr: /\bdelphi\b/gi,
                replacement: "Delphi",
                reason: App.consts.reasons.trademark
            },
            dotnetnuke: {
                expr: /\bdotnetnuke\b/gi,
                replacement: "DotNetNuke",
                reason: App.consts.reasons.trademark
            },
            silverlight: {
                expr: /\bsilv?erl(?:ight|ite)\b/gi,
                replacement: "Silverlight",
                reason: App.consts.reasons.trademark
            },
            scipy: {
                expr: /([^\b\w.]|^)scipy\b/gi,
                replacement: "$1SciPy",
                reason: App.consts.reasons.trademark
            },
            numpy: {
                expr: /([^\b\w.]|^)numpy\b/gi,
                replacement: "$1NumPy",
                reason: App.consts.reasons.trademark
            },
            openssl: {
                expr: /([^\b\w.]|^)openssl\b/gi,
                replacement: "$1OpenSSL",
                reason: App.consts.reasons.trademark
            },
            drupal: {
                expr: /([^\b\w.]|^)drupal\b/gi,
                replacement: "$1Drupal",
                reason: App.consts.reasons.trademark
            },
            saas: {
                expr: /([^\b\w.]|^)saas\b/gi,
                replacement: "$1SaaS",
                reason: App.consts.reasons.trademark
            },
            gwt: {
                expr: /([^\b\w.]|^)gwt[- ](mosaic|designer)?\b/gi,
                replacement: function (str,pre,titlecase) {
                    var fixed = pre + "GWT" + (titlecase ? ' ' + toTitleCase(titlecase) : ' ');
                    return fixed;
                },
                reason: App.consts.reasons.trademark
            },
            gmail: {
                expr: /([^\b\w.]|^)gmail(s)?\b/gi,
                replacement: "$1Gmail$2",
                reason: App.consts.reasons.trademark
            },
            xampp: {
                expr: /([^\b\w.]|^)xam+p+\b/gi,
                replacement: "$1XAMPP",
                reason: App.consts.reasons.trademark
            },
            galaxy: {
                expr: /([^\b\w.]|^)galaxy\b/gi,
                replacement: "$1Galaxy",
                reason: App.consts.reasons.trademark
            },
            mongo: {
                expr: /([^\b\w.]|^)mongo(?:\s?(db))?\b/gi,
                replacement: function(str,pre,uppercase) {
                    var fixed = pre + "Mongo" + (uppercase ? uppercase.toUpperCase() : '');
                    return fixed;
                },
                reason: App.consts.reasons.trademark
            },
            pymongo: {
                expr: /([^\b\w.]|^)pymongo\b/gi,
                replacement: "$1PyMongo",
                reason: App.consts.reasons.trademark
            },
            scala: {
                expr: /([^\b\w.]|^)scala\b/gi,
                replacement: "$1Scala",
                reason: App.consts.reasons.trademark
            },
            microsoft: { // https://regex101.com/r/dJ5tE3/1
                expr: /\b([mM]icrosoft?|[mM]ircosoft|M[Ss]oft)\b/g,
                replacement: "Microsoft",
                reason: App.consts.reasons.trademark
            },
            intellisense: {
                expr: /\bintell?isen[sc]e?\b/gi,
                replacement: "IntelliSense",
                reason: App.consts.reasons.trademark
            },
            sass: {  // Syntactically Awesome Style Sheets
                expr: /\bsass\b/gi,
                replacement: "Sass",
                reason: App.consts.reasons.trademark
            },
            heroku: {
                expr: /\bheroku\b/gi,
                replacement: "Heroku",
                reason: App.consts.reasons.trademark
            },
            os_x: {
                expr: /\bos ?x\b/gi,
                replacement: "OS X",
                reason: App.consts.reasons.trademark
            },
            el_capitan: {
                expr: /\bel ?capi?tan\b/gi,
                replacement: "El Capitan",
                reason: App.consts.reasons.trademark
            },
            hadoop: {
                expr: /\bhad+o+p+\b/gi,
                replacement: "Hadoop",
                reason: App.consts.reasons.trademark
            },
            django: {
                expr: /\bdjango\b/gi,
                replacement: "Django",
                reason: App.consts.reasons.trademark
            },
            tcl: {
                expr: /([^\b\w.]|^)tcl\b/gi,
                replacement: "$1Tcl",
                reason: App.consts.reasons.trademark
            },
            flickr: {
                expr: /\bflickr(?!\.\w)/gi,
                replacement: "Flickr",
                reason: App.consts.reasons.trademark
            },
            poi: {
                expr: /(?:[^\b\w.]|^)poi\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.trademark
            },
            vmware: {
                expr: /\bvmware?\b/gi,
                replacement: "VMware",
                reason: App.consts.reasons.trademark
            },
            hortonworks: {
                expr: /([^\b\w.]|^)horton ?works[- ](sandbox|data platform|phoenix|hive)?\b/gi,
                replacement: function (str,pre,titlecase) {
                    var fixed = pre + "Hortonworks" + (titlecase ? ' ' + toTitleCase(titlecase) : ' ');
                    return fixed;
                },
                reason: App.consts.reasons.trademark
            },
            ambari: {
                expr: /\bambari\b/gi,
                replacement: "Ambari",
                reason: App.consts.reasons.trademark
            },
            eclipse: {
                expr: /\becli[ps]+e\b/gi,
                replacement: "Eclipse",
                reason: App.consts.reasons.trademark
            },
            pthread: {
                expr: /([^\w.\-/\\_]|^)pthr[ea]+d(s)?\b(?![.\-]\w|[/\\_])/gi,
                replacement: "$1Pthread$2",
                reason: App.consts.reasons.trademark
            },
            perl: {
                expr: /([^\w.\-/\\_]|^)perl\b(?![.\-]\w|[/\\_])/gi,
                replacement: "$1Perl",
                reason: App.consts.reasons.trademark
            },
            htc: {
                expr: /\bhtc\b/gi,
                replacement: "HTC",
                reason: App.consts.reasons.trademark
            },
            greasemonkey: {
                expr: /\bgre[ea]se\W?monkey\b/gi, //Should this also be correcting spelling, or should that be a separate rule?
                replacement: "Greasemonkey",
                reason: App.consts.reasons.trademark
            },
            tampermonkey: {
                expr: /\btamper\W?monkey\b/gi,
                replacement: "Tampermonkey",
                reason: App.consts.reasons.trademark
            },
            violentmonkey: {
                expr: /\bviolent\W?monkey\b/gi,
                replacement: "Violentmonkey",
                reason: App.consts.reasons.trademark
            },
            mozilla: {
                expr: /\bmozill?a\b/gi,
                replacement: "Mozilla",
                reason: App.consts.reasons.trademark
            },
            webextensions: {
                expr: /\bweb-*extension(s*)\b/gi,
                replacement: "WebExtension$1",
                reason: App.consts.reasons.trademark
            },
            firefoxWebextensions: {
                expr: /\bfirefox[ \-]*web[ \-]*exten[st]ion(s*)\b/gi,
                replacement: "Firefox WebExtension$1",
                reason: App.consts.reasons.trademark
            },
            microsoftedge: {
                expr: /\bmicrosoft[ \-]*edge\b/gi,
                replacement: "Microsoft Edge",
                reason: App.consts.reasons.trademark
            },
            typescript: {
                expr: /\btypescript\b/gi,
                replacement: "TypeScript",
                reason: App.consts.reasons.trademark
            },
            xulrunner: {
                expr: /\bxulrunner\b/gi,
                replacement: "XULRunner",
                reason: App.consts.reasons.trademark
            },
            xul: {
                expr: /\bxul\b/gi,
                replacement: "XUL",
                reason: App.consts.reasons.trademark
            },
            webrtc: {
                expr: /\bwebrtc\b/gi,
                replacement: "WebRTC",
                reason: App.consts.reasons.trademark
            },
            cakephp: {
                expr: /\bcakephp\b/gi,
                replacement: "CakePHP",
                reason: App.consts.reasons.trademark
            },
            usps: {
                expr: /\busps\b/gi,
                replacement: "USPS",
                reason: App.consts.reasons.trademark
            },
            ups: {
                expr: /\bups\b/gi,
                replacement: "UPS",
                reason: App.consts.reasons.trademark
            },
            fedex: {
                expr: /\bFedEx\b/gi,
                replacement: "FedEx",
                reason: App.consts.reasons.trademark
            },
            shopify: {
                expr: /\bshopify\b/gi,
                replacement: "Shopify",
                reason: App.consts.reasons.trademark
            },
            xcode: {
                expr: /\bxcode\b/gi,
                replacement: "Xcode",
                reason: App.consts.reasons.trademark
            },
            imagemagic: {
                expr: /\bimagemagic\b/gi,
                replacement: "ImageMagic",
                reason: App.consts.reasons.trademark
            },
            openfire: {
                expr: /\bopenfire\b/gi,
                replacement: "Openfire",
                reason: App.consts.reasons.trademark
            },
            wifi: {
                expr: /\bwi-?fi\b/gi,
                replacement: "Wi-Fi",
                reason: App.consts.reasons.trademark
            },
            springboot: {
                expr: /\bspring ?boot\b/gi,
                replacement: "Spring Boot",
                reason: App.consts.reasons.trademark
            },
            springcloud: {
                expr: /\bspring ?cloud\b/gi,
                replacement: "Spring Cloud",
                reason: App.consts.reasons.trademark
            },
            jmeter: {
                expr: /\bjmeter\b/gi,
                replacement: "JMeter",
                reason: App.consts.reasons.trademark
            },
            digitalocean: {
                expr: /\bdigital\W?ocean\b/gi,
                replacement: "DigitalOcean",
                reason: App.consts.reasons.trademark
            },
            orangehrm: {
                expr: /\borange\W?hrm\b/gi,
                replacement: "OrangeHRM",
                reason: App.consts.reasons.trademark
            },
            codeigniter: {
                expr: /\bcode\W?igniter\b/gi,
                replacement: "CodeIgniter",
                reason: App.consts.reasons.trademark
            },
            openvpn: {
                expr: /\bopenvpn(\d?)\b/gi,
                replacement: "OpenVPN$1",
                reason: App.consts.reasons.trademark
            },
            tensorflow: {
                expr: /\btensor\W?flow\b/gi,
                replacement: "TensorFlow",
                reason: App.consts.reasons.trademark
            },
            netsuite: {
                expr: /\bnetsuite\b/gi,
                replacement: "NetSuite",
                reason: App.consts.reasons.trademark
            },
            cpanel: {
                expr: /\bcpanel\b/gi,
                replacement: "cPanel",
                reason: App.consts.reasons.trademark
            },
            putty: {
                expr: /\bputty\b/gi,
                replacement: "PuTTY",
                reason: App.consts.reasons.trademark
            },
            godaddy: {
                expr: /\bgodaddy\b/gi,
                replacement: "GoDaddy",
                reason: App.consts.reasons.trademark
            },
            cryptoapi: {
                expr: /\bcrypto\s?api\b/gi,
                replacement: "CryptoAPI",
                reason: App.consts.reasons.trademark
            },
            selenium: {
                expr: /\bselenium\b/gi,
                replacement: "Selenium",
                reason: App.consts.reasons.trademark
            },
            testng: {
                expr: /\btest\s?ng\b/gi,
                replacement: "TestNG",
                reason: App.consts.reasons.trademark
            },
            ionic: {
                expr: /\bionic(?:\s?pro)?\b/gi,
                replacement: function(str) {
                    return toTitleCase(str);
                },
                reason: App.consts.reasons.trademark
            },
            opencart: {
                expr: /\bopen\s?cart\b/gi,
                replacement: "OpenCart",
                reason: App.consts.reasons.trademark
            },
            woocommerce: {
                expr: /\bwoo\s?commerce\b/gi,
                replacement: "WooCommerce",
                reason: App.consts.reasons.trademark
            },
            laravel: {
                expr: /\blaravel/gi,
                replacement: "Laravel",
                reason: App.consts.reasons.trademark
            },
            pfsense: {
                expr: /\bpfsense\b/gi,
                replacement: "pfSense",
                reason: App.consts.reasons.trademark
            },
            mipsN: {
                expr: /\bmips(32|64)?\b/gi,
                replacement: "MIPS$1",
                reason: App.consts.reasons.trademark
            },
            armN: {
                expr: /\barm(32|64)\b/gi, //arm by itself is too generic to automatically capitalize
                replacement: "ARM$1",
                reason: App.consts.reasons.trademark
            },
            powerpcN: {
                expr: /\bpowerpc(32|64)?\b/gi,
                replacement: "PowerPC$1",
                reason: App.consts.reasons.trademark
            },
            android_studio: {
                expr: /\bandroid ?studio\b/gi,
                replacement: "Android Studio",
                reason: App.consts.reasons.trademark
            },
            arduino: {
                expr: /\barduino(s?)\b/gi,
                replacement: "Arduino$1",
                reason: App.consts.reasons.trademark
            },
            crashlytics: {
                expr: /\bcrashl[yi]tics?\b/gi,
                replacement: "Crashlytics",
                reason: App.consts.reasons.trademark
            },
            firebase: {
                expr: /\bfirebase\b/gi,
                replacement: "Firebase",
                reason: App.consts.reasons.trademark
            },
            whatsapp: {
                expr: /\bwhatsapp\b/gi,
                replacement: "WhatsApp",
                reason: App.consts.reasons.trademark
            },
            atlassian: {
                expr: /\batl[ae]s+ian\b/gi,
                replacement: "Atlassian",
                reason: App.consts.reasons.trademark
            },
            confluence: {
                expr: /\bconfluence\b/gi,
                replacement: "Confluence",
                reason: App.consts.reasons.trademark
            },
            woocommerce: {
                expr: /\bcwoocommerce\b(?!\.\S)/gi,
                replacement: "WooCommerce",
                reason: App.consts.reasons.trademark
            },
            /*
            ** Acronyms - to be capitalized (except sometimes when part of a file name)
            **/
            x_html: {
                expr: /(?:[^\b\w.]|^)(:?g|ht|xa?|xht|sf|csht)ml[\d.]*\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            css: {
                expr: /(?:[^\b\w.]|^)s?css\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            json: {
                expr: /(?:[^\b\w.]|^)json\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ajax: {
                expr: /\bajax\b/g,     // Leave "Ajax" alone. See https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit/issues/45
                replacement: "AJAX",
                reason: App.consts.reasons.acronym
            },
            sql: {
                expr: /(?:[^\b\w.]|^)sql\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            urli: {
                expr: /\b(ur[li])(s)?\b/gi,
                replacement: function(match,upper,lower) { return upper.toUpperCase() + (lower?lower.toLowerCase():''); },
                reason: App.consts.reasons.acronym
            },
            asp: {
                expr: /([^\b\w.]|^)asp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            pdf: {
                expr: /([^\b\w.]|^)pdf(s)?/gi,
                replacement: "$1PDF$2",
                reason: App.consts.reasons.acronym
            },
            api: {
                expr: /([^\b\w.]|^)api(s)?\b/gi,
                replacement: "$1API$2",
                reason: App.consts.reasons.acronym
            },
            ssl: {
                expr: /(?:[^\b\w.]|^)ssl\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            npm: {
                expr: /\bnpm(s)?\b/g,
                replacement: "NPM$1",
                reason: App.consts.reasons.acronym
            },
            ftp: {
                expr: /(?:[^\b\w.]|^)[st]?ftps?\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ipa: {
                expr: /(?:[^\b\w.]|^)ipa\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            avl: {
                expr: /(?:[^\b\w.]|^)avl\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            cli_cgi: {
                expr: /(?:[^\b\w.]|^)c[lg]i\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            dll: {
                expr: /(?:[^\b\w.]|^)dll\b/g,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            mp3_mp4: {
                expr: /([^\b\w.]|^)mp(3|4)(s)?\b/gi,
                replacement: "$1MP$2$3",
                reason: App.consts.reasons.acronym
            },
            gui: {
                expr: /([^\b\w.]|^)gui(s)?\b/gi,
                replacement: "$1GUI$2",
                reason: App.consts.reasons.acronym
            },
            stp: {
                expr: /(?:[^\b\w.]|^)stp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            tcp: {
                expr: /(?:[^\b\w.]|^)tcp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            wpf: {
                expr: /(?:[^\b\w.]|^)wpf\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            http: {
                expr: /(?:[^\b\w.]|^)https?\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            woff: {
                expr: /(?:[^\b\w.]|^)woff\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ttf: {
                expr: /(?:[^\b\w.]|^)ttf\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ipv_n: {
                expr: /\bip(v[46])?\b/gi,
                replacement: "IP$1",
                reason: App.consts.reasons.acronym
            },
            fq_dn_s: {  // FQDN, DN, DNS
                expr: /(?:[^\b\w.]|^)(?:fq)?dns?\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            icmp: {
                expr: /\bicmp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            rsvp: {
                expr: /\brsvp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            snmp: {
                expr: /\bsnmp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            cpu: {
                expr: /\bcpu(s)?\b/gi,
                replacement: "CPU$1",
                reason: App.consts.reasons.acronym
            },
            rss: {
                expr: /(?:[^\b\w.]|^)rss?\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            mvc: {
                expr: /(?:[^\b\w.]|^)mvc\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            mvn: {
                expr: /(?:[^\b\w.]|^)mvn\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ascii: {
                expr: /([^\b\w.]|^)ascc?ii?\b/gi,
                replacement: "$1ASCII",
                reason: App.consts.reasons.acronym
            },
            gsoap: {
                expr: /([^\b\w.]|^)gsoap\b/gi,
                replacement: "$1gSOAP",
                reason: App.consts.reasons.acronym
            },
            soap: {
                expr: /([^\b\w.]|^)soap\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            csv: {
                expr: /([^\b\w.]|^)csv\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            image_types: {
                expr: /([^\b\w.]|^)(gif|jpe?g|bmp|png)\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            yaml: {
                expr: /([^\b\w.]|^)yaml\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            smtp: {
                expr: /\bsmtp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            phpmyadmin: {
                expr: /([^\b\w.]|^)phpmyadmin\b/gi,
                replacement: "$1phpMyAdmin",
                reason: App.consts.reasons.acronym
            },
            phpunit: {
                expr: /([^\b\w.]|^)phpunit\b/gi,
                replacement: "$1PHPUnit",
                reason: App.consts.reasons.acronym
            },
            mkl: {
                expr: /([^\b\w.]|^)mkl\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            xsl: {
                expr: /(?:[^\b\w.]|^)xslt?(?!:)\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            jpa: {
                expr: /(?:[^\b\w.]|^)jpa\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            jvm: {
                expr: /(?:[^\b\w.]|^)jvm\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            linq: {
                expr: /(?:[^\b\w.]|^)linq\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            md5: {
                expr: /(?:[^\b\w.]|^)md5\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            xfa_xsd: {  // XML Forms Architecture
                expr: /(?:[^\b\w.]|^)xfa|xsd\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            wsdl: {
                expr: /(?:[^\b\w.]|^)wsdl\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            hdp: {  // Hadoop related acronyms
                expr: /(?:[^\b\w.]|^)h(?:dp|dfs|sm)\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ide: {
                expr: /(?:[^\b\w.]|^)ide\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            ram_rom: {
                expr: /(?:[^\w.\-/\\_]|^)r[ao]m\b(?![.\-]\w|[/\\_])/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            sdk: {
                expr: /(?:[^\b\w.]|^)sdk\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            usb: {
                expr: /(?:[^\b\w.]|^)usb\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            utf: {
                expr: /(?:[^\b\w.]|^)utf\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            xmpp: {
                expr: /(?:[^\b\w.]|^)xmpp\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            seo: {
                expr: /(?:[^\b\w.]|^)seo\b/gi,
                replacement: function (match) { return match.toUpperCase(); },
                reason: App.consts.reasons.acronym
            },
            gps: {
                expr: /\bgps\b/gi,
                replacement: "GPS",
                reason: App.consts.reasons.acronym
            },
            vps: {
                expr: /\bvps\b/gi,
                replacement: "VPS",
                reason: App.consts.reasons.acronym
            },
            cisc: {
                expr: /\bcisc\b/gi,
                replacement: "CISC",
                reason: App.consts.reasons.acronym
            },
            risc: {
                expr: /\brisc\b/gi,
                replacement: "RISC",
                reason: App.consts.reasons.acronym
            },
            midi: {
                expr: /\bmidi\b/gi,
                replacement: "MIDI",
                reason: App.consts.reasons.acronym
            },
            cdn: {
                expr: /\bcdn\b/gi,
                replacement: "CDN",
                reason: App.consts.reasons.acronym
            },
            /*
            ** Spelling - Correct common spelling errors. (Including apostrophes, which are really grammar.)
            ** Acknowledgement: A subset of terms were adapted from Peter Mortensen's list
            ** (http://pvm-professionalengineering.blogspot.de/2011/04/word-list-for-editing-stack-exchange.html)
            **/
            voting: {
                expr: /\b(down|up)\Wvot/gi,
                replacement: "$1vote",
                reason: App.consts.reasons.spelling
            },
            succeed: {
                expr: /\b(s)uc[cs]?ee?d(ed|s)?\b/gi,
                replacement: "$1ucceed$2",
                reason: App.consts.reasons.spelling
            },
            source: {
                expr: /\b(s)orce(s|d)?\b/gi,
                replacement: "$1ource$2",
                reason: App.consts.reasons.spelling
            },
            standardize: {  // https://regex101.com/r/vN7pM0/1
                expr: /\b(s)tandari([sz](?:e|es|ed|ation))\b/gi,
                replacement: "$1tandardi$2",
                reason: App.consts.reasons.spelling
            },
            different: {  // https://regex101.com/r/xO8jU2/1
                expr: /\b(d)iff?e?re?n(t|ces?)\b/gi,
                replacement: "$1ifferen$2",
                reason: App.consts.reasons.spelling
            },
            personally: { // https://regex101.com/r/oL9aM1/2
                expr: /\b(p)erso(?:nl|nl|nal)(ly)?\b/gi,
                replacement: "$1ersonal$2",
                reason: App.consts.reasons.spelling
            },
            problem: { // https://regex101.com/r/yA8jM7/6
                expr: /\b(p)(?:or?|ro|rο|r0)b(?:le|el|e|re|l|[|]e)me?(s)?\b/gi,
                replacement: "$1roblem$2",
                reason: App.consts.reasons.spelling
            },
            written: {
                expr: /\b(w)riten\b/gi,
                replacement: "$1ritten",
                reason: App.consts.reasons.spelling
            },
            maybe: {
                expr: /\b(m)(?:aby|yabe)\b/gi,
                replacement: "$1aybe",
                reason: App.consts.reasons.spelling
            },
            pseudo: {
                expr: /\b(p)suedo\b/gi,
                replacement: "$1seudo",
                reason: App.consts.reasons.spelling
            },
            application: {  // https://regex101.com/r/bO4dP4/3
                expr: /\b(a)p[plia]+ca?[tio]+n(s)?\b/gi,
                replacement: "$1pplication$2",
                reason: App.consts.reasons.spelling
            },
            calendar: {
                expr: /\b(c)al[ea]nd[ae]r\b/gi,
                replacement: "$1alendar",
                reason: App.consts.reasons.spelling
            },
            commit: {  // https://regex101.com/r/kY6sN8/1
                expr: /\b(c)omm?it?(s|ted|ters?|ting)?\b/gi,
                replacement: "$1ommit$2",
                reason: App.consts.reasons.spelling
            },
            autocomplete: { // https://regex101.com/r/rZ9gW5/1
                expr: /\b(a)uto?[ -]?co?m?p?l?ete?(s)?\b/gi,
                replacement: "$1utocomplete$2",
                reason: App.consts.reasons.spelling
            },
            you: {
                expr: /\b(y)o+u?\b/gi,
                replacement: "$1ou",
                reason: App.consts.reasons.spelling
            },
            doesn_t: { // https://regex101.com/r/sL0uO9/5
                expr: /\b(d)(?:ose?[^\w]*n?.?t|oens.?t|oesn?[^\w]*t|oest)\b/gi,
                replacement: "$1oesn't",
                reason: App.consts.reasons.spelling
            },
            couldn_t_wouldn_t_shouldn_t: {
                expr: /\b(c|w|sh)o?ul?dn[ '`´]*t\b/gi,
                replacement: "$1ouldn't",
                reason: App.consts.reasons.spelling
            },
            didn_t: {
                expr: /\b(d)id?[^\w]*n?t\b/gi,  // Caveat: changes dint -> didn't, although "dint" is a word.
                replacement: "$1idn't",
                reason: App.consts.reasons.spelling
            },
            don_t: {  // https://regex101.com/r/nT2jV6/1
                expr: /\b(d)(?:on[^\w']*t|o[n']+o?t)\b/gi,
                replacement: "$1on't",
                reason: App.consts.reasons.spelling
            },
            haven_t: {
                expr: /\b(h)(?:avent|av[^\w]*t|ave[^\w]?t)\b/gi,
                replacement: "$1aven't",
                reason: App.consts.reasons.spelling
            },
            wasn_t: {
                expr: /\b(w)as[^\w]*n?t\b/gi,
                replacement: "$1asn't",
                reason: App.consts.reasons.spelling
            },
            //apostrophe_d: {   // Too many false positives
            //    expr: /\b(he|she|who|you)[^\w]*(d)\b/gi,
            //    replacement: "$1'$2",
            //    reason: App.consts.reasons.spelling
            //},
            apostrophe_ll: {
                expr: /\b(they|what|who|you)[^\w]*(ll)\b/gi,
                replacement: "$1'$2",
                reason: App.consts.reasons.spelling
            },
            apostrophe_re: {
                expr: /\b(they|what|you)[^\w]*(re)\b/gi,
                replacement: "$1'$2",
                reason: App.consts.reasons.spelling
            },
            apostrophe_s: { // https://regex101.com/r/bN5pA3/1
                expr: /\b(he|she|that|there|what|where|here)[^\w]*(s)\b/gi,
                replacement: "$1'$2",
                reason: App.consts.reasons.spelling
            },
            it_s: {
                expr: /\b(it)[^\w](s)\b/gi,
                replacement: "$1'$2",
                reason: App.consts.reasons.spelling
            },
            apostrophe_t: {
                expr: /\b(aren|can|couldn|didn|doesn|don|hasn|haven|isn|mightn|mustn|shan|shouldn|won|wouldn)[^\w]*(t)(?:[^\w]t)*\b/gi,
                replacement: "$1'$2",
                reason: App.consts.reasons.spelling
            },
            apostrophe_nt: {
                expr: /['`´]nt\b/gi,
                replacement: "n't",
                reason: App.consts.reasons.spelling
            },
            doesn_t_work: {  // >4K instances of this (Oct 2015)
                expr: /\b(d)oesn[^\w]t (work|like|think|want|put|save|load|get|help|make)s\b/gi,
                replacement: "$1oesn't $2",
                reason: App.consts.reasons.spelling
            },
            probably: {  // https://regex101.com/r/zU3qZ0/1
                expr: /\b(p)r(?:oll?|obb?l|o?babl?|ababl)y\b/gi,
                replacement: "$1robably",
                reason: App.consts.reasons.spelling
            },
            keyboard: {
                expr: /\b(k)ey?boa?rd\b/gi,
                replacement: "$1eyboard",
                reason: App.consts.reasons.spelling
            },
            ur: {
                expr: /\bur\b/gi,
                replacement: "your", // May also be "you are", but less common on SO
                reason: App.consts.reasons.spelling
            },
            u: {
                expr: /\bu\b/gi,
                replacement: "you",
                reason: App.consts.reasons.spelling
            },
            gr8: {
                expr: /\bgr8\b/gi,
                replacement: "great",
                reason: App.consts.reasons.spelling
            },
            cuz: {
                expr: /'?\bcuz\b|'cause\b/gi,
                replacement: "because",
                reason: App.consts.reasons.spelling
            },
            because_: {  // 10K+ posts
                expr: /\b(c)ause (?=I|you|we|if)\b/gi,
                replacement: "because ",
                reason: App.consts.reasons.spelling
            },
            ofc: {
                expr: /\b(o)fc\b/gi,
                replacement: "$1f course",
                reason: App.consts.reasons.spelling
            },
            nvm: {
                expr: /\b(n)vm\b/gi,
                replacement: "$1ever mind",
                reason: App.consts.reasons.spelling
            },
            btw: {
                expr: /\b(b)tw,?\b/gi,
                replacement: "$1y the way,",
                reason: App.consts.reasons.spelling
            },
            sry: {
                expr: /\b(s)o?r+y\b/gi,
                replacement: "$1orry",
                reason: App.consts.reasons.spelling
            },
            any1: {
                expr: /\b(a)ny1\b/gi,
                replacement: "$1nyone",
                reason: App.consts.reasons.spelling
            },
            allways: {
                expr: /\b(a)llways\b/gi,
                replacement: "$1lways",
                reason: App.consts.reasons.spelling
            },
            expect: {
                expr: /\b(e)spect(s)?\b/gi,
                replacement: "$1xpect$2",
                reason: App.consts.reasons.spelling
            },
            employee: {
                expr: /\b(e)mploye\b/gi,
                replacement: "$1mployee",
                reason: App.consts.reasons.spelling
            },
            retrieve: {
                expr: /\b(r)etreiv(e|ed|es|ing|al|able)\b/gi,
                replacement: "$1etriev$2",
                reason: App.consts.reasons.spelling
            },
            success: { // https://regex101.com/r/hK2vG4/1
                expr: /\b(s)ucc?ess?(ful|fully)?l?\b/gi,
                replacement: "$1uccess$2",
                reason: App.consts.reasons.spelling
            },
            anyones: {
                expr: /\b(a)nyones\b/gi,
                replacement: "$1nyone's",
                reason: App.consts.reasons.spelling
            },
            length: {
                expr: /\b(l)en(?:gh?t|th)\b/gi,
                replacement: "$1ength",
                reason: App.consts.reasons.spelling
            },
            height: {
                expr: /\b(h)(?:ei|i|ie)(?:gt|th|ghth|gth)\b/gi,
                replacement: "$1eight",
                reason: App.consts.reasons.spelling
            },
            width: {
                expr: /\b(w)it?dh?t\b/gi,
                replacement: "$1idth",
                reason: App.consts.reasons.spelling
            },
            aint_isnt: {
                expr: /\bain'?t\b/gi,
                replacement: "isn't",
                reason: App.consts.reasons.spelling
            },
            coordinates: {
                expr: /\b(c)ordinate(s|d)?\b/gi,
                replacement: "$1oordinate$2",
                reason: App.consts.reasons.spelling
            },
            argument: {  // https://regex101.com/r/iU2vK9/2
                expr: /\b(a)rg?[ue]+m[ea]nt(s)?\b/gi,
                replacement: "$1rgument$2",
                reason: App.consts.reasons.spelling
            },
            iterate: { // https://regex101.com/r/iL6bV3/1
                expr: /\b(i)(?:tter|tar)at(e[ds]?|ing|ion|ions)\b/gi,
                replacement: "$1terat$2",
                reason: App.consts.reasons.spelling
            },
            below: {
                expr: /\b(b)ellow\b/gi,          // "Bellow" is a word, but extremely uncommon on StackOverflow.com.
                replacement: "$1elow",
                reason: App.consts.reasons.spelling
            },
            encrypt: {
                expr: /\b(en|de)cript(s|ing)?\b/gi,
                replacement: "$1crypt$2",
                reason: App.consts.reasons.spelling
            },
            formatting: {
                expr: /\b(f)ormating\b/gi,
                replacement: "$1ormatting",
                reason: App.consts.reasons.spelling
            },
            process: {
                expr: /\b(p)roces(es|ed)?\b/gi,
                replacement: "$1rocess$2",
                reason: App.consts.reasons.spelling
            },
            program: {
                expr: /\b(p)rogr?amm?e?\b/gi,
                replacement: "$1rogram",
                reason: App.consts.reasons.spelling
            },
            programming: {
                expr: /\b(p)rogram(ing|ed|er)\b/gi,
                replacement: "$1rogramm$2",
                reason: App.consts.reasons.spelling
            },
            programmatically: {  // 40K+   https://regex101.com/r/vF2jQ8/2
                expr: /\b(p)rogram+at+ica?l+y\b/gi,
                replacement: "$1rogrammatically",
                reason: App.consts.reasons.spelling
            },
            bear_with_me: {
                expr: /\b(b)are (with m[ey]|it|in mind)\b/gi,
                replacement: "$1ear $2",
                reason: App.consts.reasons.spelling
            },
            weird: {
                expr: /\b(w)ierd(ness|ly)\b/gi,
                replacement: "$1eird$2",
                reason: App.consts.reasons.spelling
            },
            sample: {
                expr: /\b(s)maple(s|d)?\b/gi,
                replacement: "$1ample$2",
                reason: App.consts.reasons.spelling
            },
            really: {  // https://regex101.com/r/sO4zD9/1
                expr: /\b(r)(?:elly|ealy)\b/gi,
                replacement: "$1eally",
                reason: App.consts.reasons.spelling
            },
            finally_: {
                expr: /\b(f)inall?y\b/gi,
                replacement: "$1inally",
                reason: App.consts.reasons.spelling
            },
            behaviour: { // https://regex101.com/r/rU1eB7/1
                expr: /\b(b)eha?i?vi?o(r|ur|rs|urs)\b/gi,
                replacement: "$1ehavio$2",
                reason: App.consts.reasons.spelling
            },
            unfortunately: {
                expr: /\b(u)nfortu?na?tly\b/gi,
                replacement: "$1nfortunately",
                reason: App.consts.reasons.spelling
            },
            whether: {
                expr: /\b(w)h?eth?er\b/gi,
                replacement: "$1hether",
                reason: App.consts.reasons.spelling
            },
            whether_not_weather: { // https://regex101.com/r/oS1xE5/3
                expr: /\b(w)eather(?= (?:it|we|I|or not|they|[^.?!]*(?:works?|helps?))\b)/gi,
                replacement: "$1hether",
                reason: App.consts.reasons.spelling
            },
            through: {  // https://regex101.com/r/gQ0dZ1/4
                expr: /\b(t)(?:hru|rough|hroug)\b/gi,
                replacement: "$1hrough",
                reason: App.consts.reasons.spelling
            },
            throughout: {
                expr: /\b(t)(?:hruout|roughout)\b/gi,
                replacement: "$1hroughout",
                reason: App.consts.reasons.spelling
            },
            breakthrough: {
                expr: /\b(b)reak\s+through(s)?\b/gi,
                replacement: "$1reakthrough$2",
                reason: App.consts.reasons.spelling
            },
            though: {
                expr: /\b(t)(?:ho|hou|hogh)\b/gi,
                replacement: "$1hough",
                reason: App.consts.reasons.spelling
            },
            although: {
                expr: /\b(a)l(?:tho|thou|thogh|tough)\b/gi,
                replacement: "$1lthough",
                reason: App.consts.reasons.spelling
            },
            thought: {
                expr: /\b(t)r?ought(s)?\b/gi,
                replacement: "$1hough$2",
                reason: App.consts.reasons.spelling
            },
            throwing: {
                expr: /\b(t)hroughing\b/gi,       // Peter says this is "thoroughly", but a survey of SO questions indicates "throwing"
                replacement: "$1hrowing",
                reason: App.consts.reasons.spelling
            },
            a_lot: {
                expr: /\b(a)lot\b/gi,
                replacement: "$1 lot",
                reason: App.consts.reasons.spelling
            },
            one_r_two_r: {
                expr: /\b(refe|prefe|occu)r(ed|ing)\b/gi,
                replacement: "$1rr$2",
                reason: App.consts.reasons.spelling
            },
            occur: {
                expr: /\b(o)ccure(s)?\b/gi,
                replacement: "$1ccur$2",
                reason: App.consts.reasons.spelling
            },
            preferably: {
                expr: /\b(p)referrably\b/gi,
                replacement: "$1referably",
                reason: App.consts.reasons.spelling
            },
            command_line: {
                expr: /\b(c)(?:omm?andline|mdline?)\b/gi,
                replacement: "$1ommand-line",
                reason: App.consts.reasons.spelling
            },
            benefits: {
                expr: /\b(b)enifits\b/gi,
                replacement: "$1enefits",
                reason: App.consts.reasons.spelling
            },
            authorization: {  // https://regex101.com/r/pQ8mD9/1
                expr: /([^\b\w.-])(a)uth\b/gi,           // This may be too ambiguous, could also mean "authentication"
                replacement: "$1$2uthorization",
                reason: App.consts.reasons.spelling
            },
            persistent: {
                expr: /\b(p)ersistan(t|ce)\b/gi,
                replacement: "$1ersisten$2",
                reason: App.consts.reasons.spelling
            },
            access: {  // must come before _ibility to catch accessibility with spelling variations ** but does not fix acessability?
                expr: /\b(a)c+e+s+(.*)\b/gi,
                replacement: "$1ccess$2",
                reason: App.consts.reasons.spelling
            },
            _ible: {
                expr: /\b(compat|incompat|access)able\b/gi,
                replacement: "$1ible",
                reason: App.consts.reasons.spelling
            },
            _ibility: {
                expr: /\b(compat|incompat|access)abili?t(y|ies)\b/gi,
                replacement: "$1ibilit$2",
                reason: App.consts.reasons.spelling
            },
            separate: {
                expr: /\b(s)epe?rate?(d|ly|s)?\b/gi,
                replacement: "$1eparate$2",
                reason: App.consts.reasons.spelling
            },
            separation: {
                expr: /\b(s)eperation(s)?\b/gi,
                replacement: "$1eparation$2",
                reason: App.consts.reasons.spelling
            },
            definite: {
                expr: /\b(d)efin(?:ate?|ite?|al|te?|et)(ly)?\b/gi,  // Catches correct spelling, too.
                replacement: "$1efinite$2",
                reason: App.consts.reasons.spelling
            },
            definitive: {
                expr: /\b(d)efina?tive(ly)?\b/gi,
                replacement: "$1efinitive$2",
                reason: App.consts.reasons.spelling
            },
            independent: {
                expr: /\b(i)ndependant(ly)?\b/gi,
                replacement: "$1ndependent$2",
                reason: App.consts.reasons.spelling
            },
            recommend: { // https://regex101.com/r/pP9lB7/1
                expr: /\b(r)ecomm?[ao]nd(ation)?\b/gi,
                replacement: "$1ecommend$2",
                reason: App.consts.reasons.spelling
            },
            compatibility: {
                expr: /\b(c)ompatability\b/gi,
                replacement: "$1ompatibility$2",
                reason: App.consts.reasons.spelling
            },
            ps: {
                expr: /\bps\b/g,
                replacement: "PS",
                reason: App.consts.reasons.spelling
            },
            ok: {
                expr: /\bok\b/g,
                replacement: "OK",
                reason: App.consts.reasons.spelling
            },
            back_end: {  // Interesting fact: backend 3x more common than back-end
                expr: /\b(b)ackend\b/g,
                replacement: "$1ack-end",
                reason: App.consts.reasons.spelling
            },
            front_end: {
                expr: /\b(f)rontend\b/g,
                replacement: "$1ront-end",
                reason: App.consts.reasons.spelling
            },
            data_type: {
                expr: /\b(d)atatype\b/g,
                replacement: "$1ata type",
                reason: App.consts.reasons.spelling
            },
            allotted: {
                expr: /\b(a)l+ot+ed\b/g,
                replacement: "$1llotted",
                reason: App.consts.reasons.spelling
            },
            straight: {
                expr: /\b(s)traig?h?t\b/g,
                replacement: "$1traight",
                reason: App.consts.reasons.spelling
            },
            straightforward: {
                expr: /\b(s)traig?h?t[ -]?for?ward\b/g,
                replacement: "$1traightforward",
                reason: App.consts.reasons.spelling
            },
            preceding: {
                expr: /\b(p)receeding\b/gi,
                replacement: "$1receding",
                reason: App.consts.reasons.spelling
            },
            no_one: {
                expr: /\b(n)o-?one\b/gi,
                replacement: "$1o one",
                reason: App.consts.reasons.spelling
            },
            de_facto: {
                expr: /\b(d)e-?facto\b/gi,
                replacement: "$1e facto",
                reason: App.consts.reasons.spelling
            },
            accommodate: { // https://regex101.com/r/cL3mD9/1
                expr: /\b(a)(?:c+om|com+)odate\b/gi,
                replacement: "$1ccommodate",
                reason: App.consts.reasons.spelling
            },
            kind_of: {
                expr: /\b(k)inda\b/gi,
                replacement: "$1ind of",
                reason: App.consts.reasons.spelling
            },
            want_to: {
                expr: /\b(w)ann?a\b/gi,
                replacement: "$1ant to",
                reason: App.consts.reasons.spelling
            },
            sort_of: {
                expr: /\b(s)orta\b/gi,
                replacement: "$1ort of",
                reason: App.consts.reasons.spelling
            },
            got_to: { // https://regex101.com/r/rK6xR5/1
                expr: /\b(have\s+)?(g)otta\b/gi,
                replacement: "$1$2ot to",
                reason: App.consts.reasons.spelling
            },
            dont_know: { // https://regex101.com/r/rK6xR5/1
                expr: /\b(d)[uo]nn?o\b/gi,
                replacement: "$1on't know",
                reason: App.consts.reasons.spelling
            },
            going_to: {
                expr: /\b(g)[ou]nn?a\b/gi,
                replacement: "$1oing to",
                reason: App.consts.reasons.spelling
            },
            crashes: {
                expr: /\b(c)rashs\b/gi,
                replacement: "$1rashes",
                reason: App.consts.reasons.spelling
            },
            pattern: {
                expr: /\b(p)at?(?:trn|tren|tern)(s)?\b/gi,
                replacement: "$1attern$2",
                reason: App.consts.reasons.spelling
            },
            syntax: {
                expr: /\b(s)[yi]nt[ae]?x\b/gi,
                replacement: "$1yntax",
                reason: App.consts.reasons.spelling
            },
            correct: {
                expr: /\b(c)orr?ec[ty]/gi,  // No \b at end, to include correction, correcting, corrected
                replacement: "$1orrect",
                reason: App.consts.reasons.spelling
            },
            correctly: {
                expr: /\b(c)orr?ec(?:lt?|t?l)y\b/ig,
                replacement: "$1orrectly",
                reason: App.consts.reasons.spelling
            },
            integer: {
                expr: /\b(i)nte?r?ger(s)?\b/gi,
                replacement: "$1nteger$2",
                reason: App.consts.reasons.spelling
            },
            several: {
                expr: /\b(s)er?v[ea]?r[ae]?l\b/gi,
                replacement: "$1everal",
                reason: App.consts.reasons.spelling
            },
            solution: {
                expr: /\b(s)ou?lu?ti?on\b/gi,
                replacement: "$1olution",
                reason: App.consts.reasons.spelling
            },
            somebody: {
                expr: /\b(s)ombody\b/gi,
                replacement: "$1omebody",
                reason: App.consts.reasons.spelling
            },
            everything: {
                expr: /\b(e)ve?r[yi]?thing\b/gi,
                replacement: "$1verything",
                reason: App.consts.reasons.spelling
            },
            button: {
                expr: /\b(b)[uo]+tt?[ou]n\b/gi,
                replacement: "$1utton",
                reason: App.consts.reasons.spelling
            },
            before: {
                expr: /\b(b)e?fo?re?\b/gi,
                replacement: "$1efore",
                reason: App.consts.reasons.spelling
            },
            example: { // https://regex101.com/r/uU4bH5/2
                expr: /\b(e)(?:xsample|xamle|x?amp[le]{1,2}|xemple|xaple)(s)?\b/gi,
                replacement: "$1xample$2",
                reason: App.consts.reasons.spelling
            },
            somewhere: {  // https://regex101.com/r/aU2nP5/1
                expr: /\b(s)ome?(?: ?where?|w[ea]+re?)\b/gi,
                replacement: "$1omewhere",
                reason: App.consts.reasons.spelling
            },
            with: { // https://regex101.com/r/xO5dP3/2
                expr: /\b(w)(?:hith|iht)(?=(ou?t|in)?\b)/gi,
                replacement: "$1ith",
                reason: App.consts.reasons.spelling
            },
            without: {  // After 'with' rule, only need to check 'out'
                expr: /\b(w)ithou?t\b/gi,
                replacement: "$1ithout",
                reason: App.consts.reasons.spelling
            },
            reproducible: {
                expr: /\b(r)eproduct?[ia]ble\b/gi,
                replacement: "$1eproducible",
                reason: App.consts.reasons.spelling
            },
            unnecessary: {
                expr: /\b(u)nn?ecc?ess?ary\b/gi,
                replacement: "$1nnecessary",
                reason: App.consts.reasons.spelling
            },
            require: {  // https://regex101.com/r/nS6kM5/1
                expr: /\b(r)equie?re?(d|s|me?nts?)?\b/gi,
                replacement: "$1equire$2",
                reason: App.consts.reasons.spelling
            },
            address: {
                expr: /\b(a)dd?ress?(es|ed|ing)?e?\b/gi,
                replacement: "$1ddress$2",
                reason: App.consts.reasons.spelling
            },
            password: {
                expr: /\b(p)ass?wo?rd?(s)?\b/gi,
                replacement: "$1assword$2",
                reason: App.consts.reasons.spelling
            },
            method: {
                expr: /\b(m)e[th]+[oeu]+d(s)?\b/gi,
                replacement: "$1ethod$2",
                reason: App.consts.reasons.spelling
            },
            property: {
                expr: /\b(p)rope?rt[iey]?\b/gi,
                replacement: "$1roperty",
                reason: App.consts.reasons.spelling
            },
            properties: {
                expr: /\b(p)rope?rt[iey]+s\b/gi,
                replacement: "$1roperties",
                reason: App.consts.reasons.spelling
            },
            wireless: {
                expr: /\b(w)ire?le?ss?\b/gi,
                replacement: "$1ireless",
                reason: App.consts.reasons.spelling
            },
            possible: {
                expr: /\b(p)oss?[ai]?ble\b/gi,
                replacement: "$1ossible",
                reason: App.consts.reasons.spelling
            },
            fields_yields: {  // https://regex101.com/r/cJ8rM4/1
                expr: /\b(f|y)(?:ei?|ie?)l?d(s|ing|ed)?\b/gi,
                replacement: "$1ield$2",
                reason: App.consts.reasons.spelling
            },
            execute: {
                expr: /\b(e)x[ei]?cute(s|d)\b/gi,
                replacement: "$1xecute$2",
                reason: App.consts.reasons.spelling
            },
            algorithm: {
                expr: /\b(a)lgo?r[iy]?th?[iya]?m(s)?\b/gi,
                replacement: "$1lgorithm$2",
                reason: App.consts.reasons.spelling
            },
            version: { // https://regex101.com/r/wE8uD0/1
                expr: /\b(v)er(?:s[io]*|io)n(s|ing|ed)?\b/gi,
                replacement: "$1ersion$2",
                reason: App.consts.reasons.spelling
            },
            which: {  // 22,772 of these as of 12-Nov-2015!
                expr: /\b(w)(?:ich|hic)\b/gi,
                replacement: "$1hich",
                reason: App.consts.reasons.spelling
            },
            disappear: {
                expr: /\b(d)is?apea?r(ing|ed|s)?\b/gi,
                replacement: "$1isappear$2",
                reason: App.consts.reasons.spelling
            },
            because: {
                expr: /\b(b)ec[ao]u?se?\b/gi,
                replacement: "$1ecause",
                reason: App.consts.reasons.spelling
            },
            should: {
                expr: /\b(s)(?:hold|houd|huld|hud|ould)\b/gi,
                replacement: "$1hould",
                reason: App.consts.reasons.spelling
            },
            totally: {
                expr: /\b(t)ota?ll?y\b/gi,
                replacement: "$1otally",
                reason: App.consts.reasons.spelling
            },
            lambda: {
                expr: /\b(l)am[bd]+a\b/gi,
                replacement: "$1ambda",
                reason: App.consts.reasons.spelling
            },
            command: {
                expr: /\b(c)om(?:m?ad|and|mnd)(ed|s|ing|ers?|o)?\b/gi,
                replacement: "$1ommand$2",
                reason: App.consts.reasons.spelling
            },
            therefore: {
                expr: /\b(t)here?fore?\b/gi,
                replacement: "$1herefore",
                reason: App.consts.reasons.spelling
            },
            parameter: {
                expr: /\b(p)ara?m[ea]n?ter(s)?\b/gi,
                replacement: "$1arameter$2",
                reason: App.consts.reasons.spelling
            },
            just: {
                expr: /\b(j)(?:uste|us)\b/gi,
                replacement: "$1ust",
                reason: App.consts.reasons.spelling
            },
            fulfill: {
                expr: /\b(f)ull?\s?fill\b/gi,
                replacement: "$1ulfill",
                reason: App.consts.reasons.spelling
            },
            coming: {
                expr: /\b(c)omming\b/gi,
                replacement: "$1oming",
                reason: App.consts.reasons.spelling
            },
            tried: {  // 8,540 of these!
                expr: /\b(t)rye(d|s)\b/gi,
                replacement: "$1rie$2",
                reason: App.consts.reasons.spelling
            },
            basically: {  // 7,924 of these!
                expr: /\b(b)asica?l+y\b/gi,
                replacement: "$1asically",
                reason: App.consts.reasons.spelling
            },
            completely: {  // 4,793 examples!   https://regex101.com/r/oG7nH6/2
                expr: /\b(c)ompl?ete?l?e?y\b/gi,
                replacement: "$1ompletely",
                reason: App.consts.reasons.spelling
            },
            misread: {
                expr: /\b(m)is+[ -]?rea?d\b/gi,
                replacement: "$1isread",
                reason: App.consts.reasons.spelling
            },
            database: {
                expr: /\b(d)atabaes?\b/gi,
                replacement: "$1atabase",
                reason: App.consts.reasons.spelling
            },
            output: {  // https://regex101.com/r/bP9kY2/1
                expr: /\b(o)ut ?put+(?:ed)?\b/gi,
                replacement: "$1utput",
                reason: App.consts.reasons.spelling
            },
            useful: {  // 11,542  "usefull"
                expr: /\b(u)se(?:full| ful)\b/gi,
                replacement: "$1seful",
                reason: App.consts.reasons.spelling
            },
            classes: {
                expr: /\b(c)la(se|ss)s\b/gi,
                replacement: "$1lasses",
                reason: App.consts.reasons.spelling
            },
            english: {
                expr: /\benglisc?h?\b/gi,
                replacement: "English",
                reason: App.consts.reasons.spelling
            },
            inheritance: {  // 1700 x inheritence
                expr: /\b(i)nherit[ae]n[cs]e?\b/gi,
                replacement: "$1nheritance",
                reason: App.consts.reasons.spelling
            },
            advice: {  // 9000 x advices
                expr: /\b(a)dvices\b/gi,
                replacement: "$1dvice",
                reason: App.consts.reasons.spelling
            },
            when: {
                expr: /\b(w)h[ea]ne?\b/gi,
                replacement: "$1hen",
                reason: App.consts.reasons.spelling
            },
            and_then: {   // 16K instances of this!
                expr: /\b(a)nd,? tha?n\b/gi,
                replacement: "$1nd then",
                reason: App.consts.reasons.spelling
            },
            un_initialize: { // >4K instances https://regex101.com/r/lY2hY1/1
                //Should not change from/to British <-> American English.
                expr: /\b((?:un-?|re-?)?i)n?i?t[ia]+li?([zs])(e|ed|[eo]r|es|ing)\b/gi,
                replacement: function(match, prefix, engAmer, suffix) {
                    return (prefix+'nitiali' + engAmer + suffix).replace("-","");
                },
                reason: App.consts.reasons.spelling
            },
            character: { // 3500+ instances, https://regex101.com/r/lG1qH0/1
                expr: /\b(c)(?:har|h?arac?h?ter)(s|istics?|i[zs]e)?\b/gi,
                replacement: "$1haracter$2",
                reason: App.consts.reasons.spelling
            },
            found: {
                expr: /\b(f)inded\b/gi,
                replacement: "$1ound",
                reason: App.consts.reasons.spelling
            },
            tuple: {  // https://regex101.com/r/zP7zM2/1
                expr: /\b(t)o?up+e?le?(s)?\b/gi,
                replacement: "$1uple$2",
                reason: App.consts.reasons.spelling
            },
            i_read: {
                expr: /\b(I|I've|we|they) red\b/gi,
                replacement: "$1 read",
                reason: App.consts.reasons.spelling
            },
            customize: {  // http://grammarist.com/spelling/customise-customize/    Don't change AME/BRE usage.
                expr: /\b(c)u[st]+[oui]mi([zs])(e)?/gi,
                replacement: "$1ustomi$2$3",
                reason: App.consts.reasons.spelling
            },
            customizable: {  // Common errors are to retain 'e', and/or to use ible, not able
                expr: /\b(c)ustomiz[ea]+(tions?|ble|bility|bilities)/gi,
                replacement: "$1ustomiza$2",
                reason: App.consts.reasons.spelling
            },
            across: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(a)c+ros+\b/gi,
                replacement: "$1cross",
                reason: App.consts.reasons.spelling
            },
            immediate: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(i)m+ed[ia]+te?l?(ly)?\b/gi,
                replacement: "$1mmediate$2",
                reason: App.consts.reasons.spelling
            },
            every_time: {  // https://regex101.com/r/dB6jC2/1
                expr: /\b(e)v[ery]+time?\b/gi,
                replacement: "$1very time",
                reason: App.consts.reasons.spelling
            },
            achieve: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/bZ2qJ1/1
                expr: /\b(a)ch[ei]+ve?(s|d|ment)?\b/gi,
                replacement: "$1chieve$2",
                reason: App.consts.reasons.spelling
            },
            apparent: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/dO3aH4/2
                expr: /\b(a)p+ar[ae]nt?(ly)?\b/gi,
                replacement: "$1pparent$2",
                reason: App.consts.reasons.spelling
            },
            appear: {  // https://regex101.com/r/oL8lI1/1
                expr: /\b(a)p+[ea]+re?(s|ed|ing)?\b/gi,
                replacement: "$1ppear$2",
                reason: App.consts.reasons.spelling
            },
            appearance: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/eP2bF9/1
                expr: /\b(a)p+[ea]+r[ea]+nce(s)?\b/gi,
                replacement: "$1ppearance$2",
                reason: App.consts.reasons.spelling
            },
            beginning: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/sT4gQ0/2
                expr: /\b(b)egi?n+in?g/gi,
                replacement: "$1eginning",
                reason: App.consts.reasons.spelling
            },
            believe: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/pM1cC6/1
                expr: /\b(b)e?l[ei]+v(e|ing|able)/gi, // Note lack of \b at end.
                replacement: "$1eliev$2",
                reason: App.consts.reasons.spelling
            },
            colleague: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/xN8qD9/1
                expr: /\b(c)ol+[ea]+gue(s)?\b/gi,
                replacement: "$1olleague$2",
                reason: App.consts.reasons.spelling
            },
            implement: {  // https://regex101.com/r/zW1aS5/1
                expr: /\b(i)mpl?[ei]?ment/gi,
                replacement: "$1mplement",
                reason: App.consts.reasons.spelling
            },
            simultaneous: {  // https://regex101.com/r/iB0mE7/1
                expr: /\b(s)imu[lt]+an[ieou]+se?/gi,
                replacement: "$1imultaneous",
                reason: App.consts.reasons.spelling
            },
            environment: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/qD5zU6/1
                expr: /\b(e)nvi?ro?[nmt]+ent/gi,
                replacement: "$1nvironment",
                reason: App.consts.reasons.spelling
            },
            existence: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/mH7hA6/1
                expr: /\b(e)xist[ae]n[cs]e/gi,
                replacement: "$1xistence",
                reason: App.consts.reasons.spelling
            },
            further: {  // http://www.oxforddictionaries.com/words/common-misspellings https://regex101.com/r/sE6nY3/1
                expr: /\b(f)(?:u|[au]r)th?er/gi,
                replacement: "$1urther",
                reason: App.consts.reasons.spelling
            },
            jist: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\bjist of\b/gi,
                replacement: "gist of",
                reason: App.consts.reasons.spelling
            },
            noticeable: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(n)oticabl(e|y)\b/gi,
                replacement: "$1oticeabl$2",
                reason: App.consts.reasons.spelling
            },
            publicly: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(p)ublica?l*y\b/gi,
                replacement: "$1ublicly",
                reason: App.consts.reasons.spelling
            },
            receive: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(r)ec[ie]+v(e[rds]?|ing)/gi,
                replacement: "$1eceiv$2",
                reason: App.consts.reasons.spelling
            },
            referred: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/kE0oZ5/5
                expr: /\b(r)efer(?!s|enc\w*|r\w*)(?=\w)/gi,
                replacement: "$1eferr",
                reason: App.consts.reasons.spelling
            },
            remember: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(r)e(?:mber|meber|memer)/gi,
                replacement: "$1emember",
                reason: App.consts.reasons.spelling
            },
            sense: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(s)ence/gi,
                replacement: "$1ense",
                reason: App.consts.reasons.spelling
            },
            supersede: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/mA5nC1/1
                expr: /(s)uperced(e[sd]?|ing)\b/gi,
                replacement: "$1upersed$2",
                reason: App.consts.reasons.spelling
            },
            surprise: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/uS8oS4/1
                expr: /\b(s)ur?pri[scz](e[ds]?|ing(?:ly)?)\b/gi,
                replacement: "$1urpris$2",
                reason: App.consts.reasons.spelling
            },
            connection: {  // https://regex101.com/r/rO2wH0/1
                expr: /\b(c)on+e[ctx]+i?on(s)?/gi,
                replacement: "$1onnection$2",
                reason: App.consts.reasons.spelling
            },
            additional: {  // https://regex101.com/r/iM4xV5/2
                expr: /\b(a)d+i.?tio?n[al]+?(ly)?\b/gi,
                replacement: "$1dditional$2",
                reason: App.consts.reasons.spelling
            },
            automatic: {  // https://regex101.com/r/fU2hF1/3
                expr: /\b(a)(?:uto[ma]+[tic]+|tomatic)(?!e|[io]+[nr])/gi,
                replacement: "$1utomatic",
                reason: App.consts.reasons.spelling
            },
            automatically: {  // 6K+
                expr: /\b(a)utomatic[aly]+\b/gi,
                replacement: "$1utomatically",
                reason: App.consts.reasons.spelling
            },
            running: {  // 2K+
                expr: /\b(r)un+in?g\b/gi,
                replacement: "$1unning",
                reason: App.consts.reasons.spelling
            },
            even_though: {  // 2.7K+
                expr: /\b(e)venth?ou?[gh]+\b/gi,
                replacement: "$1ven though",
                reason: App.consts.reasons.spelling
            },
            tomorrow: {  // http://www.oxforddictionaries.com/words/common-misspellings
                expr: /\b(t)om+or+ow\b/gi,
                replacement: "$1omorrow",
                reason: App.consts.reasons.spelling
            },
            truly: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/yV4rZ9/1
                expr: /\b(t)rue?l+e?y\b/gi,
                replacement: "$1ruly",
                reason: App.consts.reasons.spelling
            },
            until: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/tK8rV5/2
                expr: /\b(?:(u)nti?l+|(t)il+)\b/gi,
                replacement: function (match,f1,f2) {
                    var fchar = f1||f2;
                    return ((fchar.toUpperCase() === fchar) ? "U" : "u") + "ntil";
                },
                reason: App.consts.reasons.spelling
            },
            where: {  // Must precede "wherever"
                expr: /\b(w)her\b/gi,
                replacement: "$1here",
                reason: App.consts.reasons.spelling
            },
            wherever: {  // http://www.oxforddictionaries.com/words/common-misspellings  https://regex101.com/r/iJ4bG1/1
                expr: /\b(w)here ?ever\b/gi,
                replacement: "$1herever",
                reason: App.consts.reasons.spelling
            },
            reset: {
                expr: /\b(r)eset+ed\b/gi,
                replacement: "$1eset",
                reason: App.consts.reasons.spelling
            },
            begin: {  // https://regex101.com/r/xZ9iC3/1
                expr: /\b(b)eg+in?(ning|ner)?\b/gi,
                replacement: "$1egin$2",
                reason: App.consts.reasons.spelling
            },
            update: {  // https://regex101.com/r/rF6fZ2/1
                expr: /\b(u)[pd]+at(e|ed|er|es|ing)\b/gi,
                replacement: "$1pdat$2",
                reason: App.consts.reasons.spelling
            },
            question: {  // https://regex101.com/r/tC5yN8/2
                expr: /\b(q)[ues]+t[io]+ne?/gi,
                replacement: "$1uestion",
                reason: App.consts.reasons.spelling
            },
            variable: {  // hhttps://regex101.com/r/sI3lT5/1
                //thanks Kyll - http://chat.stackoverflow.com/transcript/message/29352137#29352137
                expr: /\b(v)[ai]+r[ia]+b[le]+(s)?\b/gi,
                replacement: "$1ariable$2",
                reason: App.consts.reasons.spelling
            },
            function_: {  // https://regex101.com/r/kJu78M/1 Old regex101 URL was to RegExp for "variable"
                //thanks Kyll - http://chat.stackoverflow.com/transcript/message/29352203#29352203
                expr: /\b(f)(?:[un]+ct[io]+n*|u[ncti]+onn?)/gi,
                replacement: "$1unction",
                reason: App.consts.reasons.spelling
            },
            being: {  // 4,600+
                expr: /\b(b)eeing\b/gi,
                replacement: "$1eing",
                reason: App.consts.reasons.spelling
            },
            happen: {  // https://regex101.com/r/jH8rE5/2
                // thanks Praveen - http://chat.stackoverflow.com/transcript/message/29427717#29427717
                expr: /\b(h)ap+e?n(e?d|s|ing)?\b/gi,
                replacement: function (match,fChar,suffix) {
                    suffix = suffix || '';
                    return fChar+"appen"+suffix.replace(/^d/,'ed');
                },
                reason: App.consts.reasons.spelling
            },
            actual: {  // https://regex101.com/r/mT1cL7/2
                expr: /\b(a)(?:c+t{0,1}[ua]+|[ct]ua)l*(ly)?\b/gi,
                replacement: "$1ctual$2",
                reason: App.consts.reasons.spelling
            },
            assign: {  // https://regex101.com/r/cM7mF2/1
                expr: /\b(a)s+i[gn]+/gi,
                replacement: "$1ssign",
                reason: App.consts.reasons.spelling
            },
            prefer_refer: {  // https://regex101.com/r/gG7bQ9/1
                expr: /\b([pr]+)ef+e?r+([ea]nc|able)/gi,
                replacement: function(match,fChar,suffix) {
                    return fChar+"efer"+suffix.replace(/anc/,"enc");
                },
                reason: App.consts.reasons.spelling
            },
            use_case: {  // 4,556 (+818 usecases)
                expr: /\b(u)se(c)ase/gi,
                replacement: "$1se $2ase",
                reason: App.consts.reasons.spelling
            },
            matches: {  //
                expr: /\b(m)atc[he]s/gi,
                replacement: "$1atches",
                reason: App.consts.reasons.spelling
            },
            specific: {  //
                expr: /\b(s)pe[cs]i?fic/gi,
                replacement: "$1pecific",
                reason: App.consts.reasons.spelling
            },
            computer: {  // https://regex101.com/r/kJ3iY8/2
                expr: /\b(c)o?m?p[ue]?t?[eoa]r(s)?\b/gi,
                replacement: "$1omputer",
                reason: App.consts.reasons.spelling
            },
            something_like: {  // Some thing like -- 6,468 posts
                expr: /\b(s)ome thing like/gi,
                replacement: "$1omething like",
                reason: App.consts.reasons.spelling
            },
            maybe_something: {  // May be something -- 4,259 posts
                expr: /\b(m)ay be some ?thing/gi,
                replacement: "$1aybe something",
                reason: App.consts.reasons.spelling
            },
            targeting: {  // 3,151 posts
                expr: /\b(t)argetting/gi,
                replacement: "$1argeting",
                reason: App.consts.reasons.spelling
            },
            column: {  // 1,363 posts
                expr: /\b(c)olou?mn?(s)?/gi,
                replacement: "$1olumn$2",
                reason: App.consts.reasons.spelling
            },
            array: {
                expr: /\b(a)(?:rry|ray)(s)?/gi,
                replacement: "$1rray$2",
                reason: App.consts.reasons.spelling
            },
            suggest: { // https://regex101.com/r/mH1fY7/1
                expr: /\b(s)ugest/gi,
                replacement: "$1uggest",
                reason: App.consts.reasons.spelling
            },
            synchronize: { // subset of https://regex101.com/r/vG6jQ8/1
                expr: /(s)[yi]nch?ron/gi,
                replacement: "$1ynchron",
                reason: App.consts.reasons.spelling
            },
            synchronous: {
                expr: /(s)ynchron[ou]+s/gi,
                replacement: "$1ynchronous",
                reason: App.consts.reasons.spelling
            },
            exception: { // https://regex101.com/r/jK4gX6/1
                expr: /\b(e)[xc]+e[pt]+ion/gi,
                replacement: "$1xception",
                reason: App.consts.reasons.spelling
            },
            information: { // https://regex101.com/r/yE3fD6/1
                expr: /\b(i)nfo[rm]+at[io]+ns?\b/gi,
                replacement: "$1nformation",
                reason: App.consts.reasons.spelling
            },
            piece: {
                expr: /\b(p)eice(s|d)?\b/gi,
                replacement: "$1iece$2",
                reason: App.consts.reasons.spelling
            },
            peaceToPiece: { // https://regex101.com/r/tZ1fY3/1
                expr: /\b(p)eace(s)?(?= of [\w -]*(?:code|cake|script|text|string|content|image|file))/gi,
                replacement: "$1iece$2",
                reason: App.consts.reasons.spelling
            },
            is_there_a: {  // 2K+ posts
                expr: /\b(i)s their a\b/gi,
                replacement: "$1s there a",
                reason: App.consts.reasons.spelling
            },
            usage: {
                expr: /\b(u)s[ea]+ge?\b/gi,
                replacement: "$1sage",
                reason: App.consts.reasons.spelling
            },
            background: {  // 1,583+ posts
                expr: /\b(b)a[ck]+ ?gr[ou]+[nd]+(s?)s*\b/gi,
                replacement: "$1ackground$2",
                reason: App.consts.reasons.spelling
            },
            preempt: {
                expr: /\b(p)r[e -]+m[pt]+/gi,
                replacement: "$1reempt",
                reason: App.consts.reasons.spelling
            },
            extension: {
                expr: /\b(e)xten[st]ion(s?)s*\b/gi,
                replacement: "$1xtension$2",
                reason: App.consts.reasons.spelling
            },
            addon: {
                expr: /\b(a)ddon(s?)s*\b/gi,
                replacement: "$1dd-on$2",
                reason: App.consts.reasons.spelling
            },
            addonsdk: {
                expr: /\b(a)ddon-?sdk\b/gi,
                replacement: "$1dd-on SDK",
                reason: App.consts.reasons.spelling
            },
            thankful: {
                expr: /\b(t)hankfull?\b/gi,
                replacement: "$1hankful",
                reason: App.consts.reasons.spelling
            },
            know: {
                expr: /\b(k)now?\b/gi,
                replacement: "$1now",
                reason: App.consts.reasons.spelling
            },
            /*
            ** Grammar - Correct common grammatical errors.
            **/
            start_with_so: {  // https://regex101.com/r/gP1xA2/2
                expr: /^(?:okay\b|ok\b|so\b|[ \t,-])+/gi,
                replacement: "",
                reason: App.consts.reasons.grammar
            },
            protect_column_a_Begin: { // Prevent "Column A" from being changed (Begin); order in App.edits Object does not matter.
                expr: /(column\s+)(An?)\b/gi,
                replacement: "$1_xPlacexHolderxColumn$2PlacexHolderx_",
                notAlone: true, // Don't run unless it's as part of another edit rule.
                reason: App.consts.reasons.silent
            },
            a_vs_an: {  // See http://stackoverflow.com/q/34440307/1677912
                expr: /\b(a|an) ([\(\"'“‘`<-]*\w*)\b/gim,   // https://regex101.com/r/nE1yA4/5
                replacement: function( match, article, following ) {
                    var input = following.replace(/^[\s\(\"'“‘`<-]+|\s+$/g, "");//strip initial punctuation symbols
                    var res = AvsAnOverride_(input) || AvsAnSimple.query(input); // eslint-disable-line no-use-before-define
                    var newArticle = article[0] + res.substr(1);  // Preserve existing capitalization
                    return newArticle+' '+following;

                    // Hack alert: Due to the technical nature of SO subjects, many common terms
                    // are not well-represented in the data used by AvsAnSimple, so we need to
                    // provide a way to override it.
                    // NOTE: AvsAnSimple is susceptible to unicode mess-up; if you suddenly see many
                    // words starting with vowels being incorrectly treated, check that the script
                    // has not had a unicode substitution error. (Git did this do me, once.)
                    function AvsAnOverride_(fword) {
                        var exceptionsA_ = /^(?:uis?)/i;
                        var exceptionsAn_ = /^(?:[lr]value|a\b|sql|ns|ng|is)/i;
                        return (exceptionsA_.test(fword) ? article[0] :
                            exceptionsAn_.test(fword) ? article[0]+"n" : false);
                    }
                },
                runBefore: ['protect_column_a_Begin'],
                runAfter: ['protect_column_a_End'],
                reason: App.consts.reasons.grammar
            },
            protect_column_a_End: { // Prevent "Column A" from being changed (End); order in App.edits Object does not matter.
                expr: /_xPlacexHolderxColumn(An?)PlacexHolderx_/g,
                replacement: "$1",
                notAlone: true, // Don't run unless it's as part of another edit rule.
                reason: App.consts.reasons.silent
            },
            firstcaps: {
                //    https://regex101.com/r/JnSYVw/1
                // Regex finds all sentences; replacement must determine whether it needs to capitalize.
                expr: /(([A-Za-z]|\d(?!\d*\. )|[.$_]\w+)(\S*))((?:(?:etc\.|i\.e\.|e\.g\.|vs\.|\.\.\.|\w*\.(?![\s")])|[*-]+|\n(?![ \t]*\n| *(?:[*-]|\d+\.))|[^.?!\n]?))+(?:([.?!]+)(?=[\s")]|$)|\n\n|\n(?= *[*-])|\n(?= *\d+\.)|$))/gi,
                replacement: function(sentence, fWord, fChar, fWordPost, sentencePost/*, endpunc*/) {
                    var capChar = fChar.toUpperCase();
                    if (sentence === "undefined" || capChar === fChar) return sentence;  // MUST match sentence, or gets counted as a change.
                    if (!fWord) fWord = '';
                    var fWordChars = fWord.split('');
                    // Leave some words alone: filenames, camelCase
                    for (var i=0; i])\2{1,}/g,
                replacement: "$1$2",
                reason: App.consts.reasons.grammar
            },
            i_want: { //https://regex101.com/r/iD2tU0/5
                expr: /\b(?:are )?(I|you|they) ?(?:['a ]*m|are)? want(?:ing|s)?\b/gi,
                replacement: "$1 want",
                rerun: ["firstcaps"],
                reason: App.consts.reasons.grammar
            },
            oxford_comma: { // https://regex101.com/r/xN0mF6/6
                expr: /((?:[\w'-]+,\s+)+(?:[\w'-]+\s){0,2}[\w'-]+)(\s+(and|or)\s+[\w'-]+)/g,
                replacement: "$1,$2",
                reason: App.consts.reasons.grammar
            },
            i_have_find: {
                expr: /\b(I|you) have find\b(?![(]|\.\w)/gi,
                replacement: "$1 have found",
                reason: App.consts.reasons.grammar
            },
            let_s_say: {  // 60K!
                expr: /\b(l)ets (say|see|look|just|put|have|leave|give|write)\b/gi,
                replacement: "$1et's $2",
                reason: App.consts.reasons.grammar
            },
            suggest_me: {  // 36K
                expr: /\b(s)u[gj]+est(s)? me/gi,
                replacement: "$1uggest$2",
                reason: App.consts.reasons.grammar
            },
            perfectly: {  // 36K
                expr: /\b(p)[re]+fectly/gi,
                replacement: "$1erfectly",
                reason: App.consts.reasons.grammar
            },
            works_perfectly: {  // 13K+ posts
                expr: /\b(w)ork(s)? p[er]+fect\b/gi,
                replacement: "$1ork$2 perfectly",
                reason: App.consts.reasons.grammar
            },
            doesnt_work: {  // 900+ posts
                expr: /\b(d)on't works/gi,
                replacement: "$1oesn't work",
                reason: App.consts.reasons.grammar
            },
            how_it_works: {  // 38,563+ posts
                expr: /\b(h)ow it works\?/gi,
                replacement: "$1ow does it work?",
                reason: App.consts.reasons.grammar
            },
            double_period: {  // https://regex101.com/r/fG6lY3/1
                expr: /([^.]|^)\.{2}(?!\.)/g,
                replacement: "$1.",
                reason: App.consts.reasons.grammar
            },
            /*
            ** "Five exclamation marks, the sure sign of an insane mind"
            **/
            pysanky: {
                expr: /([^\!])[!]{5}(?!\!)/g,
                replacement: "$1!",
                reason: window.atob('IkZpdmUgZXhjbGFtYXRpb24gbWFya3MsIHRoZSBzdXJlIHNpZ24gb2YgYW4gaW5zYW5lIG1pbmQi')
            },
            /*
            ** Noise reduction - Remove fluff that adds nothing of technical value to posts.
            **/
            help: {
                expr: /\b(h)(?:[ea]l?p)(?![-])\b/gi,
                replacement: "$1elp",
                reason: App.consts.reasons.silent
            },
            thank: {  // https://regex101.com/r/pN0sX4/2
                expr: /\b(t)(?:[hank]{2,4}|hx)(?= *(you\b))\b/gi,
                replacement: "$1hank",
                reason: App.consts.reasons.silent
            },
            thanks: {  // https://regex101.com/r/cO7gG2/2
                expr: /\b(t)(?:anks *(?=[.?!]\n|to|for|in|ever)|[han]{3}([ks]{2}|x)+|hx|anx)\b/gi,
                replacement: "$1hanks",
                reason: App.consts.reasons.silent
            },
            please: {
                expr: /\b(p)(?:lz+|lse?|l?ease?)\b/gi,
                replacement: "$1lease",
                reason: App.consts.reasons.silent
            },
            tia: {  // common acronym; should only remove "thanks in advance" at end of post
                expr: /\btia$/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            editupdate: {
                // https://regex101.com/r/tT2pK6/9
                expr: /([-_*]+[\t ]*\b(edit|update)\b([\t ]*#?[0-9]+)?[\t ]*:*[\t ]*[-_*]+:*|[\t ]*\b(edit|update)\b([\t ]*#?[0-9]+)?\s*:+[\t ]*)/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            complimentaryClose: {  // https://regex101.com/r/hL3kT5/7
                expr: /^\s*(?:(?:kind(?:est)* |best )*regards?|cheers?|greetings?|thanks|thank you|peace)\b,?(?:[^_\r\n]|_(?:[^x\r\n]|$))*(?: *[\r\n]){0,2}(?:[^_\r\n]|_(?:[^x\r\n]|$))*(?:[.!?: ]*|$)/gim,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            // http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts/93989#93989
            salutation: { // https://regex101.com/r/yS9lN8/11
                expr: /^\s*(?:dears?\b.*$|greetings?\b.*$|(?:hi(?:ya)*|hel+o+|heya?|hai|g'?day|peace[^.!]*|good\s?(?:evening|morning|day|afternoon)|ahoy|folks|guys)[,\s]*(?:\s+(?:you|all|guys|folks|friends?|there|everyone|people|matey?s?|bud+(y|ies))*))(?:[,.!?: ]*|$)/gmi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            badphrases: { // https://regex101.com/r/gE2hH6/18
                expr: /[^\n.!?:]*(?:thanks|thank[ -]you|please|help|suggest(?:ions))\b(?:[ .?!]*$|[^\n.!?:]*\b(?:help|ap+reciat\w*|me|advan\w*|a ?lot|beforehand)\b[^\n.!?:]*)[.!?_*]*(?!xPlacexHolder)/gim,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            imnew: {
                expr: /(?! )[\w\s]*\bi[' ]?a?m +(?:kinda|really) *new\w* +(?:to|in) *\w* *(?:and|[;,.!?])? */gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            sorry4english: { // https://regex101.com/r/pG3oD6/8
                expr: /[^\n.!?]*((sorry|ap+olog.*|forgive)\b[^.!?:\n\r]+\b((bad|my|poor) english)|(english[^.!?:\n\r]+)\b(tongue|language))\b[^.!?:\n\r]*(?:[.!?:_*])*/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            hope_this_helps: {  // https://regex101.com/r/yF1uY0/1
                expr: /^\s*i? ?\bhope\b[^\n.!?:]*helps?[^\n.!?:]*[,.!?: ()^-]*$/gmi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            enter_code_here: {
                expr: /\benter (?:code|image description|link description) here\b/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            i_have_a_question: {  // https://regex101.com/r/uM0nQ1/1
                expr: /^(?:I have|I've)(?: got)* a question[ \t,.?:-]*(?:about|when)?[ \t,.?:-]*/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            no_rep_to_comment: {  // https://regex101.com/r/vL2uI0/3
                expr: /(?:[^\n.!?:]*(?:rep|reputation)\b[^.!?:\n\r]+\bcomment(?:[.!?:\n\r)]+|[^.!?:\n\r]*?(?:\bbut\b|[, ]*so|[.,)]+)))/gi,
                replacement: "",
                reason: App.consts.reasons.noise
            },
            /*
            ** Layout  - Minimize whitespace (which is compressed by markup).
            **           Must follow noise reduction.
            **           Leading and trailing spaces are part of Markdown formatting; leave them.
            **/
            space_then_symbol: {  // https://regex101.com/r/fN6lL7/6
                expr: /([^ \n\r\[\)])(\((?!\)))/gm,
                replacement: "$1 $2",
                debug: false,
                reason: App.consts.reasons.layout
            },
            no_space_before_symbol: {  // https://regex101.com/r/qB9lS0/2
                expr: /(?:(^ +)|[ ]+?([,?!:)]+|[.]+(?![\S])))/gm,
                replacement: "$1$2",
                debug: false,
                reason: App.consts.reasons.layout
            },
            symbol_then_space: {  // https://regex101.com/r/iD9aS1/6
                expr: /(?:\b)([,?!:)]+|[.]{3})(?:\b)(?![\d])/gm,
                replacement: "$1 ",
                debug: false,
                reason: App.consts.reasons.layout
            },
            space_symbol_space: {
                expr: /(?:\b| +)([&])(?: |\b)(?![\d])/g,
                replacement: " $1 ",
                debug: false,
                reason: App.consts.reasons.layout
            },
            multiplespaces: { // https://regex101.com/r/hY9hQ3/3
                expr: /(?!^)[ ]{2,}(?! ?$)/gm,
                replacement: " ",
                debug: false,
                reason: App.consts.reasons.layout
            },
            numbered_list: { // https://regex101.com/r/mI1aV3/3
                expr: /([\n\r]|^)+\(?([ \t]*[\d]+)[).:-] */gm,
                replacement: "$1$1$2. ",
                reason: App.consts.reasons.layout
            },
            no_html_break: { // https://regex101.com/r/xP2oW9/4
                expr: / *< *br *\/? *> */gi,
                replacement: "  ",
                reason: App.consts.reasons.layout
            },
            // DISABLED temporarily - see Issue #115
            //blanklines: {  // https://regex101.com/r/eA5hA2/2
            //    expr: /^(?: *[\n\r\f])+|(?: *[\n\r\f])+$|((?: *[\n\r\f]){2})(?:(?: *[\n\r\f]))+/g,
            //    replacement: "$1",
            //    debug: false,
            //    reason: App.consts.reasons.layout
            //},
            //mdash and ndash
            // See: https://regex101.com/r/vnM5cO/1 for text which was tested.
            // --- is converted to —
            // -- is converted to –
            // Not having spaces is enforced around the — (For those — which are added here).
            // Spaces are enforced around the – (For those – which are added here).
            mdash: { //Must follow layout changes, due to adding an HTML tag, which would be mangled as a result of layout substitutions.
                expr: /([^-]|^)---([^-]|$)/gmi,
                replacement: "$1—$2",
                reason: App.consts.reasons.grammar,
                runAfter: [
                    'mdash_clear',
                    'mdash_clear' //Yes, twice.
                ]
            },
            mdash_clear: { //For an mdash that we've added, make it so there's no space.
                expr: /(?: *—(?! *$) +| +—)/gmi,
                //expr: /(?: +—(?! +$) +| +—|—(?! +$) +)/gmi,
                replacement: "—",
                reason: App.consts.reasons.silent,
                notAlone: true
            },
            ndash: { //Must follow layout changes, due to adding an HTML tag, which would be mangled as a result of layout substitutions.
                expr: /([^-]|^)--([^-]|$)/gmi,
                replacement: "$1–$2",
                reason: App.consts.reasons.grammar,
                runAfter: [ //These are run in the order listed.
                    'ndash_protect_start',
                    'ndash_clear_left',
                    'ndash_unprotect_start',
                    'ndash_clear_right'
                ]
            },
            ndash_protect_start: { //For an ndash that we've added at the start of the line, make it so we don't add a space
                expr: /^–/gmi,
                replacement: "&qdash;", //Tags have been substituted out, so we don't need to worry about duplication.
                reason: App.consts.reasons.silent,
                notAlone: true,
            },
            ndash_clear_left: { //For an ndash that we've added, make it so there's a space to the left.
                expr: / *–/gmi,
                replacement: " –",
                reason: App.consts.reasons.silent,
                notAlone: true,
            },
            ndash_unprotect_start: { //Reverse the protection
                expr: /&qdash;/gmi,
                replacement: "–", //Tags have been substituted out, so we don't need to worry about duplication.
                reason: App.consts.reasons.silent,
                notAlone: true,
            },
            ndash_clear_right: { //For an ndash that we've added, make it so there's a space, or whatever spaces already existed and a line-break.
                expr: /–(?! +$) */gmi,
                replacement: "– ",
                reason: App.consts.reasons.silent,
                notAlone: true
            },
            trailing_space: {  // https://regex101.com/r/iQ0yR8/1
                expr: /([^ ])[ ]{1}$/gm,
                replacement: "$1",
                debug: false,
                reason: App.consts.reasons.silent
            },
            // The title says it all
            thetitlesaysitall: {
                // https://regex101.com/r/bX1qB4/3
                expr: /(?:the )?title says (?:it all|everything)[.?!]*/gi,
                replacement: function(){
                    return App.selections.title.val().replace(/[.?!]*$/,"? \n\n");
                },
                reason: App.consts.reasons.titleSaysAll
            }
        };

        //Clear the global values which hold replacements
        App.funcs.clearPlaceHolders = function() {
            App.globals.placeHolderKeys.forEach(function(key) {
                App.globals.replacedStrings[key] = [];
                App.globals.replacedStringsOriginal[key] = [];
                App.globals.placeHolderChecks[key] = new RegExp(App.globals.placeHolders[key],'gi');
            });
        };
        App.funcs.clearPlaceHolders();

        // Check if the placeholders are the same in two pieces of text.
        App.funcs.didPlaceholdersChange = function(before, after) {
            //Currently, we only check that the number of instances for each type of placeholder is the
            //  same in both texts.
            return App.globals.placeHolderKeys.some(function(key) {
                var regEx = App.globals.placeHolderChecks[key];
                regEx.lastIndex = 0;
                var beforeMatches = before.match(regEx);
                regEx.lastIndex = 0;
                var afterMatches = after.match(regEx);
                return !((beforeMatches === null && afterMatches === null) || (beforeMatches !== null && afterMatches !== null && beforeMatches.length === afterMatches.length));
            });
        };

        // This is where the magic happens: this function takes a few pieces of information and applies edits to the post
        App.funcs.fixIt = function(input, edit, editRule) {
            var expression = edit.expr;
            var replacement = edit.replacement;
            var reasoning = edit.reason;
            var debug = edit.debug;

            if (debug) {
                console.log('editRule:', editRule);
                console.log('input:', input);
                console.log('expression.toString():', expression.toString());
                console.log("replacement: '"+replacement+"'");
            }
            // If there is nothing to search, exit
            if (!input) return false;
            // Scan the post text using the expression to see if there are any matches
            var originalInput = input;
            var matches = input.match(expression);
            if (debug) console.log('matches:', matches, ':: expression.exec(input)', expression.exec(input));
            if (!matches) return false;
            var count = 0;  // # replacements to do
            var deniedCount = 0;  // # replacements not to do
            input = input.replace(expression, function(before){
                var after = before.replace(expression, replacement);
                if(after !== before) ++count;
                //Check to see if the quantity of the place holders changed between the input and output.
                if(App.funcs.didPlaceholdersChange(before, after)) {
                    //An edit rule should never change the quantity of placeholders in the text. If it does, we prevent making the change.
                    //This will prevent individual changes where they affect an entire placeholder, but won't catch changes where the part
                    //  of a placeholder is changed. To prevent that we have to also check all the changes vs. the original, complete input.
                    console.log('PREVENTED change: edit rule:', editRule, ': Placeholders changed:  before:\n', before, '::  after:\n', after, '\n::  count:', count);
                    count--;
                    deniedCount++;
                    return before;
                }
                if (debug) console.log('before:', before, '::  after:', after, '::  after !== before:', after !== before, '::  count:', count);
                return after;
            });
            if(App.funcs.didPlaceholdersChange(originalInput, input)) {
                console.log('PREVENTED change group: edit rule:', editRule, ': Placeholders changed:  originalInput:\n', originalInput, '\n::  input:\n', input, '\n::  count:', count);
                input = originalInput;
            }
            if (!count && !deniedCount) {
                // Seems like no replacements, check.
                // In some cases, the expression matches on the initial input, but
                // fails to on the individual matches. In that case, we can't count
                // the total changes accurately, but we can still complete the
                // replacement on the initial input.
                var after = input.replace(expression, replacement);
                if(App.funcs.didPlaceholdersChange(input, after)) {
                    //An edit rule should never change the quantity of placeholders in the text. If it does, we prevent making the change.
                    console.log('PREVENTED global change: edit rule:', editRule, ': Placeholders changed:  input:\n', input, '\n::  after:\n', after, '\n::  count:', count);
                    after = input;
                }
                if (debug) console.log("zero-count: ", input, after, after !== input);
                if(after !== input) {
                    ++count;
                    input = after;
                }
            }
            return count > 0 ? {
                reason: reasoning,
                fixed: String(input),
                count: count
            } : false;
        };

        // Populate or refresh DOM selections
        App.funcs.popSelections = function() {
            App.selections.redoButton     = App.globals.root.find('[id^="wmd-redo-button"]');
            App.selections.body           = App.globals.root.find('[id^="wmd-input"]');
            App.selections.title          = App.globals.root.find('#title');
            App.selections.summary        = App.globals.root.find('[id^="edit-comment"], .edit-comment');
            App.selections.tagField       = App.globals.root.find(".tag-editor");
            App.selections.submitButton   = App.globals.root.find('[id^="submit-button"]');
            App.selections.helpButton     = App.globals.root.find('[id^="wmd-help-button"]');
            App.selections.editor         = App.globals.root.find('.post-editor');
            App.selections.preview        = App.globals.root.find('.wmd-preview');
            App.selections.previewMenu    = App.globals.root.find('.preview-options').append('  ');
            if(!App.selections.previewMenu.length) {
                App.selections.previewMenu   = $('
').insertBefore(App.selections.preview); var previewToggleText = App.selections.preview.is(':visible') ? 'hide preview' : 'show preview'; App.selections.previewToggle = $('' + previewToggleText + '').click(App.funcs.togglePreview).appendTo(App.selections.previewMenu); App.selections.previewMenu.append('  '); } else { App.selections.previewToggle = App.globals.root.find('.hide-preview').off('click').attr('href','javascript:void(0)').click(App.funcs.togglePreview); } App.selections.diffToggle = $('show diff').click(App.funcs.toggleDiff).appendTo(App.selections.previewMenu); App.selections.diff = $('
').hide().appendTo(App.selections.editor); }; App.funcs.showPreview = function() { App.selections.diff.hide(); App.selections.diffToggle.text('show diff'); App.selections.preview.show(); App.selections.previewToggle.text('hide preview'); }; App.funcs.showDiff = function() { App.selections.preview.hide(); App.selections.previewToggle.text('show preview'); App.selections.diff.show(); App.selections.diffToggle.text('hide diff'); }; App.funcs.togglePreview = function() { App.selections.diff.hide(); App.selections.diffToggle.text('show diff'); if(/hide/.test(App.selections.previewToggle.text())) return App.selections.previewToggle.text('show preview'), App.selections.preview.toggle(), false; if(/show/.test(App.selections.previewToggle.text())) return App.selections.previewToggle.text('hide preview'), App.selections.preview.toggle(), false; return false; }; App.funcs.toggleDiff = function() { App.selections.preview.hide(); App.selections.previewToggle.text('show preview'); if(/hide/.test(App.selections.diffToggle.text())) return App.selections.diffToggle.text('show diff'), App.selections.diff.toggle(), false; if(/show/.test(App.selections.diffToggle.text())) return App.selections.diffToggle.text('hide diff'), App.selections.diff.toggle(), false; }; // Populate edit item sets from DOM selections App.funcs.popItems = function() { var i = App.items, s = App.selections; ['title', 'body', 'summary'].forEach(function(v) { i[v] = s[v].length ? s[v].val() : ''; }); }; // Populate original item sets from edit items for the diff App.funcs.popOriginals = function() { var i = App.originals, s = App.items; ['title', 'body', 'summary'].forEach(function(v) { i[v] = s[v]; }); }; // Insert editing button App.funcs.createButton = function() { if (!App.selections.redoButton.length) return false; App.selections.buttonWrapper = $('
  • '); App.selections.buttonFix = $('' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + '' + ''); App.selections.buttonInfo = $('
    '); // Build the button App.selections.buttonWrapper.append(App.selections.buttonFix); App.selections.buttonWrapper.append(App.selections.buttonInfo); // Insert button App.selections.redoButton.after(App.selections.buttonWrapper); // Attach the event listener to the button App.selections.buttonFix.click(App.funcs.fixEvent); App.selections.buttonWrapper.css({ 'margin-left': '40px', 'display': 'inline-block', 'overflow': 'visible', 'white-space': 'nowrap' }); App.selections.buttonFix.css({ 'display': 'inline-block', 'background-image': 'none', }); App.selections.buttonInfo.css({ 'position': 'static', 'display': 'inline-block', 'vertical-align': 'bottom', 'margin-left': '5px', 'font-size': '12px', 'color': 'var(--white)', 'background': 'var(--black-800)', 'border-radius': '3px', 'padding': '3px 6px' }).hide(); }; App.funcs.fixEvent = function() { App.funcs.clearPlaceHolders(); return App.funcs.popItems(), App.pipe(App.items, App.pipeMods, App.globals.order), false; }; App.funcs.diff = function(a1, a2) { var strings = []; function maakRij(type, rij) { if (!type) return strings.push(rij.replace(/\' + rij.replace(/\'), true; if (type === '-') return strings.push('' + rij.replace(/\'), true; } function getDiff(matrix, b1, b2, x, y) { if (x > 0 && y > 0 && b1[y - 1] === b2[x - 1]) { getDiff(matrix, b1, b2, x - 1, y - 1); maakRij(false, b1[y - 1]); } else { if (x > 0 && (y === 0 || matrix[y][x - 1] >= matrix[y - 1][x])) { getDiff(matrix, b1, b2, x - 1, y); maakRij('+', b2[x - 1]); } else if (y > 0 && (x === 0 || matrix[y][x - 1] < matrix[y - 1][x])) { getDiff(matrix, b1, b2, x, y - 1); maakRij('-', b1[y - 1]); } } } a1 = a1.split(/(?=\b|\W|_)/g); a2 = a2.split(/(?=\b|\W|_)/g); var matrix = new Array(a1.length + 1); var x, y; for (y = 0; y < matrix.length; y++) { matrix[y] = new Array(a2.length + 1); for (x = 0; x < matrix[y].length; x++) { matrix[y][x] = 0; } } for (y = 1; y < matrix.length; y++) { for (x = 1; x < matrix[y].length; x++) { if (a1[y - 1] === a2[x - 1]) { matrix[y][x] = 1 + matrix[y - 1][x - 1]; } else { matrix[y][x] = Math.max(matrix[y - 1][x], matrix[y][x - 1]); } } } try { getDiff(matrix, a1, a2, x - 1, y - 1); return strings.join(''); } catch (e) { console.log(e); } }; // Pipe data through modules in proper order, returning the result App.pipe = function(data, mods, order) { var modName; for (var i in order) { if (order.hasOwnProperty(i)) { modName = order[i]; mods[modName](data); } } }; App.pipeMods.omit = function(data) { if (!data.body) return false; for (var type in App.globals.checks) { if (App.globals.checks.hasOwnProperty(type)) { data.body = data.body.replace(App.globals.checks[type], function(match) { // eslint-disable-line no-loop-func App.globals.replacedStrings[type].push(match); App.globals.replacedStringsOriginal[type].push(match); return App.globals.placeHolders[type]; }); } } return data; }; App.pipeMods.codefix = function() { var replaced = App.globals.replacedStrings.block; for (var i in replaced) { // https://regex101.com/r/tX9pM3/1 https://regex101.com/r/tX9pM3/2 https://regex101.com/r/tX9pM3/3 if (/^`[^]+`$/.test(replaced[i])) replaced[i] = '\n\n' + /(?!`)((?!`)[^])+/.exec(replaced[i])[0].replace(/(.+)/g, ' $1'); } }; App.pipeMods.inlineImages = function(data) { //This only attempts to substitute image links in the format [foo][n]. It doesn't do [foo](URL.png) if (!data.body) return false; var links = App.globals.replacedStrings.links.filter(function(link) { return /^\s*\[[^\[]*\]\s*\[\d+\]\s*$/.test(link); }); var linkNumbers = links.map(function(link) { return link.match(/^\s*\[[^\[]*\]\s*\[(\d+)\]\s*$/)[1]; }); //Find if matching https://i.stack.imgur.com/*.png link. var imageNumbers = linkNumbers.filter(function(link, index) { var testPng = new RegExp('^\\s*\\[' + linkNumbers[index] + '\\]:\\s*https?:\\/\\/i\\.stack\\.imgur\\.com\\/.*\\.(?:png|gif|jpg|jpeg|tif|tiff|bmp)\s*$', 'm'); return App.globals.replacedStrings.lsec.some(function(section) { return testPng.test(section); }); }); var replacements = 0; imageNumbers.forEach(function(num) { var replaceLink = new RegExp('^(\\s*)\\[([^\\[]*)\\](\\s*\\[' + num + '\\])(\\s*)$',''); App.globals.replacedStrings.links.forEach(function(link, index, array) { array[index] = link.replace(replaceLink, '$2: \n$1[![$2]$3][' + num + ']$4').replace(/^enter image description here: {2}\n/,''); if(array[index] !== link) { replacements++; } }); }); if(replacements) { if ('inlineImage' in App.globals.reasons) { App.globals.reasons.inlineImage.count += replacements; } else { App.globals.reasons.inlineImage = { reason:'inline image' + (replacements > 1 ? 's' : ''), editId:'inlineImage', count:replacements }; } } return data; }; App.pipeMods.edit = function(data) { App.funcs.popOriginals(); var defaultBgColor = App.selections.body.css("background-color"); var flashColor = colour2rgb(retrieveCSSVariable("--green-200")); // Visually confirm edit - SE makes it easy because the jQuery color animation plugin seems to be there by default App.selections.body.animate({ backgroundColor: flashColor }, 10); App.selections.body.animate({ backgroundColor: defaultBgColor }, 1000); // List of fields to be edited var fields = {body:'body',title:'title'}; function applyEditRules(ruleKeyList, notAlone) { ruleKeyList = typeof ruleKeyList === 'string' ? [ruleKeyList] : ruleKeyList; if (!Array.isArray(ruleKeyList)) { return false; } var changes = false; ruleKeyList.forEach(function(ruleKey) { changes = applyEditRule(ruleKey, notAlone) || changes; }); return changes; } function applyEditRule(ruleKey, notAlone) { const editRule = App.edits[ruleKey]; const debug = editRule.debug; var rerunChanges = false; var changes = false; if (editRule.notAlone && !notAlone) { if (debug) console.log("edit " + ruleKey + " skipped: not alone"); return false; } if (debug && editRule.runBefore) console.log("edit " + ruleKey + ": running rules before:", editRule.runBefore); var beforeChanges = applyEditRules(editRule.runBefore, true); for (var field in fields) { if (fields.hasOwnProperty(field)) { if (debug) console.log("edit " + ruleKey + " in " + field); if ((editRule.titleOnly && 'title' !== field) || (editRule.bodyOnly && 'body' !== field)) { continue; // Skip title-only edits if not editing title, or the same for bodies. } var fix = App.funcs.fixIt(data[field], editRule, ruleKey); if (!fix) continue; changes = true; //A change was made: console.log('Change by edit rule: reason:', editRule.reason, ':: ruleKey:', ruleKey, ':: editRule', editRule, ':: before:', {before: data[field]}, ':: fix:', fix); if (fix.reason in App.globals.reasons) { App.globals.reasons[fix.reason].count += fix.count; } else { App.globals.reasons[fix.reason] = { reason:fix.reason, editId:ruleKey, count:fix.count }; } data[field] = fix.fixed; editRule.fixed = true; } } if (changes && editRule.rerun) { if (debug) console.log("edit " + ruleKey + ": re-running rules:", editRule.rerun); rerunChanges = applyEditRules(editRule.rerun, true); } if (debug && editRule.runAfter) console.log("edit " + ruleKey + ": running rules After:", editRule.runAfter); var afterChanges = applyEditRules(editRule.runAfter, true); return beforeChanges || changes || rerunChanges || afterChanges; } // Loop through all editing rules applyEditRules(Object.keys(App.edits)); // Remove silent change reason delete App.globals.reasons[App.consts.reasons.silent]; // If there are no reasons, exit if (App.globals.reasons == {}) return false; // We need a place to store the reasons being applied to the summary. var reasons = []; App.globals.changes = 0; for (var z in App.globals.reasons) { if (App.globals.reasons.hasOwnProperty(z)) { // For each type of change made, add a reason string with the reason text, // optionally the rule ID, and the number of repeats if 2 or more. reasons.push(App.globals.reasons[z].reason + (App.globals.showRules ? ' ['+ App.globals.reasons[z].editId +']' : '') + (App.globals.showCounts ? ((App.globals.reasons[z].count > 1) ? ' ('+App.globals.reasons[z].count+')' : '') : '') ); App.globals.changes += App.globals.reasons[z].count; } } var reasonStr = reasons.length ? reasons.join('; ')+'.' : ''; // Unique reasons separated by ; and terminated by . if (!data.hasOwnProperty('summaryOrig')) { // Remember original summary data.summaryOrig = data.summary.trim().replace(/([^;])[.?!:]?$/,"$1;"); } if (data.summaryOrig.length) { data.summaryOrig += ' '; } else { reasonStr = reasonStr.charAt(0).toUpperCase() + reasonStr.slice(1); // Cap first letter. } data.summary = data.summaryOrig + reasonStr; // Limit summary to 300 chars if (data.summary.length > 300) { data.summary = data.summary.substr(0,300-3) + '...'; } return data; }; // Populate the diff App.pipeMods.diff = function() { App.selections.diff.empty().append('
    ' + App.funcs.diff(App.originals.title, App.items.title, true) + '
    ' + '
    ' + App.pipeMods.replace({body:App.funcs.diff(App.originals.body, App.items.body)}, true).body + '
    '); App.funcs.showDiff(); }; // Replace the previously omitted code App.pipeMods.replace = function(data, literal) { if (!data.body) return false; for (var type in App.globals.checksr) { if (App.globals.checksr.hasOwnProperty(type)) { var i = 0; data.body = data.body.replace(App.globals.placeHolderChecks[type], function() { // eslint-disable-line no-loop-func var replace = App.globals.replacedStrings[type][i++]; if(literal && /block|lsec/.test(type)) { var after = replace.replace(/^\n\n/,''); var prepend = after !== replace ? '\n\n`' : ''; var append = after !== replace ? '`' : ''; var klass = /lsec/.test(type) ? ' class="lang-none prettyprint prettyprinted"' : ''; return prepend + '' + after.replace(/' + append; } if(literal && /quote/.test(type)) return '
    ' + replace.replace(//gm,'') + '
    '; if(literal) return '' + replace.replace(/'; return replace; }); } } return data; }; // Handle pipe output App.pipeMods.output = function(data) { App.selections.title.val(data.title); App.selections.body.val(data.body.replace(/\n{3,}/,'\n\n')); App.selections.summary.val(data.summary); App.globals.root.find('.actual-edit-overlay').remove(); App.selections.summary.css({opacity:1}); App.selections.buttonInfo.text(App.globals.changes).show(); StackExchange.MarkdownEditor.refreshAllPreviews(); }; // Init app App.init = function() { var count = 0; var toolbarchk = setInterval(function(){ if(++count === 10) clearInterval(toolbarchk); if(!App.globals.root.find('.wmd-button-row').length) return; clearInterval(toolbarchk); App.funcs.popSelections(); App.funcs.createButton(); }, 100); return App; }; return App.init(); } try { StackExchange.using('inlineEditing', function() { StackExchange.ready(function() { var test = window.location.href.match(/.posts.(\d+).edit/); if(test) { extendEditor($('form[action^="/posts/' + test[1] + '"]')); } $('#post-form').each(function(){ extendEditor($(this)); }); }); }); $(document).ajaxComplete(function() { var test = arguments[2].url.match(/posts.(\d+).edit-inline/); if(!test) { test = arguments[2].url.match(/review.inline-edit-post/); if(!test) return; test = arguments[2].data.match(/id=(\d+)/); if(!test) return; } StackExchange.ready(function() { extendEditor($('form[action^="/posts/' + test[1] + '"]')); }); }); // This is the styling for the diff output. $('body').append(''); } catch (e) { console.log(e); } /* eslint-disable */ /* * To Title Case 2.1 – http://individed.com/code/to-title-case/ * Copyright © 2008–2013 David Gouch. Licensed under the MIT License. * It has been modified to be a function call, rather than added to the String prototype. */ //This is function call, rather than a method on the String prototype, because a userscript, unless it's intended // purpose is to make such a basic change, shouldn't be making a change to the prototype of a built-in type. // Changing the prototype of a built-in has a significant chance of causing compatibility issues. function toTitleCase(text){ var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\.?|via)$/i; return text.replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, function(match, index, title){ if (index > 0 && index + match.length !== title.length && match.search(smallWords) > -1 && title.charAt(index - 2) !== ":" && (title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-') && title.charAt(index - 1).search(/[^\s-]/) < 0) { return match.toLowerCase(); } if (match.substr(1).search(/[A-Z]|\../) > -1) { return match; } return match.charAt(0).toUpperCase() + match.substr(1); }); }; // From https://github.com/EamonNerbonne/a-vs-an var AvsAnSimple=function(n){function i(n){var r=parseInt(t,36)||0,f=r&&r.toString(36).length,u,e;for(n.article=t[f]=="."?"a":"an",t=t.substr(1+f),u=0;u=0);for(;;){if(u=i.article||u,i=i[r],!i)return u;r=t[f++]||" "}}}}({}) // Adapted from http://stackoverflow.com/a/6969486/1677912 function escapeTag(tag) { // See https://regex101.com/r/yW9cD4/1 var retag = tag.replace(/(?:(\-)|([+.#]))/g, function (match, hyphen, other) { var escaped = (hyphen) ? "[ \\-]" : "\\"+match; return escaped; }); return "(?:\\s|\\b|$)" + retag + "(?:\\s|\\b|$)"; // hack - enclose tag in regexp boundary checks. WBN to do this in the taglist regexp. } /** * Pass a CSS variable to get its value * @param {string} val - for example "--black" or "--green-600" */ function retrieveCSSVariable(val) { return getComputedStyle(document.body) .getPropertyValue(val); } /** * Converts an arbitrary colour representation to an RGB string representation. Invalid colours might return "rgb(0,0,0)". The conversioon is done by * offloading the interpreting the string to a canvas - common strings like hex or HSL would be supported but perhapos not all named colours would be. * * Based on the code by Aaron Watters: https://stackoverflow.com/a/52044517 * * @param {string} string - any colour representation, for example: "salmon", "#FA8072", "#fa8072", hsl(6,93%,71%), hsl(6, 93%, 71%) * @returns {string} - String of the format: "rgb(250,126,113)". Invalid input would produce black "rgb(0,0,0)" */ function colour2rgb(string) { var canvas = document.createElement("canvas"); //make 1x1 px rectangle in the arbitrary colour var context = canvas.getContext("2d"); context.beginPath(); context.rect(0,0,1,1); context.fillStyle = string; context.fill(); //extract the three primary colours and omit the alpha channel information var rgbData = context.getImageData(0, 0, 1, 1).data.slice(0, 3); return "rgb(" + rgbData.join(",") + ")"; } // Better handling of indentation and the TAB key when editing posts // From balpha's stackexchange-tab-editing // (c) 2012 Benjamin Dumke-von der Ehe // Which is released under the MIT License - https://opensource.org/licenses/MIT // See http://stackapps.com/questions/3247/better-handling-of-indentation-and-the-tab-key-when-editing-posts // Current version: 2.0.0, from https://github.com/mogsdad/UserScripts/blob/master/tab-editing.user.js function with_jquery(t){var e=document.createElement("script");e.type="text/javascript",e.textContent="("+t.toString()+")(jQuery)",document.body.appendChild(e)}with_jquery(function(t){t(function(){if(window.StackExchange&&StackExchange.ready){var e=4,n=" ".repeat(e),r="selectionDirection"in t("