Creating Nice Reports with wxWidgets ZIP API

wxWidgets contains a set of classes which handle several archive formats. Most commonly used archive format is ZIP. This tutorial shows how to use wxWidgets API for reading and writing ZIP-files.
As you probably know, Microsoft Office 2007 produces .DOCX files which are ZIP archives which contain several XML files, images and few directories inside. After visiting Microsoft Developer’s Days I decided to add new feature to my current project and create new type of report which is based on .DOCX file format. This tutorial contains some techniques which you can use for generating such reports by yourself.

First of all, we have to open Microsoft Word 2007 and create a stub document. The document which is used in this article contains:

  • Header
  • Textual description
  • Table with two columns
  • Image at the bottom of the table

Creating nice reports in wxWidgets using ZPI API

So, we created our document and saved it as “Sample.docx”. Now we have to rename it to “Sample.zip” and extract all files from our archive to some directory.

Creating nice reports in wxWidgets using ZPI API - 2

Well, now we can start the development of our sample application.

ZIPTestApp.h

#ifndef _ZIP_TEST_APP_H
#define _ZIP_TEST_APP_H

#include 

class ZIPTestApp : public wxApp
{
public:
	virtual bool OnInit();
};

DECLARE_APP(ZIPTestApp)

#endif

ZIPTestApp.cpp

#include "ZIPTestApp.h"
#include "ZIPTestMainFrame.h"
#include 
#include 
#include 

IMPLEMENT_APP(ZIPTestApp)

bool ZIPTestApp::OnInit()
{
	wxImage::AddHandler(new wxPNGHandler);
	wxFileSystem::AddHandler(new wxZipFSHandler);
	ZIPTestMainFrame * frame = new ZIPTestMainFrame;
	SetTopWindow(frame);
	frame->Centre();
	frame->Show();
	return true;
}

ZIPTestMainFrame.h

#ifndef _ZIP_TEST_MAINFRAME_H
#define _ZIP_TEST_MAINFRAME_H

#include 
#include 
#include 
#include 
#include 

class ZIPTestMainFrame : public wxFrame
{
	DECLARE_DYNAMIC_CLASS(ZIPTestMainFrame)
	int m_RowCount;
	wxString m_ImageFile;
	wxAuiManager m_FrameManager;	
	wxTextCtrl * m_Editor;
	wxSpinCtrl * m_RowCountSpin;
	wxGrid * m_Grid;
	wxFilePickerCtrl * m_ImageFilePicker;	
	wxArrayString m_DocumentTemplate;
	void CreateControls();
	
public:
	ZIPTestMainFrame();
	~ZIPTestMainFrame();
	bool Create(wxWindow * parent, 
		wxWindowID id = wxID_ANY, 
		const wxString & title = wxEmptyString);

	DECLARE_EVENT_TABLE()
	void OnSave(wxCommandEvent & event);
	void OnExit(wxCommandEvent & event);
	void OnAbout(wxCommandEvent & event);
	void OnRowCountSpin(wxSpinEvent & event);
};

#endif

ZIPTestMainFrame.cpp

#include "ZIPTestMainFrame.h"
#include "ZIPTestApp.h"
#include "ZIPTestBinaryData.h"
#include 
#include 

IMPLEMENT_DYNAMIC_CLASS(ZIPTestMainFrame, wxFrame)

enum
{
	ID_EDITOR = 10001,
	ID_ROWCOUNT_SPIN,
	ID_GRID,		
	ID_IMAGE_FILEPICKER
};

BEGIN_EVENT_TABLE(ZIPTestMainFrame, wxFrame)
EVT_MENU(wxID_SAVE, ZIPTestMainFrame::OnSave)
EVT_MENU(wxID_EXIT, ZIPTestMainFrame::OnExit)
EVT_MENU(wxID_ABOUT, ZIPTestMainFrame::OnAbout)
EVT_SPINCTRL(ID_ROWCOUNT_SPIN, ZIPTestMainFrame::OnRowCountSpin)
END_EVENT_TABLE()

ZIPTestMainFrame::ZIPTestMainFrame()
{
	Create(NULL, wxID_ANY, _("ZIP Test"));
}

ZIPTestMainFrame::~ZIPTestMainFrame()
{
	m_FrameManager.UnInit();
}

bool ZIPTestMainFrame::Create(wxWindow * parent, 
	wxWindowID id, const wxString & title)
{
	bool res = wxFrame::Create(parent, id, title, 
		wxDefaultPosition, wxSize(500,400));
	if(res)
	{
		CreateControls();
	}
	return res;
}

void ZIPTestMainFrame::CreateControls()
{
	SetMinSize(wxSize(400, 300));
	SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);

	wxMenuBar * menuBar = new wxMenuBar;
	SetMenuBar(menuBar);

	wxMenu * fileMenu = new wxMenu;
	fileMenu->Append(wxID_SAVE, _("Save\tCtrl+S"));
	fileMenu->AppendSeparator();
	fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

	wxMenu * helpMenu = new wxMenu;
	helpMenu->Append(wxID_ABOUT, _("About..."));

	menuBar->Append(fileMenu, _("File"));
	menuBar->Append(helpMenu, _("Help"));

	m_FrameManager.SetManagedWindow(this);

	wxPanel * panel = new wxPanel(this, wxID_ANY);
	wxBoxSizer * panelSizer = new wxBoxSizer(wxVERTICAL);
	panel->SetSizer(panelSizer);

	wxStaticText * editor_label = new wxStaticText(panel, 
		wxID_ANY, _("Description:"));
	m_Editor = new wxTextCtrl(panel, ID_EDITOR);
	wxBoxSizer * description_sizer = new wxBoxSizer(wxHORIZONTAL);
	description_sizer->Add(editor_label, 0, wxALIGN_CENTER_VERTICAL);
	description_sizer->Add(m_Editor, 1, wxGROW|wxLEFT, 5);

	wxStaticBoxSizer * grid_sizer = new wxStaticBoxSizer(wxVERTICAL, 
		panel, _("Grid"));
	wxStaticText * rows_label = new wxStaticText(panel, 
		wxID_ANY, _("Amount of rows:"));
	m_RowCountSpin = new wxSpinCtrl(panel, ID_ROWCOUNT_SPIN);
	m_RowCountSpin->SetValidator(wxGenericValidator(&m_RowCount));
	wxBoxSizer * rows_sizer = new wxBoxSizer(wxHORIZONTAL);
	rows_sizer->Add(rows_label, 0, wxALIGN_CENTER_VERTICAL);
	rows_sizer->Add(m_RowCountSpin, 1, wxGROW|wxLEFT, 5);
	m_Grid = new wxGrid(panel, ID_GRID, wxDefaultPosition, 
		wxSize(-1, 300), wxSUNKEN_BORDER);
	m_Grid->CreateGrid(0, 2);
	m_Grid->SetColLabelValue(0, _("Name"));
	m_Grid->SetColLabelValue(1, _("Price"));
	m_Grid->SetColMinimalWidth(0, 150);
	m_Grid->SetColMinimalWidth(1, 150);
	m_Grid->SetColFormatFloat(1, 2, 2);
	grid_sizer->Add(rows_sizer, 0, wxGROW|wxLEFT|wxRIGHT|wxBOTTOM, 5);
	grid_sizer->Add(m_Grid, 1, wxEXPAND|wxLEFT|wxRIGHT|wxBOTTOM, 5);

	wxStaticText * image_label = new wxStaticText(panel, 
		wxID_ANY, _("Image file:"));
	m_ImageFilePicker = new wxFilePickerCtrl(panel, 
		ID_IMAGE_FILEPICKER, wxEmptyString, wxFileSelectorPromptStr, 
		_("PNG Images (*.png)|*.png"));
	wxBoxSizer * image_sizer = new wxBoxSizer(wxHORIZONTAL);
	image_sizer->Add(image_label, 0, wxALIGN_CENTER_VERTICAL);
	image_sizer->Add(m_ImageFilePicker, 1, wxGROW|wxLEFT, 5);
		
	panelSizer->Add(description_sizer, 0, wxGROW|wxALL, 5);	
	panelSizer->Add(grid_sizer, 1, wxGROW|wxLEFT|wxRIGHT|wxBOTTOM, 5);	
	panelSizer->Add(image_sizer, 0, wxGROW|wxLEFT|wxRIGHT|wxBOTTOM, 5);

	CreateStatusBar(2);

	m_FrameManager.AddPane(panel, wxAuiPaneInfo().CenterPane());
	m_FrameManager.Update();
}

void ZIPTestMainFrame::OnExit(wxCommandEvent & event)
{
	Close();
}

void ZIPTestMainFrame::OnAbout(wxCommandEvent & event)
{
	wxAboutDialogInfo info;
	info.SetName(wxT("ZIPTest"));
	info.SetVersion(wxT("v0.1"));
	info.SetWebSite(wxT("https://wxwidgets.info"));
	info.AddDeveloper(wxT("Volodymir (T-Rex) Tryapichko"));
	wxAboutBox(info);
}

void ZIPTestMainFrame::OnRowCountSpin(wxSpinEvent & event)
{
	TransferDataFromWindow();
	if(m_Grid->GetNumberRows() > m_RowCount)
	{
		m_Grid->DeleteRows(0, m_Grid->GetNumberRows()-m_RowCount);
	} 
	else
	{		
		m_Grid->AppendRows(m_RowCount-m_Grid->GetNumberRows());
	}
	
}

void ZIPTestMainFrame::OnSave(wxCommandEvent & event)
{
	wxFileDialog dlg(this, wxFileSelectorPromptStr, 
		wxEmptyString, wxEmptyString, 
		_("Microsoft Word 2007 Documents (*.docx)|*.docx"), 
		wxFD_SAVE);	
	if(dlg.ShowModal() == wxID_OK)
	{
		TransferDataFromWindow();
		m_ImageFile = m_ImageFilePicker->GetPath();			
	}
}

Creating nice reports in wxWidgets using ZPI API - 3

As you can see, our sample application contains a single frame with menu bar and status bar. At the top of main frame we have a text box where we should enter a description of our report. Also our main frame contains a grid and a spin control where we can specify the amount of rows in our grid. At the bottom of main frame we have a file picker. It is used for loading an image which should be displayed at the bottom of our report.
Now we can start implementing the functionality related to our ZIP archive. As you can see, there is “[Content_Types].xml” file in root directory of .DOCX file. It is a small XML document. We can use wxXmlDpcument class and generate this file manually.
Creating nice reports in wxWidgets using ZPI API - 4

ZIPTestMainFrame.cpp

void ZIPTestMainFrame::SaveContentTypesXML(wxZipOutputStream & zip)
{
	wxXmlDocument doc;
	wxXmlNode * root = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Types"));
	root->AddProperty(wxT("xmlns"),
		wxT("http://schemas.openxmlformats.org/package/2006/content-types"));
	doc.SetRoot(root);
	wxXmlNode * node;
	
	if(!m_ImageFile.IsEmpty() && wxFileExists(m_ImageFile))
	{
		node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Default"));
		node->AddProperty(wxT("Extension"), wxT("png"));
		node->AddProperty(wxT("ContentType"), wxT("image/png"));
		root->AddChild(node);
	}

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Default"));
	node->AddProperty(wxT("Extension"), wxT("rels"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-package.relationships+xml"));
	root->AddChild(node);
	
	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Default"));
	node->AddProperty(wxT("Extension"), wxT("xml"));
	node->AddProperty(wxT("ContentType"), wxT("application/xml"));
	root->AddChild(node);
	
	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/document.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
			wxT("wordprocessingml.document.main+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/styles.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
		wxT("wordprocessingml.styles+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/docProps/app.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
		wxT("extended-properties+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/settings.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
		wxT("wordprocessingml.settings+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/theme/theme1.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.theme+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/fontTable.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
		wxT("wordprocessingml.fontTable+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/word/webSettings.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-officedocument.")
		wxT("wordprocessingml.webSettings+xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Override"));
	node->AddProperty(wxT("PartName"), wxT("/docProps/core.xml"));
	node->AddProperty(wxT("ContentType"), 
		wxT("application/vnd.openxmlformats-package.")
		wxT("core-properties+xml"));
	root->AddChild(node);

	zip.PutNextEntry(wxT("[Content_Types].xml"));
	doc.Save(zip);
}

This method creates XML document, populates it with nodes, then creates a new entry named “[Content_Types].xml” in ZIP archive and saves XML document into this entry.
The same way we use for generating “_rels/.rels” file.

ZIPTestMainFrame.cpp

void ZIPTestMainFrame::SaveDocumentRels(wxZipOutputStream & zip)
{
	wxXmlDocument doc;
	wxXmlNode * root = new wxXmlNode(wxXML_ELEMENT_NODE, 
		wxT("Relationships"));
	root->AddProperty(wxT("xmlns"),
		wxT("http://schemas.openxmlformats.org/package/2006/relationships"));
	doc.SetRoot(root);
	wxXmlNode * node;

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Relationship"));
	node->AddProperty(wxT("Id"), wxT("rId3"));
	node->AddProperty(wxT("Type"), 
		wxT("http://schemas.openxmlformats.org/officeDocument")
		wxT("/2006/relationships/extended-properties"));
	node->AddProperty(wxT("Target"), 
		wxT("docProps/app.xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Relationship"));
	node->AddProperty(wxT("Id"), wxT("rId2"));
	node->AddProperty(wxT("Type"), 
		wxT("http://schemas.openxmlformats.org/package/2006/")
		wxT("relationships/metadata/core-properties"));
	node->AddProperty(wxT("Target"), 
		wxT("docProps/core.xml"));
	root->AddChild(node);

	node = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("Relationship"));
	node->AddProperty(wxT("Id"), wxT("rId1"));
	node->AddProperty(wxT("Type"), 
		wxT("http://schemas.openxmlformats.org/officeDocument/")
		wxT("2006/relationships/officeDocument"));
	node->AddProperty(wxT("Target"), 
		wxT("word/document.xml"));
	root->AddChild(node);
		
	zip.PutNextEntry(wxT("_rels/.rels"));
	doc.Save(zip);
}

Other XML documents placed into .DOCX file are rather large and it is not a good idea to generate them manually. We will used Bin2C utility, whose source code is freely available from wxForum for generating c-arrays from this files.

ZIPTestBinaryData.h

#ifndef _ZIP_TEST_BINARY_DATA_H
#define _ZIP_TEST_BINARY_DATA_H
...
unsigned char websettings_xml[] ={
	0x3C,0x3F,0x78,0x6D,0x6C,0x20,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x3D,
	0x22,0x31,0x2E,0x30,0x22,0x20,0x65,0x6E,0x63,0x6F,0x64,0x69,0x6E,0x67,
	...
	0x65,0x74,0x74,0x69,0x6E,0x67,0x73,0x3E
};
...

So, these files will be hard-coded into our executable but you can create e.g. a set of DLLs which will contain different templates for different types of reports and then use these DLLs as plug-ins.
Now we can generate new entries in our ZIP archive using this binary data:

ZIPTestMainFrame.cpp

void ZIPTestMainFrame::SaveDocumentSettings(wxZipOutputStream & zip)
{
	zip.PutNextEntry(wxT("word/fontTable.xml"));
	zip.Write(fonttable_xml, sizeof(fonttable_xml));
	zip.PutNextEntry(wxT("word/settings.xml"));
	zip.Write(settings_xml, sizeof(settings_xml));
	zip.PutNextEntry(wxT("word/styles.xml"));
	zip.Write(styles_xml, sizeof(styles_xml));
	zip.PutNextEntry(wxT("word/webSettings.xml"));
	zip.Write(websettings_xml, sizeof(websettings_xml));
	zip.PutNextEntry(wxT("word/_rels/document.xml.rels"));
	zip.Write(document_xml_rels, sizeof(document_xml_rels));
	SaveTheme(zip);
}

void ZIPTestMainFrame::SaveTheme(wxZipOutputStream & zip)
{
	wxFileInputStream theme(wxT("theme1.xml"));	
	if(theme.IsOk())
	{
		zip.PutNextEntry(wxT("word/theme/theme1.xml"));
		zip.Write(theme);
	}
}

As you can see, ZIPTestMainFrame::SaveTheme loads “theme1.xml” file from disk and then saves it into ZIP archive. This way is used just as an example and in your applications you can also load “theme1.xml” from binary data.
Now we have load an image from disk and place it into archive. There is no need to load a bitmap from image file and save it manually. We just open wxFileInputStream and save it onto ZIP entry

ZIPTestMainFrame.cpp

void ZIPTestMainFrame::SaveImageFile(wxZipOutputStream & zip)
{
	if(!m_ImageFile.IsEmpty() && wxFileExists(m_ImageFile))
	{
		zip.PutNextEntry(wxString::Format(wxT("word/media/%s"), 
			wxFileNameFromPath(m_ImageFile).GetData()));
		wxFileInputStream stream(m_ImageFile);
		zip.Write(stream);
	}
}

OK, now we have to perform the most complicated task: we have to generate the body of our report and place it into word/document.xml file inside our ZIP archive. If you open document.xml file in text editor then you will see that it is an XML document which describes the structure of Word document. All paragraphs, tables, images and other objects are described inside this file.
We can split document.xml in several logical parts, save them as separate text files, pack them into ZIP archive and load these parts in our application.
Creating nice reports in wxWidgets using ZPI API - 5
In this sample I replaced all textual values with macros and then used these macros inside my application for populating the report.
Creating nice reports in wxWidgets using ZPI API - 6
E.g. “report_name.txt” file contains %REPORTNAME% macro which should be replaced by some text when generating the report.
Now, after we packed all parts inside ZIP archive, we can load them when application starts…

ZIPTestMainFrame.cpp

bool ZIPTestMainFrame::Create(wxWindow * parent, 
	wxWindowID id, const wxString & title)
{
	bool res = wxFrame::Create(parent, id, title, 
		wxDefaultPosition, wxSize(500,400));
	if(res)
	{
		LoadDocumentTemplate();
		CreateControls();
	}
	return res;
}

void ZIPTestMainFrame::LoadDocumentTemplate()
{
	wxFileSystem fs;
	wxString wildcard = wxT("document.template.dat#zip:*.txt");
	wxString filename;
	wxFSFile * file(NULL);
	m_DocumentTemplate.SetCount(DOCUMENT_PARTS_COUNT);	
	for(filename = fs.FindFirst(wildcard, wxFILE); 
			!filename.IsEmpty(); filename = fs.FindNext())
	{		
		if(LoadTemplatePart(fs, filename, 
			wxT("document_start.txt"), DOCUMENT_START)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("document_end.txt"), DOCUMENT_END)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("report_name.txt"), REPORT_NAME)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("report_description.txt"), REPORT_DESCRIPTION)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_start.txt"), TABLE_START)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_end.txt"), TABLE_END)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_header_start.txt"), TABLE_HEADER_START)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_header_cell.txt"), TABLE_HEADER_CELL)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_header_end.txt"), TABLE_HEADER_END)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_row_start.txt"), TABLE_ROW_START)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_row_cell.txt"), TABLE_ROW_CELL)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("table_row_end.txt"), TABLE_ROW_END)) continue;
		if(LoadTemplatePart(fs, filename, 
			wxT("image.txt"), DOCUMENT_IMAGE)) continue;	
	}
}

bool ZIPTestMainFrame::LoadTemplatePart(wxFileSystem & fs, 
		const wxString & filename, const wxString & part_name, 
		DocumentTemplateParts part_id)
{
	if(filename.EndsWith(part_name))
	{
		wxFSFile * file = fs.OpenFile(filename);
		if(file)
		{
			wxTextInputStream stream(*file->GetStream());
			wxChar ch;
			while((ch = stream.GetChar()))
			{
				m_DocumentTemplate[part_id] += ch;
			}			
			wxDELETE(file);
			return true;
		}			
	}
	return false;
}

…and then generate document.xml file using this parts
ZIPTestMainFrame.cpp

void ZIPTestMainFrame::SaveDocumentBody(wxZipOutputStream & zip)
{
	do
	{
		if(m_DocumentTemplate.Count() != DOCUMENT_PARTS_COUNT) break;
		int i, j;
		wxString part;
		zip.PutNextEntry(wxT("word/document.xml"));
		wxCharBuffer buffer;
		
		part = m_DocumentTemplate[DOCUMENT_START];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));
		
		part = m_DocumentTemplate[REPORT_NAME];
		part.Replace(wxT("%REPORTNAME%"), _("Sample Report"));
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		part = m_DocumentTemplate[REPORT_DESCRIPTION];
		part.Replace(wxT("%DESCRIPTION%"), m_Editor->GetValue());
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		part = m_DocumentTemplate[TABLE_START];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		part = m_DocumentTemplate[TABLE_HEADER_START];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		for(i = 0; i < m_Grid->GetNumberCols(); i++)
		{
			part = m_DocumentTemplate[TABLE_HEADER_CELL];
			part.Replace(wxT("%HEADERCELL%"), m_Grid->GetColLabelValue(i));
			buffer = wxConvertWX2MB(part);
			zip.Write(buffer.data(), strlen(buffer.data()));
		}

		part = m_DocumentTemplate[TABLE_HEADER_END];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		for(i = 0; i < m_Grid->GetNumberRows(); i++)
		{
			part = m_DocumentTemplate[TABLE_ROW_START];
			buffer = wxConvertWX2MB(part);
			zip.Write(buffer.data(), strlen(buffer.data()));

			for(j = 0; j < m_Grid->GetNumberCols(); j++)
			{
				part = m_DocumentTemplate[TABLE_ROW_CELL];
				part.Replace(wxT("%CELL%"), m_Grid->GetCellValue(i, j));
				buffer = wxConvertWX2MB(part);
				zip.Write(buffer.data(), strlen(buffer.data()));
			}

			part = m_DocumentTemplate[TABLE_ROW_END];
			buffer = wxConvertWX2MB(part);
			zip.Write(buffer.data(), strlen(buffer.data()));
		}
		part = m_DocumentTemplate[TABLE_END];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));

		if(!m_ImageFile.IsEmpty() && wxFileExists(m_ImageFile))
		{
			part = m_DocumentTemplate[DOCUMENT_IMAGE];
			part.Replace(wxT("%IMAGENAME%"), wxT("Sample Image"));
			part.Replace(wxT("%IMAGEDESCR%"), wxT("Some description"));
			part.Replace(wxT("%IMAGEFILE%"), wxFileNameFromPath(m_ImageFile));
			buffer = wxConvertWX2MB(part);
			zip.Write(buffer.data(), strlen(buffer.data()));
		}
		part = m_DocumentTemplate[DOCUMENT_END];
		buffer = wxConvertWX2MB(part);
		zip.Write(buffer.data(), strlen(buffer.data()));
	}
	while(false);
}

OK, now we have to call all methods which save the parts of our report from event handler for wxID_SAVE command:
ZIPTestMainFrame.cpp

void ZIPTestMainFrame::OnSave(wxCommandEvent & event)
{
	wxFileDialog dlg(this, wxFileSelectorPromptStr, 
		wxEmptyString, wxEmptyString, 
		_("Microsoft Word 2007 Documents (*.docx)|*.docx"), 
		wxFD_SAVE);	
	if(dlg.ShowModal() == wxID_OK)
	{
		TransferDataFromWindow();
		m_ImageFile = m_ImageFilePicker->GetPath();		
		wxFFileOutputStream out(dlg.GetPath());
		wxZipOutputStream zip(out);
		SaveContentTypesXML(zip);
		SaveDocumentRels(zip);
		SaveDocProps(zip);
		SaveDocumentSettings(zip);
		SaveImageFile(zip);
		SaveDocumentBody(zip);
	}
}

Well, it seems that we finished implementing all functionality and can test the application.

  • Enter some text into “Description” filed
  • Add few rows to the grid and enter some text into the cells
  • Load an image file
  • Select “File” -> “Save”, enter the file name and press OK

Creating nice reports in wxWidgets using ZPI API - 7
Under Linux, our main frame should look like this:
Creating nice reports in wxWidgets using ZPI API - 8
Now, if we open the file, generated by our application, in Microsoft Word, we will see something like this:
Creating nice reports in wxWidgets using ZPI API - 9
The most important thing is that we can create Microsoft Word documents without Microsoft Office installed on local machine. Certainly if we have no Microsoft Office installed then we have no way to see the result but in any case we have the portable way of generating nice reports. It works under Linux and, I’m sure, that it should work on mobile devices.
Download sample source code for this article (VC++ 2005, CodeBlocks – Windows) (Eclipse – Linux)
Source code of Bin2C utility.

Leave a Reply

Your email address will not be published. Required fields are marked *

Please leave these two fields as-is: