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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
---
[article]
uuid = "eed112e4-3227-4b3f-9991-7e11c288ee2b"
title = "【Go】 text/template の with や range の内側から外側の \".\" にアクセスする"
description = "Go言語の text/template における with や range は \".\" を上書きする。これらの内側から外側の \".\" にアクセスする方法を調べた。"
tags = [
"go",
]
[[article.revisions]]
date = "2024-08-19"
remark = "公開"
---
<article>
<section id="tldr">
<h>TL;DR</h>
<p>
常にトップレベルを指す特殊変数 <code>$</code> を使えばよい。
</p>
</section>
<section id="intro">
<h>はじめに</h>
<p>
Go には、標準ライブラリにテンプレートライブラリ <code>text/template</code> がある。
この <code>text/template</code> における制御構造、<code>with</code> と <code>range</code> は次のように使われる。
</p>
<codeblock>
<![CDATA[
# {{ .Title }}
# User
{{ with .User }}
{{ .Name }} ({{ .ID }})
{{ end }}
# Items
{{ range .Items }}
- {{ . }}
{{ end }}
]]>
</codeblock>
<p>
<code>text/template</code> の <code>.</code> は、現在の操作対象を表す特殊なオブジェクトである。
</p>
<p>
<code>with</code> や <code>range</code> は、<code>.</code> を変更する効果を持つ。
<code>with</code> は引数に渡されたオブジェクトを <code>.</code> へセットして、内部のテンプレートを実行する。
<code>range</code> は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を <code>.</code> へセットして、要素の個数だけ内部のテンプレートを実行する。
</p>
<p>
つまりこのテンプレートは、次のような構造をレンダリングしている (<code>Execute()</code> の第2引数)。
</p>
<codeblock language="go">
<![CDATA[
tmpl.Execute(out, Params{
Title: "foo",
User: User{
ID: 123,
Name: "john",
},
Items: []string{
"hoge",
"piyo",
"fuga",
},
})
]]>
</codeblock>
</section>
<section id="what-i-want-to-do">
<h>やりたいこと</h>
<p>
今回おこないたいのは、<code>with</code> や <code>range</code> の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。
</p>
<codeblock>
<![CDATA[
{{ with .User }}
ここから .Title を参照するには?
{{ end }}
{{ range .Items }}
ここから .User を参照するには?
{{ end }}
]]>
</codeblock>
<p>
<code>with</code> や <code>range</code> は、<code>.</code> を自身の対象オブジェクトに変更するので、
単に <code>{{ with .User }}</code> の中で <code>.Title</code> と書いても、それは <code>User</code> の <code>Title</code> プロパティを参照しているとみなされる。
</p>
<p>
<code>text/template</code> では変数が使えるので、テンプレートの先頭で
</p>
<codeblock>
<![CDATA[
{{ $params := . }}
]]>
</codeblock>
<p>
とでもしておけば実現は可能である。
</p>
<p>
しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。
</p>
</section>
<section id="solution">
<h>解決方法</h>
<p>
常にトップレベルを指す特殊変数 <code>$</code> を使えばよい。
</p>
<codeblock>
<![CDATA[
{{ with .User }}
{{ $.Title }}
{{ end }}
{{ range .Items }}
{{ $.User.Name }}
{{ end }}
]]>
</codeblock>
<p>
<code>$</code> は、テンプレートが実行されるときに渡されたオブジェクトを指す。
これを使えば現在の <code>.</code> に関係なくトップレベルを参照できる。
</p>
<p>
このことは、<a href="https://pkg.go.dev/text/template#hdr-Variables"><code>text/template</code> の公式ドキュメント</a>にも以下のように記載されている。
</p>
<blockquote>
When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.
</blockquote>
</section>
<section id="reference">
<h>参考</h>
<ul>
<li><a href="https://stackoverflow.com/questions/14800204/in-a-template-how-do-you-access-an-outer-scope-while-inside-of-a-with-or-rang">直接の出典である Stack Overflow の回答: "In a template how do you access an outer scope while inside of a "with" or "range" scope?"</a></li>
<li><a href="https://pkg.go.dev/text/template#hdr-Variables">大元の出典である <code>text/template</code> の公式ドキュメント</a></li>
</ul>
</section>
</article>
|