/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** All rights reserved **
** **
** This program and the accompanying materials are made available under **
** the terms of the Eclipse Public License v1.0 which accompanies this **
** distribution, and is available at: **
** http://www.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.core.internal.interpreter;
import static org.rssowl.core.internal.interpreter.OPMLConstants.RSSOWL_NS;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.internal.interpreter.OPMLConstants.Attributes;
import org.rssowl.core.internal.interpreter.OPMLConstants.Tag;
import org.rssowl.core.internal.newsaction.CopyNewsAction;
import org.rssowl.core.internal.newsaction.LabelNewsAction;
import org.rssowl.core.internal.newsaction.MoveNewsAction;
import org.rssowl.core.interpreter.ITypeImporter;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFilterAction;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.IPersistable;
import org.rssowl.core.persist.IPreference;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.ISearchFilter;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.ISearchValueType;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.pref.IPreferenceType;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.URIUtils;
import java.io.Serializable;
import java.net.URI;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
/**
* Importer for the popular OPML Format. Will create a new Folder that contains
* all Folders and Feeds of the OPML.
*
* @author bpasero
*/
public class OPMLImporter implements ITypeImporter {
/*
* @see
* org.rssowl.core.interpreter.ITypeImporter#importFrom(org.jdom.Document)
*/
public List<? extends IEntity> importFrom(Document document) {
DateFormat dateFormat = DateFormat.getDateInstance();
Element root = document.getRootElement();
/* Interpret Children */
List<?> feedChildren = root.getChildren();
for (Iterator<?> iter = feedChildren.iterator(); iter.hasNext();) {
Element child = (Element) iter.next();
String name = child.getName().toLowerCase();
/* Process Body */
if (Tag.BODY.get().equals(name))
return processBody(child, dateFormat);
}
return null;
}
private List<IEntity> processBody(Element body, DateFormat dateFormat) {
IFolder defaultRootFolder = Owl.getModelFactory().createFolder(null, null, Messages.OPMLImporter_BOOKMARKS);
defaultRootFolder.setProperty(ITypeImporter.TEMPORARY_FOLDER, true);
List<IEntity> importedEntities = new ArrayList<IEntity>();
/* Interpret Children */
List<?> feedChildren = body.getChildren();
for (Iterator<?> iter = feedChildren.iterator(); iter.hasNext();) {
Element child = (Element) iter.next();
String name = child.getName().toLowerCase();
/* Process Outline */
if (Tag.OUTLINE.get().equals(name))
processOutline(child, defaultRootFolder, importedEntities, dateFormat);
/* Process Saved Search */
else if (Tag.SAVED_SEARCH.get().equals(name))
processSavedSearch(child, defaultRootFolder, dateFormat);
/* Process News Bin */
else if (Tag.BIN.get().equals(name))
processNewsBin(child, defaultRootFolder);
/* Process Label */
else if (Tag.LABEL.get().equals(name))
processLabel(child, importedEntities);
/* Process News Filter */
else if (Tag.FILTER.get().equals(name))
processFilter(child, importedEntities, dateFormat);
/* Process Global/Eclipse Preferences */
else if (Tag.PREFERENCE.get().equals(name))
processPreference(child, importedEntities);
}
/* Only add the default root folder if actually used */
if (!defaultRootFolder.getChildren().isEmpty())
importedEntities.add(defaultRootFolder);
return importedEntities;
}
private void processFilter(Element filterElement, List<IEntity> importedEntities, DateFormat dateFormat) {
IModelFactory factory = Owl.getModelFactory();
String name = filterElement.getAttributeValue(Attributes.NAME.get());
int order = Integer.parseInt(filterElement.getAttributeValue(Attributes.ORDER.get()));
boolean isEnabled = Boolean.parseBoolean(filterElement.getAttributeValue(Attributes.ENABLED.get()));
boolean matchAllNews = Boolean.parseBoolean(filterElement.getAttributeValue(Attributes.MATCH_ALL_NEWS.get()));
/* Search if provided */
ISearch search = null;
Element searchElement = filterElement.getChild(Tag.SEARCH.get(), RSSOWL_NS);
if (searchElement != null) {
search = factory.createSearch(null);
search.setMatchAllConditions(Boolean.parseBoolean(searchElement.getAttributeValue(Attributes.MATCH_ALL_CONDITIONS.get())));
/* Search Conditions */
List<?> conditions = searchElement.getChildren(Tag.SEARCH_CONDITION.get(), RSSOWL_NS);
for (int i = 0; i < conditions.size(); i++) {
try {
Element condition = (Element) conditions.get(i);
ISearchCondition searchCondition = processSearchCondition(condition, dateFormat);
if (searchCondition != null)
search.addSearchCondition(searchCondition);
} catch (NumberFormatException e) {
Activator.getDefault().logError(e.getMessage(), e);
} catch (ParseException e) {
Activator.getDefault().logError(e.getMessage(), e);
}
}
}
/* Filter */
ISearchFilter filter = factory.createSearchFilter(null, search, name);
filter.setEnabled(isEnabled);
filter.setMatchAllNews(matchAllNews);
filter.setOrder(order);
/* Filter Actions */
List<?> actions = filterElement.getChildren(Tag.ACTION.get(), RSSOWL_NS);
for (int i = 0; i < actions.size(); i++) {
Element action = (Element) actions.get(i);
String id = action.getAttributeValue(Attributes.ID.get());
String data = action.getAttributeValue(Attributes.DATA.get());
IFilterAction filterAction = factory.createFilterAction(id);
if (data != null) {
/* Special case Label Action */
if (LabelNewsAction.ID.equals(id)) {
Long labelId = Long.parseLong(data);
filterAction.setData(labelId);
}
/* Special case Move/Copy Action */
else if (MoveNewsAction.ID.equals(id) || CopyNewsAction.ID.equals(id)) {
String[] binIds = data.split(OPMLConstants.SEPARATOR);
Long[] binIdsLong = new Long[binIds.length];
for (int j = 0; j < binIds.length; j++) {
binIdsLong[j] = Long.parseLong(binIds[j]);
}
filterAction.setData(binIdsLong);
}
/* Any other Action */
else {
filterAction.setData(data);
}
}
/* Look for Action Properties */
else {
List<?> actionProperties = action.getChildren(Tag.ACTION_PROPERTY.get(), RSSOWL_NS);
if (!actionProperties.isEmpty()) {
Properties properties = new Properties();
for (Object actionProperty : actionProperties) {
Element actionPropertyElement = (Element) actionProperty;
String key = actionPropertyElement.getAttributeValue(Attributes.ID.get());
String value = actionPropertyElement.getAttributeValue(Attributes.VALUE.get());
properties.setProperty(key, value);
}
filterAction.setData(properties);
}
}
filter.addAction(filterAction);
}
importedEntities.add(filter);
}
private void processSavedSearch(Element savedSearchElement, IFolder folder, DateFormat dateFormat) {
String name = savedSearchElement.getAttributeValue(Attributes.NAME.get());
boolean matchAllConditions = Boolean.parseBoolean(savedSearchElement.getAttributeValue(Attributes.MATCH_ALL_CONDITIONS.get()));
ISearchMark searchmark = Owl.getModelFactory().createSearchMark(null, folder, name);
searchmark.setMatchAllConditions(matchAllConditions);
/* Recursively Interpret Children */
List<?> children = savedSearchElement.getChildren();
for (Iterator<?> iter = children.iterator(); iter.hasNext();) {
Element child = (Element) iter.next();
String childName = child.getName().toLowerCase();
/* Process Search Condition */
if (Tag.SEARCH_CONDITION.get().equals(childName)) {
try {
ISearchCondition searchCondition = processSearchCondition(child, dateFormat);
if (searchCondition != null)
searchmark.addSearchCondition(searchCondition);
} catch (NumberFormatException e) {
Activator.getDefault().logError(e.getMessage(), e);
} catch (ParseException e) {
Activator.getDefault().logError(e.getMessage(), e);
}
}
/* Process Preference */
else if (Tag.PREFERENCE.get().equals(childName))
processPreference(searchmark, child);
}
}
private ISearchCondition processSearchCondition(Element conditionElement, DateFormat dateFormat) throws ParseException {
/* Search Specifier */
Element specifierElement = conditionElement.getChild(Tag.SPECIFIER.get(), RSSOWL_NS);
if (specifierElement == null)
return null;
SearchSpecifier searchSpecifier = SearchSpecifier.values()[Integer.parseInt(specifierElement.getAttributeValue(Attributes.ID.get()))];
/* Search Value */
Element valueElement = conditionElement.getChild(Tag.SEARCH_VALUE.get(), RSSOWL_NS);
if (valueElement == null)
return null;
Object value = getValue(valueElement, RSSOWL_NS, dateFormat);
/* Search Field */
Element fieldElement = conditionElement.getChild(Tag.SEARCH_FIELD.get(), RSSOWL_NS);
if (fieldElement == null)
return null;
String entityName = fieldElement.getAttributeValue(Attributes.ENTITY.get());
String fieldName = fieldElement.getAttributeValue(Attributes.NAME.get());
String fieldId = fieldElement.getAttributeValue(Attributes.ID.get());
ISearchField searchField;
if (StringUtils.isSet(fieldName)) //Pre 2.0 M10
searchField = Owl.getModelFactory().createSearchField(getFieldID(fieldName), entityName);
else
//Since 2.0 M10
searchField = Owl.getModelFactory().createSearchField(Integer.parseInt(fieldId), entityName);
/*
* Guard against null (Location Conditions may potentially lead to NULL if
* they are stale since they are not updated when locations change)
*/
if (value != null)
return Owl.getModelFactory().createSearchCondition(searchField, searchSpecifier, value);
return null;
}
/* TODO Delete me in 2.1 (only works for pre 2.0 M10) */
private int getFieldID(String fieldName) {
if ("allFields".equals(fieldName)) //$NON-NLS-1$
return IEntity.ALL_FIELDS;
if ("title".equals(fieldName)) //$NON-NLS-1$
return INews.TITLE;
if ("link".equals(fieldName)) //$NON-NLS-1$
return INews.LINK;
if ("description".equals(fieldName)) //$NON-NLS-1$
return INews.DESCRIPTION;
if ("publishDate".equals(fieldName)) //$NON-NLS-1$
return INews.PUBLISH_DATE;
if ("modifiedDate".equals(fieldName)) //$NON-NLS-1$
return INews.MODIFIED_DATE;
if ("receiveDate".equals(fieldName)) //$NON-NLS-1$
return INews.RECEIVE_DATE;
if ("author".equals(fieldName)) //$NON-NLS-1$
return INews.AUTHOR;
if ("comments".equals(fieldName)) //$NON-NLS-1$
return INews.COMMENTS;
if ("guid".equals(fieldName)) //$NON-NLS-1$
return INews.GUID;
if ("source".equals(fieldName)) //$NON-NLS-1$
return INews.SOURCE;
if ("hasAttachments".equals(fieldName)) //$NON-NLS-1$
return INews.HAS_ATTACHMENTS;
if ("attachments".equals(fieldName)) //$NON-NLS-1$
return INews.ATTACHMENTS_CONTENT;
if ("categories".equals(fieldName)) //$NON-NLS-1$
return INews.CATEGORIES;
if ("isFlagged".equals(fieldName)) //$NON-NLS-1$
return INews.IS_FLAGGED;
if ("state".equals(fieldName)) //$NON-NLS-1$
return INews.STATE;
if ("label".equals(fieldName)) //$NON-NLS-1$
return INews.LABEL;
if ("rating".equals(fieldName)) //$NON-NLS-1$
return INews.RATING;
if ("feed".equals(fieldName)) //$NON-NLS-1$
return INews.FEED;
if ("ageInDays".equals(fieldName)) //$NON-NLS-1$
return INews.AGE_IN_DAYS;
if ("location".equals(fieldName)) //$NON-NLS-1$
return INews.LOCATION;
return IEntity.ALL_FIELDS;
}
private Object getValue(Element valueElement, Namespace namespace, DateFormat dateFormat) throws ParseException {
Object value = null;
int valueType = Integer.parseInt(valueElement.getAttributeValue(Attributes.TYPE.get()));
List<?> locationElements = valueElement.getChildren(Tag.LOCATION.get(), namespace);
List<?> newsStateElements = valueElement.getChildren(Tag.STATE.get(), namespace);
/* Treat set of Locations separately */
if (locationElements.size() > 0) {
List<Long> folderIds = new ArrayList<Long>(locationElements.size());
List<Long> bookmarkIds = new ArrayList<Long>(locationElements.size());
List<Long> newsbinIds = new ArrayList<Long>(locationElements.size());
for (int i = 0; i < locationElements.size(); i++) {
Element locationElement = (Element) locationElements.get(i);
Long id = Long.parseLong(locationElement.getAttributeValue(Attributes.VALUE.get()));
boolean isFolder = Boolean.parseBoolean(locationElement.getAttributeValue(Attributes.IS_FOLDER.get()));
boolean isBin = Boolean.parseBoolean(locationElement.getAttributeValue(Attributes.IS_BIN.get()));
if (isFolder)
folderIds.add(id);
else if (isBin)
newsbinIds.add(id);
else
bookmarkIds.add(id);
}
Long[][] result = new Long[3][];
result[0] = folderIds.toArray(new Long[folderIds.size()]);
result[1] = bookmarkIds.toArray(new Long[bookmarkIds.size()]);
result[2] = newsbinIds.toArray(new Long[bookmarkIds.size()]);
return result;
}
/* Treat set of News States separately */
else if (newsStateElements.size() > 0) {
List<INews.State> states = new ArrayList<INews.State>(newsStateElements.size());
for (int i = 0; i < newsStateElements.size(); i++) {
Element newsStateElement = (Element) newsStateElements.get(i);
int ordinal = Integer.parseInt(newsStateElement.getAttributeValue(Attributes.VALUE.get()));
states.add(INews.State.values()[ordinal]);
}
value = EnumSet.copyOf(states);
}
/* Any other Value */
else {
String valueAsString = valueElement.getAttributeValue(Attributes.VALUE.get());
switch (valueType) {
case ISearchValueType.BOOLEAN:
value = Boolean.parseBoolean(valueAsString);
break;
case ISearchValueType.STRING:
value = valueAsString;
break;
case ISearchValueType.LINK:
value = valueAsString;
break;
case ISearchValueType.INTEGER:
value = Integer.parseInt(valueAsString);
break;
case ISearchValueType.NUMBER:
value = Integer.parseInt(valueAsString);
break;
case ISearchValueType.DATE:
value = dateFormat.parse(valueAsString);
break;
case ISearchValueType.DATETIME:
value = dateFormat.parse(valueAsString);
break;
case ISearchValueType.TIME:
value = dateFormat.parse(valueAsString);
break;
case ISearchValueType.ENUM:
value = valueAsString;
break;
}
}
return value;
}
private void processOutline(Element outline, IPersistable parent, List<IEntity> importedEntities, DateFormat dateFormat) {
IEntity type = null;
Long id = null;
String title = null;
String link = null;
String homepage = null;
String description = null;
/* Interpret Attributes */
List<?> attributes = outline.getAttributes();
for (Iterator<?> iter = attributes.iterator(); iter.hasNext();) {
Attribute attribute = (Attribute) iter.next();
String name = attribute.getName();
/* Link */
if (name.toLowerCase().equals(Attributes.XML_URL.get().toLowerCase()))
link = attribute.getValue();
/* Title */
else if (name.toLowerCase().equals(Attributes.TITLE.get()))
title = attribute.getValue();
/* Text */
else if (title == null && name.toLowerCase().equals(Attributes.TEXT.get()))
title = attribute.getValue();
/* Homepage */
else if (name.toLowerCase().equals(Attributes.HTML_URL.get().toLowerCase()))
homepage = attribute.getValue();
/* Description */
else if (name.toLowerCase().equals(Attributes.DESCRIPTION.get()))
description = attribute.getValue();
}
/* RSSOwl Namespace Attributes */
Attribute idAttribute = outline.getAttribute(Attributes.ID.get(), RSSOWL_NS);
if (idAttribute != null)
id = Long.valueOf(idAttribute.getValue());
boolean isSet = Boolean.parseBoolean(outline.getAttributeValue(Attributes.IS_SET.get(), RSSOWL_NS));
/* Outline is a Folder */
if (link == null && title != null) {
type = Owl.getModelFactory().createFolder(null, isSet ? null : (IFolder) parent, title);
/* Assign old ID value */
if (id != null)
type.setProperty(ID_KEY, id);
if (isSet)
importedEntities.add(type);
}
/* Outline is a BookMark */
else {
URI uri = link != null ? URIUtils.createURI(link) : null;
if (uri != null) {
/* If no Scheme is Provided, use HTTP as default */
if (uri.getScheme() == null)
uri = URIUtils.createURI(URIUtils.HTTP + link);
/* Create the BookMark */
FeedLinkReference feedLinkRef = new FeedLinkReference(uri);
type = Owl.getModelFactory().createBookMark(null, (IFolder) parent, feedLinkRef, title != null ? title : link);
/* Assign old ID value */
if (id != null)
type.setProperty(ID_KEY, id);
/* Store Description if set */
if (StringUtils.isSet(description))
type.setProperty(ITypeImporter.DESCRIPTION_KEY, description);
/* Store Homepage if set */
if (StringUtils.isSet(homepage))
type.setProperty(ITypeImporter.HOMEPAGE_KEY, homepage);
}
}
/* In case this Outline Element did not represent a Entity */
if (type == null)
return;
/* Recursively Interpret Children */
List<?> children = outline.getChildren();
for (Iterator<?> iter = children.iterator(); iter.hasNext();) {
Element child = (Element) iter.next();
String name = child.getName().toLowerCase();
/* Process Outline */
if (Tag.OUTLINE.get().equals(name))
processOutline(child, type, importedEntities, dateFormat);
/* Process Saved Search */
else if (Tag.SAVED_SEARCH.get().equals(name))
processSavedSearch(child, (IFolder) type, dateFormat);
/* Process News Bin */
else if (Tag.BIN.get().equals(name))
processNewsBin(child, (IFolder) type);
/* Process Preference */
else if (Tag.PREFERENCE.get().equals(name))
processPreference(type, child);
}
}
private void processLabel(Element labelElement, List<IEntity> importedEntities) {
String id = labelElement.getAttributeValue(Attributes.ID.get());
String name = labelElement.getAttributeValue(Attributes.NAME.get());
String order = labelElement.getAttributeValue(Attributes.ORDER.get());
String color = labelElement.getAttributeValue(Attributes.COLOR.get());
ILabel label = Owl.getModelFactory().createLabel(null, name);
label.setColor(color);
label.setOrder(Integer.parseInt(order));
label.setProperty(ID_KEY, Long.valueOf(id));
importedEntities.add(label);
}
private void processNewsBin(Element newsBinElement, IFolder folder) {
String name = newsBinElement.getAttributeValue(Attributes.NAME.get());
/* RSSOwl Namespace Attributes */
Long id = null;
Attribute idAttribute = newsBinElement.getAttribute(Attributes.ID.get(), RSSOWL_NS);
if (idAttribute != null)
id = Long.valueOf(idAttribute.getValue());
INewsBin newsbin = Owl.getModelFactory().createNewsBin(null, folder, name);
/* Assign old ID value */
if (id != null)
newsbin.setProperty(ID_KEY, id);
/* Recursively Interpret Children */
List<?> children = newsBinElement.getChildren();
for (Iterator<?> iter = children.iterator(); iter.hasNext();) {
Element child = (Element) iter.next();
String childName = child.getName().toLowerCase();
/* Process Preference */
if (Tag.PREFERENCE.get().equals(childName))
processPreference(newsbin, child);
}
}
private void processPreference(IEntity entity, Element element) {
String key = element.getAttributeValue(Attributes.ID.get());
String value = element.getAttributeValue(Attributes.VALUE.get());
String type = element.getAttributeValue(Attributes.TYPE.get());
if (StringUtils.isSet(key) && value != null && type != null)
entity.setProperty(key, getPropertyValue(value, IPreferenceType.values()[Integer.parseInt(type)]));
}
private Serializable getPropertyValue(String value, IPreferenceType type) {
switch (type) {
case BOOLEAN:
return Boolean.valueOf(value);
case INTEGER:
return Integer.valueOf(value);
case INTEGERS:
String[] values = value.split(OPMLConstants.SEPARATOR);
int[] intValues = new int[values.length];
for (int i = 0; i < values.length; i++) {
intValues[i] = Integer.parseInt(values[i]);
}
return intValues;
case LONG:
return Long.valueOf(value);
case LONGS:
values = value.split(OPMLConstants.SEPARATOR);
long[] longValues = new long[values.length];
for (int i = 0; i < values.length; i++) {
longValues[i] = Long.parseLong(values[i]);
}
return longValues;
case STRING:
return value;
case STRINGS:
return value.split(OPMLConstants.SEPARATOR);
}
return value;
}
private void processPreference(Element element, List<IEntity> importedEntities) {
String key = element.getAttributeValue(Attributes.ID.get());
String value = element.getAttributeValue(Attributes.VALUE.get());
String typeVal = element.getAttributeValue(Attributes.TYPE.get());
String kindVal = element.getAttributeValue(Attributes.KIND.get());
if (StringUtils.isSet(key) && value != null && typeVal != null && kindVal != null) {
IPreferenceScope.Kind kind = IPreferenceScope.Kind.values()[Integer.parseInt(kindVal)];
IPreferenceType type = IPreferenceType.values()[Integer.parseInt(typeVal)];
IPreference preference = Owl.getModelFactory().createPreference(key);
preference.setProperty(ITypeImporter.DATA_KEY, new Object[] { kind, type });
switch (type) {
case BOOLEAN:
preference.putBooleans(Boolean.parseBoolean(value));
break;
case INTEGER:
preference.putIntegers(Integer.parseInt(value));
break;
case INTEGERS:
preference.putIntegers((int[]) getPropertyValue(value, type));
break;
case LONG:
preference.putLongs(Long.parseLong(value));
break;
case LONGS:
preference.putLongs((long[]) getPropertyValue(value, type));
break;
case STRING:
preference.putStrings(value);
break;
case STRINGS:
preference.putStrings((String[]) getPropertyValue(value, type));
break;
}
importedEntities.add(preference);
}
}
}