Кодирование двоичных данных в XML: есть ли лучшие альтернативы, чем base64?

Я хочу кодировать и декодировать двоичные данные в XML-файле (с Python, но что угодно). Я должен столкнуться с тем, что содержимое XML-тега имеет незаконные символы. Единственные разрешенные описаны в в XML-спецификаций:

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

что означает, что непризнанные:

  • 29 символов управления Unicode являются незаконными (0x00 - 0x20) ie (000xxxxx) кроме 0x09, 0x0A, 0x0D
  • любое представление символов Юникода выше 2 байт (кодировка UTF-16+) являются незаконными (от U+D800 до U б+до dfff), т. е. (11011xxx)
  • специальные Unicode нехарактерные являются незаконными (0xFFFE - 0xFFFF) ie (11111111 1111111x)
  • , & по данным этот пост для содержания лиц

1 байта можно закодировать 256 возможных. С этими ограничениями первый байт ограничен 256-29-8-1-3 = 215 возможности.

из этих первых байтов 215 зависим, в base64 использует только 64 возможности. Base64 генерирует 33% накладных расходов (6 бит становится 1 байтом после кодирования base64).

поэтому мой вопрос прост: есть ли алгоритм более эффективный, чем base64 для кодирования двоичных данных в XML? Если нет, то с чего мы начнем его создавать? (библиотеки и т. д.)

NB: вы не ответили бы на этот пост "вы не должны использовать XML для кодирования двоичных данных, потому что...". Просто не надо. Вы могли бы в лучшем случае поспорить почему не использовать 215 возможностей для поддержки плохой XML парсера.

NB2: я не говорю о втором байте, но, безусловно, есть некоторые соображения, которые wa может развить относительно количества возможностей и того факта, что он должен начинаться с 10xxxxxx, чтобы уважать стандарт UTF8, когда мы используем дополнительные плоскости Unicode (что, если нет?).

3 ответов


спасибо Ая за ссылку Asci85, есть очень хорошие идеи.

Я разработал их для нашего случая.


UTF-8 символов возможности:


для символов 1 байт (0xxxxxxx): 96 possibilites на байт

  • + UTF-8 ASCII символов 0xxxxxxx = +2^7
  • - UTF-8 контрольные символы 000xxxxx = -2^5
  • + XML разрешенные символы управления UTF-8 (00000009, 0000000A, 0000000D) = +3
  • - недопустимые символы сущности XML(, &) = -3

EDIT: это для XML1.0 спецификаций. в XML 1.1 спецификации позволяет использовать управляющие символы, кроме 0x00...

для 2-байтовых символов (110xxxxx 10xxxxxx): 1920 possibilites на 2 байта

  • + UTF-8 2-байтовые символы 110xxxxx 10xxxxxx = +2^11
  • - UTF-8 незаконные неканонические символы (1100000x 10xxxxxx) = -2^7

для 3-байтовых символов (1110xxxx 10xxxxxx 10xxxx): 61440 possibilites на 3 байта

  • + UTF-8 3-байтовые символы 1110xxxx 10xxxxxx 10xxxx = +2^16
  • - UTF-8 незаконные неканонические символы (11100000 100xxxxx 10xxxxxx) = -2^11
  • - Юникод зарезервирован UTF-16 кодовых точек (11101101 101xxxxx 10xxxxxx) = -2^11

и я не буду делать расчет для 4-байт символы, это бессмысленно: количество возможных было бы незначительным, и в этом диапазоне слишком много незаконных символов UTF-8.


возможности кодирования, скажем, в 3-байтовом пространстве


Итак, давайте посмотрим, какие комбинации мы можем сделать на 3 байта (24 бит) площадь:

  • 0xxxxxxx 0xxxxxxx 0xxxxxxx : это 96*96*96 = 884736 возможности
  • 0xxxxxxx 110xxxxx 10xxxxxx: это 96 * 1920 = 184320 возможности
  • 110xxxxx 10xxxxxx 0xxxxxxx : это 1920*96 = 184320 возможности
  • 1110xxxx 10xxxxxx 10xxxxxx : это 61440 = 61440 возможности

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

общее количество возможности:

  • 3-байтовое пространство имеет 2^24 = 16777216 возможности.
  • UTF-8 совместимые возможности в этом пространстве 884736+2*184320+61440 = 1314816 возможностей.

какую нагрузку это значит?

  • 24-битное пространство полезные биты: Log2 (16777216)=24 (конечно! это для понимания математики)
  • UTF-8 полезных битов этого пространства: Log2 (1314816)=20,32 полезных бита.
  • это означает, что нам нужно 24 бита пространства для кодирования 20,32 бит полезных информация, т. е.. минимальные теоретические накладные расходы 18% overhead. лучше, чем 33% накладных расходов Base64 и 25% накладных расходов Ascii85!

EDIT: это для XML1.0 спецификаций. С XML1.1 (широко не поддерживается...), теоретические накладные расходы составляют 12,55%. Мне удалось сделать бинарный безопасный алгоритм с 14.7% накладными расходами для XML1.1.


как приблизиться к этим 18% накладных расходов?


плохая новость в том, что мы не можем получить легко, что 18% ovearhead без использования большого "словаря" (т. е. длинных наборов enconding). Но это легко получить 20%, и довольно легко, но менее практично получить 19%.

хорошие кандидаты длины кодирования:

  • 6 битов могут зашифровать 5 битов с накладными расходами 20% (2^(6*0,84) > 2^5)
  • 12 бита могут зашифровать 10 битов с накладными расходами 20% (2^(12*0,84) > 2^10)
  • 24 бита могут зашифровать 20 битов с накладными расходами 20% (2^(24*0,84) > 2^20)
  • 25 битов могут зашифровать 21 бит с накладными расходами 19% (2^(25*0,84) > 2^21)

NB: 0,84-средняя "полезность" бита пробела (20,32/24).


как построить наш алгоритм кодирования?


нам нужно построить "словарь", который будет отображать " возможности пространства "(последовательность randoms битов длиной 5, 10, 20 или 21 бит в зависимости от выбранной длины кодирования для алгоритма - просто выберите один) в utf8-совместимые последовательности (битовая последовательность utf8, длина которой составляет 6, 12, 24 или 25 бит соответственно).

самой простой отправной точкой было бы кодирование последовательности 20bits в 24bits совместимые последовательности UTF-8: это именно тот пример, который был взят выше, чтобы вычислить возможности, и это 3 байта UTF-8 (поэтому нам не придется беспокоиться о unterminated символах UTF8).

обратите внимание, что мы должны использовать 2-байтовое (или выше) пространство кодировки UTF-8 символов для достижения 20% накладных расходов. С только 1-байтовыми длинными символами UTF8 мы можем достичь только 25% накладных расходов с RADIX-24. Однако 3-байтовые длинные символы UTF-8 излишне достигать 20% накладных расходов.

это следующий вызов для этого вопроса. Кто хочет играть? :)


предложение алгоритма, я назову BaseUTF-8 для XML


20 двоичных битов для кодирования: ABCDEFGHIJKLMNOPQRST

результирующая строка UTF-8 с именем "encoded": 24 бита долго

математический алгоритм кодирования (не основанный на каком-либо известном языке программирования):

If GH != 00 && NO != 00:
    encoded = 01ABCDEF 0GHIJKLM 0NOPQRST # 20 bits to encode, 21 space bits with restrictions (1-byte UTF-8 char not starting by 000xxxxx ie ASCII control chars)

If ABCD != 0000:
    If GH == 00 && NO == 00: # 16 bits to encode
        encoded = 0010ABCD 01EFIJKL 01MPQRST    
    Else If GH == 00:  # 18 bits to encode, 18 space bits with restrictions (1-byte  UTF-8 ASCII control char, 2-bytes UTF-8 char noncanonical)
        encoded = 0NOFIJKL 110ABCDE 10MPQRST
    Else If NO == 00:  # 18 bits to encode
        encoded = 110ABCDE 10MPQRST 0GHFIJKL

If ABCD == 0000: # 16 bits to encode
    encoded = 0011EFGH 01IJKLMN 01OPQRST

On "encoded" variable apply:
    convert < (0x3C) to Line Feed (0x0A)
    convert > (0x3E) to Cariage Return (0x0D)
    convert & (0x26) to TAB (0x09)

и вот как вы получаете только 20% накладных расходов.

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


Я разработал концепцию в коде C.

проект находится на GitHub и, наконец, называется BaseXML:https://github.com/kriswebdev/BaseXML

Он имеет накладные расходы 20%, что хорошо для бинарной безопасной версии.

Мне было трудно заставить его работать с Expat, который находится за парсером XML сцены Python (который не поддерживает XML1.1!). Так вы найдете BaseXML1.0 Бинарные безопасная версия для XML1.0.

Я может отпустите " для XML1.1 " версия позже, если требуется (она также является бинарной безопасной и имеет накладные расходы 14.7%), она готова и работает, но бесполезна со встроенными XML-синтаксическими анализаторами Python, поэтому я не хочу путать людей со слишком большим количеством версий (пока).


Это хуже: у вас на самом деле нет 215 различных значений байтов, которые вы можете использовать. Полученные двоичные данные должны быть действительны в любой кодировке, в которой представлен XML (что почти наверняка UTF-8), что означает, что многие, многие байтовые последовательности запрещены. 0xc2 последующим 0x41 будет только один случайный пример. XML-это текст (последовательность символов Unicode), а не двоичные данные. При передаче он кодируется с использованием некоторой кодировки (которая почти alwats UTF-8). Если ты попытаешься ... рассматривайте его как двоичные данные, тогда вы, на мой взгляд, просите больше проблем, чем стоит иметь дело.

Если вы все еще хотите сделать это...

XML-это текст. Поэтому давайте не будем пытаться кодировать ваши двоичные данные как двоичные данные. Это не приведет к простым или очевидным способом showhorn его в XML-документ. Давайте попробуем вместо этого кодировать ваши двоичные данные в виде текста!

давайте попробуем одну очень простую кодировку:

  • сгруппировать двоичные данные в блоки 20 бит
  • кодируйте каждую группу из 20 бит как символ Юникода U + 10000 плюс числовое значение 20 бит.

Это будет означать, что вы используете исключительно символы из плоскостей с 1 по 16. Все ограниченные символы находятся в плоскости 0 (BMP), поэтому здесь вы в безопасности.

когда вы затем кодируете этот XML-документ как UTF-8 для передачи, каждый из этих символов потребует 4 байта для кодирования. Таким образом, вы потребляете 32 бита на каждые 20 бит исходных данных, что составляет 60% накладных расходов по сравнению с чистым двоичным кодированием исходных данных. это хуже, чем 33% base64 в, что делает его ужасной идеей.

эта схема кодирования немного расточительна, потому что она не использует символы BMP. Можем ли мы использовать символы BMP, чтобы сделать его лучше? Не тривиально. 20 самый большой размер мы можем использовать для групп (log(0x10FFFF) ~ 20.09). Мы могли бы переназначить схему, чтобы использовать некоторые как символы manu BMP, поскольку они занимают меньше пространство для кодирования с помощью UTF-8, но это не только сильно усложнит кодировку (запрещенные символы разбросаны, поэтому у нас есть несколько случаев для обработки), но это может привести только к улучшению около 6.25% битовых шаблонов (доля символов Юникода, которые находятся в BMP), и для большинства из этих 6.25% мы сохраним только один байт. Для случайных данных накладные расходы уменьшаются с 60% до 55%. результат все равно будет намного хуже, чем base64, за исключением некоторых очень надуманных данные. Обратите внимание, что накладные расходы зависят от данных. Для 0.2% битовых шаблонов вы фактически получите сжатие вместо накладных расходов (60% сжатия для 0.012% шаблонов и 20% сжатия для 0.18% шаблонов). Но эти фракции очень низкие. Это того не стоит.

другими словами: если вы хотите кодировать что-либо с помощью 4-байтовых последовательностей UTF-8, вам нужно использовать 32 бита на последовательность (конечно), но 11 из этих битов фиксированы и неизменяемы: биты должен соответствовать шаблону 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx и есть только 21 xs там). Эти накладные расходы 60% встроены в UTF-8, поэтому, если вы хотите использовать это в качестве основы любой кодировки, которая улучшает накладные расходы base64, вы начинаете сзади!

надеюсь, это убедит вас, что вы не можете улучшить плотность base64, используя любую схему этого типа.