繁体   English   中英

Javascript RegExp + 单词边界 + unicode 个字符

[英]Javascript RegExp + Word boundaries + unicode characters

我正在构建搜索,我将使用 javascript 自动完成。 我来自芬兰(芬兰语),所以我必须处理一些特殊字符,如 ä、ö 和 å

当用户在搜索输入字段中键入文本时,我尝试将文本与数据匹配。

这是一个简单的例子,如果用户输入例如“ää”,它就不能正常工作。 与“äl”相同

var title = "this is simple string with finnish word tämä on ääkköstesti älkää ihmetelkö";
// Does not work
var searchterm = "äl";

// does not work
//var searchterm = "ää";

// Works
//var searchterm = "wi";

if ( new RegExp("\\b"+searchterm, "gi").test(title) ) {
    $("#result").html("Match: ("+searchterm+"): "+title);
} else {
    $("#result").html("nothing found with term: "+searchterm);   
}

http://jsfiddle.net/7TsxB/

那么如何让这些 ä、ö 和 å 字符与 javascript 正则表达式一起使用呢?

我想我应该使用 unicode 代码,但我该怎么做呢? 这些字符的代码是:[Ä,ä,Å,å,Ö,ö]

=> äÄåÅöÖ

Regex 似乎存在问题,并且单词边界\b与字符串的开头匹配且起始字符超出了正常的 256 字节范围。

而不是使用\b ,尝试使用(?:^|\\s)

var title = "this is simple string with finnish word tämä on ääkköstesti älkää ihmetelkö";
// Does not work
var searchterm = "äl";

// does not work
//var searchterm = "ää";

// Works
//var searchterm = "wi";

if ( new RegExp("(?:^|\\s)"+searchterm, "gi").test(title) ) {
    $("#result").html("Match: ("+searchterm+"): "+title);
} else {
    $("#result").html("nothing found with term: "+searchterm);   
}

分解:

(?:括号()在正则表达式中形成一个捕获组。括号以问号和冒号开头?:形成一个非捕获组。它们只是将术语组合在一起

^插入符号匹配字符串的开头

| 条是“或”运算符。

\s匹配空格(在字符串中显示为\\s ,因为我们必须转义反斜杠)

)关闭组

因此,我们不使用匹配单词边界且不适用于 unicode 字符的\b ,而是使用匹配字符串开头或空格的非捕获组。

JavaScript RegEx 中的\b字符类实际上只对简单的 ASCII 编码有用。 \b\w\W集合或\w和字符串的开头或结尾之间的边界的快捷代码。 这些字符集只考虑 ASCII “单词”字符,其中\w等于[a-zA-Z0-9_]并且\W是该类的否定。

这使得 RegEx 字符类在处理任何真实语言时基本上没有用处。

\s应该适用于您想要做的事情,前提是搜索词仅由空格分隔。

这个问题很老,但我想我找到了一个更好的解决方案,用于使用 unicode 字母的正则表达式中的边界。 使用 XRegExp 库,您可以实现一个有效的 \b 边界扩展它

XRegExp('(?=^|$|[^\\p{L}])')

结果是 4000+ 字符长,但它似乎工作得很好。

一些解释: (?= ) 是一个零长度的前瞻,它查找开始或结束边界或非字母 unicode 字符。 最重要的想法是前瞻,因为 \b 不捕获任何东西:它只是对或错。

\b是字母和非字母字符之间转换的快捷方式,反之亦然。

更新和改进max_massti的答案:

随着 ES2018 中 RegEx 的/u修饰符的引入,您现在可以使用\p{L}来表示任何 unicode 字母,而\P{L} (注意大写的P )来表示除此之外的任何内容。

编辑:以前的版本不完整。

像这样:

const text = 'A Fé, o Império, e as terras viciosas';

text.split(/(?<=\p{L})(?=\P{L})|(?<=\P{L})(?=\p{L})/);

// ['A', ' Fé', ',', ' o', ' Império', ',', ' e', ' as', ' terras', ' viciosas']

我们使用lookbehind (?<=...)来查找字母和lookahead (?=...)来查找非字母,反之亦然。

当您必须使用 Unicode 中的特定字符集时,我建议您使用XRegExp ,该库的作者映射了所有类型的区域字符集,从而使使用不同语言的工作更容易。

["

在使用 Unicode 时,我注意到\b确实有些奇怪:

/\bo/.test("pop"); // false (obviously)
/\bä/.test("päp"); // true (what..?)

/\Bo/.test("pop"); // true
/\Bä/.test("päp"); // false (what..?)

似乎\b\B的含义是相反的,但仅在与非 ASCII Unicode 一起使用时? 这里可能有更深层次的东西,但我不确定它是什么。

无论如何,似乎单词边界是问题,而不是 Unicode 字符本身。 也许您应该将\b替换为(^|[\s\\/-_&]) ,因为这似乎可以正常工作。 (不过,让你的符号列表比我的更全面。)

我的想法是使用代表芬兰字母的代码进行搜索

new RegExp("\\b"+asciiOnly(searchterm), "gi").test(asciiOnly(title))

我最初的想法是使用普通的encodeURI但 % 符号似乎会干扰正则表达式。

http://jsfiddle.net/7TsxB/5/

我使用 encodeURI 编写了一个粗略的函数来编码超过 128 的代码的每个字符,但删除它的 % 并在开头添加“QQ”。 它不是最好的标记,但我无法让非字母数字工作。

您正在寻找的是 Unicode 字边界标准:

http://unicode.org/reports/tr29/tr29-9.html#Word_Boundaries

这里有一个 JavaScript 实现(unciodejs.wordbreak.js)

https://github.com/wikimedia/unicodejs

试图找到文本“myTest”:

/(?<?[\p{L}\p{N}_])myTest(?![\p{L}\p{N}_])/gu

类似NetBeans或Notepad++形式。 试图在表达式前后的任何 unicode 个字母和数字字符中找到没有任何字母或数字或下划线(如单词边界的\w字符\b )的表达式。

我遇到了类似的问题,但我不得不替换一系列术语。 如果两个术语在文本中彼此相邻(因为它们的边界重叠),我发现的所有解决方案都不起作用。 所以我不得不使用一些修改过的方法:

var text = "Ještě. že; \"už\" à. Fürs, 'anlässlich' že že že.";
var terms = ["à","anlässlich","Fürs","už","Ještě", "že"];
var replaced = [];
var order = 0;
for (i = 0; i < terms.length; i++) {
    terms[i] = "(^\|[ \n\r\t.,;'\"\+!?-])(" + terms[i] + ")([ \n\r\t.,;'\"\+!?-]+\|$)";
}
var re = new RegExp(terms.join("|"), "");
while (true) {
    var replacedString = "";
    text = text.replace(re, function replacer(match){
        var beginning = match.match("^[ \n\r\t.,;'\"\+!?-]+");
        if (beginning == null) beginning = "";
        var ending = match.match("[ \n\r\t.,;'\"\+!?-]+$");
        if (ending == null) ending = "";
        replacedString = match.replace(beginning,"");
        replacedString = replacedString.replace(ending,"");
        replaced.push(replacedString);
        return beginning+"{{"+order+"}}"+ending;
    });
if (replacedString == "") break;
order += 1;
}

请参阅小提琴中的代码:http: //jsfiddle.net/antoninslejska/bvbLpdos/1/

正则表达式的灵感来自: http ://breakthebit.org/post/3446894238/word-boundaries-in-javascripts-regular

我不能说,我觉得解决方案很优雅......

该问题的正确答案由andrefs给出。 在将所有必需的东西放在一起之后,我只会更清楚地重写它。

对于 ASCII 文本,您可以使用\b来匹配模式开头和结尾的单词边界。 使用 Unicode 文本时,您需要使用 2 种不同的模式来执行相同的操作:

  • 使用(?<=^|\P{L})匹配主要模式之前的开始或单词边界。
  • 使用(?=\P{L}|$)匹配主模式之后的结尾或单词边界。
  • 此外,在所有内容的开头使用(?i)以使所有这些匹配不区分大小写。

所以得到的答案是: (?i)(?<=^|\P{L})xxx(?=\P{L}|$) ,其中 xxx 是您的主要模式。 这相当于 ASCII 文本的(?i)\bxxx\b

为了使您的代码正常工作,您现在需要执行以下操作:

  • 将要查找的模式或单词分配给您的变量“searchterm”。
  • 转义变量的内容。 例如,将'\'替换为'\\'并对正则表达式的任何保留特殊字符执行相同操作,例如'\^', '\$', '\/'等。查看此处了解有关如何操作的问题去做这个。
  • 只需使用string.replace()方法,将变量的内容插入到上面的模式中,代替“xxx”。

我有一个类似的问题,我试图用不同的 unicode 词替换所有特定的 unicode 词,但我不能使用lookbehind,因为 JS 引擎不支持此代码。我最终像这样解决了它:

const needle = "КАРТОПЛЯ";
const replace = "БАРАБОЛЯ";
const regex = new RegExp(
  String.raw`(^|[^\n\p{L}])`
    + needle
    + String.raw`(?=$|\P{L})`,
   "gimu",
);

const result = (
    'КАРТОПЛЯ сдффКАРТОПЛЯдадф КАРТОПЛЯ КАРТОПЛЯ КАРТОПЛЯ??? !!!КАРТОПЛЯ ;!;!КАРТОПЛЯ/#?#?'
    + '\n\nКАРТОПЛЯ КАРТОПЛЯ - - -КАРТОПЛЯ--'
  )
    .replace(regex, function (match, ...args) {
      return args[0] + replace;
    });
console.log(result)

输出:

БАРАБОЛЯ сдффКАРТОПЛЯдадф БАРАБОЛЯ БАРАБОЛЯ БАРАБОЛЯ??? !!!БАРАБОЛЯ ;!;!БАРАБОЛЯ/#?#?

БАРАБОЛЯ БАРАБОЛЯ - - -БАРАБОЛЯ--

拆开它

第一个正则表达式: (^|[^\n\p{L}])

  • ^| = 行首或
  • [^\n\p{L}] = 任何不是字母或换行符的字符

第二个正则表达式: (?=$|\P{L})

  • ?= = 前瞻
  • $| = 行尾或
  • \P{L} = 任何不是字母的字符

第一个正则表达式捕获该组,然后通过args[0]在替换期间将其放回字符串中,从而避免向后查找。 第二个正则表达式使用了前瞻。

请注意,第二个必须是前瞻,因为如果我们捕获它,则不会触发重叠的正则表达式匹配(例如КАРТОПЛЯ КАРТОПЛЯ КАРТОПЛЯ只会匹配第一个和第三个)。

不好但有效:

var text = " аб аб АБ абвг ";
var ttt = "(аб)"
var p = "(^|$|[^A-Za-zА-Я-а-я0-9()])"; // add other word boundary symbols here
var exp = new RegExp(p+ttt+p,"gi");
text = text.replace(exp, "$1($2)$3").replace(exp, "$1($2)$3");
const t1 = performance.now();
console.log(text);

结果(没有引号):

" (аб) (аб) (АБ) абвг "  

我为此苦苦挣扎。 使用法语重音字符,我设法找到了这个解决方案:

const myString = "MyString";
const regex = new RegExp(
    "(?:[^À-ú]|^)\\b(" + myString + ")\\b(?:[^À-ú]|$)",
    "ig"
);

id 的作用:它在“MyString”前后用\b不断检查单词边界。 除此之外, (?:[^À-ú]|^)(?:[^À-ú]|$)将检查 MyString 是否未被任何重音字符包围

它不适用于西里尔字母,但可能会找到西里尔字符的范围并因此编辑[^À-ú]

警告,它只捕获组(MyString) ,但总匹配包含前一个和下一个字符

参见示例: https://regex101.com/r/5P0ZIe/1

匹配示例:

  • MyString
    • 匹配:“MyString”
    • 第 1 组:“MyString”
  • Lorem ipsum. MyString dolor sit amet
    • 匹配:“MyString”
    • 第 1 组:“MyString”
  • (MyString)
    • 匹配:“(我的字符串)”
    • 第 1 组:“MyString”
  • BetweenCharactersMyStringIsNotFound
    • 匹配:无
    • 第 1 组:无
  • éMyStringé
    • 匹配:无
    • 第 1 组:无
  • ùMyString
    • 匹配:无
    • 第 1 组:无
  • MyStringÖ
    • 匹配:无
    • 第 1 组:无

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM