Как запретить слова с диакритикой, используя массив черного списка и регулярное выражение?

У меня есть ввод текста типа, где я возвращаю true или false в зависимости от списка запрещенных слов. Все работает нормально. Моя проблема в том, что я не знаю, как проверять слова с диакритикой из массива:

var bannedWords = ["bad", "mad", "testing", "băţ"];
var regex = new RegExp('\\b' + bannedWords.join("\\b|\\b") + '\\b', 'i');

$(function () {
  $("input").on("change", function () {
    var valid = !regex.test(this.value);
    alert(valid);
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type='text' name='word_to_check'>

Ответ 1

Комментарий Chiu прав: 'aaáaa'.match(/\b.+?\b/g) имеет довольно контр-интуитивно понятный [ "aa", "á", "aa" ], потому что "символ слова" (\w) в регулярных выражениях JavaScript просто сокращенное обозначение [A-Za-z0-9_] ( "регистр-нечувствительность-альфа-цифра-и-подчеркивание" ), поэтому граница слова (\b) соответствует любому месту между фрагментом альфа-чисел и любым другим символом, Это делает извлечение "слов Юникода" довольно сложным.

Для систем написания не unicase можно идентифицировать "символ слова" по его двойной природе: ch.toUpperCase() != ch.toLowerCase(), поэтому ваш измененный фрагмент может выглядеть так: это:

var bannedWords = ["bad", "mad", "testing", "băţ", "bať"];
var bannedWordsRegex = new RegExp('-' + bannedWords.join("-|-") + '-', 'i');

$(function() {
  $("input").on("input", function() {
    var invalid = bannedWordsRegex.test(dashPaddedWords(this.value));
    $('#log').html(invalid ? 'bad' : 'good');
  });
  $("input").trigger("input").focus();

  function dashPaddedWords(str) {
    return '-' + str.replace(/./g, wordCharOrDash) + '-';
  };

  function wordCharOrDash(ch) {
    return isWordChar(ch) ? ch : '-'
  };

  function isWordChar(ch) {
    return ch.toUpperCase() != ch.toLowerCase();
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type='text' name='word_to_check' value="ba">
<p id="log"></p>

Ответ 2

Вам нужна граница слов в кодировке Unicode. Самый простой способ - использовать XRegExp.

Хотя его \b по-прежнему основан на ASCII, есть \p{L} (или более короткий pL version), которая соответствует любой букве Юникода из плоскости BMP. Для создания пользовательской границы слова с помощью этого принципа легко:

\b                     word            \b
  ---------------------------------------
 |                       |               |
([^\pL0-9_]|^)         word       (?=[^\pL0-9_]|$)

Ведущая граница слова может быть представлена ​​группой (не) захвата ([^\pL0-9_]|^), которая соответствует (и потребляет) либо символ, отличный от буквы Unicode от плоскости BMP, цифру и _, либо начало string перед word.

Конечная граница слова может быть представлена ​​с положительным представлением (?=[^\pL0-9_]|$), для которого требуется символ, отличный от буквы Юникода, из плоскости BMP, цифры и _ или конца строки после word.

См. ниже фрагмент, который будет определять băţ как запрещенное слово и băţy как допустимое слово.

var bannedWords = ["bad", "mad", "testing", "băţ"];
var regex = new XRegExp('(?:^|[^\\pL0-9_])(?:' + bannedWords.join("|") + ')(?=$|[^\\pL0-9_])', 'i');

$(function () {
  $("input").on("change", function () {
    var valid = !regex.test(this.value);
    //alert(valid);
    console.log("The word is", valid ? "allowed" : "banned");
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xregexp/3.1.1/xregexp-all.min.js"></script>
<input type='text' name='word_to_check'>

Ответ 3

Посмотрим, что происходит:

alert("băţ".match(/\w\b/));

Это [ "b" ], потому что граница слова \b не распознает символы слов за пределами ASCII. JavaScript "словарные символы" строго [0-9A-Z_a-z], поэтому , и соответствуют \w\b\W, поскольку они содержат символ слова, границу слова и символ неслов.

Я думаю, что лучшее, что вы можете сделать, это что-то вроде этого:

var bound = '[^\\w\u00c0-\u02c1\u037f-\u0587\u1e00-\u1ffe]';
var regex = new RegExp('(?:^|' + bound + ')(?:'
                       + bannedWords.join('|')
                       + ')(?=' + bound + '|$)', 'i');

где bound - это обратный список всех символов слова ASCII плюс большинство букв латинского алфавита, используемых с маркерами начала и конца строки для приближения интернационализированного \b. (Второй из них - zero-width просмотр, который лучше подражает \b и поэтому хорошо работает с флагом g regex.)

Учитывая ["bad", "mad", "testing", "băţ"], это становится:

/(?:^|[^\w\u00c0-\u02c1\u037f-\u0587\u1e00-\u1ffe])(?:bad|mad|testing|băţ)(?=[^\w\u00c0-\u02c1\u037f-\u0587\u1e00-\u1ffe]|$)/i

Это не нужно ничего, как ….join('\\b|\\b')…, потому что вокруг списка есть круглые скобки (и это создаст такие вещи, как \b(?:hey\b|\byou)\b, который сродни \bhey\b\b|\b\byou\b, включая бессмысленный \b\b), который JavaScript интерпретирует как просто \b).

Вы также можете использовать var bound = '[\\s!-/:[email protected][-`{-~]' для более простого списка допустимых символов, отличных от слов, ASCII. Будьте осторожны в этом заказе! Черточки указывают диапазоны между символами.

Ответ 4

Вместо использования границы слова вы можете сделать это с помощью

(?:[^\w\u0080-\u02af]+|^)

чтобы проверить начало слова, и

(?=[^\w\u0080-\u02af]|$)

чтобы проверить его конец.

[^\w\u0080-\u02af] соответствует любым символам не (^) основным символам латинского слова - \w - или Unicode 1_Supplement, Extended-A, Extended-B и расширениям. Это включает в себя некоторые знаки препинания, но будет очень длинным, чтобы соответствовать буквам. Он также может быть расширен, если необходимо включить другие наборы символов. См. например, Wikipedia.

Поскольку javascript не поддерживает look-behinds, тест начала слова потребляет любые ранее не-словарные символы, но я не думаю, что это должно быть проблемой. Важно то, что тест конца слова не работает.

Кроме того, сдача этих тестов за пределами группы, не содержащей захвата, которая чередует слова, делает ее значительно более эффективной.

var bannedWords = ["bad", "mad", "testing", "băţ", "båt", "süß"],
    regex = new RegExp('(?:[^\\w\\u00c0-\\u02af]+|^)(?:' + bannedWords.join("|") + ')(?=[^\\w\\u00c0-\\u02af]|$)', 'i');

function myFunction() {
    document.getElementById('result').innerHTML = 'Banned = ' + regex.test(document.getElementById('word_to_check').value);
}
<!DOCTYPE html>
<html>
<body>

Enter word: <input type='text' id='word_to_check'>
<button onclick='myFunction()'>Test</button>

<p id='result'></p>

</body>
</html>

Ответ 5

При работе с символами вне моего базового набора (который может отображаться в любое время), я конвертирую их в соответствующий базовый эквивалент (8 бит, 16 бит, 32 бит). перед запуском любого символа, соответствующего им.

var bannedWords = ["bad", "mad", "testing", "băţ"];
var bannedWordsBits = {};
bannedWords.forEach(function(word){
  bannedWordsBits[word] = "";
  for (var i = 0; i < word.length; i++){
    bannedWordsBits[word] += word.charCodeAt(i).toString(16) + "-";
  }
});
var bannedWordsJoin = []
var keys = Object.keys(bannedWordsBits);
keys.forEach(function(key){
  bannedWordsJoin.push(bannedWordsBits[key]);
});
var regex = new RegExp(bannedWordsJoin.join("|"), 'i');

function checkword(word) {
  var wordBits = "";
  for (var i = 0; i < word.length; i++){
    wordBits += word.charCodeAt(i).toString(16) + "-";
  }
  return !regex.test(wordBits);
};

Разделитель "-" должен убедиться, что уникальные символы не сливаются вместе, создавая нежелательные соответствия.

Очень полезно, поскольку он приводит все символы в общую базу, с которой все может взаимодействовать. И это может быть повторно закодировано обратно к оригиналу без отправки его в пару ключ/значение.

Для меня лучше всего в том, что мне не нужно знать все правила для всех наборов символов, с которыми я мог бы пересекаться, потому что я могу вывести их всех в обычное игровое поле.

В качестве примечания:

Чтобы ускорить процесс, вместо того, чтобы передавать большой оператор регулярного выражения, который у вас есть, который занимает экспоненциально длиннее, чтобы пройти с длиной слов, которые вы запрещаете, я передавал каждое отдельное слово в предложении через фильтр, И разбить фильтр на сегменты, основанные на длине. подобные;

  • checkword3Chars();
  • checkword4Chars();
  • checkword5chars();

функции которого вы можете генерировать систематически и даже создавать "на лету", когда и когда они становятся необходимыми.