#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
неверно?