当使用ASCII编码并将字符串编码为字节时,诸如ö
之类的字符将生成为?
。
Encoding encoding = Encoding.GetEncoding("us-ascii"); // or Encoding encoding = Encoding.ASCI;
data = encoding.GetBytes(s);
我正在寻找一种用不同的字符替换这些字符的方法,而不仅仅是问号。
例子:
ä -> ae
ö -> oe
ü -> ue
ß -> ss
如果无法将一个字符替换为多个字符,我将接受甚至可以将一个字符替换为一个字符(
ö
-> o
)现在有
EncoderFallback
的几种实现,但是我不明白它们是如何工作的。一种快速而肮脏的解决方案是在将字符串提供给
Encoding.GetBytes()
之前替换所有这些字符,但这似乎不是“正确”的方法。我希望可以给编码对象一个替换表。
我该怎么做?
最佳答案
实现所需目标的“最正确”方法是实现一个自定义回退编码器,该编码器会执行最适合的回退。出于多种原因,内置到.NET中的那个对于它尝试最适合的字符是非常保守的(存在安全隐患,取决于您打算放置重新编码的字符串的用途。)您的自定义后备策略可以根据您想要的任何规则进行最合适的选择。
话虽如此-在您的后备类中,您将最终编写一个包含所有无法编码的Unicode代码点的大型case语句,然后手动将它们映射到最适合的替代方法。您可以通过简单地提前遍历字符串并替换掉不受支持的字符来实现相同的目标。后备策略的主要好处是性能:您最终只能在字符串中循环一次,而不是至少两次。但是,除非您的弦很大,否则我不会对此太担心。
如果您确实想实现自定义的后备策略,则一定要阅读我的评论中的文章Character Encoding in the .NET Framework。这并不难,但是您必须了解编码后备的工作方式。
您为Encoder.GetEncoding
方法提供了自定义类的实现,该类必须从EncoderFallback
派生。但是,该类基本上只是实际工作的包装,它是通过EncoderFallbackBuffer
完成的。您需要缓冲的原因是回退不一定是一对一的过程。在您的示例中,您可能最终将一个Unicode字符映射到两个ASCII字符。
在编码过程首先遇到问题并且需要依靠您的策略时,它使用EncoderFallback
实现创建EncoderFallbackBuffer
的实例。然后,它将调用自定义缓冲区的Fallback
方法。
在内部,缓冲区建立了一组要返回的字符来代替不可编码的字符,并返回true
。从那里开始,编码器将重复调用GetNextChar
,直到Remaining > 0
和/或直到GetNextChar
返回CP 0,然后将这些字符粘贴到编码结果中。
本文包含了您要执行的操作的几乎完全的实现。我已经复制了下面的基本框架,应该可以帮助您入门。
public class CustomMapper : EncoderFallback
{
// Use can override the "replacement character", so track what they
// give us.
public string DefaultString;
public CustomMapper() : this("*")
{
}
public CustomMapper(string defaultString)
{
this.DefaultString = defaultString;
}
public override EncoderFallbackBuffer CreateFallbackBuffer()
{
return new CustomMapperFallbackBuffer(this);
}
// This is the length of the largest possible replacement string we can
// return for a single Unicode code point.
public override int MaxCharCount
{
get { return 2; }
}
}
public class CustomMapperFallbackBuffer : EncoderFallbackBuffer
{
CustomMapper fb;
public CustomMapperFallbackBuffer(CustomMapper fallback)
{
// We can use the same custom buffer with different fallbacks, e.g.
// we might have different sets of replacement characters for different
// cases. This is just a reference to the parent in case we want it.
this.fb = fallback;
}
public override bool Fallback(char charUnknown, int index)
{
// Do the work of figuring out what sequence of characters should replace
// charUnknown. index is the position in the original string of this character,
// in case that's relevant.
// If we end up generating a sequence of replacement characters, return
// true, and the encoder will start calling GetNextChar. Otherwise return
// false.
// Alternatively, instead of returning false, you can simply extract
// DefaultString from this.fb and return that for failure cases.
}
public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
{
// Same as above, except we have a UTF-16 surrogate pair. Same rules
// apply: if we can map this pair, return true, otherwise return false.
// Most likely, you're going to return false here for an ASCII-type
// encoding.
}
public override char GetNextChar()
{
// Return the next character in our internal buffer of replacement
// characters waiting to be put into the encoded byte stream. If
// we're all out of characters, return '\u0000'.
}
public override bool MovePrevious()
{
// Back up to the previous character we returned and get ready
// to return it again. If that's possible, return true; if that's
// not possible (e.g. we have no previous character) return false;
}
public override int Remaining
{
// Return the number of characters that we've got waiting
// for the encoder to read.
get { return count < 0 ? 0 : count; }
}
public override void Reset()
{
// Reset our internal state back to the initial one.
}
}