Regex - проверьте, есть ли у входа все шансы стать совпадающими
У нас есть такое регулярное выражение:
var regexp = /^one (two)+ three/;
Таким образом, будет соответствовать только строка типа "one two three"
или "one two three four"
или "one twotwo three"
и т.д.
Однако, если у нас есть строка типа
"one "
- все еще "многообещающий", что, возможно, скоро он будет соответствовать
но эта строка:
"one three"
никогда не будет соответствовать независимо от того, что мы будем делать.
Есть ли способ проверить, есть ли у данной строки шансы стать подходящими или нет?
Мне нужно это для некоторых советов во время написания, когда я хочу порекомендовать все варианты, начинающиеся с данного ввода (регулярное выражение, которое я использую, довольно длинное, и я не хочу, чтобы с ними было действительно бесполезно).
Другими словами - я хочу проверить, закончилась ли строка во время проверки, и ничего не соответствовало.
В еще более других словах - ответ был бы из-за несоответствия. Если причина - конец строки - тогда это будет обещание. Однако я не знаю, как проверить, почему строка не соответствует
Ответы
Ответ 1
Это функция регулярных выражений, известная как частичное сопоставление, она доступна в нескольких механизмах регулярных выражений, таких как PCRE, Boost, Java, но не в JavaScript.
Обильный ответ показывает очень хороший способ преодолеть это ограничение, нам просто нужно автоматизировать это.
Ну... вызов принят :)
К счастью, JavaScript имеет очень ограниченный набор функций регулярных выражений с простым синтаксисом, поэтому я написал простой синтаксический анализатор и преобразование на лету для этой задачи на основе функций, перечисленных в MDN.
Пара интересных мест:
- Это создает регулярное выражение, которое почти всегда будет соответствовать пустой строке. Поэтому неудачное частичное совпадение происходит, когда результат
exec
равен null
или массиву, первый элемент которого является пустой строкой - Отрицательные взгляды сохраняются как есть. Я считаю, что правильно делать. Единственный способ потерпеть неудачу в сопоставлении - это использовать их (то есть вставить
(?!)
В регулярное выражение) и привязки (^
и $
). - Парсер предполагает допустимый шаблон ввода: вы не можете сначала создать объект
RegExp
из недопустимого шаблона. - Этот код не будет правильно обрабатывать
^(\w+)\s+\1$
: ^(\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();
}());
Ответ 2
Еще один интересный вариант, который я использовал ранее, - это 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;
Ответ 3
Ваш первоначальный вопрос - это просто тестирование размещения строк в другом, в частности, начало. Самый быстрый способ сделать это - использовать 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
строк соответствия и ввода. (Если интересно, здесь jsperf из substr
, indexOf
и RegExp
тестирования для начала строки без учета регистра: http://jsperf.com/substr-vs-indexof-vs-regex)
Ответ 4
Это действительно интересный вопрос.
Лично я сделаю это с помощью конструктора 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
}
Очевидно, что вы можете обработать случай "точного соответствия", чтобы избежать цепочки. Если весь шаблон соответствует входу, тогда вы все хорошо.
EDIT: Я только что понял, что это не сработает для групп и всего. Он будет сбой из-за неправильных выражений, но я надеюсь, что это может послужить основой для вашего запроса.
Ответ 5
РЕДАКТИРОВАТЬ: после того, как вы отредактировали свой пост, уточните, этот ответ может не иметь отношения к вашему конкретному вопросу. Оставив его здесь для справки.
Внутри самого регулярного выражения на предмет сбоя быстро, как только вы узнаете, что ничего не найдете:
Якорь в вашем регулярном выражении означает, что после того, как двигатель регулярных выражений достигнет h
three
, он перестанет искать совпадения и не попытается начать совпадение со вторым символом. В этом случае я не думаю, что вы можете сделать лучше, чем это (но это уже линейная сложность, так что не так уж плохо).
В других случаях я считаю, что у вас есть некоторые обычные лучшие практики, чтобы учиться, чтобы как можно быстрее потерпеть неудачу, когда вы знаете, что матч больше не может быть найден.
Вы можете посмотреть притяжательные квантификаторы, если вы еще не знаете их, но есть много других трюков...
Ответ 6
Здесь простое доказательство понятия в 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 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]()
Ответ 7
Основываясь на @Отзывном ответе, я сделал свой собственный, немного более продвинутый и, похоже, работает.
Я сделал метод, который изменяет регулярное выражение, позволяя ему принимать многообещающие ответы.
Он заменяет любой литерал char на "(?:OLD_LETTER|$)"
, поэтому k
становится (?:k|$)
ищет соответствующую букву или конец ввода.
Он также ищет части, которые нельзя заменить как {1,2}, и оставить их такими, какие они есть.
Я уверен, что он не завершен, но его очень легко добавить новые правила проверки и основного трюка с помощью any_sign or end of input
, похоже, работают в любом случае как конец совпадения строк и не продолжаются, поэтому в основном нам нужно сделать такую модификацию к основному регулярному выражению, что любой литерал char или группа символов будет иметь альтернативный |$
, и каждый синтаксис (который иногда кажется буквальным символом тоже) не может быть уничтожен.
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);
}
Тест в jsFiddle
Ответ 8
Все еще не 100% уверены в том, что вы просите, но вы также можете вложить их в следующее:
var regexp = /^(one)+((\s+two)+((\s+three)+((\s+four)+)?)?)?$/;
Матчи:
- one
- один два два
- один два два три
- один два два три три четыре
Не соответствует:
Ответ 9
Я нашел пакет npm с JavaScript-реализацией RegEx с добавочным сопоставлением регулярных выражений: https://www.npmjs.com/package/incr-regex-package. Похоже, стоит проверить. Он может сообщать результаты DONE
, MORE
, MAYBE
и FAILED
для данного входа.
Здесь также приведен пример реализации компонента ввода для React: https://www.npmjs.com/package/react-maskedinput. Он использует {RXInputMask} from incr-regex-package
для обеспечения более ориентированного на пользовательский ввод представления о взаимодействии с библиотекой RegEx.
Ответ 10
Не уверен, есть ли способ сделать это с помощью регулярного выражения, не создавая чрезвычайно сложный шаблон. Но если вы просто проверяете строку, вы можете сделать что-то вроде этого:
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;
}
Ответ 11
Я думаю, что автоматическое "Применить к каждому шаблону" не существует. Однако вы можете вручную вывести "необязательный" шаблон из вашего регулярного выражения и проверить оба шаблона:
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()
, который преобразует шаблон регулярных выражений в такую необязательную деривацию. (Будет трудной задачей охватить ВСЕ возможные шаблоны. Не знаю, если вообще возможно.)