# Sitemaps

[![CI](https://github.com/manuel-rubio/sitemaps/actions/workflows/ci.yml/badge.svg)](https://github.com/manuel-rubio/sitemaps/actions/workflows/ci.yml)
[![Hex Package](https://img.shields.io/hexpm/v/sitemaps.svg)](https://hex.pm/packages/sitemaps)
[![Hex Docs](https://img.shields.io/badge/hex-docs-purple.svg)](https://hexdocs.pm/sitemaps/)
[![Hex.pm Total Downloads](https://img.shields.io/hexpm/dt/sitemaps.svg)](https://hex.pm/packages/sitemaps)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/manuel-rubio/sitemaps/blob/master/LICENSE)
[![Last Updated](https://img.shields.io/github/last-commit/manuel-rubio/sitemaps.svg)](https://github.com/manuel-rubio/sitemaps/commits/master)

**Sitemaps** is a flexible, fast Elixir library for generating standard XML and gzip-compressed (`.xml.gz`) sitemaps and sitemap index files compliant with search engine specifications (Google, Bing, etc.).

> **Origin & Attribution:**
> This library is an actively maintained fork and evolution of the original [`sitemap`](https://hex.pm/packages/sitemap) package created by [Tatsuo Ikeda (`ikeikeikeike`)](https://github.com/ikeikeikeike/sitemap). It was modernized for current Elixir and Erlang/OTP releases, featuring strict test coverage (95%+), improved documentation, type specifications, and updated dependencies.

---

## Features

- **Standard & Gzip Sitemaps:** Generates uncompressed `.xml` or gzip-compressed `.xml.gz` files out of the box.
- **Automatic Index & Segmentation:** Seamlessly rotates files and builds a `<sitemapindex>` when exceeding link count (default 10,000, up to 50,000) or file size limits (50 MB).
- **Clean Macro DSL:** Intuitive `create`, `add`, and `add_to_index` macros for structuring your sitemaps.
- **Comprehensive Metadata Extensions:**
  - **Images:** Captions, titles, geo-location, and license information.
  - **Videos:** Thumbnails, titles, descriptions, player locations, duration, ratings, tags, and family-friendly flags.
  - **News:** Publication names, languages, publication dates, titles, genres, keywords, and stock tickers.
  - **Alternate Languages:** Localized URL alternates (`hreflang`) via `xhtml:link`.
  - **Mobile:** Mobile search engine tags (`<mobile:mobile/>`).
  - **PageMap:** Google structured data attributes.
- **Search Engine Pings:** Built-in automatic ping notifications for Google and Bing (`Sitemaps.Generator.ping/1` or `ping/1`).
- **Flexible Configuration:** Configure globally via `config.exs`, system environment variables, or per-sitemap options.

---

## Installation

Add `sitemaps` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:sitemaps, "~> 0.1"}
  ]
end
```

---

## Quick Start

### Basic Usage

```elixir
defmodule MyApp.Sitemaps do
  use Sitemaps

  def generate do
    create do
      add "/", priority: 1.0, changefreq: "daily"
      add "/about", priority: 0.8, changefreq: "weekly"
      add "/contact", priority: 0.5, changefreq: "monthly"
    end

    # Notify search engines (Google and Bing) of the updated sitemap
    ping()
  end
end
```

### With Phoenix (Verified Routes)

```elixir
defmodule MyAppWeb.Schedulers.Sitemaps do
  use Sitemaps
  use Phoenix.VerifiedRoutes,
    endpoint: MyAppWeb.Endpoint,
    router: MyAppWeb.Router

  def generate do
    Sitemaps.Config.set(:files_path, "#{:code.priv_dir(:my_app_web)}/static/sitemaps/")
    Sitemaps.Config.set(:host, "https://example.com")

    create do
      add ~p"/", priority: 1.0, changefreq: "daily"
      add ~p"/about", priority: 0.8, changefreq: "weekly"

      for post <- MyApp.Blog.list_posts() do
        add ~p"/blog/#{post.slug}",
          priority: 0.7,
          changefreq: "monthly",
          lastmod: post.updated_at
      end
    end

    ping()
  end
end
```

---

## Rich Metadata Examples

### Images

```elixir
add "/gallery",
  images: [
    [loc: "https://example.com/photo1.jpg", title: "Sunset", caption: "Beach sunset", geo_location: "Malaga, Spain"],
    [loc: "https://example.com/photo2.jpg", title: "Mountains", license: "https://creativecommons.org/licenses/by/4.0/"]
  ]
```

### Videos

```elixir
add "/videos/elixir-intro",
  videos: [
    [
      thumbnail_loc: "https://example.com/thumb.jpg",
      title: "Introduction to Elixir",
      description: "Learn concurrent programming with Elixir and OTP",
      content_loc: "https://example.com/video.mp4",
      player_loc: "https://example.com/player?id=123",
      duration: 600,
      family_friendly: true,
      tag: ["elixir", "erlang", "otp"]
    ]
  ]
```

### News

```elixir
add "/news/release-announcement",
  news: [
    publication: [name: "Tech Times", language: "en"],
    publication_date: ~D[2026-09-02],
    title: "New Elixir Release Out Now",
    genres: "PressRelease",
    keywords: ["elixir", "programming", "technology"]
  ]
```

### Alternate Languages (hreflang)

```elixir
add "/products/book",
  alternates: [
    [href: "https://example.com/en/products/book", lang: "en"],
    [href: "https://example.com/es/products/book", lang: "es"]
  ]
```

### Mobile & PageMap

```elixir
add "/mobile-page", mobile: true

add "/structured-page",
  pagemap: [
    dataobjects: [
      [
        type: "document",
        id: "doc-1",
        attributes: [
          [name: "title", value: "Custom Title"],
          [name: "rating", value: "4.8"]
        ]
      ]
    ]
  ]
```

---

## Configuration

You can configure options in `config/config.exs`, passing them to `use Sitemaps`, or at runtime:

```elixir
config :sitemaps,
  host: "https://example.com",
  files_path: "priv/static/sitemaps/",
  public_path: "sitemaps/",
  compress: true,
  max_sitemap_links: 10_000
```

### Available Options

| Option | Environment Variable | Default | Description |
|---|---|---|---|
| `:host` | `SITEMAPS_HOST` | `"http://www.example.com"` | Base URL scheme and host for generated links |
| `:files_path` | `SITEMAPS_SITEMAPS_PATH` | `"sitemaps/"` | Filesystem path where sitemap files are written |
| `:public_path` | `SITEMAPS_PUBLIC_PATH` | `"sitemaps/"` | URL path where sitemaps are served from |
| `:filename` | `SITEMAPS_FILENAME` | `"sitemap"` | Base filename for the sitemaps |
| `:compress` | `SITEMAPS_COMPRESS` | `true` | When `true`, compresses files with gzip (`.xml.gz`) |
| `:max_sitemap_links` | `SITEMAPS_MAXLINKS` | `10_000` | Maximum number of URLs per sitemap file (max 50,000) |
| `:max_sitemap_files` | `SITEMAPS_MAXFILES` | `10_000` | Maximum sitemaps per index file |
| `:max_sitemap_file_size` | `SITEMAPS_MAXFILESIZE` | `5_000_000` | Maximum uncompressed byte size per file |
| `:create_index` | `SITEMAPS_CREATE_INDEX` | `"auto"` | `"auto"`, `true`, or `false` for index creation |
| `:verbose` | `SITEMAPS_VERBOSE` | `true` | Enable or disable informational console logs |

---

## Acknowledgements & Credits

- Original creator and maintainer: [Tatsuo Ikeda (`ikeikeikeike`)](https://github.com/ikeikeikeike/sitemap).
- Inspired by:
  - [sitemap_generator](https://github.com/kjvarga/sitemap_generator) (Ruby)
  - [go-sitemap-generator](https://github.com/ikeikeikeike/go-sitemap-generator) (Go)

---

## License

This project is licensed under the [MIT License](LICENSE).
