001/**
002 * Copyright 2011 Bill Brown
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package com.colorfulsoftware.rss;
017
018import java.io.ByteArrayInputStream;
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.io.Serializable;
025import java.io.StringWriter;
026import java.lang.reflect.Constructor;
027import java.util.List;
028import java.util.Properties;
029
030import javax.xml.stream.XMLInputFactory;
031import javax.xml.stream.XMLOutputFactory;
032import javax.xml.stream.XMLStreamWriter;
033
034/**
035 * <p>
036 * This class reads and writes RSS documents to and from xml files, objects or
037 * Strings. It contains all of the factory methods for building immutable copies
038 * of the object elements.
039 * </p>
040 * 
041 * <p>
042 * Here are some examples of how to use the RSSpect library in your application:
043 * <br />
044 * <ul style="margin-left:15px;">
045 * <li>Read a file from disk into an RSS bean.<br />
046 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">RSS myRSS = new RSSDoc().readRSSToBean(new File("/myPath/myRSS.xml");</code>
047 * </li>
048 * <li>Read a file from the web into an RSS bean.<br />
049 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">RSS myRSS = new RSSDoc().readRSSToBean(new URL("http://www.abcdefg.net/myRSS.xml");</code>
050 * </li>
051 * <li>Read an RSS bean into a String.<br />
052 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">String myRssStr = myRSS.toString();</code>
053 * </li>
054 * <li>Read an RSS bean into a formatted String.<br />
055 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">String myRssStr = new RSSDoc().readRSSToString(myRSS, "javanet.staxutils.IndentingXMLStreamWriter");</code>
056 * </li>
057 * <li>Write an RSS bean to disk.<br />
058 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">String myRssStr = new RSSDoc().writeRSSDoc(new File("/somewhere/myRSS.xml"), myRSS, "UTF-8", "1.0");</code>
059 * </li>
060 * <li>Write a formatted RSS bean to disk.<br />
061 * <code style="margin-left:20px;padding-bottom:10px;font-style: italic;">String myRssStr = new RSSDoc().writeRSSDoc(new javanet.staxutils.IndentingXMLStreamWriter( XMLOutputFactory.newInstance().createXMLStreamWriter( new FileOutputStream("/somewhere/myRSS.xml"), "UTF-8")), myRSS, "UTF-8", "1.0");</code>
062 * </li>
063 * </ul>
064 * 
065 * 
066 * @author Bill Brown
067 * 
068 */
069public final class RSSDoc implements Serializable {
070
071        private static final long serialVersionUID = 649162683570000798L;
072
073        /**
074         * the default document encoding of "UTF-8"
075         */
076        private String encoding = System.getProperty("file.encoding");
077
078        /**
079         * the default XML version of "1.0"
080         */
081        private String xmlVersion = "1.0";
082
083        private Generator libVersion;
084
085        private XMLInputFactory inputFactory;
086
087        private List<ProcessingInstruction> processingInstructions;
088
089        /**
090         * @throws Exception
091         *             if the rsspect.properties file cant be read.
092         * 
093         */
094        public RSSDoc() throws Exception {
095                Properties props = new Properties();
096                props.load(RSSDoc.class.getResourceAsStream("/rsspect.properties"));
097                String libUri = props.getProperty("uri");
098                String libVersionStr = props.getProperty("version");
099
100                libVersion = new Generator(libUri + " v" + libVersionStr);
101                inputFactory = XMLInputFactory.newInstance();
102                // this is done to help for parsing documents that have undeclared and
103                // unescaped html or xhtml entities.
104                inputFactory.setProperty(
105                                "javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE);
106        }
107
108        /**
109         * @param processingInstructions
110         *            xml processing instructions.
111         * @throws Exception
112         *             if the library version information cannot be loaded from the
113         *             environment.
114         */
115        public RSSDoc(List<ProcessingInstruction> processingInstructions)
116                        throws Exception {
117                this();
118                this.processingInstructions = processingInstructions;
119        }
120
121        class ProcessingInstruction implements Serializable {
122                private final String target;
123                private final String data;
124                private static final long serialVersionUID = -4261298860522801834L;
125
126                ProcessingInstruction(String target, String data) {
127                        this.target = target;
128                        this.data = data;
129                }
130
131                /**
132                 * @return the target of the processing instruction
133                 */
134                public String getTarget() {
135                        return target;
136                }
137
138                /**
139                 * @return the processing instruction data.
140                 */
141                public String getData() {
142                        return data;
143                }
144        }
145
146        /**
147         * @return the RSSpect library version in the form of a generator element.
148         *         This element is output for all feeds that are generated by
149         *         RSSpect.
150         */
151        public Generator getLibVersion() {
152                return new Generator(libVersion);
153        }
154
155        /**
156         * 
157         * @param output
158         *            the target output stream for the rss document.
159         * @param rss
160         *            the rss object containing the content of the feed
161         * @param encoding
162         *            the file encoding (default is UTF-8)
163         * @param version
164         *            the xml version (default is 1.0)
165         * @throws Exception
166         *             thrown if the feed cannot be written to the output
167         */
168        public void writeRSSDoc(OutputStream output, RSS rss, String encoding,
169                        String version) throws Exception {
170                writeRSSOutput(rss, XMLOutputFactory.newInstance()
171                                .createXMLStreamWriter(output, encoding), encoding, version);
172
173        }
174
175        /**
176         * 
177         * @param file
178         *            the target output file for the document.
179         * @param rss
180         *            the rss object containing the content of the feed
181         * @param encoding
182         *            the file encoding (default is UTF-8)
183         * @param version
184         *            the xml version (default is 1.0)
185         * @throws Exception
186         *             thrown if the feed cannot be written to the output
187         */
188        public void writeRSSDoc(File file, RSS rss, String encoding, String version)
189                        throws Exception {
190                writeRSSOutput(rss, XMLOutputFactory.newInstance()
191                                .createXMLStreamWriter(new FileOutputStream(file), encoding),
192                                encoding, version);
193        }
194
195        /**
196         * For example: to pass the TXW
197         * com.sun.xml.txw2.output.IndentingXMLStreamWriter or the stax-utils
198         * javanet.staxutils.IndentingXMLStreamWriter for indented printing do this:
199         * 
200         * <pre>
201         * XmlStreamWriter writer = new IndentingXMLStreamWriter(XMLOutputFactory
202         *              .newInstance().createXMLStreamWriter(
203         *                              new FileOutputStream(outputFilePath), encoding));
204         * RSSDoc.writeFeedDoc(writer, myFeed, null, null);
205         * </pre>
206         * 
207         * @param output
208         *            the target output for the feed.
209         * @param rss
210         *            the rss object containing the content of the feed
211         * @param encoding
212         *            the file encoding (default is UTF-8)
213         * @param version
214         *            the xml version (default is 1.0)
215         * @throws Exception
216         *             thrown if the feed cannot be written to the output
217         */
218        public void writeRSSDoc(XMLStreamWriter output, RSS rss, String encoding,
219                        String version) throws Exception {
220                writeRSSOutput(rss, output, encoding, version);
221        }
222
223        /**
224         * This method reads in a Feed element and returns the contents as an rss
225         * feed string with formatting specified by the fully qualified
226         * XMLStreamWriter class name (uses reflection internally). For example you
227         * can pass the TXW com.sun.xml.txw2.output.IndentingXMLStreamWriter or the
228         * stax-utils javanet.staxutils.IndentingXMLStreamWriter for indented
229         * printing. It will fall back to the feeds' toString() method if the
230         * xmlStreamWriter is not recognized.
231         * 
232         * if the XMLStreamWriter class cannot be found in the classpath.
233         * 
234         * @param rss
235         *            the rss object to be converted to an rss document string.
236         * @param xmlStreamWriter
237         *            the fully qualified XMLStreamWriter class name.
238         * @return an rss feed document string.
239         * @throws Exception
240         *             thrown if the feed cannot be returned as a String
241         */
242        public String readRSSToString(RSS rss, String xmlStreamWriter)
243                        throws Exception {
244                if (rss == null) {
245                        throw new RSSpectException("The rss feed object cannot be null.");
246                }
247                try {
248                        StringWriter theString = new StringWriter();
249                        if (xmlStreamWriter == null || xmlStreamWriter.equals("")) {
250                                writeRSSOutput(rss, XMLOutputFactory.newInstance()
251                                                .createXMLStreamWriter(theString), encoding, xmlVersion);
252                        } else {
253                                Class<?> cls = Class.forName(xmlStreamWriter);
254                                Constructor<?> ct = cls
255                                                .getConstructor(new Class[] { XMLStreamWriter.class });
256                                Object arglist[] = new Object[] { XMLOutputFactory
257                                                .newInstance().createXMLStreamWriter(theString) };
258                                XMLStreamWriter writer = (XMLStreamWriter) ct
259                                                .newInstance(arglist);
260
261                                writeRSSOutput(rss, writer, encoding, xmlVersion);
262                        }
263
264                        return theString.toString();
265
266                        // if the xmlStreamWriter cannot be found, return the default
267                } catch (Exception e) {
268                        return rss.toString();
269                }
270        }
271
272        /**
273         * This method reads an xml string into a Feed element.
274         * 
275         * @param xmlString
276         *            the xml string to be transformed into a RSS element.
277         * @return the RSS element
278         * @throws Exception
279         *             if the string cannot be parsed into a RSS element.
280         */
281        public RSS readRSSToBean(String xmlString) throws Exception {
282                // try to grab the encoding first:
283                if (xmlString.contains("encoding=\"")) {
284                        String localEncoding = xmlString.substring(xmlString
285                                        .indexOf("encoding=\"") + 10);
286                        localEncoding = localEncoding.substring(0, localEncoding
287                                        .indexOf('"'));
288                        encoding = localEncoding;
289
290                }
291                return new RSSReader(this).readRSS(inputFactory
292                                .createXMLStreamReader(new ByteArrayInputStream(xmlString
293                                                .getBytes(encoding))));
294        }
295
296        /**
297         * This method reads an xml File object into a Feed element.
298         * 
299         * @param file
300         *            the file object representing an rss feed.
301         * @return the RSS element.
302         * @throws Exception
303         *             if the file cannot be parsed into a RSS element.
304         */
305        public RSS readRSSToBean(File file) throws Exception {
306                return new RSSReader(this).readRSS(inputFactory
307                                .createXMLStreamReader(new FileInputStream(file)));
308        }
309
310        /**
311         * This method reads an rss file from a URL into a Feed element.
312         * 
313         * @param url
314         *            the Internet network location of an rss file.
315         * @return the RSS element.
316         * @throws Exception
317         *             if the URL cannot be parsed into a RSS element.
318         */
319        public RSS readRSSToBean(java.net.URL url) throws Exception {
320                return readRSSToBean(url.openStream());
321        }
322
323        /**
324         * This method reads an rss file from an input stream into a RSS element.
325         * 
326         * @param inputStream
327         *            the input stream containing an rss file.
328         * @return the RSS element.
329         * @throws Exception
330         *             if the URL cannot be parsed into a RSS element.
331         */
332        public RSS readRSSToBean(InputStream inputStream) throws Exception {
333                return new RSSReader(this).readRSS(inputFactory
334                                .createXMLStreamReader(inputStream));
335        }
336
337        /**
338         * 
339         * @param channel
340         *            the unique channel element (required)
341         * @param attributes
342         *            additional attributes (optional)
343         * @param extensions
344         *            additional extensions (optional)
345         * @return an immutable RSS object.
346         * @throws RSSpectException
347         *             if the format of the data is not valid.
348         */
349        public RSS buildRSS(Channel channel, List<Attribute> attributes,
350                        List<Extension> extensions) throws RSSpectException {
351                return new RSS(channel, attributes, extensions);
352        }
353
354        /**
355         * 
356         * @param name
357         *            the attribute name.
358         * @param value
359         *            the attribute value.
360         * @return an immutable Attribute object.
361         * @throws RSSpectException
362         *             if the data is not valid.
363         */
364        public Attribute buildAttribute(String name, String value)
365                        throws RSSpectException {
366                return new Attribute(name, value);
367        }
368
369        /**
370         * 
371         * @param author
372         *            the author element. (required)
373         * @return an immutable Author object.
374         * @throws RSSpectException
375         *             if the format of the data is not valid.
376         */
377        public Author buildAuthor(String author) throws RSSpectException {
378                return new Author(author);
379        }
380
381        /**
382         * @param domain
383         *            the domain attribute
384         * @param category
385         *            the category text
386         * @return an immutable Category object.
387         * @throws RSSpectException
388         *             if the format of the data is not valid.
389         */
390        public Category buildCategory(Attribute domain, String category)
391                        throws RSSpectException {
392                return new Category(domain, category);
393        }
394
395        /**
396         * 
397         * @param title
398         *            the title element.
399         * @param link
400         *            the link element.
401         * @param description
402         *            the description element.
403         * @param language
404         *            the language element.
405         * @param copyright
406         *            the copyright element.
407         * @param managingEditor
408         *            the managingEditor element.
409         * @param webMaster
410         *            the webMaster element.
411         * @param pubDate
412         *            the pubDate element.
413         * @param lastBuildDate
414         *            the lastBuildDate element.
415         * @param categories
416         *            the list of categories
417         * @param generator
418         *            the generator element.
419         * @param docs
420         *            the docs element.
421         * @param cloud
422         *            the cloud element.
423         * @param ttl
424         *            the ttl element.
425         * @param image
426         *            the image element.
427         * @param rating
428         *            the rating element.
429         * @param textInput
430         *            the textInput element.
431         * @param skipHours
432         *            the skipHours element.
433         * @param skipDays
434         *            the skipDays element.
435         * @param items
436         *            the list of items.
437         * @param extensions
438         *            the list of extensions.
439         * @return an immutable Channel object.
440         * @throws RSSpectException
441         *             if the format of the data is not valid.
442         */
443        public Channel buildChannel(Title title, Link link,
444                        Description description, Language language, Copyright copyright,
445                        ManagingEditor managingEditor, WebMaster webMaster,
446                        PubDate pubDate, LastBuildDate lastBuildDate,
447                        List<Category> categories, Generator generator, Docs docs,
448                        Cloud cloud, TTL ttl, Image image, Rating rating,
449                        TextInput textInput, SkipHours skipHours, SkipDays skipDays,
450                        List<Extension> extensions, List<Item> items)
451                        throws RSSpectException {
452                return new Channel(title, link, description, language, copyright,
453                                managingEditor, webMaster, pubDate, lastBuildDate, categories,
454                                generator, docs, cloud, ttl, image, rating, textInput,
455                                skipHours, skipDays, extensions, items);
456        }
457
458        /**
459         * 
460         * @param attributes
461         *            the list of attributes.
462         * @return an immutable Cloud object.
463         * @throws RSSpectException
464         *             if the format of the data is not valid.
465         */
466        public Cloud buildCloud(List<Attribute> attributes) throws RSSpectException {
467                return new Cloud(attributes);
468        }
469
470        /**
471         * 
472         * @param comments
473         *            the comments.
474         * @return an immutable Comments object.
475         * @throws RSSpectException
476         *             if the format of the data is not valid.
477         */
478        public Comments buildComments(String comments) throws RSSpectException {
479                return new Comments(comments);
480        }
481
482        /**
483         * 
484         * @param copyright
485         *            the copyright.
486         * @return an immutable Copyright object.
487         * @throws RSSpectException
488         *             if the format of the data is not valid.
489         */
490        public Copyright buildCopyright(String copyright) throws RSSpectException {
491                return new Copyright(copyright);
492        }
493
494        /**
495         * 
496         * @param description
497         *            the description.
498         * @return an immutable Description object.
499         * @throws RSSpectException
500         *             if the format of the data is not valid.
501         */
502        public Description buildDescription(String description)
503                        throws RSSpectException {
504                return new Description(description);
505        }
506
507        /**
508         * 
509         * @param docs
510         *            the documentation information.
511         * @return an immutable Docs object.
512         * @throws RSSpectException
513         *             if the format of the data is not valid.
514         */
515        public Docs buildDocs(String docs) throws RSSpectException {
516                return new Docs(docs);
517        }
518
519        /**
520         * 
521         * @param attributes
522         *            should contain url, length and type
523         * @return an immutable Enclosure object.
524         * @throws RSSpectException
525         *             if the format of the data is not valid.
526         */
527        public Enclosure buildEnclosure(List<Attribute> attributes)
528                        throws RSSpectException {
529                return new Enclosure(attributes);
530        }
531
532        /**
533         * 
534         * @param elementName
535         *            the name of the extension element.
536         * @param attributes
537         *            additional attributes.
538         * @param content
539         *            the content of the extension element.
540         * @return an immutable Extension object.
541         * @throws RSSpectException
542         *             if the format of the data is not valid.
543         */
544        public Extension buildExtension(String elementName,
545                        List<Attribute> attributes, String content) throws RSSpectException {
546                return new Extension(elementName, attributes, content);
547        }
548
549        /**
550         * @param text
551         *            the text content.
552         * @return an immutable Generator object.
553         * @throws RSSpectException
554         *             if the format of the data is not valid.
555         */
556        public Generator buildGenerator(String text) throws RSSpectException {
557                return new Generator(text);
558        }
559
560        /**
561         * 
562         * @param isPermaLink
563         *            the isPermaLink attributes.
564         * @param guid
565         *            the guid data.
566         * @return an immutable GUID object.
567         * @throws RSSpectException
568         *             if the format of the data is not valid.
569         */
570        public GUID buildGUID(Attribute isPermaLink, String guid)
571                        throws RSSpectException {
572                return new GUID(isPermaLink, guid);
573        }
574
575        /**
576         * 
577         * @param height
578         *            should be a number 400 or less
579         * @return an immutable Height object.
580         * @throws RSSpectException
581         *             if the format of the data is not valid.
582         */
583        public Height buildHeight(String height) throws RSSpectException {
584                return new Height(height);
585        }
586
587        /**
588         * 
589         * @param url
590         *            the url element.
591         * @param title
592         *            the title element.
593         * @param link
594         *            the link element.
595         * @param width
596         *            the width element.
597         * @param height
598         *            the height element.
599         * @param description
600         *            the description element.
601         * @return an immutable Image object.
602         * @throws RSSpectException
603         *             if the format of the data is not valid.
604         */
605        public Image buildImage(URL url, Title title, Link link, Width width,
606                        Height height, Description description) throws RSSpectException {
607                return new Image(url, title, link, width, height, description);
608        }
609
610        /**
611         * 
612         * @param title
613         *            the title element.
614         * @param link
615         *            the link element.
616         * @param description
617         *            the description element.
618         * @param author
619         *            the author element.
620         * @param categories
621         *            the list of categories.
622         * @param comments
623         *            the comments element.
624         * @param enclosure
625         *            the enclosure element.
626         * @param guid
627         *            the guid element.
628         * @param pubDate
629         *            the published date element.
630         * @param source
631         *            the source element.
632         * @param extensions
633         *            the list of extensions.
634         * @return an immutable Item object.
635         * @throws RSSpectException
636         *             if the format of the data is not valid.
637         */
638        public Item buildItem(Title title, Link link, Description description,
639                        Author author, List<Category> categories, Comments comments,
640                        Enclosure enclosure, GUID guid, PubDate pubDate, Source source,
641                        List<Extension> extensions) throws RSSpectException {
642                return new Item(title, link, description, author, categories, comments,
643                                enclosure, guid, pubDate, source, extensions);
644        }
645
646        /**
647         * 
648         * @param language
649         *            the language.
650         * @return an immutable Language object.
651         * @throws RSSpectException
652         *             if the format of the data is not valid.
653         */
654        public Language buildLanguage(String language) throws RSSpectException {
655                return new Language(language);
656        }
657
658        /**
659         * 
660         * @param lastBuildDate
661         *            the last build date.
662         * @return an immutable LastBuildDate object.
663         * @throws RSSpectException
664         *             if the format of the data is not valid.
665         */
666        public LastBuildDate buildLastBuildDate(String lastBuildDate)
667                        throws RSSpectException {
668                return new LastBuildDate(lastBuildDate);
669        }
670
671        /**
672         * 
673         * @param link
674         *            the link information.
675         * @return an immutable Link object.
676         * @throws RSSpectException
677         *             if the format of the data is not valid.
678         */
679        public Link buildLink(String link) throws RSSpectException {
680                return new Link(link);
681        }
682
683        /**
684         * 
685         * @param managingEditor
686         *            the managing editor.
687         * @return an immutable ManagingEditor object.
688         * @throws RSSpectException
689         *             if the format of the data is not valid.
690         */
691        public ManagingEditor buildManagingEditor(String managingEditor)
692                        throws RSSpectException {
693                return new ManagingEditor(managingEditor);
694        }
695
696        /**
697         * 
698         * @param name
699         *            the name.
700         * @return an immutable Name object.
701         * @throws RSSpectException
702         *             if the format of the data is not valid.
703         */
704        public Name buildName(String name) throws RSSpectException {
705                return new Name(name);
706        }
707
708        /**
709         * 
710         * @param pubDate
711         *            the published date.
712         * @return an immutable PubDate object.
713         * @throws RSSpectException
714         *             If the dateTime format is invalid.
715         */
716        public PubDate buildPubDate(String pubDate) throws RSSpectException {
717                return new PubDate(pubDate);
718        }
719
720        /**
721         * 
722         * @param rating
723         *            the rating information.
724         * @return an immutable Rating object.
725         * @throws RSSpectException
726         *             if the format of the data is not valid.
727         */
728        public Rating buildRating(String rating) throws RSSpectException {
729                return new Rating(rating);
730        }
731
732        /**
733         * 
734         * @param skipDays
735         *            the days to skip.
736         * @throws RSSpectException
737         *             if the format of the data is not valid.
738         * @return an immutable SkipDays object.
739         */
740        public SkipDays buildSkipDays(List<Day> skipDays) throws RSSpectException {
741                return new SkipDays(skipDays);
742        }
743
744        /**
745         * 
746         * @param skipHours
747         *            the hours to skip.
748         * @throws RSSpectException
749         *             if the format of the data is not valid.
750         * @return an immutable SkipHours object.
751         */
752        public SkipHours buildSkipHours(List<Hour> skipHours)
753                        throws RSSpectException {
754                return new SkipHours(skipHours);
755        }
756
757        /**
758         * @param day
759         *            the day of the week.
760         * @return a Day object.
761         * @throws RSSpectException
762         *             if the format of the data is not valid.
763         */
764        public Day buildDay(String day) throws RSSpectException {
765                return new Day(day);
766        }
767
768        /**
769         * @param hour
770         *            the hour of the day.
771         * @return an Hour object.
772         * @throws RSSpectException
773         *             if the format of the data is not valid.
774         */
775        public Hour buildHour(String hour) throws RSSpectException {
776                return new Hour(hour);
777        }
778
779        /**
780         * 
781         * @param url
782         *            the url attribute.
783         * @param source
784         *            the source information.
785         * @return an immutable Source object.
786         * @throws RSSpectException
787         *             if the format of the data is not valid.
788         */
789        public Source buildSource(Attribute url, String source)
790                        throws RSSpectException {
791                return new Source(url, source);
792        }
793
794        /**
795         * 
796         * @param title
797         *            the title element.
798         * @param description
799         *            the description element.
800         * @param name
801         *            the name element.
802         * @param link
803         *            the link element.
804         * @return an immutable TextInput object.
805         * @throws RSSpectException
806         *             if the format of the data is not valid.
807         */
808        public TextInput buildTextInput(Title title, Description description,
809                        Name name, Link link) throws RSSpectException {
810                return new TextInput(title, description, name, link);
811        }
812
813        /**
814         * 
815         * @param title
816         *            the title.
817         * @return an immutable Title object.
818         * @throws RSSpectException
819         *             if the format of the data is not valid.
820         */
821        public Title buildTitle(String title) throws RSSpectException {
822                return new Title(title);
823        }
824
825        /**
826         * 
827         * @param ttl
828         *            the time to live.
829         * @return an immutable TTL object.
830         * @throws RSSpectException
831         *             if the format of the data is not valid.
832         */
833        public TTL buildTTL(String ttl) throws RSSpectException {
834                return new TTL(ttl);
835        }
836
837        /**
838         * 
839         * @param url
840         *            the url.
841         * @return an immutable URL object.
842         * @throws RSSpectException
843         *             if the format of the data is not valid.
844         */
845        public URL buildURL(String url) throws RSSpectException {
846                return new URL(url);
847        }
848
849        /**
850         * 
851         * @param webMaster
852         *            the web master.
853         * @return an immutable WebMaster object.
854         * @throws RSSpectException
855         *             if the format of the data is not valid.
856         */
857        public WebMaster buildWebMaster(String webMaster) throws RSSpectException {
858                return new WebMaster(webMaster);
859        }
860
861        /**
862         * 
863         * @param width
864         *            the width.
865         * @return an immutable Width object.
866         * @throws RSSpectException
867         *             if the format of the data is not valid.
868         */
869        public Width buildWidth(String width) throws RSSpectException {
870                return new Width(width);
871        }
872
873        // used to write feed output for several feed writing methods.
874        private void writeRSSOutput(RSS rss, XMLStreamWriter writer,
875                        String encoding, String version) throws Exception {
876
877                if (rss == null) {
878                        throw new RSSpectException("The rss feed object cannot be null.");
879                }
880
881                Channel channel = rss.getChannel();
882
883                rss = buildRSS(buildChannel(channel.getTitle(), channel.getLink(),
884                                channel.getDescription(), channel.getLanguage(), channel
885                                                .getCopyright(), channel.getManagingEditor(), channel
886                                                .getWebMaster(), channel.getPubDate(), channel
887                                                .getLastBuildDate(), channel.getCategories(),
888                                getLibVersion(), channel.getDocs(), channel.getCloud(), channel
889                                                .getTtl(), channel.getImage(), channel.getRating(),
890                                channel.getTextInput(), channel.getSkipHours(), channel
891                                                .getSkipDays(), channel.getExtensions(), channel
892                                                .getItems()), rss.getAttributes(), rss.getExtensions());
893
894                // write the xml header.
895                writer.writeStartDocument(encoding, version);
896                if (this.processingInstructions != null) {
897                        for (ProcessingInstruction pi : this.processingInstructions) {
898                                writer.writeProcessingInstruction(pi.getTarget(), pi.getData());
899                        }
900                }
901
902                new RSSWriter().writeRSS(writer, rss);
903                writer.flush();
904                writer.close();
905        }
906
907        /**
908         * @return the xml encoding of the document eg. UTF-8
909         */
910        public String getEncoding() {
911                return encoding;
912        }
913
914        /**
915         * @return the xml document version of the document eg. 1.0
916         */
917        public String getXmlVersion() {
918                return xmlVersion;
919        }
920
921        void setEncoding(String encoding) {
922                this.encoding = encoding;
923        }
924
925        void setXmlVersion(String xmlVersion) {
926                this.xmlVersion = xmlVersion;
927        }
928
929        void setProcessingInstructions(
930                        List<ProcessingInstruction> processingInstructions) {
931                this.processingInstructions = processingInstructions;
932        }
933
934        List<ProcessingInstruction> getProcessingInstructions() {
935                // TODO Auto-generated method stub
936                return processingInstructions;
937        }
938
939}