Page 1 of 1

Parse Dream.In.Code XML using Managed C++ & Visual Studio. Rate Topic: -----

#1 sarmanu  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 966
  • View blog
  • Posts: 2,362
  • Joined: 04-December 09

Posted 01 July 2010 - 03:50 AM

Hello everyone. In this tutorial I'm gonna show you how to parse the dynamic XML of Dream.In.Code. Besides that, you are going to learn how to create a Windows Form, add labels, buttons, and text boxes to it.

A little bit about D.I.C XML page:
This XML page contains information about every user, such as name, rating, join date, reputation, etc. It is located at: http://www.dreaminco...howuser=USER_ID, where USER_ID represents the ID of the user. For example, 34 is the USER_ID of skyhawk133, so, use http://www.dreaminco...php?showuser=34 to jump to his XML page. Ok, let's start!

Setting up the project:
I just told you that we are going to use C++/CLI for this task, and Visual Studio. So, start up Visual Studio (I used VS2008), and go to File->New->Project. Here, you are going to select CLR. Now, you have a list of CLR templates. Select CLR Console Application, then give your project a name (I used DICInfo). Press Enter. Now, my project contains a bunch of files, like DICInfo.cpp (which is going to be the only file that we will work with), AssemblyInfo.cpp, stdafx.h/.cpp, and the resource files.

Creating the application:
In your main project file (YourProjectName.cpp, in my case: DICInfo.cpp), you have a sample of the classic Hello World! application, auto-generated by VS. We will not need it, so simply delete everything. You should have a blank file now.
Since we are going to deal with Windows Forms, then some specific .dlls must be added to the project. Copy-paste the following into main project file:
// DICInfo.cpp : main project file.

#include "stdafx.h"

// Drawing.dll and Windows.Forms.dll allows us to
// import specific classes, needed for creating
// the form.
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>

// Namespaces, they are optional, but they 
// are a time-saver. E.g instead of System::Xml::XmlReader
// you will simply write XmlReader.
using namespace System;
using namespace System::ComponentModel;
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::Xml;
using namespace System::Net;
using namespace System::IO;


And done. Now, you have everything you need to design your own form!
Before creating the main class, we are going to create two global functions. The first one will be a function that extracts image data from a specific URL, and appends it to a Bitmap class. The second one is one of the most important function in the entire program: it jumps to a specific sub-element in the XML page. The code for both of the functions (copy-paste into your main project file):
// Load an image from URL
Bitmap ^LoadPicture(String ^URL)
{
	Stream ^bmpStream;
	Bitmap ^bmpImage;
	HttpWebRequest ^req;
	HttpWebResponse ^response;

	// Create the web request from the URL
	req = (HttpWebRequest ^)WebRequest::Create(URL);
	// Can write to stream
	req->AllowWriteStreamBuffering = true;
	// Returns the response from our URL
	response = (HttpWebResponse^)req->GetResponse();

	// Read the response that was just sent. GetResponseStream
    // returns nullptr if the URL was invalid.
	bmpStream = response->GetResponseStream();
	// Use the Bitmap constructor to build the image from the stream
	bmpImage = bmpStream != nullptr ? gcnew Bitmap(bmpStream) : nullptr;
	if (bmpImage == nullptr)
		throw (String ^)"Invalid image URL";
        
    // Close the streams and return the newly created
    // bitmap.
	bmpStream->Close();
	response->Close();

	return (bmpImage);
}

// Jump to a specific element, given as parameter.
void JumpToElement(String ^elemNode, XmlTextReader ^XmlReader)
{
    // XmlTextReader throws exceptions. I'm not too sure
    // that this is the case where we need a try-catch
    // since DIC XML page is not broken. I hope :)/>
	try
	{
        // Read() keeps on reading until the end of
        // XML page was encountered.
		while (XmlReader->Read())
		{
	        // Since we linearly loop through XML nodes, compare
            // each one with elemNode.
			if (XmlReader->Name->Equals(elemNode))
			{
                // Got to the desired node. Read it's data
                // and return!
				XmlReader->Read();
				return;
			}
		}
	}
	catch (System::Xml::XmlException ^exc)
	{
		Console::WriteLine("{0}", exc);
	}
}


And done! Now, let's create the main class, which will be derived from Form class.

Creating the main class:
Creating this class, which will inherit Form class member functions is a vital step for our program. Without this class, we will not be able to draw the form. Let's start by giving it a name. I have used DICInfo:
// Header of the class, this is how it should
// look like.
ref class DICInfo : public Form 
{
   // Member functions & variables will go here.
};


Paste the above in your main project file. Now, we have to populate the class with member functions and member variables. The form will contain: a text box, where we will enter the user ID; a button, to submit the user ID; the photo of the user (not avatar, profile photo); and the following labels: name; group; posts; join date; reputation and rating. So, let's add these to our class. Copy-paste the following into your private: section of the class:
// The text box.
TextBox ^textBox;

// The button.
Button ^button;

// Labels.
Label ^imgLabel, ^nameLabel, ^groupLabel, ^ratingLabel;
Label ^repLabel, ^postsLabel, ^joinedLabel;

// Picture box, will contain the photo.
PictureBox ^pic;

// The user ID that we will enter into the text box.
String ^userID;

// The following represents the name, group, rating,
// rep, posts, join date, and URL of the user -> all
// of the are extracted from the DIC XML.
String ^name, ^URL, ^group, ^rating, ^reputation;
String ^posts, ^joined;

// This is specific to user rating. It will be populated
// with five images. Each image represents a star. Based
// on user rating, we will draw as many stars as we have
// to.
array<PictureBox ^> ^pb_array;


Those are the variables that we will be working with. I hope that everything was self explanatory.
Now, in the same private zone of the class, we will create a very simple function which will make sure that we have entered a valid integer, in the text box. Copy-paste it:
// Validate text. Simply checks if it contains non-digits.
bool isValidFormat(String ^text)
{
    for (int i = 0; i < text->Length; i++)
	if (System::Char::IsLetter(text[i]))
	    return false;

    return true;
}


Now, we are going to create the function that sets the attributes of the form. In the same private section of your class, copy-paste this:
void InitForm()
{
   // Text sets the caption (title).
   this->Text = "DreamInCode User Information";
   // Border style: fixed 3D, you can experiment with more.
   this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::Fixed3D;
   // Signals that we can't maximize the window.
   this->MaximizeBox = false;
   // Size: 300 by 500.
   this->Size = System::Drawing::Size(300, 500);
}


Furthermore, we will have to create a function that removes every label and the picture, from our form. At every button press the form will be cleared from the labels & picture, then it will draw them again. Copy-paste the following function into the same private zone of your class:
void DestroyAll()
{
   // Remove every label + image. That's what
   // Controls->Remove does. It simply removes
   // a control from the form.
   this->Controls->Remove(imgLabel);
   this->Controls->Remove(pic);
   this->Controls->Remove(nameLabel);
   this->Controls->Remove(groupLabel);
   this->Controls->Remove(ratingLabel); 
   this->Controls->Remove(repLabel);
   this->Controls->Remove(postsLabel);
   this->Controls->Remove(joinedLabel);
}


Now, after you have copy-pasted that, we will create two more member functions, that deals with colors: one which will color the text of the label repLabel (based on reputation, the text color can be red, grey, or green), and one which will color the text of the label groupLabel (e.g: for Webmaster, the color will be RED, for moderators, the color will be Dark blue, etc ...). Copy-paste the following, again, into your private zone of the class:
// Set the color of reputation label, based on reputation value.
void SetFontRepLabel(Label ^repLabel)
{
    int rVal = Convert::ToInt32(reputation);
    // Negative rep -> red.
    if (rVal < 0)
	repLabel->ForeColor = System::Drawing::Color::DarkRed;
    // 0 : 10 -> neutral, color unchanged.
    else if (rVal >= 0 && rVal <= 10)
	return;
    // 11 : 20 -> good, color forest green.
    else if (rVal >= 11 && rVal <= 20)
	repLabel->ForeColor = System::Drawing::Color::ForestGreen;
    else
	// Bigger or equal to 21, green.
	repLabel->ForeColor = System::Drawing::Color::Green;
}

// Based on group, change group label color.
// RGB values taken directly from DIC.
void SetFontGroupLabel(Label ^groupLabel)
{
    // Text color of a label is changed using ForeColor property.
    if (group->Equals("Expert") || group->Equals("Expert w/DIC++"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(153, 51, 153);
    else if (group->Equals("Moderators"))
	groupLabel->ForeColor = System::Drawing::Color::Blue;
    else if (group->Equals("Mentors"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(0, 204, 204);
    else if (group->Equals("Alumni"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(255, 102, 204);
    else if (group->Equals("Webmaster"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(223, 0, 0);
    else if (group->Equals("Author w/DIC++") || group->Equals("Authors"))
        groupLabel->ForeColor = System::Drawing::Color::FromArgb(153, 153, 51);
    else if (group->Equals("Contributors") || group->Equals("Contributor w/DIC++"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(204, 102, 51);
    else if (group->Equals("Greeters"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(153, 0, 0);
    else if (group->Equals("Admins"))
	groupLabel->ForeColor = System::Drawing::Color::FromArgb(0, 98, 0);
    else 
	groupLabel->ForeColor = System::Drawing::Color::CadetBlue;
}


So, as you can see, the ForeColor attribute of a Label, changes it's text color.
Great, let's more further. Now, we will make a function that loads the stars images into pb_array (remember, the member variable, as a cli::array). Also, we will create two more functions: one that draws the stars right after the ratingLabel and a function that removes the stars. Copy-paste the following code into your private section of the class:
// This function will be called only once, when the form
// is loaded.
void InitRatingStarsArray()
{
    // Initialize the PictureBox array with five pictures (maximum available).
    pb_array = gcnew array<PictureBox ^>(5);
    for (size_t i = 0; i < 5; i++)
    {
	// Set attributes of the images.
       
        // pb_array[i] is a PictureBox, so allocate memory first.
	pb_array[i] = gcnew PictureBox;
        // The size of the image, 15x15 should be good.
	pb_array[i]->Size = System::Drawing::Size(15, 15);
	// i * 12 spacing should be sufficient.
	pb_array[i]->Location = Point(85 + (i * 12), 273);
	// The link containing the star.
	pb_array[i]->Image = LoadPicture("http://http.cdnlayer.com/dreamincode/forums/public/style_images/DIC/bullet_star.png");
    }
}

// Based on rating, draw the stars.
void DrawStars()
{
     // We will draw as many stars as the user rating.
     for (int i = 0; i < Convert::ToInt32(rating); i++)
	 this->Controls->Add(pb_array[i]);
}

// Remove the stars from the form.
void DeleteStars()
{
    for (int i = 0; i < Convert::ToInt32(rating); i++)
	 this->Controls->Remove(pb_array[i]);
}


So, what did you learn from the above? Well, you have learned that Size property of a PictureBox sets it's size, that the Location property sets it's location on the form, and the the Image property simply loads the image into your PictureBox.
Great, we are done with that. Now, the fun part: draw every label & picture on the form. Copy-paste the following function into the same private zone of your class:
// Called when the button is pressed.
void DrawAll()
{
    // Draw image label:
    imgLabel = gcnew Label;
    imgLabel->Size = System::Drawing::Size(52, 20);
    imgLabel->Location = Point(32, 35);
    imgLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);
    imgLabel->Text = "Photo:";

    this->Controls->Add(imgLabel);

    // Draw the photo of the user:
    pic = gcnew PictureBox;
    pic->Size = System::Drawing::Size(300, 150);
    pic->Location = Point(64, 56);
    try
    {
	 pic->Image = LoadPicture(URL);
    }
    catch (String ^exc)
    {
	 Console::WriteLine("{0}", exc);
    }

    this->Controls->Add(pic);

    // Draw the name label:
    nameLabel = gcnew Label;
    nameLabel->Size = System::Drawing::Size(200, 20);
    nameLabel->Location = Point(32, 220);
    nameLabel->Text = "Name: " + name;
    nameLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(nameLabel);

    // Draw the group label:
    groupLabel = gcnew Label;
    groupLabel->Size = System::Drawing::Size(190, 20);
    groupLabel->Location = Point(32, 245);
    groupLabel->Text = "Group: " + group;
    SetFontGroupLabel(groupLabel);
    groupLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(groupLabel);

    // Draw the rating label:
    ratingLabel = gcnew Label;
    ratingLabel->Size = System::Drawing::Size(Convert::ToUInt32(rating) > 0 ? 55 : 95, 20);
    ratingLabel->Location = Point(32, 270);
    ratingLabel->Text = Convert::ToUInt32(rating) > 0 ? "Rating: " : "Rating: N/A";
    ratingLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(ratingLabel);
    DrawStars();

    // Draw the reputation label:
    repLabel = gcnew Label;
    repLabel->Size = System::Drawing::Size(150, 20);
    repLabel->Location = Point(32, 295);
    repLabel->Text = "Reputation: " + reputation;
    SetFontRepLabel(repLabel);
    repLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(repLabel);

    // Draw the posts label:
    postsLabel = gcnew Label;
    postsLabel->Size = System::Drawing::Size(150, 20);
    postsLabel->Location = Point(32, 320);
    postsLabel->Text = "Posts: " + posts;
    postsLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(postsLabel);

    // Join date label:
    joinedLabel = gcnew Label;
    joinedLabel->Size = System::Drawing::Size(200, 20);
    joinedLabel->Location = Point(32, 345);
    joinedLabel->Text = "Joined: " + joined;
    joinedLabel->Font = gcnew System::Drawing::Font("Times New Roman", 12.0f);

    this->Controls->Add(joinedLabel);
}


Now, some things you should remember:
this->Controls->Add(Control);


simply adds a control to the form. It can be a label, a PictureBox, a CheckBox, etc.
About Labels properties:
1. Size property - sets the size of the label.
2. Location property - sets the location of the label.
3. Font property - sets the font of the label. Should be an instance of System::Drawing::Font.
And done with the drawings. Now, that we are almost done with the GUI part, let's deal with the XmlTextReader. We will be using it to perform XML elements extraction, from an URL. The following function extracts the group subelement, from the XML page. The function is commented so you should have no trouble understanding it. Copy-paste it into private zone of the class:
String ^getGroup(String ^path)
{
   XmlTextReader ^reader = gcnew XmlTextReader(path);
   JumpToElement("group", reader);
   // This if executes in case of Members and New Members, because
   // the <group> element doesn't have the <span> subelement.
   if (reader->Value->Length != 0)
	  return reader->Value;
   else
   {
	   // No other way but the open the reader again, because we simply
	   // can't go back to read the group again. That's because of the 
	   // linear search. 
	   reader->Close();
	   reader = gcnew XmlTextReader(path);
	   try
	   {
	        while (reader->Read())
	        {
		         // Got to <group>
		         if (reader->MoveToContent() == XmlNodeType::Element && reader->Name->Equals("group"))
	             {
		              // Got to <span>
		              JumpToElement("span", reader);
		              return reader->Value;
		         }
	        }
        }
	    catch (System::Xml::XmlException ^exc)
	    {
	         Console::WriteLine("{0}", exc);
	    }
    }

    return "Error";
}


So, as you can see, Value property of the XmlTextReader returns the current data of a XML node. The MoveToContent of the XmlTextReader simply moves to "content" (skips whitespace, comments, etc.).
Great, done with that too! Now, let's create the function that extracts the attributes from the XML: reputation, rating, URL of image, etc. Paste this into the same private zone of the class:
void GetData(XmlTextReader ^reader, String ^path)
{
   // Extracts the name of the user:
   JumpToElement("name", reader);
   name = reader->Value;

   // Extract the group. Perform special action in that function.
   group = getGroup(path);

   // Extract rating before photo. XmlTextReader performs
   // linear search, and we can't go back. <photo> element
   // is below <rating>.
   JumpToElement("rating", reader);
   rating = reader->Value;

   // Extracts the photo URL:
   JumpToElement("photo", reader);
   URL = reader->Value;

   // Extracts the reputation:
   JumpToElement("reputation", reader);
   reputation = reader->Value;

   // Extracts posts:
   JumpToElement("posts", reader);
   posts = reader->Value;

   // Join date:
   JumpToElement("joined", reader);
   joined = reader->Value;
}


So, as you can see, we simply jump to specific nodes, and extract data. Easy.
Now, let's create the function that performs the data extraction. It is a function that does nothing but move to <profile> attribute of the XML page, then it simply calls the last two functions that I've presented. It looks like this (again, simply copy-paste it in the private section of the class):
void PerformExtraction(bool &correctUser)
{
    // Build the complete path to the XML page, and open the XML reader.
    String ^path = "http://www.dreamincode.net/forums/xml.php?showuser=" + userID;
    XmlTextReader ^reader = gcnew XmlTextReader(path);

    correctUser = false;
    try
    {
	     while (reader->Read())
	     {
	           // Got to <profile>
	           if (reader->MoveToContent() == XmlNodeType::Element && reader->Name->Equals("profile"))
	           {
		            // Signals that the user ID is correct, and perform data extraction.
		            correctUser = true;
		            GetData(reader, path);
	                    break;
	           }		
	     }
     }
     catch (System::Xml::XmlException ^exc)
     {
	     // Let's keep exceptions in console.
	     Console::WriteLine("{0}", exc);
     }
}


And at this point, we are almost done. We only need to implement an onclick function, that will be called whenever the button is pressed. Copy-paste it in your private section of the class:
void on_click(Object ^sender, System::EventArgs ^e)
{
    // Same ID provided more than once? Simply perform nothing.
    // User details already loaded.
    if (userID == textBox->Text)
	     return;

    // Extract the userID from the textbox, and validate it.
    userID = textBox->Text;
    if (!isValidFormat(userID) || userID->Length == 0)
    {
	    MessageBox::Show("Please enter a valid integer!", "DICInfo");
	    return;
    }
    
    DestroyAll();
    DeleteStars();

    // Deal with XML data extraction.
    bool correctUser;
    PerformExtraction(correctUser);

    if (!correctUser)
    {
            MessageBox::Show("Please enter a valid user ID!", "DICInfo");
	    return;
    }

    // Draw every label + image.
    DrawAll();
}


So, overall, what does this function do? Well, first of all, it extracts the user ID from the textbox, and validates it (checks if it is a valid integer). After the User ID is validated, everything is destroyed from the form (labels & photo are removed). Then, it calls the functions that extracts the data from the XML, and builds the labels text & photo, again! Then, everything is redrawn.
Now, the only thing that we have to do is to draw the textbox and the button, onto the form. We are going to do that into the class constructor, which must be placed in the public zone of your class. Add a "public area", then paste this here:
DICInfo()
{
    InitForm();
    InitRatingStarsArray();

    // This is what the initial form contains. The
    // text box where we enter the user ID, and a button
    // that parses that ID & performs the other things.
    // Notice that the text box and the button are static
    // unlike Labels, that are destroyed everytime the button
    // is pressed.

    // Draw the text box:
    textBox = gcnew TextBox;
    textBox->Size = System::Drawing::Size(60, 20);
    textBox->Text = "ID HERE";
    // Maximum of 9 digits & letters.
    textBox->MaxLength = 9;
    textBox->Location = Point(110, 400);

    this->Controls->Add(textBox);

    // Draw the button:
    button = gcnew Button;
    button->Size = System::Drawing::Size(50, 20);
    button->Text = "Parse";
    button->Location = Point(115, 420);
    button->AutoSize = false;
    // This tells us that we can press ENTER too, and the button
    // click event will be triggered.
    AcceptButton = button;
    // Add the event handler to the button
    button->Click += gcnew EventHandler(this, &DICInfo::on_click);

    this->Controls->Add(button);
}


And that's all our class should contain! Now, the only thing that we have to do, is to add the entry point of the program. Copy-paste this into your main source file:
int main()
{
	DICInfo ^inf = gcnew DICInfo;
	// Uncomment the following line if you use XP. It will
	// enable better visuals on buttons and form itself.
	//Application::EnableVisualStyles();
	Application::Run(inf);

	return 0;
}


So, as you can see, Application::Run(yourForm) simply draws the form.

Well, that was it. I hope you understood what was all about. I have also attached the whole source code, and a screenshot, to see the program in action:
Posted Image
Enjoy, and thanks for reading! And don't forget that you can customize the form & buttons & labels design as you wish!

Is This A Good Question/Topic? 2
  • +

Replies To: Parse Dream.In.Code XML using Managed C++ & Visual Studio.

#2 DaneAU  Icon User is offline

  • Great::Southern::Land
  • member icon

Reputation: 286
  • View blog
  • Posts: 1,619
  • Joined: 15-May 08

Posted 24 August 2010 - 04:52 AM

Nice work there :)

This post has been edited by DaneAU: 24 August 2010 - 04:53 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1