正则表达式 - 检查输入是否仍有机会匹配

时间:2014-03-18 15:03:14

标签: javascript regex

我们有这样的正则表达式:

var regexp = /^one (two)+ three/;

因此,只有"one two three""one two three four""one twotwo three"等字符串才能匹配。

但是,如果我们有像

这样的字符串

"one " - 仍然'承诺' 可能很快会匹配

但是这个字符串: 无论我们做什么,"one three"永远不会匹配。

有没有办法检查给定字符串是否有机会匹配?

我需要在写作期间提供一些提示,因为我想推荐所有以给定输入开头的选项(正则表达式&#39;我使用的时间很长,而且我真的不想弄乱它们)。< / p>


换句话说 - 我想在检查过程中检查字符串是否已经结束,并且没有“匹配”#39;面对。

更多其他的话 - 答案将在不匹配的内部。如果原因是字符串结束 - 那么它就会失败。但是我不知道有什么方法可以检查为什么有些字符串不匹配

11 个答案:

答案 0 :(得分:16)

这是一个名为部分匹配的正则表达式功能,它可以在几个正则表达式引擎中使用,例如PCRE,Boost,Java,但在JavaScript中

Andacious's answer显示了克服此限制的一种非常好的方法,我们只需要自动执行此操作。

嗯......接受挑战:)

幸运的是,JavaScript的正则表达式功能集非常有限,语法简单,因此我根据功能listed on MDN为此任务编写了一个简单的解析器和动态转换。

有几点兴趣:

  • 这会生成一个几乎总是匹配空字符串的正则表达式。因此,当exec的结果为null或第一个元素为空字符串的数组时,会发生失败的部分匹配
  • 负面前瞻保持原样。我认为这是正确的做法。失败匹配的唯一方法是通过它们(即在正则表达式中放置(?!))和锚点(^$)。
  • 解析器采用有效的输入模式:您无法首先从无效模式创建RegExp对象。
  • 此代码无法正确处理反向引用:例如,^(\w+)\s+\1$不会与hello hel产生部分匹配

RegExp.prototype.toPartialMatchRegex = function() {
    "use strict";
    
    var re = this,
        source = this.source,
        i = 0;
    
    function process () {
        var result = "",
            tmp;

        function appendRaw(nbChars) {
            result += source.substr(i, nbChars);
            i += nbChars;
        };
        
        function appendOptional(nbChars) {
            result += "(?:" + source.substr(i, nbChars) + "|$)";
            i += nbChars;
        };

        while (i < source.length) {
            switch (source[i])
            {
                case "\\":
                    switch (source[i + 1])
                    {
                        case "c":
                            appendOptional(3);
                            break;
                            
                        case "x":
                            appendOptional(4);
                            break;
                            
                        case "u":
                            if (re.unicode) {
                                if (source[i + 2] === "{") {
                                    appendOptional(source.indexOf("}", i) - i + 1);
                                } else {
                                    appendOptional(6);
                                }
                            } else {
                                appendOptional(2);
                            }
                            break;
                            
                        default:
                            appendOptional(2);
                            break;
                    }
                    break;
                    
                case "[":
                    tmp = /\[(?:\\.|.)*?\]/g;
                    tmp.lastIndex = i;
                    tmp = tmp.exec(source);
                    appendOptional(tmp[0].length);
                    break;
                    
                case "|":
                case "^":
                case "$":
                case "*":
                case "+":
                case "?":
                    appendRaw(1);
                    break;
                    
                case "{":
                    tmp = /\{\d+,?\d*\}/g;
                    tmp.lastIndex = i;
                    tmp = tmp.exec(source);
                    if (tmp) {
                        appendRaw(tmp[0].length);
                    } else {
                        appendOptional(1);
                    }
                    break;
                    
                case "(":
                    if (source[i + 1] == "?") {
                        switch (source[i + 2])
                        {
                            case ":":
                                result += "(?:";
                                i += 3;
                                result += process() + "|$)";
                                break;
                                
                            case "=":
                                result += "(?=";
                                i += 3;
                                result += process() + ")";
                                break;
                                
                            case "!":
                                tmp = i;
                                i += 3;
                                process();
                                result += source.substr(tmp, i - tmp);
                                break;
                        }
                    } else {
                        appendRaw(1);
                        result += process() + "|$)";
                    }
                    break;
                    
                case ")":
                    ++i;
                    return result;
                    
                default:
                    appendOptional(1);
                    break;
            }
        }
        
        return result;
    }
    
    return new RegExp(process(), this.flags);
};






// Test code
(function() {
    document.write('<span style="display: inline-block; width: 60px;">Regex: </span><input id="re" value="^one (two)+ three"/><br><span style="display: inline-block; width: 60px;">Input: </span><input id="txt" value="one twotw"/><br><pre id="result"></pre>');
    document.close();

    var run = function() {
        var output = document.getElementById("result");
        try
        {
            var regex = new RegExp(document.getElementById("re").value);
            var input = document.getElementById("txt").value;
            var partialMatchRegex = regex.toPartialMatchRegex();
            var result = partialMatchRegex.exec(input);
            var matchType = regex.exec(input) ? "Full match" : result && result[0] ? "Partial match" : "No match";
            output.innerText = partialMatchRegex + "\n\n" + matchType + "\n" + JSON.stringify(result);
        }
        catch (e)
        {
            output.innerText = e;
        }
    };

    document.getElementById("re").addEventListener("input", run);
    document.getElementById("txt").addEventListener("input", run);
    run();
}());

我测试了一下它似乎工作正常,如果你发现任何错误,请告诉我。

答案 1 :(得分:10)

我之前使用过的另一个有趣的选项是OR使用$符号预期每个字符。这可能不适用于所有情况,但对于您正在查看特定字符并需要对每个字符进行部分匹配的情况,这可行。

例如(在Javascript中):

var reg = /^(o|$)(n|$)(e|$)(\s|$)$/;

reg.test('')      -> true;
reg.test('o')     -> true;
reg.test('on')    -> true;
reg.test('one')   -> true;
reg.test('one ')  -> true;
reg.test('one t') -> false;
reg.test('x')     -> false;
reg.test('n')     -> false;
reg.test('e')     -> false;
reg.test(' ')     -> false;

虽然这不是最漂亮的正则表达式,但它是可重复的,所以如果你因某种原因需要动态生成它,你就会知道一般模式。

同样的模式也可以应用于整个单词,这可能不是有用的,因为他们无法逐个输入以获得这些点。

var reg = /^(one|$)(\stwo|$)$/;

reg.test('')        -> true;
reg.test('one')     -> true;
reg.test('one ')    -> false;
reg.test('one two') -> true;

答案 2 :(得分:1)

您的原始问题只是测试字符串在另一个字符串中的位置,特别是开头。最快的方法是在匹配字符串上使用substr,然后使用indexOf。我已更新原始答案以反映:

function check(str){
  return 'one two three'.substr(0, str.length) === str;
};
console.log(check('one'));           // true
console.log(check('one two'));       // true
console.log(check('one two three')); // true
console.log(check('one three'));     // false

如果您需要不区分大小写,那么只需toLowerCase匹配&amp;输入字符串。 (如果有兴趣,请substrindexOfRegExp对字符串的开头进行测试,不区分大小写:http://jsperf.com/substr-vs-indexof-vs-regex)< / em>的

答案 3 :(得分:0)

这实际上是一个非常有趣的问题。

就个人而言,我会使用RegExp构造函数,因此查询可以分解:

var input = "one ";
var query = "^one two three";
var q_len = query.length;
var match;
for( var i=1; i<=q_len; i++) {
    match = input.match(new RegExp(query.substr(0,i));
    if( !match) {alert("Pattern does NOT match"); break;}
    if( match[0].length == input.length) {alert("Input is promising!"); break;}
    // else continue to the next iteration
}

显然你可以预先处理“完全匹配”的情况,以避免整个循环的事情。如果整个模式与输入匹配,那么你们都很好。

编辑:我刚刚意识到这不适合团体和东西。它会因格式错误的正则表达式而失败,但我希望它可以作为查询的基础。

答案 4 :(得分:0)

编辑:在您对帖子进行编辑澄清后,此答案可能与您的具体问题无关。留在这里作为参考。


在正则表达式本身内部,一旦你知道你找不到任何东西就会迅速失败:

正则表达式中的锚点意味着一旦正则表达式引擎到达h three,它将停止查找匹配项,而不是尝试在第二个字符上开始匹配。在这种情况下,我认为你不能做得更好(但它已经是线性复杂性,所以不是那么糟糕)。

在其他情况下,我相信你有一些常用的最佳实践,以便在你知道找不到比赛时尽快失败。

如果您还不了解它们,可以查看possessive quantifiers,但还有很多其他技巧......

答案 5 :(得分:0)

这是jsFiddle中的简单概念证明。你基本上只是反过来循环建议的正则表达式并寻找最长的子匹配。

注意:这有一个已知问题,因为它并不总能很好地处理组。例如,它会说foo bar b根本不匹配foo( bar)+,当它应该说还有希望时。这可以通过使用以下行获得更多创意来解决:

temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex

基本上,您需要解析部分正则表达式以查看它是否以组结尾,然后递归检查针对该结束组的部分匹配。这是相当复杂的,所以我没有为了我的演示而进入它。

JavaScript(仅用于演示目的使用jQuery):

var strings = {
    matches: "Matches!",
    stillhope: "There's still hope...",
    mismatch: "Does not match",
    empty: "No text yet"
};

// Object to handle watching for partial matches
function PartialRegexMonitor(regex, input_id) {
    var self = this;
    this.relen = regex.length;
    $('body').on('keyup', '#'+input_id, function() {
         self.update_test_results(regex, input_id);
    });
}

PartialRegexMonitor.prototype.update_test_results = function(regex, input_id) {
    var input = $('#'+input_id).val(),
        matches = find_partial_matches(regex, input),
        span = $('#'+input_id).siblings('.results');
    span.removeClass('match');
    span.removeClass('stillhope');
    span.removeClass('mismatch');
    span.removeClass('empty');
    span.addClass(matches.type)
        .html(strings[matches.type]);
}

// Test a partial regex against a string
function partial_match_tester(regex_part, str) {
    var matched = false;
    try {
        var re = new RegExp(regex_part, 'g'),
            matches = str.match(re),
            match_count = matches.length,
            temp_re;
        for(var i = 0; i < match_count; i++) {
            temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex
            matched = temp_re.test(str);
            if(matched) break;
        }
    }
    catch(e) {
    }
    return matched;
}

// Find the longest matching partial regex
function find_partial_matches(regex, str) {
    var relen = regex.length,
        matched = false,
        matches = {type: 'mismatch',
                   len: 0},
        regex_part = '';
    if(str.length == 0) {
        matches.type = 'empty';
        return matches;
    }

    for(var i=relen; i>=1; i--) {
        if(i==1 && str[0] == '^') { // don't allow matching against only '^'
            continue;
        }
        regex_part = regex.substr(0,i);
        // replace {\d}$ with {0,\d} for testing
        regex_part = regex_part.replace(/\{(\d)\}$/g, '{0,$1}');

        matched = partial_match_tester(regex_part, str);
        if(matched) {
            matches.type = (i==relen ? 'matches' : 'stillhope');
            console.log(matches.type + ": "+regex.substr(0,i)+" "+str);
            matches.len = i;
            break;
        }
    }
    return matches;
}

// Demo
$(function() {
    new PartialRegexMonitor('foo bar', 'input_0');
    new PartialRegexMonitor('^foo bar$', 'input_1');
    new PartialRegexMonitor('^fo+(\\s*b\\S[rz])+$', 'input_2');
    new PartialRegexMonitor('^\\d{3}-\\d{3}-\\d{4}$', 'input_3');
});

演示的HTML:

<p>
Test against <span class="regex">foo bar</span>:<br/>
    <input type="text" id="input_0" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^foo bar$</span>:<br/>
    <input type="text" id="input_1" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^fo+(\s*b\S[rz])+$</span> (e.g., "foo bar", "foo baz", "foo bar baz"):<br/>
    <input type="text" id="input_2" />
    <span class="results empty">No text yet</span>
</p>
<p>
    Test against <span class="regex">^\d{3}-\d{3}-\d{4}$</span>:<br/>
    <input type="text" id="input_3" />
    <span class="results empty">No text yet</span>
</p>

演示的CSS

.empty {
    background-color: #eeeeee;
}
.matches {
    background-color: #ccffcc;
    font-weight: bold;
}
.stillhope {
    background-color: #ccffff;
}
.mismatch {
    background-color: #ffcccc;
    font-weight: bold;
}
.regex {
    border-top:1px solid #999;
    border-bottom:1px solid #999;
    font-family: Courier New, monospace;
    background-color: #eee;
    color: #666;
}

演示中的示例截图

enter image description here

答案 6 :(得分:0)

基于@Andacious的答案,我已经制作了自己的,更高级的,似乎有效。

我制作了修改正则表达式的方法,使其接受有希望的答案。

它用"(?:OLD_LETTER|$)"替换任何文字字符,以便k变为(?:k|$)寻找匹配的字母或输入的结尾。

它还会查找不像{1,2}那样替换的部分,并将它们保留原样。

我确定它不完整,但很容易添加新的检查规则和使用any_sign or end of input的主要技巧似乎在任何情况下都可以作为字符串匹配结束并且不会继续所以基本上我们需要对主正则表达式进行这样的修改,任何文字字符或字符组都有替代|$,并且每个语法(有时似乎也有文字字符)都不能被销毁。 / p>

RegExp.prototype.promising = function(){
    var source = this.source;
    var regexps = {
        replaceCandidates : /(\{[\d,]\})|(\[[\w-]+\])|((?:\\\w))|([\w\s-])/g,   //parts of regexp that may be replaced
        dontReplace : /\{[\d,]\}/g,     //dont replace those
    }

    source =  source.replace(regexps.replaceCandidates, function(n){ 
        if ( regexps.dontReplace.test(n) ) return n;
        return "(?:" + n + "|$)";
    });


    source = source.replace(/\s/g, function(s){
        return "(?:" + s + "|$)";
    });

    return new RegExp(source);

}

测试on jsFiddle

答案 7 :(得分:0)

仍然不是100%肯定你要求的,但你也可以嵌套它们,如下:

var regexp = /^(one)+((\s+two)+((\s+three)+((\s+four)+)?)?)?$/;

匹配

  • 一个
  • 一两二
  • 一二二三
  • 一二二三三四

不匹配:

  • 一三

答案 8 :(得分:0)

我找到了一个npm包,该包带有RegEx的JavaScript实现,且正则表达式匹配为https://www.npmjs.com/package/incr-regex-package。看起来值得一试。它可以报告给定输入的DONEMOREMAYBEFAILED结果。

这里还有一个用于React的输入组件的示例实现:https://www.npmjs.com/package/react-maskedinput。它使用{RXInputMask} from incr-regex-package来提供与RegEx库进行交互的更多用户输入视图。

答案 9 :(得分:-1)

不确定是否有办法使用正则表达式执行此操作而不会产生极其复杂的模式。但是如果你只是检查字符串,那么你可以这样做:

function matchPartialFromBeginning(pattern, value) {
    if(pattern.length == value.length)
        return pattern == value;
    if(pattern.length > value.length)
        return pattern.substr(0, value.length) == value;
    return false;
}

答案 10 :(得分:-1)

我认为没有自动“适用于每个模式”-Solution。但是,您可以手动从正则表达式中派生出“可选”模式,并检查这两种模式:

var fullPattern = /^one (two)+ three/;
var optPattern = /^o?n?e? ?(t?w?o?)+ ?t?h?r?e?e?$/;

//verbal
if (fullPattern.matches()){
   //matched
} else {
  if (optPattern.matches()){
     //looks promising
  }else{
     //no match.
  }
}

您还可以实现自己的optionalizePattern()方法,将Regex Pattern转换为此类可选派生。 (覆盖所有可能的模式将是一项艰巨的任务。如果可能的话,不要使用Dunno。)