Удалить недопустимые байты, сохранить действительный UTF-8 (в Ruby 2)

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

Использование 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
b.encode('UTF-8', invalid: :replace, replace: '') у меня работает (Ruby 3.3.0). Он удаляет байт 246.
Dogbert 02.07.2024 11:19

@Dogbert: Возможно, это было исправлено в более поздних версиях Ruby. Я обновлю свой пост своей версией.

user1934428 02.07.2024 11:21

Я могу подтвердить, что метод String#encode работает на Ruby v. 2.2.4, по крайней мере, с предоставленным примером строки. Посмотрите это здесь: jdoodle.com/ia/14yy

mate 02.07.2024 11:37

Примечание: часто лучше заменить (с символом замены), а не удалять: подумайте о последствиях для безопасности (обходите некоторую проверку, и вы удаляете, и у вас могут быть неожиданные строки)

Giacomo Catenazzi 02.07.2024 11:41

Такого Encoding::ASCII_UTF8 не существует. Проверьте свои примеры, пожалуйста.

Stefan 02.07.2024 11:45

@Stefan: Спасибо, исправлено. Я по ошибке скопировал строку из другого неудачного примера.

user1934428 02.07.2024 11:46

@GiacomoCatenazzi: Я знаю, но как платный программист, я должен реализовать то, что предлагает спецификация, даже если мне это не нравится.

user1934428 02.07.2024 11:47

@mate: Проблема, о которой я сообщил для MRI Ruby 2.6, была сообщена неверно. Меня ввело в заблуждение то, как /usr/bin/irb ведет себя на моем Mac (irb - возможно, неправильно - сообщает RUBY_VERSION как 2.6.10, а затем encode стирает ошибочные символы). С /usr/bin/ruby ваш пример действительно работает, а это означает, что проблема сводится к более старой версии Ruby, с которой мне нужно быть совместимой. Я включил в свою публикацию, которая в основном содержит ваш пример, измененный так, чтобы работало, по крайней мере, назначение строки. Ваш пример уже прервал бы компиляцию на моем JRuby из-за недопустимого символа в строке.

user1934428 02.07.2024 12:42

@Стефан: Да. Думаю, кто-то еще тоже хотел это отредактировать.

user1934428 02.07.2024 12:46

Это работает для вас? b.each_char.select(&:valid_encoding?).join()

Fravadona 02.07.2024 12:59

@Fravadona: Да! Не могли бы вы опубликовать это как ответ? Если кто-то другой не предложит еще лучшую версию, я приму ее!

user1934428 02.07.2024 13:10
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
2
11
104
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы можете разделить строку по «символам», выбрать допустимые и снова объединить их в строку:

"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 делает что-то подобное; Я не удосужился изучить код библиотеки. Поскольку преимущество в скорости является единственным критерием, который я могу придумать, я выберу его ответ как принятый.

user1934428 02.07.2024 16:37

Другие вопросы по теме