Correct substring position after replacement

前端 未结 1 555
南方客
南方客 2021-01-26 13:39

I have a function like this that\'s provided by a user:

function replace_function(string) {
    return string.replace(/:smile:/g, \'⻇\')
      .replace(/(foo|bar         


        
相关标签:
1条回答
  • 2021-01-26 14:07

    To do this, you'll have to do the replace operation yourself with a RegExp#exec loop, and keep track of how the replacements affect the position, something along these lines (but this can probably be optimized):

    function trackingReplace(rex, string, replacement, position) {
        var newString = "";
        var match;
        var index = 0;
        var repString;
        var newPosition = position;
        var start;
        rex.lastIndex = 0; // Just to be sure
        while (match = rex.exec(string)) {
            // Add any of the original string we just skipped
            if (rex.global) {
                start = rex.lastIndex - match[0].length;
            } else {
                start = match.index;
                rex.lastIndex = start + match[0].length;
            }
            if (index < start) {
                newString += string.substring(index, start);
            }
            index = rex.lastIndex;
            // Build the replacement string. This just handles $$ and $n,
            // you may want to add handling for $`, $', and $&.
            repString = replacement.replace(/\$(\$|\d)/g, function(m, c0) {
                if (c0 == "$") return "$";
                return match[c0];
            });
            // Add on the replacement
            newString += repString;
            // If the position is affected...
            if (start < position) {
                // ... update it:
                if (rex.lastIndex < position) {
                    // It's after the replacement, move it
                    newPosition = Math.max(0, newPosition + repString.length - match[0].length);
                } else {
                    // It's *in* the replacement, put it just after
                    newPosition += repString.length - (position - start);
                }
            }
    
            // If the regular expression doesn't have the g flag, break here so
            // we do just one replacement (and so we don't have an endless loop!)
            if (!rex.global) {
                break;
            }
        }
        // Add on any trailing text in the string
        if (index < string.length) {
            newString += string.substring(index);
        }
        // Return the string and the updated position
        return [newString, newPosition];
    }
    

    Here's a snippet showing us testing that with various positions:

    function trackingReplace(rex, string, replacement, position) {
        var newString = "";
        var match;
        var index = 0;
        var repString;
        var newPosition = position;
        var start;
        rex.lastIndex = 0; // Just to be sure
        while (match = rex.exec(string)) {
            // Add any of the original string we just skipped
            if (rex.global) {
                start = rex.lastIndex - match[0].length;
            } else {
                start = match.index;
                rex.lastIndex = start + match[0].length;
            }
            if (index < start) {
                newString += string.substring(index, start);
            }
            index = rex.lastIndex;
            // Build the replacement string. This just handles $$ and $n,
            // you may want to add handling for $`, $', and $&.
            repString = replacement.replace(/\$(\$|\d)/g, function(m, c0) {
                if (c0 == "$") return "$";
                return match[c0];
            });
            // Add on the replacement
            newString += repString;
            // If the position is affected...
            if (start < position) {
                // ... update it:
                if (rex.lastIndex < position) {
                    // It's after the replacement, move it
                    newPosition = Math.max(0, newPosition + repString.length - match[0].length);
                } else {
                    // It's *in* the replacement, put it just after
                    newPosition += repString.length - (position - start);
                }
            }
    
            // If the regular expression doesn't have the g flag, break here so
            // we do just one replacement (and so we don't have an endless loop!)
            if (!rex.global) {
                break;
            }
        }
        // Add on any trailing text in the string
        if (index < string.length) {
            newString += string.substring(index);
        }
        // Return the string and the updated position
        return [newString, newPosition];
    }
    
    function show(str, pos) {
        console.log(str.substring(0, pos) + "|" + str.substring(pos));
    }
    function test(rex, str, replacement, pos) {
        show(str, pos);
        var result = trackingReplace(rex, str, replacement, pos);
        show(result[0], result[1]);
    }
    for (var n = 3; n < 22; ++n) {
        if (n > 3) {
           console.log("----");
        }
        test(/([f])([o])o/g, "test foo result foo x", "...$2...", n);
    }
    .as-console-wrapper {
      max-height: 100% !important;
    }

    And here's your snippet updated to use it:

    function trackingReplace(rex, string, replacement, position) {
        var newString = "";
        var match;
        var index = 0;
        var repString;
        var newPosition = position;
        var start;
        rex.lastIndex = 0; // Just to be sure
        while (match = rex.exec(string)) {
            // Add any of the original string we just skipped
            if (rex.global) {
                start = rex.lastIndex - match[0].length;
            } else {
                start = match.index;
                rex.lastIndex = start + match[0].length;
            }
            if (index < start) {
                newString += string.substring(index, start);
            }
            index = rex.lastIndex;
            // Build the replacement string. This just handles $$ and $n,
            // you may want to add handling for $`, $', and $&.
            repString = replacement.replace(/\$(\$|\d)/g, function(m, c0) {
                if (c0 == "$") return "$";
                return match[c0];
            });
            // Add on the replacement
            newString += repString;
            // If the position is affected...
            if (start < position) {
                // ... update it:
                if (rex.lastIndex < position) {
                    // It's after the replacement, move it
                    newPosition = Math.max(0, newPosition + repString.length - match[0].length);
                } else {
                    // It's *in* the replacement, put it just after
                    newPosition += repString.length - (position - start);
                }
            }
    
            // If the regular expression doesn't have the g flag, break here so
            // we do just one replacement (and so we don't have an endless loop!)
            if (!rex.global) {
                break;
            }
        }
        // Add on any trailing text in the string
        if (index < string.length) {
            newString += string.substring(index);
        }
        // Return the string and the updated position
        return [newString, newPosition];
    }
    
    function replace_function(string, position) {
        var result = trackingReplace(/:smile:/g, string, '⻇', position);
        result = trackingReplace(/(foo|bar|baz)/g, result[0], 'text_$1', result[1]);
        return result;
    }
    
    var textarea = document.querySelector('textarea');
    var pre = document.querySelector('pre');
    function split() {
      var position = textarea.selectionStart;
      var result = replace_function(textarea.value, position);
      var string = result[0];
      position = result[1];
      var split = [
        string.substring(0, position),
        string.substring(position)
      ];
      pre.innerHTML = JSON.stringify(split);
    }
    textarea.addEventListener('click', split);
    <textarea>:smile: foo</textarea>
    <pre></pre>

    0 讨论(0)
提交回复
热议问题