/* * @copyright 2010 Evan Leybourn * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue 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 3 of the License, or * (at your option) any later version. * * Book Catalogue 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 Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.os.Bundle; import com.eleybourn.bookcatalogue.booklist.DatabaseDefinitions; import com.eleybourn.bookcatalogue.utils.Logger; import com.eleybourn.bookcatalogue.utils.Utils; /** * An XML handler for the Amazon return * * An example response looks like; * <ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2005-10-05"> * <OperationRequest> * <HTTPHeaders> * <Header Name="UserAgent" Value="Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.29 Safari/533.4"> * </Header> * </HTTPHeaders> * <RequestId>df6342c0-c939-4b28-877a-5096da83a959</RequestId> * <Arguments> * <Argument Name="Operation" Value="ItemSearch"></Argument> * <Argument Name="Service" Value="AWSECommerceService"></Argument> * <Argument Name="Signature" Value="xHegBeDoF4U5vlC66493SULuAgQulFQSJyqHz3s2obE="></Argument> * <Argument Name="Keywords" Value="9780006483830"></Argument> * <Argument Name="Timestamp" Value="2010-05-16T01:30:12.000Z"></Argument> * <Argument Name="ResponseGroup" Value="Medium,Images"></Argument> * <Argument Name="SubscriptionId" Value="AKIAIHF2BM6OTOA23JEQ"></Argument> * <Argument Name="SearchIndex" Value="Books"></Argument> * </Arguments> * <RequestProcessingTime>0.0740140000000000</RequestProcessingTime> * </OperationRequest> * <Items> * <Request> * <IsValid>True</IsValid> * <ItemSearchRequest> * <Condition>New</Condition> * <DeliveryMethod>Ship</DeliveryMethod> * <Keywords>9780006483830</Keywords> * <MerchantId>Amazon</MerchantId> * <ResponseGroup>Medium</ResponseGroup> * <ResponseGroup>Images</ResponseGroup> * <SearchIndex>Books</SearchIndex> * </ItemSearchRequest> * </Request> * <TotalResults>1</TotalResults> * <TotalPages>1</TotalPages> * <Item> * <ASIN>0006483836</ASIN> * <DetailPageURL>http://www.amazon.com/TRIGGER-CLARKE-KUBE-MCDOWELL/dp/0006483836%3FSubscriptionId%3DAKIAIHF2BM6OTOA23JEQ%26tag%3Dws%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0006483836</DetailPageURL> * <SalesRank>4575972</SalesRank> * <SmallImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL._SL75_.jpg</URL> * <Height Units="pixels">75</Height> * <Width Units="pixels">48</Width> * </SmallImage> * <MediumImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL._SL160_.jpg</URL> * <Height Units="pixels">160</Height> * <Width Units="pixels">102</Width> * </MediumImage> * <LargeImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL.jpg</URL> * <Height Units="pixels">475</Height> * <Width Units="pixels">304</Width> * </LargeImage> * <ImageSets> * <ImageSet Category="primary"> * <SwatchImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL._SL30_.jpg</URL> * <Height Units="pixels">30</Height> * <Width Units="pixels">19</Width> * </SwatchImage> * <SmallImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL._SL75_.jpg</URL> * <Height Units="pixels">75</Height> * <Width Units="pixels">48</Width> * </SmallImage> * <MediumImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL._SL160_.jpg</URL> * <Height Units="pixels">160</Height> * <Width Units="pixels">102</Width> * </MediumImage> * <LargeImage> * <URL>http://ecx.images-amazon.com/images/I/51B6YYGE3PL.jpg</URL> * <Height Units="pixels">475</Height> * <Width Units="pixels">304</Width> * </LargeImage> * </ImageSet> * </ImageSets> * <ItemAttributes> * <Author>CLARKE / KUBE-MCDOWELL</Author> * <Binding>Paperback</Binding> * <DeweyDecimalNumber>823.914</DeweyDecimalNumber> * <EAN>9780006483830</EAN> * <Edition>New Ed</Edition> * <Format>Import</Format> * <ISBN>0006483836</ISBN> * <Label>Voyager</Label> * <Languages> * <Language> * <Name>English</Name> * <Type>Original Language</Type> * </Language> * <Language> * <Name>English</Name> * <Type>Unknown</Type> * </Language> * <Language> * <Name>English</Name> * <Type>Published</Type> * </Language> * </Languages> * <Manufacturer>Voyager</Manufacturer> * <NumberOfPages>560</NumberOfPages> * <PackageDimensions> * <Height Units="hundredths-inches">140</Height> * <Length Units="hundredths-inches">680</Length> * <Weight Units="hundredths-pounds">60</Weight> * <Width Units="hundredths-inches">430</Width> * </PackageDimensions> * <ProductGroup>Book</ProductGroup> * <PublicationDate>2000-01-01</PublicationDate> * <Publisher>Voyager</Publisher> * <Studio>Voyager</Studio> * <Title>TRIGGER</Title> * </ItemAttributes> * <OfferSummary> * <LowestUsedPrice> * <Amount>1</Amount> * <CurrencyCode>USD</CurrencyCode> * <FormattedPrice>$0.01</FormattedPrice> * </LowestUsedPrice> * <TotalNew>0</TotalNew> * <TotalUsed>43</TotalUsed> * <TotalCollectible>0</TotalCollectible> * <TotalRefurbished>0</TotalRefurbished> * </OfferSummary> * </Item> * </Items> * </ItemSearchResponse> * * @author evan * */ public class SearchAmazonHandler extends DefaultHandler { private Bundle mBookData; private StringBuilder mBuilder; private String mThumbnailUrl = ""; private int mThumbnailSize = -1; private static boolean mFetchThumbnail; /* How many results found */ public int count = 0; /* A flag to identify if we are in the correct node */ private boolean mInLanguage = false; private boolean mInListPrice = false; private String mCurrencyCode = ""; private String mCurrencyAmount = ""; private boolean entry = false; private boolean image = false; private boolean done = false; /* The XML element names */ public static String ID = "id"; public static String TOTALRESULTS = "TotalResults"; public static String ENTRY = "Item"; public static String AUTHOR = "Author"; public static String TITLE = "Title"; public static String ISBN = "EAN"; public static String EISBN = "EISBN"; public static String ISBNOLD = "ISBN"; public static String DATE_PUBLISHED = "PublicationDate"; public static String PUBLISHER = "Publisher"; public static String PAGES = "NumberOfPages"; public static String THUMBNAIL = "URL"; public static String SMALLIMAGE = "SmallImage"; public static String MEDIUMIMAGE = "MediumImage"; public static String LARGEIMAGE = "LargeImage"; public static String DESCRIPTION = "Content"; public static String BINDING = "Binding"; public static String LANGUAGE = "Language"; public static String NAME = "Name"; public static String LIST_PRICE = "ListPrice"; public static String CURRENCY_CODE = "CurrencyCode"; public static String AMOUNT = "Amount"; SearchAmazonHandler(Bundle bookData, boolean fetchThumbnail) { mBookData = bookData; mFetchThumbnail = fetchThumbnail; } // /* // * A public function the return a book structure // */ // public String[] getBook(){ // String[] book = {author, title, isbn, publisher, date_published, rating, bookshelf, read, series, pages, series_num, list_price, anthology, location, read_start, read_end, audiobook, signed, description, genre}; // return book; // } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); mBuilder.append(ch, start, length); } /** * Add the current characters to the book collection if not already present. * * @param key Key for data to add */ private void addIfNotPresent(String key) { if (!mBookData.containsKey(key) || mBookData.getString(key).length() == 0) { mBookData.putString(key, mBuilder.toString()); } } /** * Add the passed characters to the book collection if not already present. * * @param key Key for data to add */ private void addIfNotPresent(String key, String value) { if (!mBookData.containsKey(key) || mBookData.getString(key).length() == 0) { mBookData.putString(key, value); } } /** * Add the current characters to the book collection if not already present. * * @param key Key for data to add * @param value Value to compare to; if present but equal to this, it will be overwritten */ private void addIfNotPresentOrEqual(String key, String value) { if (!mBookData.containsKey(key) || mBookData.getString(key).length() == 0 || mBookData.getString(key).equals(value)) { mBookData.putString(key, mBuilder.toString()); } } /** * Overridden method to get the best thumbnail, if present. */ @Override public void endDocument() { if (mFetchThumbnail && mThumbnailUrl.length() > 0) { String filename = Utils.saveThumbnailFromUrl(mThumbnailUrl, "_AM"); if (filename.length() > 0) Utils.appendOrAdd(mBookData, "__thumbnail", filename); } } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) * * Populate the class variables for each appropriate element. * Also download the thumbnail and store in a tmp location */ @Override public void endElement(String uri, String localName, String name) throws SAXException { super.endElement(uri, localName, name); try { if (localName.equalsIgnoreCase(TOTALRESULTS)){ count = Integer.parseInt(mBuilder.toString()); } else if (localName.equalsIgnoreCase(THUMBNAIL)){ if (image == true) { mThumbnailUrl = mBuilder.toString(); image = false; } } else if (localName.equalsIgnoreCase(ENTRY)){ done = true; entry = false; } else if (localName.equalsIgnoreCase(LANGUAGE)){ mInLanguage = false; } else if (localName.equalsIgnoreCase(LIST_PRICE)){ if (mCurrencyCode.equalsIgnoreCase("usd") && !mCurrencyAmount.equals("")) { try { Float price = Float.parseFloat(mCurrencyAmount) / 100; addIfNotPresent(CatalogueDBAdapter.KEY_LIST_PRICE, String.format("%.2f", price)); } catch(Exception e) { // Ignore } } mCurrencyCode = ""; mCurrencyAmount = ""; mInListPrice = false; } else if (entry == true) { if (localName.equalsIgnoreCase(AUTHOR)){ Utils.appendOrAdd(mBookData, CatalogueDBAdapter.KEY_AUTHOR_DETAILS, mBuilder.toString()); } else if (localName.equalsIgnoreCase(TITLE)){ addIfNotPresent(CatalogueDBAdapter.KEY_TITLE); } else if (localName.equalsIgnoreCase(ISBN) || localName.equalsIgnoreCase(EISBN)){ String tmp = mBuilder.toString(); if (!mBookData.containsKey(CatalogueDBAdapter.KEY_ISBN) || mBookData.getString(CatalogueDBAdapter.KEY_ISBN).length() < tmp.length()) { mBookData.putString(CatalogueDBAdapter.KEY_ISBN, tmp); } } else if (localName.equalsIgnoreCase(ISBNOLD)){ String tmp = mBuilder.toString(); if (!mBookData.containsKey(CatalogueDBAdapter.KEY_ISBN) || mBookData.getString(CatalogueDBAdapter.KEY_ISBN).length() < tmp.length()) { mBookData.putString(CatalogueDBAdapter.KEY_ISBN, tmp); } } else if (localName.equalsIgnoreCase(PUBLISHER)){ addIfNotPresent(CatalogueDBAdapter.KEY_PUBLISHER); } else if (localName.equalsIgnoreCase(DATE_PUBLISHED)){ addIfNotPresent(CatalogueDBAdapter.KEY_DATE_PUBLISHED); } else if (localName.equalsIgnoreCase(PAGES)){ addIfNotPresentOrEqual(CatalogueDBAdapter.KEY_PAGES, "0"); } else if (localName.equalsIgnoreCase(DESCRIPTION)){ addIfNotPresent(CatalogueDBAdapter.KEY_DESCRIPTION); } else if (localName.equalsIgnoreCase(BINDING)){ addIfNotPresent(CatalogueDBAdapter.KEY_FORMAT); } else if (mInLanguage && localName.equalsIgnoreCase(NAME)){ addIfNotPresent(DatabaseDefinitions.DOM_LANGUAGE.name); } else if (mInListPrice && localName.equalsIgnoreCase(AMOUNT)){ mCurrencyAmount = mBuilder.toString(); } else if (mInListPrice && localName.equalsIgnoreCase(CURRENCY_CODE)){ mCurrencyCode = mBuilder.toString(); } else { // Debug Only; see what we are missing. //System.out.println(localName + "->'" + mBuilder.toString() + "'"); } } mBuilder.setLength(0); } catch (Exception e) { Logger.logError(e); } } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startDocument() * * Start the XML document and the StringBuilder object */ @Override public void startDocument() throws SAXException { super.startDocument(); mBuilder = new StringBuilder(); } /* * (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) * * Start each XML element. Specifically identify when we are in the item element and set the appropriate flag. */ @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { super.startElement(uri, localName, name, attributes); if (done == false && localName.equalsIgnoreCase(ENTRY)){ entry = true; } else if (localName.equalsIgnoreCase(SMALLIMAGE)) { if (mThumbnailSize < 1) { image = true; mThumbnailSize = 1; } } else if (localName.equalsIgnoreCase(MEDIUMIMAGE)) { if (mThumbnailSize < 2) { image = true; mThumbnailSize = 2; } } else if (localName.equalsIgnoreCase(LARGEIMAGE)){ if (mThumbnailSize < 3) { image = true; mThumbnailSize = 3; } } else if (localName.equalsIgnoreCase(LANGUAGE)){ mInLanguage = true; } else if (localName.equalsIgnoreCase(LIST_PRICE)){ mInListPrice = true; } } }