]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
tpl/transform: Add transform.XMLEscape template function
authorJoe Mooring <joe.mooring@veriphor.com>
Fri, 24 Nov 2023 23:51:27 +0000 (15:51 -0800)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 27 Nov 2023 09:52:33 +0000 (10:52 +0100)
Fixes #3268

docs/content/en/functions/transform/XMLEscape.md [new file with mode: 0644]
tpl/tplimpl/embedded/templates/_default/rss.xml
tpl/tplimpl/template_funcs_test.go
tpl/transform/init.go
tpl/transform/integration_test.go
tpl/transform/transform.go

diff --git a/docs/content/en/functions/transform/XMLEscape.md b/docs/content/en/functions/transform/XMLEscape.md
new file mode 100644 (file)
index 0000000..17ed2a1
--- /dev/null
@@ -0,0 +1,38 @@
+---
+title: transform.XMLEscape
+description: Returns the given string, removing disallowed characters then escaping the result to its XML equivalent.
+categories: []
+keywords: []
+action:
+  aliases: []
+  related: []
+  returnType: string
+  signatures: [transform.XMLEscape INPUT]
+---
+
+The `transform.XMLEscape` function removes [disallowed characters] as defined in the XML specification, then escapes the result by replacing the following characters with [HTML entities]:
+
+- `"` → `&#34;`
+- `'` → `&#39;`
+- `&` → `&amp;`
+- `<` → `&lt;`
+- `>` → `&gt;`
+- `\t` → `&#x9;`
+- `\n` → `&#xA;`
+- `\r` → `&#xD;`
+
+For example:
+
+```go-html-template
+transform.XMLEscape "<p>abc</p>" → &lt;p&gt;abc&lt;/p&gt;
+```
+
+When using `transform.XMLEscape` in a template rendered by Go's [html/template] package, declare the string to be safe HTML to avoid double escaping. For example, in an RSS template:
+
+{{< code file="layouts/_default/rss.xml" >}}
+<description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
+{{< /code >}}
+
+[disallowed characters]: https://www.w3.org/TR/xml/#charsets
+[html entities]: https://developer.mozilla.org/en-us/docs/glossary/entity
+[html/template]: https://pkg.go.dev/html/template
index a4cdd2383a26da7e50725baa0f2b0b4f4fd7c823..6818d31ec16c82d507081eb3e2afa4365dbec85e 100644 (file)
@@ -64,7 +64,7 @@
       <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
       {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
       <guid>{{ .Permalink }}</guid>
-      <description>{{ .Summary | html }}</description>
+      <description>{{ .Summary | transform.XMLEscape | safeHTML }}</description>
     </item>
     {{- end }}
   </channel>
index cb1aa6febd4c395dcfca3b73b8fe696061e289ed..9cc84934b9fed423d5fbe3a1cf1a127798fa215d 100644 (file)
@@ -37,7 +37,7 @@ home=["HTML"]
 -- files/README.txt --
 Hugo Rocks!
 -- content/blog/hugo-rocks.md --
---- 
+---
 title: "**BatMan**"
 ---
 `
@@ -65,6 +65,10 @@ title: "**BatMan**"
                                        // This will fail the build, so skip for now.
                                        continue
                                }
+                               if strings.Contains(example[0], "transform.XMLEscape") {
+                                       // This will fail the build, so skip for now.
+                                       continue
+                               }
                                templates = append(templates, example[0])
                                expected = append(expected, example[1])
                        }
index 00ae8f89d048011b8fc88dc954878ade39a656b3..e439604277a35920b44930e084726e9ba17a49fd 100644 (file)
@@ -112,6 +112,16 @@ func init() {
                        },
                )
 
+               ns.AddMethodMapping(ctx.XMLEscape,
+                       nil,
+                       [][2]string{
+                               {
+                                       `{{ transform.XMLEscape "<p>abc</p>" }}`,
+                                       `&lt;p&gt;abc&lt;/p&gt;`,
+                               },
+                       },
+               )
+
                return ns
        }
 
index 17348928db6c8f69c509f01ec1607a5c1df06f6d..9a68b7ff2c3c9906c413f56e952ff62bfd117f1e 100644 (file)
@@ -65,3 +65,23 @@ foo
        b.AssertFileContent("public/p3/index.html", "_<h2 id=\"foo\">foo</h2>\n<p>bar</p>\n_")
        b.AssertFileContent("public/p4/index.html", "_<p id=\"bar\">foo</p>\n_")
 }
+
+func TestXMLEscape(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- config.toml --
+disableKinds = ['section','sitemap','taxonomy','term']
+-- content/p1.md --
+---
+title: p1
+---
+a **b** \v c
+<!--more-->
+  `
+       b := hugolib.Test(t, files)
+
+       b.AssertFileContent("public/index.xml", `
+       <description>&lt;p&gt;a &lt;strong&gt;b&lt;/strong&gt;  c&lt;/p&gt;</description>
+       `)
+}
index d943b0b5752beea996a23b70392faafef6ecf6bb..178db84e412b1605e8ac9c4bebcbf078db0f28f5 100644 (file)
 package transform
 
 import (
+       "bytes"
        "context"
+       "encoding/xml"
        "html"
        "html/template"
+       "strings"
 
        "github.com/gohugoio/hugo/cache/namedmemcache"
        "github.com/gohugoio/hugo/markup/converter/hooks"
@@ -118,6 +121,34 @@ func (ns *Namespace) HTMLUnescape(s any) (string, error) {
        return html.UnescapeString(ss), nil
 }
 
+// XMLEscape returns the given string, removing disallowed characters then
+// escaping the result to its XML equivalent.
+func (ns *Namespace) XMLEscape(s any) (string, error) {
+       ss, err := cast.ToStringE(s)
+       if err != nil {
+               return "", err
+       }
+
+       // https://www.w3.org/TR/xml/#NT-Char
+       cleaned := strings.Map(func(r rune) rune {
+               if r == 0x9 || r == 0xA || r == 0xD ||
+                       (r >= 0x20 && r <= 0xD7FF) ||
+                       (r >= 0xE000 && r <= 0xFFFD) ||
+                       (r >= 0x10000 && r <= 0x10FFFF) {
+                       return r
+               }
+               return -1
+       }, ss)
+
+       var buf bytes.Buffer
+       err = xml.EscapeText(&buf, []byte(cleaned))
+       if err != nil {
+               return "", err
+       }
+
+       return buf.String(), nil
+}
+
 // Markdownify renders s from Markdown to HTML.
 func (ns *Namespace) Markdownify(ctx context.Context, s any) (template.HTML, error) {