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("") b.WriteString(EscapeText(e.Loc)) b.WriteString("") if e.Lastmod != "" { b.WriteString("") b.WriteString(EscapeText(e.Lastmod)) b.WriteString("") } b.WriteString("") b.WriteString(EscapeText(e.Changefreq)) b.WriteString("") b.WriteString(EscapeText(e.Priority)) b.WriteString("") if len(e.Alternates) > 1 { for _, a := range e.Alternates { b.WriteString(``) } } for _, x := range e.Extra { b.WriteString(x) } b.WriteString("") } // 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("\n") b.WriteString(`\n\n") return b.String() } b.WriteString(">") for _, e := range entries { writeURL(&b, e) } b.WriteString("\n") return b.String() }