Go에서 템플릿을 사용하는 방법
소개
일부 데이터를 형식이 잘 지정된 출력, 텍스트 보고서 또는 HTML 페이지로 표시해야 합니까? Go 템플릿으로 그렇게 할 수 있습니다. 모든 Go 프로그램은 Go 표준 라이브러리에 포함된 html/template
패키지를 사용하여 데이터를 깔끔하게 표시할 수 있습니다.
두 패키지 모두 텍스트 템플릿을 작성하고 데이터를 템플릿에 전달하여 원하는 형식의 문서를 렌더링할 수 있습니다. 템플릿 내에서 데이터를 반복하고 조건 논리를 사용하여 문서에 포함할 항목과 표시 방법을 결정할 수 있습니다. 이 자습서에서는 두 템플릿 패키지를 모두 사용하는 방법을 보여줍니다. 먼저 text/template
을 사용하여 루프, 조건 논리 및 사용자 지정 함수를 사용하여 일부 데이터를 일반 텍스트 보고서로 렌더링합니다. 그런 다음 html/template
을 사용하여 동일한 데이터를 코드 주입이 없는 HTML 문서로 렌더링합니다.
전제 조건
이 튜토리얼을 시작하기 전에 Go만 설치하면 됩니다. 운영 체제에 적합한 자습서를 읽으십시오.
- Go On Ubuntu 20.04 설치 방법
- macOS에서 Go를 설치하고 로컬 프로그래밍 환경을 설정하는 방법
- Windows 10에서 Go를 설치하고 로컬 프로그래밍 환경을 설정하는 방법
구조체 메서드 사용을 포함하여 Go 언어에 대한 작업 지식도 필요합니다.
시작하자.
1단계 - 텍스트/템플릿 가져오기
개에 대한 일부 데이터에 대한 간단한 보고서를 생성한다고 가정합니다. 다음과 같이 표시하고 싶습니다.
---
Name: Jujube
Sex: Female (spayed)
Age: 10 months
Breed: German Shepherd/Pitbull
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd/Border Collie
이것은 text/template
패키지를 사용하여 생성할 보고서입니다. 강조 표시된 항목은 데이터이고 나머지는 템플릿의 정적 텍스트입니다. 템플릿은 코드에 문자열로 존재하거나 코드와 함께 자체 파일에 존재합니다. 여기에는 조건문(예: if/else), 흐름 제어문(예: 루프) 및 함수 호출과 인터레이스된 상용구 정적 텍스트가 포함되며 모두 {{ 안에 래핑됩니다. . .}}
마커. 위와 같은 최종 문서를 렌더링하기 위해 일부 데이터를 템플릿에 전달합니다.
시작하려면 Go 작업 공간(go env GOPATH
)으로 이동하여 이 프로젝트를 위한 새 디렉터리를 만듭니다.
- cd `go env GOPATH`
- mkdir pets
- cd pets
nano
또는 선호하는 텍스트 편집기를 사용하여 pets.go
라는 새 파일을 열고 다음을 붙여넣습니다.
- nano pets.go
package main
import (
"os"
"text/template"
)
func main() {
}
이 파일은 main
패키지에 있다고 선언하고 go run
을 사용하여 실행할 수 있음을 의미하는 main
함수를 포함합니다. 템플릿을 작성하고 렌더링할 수 있도록 text/template
표준 라이브러리 패키지와 터미널에 인쇄하는 데 사용되는 os
를 가져옵니다.
2단계 - 템플릿 데이터 생성
템플릿을 작성하기 전에 템플릿에 전달할 데이터를 만들어 봅시다. import
문 아래와 main()
전에 애완 동물의 Name
에 대한 필드를 포함하는 Pet
이라는 구조체를 정의합니다. , 성별
, 애완동물의 중성화 여부(온전한
), 나이
및 품종
. pets.go
를 편집하고 다음 구조체를 추가합니다.
. . .
type Pet struct {
Name string
Sex string
Intact bool
Age string
Breed string
}
. . .
이제 main()
함수 본문에서 Pet
조각을 만들어 두 마리의 개에 대한 데이터를 저장합니다.
. . .
func main() {
dogs := []Pet{
{
Name: "Jujube",
Sex: "Female",
Intact: false,
Age: "10 months",
Breed: "German Shepherd/Pitbull",
},
{
Name: "Zephyr",
Sex: "Male",
Intact: true,
Age: "13 years, 3 months",
Breed: "German Shepherd/Border Collie",
},
}
} // end main
이 데이터는 최종 보고서를 렌더링하기 위해 템플릿으로 전달됩니다. 물론 템플릿에 전달하는 데이터는 데이터베이스, 타사 API 등 어디에서나 가져올 수 있습니다. 이 자습서에서는 일부 샘플 데이터를 코드에 붙여넣는 것이 가장 간단합니다.
이제 이러한 패키지의 용어로 템플릿을 렌더링하거나 실행하는 방법을 살펴보겠습니다.
3단계 - 템플릿 실행
이 단계에서는 text/template
을 사용하여 템플릿에서 완성된 문서를 생성하는 방법을 살펴보지만 4단계까지는 실제로 유용한 템플릿을 작성하지 않습니다.
정적 텍스트가 포함된 pets.tmpl
이라는 빈 텍스트 파일을 만듭니다.
Nothing here yet.
템플릿을 저장하고 편집기를 종료합니다. nano
를 사용하는 경우 CTRL+X
를 누른 다음 Y
및 ENTER
를 눌러 변경 사항을 확인합니다.
이 템플릿을 실행하면 "Nothing here yet.\이 인쇄되지만 text/template
이 작동하는지 확인하기 위해 데이터를 전달하고 템플릿을 실행해 보겠습니다. main에 다음을 추가합니다.()
함수는 dogs
슬라이스 뒤에 있습니다.
. . .
var tmplFile = “pets.tmpl”
tmpl, err := template.New(tmplFile).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, dogs)
if err != nil {
panic(err)
}
} // end main
이 코드 스니펫에서는 Template.New
를 사용하여 새 Template
을 만든 다음 결과 템플릿에서 ParseFiles
를 호출하여 최소 템플릿 파일. 오류를 확인한 후 새 템플릿의 Execute
메서드를 호출하고 os.Stdout
을 전달하여 완성된 보고서를 터미널에 인쇄하고 도 전달합니다. dogs
슬라이스. 첫 번째 인수의 경우 io.Writer
인터페이스를 구현하는 모든 항목을 전달할 수 있습니다. 즉, 예를 들어 보고서를 파일에 작성할 수 있습니다. 나중에 그 방법을 알아보겠습니다.
전체 프로그램은 다음과 같아야 합니다.
package main
import (
"os"
"text/template"
)
type Pet struct {
Name string
Sex string
Intact bool
Age string
Breed string
}
func main() {
dogs := []Pet{
{
Name: "Jujube",
Sex: "Female",
Intact: false,
Age: "10 months",
Breed: "German Shepherd/Pitbull",
},
{
Name: "Zephyr",
Sex: "Male",
Intact: true,
Age: "13 years, 3 months",
Breed: "German Shepherd/Border Collie",
},
}
var tmplFile = “pets.tmpl”
tmpl, err := template.New(tmplFile).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, dogs)
if err != nil {
panic(err)
}
} // end main
프로그램을 저장한 다음 go run
으로 실행합니다.
- go run pets.go
OutputNothing here yet.
프로그램은 아직 데이터를 인쇄하지 않지만 적어도 코드는 깨끗하게 실행됩니다. 이제 템플릿을 작성해 보겠습니다.
4단계 - 템플릿 작성
템플릿은 전달된 데이터를 살펴보는 방법과 출력에 포함할 항목을 템플릿 엔진에 지시하는 작업일 뿐입니다. 작업은 한 쌍의 여는 이중 중괄호({{
)로 둘러싸여 있으며 커서 를 통해 데이터에 액세스합니다. , 점(.
)으로 표시됩니다.
템플릿에 전달되는 데이터는 절대적으로 무엇이든 될 수 있지만 반복 가능한 슬라이스, 배열 또는 맵을 전달하는 것이 일반적입니다. 템플릿이 dogs
슬라이스를 통과하도록 합시다.
슬라이스 반복
Go 코드에서는 for
루프의 여는 문 내에서 range
를 사용하여 슬라이스를 반복할 수 있습니다. 템플릿에서 동일한 목적으로 range
작업을 사용하지만 구문이 다릅니다. for
는 없지만 end
가 추가되었습니다. 루프를 닫습니다.
pets.tmpl
을 열고 내용을 다음으로 바꿉니다.
{{ range . }}
---
(Pet will appear here...)
{{ end }}
여기서 range
작업은 전체 dogs
슬라이스를 참조하는 커서(.
)라는 하나의 인수를 사용합니다. 루프는 하단의 {{ end }}
로 닫힙니다. 루프 본문에서 일부 정적 텍스트를 인쇄하고 있지만 아직 개에 대한 내용은 없습니다.
pets.tmpl
을 저장하고 pets.go
를 다시 실행합니다.
- go run pets.go
Output
---
(Pet will appear here...)
---
(Pet will appear here...)
조각에 두 마리의 개가 있으므로 정적 텍스트가 두 번 인쇄됩니다. 이제 이것을 개 데이터와 함께 좀 더 유용한 정적 텍스트로 바꾸겠습니다.
필드 표시
이 템플릿에서 .
를 range
에 전달할 때 점은 전체 슬라이스를 나타내지만 range
루프의 각 반복 내에서 점은 슬라이스의 현재 항목으로. 이렇게 하면 맨 점만 사용하여 각 애완 동물의 내보낸 필드에 액세스할 수 있습니다. 슬라이스 인덱스를 참조할 필요가 없습니다.
필드를 표시하는 것은 중괄호로 묶고 앞에 점을 추가하는 것만큼 간단합니다. pets.tmpl
을 열고 내용을 다음과 같이 바꿉니다.
{{ range . }}
---
Name: {{ .Name }}
Sex: {{ .Sex }}
Age: {{ .Age }}
Breed: {{ .Breed }}
{{ end }}
이제 pets.go
는 필드에 대한 일부 레이블을 포함하여 두 마리 개 각각에 대해 5개 필드 중 4개를 인쇄합니다. (잠시 후 다섯 번째 필드에 도달합니다.)
프로그램을 저장하고 다시 실행하십시오.
- go run pets.go
Output
---
Name: Jujube
Sex: Female
Age: 10 months
Breed: German Shepherd/Pitbull
---
Name: Zephyr
Sex: Male
Age: 13 years, 3 months
Breed: German Shepherd/Border Collie
좋아 보인다. 이제 일부 조건 논리를 사용하여 다섯 번째 필드를 표시하는 방법을 살펴보겠습니다.
조건부 사용
템플릿에 {{ .Intact }}
를 추가하여 Intact
필드를 포함하지 않은 이유는 이것이 독자에게 친숙하지 않기 때문입니다. 귀하의 수의사 청구서가 귀하의 개에 대한 요약에 Intact: false
라고 되어 있다고 상상해 보십시오. 이 필드를 문자열이 아닌 부울로 저장하는 것이 효율적일 수 있고 Intact
가 이 필드에 대한 좋은 성별 중립 이름이지만 if를 사용하여 최종 보고서에서 다르게 표시할 수 있습니다. -기타 조치.
pets.tmpl
을 다시 열고 여기에 표시된 강조 표시된 부분을 추가합니다.
{{ range . }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}fixed{{ end }})
Age: {{ .Age }}
Breed: {{ .Breed }}
{{ end }}
템플릿은 이제 Intact
필드가 true인지 확인하고 true이면 (intact)
를 인쇄하고 그렇지 않으면 (fixed)
를 인쇄합니다. 그러나 우리는 그것보다 더 잘할 수 있습니다. 고정
이 아닌 중성화
또는 중성화
와 같은 고정된 개에 대한 성별 관련 용어를 인쇄하도록 템플릿을 추가로 편집해 보겠습니다. 원래 else
내에 중첩된 if
를 추가합니다.
{{ range . }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})
Age: {{ .Age }}
Breed: {{ .Breed }}
{{ end }}
템플릿을 저장하고 pets.go
를 실행합니다.
- go run pets.go
Output
---
Name: Jujube
Sex: Female (spayed)
Age: 10 months
Breed: German Shepherd/Pitbull
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd/Border Collie
개가 두 마리 있지만 온전한
을 표시할 수 있는 세 가지 경우가 있습니다. 세 가지 경우를 모두 다루기 위해 pets.go
의 슬라이스에 개를 한 마리 더 추가해 보겠습니다. pets.go
를 편집하고 슬라이스에 세 번째 개를 추가합니다.
. . .
func main() {
dogs := []Pet{
{
Name: "Jujube",
Sex: "Female",
Intact: false,
Age: "10 months",
Breed: "German Shepherd/Pitbull",
},
{
Name: "Zephyr",
Sex: "Male",
Intact: true,
Age: "13 years, 3 months",
Breed: "German Shepherd/Border Collie",
},
{
Name: "Bruce Wayne",
Sex: "Male",
Intact: false,
Age: "3 years, 8 months",
Breed: "Chihuahua",
},
}
. . .
이제 pets.go
를 저장하고 실행합니다.
- go run pets.go
Output
---
Name: Jujube
Sex: Female (spayed)
Age: 10 months
Breed: German Shepherd/Pitbull
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd/Border Collie
---
Name: Bruce Wayne
Sex: Male (neutered)
Age: 3 years, 8 months
Breed: Chihuahua
좋습니다. 예상대로 표시됩니다.
이제 방금 사용한 eq
함수와 같은 템플릿 함수에 대해 논의하겠습니다.
템플릿 기능 사용
eq
와 함께 필드 값을 비교하고 부울을 반환하는 다른 함수가 있습니다: gt
(>), ne
(!=), <le (<=) 등. 다음 두 가지 방법 중 하나로 이러한 함수와 모든 템플릿 함수를 호출할 수 있습니다.
- 함수 이름을 쓰고 각 매개변수 사이에 공백을 두고 하나 이상의 매개변수를 씁니다. 이것이 위에서
eq
를 사용한 방법입니다:eq .Sex "Female”
. - 먼저 하나의 매개변수를 작성한 다음 파이프(
|
), 함수 이름, 추가 매개변수 순으로 작성하십시오. 이는 한 호출의 출력을 다음 호출의 입력으로 전달하는 파이프라인과 유사합니다.
따라서 템플릿의 eq
비교는 eq .Sex "Female”
로 작성되지만 .Sex | eq "Female”로 작성될 수도 있습니다.
. 이 두 표현은 동일합니다.
len
함수를 사용하여 보고서 상단에 개의 수를 표시해 보겠습니다. pets.tmpl
을 열고 맨 위에 다음을 추가합니다.
Number of dogs: {{ . | len -}}
{{ range . }}
. . .
{{ len 을 작성할 수도 있습니다. -}}
.
닫는 이중 중괄호 옆에 대시(-
)가 있습니다. 이렇게 하면 개행(\n
) 작업 후에 인쇄됩니다. 여는 이중 중괄호({{-
)에 대시를 추가하여 작업 전에 새 줄을 표시하지 않을 수도 있습니다.
템플릿을 저장하고 pets.go
를 실행합니다.
- go run pets.go
OutputNumber of dogs: 3
---
Name: Jujube
Sex: Female (spayed)
Age: 10 months
Breed: German Shepherd & Pitbull
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd & Border Collie
---
Name: Bruce Wayne
Sex: Male (neutered)
Age: 3 years, 8 months
Breed: Chihuahua
{{ 의 대시 때문입니다. | len -}}
, 개 수
레이블과 첫 번째 개 사이에 빈 줄이 없습니다.
text/template
문서의 내장 함수 목록이 매우 작다는 것을 눈치채셨을 것입니다. 좋은 소식은 하나의 값을 반환하거나 두 번째 값이 오류
유형인 경우 두 개의 값을 반환하는 한 템플릿에서 모든 Go 함수를 사용할 수 있다는 것입니다.
템플릿에서 Go 기능 사용
개 조각을 가져와 마지막 개만 인쇄하는 템플릿을 작성하고 싶다고 가정합니다. Go의 mySlice[x:y]
와 유사하게 작동하는 slice
내장 함수를 사용하여 템플릿에서 조각의 하위 집합을 가져올 수 있습니다. {{ 슬라이스라고 쓸 수 있습니다. 2 }}
는 slice
함수가 항목이 아닌 다른 조각을 반환하지만 3개 항목 슬라이스의 마지막 항목을 가져옵니다. 즉, {{ 슬라이스 . 2 }}
는 slice[2]
가 아니라 slice[2:]
와 같습니다. (이 함수는 slice[0:2]
슬라이스를 얻기 위해 {{ slice . 0 2 }}
와 같이 둘 이상의 인덱스를 사용할 수도 있지만 여기에서.)
그러나 템플릿 내 슬라이스의 마지막 인덱스를 어떻게 참조할 수 있습니까? len
함수를 사용할 수 있지만 슬라이스의 마지막 항목에는 len - 1
의 인덱스가 있으며 불행하게도 템플릿에서 산술을 수행할 수 없습니다.
사용자 정의 기능이 매우 유용한 곳입니다. 정수를 감소시키는 함수를 작성하고 템플릿에서 해당 함수를 사용할 수 있도록 합시다.
하지만 그 전에 새 템플릿을 만들어 보겠습니다. 새 파일 lastPet.tmpl
을 열고 다음을 붙여넣습니다.
{{- range (len . | dec | slice . ) }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})
Age: {{ .Age }}
Breed: {{ .Breed }}
{{ end -}}
첫 번째 줄의 dec
함수에 대한 호출은 템플릿에 정의하고 전달하려는 사용자 정의 함수를 참조합니다. pets.go
의 main()
함수 내에서 dogs
슬라이스 아래와 tmpl.실행()
:
. . .
funcMap := template.FuncMap{
"dec": func(i int) int { return i - 1 },
}
var tmplFile = “lastPet.tmpl”
tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
. . .
먼저 함수 맵인 FuncMap
을 선언합니다. 키는 템플릿에서 사용할 수 있는 함수 이름이고 값은 함수 자체입니다. 여기 있는 함수 dec
는 너무 짧기 때문에 인라인으로 제공되는 익명 함수입니다. 정수를 가져와서 1을 빼고 결과를 반환합니다.
그런 다음 템플릿 파일 이름을 변경합니다. 마지막으로 ParseFile
에 대한 호출 전에 Template.Funcs
에 대한 호출을 삽입하여 방금 정의한 funcMap
에 전달합니다. Funcs
메서드는 ParseFiles
전에 호출해야 합니다.
코드를 실행하기 전에 템플릿의 range
작업에서 어떤 일이 발생하는지 이해해 보겠습니다.
{{- range (len . | dec | slice . ) }}
dogs
슬라이스의 길이를 가져오고 이를 사용자 지정 dec
함수에 전달하여 1을 뺀 다음 두 번째 매개변수로 slice
에 전달합니다. 앞에서 설명한 함수입니다. 따라서 3개 조각의 경우 range
작업은 {{- range (slice . 2) }}
와 동일합니다.
pets.go
를 저장하고 실행합니다.
go run pets.go
Output
---
Name: Bruce Wayne
Sex: Male (neutered)
Age: 3 years, 8 months
Breed: Chihuahua
좋아 보인다. 마지막 개가 아닌 마지막 두 개를 보여주고 싶다면 어떻게 해야 할까요? lastPet.tmpl
을 편집하고 dec
에 대한 다른 호출을 파이프라인에 추가합니다.
{{- range (len . | dec | dec | slice . ) }}
. . .
파일을 저장하고 pets.go
를 다시 실행합니다.
go run pets.go
Output
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd/Border Collie
---
Name: Bruce Wayne
Sex: Male (neutered)
Age: 3 years, 8 months
Breed: Chihuahua
dec | 12월
.
이제 슬래시를 앰퍼샌드로 대체하여 Zephyr와 같은 잡종 개를 다르게 표시하고 싶다고 가정해 보겠습니다. strings
패키지에 하나가 있고 템플릿에서 사용하기 위해 모든 패키지에서 함수를 빌릴 수 있기 때문에 그렇게 하기 위해 자신의 함수를 작성할 필요가 없습니다. pets.go
를 편집하여 strings
패키지를 가져오고 해당 기능 중 하나를 funcMap
에 추가합니다.
package main
import (
"os"
"strings"
"text/template"
)
. . .
func main() {
. . .
funcMap := template.FuncMap{
"dec": func(i int) int { return i - 1 },
"replace": strings.ReplaceAll,
}
. . .
} // end main
이제 strings
패키지를 가져오고 해당 ReplaceAll
함수를 replace
라는 이름으로 funcMap
에 추가합니다. 이제 lastPet.tmpl
을 편집하여 이 기능을 사용하십시오.
{{- range (len . | dec | dec | slice . ) }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})
Age: {{ .Age }}
Breed: {{ replace .Breed “/” “ & ” }}
{{ end -}}
파일을 저장하고 한 번 더 실행합니다.
- go run pets.go
Output
---
Name: Zephyr
Sex: Male (intact)
Age: 13 years, 3 months
Breed: German Shepherd & Border Collie
---
Name: Bruce Wayne
Sex: Male (neutered)
Age: 3 years, 8 months
Breed: Chihuahua
Zephyr의 품종에는 이제 슬래시 대신 앰퍼샌드가 포함됩니다.
템플릿 대신 pets.go
내에서 해당 문자열을 조작할 수 있었지만 데이터 표시는 코드가 아니라 템플릿의 작업입니다.
사실 일부 개 데이터에는 이미 일부 표현이 포함되어 있으며 그렇지 않을 수도 있습니다. Breed
필드는 여러 품종을 하나의 문자열에 넣고 슬래시로 구두점을 찍습니다. 이 단일 문자열 패턴은 데이터 입력자가 Labrador/Poodle
, Labrador & Poodle
, Labrador, Poodle
, Labrador-Poodle mix
등. Breed
를 문자열 대신 문자열 조각([]string
)으로 저장하는 것이 더 나을 수 있습니다. 이 형식의 모호성을 피하고 품종별 검색을 더 유연하게 만들고 더 쉽게 제시할 수 있습니다. 그런 다음 템플릿에서 strings.Join
기능을 사용하여 모든 품종을 인쇄하고 .Breed
필드((purebred)
또는 (혼혈종)
).
코드와 템플릿을 수정하여 이러한 변경 사항을 구현해 보십시오. 완료되면 아래 솔루션을 클릭하여 작업을 확인하십시오.
. . .
type Pet struct {
Name string
Sex string
Intact bool
Age string
Breed []string
}
func main() {
dogs := []Pet{
{
Name: "Jujube",
. . .
Breed: []string{"German Shepherd", "Pit Bull"},
},
{
Name: "Zephyr",
. . .
Breed: []string{"German Shepherd", "Border Collie"},
},
{
Name: "Bruce Wayne",
. . .
Breed: []string{"Chihuahua"},
},
}
funcMap := template.FuncMap{
"dec": func(i int) int { return i - 1 },
"replace": strings.ReplaceAll,
"join": strings.Join,
}
. . .
} // end main
{{- range (len . | dec | dec | slice . ) }}
---
Name: {{ .Name }}
Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }})
Age: {{ .Age }}
Breed: {{ join .Breed " & " }} ({{ if len .Breed | eq 1 }}purebred{{ else }}mixed breed{{ end }})
{{ end -}}
마지막으로 이 동일한 개 데이터를 HTML 문서로 렌더링하고 템플릿 출력이 HTML일 때 항상 html/template
패키지를 사용해야 하는 이유를 살펴보겠습니다.
5단계 - HTML 템플릿 작성
명령줄 도구는 출력을 깔끔하게 인쇄하기 위해 text/template
을 사용할 수 있고 일부 다른 배치 프로그램은 일부 데이터에서 잘 구조화된 파일을 만드는 데 사용할 수 있습니다. 그러나 Go 템플릿은 일반적으로 웹 애플리케이션용 HTML 페이지를 렌더링하는 데에도 사용됩니다. 예를 들어 인기 있는 오픈 소스 정적 사이트 생성기 Hugo는 템플릿 기반으로 text/template
과 html/template
을 모두 사용합니다.
HTML에는 일반 텍스트에는 없는 몇 가지 특별한 문제가 있습니다. 꺾쇠 괄호를 사용하여 요소를 감싸고(<td>
), 앰퍼샌드를 사용하여 엔티티를 표시하고(
), 태그 특성을 감싸기 위해 인용 문자를 사용합니다. 템플릿에 삽입하는 데이터에 이러한 문자가 포함된 경우 text/template
패키지를 사용하면 다음과 같은 결과가 발생할 수 있습니다. 형식이 잘못된 HTML 또는 더 나쁜 경우 코드 삽입.
html/template
패키지는 그렇지 않습니다. 이 패키지는 이러한 문제가 있는 문자를 이스케이프하여 안전한 HTML 등가물(엔티티)을 삽입합니다. 데이터의 앰퍼샌드는 &
가 되고 왼쪽 꺾쇠괄호는 <
가 됩니다.
이전과 동일한 개 데이터를 사용하여 HTML 문서를 생성하지만 먼저 텍스트/템플릿
을 계속 사용하여 위험을 보여줍니다.
pets.go
를 열고 다음 강조 표시된 텍스트를 Jujube의 Name
필드에 추가합니다.
. . .
dogs := []Pet{
{
Name: "<script>alert(\"Gotcha!\");</script>Jujube",
Sex: "Female",
Intact: false,
Age: "10 months",
Breed: "German Shepherd/Pit Bull",
},
{
Name: "Zephyr",
Sex: "Male",
Intact: true,
Age: "13 years, 3 months",
Breed: "German Shepherd/Border Collie",
},
{
Name: "Bruce Wayne",
Sex: "Male",
Intact: false,
Age: "3 years, 8 months",
Breed: "Chihuahua",
},
}
. . .
이제 petsHtml.tmpl
이라는 새 파일에 HTML 템플릿을 만듭니다.
<p><strong>Pets:</strong> {{ . | len }}</p>
{{ range . }}
<hr />
<dl>
<dt>Name</dt>
<dd>{{ .Name }}</dd>
<dt>Sex</dt>
<dd>{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</dd>
<dt>Age</dt>
<dd>{{ .Age }}</dd>
<dt>Breed</dt>
<dd>{{ replace .Breed “/” “ & ” }}</dd>
</dl>
{{ end }}
HTML 템플릿을 저장합니다. pets.go
를 실행하기 전에 tmpFile
var를 편집해야 하지만 템플릿을 터미널이 아닌 파일로 출력하도록 프로그램도 편집해 봅시다. pets.go
를 열고 main()
함수 내에 강조 표시된 코드를 추가합니다.
. . .
funcMap := template.FuncMap{
"dec": func(i int) int { return i - 1 },
"replace": strings.ReplaceAll,
}
var tmplFile = "petsHtml.tmpl"
tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
var f *os.File
f, err = os.Create("pets.html")
if err != nil {
panic(err)
}
err = tmpl.Execute(f, dogs)
if err != nil {
panic(err)
}
err = f.Close()
if err != nil {
panic(err)
}
} // end main
pets.html
이라는 새 파일
을 열고 (os.Stdout
대신) tmpl.Execute
에 전달합니다, 완료되면 파일을 닫습니다.
이제 go run pets.go
를 실행하여 HTML 파일을 생성합니다. 그런 다음 브라우저에서 이 로컬 웹페이지를 엽니다.
브라우저가 주입된 스크립트를 실행했습니다. 이것이 특히 템플릿 데이터의 소스를 완전히 신뢰할 수 없는 경우 text/template
패키지를 사용하여 HTML을 생성해서는 안 되는 이유입니다.
데이터의 HTML 문자를 이스케이프 처리하는 것 외에 html/template
패키지는 text/template
과 동일하게 작동하며 기본 이름(template
)이 동일합니다. 즉, 템플릿 삽입을 안전하게 만들기 위해 해야 할 일은 text/template
가져오기를 html/template
로 바꾸는 것뿐입니다. pets.go
를 편집하고 지금 수행하십시오.
package main
import (
"os"
"strings"
"html/template"
)
. . .
파일을 저장하고 마지막으로 실행하여 pets.html
을 덮어씁니다. 그런 다음 브라우저에서 HTML 파일을 새로 고칩니다.
html/template
패키지는 삽입된 스크립트를 웹 페이지의 텍스트로 렌더링했습니다. 텍스트 편집기에서 pets.html
을 열고(또는 브라우저에서 페이지 소스 보기) 첫 번째 개 Jujube를 살펴보세요.
. . .
<dl>
<dt>Name</dt>
<dd><script>alert("Gotcha!");</script>Jujube</dd>
<dt>Sex</dt>
<dd>Female (spayed)</dd>
<dt>Age</dt>
<dd>10 months</dd>
<dt>Breed</dt>
<dd>German Shepherd & Pit Bull</dd>
</dl>
. . .
html 패키지는 대추 이름의 꺾쇠 괄호와 인용 문자, 품종의 앰퍼샌드를 대체했습니다.
결론
Go 템플릿은 모든 데이터 주위에 텍스트를 래핑할 수 있는 편리한 도구입니다. 명령줄 도구에서 출력 형식을 지정하고 웹 애플리케이션에서 HTML을 렌더링하는 등의 작업을 수행할 수 있습니다. 이 자습서에서는 Go의 기본 제공 템플릿 패키지를 사용하여 동일한 데이터에서 올바른 형식의 텍스트와 HTML 페이지를 인쇄했습니다. 이러한 패키지 사용 방법에 대한 자세한 내용은 html/template
문서를 확인하십시오.