웹사이트 검색

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)으로 이동하여 이 프로젝트를 위한 새 디렉터리를 만듭니다.

  1. cd `go env GOPATH`
  2. mkdir pets
  3. cd pets

nano 또는 선호하는 텍스트 편집기를 사용하여 pets.go라는 새 파일을 열고 다음을 붙여넣습니다.

  1. 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를 누른 다음 YENTER를 눌러 변경 사항을 확인합니다.

이 템플릿을 실행하면 "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으로 실행합니다.

  1. go run pets.go
Output
Nothing 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를 다시 실행합니다.

  1. 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개를 인쇄합니다. (잠시 후 다섯 번째 필드에 도달합니다.)

프로그램을 저장하고 다시 실행하십시오.

  1. 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를 실행합니다.

  1. 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를 저장하고 실행합니다.

  1. 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 (<=) 등. 다음 두 가지 방법 중 하나로 이러한 함수와 모든 템플릿 함수를 호출할 수 있습니다.

  1. 함수 이름을 쓰고 각 매개변수 사이에 공백을 두고 하나 이상의 매개변수를 씁니다. 이것이 위에서 eq를 사용한 방법입니다: eq .Sex "Female”.
  2. 먼저 하나의 매개변수를 작성한 다음 파이프(|), 함수 이름, 추가 매개변수 순으로 작성하십시오. 이는 한 호출의 출력을 다음 호출의 입력으로 전달하는 파이프라인과 유사합니다.

따라서 템플릿의 eq 비교는 eq .Sex "Female”로 작성되지만 .Sex | eq "Female”로 작성될 수도 있습니다. . 이 두 표현은 동일합니다.

len 함수를 사용하여 보고서 상단에 개의 수를 표시해 보겠습니다. pets.tmpl을 열고 맨 위에 다음을 추가합니다.

Number of dogs: {{ . | len -}}

{{ range . }}
. . .

{{ len 을 작성할 수도 있습니다. -}}.

닫는 이중 중괄호 옆에 대시(-)가 있습니다. 이렇게 하면 개행(\n) 작업 후에 인쇄됩니다. 여는 이중 중괄호({{-)에 대시를 추가하여 작업 전에 새 줄을 표시하지 않을 수도 있습니다.

템플릿을 저장하고 pets.go를 실행합니다.

  1. go run pets.go
Output
Number 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.gomain() 함수 내에서 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 -}}

파일을 저장하고 한 번 더 실행합니다.

  1. 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/templatehtml/template을 모두 사용합니다.

HTML에는 일반 텍스트에는 없는 몇 가지 특별한 문제가 있습니다. 꺾쇠 괄호를 사용하여 요소를 감싸고(<td>), 앰퍼샌드를 사용하여 엔티티를 표시하고(&nbsp;), 태그 특성을 감싸기 위해 인용 문자를 사용합니다. 템플릿에 삽입하는 데이터에 이러한 문자가 포함된 경우 text/template 패키지를 사용하면 다음과 같은 결과가 발생할 수 있습니다. 형식이 잘못된 HTML 또는 더 나쁜 경우 코드 삽입.

html/template 패키지는 그렇지 않습니다. 이 패키지는 이러한 문제가 있는 문자를 이스케이프하여 안전한 HTML 등가물(엔티티)을 삽입합니다. 데이터의 앰퍼샌드는 &amp;가 되고 왼쪽 꺾쇠괄호는 &lt;가 됩니다.

이전과 동일한 개 데이터를 사용하여 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>&lt;script&gt;alert(&#34;Gotcha!&#34;);&lt;/script&gt;Jujube</dd>
        <dt>Sex</dt>
        <dd>Female (spayed)</dd>
        <dt>Age</dt>
        <dd>10 months</dd>
        <dt>Breed</dt>
        <dd>German Shepherd &amp; Pit Bull</dd>
</dl>
. . .

html 패키지는 대추 이름의 꺾쇠 괄호와 인용 문자, 품종의 앰퍼샌드를 대체했습니다.

결론

Go 템플릿은 모든 데이터 주위에 텍스트를 래핑할 수 있는 편리한 도구입니다. 명령줄 도구에서 출력 형식을 지정하고 웹 애플리케이션에서 HTML을 렌더링하는 등의 작업을 수행할 수 있습니다. 이 자습서에서는 Go의 기본 제공 템플릿 패키지를 사용하여 동일한 데이터에서 올바른 형식의 텍스트와 HTML 페이지를 인쇄했습니다. 이러한 패키지 사용 방법에 대한 자세한 내용은 html/template 문서를 확인하십시오.