//// Sitemap XML! //// import gleam/float import gleam/list import gleam/option.{type Option} import gleam/string_tree import gleam/time/calendar import gleam/time/timestamp.{type Timestamp} import xmb.{type Xml, text, x} pub type Url { Url( /// URL of the page. This URL must begin with the protocol (such as http) /// and end with a trailing slash, if your web server requires it. This /// value must be less than 2,048 characters. /// location: String, /// The date of last modification of the page. /// /// Note that the date must be set to the date the linked page was last /// modified, not when the sitemap is generated. /// /// Note also that this tag is separate from the If-Modified-Since (304) /// header the server can return, and search engines may use the /// information from both sources differently. /// last_modified: option.Option(Timestamp), /// How frequently the page is likely to change. This value provides /// general information to search engines and may not correlate exactly to /// how often they crawl the page. /// /// Please note that the value of this tag is considered a hint and not a /// command. Even though search engine crawlers may consider this information /// when making decisions, they may crawl pages marked "hourly" less /// frequently than that, and they may crawl pages marked "yearly" more /// frequently than that. Crawlers may periodically crawl pages marked /// "never" so that they can handle unexpected changes to those pages. /// change_frequency: option.Option(ChangeFrequency), /// The priority of this URL relative to other URLs on your site. Valid /// values range from 0.0 to 1.0. This value does not affect how your pages /// are compared to pages on other sites—it only lets the search engines /// know which pages you deem most important for the crawlers. /// /// Values outside of the range of 0.0 to 1.0 will be replaced with 0.0 or /// 1.0. /// /// If not specified then the priority of a page is considered to be 0.5. /// /// Please note that the priority you assign to a page is not likely to /// influence the position of your URLs in a search engine's result pages. /// Search engines may use this information when selecting between URLs on /// the same site, so you can use this tag to increase the likelihood that /// your most important pages are present in a search index. /// /// Also, please note that assigning a high priority to all of the URLs on /// your site is not likely to help you. Since the priority is relative, it /// is only used to select between URLs on your site. /// priority: option.Option(Float), ) } /// How frequently a page is likely to change. This value provides general /// information to search engines and may not correlate exactly to how often /// they crawl the page. /// /// The value "always" should be used to describe documents that change each /// time they are accessed. The value "never" should be used to describe /// archived URLs. /// pub type ChangeFrequency { Always Hourly Daily Weekly Monthly Yearly Never } const namespace = #("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9") /// Render an XML sitemap. /// /// This function lets you specify all the properties of each page. /// pub fn render(site: List(Url)) -> string_tree.StringTree { let url = fn(url) { let Url(location:, last_modified:, change_frequency:, priority:) = url x("url", [], [ x("loc", [], [text(location)]), maybe("lastmod", last_modified, render_timestamp), maybe("changefreq", change_frequency, render_change_frequency), maybe("priority", priority, render_priority), ]) } x("urlset", [namespace], list.map(site, url)) |> list.wrap |> xmb.render } /// Render an XML sitemap. /// /// This function takes only URLs. Use the `render` function if you want to /// specify the other properties for eaech page. /// pub fn render_basic(site: List(String)) -> string_tree.StringTree { let url = fn(url) { x("url", [], [x("loc", [], [text(url)])]) } x("urlset", [namespace], list.map(site, url)) |> list.wrap |> xmb.render } fn render_priority(priority: Float) -> Xml { priority |> float.clamp(min: 0.0, max: 1.0) |> float.to_string |> text } fn render_timestamp(timestamp: Timestamp) -> Xml { text(timestamp.to_rfc3339(timestamp, calendar.utc_offset)) } fn render_change_frequency(frequency: ChangeFrequency) -> Xml { text(case frequency { Always -> "always" Hourly -> "hourly" Daily -> "daily" Weekly -> "weekly" Monthly -> "monthly" Yearly -> "yearly" Never -> "never" }) } fn maybe(tag: String, item: Option(a), render: fn(a) -> Xml) -> Xml { case item { option.Some(value) -> x(tag, [], [render(value)]) option.None -> xmb.nothing() } }