動かしてみる
入力して送ると、このページの下に載せたプログラムがサーバーの上で実際に動いて、答えを返します。 ブラウザの側では何も確かめません。送った内容は、どこにも届けず、保存もしません。
ここに答えが出ます。まずは「入力の例」を押して、送ってみてください。
画面にこう出れば正解
どの言語で書いても、同じ入力なら画面に出る文字は1文字も違わない、というのが正解の決め方です(例は Python で動かしたときのコマンドです)。正解表の36件のうち4件を載せています。すべての件を、毎朝10言語で動かして確かめています。
すべて正しい
$ python3 form.py "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
OK
名前 OK 山田 太郎
メール OK taro@example.com
郵便番号 OK 123-4567
電話番号 OK 03-1234-5678
本文 OK 11文字 資料をお送りください。全角で入力(そろえてから確かめる)
$ python3 form.py 山田 太郎 taro@example.com 1234567 03-1234-5678 資料をお送りください。
OK
名前 OK 山田 太郎
メール OK taro@example.com
郵便番号 OK 123-4567
電話番号 OK 03-1234-5678
本文 OK 11文字 資料をお送りください。郵便番号と電話番号は空でよい
$ python3 form.py "山田 太郎" taro@example.com "" "" 資料をお送りください。
OK
名前 OK 山田 太郎
メール OK taro@example.com
郵便番号 OK (なし)
電話番号 OK (なし)
本文 OK 11文字 資料をお送りください。全部まちがい
$ python3 form.py "" taro@ 123 123 ""
NG
名前 NG 入力してください
メール NG メールアドレスの形ではありません
郵便番号 NG 半角の数字7桁で入力してください(例 123-4567)
電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)
本文 NG 入力してください正解表の36件をすべて見る
| 入力 | 画面に出る答え |
|---|---|
c001 すべて正しい山田 太郎taro@example.com123-456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c002 全角で入力(そろえてから確かめる) 山田 太郎 taro@example.com123456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c003 郵便番号と電話番号は空でよい山田 太郎taro@example.com(空)(空)資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK (なし) 電話番号 OK (なし) 本文 OK 11文字 資料をお送りください。 |
c004 名前が空(空)taro@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 NG 入力してください メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c005 名前が空白だけ \ttaro@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 NG 入力してください メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c006 名前の途中に改行山田\n太郎taro@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 NG 改行やタブは使えません メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c007 名前がちょうど50文字(絵文字も1文字)🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣…(50文字)taro@example.com123-456703-1234-5678資料をお送りください。 | OK 名前 OK 🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c008 名前が51文字🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣…(51文字)taro@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 NG 50文字以内で入力してください メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c009 名前に半角カナ(そのまま)ヤマダ タロウtaro@example.com123-456703-1234-5678資料をお送りください。 | OK 名前 OK ヤマダ タロウ メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c010 メールが空山田 太郎(空)123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG 入力してください 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c011 メールにドメインの点がない山田 太郎taro@example123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG メールアドレスの形ではありません 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c012 メールの点が続く山田 太郎taro..yamada@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG メールアドレスの形ではありません 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c013 メールの最後に改行(前後の空白として取る)山田 太郎taro@example.com\n123-456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c014 メールの途中に改行山田 太郎hello\ntaro@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG メールアドレスの形ではありません 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c015 メールにひらがな山田 太郎たろう@example.com123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG メールアドレスの形ではありません 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c016 郵便番号がハイフンなし山田 太郎taro@example.com123456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c017 郵便番号のハイフンが長音「ー」山田 太郎taro@example.com123ー456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c018 郵便番号のハイフンがマイナス記号「−」山田 太郎taro@example.com123−456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c019 郵便番号が6桁山田 太郎taro@example.com123-45603-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 NG 半角の数字7桁で入力してください(例 123-4567) 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c020 郵便番号に全角の数字以外(①)山田 太郎taro@example.com①23-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 NG 半角の数字7桁で入力してください(例 123-4567) 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c021 電話番号がハイフンなし11桁山田 太郎taro@example.com123-456709012345678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 09012345678 本文 OK 11文字 資料をお送りください。 |
c022 電話番号が9桁山田 太郎taro@example.com123-456703-1234-567資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 OK 11文字 資料をお送りください。 |
c023 電話番号が0から始まらない山田 太郎taro@example.com123-45671234567890資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 OK 11文字 資料をお送りください。 |
c024 電話番号のハイフンが続く山田 太郎taro@example.com123-456703--1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 OK 11文字 資料をお送りください。 |
c025 電話番号に +81山田 太郎taro@example.com123-4567+81-3-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 OK 11文字 資料をお送りください。 |
c026 本文が空山田 太郎taro@example.com123-456703-1234-5678(空) | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 NG 入力してください |
c027 本文が改行だけ山田 太郎taro@example.com123-456703-1234-5678\r\n\r\n | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 NG 入力してください |
c028 本文の改行は \r\n でも1文字(ブラウザは \r\n で送る)山田 太郎taro@example.com123-456703-1234-56781行目\r\n2行目 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 7文字 1行目 2行目 |
c029 本文がちょうど1000文字(\r\n を含む)山田 太郎taro@example.com123-456703-1234-5678あい\r\nあい\r\nあい\r\nあい\r\nあい\r\nあい\r\nあい\r…(1333文字) | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 1000文字 あい あい あい あい あい あい あい あい あい あい … |
c030 本文が1001文字山田 太郎taro@example.com123-456703-1234-5678あい\r\nあい\r\nあい\r\nあい\r\nあい\r\nあい\r\nあい\r…(1334文字) | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 NG 1000文字以内で入力してください |
c031 本文の先頭30文字(絵文字)山田 太郎taro@example.com123-456703-1234-5678🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣 | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 31文字 🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣🍣… |
c032 本文がちょうど30文字山田 太郎taro@example.com123-456703-1234-5678ああああああああああああああああああああああああああああああ | OK 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 30文字 ああああああああああああああああああああああああああああああ |
c033 全部まちがい(空)taro@123123(空) | NG 名前 NG 入力してください メール NG メールアドレスの形ではありません 郵便番号 NG 半角の数字7桁で入力してください(例 123-4567) 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 NG 入力してください |
c034 メールのローカル部が長い(点で区切った部分が4500個)山田 太郎a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.…(9011文字)123-456703-1234-5678資料をお送りください。 | OK 名前 OK 山田 太郎 メール OK a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a@example.com 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c035 メールのドメインが長い(点で区切った部分が4500個・最後が数字)山田 太郎taro@a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a…(9006文字)123-456703-1234-5678資料をお送りください。 | NG 名前 OK 山田 太郎 メール NG メールアドレスの形ではありません 郵便番号 OK 123-4567 電話番号 OK 03-1234-5678 本文 OK 11文字 資料をお送りください。 |
c036 電話番号のハイフンが4500個山田 太郎taro@example.com123-45670-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-…(9001文字)資料をお送りください。 | NG 名前 OK 山田 太郎 メール OK taro@example.com 郵便番号 OK 123-4567 電話番号 NG 0から始まる数字10桁か11桁で入力してください(例 03-1234-5678) 本文 OK 11文字 資料をお送りください。 |
10言語のプログラム
どれもそのまま保存して動かせます。毎朝、上の正解の例をすべて実際に動かして確かめています。
form.php8.4.26 で 36 / 36 件の出力が正解と一致
<?php
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
const TRIM = " \t\n\r"; // 全角スペースは、先に半角にしてから取る
const HYPHENS = ['‐', '−', 'ー', 'ー']; // ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
const LOCAL = '[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*';
const LABEL = '[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?';
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)。
// mbstring 拡張が無い環境でも動くように、文字の番号から文字を作るのに html_entity_decode(&#番号; の形)を使い、対応表で置き換える
function to_half(string $s): string
{
static $table = null;
if ($table === null) {
$table = [" " => " "];
for ($code = 0x21; $code <= 0x7E; $code++) {
$table[html_entity_decode("&#" . ($code + 0xFEE0) . ";", ENT_QUOTES, "UTF-8")] = chr($code);
}
}
return strtr($s, $table);
}
// strlen はバイトで数えるので、/u を付けた preg_split で1文字ずつの配列にする(mb_strlen は mbstring が無いと使えない)
function chars(string $s): array
{
return preg_split('//u', $s, -1, PREG_SPLIT_NO_EMPTY);
}
// trim はバイトで取るので、全角スペース(3バイト)は取れない。先に半角にしてから取る
function tidy(string $s): string
{
return trim(to_half($s), TRIM);
}
// 項目ごとに [正しいか, そろえた値か理由] を返す
function check_name(string $v): array
{
$v = tidy($v);
if ($v === '') return [false, '入力してください'];
if (preg_match('/[\t\n\r]/', $v)) return [false, '改行やタブは使えません'];
if (count(chars($v)) > 50) return [false, '50文字以内で入力してください'];
return [true, $v];
}
function check_email(string $v): array
{
$v = tidy($v);
if ($v === '') return [false, '入力してください'];
// 「$」は最後の改行の前にも合うので、文字列の本当の最後を表す「\z」を使う
if (!preg_match('/\A' . LOCAL . '@(' . LABEL . '\.)+[A-Za-z]{2,}\z/', $v)) {
return [false, 'メールアドレスの形ではありません'];
}
return [true, $v];
}
function check_zip(string $v): array
{
$v = str_replace(HYPHENS, '-', tidy($v));
if ($v === '') return [true, '(なし)'];
if (!preg_match('/\A[0-9]{3}-?[0-9]{4}\z/', $v)) return [false, '半角の数字7桁で入力してください(例 123-4567)'];
return [true, substr($v, 0, 3) . '-' . substr($v, -4)];
}
function check_tel(string $v): array
{
$v = str_replace(HYPHENS, '-', tidy($v));
if ($v === '') return [true, '(なし)'];
$digits = str_replace('-', '', $v);
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if (!preg_match('/\A[0-9]+(-[0-9]+)*\z/', $v) || $digits[0] !== '0' || !in_array(strlen($digits), [10, 11], true)) {
return [false, '0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)'];
}
return [true, $v];
}
function check_body(string $v): array
{
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
$v = str_replace(["\r\n", "\r"], "\n", $v);
$v = preg_replace('/\A[ \t\n\r\x{3000}]+|[ \t\n\r\x{3000}]+\z/u', '', $v);
if ($v === '') return [false, '入力してください'];
$c = chars(str_replace("\n", ' ', $v));
$n = count($c);
if ($n > 1000) return [false, '1000文字以内で入力してください'];
$preview = $n > 30 ? implode('', array_slice($c, 0, 30)) . '…' : implode('', $c);
return [true, "{$n}文字 {$preview}"];
}
$fields = [['名前', 'check_name'], ['メール', 'check_email'], ['郵便番号', 'check_zip'], ['電話番号', 'check_tel'], ['本文', 'check_body']];
$rows = [];
foreach ($fields as $i => [$label, $check]) {
$rows[] = [$label, ...$check($argv[$i + 1])];
}
echo (array_product(array_column($rows, 1)) ? 'OK' : 'NG') . "\n";
foreach ($rows as [$label, $ok, $text]) {
echo $label . ' ' . ($ok ? 'OK' : 'NG') . ' ' . $text . "\n";
}
動かし方(ファイルを保存したフォルダで) ・ PHP を入れる
$ php form.php "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.py3.14.7 で 36 / 36 件の出力が正解と一致
# 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
import re
import sys
TRIM = " \t\n\r" # 全角スペースは、先に半角にしてから取る
HYPHENS = "‐−ーー" # ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
LOCAL = r"[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*"
LABEL = r"[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?"
def to_half(s):
"""全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)"""
out = ""
for c in s:
if "!" <= c <= "~":
out += chr(ord(c) - 0xFEE0)
elif c == " ":
out += " "
else:
out += c
return out
def tidy(s):
"""半角にして、前後の空白を取る"""
return to_half(s).strip(TRIM)
def hyphen(s):
for h in HYPHENS:
s = s.replace(h, "-")
return s
# 項目ごとに (正しいか, そろえた値か理由) を返す。Python の len は Unicode の文字で数える
def check_name(v):
v = tidy(v)
if v == "":
return False, "入力してください"
if re.search(r"[\t\n\r]", v):
return False, "改行やタブは使えません"
if len(v) > 50:
return False, "50文字以内で入力してください"
return True, v
def check_email(v):
v = tidy(v)
if v == "":
return False, "入力してください"
# 「\w」は日本語にも合い、「$」は最後の改行の前にも合うので、使う文字を書き出して fullmatch で調べる
if not re.fullmatch(LOCAL + "@(" + LABEL + r"\.)+[A-Za-z]{2,}", v):
return False, "メールアドレスの形ではありません"
return True, v
def check_zip(v):
v = hyphen(tidy(v))
if v == "":
return True, "(なし)"
if not re.fullmatch(r"[0-9]{3}-?[0-9]{4}", v):
return False, "半角の数字7桁で入力してください(例 123-4567)"
return True, v[:3] + "-" + v[-4:]
def check_tel(v):
v = hyphen(tidy(v))
if v == "":
return True, "(なし)"
digits = v.replace("-", "")
# ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if not re.fullmatch(r"[0-9]+(-[0-9]+)*", v) or not digits.startswith("0") or len(digits) not in (10, 11):
return False, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)"
return True, v
def check_body(v):
# ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = v.replace("\r\n", "\n").replace("\r", "\n").strip(TRIM + " ")
if v == "":
return False, "入力してください"
if len(v) > 1000:
return False, "1000文字以内で入力してください"
flat = v.replace("\n", " ")
preview = flat[:30] + "…" if len(flat) > 30 else flat
return True, f"{len(v)}文字 {preview}"
fields = [("名前", check_name), ("メール", check_email), ("郵便番号", check_zip), ("電話番号", check_tel), ("本文", check_body)]
rows = [(label, *check(value)) for (label, check), value in zip(fields, sys.argv[1:6])]
print("OK" if all(ok for _, ok, _ in rows) else "NG")
for label, ok, text in rows:
print(label, "OK" if ok else "NG", text)
動かし方(ファイルを保存したフォルダで) ・ Python を入れる
$ python3 form.py "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.go1.27.2 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
package main
import (
"fmt"
"os"
"regexp"
"strings"
"unicode/utf8"
)
const trim = " \t\n\r" // 全角スペースは、先に半角にしてから取る
// ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
var hyphens = strings.NewReplacer("‐", "-", "−", "-", "ー", "-", "ー", "-")
const local = `[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*`
const label = `[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?`
// Go の「^」「$」は、何も指定しなければ文字列の先頭と最後だけに合う
var email = regexp.MustCompile(`^` + local + `@(` + label + `\.)+[A-Za-z]{2,}$`)
var zipRe = regexp.MustCompile(`^[0-9]{3}-?[0-9]{4}$`)
var telRe = regexp.MustCompile(`^[0-9]+(-[0-9]+)*$`)
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
func toHalf(s string) string {
var b strings.Builder
for _, c := range s {
switch {
case c >= 0xFF01 && c <= 0xFF5E:
b.WriteRune(c - 0xFEE0)
case c == 0x3000:
b.WriteRune(' ')
default:
b.WriteRune(c)
}
}
return b.String()
}
// strings.TrimSpace は全角スペースなども取るので、取る文字を決めて Trim を使う
func tidy(s string) string { return strings.Trim(toHalf(s), trim) }
// 項目ごとに (正しいか, そろえた値か理由) を返す。len はバイトで数えるので、utf8.RuneCountInString で文字を数える
func checkName(v string) (bool, string) {
v = tidy(v)
if v == "" {
return false, "入力してください"
}
if strings.ContainsAny(v, "\t\n\r") {
return false, "改行やタブは使えません"
}
if utf8.RuneCountInString(v) > 50 {
return false, "50文字以内で入力してください"
}
return true, v
}
func checkEmail(v string) (bool, string) {
v = tidy(v)
if v == "" {
return false, "入力してください"
}
if !email.MatchString(v) {
return false, "メールアドレスの形ではありません"
}
return true, v
}
func checkZip(v string) (bool, string) {
v = hyphens.Replace(tidy(v))
if v == "" {
return true, "(なし)"
}
if !zipRe.MatchString(v) {
return false, "半角の数字7桁で入力してください(例 123-4567)"
}
return true, v[:3] + "-" + v[len(v)-4:]
}
func checkTel(v string) (bool, string) {
v = hyphens.Replace(tidy(v))
if v == "" {
return true, "(なし)"
}
digits := strings.ReplaceAll(v, "-", "")
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if !telRe.MatchString(v) || !strings.HasPrefix(digits, "0") || (len(digits) != 10 && len(digits) != 11) {
return false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)"
}
return true, v
}
func checkBody(v string) (bool, string) {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = strings.ReplaceAll(strings.ReplaceAll(v, "\r\n", "\n"), "\r", "\n")
v = strings.Trim(v, trim+" ")
if v == "" {
return false, "入力してください"
}
chars := []rune(strings.ReplaceAll(v, "\n", " "))
if len(chars) > 1000 {
return false, "1000文字以内で入力してください"
}
preview := string(chars)
if len(chars) > 30 {
preview = string(chars[:30]) + "…"
}
return true, fmt.Sprintf("%d文字 %s", len(chars), preview)
}
func main() {
labels := []string{"名前", "メール", "郵便番号", "電話番号", "本文"}
checks := []func(string) (bool, string){checkName, checkEmail, checkZip, checkTel, checkBody}
all := true
lines := []string{}
for i, check := range checks {
ok, text := check(os.Args[i+1])
mark := "OK"
if !ok {
mark, all = "NG", false
}
lines = append(lines, labels[i]+" "+mark+" "+text)
}
if all {
fmt.Println("OK")
} else {
fmt.Println("NG")
}
for _, l := range lines {
fmt.Println(l)
}
}
動かし方(ファイルを保存したフォルダで) ・ Go を入れる
$ go run form.go "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.js24.15.0 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
const TRIM = " \t\n\r"; // 全角スペースは、先に半角にしてから取る
const HYPHENS = /[‐−ーー]/g; // ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
const LOCAL = String.raw`[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*`;
const LABEL = String.raw`[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?`;
const EMAIL = new RegExp(`^${LOCAL}@(${LABEL}\\.)+[A-Za-z]{2,}$`);
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
function toHalf(s) {
return s.replace(/[!-~]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xfee0)).replace(/ /g, " ");
}
// 取る文字を決めて前後を取る(trim() は全角スペースなども取るので、決まりと合わせるため自分で書く)
function strip(s, chars) {
let a = 0, b = s.length;
while (a < b && chars.includes(s[a])) a++;
while (b > a && chars.includes(s[b - 1])) b--;
return s.slice(a, b);
}
const tidy = s => strip(toHalf(s), TRIM);
// s.length は UTF-16 の単位で数える(🍣 は 2)。[...s] で Unicode の文字の並びにしてから数える
const chars = s => [...s];
// 項目ごとに [正しいか, そろえた値か理由] を返す
function checkName(v) {
v = tidy(v);
if (v === "") return [false, "入力してください"];
if (/[\t\n\r]/.test(v)) return [false, "改行やタブは使えません"];
if (chars(v).length > 50) return [false, "50文字以内で入力してください"];
return [true, v];
}
function checkEmail(v) {
v = tidy(v);
if (v === "") return [false, "入力してください"];
// JavaScript の「$」は、m を付けなければ文字列の最後にだけ合う
if (!EMAIL.test(v)) return [false, "メールアドレスの形ではありません"];
return [true, v];
}
function checkZip(v) {
v = tidy(v).replace(HYPHENS, "-");
if (v === "") return [true, "(なし)"];
if (!/^[0-9]{3}-?[0-9]{4}$/.test(v)) return [false, "半角の数字7桁で入力してください(例 123-4567)"];
return [true, v.slice(0, 3) + "-" + v.slice(-4)];
}
function checkTel(v) {
v = tidy(v).replace(HYPHENS, "-");
if (v === "") return [true, "(なし)"];
const digits = v.replaceAll("-", "");
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if (!/^[0-9]+(-[0-9]+)*$/.test(v) || !digits.startsWith("0") || ![10, 11].includes(digits.length)) {
return [false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)"];
}
return [true, v];
}
function checkBody(v) {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = strip(v.replace(/\r\n?/g, "\n"), TRIM + " ");
if (v === "") return [false, "入力してください"];
const c = chars(v);
if (c.length > 1000) return [false, "1000文字以内で入力してください"];
const flat = c.map(x => (x === "\n" ? " " : x));
const preview = flat.length > 30 ? flat.slice(0, 30).join("") + "…" : flat.join("");
return [true, `${c.length}文字 ${preview}`];
}
const fields = [["名前", checkName], ["メール", checkEmail], ["郵便番号", checkZip], ["電話番号", checkTel], ["本文", checkBody]];
const rows = fields.map(([label, check], i) => [label, ...check(process.argv[2 + i])]);
console.log(rows.every(r => r[1]) ? "OK" : "NG");
for (const [label, ok, text] of rows) console.log(`${label} ${ok ? "OK" : "NG"} ${text}`);
動かし方(ファイルを保存したフォルダで) ・ JavaScript を入れる
$ node form.js "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
Form.java21.0.12 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
import java.util.List;
import java.util.function.Function;
public class Form {
static final String TRIM = " \t\n\r"; // 全角スペースは、先に半角にしてから取る
static final String LABEL = "[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?";
record Result(boolean ok, String text) {}
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
static String toHalf(String s) {
StringBuilder b = new StringBuilder();
s.codePoints().forEach(c -> {
if (c >= 0xFF01 && c <= 0xFF5E) b.appendCodePoint(c - 0xFEE0);
else if (c == 0x3000) b.append(' ');
else b.appendCodePoint(c);
});
return b.toString();
}
// trim() は「U+0020 以下の文字」を、strip() は全角スペースも取るので、取る文字を決めて自分で書く
static String strip(String s, String chars) {
int a = 0, b = s.length();
while (a < b && chars.indexOf(s.charAt(a)) >= 0) a++;
while (b > a && chars.indexOf(s.charAt(b - 1)) >= 0) b--;
return s.substring(a, b);
}
static String tidy(String s) { return strip(toHalf(s), TRIM); }
// ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
static String hyphen(String s) { return s.replaceAll("[‐−ーー]", "-"); }
// length() は UTF-16 の単位で数える(🍣 は 2)。codePointCount で Unicode の文字を数える
static int count(String s) { return s.codePointCount(0, s.length()); }
static Result checkName(String v) {
v = tidy(v);
if (v.isEmpty()) return new Result(false, "入力してください");
if (v.matches("(?s).*[\\t\\n\\r].*")) return new Result(false, "改行やタブは使えません");
if (count(v) > 50) return new Result(false, "50文字以内で入力してください");
return new Result(true, v);
}
// 区切りの文字で分けて、どの部分も pattern に合うか(空の部分があれば合わない)。
// 「[0-9]+(-[0-9]+)*」のように ( )* で繰り返す正規表現は、Java では繰り返し1回ごとに再帰で調べるので、
// 繰り返しが数千回になると StackOverflowError で止まる(正解表の c034〜c036)。分けてから1つずつ調べる
static boolean allParts(String s, String sepRegex, String pattern) {
for (String p : s.split(sepRegex, -1)) if (!p.matches(pattern)) return false;
return true;
}
static Result checkEmail(String v) {
v = tidy(v);
if (v.isEmpty()) return new Result(false, "入力してください");
// 「@」で2つに分け、ローカル部は「.」で区切った部分ごとに、ドメインは「.」で区切ったラベルごとに確かめる
String[] at = v.split("@", -1);
String[] labels = at.length == 2 ? at[1].split("\\.", -1) : new String[0];
boolean ok = at.length == 2 && allParts(at[0], "\\.", "[A-Za-z0-9_+-]+") && labels.length >= 2
&& labels[labels.length - 1].matches("[A-Za-z]{2,}");
for (int i = 0; ok && i < labels.length - 1; i++) ok = labels[i].matches(LABEL);
if (!ok) return new Result(false, "メールアドレスの形ではありません");
return new Result(true, v);
}
static Result checkZip(String v) {
v = hyphen(tidy(v));
if (v.isEmpty()) return new Result(true, "(なし)");
if (!v.matches("[0-9]{3}-?[0-9]{4}")) return new Result(false, "半角の数字7桁で入力してください(例 123-4567)");
return new Result(true, v.substring(0, 3) + "-" + v.substring(v.length() - 4));
}
static Result checkTel(String v) {
v = hyphen(tidy(v));
if (v.isEmpty()) return new Result(true, "(なし)");
String digits = v.replace("-", "");
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if (!allParts(v, "-", "[0-9]+") || !digits.startsWith("0") || (digits.length() != 10 && digits.length() != 11)) {
return new Result(false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)");
}
return new Result(true, v);
}
static Result checkBody(String v) {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = strip(v.replace("\r\n", "\n").replace("\r", "\n"), TRIM + " ");
if (v.isEmpty()) return new Result(false, "入力してください");
int n = count(v);
if (n > 1000) return new Result(false, "1000文字以内で入力してください");
String flat = v.replace("\n", " ");
// substring(0, 30) は UTF-16 の単位で切るので、文字の位置を offsetByCodePoints で求める
String preview = n > 30 ? flat.substring(0, flat.offsetByCodePoints(0, 30)) + "…" : flat;
return new Result(true, n + "文字 " + preview);
}
public static void main(String[] args) {
List<String> labels = List.of("名前", "メール", "郵便番号", "電話番号", "本文");
List<Function<String, Result>> checks = List.of(Form::checkName, Form::checkEmail, Form::checkZip, Form::checkTel, Form::checkBody);
StringBuilder lines = new StringBuilder();
boolean all = true;
for (int i = 0; i < checks.size(); i++) {
Result r = checks.get(i).apply(args[i]);
all &= r.ok();
lines.append(labels.get(i)).append(r.ok() ? " OK " : " NG ").append(r.text()).append("\n");
}
System.out.print((all ? "OK" : "NG") + "\n" + lines);
}
}
動かし方(ファイルを保存したフォルダで) ・ Java を入れる
$ java Form.java "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.cs10.0.402 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
using System.Text;
using System.Text.RegularExpressions;
const string Local = @"[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*";
const string Label = @"[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?";
char[] trimChars = { ' ', '\t', '\n', '\r' }; // 全角スペースは、先に半角にしてから取る
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
string ToHalf(string s)
{
var b = new StringBuilder();
foreach (char c in s)
{
if (c >= '!' && c <= '~') b.Append((char)(c - 0xFEE0));
else if (c == ' ') b.Append(' ');
else b.Append(c);
}
return b.ToString();
}
// Trim() は全角スペースなども取るので、取る文字を決めて Trim(chars) を使う
string Tidy(string s) => ToHalf(s).Trim(trimChars);
// ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
string Hyphen(string s) => Regex.Replace(s, "[‐−ーー]", "-");
// Length は UTF-16 の単位で数える(🍣 は 2)。EnumerateRunes で Unicode の文字の並びにする
string[] Chars(string s) => s.EnumerateRunes().Select(r => r.ToString()).ToArray();
// $ は最後の改行の前にも合うので、\A と \z で挟む
bool Whole(string s, string pattern) => Regex.IsMatch(s, $@"\A(?:{pattern})\z");
(bool, string) CheckName(string v)
{
v = Tidy(v);
if (v == "") return (false, "入力してください");
if (v.IndexOfAny(new[] { '\t', '\n', '\r' }) >= 0) return (false, "改行やタブは使えません");
if (Chars(v).Length > 50) return (false, "50文字以内で入力してください");
return (true, v);
}
(bool, string) CheckEmail(string v)
{
v = Tidy(v);
if (v == "") return (false, "入力してください");
if (!Whole(v, $@"{Local}@({Label}\.)+[A-Za-z]{{2,}}")) return (false, "メールアドレスの形ではありません");
return (true, v);
}
(bool, string) CheckZip(string v)
{
v = Hyphen(Tidy(v));
if (v == "") return (true, "(なし)");
if (!Whole(v, "[0-9]{3}-?[0-9]{4}")) return (false, "半角の数字7桁で入力してください(例 123-4567)");
return (true, v[..3] + "-" + v[^4..]);
}
(bool, string) CheckTel(string v)
{
v = Hyphen(Tidy(v));
if (v == "") return (true, "(なし)");
string digits = v.Replace("-", "");
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if (!Whole(v, "[0-9]+(-[0-9]+)*") || !digits.StartsWith('0') || (digits.Length != 10 && digits.Length != 11))
return (false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)");
return (true, v);
}
(bool, string) CheckBody(string v)
{
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = v.Replace("\r\n", "\n").Replace("\r", "\n").Trim(' ', '\t', '\n', '\r', ' ');
if (v == "") return (false, "入力してください");
string[] chars = Chars(v.Replace("\n", " "));
if (chars.Length > 1000) return (false, "1000文字以内で入力してください");
string preview = chars.Length > 30 ? string.Concat(chars.Take(30)) + "…" : string.Concat(chars);
return (true, $"{chars.Length}文字 {preview}");
}
var fields = new (string, Func<string, (bool, string)>)[]
{
("名前", CheckName), ("メール", CheckEmail), ("郵便番号", CheckZip), ("電話番号", CheckTel), ("本文", CheckBody),
};
var rows = fields.Select((f, i) => (label: f.Item1, result: f.Item2(args[i]))).ToList();
Console.WriteLine(rows.All(r => r.result.Item1) ? "OK" : "NG");
foreach (var (label, (ok, text)) in rows) Console.WriteLine($"{label} {(ok ? "OK" : "NG")} {text}");
動かし方(ファイルを保存したフォルダで) ・ C# を入れる
$ dotnet run form.cs -- "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.rs1.99.0 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
// Rust の標準ライブラリには正規表現がないので、決まりを1つずつ確かめる
const TRIM: &[char] = &[' ', '\t', '\n', '\r']; // 全角スペースは、先に半角にしてから取る
const HYPHENS: &[char] = &['‐', '−', 'ー', 'ー']; // ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
fn to_half(s: &str) -> String {
s.chars()
.map(|c| match c {
'\u{FF01}'..='\u{FF5E}' => char::from_u32(c as u32 - 0xFEE0).unwrap(),
'\u{3000}' => ' ',
_ => c,
})
.collect()
}
// trim() は全角スペースなども取るので、取る文字を決めて trim_matches を使う
fn tidy(s: &str) -> String {
to_half(s).trim_matches(TRIM).to_string()
}
// 半角の数字だけでできているか(is_numeric は全角の数字にも合うので、is_ascii_digit を使う)
fn digits_only(s: &str) -> bool {
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
}
// メールアドレスの形(第16回)。ローカル部: 英数字と「. _ + -」、先頭・最後は「.」でなく「..」と続かない
fn is_email(s: &str) -> bool {
let parts: Vec<&str> = s.split('@').collect();
if parts.len() != 2 {
return false;
}
let local = parts[0];
let local_ok = !local.is_empty()
&& local.chars().all(|c| c.is_ascii_alphanumeric() || "._+-".contains(c))
&& !local.starts_with('.')
&& !local.ends_with('.')
&& !local.contains("..");
// ドメイン: 「.」で区切った2つ以上の部分。各部分は英数字と「-」、先頭・最後は「-」でない。最後は英字2文字以上
let labels: Vec<&str> = parts[1].split('.').collect();
let last = labels[labels.len() - 1];
local_ok
&& labels.len() >= 2
&& labels.iter().all(|l| {
!l.is_empty() && l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') && !l.starts_with('-') && !l.ends_with('-')
})
&& last.len() >= 2
&& last.chars().all(|c| c.is_ascii_alphabetic())
}
// 項目ごとに (正しいか, そろえた値か理由) を返す。len() はバイトで数えるので、chars().count() で文字を数える
fn check_name(v: &str) -> (bool, String) {
let v = tidy(v);
if v.is_empty() {
(false, "入力してください".into())
} else if v.contains(['\t', '\n', '\r']) {
(false, "改行やタブは使えません".into())
} else if v.chars().count() > 50 {
(false, "50文字以内で入力してください".into())
} else {
(true, v)
}
}
fn check_email(v: &str) -> (bool, String) {
let v = tidy(v);
if v.is_empty() {
(false, "入力してください".into())
} else if !is_email(&v) {
(false, "メールアドレスの形ではありません".into())
} else {
(true, v)
}
}
fn check_zip(v: &str) -> (bool, String) {
let v = tidy(v).replace(HYPHENS, "-");
if v.is_empty() {
return (true, "(なし)".into());
}
// 「123-4567」か「1234567」
let ok = match v.split_once('-') {
Some((a, b)) => a.len() == 3 && b.len() == 4 && digits_only(a) && digits_only(b),
None => v.len() == 7 && digits_only(&v),
};
if !ok {
return (false, "半角の数字7桁で入力してください(例 123-4567)".into());
}
(true, format!("{}-{}", &v[..3], &v[v.len() - 4..]))
}
fn check_tel(v: &str) -> (bool, String) {
let v = tidy(v).replace(HYPHENS, "-");
if v.is_empty() {
return (true, "(なし)".into());
}
let digits = v.replace('-', "");
// ハイフンは数字の間に1つずつ(区切ると、どの部分も空でない数字になる)。0から始まり、数字は10桁か11桁
let ok = v.split('-').all(digits_only) && digits.starts_with('0') && (digits.len() == 10 || digits.len() == 11);
if !ok {
return (false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)".into());
}
(true, v)
}
fn check_body(v: &str) -> (bool, String) {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
let v = v.replace("\r\n", "\n").replace('\r', "\n");
let v = v.trim_matches(&[' ', '\t', '\n', '\r', '\u{3000}'][..]);
if v.is_empty() {
return (false, "入力してください".into());
}
let chars: Vec<char> = v.chars().map(|c| if c == '\n' { ' ' } else { c }).collect();
if chars.len() > 1000 {
return (false, "1000文字以内で入力してください".into());
}
let preview: String = if chars.len() > 30 { chars[..30].iter().collect::<String>() + "…" } else { chars.iter().collect() };
(true, format!("{}文字 {}", chars.len(), preview))
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let fields: [(&str, fn(&str) -> (bool, String)); 5] =
[("名前", check_name), ("メール", check_email), ("郵便番号", check_zip), ("電話番号", check_tel), ("本文", check_body)];
let rows: Vec<(&str, bool, String)> = fields.iter().enumerate().map(|(i, (label, check))| {
let (ok, text) = check(&args[i + 1]);
(*label, ok, text)
}).collect();
println!("{}", if rows.iter().all(|r| r.1) { "OK" } else { "NG" });
for (label, ok, text) in rows {
println!("{} {} {}", label, if ok { "OK" } else { "NG" }, text);
}
}
動かし方(ファイルを保存したフォルダで) ・ Rust を入れる
$ rustc form.rs && ./form "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.rb4.0.7 で 36 / 36 件の出力が正解と一致
# 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
TRIM = " \t\n\r" # 全角スペースは、先に半角にしてから取る
HYPHENS = /[‐−ーー]/ # ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
LOCAL = '[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*'
LABEL = '[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?'
# Ruby の「^」「$」は行の先頭・最後にも合うので、文字列の先頭・最後だけを表す「\A」「\z」を使う
EMAIL = /\A#{LOCAL}@(#{LABEL}\.)+[A-Za-z]{2,}\z/
# 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
def to_half(s)
s.gsub(/[!-~]/) { |c| (c.ord - 0xFEE0).chr }.gsub(" ", " ")
end
# 取る文字を決めて前後を取る
def strip_chars(s, chars)
s.sub(/\A[#{Regexp.escape(chars)}]+/, "").sub(/[#{Regexp.escape(chars)}]+\z/, "")
end
def tidy(s) = strip_chars(to_half(s), TRIM)
# 項目ごとに [正しいか, そろえた値か理由] を返す。Ruby の length は Unicode の文字で数える
def check_name(v)
v = tidy(v)
return [false, "入力してください"] if v.empty?
return [false, "改行やタブは使えません"] if v.match?(/[\t\n\r]/)
return [false, "50文字以内で入力してください"] if v.length > 50
[true, v]
end
def check_email(v)
v = tidy(v)
return [false, "入力してください"] if v.empty?
return [false, "メールアドレスの形ではありません"] unless v.match?(EMAIL)
[true, v]
end
def check_zip(v)
v = tidy(v).gsub(HYPHENS, "-")
return [true, "(なし)"] if v.empty?
return [false, "半角の数字7桁で入力してください(例 123-4567)"] unless v.match?(/\A[0-9]{3}-?[0-9]{4}\z/)
[true, "#{v[0, 3]}-#{v[-4, 4]}"]
end
def check_tel(v)
v = tidy(v).gsub(HYPHENS, "-")
return [true, "(なし)"] if v.empty?
digits = v.delete("-")
# ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
unless v.match?(/\A[0-9]+(-[0-9]+)*\z/) && digits.start_with?("0") && [10, 11].include?(digits.length)
return [false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)"]
end
[true, v]
end
def check_body(v)
# ブラウザは改行を \r\n で送るので、\n にそろえてから数える
v = strip_chars(v.gsub(/\r\n?/, "\n"), TRIM + " ")
return [false, "入力してください"] if v.empty?
return [false, "1000文字以内で入力してください"] if v.length > 1000
flat = v.tr("\n", " ")
preview = flat.length > 30 ? flat[0, 30] + "…" : flat
[true, "#{v.length}文字 #{preview}"]
end
fields = [["名前", :check_name], ["メール", :check_email], ["郵便番号", :check_zip], ["電話番号", :check_tel], ["本文", :check_body]]
rows = fields.each_with_index.map { |(label, check), i| [label, *send(check, ARGV[i])] }
puts(rows.all? { |r| r[1] } ? "OK" : "NG")
rows.each { |label, ok, text| puts "#{label} #{ok ? "OK" : "NG"} #{text}" }
動かし方(ファイルを保存したフォルダで) ・ Ruby を入れる
$ ruby form.rb "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.kt2.4.21 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
const val TRIM = " \t\n\r" // 全角スペースは、先に半角にしてから取る
const val LABEL = """[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?"""
val HYPHENS = Regex("[‐−ーー]") // ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
fun toHalf(s: String): String = buildString {
s.codePoints().forEach { c ->
when {
c in 0xFF01..0xFF5E -> appendCodePoint(c - 0xFEE0)
c == 0x3000 -> append(' ')
else -> appendCodePoint(c)
}
}
}
// trim() は全角スペースなども取るので、取る文字を決めて trim { } を使う
fun tidy(s: String) = toHalf(s).trim { it in TRIM }
// length は UTF-16 の単位で数える(🍣 は 2)。codePointCount で Unicode の文字を数える
fun count(s: String) = s.codePointCount(0, s.length)
// 項目ごとに (正しいか, そろえた値か理由) を返す
fun checkName(raw: String): Pair<Boolean, String> {
val v = tidy(raw)
return when {
v.isEmpty() -> false to "入力してください"
v.any { it in "\t\n\r" } -> false to "改行やタブは使えません"
count(v) > 50 -> false to "50文字以内で入力してください"
else -> true to v
}
}
// 区切りの文字で分けて、どの部分も pattern に合うか(空の部分があれば合わない)。
// Kotlin の Regex は Java の正規表現を使う。「[0-9]+(-[0-9]+)*」のように ( )* で繰り返すと、繰り返し1回ごとに再帰で調べるので、
// 繰り返しが数千回になると StackOverflowError で止まる(正解表の c034〜c036)。分けてから1つずつ調べる
fun allParts(s: String, sep: String, pattern: String) = s.split(sep).all { it.matches(Regex(pattern)) }
// 「@」で2つに分け、ローカル部は「.」で区切った部分ごとに、ドメインは「.」で区切ったラベルごとに確かめる
fun isEmail(v: String): Boolean {
val at = v.split("@")
if (at.size != 2 || !allParts(at[0], ".", "[A-Za-z0-9_+-]+")) return false
val labels = at[1].split(".")
return labels.size >= 2 && labels.last().matches(Regex("[A-Za-z]{2,}")) && labels.dropLast(1).all { it.matches(Regex(LABEL)) }
}
fun checkEmail(raw: String): Pair<Boolean, String> {
val v = tidy(raw)
return when {
v.isEmpty() -> false to "入力してください"
!isEmail(v) -> false to "メールアドレスの形ではありません"
else -> true to v
}
}
fun checkZip(raw: String): Pair<Boolean, String> {
val v = tidy(raw).replace(HYPHENS, "-")
return when {
v.isEmpty() -> true to "(なし)"
!v.matches(Regex("[0-9]{3}-?[0-9]{4}")) -> false to "半角の数字7桁で入力してください(例 123-4567)"
else -> true to v.take(3) + "-" + v.takeLast(4)
}
}
fun checkTel(raw: String): Pair<Boolean, String> {
val v = tidy(raw).replace(HYPHENS, "-")
val digits = v.replace("-", "")
return when {
v.isEmpty() -> true to "(なし)"
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
!allParts(v, "-", "[0-9]+") || !digits.startsWith("0") || digits.length !in 10..11 ->
false to "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)"
else -> true to v
}
}
fun checkBody(raw: String): Pair<Boolean, String> {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
val v = raw.replace("\r\n", "\n").replace("\r", "\n").trim { it in TRIM + " " }
if (v.isEmpty()) return false to "入力してください"
val n = count(v)
if (n > 1000) return false to "1000文字以内で入力してください"
val flat = v.replace("\n", " ")
// take(30) は UTF-16 の単位で切るので、文字の位置を offsetByCodePoints で求める
val preview = if (n > 30) flat.substring(0, flat.offsetByCodePoints(0, 30)) + "…" else flat
return true to "${n}文字 $preview"
}
fun main(args: Array<String>) {
val fields = listOf("名前" to ::checkName, "メール" to ::checkEmail, "郵便番号" to ::checkZip, "電話番号" to ::checkTel, "本文" to ::checkBody)
val rows = fields.mapIndexed { i, (label, check) -> label to check(args[i]) }
println(if (rows.all { it.second.first }) "OK" else "NG")
for ((label, r) in rows) println("$label ${if (r.first) "OK" else "NG"} ${r.second}")
}
動かし方(ファイルを保存したフォルダで) ・ Kotlin を入れる
$ kotlinc form.kt -include-runtime -d form.jar && java -jar form.jar "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
form.swift6.3.3 で 36 / 36 件の出力が正解と一致
// 問い合わせフォームの5つの項目をそろえて、正しいかを確かめる
let trimChars: Set<Character> = [" ", "\t", "\n", "\r"] // 全角スペースは、先に半角にしてから取る
let hyphens: Set<Character> = ["‐", "−", "ー", "ー"] // ハイフンに似た文字(全角の「-」は半角にする処理で「-」になる)
let local = #"[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*"#
let label = #"[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?"#
// 全角の英数字・記号(U+FF01〜FF5E)と全角スペースを半角にする(第9回)
func toHalf(_ s: String) -> String {
var out = String.UnicodeScalarView()
for u in s.unicodeScalars {
if u.value >= 0xFF01 && u.value <= 0xFF5E {
out.append(Unicode.Scalar(u.value - 0xFEE0)!)
} else if u.value == 0x3000 {
out.append(" ")
} else {
out.append(u)
}
}
return String(out)
}
// Swift の String は「\r\n」を1つの文字(Character)として扱うので、文字ではなく Unicode の文字(unicodeScalars)で前後を取る
func strip(_ s: String, _ chars: Set<Character>) -> String {
var u = Array(s.unicodeScalars)
while let f = u.first, chars.contains(Character(f)) { u.removeFirst() }
while let l = u.last, chars.contains(Character(l)) { u.removeLast() }
var v = String.UnicodeScalarView()
v.append(contentsOf: u)
return String(v)
}
func tidy(_ s: String) -> String { strip(toHalf(s), trimChars) }
func hyphen(_ s: String) -> String { String(s.map { hyphens.contains($0) ? "-" : $0 }) }
// wholeMatch は文字列全体が合うかを調べる
func whole(_ s: String, _ pattern: String) -> Bool { s.wholeMatch(of: try! Regex(pattern)) != nil }
// count は「見た目の1文字」(書記素)で数えるので、決まりどおり Unicode の文字(unicodeScalars)で数える
func checkName(_ raw: String) -> (Bool, String) {
let v = tidy(raw)
if v.isEmpty { return (false, "入力してください") }
if v.unicodeScalars.contains(where: { $0 == "\t" || $0 == "\n" || $0 == "\r" }) { return (false, "改行やタブは使えません") }
if v.unicodeScalars.count > 50 { return (false, "50文字以内で入力してください") }
return (true, v)
}
func checkEmail(_ raw: String) -> (Bool, String) {
let v = tidy(raw)
if v.isEmpty { return (false, "入力してください") }
if !whole(v, local + "@(" + label + #"\.)+[A-Za-z]{2,}"#) { return (false, "メールアドレスの形ではありません") }
return (true, v)
}
func checkZip(_ raw: String) -> (Bool, String) {
let v = hyphen(tidy(raw))
if v.isEmpty { return (true, "(なし)") }
if !whole(v, "[0-9]{3}-?[0-9]{4}") { return (false, "半角の数字7桁で入力してください(例 123-4567)") }
return (true, v.prefix(3) + "-" + v.suffix(4))
}
func checkTel(_ raw: String) -> (Bool, String) {
let v = hyphen(tidy(raw))
if v.isEmpty { return (true, "(なし)") }
let digits = v.filter { $0 != "-" }
// ハイフンは数字の間に1つずつ。0から始まり、数字は10桁か11桁
if !whole(v, "[0-9]+(-[0-9]+)*") || !digits.hasPrefix("0") || !(digits.count == 10 || digits.count == 11) {
return (false, "0から始まる数字10桁か11桁で入力してください(例 03-1234-5678)")
}
return (true, v)
}
func checkBody(_ raw: String) -> (Bool, String) {
// ブラウザは改行を \r\n で送るので、\n にそろえてから数える
var scalars: [Unicode.Scalar] = []
var prev: Unicode.Scalar? = nil
for u in raw.unicodeScalars {
// \r の直後の \n は、\r を \n にした分と合わせて1つの改行
if !(u == "\n" && prev == "\r") { scalars.append(u == "\r" ? "\n" : u) }
prev = u
}
var view = String.UnicodeScalarView()
view.append(contentsOf: scalars)
let v = strip(String(view), trimChars.union(["\u{3000}"]))
if v.isEmpty { return (false, "入力してください") }
let chars = v.unicodeScalars.map { $0 == "\n" ? " " : String($0) }
if chars.count > 1000 { return (false, "1000文字以内で入力してください") }
let preview = chars.count > 30 ? chars.prefix(30).joined() + "…" : chars.joined()
return (true, "\(chars.count)文字 \(preview)")
}
let args = CommandLine.arguments
let fields: [(String, (String) -> (Bool, String))] = [("名前", checkName), ("メール", checkEmail), ("郵便番号", checkZip), ("電話番号", checkTel), ("本文", checkBody)]
let rows = fields.enumerated().map { i, f in (f.0, f.1(args[i + 1])) }
print(rows.allSatisfy { $0.1.0 } ? "OK" : "NG")
for (label, r) in rows { print("\(label) \(r.0 ? "OK" : "NG") \(r.1)") }
動かし方(ファイルを保存したフォルダで) ・ Swift を入れる
$ swift form.swift "山田 太郎" taro@example.com 123-4567 03-1234-5678 資料をお送りください。
この回でやること
- フォームで受け取った5つの項目を、そろえてから確かめる(全角を半角に・前後の空白を取る・改行をそろえる)
- 正しくない項目には、直し方の分かる理由を返す
- 同じ入力を10言語に送り、10言語の答えが1文字も違わないことを確かめる
これまでの回が、そのまま部品になります。
| 部品 | 使うところ |
|---|---|
| 第9回 全角を半角に | 名前・メール・郵便番号・電話番号 |
| 第7回 郵便番号の形 | 郵便番号 |
| 第16回 メールアドレスの形 | メール |
| 第4回・第15回 文字数・先頭だけ出す | 名前の文字数・本文の文字数とプレビュー |
この回の決まり
プログラムは、5つの項目を引数で受け取り(名前・メール・郵便番号・電話番号・本文の順。空の項目は空の文字列)、1行目に OK(全部正しい)か NG を、続けて項目ごとに <項目> OK <そろえた値> か <項目> NG <理由> を出します。
| 項目 | 必須 | 決まり |
|---|---|---|
| 名前 | ○ | 半角にして前後の空白を取る。途中に改行・タブがあれば NG。50文字まで |
| メール | ○ | 半角にして前後の空白を取ってから、第16回の形を確かめる(全角の @ も通る) |
| 郵便番号 | 半角にして、ハイフンに似た文字(‐ − ー ー)を - に。123-4567 か 1234567。出すときは 123-4567 の形 | |
| 電話番号 | 郵便番号と同じくそろえる。数字とハイフンだけで、0から始まり、数字が10桁か11桁 | |
| 本文 | ○ | 改行を \n にそろえてから前後の空白を取る。1000文字まで。先頭30文字のプレビューを付ける |
- 前後の空白として取るのは、半角スペース・タブ・改行・全角スペースだけです
- 文字数は Unicode の文字(コードポイント)で数えます。絵文字も1文字です
- 郵便番号・電話番号は空でもかまいません(
(なし)と出します)
解説
確かめる前に、そろえる
人が入力する値には、全角の数字(1234567)、全角の @、うっかり入った前後の空白、長音「ー」のハイフンなどが混ざります。そのまま正規表現で確かめると、正しい内容なのに NG になります。この回では、先にそろえてから、形を確かめる順番にしています。そろえた値を返すので、保存するときもそろった形で残せます。
「前後の空白を取る」関数は、言語ごとに取る文字が違う
どの言語にも前後の空白を取る関数がありますが、何を空白とみなすかが違います。PHP の trim は全角スペースを取りません。一方、JavaScript の trim()・Java の strip()・C# の Trim()・Go の strings.TrimSpace・Rust の trim()・Kotlin の trim() は、全角スペースのほかにも、いくつかの空白の文字を取ります。
10言語で答えをそろえるため、この回では取る文字(半角スペース・タブ・\n・\r)を決めて、それだけを取る書き方にしました。全角スペースは、先に半角にしてから取ります。
文字数は「Unicode の文字」で数える
length が何を数えるかも、言語ごとに違います(第4回)。
| 数え方 | 言語 | この回での書き方 |
|---|---|---|
| Unicode の文字 | Python・Ruby | len・length そのまま |
| UTF-16 の単位(絵文字1つが 2) | JavaScript・Java・C#・Kotlin | [...s]・codePointCount・EnumerateRunes |
| バイト(絵文字1つが 4) | PHP・Go・Rust | preg_split('//u', …)・utf8.RuneCountInString・chars().count() |
| 見た目の1文字(書記素) | Swift | unicodeScalars.count |
ブラウザは、改行を \r\n で送る
フォームの複数行の欄(textarea)の改行は、送るときに \r\n(2文字)になります。そのまま数えると、改行1つが2文字になり、ちょうど1000文字の本文が NG になります(正解表の c029)。本文は、\r\n と \r を \n にそろえてから数えています。下の「動かしてみる」も、ふつうのフォームと同じく改行を \r\n にして送っています。
Swift の String は \r\n を1つの文字として扱うので、前後を取るときも Unicode の文字(unicodeScalars)で見ています。
引数で受け取る理由
ブラウザとサーバーのやりとりは JSON ですが、各言語のプログラムは引数で受け取り、決まった形の行を出すだけにしました。Java・Kotlin・Rust は、標準の機能だけでは JSON を読めない(ライブラリが要る)ためです。JSON を読んで各言語のプログラムに引数で渡し、出した行を JSON に直してブラウザに返すところは、サーバーの入口(Python)が受け持っています。
作ってから分かったこと:PHP の mbstring
最初に PHP を mb_strlen・mb_str_split で書いて、毎朝の検証に使う Linux のコンテナで動かすと、当時の正解表33件中0件でした。コンテナの PHP には mbstring 拡張が入っておらず、mb_strlen が無いので止まっていました。mb_ で始まる関数は、PHP に必ず入っているとは限りません。この回の PHP は、preg_split('//u', …) で1文字ずつに分けて数える書き方にしています。
作ってから分かったこと:Java の正規表現は、長い繰り返しで止まる
公開したあと、メールアドレスに a.a.a.… と「.」で区切った部分を4500個並べて送ると、Java と Kotlin のプログラムだけが止まり、10言語の答えが割れました(正解表の c034〜c036)。
Java の正規表現は、(\.[A-Za-z0-9_+-]+)* のように ( ) を繰り返す部分を、繰り返し1回ごとに再帰で調べます。繰り返しが数千回になると、再帰の深さの上限を超えて StackOverflowError で止まります。この Mac の Java 21 では、2000個では動き、4500個で止まりました。Kotlin の Regex は Java の正規表現を使うので、同じように止まります。ほかの8言語は、同じ正規表現でも止まりませんでした。
Java と Kotlin は、「@」と「.」(電話番号は「-」)で先に分けてから、部分ごとに繰り返しのない正規表現で確かめる書き方に直しました。決まりの意味は変わっていません。第16回 の Java・Kotlin も同じ書き方だったので、あわせて直しています。
動かしているところ
「動かしてみる」で送った内容は、AWS の上の1つのプログラム(AWS Lambda)が受け取り、このページに載せている10言語のプログラムをそのまま動かして答えを返します。毎朝の検証と同じ環境(10言語が入ったコンテナ)です。
- 送った内容は、どこにも届けず、保存もしません(記録にも入力の中身は書きません)
- しばらく使われていないと、最初の1回は答えまで数秒から20秒ほどかかります
- 1秒に2回まで、入力は全体で10,000バイトまでです。短い時間にたくさん呼ばれると、いたずらとみなして自動で止まります
練習
- 「会社名」の項目を足してみましょう(任意。100文字まで)
- 電話番号を、ハイフンなしでも
03-1234-5678の形にそろえて返すには、どんな決まりが要るか考えてみましょう(市外局番の長さは地域で違います)