Java >> Java tutorial >  >> Tag >> String

Fjern accenter og diakritiske tegn fra en streng i Java

1. Oversigt

Mange alfabeter indeholder accent- og diakritiske tegn. For at søge eller indeksere data pålideligt vil vi måske konvertere en streng med diakritiske tegn til en streng, der kun indeholder ASCII-tegn. Unicode definerer en tekstnormaliseringsprocedure, der hjælper med at gøre dette.

I denne tutorial vil vi se, hvad Unicode-tekstnormalisering er, hvordan vi kan bruge det til at fjerne diakritiske tegn og faldgruberne at holde øje med. Derefter vil vi se nogle eksempler, der bruger Java Normalizer klasse og Apache Commons StringUtils.

2. Problemet på et øjeblik

Lad os sige, at vi arbejder med tekst, der indeholder den række af diakritiske tegn, vi ønsker at fjerne:

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

Efter at have læst denne artikel, ved vi, hvordan vi slipper af med diakritiske tegn og ender med:

aaaeiiiiggnnsssuuy

3. Unicode Fundamentals

Før vi hopper direkte ind i kode, lad os lære nogle grundlæggende Unicode-principper.

For at repræsentere et tegn med et diakritisk eller accenttegn kan Unicode bruge forskellige sekvenser af kodepunkter. Grunden til det er historisk kompatibilitet med ældre karaktersæt.

Unicode-normalisering er nedbrydning af tegn ved at bruge ækvivalensformer defineret af standarden .

3.1. Unicode-ækvivalensformularer

For at sammenligne sekvenser af kodepunkter definerer Unicode to udtryk:kanonisk ækvivalens og kompatibilitet .

Kanonisk ækvivalente kodepunkter har samme udseende og betydning, når de vises. For eksempel kan bogstavet "ś" (latinsk bogstav "s" med akut) repræsenteres med et kodepunkt +U015B eller to kodepunkter +U0073 (latinsk bogstav "s") og +U0301 (akut symbol).

På den anden side kan kompatible sekvenser have forskellige udseender, men den samme betydning i nogle sammenhænge. For eksempel er kodepunktet +U013F (latinsk ligatur "Ŀ") kompatibel med sekvensen +U004C (latinsk bogstav "L") og +U00B7 (symbol "·"). Desuden kan nogle skrifttyper vise den midterste prik inde i L'et og nogle efter den.

Kanonisk ækvivalente sekvenser er kompatible, men det modsatte er ikke altid sandt.

3.2. Karakternedbrydning

Tegnnedbrydning erstatter det sammensatte tegn med kodepunkter af et grundbogstav, efterfulgt af kombination af tegn (i henhold til ækvivalensformen). For eksempel vil denne procedure opdele bogstavet "ā" i tegnene "a" og "-".

3.3. Matchende diakritiske og accenttegn

Når vi har adskilt grundtegnet fra det diakritiske mærke, skal vi skabe et udtryk, der matcher uønskede tegn. Vi kan bruge enten en karakterblok eller en kategori.

Den mest populære Unicode-kodeblok er Kombinering af diakritiske mærker . Den er ikke særlig stor og indeholder kun 112 mest almindelige kombinationstegn. På den anden side kan vi også bruge Unicode-kategorien Mark . Den består af kodepunkter, der kombinerer mærker og opdeles yderligere i tre underkategorier:

  • Nonspaceing_Mark : denne kategori omfatter 1.839 kodepunkter
  • Enclosing_Mark :indeholder 13 kodepunkter
  • Spacing_Combining_Mark :indeholder 443 point

Den største forskel mellem en Unicode-tegnblok og en kategori er, at tegnblokken indeholder en sammenhængende række af tegn. På den anden side kan en kategori have mange karakterblokke. For eksempel er det netop tilfældet med Kombinering af diakritiske tegn :alle kodepunkter, der hører til denne blok, er også inkluderet i Nonspaceing_Mark  kategori.

4. Algoritme

Nu hvor vi forstår de grundlæggende Unicode-udtryk, kan vi planlægge algoritmen til at fjerne diakritiske tegn fra en streng .

Først vil vi adskille grundtegn fra accent og diakritiske tegn ved hjælp af Normalizer klasse . Desuden vil vi udføre kompatibilitetsdekomponeringen repræsenteret som Java-enum NFKD . Derudover bruger vi kompatibilitetsdekomponering, fordi den nedbryder flere ligaturer end den kanoniske metode (f.eks. ligatur "fi").

For det andet vil vi fjerne alle tegn, der matcher Unicode-mærket kategori ved hjælp af \p{M} regex-udtryk . Vi vælger denne kategori, fordi den tilbyder det bredeste udvalg af mærker.

5. Brug af Core Java

Lad os starte med nogle eksempler, der bruger kerne-Java.

5.1. Tjek, om en streng Er normaliseret

Før vi udfører en normalisering, vil vi måske kontrollere, at strengen er ikke allerede normaliseret:

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

5.2. Strengnedbrydning

Hvis vores streng er ikke normaliseret, går vi videre til næste trin. For at adskille ASCII-tegn fra diakritiske tegn udfører vi Unicode-tekstnormalisering ved hjælp af kompatibilitetsdekomponering:

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

Efter dette trin vil begge bogstaver "â" og "ä" blive reduceret til "a" efterfulgt af respektive diakritiske tegn.

5.3. Fjernelse af kodepunkter, der repræsenterer diakritiske og accenttegn

Når vi har nedbrudt vores streng , vi ønsker at fjerne uønskede kodepunkter. Derfor vil vi bruge Unicode regulære udtryk \p{M} :

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

5.4. Tester

Lad os se, hvordan vores nedbrydning fungerer i praksis. Lad os først vælge tegn med normaliseringsform defineret af Unicode og forvente at fjerne alle diakritiske tegn:

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

For det andet, lad os vælge et par tegn uden dekomponeringsmapping:

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

Som forventet var vores metode ikke i stand til at nedbryde dem.

Derudover kan vi oprette en test for at validere hex-koderne for dekomponerede tegn:

@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. Sammenlign strenge inklusive accenter ved hjælp af Collator

Pakke java.text indeholder en anden interessant klasse – Collator . Det gør os i stand til at udføre lokalitetsfølsom streng sammenligninger . En vigtig konfigurationsegenskab er Collator's styrke. Denne egenskab definerer minimumsniveauet af forskel, der anses for at være signifikant under en sammenligning.

Java giver fire styrkeværdier for en Collator :

  • PRIMÆR :sammenligning udeladelse af store og små bogstaver og accenter
  • SEKUNDÆR :sammenligning med udeladelse af store og små bogstaver, men med accenter og diakritik
  • TERTIÆR :sammenligning inklusive store og små bogstaver og accenter
  • IDENTISK :alle forskelle er signifikante

Lad os tjekke nogle eksempler, først med primær styrke:

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

Sekundær styrke slår accentfølsomhed til:

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

Tertiær styrke omfatter tilfælde:

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

Identisk styrke gør alle forskelle vigtige. Det næstsidste eksempel er interessant, da vi kan opdage forskellen mellem Unicode-kontrolkodepunkter +U001 (kode for "Start af overskrift") og +U002 ("Start af tekst"):

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

Et sidste eksempel, der er værd at nævne, viser, at hvis tegnet ikke har en defineret nedbrydningsregel, vil det ikke blive betragtet som lig med et andet tegn med samme grundbogstav . Dette skyldes det faktum, at Collator vil ikke være i stand til at udføre Unicode-nedbrydningen :

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

6. Brug af Apache Commons StringUtils

Nu hvor vi har set, hvordan man bruger kerne-Java til at fjerne accenter, vil vi tjekke, hvad Apache Commons Text tilbyder. Som vi snart vil lære, er det nemmere at bruge, men vi har mindre kontrol over nedbrydningsprocessen . Under hætten bruger den Normalizer.normalize() metode med NFD nedbrydningsform og \p{InCombiningDiacriticalMarks} regulære udtryk:

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

6.1. Tester

Lad os se denne metode i praksis - først kun med nedbrydelige Unicode-tegn :

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

Som forventet slap vi af med alle accenterne.

Lad os prøve en streng indeholdende ligatur og bogstaver med streg :

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

Som vi kan se, er StringUtils.stripAccents() metode definerer manuelt oversættelsesreglen for latinske ł og Ł tegn. Men desværre normaliserer det ikke andre ligaturer .

7. Begrænsninger af tegnnedbrydning i Java

For at opsummere så vi, at nogle tegn ikke har definerede nedbrydningsregler. Mere specifikt, Unicode definerer ikke nedbrydningsregler for ligaturer og tegn med streg . På grund af det vil Java heller ikke være i stand til at normalisere dem. Hvis vi vil slippe af med disse tegn, er vi nødt til at definere transkriptionskortlægning manuelt.

Endelig er det værd at overveje, om vi skal slippe for accenter og diakritiske tegn. For nogle sprog vil et bogstav fjernet fra diakritiske tegn ikke give meget mening. I sådanne tilfælde er en bedre idé at bruge Sammenlæseren klasse og sammenlign to strenge , inklusive oplysninger om lokalitet.

8. Konklusion

I denne artikel undersøgte vi at fjerne accenter og diakritiske mærker ved hjælp af kerne-Java og det populære Java-værktøjsbibliotek, Apache Commons. Vi så også et par eksempler og lærte, hvordan man sammenligner tekst, der indeholder accenter, samt et par ting, man skal være opmærksom på, når man arbejder med tekst, der indeholder accenter.

Som altid er den fulde kildekode til artiklen tilgængelig på GitHub.


Java tag