Introduction
In this article I am going to discuss the various posibilities of working with XML files for storing your own custom configurations.
XML files are ideal for doing this sort of thing for a number of reasons...They absorb easily into Android Object Models (AOM) using the
XML DOM or SAX parsers. As we'll soon see, in a future expansion of this article, XML configurations could also be generated and ingested
into your objects via the SIMPLE method, which I have briefly touched on in my previous article.
.NET technique for converting large text
files into XML structures intended for Android objects
XML does not even require a physical storage location on the Android device! This implies
your configurations can be served on the fly through HTTP responses from a webservice or cloud service.
Just for the sake of an illustrative example, we can have one or more XML configuration files stored in the ASSETS folder initially and later
modify these default configurations and have them moved to the device SD Card. This way, the end user will always have a default configuration
to fall back on! I generally give end user the option to restore back to "the factory configuration" or more commonly refered to
as "default config". What this simply translates to, is a one-step approach of deleting the SD Card config and the software will
do all the smart checking for you to see if the user reverted back to default. Once the default config is modified, it gets copied back to
the SD Card. The user always has a copy of the original footprint and that is never lost. It could actually come from a config server if so
desired. I will discuss this attractive method in more detail later on. I also confess, this dual method is the way I generally prefer
to work with XML config files rather than store configuration in alternate databases. Overall, I find XML to be my method of choice for
storing meta-data, configuration specific data and as we saw in the previous article, I even use it in my e-reader application to absorb
entire e-book sections into my baseline AOM. Please see my last article for further clarification on what this XML data store means. Please make sure to
experiment with your own unique implementations as well, it will only help you master this technique...
Background
I will not take too much of your time discussing the exhaustive needs for implementing XML as a strategy for storing frequently changing information inside your app!
I'm not going to advocate you use this technique over other tools in the Android arsenal, you can better make up your own mind according to your needs and application
specifications. As I have mentioned before, I just seem to find XML favorable, because it is so easy to work with and extendable for a wide range of uses!
Its structure can be quite intuitively simple and self descriptive, by its very nature if you just happen to build it that way...It can offer a good clue as to the
final structure of your object model if that is not somehow already defined! Either way, one could easily traverse into the other! Sometimes,
I like to think of my object model exactly in terms of the XML structure itself. I have provided a small example of the configuration which
I have used in my e-book reader. This config contains all the Style Manager parameters used to model the fonts used throughout the body of the e-reader.
This can obviously be evolved to contain a myriad of many other options the core application, but for now, lets just imagine it for exactly what
I have designed it for and that is to format Font Styles inside the e-reader app. Lets take a closer look!
Using the code
<?xml version="1.0" encoding="utf-8"?>
<STYLEMANAGER>
<FONTSTYLES>
<DISPLAYTEXT>Book Title Font</DISPLAYTEXT>
<FONT>Celebration Text Fancy-Normal</FONT>
<FONTSIZE>37</FONTSIZE>
<FONTCOLOR>Ghost White</FONTCOLOR>
<FONTBOLD>False</FONTBOLD>
<FONTSHADOW></FONTSHADOW>
<DISPLAYTEXT>Book ID Font</DISPLAYTEXT>
<FONT>Caligrafia De Bula-Regio</FONT>
<FONTSIZE>28</FONTSIZE>
<FONTCOLOR>Black</FONTCOLOR>
<FONTBOLD>False</FONTBOLD>
<FONTSHADOW></FONTSHADOW>
<DISPLAYTEXT>Book Name Font</DISPLAYTEXT>
<FONT>Ballade Bold</FONT>
<FONTSIZE>27</FONTSIZE>
<FONTCOLOR>Red</FONTCOLOR>
<FONTBOLD>False</FONTBOLD>
<FONTSHADOW></FONTSHADOW>
<DISPLAYTEXT>Section Font</DISPLAYTEXT>
<FONT>Ballade Bold</FONT>
<FONTSIZE>15</FONTSIZE>
<FONTCOLOR>White</FONTCOLOR>
<FONTBOLD>False</FONTBOLD>
<FONTSHADOW></FONTSHADOW>
<DISPLAYTEXT>Verse Font</DISPLAYTEXT>
<FONT>Ballade Contour</FONT>
<FONTSIZE>25</FONTSIZE>
<FONTCOLOR>Aqua</FONTCOLOR>
<FONTBOLD>False</FONTBOLD>
<FONTSHADOW></FONTSHADOW>
</FONTSTYLES>
<SKINSTYLES>
<MAIN_BACKGROUND>Stationary Rust</MAIN_BACKGROUND>
<VERSES_BACKGROUND>Stationary Rust</VERSES_BACKGROUND>
<CLICKSELECTOR>Blue Selector with Bible</CLICKSELECTOR>
</SKINSTYLES>
</STYLEMANAGER>
The FontStyle Manager is broken down into two sections! From the XML structure we can determine that we have
</FONTSTYLES>
nodes and </SKINSTYLES>
nodes. It is quite self-explanatory, that some sections only repeat themselves. In this configuration
(5) five of such sections would consist of the various font style types. This is nice on many levels, because it makes the reader object
generously simple by easily indexing through the presented information found in this particular config file. So how can this information
be readily parsed and presented in object form? There are many ways to do this, but I will only present my own perspective on how this could
be done. You might come up with a more creative option, to better suit your own needs!
package com.solara.engineering.kjv.Parsers;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
public class XMLParser {
public XMLParser() {
}
public String getXmlFromUrl(String url) {
String xml = null;
try {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
xml = EntityUtils.toString(httpEntity);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return xml;
}
public String readXMLFromFile(Context activity, String xmlFile, boolean useConfigDir)
{
InputStream is = null;
File file = null;
File sdCard = null;
Writer writer = new StringWriter();
boolean exists = false;
if (useConfigDir)
{
sdCard = Environment.getExternalStorageDirectory();
file = new File (sdCard.getAbsolutePath() + "/kjv/config/" + xmlFile);
if (!(file == null))
{
if (exists = file.exists())
try {
is = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
if (!exists)
{
AssetManager assetManager = activity.getAssets();
try {
is = assetManager.open(xmlFile);
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null)
{
char[] buffer = new char[1024];
try
{
Reader reader = new BufferedReader(
new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
is.close();
}
catch (IOException e) {
Log.e("Error: ", e.getMessage());
return null;
}
}
return writer.toString();
}
public void writeXMLToFile(Context context, String xmlFile, String xmlData)
{
FileOutputStream fOut = null;
OutputStreamWriter osw = null;
try
{
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File (sdCard.getAbsolutePath() + "/kjv/config");
if (!dir.exists())
{
if (!(dir.mkdirs()))
Log.e("mkdirs", "Failed to create SDCARD mounted directory!!!");
}
fOut = new FileOutputStream(new File(dir, xmlFile));
osw = new OutputStreamWriter(fOut);
osw.write(xmlData);
osw.flush();
Toast.makeText(context, "Settings saved",Toast.LENGTH_SHORT).show();
}
catch (Exception e)
{
e.printStackTrace();
Toast.makeText(context, "Settings not saved",Toast.LENGTH_SHORT).show();
}
finally
{
try
{
osw.close();
fOut.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public Document getDomElement(String xml){
Document doc = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xml));
doc = db.parse(is);
} catch (ParserConfigurationException e) {
Log.e("Error: ", e.getMessage());
return null;
} catch (SAXException e) {
Log.e("Error: ", e.getMessage());
return null;
} catch (IOException e) {
Log.e("Error: ", e.getMessage());
return null;
}
return doc;
}
public final String getElementValue( Node elem ) {
Node child;
if( elem != null){
if (elem.hasChildNodes()){
for( child = elem.getFirstChild(); child != null; child = child.getNextSibling() ){
if( child.getNodeType() == Node.TEXT_NODE ){
return child.getNodeValue();
}
}
}
}
return "";
}
public String getValue(Element item, String str) {
NodeList n = item.getElementsByTagName(str);
return this.getElementValue(n.item(0));
}
public void setValue(Element elem, String str){
Node child;
if( elem != null){
if (elem.hasChildNodes()){
for( child = elem.getFirstChild(); child != null; child = child.getNextSibling() ){
if( child.getNodeType() == Node.TEXT_NODE ){
child.setNodeValue(str);
}
}
}
}
}
public String GetElementAttribute(Element item, String attribName){
return item.getAttribute(attribName);
}
}
The code I have listed above is a helper class called XMLParser.java which is used to load various XML configs through the two methods I have
specified above! One method, getXmlFromUrl()
will fetch it from an web server as an HTTP request for a file resource!
The other method, readXMLFromFile()
will fetch the XML config from a file stored in the Assets folder.
The AssetManager
will open and return this file as an InputStream
object. This is further
subdivided into independent fixed size chunks inside a Reader
buffer and written out in much the same way.
In the end you get your concatenated XML config that was just read from the original file, in a useful string format.
The boolean useConfigDir
, when set to True
, specifies that you want to use the SD Card
directory instead. Method writeXMLToFile()
will conversely, allow you to save these various XML configs
to SD Card and does all the required sanity checking for you! Feel free to expand and modify these functions to your heart's content!
They are not perfect by any means and are only used as a template mechanism for loading and saving XML configs. More efficient and perhaps
faster means of doing this may be available...Keep me informed if you come up with alternatives that work better and faster! This was my
quick fix and it worked relatively well for my application! As final closing statements on the helper code, I should mention that the last
few functions are only there for convenience and are responsible for either getting the XML Dom object hierarchy consisting of all the element
nodes or otherwise getting and setting values inside the element nodes. That's all there is to it! In the next section we will discuss a simple
implementation of this helper class!
package com.solara.engineering.kjv.StyleManager;
import java.io.StringWriter;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.solara.engineering.kjv.Parsers.XMLParser;
import android.content.Context;
public class KjvFontStyle
{
static final int maxPos = 4;
private int position = 0;
private String displayText;
private String font;
private String fontSize;
private String fontColor;
private String fontBold;
private String mainBackground;
private String versesBackground;
private String clickSelector;
static final String TAG_FONTSTYLES = "FONTSTYLES";
static final String TAG_SKINSTYLES = "SKINSTYLES";
static final String TAG_DISPLAYTEXT = "DISPLAYTEXT";
static final String TAG_FONT = "FONT";
static final String TAG_FONTSIZE = "FONTSIZE";
static final String TAG_FONTCOLOR = "FONTCOLOR";
static final String TAG_FONTBOLD = "FONTBOLD";
static final String TAG_MAIN_BACKGROUND = "MAIN_BACKGROUND";
static final String TAG_VERSES_BACKGROUND = "VERSES_BACKGROUND";
static final String TAG_CLICKSELECTOR = "CLICKSELECTOR";
XMLParser parser;
Document doc;
NodeList nl;
Element e;
Context context;
String xml;
public void LoadFontStyle(Context activity)
{
context = activity;
parser = new XMLParser();
xml = parser.readXMLFromFile(activity,"stylemanager.xml", true);
doc = parser.getDomElement(xml);
nl = doc.getElementsByTagName(TAG_DISPLAYTEXT);
Element e = (Element) nl.item(position);
displayText = parser.getValue(e, TAG_DISPLAYTEXT);
nl = doc.getElementsByTagName(TAG_FONT);
e = (Element) nl.item(position);
font = parser.getValue(e, TAG_FONT);
nl = doc.getElementsByTagName(TAG_FONTSIZE);
e = (Element) nl.item(position);
fontSize = parser.getValue(e, TAG_FONTSIZE);
nl = doc.getElementsByTagName(TAG_FONTCOLOR);
e = (Element) nl.item(position);
fontColor = parser.getValue(e, TAG_FONTCOLOR);
nl = doc.getElementsByTagName(TAG_FONTBOLD);
e = (Element) nl.item(position);
fontBold = parser.getValue(e, TAG_FONTBOLD);
nl = doc.getElementsByTagName(TAG_MAIN_BACKGROUND);
e = (Element) nl.item(0);
mainBackground = parser.getValue(e, TAG_MAIN_BACKGROUND);
nl = doc.getElementsByTagName(TAG_VERSES_BACKGROUND);
e = (Element) nl.item(0);
versesBackground = parser.getValue(e, TAG_VERSES_BACKGROUND);
nl = doc.getElementsByTagName(TAG_CLICKSELECTOR);
e = (Element) nl.item(0);
clickSelector = parser.getValue(e, TAG_CLICKSELECTOR);
}
public void SetPosition(int pos){
if (pos >= 0 && pos <= maxPos)
position = pos;
}
public int GetMaxPosition()
{
return maxPos;
}
public int GetPosition()
{
return position;
}
public void ResetPosition()
{
position = 0;
}
public String GetDisplayText()
{
return displayText;
}
public String GetFont()
{
return font;
}
public String GetFontSize()
{
return fontSize;
}
public String GetFontColor()
{
return fontColor;
}
public String GetFontBold()
{
return fontBold;
}
public String GetMainBackground()
{
return mainBackground;
}
public String GetVersesBackground()
{
return versesBackground;
}
public String GetClickSelector()
{
return clickSelector;
}
public void SetFont(String fontValue)
{
nl = doc.getElementsByTagName(TAG_FONT);
e = (Element) nl.item(position);
parser.setValue(e, fontValue);
font = parser.getValue(e, TAG_FONT);
}
public void SetFontSize(String fontSizeValue)
{
nl = doc.getElementsByTagName(TAG_FONTSIZE);
e = (Element) nl.item(position);
parser.setValue(e, fontSizeValue);
fontSize = parser.getValue(e, TAG_FONTSIZE);
}
public void SetFontColor(String fontColorValue)
{
nl = doc.getElementsByTagName(TAG_FONTCOLOR);
e = (Element) nl.item(position);
parser.setValue(e, fontColorValue);
fontColor = parser.getValue(e, TAG_FONTCOLOR);
}
public void SetFontBold(String fontBoldValue)
{
nl = doc.getElementsByTagName(TAG_FONTBOLD);
e = (Element) nl.item(position);
parser.setValue(e, fontBoldValue);
fontBold = parser.getValue(e, TAG_FONTBOLD);
}
public void SetMainBackground(String mainBackgroundImage)
{
nl = doc.getElementsByTagName(TAG_MAIN_BACKGROUND);
e = (Element) nl.item(0);
parser.setValue(e, mainBackgroundImage);
mainBackground = parser.getValue(e, TAG_MAIN_BACKGROUND);
}
public void SetVersesBackground(String versesBackgroundImage)
{
nl = doc.getElementsByTagName(TAG_VERSES_BACKGROUND);
e = (Element) nl.item(0);
parser.setValue(e, versesBackgroundImage);
versesBackground = parser.getValue(e, TAG_VERSES_BACKGROUND);
}
public void SetClickSelector(String clickSelectorImage)
{
nl = doc.getElementsByTagName(TAG_CLICKSELECTOR);
e = (Element) nl.item(0);
parser.setValue(e, clickSelectorImage);
clickSelector = parser.getValue(e, TAG_CLICKSELECTOR);
}
public void SaveXMLSettings()
{
String serializedXML = this.SerializeXML(doc);
parser.writeXMLToFile(context, "stylemanager.xml", serializedXML );
}
public String SerializeXML (Document doc)
{
Transformer transformer = null;
try {
transformer = TransformerFactory.newInstance().newTransformer();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
}
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
try {
transformer.transform(new DOMSource(doc), result);
} catch (TransformerException e) {
e.printStackTrace();
}
return writer.toString();
}
}
In this section of code, I proceed to demonstrate the implementation of the XML Helper class by encapsulating its use in a
KjvFontStyle
object.
I have also decided to include as a free gift, an XML Serializer since DOM did not posess one internally! This becomes quite
useful when modifying many of the internal node values and attributes inside the original XML configuration. Without the bonus serializer,
you would otherwise be burdened with having to devise a clever way to stringiffy this information back into the ubiquitous
String
format!
In a nutshell, this is what all those Transformer object manipulations inside the Serializer are for! Well, I think I might have exhausted
the topic of this article by now, and I hope that you have found these code listings useful! They could hopefully provide you with some insight
into the nature of the beast! The neat thing is that once you have done the main body of work using this parsing framework by adding your
own unique improvements, of course, it could be easily scaled to access an unlimited number of XML configurations of your own choosing!
One last topic I wanted to touch on briefly, as I promissed at the beginning of this article, is the beneficial use of the SIMPLE framework!
But since we ran out of space in this article, I will refrain from cluttering this section with anymore lengthy snippets of code from my somewhat
extensive e-reader framework! I will reveal more tips on these Android XML ingestion techniques in the next forth coming series quite soon!
Please check back often and if you find any of these articles useful, drop me a line and don't forget to give me some form of a rating nevertheless...
See you all again soon here on CP!
History
First revision 7/11/2012.