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.Serializable; 019import java.util.LinkedList; 020import java.util.List; 021 022/** 023 * <p> 024 * The <rss> element. 025 * </p> 026 * <p> 027 * From the <a href="http://cyber.law.harvard.edu/rss/rss.html">RSS 2.0 028 * specification</a>... 029 * </p> 030 * <p> 031 * At the top level, a RSS document is a <rss> element, with a mandatory 032 * attribute called version, that specifies the version of RSS that the document 033 * conforms to. If it conforms to this specification, the version attribute must 034 * be 2.0. 035 * </p> 036 * 037 * <p> 038 * Subordinate to the <rss> element is a single <channel> element, which 039 * contains information about the channel (metadata) and its contents. RSS 040 * originated in 1999, and has strived to be a simple, easy to understand 041 * format, with relatively modest goals. After it became a popular format, 042 * developers wanted to extend it using modules defined in namespaces, as 043 * specified by the W3C. 044 * </p> 045 * 046 * <p> 047 * RSS 2.0 adds that capability, following a simple rule. A RSS feed may contain 048 * elements not described on this page, only if those elements are defined in a 049 * namespace. 050 * </p> 051 * 052 * <p> 053 * The elements defined in this document are not themselves members of a 054 * namespace, so that RSS 2.0 can remain compatible with previous versions in 055 * the following sense - - a version 0.91 or 0.92 file is also a valid 2.0 file. 056 * If the elements of RSS 2.0 were in a namespace, this constraint would break, 057 * a version 0.9x file would not be a valid 2.0 file. 058 * </p> 059 * 060 * <p> 061 * The version="2.0" attribute of the <rss> element is automatically provided 062 * in this implementation. 063 * </p> 064 * 065 * @author Bill Brown 066 * 067 */ 068public class RSS implements Serializable { 069 070 /** 071 * 072 */ 073 private static final long serialVersionUID = -2952021194132587749L; 074 075 private final Channel channel; 076 private final List<Attribute> attributes; 077 private final List<Extension> extensions; 078 private List<String> unboundPrefixes; 079 080 RSS(Channel channel, List<Attribute> attributes, List<Extension> extensions) 081 throws RSSpectException { 082 if (channel == null) { 083 throw new RSSpectException( 084 "rss elements MUST contain a channel element."); 085 } 086 087 this.channel = new Channel(channel); 088 089 if (attributes == null) { 090 throw new RSSpectException( 091 "RSS elements must contain a version attribute."); 092 } else { 093 this.attributes = new LinkedList<Attribute>(); 094 095 boolean containsVersion = false; 096 097 for (Attribute attr : attributes) { 098 this.attributes.add(new Attribute(attr)); 099 if (attr.getName().equals("version")) { 100 containsVersion = true; 101 } 102 } 103 104 if (!containsVersion) { 105 throw new RSSpectException( 106 "RSS elements must contain a version attribute."); 107 } 108 } 109 110 // check that all extension prefixes are bound to a namespace 111 this.unboundPrefixes = new LinkedList<String>(); 112 113 if (this.channel.getUnboundPrefixes() != null) { 114 for (String unboundPrefix : this.channel.getUnboundPrefixes()) { 115 if (getAttribute("xmlns:" + unboundPrefix) == null) { 116 this.unboundPrefixes.add(unboundPrefix); 117 } 118 } 119 } 120 121 if (extensions == null) { 122 this.extensions = null; 123 } else { 124 this.extensions = new LinkedList<Extension>(); 125 for (Extension extension : extensions) { 126 // check that the extension prefix is bound to a namespace 127 String namespacePrefix = extension.getNamespacePrefix(); 128 if (namespacePrefix != null) { 129 if (getAttribute("xmlns:" + namespacePrefix) == null) { 130 this.unboundPrefixes.add(namespacePrefix); 131 } 132 } 133 this.extensions.add(new Extension(extension)); 134 } 135 } 136 137 // if there are any unbound prefixes, throw an exception 138 if (this.unboundPrefixes.size() > 0) { 139 StringBuilder sb = new StringBuilder(); 140 for (String namePrefix : this.unboundPrefixes) { 141 sb.append(namePrefix + " "); 142 } 143 throw new RSSpectException( 144 "the following extension prefix(es) ( " 145 + sb 146 + ") are not bound to a namespace declaration. See http://www.w3.org/TR/1999/REC-xml-names-19990114/#ns-decl."); 147 } 148 } 149 150 /** 151 * @return the channel object. 152 */ 153 public Channel getChannel() { 154 return new Channel(channel); 155 } 156 157 /** 158 * 159 * @return the attribute list. 160 */ 161 public List<Attribute> getAttributes() { 162 163 List<Attribute> attrsCopy = new LinkedList<Attribute>(); 164 for (Attribute attr : this.attributes) { 165 attrsCopy.add(new Attribute(attr)); 166 167 } 168 return attrsCopy; 169 } 170 171 /** 172 * 173 * @return the extensions for this entry. 174 */ 175 public List<Extension> getExtensions() { 176 if (extensions == null) { 177 return null; 178 } 179 List<Extension> extsCopy = new LinkedList<Extension>(); 180 for (Extension extension : this.extensions) { 181 extsCopy.add(new Extension(extension)); 182 } 183 return extsCopy; 184 } 185 186 /** 187 * @param attrName 188 * the name of the attribute to get. 189 * @return the Attribute object if attrName matches or null if not found. 190 */ 191 public Attribute getAttribute(String attrName) { 192 for (Attribute attribute : this.attributes) { 193 if (attribute.getName().equals(attrName)) { 194 return new Attribute(attribute); 195 196 } 197 } 198 return null; 199 } 200 201 /** 202 * @param extName 203 * the element name of the extension. eg. "atom:link" or 204 * "someExtension" 205 * @return the extension matching the element or null if not found. 206 */ 207 public Extension getExtension(String extName) { 208 if (this.extensions != null) { 209 for (Extension extension : this.extensions) { 210 if (extension.getElementName().equals(extName)) { 211 return new Extension(extension); 212 } 213 } 214 } 215 return null; 216 } 217 218 /** 219 * Shows the contents of the <rss> element. 220 */ 221 @Override 222 public String toString() { 223 StringBuilder sb = new StringBuilder("<rss"); 224 for (Attribute attribute : attributes) { 225 sb.append(attribute); 226 } 227 // close the parent element 228 sb.append(">"); 229 230 sb.append(channel); 231 232 if (extensions != null) { 233 for (Extension extension : extensions) { 234 sb.append(extension); 235 } 236 } 237 238 sb.append("</rss>"); 239 return sb.toString(); 240 } 241 242 @Override 243 public boolean equals(Object obj) { 244 if (obj == this) { 245 return true; 246 } 247 if (!(obj instanceof RSS)) { 248 return false; 249 } 250 return this.toString().equals(obj.toString()); 251 } 252 253 @Override public int hashCode() { 254 return toString().hashCode(); 255 } 256}