🧑‍🔧

Go言語で、html テンプレートエンジンを使わずに済ませる方法

2024/12/06に公開

この記事は、Go Advent Calendar 2024 シリーズ2 6日目の記事です。

はじめに

Go で、Web サーバーアプリケーションを開発する場合、Routing は標準ライブラリもかなり便利になり、DB アクセスについても、それぞれの目的や好みにに合わせて選択肢があると思います。それでも、Go が Web アプリケーションを作成することに向いていないという評判があるように思います。これは、いろいろある html テンプレートエンジンに機能が足りないということではなく(htm/template などはそれだけで一つの言語システムと言ってもいいと思います)、使い心地が、Go を使いたい理由にうまくあっていないことだと思います。

そこで、思いついたことがありますので書こうと思います。

golang.org/x/net/html パッケージ

この準標準パッケージは、html を パースするパッケージです。スクレイピングを行うときに使うことがあるのではないでしょうか? パースした結果は、次の構造体のツリーになります。

type Node struct {
	Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node

	Type      NodeType
	DataAtom  atom.Atom
	Data      string
	Namespace string
	Attr      []Attribute
}

この構造体は、html エレメントか テキストを表します(他にもありますが、詳しくはx/net/htmlパッケージを参照してください)。親、子、兄弟へのリンクがあり、Data は、エレメントのタグ もしくは、テキストです。

そして、このパッケージには、Render 関数があります。

何らかの html ファイルがあれば、それテンプレートとして使って、加工して、Render することができます。ちなみに、この Render 関数は、テキストと属性値の html escape 処理を行ってくれます。

つまり、html をパースして、加工したい箇所を特定して、属性を変更/設定し、子孫要素を追加したり、テキストにデータを流し込めば、template エンジンにさせていることができます。要素やテキストの削除もできます。しかも、この加工には、Go 以外のものは必要ありません。

加工したい箇所の特定は、ゴリゴリコードを書いてもいいですが、css セレクターを使うライブラリもあります。

そこで、x/net/html パッケージと css セレクターライブラリ(https://github.com/ericchiang/css)の薄いラッパーライブラリ https://github.com/turutcrane/haat を作成しました。

サンプル

package main

import (
	"log"
	"os"

	"github.com/turutcrane/haat"
)

func main() {

	h, err := haat.ParseHtml(strings.NewReader(`
<!DOCTYPE html>
<html>
<head>
<title>Hello haat</title>
</head>
<body>
Hello <span id="pkgname"></span>!!
</body></html>`))
	if err != nil {
		log.Panicln(err)
	}
	for _, span := range h.Query("span#pkgname") { // id=pkgname の span を探す
		span.C(haat.T("haat"))  // span の テキストに haat を設定する
	}
	h.Render(os.Stdout)
}

このサンプルを実行すると、

<!DOCTYPE html><html><head>
<title>Hello haat</title>
</head>
<body>
Hello <span id="pkgname">haat</span>!!
</body></html>

が出力されます。

このサンプルはテキストを設定していますが、もちろん他のエレメントを追加したり、属性を設定することもできます。

この方法のメリット

実際の業務などでは使用していないので、多分に想像が入っています。

  • コードは、Go ですし、html は正しい html であればよいので、それぞれのツールが使えます。(これが大切)
  • コードは、Go なので、関数の分解・共通化・ライブラリ化は自由にできる。
  • 元 html に、サンプル表示用のダーミーデータがあっても構わない。不要な部分は、プログラムで削除/更新すればよいので、デザインツールから出力された html を加工せずテンプレートにできる。
  • パーツ集のような html から一部を取ってきて、表示用 html に組み込むことができる。
  • テストも Render 後の html ではなく、 加工後の ツリーを確認することは比較的簡単にできる。
  • すでに稼働しているシステムからの移管もテンプレートエンジンのソースをどうにかするのではなく、ブラウザーのページソースを表示から、持ってくることができる。
  • GopherJS と組み合わせると面白いことができるかもしれない。

足りないところ、デメリット(かもしれないこと)

  • js/css との連携は、別途考える必要がある。とくに、部品化を行うときに、style 要素は、head 要素に置かないといけないようなので、工夫が必要だと思います。
  • 変更箇所を特定するために、何度もツリーを走査することになるので、遅くなるかもしれない。
  • goroutine safe でない。(これは、template/html も)

おわりに

html がある場合は、それをテンプレートエンジンに合わせて書き換えることは不要なので、お手軽に使える方法ではないでしょうか?

この方法は、信頼できるパーサーがあれば、Go 言語にかぎらず使えるとおもいます。

Discussion