thymeleaf の fragment でハマったこと

# はじめに
自宅の趣味管理システムで thymeleaf を使っており、selectタグ を fragmentで共通化していました。

ある日、別のselectタグを追加したら意図しない動きになりハマりました。
パターンがわかったので、記事に残します。

同じようにハマった人がいたらご参考になれば幸いです。

# 環境
spring-boot-starter-thymeleaf 3.4.4

# thymeleaf の fragment の前提知識
本家サイトの 8.1 テンプレートフラグメントのインクルード をご参照

https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf_ja.html#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%83%95%E3%83%A9%E3%82%B0%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89

# 何をやった？
同一ファイル内で th:fragment="select(引数) に加えて、th:fragment="selectTrainingMenu(引数) を追加しました。

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Parts</title>
</head>
<body>
	<select
		th:fragment="select(selectMap, selectedKey, fieldName, hasBlank)"
		class="form-select" th:field="*{__${fieldName}__}">
		<option th:if="${hasBlank}" value=""></option>
        <option th:each="item : ${selectMap}" th:value="${item.key}"
            th:text="${item.value}" th:selected="${item.key == selectedKey}"></option>
	</select>
    
	<select
		th:fragment="selectTrainingMenu(selectList, selectedKey, fieldName, hasBlank)"
		class="form-select" th:field="*{__${fieldName}__}">
		<option th:if="${hasBlank}" value=""></option>
		<option th:each="item : ${selectList}" th:value="${item.key}" 
			th:data-parent-key="${item.parentKey}"
        	th:data-max-weight="${item.maxWeight}" 
			th:data-max-reps="${item.maxReps}" 
			th:data-max-sets="${item.maxSets}"     
		th:text="${item.value}" th:selected="${item.key == selectedKey}"></option>
	</select>

</body>
</html>
```

# 何が起こった？
「th:insert="~{parts/parts :: select(引数)」の読み込んでいる箇所で、select と selectTrainingMenuの2つのselectタグが表示されました。

意図としては、元の select だけの気持ちでした。

```html
				<div class="col-sm-10"
					th:insert="~{parts/parts :: select(${trainingTargetAreaMap}, trainingAreaId, 'trainingAreaId', true)}">
				</div>
```

# 原因
HTMLタグと一致する名前を指定すると、HTMLタグで一致する fragmentを表示してしまうようでした。

select と書いたので、select と selectTrainingMenu の両方が一致してしまったと。

# 検証
SELECTタグとDIVタグで、前方一致、後方一致、部分一致、一致しない、HTMLタグと同じ。パターンを用意して試してみました。

## GitHub
https://github.com/tarosuke777/spring-boot-thymeleaf-fragment

## ソース
templates/parts.html
```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="UTF-8"></meta>
	<title>Fragment Sample</title>
</head>
<body>
	<h1>Fragment Sample</h1>
	<h2>SELECTタグ</h2>
		<div>前方一致：th:insert="~{parts :: select1}"
			<div th:insert="~{parts :: select1}"></div>
		</div>
		<div>後方一致：th:insert="~{parts :: 2select}"
			<div th:insert="~{parts :: 2select}"></div>	
		</div>
		<div>部分一致：th:insert="~{parts :: 3select3}"
			<div th:insert="~{parts :: 3select3}"></div>
		</div>
		<div>一致しない：th:insert="~{parts :: 4sel4}"
			<div th:insert="~{parts :: 4sel4}"></div>
		</div>	
		<div>th:insert="~{parts :: select}"
			<div th:insert="~{parts :: select}"></div>
		</div>
	<h2>DIVタグ</h2>
        <div>前方一致：th:insert="~{parts :: div1}"
            <div th:insert="~{parts :: div1}"></div>
        </div>
        <div>後方一致：th:insert="~{parts :: 2div}"
            <div th:insert="~{parts :: 2div}"></div>	
        </div>
        <div>部分一致：th:insert="~{parts :: 3div3}"
            <div th:insert="~{parts :: 3div3}"></div>
        </div>
        <div>一致しない：th:insert="~{parts :: 4div4}"
            <div th:insert="~{parts :: 4div4}"></div>
        </div>	
        <div>th:insert="~{parts :: div}"
            <div th:insert="~{parts :: div}"></div>
        </div>
<!--
	<h2>SPANタグ</h2>
	<div>th:insert="~{parts :: span}"
	    <div th:insert="~{parts :: span}"></div>
	</div>
-->
</body>
</html>
```
templates/index.html

```html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Parts</title>
</head>
<body>
	<select
		th:fragment="select1">
        <option value="1">select11</option>
        <option value="2">select12</option>
        <option value="3">select13</option>
	</select>
	<select
		th:fragment="2select">
	    <option value="1">2select1</option>
	    <option value="2">2select2</option>
	    <option value="3">2select3</option>
	</select>
	<select
		th:fragment="3select3">
	    <option value="1">3select31</option>
	    <option value="2">3select32</option>
	    <option value="3">3select33</option>
	</select>
	<select
		th:fragment="4sel4">
	    <option value="1">4sel41</option>
	    <option value="2">4sel42</option>
	    <option value="3">4sel43</option>
	</select>
	<select
        th:fragment="select">
        <option value="1">select1</option>
        <option value="2">select2</option>
        <option value="3">select3</option>
	</select>

	<div th:fragment="div1">div1</div>
	<div th:fragment="2div">2div</div>
	<div th:fragment="3div3">3div3</div>
	<div th:fragment="4div4">4div4</div>
	<div th:fragment="div">div</div>
</body>
</html>
```
## 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206526/694f98ca-4cc8-4cde-9966-4242f87a7067.png)

## 余談
templates/parts.html に存在しないHTMLタグ＝SPANタグを書いてみたら解析エラーでした。
```
Caused by: org.attoparser.ParseException: Error resolving fragment: "~{parts :: span}": template or fragment could not be resolved (template: "index" - line 43, col 11)
```