# `PhoenixKit.Modules.Sitemap.UrlEntry`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.87/lib/modules/sitemap/url_entry.ex#L1)

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)

# `alternate`

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

# `t`

```elixir
@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
}
```

# `escape_xml`

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

Escapes XML special characters in a string.

# `format_date`

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

Formats date/datetime to ISO8601 string for lastmod element.

# `new`

```elixir
@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`

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

Normalizes priority value to a float between 0.0 and 1.0.

# `parse_priority`

```elixir
@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

# `to_xml`

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
