diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e975ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.classpath +/.project +/.settings +/target \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..65a6e36 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +rome +==== + +ROME is a set of RSS and Atom Utilities for Java. It makes it easy to work in Java with most syndication formats: RSS 0.90, RSS 0.91 Netscape, RSS 0.91 Userland, RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, RSS 2.0, Atom 0.3, Atom 1.0 + +More Information: http://rometools.github.io/rome-opml/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b8d6cc4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + com.rometools + rome-parent + 1.6.0-SNAPSHOT + + + rome-opml + 1.6.0-SNAPSHOT + jar + + rome-opml + + Support for OPML 1 and OPML 2 in ROME + + http://rometools.github.io/rome-opml/ + + + scm:git:ssh://github.com/rometools/rome-opml.git + scm:git:ssh://git@github.com/rometools/rome-opml.git + https://github.com/rometools/rome-opml + + + + + kebernet + kebernet@gmail.com + Robert Cooper + + + + + + sonatype-nexus-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + + org.apache.maven.plugins + maven-scm-publish-plugin + + gh-pages + ${project.scm.developerConnection} + ${project.build.directory}/site + + + + + + + + com.rometools + rome + 1.6.0-SNAPSHOT + + + ch.qos.logback + logback-classic + test + + + junit + junit + test + + + org.hamcrest + hamcrest-library + + + xmlunit + xmlunit + 1.6 + test + + + + diff --git a/src/main/java/com/rometools/opml/feed/opml/Attribute.java b/src/main/java/com/rometools/opml/feed/opml/Attribute.java new file mode 100644 index 0000000..7e29065 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/opml/Attribute.java @@ -0,0 +1,87 @@ +/* + * Attribute.java + * + * Created on April 24, 2006, 11:11 PM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.feed.opml; + +import java.io.Serializable; + +import com.rometools.rome.feed.impl.EqualsBean; +import com.rometools.rome.feed.impl.ToStringBean; + +/** + * This is a simple name-value pair attribute for outlines. + * + * @author Robert "kebernet" Cooper + */ +public class Attribute implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; + + private String name; + private String value; + + /** + * Creates a new instance of Attribute. + * + * @param name name of the attribute. + * @param value value of the attribute. + */ + public Attribute(final String name, final String value) { + if (name == null || value == null) { + throw new NullPointerException("Name and value are required."); + } + setName(name); + setValue(value); + } + + public void setName(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setValue(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public Object clone() { + return new Attribute(name, value); + } + + @Override + public boolean equals(final Object obj) { + return new EqualsBean(Attribute.class, this).beanEquals(obj); + } + + @Override + public int hashCode() { + return new EqualsBean(Attribute.class, this).beanHashCode(); + } + + @Override + public String toString() { + return new ToStringBean(Attribute.class, this).toString(); + } + +} diff --git a/src/main/java/com/rometools/opml/feed/opml/Opml.java b/src/main/java/com/rometools/opml/feed/opml/Opml.java new file mode 100644 index 0000000..3b1fbd8 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/opml/Opml.java @@ -0,0 +1,322 @@ +/* + * Opml.java + * + * Created on April 24, 2006, 11:00 PM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.feed.opml; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.rometools.rome.feed.WireFeed; + +/** + * This class represents the root of an OPML 1/2 feed and contains the elements that may appear in the <head> tag + * of the feed. + * + * @author Robert "kebernet" Cooper + */ +public class Opml extends WireFeed { + + private static final long serialVersionUID = 1L; + + private Date created; + private Date modified; + private Integer verticalScrollState; + private Integer windowBottom; + private Integer windowLeft; + private Integer windowRight; + private Integer windowTop; + private List outlines; + private String docs; + private String ownerEmail; + private String ownerId; + private String ownerName; + private String title; + private int[] expansionState; + + /** + * is a date-time, indicating when the document was created. + * + * @param created date-time, indicating when the document was created. + */ + public void setCreated(final Date created) { + this.created = created; + } + + /** + * <dateCreated> is a date-time, indicating when the document was created. + * + * @return date-time, indicating when the document was created. + */ + public Date getCreated() { + return created; + } + + /** + * (OPML 2) <docs> is the http address of documentation for the format used in the OPML file. It's probably a + * pointer to this page for people who might stumble across the file on a + * web server 25 years from now and wonder what it is. + * + * @param docs http address of documentation for the format used + */ + public void setDocs(final String docs) { + this.docs = docs; + } + + /** + * (OPML 2) <docs> is the http address of documentation for the format used in the OPML file. It's probably a + * pointer to this page for people who might stumble across the file on a + * web server 25 years from now and wonder what it is. + * + * @return http address of documentation for the format used + */ + public String getDocs() { + return docs; + } + + /** + * <expansionState>is a comma-separated list of line numbers that are expanded. The line numbers in the list + * tell you which headlines to expand. The order is important. For each element in the list, X, starting at the + * first summit, navigate flatdown X times and expand. Repeat for each element in the list. + * + * @param expansionState int array containing expanded elements. + */ + public void setExpansionState(final int[] expansionState) { + this.expansionState = expansionState; + } + + /** + * <expansionState> is a comma-separated list of line numbers that are expanded. The line numbers in the list + * tell you which headlines to expand. The order is important. For each element in the list, X, starting at the + * first summit, navigate flatdown X times and expand. Repeat for each element in the list. + * + * @return int array containing expanded elements. + */ + public int[] getExpansionState() { + return expansionState; + } + + /** + * <dateModified> is a date-time, indicating when the document was last modified. + * + * @param modified date-time, indicating when the document was last modified. + */ + public void setModified(final Date modified) { + this.modified = modified; + } + + /** + * <dateModified> is a date-time, indicating when the document was last modified. + * + * @return date-time, indicating when the document was last modified. + */ + public Date getModified() { + return modified; + } + + /** + * Root level Outline object that should appear in the <body> + * + * @param outlines Root level Outline object that should appear in the <body> + */ + public void setOutlines(final List outlines) { + this.outlines = outlines; + } + + /** + * Root level Outline object that should appear in the <body> + * + * @return Root level Outline object that should appear in the <body> + */ + public List getOutlines() { + if (outlines == null) { + outlines = new ArrayList(); + } + + return outlines; + } + + /** + * <ownerEmail> is a string, the email address of the owner of the document. + * + * @param ownerEmail the email address of the owner of the document. + */ + public void setOwnerEmail(final String ownerEmail) { + this.ownerEmail = ownerEmail; + } + + /** + * <ownerEmail> is a string, the email address of the owner of the document. + * + * @return the email address of the owner of the document. + */ + public String getOwnerEmail() { + return ownerEmail; + } + + /** + * (OPML 2) <ownerId> is the http address of a web page that contains an HTML a form that + * allows a human reader to communicate with the author of the document via email or other means. + * + * @param ownerId http address of a web page that contains an HTML a form that allows a human + * reader to communicate with the author of the document via email or other means. + */ + public void setOwnerId(final String ownerId) { + this.ownerId = ownerId; + } + + /** + * (OPML 2) <ownerId> is the http address of a web page that contains an HTML a form that + * allows a human reader to communicate with the author of the document via email or other means. + * + * @return http address of a web page that contains an HTML a form that allows a human reader to + * communicate with the author of the document via email or other means. + */ + public String getOwnerId() { + return ownerId; + } + + /** + * <ownerName> is a string, the owner of the document. + * + * @param ownerName the owner of the document. + */ + public void setOwnerName(final String ownerName) { + this.ownerName = ownerName; + } + + /** + * <ownerName> is a string, the owner of the document. + * + * @return the owner of the document. + */ + public String getOwnerName() { + return ownerName; + } + + /** + * <title> is the title of the document. + * + * @param title title of the document. + */ + public void setTitle(final String title) { + this.title = title; + } + + /** + * <title> is the title of the document. + * + * @return title of the document. + */ + public String getTitle() { + return title; + } + + /** + * <vertScrollState> is a number, saying which line of the outline is displayed on the top line of the window. + * This number is calculated with the expansion state already applied. + * + * @param verticalScrollState which line of the outline is displayed on the top line of the window. + */ + public void setVerticalScrollState(final Integer verticalScrollState) { + this.verticalScrollState = verticalScrollState; + } + + /** + * <vertScrollState> is a number, saying which line of the outline is displayed on the top line of the window. + * This number is calculated with the expansion state already applied. + * + * @return which line of the outline is displayed on the top line of the window. This number is calculated with the + * expansion state already applied. + */ + public Integer getVerticalScrollState() { + return verticalScrollState; + } + + /** + * <windowBottom> is a number, the pixel location of the bottom edge of the window. + * + * @param windowBottom the pixel location of the bottom edge of the window. + */ + public void setWindowBottom(final Integer windowBottom) { + this.windowBottom = windowBottom; + } + + /** + * <windowBottom> is a number, the pixel location of the bottom edge of the window. + * + * @return the pixel location of the bottom edge of the window. + */ + public Integer getWindowBottom() { + return windowBottom; + } + + /** + * <windowLeft> is a number, the pixel location of the left edge of the window. + * + * @param windowLeft the pixel location of the left edge of the window. + */ + public void setWindowLeft(final Integer windowLeft) { + this.windowLeft = windowLeft; + } + + /** + * <windowLeft> is a number, the pixel location of the left edge of the window. + * + * @return the pixel location of the left edge of the window. + */ + public Integer getWindowLeft() { + return windowLeft; + } + + /** + * <windowRight> is a number, the pixel location of the right edge of the window. + * + * @param windowRight the pixel location of the right edge of the window. + */ + public void setWindowRight(final Integer windowRight) { + this.windowRight = windowRight; + } + + /** + * <windowRight> is a number, the pixel location of the right edge of the window. + * + * @return the pixel location of the right edge of the window. + */ + public Integer getWindowRight() { + return windowRight; + } + + /** + * <windowTop> is a number, the pixel location of the top edge of the window. + * + * @param windowTop the pixel location of the top edge of the window. + */ + public void setWindowTop(final Integer windowTop) { + this.windowTop = windowTop; + } + + /** + * <windowTop> is a number, the pixel location of the top edge of the window. + * + * @return the pixel location of the top edge of the window. + */ + public Integer getWindowTop() { + return windowTop; + } + +} diff --git a/src/main/java/com/rometools/opml/feed/opml/Outline.java b/src/main/java/com/rometools/opml/feed/opml/Outline.java new file mode 100644 index 0000000..2d97405 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/opml/Outline.java @@ -0,0 +1,367 @@ +/* + * Outline.java + * + * Created on April 24, 2006, 11:04 PM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.feed.opml; + +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.rometools.rome.feed.impl.EqualsBean; +import com.rometools.rome.feed.impl.ToStringBean; +import com.rometools.rome.feed.module.Module; + +/** + * This class represents an OPML outline element. + * + * @author Robert "kebernet" Cooper + */ +public class Outline implements Cloneable, Serializable { + + private static final long serialVersionUID = 1L; + + private Date created; + private List attributes; + private List categories; + private List children; + private List modules; + private String text; + private String title; + private String type; + private boolean breakpoint; + private boolean comment; + + public Outline() { + } + + /** + * Creates a new outline with the specified type and text values. + * + * @param type type attribute value/ + * @param text text attribute value + */ + public Outline(final String type, final String text) { + setType(type); + setText(text); + } + + /** + * Creates an outline with the given title, xmlUrl and htmlUrl. This is traditionally used for aggregator feed lists + * and will get a type of "rss". + * + * @param title Title of the entry. + * @param xmlUrl link to XML file. + * @param htmlUrl link to html page. + */ + public Outline(final String title, final URL xmlUrl, final URL htmlUrl) { + super(); + setType("rss"); + setTitle(title); + setAttributes(new ArrayList()); + + if (xmlUrl != null) { + getAttributes().add(new Attribute("xmlUrl", xmlUrl.toString())); + } + + if (htmlUrl != null) { + getAttributes().add(new Attribute("htmlUrl", htmlUrl.toString())); + } + } + + /** + * List of attributes on this outline excluding the "common types" for the specification. + * + * @param attributes List of attributes on this outline. + */ + public void setAttributes(final List attributes) { + this.attributes = attributes; + } + + /** + * List of attributes on this outline excluding the "common types" for the specification. + * + * @return List of attributes on this outline. + */ + public List getAttributes() { + if (attributes == null) { + attributes = new ArrayList(); + } + + return attributes; + } + + /** + * isBreakpoint is a string, either "true" or "false", indicating whether a breakpoint is set on this outline. This + * attribute is mainly necessary for outlines used to edit scripts. If it's not present, the value is false. + * + * @param breakpoint whether a breakpoint is set on this outline. + */ + public void setBreakpoint(final boolean breakpoint) { + this.breakpoint = breakpoint; + } + + /** + * isBreakpoint is a string, either "true" or "false", indicating whether a breakpoint is set on this outline. This + * attribute is mainly necessary for outlines used to edit scripts. If it's not present, the value is false. + * + * @return whether a breakpoint is set on this outline + */ + public boolean isBreakpoint() { + return breakpoint; + } + + /** + * (OPML 2) A List of Strings indicating values in the category attribute. + * + * @param categories (OPML 2) A List of Strings indicating values in the category attribute. + */ + public void setCategories(final List categories) { + this.categories = categories; + } + + /** + * (OPML 2) A List of Strings indicating values in the category attribute. + * + * @return (OPML 2) A List of Strings indicating values in the category attribute. + */ + public List getCategories() { + if (categories == null) { + categories = new ArrayList(); + } + + return categories; + } + + /** + * A list of sub-outlines for this entry. + * + * @param children A list of sub-outlines for this entry. + */ + public void setChildren(final List children) { + this.children = children; + } + + /** + * A list of sub-outlines for this entry. + * + * @return A list of sub-outlines for this entry. + */ + public List getChildren() { + if (children == null) { + children = new ArrayList(); + } + + return children; + } + + /** + * isComment is a string, either "true" or "false", indicating whether the outline is commented or not. By + * convention if an outline is commented, all subordinate outlines are considered to also be commented. If it's not + * present, the value is false. + * + * @param comment whether the outline is commented + */ + public void setComment(final boolean comment) { + this.comment = comment; + } + + /** + * isComment is a string, either "true" or "false", indicating whether the outline is commented or not. By + * convention if an outline is commented, all subordinate outlines are considered to also be commented. If it's not + * present, the value is false. + * + * @return whether the outline is commented + */ + public boolean isComment() { + return comment; + } + + /** + * (OPML 2) created is the date-time that the outline node was created. + * + * @param created date-time that the outline node was created. + */ + public void setCreated(final Date created) { + this.created = created; + } + + /** + * (OPML 2) created is the date-time that the outline node was created. + * + * @return date-time that the outline node was created. + */ + public Date getCreated() { + return created; + } + + /** + * A convenience method to return the value of the url attribute. + * + * @return value of the htmlUrl attribute. + */ + public String getUrl() { + return getAttributeValue("url"); + } + + /** + * A convenience method to return the value of the htmlUrl attribute. + * + * @return value of the htmlUrl attribute. + */ + public String getHtmlUrl() { + return getAttributeValue("htmlUrl"); + } + + public void setModules(final List modules) { + this.modules = modules; + } + + public List getModules() { + if (modules == null) { + modules = new ArrayList(); + } + + return modules; + } + + /** + * The "text" attribute of the outline. + * + * @param text The "text" attribute of the outline. + */ + public void setText(final String text) { + this.text = text; + } + + /** + * The "text" attribute of the outline. + * + * @return The "text" attribute of the outline. + */ + public String getText() { + return text; + } + + /** + * The "title" attribute of the outline. + * + * @param title The "title" attribute of the outline. + */ + public void setTitle(final String title) { + this.title = title; + } + + /** + * The "title" attribute of the outline. + * + * @return The "title" attribute of the outline. + */ + public String getTitle() { + return title; + } + + /** + * The "type" attribute of the outline. + * + * @param type The "type" attribute of the outline. + */ + public void setType(final String type) { + this.type = type; + } + + /** + * The "type" attribute of the outline. + * + * @return The "type" attribute of the outline. + */ + public String getType() { + return type; + } + + /** + * A convenience method to return the value of the xmlUrl attribute. + * + * @return value of the xmlUrl attribute. + */ + public String getXmlUrl() { + return getAttributeValue("xmlUrl"); + } + + /** + * Returns the value of an attribute on the outline or null. + * + * @param name name of the attribute. + */ + public String getAttributeValue(final String name) { + final List attributes = Collections.synchronizedList(getAttributes()); + for (int i = 0; i < attributes.size(); i++) { + final Attribute a = attributes.get(i); + + if (a.getName() != null && a.getName().equals(name)) { + return a.getValue(); + } + } + return null; + } + + @Override + public Object clone() { + + final Outline o = new Outline(); + o.setBreakpoint(isBreakpoint()); + o.setCategories(new ArrayList(getCategories())); + o.setComment(isComment()); + o.setCreated(created != null ? (Date) created.clone() : null); + o.setModules(new ArrayList(getModules())); + o.setText(getText()); + o.setTitle(getTitle()); + o.setType(getType()); + + final ArrayList children = new ArrayList(); + for (int i = 0; i < getChildren().size(); i++) { + children.add((Outline) this.children.get(i).clone()); + } + o.setChildren(children); + + final ArrayList attributes = new ArrayList(); + for (int i = 0; i < getAttributes().size(); i++) { + attributes.add((Attribute) this.attributes.get(i).clone()); + } + o.setAttributes(attributes); + + return o; + } + + @Override + public boolean equals(final Object obj) { + return new EqualsBean(Outline.class, this).beanEquals(obj); + } + + @Override + public int hashCode() { + return new EqualsBean(Outline.class, this).beanHashCode(); + } + + @Override + public String toString() { + return new ToStringBean(Outline.class, this).toString(); + } + +} diff --git a/src/main/java/com/rometools/opml/feed/opml/package.html b/src/main/java/com/rometools/opml/feed/opml/package.html new file mode 100644 index 0000000..3e0dfed --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/opml/package.html @@ -0,0 +1,47 @@ + + + + + + + + This package represents the base objects for OPML support for ROME. +

+ There are three classes here that are relevant. Opml, + which represents the root document and head information, + Outline which represents a single node on an Opml + outline tree, and provides convenience methods for commonly used + attributes such as xmlUrl. Finally, the + Attribute class, which represents a specific attribute + on an Outline object. Since OPML supports free-form attribute + assignments, this is a very multi-purpose class. +

+

Sample Usage: + To use this parser, simply include the jar file in your classpath + as you are using ROME. Be sure it exists at the same level as ROME, + such that, if ROME is in the common classpath of an application server, + don't include this jar in your webapps WEB-INF/lib. +

+  WireFeedInput input = new WireFeedInput();
+  Opml feed = (Opml) input.build( new File("myOpml.xml") );
+  List<Outline> outlines = (List<Outline>) feed.getOutlines();
+        
+

+ + +
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+    
+ + diff --git a/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML10.java b/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML10.java new file mode 100644 index 0000000..4cf6363 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML10.java @@ -0,0 +1,349 @@ +/* + * ConverterForOPML10.java + * + * Created on April 25, 2006, 1:26 AM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.feed.synd.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rometools.opml.feed.opml.Attribute; +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.Converter; +import com.rometools.rome.feed.synd.SyndCategory; +import com.rometools.rome.feed.synd.SyndCategoryImpl; +import com.rometools.rome.feed.synd.SyndContent; +import com.rometools.rome.feed.synd.SyndContentImpl; +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndEntryImpl; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndLink; +import com.rometools.rome.feed.synd.SyndLinkImpl; +import com.rometools.rome.feed.synd.SyndPerson; +import com.rometools.rome.feed.synd.SyndPersonImpl; + +/** + * @author cooper + */ +public class ConverterForOPML10 implements Converter { + + private static final Logger LOG = LoggerFactory.getLogger(ConverterForOPML10.class); + + public static final String URI_TREE = "urn:rome.tree"; + public static final String URI_ATTRIBUTE = "urn:rome.attribute#"; + + protected void addOwner(final Opml opml, final SyndFeed syndFeed) { + if (opml.getOwnerEmail() != null || opml.getOwnerName() != null) { + final List authors = new ArrayList(); + final SyndPerson person = new SyndPersonImpl(); + person.setEmail(opml.getOwnerEmail()); + person.setName(opml.getOwnerName()); + authors.add(person); + syndFeed.setAuthors(authors); + } + } + + /** + * Makes a deep copy/conversion of the values of a real feed into a SyndFeedImpl. + *

+ * It assumes the given SyndFeedImpl has no properties set. + *

+ * + * @param feed real feed to copy/convert. + * @param syndFeed the SyndFeedImpl that will contain the copied/converted values of the real feed. + */ + @Override + public void copyInto(final WireFeed feed, final SyndFeed syndFeed) { + final Opml opml = (Opml) feed; + syndFeed.setTitle(opml.getTitle()); + addOwner(opml, syndFeed); + syndFeed.setPublishedDate(opml.getModified() != null ? opml.getModified() : opml.getCreated()); + syndFeed.setFeedType(opml.getFeedType()); + syndFeed.setModules(opml.getModules()); + syndFeed.setFeedType(getType()); + + createEntries(new Stack(), syndFeed.getEntries(), opml.getOutlines()); + } + + protected void createEntries(final Stack context, final List allEntries, final List outlines) { + final List so = Collections.synchronizedList(outlines); + + for (int i = 0; i < so.size(); i++) { + createEntry(context, allEntries, so.get(i)); + } + } + + protected SyndEntry createEntry(final Stack context, final List allEntries, final Outline outline) { + final SyndEntry entry = new SyndEntryImpl(); + + if (outline.getType() != null && outline.getType().equals("rss")) { + entry.setLink(outline.getHtmlUrl() != null ? outline.getHtmlUrl() : outline.getXmlUrl()); + } else if (outline.getType() != null && outline.getType().equals("link")) { + entry.setLink(outline.getUrl()); + } + + if (outline.getHtmlUrl() != null) { + final SyndLink link = new SyndLinkImpl(); + link.setRel("alternate"); + link.setType("text/html"); + link.setHref(outline.getHtmlUrl()); + entry.getLinks().add(link); + entry.setLink(outline.getHtmlUrl()); + } + + if (outline.getXmlUrl() != null && outline.getType() != null && outline.getType().equalsIgnoreCase("rss")) { + final SyndLink link = new SyndLinkImpl(); + link.setRel("alternate"); + link.setType("application/rss+xml"); + link.setHref(outline.getXmlUrl()); + entry.getLinks().add(link); + + if (entry.getLink() == null) { + entry.setLink(outline.getXmlUrl()); + } + } + + if (outline.getXmlUrl() != null && outline.getType() != null && outline.getType().equalsIgnoreCase("atom")) { + final SyndLink link = new SyndLinkImpl(); + link.setRel("alternate"); + link.setType("application/atom+xml"); + link.setHref(outline.getXmlUrl()); + entry.getLinks().add(link); + + if (entry.getLink() == null) { + entry.setLink(outline.getXmlUrl()); + } + } + + if (outline.getType() != null && outline.getType().equals("rss")) { + entry.setTitle(outline.getTitle()); + } else { + entry.setTitle(outline.getText()); + } + + if (outline.getText() == null && entry.getTitle() != null) { + final SyndContent c = new SyndContentImpl(); + c.setValue(outline.getText()); + entry.setDescription(c); + } + + entry.setPublishedDate(outline.getCreated()); + + final String nodeName = "node." + outline.hashCode(); + + final SyndCategory cat = new TreeCategoryImpl(); + cat.setTaxonomyUri(URI_TREE); + cat.setName(nodeName); + entry.getCategories().add(cat); + + if (!context.isEmpty()) { + final Integer parent = context.peek(); + final SyndCategory pcat = new TreeCategoryImpl(); + pcat.setTaxonomyUri(URI_TREE); + pcat.setName("parent." + parent); + entry.getCategories().add(pcat); + } + + final List attributes = Collections.synchronizedList(outline.getAttributes()); + + for (int i = 0; i < attributes.size(); i++) { + final Attribute a = attributes.get(i); + final SyndCategory acat = new SyndCategoryImpl(); + acat.setName(a.getValue()); + acat.setTaxonomyUri(URI_ATTRIBUTE + a.getName()); + entry.getCategories().add(acat); + } + + entry.setModules(outline.getModules()); + allEntries.add(entry); + context.push(new Integer(outline.hashCode())); + createEntries(context, allEntries, outline.getChildren()); + context.pop(); + + return entry; + } + + /** + * Creates real feed with a deep copy/conversion of the values of a SyndFeedImpl. + *

+ * + * @param syndFeed SyndFeedImpl to copy/convert value from. + * @return a real feed with copied/converted values of the SyndFeedImpl. + */ + @Override + public WireFeed createRealFeed(final SyndFeed syndFeed) { + + final List entries = Collections.synchronizedList(syndFeed.getEntries()); + + final HashMap entriesByNode = new HashMap(); + + // this will hold entries that we can't parent the first time. + final ArrayList doAfterPass = new ArrayList(); + + // this holds root level outlines; + final ArrayList root = new ArrayList(); + + for (int i = 0; i < entries.size(); i++) { + final SyndEntry entry = entries.get(i); + final Outline o = new Outline(); + + final List cats = Collections.synchronizedList(entry.getCategories()); + boolean parentFound = false; + final StringBuffer category = new StringBuffer(); + + for (int j = 0; j < cats.size(); j++) { + final SyndCategory cat = cats.get(j); + + if (cat.getTaxonomyUri() != null && cat.getTaxonomyUri().equals(URI_TREE)) { + final String nodeVal = cat.getName().substring(cat.getName().lastIndexOf("."), cat.getName().length()); + + if (cat.getName().startsWith("node.")) { + entriesByNode.put(nodeVal, o); + } else if (cat.getName().startsWith("parent.")) { + parentFound = true; + + final Outline parent = entriesByNode.get(nodeVal); + + if (parent != null) { + parent.getChildren().add(o); + } else { + doAfterPass.add(new OutlineHolder(o, nodeVal)); + } + } + } else if (cat.getTaxonomyUri() != null && cat.getTaxonomyUri().startsWith(URI_ATTRIBUTE)) { + final String name = cat.getTaxonomyUri().substring(cat.getTaxonomyUri().indexOf("#") + 1, cat.getTaxonomyUri().length()); + o.getAttributes().add(new Attribute(name, cat.getName())); + } else { + if (category.length() > 0) { + category.append(", "); + } + + category.append(cat.getName()); + } + } + + if (!parentFound) { + root.add(o); + } + + if (category.length() > 0) { + o.getAttributes().add(new Attribute("category", category.toString())); + } + + final List links = Collections.synchronizedList(entry.getLinks()); + // final String entryLink = entry.getLink(); + + for (int j = 0; j < links.size(); j++) { + final SyndLink link = links.get(j); + + // if(link.getHref().equals(entryLink)) { + if (link.getType() != null && link.getRel() != null && link.getRel().equals("alternate") + && (link.getType().equals("application/rss+xml") || link.getType().equals("application/atom+xml"))) { + o.setType("rss"); + + if (o.getXmlUrl() == null) { + o.getAttributes().add(new Attribute("xmlUrl", link.getHref())); + } + } else if (link.getType() != null && link.getType().equals("text/html")) { + if (o.getHtmlUrl() == null) { + o.getAttributes().add(new Attribute("htmlUrl", link.getHref())); + } + } else { + o.setType(link.getType()); + } + + // } + } + + if (o.getType() == null || o.getType().equals("link")) { + o.setText(entry.getTitle()); + + } else { + o.setTitle(entry.getTitle()); + } + + if (o.getText() == null && entry.getDescription() != null) { + o.setText(entry.getDescription().getValue()); + } + } + + // Do back and parenting for things we missed. + for (int i = 0; i < doAfterPass.size(); i++) { + final OutlineHolder o = doAfterPass.get(i); + final Outline parent = entriesByNode.get(o.parent); + + if (parent == null) { + root.add(o.outline); + LOG.warn("Unable to find parent node: {}", o.parent); + } else { + parent.getChildren().add(o.outline); + } + } + + final Opml opml = new Opml(); + opml.setFeedType(getType()); + opml.setCreated(syndFeed.getPublishedDate()); + opml.setTitle(syndFeed.getTitle()); + + final List authors = Collections.synchronizedList(syndFeed.getAuthors()); + + for (int i = 0; i < authors.size(); i++) { + final SyndPerson p = authors.get(i); + + if (syndFeed.getAuthor() == null || syndFeed.getAuthor().equals(p.getName())) { + opml.setOwnerName(p.getName()); + opml.setOwnerEmail(p.getEmail()); + opml.setOwnerId(p.getUri()); + } + } + + opml.setOutlines(root); + + return opml; + } + + /** + * Returns the type (version) of the real feed this converter handles. + * + * @return the real feed type. + * @see WireFeed for details on the format of this string. + */ + @Override + public String getType() { + return "opml_1.0"; + } + + private static class OutlineHolder { + + private final Outline outline; + private final String parent; + + public OutlineHolder(final Outline outline, final String parent) { + this.outline = outline; + this.parent = parent; + } + + } + +} diff --git a/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML20.java b/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML20.java new file mode 100644 index 0000000..672da88 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/synd/impl/ConverterForOPML20.java @@ -0,0 +1,50 @@ +package com.rometools.opml.feed.synd.impl; + +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; + +/** + * @author cooper + */ +public class ConverterForOPML20 extends ConverterForOPML10 { + + /** + * Returns the type (version) of the real feed this converter handles. + *

+ * + * @return the real feed type. + * @see WireFeed for details on the format of this string. + *

+ */ + @Override + public String getType() { + return "opml_2.0"; + } + + /** + * Makes a deep copy/conversion of the values of a real feed into a SyndFeedImpl. + *

+ * It assumes the given SyndFeedImpl has no properties set. + *

+ * + * @param feed real feed to copy/convert. + * @param syndFeed the SyndFeedImpl that will contain the copied/converted values of the real feed. + */ + @Override + public void copyInto(final WireFeed feed, final SyndFeed syndFeed) { + super.copyInto(feed, syndFeed); + } + + /** + * Creates real feed with a deep copy/conversion of the values of a SyndFeedImpl. + *

+ * + * @param syndFeed SyndFeedImpl to copy/convert value from. + * @return a real feed with copied/converted values of the SyndFeedImpl. + */ + @Override + public WireFeed createRealFeed(final SyndFeed syndFeed) { + return super.createRealFeed(syndFeed); + } + +} diff --git a/src/main/java/com/rometools/opml/feed/synd/impl/TreeCategoryImpl.java b/src/main/java/com/rometools/opml/feed/synd/impl/TreeCategoryImpl.java new file mode 100644 index 0000000..b338798 --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/synd/impl/TreeCategoryImpl.java @@ -0,0 +1,23 @@ +package com.rometools.opml.feed.synd.impl; + +import com.rometools.rome.feed.synd.SyndCategory; +import com.rometools.rome.feed.synd.SyndCategoryImpl; + +/** + * @author cooper + */ +public class TreeCategoryImpl extends SyndCategoryImpl { + + private static final long serialVersionUID = 1L; + + @Override + public boolean equals(final Object o) { + final SyndCategory c = (SyndCategory) o; + if (c.getTaxonomyUri() != null && c.getTaxonomyUri().equals(getTaxonomyUri())) { + return true; + } else { + return false; + } + } + +} diff --git a/src/main/java/com/rometools/opml/feed/synd/impl/package.html b/src/main/java/com/rometools/opml/feed/synd/impl/package.html new file mode 100644 index 0000000..40f63ad --- /dev/null +++ b/src/main/java/com/rometools/opml/feed/synd/impl/package.html @@ -0,0 +1,30 @@ + + + + + + + + + This packages contains the SyndFeed converters for the + OPML module. For information on how Opml gets turned into + other feed types through the Synd structures, see the OPML + Subproject page on the ROME wiki. + +

+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+    
+ + + diff --git a/src/main/java/com/rometools/opml/io/impl/OPML10Generator.java b/src/main/java/com/rometools/opml/io/impl/OPML10Generator.java new file mode 100644 index 0000000..8851312 --- /dev/null +++ b/src/main/java/com/rometools/opml/io/impl/OPML10Generator.java @@ -0,0 +1,186 @@ +/* + * Opml10Generator.java + * + * Created on April 24, 2006, 11:35 PM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.io.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.jdom2.Document; +import org.jdom2.Element; + +import com.rometools.opml.feed.opml.Attribute; +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.WireFeedGenerator; +import com.rometools.rome.io.impl.BaseWireFeedGenerator; +import com.rometools.rome.io.impl.DateParser; + +/** + * @author Robert "kebernet" Cooper + */ +public class OPML10Generator extends BaseWireFeedGenerator implements WireFeedGenerator { + + public OPML10Generator() { + super("opml_1.0"); + } + + public OPML10Generator(final String type) { + super(type); + } + + /** + * Creates an XML document (JDOM) for the given feed bean. + * + * @param feed the feed bean to generate the XML document from. + * @return the generated XML document (JDOM). + * @throws IllegalArgumentException thrown if the type of the given feed bean does not match with the type of the + * WireFeedGenerator. + * @throws FeedException thrown if the XML Document could not be created. + */ + @Override + public Document generate(final WireFeed feed) throws IllegalArgumentException, FeedException { + + if (!(feed instanceof Opml)) { + throw new IllegalArgumentException("Not an OPML file"); + } + + final Opml opml = (Opml) feed; + final Document doc = new Document(); + final Element root = new Element("opml"); + root.setAttribute("version", "1.0"); + doc.addContent(root); + + final Element head = generateHead(opml); + + if (head != null) { + root.addContent(head); + } + + final Element body = new Element("body"); + root.addContent(body); + super.generateFeedModules(opml.getModules(), root); + body.addContent(generateOutlines(opml.getOutlines())); + + return doc; + } + + protected boolean addNotNullAttribute(final Element target, final String name, final Object value) { + if (target == null || name == null || value == null) { + return false; + } + target.setAttribute(name, value.toString()); + return true; + } + + protected boolean addNotNullSimpleElement(final Element target, final String name, final Object value) { + if (target == null || name == null || value == null) { + return false; + } + + final Element e = new Element(name); + e.addContent(value.toString()); + target.addContent(e); + + return true; + } + + protected Element generateHead(final Opml opml) { + final Element head = new Element("head"); + boolean hasHead = false; + + if (opml.getCreated() != null) { + hasHead |= addNotNullSimpleElement(head, "dateCreated", DateParser.formatRFC822(opml.getCreated(), Locale.US)); + } + + hasHead |= addNotNullSimpleElement(head, "expansionState", intArrayToCsvString(opml.getExpansionState())); + + if (opml.getModified() != null) { + hasHead |= addNotNullSimpleElement(head, "dateModified", DateParser.formatRFC822(opml.getModified(), Locale.US)); + } + + hasHead |= addNotNullSimpleElement(head, "ownerEmail", opml.getOwnerEmail()); + hasHead |= addNotNullSimpleElement(head, "ownerName", opml.getOwnerName()); + hasHead |= addNotNullSimpleElement(head, "title", opml.getTitle()); + hasHead |= addNotNullSimpleElement(head, "vertScrollState", opml.getVerticalScrollState()); + hasHead |= addNotNullSimpleElement(head, "windowBottom", opml.getWindowBottom()); + hasHead |= addNotNullSimpleElement(head, "windowLeft", opml.getWindowLeft()); + hasHead |= addNotNullSimpleElement(head, "windowRight", opml.getWindowRight()); + hasHead |= addNotNullSimpleElement(head, "windowTop", opml.getWindowTop()); + + if (hasHead) { + return head; + } else { + return null; + } + } + + protected Element generateOutline(final Outline outline) { + final Element e = new Element("outline"); + addNotNullAttribute(e, "text", outline.getText()); + addNotNullAttribute(e, "type", outline.getType()); + addNotNullAttribute(e, "title", outline.getTitle()); + + if (outline.isBreakpoint()) { + addNotNullAttribute(e, "isBreakpoint", "true"); + } + + if (outline.isComment()) { + addNotNullAttribute(e, "isComment", "true"); + } + + final List atts = Collections.synchronizedList(outline.getAttributes()); + + for (int i = 0; i < atts.size(); i++) { + final Attribute att = atts.get(i); + addNotNullAttribute(e, att.getName(), att.getValue()); + } + + super.generateItemModules(outline.getModules(), e); + e.addContent(generateOutlines(outline.getChildren())); + + return e; + } + + protected List generateOutlines(final List outlines) { + final ArrayList elements = new ArrayList(); + for (int i = 0; outlines != null && i < outlines.size(); i++) { + elements.add(generateOutline(outlines.get(i))); + } + return elements; + } + + protected String intArrayToCsvString(final int[] value) { + if (value == null || value.length == 0) { + return null; + } + + final StringBuffer sb = new StringBuffer(); + sb.append(value[0]); + + for (int i = 1; i < value.length; i++) { + sb.append(","); + sb.append(value[i]); + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/rometools/opml/io/impl/OPML10Parser.java b/src/main/java/com/rometools/opml/io/impl/OPML10Parser.java new file mode 100644 index 0000000..02b1bf5 --- /dev/null +++ b/src/main/java/com/rometools/opml/io/impl/OPML10Parser.java @@ -0,0 +1,260 @@ +/* + * Opml10Parser.java + * + * Created on April 24, 2006, 11:34 PM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.io.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.StringTokenizer; + +import org.jdom2.Document; +import org.jdom2.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rometools.opml.feed.opml.Attribute; +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.WireFeedParser; +import com.rometools.rome.io.impl.BaseWireFeedParser; +import com.rometools.rome.io.impl.DateParser; + +/** + * @author Robert "kebernet" Cooper + */ +public class OPML10Parser extends BaseWireFeedParser implements WireFeedParser { + + private static Logger LOG = LoggerFactory.getLogger(OPML10Parser.class); + + public OPML10Parser() { + super("opml_1.0", null); + } + + public OPML10Parser(final String type) { + super(type, null); + } + + /** + * Inspects an XML Document (JDOM) to check if it can parse it. + *

+ * It checks if the given document if the type of feeds the parser understands. + *

+ * + * @param document XML Document (JDOM) to check if it can be parsed by this parser. + * @return true if the parser know how to parser this feed, false otherwise. + */ + @Override + public boolean isMyType(final Document document) { + final Element e = document.getRootElement(); + + if (e.getName().equals("opml") && (e.getChild("head") == null || e.getChild("head").getChild("docs") == null) + && (e.getAttributeValue("version") == null || e.getAttributeValue("version").equals("1.0"))) { + return true; + } + + return false; + } + + /** + * Parses an XML document (JDOM Document) into a feed bean. + *

+ * + * @param document XML document (JDOM) to parse. + * @param validate indicates if the feed should be strictly validated (NOT YET IMPLEMENTED). + * @return the resulting feed bean. + * @throws IllegalArgumentException thrown if the parser cannot handle the given feed type. + * @throws FeedException thrown if a feed bean cannot be created out of the XML document (JDOM). + */ + @Override + public WireFeed parse(final Document document, final boolean validate, final Locale locale) throws IllegalArgumentException, FeedException { + final Opml opml = new Opml(); + opml.setFeedType("opml_1.0"); + + final Element root = document.getRootElement(); + final Element head = root.getChild("head"); + + if (head != null) { + opml.setTitle(head.getChildText("title")); + + if (head.getChildText("dateCreated") != null) { + opml.setCreated(DateParser.parseRFC822(head.getChildText("dateCreated"), Locale.US)); + } + + if (head.getChildText("dateModified") != null) { + opml.setModified(DateParser.parseRFC822(head.getChildText("dateModified"), Locale.US)); + } + + opml.setOwnerName(head.getChildTextTrim("ownerName")); + opml.setOwnerEmail(head.getChildTextTrim("ownerEmail")); + opml.setVerticalScrollState(readInteger(head.getChildText("vertScrollState"))); + + try { + opml.setWindowBottom(readInteger(head.getChildText("windowBottom"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse windowBottom", nfe); + if (validate) { + throw new FeedException("Unable to parse windowBottom", nfe); + } + } + + try { + opml.setWindowLeft(readInteger(head.getChildText("windowLeft"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse windowLeft", nfe); + if (validate) { + throw new FeedException("Unable to parse windowLeft", nfe); + } + } + + try { + opml.setWindowRight(readInteger(head.getChildText("windowRight"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse windowRight", nfe); + if (validate) { + throw new FeedException("Unable to parse windowRight", nfe); + } + } + + try { + opml.setWindowLeft(readInteger(head.getChildText("windowLeft"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse windowLeft", nfe); + if (validate) { + throw new FeedException("Unable to parse windowLeft", nfe); + } + } + + try { + opml.setWindowTop(readInteger(head.getChildText("windowTop"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse windowTop", nfe); + if (validate) { + throw new FeedException("Unable to parse windowTop", nfe); + } + } + + try { + opml.setExpansionState(readIntArray(head.getChildText("expansionState"))); + } catch (final NumberFormatException nfe) { + LOG.warn("Unable to parse expansionState", nfe); + if (validate) { + throw new FeedException("Unable to parse expansionState", nfe); + } + } + } + + opml.setOutlines(parseOutlines(root.getChild("body").getChildren("outline"), validate, locale)); + opml.setModules(parseFeedModules(root, locale)); + + return opml; + } + + protected Outline parseOutline(final Element e, final boolean validate, final Locale locale) throws FeedException { + if (!e.getName().equals("outline")) { + throw new RuntimeException("Not an outline element."); + } + + final Outline outline = new Outline(); + outline.setText(e.getAttributeValue("text")); + outline.setType(e.getAttributeValue("type")); + outline.setTitle(e.getAttributeValue("title")); + + final List jAttributes = e.getAttributes(); + final ArrayList attributes = new ArrayList(); + + for (int i = 0; i < jAttributes.size(); i++) { + final org.jdom2.Attribute a = jAttributes.get(i); + + if (!a.getName().equals("isBreakpoint") && !a.getName().equals("isComment") && !a.getName().equals("title") && !a.getName().equals("text") + && !a.getName().equals("type")) { + attributes.add(new Attribute(a.getName(), a.getValue())); + } + } + + outline.setAttributes(attributes); + + try { + outline.setBreakpoint(readBoolean(e.getAttributeValue("isBreakpoint"))); + } catch (final Exception ex) { + LOG.warn("Unable to parse isBreakpoint value", ex); + + if (validate) { + throw new FeedException("Unable to parse isBreakpoint value", ex); + } + } + + try { + outline.setComment(readBoolean(e.getAttributeValue("isComment"))); + } catch (final Exception ex) { + LOG.warn("Unable to parse isComment value", ex); + + if (validate) { + throw new FeedException("Unable to parse isComment value", ex); + } + } + + final List children = e.getChildren("outline"); + outline.setModules(parseItemModules(e, locale)); + outline.setChildren(parseOutlines(children, validate, locale)); + + return outline; + } + + protected List parseOutlines(final List elements, final boolean validate, final Locale locale) throws FeedException { + final ArrayList results = new ArrayList(); + for (int i = 0; i < elements.size(); i++) { + results.add(parseOutline(elements.get(i), validate, locale)); + } + return results; + } + + protected boolean readBoolean(final String value) { + if (value == null) { + return false; + } else { + return Boolean.getBoolean(value.trim()); + } + } + + protected int[] readIntArray(final String value) { + if (value == null) { + return null; + } else { + final StringTokenizer tok = new StringTokenizer(value, ","); + final int[] result = new int[tok.countTokens()]; + int count = 0; + + while (tok.hasMoreElements()) { + result[count] = Integer.parseInt(tok.nextToken().trim()); + count++; + } + + return result; + } + } + + protected Integer readInteger(final String value) { + if (value != null) { + return new Integer(value); + } else { + return null; + } + } +} diff --git a/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java b/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java new file mode 100644 index 0000000..96ff4ae --- /dev/null +++ b/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java @@ -0,0 +1,106 @@ +package com.rometools.opml.io.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +import org.jdom2.Document; +import org.jdom2.Element; + +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.impl.DateParser; + +/** + * Generator for OPML 2.0 documents. + * + * @see http://dev.opml.org/spec2.html + */ +public class OPML20Generator extends OPML10Generator { + + public OPML20Generator() { + } + + /** + * Returns the type of feed the generator creates. + * + * @return the type of feed the generator creates. + * @see WireFeed for details on the format of this string. + */ + @Override + public String getType() { + return "opml_2.0"; + } + + /** + * Creates an XML document (JDOM) for the given feed bean. + * + * @param feed the feed bean to generate the XML document from. + * @return the generated XML document (JDOM). + * @throws IllegalArgumentException thrown if the type of the given feed bean does not match with the type of the + * WireFeedGenerator. + * @throws FeedException thrown if the XML Document could not be created. + */ + @Override + public Document generate(final WireFeed feed) throws IllegalArgumentException, FeedException { + final Document document = super.generate(feed); + document.getRootElement().setAttribute("version", "2.0"); + return document; + } + + @Override + protected Element generateHead(final Opml opml) { + + final Element docsElement = new Element("docs"); + docsElement.setText(opml.getDocs()); + + final Element headElement = super.generateHead(opml); + headElement.addContent(docsElement); + return headElement; + + } + + @Override + protected Element generateOutline(final Outline outline) { + + final Element outlineElement = super.generateOutline(outline); + + if (outline.getCreated() != null) { + outlineElement.setAttribute("created", DateParser.formatRFC822(outline.getCreated(), Locale.US)); + } + + final List categories = outline.getCategories(); + final String categoryValue = generateCategoryValue(categories); + addNotNullAttribute(outlineElement, "category", categoryValue); + + return outlineElement; + + } + + private String generateCategoryValue(final Collection categories) { + + final StringBuilder builder = new StringBuilder(); + + boolean first = true; + for (final String category : categories) { + if (category != null && !category.trim().isEmpty()) { + if (first) { + first = false; + } else { + builder.append(","); + } + builder.append(category.trim()); + } + } + + if (builder.length() > 0) { + return builder.toString(); + } else { + return null; + } + + } + +} diff --git a/src/main/java/com/rometools/opml/io/impl/OPML20Parser.java b/src/main/java/com/rometools/opml/io/impl/OPML20Parser.java new file mode 100644 index 0000000..fa56857 --- /dev/null +++ b/src/main/java/com/rometools/opml/io/impl/OPML20Parser.java @@ -0,0 +1,120 @@ +/* + * Opml20Parser.java + * + * Created on April 25, 2006, 1:04 AM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.rometools.opml.io.impl; + +import java.util.List; +import java.util.Locale; + +import org.jdom2.Document; +import org.jdom2.Element; + +import com.rometools.opml.feed.opml.Attribute; +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.impl.DateParser; + +/** + * + * @author cooper + */ +public class OPML20Parser extends OPML10Parser { + /** Creates a new instance of Opml20Parser */ + public OPML20Parser() { + super("opml_2.0"); + } + + /** + * Inspects an XML Document (JDOM) to check if it can parse it. + *

+ * It checks if the given document if the type of feeds the parser understands. + *

+ * + * @param document XML Document (JDOM) to check if it can be parsed by this parser. + * @return true if the parser know how to parser this feed, false otherwise. + */ + @Override + public boolean isMyType(final Document document) { + final Element e = document.getRootElement(); + + if (e.getName().equals("opml") + && (e.getChild("head") != null && e.getChild("head").getChild("docs") != null || e.getAttributeValue("version") != null + && e.getAttributeValue("version").equals("2.0") || e.getChild("head") != null && e.getChild("head").getChild("ownerId") != null)) { + return true; + } + + return false; + } + + /** + * Parses an XML document (JDOM Document) into a feed bean. + *

+ * + * @param document XML document (JDOM) to parse. + * @param validate indicates if the feed should be strictly validated (NOT YET IMPLEMENTED). + * @return the resulting feed bean. + * @throws IllegalArgumentException thrown if the parser cannot handle the given feed type. + * @throws FeedException thrown if a feed bean cannot be created out of the XML document (JDOM). + */ + @Override + public WireFeed parse(final Document document, final boolean validate, final Locale locale) throws IllegalArgumentException, FeedException { + Opml opml; + opml = (Opml) super.parse(document, validate, locale); + + final Element head = document.getRootElement().getChild("head"); + + if (head != null) { + opml.setOwnerId(head.getChildTextTrim("ownerId")); + opml.setDocs(head.getChildTextTrim("docs")); + + if (opml.getDocs() == null) { + opml.setDocs("http://www.opml.org/spec2"); + } + } + + opml.setFeedType("opml_2.0"); + + return opml; + } + + @Override + protected Outline parseOutline(final Element e, final boolean validate, final Locale locale) throws FeedException { + Outline retValue; + + retValue = super.parseOutline(e, validate, locale); + + if (e.getAttributeValue("created") != null) { + retValue.setCreated(DateParser.parseRFC822(e.getAttributeValue("created"), locale)); + } + + final List atts = retValue.getAttributes(); + + for (int i = 0; i < atts.size(); i++) { + final Attribute a = atts.get(i); + + if (a.getName().equals("created")) { + retValue.getAttributes().remove(a); + + break; + } + } + + return retValue; + } +} diff --git a/src/main/java/com/rometools/opml/io/impl/package.html b/src/main/java/com/rometools/opml/io/impl/package.html new file mode 100644 index 0000000..64d1989 --- /dev/null +++ b/src/main/java/com/rometools/opml/io/impl/package.html @@ -0,0 +1,28 @@ + + + + + + + + + This package contains the feed parsers for OPML1 and OPML2. + + +

+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+    
+ + + diff --git a/src/main/resources/rome.properties b/src/main/resources/rome.properties new file mode 100644 index 0000000..f57e9e3 --- /dev/null +++ b/src/main/resources/rome.properties @@ -0,0 +1,22 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +WireFeedGenerator.classes=com.rometools.opml.io.impl.OPML10Generator \ + com.rometools.opml.io.impl.OPML20Generator + +WireFeedParser.classes=com.rometools.opml.io.impl.OPML10Parser \ + com.rometools.opml.io.impl.OPML20Parser + +Converter.classes=com.rometools.opml.feed.synd.impl.ConverterForOPML10 \ + com.rometools.opml.feed.synd.impl.ConverterForOPML20 diff --git a/src/site/apt/index.apt b/src/site/apt/index.apt new file mode 100644 index 0000000..0e7e427 --- /dev/null +++ b/src/site/apt/index.apt @@ -0,0 +1,125 @@ + ----- + Home + ----- + kwebble + ----- + 2011-09-23 14:15:28.442 + ----- + +ROME OPML + + This project provides support for {{{http://www.opml.org/}OPML}} in ROME. + +*Downloads + + * {{{./apidocs/index.html}JavaDocs}} + + [] + +*Sample Usage + + To use this parser, simply include the jar file in your classpath as you are using ROME. Be sure it exists at the same level as ROME, + such that, if ROME is in the common classpath of an application server, don't include this jar in your webapps WEB\-INF/lib. + ++------+ +WireFeedInput input = new WireFeedInput(); + Opml feed = (Opml) input.build( new File("myOpml.xml") ); + List outlines = (List) feed.getOutlines(); ++------+ + +*Hierarchy vs Flat + + Since OPML is a hierarchical format, some magic is required to preserve this information when they feed is moved to a Synd\* structure. + This is accomplished by adding categories indicating the tree structure to the {{{http://wiki.java.net/bin/edit/Javawsxml/SyndEntries?topicparent\=Javawsxml.OPML;nowysiwyg\=0}SyndEntries}} ... + + For example: + ++------+ + + + Top Ten Sources for podcasting + Newsilike Media Group + opml@TopTenSources.com + + + + + + + + + + + + + + + + + + + + ++------+ + + ...\ + When converted to RSS2 becomes: + ++------+ + + Top Ten Sources for podcasting + http://foo.com + + Newsilike Media Group + + TopTenSources: podcasting + http://podcasting.TopTenSources.com/TopTenSources/ + node.-1732517202 + http://podcasting.TopTenSources.com/TopTenSources/ + http://podcasting.TopTenSources.com/TopTenSources/ + + + CBS Technology News Podcast - Larry Magid' Tech Report + node.1353657827 + + + Larry Magid's Tech Report + http://www.cbsnews.com + node.-4085850 + parent.1353657827 + http://www.cbsnews.com + http://www.cbsnews.com + + + Adam Curry: Daily Source Code + node.835791399 + + + #374 Daily Source Code for Tuesday April 25th 2006 + http://radio.weblogs.com/0001014/2006/04/26.html#a7304 + node.222050897 + parent.835791399 + http://radio.weblogs.com/0001014/2006/04/26.html#a7304 + http://radio.weblogs.com/0001014/2006/04/26.html#a7304 + + + #373 Daily Source Code for Monday April 24th 2006 + http://radio.weblogs.com/0001014/2006/04/24.html#a7303 + node.2088220478 + parent.835791399 + http://radio.weblogs.com/0001014/2006/04/24.html#a7303 + http://radio.weblogs.com/0001014/2006/04/24.html#a7303 + ++------+ + + Nodes get categories with the "urn:rome.tree" URI that is used to maintain the tree structure. + + The other thing you will notice is the "urn:rome.attribute#url". Since OPML allows you to add arbitrary attributes to each outline element, these are used to preserve these values. + + \ No newline at end of file diff --git a/src/site/resources/.nojekyll b/src/site/resources/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css new file mode 100644 index 0000000..43c3cd8 --- /dev/null +++ b/src/site/resources/css/site.css @@ -0,0 +1,8 @@ +h1 { + padding: 4px 4px 4px 6px; + border: 1px solid #999; + color: #900; + background-color: #ddd; + font-weight:900; + font-size: x-large; +} \ No newline at end of file diff --git a/src/site/resources/images/romelogo.png b/src/site/resources/images/romelogo.png new file mode 100644 index 0000000..2c90608 Binary files /dev/null and b/src/site/resources/images/romelogo.png differ diff --git a/src/site/site.xml b/src/site/site.xml new file mode 100644 index 0000000..5b4d4a7 --- /dev/null +++ b/src/site/site.xml @@ -0,0 +1,30 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + 1.3.0 + + + + ROME + images/romelogo.png + http://github.com/rometools/ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/rometools/opml/FeedOpsTest.java b/src/test/java/com/rometools/opml/FeedOpsTest.java new file mode 100644 index 0000000..5eb5f61 --- /dev/null +++ b/src/test/java/com/rometools/opml/FeedOpsTest.java @@ -0,0 +1,146 @@ +package com.rometools.opml; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier; +import org.custommonkey.xmlunit.XMLAssert; +import org.custommonkey.xmlunit.XMLUnit; +import org.jdom2.Document; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; + +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; +import com.rometools.rome.io.WireFeedOutput; + +/** + * + *

+ * + * @author Alejandro Abdelnur + * + */ +public abstract class FeedOpsTest extends FeedTest { + + protected FeedOpsTest(final String feedType) { + super(feedType + ".xml"); + new File("target/test-reports").mkdirs(); + } + + // 1.2a + public void testWireFeedEquals() throws Exception { + final WireFeed feed1 = getCachedWireFeed(); + final WireFeed feed2 = getWireFeed(); + assertTrue(feed1.equals(feed2)); + } + + // 1.2b + public void testWireFeedNotEqual() throws Exception { + final WireFeed feed1 = getCachedWireFeed(); + final WireFeed feed2 = getWireFeed(); + feed2.setFeedType("dummy"); + assertFalse(feed1.equals(feed2)); + } + + // 1.3 + public void testWireFeedCloning() throws Exception { + final WireFeed feed1 = getCachedWireFeed(); + final WireFeed feed2 = (WireFeed) feed1.clone(); + ; + assertTrue(feed1.equals(feed2)); + } + + // 1.4 + public void testWireFeedSerialization() throws Exception { + final WireFeed feed1 = getCachedWireFeed(); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(feed1); + oos.close(); + + final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream ois = new ObjectInputStream(bais); + final WireFeed feed2 = (WireFeed) ois.readObject(); + ois.close(); + + assertTrue(feed1.equals(feed2)); + } + + // 1.5 + public void testWireFeedJDOMSerialization() throws Exception { + Document inputDoc = getCachedJDomDoc(); + + final WireFeed feed = getCachedWireFeed(); + WireFeedOutput output = new WireFeedOutput(); + Document outputDoc = output.outputJDom(feed); + + XMLOutputter outputter = new XMLOutputter(Format.getCompactFormat()); + String inputString = outputter.outputString(inputDoc); + String outputString = outputter.outputString(outputDoc); + + XMLUnit.setIgnoreWhitespace(true); + XMLUnit.setIgnoreAttributeOrder(true); + Diff diff = XMLUnit.compareXML(inputString, outputString); + // ignore elements order + diff.overrideElementQualifier(new ElementNameAndAttributeQualifier()); + + XMLAssert.assertXMLEqual(diff, true); + } + + // 1.6 + public void testWireFeedSyndFeedConversion() throws Exception { + final SyndFeed sFeed1 = getCachedSyndFeed(); + final WireFeed wFeed1 = sFeed1.createWireFeed(); + final SyndFeed sFeed2 = new SyndFeedImpl(wFeed1); + + assertEquals(sFeed1, sFeed2); + } + + // 1.7a + public void testSyndFeedEquals() throws Exception { + final SyndFeed feed1 = getCachedSyndFeed(); + final SyndFeed feed2 = getSyndFeed(); + assertTrue(feed1.equals(feed2)); + } + + // 1.7b + public void testSyndFeedNotEqual() throws Exception { + final SyndFeed feed1 = getCachedSyndFeed(); + final SyndFeed feed2 = getSyndFeed(); + feed2.setFeedType("dummy"); + assertFalse(feed1.equals(feed2)); + } + + // 1.8 + public void testSyndFeedCloning() throws Exception { + final SyndFeed feed1 = getCachedSyndFeed(); + final SyndFeed feed2 = (SyndFeed) feed1.clone(); + ; + assertTrue(feed1.equals(feed2)); + } + + // 1.9 + public void testSyndFeedSerialization() throws Exception { + final SyndFeed feed1 = getCachedSyndFeed(); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(feed1); + oos.close(); + + final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream ois = new ObjectInputStream(bais); + final SyndFeed feed2 = (SyndFeed) ois.readObject(); + ois.close(); + + assertTrue(feed1.equals(feed2)); + } + +} diff --git a/src/test/java/com/rometools/opml/FeedTest.java b/src/test/java/com/rometools/opml/FeedTest.java new file mode 100644 index 0000000..a2919e9 --- /dev/null +++ b/src/test/java/com/rometools/opml/FeedTest.java @@ -0,0 +1,79 @@ +package com.rometools.opml; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import junit.framework.TestCase; + +import org.jdom2.Document; +import org.jdom2.input.SAXBuilder; +import org.jdom2.input.sax.XMLReaders; + +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.io.SyndFeedInput; +import com.rometools.rome.io.WireFeedInput; + +/** + * @author pat, tucu + * + */ +public abstract class FeedTest extends TestCase { + + private final String fileName; + private Document jDomDoc = null; + private WireFeed wireFeed = null; + private SyndFeed syndFeed = null; + + protected FeedTest(final String feedFileName) { + fileName = feedFileName; + } + + protected String getFeedFileName() { + return fileName; + } + + protected Reader getFeedReader() throws Exception { + final InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(getFeedFileName()); + assertNotNull("Could not find resource " + getFeedFileName(), resource); + return new InputStreamReader(resource); + } + + protected Document getJDomDoc() throws Exception { + final SAXBuilder saxBuilder = new SAXBuilder(XMLReaders.NONVALIDATING); + return saxBuilder.build(getFeedReader()); + } + + protected WireFeed getWireFeed() throws Exception { + final WireFeedInput in = new WireFeedInput(); + return in.build(getFeedReader()); + } + + protected SyndFeed getSyndFeed() throws Exception { + final SyndFeedInput in = new SyndFeedInput(); + return in.build(getFeedReader()); + } + + protected Document getCachedJDomDoc() throws Exception { + if (jDomDoc == null) { + jDomDoc = getJDomDoc(); + } + return jDomDoc; + } + + protected WireFeed getCachedWireFeed() throws Exception { + if (wireFeed == null) { + wireFeed = getWireFeed(); + } + return wireFeed; + } + + protected SyndFeed getCachedSyndFeed() throws Exception { + if (syndFeed == null) { + syndFeed = getSyndFeed(); + } + return syndFeed; + } + +} diff --git a/src/test/java/com/rometools/opml/SyndFeedTest.java b/src/test/java/com/rometools/opml/SyndFeedTest.java new file mode 100644 index 0000000..09bc640 --- /dev/null +++ b/src/test/java/com/rometools/opml/SyndFeedTest.java @@ -0,0 +1,136 @@ +package com.rometools.opml; + +/** + * @author pat + * + */ +public abstract class SyndFeedTest extends FeedTest { + private String _prefix = null; + + protected SyndFeedTest(final String feedType) { + this(feedType, feedType + ".xml"); + } + + protected SyndFeedTest(final String feedType, final String feedFileName) { + super(feedFileName); + _prefix = feedType; + } + + protected String getPrefix() { + return _prefix; + } + + protected void assertProperty(final String property, final String value) { + assertEquals(property, getPrefix() + "." + value); + } + + public void testType() throws Exception { + assertEquals(getCachedSyndFeed().getFeedType(), getPrefix()); + } + + /* + * public void testType() throws Exception { assertEquals(getPrefix(), + * getCachedSyndFeed().getFeedType()); } public void testTitle() throws Exception { + * assertEqualsStr("channel.title", getCachedSyndFeed().getTitle()); } public void testLink() + * throws Exception { assertEqualsStr("channel.link", getCachedSyndFeed().getLink()); } public + * void testDescription() throws Exception { assertEqualsStr("channel.description", + * getCachedSyndFeed().getDescription()); } public void testLanguage() throws Exception { + * assertEqualsStr("channel.language", getCachedSyndFeed().getLanguage()); } public void + * testCategories() throws Exception { List catlist = getCachedSyndFeed().getCategories(); + * //don't understand why this one fails assertEquals(2, catlist.size()); SyndCategory cat = + * (SyndCategory)catlist.get(0); assertEqualsStr("channel.category[0]", cat.getName()); + * assertEqualsStr("channel.category[0]^domain", cat.getTaxonomyUri()); cat = + * (SyndCategory)catlist.get(1); assertEqualsStr("channel.category[1]", cat.getName()); + * assertEqualsStr("channel.category[1]^domain", cat.getTaxonomyUri()); } public void + * testPublishedDate() throws Exception { + * assertEquals(DateParser.parseRFC822("Mon, 01 Jan 2001 00:00:00 GMT"), + * getCachedSyndFeed().getPublishedDate()); } //how do i get height and width? public void + * testImage() throws Exception { SyndImage img = getCachedSyndFeed().getImage(); + * assertEqualsStr("channel.image.description", img.getDescription()); + * assertEqualsStr("channel.image.link", img.getLink()); assertEqualsStr("channel.image.title", + * img.getTitle()); assertEqualsStr("channel.image.url", img.getUrl()); } public void + * testEntries() throws Exception { List entrylist = getCachedSyndFeed().getEntries(); + * assertEquals(2, entrylist.size()); } public void testEntryTitle() throws Exception { + * assertEqualsStr("channel.item[0].title", + * getEntryTitle(getCachedSyndFeed().getEntries().get(0))); + * assertEqualsStr("channel.item[1].title", + * getEntryTitle(getCachedSyndFeed().getEntries().get(1))); } public String getEntryTitle(Object + * o) throws Exception { SyndEntry e = (SyndEntry) o; return e.getTitle(); } public void + * testEntryDescription() throws Exception { assertEqualsStr("channel.item[0].description", + * getEntryDescription(getCachedSyndFeed().getEntries().get(0))); + * assertEqualsStr("channel.item[1].description", + * getEntryDescription(getCachedSyndFeed().getEntries().get(1))); } public String + * getEntryDescription(Object o) throws Exception { SyndEntry e = (SyndEntry) o; return + * e.getDescription().getValue(); } public void testEntryLink() throws Exception { + * assertEqualsStr("channel.item[0].link", + * getEntryLink(getCachedSyndFeed().getEntries().get(0))); + * assertEqualsStr("channel.item[1].link", + * getEntryLink(getCachedSyndFeed().getEntries().get(1))); } public String getEntryLink(Object + * o) { SyndEntry e = (SyndEntry) o; return e.getLink(); } public void testEntryPublishedDate() + * throws Exception { assertEquals(DateParser.parseRFC822("Mon, 01 Jan 2001 00:00:00 GMT"), + * getEntryPublishedDate(getCachedSyndFeed().getEntries().get(0))); + * assertEquals(DateParser.parseRFC822("Mon, 01 Jan 2001 00:00:00 GMT"), + * getEntryPublishedDate(getCachedSyndFeed().getEntries().get(1))); } public Date + * getEntryPublishedDate(Object o) { SyndEntry e = (SyndEntry) o; return e.getPublishedDate(); } + * public void testEntryCategories() throws Exception { SyndEntry e = + * (SyndEntry)getCachedSyndFeed().getEntries().get(0); List catlist = e.getCategories(); //don't + * understand why this one fails assertEquals(2, catlist.size()); SyndCategory cat = + * (SyndCategory)catlist.get(0); assertEqualsStr("channel.item[0].category[0]", cat.getName()); + * assertEqualsStr("channel.item[0].category[0]^domain", cat.getTaxonomyUri()); cat = + * (SyndCategory)catlist.get(1); assertEqualsStr("channel.item[0].category[1]", cat.getName()); + * assertEqualsStr("channel.item[0].category[1]^domain", cat.getTaxonomyUri()); //DO 2nd set of + * items } public void testEntryAuthor() throws Exception { + * assertEqualsStr("channel.item[0].author", + * getEntryAuthor(getCachedSyndFeed().getEntries().get(0))); + * assertEqualsStr("channel.item[1].author", + * getEntryAuthor(getCachedSyndFeed().getEntries().get(1))); } public String + * getEntryAuthor(Object o) { SyndEntry e = (SyndEntry) o; return e.getAuthor(); } + */ + /* + * //things you cannot get from SyndEntryImpl // item[0].source // // + * + * item0.category0 item0.category1 Thu, 08 Jul 1999 08:00:00 + * GMT Thu, 08 Jul 1999 09:00:00 GMT + * item0.author http://localhost:8080/item0/comments http://localhost:8080/item0/guid //TODO: I still have the elements + * to test + */ + /* + * public void test() { assertEqualsStr(feed, ""); } public void test() { assertEqualsStr(feed, + * ""); } + */ + // Things that you cannot get form a SyndFeedImpl today + // these need to be put in a RSS 2.0 module + // or is a roundtrip to wirefeed the right way to do this? + /* + * Search Search this site: q + * http://example.org/mt/mt-search.cgi image height and width + * //Copyright 2004, Mark Pilgrim public void test() { + * assertEqualsStr(getCachedSyndFeed()., ""); } //Sample Toolkit public + * void test() { assertEqualsStr(feed, ""); } // + * editor@example.org public void test() { + * assertEqualsStr(feed, ""); } // webmaster@example.org public void + * test() { assertEqualsStr(feed, ""); } http://blogs.law.harvard.edu/tech/rss + * 60 0 1 + * 2 3 4 5 6 7 + * 8 9.5 10 11 12 + * 13 14 15 16 17 + * 18 19 20 21 22 + * 23 Monday Tuesday + * Wednesday Thursday Friday Saturday + * Sunday + */ + + /* + * @see TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/rometools/opml/TestOpsOPML10.java b/src/test/java/com/rometools/opml/TestOpsOPML10.java new file mode 100644 index 0000000..7f78a80 --- /dev/null +++ b/src/test/java/com/rometools/opml/TestOpsOPML10.java @@ -0,0 +1,46 @@ +/* + * TestOpsOPML10.java + * + * Created on April 25, 2006, 4:26 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package com.rometools.opml; + +import java.io.FileOutputStream; +import java.io.PrintWriter; + +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; + +/** + * + * @author cooper + */ +public class TestOpsOPML10 extends FeedOpsTest { + + /** Creates a new instance of TestOpsOPML10 */ + public TestOpsOPML10() { + super("opml_1.0"); + } + + // 1.6 + @Override + public void testWireFeedSyndFeedConversion() throws Exception { + final SyndFeed sFeed1 = getCachedSyndFeed(); + final WireFeed wFeed1 = sFeed1.createWireFeed(); + final SyndFeed sFeed2 = new SyndFeedImpl(wFeed1); + PrintWriter w = new PrintWriter(new FileOutputStream("target/test-reports/1")); + w.println(sFeed1.toString()); + w.close(); + w = new PrintWriter(new FileOutputStream("target/test-reports/2")); + w.println(sFeed2.toString()); + w.close(); + + assertEquals(sFeed1, sFeed2); + } + +} diff --git a/src/test/java/com/rometools/opml/TestOpsOPML10links.java b/src/test/java/com/rometools/opml/TestOpsOPML10links.java new file mode 100644 index 0000000..33c6216 --- /dev/null +++ b/src/test/java/com/rometools/opml/TestOpsOPML10links.java @@ -0,0 +1,63 @@ +/* + * TestOpsOPML10.java + * + * Created on April 25, 2006, 4:26 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package com.rometools.opml; + +import java.io.FileOutputStream; +import java.io.PrintWriter; + +import com.rometools.opml.test.NullWriter; +import com.rometools.opml.test.TestUtil; +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; +import com.rometools.rome.io.WireFeedInput; +import com.rometools.rome.io.WireFeedOutput; + +/** + * + * @author cooper + */ +public class TestOpsOPML10links extends FeedOpsTest { + + /** Creates a new instance of TestOpsOPML10 */ + public TestOpsOPML10links() { + super("opml_1.0_links"); + } + + // 1.6 + @Override + public void testWireFeedSyndFeedConversion() throws Exception { + final SyndFeed sFeed1 = getCachedSyndFeed(); + final WireFeed wFeed1 = sFeed1.createWireFeed(); + final SyndFeed sFeed2 = new SyndFeedImpl(wFeed1); + PrintWriter w = new PrintWriter(new FileOutputStream("target/test-reports/1")); + w.println(sFeed1.toString()); + w.close(); + w = new PrintWriter(new FileOutputStream("target/test-reports/2")); + w.println(sFeed2.toString()); + w.close(); + + assertEquals(sFeed2.createWireFeed(), sFeed1.createWireFeed()); + } + + public void testTemp() throws Exception { + final WireFeedInput input = new WireFeedInput(); + final WireFeed wf = input.build(TestUtil.loadFile("/opml_1.0_links.xml")); + final WireFeedOutput output = new WireFeedOutput(); + + final SyndFeedImpl sf = new SyndFeedImpl(wf); + sf.setFeedType("rss_2.0"); + sf.setDescription(""); + sf.setLink("http://foo.com"); + sf.setFeedType("opml_1.0"); + output.output(sf.createWireFeed(), new NullWriter()); + } + +} diff --git a/src/test/java/com/rometools/opml/TestOpsOPML20.java b/src/test/java/com/rometools/opml/TestOpsOPML20.java new file mode 100644 index 0000000..32f8348 --- /dev/null +++ b/src/test/java/com/rometools/opml/TestOpsOPML20.java @@ -0,0 +1,44 @@ +/* + * TestOpsOPML20.java + * + * Created on April 25, 2006, 5:38 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package com.rometools.opml; + +import java.io.FileOutputStream; +import java.io.PrintWriter; + +import com.rometools.rome.feed.WireFeed; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; + +/** + * + * @author cooper + */ +public class TestOpsOPML20 extends FeedOpsTest { + + /** Creates a new instance of TestOpsOPML20 */ + public TestOpsOPML20() { + super("opml_2.0"); + } + + @Override + public void testWireFeedSyndFeedConversion() throws Exception { + final SyndFeed sFeed1 = getCachedSyndFeed(); + final WireFeed wFeed1 = sFeed1.createWireFeed(); + final SyndFeed sFeed2 = new SyndFeedImpl(wFeed1); + PrintWriter w = new PrintWriter(new FileOutputStream("target/test-reports/3")); + w.println(sFeed1.toString()); + w.close(); + w = new PrintWriter(new FileOutputStream("target/test-reports/4")); + w.println(sFeed2.toString()); + w.close(); + assertEquals(sFeed1, sFeed2); + } + +} diff --git a/src/test/java/com/rometools/opml/TestXmlFixerReader.java b/src/test/java/com/rometools/opml/TestXmlFixerReader.java new file mode 100644 index 0000000..eea78af --- /dev/null +++ b/src/test/java/com/rometools/opml/TestXmlFixerReader.java @@ -0,0 +1,139 @@ +/* + * Copyright 2004 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.rometools.opml; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +import junit.framework.TestCase; + +import org.jdom2.input.SAXBuilder; + +import com.rometools.rome.io.XmlReader; +import com.rometools.rome.io.impl.XmlFixerReader; + +/** + * @author pat, tucu + * + */ +public class TestXmlFixerReader extends TestCase { + private static final String XML_PROLOG = ""; + + public void testTrim() throws Exception { + _testValidTrim("", ""); + _testValidTrim("", XML_PROLOG + ""); + _testValidTrim(" ", ""); + _testValidTrim(" ", XML_PROLOG + ""); + _testValidTrim(" \n", ""); + _testValidTrim(" \n", XML_PROLOG + ""); + _testValidTrim("", ""); + _testValidTrim("", XML_PROLOG + ""); + _testValidTrim(" ", ""); + _testValidTrim(" ", XML_PROLOG + ""); + _testValidTrim(" ", ""); + _testValidTrim(" ", XML_PROLOG + ""); + _testValidTrim(" ", ""); + _testValidTrim(" ", XML_PROLOG + ""); + _testValidTrim(" \n ", ""); + _testValidTrim(" \n ", XML_PROLOG + ""); + + _testInvalidTrim("x", ""); + _testInvalidTrim("x", XML_PROLOG + ""); + _testInvalidTrim(" x", ""); + _testInvalidTrim(" x", XML_PROLOG + ""); + _testInvalidTrim(" x\n", ""); + _testInvalidTrim(" x\n", XML_PROLOG + ""); + _testInvalidTrim("x ", ""); + _testInvalidTrim(" x ", XML_PROLOG + ""); + _testInvalidTrim(" x ", ""); + _testInvalidTrim(" x ", XML_PROLOG + ""); + _testInvalidTrim(" x\n ", ""); + _testInvalidTrim(" x\n ", XML_PROLOG + ""); + } + + public void testHtmlEntities() throws Exception { + _testValidEntities(""); + _testValidEntities(XML_PROLOG + ""); + _testValidEntities(" \n" + XML_PROLOG + ""); + + _testValidEntities("'¥ú¥"); + _testValidEntities(XML_PROLOG + "'¥ú¥"); + _testValidEntities(" \n" + XML_PROLOG + "'¥ú¥"); + + _testInvalidEntities("'&yexn;ú¥"); + _testInvalidEntities(XML_PROLOG + "'&yexn;ú¥"); + _testInvalidEntities(" \n" + XML_PROLOG + "'&yexn;ú¥"); + + _testInvalidEntities("'¥x50;¥"); + _testInvalidEntities(XML_PROLOG + "'¥x50;¥"); + _testInvalidEntities(" \n" + XML_PROLOG + "'¥x50;¥"); + + } + + protected void _testXmlParse(final String garbish, final String xmlDoc) throws Exception { + final InputStream is = getStream(garbish, xmlDoc); + Reader reader = new XmlReader(is); + reader = new XmlFixerReader(reader); + final SAXBuilder saxBuilder = new SAXBuilder(); + saxBuilder.build(reader); + } + + protected void _testValidTrim(final String garbish, final String xmlDoc) throws Exception { + _testXmlParse(garbish, xmlDoc); + } + + protected void _testInvalidTrim(final String garbish, final String xmlDoc) throws Exception { + try { + _testXmlParse(garbish, xmlDoc); + assertTrue(false); + } catch (final Exception ex) { + } + } + + protected void _testValidEntities(final String xmlDoc) throws Exception { + _testXmlParse("", xmlDoc); + } + + protected void _testInvalidEntities(final String xmlDoc) throws Exception { + try { + _testXmlParse("", xmlDoc); + assertTrue(false); + } catch (final Exception ex) { + } + } + + // XML Stream generator + + protected InputStream getStream(final String garbish, final String xmlDoc) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + final Writer writer = new OutputStreamWriter(baos); + writer.write(garbish); + writer.write(xmlDoc); + writer.close(); + return new ByteArrayInputStream(baos.toByteArray()); + } + +} diff --git a/src/test/java/com/rometools/opml/TestXmlReader.java b/src/test/java/com/rometools/opml/TestXmlReader.java new file mode 100644 index 0000000..a7137a0 --- /dev/null +++ b/src/test/java/com/rometools/opml/TestXmlReader.java @@ -0,0 +1,269 @@ +/* + * Copyright 2004 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.rometools.opml; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import com.rometools.rome.io.XmlReader; + +/** + * @author pat, tucu + * + */ +public class TestXmlReader extends TestCase { + + public static void main(final String[] args) throws Exception { + final TestXmlReader test = new TestXmlReader(); + test.testRawBom(); + test.testRawNoBom(); + test.testHttp(); + } + + protected void _testRawNoBomValid(final String encoding) throws Exception { + + // TODO review this test (XmlReader is never closed, test fails when using a new XmlReader + // for each Assert) + + InputStream is = getXmlStream("no-bom", "xml", encoding, encoding); + XmlReader xmlReader = new XmlReader(is, false); + assertEquals(xmlReader.getEncoding(), "UTF-8"); + xmlReader.close(); + + is = getXmlStream("no-bom", "xml-prolog", encoding, encoding); + xmlReader = new XmlReader(is); + assertEquals(xmlReader.getEncoding(), "UTF-8"); + xmlReader.close(); + + is = getXmlStream("no-bom", "xml-prolog-encoding", encoding, encoding); + xmlReader = new XmlReader(is); + assertEquals(xmlReader.getEncoding(), encoding); + xmlReader.close(); + + } + + protected void _testRawNoBomInvalid(final String encoding) throws Exception { + final InputStream is = getXmlStream("no-bom", "xml-prolog-encoding", encoding, encoding); + try { + final XmlReader xmlReader = new XmlReader(is, false); + fail("It should have failed"); + xmlReader.close(); + } catch (final IOException ex) { + assertTrue(ex.getMessage().indexOf("Invalid encoding,") > -1); + } + } + + public void testRawNoBom() throws Exception { + _testRawNoBomValid("US-ASCII"); + _testRawNoBomValid("UTF-8"); + _testRawNoBomValid("ISO-8859-1"); + } + + protected void _testRawBomValid(final String encoding) throws Exception { + final InputStream is = getXmlStream(encoding + "-bom", "xml-prolog-encoding", encoding, encoding); + final XmlReader xmlReader = new XmlReader(is, false); + if (!encoding.equals("UTF-16")) { + assertEquals(xmlReader.getEncoding(), encoding); + } else { + assertEquals(xmlReader.getEncoding().substring(0, encoding.length()), encoding); + } + xmlReader.close(); + } + + protected void _testRawBomInvalid(final String bomEnc, final String streamEnc, final String prologEnc) throws Exception { + final InputStream is = getXmlStream(bomEnc, "xml-prolog-encoding", streamEnc, prologEnc); + try { + final XmlReader xmlReader = new XmlReader(is, false); + fail("It should have failed for BOM " + bomEnc + ", streamEnc " + streamEnc + " and prologEnc " + prologEnc); + xmlReader.close(); + } catch (final IOException ex) { + assertTrue(ex.getMessage().indexOf("Invalid encoding,") > -1); + } + } + + public void testRawBom() throws Exception { + _testRawBomValid("UTF-8"); + _testRawBomValid("UTF-16BE"); + _testRawBomValid("UTF-16LE"); + _testRawBomValid("UTF-16"); + + _testRawBomInvalid("UTF-8-bom", "US-ASCII", "US-ASCII"); + _testRawBomInvalid("UTF-8-bom", "ISO-8859-1", "ISO-8859-1"); + _testRawBomInvalid("UTF-8-bom", "UTF-8", "UTF-16"); + _testRawBomInvalid("UTF-8-bom", "UTF-8", "UTF-16BE"); + _testRawBomInvalid("UTF-8-bom", "UTF-8", "UTF-16LE"); + _testRawBomInvalid("UTF-16BE-bom", "UTF-16BE", "UTF-16LE"); + _testRawBomInvalid("UTF-16LE-bom", "UTF-16LE", "UTF-16BE"); + _testRawBomInvalid("UTF-16LE-bom", "UTF-16LE", "UTF-8"); + } + + public void testHttp() throws Exception { + _testHttpValid("application/xml", "no-bom", "US-ASCII", null); + _testHttpValid("application/xml", "UTF-8-bom", "US-ASCII", null); + _testHttpValid("application/xml", "UTF-8-bom", "UTF-8", null); + _testHttpValid("application/xml", "UTF-8-bom", "UTF-8", "UTF-8"); + _testHttpValid("application/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", null); + _testHttpValid("application/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", "UTF-8"); + _testHttpValid("application/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", null); + _testHttpValid("application/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16"); + _testHttpValid("application/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE"); + + _testHttpInvalid("application/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", null); + _testHttpInvalid("application/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16"); + _testHttpInvalid("application/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE"); + _testHttpInvalid("application/xml", "UTF-8-bom", "US-ASCII", "US-ASCII"); + _testHttpInvalid("application/xml;charset=UTF-16", "UTF-16LE", "UTF-8", "UTF-8"); + _testHttpInvalid("application/xml;charset=UTF-16", "no-bom", "UTF-16BE", "UTF-16BE"); + + _testHttpValid("text/xml", "no-bom", "US-ASCII", null); + _testHttpValid("text/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", "UTF-8"); + _testHttpValid("text/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", null); + _testHttpValid("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", null); + _testHttpValid("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16"); + _testHttpValid("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE"); + _testHttpValid("text/xml", "UTF-8-bom", "US-ASCII", null); + + _testHttpInvalid("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", null); + _testHttpInvalid("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16"); + _testHttpInvalid("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE"); + _testHttpInvalid("text/xml;charset=UTF-16", "no-bom", "UTF-16BE", "UTF-16BE"); + _testHttpInvalid("text/xml;charset=UTF-16", "no-bom", "UTF-16BE", null); + + _testHttpLenient("text/xml", "no-bom", "US-ASCII", null, "US-ASCII"); + _testHttpLenient("text/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", "UTF-8", "UTF-8"); + _testHttpLenient("text/xml;charset=UTF-8", "UTF-8-bom", "UTF-8", null, "UTF-8"); + _testHttpLenient("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", null, "UTF-16BE"); + _testHttpLenient("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16", "UTF-16"); + _testHttpLenient("text/xml;charset=UTF-16", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE", "UTF-16BE"); + _testHttpLenient("text/xml", "UTF-8-bom", "US-ASCII", null, "US-ASCII"); + + _testHttpLenient("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", null, "UTF-16BE"); + _testHttpLenient("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16", "UTF-16"); + _testHttpLenient("text/xml;charset=UTF-16BE", "UTF-16BE-bom", "UTF-16BE", "UTF-16BE", "UTF-16BE"); + _testHttpLenient("text/xml;charset=UTF-16", "no-bom", "UTF-16BE", "UTF-16BE", "UTF-16BE"); + _testHttpLenient("text/xml;charset=UTF-16", "no-bom", "UTF-16BE", null, "UTF-16"); + + _testHttpLenient("text/html", "no-bom", "US-ASCII", "US-ASCII", "US-ASCII"); + _testHttpLenient("text/html", "no-bom", "US-ASCII", null, "US-ASCII"); + _testHttpLenient("text/html;charset=UTF-8", "no-bom", "US-ASCII", "UTF-8", "UTF-8"); + _testHttpLenient("text/html;charset=UTF-16BE", "no-bom", "US-ASCII", "UTF-8", "UTF-8"); + } + + public void _testHttpValid(final String cT, final String bomEnc, final String streamEnc, final String prologEnc) throws Exception { + final InputStream is = getXmlStream(bomEnc, prologEnc == null ? "xml" : "xml-prolog-encoding", streamEnc, prologEnc); + final XmlReader xmlReader = new XmlReader(is, cT, false); + if (!streamEnc.equals("UTF-16")) { + // we can not assert things here becuase UTF-8, US-ASCII and ISO-8859-1 look alike for + // the chars used for detection + } else { + assertEquals(xmlReader.getEncoding().substring(0, streamEnc.length()), streamEnc); + } + xmlReader.close(); + } + + protected void _testHttpInvalid(final String cT, final String bomEnc, final String streamEnc, final String prologEnc) throws Exception { + final InputStream is = getXmlStream(bomEnc, prologEnc == null ? "xml-prolog" : "xml-prolog-encoding", streamEnc, prologEnc); + try { + final XmlReader xmlReader = new XmlReader(is, cT, false); + fail("It should have failed for HTTP Content-type " + cT + ", BOM " + bomEnc + ", streamEnc " + streamEnc + " and prologEnc " + prologEnc); + xmlReader.close(); + } catch (final IOException ex) { + assertTrue(ex.getMessage().indexOf("Invalid encoding,") > -1); + } + } + + protected void _testHttpLenient(final String cT, final String bomEnc, final String streamEnc, final String prologEnc, final String shouldbe) + throws Exception { + final InputStream is = getXmlStream(bomEnc, prologEnc == null ? "xml-prolog" : "xml-prolog-encoding", streamEnc, prologEnc); + final XmlReader xmlReader = new XmlReader(is, cT, true); + assertEquals(xmlReader.getEncoding(), shouldbe); + xmlReader.close(); + } + + // XML Stream generator + + private static final int[] NO_BOM_BYTES = {}; + private static final int[] UTF_16BE_BOM_BYTES = { 0xFE, 0xFF }; + private static final int[] UTF_16LE_BOM_BYTES = { 0xFF, 0XFE }; + private static final int[] UTF_8_BOM_BYTES = { 0xEF, 0xBB, 0xBF }; + + private static final Map BOMs = new HashMap(); + + static { + BOMs.put("no-bom", NO_BOM_BYTES); + BOMs.put("UTF-16BE-bom", UTF_16BE_BOM_BYTES); + BOMs.put("UTF-16LE-bom", UTF_16LE_BOM_BYTES); + BOMs.put("UTF-16-bom", NO_BOM_BYTES); // it's added by the writer + BOMs.put("UTF-8-bom", UTF_8_BOM_BYTES); + } + + private static final MessageFormat XML = new MessageFormat("{2}"); + private static final MessageFormat XML_WITH_PROLOG = new MessageFormat("\n{2}"); + private static final MessageFormat XML_WITH_PROLOG_AND_ENCODING = new MessageFormat("\n{2}"); + + private static final MessageFormat INFO = new MessageFormat("\nBOM : {0}\nDoc : {1}\nStream Enc : {2}\nProlog Enc : {3}\n"); + + private static final Map XMLs = new HashMap(); + + static { + XMLs.put("xml", XML); + XMLs.put("xml-prolog", XML_WITH_PROLOG); + XMLs.put("xml-prolog-encoding", XML_WITH_PROLOG_AND_ENCODING); + } + + /** + * + * @param bomType no-bom, UTF-16BE-bom, UTF-16LE-bom, UTF-8-bom + * @param xmlType xml, xml-prolog, xml-prolog-charset + * @return XML stream + */ + protected InputStream getXmlStream(final String bomType, final String xmlType, final String streamEnc, final String prologEnc) throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + int[] bom = BOMs.get(bomType); + if (bom == null) { + bom = new int[0]; + } + final MessageFormat xml = XMLs.get(xmlType); + for (final int element : bom) { + baos.write(element); + } + final Writer writer = new OutputStreamWriter(baos, streamEnc); + final String info = INFO.format(new Object[] { bomType, xmlType, prologEnc }); + final String xmlDoc = xml.format(new Object[] { streamEnc, prologEnc, info }); + writer.write(xmlDoc); + + // PADDDING TO TEST THINGS WORK BEYOND PUSHBACK_SIZE + writer.write("\n"); + for (int i = 0; i < 10000; i++) { + writer.write("\n"); + } + writer.write("\n"); + + writer.close(); + return new ByteArrayInputStream(baos.toByteArray()); + } + +} diff --git a/src/test/java/com/rometools/opml/io/impl/OPML20GeneratorTest.java b/src/test/java/com/rometools/opml/io/impl/OPML20GeneratorTest.java new file mode 100644 index 0000000..28dd3d1 --- /dev/null +++ b/src/test/java/com/rometools/opml/io/impl/OPML20GeneratorTest.java @@ -0,0 +1,73 @@ +package com.rometools.opml.io.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.util.Arrays; + +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + +import com.rometools.opml.feed.opml.Opml; +import com.rometools.opml.feed.opml.Outline; +import com.rometools.rome.io.WireFeedOutput; + +public class OPML20GeneratorTest { + + @Test + public void testOutputOfNullCategory() { + assertThat(categoryOf((String) null).getLength(), is(equalTo(0))); + } + + @Test + public void testOutputOfEmptyCategory() { + assertThat(categoryOf("").getLength(), is(equalTo(0))); + } + + @Test + public void testOutputOfBlankCategory() { + assertThat(categoryOf(" ").getLength(), is(equalTo(0))); + } + + @Test + public void testOutputOfOneCategory() { + assertThat(categoryValueOf("category1"), is(equalTo("category1"))); + } + + @Test + public void testOutputOfMultipleCategories() { + assertThat(categoryValueOf("category1", "category2"), is(equalTo("category1,category2"))); + } + + private NodeList categoryOf(final String... categories) { + + try { + + final Outline outline = new Outline("outline1", null); + outline.setCategories(Arrays.asList(categories)); + + final Opml opml = new Opml(); + opml.setFeedType("opml_2.0"); + opml.setTitle("title"); + opml.setOutlines(Arrays.asList(outline)); + + final WireFeedOutput output = new WireFeedOutput(); + final String xml = output.outputString(opml); + + final Document document = XMLUnit.buildControlDocument(xml); + return XMLUnit.newXpathEngine().getMatchingNodes("/opml/body/outline/@category", document); + + } catch (final Exception e) { + throw new RuntimeException(e); + } + + } + + private String categoryValueOf(final String... categories) { + return categoryOf(categories).item(0).getNodeValue(); + } + +} diff --git a/src/test/java/com/rometools/opml/test/NullWriter.java b/src/test/java/com/rometools/opml/test/NullWriter.java new file mode 100644 index 0000000..f4783e2 --- /dev/null +++ b/src/test/java/com/rometools/opml/test/NullWriter.java @@ -0,0 +1,20 @@ +package com.rometools.opml.test; + +import java.io.IOException; +import java.io.Writer; + +public class NullWriter extends Writer { + + @Override + public void write(final char[] cbuf, final int off, final int len) throws IOException { + } + + @Override + public void flush() throws IOException { + } + + @Override + public void close() throws IOException { + } + +} diff --git a/src/test/java/com/rometools/opml/test/TestUtil.java b/src/test/java/com/rometools/opml/test/TestUtil.java new file mode 100644 index 0000000..1af2465 --- /dev/null +++ b/src/test/java/com/rometools/opml/test/TestUtil.java @@ -0,0 +1,15 @@ +package com.rometools.opml.test; + +import java.io.InputStreamReader; +import java.io.Reader; + +public final class TestUtil { + + private TestUtil() { + } + + public static Reader loadFile(final String path) { + return new InputStreamReader(TestUtil.class.getResourceAsStream(path)); + } + +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..44dea42 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/opml_1.0.xml b/src/test/resources/opml_1.0.xml new file mode 100644 index 0000000..98b1524 --- /dev/null +++ b/src/test/resources/opml_1.0.xml @@ -0,0 +1,29 @@ + + + + Bloglines Subscriptions + Tue, 25 Apr 2006 19:19:28 GMT + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/opml_1.0_links.xml b/src/test/resources/opml_1.0_links.xml new file mode 100644 index 0000000..ae89624 --- /dev/null +++ b/src/test/resources/opml_1.0_links.xml @@ -0,0 +1,76 @@ + + + + Top Ten Sources for podcasting + Newsilike Media Group + opml@TopTenSources.com + Thu, 27 Apr 2006 05:47:27 GMT + Thu, 27 Apr 2006 05:47:27 GMT + 0 + 0 + 0 + 0 + 100 + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/opml_2.0.xml b/src/test/resources/opml_2.0.xml new file mode 100644 index 0000000..22c9586 --- /dev/null +++ b/src/test/resources/opml_2.0.xml @@ -0,0 +1,30 @@ + + + + Bloglines Subscriptions + Tue, 25 Apr 2006 19:19:28 GMT + http://foo.com + Robert Cooper + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file