001package ezvcard.io.html; 002 003import java.io.IOException; 004import java.io.OutputStream; 005import java.io.OutputStreamWriter; 006import java.io.StringWriter; 007import java.io.UncheckedIOException; 008import java.io.Writer; 009import java.nio.file.Files; 010import java.nio.file.Path; 011import java.time.Instant; 012import java.time.ZoneId; 013import java.time.ZoneOffset; 014import java.time.format.DateTimeFormatter; 015import java.time.temporal.Temporal; 016import java.util.ArrayList; 017import java.util.HashMap; 018import java.util.List; 019import java.util.Map; 020import java.util.regex.Pattern; 021 022import ezvcard.Ezvcard; 023import ezvcard.VCard; 024import ezvcard.io.scribe.ScribeIndex; 025import ezvcard.parameter.ImageType; 026import ezvcard.property.Photo; 027import ezvcard.util.DataUri; 028import ezvcard.util.VCardDateFormat; 029import freemarker.template.Configuration; 030import freemarker.template.Template; 031import freemarker.template.TemplateException; 032 033/* 034 Copyright (c) 2012-2023, Michael Angstadt 035 All rights reserved. 036 037 Redistribution and use in source and binary forms, with or without 038 modification, are permitted provided that the following conditions are met: 039 040 1. Redistributions of source code must retain the above copyright notice, this 041 list of conditions and the following disclaimer. 042 2. Redistributions in binary form must reproduce the above copyright notice, 043 this list of conditions and the following disclaimer in the documentation 044 and/or other materials provided with the distribution. 045 046 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 047 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 048 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 049 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 050 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 051 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 052 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 053 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 054 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 055 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 056 057 The views and conclusions contained in the software and documentation are those 058 of the authors and should not be interpreted as representing official policies, 059 either expressed or implied, of the FreeBSD Project. 060 */ 061 062/** 063 * <p> 064 * Writes {@link VCard} objects to a templated HTML page (hCard format). 065 * </p> 066 * <p> 067 * <b>Example:</b> 068 * </p> 069 * 070 * <pre class="brush:java"> 071 * VCard vcard1 = ... 072 * VCard vcard2 = ... 073 * 074 * HCardPage page = new HCardPage(); 075 * page.add(vcard1); 076 * page.add(vcard2); 077 * 078 * File file = new File("hcard.html"); 079 * page.write(file); 080 * </pre> 081 * @author Michael Angstadt 082 * @see <a 083 * href="http://microformats.org/wiki/hcard">http://microformats.org/wiki/hcard</a> 084 */ 085public class HCardPage { 086 private final Template template; 087 private final List<VCard> vcards = new ArrayList<>(); 088 089 /** 090 * Creates a new hCard page that uses the default template. 091 */ 092 public HCardPage() { 093 Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); 094 cfg.setClassForTemplateLoading(HCardPage.class, ""); 095 cfg.setWhitespaceStripping(true); 096 try { 097 template = cfg.getTemplate("hcard-template.html"); 098 } catch (IOException e) { 099 //should never be thrown because it's always on the classpath 100 throw new UncheckedIOException(e); 101 } 102 } 103 104 /** 105 * Creates a new hCard page using a custom template. See the <a href= 106 * "https://github.com/mangstadt/ez-vcard/blob/master/src/main/resources/ezvcard/io/html/hcard-template.html" 107 * >default template</a> for an example. 108 * @param template the template to use 109 */ 110 public HCardPage(Template template) { 111 this.template = template; 112 } 113 114 /** 115 * Adds a vCard to the HTML page. 116 * @param vcard the vCard to add 117 */ 118 public void add(VCard vcard) { 119 vcards.add(vcard); 120 } 121 122 /** 123 * Writes the HTML document to a string. 124 * @return the HTML document 125 */ 126 public String write() { 127 StringWriter sw = new StringWriter(); 128 try { 129 write(sw); 130 } catch (IOException e) { 131 //should never thrown because we're writing to a string 132 throw new UncheckedIOException(e); 133 } 134 return sw.toString(); 135 } 136 137 /** 138 * Writes the HTML document to an output stream. 139 * @param out the output stream 140 * @throws IOException if there's a problem writing to the output stream 141 */ 142 public void write(OutputStream out) throws IOException { 143 write(new OutputStreamWriter(out)); 144 } 145 146 /** 147 * Writes the HTML document to a file. 148 * @param file the file 149 * @throws IOException if there's a problem writing to the file 150 */ 151 public void write(Path file) throws IOException { 152 try (Writer writer = Files.newBufferedWriter(file)) { 153 write(writer); 154 } 155 } 156 157 /** 158 * Writes the HTML document to a writer. 159 * @param writer the writer 160 * @throws IOException if there's a problem writing to the writer 161 */ 162 public void write(Writer writer) throws IOException { 163 Map<String, Object> map = new HashMap<>(); 164 map.put("vcards", vcards); 165 map.put("utils", new TemplateUtils()); 166 map.put("translucentBg", readImage("translucent-bg.png", ImageType.PNG)); 167 map.put("noProfile", readImage("no-profile.png", ImageType.PNG)); 168 map.put("ezVCardVersion", Ezvcard.VERSION); 169 map.put("ezVCardUrl", Ezvcard.URL); 170 map.put("scribeIndex", new ScribeIndex()); 171 try { 172 template.process(map, writer); 173 } catch (TemplateException e) { 174 throw new RuntimeException(e); 175 } 176 writer.flush(); 177 } 178 179 /** 180 * Reads an image from the classpath. 181 * @param name the file name, relative to this class 182 * @param mediaType the media type of the image 183 * @return the image 184 * @throws IOException if there's a problem reading the image 185 */ 186 private Photo readImage(String name, ImageType mediaType) throws IOException { 187 return new Photo(getClass().getResourceAsStream(name), mediaType); 188 } 189 190 /** 191 * Utility functions for the freemarker template. 192 */ 193 public static class TemplateUtils { 194 private final Pattern newlineRegex = Pattern.compile("\\r\\n|\\r|\\n"); 195 196 public String base64(String contentType, byte[] data) { 197 return new DataUri(contentType, data).toString(); 198 } 199 200 public String lineBreaks(String value) { 201 return newlineRegex.matcher(value).replaceAll("<br />"); 202 } 203 204 public String format(Temporal temporal, String format) { 205 return format(temporal, format, ZoneOffset.UTC); 206 } 207 208 public String formatLocal(Temporal temporal, String format) { 209 return format(temporal, format, ZoneId.systemDefault()); 210 } 211 212 private String format(Temporal temporal, String format, ZoneId offset) { 213 if (temporal instanceof Instant) { 214 temporal = ((Instant) temporal).atZone(offset); 215 } 216 return DateTimeFormatter.ofPattern(format).format(temporal); 217 } 218 219 public String format(ZoneOffset offset) { 220 return VCardDateFormat.BASIC.format(offset); 221 } 222 } 223}