/*******************************************************************************
* Copyright (c) 2013 Gabriele Mariotti.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package it.gmariotti.changelibs.library.parser;
import android.content.Context;
import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import it.gmariotti.changelibs.library.Constants;
import it.gmariotti.changelibs.library.Util;
import it.gmariotti.changelibs.library.internal.ChangeLog;
import it.gmariotti.changelibs.library.internal.ChangeLogAdapter;
import it.gmariotti.changelibs.library.internal.ChangeLogException;
import it.gmariotti.changelibs.library.internal.ChangeLogRow;
import it.gmariotti.changelibs.library.internal.ChangeLogRowHeader;
/**
* Read and parse res/raw/changelog.xml.
* Example:
*
* <pre>
* XmlParser parse = new XmlParser(this);
* ChangeLog log=parse.readChangeLogFile();
* </pre>
*
* If you want to use a custom xml file, you can use:
* <pre>
* XmlParser parse = new XmlParser(this,R.raw.mycustomfile);
* ChangeLog log=parse.readChangeLogFile();
* </pre>
*
* It is a example for changelog.xml
* <pre>
* <?xml version="1.0" encoding="utf-8"?>
* <changelog bulletedList=false>
* <changelogversion versionName="1.2" changeDate="20/01/2013">
* <changelogtext>new feature to share data</changelogtext>
* <changelogtext>performance improvement</changelogtext>
* </changelogversion>
* <changelogversion versionName="1.1" changeDate="13/01/2013">
* <changelogtext>issue on wifi connection</changelogtext>*
* </changelogversion>*
* </changelog>
* </pre>
*
* @author Gabriele Mariotti (gabri.mariotti@gmail.com)
*
*/
public class XmlParser extends BaseParser {
/** TAG for logging **/
private static String TAG="XmlParser";
private int mChangeLogFileResourceId= Constants.mChangeLogFileResourceId;
private String mChangeLogFileResourceUrl= null;
protected ChangeLogAdapter mChangeLogAdapter;
//--------------------------------------------------------------------------------
//TAGs and ATTRIBUTEs in xml file
//--------------------------------------------------------------------------------
private static final String TAG_CHANGELOG="changelog";
private static final String TAG_CHANGELOGVERSION="changelogversion";
private static final String TAG_CHANGELOGTEXT="changelogtext";
private static final String TAG_CHANGELOGBUG="changelogbug";
private static final String TAG_CHANGELOGIMPROVEMENT="changelogimprovement";
private static final String ATTRIBUTE_BULLETEDLIST="bulletedList";
private static final String ATTRIBUTE_VERSIONNAME="versionName";
private static final String ATTRIBUTE_VERSIONCODE="versionCode";
private static final String ATTRIBUTE_CHANGEDATE="changeDate";
//private static final String ATTRIBUTE_CHANGETEXT="changeText";
private static final String ATTRIBUTE_CHANGETEXTTITLE= "changeTextTitle";
private static List<String> mChangeLogTags = new ArrayList<String>() {{
add(TAG_CHANGELOGBUG);
add(TAG_CHANGELOGIMPROVEMENT);
add(TAG_CHANGELOGTEXT);
}};
//--------------------------------------------------------------------------------
//Constructors
//--------------------------------------------------------------------------------
/**
* Create a new instance for a context.
*
* @param context current Context
*/
public XmlParser(Context context){
super(context);
}
/**
* Create a new instance for a context and for a custom changelogfile.
*
* You have to use file in res/raw folder.
*
* @param context current Context
* @param changeLogFileResourceId reference for a custom xml file
*/
public XmlParser(Context context,int changeLogFileResourceId){
super(context);
this.mChangeLogFileResourceId=changeLogFileResourceId;
}
/**
* Create a new instance for a context and with a custom url .
*
* @param context current Context
* @param changeLogFileResourceUrl url with xml files
*/
public XmlParser(Context context,String changeLogFileResourceUrl){
super(context);
this.mChangeLogFileResourceUrl=changeLogFileResourceUrl;
}
//--------------------------------------------------------------------------------
/**
* Read and parse res/raw/changelog.xml or custom file
*
* @throws Exception if changelog.xml or custom file is not found or if there are errors on parsing
*
* @return {@link ChangeLog} obj with all data
*/
@Override
public ChangeLog readChangeLogFile() throws Exception{
ChangeLog chg=null;
try {
InputStream is=null;
if (mChangeLogFileResourceUrl!=null){
if (Util.isConnected(super.mContext)){
URL url = new URL(mChangeLogFileResourceUrl);
is = url.openStream();
}
}else{
is = mContext.getResources().openRawResource(mChangeLogFileResourceId);
}
if (is!=null){
// Create a new XML Pull Parser.
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(is, null);
parser.nextTag();
// Create changelog obj that will contain all data
chg=new ChangeLog();
// Parse file
readChangeLogNode(parser, chg);
// Close inputstream
is.close();
}else{
Log.d(TAG,"Changelog.xml not found");
throw new ChangeLogException("Changelog.xml not found");
}
} catch (XmlPullParserException xpe) {
Log.d(TAG,"XmlPullParseException while parsing changelog file",xpe);
throw xpe;
} catch (IOException ioe){
Log.d(TAG,"Error i/o with changelog.xml",ioe);
throw ioe;
}
return chg;
}
/**
* Parse changelog node
*
* @param parser
* @param changeLog
*/
protected void readChangeLogNode(XmlPullParser parser,ChangeLog changeLog) throws Exception{
if (parser==null || changeLog==null) return;
// Parse changelog node
parser.require(XmlPullParser.START_TAG, null,TAG_CHANGELOG);
//Log.d(TAG,"Processing main tag=");
// Read attributes
String bulletedList = parser.getAttributeValue(null, ATTRIBUTE_BULLETEDLIST);
if (bulletedList==null || bulletedList.equals("true")){
changeLog.setBulletedList(true);
super.bulletedList=true;
}else{
changeLog.setBulletedList(false);
super.bulletedList=false;
}
//Parse nested nodes
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tag = parser.getName();
//Log.d(TAG,"Processing tag="+tag);
if (tag.equals(TAG_CHANGELOGVERSION)) {
readChangeLogVersionNode(parser, changeLog);
}
}
}
/**
* Parse changeLogVersion node
*
* @param parser
* @param changeLog
* @throws Exception
*/
protected void readChangeLogVersionNode(XmlPullParser parser, ChangeLog changeLog) throws Exception{
if (parser==null) return;
parser.require(XmlPullParser.START_TAG, null,TAG_CHANGELOGVERSION);
// Read attributes
String versionName = parser.getAttributeValue(null, ATTRIBUTE_VERSIONNAME);
String versionCodeStr = parser.getAttributeValue(null, ATTRIBUTE_VERSIONCODE);
int versionCode = 0;
if (versionCodeStr != null){
try {
versionCode = Integer.parseInt(versionCodeStr);
}catch (NumberFormatException ne){
Log.w(TAG,"Error while parsing versionCode.It must be a numeric value. Check you file.");
}
}
String changeDate= parser.getAttributeValue(null, ATTRIBUTE_CHANGEDATE);
if (versionName==null)
throw new ChangeLogException("VersionName required in changeLogVersion node");
ChangeLogRowHeader row=new ChangeLogRowHeader();
row.setVersionName(versionName);
//row.setVersionCode(versionCode);
row.setChangeDate(changeDate);
changeLog.addRow(row);
//Log.d(TAG,"Added rowHeader:"+row.toString());
// Parse nested nodes
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String tag = parser.getName();
//Log.d(TAG,"Processing tag="+tag);
if (mChangeLogTags.contains(tag)){
readChangeLogRowNode(parser, changeLog,versionName,versionCode);
}
}
}
/**
* Parse changeLogText node
*
* @param parser
* @param changeLog
* @throws Exception
*/
private void readChangeLogRowNode(XmlPullParser parser, ChangeLog changeLog, String versionName,int versionCode) throws Exception {
if (parser == null) return;
String tag = parser.getName();
ChangeLogRow row = new ChangeLogRow();
row.setVersionName(versionName);
row.setVersionCode(versionCode);
// Read attributes
String changeLogTextTitle = parser.getAttributeValue(null, ATTRIBUTE_CHANGETEXTTITLE);
if (changeLogTextTitle != null)
row.setChangeTextTitle(changeLogTextTitle);
// It is possible to force bulleted List
String bulletedList = parser.getAttributeValue(null, ATTRIBUTE_BULLETEDLIST);
if (bulletedList != null) {
if (bulletedList.equals("true")) {
row.setBulletedList(true);
} else {
row.setBulletedList(false);
}
} else {
row.setBulletedList(super.bulletedList);
}
// Read text
if (parser.next() == XmlPullParser.TEXT) {
String changeLogText = parser.getText();
if (changeLogText == null)
throw new ChangeLogException("ChangeLogText required in changeLogText node");
row.parseChangeText(changeLogText);
row.setType(tag.equalsIgnoreCase(TAG_CHANGELOGBUG) ? ChangeLogRow.BUGFIX : tag.equalsIgnoreCase(TAG_CHANGELOGIMPROVEMENT) ? ChangeLogRow.IMPROVEMENT : ChangeLogRow.DEFAULT);
parser.nextTag();
}
changeLog.addRow(row);
//Log.d(TAG, "Added row:" + row.toString());
}
public void setChangeLogAdapter(ChangeLogAdapter changeLogAdapter) {
mChangeLogAdapter = changeLogAdapter;
}
}