拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 用JAVA从字符串中删除重音和变音符号

用JAVA从字符串中删除重音和变音符号

白鹭 - 2021-11-06 2205 0 2

1. 概述

许多字母表包含重音符号和变音符号。为了可靠地搜索或索引数据,我们可能希望将带有变音符号的字符串转换为仅包含ASCII 字符的字符串。Unicode 定义了有助于实现此目的的文本规范化过程。

在本教程中,我们将了解什么是Unicode 文本规范化、我们如何使用它来删除变音符号以及需要注意的陷阱。然后,我们将看到一些使用JavaNormalizerStringUtils.

2. 问题概览

假设我们正在处理包含要删除的变音符号范围的文本:

āăąēîïĩíĝġńñšŝśûůŷ

阅读本文后,我们将知道如何摆脱变音符号并最终得到:

aaaeiiiiggnnsssuuy

3. Unicode 基础

在直接进入代码之前,让我们学习一些Unicode 基础知识。

为了用变音符号或重音符号表示字符,Unicode 可以使用 原因是与旧字符集的历史兼容性。

Unicode 规范化是使用标准定义的等价形式分解字符

3.1. Unicode 等价形式

为了比较代码点序列,Unicode 定义了两个术语:canonical equivalencecompatibility

标准等效代码点在显示时具有相同的外观和含义。例如,字母“ś”(带有锐角的拉丁字母“s”)可以用一个码位+U015B 或两个码位+U0073(拉丁字母“s”)和+U0301(锐角符号)来表示。

另一方面,兼容序列在某些情况下可以具有不同的外观但具有相同的含义。例如,代码点+U013F(拉丁文连字“Ŀ”)与序列+U004C(拉丁字母“L”)和+U00B7(符号“·”)兼容。此外,有些字体可以在L 内显示中间点,有些在它后面。

规范等价序列是兼容的,但相反的情况并不总是正确的。

3.2.字符分解

字符分解用基本字母的代码点替换复合字符,然后组合字符(根据等价形式)。例如,此过程将字母“ā”分解为字符“a”和“-”。

3.3.匹配变音符号和重音符号

一旦我们将基本字符与变音符号分开,我们必须创建一个匹配不需要的字符的表达式。我们可以使用字符块或类别。

最流行的Unicode 代码块是 它不是很大,仅包含112 个最常见的组合字符。另一方面,我们也可以使用Unicode 类别 它由组合标记的代码点组成,并进一步分为三个子类别:Combining Diacritical MarksMark

  • Nonspacing_Mark :该类别包括1,839 个代码点

  • Enclosing_Mark : 包含13 个代码点

  • Spacing_Combining_Mark : 包含443 个点

Unicode 字符块和类别之间的主要区别在于字符块包含一个连续的字符范围。另一方面,一个类别可以有许多字符块。例如,这正是Combining Diacritical MarksNonspacing_Mark

4. 算法

现在我们了解了基本的Unicode 术语,我们可以规划算法以从String

首先,我们将** 此外,我们将执行表示为Java 枚举 此外,我们使用兼容性分解,因为它比规范方法分解了更多的连字(例如,连字“fi”)。NormalizerNFKD

其次,我们将 我们选择这个类别是因为它提供了最广泛的标记。\p{M}Mark

5. 使用核心Java

让我们从一些使用核心Java 的示例开始。

5.1.检查String

在我们执行规范化之前,我们可能想要检查String

assertFalse(Normalizer.isNormalized("āăąēîïĩíĝġńñšŝśûůŷ", Normalizer.Form.NFKD));

5.2.字符串分解

如果我们的 为了将ASCII 字符与变音符号分开,我们将使用兼容性分解来执行Unicode 文本规范化:String

private static String normalize(String input) { return input == null ? null : Normalizer.normalize(input, Normalizer.Form.NFKD);
 }

在这一步之后,字母“â”和“ä”都将缩减为“a”,后跟各自的变音符号。

5.3.删除代表变音符号和重音符号的代码点

一旦我们分解了我们的 因此,我们将使用String\p{M}

static String removeAccents(String input) { return normalize(input).replaceAll("\\p{M}", "");
 }

5.4.单元测试

让我们看看我们的分解在实践中是如何工作的。首先,让我们选择具有由Unicode 定义的规范化形式的字符,并期望删除所有变音符号:

@Test
 void givenStringWithDecomposableUnicodeCharacters_whenRemoveAccents_thenReturnASCIIString() {
 assertEquals("aaaeiiiiggnnsssuuy", StringNormalizer.removeAccents("āăąēîïĩíĝġńñšŝśûůŷ"));
 }

其次,我们挑几个没有分解映射的字符:

@Test
 void givenStringWithNondecomposableUnicodeCharacters_whenRemoveAccents_thenReturnOriginalString() {
 assertEquals("łđħœ", StringNormalizer.removeAccents("łđħœ"));
 }

正如预期的那样,我们的方法无法分解它们。

此外,我们可以创建一个测试来验证分解字符的十六进制代码:

@Test
 void givenStringWithDecomposableUnicodeCharacters_whenUnicodeValueOfNormalizedString_thenReturnUnicodeValue() {
 assertEquals("\\u0066 \\u0069", StringNormalizer.unicodeValueOfNormalizedString("fi"));
 assertEquals("\\u0061 \\u0304", StringNormalizer.unicodeValueOfNormalizedString("ā"));
 assertEquals("\\u0069 \\u0308", StringNormalizer.unicodeValueOfNormalizedString("ï"));
 assertEquals("\\u006e \\u0301", StringNormalizer.unicodeValueOfNormalizedString("ń"));
 }

5.5.Collator

包 它 一个重要的配置属性是 此属性定义在比较期间被视为显著的最小差异级别。java.textCollatorStringCollator's

Collator提供了四个强度值

  • PRIMARY :比较省略大小写和重音

  • SECONDARY :比较省略大小写但包括重音和变音符号

  • TERTIARY :比较包括大小写和口音

  • IDENTICAL :所有差异都显著

让我们检查一些例子,首先是主要力量:

Collator collator = Collator.getInstance();
 collator.setDecomposition(2);
 collator.setStrength(0);
 assertEquals(0, collator.compare("a", "a"));
 assertEquals(0, collator.compare("ä", "a"));
 assertEquals(0, collator.compare("A", "a"));
 assertEquals(1, collator.compare("b", "a"));

次要强度开启重音敏感度:

collator.setStrength(1);
 assertEquals(1, collator.compare("ä", "a"));
 assertEquals(1, collator.compare("b", "a"));
 assertEquals(0, collator.compare("A", "a"));
 assertEquals(0, collator.compare("a", "a"));

三级强度包括以下情况:

collator.setStrength(2);
 assertEquals(1, collator.compare("A", "a"));
 assertEquals(1, collator.compare("ä", "a"));
 assertEquals(1, collator.compare("b", "a"));
 assertEquals(0, collator.compare("a", "a"));
 assertEquals(0, collator.compare(valueOf(toChars(0x0001)), valueOf(toChars(0x0002))));

相同的强度使所有差异都变得重要。倒数第二个例子很有趣,因为我们可以检测Unicode 控制代码点+U001(“标题开头”的代码)和+U002(“文本开头”)之间的差异:

collator.setStrength(3);
 assertEquals(1, collator.compare("A", "a"));
 assertEquals(1, collator.compare("ä", "a"));
 assertEquals(1, collator.compare("b", "a"));
 assertEquals(-1, collator.compare(valueOf(toChars(0x0001)), valueOf(toChars(0x0002))));
 assertEquals(0, collator.compare("a", "a")));

最后一个值得一提的例子表明,这是因为**Collator

collator.setStrength(0);
 assertEquals(1, collator.compare("ł", "l"));
 assertEquals(1, collator.compare("ø", "o"));

6. 使用Apache Commons StringUtils

现在我们已经了解了如何使用核心Java 来删除重音符号,我们将检查Apache Commons Text 提供的内容。我们很快就会了解到,在幕后,它使用Normalizer.normalize()NFD

static String removeAccentsWithApacheCommons(String input) { return StringUtils.stripAccents(input);
 }

6.1.测试

让我们在实践中看看这个方法——首先,

@Test
 void givenStringWithDecomposableUnicodeCharacters_whenRemoveAccentsWithApacheCommons_thenReturnASCIIString() {
 assertEquals("aaaeiiiiggnnsssuuy", StringNormalizer.removeAccentsWithApacheCommons("āăąēîïĩíĝġńñšŝśûůŷ"));
 }

正如预期的那样,我们摆脱了所有的口音。

让我们尝试一个

@Test
 void givenStringWithNondecomposableUnicodeCharacters_whenRemoveAccentsWithApacheCommons_thenReturnModifiedString() {
 assertEquals("lđħœ", StringNormalizer.removeAccentsWithApacheCommons("łđħœ"));
 }

正如我们所见,但是,不幸的是,它并没有规范其他连字StringUtils.stripAccents()

7. Java 中字符分解的局限性

综上所述,我们看到有些字符没有定义分解规则。更具体地说,因此,Java 也无法对它们进行规范化。如果我们想摆脱这些字符,我们必须手动定义转录映射。

最后,值得考虑的是我们是否需要摆脱重音和变音符号。对于某些语言,去掉变音符号的字母没有多大意义。在这种情况下,更好的主意是使用CollatorStrings

8. 结论

在本文中,我们研究了使用核心Java 和流行的Java 实用程序库Apache Commons 删除重音和变音符号。我们还看到了一些示例并学习了如何比较包含重音的文本,以及在处理包含重音的文本时需要注意的一些事项。


标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *