Czy istnieje sposób na pozbycie się akcentów i zamianę całego ciągu na zwykłe litery?


263

Czy istnieje lepszy sposób na pozbycie się akcentów i regularne pisanie tych liter oprócz używania String.replaceAll()metody i zastępowania liter jedna po drugiej? Przykład:

Wejście: orčpžsíáýd

Wynik: orcpzsiayd

Nie musi zawierać wszystkich liter z akcentami, takimi jak alfabet rosyjski lub chiński.

Odpowiedzi:


387

Użyj, java.text.Normalizeraby sobie z tym poradzić.

string = Normalizer.normalize(string, Normalizer.Form.NFD);
// or Normalizer.Form.NFKD for a more "compatable" deconstruction 

To oddzieli wszystkie znaki akcentu od znaków. Następnie wystarczy porównać każdą postać z literą i wyrzucić te, które nie są.

string = string.replaceAll("[^\\p{ASCII}]", "");

Jeśli twój tekst jest w standardzie Unicode, powinieneś użyć tego:

string = string.replaceAll("\\p{M}", "");

W przypadku Unicode \\P{M}dopasowuje glif podstawowy, a \\p{M}(małe litery) dopasowuje każdy akcent.

Dzięki GarretWilson za wskaźnik i regularne - wyrażenia.info za świetny przewodnik po Unicode.


7
To kompiluje wyrażenie regularne za każdym razem, co jest w porządku, jeśli potrzebujesz go tylko raz, ale jeśli musisz to zrobić z dużą ilością tekstu, wstępne skompilowanie wyrażenia regularnego jest zwycięstwem.
David Conrad

3
Pamiętaj, że nie wszystkie litery łacińskie rozkładają się na akcenty ASCII +. To zabije np. „Łacińska {duża, mała} litera l z obrysem” używane w języku polskim.
Michał Politowski

12
To dobre podejście, ale usunięcie wszystkich znaków spoza ASCII jest przesadą i prawdopodobnie usunie rzeczy, których nie chcesz, jak wskazali inni. Lepiej byłoby usunąć wszystkie „znaki” Unicode; w tym znaki spacji, znaki spacji / łączenia i znaki otaczające. Możesz to zrobić za pomocą string.replaceAll("\\p{M}", ""). Aby uzyskać więcej informacji, zobacz regular-expressions.info/unicode.html .
Garret Wilson

4
Prawdopodobnie chcesz użyć Normalizer.Form.NFKD zamiast NFD - NFKD przekształci takie rzeczy jak ligatury w znaki ascii (np. Fi do fi), NFD tego nie zrobi.
chesterm8

2
@ chesterm8, co ciekawe, NFKD konwertuje „fi” na „fi”, ale nie konwertuje „Æ” na „AE”. Chyba będę musiał przywołać dane Unicode, aby dowiedzieć się, dlaczego, ale nie tego się spodziewałem.
Garret Wilson,

136

Począwszy od 2011 roku możesz używać Apache Commons StringUtils.stripAccents (dane wejściowe) (od 3.0):

    String input = StringUtils.stripAccents("Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ");
    System.out.println(input);
    // Prints "This is a funky String"

Uwaga:

Przyjęta odpowiedź (odpowiedź Ericka Robertsona) nie działa dla Ø ani Ł. Apache Commons 3.5 też nie działa na Ø, ale działa na Ł. Po przeczytaniu artykułu z Wikipedii dotyczącego Ø , nie jestem pewien, czy należy go zastąpić „O”: jest to osobna litera w języku norweskim i duńskim, alfabetycznie po „z”. To dobry przykład ograniczeń podejścia „strip accents”.


2
Widzę, że istnieje otwarty raport o błędach dla Ł , @KololS. Ktoś przesłał żądanie ściągnięcia, ale nie przeszło niektórych testów i nie było aktualizowane od lipca ubiegłego roku.
DavidS

1
Była tam aktualizacja 5 dni temu i żądanie ściągnięcia zostało scalone.
EpicPandaForce

6
Commons Lang 3.5 został wydany kilka dni temu. Potwierdziłem, że teraz działa na Ł. Nie działa na Ø. Czytając artykuł Wiki dla Ø , nie jestem pewien, czy należy go zastąpić „O”: jest to osobna litera w języku norweskim i duńskim, alfabetycznie po „z”. To dobry przykład ograniczeń podejścia „strip accents”.
DavidS

2
Jeśli nie chcesz dołączać biblioteki, możesz łatwo pobrać dwie metody związane z tą funkcją ze źródła pod adresem commons.apache.org/proper/commons-lang/apidocs/src-html/org/…
lujop

2
Jako Duńczyk ø duński / norweski, podobnie jak francuski œ i niemiecki / szwedzki / węgierski / estoński itp. Ö, pochodzi od krótkiej drogi do napisania oe. Więc w zależności od celu może to być podstawienie, którego chcesz.
Ole VV

57

Rozwiązanie @ virgo47 jest bardzo szybkie, ale przybliżone. W zaakceptowanej odpowiedzi użyto Normalizera i wyrażenia regularnego. Zastanawiałem się, jaka część czasu zajęła Normalizer a wyrażenie regularne, ponieważ usunięcie wszystkich znaków spoza ASCII można wykonać bez wyrażenia regularnego:

import java.text.Normalizer;

public class Strip {
    public static String flattenToAscii(String string) {
        StringBuilder sb = new StringBuilder(string.length());
        string = Normalizer.normalize(string, Normalizer.Form.NFD);
        for (char c : string.toCharArray()) {
            if (c <= '\u007F') sb.append(c);
        }
        return sb.toString();
    }
}

Małe dodatkowe przyspieszenia można uzyskać, pisząc do char [] i nie wywołując metodyCharArray (), chociaż nie jestem pewien, czy zasługuje na to zmniejszenie przejrzystości kodu:

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    string = Normalizer.normalize(string, Normalizer.Form.NFD);
    int j = 0;
    for (int i = 0, n = string.length(); i < n; ++i) {
        char c = string.charAt(i);
        if (c <= '\u007F') out[j++] = c;
    }
    return new String(out);
}

Ta odmiana ma tę zaletę, że poprawność tej przy użyciu Normalizera i pewnej prędkości tej przy użyciu tabeli. Na moim komputerze ten jest około 4x szybszy niż zaakceptowana odpowiedź i 6,6x do 7x wolniejszy niż @ virgo47 (akceptowana odpowiedź jest około 26x wolniejsza niż @ virgo47 na moim komputerze).


2
outnależy zmienić rozmiar, aby dopasować liczbę prawidłowych znaków, jzanim zostanie on użyty do skonstruowania obiektu ciągu
Lefteris E

4
Mam sprzeciw wobec tego rozwiązania. Wyobraź sobie wejście „æøåá”. Bieżący flattenToAsciitworzy wynik „aa ..”, w którym kropki oznaczają \ u0000. To nie jest dobre. Pierwsze pytanie brzmi - jak reprezentować „nienormalne” postacie? Powiedzmy, że tak będzie, czy możemy zostawić tam znak NULL, ale w każdym razie musimy zachować ich prawidłowe położenie (podobnie jak rozwiązanie wyrażenia regularnego). W tym celu if w pętli musi być coś w stylu: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';Spowolni to nieco, ale przede wszystkim musi być poprawne. ;-)
virgo47,

Dodaj mój ostatni komentarz (szkoda, że ​​nie mogą być dłużej) - być może pozytywne podejście ( isLetter) nie jest właściwe, ale nie znalazłem lepszego. Nie jestem ekspertem od Unicode, więc nie wiem, jak lepiej zidentyfikować klasę pojedynczego znaku, który zastępuje oryginalny znak. Listy działają OK dla większości aplikacji / zastosowań.
virgo47,

1
Prawdopodobnie chcesz użyć Normalizer.Form.NFKD zamiast NFD - NFKD przekształci takie rzeczy jak ligatury w znaki ascii (np. Fi do fi), NFD tego nie zrobi.
chesterm8

2
Dla nas chcieliśmy całkowicie usunąć postać. Aby upewnić się, że nie ma końcowych znaków zerowych, usunąłem je za pomocą alternatywnego konstruktora ciągów: zwróć nowy ciąg (out, 0, j);
Mike Samaras

30

EDYCJA: Jeśli nie utkniesz z Javą <6, a szybkość nie jest krytyczna i / lub tabela tłumaczeń jest zbyt ograniczona, skorzystaj z odpowiedzi Davida. Chodzi o to, aby użyć Normalizer(wprowadzonego w Javie 6) zamiast tabeli translacji wewnątrz pętli.

Chociaż nie jest to „idealne” rozwiązanie, działa dobrze, gdy znasz zakres (w naszym przypadku Latin1,2), działał przed Javą 6 (choć nie jest to prawdziwy problem) i jest znacznie szybszy niż najbardziej sugerowana wersja (może lub może nie stanowić problemu):

    /**
 * Mirror of the unicode table from 00c0 to 017f without diacritics.
 */
private static final String tab00c0 = "AAAAAAACEEEEIIII" +
    "DNOOOOO\u00d7\u00d8UUUUYI\u00df" +
    "aaaaaaaceeeeiiii" +
    "\u00f0nooooo\u00f7\u00f8uuuuy\u00fey" +
    "AaAaAaCcCcCcCcDd" +
    "DdEeEeEeEeEeGgGg" +
    "GgGgHhHhIiIiIiIi" +
    "IiJjJjKkkLlLlLlL" +
    "lLlNnNnNnnNnOoOo" +
    "OoOoRrRrRrSsSsSs" +
    "SsTtTtTtUuUuUuUu" +
    "UuUuWwYyYZzZzZzF";

/**
 * Returns string without diacritics - 7 bit approximation.
 *
 * @param source string to convert
 * @return corresponding string without diacritics
 */
public static String removeDiacritic(String source) {
    char[] vysl = new char[source.length()];
    char one;
    for (int i = 0; i < source.length(); i++) {
        one = source.charAt(i);
        if (one >= '\u00c0' && one <= '\u017f') {
            one = tab00c0.charAt((int) one - '\u00c0');
        }
        vysl[i] = one;
    }
    return new String(vysl);
}

Testy na moim HW z 32-bitowym JDK pokazują, że wykonuje konwersję z àèéľšťč89FDČ do aeelstc89FDC 1 milion razy w ~ 100ms, podczas gdy sposób Normalizera robi to w 3,7s (37x wolniej). Jeśli Twoje potrzeby dotyczą wydajności i znasz zakres wejściowy, może to być dla Ciebie.

Cieszyć się :-)


1
Duża powolność sugerowanej wersji wynika z wyrażenia regularnego, a nie z Normalizera. Używanie Normalizera, ale „ręczne” usuwanie znaków spoza ASCII jest szybsze, choć nadal nie tak szybkie, jak w twojej wersji. Ale działa dla wszystkich Unicode zamiast tylko latin1 i latin2.
David Conrad

Rozszerzyłem to do pracy z większą liczbą znaków, pastebin.com/FAAm6a2j , Uwaga: nie będzie działać poprawnie ze znakami wielowymiarowymi, takimi jak DŽ (DZ). Wyprodukuje z niej tylko 1 postać. Również moja funkcja używa char zamiast ciągów, co jest szybsze JEŻELI idziesz do obsługi char, więc nie musisz konwertować.
James T

Hej, nie rozumiem, co oznaczają te litery w polu tab00c0? na przykład „AAAAAAACEACEEIIII” lub „lLlNnNnNnnNnOoOo” itp. Nigdy wcześniej ich nie widziałem. Gdzie ich znalazłeś? Również dlaczego nie użyjesz odpowiednich kodów?
ThanosFisherman,

@ThanosF po prostu spróbuj przejść przez kod (w razie potrzeby z debuggerem). Działa to dla każdego znaku w ciągu: „Czy ten znak jest między \ u00c0 a \ u017f? Jeśli tak, zamień go na 7-bitowy znak ASCII z tabeli.” Tabela obejmuje tylko dwie strony kodowania (Latin 1 i 2) z ich 7-bitowymi odpowiednikami. Więc jeśli jest to znak z kodem \ u00e0 (à), to przyjmie swoje 7-bitowe przybliżenie od 32 pozycji tabeli (e0-c0 = 32) - to znaczy „a”. Niektóre znaki nie są literami, pozostały tam z kodem.
virgo47,

Dziękuję za wyjaśnienie. Gdzie mogę znaleźć te strony kodujące, aby rozszerzyć tę zmienną na mój język? (Grecki) Zaakceptowana odpowiedź już zastępuje greckie litery akcentowane, ale chciałem też wypróbować twoją metodę i przeprowadzić testy porównawcze :)
ThanosFisherman

22
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

pracował dla mnie. Wyjście powyższego fragmentu daje „aee”, co chciałem, ale

System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));

nie dokonał żadnej zamiany.


1
Potwierdzenie tego ... normalnie ASCII działa dobrze, ale napotkałem ten problem na Linuksie (64b) z JRockit (1.6.0_29 64b). Nie mogę potwierdzić to z żadną inną konfigurację, nie można potwierdzić, że korelacja, ale może potwierdzić, że inne zaproponowane rozwiązanie pracował i że zagłosuję ten jeden w górę. :-) (BTW: Dokonał pewnej wymiany, ale niewystarczająco, zmienił na przykład Ú na U, ale nie á na a.)
virgo47

1
Prawdopodobnie chcesz użyć Normalizer.Form.NFKD zamiast NFD - NFKD przekształci takie rzeczy jak ligatury w znaki ascii (np. Fi do fi), NFD tego nie zrobi.
chesterm8

@KarolS nie widzę żadnego z nich zawiera żadnych akcentów
eis

@eis Cięcie na literę liczy się jako diakrytyczne: en.wikipedia.org/wiki/Diacritic A jeśli podążasz za bardziej rygorystyczną definicją „akcentu”, jak na tej stronie Wikipedii, to diureza nie jest akcentem, więc odpowiedź Nico wciąż jest źle.
Karol S

6

W zależności od języka mogą to nie być akcenty (które zmieniają dźwięk litery), ale znaki diakrytyczne

https://en.wikipedia.org/wiki/Diacritic#Languages_with_letters_containing_diacritics

„Bośniacki i chorwacki mają symbole č, ć, đ, š i ž, które są uważane za osobne litery i są wymienione jako takie w słownikach i innych kontekstach, w których słowa są wymienione zgodnie z kolejnością alfabetyczną.”

Ich usunięcie może z natury zmienić znaczenie słowa lub zmienić litery na zupełnie inne.


5
Zgoda. Na przykład w języku szwedzkim: „höra” (usłyszeć) -> „hora” (kurwa)
Christoffer Hammarström

14
Nie ma znaczenia, co mają na myśli. Pytanie brzmi, jak je usunąć.
Erick Robertson

7
Erick: Ma znaczenie, jak się nazywają. Jeśli pytanie dotyczy sposobu usuwania akcentów, a jeśli nie są to akcenty, odpowiedź może nie polegać tylko na usunięciu wszystkich rzeczy, które wyglądają jak akcenty. Chociaż prawdopodobnie powinien to być komentarz, a nie odpowiedź.
Smig

4
Myślę, że normalnym przypadkiem użycia jest wyszukiwanie, szczególnie wyszukiwanie mieszanych języków, często z użyciem klawiatury angielskiej, w którym to przypadku lepiej jest uzyskać fałszywie dodatnie niż fałszywe negatywy.
nilskp

3

Napotkałem ten sam problem związany z kontrolą równości ciągów, jeden z ciągów porównawczych ma kod znakowy ASCII 128-255 .

tj. Nieprzerwana spacja - [Hex - A0] Space [Hex - 20]. Aby pokazać nieprzerwaną przestrzeń nad HTML. Użyłem następujących spacing entities. Ich charakter i bajty są podobne&emsp is very wide space[ ]{-30, -128, -125}, &ensp is somewhat wide space[ ]{-30, -128, -126}, &thinsp is narrow space[ ]{32} , Non HTML Space {}

String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
System.out.format("S1: %s\n", java.util.Arrays.toString(s1.getBytes()));
System.out.format("S2: %s\n", java.util.Arrays.toString(s2.getBytes()));

Dane wyjściowe w bajtach:

S1: [77, 121,, 3283, 97, 109, 112, 108, 101,, 3283, 112, 97, 99, 101,, 3268, 97, 116, 97] S2: [77, 121,, -30, -128, -12583, 97, 109, 112, 108, 101,, -30, -128, -12583, 112, 97, 99, 101,, -30, -128, -12568, 97, 116, 97]

Użyj poniższego kodu dla różnych spacji i ich kodów bajtowych: wiki for List_of_Unicode_characters

String spacing_entities = "very wide space,narrow space,regular space,invisible separator";
System.out.println("Space String :"+ spacing_entities);
byte[] byteArray = 
    // spacing_entities.getBytes( Charset.forName("UTF-8") );
    // Charset.forName("UTF-8").encode( s2 ).array();
    {-30, -128, -125, 44, -30, -128, -126, 44, 32, 44, -62, -96};
System.out.println("Bytes:"+ Arrays.toString( byteArray ) );
try {
    System.out.format("Bytes to String[%S] \n ", new String(byteArray, "UTF-8"));
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
  • Transl Transliteracje ASCII ciągu Unicode dla Java. unidecode

    String initials = Unidecode.decode( s2 );
  • ➩ przy użyciu Guava: Google Core Libraries for Java.

    String replaceFrom = CharMatcher.WHITESPACE.replaceFrom( s2, " " );

    Do kodowania adresu URL miejsca użyj biblioteki Guava.

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • ➩ W celu przezwyciężenia tego problemu zastosowano go String.replaceAll()z niektórymi RegularExpression.

    // \p{Z} or \p{Separator}: any kind of whitespace or invisible separator.
    s2 = s2.replaceAll("\\p{Zs}", " ");
    
    
    s2 = s2.replaceAll("[^\\p{ASCII}]", " ");
    s2 = s2.replaceAll(" ", " ");
  • ➩ Korzystanie z java.text.Normalizer.Form . To wyliczenie zapewnia stałe czterech form normalizacji Unicode opisanych w Aneksie Standardu Unicode nr 15 - Formy normalizacji Unicode i dwie metody dostępu do nich.

    wprowadź opis zdjęcia tutaj

    s2 = Normalizer.normalize(s2, Normalizer.Form.NFKC);

Testowanie napisów i danych wyjściowych w różnych podejściach, takich jak d Unidecode, Normalizer, StringUtils .

String strUni = "Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß";

// This is a funky String AE,O,D,ss
String initials = Unidecode.decode( strUni );

// Following Produce this o/p: Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß
String temp = Normalizer.normalize(strUni, Normalizer.Form.NFD);
Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
temp = pattern.matcher(temp).replaceAll("");

String input = org.apache.commons.lang3.StringUtils.stripAccents( strUni );

Użycie Unidecode to best choiceMój kod końcowy pokazany poniżej.

public static void main(String[] args) {
    String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
    String initials = Unidecode.decode( s2 );
    if( s1.equals(s2)) { //[ , ] %A0 - %2C - %20 « http://www.ascii-code.com/
        System.out.println("Equal Unicode Strings");
    } else if( s1.equals( initials ) ) {
        System.out.println("Equal Non Unicode Strings");
    } else {
        System.out.println("Not Equal");
    }

}

3

Sugeruję kod Junidecode . Będzie obsługiwał nie tylko „Ł” i „Ø”, ale również dobrze sprawdza się w przypadku transkrypcji z innych alfabetów, takich jak chiński, na alfabet łaciński.


1
Wygląda obiecująco, ale chciałbym, żeby to był bardziej aktywny / utrzymany projekt i dostępny w Maven.
Phil

2

Rozwiązanie Davida Conrada jest najszybsze, jakie próbowałem użyć Normalizatora, ale ma błąd. Zasadniczo usuwa znaki, które nie są akcentami, na przykład chińskie znaki i inne litery, takie jak æ, są usuwane. Znaki, które chcemy usunąć, to znaki spacji, znaki, które nie zajmują dodatkowej szerokości w końcowym ciągu. Te znaki o zerowej szerokości w zasadzie łączą się w jakiś inny znak. Jeśli widzisz, że są odizolowane jako postać, na przykład jak ten `, zgaduję, że jest to połączone ze znakiem spacji.

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    String norm = Normalizer.normalize(string, Normalizer.Form.NFD);

    int j = 0;
    for (int i = 0, n = norm.length(); i < n; ++i) {
        char c = norm.charAt(i);
        int type = Character.getType(c);

        //Log.d(TAG,""+c);
        //by Ricardo, modified the character check for accents, ref: http://stackoverflow.com/a/5697575/689223
        if (type != Character.NON_SPACING_MARK){
            out[j] = c;
            j++;
        }
    }
    //Log.d(TAG,"normalized string:"+norm+"/"+new String(out));
    return new String(out);
}

1

Jednym z najlepszych sposobów używania wyrażeń regularnych i Normalizatora, jeśli nie masz biblioteki, jest:

    public String flattenToAscii(String s) {
                if(s == null || s.trim().length() == 0)
                        return "";
                return Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
}

Jest to bardziej wydajne niż replaceAll („[^ \ p {ASCII}]”, „”)) i jeśli nie potrzebujesz znaków diakrytycznych (tak jak w twoim przykładzie).

W przeciwnym razie musisz użyć wzorca p {ASCII}.

Pozdrowienia.


0

Myślę, że najlepszym rozwiązaniem jest konwersja każdego znaku na HEX i zastąpienie go innym HEX. Jest tak, ponieważ istnieją 2 typy pisania w Unicode:

Composite Unicode
Precomposed Unicode

Na przykład „Ồ” napisane przez Unicode kompozytowe różni się od „Ồ” napisanego przez wstępnie skomponowany Unicode. Możesz skopiować moje przykładowe znaki i przekonwertować je, aby zobaczyć różnicę.

In Composite Unicode, "Ồ" is combined from 2 char: Ô (U+00d4) and ̀ (U+0300)
In Precomposed Unicode, "Ồ" is single char (U+1ED2)

Opracowałem tę funkcję dla niektórych banków do konwersji informacji przed wysłaniem ich do banku podstawowego (zwykle nie obsługują Unicode) i napotkałem ten problem, gdy użytkownicy końcowi używają wielu typów Unicode do wprowadzania danych. Więc myślę, że konwersja na HEX i zamiana jest najbardziej niezawodnym sposobem.


-1

W przypadku, gdy ktoś próbuje to zrobić w kotlin, ten kod działa jak urok. Aby uniknąć niespójności, używam również .toUpperCase i Trim (). następnie rzutuję tę funkcję:

   fun stripAccents(s: String):String{

   if (s == null) {
      return "";
   }

val chars: CharArray = s.toCharArray()

var sb = StringBuilder(s)
var cont: Int = 0

while (chars.size > cont) {
    var c: kotlin.Char
    c = chars[cont]
    var c2:String = c.toString()
   //these are my needs, in case you need to convert other accents just Add new entries aqui
    c2 = c2.replace("Ã", "A")
    c2 = c2.replace("Õ", "O")
    c2 = c2.replace("Ç", "C")
    c2 = c2.replace("Á", "A")
    c2 = c2.replace("Ó", "O")
    c2 = c2.replace("Ê", "E")
    c2 = c2.replace("É", "E")
    c2 = c2.replace("Ú", "U")

    c = c2.single()
    sb.setCharAt(cont, c)
    cont++

}

return sb.toString()

}

aby skorzystać z tej zabawy, wyślij kod w ten sposób:

     var str: String
     str = editText.text.toString() //get the text from EditText
     str = str.toUpperCase().trim()

     str = stripAccents(str) //call the function
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.