FileMakerでフィールド内の漢字を判定する・漢字をカウントする

![ContainsKanji_CountKanji.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296228/93397da0-42f6-7a49-70dd-6db4a13f7d4c.jpeg)


※ はじめに断っておきますが，計算式はAdventCalendar2019の昨日の記事（コーディング規約）から外れた書き方をしています（汗；）前日の人の投稿でハードルが上がると思っていませんでした……

#概要
あるフィールドに入力された文字列が，意図したものであるかどうかチェックすることは頻繁にあると思います．数字・アルファベットなどのチェックは容易ですが，これが漢字であるかどうかのチェックは意外と面倒です．
フィールドに漢字を入れさせたくないときや，漢字だけを検索したいとき，ありませんか？
あるいは，フィールド内の漢字だけの文字数を数えたいとき，どうやっていますか？
漢字に関しては文字コードに関する知識がないと取り扱いが難しいので，そのあたりを楽にする記事を書いておきたいと思います．

この記事では，計算フィールドやカスタム関数で使う計算式を紹介したあとに，簡単に解説をします．「面倒なことを知らなくてもいいから，計算式・カスタム関数だけあればいい」という方は下記の計算フィールドの式・カスタム関数をお使いください．（できるだけ多くの人が使えるように，計算フィールド用の式とカスタム関数，また，Let関数を使用したFileMaker17以前用とWhile関数を使用したFileMaker18以降用，を別々に書いていますが，ロジックの内容は同じです．使いやすいものを使って下さい．使用に関しては自己責任でお願いします．）

##漢字判定の部　

説明：テキストの中に漢字が1つでも含まれていれば1を，そうでなければ0を返します．

---
###計算フィールド編



####漢字判定の計算フィールド　While関数版（FileMaker18以降）
文字列の長さに制限があり，約5万字までです．

<details><summary>**While関数版の計算式**</summary><div>

```
Let ( [
		txt = テキストフィールド ;
		len = Length(txt)
		] ; 
		While ( 
				[ i = 1 ; flg = 0 ] ;								//初期変数
						 i ≤ len  and flg = 0 ;						//条件
								[
								tp = Middle(txt ; i ; 1);
								code = Code(tp) ;
								flg =  flg or Case(
													code ≥ 13312 and code ≤ 40959
													or
													code ≥ 63744 and code ≤ 64255
													or
													If ( 
													Length(code)=10 
													and 
													(
													GetAsNumber(Left(code ; 5)) ≥ 55360 
													and 
													GetAsNumber(Left(code ; 5)) ≤ 56320 
													)
													or
													(
													GetAsNumber(Right(code ; 5)) ≥ 55360 
													and 
													GetAsNumber(Right(code ; 5)) ≤ 56320 
													)
													or
													(
													GetAsNumber(Left(code ; 5)) ≥ 55423 
													and 
													GetAsNumber(Left(code ; 5)) ≤ 57343 
													)
													or
													(
													GetAsNumber(Right(code ; 5)) ≥ 55423 
													and 
													GetAsNumber(Right(code ; 5)) ≤ 57343 
													)
													; 1 ; 0 )
													or
													code ≥ 131072 and code ≤ 173791
													or
													code ≥ 173824 and code ≤ 191471
													or
													code ≥ 194560 and code ≤ 195103
													or
													code = 12293 or code = 12295 or code = 12347
													; 1 ; 0) ; 
								i = i + 1
								] ;						//ロジック
				flg										//結果
				)
     )

```


</div>

</details>


####漢字判定の計算フィールド　Let関数版（FileMaker17までの場合）
文字列の長さに制限があり，約294字までです．

<details><summary>**Let関数版計算式**</summary><div>

```
Let([
		txt=テキストフィールド;
		len=Length(txt);
		pos= 0;
		tp=Middle(txt;pos;1);
		code=Code(tp);
		i=0;
		res="";
		fnc="																/*fnc開始*/
			 Case( pos>len or res=1 ; res;								/*Case文開始．終了条件：最後の文字まで行くか，または漢字が1文字でも見つかれば終了*/
					Let([													/*内側のLet開始*/
						pos = pos + 1;
						tp = Middle( txt ; pos ; 1);						/*pos番目の文字を取得*/
						code=Code(tp);   									/*pos番目の文字のコードを取得*/
						LCode = GetAsNumber(Left(code ; 5)) ;
						RCode = GetAsNumber(Right(code ; 5)) ;
						res=If ( code ≥ 13312 and code ≤ 40959
									or
									code ≥ 63744 and code ≤ 64255
									or
									If ( 
									Length(code)=10 
									and 
									(
									LCode ≥ 55360 
									and 
									LCode ≤ 56320 
									)
									or
									(
									RCode ≥ 55360 
									and 
									RCode ≤ 56320 
									)
									or
									(
									LCode ≥ 55423 
									and 
									LCode ≤ 57343 
									)
									or
									(
									RCode ≥ 55423 
									and 
									RCode ≤ 57343 
									)
									; 1 ; 0 )
									or
									code ≥ 131072 and code ≤ 173791
									or
									code ≥ 173824 and code ≤ 191471
									or
									code ≥ 194560 and code ≤ 195103
									or
									code = 12293 or code = 12295 or code = 12347
									; 1; 0 )								/*コードが漢字かどうか判定．itmがヌルなら、何も追加せず、そのままの値を維持．そうでなければitmの値を追加*/
						]; Evaluate(fnc))									/*内側のLet終了、内側のfnc計算*/
					)														/*Case文終了*/
			"																/*fnc終了*/
	];
	Evaluate(fnc)															/*外側のfnc計算*/
	)

```
</div>

</details>


###カスタム関数編

####漢字判定のカスタム関数　While関数版（FileMaker18以降）

<details><summary>**While関数版のカスタム関数：ContainsKanji(Text)**</summary><div>

```
Let ( [
		txt = Text ;
		len = Length(txt)
		] ; 
		While ( 
				[ i = 1 ; flg = 0 ] ;								//初期変数
						 i ≤ len  and flg = 0 ;						//条件
								[
								tp = Middle(txt ; i ; 1);
								code = Code(tp) ;
								flg =  flg or Case(
													code ≥ 13312 and code ≤ 40959
													or
													code ≥ 63744 and code ≤ 64255
													or
													If ( 
													Length(code)=10 
													and 
													(
													GetAsNumber(Left(code ; 5)) ≥ 55360 
													and 
													GetAsNumber(Left(code ; 5)) ≤ 56320 
													)
													or
													(
													GetAsNumber(Right(code ; 5)) ≥ 55360 
													and 
													GetAsNumber(Right(code ; 5)) ≤ 56320 
													)
													or
													(
													GetAsNumber(Left(code ; 5)) ≥ 55423 
													and 
													GetAsNumber(Left(code ; 5)) ≤ 57343 
													)
													or
													(
													GetAsNumber(Right(code ; 5)) ≥ 55423 
													and 
													GetAsNumber(Right(code ; 5)) ≤ 57343 
													)
													; 1 ; 0 )
													or
													code ≥ 131072 and code ≤ 173791
													or
													code ≥ 173824 and code ≤ 191471
													or
													code ≥ 194560 and code ≤ 195103
													or
													code = 12293 or code = 12295 or code = 12347
													; 1 ; 0) ; 
								i = i + 1
								] ;						//ロジック
				flg										//結果
				)
     )

```

</div>

</details>


####漢字判定のカスタム関数　Let関数版（FileMaker17までの場合）
文字列の長さに制限があり，約294字までです．

<details><summary>**Let関数版カスタム関数：ContainsKanji(Text)**</summary><div>

```
Let([
		txt=Text;
		len=Length(txt);
		pos= 0;
		tp=Middle(txt;pos;1);
		code=Code(tp);
		i=0;
		res="";
		fnc="																/*fnc開始*/
			 Case( pos>len or res=1 ; res;								/*Case文開始．終了条件：最後の文字まで行くか，または漢字が1文字でも見つかれば終了*/
					Let([													/*内側のLet開始*/
						pos = pos + 1;
						tp = Middle( txt ; pos ; 1);						/*pos番目の文字を取得*/
						code=Code(tp);   									/*pos番目の文字のコードを取得*/
						LCode = GetAsNumber(Left(code ; 5)) ;
						RCode = GetAsNumber(Right(code ; 5)) ;
						res=If ( code ≥ 13312 and code ≤ 40959
									or
									code ≥ 63744 and code ≤ 64255
									or
									If ( 
									Length(code)=10 
									and 
									(
									LCode ≥ 55360 
									and 
									LCode ≤ 56320 
									)
									or
									(
									RCode ≥ 55360 
									and 
									RCode ≤ 56320 
									)
									or
									(
									LCode ≥ 55423 
									and 
									LCode ≤ 57343 
									)
									or
									(
									RCode ≥ 55423 
									and 
									RCode ≤ 57343 
									)
									; 1 ; 0 )
									or
									code ≥ 131072 and code ≤ 173791
									or
									code ≥ 173824 and code ≤ 191471
									or
									code ≥ 194560 and code ≤ 195103
									or
									code = 12293 or code = 12295 or code = 12347
									; 1; 0 )								/*コードが漢字かどうか判定．itmがヌルなら、何も追加せず、そのままの値を維持．そうでなければitmの値を追加*/
						]; Evaluate(fnc))									/*内側のLet終了、内側のfnc計算*/
					)														/*Case文終了*/
			"																/*fnc終了*/
	];
	Evaluate(fnc)															/*外側のfnc計算*/
	)
```

</div>

</details>


##漢字カウントの部

説明：テキストフィールド中の漢字の数を返します．

###計算フィールド編

####漢字カウントの計算フィールド　While関数版（FileMaker18以降）
文字列の長さに制限があり，約5万字までです．

<details><summary>**While関数版計算式**</summary><div>

```
Let ( [
		txt=テキストフィールド ;
		len=Length(txt) ;
		num = 0 
		] ; 
		While ( 
				[ i = 1 ] ;									//初期変数
				i ≤ len ;									//条件
								[ 
								tp = Middle(txt ; i ; 1);
								code = Code(tp);
								Lcode = GetAsNumber(Left(code ; 5)) ;
								Rcode = GetAsNumber(Right(code ; 5)) ;
								i = i + 1 ;
								num = num +If ( 
												Length(code)=5 
												and 
												code ≥ 13312 and code ≤ 40959 
												or
												code ≥ 63744 and code ≤ 64255
												or
												code ≥ 131072 and code ≤ 173791
												or
												code ≥ 173824 and code ≤ 191471
												or
												code ≥ 194560 and code ≤ 195103
												or
												code = 12293 or code = 12295 or code = 12347

												; 1; 

												If ( 
												Length(code)=10 
												and 
												(LCode ≥ 55360 and LCode ≤ 56320 )
												or
												(LCode ≥ 55423 and LCode ≤ 57343 )
												or
												(RCode ≥ 55360 and RCode ≤ 56320 )
												or
												(RCode ≥ 55423 and RCode ≤ 57343 )
												; 1 ; 0 )
												) 
												
								] ;							//ロジック
				 num 										//結果
				)
	)

```
</div>

</details>

####漢字カウントの計算フィールド　Let関数版（FileMaker17までの場合）
文字列の長さに制限があり，約294字までです．

<details><summary>**Let関数版計算式**</summary><div>

```
Let([
	txt = テキストフィールド;
	num = 0 ;
	fnc ="Case ( IsEmpty ( txt ) ; num ;					/*fnc開始．終了条件：最後の文字まで判定したらnumを返す*/
		Let([												/*内側のLet開始*/
			tp = left ( txt ; 1) ;
			code = Code ( tp ) ;
			LCode = GetAsNumber(Left(code ; 5)) ;			/*左5桁のコード*/
			RCode = GetAsNumber(Right(code ; 5)) ;		/*右5桁のコード*/
			num = num +  


			(code ≥ 13312 and code ≤ 40959) + 
			(code ≥ 63744 and code ≤ 64255) +

			If (											/*サロゲートペアの対策*/
			Length(code)=10 
			and 
			(LCode ≥ 55360 and LCode ≤ 56320 )
			or
			(LCode ≥ 55423 and LCode ≤ 57343 )
			or
			(RCode ≥ 55360 and RCode ≤ 56320 )
			or
			(RCode ≥ 55423 and RCode ≤ 57343 )
			; 1 ; 0 )									   /*サロゲートペアの対策終了*/

			+ 
			
			(code ≥ 131072 and code ≤ 173791) + 
			(code ≥ 173824 and code ≤ 191471) + 
			(code ≥ 194560 and code ≤ 195103) + 
			(code = 12293 or code = 12295 or code = 12347)
			;

			txt = right ( txt ; length ( txt ) - 1)
		   ]; Evaluate ( fnc )								/*fnc終了*/
		)
	)"
	];
	Evaluate(fnc)											/*外側のfnc計算*/
	)

```
</div>

</details>

###カスタム関数編

####漢字カウントのカスタム関数：CountKanji(Text)　While関数版（FileMaker18以降）

<details><summary>**While関数版カスタム関数**</summary><div>

```
Let ( [
		txt=Text ;
		len=Length(txt) ;
		num = 0 
		] ; 
		While ( 
				[ i = 1 ] ;									//初期変数
				i ≤ len ;									//条件
								[ 
								tp = Middle(txt ; i ; 1);
								code = Code(tp);
								Lcode = GetAsNumber(Left(code ; 5)) ;
								Rcode = GetAsNumber(Right(code ; 5)) ;
								i = i + 1 ;
								num = num +If ( 
												Length(code)=5 
												and 
												code ≥ 13312 and code ≤ 40959 
												or
												code ≥ 63744 and code ≤ 64255
												or
												code ≥ 131072 and code ≤ 173791
												or
												code ≥ 173824 and code ≤ 191471
												or
												code ≥ 194560 and code ≤ 195103
												or
												code = 12293 or code = 12295 or code = 12347

												; 1; 

												If ( 
												Length(code)=10 
												and 
												(LCode ≥ 55360 and LCode ≤ 56320 )
												or
												(LCode ≥ 55423 and LCode ≤ 57343 )
												or
												(RCode ≥ 55360 and RCode ≤ 56320 )
												or
												(RCode ≥ 55423 and RCode ≤ 57343 )
												; 1 ; 0 )
												) 
												
								] ;							//ロジック
				 num 										//結果
				)
	)

```
</div>

</details>




####漢字カウントのカスタム関数：CountKanji(Text)　Let関数版（FileMaker17までの場合）

<details><summary>**Let関数版カスタム関数**</summary><div>

```
Let([
	txt = Text;
	num = 0 ;
	fnc ="Case ( IsEmpty ( txt ) ; num ;					/*fnc開始．終了条件：最後の文字まで判定したらnumを返す*/
		Let([													/*内側のLet開始*/
			tp = left ( txt ; 1) ;
			code = Code ( tp ) ;
			LCode = GetAsNumber(Left(code ; 5)) ;			/*左5桁のコード*/
			RCode = GetAsNumber(Right(code ; 5)) ;		/*右5桁のコード*/
			num = num +  


			(code ≥ 13312 and code ≤ 40959) + 
			(code ≥ 63744 and code ≤ 64255) +

			If (											/*サロゲートペアの対策*/
			Length(code)=10 
			and 
			(LCode ≥ 55360 and LCode ≤ 56320 )
			or
			(LCode ≥ 55423 and LCode ≤ 57343 )
			or
			(RCode ≥ 55360 and RCode ≤ 56320 )
			or
			(RCode ≥ 55423 and RCode ≤ 57343 )
			; 1 ; 0 )									   /*サロゲートペアの対策終了*/

			+ 
			
			(code ≥ 131072 and code ≤ 173791) + 
			(code ≥ 173824 and code ≤ 191471) + 
			(code ≥ 194560 and code ≤ 195103) + 
			(code = 12293 or code = 12295 or code = 12347)
			;

			txt = right ( txt ; length ( txt ) - 1)
		   ]; Evaluate ( fnc )								/*fnc終了*/
		)
	)"
	];
	Evaluate(fnc)											/*外側のfnc計算*/
	)

```
</div>

</details>
---

# 解説
上記に挙げた式の簡単な説明をします．

## 意外に面倒な漢字の判定

漢字かどうかの判定は，文字コードをUnicodeで取得し，10進数に変換したうえで行っています．（ここで使う「漢字」の定義は，当然のことですが「日本語における漢字」という意味で使っています．）
文字コードが以下の**表1**の範囲のものを漢字とみなしています．

表の最後に追加してある，「々」＝漢字の踊り字（佐々木，時々，代々木など），「〇」＝漢数字のゼロ，「〻」＝漢字の踊り字（二の字点）は，**コードとしては漢字の領域には該当しないが，漢字と見なさないとおかしなことになるもの**です．この部分がないと，例えば，CountKanji ( "佐々木二〇世紀" )＝5，となってしまいます．

## 表1　漢字の文字コードとUnicodeの領域
| コード範囲（16進数）       | コード範囲（10進数）      | 領域の名前    | 備考    |
|:-----------------|------------------:|:------------------:|:------------------:|
|U+3400 ～ U+4DBF|13312～19903|CJK Unified Ideographs Extension A|よく使われる漢字|
|U+4E00 ～ U+9FFF|19968～40959|CJK Unified Ideographs|よく使われる漢字|
|U+F900 ～ U+FAFF|63744～64255|CJK Compatibility Ideographs|   |
|U+D840 ～ U+DC00|55360～56320|サロゲートペアによるU+20000|   |
|U+D87F ～ U+DFFF|55423～57343|サロゲートペアによるU+2FFFF   |
|U+20000 ～ U+2A6DF|131072～173791|CJK Unified Ideographs Extension B|   |
|U+2A700 ～ U+2B73F|173824～177983|CJK Unified Ideographs Extension C|   |
|U+2B740 ～ U+2B81F|177984～178207|CJK Unified Ideographs Extension D|   |
|U+2B820 ～ U+2CEAF|178208～183983|CJK Unified Ideographs Extension E|   |
|U+2CEB0 ～ U+2EBEF|183984～191471|CJK Unified Ideographs Extension F|   |
|U+2F800 ～ U+2FA1F|194560～195103|CJK Compatibility Ideographs Supplement|   |
|U+3005|12293|々 | 漢字の踊り字（佐々木，時々，代々木など）  |
|U+3007|12295|〇| 漢数字のゼロ   |
|U+303B|12347|〻| 漢字の踊り字（二の字点）   |


## Code関数に注意

たとえば，「漢」という文字をCode関数でコードポイントを調べると，「28450」という10進数1つが返ってきます．ほとんどの漢字は5桁の10進数1つが返ってくるのですが，一部，例外があります．
たとえば，「𠮷」（つちよし，下の横画が長いほうの「吉」）をCode関数で調べると，「5727155362」が返ってきます．これは「5727155362」で1つの10進数ではなく，「57271」「55362」という10進数2つを連続したものです．これは，サロゲートペアと呼ばれます．（サロゲートペアについての詳細な説明は省略します．興味のある方は下の記事を読んで下さい）

https://ja.wikipedia.org/wiki/Unicode#%E3%82%B5%E3%83%AD%E3%82%B2%E3%83%BC%E3%83%88%E3%83%9A%E3%82%A2

日常で使われるほとんどの漢字は問題がないのですが，一部の漢字（先ほど挙げた「𠮷」のほかに，「𠮟」（しかる，の異体字）＝U+20B9F，「𩸽」（ほっけ）＝29E3D などでサロゲートペアが使われているため，注意が必要です．
**Code関数は，ほとんどの漢字で5桁の10進数1つを返すが，まれに10進数2つの組みを返す**，というぐらいに覚えておきましょう．
ここで使った計算式では，10桁の数字が返ってきた場合はサロゲートペアとみなし，左5桁と右5桁に分けて処理することで，この問題を回避しています．

FileMakerの公式のヘルプ：
https://fmhelp.filemaker.com/help/18/fmp/ja/#page/FMP_Help%2Fcode.html%23wwconnect_header
にも，Code関数についての説明で，

>各文字の Unicode コードポイントは、5 桁のグループで返されます。最初の文字のコードポイントは下位の 5 桁で表され、2 番目の文字のコードポイントは次に位の高い (左方向に) 5 桁で表されます。
>ä などの複合文字を変換する場合に、関数は Unicode コードポイントを複合文字用に返します。

と書かれていますが，これでは何のことだかわからないですよね．

## 使い方あれこれ

日本語を扱う際にはいろいろあると思いますが，

- 漢字以外，入力させたくない
- 漢字が1つでも含まれていたら，アラートを出したい

などでしょうか．
括弧類や記号，約物など，使う人によって「漢字」と判定したい基準は変わることがあります．
その際は，適宜，計算式の部分を変更して使って下さい．

また，多条件・多重のループを使っているので，長文テキストや多数レコードを一括でチェックしようとすると処理が重くなるかもしれません．
レコード確定時などにスクリプトトリガで1度チェックを掛けるぐらいが良いでしょう．

では，楽しいFileMakerライフを！