私はメールを扱っていますが、名前と件名は次のようにqエンコードされることがあります。
=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?=
Rubyでそれらをデコードする方法はありますか?TMailがそれを処理する必要があるようですが、それは実行していません。
私はこれを使用して電子メールの件名を解析します。
次のことを試すことができます。
str = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
if m = /=\?([A-Za-z0-9\-]+)\?(B|Q)\?([!->@-~]+)\?=/i.match(str)
case m[2]
when "B" # Base64 encoded
decoded = Base64.decode64(m[3])
when "Q" # Q encoded
decoded = m[3].unpack("M").first.gsub('_',' ')
else
p "Could not find keyword!!!"
end
Iconv.conv('utf-8',m[1],decoded) # to convert to utf-8
end
Rubyには、Quoted-Printable文字列をデコードするメソッドが含まれています。
puts "Pablo_Fern=C3=A1ndez".unpack "M"
# => Pablo_Fernández
しかし、これは文字列全体(最初の部分を含む)では機能しないようです=?UTF-8?Q?
。ただし、そこから解決できるかもしれません。
これはかなり古い質問ですが、TMail :: Unquoter(またはその新しい化身のMail :: Encodings)も同様に機能します。
TMail::Unquoter.unquote_and_convert_to(str, 'utf-8' )
また
Mail::Encodings.unquote_and_convert_to( str, 'utf-8' )
行ごとのデコード:
line.unpack("M")
STDINまたはファイル提供のエンコードされた文字列の入力をデコードされた出力に変換します。
if ARGV[0]
lines = File.read(ARGV[0]).lines
else
lines = STDIN.each_line.to_a
end
puts lines.map { |c| c.unpack("M") }.join
これは、電子メールをテストしたい人に役立つかもしれません。delivery.html_partは通常エンコードされますが、を使用してストレートHTML本文にデコードできます.decoded
。
test "email test" do
UserMailer.confirm_email(user).deliver_now
assert_equal 1, ActionMailer::Base.deliveries.size
delivery = ActionMailer::Base.deliveries.last
assert_equal "Please confirm your email", delivery.subject
assert delivery.html_part.decoded =~ /Click the link below to confirm your email/ # DECODING HERE
end
最も効率的で最新のソリューションは、 Mailgemvalue_decode
の方法を使用しているようです。
> Mail::Encodings.value_decode("=?UTF-8?Q?Greg_of_Google?=")
=> "Greg of Google"
https://www.rubydoc.info/github/mikel/mail/Mail/Encodings#value_decode-class_method
以下は、傾斜している場合にカットアンドペーストできるRubyコードです。rubyを使用して直接実行すると、テストが実行されますruby ./copy-pasted.rb
。コードで行われているように、私はこのモジュールをStringコアクラスの改良版として使用します。
解決策に関するいくつかの意見:
他のソリューション.gsub('_', ' ')
は、解凍された文字列に対して実行されます。ただし、これが正しいとは思わないため、文字セットによっては誤ったデコードが発生する可能性があります。 RFC2047セクション4.2(2)は、「_
常に16進数を表す」と示し20
ているため、最初に置き換え=20
て_
から、解凍結果に依存するのが正しいようです。(これにより、実装がよりエレガントになります。)これは、関連する質問への回答でも説明されています。
よりわかりやすくするために、コメントを許可するためにフリースペースモードで正規表現を記述しました(これは一般的に複雑な正規表現に役立ちます)。正規表現を調整する場合は、フリースペースモードで空白の一致が変更されることに注意してください。空白の一致は、エスケープするか、(コードのように)文字クラスとして実行する必要があります。また、regex101に正規表現を追加したので、名前付きキャプチャグループ、遅延数量詞などの説明を読んで、自分で実験することができます。
正規表現は、stringで示されているように、単一の文字列内の複数のQエンコードされたフレーズ間のスペース(
;ではなく改行)を吸収します。これは、RFC2047セクション5(1)が、複数のQエンコードされたフレーズを線形空白で互いに分離する必要があることを示しているためです。ユースケースによっては、空白を吸収することが望ましくない場合があります。TAB
test_4
キャプチャという名前の正規表現code
は、予期しないquoted printableコードを許可します([bBqQ]
一致が発生してコードがエラーを発生させる場合を除きます。これは、テキストを処理するときに予期しない値を検出するのに役立ちます。そうでない場合は、code
captureという名前の正規表現をに変更してください。[bBqQ]
この動作が必要です(一致するものはなく、元の文字列が返されます)。
ブロックRegexp.last_match
内の利便性としてグローバルを利用します。gsub
これをマルチスレッドコードで使用する場合は注意が必要な場合がありますが、私はこれを考慮していません。
追加の参考資料と読み物:
require "minitest/autorun"
module QuotedPrintableDecode
class UnhandledCodeError < StandardError
def initialize(code)
super("Unhandled quoted printable code: '#{code}'.")
end
end
@@qp_text_regex = %r{
=\? # Opening literal: `=?`
(?<charset>[^\?]+) # Character set, e.g. "Windows-1252" in `=?Windows-1252?`
\? # Literal: `?`
(?<code>[a-zA-Z]) # Encoding, e.g. "Q" in `?Q?` (`B`ase64); [BbQq] expected, others raise
\? # Literal: `?`
(?<text>[^\?]+?) # Encoded text, lazy (non-greedy) matched, e.g. "Foo_bar" in `?Foo_bar?`
\?= # Closing literal: `?=`
(?:[ ]+(?==\?))? # Optional separating linear whitespace if another Q-encode follows
}x # Free-spacing mode to allow above comments, also changes whitespace match
refine String do
def decode_q_p(to: "UTF-8")
self.gsub(@@qp_text_regex) do
code, from, text = Regexp.last_match.values_at(:code, :charset, :text)
q_p_charset_to_charset(code, text, from, to)
end
end
private
def q_p_charset_to_charset(code, text, from, to)
case code
when "q", "Q"
text.gsub("_", "=20").unpack("M")
when "b", "B"
text.unpack("m")
else
raise UnhandledCodeError.new(code)
end.first.encode(to, from)
end
end
end
class TestQPDecode < Minitest::Test
using QuotedPrintableDecode
def test_decode_single_utf_8_phrase
encoded = "=?UTF-8?Q?J=2E_Pablo_Fern=C3=A1ndez?="
assert_equal encoded.decode_q_p, "J. Pablo Fernández"
end
def test_decoding_preserves_space_between_unencoded_phrase
encoded = "=?utf-8?Q?Alfred_Sanford?= <me@example.com>"
assert_equal encoded.decode_q_p, "Alfred Sanford <me@example.com>"
end
def test_decodinge_multiple_adjacent_phrases_absorbs_separating_whitespace
encoded = "=?Windows-1252?Q?Foo_-_D?= =?Windows-1252?Q?ocument_World=9617=96520;_Recor?= =?Windows-1252?Q?d_People_to_C?= =?Windows-1252?Q?anada's_History?="
assert_equal encoded.decode_q_p, "Foo - Document World–17–520; Record People to Canada's History"
end
def test_decoding_string_without_encoded_phrases_preserves_original
encoded = "Contains no QP phrases"
assert_equal encoded.decode_q_p, encoded
end
def test_unhandled_code_raises
klass = QuotedPrintableDecode::UnhandledCodeError
message = "Unhandled quoted printable code: 'Z'."
encoded = "=?utf-8?Z?Unhandled code Z?="
raised_error = assert_raises(klass) { encoded.decode_q_p }
assert_equal message, raised_error.message
end
end