001 package ezvcard.io.scribe; 002 003 import java.net.URI; 004 import java.net.URISyntaxException; 005 import java.util.ArrayList; 006 import java.util.Collections; 007 import java.util.List; 008 import java.util.regex.Matcher; 009 import java.util.regex.Pattern; 010 011 import ezvcard.VCardDataType; 012 import ezvcard.VCardVersion; 013 import ezvcard.io.CannotParseException; 014 import ezvcard.io.html.HCardElement; 015 import ezvcard.io.json.JCardValue; 016 import ezvcard.io.xml.XCardElement; 017 import ezvcard.parameter.VCardParameters; 018 import ezvcard.property.Impp; 019 020 /* 021 Copyright (c) 2013, Michael Angstadt 022 All rights reserved. 023 024 Redistribution and use in source and binary forms, with or without 025 modification, are permitted provided that the following conditions are met: 026 027 1. Redistributions of source code must retain the above copyright notice, this 028 list of conditions and the following disclaimer. 029 2. Redistributions in binary form must reproduce the above copyright notice, 030 this list of conditions and the following disclaimer in the documentation 031 and/or other materials provided with the distribution. 032 033 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 034 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 035 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 036 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 037 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 039 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 040 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 041 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 042 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 043 */ 044 045 /** 046 * Marshals {@link Impp} properties. 047 * @author Michael Angstadt 048 */ 049 public class ImppScribe extends VCardPropertyScribe<Impp> { 050 public static final String AIM = "aim"; 051 public static final String ICQ = "icq"; 052 public static final String IRC = "irc"; 053 public static final String MSN = "msnim"; 054 public static final String SIP = "sip"; 055 public static final String SKYPE = "skype"; 056 public static final String XMPP = "xmpp"; 057 public static final String YAHOO = "ymsgr"; 058 059 public ImppScribe() { 060 super(Impp.class, "IMPP"); 061 } 062 063 @Override 064 protected VCardDataType _defaultDataType(VCardVersion version) { 065 return VCardDataType.URI; 066 } 067 068 @Override 069 protected String _writeText(Impp property, VCardVersion version) { 070 return write(property); 071 } 072 073 @Override 074 protected Impp _parseText(String value, VCardDataType dataType, VCardVersion version, VCardParameters parameters, List<String> warnings) { 075 value = unescape(value); 076 return parse(value); 077 } 078 079 @Override 080 protected void _writeXml(Impp property, XCardElement parent) { 081 parent.append(VCardDataType.URI, write(property)); 082 } 083 084 @Override 085 protected Impp _parseXml(XCardElement element, VCardParameters parameters, List<String> warnings) { 086 String value = element.first(VCardDataType.URI); 087 if (value != null) { 088 return parse(value); 089 } 090 091 throw missingXmlElements(VCardDataType.URI); 092 } 093 094 @Override 095 protected Impp _parseHtml(HCardElement element, List<String> warnings) { 096 String href = element.attr("href"); 097 if (href.length() == 0) { 098 href = element.value(); 099 } 100 101 try { 102 URI uri = parseHtmlLink(href); 103 if (uri == null) { 104 throw new IllegalArgumentException(); 105 } 106 return new Impp(uri); 107 } catch (IllegalArgumentException e) { 108 throw new CannotParseException(14, href); 109 } 110 } 111 112 @Override 113 protected JCardValue _writeJson(Impp property) { 114 return JCardValue.single(write(property)); 115 } 116 117 @Override 118 protected Impp _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, List<String> warnings) { 119 return parse(value.asSingle()); 120 } 121 122 private String write(Impp property) { 123 URI uri = property.getUri(); 124 return (uri == null) ? "" : uri.toString(); 125 } 126 127 private Impp parse(String value) { 128 if (value == null || value.length() == 0) { 129 return new Impp((URI) null); 130 } 131 132 try { 133 return new Impp(value); 134 } catch (IllegalArgumentException e) { 135 throw new CannotParseException(15, value, e.getMessage()); 136 } 137 } 138 139 /** 140 * List of recognized IM protocols that can be parsed from an HTML link 141 * (hCard). 142 */ 143 private static final List<HtmlLinkFormat> htmlLinkFormats; 144 static { 145 List<HtmlLinkFormat> list = new ArrayList<HtmlLinkFormat>(); 146 147 //http://en.wikipedia.org/wiki/AOL_Instant_Messenger#URI_scheme 148 list.add(new HtmlLinkFormat(AIM, "(goim|addbuddy)\\?.*?\\bscreenname=(.*?)(&|$)", 2, "goim?screenname=%s")); 149 150 //http://en.wikipedia.org/wiki/Yahoo!_Messenger#URI_scheme 151 list.add(new HtmlLinkFormat(YAHOO, "(sendim|addfriend|sendfile|call)\\?(.*)", 2, "sendim?%s")); 152 153 //http://developer.skype.com/skype-uri/skype-uri-ref-api 154 list.add(new HtmlLinkFormat(SKYPE, "(.*?)(\\?|$)", 1, "%s")); 155 156 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 157 list.add(new HtmlLinkFormat(MSN, "(chat|add|voice|video)\\?contact=(.*?)(&|$)", 2, "chat?contact=%s")); 158 159 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 160 list.add(new HtmlLinkFormat(XMPP, "(.*?)(\\?|$)", 1, "%s?message")); 161 162 //http://forums.miranda-im.org/showthread.php?26589-Add-support-to-quot-icq-message-uin-12345-quot-web-links 163 list.add(new HtmlLinkFormat(ICQ, "message\\?uin=(\\d+)", 1, "message?uin=%s")); 164 165 //SIP: http://en.wikipedia.org/wiki/Session_Initiation_Protocol 166 //leave as-is 167 list.add(new HtmlLinkFormat(SIP)); 168 169 //IRC: http://stackoverflow.com/questions/11970897/how-do-i-open-a-query-window-using-the-irc-uri-scheme 170 //IRC handles are not globally unique, so leave as-is 171 list.add(new HtmlLinkFormat(IRC)); 172 173 htmlLinkFormats = Collections.unmodifiableList(list); 174 } 175 176 /** 177 * Parses an IM URI from an HTML link. 178 * @param linkUri the HTML link (e.g. "aim:goim?screenname=theuser") 179 * @return the IM URI or null if not recognized 180 */ 181 public URI parseHtmlLink(String linkUri) { 182 for (HtmlLinkFormat format : htmlLinkFormats) { 183 String handle = format.parseHandle(linkUri); 184 if (handle == null) { 185 continue; 186 } 187 188 try { 189 return new URI(format.getProtocol(), handle, null); 190 } catch (URISyntaxException e) { 191 throw new IllegalArgumentException(e); 192 } 193 } 194 return null; 195 } 196 197 /** 198 * Builds a URI suitable for use as a link on a webpage. 199 * @param property the property 200 * @return the link URI (e.g. "aim:goim?screenname=theuser") or null if the 201 * property has no URI 202 */ 203 public String writeHtmlLink(Impp property) { 204 URI uri = property.getUri(); 205 if (uri == null) { 206 return null; 207 } 208 209 String protocol = uri.getScheme(); 210 String handle = uri.getSchemeSpecificPart(); 211 212 for (HtmlLinkFormat format : htmlLinkFormats) { 213 if (protocol.equals(format.getProtocol())) { 214 return format.buildLink(handle); 215 } 216 } 217 return uri.toString(); 218 } 219 220 private static class HtmlLinkFormat { 221 private final Pattern parseRegex; 222 private final String protocol; 223 private final int handleGroup; 224 private final String linkFormat; 225 226 /** 227 * @param protocol the IM protocol (e.g. "aim") 228 */ 229 public HtmlLinkFormat(String protocol) { 230 this(protocol, "(.*)", 1, "%s"); 231 } 232 233 /** 234 * @param protocol the IM protocol (e.g. "aim") 235 * @param linkRegex the regular expression used to parse a link 236 * @param handleGroup the group number from the regular expression that 237 * contains the IM handle 238 * @param linkFormat the format string for building a link 239 */ 240 public HtmlLinkFormat(String protocol, String linkRegex, int handleGroup, String linkFormat) { 241 this.parseRegex = Pattern.compile('^' + protocol + ':' + linkRegex, Pattern.CASE_INSENSITIVE); 242 this.protocol = protocol; 243 this.handleGroup = handleGroup; 244 this.linkFormat = protocol + ':' + linkFormat; 245 } 246 247 /** 248 * Parses the IM handle out of a link. 249 * @param linkUri the link 250 * @return the IM handle or null if it can't be found 251 */ 252 public String parseHandle(String linkUri) { 253 Matcher m = parseRegex.matcher(linkUri); 254 return m.find() ? m.group(handleGroup) : null; 255 } 256 257 /** 258 * Builds a link for inclusion in a webpage. 259 * @param handle the IM handle 260 * @return the link 261 */ 262 public String buildLink(String handle) { 263 return String.format(linkFormat, handle); 264 } 265 266 /** 267 * Gets the protocol. 268 * @return the protocol (e.g. "aim") 269 */ 270 public String getProtocol() { 271 return protocol; 272 } 273 } 274 }