Merge pull request #45 from ThorKarlsson/GoogleImageExtension

Google image extension
This commit is contained in:
Matthias Kurz 2019-01-27 14:34:07 +01:00 committed by GitHub
commit 6a605cf6ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 442 additions and 0 deletions

View File

@ -0,0 +1,103 @@
package com.redfin.sitemapgenerator;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Builds a sitemap for Google Image search. To configure options use {@link #builder(URL, File)}
* @see <a href="https://support.google.com/webmasters/answer/183668">Manage your sitemaps</a>
* */
public class GoogleImageSitemapGenerator extends SitemapGenerator<GoogleImageSitemapUrl, GoogleImageSitemapGenerator> {
GoogleImageSitemapGenerator(AbstractSitemapGeneratorOptions<?> options) {
super(options, new GoogleImageSitemapGenerator.Renderer());
}
/** Configures the generator with a base URL and directory to write the sitemap files.
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
* @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on.
* @throws MalformedURLException
*/
public GoogleImageSitemapGenerator(String baseUrl, File baseDir)
throws MalformedURLException {
this(new SitemapGeneratorOptions(baseUrl, baseDir));
}
/**Configures the generator with a base URL and directory to write the sitemap files.
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
* @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on.
*/
public GoogleImageSitemapGenerator(URL baseUrl, File baseDir) {
this(new SitemapGeneratorOptions(baseUrl, baseDir));
}
/**Configures the generator with a base URL and a null directory. The object constructed
* is not intended to be used to write to files. Rather, it is intended to be used to obtain
* XML-formatted strings that represent sitemaps.
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
*/
public GoogleImageSitemapGenerator(String baseUrl) throws MalformedURLException {
this(new SitemapGeneratorOptions(new URL(baseUrl)));
}
/**Configures the generator with a base URL and a null directory. The object constructed
* is not intended to be used to write to files. Rather, it is intended to be used to obtain
* XML-formatted strings that represent sitemaps.
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
*/
public GoogleImageSitemapGenerator(URL baseUrl) {
this(new SitemapGeneratorOptions(baseUrl));
}
/** Configures a builder so you can specify sitemap generator options
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
* @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on.
* @return a builder; call .build() on it to make a sitemap generator
*/
public static SitemapGeneratorBuilder<GoogleImageSitemapGenerator> builder(URL baseUrl, File baseDir) {
return new SitemapGeneratorBuilder<GoogleImageSitemapGenerator>(baseUrl, baseDir, GoogleImageSitemapGenerator.class);
}
/** Configures a builder so you can specify sitemap generator options
*
* @param baseUrl All URLs in the generated sitemap(s) should appear under this base URL
* @param baseDir Sitemap files will be generated in this directory as either "sitemap.xml" or "sitemap1.xml" "sitemap2.xml" and so on.
* @return a builder; call .build() on it to make a sitemap generator
* @throws MalformedURLException
*/
public static SitemapGeneratorBuilder<GoogleImageSitemapGenerator> builder(String baseUrl, File baseDir) throws MalformedURLException {
return new SitemapGeneratorBuilder<GoogleImageSitemapGenerator>(baseUrl, baseDir, GoogleImageSitemapGenerator.class);
}
private static class Renderer extends AbstractSitemapUrlRenderer<GoogleImageSitemapUrl> implements ISitemapUrlRenderer<GoogleImageSitemapUrl> {
public Class<GoogleImageSitemapUrl> getUrlClass() {
return GoogleImageSitemapUrl.class;
}
public String getXmlNamespaces() {
return "xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\"";
}
public void render(GoogleImageSitemapUrl url, StringBuilder sb, W3CDateFormat dateFormat) {
StringBuilder tagSb = new StringBuilder();
for(Image image : url.getImages()) {
tagSb.append(" <image:image>\n");
renderTag(tagSb, "image", "loc", image.getUrl());
renderTag(tagSb, "image", "caption", image.getCaption());
renderTag(tagSb, "image", "title", image.getTitle());
renderTag(tagSb, "image", "geo_location", image.getGeoLocation());
renderTag(tagSb, "image", "license", image.getLicense());
tagSb.append(" </image:image>\n");
}
super.render(url, sb, dateFormat, tagSb.toString());
}
}
}

View File

@ -0,0 +1,74 @@
package com.redfin.sitemapgenerator;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** One configurable Google Image Search URL. To configure, use {@link Options}
*
* @see Options
* @see <a href="http://www.google.com/support/webmasters/bin/answer.py?answer=183668">Creating Image Sitemaps</a>
*/
public class GoogleImageSitemapUrl extends WebSitemapUrl {
private final List<Image> images;
public GoogleImageSitemapUrl(String url) throws MalformedURLException {
this(new Options(url));
}
public GoogleImageSitemapUrl(URL url) {
this(new Options(url));
}
public GoogleImageSitemapUrl(Options options) {
super(options);
this.images = options.images;
}
public void addImage(Image image) {
this.images.add(image);
if(this.images.size() > 1000) {
throw new RuntimeException("A URL cannot have more than 1000 image tags");
}
}
/** Options to configure Google Extension URLs */
public static class Options extends AbstractSitemapUrlOptions<GoogleImageSitemapUrl, GoogleImageSitemapUrl.Options> {
private List<Image> images;
public Options(URL url) {
super(url, GoogleImageSitemapUrl.class);
images = new ArrayList<Image>();
}
public Options(String url) throws MalformedURLException {
super(url, GoogleImageSitemapUrl.class);
images = new ArrayList<Image>();
}
public Options images(List<Image> images) {
if(images != null && images.size() > 1000) {
throw new RuntimeException("A URL cannot have more than 1000 image tags");
}
this.images = images;
return this;
}
public Options images(Image...images) {
if(images.length > 1000) {
throw new RuntimeException("A URL cannot have more than 1000 image tags");
}
return images(Arrays.asList(images));
}
}
/**Retrieves list of images*/
public List<Image> getImages() {
return this.images;
}
}

View File

@ -0,0 +1,100 @@
package com.redfin.sitemapgenerator;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Represent a single image and image properties for use in extended sitemaps
* @see <a href="https://support.google.com/webmasters/answer/178636">Image sitemaps</a>
*/
public class Image {
private final URL url;
private final String title;
private final String caption;
private final String geoLocation;
private final URL license;
public Image(String url) throws MalformedURLException {
this(new URL(url));
}
public Image(URL url) {
this.url = url;
this.title = null;
this.caption = null;
this.geoLocation = null;
this.license = null;
}
public Image(URL url, String title, String caption, String geoLocation, String license) throws MalformedURLException {
this(url, title, caption, geoLocation, new URL(license));
}
public Image(URL url, String title, String caption, String geoLocation, URL license) {
this.url = url;
this.title = title;
this.caption = caption;
this.geoLocation = geoLocation;
this.license = license;
}
/** Retrieves URL of Image*/
public URL getUrl() { return url; }
/** Retrieves title of image*/
public String getTitle() { return title; }
/** Retrieves captionof image*/
public String getCaption() { return caption; }
/** Retrieves geolocation string of image*/
public String getGeoLocation() { return geoLocation; }
/** Retrieves license string of image*/
public URL getLicense() { return license; }
public static class ImageBuilder {
private URL url;
private String title;
private String caption;
private String geoLocation;
private URL license;
public ImageBuilder(String url) throws MalformedURLException {
this(new URL(url));
}
public ImageBuilder(URL url) {
this.url = url;
}
public ImageBuilder title(String title) {
this.title = title;
return this;
}
public ImageBuilder caption(String caption) {
this.caption = caption;
return this;
}
public ImageBuilder geoLocation(String geoLocation) {
this.geoLocation = geoLocation;
return this;
}
public ImageBuilder license(String license) throws MalformedURLException {
return license(new URL(license));
}
public ImageBuilder license(URL license) {
this.license = license;
return this;
}
public Image build() {
return new Image(url, title, caption, geoLocation, license);
}
}
}

View File

@ -0,0 +1,165 @@
package com.redfin.sitemapgenerator;
import junit.framework.TestCase;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GoogleImageSitemapUrlTest extends TestCase {
private static final URL LANDING_URL = newURL("http://www.example.com/index.html");
private static final URL CONTENT_URL = newURL("http://www.example.com/index.flv");
File dir;
GoogleImageSitemapGenerator wsg;
private static URL newURL(String url) {
try {
return new URL(url);
} catch (MalformedURLException e) {}
return null;
}
public void setUp() throws Exception {
dir = File.createTempFile(GoogleVideoSitemapUrlTest.class.getSimpleName(), "");
dir.delete();
dir.mkdir();
dir.deleteOnExit();
}
public void tearDown() {
wsg = null;
for (File file : dir.listFiles()) {
file.deleteOnExit();
file.delete();
}
dir.delete();
dir = null;
}
public void testSimpleUrl() throws Exception {
wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir);
GoogleImageSitemapUrl url = new GoogleImageSitemapUrl(LANDING_URL);
url.addImage(new Image("http://cdn.example.com/image1.jpg"));
url.addImage(new Image("http://cdn.example.com/image2.jpg"));
wsg.addUrl(url);
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" >\n" +
" <url>\n" +
" <loc>http://www.example.com/index.html</loc>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image1.jpg</image:loc>\n" +
" </image:image>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image2.jpg</image:loc>\n" +
" </image:image>\n" +
" </url>\n" +
"</urlset>";
String sitemap = writeSingleSiteMap(wsg);
assertEquals(expected, sitemap);
}
public void testBaseOptions() throws Exception {
wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir);
GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL)
.images(new Image("http://cdn.example.com/image1.jpg"), new Image("http://cdn.example.com/image2.jpg"))
.priority(0.5)
.changeFreq(ChangeFreq.WEEKLY)
.build();
wsg.addUrl(url);
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" >\n" +
" <url>\n" +
" <loc>http://www.example.com/index.html</loc>\n" +
" <changefreq>weekly</changefreq>\n" +
" <priority>0.5</priority>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image1.jpg</image:loc>\n" +
" </image:image>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image2.jpg</image:loc>\n" +
" </image:image>\n" +
" </url>\n" +
"</urlset>";
String sitemap = writeSingleSiteMap(wsg);
assertEquals(expected, sitemap);
}
public void testImageOptions() throws Exception {
wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir);
GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL)
.images(new Image.ImageBuilder("http://cdn.example.com/image1.jpg")
.title("image1.jpg")
.caption("An image of the number 1")
.geoLocation("Pyongyang, North Korea")
.license("http://cdn.example.com/licenses/imagelicense.txt")
.build(),
new Image.ImageBuilder("http://cdn.example.com/image2.jpg")
.title("image2.jpg")
.caption("An image of the number 2")
.geoLocation("Pyongyang, North Korea")
.license("http://cdn.example.com/licenses/imagelicense.txt")
.build())
.priority(0.5)
.changeFreq(ChangeFreq.WEEKLY)
.build();
wsg.addUrl(url);
String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:image=\"http://www.google.com/schemas/sitemap-image/1.1\" >\n" +
" <url>\n" +
" <loc>http://www.example.com/index.html</loc>\n" +
" <changefreq>weekly</changefreq>\n" +
" <priority>0.5</priority>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image1.jpg</image:loc>\n" +
" <image:caption>An image of the number 1</image:caption>\n" +
" <image:title>image1.jpg</image:title>\n" +
" <image:geo_location>Pyongyang, North Korea</image:geo_location>\n" +
" <image:license>http://cdn.example.com/licenses/imagelicense.txt</image:license>\n" +
" </image:image>\n" +
" <image:image>\n" +
" <image:loc>http://cdn.example.com/image2.jpg</image:loc>\n" +
" <image:caption>An image of the number 2</image:caption>\n" +
" <image:title>image2.jpg</image:title>\n" +
" <image:geo_location>Pyongyang, North Korea</image:geo_location>\n" +
" <image:license>http://cdn.example.com/licenses/imagelicense.txt</image:license>\n" +
" </image:image>\n" +
" </url>\n" +
"</urlset>";
String sitemap = writeSingleSiteMap(wsg);
assertEquals(expected, sitemap);
}
public void testTooManyImages() throws Exception {
wsg = new GoogleImageSitemapGenerator("http://www.example.com", dir);
List<Image> images = new ArrayList<Image>();
for(int i = 0; i <= 1000; i++) {
images.add(new Image("http://cdn.example.com/image" + i + ".jpg"));
}
try {
GoogleImageSitemapUrl url = new GoogleImageSitemapUrl.Options(LANDING_URL)
.images(images)
.priority(0.5)
.changeFreq(ChangeFreq.WEEKLY)
.build();
fail("Too many images allowed");
} catch (RuntimeException r) {}
}
private String writeSingleSiteMap(GoogleImageSitemapGenerator wsg) {
List<File> files = wsg.write();
assertEquals("Too many files: " + files.toString(), 1, files.size());
assertEquals("Sitemap misnamed", "sitemap.xml", files.get(0).getName());
return TestUtil.slurpFileAndDelete(files.get(0));
}
}