Я пытаюсь преобразовать некоторые строки на французском канадском языке, и в основном я хотел бы иметь возможность убрать французские знаки ударения в буквах, сохранив букву. (Например, преобразовать é в e, чтобы crème brûlée стал creme brulee)
Как лучше всего этого добиться?
Поскольку большинство методов для достижения этого основаны на нормализации Unicode, этот документ, описывающий стандарт, может быть полезно прочитать: unicode.org/reports/tr15
Я думаю, что команда Azure устранила эту проблему, я попытался загрузить файл с таким именем «Mémo de la réunion.pdf», и операция прошла успешно.





Я не использовал этот метод, но Майкл Каплан описывает метод для этого в своем сообщении в блоге (с запутанным заголовком), в котором говорится об удалении диакритических знаков: Стриптиз - интересная работа (она же О значении бессмысленного, ака Все Mn символов не имеют пробелов, но некоторые из них больше без пробелов, чем другие)
static string RemoveDiacritics(string text)
{
var normalizedString = text.Normalize(NormalizationForm.FormD);
var stringBuilder = new StringBuilder();
foreach (var c in normalizedString)
{
var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}
Обратите внимание, что это продолжение его более ранней публикации: Удаление диакритических знаков ....
Подход использует String.Normalize для разделения входной строки на составляющие глифы (в основном отделяя «базовые» символы от диакритических знаков), а затем сканирует результат и сохраняет только базовые символы. Это немного сложно, но на самом деле вы сталкиваетесь с сложной проблемой.
Конечно, если вы ограничиваетесь французским, вам, вероятно, удастся обойтись простым табличным подходом в Как убрать акценты и тильду в C++ std :: string, как рекомендовано @David Dibben.
Спасибо, помечено как ответ. В основном моему приложению нужно было взять заголовок раздела для веб-сайта и изменить его на «имя просмотра», в котором наше флэш-приложение могло связать его с нашей панелью навигации. это именно то, что мне нужно.
Идеальное решение ... намного лучше, чем очистка на основе регулярных выражений, подобная этой myownpercept.com/2009/06/replacing-special-chars-string-csha rp
Почему необходимо выполнять каноническую композицию на результате: sb.ToString (). Normalize (NormalizationForm.FormC)? Отбросив метки без пробелов, разве нельзя просто вернуть sb.ToString ()? Или мне что-то здесь не хватает?
@SimonTewsi Сразу же возникает одна вещь: это будет означать, что у вас есть слоги хангыль, а не соединение Jamo, которое, помимо того, что требуется для NFC, также имеет лучшую поддержку шрифтов на практике.
Это не правильно. Немецкие иероглифы ä, ö и ü латинизируются как ae ue и oe, а не как a, o u ...
Также игнорируется польская буква ł.
Обе ссылки блога michkap мертвы.
Также игнорируется норвежский ø
Это зависит от настроек персонализации ПК? На моем английском рабочем столе этот метод работает. Для ввода ä, ö, ü, ø я получаю вывод a, o, u, o.
@ a.farkas2508: Да, но это неверно. ä должно быть ae, ö должно быть oe и ü должно быть ue, но на самом деле это a, o, u ...
@StefanSteiger Опишите, почему ä должно быть ae вместо a. Не должно ли разлагаться до ae?
@ IllidanS4: Это распространенное очень древнее соглашение, согласно которому, когда вы «латинируете» немецкие символы ä, ö или ü, вы используете символ без ¨ и добавляете e; ä становится ae, ö становится oe, а ü становится ue. Точно так же, когда вы используете эти «комбинированные гласные» в немецком тексте, они произносятся как ä ö ü. æ наверное тоже надо разложить до ae. С другой стороны, какой бы ни был NormalizationForm.FormC;) Возможно, изменение формы поможет.
@StefanSteiger Знаете, в чешском есть такие буквы, как áčěů, которые мы обычно латинизируем до aceu, хотя это звучит по-другому и может вызывать путаницу в таких словах, как «hrábě» / hra: bje /, «hrabě» / hrabje /, и «грабе» / грабе /. Мне кажется, что удаление диакритических знаков - чисто графический вопрос, не зависящий от фонетики или истории письма. Такие буквы, как ä ö ü, были созданы добавлением надстрочного индекса «е» к основным буквам, таким образом, разложение «ае» имеет исторический смысл. Это зависит от цели - удалить графические метки или разложить букву на символы ASCII.
Эта функция не зависит от языка. Он не знает, написана ли строка на немецком или на другом языке. Если мы примем во внимание, что можно заменить ö на oe в немецком тексте, но нет никакого смысла делать это с турецким, то мы увидим, что без определения языка эта проблема на самом деле не разрешима.
Таким образом, это решение не работает для норвежского ø, польского ł, турецкого ı, азербайджанского ə и т. д. В основном, для любых букв без разделительных диакритических знаков.
@thorn: Ага, и это неправильно для немецкого языка - если вы не считаете ä "графически правильным";) Но это может быть хорошим способом нормализовать турецкий i.
Другой неработающий пример: Volaa (название альбома «Volaa Land» от художника Draugsól). Не уменьшается до o.
не забудьте добавить using System; using System.Globalization; using System.Text; using System.Text.RegularExpressions;
Я не думаю, что цель состоит в том, чтобы иметь идеально понятный способ его написания, а скорее в том, чтобы иметь что-то, что пройдет через большинство приложений. Я использую это для автоматизации, в основном для создания учетной записи и имени с диакритическими знаками и странными символами. Active Directory и многие другие приложения не очень хорошо воспринимают эти символы, поэтому при создании любой учетной записи я нормализую их к чему-то более «нормальному», например, для имени учетной записи.
По какой-то причине String.Normalize не работает на производственных сборках ?
См. Мой ответ ниже для метода расширения строки, который более подробно описывает, как и что он преобразует: stackoverflow.com/a/56797567/479701
@StefanSteiger: эта функция не предназначена для «латинизации» или «транслитерации» символов. Он просто предназначен для выполнения того, что он назвал - «RemoveDiacritics». То, чего вы от него ожидаете, - это совсем другая проблема. Ответ CIRCLE ниже может помочь вам больше.
после normalizedString мы можем просто вернуться в одном плавном синтаксисе, таком как этот return normalizedString.Aggregate(new StringBuilder(), (c,e) => CharUnicodeInfo.GetUnicodeCategory(e) == UnicodeCategory.NonSpacingMark ? c : c.Append(e)) .ToString().Normalize(NormalizationForm.FormC);
Если кому-то интересно, вот Java-эквивалент:
import java.text.Normalizer;
public class MyClass
{
public static String removeDiacritics(String input)
{
String nrml = Normalizer.normalize(input, Normalizer.Form.NFD);
StringBuilder stripped = new StringBuilder();
for (int i=0;i<nrml.length();++i)
{
if (Character.getType(nrml.charAt(i)) != Character.NON_SPACING_MARK)
{
stripped.append(nrml.charAt(i));
}
}
return stripped.toString();
}
}
вместо stripped + = nrml.charAt (i) используйте StringBuilder. у вас здесь спрятано время выполнения O (n²).
Этот и другие ответы Java здесь - это просто беспорядок в этой теме. Вопрос касается C# (.NET), а не Java!
Если кому-то интересно, я искал что-то подобное и закончил писать следующее:
public static string NormalizeStringForUrl(string name)
{
String normalizedString = name.Normalize(NormalizationForm.FormD);
StringBuilder stringBuilder = new StringBuilder();
foreach (char c in normalizedString)
{
switch (CharUnicodeInfo.GetUnicodeCategory(c))
{
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
stringBuilder.Append(c);
break;
case UnicodeCategory.SpaceSeparator:
case UnicodeCategory.ConnectorPunctuation:
case UnicodeCategory.DashPunctuation:
stringBuilder.Append('_');
break;
}
}
string result = stringBuilder.ToString();
return String.Join("_", result.Split(new char[] { '_' }
, StringSplitOptions.RemoveEmptyEntries)); // remove duplicate underscores
}
Вы должны предварительно выделить буфер StringBuilder для name.Length, чтобы минимизировать накладные расходы на выделение памяти. Интересен последний вызов Split / Join для удаления последовательного дубликата _. Возможно, нам просто не следует добавлять их в цикл. Установите флаг для предыдущего символа, являющегося _, и не выдавать его, если он истинен.
2 действительно хороших момента, я перепишу его, если когда-нибудь у меня будет время вернуться к этой части кода :)
Хороший. В дополнение к комментарию IDisposables нам, вероятно, следует проверить наличие c < 128, чтобы убедиться, что мы не забираем какие-либо символы UTF, глянь сюда.
Или, возможно, более эффективно c < 123. см. ASCI
Это отлично работает в java.
Он в основном преобразует все акцентированные символы в их деакцентированные аналоги, за которыми следуют их комбинированные диакритические знаки. Теперь вы можете использовать регулярное выражение, чтобы убрать диакритические знаки.
import java.text.Normalizer;
import java.util.regex.Pattern;
public String deAccent(String str) {
String nfdNormalizedString = Normalizer.normalize(str, Normalizer.Form.NFD);
Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
return pattern.matcher(nfdNormalizedString).replaceAll("");
}
Или в Java 7 "\\p{Block=CombiningDiacriticalMarks}"
Зачем размещать решение Java, если вопрос конкретно касается .NET?
@David Этот вопрос является самым популярным в Google по вопросу "java drop acnts". Не говорю, что это здесь место, но здесь полезно.
это помогло мне ...
string accentedStr;
byte[] tempBytes;
tempBytes = System.Text.Encoding.GetEncoding("ISO-8859-8").GetBytes(accentedStr);
string asciiStr = System.Text.Encoding.UTF8.GetString(tempBytes);
быстро и коротко!
почему вы используете ISO-8859-8 для кодирования строки и UTF8 для ее декодирования? Не лучше ли использовать ISO-8859-8 для декодирования массива байтов?
@DominikvonWeber, это всего лишь небольшая «проблема», но решение намного лучше, чем то, за которое проголосовали наибольшее количество голосов. этот, по крайней мере, работает для таких символов, как ł, ą, ó и т. д. :)
Это лучший метод, который я когда-либо видел.
Это работает для ł и ø, в отличие от тех, которые используют Normalize () в C#.
Мне нравится это решение, и оно хорошо работает для приложений Магазина Windows. Однако это не работает для приложений Windows Phone, поскольку кодировка ISO-8859-8, похоже, недоступна. Можно ли вместо этого использовать другую кодировку?
Я видел этот код следующим образом: tempBytes = Encoding.GetEncoding(1251).GetBytes(s); asciiStr = Encoding.ASCII.GetString(tempBytes); Мне кажется, что исходное кодирование сильно связано с используемой системой кодирования по умолчанию (к сожалению, это может отличаться в среде разработки и продукта).
Это будет работать для большинства распространенных символов, но многие специальные символы, такие как «» и … (как один символ), будут изменены в процессе, что не относится к принятому решению.
Обратите внимание, что это не работает в .NET Core в Linux: System.ArgumentException: 'ISO-8859-8' is not a supported encoding name.
Если вы используете .NET Core, установите System.Text.Encoding.CodePages из nuget, затем вызовите это, чтобы зарегистрировать поставщика: Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - как только вы это сделаете, вы можете использовать ISO-8859-8
Для UTF8 это работает, но как мы обрабатываем источник в кодировке ANSI?
Отлично, может кто-нибудь посоветует мне, как его использовать на StringBuilder? Пытался использовать, как показано ниже, но, похоже, не работает. StringBuilder sb = новый StringBuilder (); byte [] tempBytes; tempBytes = System.Text.Encoding.GetEncoding («ISO-8859-8»). GetBytes (sb); строка asciiStr = System.Text.Encoding.UTF8.GetString (tempBytes); sb.AppendLine (MyDataHeader); sb.AppendLine (MyDataString);
ЭТО ВЕРСИЯ VB (работает с GREEK):
Импортирует System.Text
Система импорта. Глобализация
Public Function RemoveDiacritics(ByVal s As String)
Dim normalizedString As String
Dim stringBuilder As New StringBuilder
normalizedString = s.Normalize(NormalizationForm.FormD)
Dim i As Integer
Dim c As Char
For i = 0 To normalizedString.Length - 1
c = normalizedString(i)
If CharUnicodeInfo.GetUnicodeCategory(c) <> UnicodeCategory.NonSpacingMark Then
stringBuilder.Append(c)
End If
Next
Return stringBuilder.ToString()
End Function
Может быть, старый ответ, но почему вы используете отдельные строки для объявления переменных и первого присваивания?
Я часто использую метод расширения, основанный на другой версии, которую я нашел здесь (см. Замена символов в C# (ascii)) Краткое объяснение:
Код:
using System.Linq;
using System.Text;
using System.Globalization;
// namespace here
public static class Utility
{
public static string RemoveDiacritics(this string str)
{
if (null == str) return null;
var chars =
from c in str.Normalize(NormalizationForm.FormD).ToCharArray()
let uc = CharUnicodeInfo.GetUnicodeCategory(c)
where uc != UnicodeCategory.NonSpacingMark
select c;
var cleanStr = new string(chars.ToArray()).Normalize(NormalizationForm.FormC);
return cleanStr;
}
// or, alternatively
public static string RemoveDiacritics2(this string str)
{
if (null == str) return null;
var chars = str
.Normalize(NormalizationForm.FormD)
.ToCharArray()
.Where(c=> CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
.ToArray();
return new string(chars).Normalize(NormalizationForm.FormC);
}
}
Попробуйте Пакет HelperSharp.
Есть метод RemoveAccents:
public static string RemoveAccents(this string source)
{
//8 bit characters
byte[] b = Encoding.GetEncoding(1251).GetBytes(source);
// 7 bit characters
string t = Encoding.ASCII.GetString(b);
Regex re = new Regex("[^a-zA-Z0-9]=-_/");
string c = re.Replace(t, " ");
return c;
}
Вот как я заменяю диакритические символы на недиакритические во всех моих программах .NET.
C#:
//Transforms the culture of a letter to its equivalent representation in the 0-127 ascii table, such as the letter 'é' is substituted by an 'e'
public string RemoveDiacritics(string s)
{
string normalizedString = null;
StringBuilder stringBuilder = new StringBuilder();
normalizedString = s.Normalize(NormalizationForm.FormD);
int i = 0;
char c = '\0';
for (i = 0; i <= normalizedString.Length - 1; i++)
{
c = normalizedString[i];
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
{
stringBuilder.Append(c);
}
}
return stringBuilder.ToString().ToLower();
}
VB .NET:
'Transforms the culture of a letter to its equivalent representation in the 0-127 ascii table, such as the letter "é" is substituted by an "e"'
Public Function RemoveDiacritics(ByVal s As String) As String
Dim normalizedString As String
Dim stringBuilder As New StringBuilder
normalizedString = s.Normalize(NormalizationForm.FormD)
Dim i As Integer
Dim c As Char
For i = 0 To normalizedString.Length - 1
c = normalizedString(i)
If CharUnicodeInfo.GetUnicodeCategory(c) <> UnicodeCategory.NonSpacingMark Then
stringBuilder.Append(c)
End If
Next
Return stringBuilder.ToString().ToLower()
End Function
вы можете использовать расширение строки из пакета nuget MMLib.Extensions:
using MMLib.RapidPrototyping.Generators;
public void ExtensionsExample()
{
string target = "aácčeéií";
Assert.AreEqual("aacceeii", target.RemoveDiacritics());
}
Страница Nuget: https://www.nuget.org/packages/MMLib.Extensions/ Сайт проекта Codeplex https://mmlib.codeplex.com/
Imports System.Text
Imports System.Globalization
Public Function DECODE(ByVal x As String) As String
Dim sb As New StringBuilder
For Each c As Char In x.Normalize(NormalizationForm.FormD).Where(Function(a) CharUnicodeInfo.GetUnicodeCategory(a) <> UnicodeCategory.NonSpacingMark)
sb.Append(c)
Next
Return sb.ToString()
End Function
Использование NFD вместо NFC приведет к изменениям, выходящим далеко за рамки запрошенных.
Encoding.ASCII.GetString(Encoding.GetEncoding(1251).GetBytes(text));
Фактически он разделяет подобные å, состоящие из одного символа (это код символа 00E5, нет0061 плюс модификатор 030A, который будет выглядеть одинаково) на a плюс какой-то модификатор, а затем преобразование ASCII удаляет модификатор, оставляя единственный a.
Мне нужно было что-то, что преобразует все основные символы Unicode, и проголосовавший ответ оставил некоторые из них, поэтому я создал версию CodeIgniter convert_accented_characters($str) на C#, которая легко настраивается:
using System;
using System.Text;
using System.Collections.Generic;
public static class Strings
{
static Dictionary<string, string> foreign_characters = new Dictionary<string, string>
{
{ "äæǽ", "ae" },
{ "öœ", "oe" },
{ "ü", "ue" },
{ "Ä", "Ae" },
{ "Ü", "Ue" },
{ "Ö", "Oe" },
{ "ÀÁÂÃÄÅǺĀĂĄǍΑΆẢẠẦẪẨẬẰẮẴẲẶА", "A" },
{ "àáâãåǻāăąǎªαάảạầấẫẩậằắẵẳặа", "a" },
{ "Б", "B" },
{ "б", "b" },
{ "ÇĆĈĊČ", "C" },
{ "çćĉċč", "c" },
{ "Д", "D" },
{ "д", "d" },
{ "ÐĎĐΔ", "Dj" },
{ "ðďđδ", "dj" },
{ "ÈÉÊËĒĔĖĘĚΕΈẼẺẸỀẾỄỂỆЕЭ", "E" },
{ "èéêëēĕėęěέεẽẻẹềếễểệеэ", "e" },
{ "Ф", "F" },
{ "ф", "f" },
{ "ĜĞĠĢΓГҐ", "G" },
{ "ĝğġģγгґ", "g" },
{ "ĤĦ", "H" },
{ "ĥħ", "h" },
{ "ÌÍÎÏĨĪĬǏĮİΗΉΊΙΪỈỊИЫ", "I" },
{ "ìíîïĩīĭǐįıηήίιϊỉịиыї", "i" },
{ "Ĵ", "J" },
{ "ĵ", "j" },
{ "ĶΚК", "K" },
{ "ķκк", "k" },
{ "ĹĻĽĿŁΛЛ", "L" },
{ "ĺļľŀłλл", "l" },
{ "М", "M" },
{ "м", "m" },
{ "ÑŃŅŇΝН", "N" },
{ "ñńņňʼnνн", "n" },
{ "ÒÓÔÕŌŎǑŐƠØǾΟΌΩΏỎỌỒỐỖỔỘỜỚỠỞỢО", "O" },
{ "òóôõōŏǒőơøǿºοόωώỏọồốỗổộờớỡởợо", "o" },
{ "П", "P" },
{ "п", "p" },
{ "ŔŖŘΡР", "R" },
{ "ŕŗřρр", "r" },
{ "ŚŜŞȘŠΣС", "S" },
{ "śŝşșšſσςс", "s" },
{ "ȚŢŤŦτТ", "T" },
{ "țţťŧт", "t" },
{ "ÙÚÛŨŪŬŮŰŲƯǓǕǗǙǛŨỦỤỪỨỮỬỰУ", "U" },
{ "ùúûũūŭůűųưǔǖǘǚǜυύϋủụừứữửựу", "u" },
{ "ÝŸŶΥΎΫỲỸỶỴЙ", "Y" },
{ "ýÿŷỳỹỷỵй", "y" },
{ "В", "V" },
{ "в", "v" },
{ "Ŵ", "W" },
{ "ŵ", "w" },
{ "ŹŻŽΖЗ", "Z" },
{ "źżžζз", "z" },
{ "ÆǼ", "AE" },
{ "ß", "ss" },
{ "IJ", "IJ" },
{ "ij", "ij" },
{ "Œ", "OE" },
{ "ƒ", "f" },
{ "ξ", "ks" },
{ "π", "p" },
{ "β", "v" },
{ "μ", "m" },
{ "ψ", "ps" },
{ "Ё", "Yo" },
{ "ё", "yo" },
{ "Є", "Ye" },
{ "є", "ye" },
{ "Ї", "Yi" },
{ "Ж", "Zh" },
{ "ж", "zh" },
{ "Х", "Kh" },
{ "х", "kh" },
{ "Ц", "Ts" },
{ "ц", "ts" },
{ "Ч", "Ch" },
{ "ч", "ch" },
{ "Ш", "Sh" },
{ "ш", "sh" },
{ "Щ", "Shch" },
{ "щ", "shch" },
{ "ЪъЬь", "" },
{ "Ю", "Yu" },
{ "ю", "yu" },
{ "Я", "Ya" },
{ "я", "ya" },
};
public static char RemoveDiacritics(this char c){
foreach(KeyValuePair<string, string> entry in foreign_characters)
{
if (entry.Key.IndexOf (c) != -1)
{
return entry.Value[0];
}
}
return c;
}
public static string RemoveDiacritics(this string s)
{
//StringBuilder sb = new StringBuilder ();
string text = "";
foreach (char c in s)
{
int len = text.Length;
foreach(KeyValuePair<string, string> entry in foreign_characters)
{
if (entry.Key.IndexOf (c) != -1)
{
text += entry.Value;
break;
}
}
if (len == text.Length) {
text += c;
}
}
return text;
}
}
использование
// for strings
"crème brûlée".RemoveDiacritics (); // creme brulee
// for chars
"Ã"[0].RemoveDiacritics (); // A
Ваша реализация выполняет свою работу, но ее следует улучшить перед использованием в производственном коде.
почему бы просто не заменить этот if (entry.Key.IndexOf(c) != -1) на if (entry.Key.Contains(c))
Почему бы не использовать повторно RemoveDiacritics (char c) в цикле, почему бы не использовать StringBuilder. Я голосую за сложный словарь и рабочее решение, но код мог бы быть намного проще
Это решение предполагает, что вы знаете, что нужно обрабатывать диакритический знак ... Предполагая, что вы запускаете это со списком из многих имен, вам потребуется дополнительная логика для идентификации этих имен диакритическим знаком, чтобы добавить в это решение.
Я использовал ссылку @ Alexander, чтобы ответить ниже: stackoverflow.com/a/56797567/479701
Я не понимаю, почему так много обручей, чтобы использовать { "äæǽ", "ae" } вместо { "ä", "ae" }, { "æ", "ae" }, { "ǽ", "ae" } и просто звонить if (foreign_characters.TryGetValue(...)) .... Вы полностью лишили смысла индекс, который уже есть в словаре.
"иностранный"? Как грубо! Может быть, чужое для тебя! Также разные языки имеют разные правила.
Могу ли я использовать это в своем личном проекте?
@Jotarata конечно;)
@BaconBits Еще лучше было бы использовать в качестве типа ключа char, а не строку.
CodePage Греческий (ISO) может это сделать
Информация об этой кодовой странице находится в System.Text.Encoding.GetEncodings(). Подробнее об этом: https://msdn.microsoft.com/pt-br/library/system.text.encodinginfo.getencoding(v=vs.110).aspx
Греческий (ISO) имеет кодовую страницу 28597 и имя iso-8859-7.
Перейти к коду ... \ o /
string text = "Você está numa situação lamentável";
string textEncode = System.Web.HttpUtility.UrlEncode(text, Encoding.GetEncoding("iso-8859-7"));
//result: "Voce+esta+numa+situacao+lamentavel"
string textDecode = System.Web.HttpUtility.UrlDecode(textEncode);
//result: "Voce esta numa situacao lamentavel"
Итак, напишите эту функцию ...
public string RemoveAcentuation(string text)
{
return
System.Web.HttpUtility.UrlDecode(
System.Web.HttpUtility.UrlEncode(
text, Encoding.GetEncoding("iso-8859-7")));
}
Обратите внимание, что ... Encoding.GetEncoding("iso-8859-7") эквивалентен Encoding.GetEncoding(28597), потому что первое - это имя, а второе - кодовая страница кодирования.
Это блестяще! Коротко и эффективно!
Отличный материал. Практически все персонажи, которые я тестировал, прошли успешно. (äáčďěéíľľňôóřŕšťúůýž ÄÁČĎĚÉÍĽĽŇÔÓŘŔŠŤÚŮÝŽ ÖÜË łŁđĐ ţŢşŞçÇ øı). Проблемы были обнаружены только с ßə, которые преобразованы в ?, но такие исключения всегда можно обрабатывать отдельно. Прежде чем запускать это в производство, лучше провести тест для всех областей Unicode, содержащих буквы с диакритическими знаками.
Забавно, что на такой вопрос можно получить так много ответов, но ни один из них не соответствует моим требованиям :) Существует так много языков, полное языковое независимое решение, AFAIK, на самом деле невозможно, поскольку другие упоминали, что FormC или FormD вызывают проблемы.
Поскольку исходный вопрос был связан с французским языком, самый простой рабочий ответ действительно
public static string ConvertWesternEuropeanToASCII(this string str)
{
return Encoding.ASCII.GetString(Encoding.GetEncoding(1251).GetBytes(str));
}
1251 следует заменить кодом кодировки языка ввода.
Однако это заменяет только один символ одним символом. Поскольку я также работаю с немецким языком в качестве ввода, я сделал ручное преобразование
public static string LatinizeGermanCharacters(this string str)
{
StringBuilder sb = new StringBuilder(str.Length);
foreach (char c in str)
{
switch (c)
{
case 'ä':
sb.Append("ae");
break;
case 'ö':
sb.Append("oe");
break;
case 'ü':
sb.Append("ue");
break;
case 'Ä':
sb.Append("Ae");
break;
case 'Ö':
sb.Append("Oe");
break;
case 'Ü':
sb.Append("Ue");
break;
case 'ß':
sb.Append("ss");
break;
default:
sb.Append(c);
break;
}
}
return sb.ToString();
}
Возможно, он не обеспечивает лучшую производительность, но, по крайней мере, его очень легко читать и расширять. Regex - это НЕЛЬЗЯ, намного медленнее, чем любые символы char / string.
У меня также есть очень простой способ удалить пробел:
public static string RemoveSpace(this string str)
{
return str.Replace(" ", string.Empty);
}
В конце концов, я использую комбинацию всех трех вышеуказанных расширений:
public static string LatinizeAndConvertToASCII(this string str, bool keepSpace = false)
{
str = str.LatinizeGermanCharacters().ConvertWesternEuropeanToASCII();
return keepSpace ? str : str.RemoveSpace();
}
И небольшой юнит-тест для тех (не исчерпывающих), которые прошли успешно.
[TestMethod()]
public void LatinizeAndConvertToASCIITest()
{
string europeanStr = "Bonjour ça va? C'est l'été! Ich möchte ä Ä á à â ê é è ë Ë É ï Ï î í ì ó ò ô ö Ö Ü ü ù ú û Û ý Ý ç Ç ñ Ñ";
string expected = "Bonjourcava?C'estl'ete!IchmoechteaeAeaaaeeeeEEiIiiiooooeOeUeueuuuUyYcCnN";
string actual = europeanStr.LatinizeAndConvertToASCII();
Assert.AreEqual(expected, actual);
}
Мне очень нравится лаконичный и функциональный код, предоставляемый azrafe7. Итак, я немного изменил его, чтобы преобразовать в метод расширения:
public static class StringExtensions
{
public static string RemoveDiacritics(this string text)
{
const string SINGLEBYTE_LATIN_ASCII_ENCODING = "ISO-8859-8";
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
return Encoding.ASCII.GetString(
Encoding.GetEncoding(SINGLEBYTE_LATIN_ASCII_ENCODING).GetBytes(text));
}
}
Это единственный метод, который работает со всеми польскими диакритическими знаками. Принятый ответ не работает с символами Ł и ł.
Добавьте сюда эту библиотеку, если вы еще не думали об этом. Похоже, с ним есть полный набор юнит-тестов.
Не имея достаточной репутации, видимо не могу комментировать отличную ссылку Александра. - Lucene кажется единственным решением, работающим в достаточно общих случаях.
Для тех, кто хочет простое решение для копирования и вставки, вот оно, используя код в Lucene:
string testbed = "ÁÂÄÅÉÍÎÓÖØÚÜÞàáâãäåæçèéêëìíîïðñóôöøúüāăčĐęğıŁłńŌōřŞşšźžșțệủ";
Console.WriteLine (Lucene.latinizeLucene (стенд));
AAAACEIIOOOUUTHaaaaaaaeceeeeiiiidnoooouuaacDegiLlnOorSsszzsteu
//////////
public static class Lucene
{
// source: https://raw.githubusercontent.com/apache/lucenenet/master/src/Lucene.Net.Analysis.Common/Analysis/Miscellaneous/ASCIIFoldingFilter.cs
// idea: https://stackoverflow.com/questions/249087/how-do-i-remove-diacritics-accents-from-a-string-in-net (scroll down, search for lucene by Alexander)
public static string latinizeLucene(string arg)
{
char[] argChar = arg.ToCharArray();
// latinizeLuceneImpl can expand one char up to four chars - e.g. Þ to TH, or æ to ae, or in fact ⑽ to (10)
char[] resultChar = new String(' ', arg.Length * 4).ToCharArray();
int outputPos = Lucene.latinizeLuceneImpl(argChar, 0, ref resultChar, 0, arg.Length);
string ret = new string(resultChar);
ret = ret.Substring(0, outputPos);
return ret;
}
/// <summary>
/// Converts characters above ASCII to their ASCII equivalents. For example,
/// accents are removed from accented characters.
/// <para/>
/// @lucene.internal
/// </summary>
/// <param name = "input"> The characters to fold </param>
/// <param name = "inputPos"> Index of the first character to fold </param>
/// <param name = "output"> The result of the folding. Should be of size >= <c>length * 4</c>. </param>
/// <param name = "outputPos"> Index of output where to put the result of the folding </param>
/// <param name = "length"> The number of characters to fold </param>
/// <returns> length of output </returns>
private static int latinizeLuceneImpl(char[] input, int inputPos, ref char[] output, int outputPos, int length)
{
int end = inputPos + length;
for (int pos = inputPos; pos < end; ++pos)
{
char c = input[pos];
// Quick test: if it's not in range then just keep current character
if (c < '\u0080')
{
output[outputPos++] = c;
}
else
{
switch (c)
{
case '\u00C0': // À [LATIN CAPITAL LETTER A WITH GRAVE]
case '\u00C1': // Á [LATIN CAPITAL LETTER A WITH ACUTE]
case '\u00C2': // Â [LATIN CAPITAL LETTER A WITH CIRCUMFLEX]
case '\u00C3': // Ã [LATIN CAPITAL LETTER A WITH TILDE]
case '\u00C4': // Ä [LATIN CAPITAL LETTER A WITH DIAERESIS]
case '\u00C5': // Å [LATIN CAPITAL LETTER A WITH RING ABOVE]
case '\u0100': // Ā [LATIN CAPITAL LETTER A WITH MACRON]
case '\u0102': // Ă [LATIN CAPITAL LETTER A WITH BREVE]
case '\u0104': // Ą [LATIN CAPITAL LETTER A WITH OGONEK]
case '\u018F': // Ə http://en.wikipedia.org/wiki/Schwa [LATIN CAPITAL LETTER SCHWA]
case '\u01CD': // Ǎ [LATIN CAPITAL LETTER A WITH CARON]
case '\u01DE': // Ǟ [LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON]
case '\u01E0': // Ǡ [LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON]
case '\u01FA': // Ǻ [LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE]
case '\u0200': // Ȁ [LATIN CAPITAL LETTER A WITH DOUBLE GRAVE]
case '\u0202': // Ȃ [LATIN CAPITAL LETTER A WITH INVERTED BREVE]
case '\u0226': // Ȧ [LATIN CAPITAL LETTER A WITH DOT ABOVE]
case '\u023A': // Ⱥ [LATIN CAPITAL LETTER A WITH STROKE]
case '\u1D00': // ᴀ [LATIN LETTER SMALL CAPITAL A]
case '\u1E00': // Ḁ [LATIN CAPITAL LETTER A WITH RING BELOW]
case '\u1EA0': // Ạ [LATIN CAPITAL LETTER A WITH DOT BELOW]
case '\u1EA2': // Ả [LATIN CAPITAL LETTER A WITH HOOK ABOVE]
case '\u1EA4': // Ấ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE]
case '\u1EA6': // Ầ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE]
case '\u1EA8': // Ẩ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
case '\u1EAA': // Ẫ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE]
case '\u1EAC': // Ậ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
case '\u1EAE': // Ắ [LATIN CAPITAL LETTER A WITH BREVE AND ACUTE]
case '\u1EB0': // Ằ [LATIN CAPITAL LETTER A WITH BREVE AND GRAVE]
case '\u1EB2': // Ẳ [LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE]
case '\u1EB4': // Ẵ [LATIN CAPITAL LETTER A WITH BREVE AND TILDE]
case '\u1EB6': // Ặ [LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW]
case '\u24B6': // Ⓐ [CIRCLED LATIN CAPITAL LETTER A]
case '\uFF21': // A [FULLWIDTH LATIN CAPITAL LETTER A]
output[outputPos++] = 'A';
break;
case '\u00E0': // à [LATIN SMALL LETTER A WITH GRAVE]
case '\u00E1': // á [LATIN SMALL LETTER A WITH ACUTE]
case '\u00E2': // â [LATIN SMALL LETTER A WITH CIRCUMFLEX]
case '\u00E3': // ã [LATIN SMALL LETTER A WITH TILDE]
case '\u00E4': // ä [LATIN SMALL LETTER A WITH DIAERESIS]
case '\u00E5': // å [LATIN SMALL LETTER A WITH RING ABOVE]
case '\u0101': // ā [LATIN SMALL LETTER A WITH MACRON]
case '\u0103': // ă [LATIN SMALL LETTER A WITH BREVE]
case '\u0105': // ą [LATIN SMALL LETTER A WITH OGONEK]
case '\u01CE': // ǎ [LATIN SMALL LETTER A WITH CARON]
case '\u01DF': // ǟ [LATIN SMALL LETTER A WITH DIAERESIS AND MACRON]
case '\u01E1': // ǡ [LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON]
case '\u01FB': // ǻ [LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE]
case '\u0201': // ȁ [LATIN SMALL LETTER A WITH DOUBLE GRAVE]
case '\u0203': // ȃ [LATIN SMALL LETTER A WITH INVERTED BREVE]
case '\u0227': // ȧ [LATIN SMALL LETTER A WITH DOT ABOVE]
case '\u0250': // ɐ [LATIN SMALL LETTER TURNED A]
case '\u0259': // ə [LATIN SMALL LETTER SCHWA]
case '\u025A': // ɚ [LATIN SMALL LETTER SCHWA WITH HOOK]
case '\u1D8F': // ᶏ [LATIN SMALL LETTER A WITH RETROFLEX HOOK]
case '\u1D95': // ᶕ [LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK]
case '\u1E01': // ạ [LATIN SMALL LETTER A WITH RING BELOW]
case '\u1E9A': // ả [LATIN SMALL LETTER A WITH RIGHT HALF RING]
case '\u1EA1': // ạ [LATIN SMALL LETTER A WITH DOT BELOW]
case '\u1EA3': // ả [LATIN SMALL LETTER A WITH HOOK ABOVE]
case '\u1EA5': // ấ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE]
case '\u1EA7': // ầ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE]
case '\u1EA9': // ẩ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE]
case '\u1EAB': // ẫ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE]
case '\u1EAD': // ậ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW]
case '\u1EAF': // ắ [LATIN SMALL LETTER A WITH BREVE AND ACUTE]
case '\u1EB1': // ằ [LATIN SMALL LETTER A WITH BREVE AND GRAVE]
case '\u1EB3': // ẳ [LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE]
case '\u1EB5': // ẵ [LATIN SMALL LETTER A WITH BREVE AND TILDE]
case '\u1EB7': // ặ [LATIN SMALL LETTER A WITH BREVE AND DOT BELOW]
case '\u2090': // ₐ [LATIN SUBSCRIPT SMALL LETTER A]
case '\u2094': // ₔ [LATIN SUBSCRIPT SMALL LETTER SCHWA]
case '\u24D0': // ⓐ [CIRCLED LATIN SMALL LETTER A]
case '\u2C65': // ⱥ [LATIN SMALL LETTER A WITH STROKE]
case '\u2C6F': // Ɐ [LATIN CAPITAL LETTER TURNED A]
case '\uFF41': // a [FULLWIDTH LATIN SMALL LETTER A]
output[outputPos++] = 'a';
break;
case '\uA732': // Ꜳ [LATIN CAPITAL LETTER AA]
output[outputPos++] = 'A';
output[outputPos++] = 'A';
break;
case '\u00C6': // Æ [LATIN CAPITAL LETTER AE]
case '\u01E2': // Ǣ [LATIN CAPITAL LETTER AE WITH MACRON]
case '\u01FC': // Ǽ [LATIN CAPITAL LETTER AE WITH ACUTE]
case '\u1D01': // ᴁ [LATIN LETTER SMALL CAPITAL AE]
output[outputPos++] = 'A';
output[outputPos++] = 'E';
break;
case '\uA734': // Ꜵ [LATIN CAPITAL LETTER AO]
output[outputPos++] = 'A';
output[outputPos++] = 'O';
break;
case '\uA736': // Ꜷ [LATIN CAPITAL LETTER AU]
output[outputPos++] = 'A';
output[outputPos++] = 'U';
break;
// etc. etc. etc.
// see link above for complete source code
//
// unfortunately, postings are limited, as in
// "Body is limited to 30000 characters; you entered 136098."
[...]
case '\u2053': // ⁓ [SWUNG DASH]
case '\uFF5E': // ~ [FULLWIDTH TILDE]
output[outputPos++] = '~';
break;
default:
output[outputPos++] = c;
break;
}
}
}
return outputPos;
}
}
TL; DR - C# метод расширения строки
Я думаю, что лучшим решением для сохранения значения строки является преобразование символов, а не их удаление, что хорошо проиллюстрировано в примере crème brûlée в crme brle против creme brulee.
Я проверил Комментарий Александра выше и увидел, что код Lucene.Net имеет лицензию Apache 2.0, поэтому я преобразовал класс в простой метод расширения строки. Вы можете использовать это так:
var originalString = "crème brûlée";
var maxLength = originalString.Length; // limit output length as necessary
var foldedString = originalString.FoldToASCII(maxLength);
// "creme brulee"
Функция слишком длинная для публикации в ответе StackOverflow (~ 139 тыс. Символов из 30 тыс. Разрешенных lol), поэтому Я сформулировал суть и приписал авторов:
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>
/// This class converts alphabetic, numeric, and symbolic Unicode characters
/// which are not in the first 127 ASCII characters (the "Basic Latin" Unicode
/// block) into their ASCII equivalents, if one exists.
/// <para/>
/// Characters from the following Unicode blocks are converted; however, only
/// those characters with reasonable ASCII alternatives are converted:
///
/// <ul>
/// <item><description>C1 Controls and Latin-1 Supplement: <a href = "http://www.unicode.org/charts/PDF/U0080.pdf">http://www.unicode.org/charts/PDF/U0080.pdf</a></description></item>
/// <item><description>Latin Extended-A: <a href = "http://www.unicode.org/charts/PDF/U0100.pdf">http://www.unicode.org/charts/PDF/U0100.pdf</a></description></item>
/// <item><description>Latin Extended-B: <a href = "http://www.unicode.org/charts/PDF/U0180.pdf">http://www.unicode.org/charts/PDF/U0180.pdf</a></description></item>
/// <item><description>Latin Extended Additional: <a href = "http://www.unicode.org/charts/PDF/U1E00.pdf">http://www.unicode.org/charts/PDF/U1E00.pdf</a></description></item>
/// <item><description>Latin Extended-C: <a href = "http://www.unicode.org/charts/PDF/U2C60.pdf">http://www.unicode.org/charts/PDF/U2C60.pdf</a></description></item>
/// <item><description>Latin Extended-D: <a href = "http://www.unicode.org/charts/PDF/UA720.pdf">http://www.unicode.org/charts/PDF/UA720.pdf</a></description></item>
/// <item><description>IPA Extensions: <a href = "http://www.unicode.org/charts/PDF/U0250.pdf">http://www.unicode.org/charts/PDF/U0250.pdf</a></description></item>
/// <item><description>Phonetic Extensions: <a href = "http://www.unicode.org/charts/PDF/U1D00.pdf">http://www.unicode.org/charts/PDF/U1D00.pdf</a></description></item>
/// <item><description>Phonetic Extensions Supplement: <a href = "http://www.unicode.org/charts/PDF/U1D80.pdf">http://www.unicode.org/charts/PDF/U1D80.pdf</a></description></item>
/// <item><description>General Punctuation: <a href = "http://www.unicode.org/charts/PDF/U2000.pdf">http://www.unicode.org/charts/PDF/U2000.pdf</a></description></item>
/// <item><description>Superscripts and Subscripts: <a href = "http://www.unicode.org/charts/PDF/U2070.pdf">http://www.unicode.org/charts/PDF/U2070.pdf</a></description></item>
/// <item><description>Enclosed Alphanumerics: <a href = "http://www.unicode.org/charts/PDF/U2460.pdf">http://www.unicode.org/charts/PDF/U2460.pdf</a></description></item>
/// <item><description>Dingbats: <a href = "http://www.unicode.org/charts/PDF/U2700.pdf">http://www.unicode.org/charts/PDF/U2700.pdf</a></description></item>
/// <item><description>Supplemental Punctuation: <a href = "http://www.unicode.org/charts/PDF/U2E00.pdf">http://www.unicode.org/charts/PDF/U2E00.pdf</a></description></item>
/// <item><description>Alphabetic Presentation Forms: <a href = "http://www.unicode.org/charts/PDF/UFB00.pdf">http://www.unicode.org/charts/PDF/UFB00.pdf</a></description></item>
/// <item><description>Halfwidth and Fullwidth Forms: <a href = "http://www.unicode.org/charts/PDF/UFF00.pdf">http://www.unicode.org/charts/PDF/UFF00.pdf</a></description></item>
/// </ul>
/// <para/>
/// See: <a href = "http://en.wikipedia.org/wiki/Latin_characters_in_Unicode">http://en.wikipedia.org/wiki/Latin_characters_in_Unicode</a>
/// <para/>
/// For example, '&agrave;' will be replaced by 'a'.
/// </summary>
public static partial class StringExtensions
{
/// <summary>
/// Converts characters above ASCII to their ASCII equivalents. For example,
/// accents are removed from accented characters.
/// </summary>
/// <param name = "input"> The string of characters to fold </param>
/// <param name = "length"> The length of the folded return string </param>
/// <returns> length of output </returns>
public static string FoldToASCII(this string input, int? length = null)
{
// See https://gist.github.com/andyraddatz/e6a396fb91856174d4e3f1bf2e10951c
}
}
Надеюсь, это поможет кому-то другому, это самое надежное решение, которое я нашел!
Предостережения: 1) Концепция зависит от локали. Например, «ä» может быть «а» или «аа». 2) Неправильно названо / неправильно описано: результат не обязательно только из блока C0 Controls и Basic Latin. Он преобразует только латинские буквы и некоторые варианты символов в «эквиваленты». (Конечно, потом можно было бы выполнить еще один проход, чтобы заменить или удалить не-C0 Controls и символы основного латинского блока.) Но это будет делать то, что делает хорошо.
Спасибо, что разместили это там. Я полагаю, у вас есть завершающая скобка } в конце файла.
Этот код работал у меня:
var updatedText = text.Normalize(NormalizationForm.FormD)
.Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
.ToArray();
Однако, пожалуйста, не делайте этого с именами. Это не только оскорбление людей с умляутами / акцентами в своем имени, это также может быть опасно неправильным в определенных ситуациях (см. Ниже). Есть альтернативные варианты написания вместо того, чтобы просто убрать акцент.
Более того, это просто неправильно и опасно, например если пользователь должен указать свое имя именно так, как оно указано в паспорте.
Например, мое имя написано Zuberbühler, а в машиночитаемой части моего паспорта вы найдете Zuberbuehler. Если убрать умлаут, имя не будет совпадать ни с одной из частей. Это может вызвать проблемы у пользователей.
Лучше запретить умляуты / акцент в форме ввода для имен, чтобы пользователь мог правильно написать свое имя без умляута или акцента.
Практический пример: если веб-служба для подачи заявки на ESTA (https://www.application-esta.co.uk/special-characters-and) будет использовать приведенный выше код вместо правильного преобразования умляутов, заявка ESTA будет либо отклонена, либо у путешественника возникнут проблемы с американским пограничным контролем при въезде в Штаты.
Другой пример - авиабилеты. Предполагая, что у вас есть веб-приложение для бронирования авиабилетов, пользователь указывает свое имя с акцентом, а ваша реализация просто убирает акценты, а затем использует веб-службу авиакомпании для бронирования билета! Вашему клиенту может быть отказано в посадке, поскольку имя не соответствует ни одной части его / ее паспорта.
Предупреждение: этот подход может работать в некоторых конкретных случаях, но в целом вы не можете просто удалить диакритические знаки. В некоторых случаях и на некоторых языках это может изменить смысл текста. Вы не говорите, почему хотите это сделать; если это делается для сравнения строк или поиска, вам, скорее всего, будет лучше использовать для этого библиотеку с поддержкой Unicode.