带有变音符号的 Unicode 字符串,按字符分割

Posted

技术标签:

【中文标题】带有变音符号的 Unicode 字符串,按字符分割【英文标题】:Unicode string with diacritics split by chars 【发布时间】:2012-06-01 07:14:24 【问题描述】:

我有这个 Unicode 字符串:Ааа́Ббб́Ввв́Г㥴Дд

我想用字符分割它。 现在,如果我尝试循环所有字符,我会得到这样的结果:A a a ' Б ...

有没有办法将此字符串正确拆分为字符:А а а́

【问题讨论】:

你如何循环遍历字符 @Nivas 并不重要,"а́"javascript 的角度来看是 2 个字符。 "а" + "́" === "а́" @Esailija 没关系。无论出于何种原因,我认为这是一个 Java 问题。没有阅读标签(也没有标题)... @Nivas 自从 ES6 出来后,你的迭代方式实际上产生了很大的不同,因为 for..of 使用 String.prototype[Symbol.iterator],它在代码点步骤中迭代(有时超过一个字符长),同时索引使用括号不会。 【参考方案1】:

如果您正在编写一个需要使用来自 Node.js stream 的数据块的应用程序,那么您可能只需通过 utf8-stream 管道来防止这种情况发生:

https://github.com/substack/utf8-stream

【讨论】:

【参考方案2】:

对此有一点更新。

随着 ES6 的到来,出现了新的字符串方法和处理字符串的方法。 有两个问题的解决方案。

1) 表情符号和代理对

表情符号和其他位于基本多语言平面 (BMP) 之上的 Unicode 字符(0x0000 - 0xFFFF 范围内的 Unicode“代码点”)可以作为 ES6 adhere to the iterator protocol 中的字符串计算出来,所以你可以这样做:

let textWithEmoji = '\ud83d\udc0e\ud83d\udc71\u2764'; //horse, happy face and heart
[...textWithEmoji].length //3
for (char of textWithEmoji)  console.log(char)  //will log 3 chars

2) 变音符号

当您开始使用“字素簇”(一个字符和它的变音符号)时,这是一个更难解决的问题。在 ES6 中,有一种方法可以简化它的工作,但它仍然很难工作。 String.prototype.normalize 方法简化了工作,但正如Mathias Bynens 所说:

(A) 应用了多个组合标记的代码点始终会产生单个视觉字形,但可能没有规范化的形式,在这种情况下规范化无济于事。

可以在此处找到更多见解:

https://ponyfoo.com/articles/es6-strings-and-unicode-in-depth https://mathiasbynens.be/notes/javascript-unicode

【讨论】:

这是自 ES6 出来以来最好的答案。可以提及Array.from,为了完整起见,它也使用字符串迭代器。 现在我看到这并不是 OP 所要求的,但非常适合将我带到这里的问题(代理对)。问题标题需要改进。 分割表情符号的绝佳答案。 "??❤".length 是 5,但使用扩展运算符 [..."??❤"].length 是 3,太棒了。【参考方案3】:

此软件包可能会帮助您: https://www.npmjs.com/package/runes

const runes = require('runes')

const example = 'Emoji ?'
example.split('') // ["E", "m", "o", "j", "i", " ", "�", "�"] 
runes(example)    // ["E", "m", "o", "j", "i", " ", "?"] 

【讨论】:

【参考方案4】:

要正确执行此操作,您需要的是用于计算字素簇边界的算法,如UAX 29 中所定义。不幸的是,这需要从 Unicode 字符数据库中了解哪些字符是哪些类的成员,而 JavaScript 不提供该信息(*)。因此,您必须在脚本中包含 UCD 的副本,这会使其非常庞大。

如果您只需要担心拉丁语或西里尔语使用的基本重音,另一种选择是仅使用组合变音符号块 (U+0300-U+036F)。这对于其他语言和符号可能会失败,但对于您想要做的事情可能就足够了。

function findGraphemesNotVeryWell(s) 
    var re= /.[\u0300-\u036F]*/g;
    var match, matches= [];
    while (match= re.exec(s))
        matches.push(match[0]);
    return matches;


findGraphemesNotVeryWell('Ааа́Ббб́Ввв́Г㥴Дд');
["А", "а", "а́", "Б", "б", "б́", "В", "в", "в́", "Г", "г", "Ґ", "ґ", "Д", "д"]

(*: 可能有一种方法可以通过让浏览器渲染字符串并测量其中选择的位置来提取信息......但这肯定会非常混乱和困难让跨浏览器工作。)

【讨论】:

【参考方案5】:

您的字符串的问题是代理对(“a”“́)仅在浏览器显示时才组合为符号字符。对于您的情况,如果将 \u0301 附加到前一个字符就足够了,但这是通过不是一般的解决方案。

var a="Ааа́Ббб́Ввв́Г㥴Дд",
    i =0,
    chars=[];

while(a.charAt(i)) 
  if (a.charAt(i+1) == "\u0301") 
    chars.push(a.charAt(i++)+a.charAt(i++));
   else 
    chars.push(a.charAt(i++));

要澄清问题,请阅读Mathias Bynens's blog post。

【讨论】:

您的代码存在严重缺陷——除了有一个错误,a.fromCharCode(i),真的吗? -- 它不做合成,所以你回到第 1 格... 感谢警告。已更正。 charCodeAt(index) 在 UTF-16 代码单元方面不工作吗?所以这对 BMP 之外的任何东西都不起作用。 问题是如何将 unicode 字符串拆分为单个 unicode 字符的数组,而代码就是这样做的。检查chars 数组。 代理对与组合字符完全不同。代理是在 UTF-16 中,两个连续的 16 位值组合成一个 32 位代码点。组合字符是完整的代码点,它与以前的基本代码点组合形成一个用户感知的字符,称为“字素簇”。

以上是关于带有变音符号的 Unicode 字符串,按字符分割的主要内容,如果未能解决你的问题,请参考以下文章

带有德语变音符号的 NSJSONSerialization 异常

MongoDB diacriticInSensitive 搜索未按预期显示所有重音(带有变音符号的单词)行,反之亦然

字符編碼

带有变音符号的 xcodebuild 目标名称

将单词与特殊字符(é、è、...)进行比较时忽略变音符号

带有德语变音符号的 iOS 上的 FacebookDisplayName