Go小ネタ: 正規表現を全角半角問わずマッチするよう変換する

## なぜ必要になったか

Google Analytics の CoreReporting API では、検索キーワードの一覧を取得できるが、取得したキーワードは、全角英数字は半角に変換される。

一方、正規表現でクエリを投げられるんだけど、そいつはローデータに対してマッチしているよう。
さらに悪いことに、ローデータを取れない。。。

なので、ユーザーが入力した正規表現を、全角半角英数両方にマッチする正規表現に変換するコードを書いた。

## テストコード

```go
package util

import "testing"

func TestZenHanRegexp(t *testing.T) {
	testPatterns := [][]string{
		// literal
		{`1923`, `[1１][9９][2２][3３]`},
		{`abc`, `[aａ][bｂ][cｃ]`},
		{`aBc`, `[aａ][BＢ][cｃ]`},
		{`ABC`, `[AＡ][BＢ][CＣ]`},
		{`ａｂｃ`, `[aａ][bｂ][cｃ]`},
		{`ＡＢＣ`, `[AＡ][BＢ][CＣ]`},
		{`abc.*`, `[aａ][bｂ][cｃ](?-s:.)*`},
		{`あいうえおabc.*`, `あいうえお[aａ][bｂ][cｃ](?-s:.)*`},

		// char case
		{`[a-z]`, `[a-zａ-ｚ]`},
		{`[0-9a-zA-Z]`, `[0-9０-９A-ZＡ-Ｚa-zａ-ｚ]`},
		{`[ａ-ｚ]`, `[a-zａ-ｚ]`},
		{`[A-z]`, `[A-z]`},
		{`[acz]`, `[aａcｃzｚ]`},
		{`\ba`, `\b[aａ]`},
		{`[[:alpha:]]`, `[A-ZＡ-Ｚa-zａ-ｚ]`},
		{`\d`, `[0-9０-９]`},

		// complex
		{`\Qaiueo\E`, `[aａ][iｉ][uｕ][eｅ][oｏ]`},
		{`test(test)(あいうえおb)+[a-z]{0,4}$`, `[tｔ][eｅ][sｓ][tｔ]([tｔ][eｅ][sｓ][tｔ])(あいうえお[bｂ])+[a-zａ-ｚ]{0,4}$`},
	}

	for _, pt := range testPatterns {
		out, err := ZenHanRegexp(pt[0])
		if err != nil {
			t.Errorf("parse error for %s, %s", pt[0], err.Error())
		}
		if out != pt[1] {
			t.Errorf("original %s: %s expected to be %s", pt[0], out, pt[1])
		}
	}
}

```

## 実装

```go
package util

import (
	"bytes"
	"fmt"
	"regexp/syntax"
)

func ZenHanRegexp(src string) (string, error) {
	if src == "" {
		return "", nil
	}
	r, err := syntax.Parse(src, syntax.PerlX|syntax.Simple)
	if err != nil {
		return "", err
	}
	newR := replaceZenHan(r)
	return newR.String(), nil
}

func replaceZenHan(re *syntax.Regexp) *syntax.Regexp {
	if re == nil {
		return nil
	}

	copied := *re
	switch copied.Op {
	case syntax.OpLiteral:
		return createZenHanLiteral(&copied)
	case syntax.OpCharClass:
		return createZenHanCharClass(&copied)
	}
	for i, cr := range copied.Sub {
		copied.Sub[i] = replaceZenHan(cr)
	}
	return &copied
}

func createZenHanLiteral(re *syntax.Regexp) *syntax.Regexp {
	var buf bytes.Buffer
	for _, r := range re.Rune {
		buf.Write([]byte(runeToZenHanString(r)))
	}
	newRe, _ := syntax.Parse(buf.String(), 0)
	return newRe
}

func runeToZenHanString(r rune) string {
	runes, found := allRuneMap[r]
	if !found {
		return string(r)
	}
	return fmt.Sprintf("[%c%c]", runes[0], runes[1])
}

func createZenHanCharClass(re *syntax.Regexp) *syntax.Regexp {
	copied := *re
	var buf bytes.Buffer

	for i := 0; i < len(re.Rune)/2; i++ {
		start, end := re.Rune[2*i], re.Rune[2*i+1]
		buf.Write([]byte(classRunesToZenHanString(start, end)))
	}

	copied.Rune = []rune(buf.String())

	return &copied
}

func classRunesToZenHanString(start, end rune) string {
	// それぞれの文字の範囲でのみ全角半角に置き換えて返す
	for _, targetMap := range []map[rune][2]rune{lowerAlphaRuneMap, upperAlphaRuneMap, digitRuneMap} {
		if runeMapExists(targetMap, start) && runeMapExists(targetMap, end) {
			return convertZenHanCharClass(start, end)
		}
	}

	return string([]rune{start, end})
}

func convertZenHanCharClass(start, end rune) string {
	ss := allRuneMap[start]
	es := allRuneMap[end]
	return string([]rune{ss[0], es[0], ss[1], es[1]})
}

func runeMapExists(m map[rune][2]rune, r rune) bool {
	_, exists := m[r]
	return exists
}

var (
	lowerAlphaPairs   [][2]rune
	upperAlphaPairs   [][2]rune
	digitPairs        [][2]rune
	lowerAlphaRuneMap map[rune][2]rune
	upperAlphaRuneMap map[rune][2]rune
	digitRuneMap      map[rune][2]rune
	allRuneMap        map[rune][2]rune
)

func init() {
	// create map
	allRuneMap = map[rune][2]rune{}
	lowerAlphaRuneMap = map[rune][2]rune{}
	upperAlphaRuneMap = map[rune][2]rune{}
	digitRuneMap = map[rune][2]rune{}

	// init alphabets
	for i := 0; i < 26; i++ {
		lowerAlphaPairs = append(lowerAlphaPairs, [2]rune{rune(int('a') + i), rune(int('ａ') + i)})
		upperAlphaPairs = append(upperAlphaPairs, [2]rune{rune(int('A') + i), rune(int('Ａ') + i)})
	}

	for _, pair := range lowerAlphaPairs {
		for _, r := range pair {
			lowerAlphaRuneMap[r] = pair
			allRuneMap[r] = pair
		}
	}
	for _, pair := range upperAlphaPairs {
		for _, r := range pair {
			upperAlphaRuneMap[r] = pair
			allRuneMap[r] = pair
		}
	}

	// init digits
	for i := 0; i < 10; i++ {
		digitPairs = append(digitPairs, [2]rune{rune(int('0') + i), rune(int('０') + i)})
	}

	for _, pair := range digitPairs {
		for _, r := range pair {
			digitRuneMap[r] = pair
			allRuneMap[r] = pair
		}
	}
}

```