/*
* Copyright (C) 2010-2011 Geometer Plus <contact@geometerplus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.geometerplus.fbreader.network.atom;
import java.util.Map;
import org.geometerplus.zlibrary.core.constants.XMLNamespaces;
import org.geometerplus.zlibrary.core.util.MimeType;
import org.geometerplus.zlibrary.core.xml.ZLStringMap;
import org.geometerplus.zlibrary.core.xml.ZLXMLReaderAdapter;
public abstract class ATOMXMLReader<T1 extends ATOMFeedMetadata, T2 extends ATOMEntry> extends ZLXMLReaderAdapter {
public static String intern(String str) {
if (str == null || str.length() == 0) {
return null;
}
return str.intern();
}
private final ATOMFeedHandler<T1, T2> myFeedHandler;
private T1 myFeed;
private T2 myEntry;
private ATOMAuthor myAuthor;
private ATOMId myId;
private ATOMLink myLink;
private ATOMCategory myCategory;
private ATOMUpdated myUpdated;
private ATOMPublished myPublished;
private ATOMIcon myIcon;
private Map<String,String> myNamespaceMap;
private static final int START = 0;
protected static final int FEED = 1;
protected static final int F_ENTRY = 2;
private static final int F_ID = 3;
private static final int F_LINK = 4;
private static final int F_CATEGORY = 5;
private static final int F_TITLE = 6;
private static final int F_UPDATED = 7;
private static final int F_AUTHOR = 8;
private static final int F_SUBTITLE = 9;
private static final int F_ICON = 10;
private static final int FA_NAME = 11;
private static final int FA_URI = 12;
private static final int FA_EMAIL = 13;
private static final int FE_AUTHOR = 14;
private static final int FE_ID = 15;
private static final int FE_CATEGORY = 16;
protected static final int FE_LINK = 17;
private static final int FE_PUBLISHED = 18;
private static final int FE_SUMMARY = 19;
protected static final int FE_CONTENT = 20;
private static final int FE_TITLE = 21;
private static final int FE_UPDATED = 22;
private static final int FEA_NAME = 23;
private static final int FEA_URI = 24;
private static final int FEA_EMAIL = 25;
protected static final int ATOM_STATE_FIRST_UNUSED = 26;
protected static final String TAG_FEED = "feed";
protected static final String TAG_ENTRY = "entry";
protected static final String TAG_AUTHOR = "author";
protected static final String TAG_NAME = "name";
protected static final String TAG_URI = "uri";
protected static final String TAG_EMAIL = "email";
protected static final String TAG_ID = "id";
protected static final String TAG_CATEGORY = "category";
protected static final String TAG_LINK = "link";
protected static final String TAG_PUBLISHED = "published";
protected static final String TAG_SUMMARY = "summary";
protected static final String TAG_CONTENT = "content";
protected static final String TAG_TITLE = "title";
protected static final String TAG_UPDATED = "updated";
protected static final String TAG_SUBTITLE = "subtitle";
protected static final String TAG_ICON = "icon";
protected int myState;
private final StringBuilder myBuffer = new StringBuilder();
protected final FormattedBuffer myFormattedBuffer = new FormattedBuffer();
protected boolean myFeedMetadataProcessed;
public ATOMXMLReader(ATOMFeedHandler<T1, T2> handler, boolean readEntryNotFeed) {
myFeedHandler = handler;
myState = readEntryNotFeed ? FEED : START;
}
protected final ATOMFeedHandler<T1, T2> getATOMFeedHandler() {
return myFeedHandler;
}
protected final T1 getATOMFeed() {
return myFeed;
}
protected final T2 getATOMEntry() {
return myEntry;
}
protected final ATOMLink getATOMLink() {
return myLink;
}
@Override
public final boolean processNamespaces() {
return true;
}
@Override
public final void namespaceMapChangedHandler(Map<String,String> namespaceMap) {
myNamespaceMap = namespaceMap;
}
protected final String getNamespace(String prefix) {
if (myNamespaceMap == null) {
return null;
}
final String ns = myNamespaceMap.get(prefix);
return ns != null ? ns.intern() : null;
}
@Override
public final boolean startElementHandler(String tag, ZLStringMap attributes) {
final int index = tag.indexOf(':');
final String tagPrefix;
if (index != -1) {
tagPrefix = tag.substring(0, index).intern();
tag = tag.substring(index + 1).intern();
} else {
tagPrefix = "";
tag = tag.intern();
}
return startElementHandler(getNamespace(tagPrefix), tag, attributes, extractBufferContent());
}
@Override
public final boolean endElementHandler(String tag) {
final int index = tag.indexOf(':');
final String tagPrefix;
if (index != -1) {
tagPrefix = tag.substring(0, index).intern();
tag = tag.substring(index + 1).intern();
} else {
tagPrefix = "";
tag = tag.intern();
}
return endElementHandler(getNamespace(tagPrefix), tag, extractBufferContent());
}
private final String extractBufferContent() {
final char[] bufferContentArray = myBuffer.toString().trim().toCharArray();
myBuffer.delete(0, myBuffer.length());
if (bufferContentArray.length == 0) {
return null;
}
return new String(bufferContentArray);
}
protected abstract T1 createFeed(ZLStringMap attributes);
protected abstract T2 createEntry(ZLStringMap attributes);
protected ATOMLink createLink(ZLStringMap attributes) {
return new ATOMLink(attributes);
}
public boolean startElementHandler(
final String ns, final String tag,
final ZLStringMap attributes, final String bufferContent
) {
boolean interruptReading = false;
switch (myState) {
case START:
if (ns == XMLNamespaces.Atom && tag == TAG_FEED) {
myFeedHandler.processFeedStart();
myFeed = createFeed(attributes);
myState = FEED;
myFeedMetadataProcessed = false;
}
break;
case FEED:
if (ns == XMLNamespaces.Atom) {
if (tag == TAG_AUTHOR) {
myAuthor = new ATOMAuthor(attributes);
myState = F_AUTHOR;
} else if (tag == TAG_ID) {
myId = new ATOMId(attributes);
myState = F_ID;
} else if (tag == TAG_ICON) {
myIcon = new ATOMIcon(attributes);
myState = F_ICON;
} else if (tag == TAG_LINK) {
myLink = createLink(attributes); // TODO
myState = F_LINK;
} else if (tag == TAG_CATEGORY) {
myCategory = new ATOMCategory(attributes);
myState = F_CATEGORY;
} else if (tag == TAG_TITLE) {
//myTitle = new ATOMTitle(attributes); // TODO:implement ATOMTextConstruct & ATOMTitle
setFormattingType(attributes.getValue("type"));
myState = F_TITLE;
} else if (tag == TAG_SUBTITLE) {
//mySubtitle = new ATOMTitle(attributes); // TODO:implement ATOMTextConstruct & ATOMSubtitle
setFormattingType(attributes.getValue("type"));
myState = F_SUBTITLE;
} else if (tag == TAG_UPDATED) {
myUpdated = new ATOMUpdated(attributes);
myState = F_UPDATED;
} else if (tag == TAG_ENTRY) {
myEntry = createEntry(attributes);
myState = F_ENTRY;
// Process feed metadata just before first feed entry
if (myFeed != null && !myFeedMetadataProcessed) {
interruptReading = myFeedHandler.processFeedMetadata(myFeed, true);
myFeedMetadataProcessed = true;
}
}
}
break;
case F_ENTRY:
if (ns == XMLNamespaces.Atom) {
if (tag == TAG_AUTHOR) {
myAuthor = new ATOMAuthor(attributes);
myState = FE_AUTHOR;
} else if (tag == TAG_ID) {
myId = new ATOMId(attributes);
myState = FE_ID;
} else if (tag == TAG_CATEGORY) {
myCategory = new ATOMCategory(attributes);
myState = FE_CATEGORY;
} else if (tag == TAG_LINK) {
myLink = createLink(attributes); // TODO
myState = FE_LINK;
} else if (tag == TAG_PUBLISHED) {
myPublished = new ATOMPublished(attributes);
myState = FE_PUBLISHED;
} else if (tag == TAG_SUMMARY) {
//mySummary = new ATOMSummary(attributes); // TODO:implement ATOMTextConstruct & ATOMSummary
setFormattingType(attributes.getValue("type"));
myState = FE_SUMMARY;
} else if (tag == TAG_CONTENT) {
//myConent = new ATOMContent(attributes); // TODO:implement ATOMContent
setFormattingType(attributes.getValue("type"));
myState = FE_CONTENT;
} else if (tag == TAG_TITLE) {
//myTitle = new ATOMTitle(attributes); // TODO:implement ATOMTextConstruct & ATOMTitle
setFormattingType(attributes.getValue("type"));
myState = FE_TITLE;
} else if (tag == TAG_UPDATED) {
myUpdated = new ATOMUpdated(attributes);
myState = FE_UPDATED;
}
}
break;
case F_AUTHOR:
if (ns == XMLNamespaces.Atom) {
if (tag == TAG_NAME) {
myState = FA_NAME;
} else if (tag == TAG_URI) {
myState = FA_URI;
} else if (tag == TAG_EMAIL) {
myState = FA_EMAIL;
}
}
break;
case FE_AUTHOR:
if (ns == XMLNamespaces.Atom) {
if (tag == TAG_NAME) {
myState = FEA_NAME;
} else if (tag == TAG_URI) {
myState = FEA_URI;
} else if (tag == TAG_EMAIL) {
myState = FEA_EMAIL;
}
}
break;
case FE_CONTENT:
case FE_SUMMARY:
case FE_TITLE:
case F_TITLE:
case F_SUBTITLE:
myFormattedBuffer.appendText(bufferContent);
myFormattedBuffer.appendStartTag(tag, attributes);
break;
default:
break;
}
return interruptReading;
}
public boolean endElementHandler(String ns, String tag, String bufferContent) {
boolean interruptReading = false;
switch (myState) {
case START:
break;
case FEED:
if (ns == XMLNamespaces.Atom && tag == TAG_FEED) {
if (myFeed != null) {
interruptReading = myFeedHandler.processFeedMetadata(myFeed, false);
}
myFeed = null;
myFeedHandler.processFeedEnd();
myState = START;
}
break;
case F_ENTRY:
if (ns == XMLNamespaces.Atom && tag == TAG_ENTRY) {
if (myEntry != null) {
interruptReading = myFeedHandler.processFeedEntry(myEntry);
}
myEntry = null;
myState = FEED;
}
break;
case F_ID:
if (ns == XMLNamespaces.Atom && tag == TAG_ID) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (bufferContent != null && myFeed != null) {
myId.Uri = bufferContent;
myFeed.Id = myId;
}
myId = null;
myState = FEED;
}
break;
case F_ICON:
if (ns == XMLNamespaces.Atom && tag == TAG_ICON) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (bufferContent != null && myFeed != null) {
myIcon.Uri = bufferContent;
myFeed.Icon = myIcon;
}
myIcon = null;
myState = FEED;
}
break;
case F_LINK:
if (ns == XMLNamespaces.Atom && tag == TAG_LINK) {
if (myFeed != null) {
myFeed.Links.add(myLink);
}
myLink = null;
myState = FEED;
}
break;
case F_CATEGORY:
if (ns == XMLNamespaces.Atom && tag == TAG_CATEGORY) {
if (myFeed != null) {
myFeed.Categories.add(myCategory);
}
myCategory = null;
myState = FEED;
}
break;
case F_TITLE:
myFormattedBuffer.appendText(bufferContent);
if (ns == XMLNamespaces.Atom && tag == TAG_TITLE) {
// TODO:implement ATOMTextConstruct & ATOMTitle
final CharSequence title = myFormattedBuffer.getText();
if (myFeed != null) {
myFeed.Title = title;
}
myState = FEED;
} else {
myFormattedBuffer.appendEndTag(tag);
}
break;
case F_SUBTITLE:
myFormattedBuffer.appendText(bufferContent);
if (ns == XMLNamespaces.Atom && tag == TAG_SUBTITLE) {
// TODO:implement ATOMTextConstruct & ATOMSubtitle
final CharSequence subtitle = myFormattedBuffer.getText();
if (myFeed != null) {
myFeed.Subtitle = subtitle;
}
myState = FEED;
} else {
myFormattedBuffer.appendEndTag(tag);
}
break;
case F_UPDATED:
if (ns == XMLNamespaces.Atom && tag == TAG_UPDATED) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (ATOMDateConstruct.parse(bufferContent, myUpdated) && myFeed != null) {
myFeed.Updated = myUpdated;
}
myUpdated = null;
myState = FEED;
}
break;
case F_AUTHOR:
if (ns == XMLNamespaces.Atom && tag == TAG_AUTHOR) {
if (myFeed != null && myAuthor.Name != null) {
myFeed.Authors.add(myAuthor);
}
myAuthor = null;
myState = FEED;
}
break;
case FA_NAME:
if (ns == XMLNamespaces.Atom && tag == TAG_NAME) {
myAuthor.Name = bufferContent;
myState = F_AUTHOR;
}
break;
case FEA_NAME:
if (ns == XMLNamespaces.Atom && tag == TAG_NAME) {
myAuthor.Name = bufferContent;
myState = FE_AUTHOR;
}
break;
case FA_URI:
if (ns == XMLNamespaces.Atom && tag == TAG_URI) {
myAuthor.Uri = bufferContent;
myState = F_AUTHOR;
}
break;
case FEA_URI:
if (ns == XMLNamespaces.Atom && tag == TAG_URI) {
myAuthor.Uri = bufferContent;
myState = FE_AUTHOR;
}
break;
case FA_EMAIL:
if (ns == XMLNamespaces.Atom && tag == TAG_EMAIL) {
myAuthor.Email = bufferContent;
myState = F_AUTHOR;
}
break;
case FEA_EMAIL:
if (ns == XMLNamespaces.Atom && tag == TAG_EMAIL) {
myAuthor.Email = bufferContent;
myState = FE_AUTHOR;
}
break;
case FE_AUTHOR:
if (ns == XMLNamespaces.Atom && tag == TAG_AUTHOR) {
if (myAuthor.Name != null) {
myEntry.Authors.add(myAuthor);
}
myAuthor = null;
myState = F_ENTRY;
}
break;
case FE_ID:
if (ns == XMLNamespaces.Atom && tag == TAG_ID) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (bufferContent != null) {
myId.Uri = bufferContent;
myEntry.Id = myId;
}
myId = null;
myState = F_ENTRY;
}
break;
case FE_CATEGORY:
if (ns == XMLNamespaces.Atom && tag == TAG_CATEGORY) {
myEntry.Categories.add(myCategory);
myCategory = null;
myState = F_ENTRY;
}
break;
case FE_LINK:
if (ns == XMLNamespaces.Atom && tag == TAG_LINK) {
myEntry.Links.add(myLink);
myLink = null;
myState = F_ENTRY;
}
break;
case FE_PUBLISHED:
if (ns == XMLNamespaces.Atom && tag == TAG_PUBLISHED) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (ATOMDateConstruct.parse(bufferContent, myPublished)) {
myEntry.Published = myPublished;
}
myPublished = null;
myState = F_ENTRY;
}
break;
case FE_SUMMARY:
myFormattedBuffer.appendText(bufferContent);
if (ns == XMLNamespaces.Atom && tag == TAG_SUMMARY) {
// TODO:implement ATOMTextConstruct & ATOMSummary
myEntry.Summary = myFormattedBuffer.getText();
myState = F_ENTRY;
} else {
myFormattedBuffer.appendEndTag(tag);
}
break;
case FE_CONTENT:
myFormattedBuffer.appendText(bufferContent);
if (ns == XMLNamespaces.Atom && tag == TAG_CONTENT) {
// TODO:implement ATOMContent
myEntry.Content = myFormattedBuffer.getText();
myState = F_ENTRY;
} else {
myFormattedBuffer.appendEndTag(tag);
}
break;
case FE_TITLE:
myFormattedBuffer.appendText(bufferContent);
if (ns == XMLNamespaces.Atom && tag == TAG_TITLE) {
// TODO:implement ATOMTextConstruct & ATOMTitle
myEntry.Title = myFormattedBuffer.getText();
myState = F_ENTRY;
} else {
myFormattedBuffer.appendEndTag(tag);
}
break;
case FE_UPDATED:
if (ns == XMLNamespaces.Atom && tag == TAG_UPDATED) {
// FIXME:uri can be lost:buffer will be truncated, if there are extension tags inside the <id> tag
if (ATOMDateConstruct.parse(bufferContent, myUpdated)) {
myEntry.Updated = myUpdated;
}
myUpdated = null;
myState = F_ENTRY;
}
break;
}
return interruptReading;
}
@Override
public final void characterDataHandler(char[] data, int start, int length) {
myBuffer.append(data, start, length);
}
public void setFormattingType(String type) {
if (ATOMConstants.TYPE_HTML.equals(type) || MimeType.TEXT_HTML.Name.equals(type)) {
myFormattedBuffer.reset(FormattedBuffer.Type.Html);
} else if (ATOMConstants.TYPE_XHTML.equals(type) || MimeType.TEXT_XHTML.Name.equals(type)) {
myFormattedBuffer.reset(FormattedBuffer.Type.XHtml);
} else {
myFormattedBuffer.reset(FormattedBuffer.Type.Text);
}
}
}