diff --git a/pom.xml b/pom.xml
index e58c56b..b8d6cc4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,10 @@
junit
test
+
+ org.hamcrest
+ hamcrest-library
+
xmlunit
xmlunit
diff --git a/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java b/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java
index dfc4137..96ff4ae 100644
--- a/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java
+++ b/src/main/java/com/rometools/opml/io/impl/OPML20Generator.java
@@ -1,5 +1,7 @@
package com.rometools.opml.io.impl;
+import java.util.Collection;
+import java.util.List;
import java.util.Locale;
import org.jdom2.Document;
@@ -12,7 +14,9 @@ import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.impl.DateParser;
/**
- * @author cooper
+ * Generator for OPML 2.0 documents.
+ *
+ * @see http://dev.opml.org/spec2.html
*/
public class OPML20Generator extends OPML10Generator {
@@ -41,33 +45,61 @@ public class OPML20Generator extends OPML10Generator {
*/
@Override
public Document generate(final WireFeed feed) throws IllegalArgumentException, FeedException {
- final Document retValue = super.generate(feed);
- retValue.getRootElement().setAttribute("version", "2.0");
- return retValue;
+ final Document document = super.generate(feed);
+ document.getRootElement().setAttribute("version", "2.0");
+ return document;
}
@Override
protected Element generateHead(final Opml opml) {
- final Element docs = new Element("docs");
- docs.setText(opml.getDocs());
+ final Element docsElement = new Element("docs");
+ docsElement.setText(opml.getDocs());
- final Element retValue = super.generateHead(opml);
- retValue.addContent(docs);
- return retValue;
+ final Element headElement = super.generateHead(opml);
+ headElement.addContent(docsElement);
+ return headElement;
}
@Override
protected Element generateOutline(final Outline outline) {
- final Element retValue = super.generateOutline(outline);
+ final Element outlineElement = super.generateOutline(outline);
if (outline.getCreated() != null) {
- retValue.setAttribute("created", DateParser.formatRFC822(outline.getCreated(), Locale.US));
+ outlineElement.setAttribute("created", DateParser.formatRFC822(outline.getCreated(), Locale.US));
}
- return retValue;
+ 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/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();
+ }
+
+}