はじめに

前回の記事の続きです。
前回の記事はこちら

テキスト処理

前回同様テキスト処理についてやっていきます。
正規表現の処理をやっていきます。

正規表現の処理

他の言語でもお馴染みの正規表現です、色んな場面で使われますね。
早速Go言語ではどういう書き方をするのか見ていきます。

正規表現はパターンマッチとテキスト操作の複雑で強力なツールです。
正規表現は純粋なテキストマッチングに比べ効率は劣りますが、より柔軟性に富みます。
この文法規則に従い作り出されるパターンはオリジナルのテキストからあなたが必要とするほとんどすべての文字列の組み合わせをフィルターすることができます。

はい、というわけでね、正規表現の処理やっていきたいと思うわけなんですけれども(Youtuber風)

正規表現を使ってマッチングするか判断する

regexp パッケージを使って、文字列のマッチング処理をするようです。

これがよくある正規表現の関数ですね。

1
2
3
func Match(pattern string, b []byte) (matched bool, error error)
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
func MatchString(pattern string, s string) (matched bool, error error)

上の3つの関数は同じ機能を実現しています。
つまり、patternが入力ソースにマッチするかを判断しています。
マッチングしたらtrueを返し、もし正規表現の解析でエラーが出たらerrorを返します。3つの関数の入力ソースはそれぞれbyte slice、RuneReaderとstringです。

なるほど?

IPアドレスを検証するコードがあるので、早速書いてみましょう。
環境はいつものです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"regexp"
)

func main() {
	ipaddr := "192.168.12.1"
	fmt.Println(IsIP(ipaddr))
}
func IsIP(ip string) (b bool) {
	if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
		return false
	}
	return true
}

出力結果です

1
2
bash-5.0# go run regex.go 
true

上手く行ってますね。
本当は、0~255の範囲でチェックもすべきだと思うのですが、厳密性はないようです。

正規表現を使って内容を取得する

Matchパターンは文字列の判断に対してのみ使うことができ、文字列の一部分を切り取ったり、文字列にフィルターをかけたり、合致する条件の文字列を取り出したりすることはできません。
これらの需要を満足したければ、正規表現の複雑なパターンを使用する必要があります。

IsIP関数は、文字列がマッチしているかどうかだけの判定でしたね。
ここではフィルタリングして、その文字列を取得するようです。
早速書いてみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
)

func main() {
	resp, err := http.Get("http://blog.k-bushi.com")
	if err != nil {
		fmt.Println("http get error.")
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("http read error")
		return
	}

	src := string(body)

	//HTMLタグを全て小文字に変換します
	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
	src = re.ReplaceAllStringFunc(src, strings.ToLower)

	//<style>タグを除去します
	re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
	src = re.ReplaceAllString(src, "")

	//<script>タグを除去
	re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
	src = re.ReplaceAllString(src, "")

	//<>内の全てのHTMLコードを削除し、改行文字に置き換えます
	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
	src = re.ReplaceAllString(src, "\n")

	//連続した改行を除去します
	re, _ = regexp.Compile("\\s{2,}")
	src = re.ReplaceAllString(src, "\n")

	fmt.Println(strings.TrimSpace(src))
}

出力結果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
bash-5.0# go run regex2.go 
かつおぶしのブログ
k-bushi
Top
Archives
Tags
Categories
About
k-bushi
Top
Archives
Tags
Categories
About
2020/02 2週目 やったこと
2020-02-08
近況
はじめに 近況です。 1週間やったこと 数学 長岡先生の授業が聞ける高校数学の教科書 ・・・進めて 
いません。 数Ⅰ の第三章(二次関数の途中まで) https://www.obunsha.co.jp/service/nagaoka/other.html 英語 mikan
Build Web Application With Golang を試してやってみる 25
2020-02-08
技術
はじめに 前回の記事の続きです。 前回の記事はこちら テキスト処理 前回同様テキスト処理について 
やっていきます。 JSONの処理をやっていきます。 JS
Build Web Application With Golang を試してやってみる 24
2020-02-02
技術
はじめに 前回の記事の続きです。 前回の記事はこちら テキスト処理 今回はテキスト処理についてや 
っていきます。 XMLの処理 今回は XML の処理についてです
2020/02 1週目 やったこと
2020-02-01
近況
はじめに 金曜日なので、近況を書きます。 1週間やったこと 数学 長岡先生の授業が聞ける高校数学の
教科書 ・・・数Ⅰ の第三章(二次関数の途中まで) →数
Build Web Application With Golang を試してやってみる 23
2020-02-01
技術
はじめに 前回の記事の続きです。 前回の記事はこちら sessionとデータの保存 今回はsession につい
て理解していきます。 今週はサボりすぎた
1
2
3
4
5
6
7
Powered by
Hugo
|
Theme -
Jane
&copy;
2020
k-bushi
bash-5.0# 

ちゃんと取れていますね。
簡単にプログラムを見ていきましょう。

  1. resp, err := http.Get("http://blog.k-bushi.com") で レスポンスを取得
  2. body, err := ioutil.ReadAll(resp.Body) で htmlを取得しています。 あとの処理はコメントに書いてあるとおりですね。

この例からわかるように、複雑な正規表現を使用する場合はまずCompileを行います。
これは正規表現が正しいかどうかを解析し、もし正しければRegexpを返します。
返されたRegexpは任意の文字列で必要な操作を実行することができるようになります。

なるほど。

他の関数を使ってみる(マッチ関数)

どのようにRegexpを作成するか理解したところで、このstructがどのような方法によって我々の文字列操作を提供しているのかもう一度見てみることにしましょう。まず下の検索を行うための関数を見てみます

書いてあるとおり、 Find*** という関数がずらっと並んでおります。
これらのサンプルがあるので使ってみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
    "fmt"
    "regexp"
)

func main() {
    a := "I am learning Go language"

    re, _ := regexp.Compile("[a-z]{2,4}")

    //正規表現にマッチする最初のものを探し出す
    one := re.Find([]byte(a))
    fmt.Println("Find:", string(one))

    //正規表現にマッチするすべてのsliceを探し出す。nが0よりも小さかった場合はすべてのマッチする文字列を返します。さもなければ指定した長さが返ります。
    all := re.FindAll([]byte(a), -1)
    fmt.Println("FindAll", all)

    //条件にマッチするindexの位置を探し出す。開始位置と終了位置。
    index := re.FindIndex([]byte(a))
    fmt.Println("FindIndex", index)

    //条件にマッチするすべてのindexの位置を探し出す、nは同上
    allindex := re.FindAllIndex([]byte(a), -1)
    fmt.Println("FindAllIndex", allindex)

    re2, _ := regexp.Compile("am(.*)lang(.*)")

    //Submatchを探し出し、配列を返します。はじめの要素はマッチしたすべての要素です。2つ目の要素ははじめの()の中で、3つ目は2つ目の()の中です。
    //以下の出力でははじめの要素は"am learning Go language"です。
    //2つ目の要素は" learning Go "です。空白を含んで出力することに注意してください。
    //3つ目の要素は"uage"です。
    submatch := re2.FindSubmatch([]byte(a))
    fmt.Println("FindSubmatch", submatch)
    for _, v := range submatch {
        fmt.Println(string(v))
    }

    //定義と上のFindIndexは同じです。
    submatchindex := re2.FindSubmatchIndex([]byte(a))
    fmt.Println(submatchindex)

    //FindAllSubmatchは条件にマッチするすべてのサブマッチを探し出します。
    submatchall := re2.FindAllSubmatch([]byte(a), -1)
    fmt.Println(submatchall)

    //FindAllSubmatchIndexはすべてのサブマッチのindexを探し出します。
    submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
    fmt.Println(submatchallindex)
}

出力結果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bash-5.0# go run regex3.go 
Find: am
FindAll [[97 109] [108 101 97 114] [110 105 110 103] [108 97 110 103] [117 97 103 101]]      
FindIndex [2 4]
FindAllIndex [[2 4] [5 9] [9 13] [17 21] [21 25]]
FindSubmatch [[97 109 32 108 101 97 114 110 105 110 103 32 71 111 32 108 97 110 103 117 97 103 101] [32 108 101 97 114 110 105 110 103 32 71 111 32] [117 97 103 101]]
am learning Go language
 learning Go
uage
[2 25 4 17 21 25]
[[[97 109 32 108 101 97 114 110 105 110 103 32 71 111 32 108 97 110 103 117 97 103 101] [32 108 101 97 114 110 105 110 103 32 71 111 32] [117 97 103 101]]]
[[2 25 4 17 21 25]]

他の関数を使ってみる(置換関数)

次は Replace** ではじまる置換関数を使ってみます。
スクレイピング例があるので省略

他の関数を使ってみる(Expand関数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
    "fmt"
    "regexp"
)

func main() {
    src := []byte(`
        call hello alice
        hello bob
        call hello eve
    `)
    pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
    res := []byte{}
    for _, s := range pat.FindAllSubmatchIndex(src, -1) {
        res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
    }
    fmt.Println(string(res))
}

出力結果

1
2
3
bash-5.0# go run regex4.go 
hello('alice')
hello('eve')

余談

余談ですが、regexp って「れじぇっくすぴー」と呼んでいるのですがあっているんでしょうか・・・?
regular expression の略なので、 れぐえくすぷ とか れげっくすぴー とかなのだろうか。
調べてみた限りは、れげっくす , れじぇっくす, れぎゅらーえくすぷれっしょん, りーじぇっくす とか色々あるようですが、
私は、れじぇっくすぴーを推していきます

ただ、相手が れじぇっくすぴー ってなんですか、と聞いてきたら、素直に れぎゅらーえくすぷれっしょん と言い直しましょう。

おわりに

次回「テンプレートの処理」