package org.ebookdroid.opds.xml; import org.ebookdroid.common.settings.OpdsSettings; import org.ebookdroid.opds.IEntryBuilder; import org.ebookdroid.opds.model.BookDownloadLink; import org.ebookdroid.opds.model.Content; import org.ebookdroid.opds.model.Feed; import org.ebookdroid.opds.model.Link; import org.ebookdroid.opds.model.LinkKind; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.emdev.common.xml.IContentHandler; import org.emdev.common.xml.TextProvider; import org.emdev.common.xml.parsers.DuckbillParser; import org.emdev.common.xml.tags.XmlTag; import org.emdev.utils.LengthUtils; public class OPDSContentHandler implements IContentHandler { final Feed feed; final IEntryBuilder builder; private boolean inEntry; private boolean grabContent; private final StringBuilder buf = new StringBuilder(); private final Map<String, String> values = new HashMap<String, String>(); private final Map<String, Link> facets = new LinkedHashMap<String, Link>(); private Link feedLink; private Link bookThumbnail; private List<BookDownloadLink> bookLinks; private final Set<String> unsupportedTypes = new HashSet<String>(); public OPDSContentHandler(final Feed feed, final IEntryBuilder builder) { this.feed = feed; this.builder = builder; } public void parse(final String content) throws Exception { TextProvider text = new TextProvider(content); DuckbillParser p = new DuckbillParser(); p.parse(text, OPDSTagFactory.instance, this); } @Override public void startElement(final XmlTag tag, final String... attributes) { buf.setLength(0); if (inEntry) { if (tag == OPDSTag.CONTENT.tag) { values.put("content@type", attributes[0]); grabContent = true; } else if (tag == OPDSTag.LINK.tag) { final String ref = attributes[0]; final String rel = attributes[1]; final String title = attributes[2]; final String type = attributes[3]; final LinkKind kind = LinkKind.valueOf(rel, type); switch (kind) { case FEED: feedLink = new Link(kind, ref, rel, type); break; case FACET_FEED: if (LengthUtils.isNotEmpty(title)) { facets.put(title, new Link(kind, ref, rel, type)); } break; case BOOK_DOWNLOAD: final BookDownloadLink bdl = new BookDownloadLink(kind, ref, rel, type); if (bdl.bookType == null) { if (unsupportedTypes.add(type)) { final String entryTitle = values.get("title"); System.out.println(entryTitle + ": Unsupported mime type: " + type); } } final OpdsSettings s = OpdsSettings.current(); if (!s.filterTypes || bdl.bookType != null && (!bdl.isZipped || s.downloadArchives)) { if (bookLinks == null) { bookLinks = new LinkedList<BookDownloadLink>(); } bookLinks.add(bdl); } break; case BOOK_THUMBNAIL: bookThumbnail = new Link(kind, ref, rel, type); break; default: break; } } else { grabContent = tag == OPDSTag.ID.tag || tag == OPDSTag.TITLE.tag; } } else { if (tag == OPDSTag.ENTRY.tag) { inEntry = true; values.clear(); facets.clear(); feedLink = null; bookThumbnail = null; bookLinks = null; } else if (tag == OPDSTag.LINK.tag) { final String ref = attributes[0]; final String rel = attributes[1]; final String type = attributes[3]; final LinkKind kind = LinkKind.valueOf(rel, type); if (kind == LinkKind.NEXT_FEED) { feed.next = new Feed(feed.parent, ref, feed.title, feed.content); feed.next.link = new Link(kind, ref, rel, type); feed.next.next = null; feed.next.prev = feed; } } } } @Override public void endElement(final XmlTag tag) { if (inEntry) { if (grabContent) { values.put(tag.name, buf.toString()); } else if (tag == OPDSTag.ENTRY.tag) { inEntry = false; final String contentString = values.get("content"); final String contentType = values.get("content@type"); final Content content = contentString != null ? new Content(contentType, contentString) : null; final String entryId = values.get("id"); final String entryTitle = values.get("title"); if (LengthUtils.isNotEmpty(bookLinks)) { feed.books.add(builder.newBook(feed, entryId, entryTitle, content, bookThumbnail, bookLinks)); } else if (feedLink != null || !facets.isEmpty()) { feed.children.add(builder.newFeed(feed, entryId, entryTitle, content, feedLink, facets)); } values.clear(); facets.clear(); } } grabContent = false; buf.setLength(0); } @Override public boolean parseAttributes(final XmlTag tag) { return LengthUtils.isNotEmpty(tag.attributes); } @Override public boolean skipCharacters() { return !grabContent; } @Override public void characters(final TextProvider p, final int start, final int length) { buf.append(p.chars, start, length); } }