Open2
[Rails] ハイフンもどき一掃計画 ~ハイフンに似たやつらを置換する~
スマホアプリでエンドユーザに住所や電話番号を入力してもらうようなサービスを運用していると、ハイフンの入力が自由すぎて困ることがあった。
具体的にサービス内で顧客リストをCSV化してS-JISで作成する必要がある時などに、文字コードの問題で変換できなかったり、番地のパース処理等で漏れたりいろいろ厄介のタネになる。
またエンドユーザは「よこぼう」であればなんでもいいと思ってるらしく、中にはアンスコ(_: U+005F)やチルダ(~: U+007E)なんかも半角・全角で混ざってる。(チルダでも波ダッシュ「〜: U+301C」と全角チルダ「~: U+FF5E」の違いあったりで何がなんだか...)
そうやってひとまずハイフンもどき系 + アンスコ系 + オーバーライン系 + チルダ系を洗い出すと以下のようなバリエーションがあるよう。
他にもありそうだがこれでおおよそカバーはできそう。
「-˗ᅳ᭸‐‑‒–—―⁃⁻−▬─━➖ーㅡ﹘﹣-ー𐄐𐆑__‾ ̄~〜~」
なのでこれらの文字が混ざった場合は強制的にハイフン(-: U+002D)に変換してコミットするように対応する例を残しておく。
一例として、app/services
配下にモジュールとして Utils::StringConverter
を作成
- app/services/utils/string_converter.rb
module Utils
module StringConverter
module_function
# ハイフンに似た文字列をハイフンに置換する
# excludes => 置換の対象としない文字列
def normalize_hyphens(str, excludes: nil)
return '' if str.nil?
pattern = /[˗ᅳ᭸‐‑‒–—―⁃⁻−▬─━➖ーㅡ﹘﹣-ー𐄐𐆑__‾ ̄~〜~]/
pattern = /#{pattern.to_s.delete(excludes)}/ if excludes
str.gsub(pattern, '-')
end
end
end
任意のモデルでコールバック等利用して変換をかける
- app/models/user.rb
class User < ApplicationRecord
# 省略
before_validate do
self.phone_number = Utils::normalize_hyphens(phone_number) unless phone_number.blank?
self.postal_code = Utils::normalize_hyphens(postal_code) unless postal_code.blank?
unless address_line.blank?
# 住所向けにはカタカナの長音を除くなど
self.address_line = Utils::normalize_hyphens(address_line, excludes: 'ーー')
end
end
# 省略
end
ちなみに各記号毎のUnicode番号、Unicode称号は以下の通り(GPT先生に洗い出してもらった)
記号 | Unicode番号 | Unicode称号 |
---|---|---|
- | U+002D | HYPHEN-MINUS |
˗ | U+02D7 | MODIFIER LETTER MINUS SIGN |
ᅳ | U+1173 | HANGUL JUNGSEONG EU |
᭸ | U+1B78 | BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PANG |
‐ | U+2010 | HYPHEN |
‑ | U+2011 | NON-BREAKING HYPHEN |
‒ | U+2012 | FIGURE DASH |
– | U+2013 | EN DASH |
— | U+2014 | EM DASH |
― | U+2015 | HORIZONTAL BAR |
⁃ | U+2043 | HYPHEN BULLET |
⁻ | U+207B | SUPERSCRIPT MINUS |
− | U+2212 | MINUS SIGN |
▬ | U+25AC | BLACK RECTANGLE |
─ | U+2500 | BOX DRAWINGS LIGHT HORIZONTAL |
━ | U+2501 | BOX DRAWINGS HEAVY HORIZONTAL |
➖ | U+2796 | HEAVY MINUS SIGN |
ー | U+30FC | KATAKANA-HIRAGANA PROLONGED SOUND MARK |
ㅡ | U+3161 | HANGUL LETTER EU |
﹘ | U+FE58 | SMALL EM DASH |
﹣ | U+FE63 | SMALL HYPHEN-MINUS |
- | U+FF0D | FULLWIDTH HYPHEN-MINUS |
ー | U+FF70 | HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK |
𐄐 | U+10110 | AEGEAN NUMBER TEN |
𐆑 | U+10191 | ROMAN UNCIA SIGN |
_ | U+005F | LOW LINE |
_ | U+FF3F | FULLWIDTH LOW LINE |
‾ | U+203E | OVERLINE |
 ̄ | U+FFE3 | FULLWIDTH MACRON |
~ | U+007E | TILDE |
~ | U+FF5E | FULLWIDTH TILDE |
〜 | U+301C | WAVE DASH |
住所については「1 ➖ 1 〜 1 パークコートタワー101」のような文字列を考慮してカタカナの長棒などは対象外とするようにした
こちらの対応について、数字の間にあるハイフンもどきだけを対象に変換をかけられるよう、住所専用のメソッドを設けることにした。
module Utils
module StringConverter
module_function
# ハイフンに似た文字列をハイフンに置換する
# excludes => 置換の対象としない文字列
def normalize_hyphens(str, excludes: nil)
return '' if str.nil?
pattern = /[˗ᅳ᭸‐‑‒–—―⁃⁻−▬─━➖ーㅡ﹘﹣-ー𐄐𐆑__‾ ̄~〜~]/
pattern = /#{pattern.to_s.delete(excludes)}/ if excludes
str.gsub(pattern, '-')
end
+ # 住所に含まれる文字列を考慮したハイフンノーマライズ
+ # 建物名に含まれる長音は残しつつ、それ以外のハイフンに似た文字列をハイフンに置換する
+ # ex. 1ー1 〜 1➖ 1 パークコート101 => 1-1-1-1 パークコート101
+ def normalize_hyphens_in_address(str)
+ # カタカナの長音(全角/半角)を除外してノーマライズ
+ str = normalize_hyphens(str, excludes: 'ーー')
+
+ # 更に数字間に残ったカタカナの長音(全角/半角)をハイフンに置換しつつ、ついでにスペース削除
+ pattern = /(?<=[0-90-9])[ ]*[-ーー]+[ ]*(?=[0-90-9])/
+ str.gsub(pattern, '\1-\2')
+ end
end
end
-
(?<=[0-90-9])
=>「直前に数字(全角含む)がある」 -
[ ]*
=> 「半角スペース/全角スペースが0回以上続く(あってもなくてもマッチ)」 -
[-ーー]+
=> 「[]内の記号が一回以上」 -
(?=[0-90-9])
=> 「直後に数字(全角含む)がある」 -
\1-\2
=> 「(?<=\d|[0-9])で一致した箇所
-(?=\d|[0-9])で一致した箇所
」となるよう置換