我有一些以下要求:
... 文档必须以 UTF-8 编码... 姓氏字段仅允许(扩展) ASCII ... 城市仅允许 ISOLatin1 ...消息必须作为IBytesMessage放在(IBM Websphere)MessageQueue上
为简单起见,XML文档看起来像这样:
<?xml version="1.0" encoding="utf-8"?>
<foo>
<lastname>John ÐØë</lastname>
<city>John ÐØë</city>
<other>UTF-8 string</other>
</foo>
“ÐØë”部分分别是(或应该)ASCII values 208,216,235。
我也有一个对象:
public class foo {
public string lastname { get; set; }
}
所以我实例化一个对象并设置姓氏:
var x = new foo() { lastname = "John ÐØë", city = "John ÐØë" };
现在这就是我头痛的地方(或inception,如果你愿意......):
我的编码通常不会遇到任何问题;我熟悉The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)但是这个让我难过......
据我所知,UTF-8文档完全能够“包含”两种编码,因为代码点“重叠”。但是,当我需要将序列化消息转换为字节数组时,我迷失了。在进行转储时,我看到C3 XX C3 XX C3 XX
(我手边没有实际的转储)。很明显(或者我已经盯着这个太长时间了)lastname / city字符串以unicode形式放入序列化文档中;字节数组表明了这一点。
现在我需要做什么,以及确保Lastname字符串进入XML文档,最后将字节数组作为 ASCII 字符串(以及实际的208,216,235)字节顺序),那个城市在那里作为 ISOLatin1 ?
我知道要求是倒退的,但我无法改变(第三方)。我总是在内部项目中使用UTF-8,因此我必须支持unicode-utf8 =&gt; ASCII / ISOLatin1转换(当然,仅适用于那些集合中的字符)。
我的头疼...
答案 0 :(得分:5)
不管怎样编码XML文档进行传输。做你想做的事情的正确方法 - 对某些非ASCII字符进行编码,使它们在没有受到伤害的情况下幸存下来 - 使用XML character references来表示需要如此保留的字符。例如,你的
ÐØë
ÐØë
接收[conformant] XML处理器将/应该/必须将这些数字字符引用转换回它们所代表的字符。这里有一些代码可以解决这个问题:
public static string ConvertToXmlCharacterReference( this string xml )
{
StringBuilder sb = new StringBuilder( s.Length ) ;
const char SP = '\u0020' ; // anything lower than SP is a control character
const char DEL = '\u007F' ; // anything above DEL isn't ASCII, per se.
foreach( char ch in xml )
{
bool isPrintableAscii = ch >= SP && ch <= DEL ;
if ( isPrintableAscii ) { sb.Append(ch) ; }
else { sb.AppendFormat( "&#x{0:X4}" , (int) ch ) ; }
}
string instance = sb.ToString() ;
return instance ;
}
您还可以使用正则表达式来替换或编写可以执行相同操作的XSLT。但任务是如此微不足道,它并不能保证这种方法。上面的代码可能更快,内存更少,而且......更容易理解。
您应该注意,由于您希望在同一文档中保留两种不同的编码,因此您的转换例程需要区分从“扩展ASCII”到XML字符引用的转换以及从“ISO Latin 1”的转换到XML字符引用。
在这两种情况下,字符引用都指定ISO / IEC 10646字符集中的代码点 - 基本上是unicode。您需要将字符映射到适当的代码点。由于CLR世界中的字符串是UTF-16编码的,因此这应该不是什么大问题。我相信上面的代码应该可以正常工作,除非你得到一些与UTF-16不能很好搭配的奇怪的东西。
答案 1 :(得分:0)
所以... System.Text.Encoding.ASCII.GetBytes(string)
可能会做你想要的......将一个字符串转换成一个ascii编码的字节数组。
答案 2 :(得分:0)
你不能在UTF-8编码的字符串/字节数组中有208,216,235字节序列。
我希望您可以将XML保存为ISO 8859-1,无论是否在XML <?xml version="1.0" encoding="XXXXXXXXXX"?>
处理指令中提及它(甚至可能在XML标头中指定无效的UTF-8编码)。
否则,如果您的要求与您所说的一样 - 只需要为给定输入询问确切的预期字节数组,并制定您自己的自定义序列化(或者可能是自定义编码,也不确定是否可行)。
答案 3 :(得分:0)
文档必须以UTF-8编码。
Lastname
字段仅允许 ASCII。City
仅允许 ISOLatin1。该消息必须作为MessageQueue
放在(IBM Websphere)IBytesMessage
上。
如果这是精确的规格,那么我认为你可能会误解它。您的任务不是编码,而是验证/回退之一。 整个文档(包括Lastname
和City
字段)必须编码为UTF-8。很简单,如果XML文档将其编码声明为UTF-8,然后包含在该编码下无效的字节值,则XML文档将无效。
方便地,ASCII与Unicode的前128个码点重叠; Latin1与前256个重叠。
要检查Lastname
是否可以表示为ASCII,那么您可以检查其所有字符的代码点是否在0-127范围内。
bool isLastnameAscii = foo.Lastname.All(c => (int)c < 128);
为了符合您的规范,您必须通过将字符串编码为ASCII,然后将其解码回来强制无效字符回退到替换字符(通常为?
):
foo.Lastname = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(foo.Lastname));
同样适用于City
:
bool isCityLatin1 = foo.City.All(c => (int)c < 256);
Encoding latin1 = Encoding.GetEncoding("iso-8859-1");
foo.City = latin1.GetString(latin1.GetBytes(foo.City));
随后,您应该将所有内容保存为UTF-8。
我的假设是您的第三方软件可以使用UTF-8正确解码XML文档;但是,它必须提取Lastname
和City
字段,并在只允许使用ASCII和Latin1的地方使用它们。它对您施加了限制,以确保不会被迫导致数据丢失(因为存在不允许的字符)。
修改:这是您提议的解决方法。我使用Latin1代替“扩展ASCII”,因为后一个术语含糊不清。
var x = new foo() { lastname = "John ÐØë", city = "John ÐØë", other = "—" };
using (var stream = new MemoryStream())
using (var utf8writer = new StreamWriter(stream, Encoding.UTF8))
using (var latin1writer = new StreamWriter(stream, Encoding.GetEncoding("iso-8859-1")))
{
utf8writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
utf8writer.WriteLine("<foo>");
utf8writer.Flush();
latin1writer.WriteLine(" <lastname>" + SecurityElement.Escape(x.lastname) + "</lastname>");
latin1writer.WriteLine(" <city>" + SecurityElement.Escape(x.city) + "</city>");
latin1writer.Flush();
utf8writer.WriteLine(" <other>" + SecurityElement.Escape(x.other) + "</other>");
utf8writer.WriteLine("/<foo>");
utf8writer.Flush();
byte[] bytes = stream.ToArray();
}
SecurityElement.Escape
使用有效的XML等效项替换字符串中的无效XML字符(例如<
到<
和&
到&
。)
答案 4 :(得分:0)
我将此理解为2个单独的要求:
1)XML必须是UTF-8编码的;
2)城市名称仅限于ISOLatin1。
这意味着当您将UTF-8解码为Uncode时,City字符仅来自ISOLatin1设置。换句话说,XML可以是ISOLatin1编码的(所有文本都来自ISOLatin1代码表),但它是UTF-8。 ISOLatin1是Unicode表的一小部分,UTF-8是Unicode的8位编码。
答案 5 :(得分:-1)
Nicholas Carey接受的答案还可以,但它有错误,而且代码不起作用。我没有足够的声誉来评论,所以我会在这里编写工作代码:
public static string ConvertToXmlCharacterReference(string xml)
{
StringBuilder sb = new StringBuilder();
const char SP = '\u0020'; // anything lower than SP is a control character
const char DEL = '\u007F'; // anything above DEL isn't ASCII, per se.
int i = 0;
foreach (char ch in xml)
{
bool isPrintableAscii = ch >= SP && ch <= DEL;
if (isPrintableAscii)
{
sb.Append(ch);
}
else
{
sb.AppendFormat("&#x{0:X4};", (int) ch);
}
}
string instance = sb.ToString();
return instance;
}