如何在JavaScript中获取字母的重音/变音符号?

9
我想在JavaScript中获取字母的重音符号/变音符号。
例如:
• ñ -> ~
• á -> ´
• è -> `
我尝试使用.normalize("NFD")方法,但它没有返回正确的重音符号/变音符号。
string = "á"
string.normalize("NFD").split("")
// ['a', '́']
string.normalize("NFD").split("").includes("´") 
// false
'́' === "´"
// false

我希望NFD或其他函数可以给出重音符号/变音符号,而不是组合的重音符号/变音符号。


这只适用于拉丁字母还是所有语言都需要? - Mohsen Alyafei
@MohsenAlyafei 所有编程语言都是可取的。 - Angel
这很困难,除非我们使用字符编码映射。没有其他方法。 - Mohsen Alyafei
包括阿拉伯文或希伯来文或印度语等语言将会是过度的。 - Mohsen Alyafei
拉丁语中只有很少的变音符号,为什么不像@Socko在他的回答中建议的那样自己进行组合-> ASCII映射呢?你不必将所有带重音的字母都映射出来,只需要映射变音符即可。 - the Hutt
显示剩余5条评论
3个回答

8
短答案是因为“COMBINING TILDE != TILDE”。
以下是涉及到“ñ”(例如)的每个Unicode字符的分解:
each of the Unicode characters”。
符号 代码 代码点 名称
ñ \u00F1 241 带波浪号的小写拉丁字母n
n \u006E 110 小写拉丁字母n
̃ \u0303 771 波形符号
~ \u007E 126 波浪号
为了将变音符号与其附加字符分离出来,您可以使用 string.normalize"NFD"。它提供了“规范分解”,将单个字形分解为不同的字符组合,从而得到相同的符号。
共有112种不同的组合变音符号。我找不到原生的方法来转换组合字符和它的独立对应体。您可以寻找一个库或为您想要处理的标记编写映射。
const combiningMarks = {
  771: 126, // tilde
  769: 180, // acute accent
  768: 96,  // grave accent
}

然后将其分解为单个字符并查找每个组合字符的相关标记,如下所示:

const combiningMarks = {
  771: 126, // tilde
  769: 180, // acute accent
  768: 96,  // grave accent
}

const startingString = "ñáè" // "\u00F1\u00E1\u00E8"
const decomposedString = startingString.normalize("NFD") // "\u006E\u0303\u0061\u0301\u0065\u0300"
const codepoints = [...decomposedString].map(c => c.codePointAt(0)) // [110, 771, 97, 769, 101, 768]
const charsWithFullMarks = codepoints.map(c => combiningMarks[c] || c) // [110, 126, 97, 180, 101, 96]
const finalString = String.fromCodePoint(...charsWithFullMarks) // "n~a´e`"
console.log(finalString);


3

您获取一个字母的重音/变音符号的方法 是正确的,需要使用 string.normalize("NFD").split("")

normalize("NFD")返回正确的结果,在这种情况下是 组合重音符号 Unicode 十进制码 &#769。

然而,您正在比较从 normalize("NFD") 得到的带有组合重音符号(字符代码769)的字母 á普通重音符号(字符代码180)。当然,这是两个不同的字母。

这同样适用于字母è,它拥有组合重音符号(字符代码768);而您正在将其与我们使用键盘输入的普通重音符号(字符代码96)进行比较;它们是两个不同的字母。

独立的(普通)字母(包括重音符号的字母)将始终是单独的字母,即使它们在字符串中出现在其他字母之前或之后。然而,字母的组合形式(具有不同的字符代码以区分它们)将位于相邻的下一个或上一个字母的上方或下方。这类似于阿拉伯语重音符号和其他语言如希伯来语。

以下是一些波浪符字母的比较:

enter image description here

请参见以下示例:

console.log("á".normalize("NFD").split("")[1].charCodeAt()); // 769 code for Combining Acute Accent
console.log("´".charCodeAt());                               // 180 code for Normal Acute Accent

console.log("è".normalize("NFD").split("")[1].charCodeAt()); // 768 Combining Grave Accent
console.log("`".charCodeAt());                               // 96 Normal Grave Accent

console.log("á".normalize("NFD").split("")[1].charCodeAt()); // 769 code for Combining Acute Accent
console.log("´".charCodeAt());                               // 180 code for Normal Acute Accent
    
console.log("è".normalize("NFD").split("")[1].charCodeAt()); // 768 Combining Grave Accent
console.log("`".charCodeAt());                               // 96 Normal Grave Accent


我理解,但我想用自动化的解决方案提取带有重音符号的字母的正常发音/变音符号。 - Angel
换句话说,您希望NFD为您提供输出;例如,波浪符号“~”,而不是组合的波浪符号。 - Mohsen Alyafei
是的,我认为我的问题已经足够清晰了,但我将添加更多细节。 - Angel
以下这些字母:Ẽẽ ḛḚṌṍṎṏṼṽẪẫỸỹṹṺÑÃ 将会被转换成波浪符号 ~,对吗? - Mohsen Alyafei
你的问题中只提供了3个例子,请更新问题并提供更多的例子,例如输入和期望输出15或20个字符。谢谢。 - Mohsen Alyafei

3
规范分解(NFD)有效:

let accent = '\u0301';
console.log(`Accute accent: ${accent}`);
let out = 'á'.normalize('NFD').split('').includes(accent);
console.log(out);

扩展示例:您不必维护包含所有字符的映射。在演示中,地图仅用于获取变音符的描述。

const dc = getDiacritics();
let inputString = 'n\u0303  \u00F1áèâäåçč السّلَامُ عَلِيْكُمُ';
inputString = inputString.normalize('NFD');

inpt.innerText = inputString;

out.innerHTML = getWholeChars(inputString).map(c => {
  let chars = c.split('');
  return `${c} -> <span>${chars[1] + '\u25cc'}</span> ${dc[chars[1]]}\n`;
}).join('');

// get characters with diacritics
function getWholeChars(str) {
  // add diacritics to the expression to extend capabilities
  var re = /.[\u064b-\u065F]+|.[\u0300-\u036F]+/g;
  var match, matches = [];
  while (match = re.exec(str))
    matches.push(match[0]);
  return matches;
}

// list taken from 
// https://en.wikipedia.org/wiki/Combining_Diacritical_Marks
// And
// https://en.wikipedia.org/wiki/Arabic_script_in_Unicode
function getDiacritics() {
  return {
    '\u0300': 'Grave Accent',
    '\u0301': 'Acute Accent',
    '\u0302': 'Circumflex Accent',
    '\u0303': 'Tilde',
    '\u0304': 'Macron',
    '\u0305': 'Overline',
    '\u0306': 'Breve',
    '\u0307': 'Dot Above',
    '\u0308': 'Diaeresis',
    '\u0309': 'Hook Above',
    '\u030A': 'Ring Above',
    '\u030B': 'Double Acute Accent',
    '\u030C': 'Caron',
    '\u030D': 'Vertical Line Above',
    '\u030E': 'Double Vertical Line Above',
    '\u030F': 'Double Grave Accent',
    '\u0310': 'Candrabindu',
    '\u0311': 'Inverted Breve',
    '\u0312': 'Turned Comma Above',
    '\u0313': 'Comma Above',
    '\u0314': 'Reversed Comma Above',
    '\u0315': 'Comma Above Right',
    '\u0316': 'Grave Accent Below',
    '\u0317': 'Acute Accent Below',
    '\u0318': 'Left Tack Below',
    '\u0319': 'Right Tack Below',
    '\u031A': 'Left Angle Above',
    '\u031B': 'Horn',
    '\u031C': 'Left Half Ring Below',
    '\u031D': 'Up Tack Below',
    '\u031E': 'Down Tack Below',
    '\u031F': 'Plus Sign Below',
    '\u0320': 'Minus Sign Below',
    '\u0321': 'Palatalized Hook Below',
    '\u0322': 'Retroflex Hook Below',
    '\u0323': 'Dot Below',
    '\u0324': 'Diaeresis Below',
    '\u0325': 'Ring Below',
    '\u0326': 'Comma Below',
    '\u0327': 'Cedilla',
    '\u0328': 'Ogonek',
    '\u0329': 'Vertical Line Below',
    '\u032A': 'Bridge Below',
    '\u032B': 'Inverted Double Arch Below',
    '\u032C': 'Caron Below',
    '\u032D': 'Circumflex Accent Below',
    '\u032E': 'Breve Below',
    '\u032F': 'Inverted Breve Below',
    '\u0330': 'Tilde Below',
    '\u0331': 'Macron Below',
    '\u0332': 'Low Line',
    '\u0333': 'Double Low Line',
    '\u0334': 'Tilde Overlay',
    '\u0335': 'Short Stroke Overlay',
    '\u0336': 'Long Stroke Overlay',
    '\u0337': 'Short Solidus Overlay',
    '\u0338': 'Long Solidus Overlay',
    '\u0339': 'Right Half Ring Below',
    '\u033A': 'Inverted Bridge Below',
    '\u033B': 'Square Below',
    '\u033C': 'Seagull Below',
    '\u033D': 'X Above',
    '\u033E': 'Vertical Tilde',
    '\u033F': 'Double Overline',
    '\u0340': 'Grave Tone Mark',
    '\u0341': 'Acute Tone Mark',
    '\u0342': 'Greek Perispomeni',
    '\u0343': 'Greek Koronis',
    '\u0344': 'Greek Dialytika Tonos',
    '\u0345': 'Greek Ypogegrammeni',
    '\u0346': 'Bridge Above',
    '\u0347': 'Equals Sign Below',
    '\u0348': 'Double Vertical Line Below',
    '\u0349': 'Left Angle Below',
    '\u034A': 'Not Tilde Above',
    '\u034B': 'Homothetic Above',
    '\u034C': 'Almost Equal To Above',
    '\u034D': 'Left Right Arrow Below',
    '\u034E': 'Upwards Arrow Below',
    '\u034F': 'Grapheme Joiner',
    '\u0350': 'Right Arrowhead Above',
    '\u0351': 'Left Half Ring Above',
    '\u0352': 'Fermata',
    '\u0353': 'X Below',
    '\u0354': 'Left Arrowhead Below',
    '\u0355': 'Right Arrowhead Below',
    '\u0356': 'Right Arrowhead And Up Arrowhead Below',
    '\u0357': 'Right Half Ring Above',
    '\u0358': 'Dot Above Right',
    '\u0359': 'Asterisk Below',
    '\u035A': 'Double Ring Below',
    '\u035B': 'Zigzag Above',
    '\u035C': 'Double Breve Below',
    '\u035D': 'Double Breve',
    '\u035E': 'Double Macron',
    '\u035F': 'Double Macron Below',
    '\u0360': 'Double Tilde',
    '\u0361': 'Double Inverted Breve',
    '\u0362': 'Double Rightwards Arrow Below',
    '\u0363': 'Latin Small Letter A',
    '\u0364': 'Latin Small Letter E',
    '\u0365': 'Latin Small Letter I',
    '\u0366': 'Latin Small Letter O',
    '\u0367': 'Latin Small Letter U',
    '\u0368': 'Latin Small Letter C',
    '\u0369': 'Latin Small Letter D',
    '\u036A': 'Latin Small Letter H',
    '\u036B': 'Latin Small Letter M',
    '\u036C': 'Latin Small Letter R',
    '\u036D': 'Latin Small Letter T',
    '\u036E': 'Latin Small Letter V',
    '\u036F': 'Latin Small Letter X',
    '\u064B': 'Arabic Fathatan',
    '\u064C': 'Arabic Dammatan',
    '\u064D': 'Arabic Kasratan',
    '\u064E': 'Arabic Fatha',
    '\u064F': 'Arabic Damma',
    '\u0650': 'Arabic Kasra',
    '\u0651': 'Arabic Shadda',
    '\u0652': 'Arabic Sukun',
    '\u0653': 'Arabic Maddah Above',
    '\u0654': 'Arabic Hamza Above',
    '\u0655': 'Arabic Hamza Below',
    '\u0656': 'Arabic Subscript Alef',
    '\u0657': 'Arabic Inverted Damma',
    '\u0658': 'Arabic Mark Noon Ghunna',
    '\u0659': 'Arabic Zwarakay',
    '\u065A': 'Arabic Vowel Sign Small V Above',
    '\u065B': 'Arabic Vowel Sign Inverted Small V Above',
    '\u065C': 'Arabic Vowel Sign Dot Below',
    '\u065D': 'Arabic Reversed Damma',
    '\u065E': 'Arabic Fatha With Two Dots',
    '\u065F': 'Arabic Wavy Hamza Below',
  };
}
body {
  padding: 1rem;
}

pre>span {
  font-size: 2rem;
  font-weight: bold;
}

#inpt {
  font-family: 'Courier New', Courier, monospace;
  font-weight: bold;
  font-size: 2rem;
}
Input: <span id="inpt"></span>
<pre id="out"></pre>

对于其他语言,在getWholeChars中的正则表达式中添加变音符号。


这仅涉及拉丁字母。 - Mohsen Alyafei
请参见此处:https://zh.wikipedia.org/wiki/%E9%87%8D%E9%9F%B3符号 - Mohsen Alyafei
1
我已经修改了代码。在正则表达式中添加变音符号以扩展语言支持。阿拉伯语变音符号来自于https://en.wikipedia.org/wiki/Arabic_diacritics。我可能会漏掉一些。 - the Hutt
1
这是与编程有关的内容。以下是翻译后的文本:这句阿拉伯语的意思是:“祝你平安”。 - Mohsen Alyafei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接