Кодирование строки ASCII в XML-документе UTF8 в байтовом массиве - PullRequest
3 голосов
/ 15 февраля 2012

У меня есть следующие требования:

... документ должен быть закодирован в UTF-8 ... Поле Фамилия разрешено только (Расширенное) ASCII ... Город разрешено только ISOLatin1 ... Сообщение должно быть помещено в (IBM Websphere)MessageQueue как IBytesMessage

Для простоты XML-документ выглядит следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<foo>
  <lastname>John ÐØë</lastname>
  <city>John ÐØë</city>
  <other>UTF-8 string</other>
</foo>

Часть "ÐØë" (или должна быть) ASCII-значения 208, 216, 235. соответственно.

У меня также есть объект:

public class foo {
  public string lastname { get; set; }
}

Поэтому я создаю экземпляр объекта и устанавливаю фамилию:

var x = new foo() { lastname = "John ÐØë", city = "John ÐØë" };

Вот где начинается моя головная боль (или начало , если хотите ...):

  • Visual studio / source code находится в Unicode
  • Следовательно: объект имеет Юникод фамилия
  • Сериализатор XML использует UTF-8 для кодирования документа
  • Фамилия должна содержатьтолько (расширенный) ASCII символов;допустимые символы ASCII , но, разумеется, в кодировке UTF-8

Обычно я не испытываю никаких проблем с кодировками;Я знаком с Абсолютным минимумом, который каждый разработчик программного обеспечения должен абсолютно, положительно знать о Юникоде и наборах символов (без извинений!) , но это меня озадачило ...

Я так понимаюДокумент UTF-8 будет в состоянии «содержать» обе кодировки, потому что кодовые точки «перекрываются».Но я заблудился, когда мне нужно преобразовать сериализованное сообщение в байтовый массив.При выполнении дампа я вижу C3 XX C3 XX C3 XX (у меня нет фактического дампа под рукой).Понятно (или я слишком долго на это смотрел), что строки фамилии / города помещаются в сериализованный документ в форме юникода;байтовый массив предлагает следующее.

Теперь, что мне нужно сделать и где, чтобы строка Lastname вошла в документ XML и, наконец, в байтовый массив в виде строки ASCII (и фактическая 208, 216, 235-байтовая последовательность), и этот Город делает его там как ISOLatin1 ?

Я знаю, что требования задом наперед, но я не могу их изменить (3-я сторона).Я всегда использую UTF-8 для наших внутренних проектов, поэтому я должен поддерживать преобразование unicode-utf8 => ASCII / ISOLatin1 (конечно, только для символов, входящих в эти наборы).

У меня болит голова ...

Ответы [ 6 ]

5 голосов
/ 15 февраля 2012

Не берите в голову, как XML-документ закодирован для передачи. Правильный способ сделать то, что вы хотите - & ndash; кодировать некоторые не-ASCII-символы, чтобы они выдержали отключение без изменений & mdash; - это использовать ссылки на символы XML для представления символов, которые необходимо сохранить. Например, ваш

ÐØë

представляется с использованием ссылок на символы XML как

&#x00D0;&#x00D8;&#x00EB;

Принимающий [соответствующий] 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.

В обоих случаях ссылка на символ указывает кодовую точку в наборе символов ИСО / МЭК 10646 & mdash; по сути юникод. Вы хотите сопоставить символы с соответствующей кодовой точкой. Поскольку строки в мире CLR имеют кодировку UTF-16, это не должно быть большой проблемой. Я полагаю, что приведенный выше код должен хорошо работать, если только у вас нет чего-то действительно странного, что не очень хорошо работает с UTF-16.

0 голосов
/ 20 июля 2016

Я понимаю это как 2 отдельных требования:

1) XML должен быть в кодировке UTF-8;

2) Название города ограничено ISOLatin1.

Это означает, что когда вы декодируете UTF-8 в Uncode, символы City только из набора ISOLatin1. Другими словами, XML может быть в кодировке ISOLatin1 (весь текст взят из кодовой таблицы ISOLatin1), но это UTF-8. ISOLatin1 является небольшой частью таблицы Unicode, а UTF-8 является 8-битной кодировкой Unicode.

0 голосов
/ 27 июня 2016

Принятый ответ от Николая Кэри в порядке, но он содержит ошибки и код не работает.У меня недостаточно репутации, чтобы комментировать, поэтому я напишу рабочий код здесь:

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;
    }
0 голосов
/ 15 февраля 2012

Документ должен быть закодирован в UTF-8.Поле Lastname допускает только ASCII.City разрешает только ISOLatin1.Сообщение (IBM Websphere) MessageQueue должно быть записано как IBytesMessage.

Если это точная спецификация, то я думаю, что вы, возможно, неправильно ее поняли.Ваша задача - не кодирование, а проверка / откат.Весь документ , включая поля Lastname и City, должен быть закодирован как UTF-8.Проще говоря, документ XML был бы недействительным, если бы он объявил свою кодировку как UTF-8, а затем содержал байтовые значения, которые недопустимы в этой кодировке.

Удобно, ASCII перекрывается с первыми 128 кодовыми точками Unicode;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.

Я предполагаю, что ваше стороннее программное обеспечение может правильно декодироватьXML-документ с использованием UTF-8;однако он должен затем извлечь поля Lastname и City и использовать их где-нибудь, где разрешены только ASCII и Latin1.Он налагает на вас ограничения, чтобы гарантировать, что он не будет вынужден нести потерю данных (из-за наличия запрещенных символов).

Редактировать : это обходной путь, который выпредлагаешь.Я использую Latin1 вместо «Extended 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 (например, < для&lt и & до &amp;).

0 голосов
/ 15 февраля 2012

Вы просто не можете иметь последовательность 208, 216, 235 байтов в массиве строк / байтов в кодировке UTF-8.

Я надеюсь, что вы можете сохранить XML как ISO 8859-1 с или без упоминания его в инструкции по обработке XML <?xml version="1.0" encoding="XXXXXXXXXX"?> (возможно, даже указав недопустимую кодировку UTF-8 в заголовке XML).

В противном случае, если ваши требования соответствуют заявленным - просто запросите точный ожидаемый байтовый массив для заданного ввода и создайте свою собственную пользовательскую сериализацию (или, возможно, пользовательскую кодировку, также не уверенную, если это возможно).

0 голосов
/ 15 февраля 2012

Итак .. System.Text.Encoding.ASCII.GetBytes(string), вероятно, будет делать то, что вы хотите ... преобразовать строку в байтовый массив в кодировке ascii.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...