- golem15.sitemap plugin: golem15_sitemap_definitions migration, Definition model (Fillable theme and data) with tolerant PHP-shaped item decoding and PHP toArray item encoding - Url::to and buildUrl port, libxml-format serializer, generator for url and registry items - GET /sitemap.xml serves the definition with theme default (404 without one, empty body without items) - tests/php/gen-goldens.php and the tracer golden; TestSitemapGolden and the Postgres TestSitemapTracer
106 lines
3.6 KiB
Go
106 lines
3.6 KiB
Go
package classes
|
|
|
|
import "strings"
|
|
|
|
// The sitemap document is written by hand in the exact format PHP's
|
|
// DOMDocument::saveXML (libxml2) produced for the PHP plugin: the XML
|
|
// declaration and the stylesheet processing instruction each followed by a
|
|
// newline, everything else on one line, an empty urlset self-closed,
|
|
// xhtml:link elements self-closed without a space. The standard library's
|
|
// XML encoder escapes and closes elements differently, so the writer is a
|
|
// strings.Builder.
|
|
|
|
// The urlset opening tag's attributes, in the order PHP set them.
|
|
const urlsetAttrs = ` xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"` +
|
|
` xmlns:xhtml="http://www.w3.org/1999/xhtml"` +
|
|
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"` +
|
|
` xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"`
|
|
|
|
// textEscaper is libxml's escaping of text nodes: &, <, > and carriage
|
|
// return; quotes, tabs, newlines and UTF-8 stay literal.
|
|
var textEscaper = strings.NewReplacer("&", "&", "<", "<", ">", ">", "\r", " ")
|
|
|
|
// attrEscaper is libxml's escaping of attribute values: also the double
|
|
// quote, tab and newline; the single quote stays literal.
|
|
var attrEscaper = strings.NewReplacer(
|
|
"&", "&", "<", "<", ">", ">", `"`, """,
|
|
"\r", " ", "\t", "	", "\n", " ",
|
|
)
|
|
|
|
// EscapeText escapes s as libxml writes a text node.
|
|
func EscapeText(s string) string { return textEscaper.Replace(s) }
|
|
|
|
// EscapeAttr escapes s as libxml writes an attribute value.
|
|
func EscapeAttr(s string) string { return attrEscaper.Replace(s) }
|
|
|
|
// Alternate is one hreflang link of a sitemap URL.
|
|
type Alternate struct {
|
|
Hreflang string
|
|
Href string
|
|
}
|
|
|
|
// URLEntry is one url element: Loc, an optional Lastmod, Changefreq and
|
|
// Priority as they are written, the alternates (written only when there are
|
|
// more than one) and Extra, pre-escaped child XML that AddItem listeners
|
|
// appended, written before the closing tag.
|
|
type URLEntry struct {
|
|
Loc string
|
|
Lastmod string
|
|
Changefreq string
|
|
Priority string
|
|
Alternates []Alternate
|
|
Extra []string
|
|
}
|
|
|
|
// writeURL appends one url element.
|
|
func writeURL(b *strings.Builder, e URLEntry) {
|
|
b.WriteString("<url><loc>")
|
|
b.WriteString(EscapeText(e.Loc))
|
|
b.WriteString("</loc>")
|
|
if e.Lastmod != "" {
|
|
b.WriteString("<lastmod>")
|
|
b.WriteString(EscapeText(e.Lastmod))
|
|
b.WriteString("</lastmod>")
|
|
}
|
|
b.WriteString("<changefreq>")
|
|
b.WriteString(EscapeText(e.Changefreq))
|
|
b.WriteString("</changefreq><priority>")
|
|
b.WriteString(EscapeText(e.Priority))
|
|
b.WriteString("</priority>")
|
|
if len(e.Alternates) > 1 {
|
|
for _, a := range e.Alternates {
|
|
b.WriteString(`<xhtml:link rel="alternate" hreflang="`)
|
|
b.WriteString(EscapeAttr(a.Hreflang))
|
|
b.WriteString(`" href="`)
|
|
b.WriteString(EscapeAttr(a.Href))
|
|
b.WriteString(`"/>`)
|
|
}
|
|
}
|
|
for _, x := range e.Extra {
|
|
b.WriteString(x)
|
|
}
|
|
b.WriteString("</url>")
|
|
}
|
|
|
|
// Serialize writes the sitemap document: the XML declaration, the
|
|
// stylesheet processing instruction pointing at stylesheet (written as it
|
|
// is, like libxml writes PI data) and the urlset with entries.
|
|
func Serialize(stylesheet string, entries []URLEntry) string {
|
|
var b strings.Builder
|
|
b.WriteString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
|
b.WriteString(`<?xml-stylesheet type="text/xsl" href="`)
|
|
b.WriteString(stylesheet)
|
|
b.WriteString("\"?>\n<urlset")
|
|
b.WriteString(urlsetAttrs)
|
|
if len(entries) == 0 {
|
|
b.WriteString("/>\n")
|
|
return b.String()
|
|
}
|
|
b.WriteString(">")
|
|
for _, e := range entries {
|
|
writeURL(&b, e)
|
|
}
|
|
b.WriteString("</urlset>\n")
|
|
return b.String()
|
|
}
|