Thursday, May 14, 2009

How to embed images into XML files



The title may sound confusing.We all know XML as plain text formats while images as binary files.So how can we embed images inside a XML file?Is it some kind of links to the external image files like the web pages?Not exactly.The image is completely embedded inside the nodes of XML in text format as base64 string.

The Base64 Format:


Base64 converts binary data to plain text using 64 case-sensitive, printable ASCII characters: A-Z, a-z, 0-9, plus sign (+) and forward slash (/), and may be terminated with 0-2 "padding" characters represented by the equal sign (=). For example, the eight-byte binary data in hex "35 71 4d 8e 4c 5f db 42″ converts to Base64 text as "NXFNjkxf20I=".

To accomplish this, we need some kind of encoder application that converts the image into the base64 format and embed the text into XML.To extract the image from the XML,we need another application that reads the base64 string,decodes it into the image again.In .NET Framework class library(FCL), we have convenient components in the "System.Convert" namespace to perform this encoding and decoding.Let's look at an example to understand the process.

The Base64 Encoder:

static void Main()
{
string base64FormattedImage= string.empty;
string imageFilePath = @"C:\Images\nature.jpg";
base64FormattedImage= EncodeToBase64FromImage(imageFilePath);

if (!string.IsNullOrEmpty(base64String))
{
string destinationXMLFile = @"C:\XML\NewsContent.xml";
WriteToXML(base64FormattedImage,destinationXMLFile);
}
}

private string EncodeToBase64FromImage(string imageFilePath)
{
System.IO.FileStream inFile;
byte[] binaryData = null;
string base64String=string.Empty;

try
{
inFile = new System.IO.FileStream(imageFilePath,
System.IO.FileMode.Open,
System.IO.FileAccess.Read);
binaryData = new Byte[inFile.Length];
long bytesRead = inFile.Read(binaryData, 0,
(int)inFile.Length);
inFile.Close();
}
catch (System.Exception exp)
{
// Error creating stream or reading from it.
System.Console.WriteLine("{0}", exp.Message);

}
// Convert the binary input into Base64 Encoded output.
try
{
base64String =
System.Convert.ToBase64String(binaryData,
0,
binaryData.Length);
}
catch (System.ArgumentNullException)
{
System.Console.WriteLine("Binary data array is null.");

}

return base64String;
}

private void WriteToXML(string base64FormattedImage,string destinationXMLFile)
{
XmlDocument doc = new XmlDocument();
doc.Load(destinationXMLFile);
XmlNode nd = doc.SelectSingleNode("/contents/content/Images").FirstChild;
nd.InnerText = base64FormattedImage;
doc.Save(destinationXMLFile);
doc = null;
}


The encoder reads an image file from the disk,converts it into Byte array and calls the "System.Convert.ToBase64String" method to get the base64 string from the Byte array.Then main method passes the base64 string to WriteToXML method to save into an XML file.

The XML file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<contents>
<content>
<ID>1</ID>
<Title>The title</Title>
<Body>
Content goes here..
</Body>
<Images>
<Image ID="1">/9j/4AAQSkZJRgABAgEAYABgAAD/4RBY ......</Image>
</Images>
</content>
</contents>


The Base64 Decoder:

static void Main()
{
string sourceXMLFilePath= @"C:\XML\NewsContent.xml";
string destinationImageFilePath = @"C:\Images\nature.jpg";
DecodeFromBase64ToImage(sourceXMLFilePath, destinationImageFilePath);
}

private void DecodeFromBase64ToImage(string sourceXMLFilePath, string destinationImageFilePath)
{
XmlDocument doc = new XmlDocument();
doc.Load(sourceXMLFilePath);
XmlNode nd = doc.SelectSingleNode("/contents/content/Images").FirstChild;
string base64String = nd.InnerText;

byte[] binaryData;
try
{
binaryData =
System.Convert.FromBase64String(base64String);
}
catch (System.ArgumentNullException)
{
System.Console.WriteLine("Base 64 string is null.");
return;
}
catch (System.FormatException)
{
System.Console.WriteLine("Base 64 string length is not " +
"4 or is not an even multiple of 4.");
return;
}

// Write out the decoded data.
System.IO.FileStream outFile;

try
{
outFile = new System.IO.FileStream(destinationImageFilePath,
System.IO.FileMode.Create,
System.IO.FileAccess.Write);
outFile.Write(binaryData, 0, binaryData.Length);
outFile.Close();
}
catch (System.Exception exp)
{
// Error creating stream or writing to it.
System.Console.WriteLine("{0}", exp.Message);
}
}


The decoder parses the XML document,retrieves the base64 string,converts it into Byte Array by calling the "System.Convert.FromBase64String" method.It then saves the Byte Array as an image file into the disk.

This approach is widely used in the online news media industry.The news media companies exchange articles and images among themselves in XML format.The schema of the XML usually follows the popular NITF(News Industry Text Format) schema or DTD.You can find more about NITF Here.

Monday, May 4, 2009

Creating a Private Ruby Gem Server



While installing a new ruby gem in a machine,we normally use the gem install <gem name> command.This command downloads the latest version of a particular gem.The problem is that ROR gems are not always backward compatible.So if a ROR application was developed using a particular version of a gem (Ex:0.1.1) and the latest version of the gem in the internet (normally rubyforge.org) is 0.1.2,problem may arise if the version 0.1.2 is not backward compatible with version 0.1.1.This may cause a serious trouble when the application is deployed into the web server and the wrong version of the gem is installed.Most probably the application will not function as expected.

A solution to this problem is to deploy the exact version of all the gems those were used during development.One way to implement this is hosting the correct version of the gems into a web server(thus call it a private gem server).I have tested this option in a Apache web server.The steps are as follows:

1.Create a directory for hosting gems on the public files area of the web server.Let's refer is as <Base Directory>.

ssh user@web.server
cd /var/www
mkdir my_gem_server


2.Create a sub directory called "gems" under the <Base Directory>.The name of the sub directory must be "gems" by convention.

ssh user@web.server
cd /var/www/my_gem_server
mkdir gems


3.Copy all the necessary gems from the development machine into the /var/www/my_gem_server/gems sub directory.

4.Generate the gem index:

gem comes with a command generate_index which generates all of the files necessary for serving gems over HTTP.Run this command into the <Base Directory> (/var/www/my_gem_server).

gem generate_index -d /var/www/my_gem_server

Now the private gem server is ready to serve the gems for download and install.

To install the private gems into the target web server where the ROR application will be deployed,log in and run the following command.

gem install <gem name> --source http://<private gem server>/<Base Directory>

The correct version of the gems will be installed and thus the application integrity will be maintained.We need to rerun the gem generate_index command each time we add or remove a gem from the private gem server.