我试图转换一些字符串,在法国加拿大,基本上,我想能够拿出法国重音标记在字母,同时保持字母。(例如,将é转换为e,那么crème brûlée就会变成creme brulee)

实现这一目标的最佳方法是什么?


当前回答

如果有人感兴趣,我正在寻找类似的东西,最后写了如下:

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
}

其他回答

我没有使用过这种方法,但是Michael Kaplan在他的博客文章(有一个令人困惑的标题)中描述了一种方法,谈论剥离变音符:剥离是一项有趣的工作(又名剥离) 论无意义的意义,即一切 Mn字符是非空格的,但是 有些更非间距比 其他人)

static string RemoveDiacritics(string text) 
{
    var normalizedString = text.Normalize(NormalizationForm.FormD);
    var stringBuilder = new StringBuilder(capacity: normalizedString.Length);

    for (int i = 0; i < normalizedString.Length; i++)
    {
        char c = normalizedString[i];
        var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
        if (unicodeCategory != UnicodeCategory.NonSpacingMark)
        {
            stringBuilder.Append(c);
        }
    }

    return stringBuilder
        .ToString()
        .Normalize(NormalizationForm.FormC);
}

请注意,这是他之前帖子的后续:剥离变音符....

该方法使用String。Normalize将输入字符串分割为组成符号(基本上是将“基本”字符与变音符符分开),然后扫描结果并仅保留基本字符。这只是有点复杂,但实际上你看到的是一个复杂的问题。

当然,如果你限制自己使用法语,你可能会使用@David Dibben推荐的如何在c++ std::string中删除重音和波浪号的简单基于表的方法。

你可以从MMLib中使用字符串扩展。扩展nuget包:

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/

有趣的是,这样的问题可以得到这么多答案,但没有一个符合我的要求:)周围有这么多语言,一个完全语言不可知的解决方案是不可能的,因为其他人已经提到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是一个不去,比任何字符/字符串的东西慢得多。

我还有一个非常简单的方法来删除空间:

    public static string RemoveSpace(this string str)
    {
        return str.Replace(" ", string.Empty);
    }

最终,我使用以上3个扩展的组合:

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

与接受的答案相同,但更快,使用Span而不是StringBuilder。 需要。net Core 3.1或更新的。net。

static string RemoveDiacritics(string text) 
{
    ReadOnlySpan<char> normalizedString = text.Normalize(NormalizationForm.FormD);
    int i = 0;
    Span<char> span = text.Length < 1000
        ? stackalloc char[text.Length]
        : new char[text.Length];

    foreach (char c in normalizedString)
    {
        if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)
            span[i++] = c;
    }

    return new string(span).Normalize(NormalizationForm.FormC);
}

此外,这是可扩展的额外字符替换,如抛光Ł。

span[i++] = c switch
{
    'Ł' => 'L',
    'ł' => 'l',
    _ => c
};

一个小提示:堆栈分配stackalloc比堆分配new要快得多,它为垃圾收集器减少了工作。1000是一个阈值,以避免在堆栈上分配大结构,这可能会导致StackOverflowException。虽然1000是一个相当安全的值,但在大多数情况下10000甚至100000也可以(100k在堆栈上分配最多200kB,而默认堆栈大小为1mb)。然而10万对我来说有点危险。

为了像最初的问题一样简单地删除法语加拿大重音标记,这里有一个使用正则表达式而不是硬编码转换和For/Next循环的替代方法。根据您的需要,它可以被压缩成一行代码;但是,我将它添加到一个扩展类中,以便于重用。

Visual Basic

Imports System.Text
Imports System.Text.RegularExpressions

Public MustInherit Class StringExtension
    Public Shared Function RemoveDiacritics(Text As String) As String
        Return New Regex("\p{Mn}", RegexOptions.Compiled).Replace(Text.Normalize(NormalizationForm.FormD), String.Empty)
    End Function
End Class

实现

    Private Shared Sub DoStuff()
        MsgBox(StringExtension.RemoveDiacritics(inputString))
    End Sub

c#

using System.Text;
using System.Text.RegularExpressions;

namespace YourApplication
{
    public abstract class StringExtension
    {
        public static string RemoveDiacritics(string Text)
        {
            return new Regex(@"\p{Mn}", RegexOptions.Compiled).Replace(Text.Normalize(NormalizationForm.FormD), string.Empty);
        }
    }
}

实现

        private static void DoStuff()
        {
            MessageBox.Show(StringExtension.RemoveDiacritics(inputString));
        }

Input: äáčďěéíľľňôóřŕšťúůýž ÄÁČĎĚÉÍĽĽŇÔŘŔŠŤÚŮÝŽ ÖÜË łŁđĐ țŢşŞçÇ øı

Output: aacdeeillnoorrstuuyz AACDEEILLNOORRSTUUYZ OUE łŁđĐ tTsScC øı

我加入了无法转换的字符,以帮助可视化接收到意外输入时会发生什么。

如果您还需要它来转换其他类型的字符,如波兰语的warsaw和Ł,那么根据您的需要,可以考虑合并这个答案(。NET Core友好),它使用CodePagesEncodingProvider到您的解决方案中。