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

发布于 2025-01-10 22:57:20 字数 516 浏览 0 评论 0原文

我想在 javascript 中获取字母的重音/变音符号。

例如:

  • ñ -> ~
  • á -> ´
  • è -> `

我尝试使用 .normalize("NFD") 但它没有返回正确的重音/变音符号

string = "á"
string.normalize("NFD").split("")
// ['a', '́']
string.normalize("NFD").split("").includes("´") 
// false
'́' === "´"
// false

我想要 NFD 或任何其他函数给出重音/变音符号而不是组合重音/变音符号

I want to get the accent/diacritic of a letter in javascript.

For example:

  • ñ -> ~
  • á -> ´
  • è -> `

I tried using .normalize("NFD") but it doesn't return the correct accent/diacritc

string = "á"
string.normalize("NFD").split("")
// ['a', '́']
string.normalize("NFD").split("").includes("´") 
// false
'́' === "´"
// false

I want NFD or any other function to give the accent/diacritic instead of the combining accent/diacritic

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

只是在用心讲痛 2025-01-17 22:57:20

简短的答案是因为 COMBINING TILDE != TILDE

下面是 每个ñ 中可能涉及的 Unicode 字符,例如:

符号代码CodePoint名称
ñ\u00F1241带有波浪号的拉丁文小写字母 N
n\u006E110拉丁文小写字母 N
̃\u0303771组合波浪号
~\u007E126TILDE

为了能够将变音符号与其附加字符分开,您可以使用 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);

The short answer is because COMBINING TILDE != TILDE

Here's a breakdown of each of the Unicode characters potentially involved in ñ for example:

SymbolCodeCodePointName
ñ\u00F1241LATIN SMALL LETTER N WITH TILDE
n\u006E110LATIN SMALL LETTER N
̃\u0303771COMBINING TILDE
~\u007E126TILDE

In order to be able to separate out the diacritical marks from their attached characters, you can use string.normalize with "NFD" which provides the "Canonical Decomposition", breaking up a single glyph into different character combinations that result in the same symbol.

There are 112 different combining diacritical marks. I can't find a native way to convert between the combining character and it's solo counterpart. You could look for a library or write the mapping yourself for marks you want to handle like this:

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

Then decompose to separate chars and lookup the associated mark for each combining char like this:

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);

咆哮 2025-01-17 22:57:20

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

normalize("NFD") 返回正确的结果,在本例中为组合锐音符号 Unicode 十进制代码 ́.

但是,您正在做的是比较 normalize("NFD") 中字母 á 的输出,这是组合急性口音 (字符代码 769) 和 正常重音< /strong>(字符代码180)。当然,这是两个不同的字母。

这同样适用于具有组合重音<的字母è /a>(字符代码768);并且您将其与正常重音(字符代码96),我们使用它并通过键盘输入;它们是2个不同的字母。

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

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

在此处输入图像描述

请参阅下面的示例:

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

Your method to get the accent/diacritic of a letter is correct by using the string.normalize("NFD").split("").

The normalize("NFD") returns the correct result which in this case is the Combining Acute Accent Unicode Decimal Code ́.

However, what you are doing is comparing the output for the letter á from the normalize("NFD") which is the Combining Acute Accent (char code 769) with the Normal Acute Accent (char code 180). Of course, these are 2 different letters.

The same applies to the letter è which has the Combining Grave Accent (char code 768); and you are comparing it to the Normal Grave Accent (char code 96) which we use and type from the keyboard; they are 2 different letters.

The standalone (normal) letters (including the Acute Accent and the Grave Accent letters) will always be separate letters even if they come before or after any other letters in a string. However, the combining forms of the letters (which have different chars codes to distinguish them) will go above or below the next or previous letter they are adjacent to. This is similar in Arabic accent letters and other languages like Hebrew.

Here are comparisons of some of the tilde letters:

enter image description here

See below the examples:

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

薯片软お妹 2025-01-17 22:57:20

规范分解(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 中向正则表达式添加变音符号。

The Canonical Decomposition(NFD) does work:

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

Extended demo: You don't have to maintain a map for all characters. In the demo map has been used only to get description of diacritics.

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>

For other languages, add diacritics to the regular expression in getWholeChars.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文