1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use crate::url::Url;
use crate::url_set_error::UrlSetError;
use crate::{
ENCODING, IMAGE_NAMESPACE, NAMESPACE, NEWS_NAMESPACE, VIDEO_NAMESPACE, XHTML_NAMESPACE,
};
use std::io::Write;
use xml_builder::{XML, XMLBuilder, XMLElement, XMLError, XMLVersion};
/// Encapsulates the file and references the current protocol standard.
pub struct UrlSet {
/// The XML version.
pub xml_version: XMLVersion,
/// The XML encoding.
pub xml_encoding: String,
/// The namespace for the \<urlset\>.
pub xmlns: String,
/// A namespace extension for allowing \<xhtml:link\> (alternate language links) in the `UrlSet`.
pub xmlns_xhtml: Option<String>,
/// A namespace extension for allowing \<image\> in the `UrlSet`.
pub xmlns_image: Option<String>,
/// A namespace extension for allowing \<video\> in the `UrlSet`.
pub xmlns_video: Option<String>,
/// A namespace extension for allowing \<news\> in the `UrlSet`.
pub xmlns_news: Option<String>,
/// All the URLs that will become indexed.
pub urls: Vec<Url>,
}
impl UrlSet {
/// # Errors
///
/// Will return `UrlSetError::TooManyUrls` if the length of `urls` is above `50,000`.
pub fn new(urls: Vec<Url>) -> Result<Self, UrlSetError> {
// UrlSets cannot contain more than 50,000 URLs
if urls.len() > 50_000 {
return Err(UrlSetError::TooManyUrls(urls.len()));
}
// check if we even need namespaces for alternative language links, images, videos, or news
let mut xmlns_xhtml: Option<String> = None;
let mut xmlns_image: Option<String> = None;
let mut xmlns_video: Option<String> = None;
let mut xmlns_news: Option<String> = None;
let mut news_exists: bool = false;
for url in &urls {
// if any <url>s exist that contain alternate language links, set the xhtml namespace
if !url.links.is_empty() {
xmlns_xhtml = Some(XHTML_NAMESPACE.to_string());
}
// if any <url>s exist that contain an image, set the image namespace
if let Some(images) = &url.images
&& !images.is_empty()
{
xmlns_image = Some(IMAGE_NAMESPACE.to_string());
}
// if any <url>s exist that contain a video, set the video namespace
if let Some(videos) = &url.videos
&& !videos.is_empty()
{
xmlns_video = Some(VIDEO_NAMESPACE.to_string());
}
// check if any URLs have news
if url.news.is_some() {
news_exists = true;
if xmlns_news.is_none() {
xmlns_news = Some(NEWS_NAMESPACE.to_string());
}
}
}
// cannot have more than 1,000 news URLs in a single UrlSet
if news_exists && urls.len() > 1000 {
return Err(UrlSetError::TooMuchNews(urls.len()));
}
Ok(Self {
xml_version: XMLVersion::XML1_0,
xml_encoding: ENCODING.to_string(),
xmlns: NAMESPACE.to_string(),
xmlns_xhtml,
xmlns_image,
xmlns_video,
xmlns_news,
urls,
})
}
/// # Errors
///
/// Will return `XMLError` if there is a problem creating XML elements.
pub fn to_xml(self) -> Result<XML, XMLError> {
// create XML document
let mut xml = XMLBuilder::new()
.version(self.xml_version)
.encoding(self.xml_encoding)
.build();
// create <urlset>
let mut urlset = XMLElement::new("urlset");
urlset.add_attribute("xmlns", self.xmlns.as_str());
// set xhtml namespace, if it exists
if let Some(xmlns_xhtml) = self.xmlns_xhtml {
urlset.add_attribute("xmlns:xhtml", xmlns_xhtml.as_str());
}
// set image namespace, if it exists
if let Some(xmlns_image) = self.xmlns_image {
urlset.add_attribute("xmlns:image", xmlns_image.as_str());
}
// set video namespace, if it exists
if let Some(xmlns_video) = self.xmlns_video {
urlset.add_attribute("xmlns:video", xmlns_video.as_str());
}
// set news namespace, if it exists
if let Some(xmlns_news) = self.xmlns_news {
urlset.add_attribute("xmlns:news", xmlns_news.as_str());
}
// add each <url>
for url in self.urls {
urlset.add_child(url.to_xml()?)?;
}
// set root element and we're done!
xml.set_root_element(urlset);
Ok(xml)
}
/// # Errors
///
/// Will return `XMLError` if there is an IO Error dealing with the
/// underlying writer or if there is an error generating XML.
pub fn write<W: Write>(self, writer: W) -> Result<(), XMLError> {
let xml: XML = self.to_xml()?;
xml.generate(writer)
}
}