use crate::error::Result;
use crate::types::*;
use crate::validator::Validator;
use crate::writer::XmlWriter;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File;
use std::io::Write;
use std::path::Path;
#[derive(Debug, Default)]
pub struct SitemapBuilder {
entries: Vec<UrlEntry>,
validate: bool,
}
impl SitemapBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_url(&mut self, entry: UrlEntry) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_urls(&mut self, entries: Vec<UrlEntry>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
Validator::validate_url_count(self.entries.len())?;
for entry in &self.entries {
Validator::validate_url(&entry.loc)?;
if let Some(ref lastmod) = entry.lastmod {
Validator::validate_date(lastmod)?;
}
if let Some(priority) = entry.priority {
Validator::validate_priority(priority)?;
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_sitemap(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct ImageSitemapBuilder {
entries: Vec<UrlWithImages>,
validate: bool,
}
impl ImageSitemapBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_url(&mut self, entry: UrlWithImages) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_urls(&mut self, entries: Vec<UrlWithImages>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
Validator::validate_url_count(self.entries.len())?;
for entry in &self.entries {
Validator::validate_url(&entry.url.loc)?;
if let Some(ref lastmod) = entry.url.lastmod {
Validator::validate_date(lastmod)?;
}
if let Some(priority) = entry.url.priority {
Validator::validate_priority(priority)?;
}
for image in &entry.images {
Validator::validate_url(&image.loc)?;
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_image_sitemap(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct VideoSitemapBuilder {
entries: Vec<UrlWithVideos>,
validate: bool,
}
impl VideoSitemapBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_url(&mut self, entry: UrlWithVideos) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_urls(&mut self, entries: Vec<UrlWithVideos>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
Validator::validate_url_count(self.entries.len())?;
for entry in &self.entries {
Validator::validate_url(&entry.url.loc)?;
if let Some(ref lastmod) = entry.url.lastmod {
Validator::validate_date(lastmod)?;
}
for video in &entry.videos {
Validator::validate_url(&video.thumbnail_loc)?;
Validator::validate_video_title(&video.title)?;
Validator::validate_video_description(&video.description)?;
if let Some(ref content_loc) = video.content_loc {
Validator::validate_url(content_loc)?;
}
if let Some(ref player_loc) = video.player_loc {
Validator::validate_url(player_loc)?;
}
if let Some(duration) = video.duration {
Validator::validate_video_duration(duration)?;
}
if let Some(rating) = video.rating {
Validator::validate_video_rating(rating)?;
}
if let Some(ref pub_date) = video.publication_date {
Validator::validate_date(pub_date)?;
}
if let Some(ref exp_date) = video.expiration_date {
Validator::validate_date(exp_date)?;
}
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_video_sitemap(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct SitemapIndexBuilder {
entries: Vec<SitemapIndexEntry>,
validate: bool,
}
impl SitemapIndexBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_sitemap(&mut self, entry: SitemapIndexEntry) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_sitemaps(&mut self, entries: Vec<SitemapIndexEntry>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
for entry in &self.entries {
Validator::validate_url(&entry.loc)?;
if let Some(ref lastmod) = entry.lastmod {
Validator::validate_date(lastmod)?;
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_sitemap_index(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
pub struct NewsSitemapBuilder {
entries: Vec<UrlWithNews>,
validate: bool,
}
impl NewsSitemapBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_url(&mut self, entry: UrlWithNews) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_urls(&mut self, entries: Vec<UrlWithNews>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
Validator::validate_news_url_count(self.entries.len())?;
for entry in &self.entries {
Validator::validate_url(&entry.url.loc)?;
Validator::validate_date(&entry.news.publication_date)?;
Validator::validate_language_code(&entry.news.publication.language)?;
if let Some(ref tickers) = entry.news.stock_tickers {
Validator::validate_stock_tickers(tickers)?;
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_news_sitemap(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
impl Default for NewsSitemapBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct CombinedSitemapBuilder {
entries: Vec<UrlWithExtensions>,
validate: bool,
}
impl CombinedSitemapBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
validate: true,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
validate: true,
}
}
pub fn validate(mut self, validate: bool) -> Self {
self.validate = validate;
self
}
pub fn add_url(&mut self, entry: UrlWithExtensions) -> &mut Self {
self.entries.push(entry);
self
}
pub fn add_urls(&mut self, entries: Vec<UrlWithExtensions>) -> &mut Self {
self.entries.extend(entries);
self
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn validate_entries(&self) -> Result<()> {
if !self.validate {
return Ok(());
}
let has_news = self.entries.iter().any(|e| e.news.is_some());
if has_news {
Validator::validate_news_url_count(self.entries.len())?;
} else {
Validator::validate_url_count(self.entries.len())?;
}
for entry in &self.entries {
Validator::validate_url(&entry.url.loc)?;
if let Some(ref lastmod) = entry.url.lastmod {
Validator::validate_date(lastmod)?;
}
if let Some(priority) = entry.url.priority {
Validator::validate_priority(priority)?;
}
for video in &entry.videos {
Validator::validate_video_title(&video.title)?;
Validator::validate_video_description(&video.description)?;
if let Some(duration) = video.duration {
Validator::validate_video_duration(duration)?;
}
if let Some(rating) = video.rating {
Validator::validate_video_rating(rating)?;
}
}
if let Some(ref news) = entry.news {
Validator::validate_date(&news.publication_date)?;
Validator::validate_language_code(&news.publication.language)?;
if let Some(ref tickers) = news.stock_tickers {
Validator::validate_stock_tickers(tickers)?;
}
}
}
Ok(())
}
pub fn build(&self) -> Result<String> {
self.validate_entries()?;
let mut writer = XmlWriter::new();
writer.write_combined_sitemap(&self.entries)?;
let xml = writer.into_string()?;
if self.validate {
Validator::validate_size(xml.len())?;
}
Ok(xml)
}
pub fn build_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
Ok(xml.into_bytes())
}
pub fn build_compressed_bytes(&self) -> Result<Vec<u8>> {
let xml = self.build()?;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(xml.as_bytes())?;
let compressed = encoder.finish()?;
Ok(compressed)
}
pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let mut file = File::create(path)?;
file.write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let xml = self.build()?;
let file = File::create(path)?;
let mut encoder = GzEncoder::new(file, Compression::default());
encoder.write_all(xml.as_bytes())?;
encoder.finish()?;
Ok(())
}
}
impl Default for CombinedSitemapBuilder {
fn default() -> Self {
Self::new()
}
}