/*
* @copyright 2012 Philip Warner
* @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.goodreads.api;
import java.util.ArrayList;
import android.os.Bundle;
import com.eleybourn.bookcatalogue.goodreads.api.XmlFilter.ElementContext;
import com.eleybourn.bookcatalogue.goodreads.api.XmlFilter.XmlHandler;
/**
* Class layered on top of XmlFilter to implement a simple set of XML filters to extract
* data from an XML file and return the results in a collection of nested Bundle objects.
*
* @author Philip Warner
*/
public class SimpleXmlFilter {
private final XmlFilter mRootFilter;
private final ArrayList<BuilderContext> mContexts = new ArrayList<BuilderContext>();
private final ArrayList<String> mTags = new ArrayList<String>();
private final DataStore mRootData = new DataStore();
public interface XmlListener {
public void onStart(SimpleXmlFilter.BuilderContext bc, ElementContext c);
public void onFinish(SimpleXmlFilter.BuilderContext bc, ElementContext c);
}
public static interface DataStoreProvider {
public void addArrayItem(Bundle b);
public Bundle getData();
}
public class DataStore implements DataStoreProvider {
private final Bundle mData;
public DataStore() {
mData = new Bundle();
}
@Override
public void addArrayItem(Bundle b) {
throw new RuntimeException("Attempt to store array at root");
}
@Override
public Bundle getData() {
return mData;
}
}
public static class BuilderContext implements DataStoreProvider {
public DataStoreProvider parent;
String collectField;
private XmlFilter mFilter;
ArrayList<AttrFilter> attrs = null;
XmlListener listener = null;
XmlHandler finishHandler = null;
private Bundle mLocalBundle = null;
private ArrayList<Bundle> mArrayItems = null;
private boolean mIsArray = false;
private String mArrayName = null;
private boolean mIsArrayItem = false;
BuilderContext(XmlFilter root, DataStoreProvider parent, ArrayList<String> tags) {
if (parent == null)
throw new RuntimeException("Parent can not be null");
mFilter = XmlFilter.buildFilter(root, tags);
this.parent = parent;
mFilter.setStartAction(mHandleStart, this);
mFilter.setEndAction(mHandleFinish, this);
}
public void addArrayItem(Bundle b) {
if (mArrayItems != null)
mArrayItems.add(b);
else
parent.addArrayItem(b);
}
public void initArray() {
mArrayItems = new ArrayList<Bundle>();
}
public void saveArray() {
getData().putParcelableArrayList(mArrayName, mArrayItems);
mArrayItems = null;
}
public XmlFilter getFilter() {
return mFilter;
}
public Bundle getData() {
if (mLocalBundle != null)
return mLocalBundle;
else
return parent.getData();
}
public void pushBundle() {
if (mLocalBundle != null)
throw new RuntimeException("Bundle already pushed!");
mLocalBundle = new Bundle();
}
public Bundle popBundle() {
if (mLocalBundle == null)
throw new RuntimeException("Bundle not pushed!");
Bundle b = mLocalBundle;
mLocalBundle = null;
return b;
}
public boolean isArray() {
return mIsArray;
}
public void setArray(String name, boolean isArray) {
mIsArray = isArray;
mArrayName = name;
}
public boolean isArrayItem() {
return mIsArrayItem;
}
public void setArrayItem(boolean isArrayItem) {
mIsArrayItem = isArrayItem;
}
}
private abstract class AttrFilter {
public String name;
public String key;
public abstract void put(BuilderContext context, String value);
AttrFilter(String key, String name) {
this.name = name;
this.key = key;
}
}
public SimpleXmlFilter isArray(String arrayName) {
BuilderContext c = mContexts.get(mContexts.size()-1);
c.setArray(arrayName, true);
return this;
}
public SimpleXmlFilter isArrayItem() {
BuilderContext c = mContexts.get(mContexts.size()-1);
c.setArrayItem(true);
return this;
}
SimpleXmlFilter(XmlFilter root) {
mRootFilter = root;
}
public SimpleXmlFilter s(String tag) {
DataStoreProvider parent;
mTags.add(tag);
int size = mContexts.size();
if (size == 0)
parent = mRootData;
else
parent = mContexts.get(size-1);
mContexts.add(new BuilderContext(mRootFilter, parent, mTags));
return this;
}
public SimpleXmlFilter done() {
mTags.clear();
mContexts.clear();
return this;
}
public Bundle getData() {
return mRootData.getData();
}
private ArrayList<AttrFilter> getAttrFilters() {
BuilderContext c = mContexts.get(mContexts.size()-1);
if (c.attrs == null) {
c.attrs = new ArrayList<AttrFilter>();
}
return c.attrs;
}
public SimpleXmlFilter setListener(XmlListener listener) {
BuilderContext c = mContexts.get(mContexts.size()-1);
c.listener = listener;
return this;
}
public SimpleXmlFilter pop() {
mContexts.remove(mContexts.size()-1);
mTags.remove(mTags.size()-1);
return this;
}
public SimpleXmlFilter popTo(String tag) {
int last = mTags.size() - 1;
while( ! mTags.get(last).equalsIgnoreCase(tag) ) {
if (last == 0)
throw new RuntimeException("Unable to find parent tag :" + tag);
mContexts.remove(last);
mTags.remove(last);
last--;
}
return this;
}
private static XmlHandler mHandleStart = new XmlHandler() {
@Override
public void process(ElementContext context) {
BuilderContext bc = (BuilderContext)context.userArg;
if (bc.isArray()) {
bc.initArray();
}
if (bc.isArrayItem()) {
bc.pushBundle();
}
if (bc.listener != null)
bc.listener.onStart(bc, context);
ArrayList<AttrFilter> attrs = bc.attrs;
if (attrs != null) {
for(AttrFilter f: attrs) {
final String name = f.name;
final String value = context.attributes.getValue(name);
if (value != null) {
try {
f.put(bc, value);
} catch (Exception e) {
// Could not be parsed....just ignore
}
}
}
}
}
};
private static XmlHandler mHandleFinish = new XmlHandler() {
@Override
public void process(ElementContext context) {
BuilderContext bc = (BuilderContext)context.userArg;
if (bc.finishHandler != null)
bc.finishHandler.process(context);
if (bc.listener != null)
bc.listener.onFinish(bc, context);
if (bc.isArrayItem()) {
Bundle b = bc.popBundle();
bc.addArrayItem(b);
}
if (bc.isArray()) {
bc.saveArray();
}
}
};
public SimpleXmlFilter booleanAttr(String key, String attrName) {
ArrayList<AttrFilter> attrs = getAttrFilters();
attrs.add(new BooleanAttrFilter(key, attrName));
return this;
}
public SimpleXmlFilter doubleAttr(String attrName, String key) {
ArrayList<AttrFilter> attrs = getAttrFilters();
attrs.add(new DoubleAttrFilter(key, attrName));
return this;
}
public SimpleXmlFilter longAttr(String attrName, String key) {
ArrayList<AttrFilter> attrs = getAttrFilters();
attrs.add(new LongAttrFilter(key, attrName));
return this;
}
public SimpleXmlFilter stringAttr(String attrName, String key) {
ArrayList<AttrFilter> attrs = getAttrFilters();
attrs.add(new StringAttrFilter(key, attrName));
return this;
}
private void setCollector(String tag, XmlHandler handler, String fieldName) {
s(tag);
setCollector(handler, fieldName);
pop();
}
private void setCollector(XmlHandler handler, String fieldName) {
BuilderContext c = mContexts.get(mContexts.size()-1);
c.collectField = fieldName;
c.finishHandler = handler;
}
public SimpleXmlFilter booleanBody(String fieldName) {
setCollector(mBooleanHandler, fieldName);
return this;
}
public SimpleXmlFilter booleanBody(String tag, String fieldName) {
setCollector(tag, mBooleanHandler, fieldName);
return this;
}
public SimpleXmlFilter doubleBody(String fieldName) {
setCollector(mDoubleHandler, fieldName);
return this;
}
public SimpleXmlFilter doubleBody(String tag, String fieldName) {
setCollector(tag, mDoubleHandler, fieldName);
return this;
}
public SimpleXmlFilter longBody(String fieldName) {
setCollector(mLongHandler, fieldName);
return this;
}
public SimpleXmlFilter longBody(String tag, String fieldName) {
setCollector(tag, mLongHandler, fieldName);
return this;
}
public SimpleXmlFilter stringBody(String fieldName) {
setCollector(mTextHandler, fieldName);
return this;
}
public SimpleXmlFilter stringBody(String tag, String fieldName) {
setCollector(tag, mTextHandler, fieldName);
return this;
}
private static XmlHandler mTextHandler = new XmlHandler() {
@Override
public void process(ElementContext context) {
final BuilderContext c = (BuilderContext)context.userArg;
c.getData().putString(c.collectField, context.body.trim());
}
};
private static XmlHandler mLongHandler = new XmlHandler() {
@Override
public void process(ElementContext context) {
final BuilderContext c = (BuilderContext)context.userArg;
final String name = c.collectField;
try {
long l = Long.parseLong(context.body.trim());
c.getData().putLong(name, l);
} catch (Exception e) {
// Ignore but dont add
}
}
};
private static XmlHandler mDoubleHandler = new XmlHandler() {
@Override
public void process(ElementContext context) {
final BuilderContext c = (BuilderContext)context.userArg;
final String name = c.collectField;
try {
double d = Double.parseDouble(context.body.trim());
c.getData().putDouble(name, d);
} catch (Exception e) {
// Ignore but dont add
}
}
};
private static XmlHandler mBooleanHandler = new XmlHandler() {
@Override
public void process(ElementContext context) {
final BuilderContext c = (BuilderContext)context.userArg;
final String name = c.collectField;
try {
boolean b = textToBoolean(context.body.trim());
c.getData().putBoolean(name, b);
} catch (Exception e) {
// Ignore but dont add
}
}
};
private class StringAttrFilter extends AttrFilter {
StringAttrFilter(String key, String name) {
super(key, name);
}
public void put(BuilderContext context, String value) {
context.getData().putString(this.key, value);
}
}
private class LongAttrFilter extends AttrFilter {
LongAttrFilter(String key, String name) {
super(key, name);
}
public void put(BuilderContext context, String value) {
context.getData().putLong(this.key, Long.parseLong(value));
}
}
private class DoubleAttrFilter extends AttrFilter {
DoubleAttrFilter(String key, String name) {
super(key, name);
}
public void put(BuilderContext context, String value) {
context.getData().putDouble(this.key, Double.parseDouble(value));
}
}
private class BooleanAttrFilter extends AttrFilter {
BooleanAttrFilter(String key, String name) {
super(key, name);
}
public void put(BuilderContext context, String value) {
boolean b = textToBoolean(value.trim());
context.getData().putBoolean(this.key, b);
}
}
private static boolean textToBoolean(final String s) {
boolean b;
if (s.length() == 0) {
b = false;
} else if (s.equalsIgnoreCase("false")) {
b = false;
} else if (s.equalsIgnoreCase("true")) {
b = true;
} else if (s.equalsIgnoreCase("f")) {
b = false;
} else if (s.equalsIgnoreCase("t")) {
b = true;
} else {
long l = Long.parseLong(s);
b = (l != 0);
}
return b;
}
}