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.
+
+ *
+ * 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 // // //
+ *
+ * item0.category0item0.category1Thu, 08 Jul 1999 08:00:00
+ * GMTThu, 08 Jul 1999 09:00:00 GMT
+ * item0.authorhttp://localhost:8080/item0/commentshttp://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?
+ /*
+ * SearchSearch 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
+ * 6001
+ * 234567
+ * 89.5101112
+ * 1314151617
+ * 1819202122
+ * 23MondayTuesday
+ * WednesdayThursdayFridaySaturday
+ * 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