001    package ezvcard;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.FileReader;
006    import java.io.FileWriter;
007    import java.io.IOException;
008    import java.io.InputStream;
009    import java.io.InputStreamReader;
010    import java.io.OutputStream;
011    import java.io.OutputStreamWriter;
012    import java.io.Reader;
013    import java.io.StringWriter;
014    import java.io.Writer;
015    import java.net.URL;
016    import java.util.ArrayList;
017    import java.util.Arrays;
018    import java.util.Collection;
019    import java.util.List;
020    import java.util.Properties;
021    
022    import javax.xml.transform.TransformerException;
023    
024    import org.w3c.dom.Document;
025    import org.xml.sax.SAXException;
026    
027    import ezvcard.io.HCardPage;
028    import ezvcard.io.HCardReader;
029    import ezvcard.io.IParser;
030    import ezvcard.io.VCardReader;
031    import ezvcard.io.VCardWriter;
032    import ezvcard.io.XCardDocument;
033    import ezvcard.io.XCardReader;
034    import ezvcard.types.VCardType;
035    import ezvcard.util.IOUtils;
036    import freemarker.template.TemplateException;
037    
038    /*
039     Copyright (c) 2012, Michael Angstadt
040     All rights reserved.
041    
042     Redistribution and use in source and binary forms, with or without
043     modification, are permitted provided that the following conditions are met: 
044    
045     1. Redistributions of source code must retain the above copyright notice, this
046     list of conditions and the following disclaimer. 
047     2. Redistributions in binary form must reproduce the above copyright notice,
048     this list of conditions and the following disclaimer in the documentation
049     and/or other materials provided with the distribution. 
050    
051     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
052     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
053     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
054     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
055     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
056     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
057     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
058     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
059     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
060     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
061    
062     The views and conclusions contained in the software and documentation are those
063     of the authors and should not be interpreted as representing official policies, 
064     either expressed or implied, of the FreeBSD Project.
065     */
066    
067    /**
068     * <p>
069     * Contains helper methods for parsing/writing vCards. The methods wrap the
070     * following classes:
071     * </p>
072     * 
073     * 
074     * <table border="1">
075     * <tr>
076     * <th></th>
077     * <th>Reading</th>
078     * <th>Writing</th>
079     * </tr>
080     * <tr>
081     * <th>Plain text</th>
082     * <td>{@link VCardReader}</td>
083     * <td>{@link VCardWriter}</td>
084     * </tr>
085     * <tr>
086     * <th>XML</th>
087     * <td>{@link XCardReader}</td>
088     * <td>{@link XCardDocument}</td>
089     * </tr>
090     * <tr>
091     * <th>HTML</th>
092     * <td>{@link HCardReader}</td>
093     * <td>{@link HCardPage}</td>
094     * </tr>
095     * </table>
096     * @author Michael Angstadt
097     */
098    public class Ezvcard {
099            /**
100             * The version of the library.
101             */
102            public static final String VERSION;
103    
104            /**
105             * The project webpage.
106             */
107            public static final String URL;
108    
109            static {
110                    InputStream in = null;
111                    try {
112                            in = Ezvcard.class.getResourceAsStream("/ez-vcard-info.properties");
113                            Properties props = new Properties();
114                            props.load(in);
115                            VERSION = props.getProperty("version");
116                            URL = props.getProperty("url");
117                    } catch (IOException e) {
118                            throw new RuntimeException(e);
119                    } finally {
120                            IOUtils.closeQuietly(in);
121                    }
122            }
123    
124            /**
125             * <p>
126             * Parses plain text vCards.
127             * </p>
128             * <p>
129             * Use {@link VCardReader} for more control over the parsing.
130             * </p>
131             * @param str the vCard string
132             * @return chainer object for completing the parse operation
133             * @see VCardReader
134             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
135             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
136             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
137             */
138            public static ParserChainTextString parse(String str) {
139                    return new ParserChainTextString(str);
140            }
141    
142            /**
143             * <p>
144             * Parses plain text vCards.
145             * </p>
146             * <p>
147             * Use {@link VCardReader} for more control over the parsing.
148             * </p>
149             * @param file the vCard file
150             * @return chainer object for completing the parse operation
151             * @throws FileNotFoundException if the file does not exist or cannot be
152             * accessed
153             * @see VCardReader
154             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
155             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
156             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
157             */
158            public static ParserChainTextReader parse(File file) throws FileNotFoundException {
159                    return parse(new FileReader(file));
160            }
161    
162            /**
163             * <p>
164             * Parses plain text vCards.
165             * </p>
166             * <p>
167             * Use {@link VCardReader} for more control over the parsing.
168             * </p>
169             * @param in the input stream
170             * @return chainer object for completing the parse operation
171             * @see VCardReader
172             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
173             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
174             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
175             */
176            public static ParserChainTextReader parse(InputStream in) {
177                    return parse(new InputStreamReader(in));
178            }
179    
180            /**
181             * <p>
182             * Parses plain text vCards.
183             * </p>
184             * <p>
185             * Use {@link VCardReader} for more control over the parsing.
186             * </p>
187             * @param reader the reader
188             * @return chainer object for completing the parse operation
189             * @see VCardReader
190             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
191             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
192             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
193             */
194            public static ParserChainTextReader parse(Reader reader) {
195                    return new ParserChainTextReader(reader);
196            }
197    
198            /**
199             * <p>
200             * Parses XML-encoded vCards (xCard).
201             * </p>
202             * <p>
203             * Use {@link XCardReader} for more control over the parsing.
204             * </p>
205             * @param xml the XML document
206             * @return chainer object for completing the parse operation
207             * @see XCardReader
208             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
209             */
210            public static ParserChainXmlString parseXml(String xml) {
211                    return new ParserChainXmlString(xml);
212            }
213    
214            /**
215             * <p>
216             * Parses XML-encoded vCards (xCard).
217             * </p>
218             * <p>
219             * Use {@link XCardReader} for more control over the parsing.
220             * </p>
221             * @param file the XML file
222             * @return chainer object for completing the parse operation
223             * @throws FileNotFoundException if the file does not exist or cannot be
224             * accessed
225             * @see XCardReader
226             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
227             */
228            public static ParserChainXmlReader parseXml(File file) throws FileNotFoundException {
229                    return parseXml(new FileReader(file));
230            }
231    
232            /**
233             * <p>
234             * Parses XML-encoded vCards (xCard).
235             * </p>
236             * <p>
237             * Use {@link XCardReader} for more control over the parsing.
238             * </p>
239             * @param in the input stream to the XML document
240             * @return chainer object for completing the parse operation
241             * @see XCardReader
242             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
243             */
244            public static ParserChainXmlReader parseXml(InputStream in) {
245                    return parseXml(new InputStreamReader(in));
246            }
247    
248            /**
249             * <p>
250             * Parses XML-encoded vCards (xCard).
251             * </p>
252             * <p>
253             * Use {@link XCardReader} for more control over the parsing.
254             * </p>
255             * @param reader the reader to the XML document
256             * @return chainer object for completing the parse operation
257             * @see XCardReader
258             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
259             */
260            public static ParserChainXmlReader parseXml(Reader reader) {
261                    return new ParserChainXmlReader(reader);
262            }
263    
264            /**
265             * <p>
266             * Parses XML-encoded vCards (xCard).
267             * </p>
268             * <p>
269             * Use {@link XCardReader} for more control over the parsing.
270             * </p>
271             * @param document the XML document
272             * @return chainer object for completing the parse operation
273             * @see XCardReader
274             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
275             */
276            public static ParserChainXmlDom parseXml(Document document) {
277                    return new ParserChainXmlDom(document);
278            }
279    
280            /**
281             * <p>
282             * Parses HTML-encoded vCards (hCard).
283             * </p>
284             * <p>
285             * Use {@link HCardReader} for more control over the parsing.
286             * </p>
287             * @param html the HTML page
288             * @return chainer object for completing the parse operation
289             * @see HCardReader
290             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
291             */
292            public static ParserChainHtmlString parseHtml(String html) {
293                    return new ParserChainHtmlString(html);
294            }
295    
296            /**
297             * <p>
298             * Parses HTML-encoded vCards (hCard).
299             * </p>
300             * <p>
301             * Use {@link HCardReader} for more control over the parsing.
302             * </p>
303             * @param file the HTML file
304             * @return chainer object for completing the parse operation
305             * @throws FileNotFoundException if the file does not exist or cannot be
306             * accessed
307             * @see HCardReader
308             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
309             */
310            public static ParserChainHtmlReader parseHtml(File file) throws FileNotFoundException {
311                    return parseHtml(new FileReader(file));
312            }
313    
314            /**
315             * <p>
316             * Parses HTML-encoded vCards (hCard).
317             * </p>
318             * <p>
319             * Use {@link HCardReader} for more control over the parsing.
320             * </p>
321             * @param in the input stream to the HTML page
322             * @return chainer object for completing the parse operation
323             * @see HCardReader
324             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
325             */
326            public static ParserChainHtmlReader parseHtml(InputStream in) {
327                    return parseHtml(new InputStreamReader(in));
328            }
329    
330            /**
331             * <p>
332             * Parses HTML-encoded vCards (hCard).
333             * </p>
334             * <p>
335             * Use {@link HCardReader} for more control over the parsing.
336             * </p>
337             * @param reader the reader to the HTML page
338             * @return chainer object for completing the parse operation
339             * @see HCardReader
340             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
341             */
342            public static ParserChainHtmlReader parseHtml(Reader reader) {
343                    return new ParserChainHtmlReader(reader);
344            }
345    
346            /**
347             * <p>
348             * Parses HTML-encoded vCards (hCard).
349             * </p>
350             * <p>
351             * Use {@link HCardReader} for more control over the parsing.
352             * </p>
353             * @param url the URL of the webpage
354             * @return chainer object for completing the parse operation
355             * @see HCardReader
356             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
357             */
358            public static ParserChainHtmlReader parseHtml(URL url) {
359                    return new ParserChainHtmlReader(url);
360            }
361    
362            /**
363             * <p>
364             * Marshals a vCard to its traditional, plain-text representation.
365             * </p>
366             * 
367             * <p>
368             * Use {@link VCardWriter} for more control over how the vCard is written.
369             * </p>
370             * @param vcard the vCard to marshal
371             * @return chainer object for completing the write operation
372             * @see VCardWriter
373             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
374             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
375             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
376             */
377            public static WriterChainTextSingle write(VCard vcard) {
378                    return new WriterChainTextSingle(vcard);
379            }
380    
381            /**
382             * <p>
383             * Marshals multiple vCards to their traditional, plain-text representation.
384             * </p>
385             * 
386             * <p>
387             * Use {@link VCardWriter} for more control over how the vCards are written.
388             * </p>
389             * @param vcards the vCards to marshal
390             * @return chainer object for completing the write operation
391             * @see VCardWriter
392             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
393             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
394             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
395             */
396            public static WriterChainTextMulti write(VCard... vcards) {
397                    return write(Arrays.asList(vcards));
398            }
399    
400            /**
401             * <p>
402             * Marshals multiple vCards to their traditional, plain-text representation.
403             * </p>
404             * 
405             * <p>
406             * Use {@link VCardWriter} for more control over how the vCards are written.
407             * </p>
408             * @param vcards the vCards to marshal
409             * @return chainer object for completing the write operation
410             * @see VCardWriter
411             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
412             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
413             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
414             */
415            public static WriterChainTextMulti write(Collection<VCard> vcards) {
416                    return new WriterChainTextMulti(vcards);
417            }
418    
419            /**
420             * <p>
421             * Marshals a vCard to its XML representation (xCard).
422             * </p>
423             * 
424             * <p>
425             * Use {@link XCardDocument} for more control over how the vCard is written.
426             * </p>
427             * @param vcard the vCard to marshal
428             * @return chainer object for completing the write operation
429             * @see XCardDocument
430             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
431             */
432            public static WriterChainXmlSingle writeXml(VCard vcard) {
433                    return new WriterChainXmlSingle(vcard);
434            }
435    
436            /**
437             * <p>
438             * Marshals multiple vCards to their XML representation (xCard).
439             * </p>
440             * 
441             * <p>
442             * Use {@link XCardDocument} for more control over how the vCards are
443             * written.
444             * </p>
445             * @param vcards the vCards to marshal
446             * @return chainer object for completing the write operation
447             * @see XCardDocument
448             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
449             */
450            public static WriterChainXmlMulti writeXml(VCard... vcards) {
451                    return writeXml(Arrays.asList(vcards));
452            }
453    
454            /**
455             * <p>
456             * Marshals multiple vCards to their XML representation (xCard).
457             * </p>
458             * 
459             * <p>
460             * Use {@link XCardDocument} for more control over how the vCards are
461             * written.
462             * </p>
463             * @param vcards the vCard to marshal
464             * @return chainer object for completing the write operation
465             * @see XCardDocument
466             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
467             */
468            public static WriterChainXmlMulti writeXml(Collection<VCard> vcards) {
469                    return new WriterChainXmlMulti(vcards);
470            }
471    
472            /**
473             * <p>
474             * Marshals one or more vCards their HTML representation (hCard).
475             * </p>
476             * 
477             * <p>
478             * Use {@link HCardPage} for more control over how the vCards are written.
479             * </p>
480             * @param vcards the vCard(s) to marshal
481             * @return chainer object for completing the write operation
482             * @see HCardPage
483             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
484             */
485            public static WriterChainHtml writeHtml(VCard... vcards) {
486                    return writeHtml(Arrays.asList(vcards));
487            }
488    
489            /**
490             * <p>
491             * Marshals one or more vCards their HTML representation (hCard).
492             * </p>
493             * 
494             * <p>
495             * Use {@link HCardPage} for more control over how the vCards are written.
496             * </p>
497             * @param vcards the vCard(s) to marshal
498             * @return chainer object for completing the write operation
499             * @see HCardPage
500             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
501             */
502            public static WriterChainHtml writeHtml(Collection<VCard> vcards) {
503                    return new WriterChainHtml(vcards);
504            }
505    
506            static abstract class ParserChain<T, U extends IParser> {
507                    final List<Class<? extends VCardType>> extendedTypes = new ArrayList<Class<? extends VCardType>>();
508                    List<List<String>> warnings;
509    
510                    /**
511                     * Registers an extended type class.
512                     * @param typeClass the extended type class
513                     * @return this
514                     */
515                    @SuppressWarnings("unchecked")
516                    public T register(Class<? extends VCardType> typeClass) {
517                            extendedTypes.add(typeClass);
518                            return (T) this;
519                    }
520    
521                    /**
522                     * Provides a list object that any unmarshal warnings will be put into.
523                     * @param warnings the list object that will be populated with the
524                     * warnings of each unmarshalled vCard. Each element of the list is the
525                     * list of warnings for one of the unmarshalled vCards. Therefore, the
526                     * size of this list will be equal to the number of parsed vCards. If a
527                     * vCard does not have any warnings, then its warning list will be
528                     * empty.
529                     * @return this
530                     */
531                    @SuppressWarnings("unchecked")
532                    public T warnings(List<List<String>> warnings) {
533                            this.warnings = warnings;
534                            return (T) this;
535                    }
536    
537                    /**
538                     * Creates the parser.
539                     * @return the parser object
540                     */
541                    abstract U init() throws IOException, SAXException;
542    
543                    U ready() throws IOException, SAXException {
544                            U parser = init();
545                            for (Class<? extends VCardType> extendedType : extendedTypes) {
546                                    parser.registerExtendedType(extendedType);
547                            }
548                            return parser;
549                    }
550    
551                    /**
552                     * Reads the first vCard from the stream.
553                     * @return the vCard or null if there are no vCards
554                     * @throws IOException if there's an I/O problem
555                     * @throws SAXException if there's a problem parsing the XML
556                     */
557                    public VCard first() throws IOException, SAXException {
558                            IParser parser = ready();
559                            VCard vcard = parser.readNext();
560                            if (warnings != null) {
561                                    warnings.add(parser.getWarnings());
562                            }
563                            return vcard;
564                    }
565    
566                    /**
567                     * Reads all vCards from the stream.
568                     * @return the parsed vCards
569                     * @throws IOException if there's an I/O problem
570                     * @throws SAXException if there's a problem parsing the XML
571                     */
572                    public List<VCard> all() throws IOException, SAXException {
573                            IParser parser = ready();
574                            List<VCard> vcards = new ArrayList<VCard>();
575                            VCard vcard;
576                            while ((vcard = parser.readNext()) != null) {
577                                    if (warnings != null) {
578                                            warnings.add(parser.getWarnings());
579                                    }
580                                    vcards.add(vcard);
581                            }
582                            return vcards;
583                    }
584    
585            }
586    
587            /**
588             * Convenience chainer class for parsing plain text vCards.
589             */
590            static abstract class ParserChainText<T> extends ParserChain<T, VCardReader> {
591                    boolean caretDecoding = true;
592    
593                    /**
594                     * Sets whether the reader will decode characters in parameter values
595                     * that use circumflex accent encoding (enabled by default).
596                     * 
597                     * @param enable true to use circumflex accent decoding, false not to
598                     * @see VCardReader#setCaretDecodingEnabled(boolean)
599                     * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
600                     */
601                    @SuppressWarnings("unchecked")
602                    public T caretDecoding(boolean enable) {
603                            caretDecoding = enable;
604                            return (T) this;
605                    }
606    
607                    @Override
608                    VCardReader ready() throws IOException, SAXException {
609                            VCardReader parser = super.ready();
610                            parser.setCaretDecodingEnabled(caretDecoding);
611                            return parser;
612                    }
613    
614                    @Override
615                    public VCard first() throws IOException {
616                            try {
617                                    return super.first();
618                            } catch (SAXException e) {
619                                    //not parsing XML
620                            }
621                            return null;
622                    }
623    
624                    @Override
625                    public List<VCard> all() throws IOException {
626                            try {
627                                    return super.all();
628                            } catch (SAXException e) {
629                                    //not parsing XML
630                            }
631                            return null;
632                    }
633            }
634    
635            /**
636             * Convenience chainer class for parsing plain text vCards.
637             */
638            public static class ParserChainTextReader extends ParserChainText<ParserChainTextReader> {
639                    private Reader reader;
640    
641                    private ParserChainTextReader(Reader reader) {
642                            this.reader = reader;
643                    }
644    
645                    @Override
646                    public ParserChainTextReader register(Class<? extends VCardType> typeClass) {
647                            return super.register(typeClass);
648                    }
649    
650                    @Override
651                    public ParserChainTextReader warnings(List<List<String>> warnings) {
652                            return super.warnings(warnings);
653                    }
654    
655                    @Override
656                    public ParserChainTextReader caretDecoding(boolean enable) {
657                            return super.caretDecoding(enable);
658                    }
659    
660                    @Override
661                    VCardReader init() {
662                            return new VCardReader(reader);
663                    }
664            }
665    
666            /**
667             * Convenience chainer class for parsing plain text vCards.
668             */
669            public static class ParserChainTextString extends ParserChainText<ParserChainTextString> {
670                    private String text;
671    
672                    private ParserChainTextString(String text) {
673                            this.text = text;
674                    }
675    
676                    @Override
677                    public ParserChainTextString register(Class<? extends VCardType> typeClass) {
678                            return super.register(typeClass);
679                    }
680    
681                    @Override
682                    public ParserChainTextString warnings(List<List<String>> warnings) {
683                            return super.warnings(warnings);
684                    }
685    
686                    @Override
687                    public ParserChainTextString caretDecoding(boolean enable) {
688                            return super.caretDecoding(enable);
689                    }
690    
691                    @Override
692                    VCardReader init() {
693                            return new VCardReader(text);
694                    }
695    
696                    @Override
697                    public VCard first() {
698                            try {
699                                    return super.first();
700                            } catch (IOException e) {
701                                    //reading from string
702                            }
703                            return null;
704                    }
705    
706                    @Override
707                    public List<VCard> all() {
708                            try {
709                                    return super.all();
710                            } catch (IOException e) {
711                                    //reading from string
712                            }
713                            return null;
714                    }
715            }
716    
717            /**
718             * Convenience chainer class for parsing XML vCards.
719             */
720            static abstract class ParserChainXml<T> extends ParserChain<T, XCardReader> {
721                    //nothing
722            }
723    
724            /**
725             * Convenience chainer class for parsing XML vCards.
726             */
727            public static class ParserChainXmlReader extends ParserChainXml<ParserChainXmlReader> {
728                    private Reader reader;
729    
730                    private ParserChainXmlReader(Reader reader) {
731                            this.reader = reader;
732                    }
733    
734                    @Override
735                    public ParserChainXmlReader register(Class<? extends VCardType> typeClass) {
736                            return super.register(typeClass);
737                    }
738    
739                    @Override
740                    public ParserChainXmlReader warnings(List<List<String>> warnings) {
741                            return super.warnings(warnings);
742                    }
743    
744                    @Override
745                    XCardReader init() throws IOException, SAXException {
746                            return new XCardReader(reader);
747                    }
748            }
749    
750            /**
751             * Convenience chainer class for parsing XML vCards.
752             */
753            public static class ParserChainXmlString extends ParserChainXml<ParserChainXmlString> {
754                    private String xml;
755    
756                    private ParserChainXmlString(String xml) {
757                            this.xml = xml;
758                    }
759    
760                    @Override
761                    public ParserChainXmlString register(Class<? extends VCardType> typeClass) {
762                            return super.register(typeClass);
763                    }
764    
765                    @Override
766                    public ParserChainXmlString warnings(List<List<String>> warnings) {
767                            return super.warnings(warnings);
768                    }
769    
770                    @Override
771                    XCardReader init() throws SAXException {
772                            return new XCardReader(xml);
773                    }
774    
775                    @Override
776                    public VCard first() throws SAXException {
777                            try {
778                                    return super.first();
779                            } catch (IOException e) {
780                                    //reading from string
781                            }
782                            return null;
783                    }
784    
785                    @Override
786                    public List<VCard> all() throws SAXException {
787                            try {
788                                    return super.all();
789                            } catch (IOException e) {
790                                    //reading from string
791                            }
792                            return null;
793                    }
794            }
795    
796            /**
797             * Convenience chainer class for parsing XML vCards.
798             */
799            public static class ParserChainXmlDom extends ParserChainXml<ParserChainXmlDom> {
800                    private Document document;
801    
802                    private ParserChainXmlDom(Document document) {
803                            this.document = document;
804                    }
805    
806                    @Override
807                    public ParserChainXmlDom register(Class<? extends VCardType> typeClass) {
808                            return super.register(typeClass);
809                    }
810    
811                    @Override
812                    public ParserChainXmlDom warnings(List<List<String>> warnings) {
813                            return super.warnings(warnings);
814                    }
815    
816                    @Override
817                    XCardReader init() {
818                            return new XCardReader(document);
819                    }
820    
821                    @Override
822                    public VCard first() {
823                            try {
824                                    return super.first();
825                            } catch (IOException e) {
826                                    //reading from DOM
827                            } catch (SAXException e) {
828                                    //reading from DOM
829                            }
830                            return null;
831                    }
832    
833                    @Override
834                    public List<VCard> all() {
835                            try {
836                                    return super.all();
837                            } catch (IOException e) {
838                                    //reading from DOM
839                            } catch (SAXException e) {
840                                    //reading from DOM
841                            }
842                            return null;
843                    }
844            }
845    
846            /**
847             * Convenience chainer class for parsing HTML vCards.
848             */
849            static abstract class ParserChainHtml<T> extends ParserChain<T, HCardReader> {
850                    String pageUrl;
851    
852                    /**
853                     * Sets the original URL of the webpage. This is used to resolve
854                     * relative links and to set the SOURCE property on the vCard. Setting
855                     * this property has no effect if reading from a {@link URL}.
856                     * @param pageUrl the webpage URL
857                     * @return this
858                     */
859                    @SuppressWarnings("unchecked")
860                    public T pageUrl(String pageUrl) {
861                            this.pageUrl = pageUrl;
862                            return (T) this;
863                    }
864    
865                    @Override
866                    public VCard first() throws IOException {
867                            try {
868                                    return super.first();
869                            } catch (SAXException e) {
870                                    //not parsing XML
871                            }
872                            return null;
873                    }
874    
875                    @Override
876                    public List<VCard> all() throws IOException {
877                            try {
878                                    return super.all();
879                            } catch (SAXException e) {
880                                    //not parsing XML
881                            }
882                            return null;
883                    }
884            }
885    
886            /**
887             * Convenience chainer class for parsing HTML vCards.
888             */
889            public static class ParserChainHtmlReader extends ParserChainHtml<ParserChainHtmlReader> {
890                    private Reader reader;
891                    private URL url;
892    
893                    private ParserChainHtmlReader(Reader reader) {
894                            this.reader = reader;
895                    }
896    
897                    private ParserChainHtmlReader(URL url) {
898                            this.url = url;
899                    }
900    
901                    @Override
902                    public ParserChainHtmlReader register(Class<? extends VCardType> typeClass) {
903                            return super.register(typeClass);
904                    }
905    
906                    @Override
907                    public ParserChainHtmlReader warnings(List<List<String>> warnings) {
908                            return super.warnings(warnings);
909                    }
910    
911                    @Override
912                    public ParserChainHtmlReader pageUrl(String pageUrl) {
913                            return super.pageUrl(pageUrl);
914                    }
915    
916                    @Override
917                    HCardReader init() throws IOException {
918                            return (url == null) ? new HCardReader(reader, pageUrl) : new HCardReader(url);
919                    }
920            }
921    
922            /**
923             * Convenience chainer class for parsing HTML vCards.
924             */
925            public static class ParserChainHtmlString extends ParserChainHtml<ParserChainHtmlString> {
926                    private String html;
927    
928                    private ParserChainHtmlString(String html) {
929                            this.html = html;
930                    }
931    
932                    @Override
933                    HCardReader init() {
934                            return new HCardReader(html, pageUrl);
935                    }
936    
937                    @Override
938                    public ParserChainHtmlString register(Class<? extends VCardType> typeClass) {
939                            return super.register(typeClass);
940                    }
941    
942                    @Override
943                    public ParserChainHtmlString warnings(List<List<String>> warnings) {
944                            return super.warnings(warnings);
945                    }
946    
947                    @Override
948                    public ParserChainHtmlString pageUrl(String pageUrl) {
949                            return super.pageUrl(pageUrl);
950                    }
951    
952                    @Override
953                    public VCard first() {
954                            try {
955                                    return super.first();
956                            } catch (IOException e) {
957                                    //reading from string
958                            }
959                            return null;
960                    }
961    
962                    @Override
963                    public List<VCard> all() {
964                            try {
965                                    return super.all();
966                            } catch (IOException e) {
967                                    //reading from string
968                            }
969                            return null;
970                    }
971            }
972    
973            static abstract class WriterChain {
974                    final Collection<VCard> vcards;
975    
976                    WriterChain(Collection<VCard> vcards) {
977                            this.vcards = vcards;
978                    }
979            }
980    
981            static abstract class WriterChainText<T> extends WriterChain {
982                    VCardVersion version;
983                    boolean prodId = true;
984                    boolean caretEncoding = false;
985    
986                    WriterChainText(Collection<VCard> vcards) {
987                            super(vcards);
988                    }
989    
990                    /**
991                     * <p>
992                     * Sets the version that all the vCards will be marshalled to. The
993                     * version that is attached to each individual {@link VCard} object will
994                     * be ignored.
995                     * </p>
996                     * <p>
997                     * If no version is passed into this method, the writer will look at the
998                     * version attached to each individual {@link VCard} object and marshal
999                     * it to that version. And if a {@link VCard} object has no version
1000                     * attached to it, then it will be marshalled to version 3.0.
1001                     * </p>
1002                     * @param version the version to marshal the vCards to
1003                     * @return this
1004                     */
1005                    @SuppressWarnings("unchecked")
1006                    public T version(VCardVersion version) {
1007                            this.version = version;
1008                            return (T) this;
1009                    }
1010    
1011                    /**
1012                     * Sets whether or not to add a PRODID type to each vCard, saying that
1013                     * the vCard was generated by this library. For 2.1 vCards, the extended
1014                     * type X-PRODID is used, since PRODID is not supported by that version.
1015                     * @param include true to add PRODID (default), false not to
1016                     * @return this
1017                     */
1018                    @SuppressWarnings("unchecked")
1019                    public T prodId(boolean include) {
1020                            this.prodId = include;
1021                            return (T) this;
1022                    }
1023    
1024                    /**
1025                     * Sets whether the writer will use circumflex accent encoding for vCard
1026                     * 3.0 and 4.0 parameter values (disabled by default).
1027                     * 
1028                     * @param enable true to use circumflex accent encoding, false not to
1029                     * @see VCardWriter#setCaretEncodingEnabled(boolean)
1030                     * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
1031                     */
1032                    @SuppressWarnings("unchecked")
1033                    public T caretEncoding(boolean enable) {
1034                            this.caretEncoding = enable;
1035                            return (T) this;
1036                    }
1037    
1038                    /**
1039                     * Writes the vCards to a string.
1040                     * @return the vCard string
1041                     */
1042                    public String go() {
1043                            StringWriter sw = new StringWriter();
1044                            try {
1045                                    go(sw);
1046                            } catch (IOException e) {
1047                                    //writing to a string
1048                            }
1049                            return sw.toString();
1050                    }
1051    
1052                    /**
1053                     * Writes the vCards to an output stream.
1054                     * @param out the output stream to write to
1055                     * @throws IOException if there's a problem writing to the output stream
1056                     */
1057                    public void go(OutputStream out) throws IOException {
1058                            go(new OutputStreamWriter(out));
1059                    }
1060    
1061                    /**
1062                     * Writes the vCards to a file.
1063                     * @param file the file to write to
1064                     * @throws IOException if there's a problem writing to the file
1065                     */
1066                    public void go(File file) throws IOException {
1067                            FileWriter writer = null;
1068                            try {
1069                                    writer = new FileWriter(file);
1070                                    go(writer);
1071                            } finally {
1072                                    IOUtils.closeQuietly(writer);
1073                            }
1074                    }
1075    
1076                    /**
1077                     * Writes the vCards to a writer.
1078                     * @param writer the writer to write to
1079                     * @throws IOException if there's a problem writing to the writer
1080                     */
1081                    public void go(Writer writer) throws IOException {
1082                            VCardWriter vcardWriter = new VCardWriter(writer);
1083                            if (version != null) {
1084                                    vcardWriter.setTargetVersion(version);
1085                            }
1086                            vcardWriter.setAddProdId(prodId);
1087                            vcardWriter.setCaretEncodingEnabled(caretEncoding);
1088    
1089                            for (VCard vcard : vcards) {
1090                                    if (version == null) {
1091                                            VCardVersion vcardVersion = vcard.getVersion();
1092                                            vcardWriter.setTargetVersion((vcardVersion == null) ? VCardVersion.V3_0 : vcardVersion);
1093                                    }
1094                                    vcardWriter.write(vcard);
1095                                    addWarnings(vcardWriter.getWarnings());
1096                            }
1097                    }
1098    
1099                    abstract void addWarnings(List<String> warnings);
1100            }
1101    
1102            /**
1103             * Convenience chainer class for writing plain text vCards
1104             */
1105            public static class WriterChainTextMulti extends WriterChainText<WriterChainTextMulti> {
1106                    private List<List<String>> warnings;
1107    
1108                    private WriterChainTextMulti(Collection<VCard> vcards) {
1109                            super(vcards);
1110                    }
1111    
1112                    @Override
1113                    public WriterChainTextMulti version(VCardVersion version) {
1114                            return super.version(version);
1115                    }
1116    
1117                    @Override
1118                    public WriterChainTextMulti prodId(boolean include) {
1119                            return super.prodId(include);
1120                    }
1121    
1122                    @Override
1123                    public WriterChainTextMulti caretEncoding(boolean enable) {
1124                            return super.caretEncoding(enable);
1125                    }
1126    
1127                    /**
1128                     * Provides a list object that any marshal warnings will be put into.
1129                     * Warnings usually occur when there is a property in the VCard that is
1130                     * not supported by the version to which the vCard is being marshalled.
1131                     * @param warnings the list object that will be populated with the
1132                     * warnings of each marshalled vCard. Each element of the list is the
1133                     * list of warnings for one of the marshalled vCards. Therefore, the
1134                     * size of this list will be equal to the number of parsed vCards. If a
1135                     * vCard does not have any warnings, then its warning list will be
1136                     * empty.
1137                     * @return this
1138                     */
1139                    public WriterChainTextMulti warnings(List<List<String>> warnings) {
1140                            this.warnings = warnings;
1141                            return this;
1142                    }
1143    
1144                    @Override
1145                    void addWarnings(List<String> warnings) {
1146                            if (this.warnings != null) {
1147                                    this.warnings.add(warnings);
1148                            }
1149                    }
1150            }
1151    
1152            /**
1153             * Convenience chainer class for writing plain text vCards
1154             */
1155            public static class WriterChainTextSingle extends WriterChainText<WriterChainTextSingle> {
1156                    private List<String> warnings;
1157    
1158                    private WriterChainTextSingle(VCard vcard) {
1159                            super(Arrays.asList(vcard));
1160                    }
1161    
1162                    @Override
1163                    public WriterChainTextSingle version(VCardVersion version) {
1164                            return super.version(version);
1165                    }
1166    
1167                    @Override
1168                    public WriterChainTextSingle prodId(boolean include) {
1169                            return super.prodId(include);
1170                    }
1171    
1172                    @Override
1173                    public WriterChainTextSingle caretEncoding(boolean enable) {
1174                            return super.caretEncoding(enable);
1175                    }
1176    
1177                    /**
1178                     * Provides a list object that any marshal warnings will be put into.
1179                     * Warnings usually occur when there is a property in the VCard that is
1180                     * not supported by the version to which the vCard is being marshalled.
1181                     * @param warnings the list object that will be populated with the
1182                     * warnings of the marshalled vCard.
1183                     * @return this
1184                     */
1185                    public WriterChainTextSingle warnings(List<String> warnings) {
1186                            this.warnings = warnings;
1187                            return this;
1188                    }
1189    
1190                    @Override
1191                    void addWarnings(List<String> warnings) {
1192                            if (this.warnings != null) {
1193                                    this.warnings.addAll(warnings);
1194                            }
1195                    }
1196            }
1197    
1198            static abstract class WriterChainXml<T> extends WriterChain {
1199                    boolean prodId = true;
1200                    int indent = -1;
1201    
1202                    WriterChainXml(Collection<VCard> vcards) {
1203                            super(vcards);
1204                    }
1205    
1206                    /**
1207                     * Sets whether or not to add a PRODID type to each vCard, saying that
1208                     * the vCard was generated by this library.
1209                     * @param include true to add PRODID (default), false not to
1210                     * @return this
1211                     */
1212                    @SuppressWarnings("unchecked")
1213                    public T prodId(boolean include) {
1214                            this.prodId = include;
1215                            return (T) this;
1216                    }
1217    
1218                    /**
1219                     * Sets the number of indent spaces to use for pretty-printing. If not
1220                     * set, then the XML will not be pretty-printed.
1221                     * @param indent the number of spaces in the indent string
1222                     * @return this
1223                     */
1224                    @SuppressWarnings("unchecked")
1225                    public T indent(int indent) {
1226                            this.indent = indent;
1227                            return (T) this;
1228                    }
1229    
1230                    /**
1231                     * Writes the xCards to a string.
1232                     * @return the XML document
1233                     */
1234                    public String go() {
1235                            StringWriter sw = new StringWriter();
1236                            try {
1237                                    go(sw);
1238                            } catch (TransformerException e) {
1239                                    //writing to a string
1240                            }
1241                            return sw.toString();
1242                    }
1243    
1244                    /**
1245                     * Writes the xCards to an output stream.
1246                     * @param out the output stream to write to
1247                     * @throws TransformerException if there's a problem writing to the
1248                     * output stream
1249                     */
1250                    public void go(OutputStream out) throws TransformerException {
1251                            go(new OutputStreamWriter(out));
1252                    }
1253    
1254                    /**
1255                     * Writes the xCards to a file.
1256                     * @param file the file to write to
1257                     * @throws IOException if the file can't be opened
1258                     * @throws TransformerException if there's a problem writing to the file
1259                     */
1260                    public void go(File file) throws IOException, TransformerException {
1261                            FileWriter writer = null;
1262                            try {
1263                                    writer = new FileWriter(file);
1264                                    go(writer);
1265                            } finally {
1266                                    IOUtils.closeQuietly(writer);
1267                            }
1268                    }
1269    
1270                    /**
1271                     * Writes the xCards to a writer.
1272                     * @param writer the writer to write to
1273                     * @throws TransformerException if there's a problem writing to the
1274                     * writer
1275                     */
1276                    public void go(Writer writer) throws TransformerException {
1277                            XCardDocument doc = createXCardDocument();
1278                            doc.write(writer, indent);
1279                    }
1280    
1281                    /**
1282                     * Generates an XML document object model (DOM) containing the xCards.
1283                     * @return the DOM
1284                     */
1285                    public Document dom() {
1286                            XCardDocument doc = createXCardDocument();
1287                            return doc.getDocument();
1288                    }
1289    
1290                    private XCardDocument createXCardDocument() {
1291                            XCardDocument doc = new XCardDocument();
1292                            doc.setAddProdId(prodId);
1293    
1294                            for (VCard vcard : vcards) {
1295                                    doc.addVCard(vcard);
1296                                    addWarnings(doc.getWarnings());
1297                            }
1298    
1299                            return doc;
1300                    }
1301    
1302                    abstract void addWarnings(List<String> warnings);
1303            }
1304    
1305            /**
1306             * Convenience chainer class for writing XML vCards (xCard).
1307             */
1308            public static class WriterChainXmlMulti extends WriterChainXml<WriterChainXmlMulti> {
1309                    private List<List<String>> warnings;
1310    
1311                    private WriterChainXmlMulti(Collection<VCard> vcards) {
1312                            super(vcards);
1313                    }
1314    
1315                    @Override
1316                    public WriterChainXmlMulti prodId(boolean include) {
1317                            return super.prodId(include);
1318                    }
1319    
1320                    @Override
1321                    public WriterChainXmlMulti indent(int indent) {
1322                            return super.indent(indent);
1323                    }
1324    
1325                    /**
1326                     * Provides a list object that any marshal warnings will be put into.
1327                     * Warnings usually occur when there is a property in the VCard that is
1328                     * not supported by the version to which the vCard is being marshalled.
1329                     * @param warnings the list object that will be populated with the
1330                     * warnings of each marshalled vCard. Each element of the list is the
1331                     * list of warnings for one of the marshalled vCards. Therefore, the
1332                     * size of this list will be equal to the number of parsed vCards. If a
1333                     * vCard does not have any warnings, then its warning list will be
1334                     * empty.
1335                     * @return this
1336                     */
1337                    public WriterChainXmlMulti warnings(List<List<String>> warnings) {
1338                            this.warnings = warnings;
1339                            return this;
1340                    }
1341    
1342                    @Override
1343                    void addWarnings(List<String> warnings) {
1344                            if (this.warnings != null) {
1345                                    this.warnings.add(warnings);
1346                            }
1347                    }
1348            }
1349    
1350            /**
1351             * Convenience chainer class for writing XML vCards (xCard).
1352             */
1353            public static class WriterChainXmlSingle extends WriterChainXml<WriterChainXmlSingle> {
1354                    private List<String> warnings;
1355    
1356                    private WriterChainXmlSingle(VCard vcard) {
1357                            super(Arrays.asList(vcard));
1358                    }
1359    
1360                    @Override
1361                    public WriterChainXmlSingle prodId(boolean include) {
1362                            return super.prodId(include);
1363                    }
1364    
1365                    @Override
1366                    public WriterChainXmlSingle indent(int indent) {
1367                            return super.indent(indent);
1368                    }
1369    
1370                    /**
1371                     * Provides a list object that any marshal warnings will be put into.
1372                     * Warnings usually occur when there is a property in the VCard that is
1373                     * not supported by the version to which the vCard is being marshalled.
1374                     * @param warnings the list object that will be populated with the
1375                     * warnings of each marshalled vCard.
1376                     * @return this
1377                     */
1378                    public WriterChainXmlSingle warnings(List<String> warnings) {
1379                            this.warnings = warnings;
1380                            return this;
1381                    }
1382    
1383                    @Override
1384                    void addWarnings(List<String> warnings) {
1385                            if (this.warnings != null) {
1386                                    this.warnings.addAll(warnings);
1387                            }
1388                    }
1389            }
1390    
1391            /**
1392             * Convenience chainer class for writing HTML vCards (hCard).
1393             */
1394            public static class WriterChainHtml extends WriterChain {
1395                    private WriterChainHtml(Collection<VCard> vcards) {
1396                            super(vcards);
1397                    }
1398    
1399                    /**
1400                     * Writes the hCards to a string.
1401                     * @return the HTML page
1402                     * @throws TemplateException if there's a problem with the freemarker
1403                     * template
1404                     */
1405                    public String go() throws TemplateException {
1406                            StringWriter sw = new StringWriter();
1407                            try {
1408                                    go(sw);
1409                            } catch (IOException e) {
1410                                    //writing string
1411                            }
1412                            return sw.toString();
1413                    }
1414    
1415                    /**
1416                     * Writes the hCards to an output stream.
1417                     * @param out the output stream to write to
1418                     * @throws IOException if there's a problem writing to the output stream
1419                     * @throws TemplateException if there's a problem with the freemarker
1420                     * template
1421                     */
1422                    public void go(OutputStream out) throws IOException, TemplateException {
1423                            go(new OutputStreamWriter(out));
1424                    }
1425    
1426                    /**
1427                     * Writes the hCards to a file.
1428                     * @param file the file to write to
1429                     * @throws IOException if there's a problem writing to the file
1430                     * @throws TemplateException if there's a problem with the freemarker
1431                     * template
1432                     */
1433                    public void go(File file) throws IOException, TemplateException {
1434                            FileWriter writer = null;
1435                            try {
1436                                    writer = new FileWriter(file);
1437                                    go(writer);
1438                            } finally {
1439                                    IOUtils.closeQuietly(writer);
1440                            }
1441                    }
1442    
1443                    /**
1444                     * Writes the hCards to a writer.
1445                     * @param writer the writer to write to
1446                     * @throws IOException if there's a problem writing to the writer
1447                     * @throws TemplateException if there's a problem with the freemarker
1448                     * template
1449                     */
1450                    public void go(Writer writer) throws IOException, TemplateException {
1451                            HCardPage page = new HCardPage();
1452                            for (VCard vcard : vcards) {
1453                                    page.addVCard(vcard);
1454                            }
1455                            page.write(writer);
1456                    }
1457            }
1458    
1459            private Ezvcard() {
1460                    //hide
1461            }
1462    }