Repository: gohugoio/hugo (language: go)

Issue:
tpl/tplimpl: Allow content exclusion in sitemap.xml
Closes #653


Discussion / hints:
The commit message is still incorrect. See <https://github.com/gohugoio/hugo/pull/12281#issuecomment-2007808649>

It should be on three lines. Make the change to the commit  message (git commit --amend) and force push (git push -f).
> The commit message is still incorrect. See [#12281 (comment)](https://github.com/gohugoio/hugo/pull/12281#issuecomment-2007808649)
> 
> It should be on three lines. Make the change to the commit message (git commit --amend) and force push (git push -f).

Updated commit message.
bep, please merge. This allows sitemap exclusion on a page-by-page basis:

```text
[sitemap]
exclude = true
changeFreq = "hourly"
priority = 0.7
```

This obviously fits nicely with the existing front matter values, and the change is not invasive (limited to the embedded template).


May I suggest this so that the output doesn't have extra whitespace?

```xml
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
  {{- range where .Data.Pages "Params.sitemap.exclude" "ne" true -}}{{ if .Permalink }}
  <url>
    <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
    <lastmod>{{ safeHTML (.Lastmod.Format "2006-01-02T15:04:05-07:00") }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
    <xhtml:link rel="alternate" hreflang="{{ .Language.LanguageCode }}" href="{{ .Permalink }}"/>{{ end }}
    <xhtml:link rel="alternate" hreflang="{{ .Language.LanguageCode }}" href="{{ .Permalink }}"/>{{ end }}
  </url>{{ end }}{{ end }}
</urlset>

```
@XhmikosR May be we can have the white space removal as a separate PR? I want to keep the PR minimal in changes so that it is merged faster. 
Not my choice to make, but it's a cosmetic change anyway. Feel free to submit it separately, I just thought now it'd be a good time
OK, we're back to https://github.com/gohugoio/hugo/pull/6370. 

Fix patch:
diff --git a/config/commonConfig.go b/config/commonConfig.go
index 6ca061093c5..6ae97c2a185 100644
--- a/config/commonConfig.go
+++ b/config/commonConfig.go
@@ -215,6 +215,8 @@ type SitemapConfig struct {
 	Priority float64
 	// The sitemap filename.
 	Filename string
+	// Whether to disable page inclusion.
+	Disable bool
 }
 
 func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) {
diff --git a/docs/data/docs.yaml b/docs/data/docs.yaml
index f9b5731f2b6..b522ffbef0c 100644
--- a/docs/data/docs.yaml
+++ b/docs/data/docs.yaml
@@ -1649,6 +1649,7 @@ config:
       disableInlineCSS: false
   sitemap:
     changeFreq: ""
+    disable: false
     filename: sitemap.xml
     priority: -1
   social: null
@@ -2797,8 +2798,8 @@ tpl:
             {{ $m.Set "Hugo" "Rocks!" }}
             {{ $m.Values | debug.Dump | safeHTML }}
           - |-
-            map[string]interface {}{
-              "Hugo": "Rocks!",
+            {
+              "Hugo": "Rocks!"
             }
       TestDeprecationErr:
         Aliases: null
diff --git a/tpl/tplimpl/embedded/templates/_default/sitemap.xml b/tpl/tplimpl/embedded/templates/_default/sitemap.xml
index b1e4b2d2d14..de1f467d13d 100644
--- a/tpl/tplimpl/embedded/templates/_default/sitemap.xml
+++ b/tpl/tplimpl/embedded/templates/_default/sitemap.xml
@@ -1,7 +1,7 @@
 {{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
   xmlns:xhtml="http://www.w3.org/1999/xhtml">
-  {{ range .Data.Pages }}
+  {{ range where .Pages "Sitemap.Disable" "ne" true }}
     {{- if .Permalink -}}
   <url>
     <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}


Test patch:
diff --git a/hugolib/config_test.go b/hugolib/config_test.go
index 22beea655e8..8acf8b36e48 100644
--- a/hugolib/config_test.go
+++ b/hugolib/config_test.go
@@ -490,11 +490,10 @@ name = "menu-theme"
 			got := b.Configs.Base
 
 			if mergeStrategy == "none" {
-				b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Priority: -1, Filename: "sitemap.xml"})
-
+				b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Disable: false, Priority: -1, Filename: "sitemap.xml"})
 				b.AssertFileContent("public/sitemap.xml", "schemas/sitemap")
 			} else {
-				b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Priority: -1, Filename: "sitemap.xml"})
+				b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Disable: false, Priority: -1, Filename: "sitemap.xml"})
 				b.AssertFileContent("public/sitemap.xml", "<changefreq>monthly</changefreq>")
 			}
 		})
diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
index a5a94c67e75..1c2642468a2 100644
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -108,11 +108,12 @@ outputs: [ "html", "amp" ]
 
 func TestParseSitemap(t *testing.T) {
 	t.Parallel()
-	expected := config.SitemapConfig{Priority: 3.0, Filename: "doo.xml", ChangeFreq: "3"}
+	expected := config.SitemapConfig{ChangeFreq: "3", Disable: true, Filename: "doo.xml", Priority: 3.0}
 	input := map[string]any{
 		"changefreq": "3",
-		"priority":   3.0,
+		"disable":    true,
 		"filename":   "doo.xml",
+		"priority":   3.0,
 		"unknown":    "ignore",
 	}
 	result, err := config.DecodeSitemap(config.SitemapConfig{}, input)
diff --git a/tpl/tplimpl/tplimpl_integration_test.go b/tpl/tplimpl/tplimpl_integration_test.go
index abda3af29a0..28d442e0d63 100644
--- a/tpl/tplimpl/tplimpl_integration_test.go
+++ b/tpl/tplimpl/tplimpl_integration_test.go
@@ -254,3 +254,54 @@ disable = false
 		`s.src = '//' + "foo" + '.disqus.com/embed.js';`,
 	)
 }
+
+func TestSitemap(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','taxonomy','term']
+[sitemap]
+disable = true
+-- content/p1.md --
+---
+title: p1
+sitemap:
+  p1_disable: foo
+---
+-- content/p2.md --
+---
+title: p2
+
+---
+-- layouts/_default/single.html --
+{{ .Title }}
+`
+
+	// Test A: Exclude all pages via site config.
+	b := hugolib.Test(t, files)
+	b.AssertFileContentExact("public/sitemap.xml",
+		"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n  xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n  \n</urlset>\n",
+	)
+
+	// Test B: Include all pages via site config.
+	files_b := strings.ReplaceAll(files, "disable = true", "disable = false")
+	b = hugolib.Test(t, files_b)
+	b.AssertFileContentExact("public/sitemap.xml",
+		"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n  xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n  <url>\n    <loc>/p1/</loc>\n  </url><url>\n    <loc>/p2/</loc>\n  </url>\n</urlset>\n",
+	)
+
+	// Test C: Exclude all pages via site config, but include p1 via front matter.
+	files_c := strings.ReplaceAll(files, "p1_disable: foo", "disable: false")
+	b = hugolib.Test(t, files_c)
+	b.AssertFileContentExact("public/sitemap.xml",
+		"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n  xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n  <url>\n    <loc>/p1/</loc>\n  </url>\n</urlset>\n",
+	)
+
+	// Test D:  Include all pages via site config, but exclude p1 via front matter.
+	files_d := strings.ReplaceAll(files_b, "p1_disable: foo", "disable: true")
+	b = hugolib.Test(t, files_d)
+	b.AssertFileContentExact("public/sitemap.xml",
+		"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n  xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n  <url>\n    <loc>/p2/</loc>\n  </url>\n</urlset>\n",
+	)
+}