В Perl, как создать строку «смешанного кодирования» (или необработанную последовательность байтов) в скаляре?

#perl #utf-8 #scalar #bytestream

Вопрос:

В моем скрипте Perl я должен записать сочетание UTf-8 и необработанных байтов в файлы.

У меня есть большая строка, в которой все закодировано как UTF-8. В этой «исходной» строке символы UTF-8 такие, какими они должны быть (то есть допустимые последовательности байтов UTF-8), в то время как «необработанные байты» были сохранены так, как если бы они были кодовыми точками значения, содержащегося в необработанном байте. Таким образом, в исходной строке «необработанный» байт 0x50 будет сохранен как один байт 0x50; в то время как «необработанный» байт 0xff будет сохранен как двухбайтовая последовательность 0xc3 0xbf, допустимая для utf-8. Когда я записываю эти «необработанные» байты обратно, мне нужно вернуть их в однобайтовую форму.

У меня есть другие структуры данных, позволяющие мне знать, какие части строки представляют какие данные. Список полей, типов, длин и т.д.

При записи в обычный файл я записываю каждое поле по очереди, либо напрямую (если это UTF-8), либо путем кодирования его значения в ISO-8859-1, если оно предназначено для необработанных байтов. Это работает идеально.

Теперь, в некоторых случаях, мне нужно записать значение не непосредственно в файл, а в виде записи базы данных BerkeleyDB (Btree, но это в основном не имеет значения). Для этого мне нужно записать ВСЕ значения, составляющие мою запись, в одной операции записи. Это означает, что мне нужен скаляр, содержащий сочетание UTF-8 и необработанных байтов.


Пример:

Входной скаляр (все шестнадцатеричные значения): 61 C3 8B 00 C3 BF

Ожидаемый формат вывода: 2 символа UTF-8, затем 2 необработанных байта.

Ожидаемый результат: 61 C3 8B 00 FF


Сначала я создал строку, объединив те же значения, которые я записывал в свой файл, из пустой строки. И я попытался записать эту самую строку в «стандартный» файл без добавления кодировки. Я получил символы»? » вместо всех моих необработанных байтов более 0x7f (потому что, очевидно, Perl решил считать мою строку UTF-8).


Затем, чтобы попытаться сказать Perl, что он уже был закодирован, и «пожалуйста, не пытайтесь быть умным», я попытался закодировать части UTF-8 в «UTF-8», закодировать двоичные части в «ISO-8859-1» и объединить все. Потом я написал это. На этот раз байты выглядели идеально, но части, которые уже были UTF-8, были «дважды закодированы», то есть каждый байт многобайтового символа рассматривался как его кодовая точка…


Я думал, что Perl не должен был перекодировать «внутренний» UTF-8 в «закодированный» UTF-8, если он был внутренне помечен как UTF-8. Строка, содержащая все значения в UTF-8, поступает из API C, который устанавливает маркер UTF-8 (или, по крайней мере, должен), чтобы Perl знал, что он уже декодирован.

Есть какие — нибудь идеи о том, что я там пропустил?

Есть ли способ сказать Perl, что я хочу сделать, это просто поместить кучу байтов один за другим и, пожалуйста, не пытаться интерпретировать их каким-либо образом? Файл, в который я пишу, открывается как «>:raw» именно по этой причине, но, полагаю, мне нужен способ указать, что данный скаляр тоже является «необработанным»?



Эпилог: Я нашел причину проблемы. Строка $biginput должна была полностью состоять из данных в кодировке UTF-8. Но он содержал необработанные байты с большими значениями из-за ошибки в C (оказывается, «символ» (а не «символ без знака») лучше всего тестировать с помощью побитовых операторов, а не «> 127″… кхм). Таким образом, «большие» байты не были разделены на двухбайтовую последовательность UTF-8 в C API.

Это означает, что строка $bigInputString, созданная из плохих данных C, не имела ожидаемого содержимого, и Perl это тоже по праву не понравилось.

После того, как я исправил ошибку, строка была правильно закодирована в UTF-8 (для частей, которые я хотел сохранить как UTF-8) или латинский-1 (для «необработанных байтов», которые я хотел преобразовать обратно), и у меня больше не было проблем.

Извините, что отняли у вас время, ребята. Но я все же кое-чему научился, так что оставлю это здесь. Мораль этой истории, Devel::Peek хорош для отладки (спасибо икегами), и всегда следует перепроверять, а не предполагать. Конечно, я торопился в пятницу, но вина все равно моя.

Итак, спасибо всем, кто помогал или пытался, и особая благодарность икегами (еще раз), который потратил довольно много своего времени, помогая мне.

Комментарии:

1. Можете ли вы обновить свой пост примерами строк?

2. В частности, предоставьте репрезентативные входные данные и какие выходные данные вы хотите получить для этих входных данных.

3. Я обновил для ясности, как написано. Я обновлю с примерами.

4. Повторите » Ожидаемый формат вывода «, Как вы узнаете, что конвертировать, а что нет?

5. Как указано в вопросе, у меня есть другие структуры данных, которые позволяют мне узнать, что представляет собой исходная строка

Ответ №1:

Если у вас есть строка Юникода, откуда вы знаете, что каждая кодовая точка должна быть сохранена в кодировке UTF-8 последовательность или один байт, и способ создания шаблона строку, где каждый символ представляет то, что соответствующий один из Юникода строки предполагается использовать ( U для UTF-8, C для одного байта, чтобы держать вещи простыми), можно использовать pack :

 #!/usr/bin/env perl
use strict;
use warnings;

sub process {
    my ($str, $formats) = @_;
    my $template = "C0$formats";
    my @chars = map { ord } split(//, $str);
    pack $template, @chars;
}

my $str = "x61xC3x8Bx00xC3xBF";
utf8::decode($str);
print process($str, "UUCC"); # Outputs 0x61 0xc3 0x8b 0x00 0xff
 

Комментарии:

1. Интересно, спасибо за совет. Мне придется подождать понедельника, чтобы проверить различные данные ответы, но это кажется многообещающим.

2. Ну, это было из-за ошибки (см. редактирование), но все равно спасибо.

Ответ №2:

Итак, у вас есть

 my $in = "x61xC3x8Bx00xC3xBF";
 

и ты хочешь

 my $out = "x61xC3x8Bx00xFF";
 

Это результат декодирования только некоторых частей входной строки, поэтому вам нужно что-то вроде следующего:

 sub decode_utf8 { my ($s) = @_; utf8::decode($s) or die("Invalid Input"); $s }

my $out = join "",
               substr($in, 0, 3),
   decode_utf8(substr($in, 3, 1)),
   decode_utf8(substr($in, 4, 2));
 

Проверенный.

В качестве альтернативы вы можете декодировать все целиком и перекодировать части, которые должны быть закодированы.

 sub encode_utf8 { my ($s) = @_; utf8::encode($s); $s }

utf8::decode($in) or die("Invalid Input");
my $out = join "",
   encode_utf8(substr($in, 0, 2)),
               substr($in, 2, 1),
               substr($in, 3, 1);
 

Проверенный.

Вы не указали, как вы знаете, что декодировать, а что нет, но вы указали, что у вас есть эта информация.

Комментарии:

1. У меня есть цикл, повторяющий массив, содержащий ссылки на хэши. Каждый хэш, на который ссылается ссылка, представляет поле записи. Хэши имеют ключи, такие как «длина» (с присваиваемое значение является количеством элементов , либо символа , если UTF-8, или необработанные байты (которые должны быть преобразованы обратно в UTF-8) если двоичный «тип» (‘U’, а для UTF-8, ‘B’ и бинарный), и тому подобное. Формат этой структуры данных не имеет отношения к вопросу. Я ЗНАЮ, что я читаю и во что мне нужно это преобразовать. Чего я не знаю, так это как предотвратить, чтобы Perl не вмешивался в данные, когда я помещаю все это в одну строку.

2. Я пользовался этим . оператор для объединения строк. Может быть, функция соединения сделает то, что я ожидаю. Офис сейчас закрыт, я попробую это сделать в понедельник. Спасибо.

3. . работает так же хорошо, как join

4. Тогда это не сработает. Как я уже сказал, моя первая попытка сохранить их была путем объединения. Однако Perl увидел, что я объединяю эти значения, отличные от UTF-8, в красивую цепочку UTF-8, и решил заменить все недопустимые значения utf-8 (например, FF в примере) символами»?». Таким образом, изменяются данные.

5. Re «*Тогда это не сработает. *», Значит, вы недостаточно объяснили проблему. $in Неверно или $out неверно?