sitemap_writer/
lib.rs

1//! # sitemap-writer
2//!
3//! A simple and lightweight Rust library for generating XML sitemaps.
4//!
5//! ## Features
6//!
7//! - Simple API for creating sitemaps
8//! - Automatic XML escaping for special characters
9//! - Support for all sitemap properties (`loc`, `lastmod`, `changefreq`, `priority`)
10//! - Support for Sitemap Index (for large sites with 50,000+ URLs)
11//! - Write directly to file or build as String
12//!
13//! ## Quick Start
14//!
15//! ```rust
16//! use sitemap_writer::{SitemapWriter, SitemapUrl, SitemapChangeFreq};
17//!
18//! // Build sitemap as String
19//! let xml = SitemapWriter::build(vec![
20//!     SitemapUrl {
21//!         loc: "https://example.com/".to_string(),
22//!         lastmod: Some("2024-01-01".to_string()),
23//!         changefreq: Some(SitemapChangeFreq::DAILY),
24//!         priority: Some(1.0),
25//!     },
26//!     SitemapUrl::new("https://example.com/about/"),
27//! ]);
28//! ```
29//!
30//! ## Writing to a File
31//!
32//! ```rust,no_run
33//! use sitemap_writer::{SitemapWriter, SitemapUrl};
34//!
35//! let result = SitemapWriter::make("sitemap.xml", vec![
36//!     SitemapUrl::new("https://example.com/"),
37//! ]);
38//! assert!(result.is_ok());
39//! ```
40//!
41//! ## Sitemap Index
42//!
43//! For large sites with more than 50,000 URLs, use Sitemap Index to reference multiple sitemaps:
44//!
45//! ```rust
46//! use sitemap_writer::{SitemapIndexWriter, SitemapIndex};
47//!
48//! let xml = SitemapIndexWriter::build(vec![
49//!     SitemapIndex {
50//!         loc: "https://example.com/sitemap1.xml".to_string(),
51//!         lastmod: Some("2024-01-01".to_string()),
52//!     },
53//!     SitemapIndex::new("https://example.com/sitemap2.xml"),
54//! ]);
55//! ```
56
57mod error;
58mod sitemap_index;
59mod sitemap_url;
60mod sitemap_writer;
61
62pub use error::SitemapError;
63pub use sitemap_index::{SitemapIndex, SitemapIndexWriter};
64pub use sitemap_url::{SitemapChangeFreq, SitemapUrl};
65pub use sitemap_writer::SitemapWriter;
66
67#[cfg(test)]
68mod tests {
69    use crate::{SitemapChangeFreq, SitemapIndex, SitemapIndexWriter, SitemapUrl, SitemapWriter};
70
71    #[test]
72    fn test_make() {
73        let res = SitemapWriter::make("test.xml", vec![]);
74        assert!(res.is_ok());
75
76        let res = SitemapWriter::make(
77            "test.xml",
78            vec![
79                SitemapUrl {
80                    loc: "https://example.com/".to_string(),
81                    lastmod: Some("2021-01-01".to_string()),
82                    changefreq: Some(SitemapChangeFreq::ALWAYS),
83                    priority: Some(1.0),
84                },
85                SitemapUrl::new("https://example.com/contact/"),
86                SitemapUrl::new("https://example.com/contact/?test=1"),
87                SitemapUrl::new("https://example.com/contact/?test=<>"),
88            ],
89        );
90        assert!(res.is_ok());
91    }
92
93    #[test]
94    fn test_build() {
95        let xml = SitemapWriter::build(vec![SitemapUrl::new("https://example.com/")]);
96        assert!(xml.contains("<loc>https://example.com/</loc>"));
97        assert!(xml.contains("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
98    }
99
100    #[test]
101    fn test_xml_escaping() {
102        let xml = SitemapWriter::build(vec![SitemapUrl::new("https://example.com/?a=1&b=2")]);
103        assert!(xml.contains("&amp;"));
104    }
105
106    #[test]
107    fn test_changefreq_display() {
108        assert_eq!(SitemapChangeFreq::ALWAYS.to_string(), "always");
109        assert_eq!(SitemapChangeFreq::DAILY.to_string(), "daily");
110        assert_eq!(SitemapChangeFreq::NEVER.to_string(), "never");
111    }
112
113    #[test]
114    fn test_sitemap_url_new() {
115        let url = SitemapUrl::new("https://example.com/test");
116        assert_eq!(url.loc, "https://example.com/test");
117        assert!(url.lastmod.is_none());
118        assert!(url.changefreq.is_none());
119        assert!(url.priority.is_none());
120    }
121
122    #[test]
123    fn test_sitemap_index_build() {
124        let xml = SitemapIndexWriter::build(vec![
125            SitemapIndex {
126                loc: "https://example.com/sitemap1.xml".to_string(),
127                lastmod: Some("2024-01-01".to_string()),
128            },
129            SitemapIndex::new("https://example.com/sitemap2.xml"),
130        ]);
131        assert!(xml.contains("<sitemapindex"));
132        assert!(xml.contains("</sitemapindex>"));
133        assert!(xml.contains("<loc>https://example.com/sitemap1.xml</loc>"));
134        assert!(xml.contains("<lastmod>2024-01-01</lastmod>"));
135        assert!(xml.contains("<loc>https://example.com/sitemap2.xml</loc>"));
136    }
137
138    #[test]
139    fn test_sitemap_index_make() {
140        let res = SitemapIndexWriter::make(
141            "test_index.xml",
142            vec![
143                SitemapIndex::new("https://example.com/sitemap1.xml"),
144                SitemapIndex {
145                    loc: "https://example.com/sitemap2.xml".to_string(),
146                    lastmod: Some("2024-01-15".to_string()),
147                },
148            ],
149        );
150        assert!(res.is_ok());
151    }
152
153    #[test]
154    fn test_sitemap_index_new() {
155        let sitemap = SitemapIndex::new("https://example.com/sitemap.xml");
156        assert_eq!(sitemap.loc, "https://example.com/sitemap.xml");
157        assert!(sitemap.lastmod.is_none());
158    }
159
160    #[test]
161    fn test_sitemap_index_xml_escaping() {
162        let xml = SitemapIndexWriter::build(vec![SitemapIndex::new(
163            "https://example.com/sitemap.xml?a=1&b=2",
164        )]);
165        assert!(xml.contains("&amp;"));
166    }
167}