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 }