PhoenixKit.Modules.Sitemap.UrlEntry (phoenix_kit v2.21.2)

Copy Markdown View Source

Struct representing a single URL entry in sitemap.

Used for both XML and HTML sitemap generation. Contains all necessary metadata for proper sitemap formatting according to sitemaps.org protocol.

Fields

  • loc - Full URL (required)
  • lastmod - Last modification date/time
  • changefreq - Change frequency hint (weekly, daily, monthly, etc.)
  • priority - Priority value 0.0-1.0
  • title - Display title for HTML sitemap
  • category - Category/group for organizing HTML sitemap
  • source - Source module that generated this entry (:entities, :publishing, etc.)
  • alternates - List of alternate language versions for hreflang (optional)
  • canonical_path - Canonical path without language prefix (for grouping alternates)

Alternates Format

Each alternate is a map with:

  • hreflang - Language code (e.g., "en", "et", "x-default")
  • href - Full URL for that language version

Usage

entry = UrlEntry.new(%{
  loc: "https://example.com/blog/my-post",
  lastmod: ~U[2025-01-15 10:00:00Z],
  changefreq: "weekly",
  priority: 0.8,
  title: "My Blog Post",
  category: "Blog",
  source: :publishing,
  alternates: [
    %{hreflang: "en", href: "https://example.com/blog/my-post"},
    %{hreflang: "et", href: "https://example.com/et/blog/my-post"},
    %{hreflang: "x-default", href: "https://example.com/blog/my-post"}
  ]
})

xml = UrlEntry.to_xml(entry)

Summary

Functions

Deduplicates entries by loc, keeping the richest description of each URL.

Escapes XML special characters in a string.

Formats date/datetime to ISO8601 string for lastmod element.

Creates a new UrlEntry struct from attributes.

Normalizes priority value to a float between 0.0 and 1.0.

Parses priority from various formats.

Ranks an entry for a same-loc collision; higher wins.

Converts a UrlEntry to XML format for sitemap.

Types

alternate()

@type alternate() :: %{hreflang: String.t(), href: String.t()}

t()

@type t() :: %PhoenixKit.Modules.Sitemap.UrlEntry{
  alternates: [alternate()] | nil,
  canonical_path: String.t() | nil,
  category: String.t() | nil,
  changefreq: String.t() | nil,
  lastmod: DateTime.t() | Date.t() | NaiveDateTime.t() | nil,
  loc: String.t(),
  priority: float() | String.t() | nil,
  source: atom(),
  title: String.t() | nil
}

Functions

dedupe_by_loc(entries)

@spec dedupe_by_loc([t()]) :: [t()]

Deduplicates entries by loc, keeping the richest description of each URL.

A sitemap file must list a URL once, but more than one producer can arrive at the same loc. RouterDiscovery blindly enumerates every GET route and can emit the loc a content source (Publishing, Entities, ...) also emits with far better metadata; and under DomainMode a locale-prefixed clone route is re-hosted prefix-free onto that language's domain, landing on the home URL the static source already placed there.

Enum.uniq_by/2 alone is not enough: it keeps whichever entry happens to come first, which depends on source ordering rather than on which entry is actually richer.

escape_xml(str)

@spec escape_xml(String.t()) :: String.t()

Escapes XML special characters in a string.

format_date(dt)

@spec format_date(DateTime.t() | Date.t() | NaiveDateTime.t() | nil) ::
  String.t() | nil

Formats date/datetime to ISO8601 string for lastmod element.

new(attrs)

@spec new(map()) :: t()

Creates a new UrlEntry struct from attributes.

Examples

iex> UrlEntry.new(%{loc: "https://example.com/page"})
%UrlEntry{loc: "https://example.com/page"}

iex> UrlEntry.new(%{loc: "https://example.com", priority: 0.8, changefreq: "weekly"})
%UrlEntry{loc: "https://example.com", priority: 0.8, changefreq: "weekly"}

normalize_priority(priority)

@spec normalize_priority(float() | String.t() | nil) :: float()

Normalizes priority value to a float between 0.0 and 1.0.

parse_priority(priority)

@spec parse_priority(String.t() | float() | nil) :: float() | nil

Parses priority from various formats.

Examples

iex> UrlEntry.parse_priority("0.8")
0.8

iex> UrlEntry.parse_priority(0.7)
0.7

iex> UrlEntry.parse_priority(nil)
nil

richness(entry)

@spec richness(t()) :: number()

Ranks an entry for a same-loc collision; higher wins.

A RouterDiscovery entry always scores lowest, so any other source wins regardless of its own priority: route discovery carries only generic metadata (priority 0.5, no hreflang) and must never mask an authoritative content-source entry. Among the rest, the entry describing the URL more fully wins.

to_xml(entry)

@spec to_xml(t()) :: String.t()

Converts a UrlEntry to XML format for sitemap.

Supports hreflang alternate links via xhtml:link elements when alternates is set.

Examples

iex> entry = UrlEntry.new(%{loc: "https://example.com", lastmod: ~D[2025-01-15]})
iex> UrlEntry.to_xml(entry)
"<url>\n  <loc>https://example.com</loc>\n  <lastmod>2025-01-15</lastmod>\n</url>"

iex> entry = UrlEntry.new(%{
...>   loc: "https://example.com/page",
...>   alternates: [
...>     %{hreflang: "en", href: "https://example.com/page"},
...>     %{hreflang: "et", href: "https://example.com/et/page"}
...>   ]
...> })
iex> UrlEntry.to_xml(entry) |> String.contains?("xhtml:link")
true