Why is this escaping the HTML tag?

66 views
Skip to first unread message

Touring Tim

unread,
Mar 13, 2026, 4:50:21 PM (2 days ago) Mar 13
to golang-nuts
I want to change the tags from H1 to H2 dynamically based upon if something is a report or sub-report.

However even though the variable I use is a template.HTML which is meant to be safe, still GO is auto escaping stuff.

Thanks in advance
Tim
-----------------------

package main

import (
"html/template"
"os"
)

type PageData struct {
HeadingLevel template.HTML // e.g., "h1" or "h2"
Title string
}

func main() {
// 1. Define the template with a variable tag
tmpl := `
<{{.HeadingLevel}}>{{.Title}}</{{.HeadingLevel}}>
`
t := template.Must(template.New("index").Parse(tmpl))

// 2. Data to pass to the template
data := PageData{
HeadingLevel: template.HTML("h1"), // This determines the tag
Title: "Hello, World!",
}

// 3. Execute
t.Execute(os.Stdout, data)
}

Dan Kortschak

unread,
Mar 13, 2026, 5:22:19 PM (2 days ago) Mar 13
to golan...@googlegroups.com
The "<" is not a template.HTML in your code. Try something like this,
https://go.dev/play/p/zcgKpSI5stw.

Touring Tim

unread,
Mar 13, 2026, 7:46:14 PM (2 days ago) Mar 13
to Dan Kortschak, golan...@googlegroups.com
Unfortunately that would change lots of other things.   There are IDs generated as well within the header tags for navigation from a table of contents

I agree I can build a workaround in many ways but I shouldn't have to.  The whole template is html and if i don't have that variable there, just H1 hardcoded then it works.  I insert a simple variable and it breaks and yet the variable is html.

There needs to be something in GO that should show this.  If they want to start second guessing it the final output is safe or not then parser the final output.  Don't get hung up on a variable.



--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/97c7fa124d84f334996a414c9c05f7ebfb86a8df.camel%40kortschak.io.

Ian Lance Taylor

unread,
Mar 13, 2026, 8:33:01 PM (2 days ago) Mar 13
to Touring Tim, Dan Kortschak, golan...@googlegroups.com
On Fri, Mar 13, 2026 at 4:45 PM Touring Tim <tim....@gmail.com> wrote:
>
> Unfortunately that would change lots of other things. There are IDs generated as well within the header tags for navigation from a table of contents
>
> I agree I can build a workaround in many ways but I shouldn't have to. The whole template is html and if i don't have that variable there, just H1 hardcoded then it works. I insert a simple variable and it breaks and yet the variable is html.
>
> There needs to be something in GO that should show this. If they want to start second guessing it the final output is safe or not then parser the final output. Don't get hung up on a variable.

The html/template package can only generate safe output if it can
recognize the tags. Otherwise it can't know what sort of escaping to
do. For example, the escaping needs to change if it sees a <script>
tag. Your program is providing the tag dynamically. html/template does
not support that kind of dynamic determination of escape context.

Ian

Touring Tim

unread,
Mar 13, 2026, 9:50:18 PM (2 days ago) Mar 13
to golang-nuts
Hi Ian / Dan,

I really appreciate there are workarounds, I use golang templates a lot so I know - in many places I have to write functions that are workarounds.

I have a template function "html" that simply returns template.html of a string.

Because the options are set by user settings and it's all buried deep in iterative loops of producing reports this is what I finally did:

The original h2 and h3 lines look something like this where there are many uses of h2, h3
<h2 id="{{$id}}"><b>{{$.sectionNumber}}.{{$.section}} {{.ProcessLibrary.FullName}}</b></h2>

So because this can now be part of a small or large generated document, now h2 can be h3 or even h4 etc.

So I create the variable h2 further up in the code with something like this - it isn't this but you get the idea
{{- $h2 := html (printf 'h%d' .level)}}

and the line I would have preferred would be very readable (not to mention a quick search and replace)
<{{$h2}} id="{{$id}}"><b>{{$.sectionNumber}}.{{$.section}} {{.ProcessLibrary.FullName}}</b></{{$h2}}>

Perfectly valid HTML in the end

The workaround I implemented is as follows:
Change creation of $h2 to a plain old string
{{- $h2 := printf 'h%d' .level}}

Do this in every place where it was <h2> .. </h2>
{{html (printf `<%s id="%s">` $h2 $id) -}}
{{$.sectionNumber}}.{{$.section}} {{.FullName}}
{{- html (printf `</%s>` $h2)}}

I agree I could have created $h2o and $h2c for opening and closing formats near the top but as I have the ID for table of contents I anyway need to create the tagged value dynamcally.

I could have even written javascript to fix it.  There are many workarounds, all of which become harder for someone to understand when they look at the code in 2 years time.

However if there are many workarounds, why is GO being so sanctimonious?  Maybe that's good for day 1 newbie but not seasoned developer!!  Sure I could use some 3rd party library with different template logic.  But what's wrong with a template.unsafe function that just accepts I know what I'm doing and doesn't try and police things and anyway has workarounds.

I really wrote this because I come across issues like this a lot with GO templates and wondered if I just was missing some obvious faux pas.  It seems not - I just have to keep writing the workarounds for GO not allowing tags to be changed dynamically.  Unexpected escaping is quite frustrating in GO templates.

Regards,
Tim.
Reply all
Reply to author
Forward
0 new messages