(Я разместил аналогичную проблему здесь, но этот новый вопрос не является дубликатом).
Использование Ruby 2.6.10 или 1.9.3 является обязательным условием для того, чтобы программное обеспечение, которое я здесь разрабатываю, работало со следующими:
Небольшая воспроизводимая проблема:
b = "L\xF6sé侍"
У нас есть строка, один из ее байтов недопустим в UTF-8 (это байт с шестнадцатеричным кодом F6). Кодировка строки с точки зрения Ruby — Encoding::UTF_8
. Глядя на последовательность байтов, мы видим
p b.bytes.to_a
=>
[76, 246, 115, 195, 169, 228, 190, 141]
Моя цель — удалить из строки все байты, которые недопустимы в UTF-8. В моем простом примере я хочу получить строку с содержимым "Lsé侍"
.
Я пытался
c1 = b.encode('UTF-8', invalid: :replace, replace: '')
но c1
имеет то же содержание, что и b
. Тогда я попробовал
b.force_encoding(Encoding::ASCII_UTF8)
c2 = b.encode('UTF-8', invalid: :replace, replace: '')
но при этом также удаляются символы é и 侍, поскольку они недопустимы в ASCII.
Я также думал о том, чтобы составить жестко закодированный список тех значений байтов, которые недействительны в UTF8, и просто удалить их из строки, но это некрасиво.
Есть идеи, как это можно сделать?
ОБНОВЛЕНИЕ: я опубликовал здесь код, основанный на моих экспериментах в irb
, но оказалось, что поведение irb здесь немного отличается от неинтерактивного Ruby. Вы можете найти здесь скриншот, основанный на комментарии пользователя @mate. Чтобы это работало, я не мог назначить строку в своей программе JRuby (это было бы отклонено уже во время компиляции), а прочитал ее из файла (что в любом случае происходит в нашем «реальном» приложении).
Следовательно, если вы хотите воспроизвести пример, загрузите файл с ошибочным текстом по этой ссылке для скачивания и используйте следующий Ruby-скрипт для его запуска:
p RUBY_VERSION
str = File.read("./errf.txt")
p str.bytes.to_a
str2 = str.encode('UTF-8', invalid: :replace, replace: '')
p str2.bytes.to_a
@Dogbert: Возможно, это было исправлено в более поздних версиях Ruby. Я обновлю свой пост своей версией.
Я могу подтвердить, что метод String#encode
работает на Ruby v. 2.2.4, по крайней мере, с предоставленным примером строки. Посмотрите это здесь: jdoodle.com/ia/14yy
Примечание: часто лучше заменить (с символом замены), а не удалять: подумайте о последствиях для безопасности (обходите некоторую проверку, и вы удаляете, и у вас могут быть неожиданные строки)
Такого Encoding::ASCII_UTF8
не существует. Проверьте свои примеры, пожалуйста.
@Stefan: Спасибо, исправлено. Я по ошибке скопировал строку из другого неудачного примера.
@GiacomoCatenazzi: Я знаю, но как платный программист, я должен реализовать то, что предлагает спецификация, даже если мне это не нравится.
@mate: Проблема, о которой я сообщил для MRI Ruby 2.6, была сообщена неверно. Меня ввело в заблуждение то, как /usr/bin/irb ведет себя на моем Mac (irb - возможно, неправильно - сообщает RUBY_VERSION как 2.6.10, а затем encode
стирает ошибочные символы). С /usr/bin/ruby ваш пример действительно работает, а это означает, что проблема сводится к более старой версии Ruby, с которой мне нужно быть совместимой. Я включил в свою публикацию, которая в основном содержит ваш пример, измененный так, чтобы работало, по крайней мере, назначение строки. Ваш пример уже прервал бы компиляцию на моем JRuby из-за недопустимого символа в строке.
@Стефан: Да. Думаю, кто-то еще тоже хотел это отредактировать.
Это работает для вас? b.each_char.select(&:valid_encoding?).join()
@Fravadona: Да! Не могли бы вы опубликовать это как ответ? Если кто-то другой не предложит еще лучшую версию, я приму ее!
Вы можете разделить строку по «символам», выбрать допустимые и снова объединить их в строку:
"L\xF6sé侍".each_char.select(&:valid_encoding?).join()
"Lsé侍"
Подход encode
отлично работает с Ruby 2.6.10.
$ ruby --version
ruby 2.6.10p210 (2022-04-12 revision 67958) [arm64-darwin23]
$ ruby -e 'p "L\xF6sé侍".encode("UTF-8", invalid: :replace, replace: "")'
"Lsé侍"
В Ruby 1.9 был Значок:
b = [76, 246, 115, 195, 169, 228, 190, 141].pack('c*').force_encoding('UTF-8')
#=> "L\xF6sé侍"
require 'iconv'
Iconv.conv('UTF-8//ignore','UTF-8', b)
#=> "Lsé侍"
Отличный подход. Я провел небольшой тест, чтобы сравнить его с версией, опубликованной @Fravadona, и, к моему удивлению, его версия работала на моей платформе немного (около 20%) быстрее, несмотря на необходимость создания временного массива (и повторного соединения). Что ж, возможно, Iconf делает что-то подобное; Я не удосужился изучить код библиотеки. Поскольку преимущество в скорости является единственным критерием, который я могу придумать, я выберу его ответ как принятый.
b.encode('UTF-8', invalid: :replace, replace: '')
у меня работает (Ruby 3.3.0). Он удаляет байт 246.