23
Open output: Producing ODF spreadsheets from your Web services Directly generate files with PHP and Python Skill Level: Intermediate Federico Kereki ([email protected]) Systems Engineer Freelance 17 Nov 2009 Writing a Web service that produces data in text format is quite simple, but users often prefer getting something they can work in, like spreadsheets. Producing ODF spreadsheets isn't particularly complicated, and this article introduces some ways of doing so working with PHP and Python. Whenever a Web page or service provides data, users particularly appreciate getting it in spreadsheet format or at least in a format they can easily load into a spreadsheet. This article shows how to produce Open Document Format (ODF) spreadsheet files (or, ODS) either by directly creating them byte by byte (which requires a study of the inner structure of ODS files), or through specific libraries that simplify the work. You also get a glance into producing CSV files—not just because they are a sort of "lowest common denominator" interchange format, but because you can convert them automatically into ODS files. Frequently used acronyms CSV: Comma-separated values FIPS: Federal Information Processing Standards ISO: International Organization for Standardization XML: Extensible Markup Language Open output: Producing ODF spreadsheets from your Web services © Copyright IBM Corporation 2009. All rights reserved. Page 1 of 23

Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Open output: Producing ODF spreadsheets fromyour Web servicesDirectly generate files with PHP and Python

Skill Level: Intermediate

Federico Kereki ([email protected])Systems EngineerFreelance

17 Nov 2009

Writing a Web service that produces data in text format is quite simple, but usersoften prefer getting something they can work in, like spreadsheets. Producing ODFspreadsheets isn't particularly complicated, and this article introduces some ways ofdoing so working with PHP and Python.

Whenever a Web page or service provides data, users particularly appreciate gettingit in spreadsheet format or at least in a format they can easily load into aspreadsheet. This article shows how to produce Open Document Format (ODF)spreadsheet files (or, ODS) either by directly creating them byte by byte (whichrequires a study of the inner structure of ODS files), or through specific libraries thatsimplify the work. You also get a glance into producing CSV files—not just becausethey are a sort of "lowest common denominator" interchange format, but becauseyou can convert them automatically into ODS files.

Frequently used acronyms

• CSV: Comma-separated values

• FIPS: Federal Information Processing Standards

• ISO: International Organization for Standardization

• XML: Extensible Markup Language

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 1 of 23

Page 2: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Before you start

Begin by getting some data. I worked with a simple database (see Listing 1) thatincludes the countries, regions, and cities of the world—about three million records. Istarted with a free table of cities around the world (see Resources for a link) andadded the ISO 3166 table of country codes plus both the ISO 3166-2 and FIPS 10-4tables of region codes, because the former codes (instead of the more standardcodes in the latter) were used for the United States. I added the completeCitiesview just to simplify the code examples. Basically, understand that:

• Countries are identified by a code (such as UY for Uruguay) and have aname.

• Countries have regions, identified by a code (unique for a country) andwith a name.

• Cities are in a region of a country and have a name (in two versions: aplain, unaccented ASCII name and a foreign-characters name), apopulation (if known), and geographical coordinates.

Listing 1. Creating the view that you will be querying

CREATE DATABASE worldDEFAULT CHARACTER SET utf8COLLATE utf8_general_ci;

USE world;

CREATE TABLE countries (countryCode char(2) NOT NULL,countryName varchar(50) NOT NULL,PRIMARY KEY (countryCode),KEY countryName (countryName)

);

CREATE TABLE regions (countryCode char(2) NOT NULL,regionCode char(2) NOT NULL,regionName varchar(50) NOT NULL,PRIMARY KEY (countryCode,regionCode),KEY regionName (regionName)

);

CREATE TABLE cities (countryCode char(2) NOT NULL,cityName varchar(50) NOT NULL,cityAccentedName varchar(50) NOT NULL,regionCode char(2) NOT NULL,population bigint(20) NOT NULL,latitude float(10,7) NOT NULL,longitude float(10,7) NOT NULL,KEY `INDEX` (countryCode,regionCode,cityName),KEY cityName (cityName),KEY cityAccentedName (cityAccentedName)

);

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 2 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 3: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

CREATE VIEW completeCities ASSELECT

co.countryCode AS countryCode,co.countryName AS countryName,re.regionCode AS regionCode,re.regionName AS regionName,ci.cityName AS cityName,ci.population AS population,ci.latitude AS latitude,ci.longitude AS longitude

FROM cities ciJOIN regions re ON re.countryCode=ci.countryCode

AND re.regionCode=ci.regionCodeJOIN countries co ON co.countryCode=re.countryCode

ORDER BY 2,4,5;

I also set up a simple page to test the services. The page lets you enter a string, andthe services get the data on all the cities whose names begin with that string (byrunning SELECT * FROM completeCities WHERE cityName LIKE '...%').The page (see Figure 1) is as simple as it gets: You just need a text box for thestring and a button for each service.

Figure 1. A simple page to allow you to call the different services

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 3 of 23

Page 4: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Clicking any of the buttons invokes the corresponding service, which produces eithera CSV or an ODS file (see Figure 2). To be on the safe side—and to make surethere were no incompatibilities—I tried opening all produced files with both KOfficeKSpread and OpenOffice.org Calc.

Figure 2. All buttons produce the same result but in different ways

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 4 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 5: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Producing CSV files

The base systemTo produce the samples for this article, I worked with OpenSUSEversion 10.3, Apache version 2.2.4, MySQL version 5.0.45, PHPversion 5.2.9, and Python version 2.5.1.

Start by producing simple CSV files. CSV files are typically accepted by all kinds ofsoftware and can be massaged automatically into ODS files (though not withoutsome setup inconveniences).

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 5 of 23

Page 6: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Producing CSV files with PHP is quite easy (see the code in Listing 2). After gettingthe desired data, it's simply a matter of going through the results and printing thefields one at a time. I limited the SELECT output to 1,000 records, but I could havegone up to 65,536, which is the maximum number of rows for OpenOffice.org Calc(and, coincidentally, Microsoft® Office Excel®), or just 32,767 for KOffice KSpread.Note the need for escaping the field values by using addslashes(); otherwise,values with quotation marks would break the code.

Listing 2. Csv_1.php produces a simple CSV file

// Get the data:

$start= addslashes($_REQUEST["start"]);$conn= mysql_connect("localhost", "testuser", "testpass");$db= mysql_select_db("world");$cur= mysql_query("SELECT * FROM completeCities ".

"WHERE cityName LIKE '{$start}%' LIMIT 1000");

// Send out the data, with headers identifying it as CSV:

header("Content-type: text/csv");header("Content-Disposition: attachment;filename=csv_1.csv");

while ($row= mysql_fetch_assoc($cur)) {$sep= "";foreach ($row as $value) {

$item= is_numeric($value) ? $value :'"'.addslashes($value).'"';

echo $sep.$item;$sep= ',';

}echo "\n";

}

You could program the main loop more elegantly by using fputcsv(), whichhandles formatting problems (see Listing 3). Using tmpfile() avoids collisionsshould the Web script be called by several users at the same time. When the file isready, the same headers as in Listing 2 are sent, and then you need to read in thetemporary file's contents and print them.

Listing 3. A variation (csv_2.php) uses fputcsv, one of PHP's CSV functions

// ...generate results...

$handle= tmpfile();while ($row= mysql_fetch_assoc($cur)) {

fputcsv($handle, $row);}

// ...put out headers...

fseek($handle,0);while ($contents= fread($handle, 1024)) {

print $contents;}

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 6 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 7: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

// ...clean up code...

From CSV to ODSIf you aren't comfortable working with CSV files, several programsare available to convert CSV files to ODS. The usual way toaccomplish this is by interacting with OpenOffice.org Calc andmaking it do the job, such as PyODConverter or the PyUNO Bridgedo (see Resources). However, there are two reasons why you mightreconsider doing so. First, you will have to run OpenOffice.org Calc(in "headless" mode), which may cause startup delays. Second,installation might prove a challenging task, as there are manycompatibility errors because of different Python library versions. So,if you want to try this method, be prepared for longer responsetimes and to stumble around a bit to get the programs to runproperly.

Python's csv module makes things even easier, as Listing 4 shows. The method forgetting the data is similar to PHP's. Creating the CSV files requires defining whichdelimiter to use (a comma [,]), and which fields to quote; I opted to quote allnon-numeric fields. Using a TemporaryFile saves clean-up code; in Pythonversion 2.6, a SpooledTemporaryFile would be even better, because data iskept in memory unless the file becomes too big. The csv.writer method producesa CSV file from an iterable object; cursor.fetchall() is a bit of a beast, and fourlines are enough to produce the CSV output. Then, just as in the previous PHPversions, you need only output headers, followed by the data itself from thetemporary file.

Listing 4. Csv_3.py

def index(req):# ...imports...

# Get the data:

start= req.form["start"]conn= MySQLdb.connect(host= "localhost", user=

"testuser",passwd= "testpass", db= "world")

cursor= conn.cursor()cursor.execute("""SELECT * FROM completeCities WHERE

cityName LIKE %s LIMIT 1000""", start+"%")

# Create the CSV file:

csv.register_dialect("simple", delimiter= ',',quoting= csv.QUOTE_NONNUMERIC)

myFile= tempfile.TemporaryFile()obj= csv.writer(myFile, dialect= "simple")obj.writerows(cursor.fetchall())

# ...clean up...

# Send back the data, with headers identifying thedata as CSV:

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 7 of 23

Page 8: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

req.headers_out.add("Content-type", "text/csv");req.headers_out.add("Content-Disposition",

"attachment;filename=csv_3.csv");myFile.seek(0)return myFile.read()

What does an ODS file look like?

ODF files are actually ZIP files that include a slew of files and directories. Not allcontents are mandatory; for example, I created simple spreadsheets (with only IBMin cell A1) with both KOffice KSpread and OpenOffice.org Calc, and then I extractedthe resulting ODS files to see what they included. Listing 5 shows the result.

Listing 5. Examining the contents of an ODS file

# unzip -l kspread_ibm.odsArchive: kspread_ibm.odsLength Date Time Name

-------- ---- ---- ----46 08-21-09 14:00 mimetype

2092 08-21-09 14:00 content.xml2631 08-21-09 14:00 styles.xml6342 08-21-09 14:00 settings.xml634 08-21-09 14:00 meta.xml1171 08-21-09 14:00 Thumbnails/thumbnail.png786 08-21-09 14:00 META-INF/manifest.xml

-------- -------13702 7 files

# unzip -l openoffice_ibm.odsArchive: openoffice_ibm.odsLength Date Time Name

-------- ---- ---- ----46 08-21-09 17:00 mimetype0 08-21-09 17:00 Configurations2/statusbar/0 08-21-09 17:00

Configurations2/accelerator/current.xml0 08-21-09 17:00 Configurations2/floater/0 08-21-09 17:00 Configurations2/popupmenu/0 08-21-09 17:00 Configurations2/progressbar/0 08-21-09 17:00 Configurations2/menubar/0 08-21-09 17:00 Configurations2/toolbar/0 08-21-09 17:00

Configurations2/images/Bitmaps/3808 08-21-09 17:00 content.xml6411 08-21-09 17:00 styles.xml876 08-21-09 17:00 meta.xml1012 08-21-09 17:00 Thumbnails/thumbnail.png7226 08-21-09 17:00 settings.xml1896 08-21-09 17:00 META-INF/manifest.xml

-------- -------21275 15 files

In both cases, the first included file is mimetype, which containsapplication/vnd.oasis.opendocument.spreadsheet. This file must be thefirst stream of the package's ZIP file.

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 8 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 9: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Another common file is thumbnail.png: ODF files include, for presentation purposes,a 128 x 128 thumbnail representation of the saved document. However, thespecification doesn't mandate including that image, so for the purposes of thisexample, you can skip that step.

Similarly, you can do away with most of the other files, but you must include theMETA-INF directory with the manifest.xml file, which describes all the other filesincluded in the ZIP, and the contents.xml file, which stores the actual spreadsheetcontents. A bit of experimentation confirmed that both KOffice KSpread andOpenOffice.org Calc could deal with such minimal contents, so I just needed tocreate three files:

• The mimetype file is constant, so producing it is trivial.

• For the reduced set of contents, the manifest.xml file is just a few lineslong, as shown in Listing 6.

• The more complicated file is contents.xml.

Listing 6. A minimalistic manifest.xml file

<?xml version='1.0' encoding='UTF-8'?><manifest:manifest><manifest:file-entrymanifest:media-type='application/vnd.oasis.opendocument.spreadsheet'

manifest:full-path='/' /><manifest:file-entry

manifest:media-type='text/xml'manifest:full-path='content.xml' />

</manifest:manifest>

Basically, the XML contents document contains an office:spreadsheetelement, which itself includes a table:table element representing each individualsheet in the spreadsheet. This element itself includes table:table-row elements(one per row), with table:table-cell elements for sequential cells in the row, asshown in Listing 7.

Listing 7. A sample contents file with just one cell

<?xml version="1.0" encoding="UTF-8"?><office:document-content ...many snipped attributes...><office:automatic-styles /><office:body>

<office:spreadsheet><table:table table:name="the sheet name">

<table:table-row><table:table-cell>

<text:p>IBM</text:p></table:table-cell>

</table:table-row></table:table>

</office:spreadsheet>

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 9 of 23

Page 10: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

</office:body></office:document-content>

Note that this kind of minimalistic contents file won't allow any styling, but you'll getto that later. Let's start producing actual ODS files.

Directly generating ODS files through XML

Because XML files are text files and compressing them is easily done from thecommand line, it's trivial to produce ODS files with any scripting language. Thisarticle provides two methods of doing so: a plain way with PHP, and a moreelaborate way, using appropriate modules, with Python. (There are several XML andZIP packages for PHP, should you want to do a more refined job.) Let's start with thesimpler version, shown in Listing 8. After getting the data (in the same way as inprevious listings), you must create the contents.xml file; a constant header is firstincluded, followed by the result data, row by row and cell by cell, ending with a finalfooter. The manifest.xml and mimetype files are easily generated by usingfile_put_contents(). Then, you compress all files, put out the contents of theresulting ZIP file preceded by appropriate headers, and delete all extra files anddirectories to clean up.

Listing 8. Xml_1.php

// ...get the data...

/*Define the constants that will be needed for the text

files(The constants were somewhat abridged; see the original

source code.)*/

define(MIMETYPE,"application/vnd.oasis.opendocument.spreadsheet");

define(XML_MANIFEST,"<?xml version='1.0' encoding='UTF-8'?>\n"."<manifest:manifest> ... </manifest:manifest>");

define(XML_START,"<?xml version='1.0' encoding='UTF-8'?> ... "."<office:body><office:spreadsheet><table:table

table:name='Results'>");

define(XML_ROW_START, "<table:table-row>");

define(XML_CELL_START, "<table:table-cell><text:p>");

define(XML_CELL_END, "</text:p></table:table-cell>");

define(XML_ROW_END, "</table:table-row>");

define(XML_END,"</table:table></office:spreadsheet></office:body></office:document-content>");

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 10 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 11: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

// Create the content.xml file:

$contents= XML_START;while ($row= mysql_fetch_assoc($cur)) {

$contents.= XML_ROW_START;foreach ($row as $value) {

$contents.= XML_CELL_START;$contents.= htmlentities($value);$contents.= XML_CELL_END;

}$contents.= XML_ROW_END;

}$contents.= XML_END;

// let $tempzip be the name of a temporary file

mkdir($tempzip);mkdir($tempzip."/META-INF");file_put_contents($tempzip."/META-INF/manifest.xml",XML_MANIFEST);file_put_contents($tempzip."/content.xml", $contents);file_put_contents($tempzip."/mimetype", MIMETYPE);system("cd {$tempzip}; zip -mr {$tempzip} mimetypeMETA-INF/* content.xml >/dev/null");

// Put out the data:

header("Content-Type:application/vnd.oasis.opendocument.spreadsheet");header("Content-Disposition: attachment;filename=xml_1.ods");header("Content-Transfer-Encoding: binary");readfile($tempzip.".zip");

// ...clean up, using unlink() and rmdir() to delete allcreated files

Now, let's turn to Python and go for a more "modular" version by creating XMLobjects in memory, dumping them to files, then using the zip module to produce thedesired ODS file, as shown in Listing 9. Getting the data is the same as in Listing 4.The manifestXml object can be created with just a few lines of code because itscontents are fixed. Building up the contentXml object is more arduous, becauseit's a larger, more complex structure; note that you need to do a loop for each cursorrow (creating a row in the XML object), then again a loop for each data field (addingcells to each previously created row). After getting everything ready, it's just a matterof writing the actual files, using zip to create the desired ZIP file, and finishing byputting out the output headers followed by the contents of the zipped structure.

Listing 9. Xml_2.py

def index(req):# ...imports...# ...get the data...# ...create the manifestXml object...

# Create the contentXml document:

contentXml=getDOMImplementation().createDocument("office",

"office:document-content", None)

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 11 of 23

Page 12: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

contentXml.documentElement.setAttribute("office:version","1.1")

contentXml.documentElement.setAttribute("xmlns:table","urn:oasis:names:tc:opendocument:xmlns:table:1.0")

# ...add more attributes to the contentXml object...# ...add an empty "office:automatic-styles" element to

the document...

obd= contentXml.createElement("office:body")contentXml.documentElement.appendChild(obd)

oss= contentXml.createElement("office:spreadsheet")obd.appendChild(oss)

table= contentXml.createElement("table:table")table.setAttribute("table:name", "Results")oss.appendChild(table)

# Each cursor row becomes a row in the table; eachfield, a cell:

for datarow in cursor.fetchall():tablerow=

contentXml.createElement("table:table-row")table.appendChild(tablerow)for datafield in datarow:

cell=contentXml.createElement("table:table-cell")

tablerow.appendChild(cell)text= contentXml.createElement("text:p")cell.appendChild(text)

text.appendChild(contentXml.createTextNode(str(datafield)))

# Create all required directories and files:

tempDir= tempfile.mkdtemp("", "xmlpy")os.mkdir(tempDir+"/META-INF")

contentFile= open(tempDir+"/content.xml", "w")contentFile.write(contentXml.toxml())contentFile.close()

# ...create files "mimetype" and"META-INF/manifest.xml" similarly...

# Zip everything:

myZip= zipfile.ZipFile(tempDir+".zip", "w")os.chdir(tempDir)myZip.write("mimetype")myZip.write("META-INF/manifest.xml")myZip.write("content.xml")myZip.close()

# ...read the contents of the created zip file intovariable dataToReturn

# ...clean up, by using os.remove() and os.rmdir()# ...send back dataToReturn, with appropriate headers

The code is verbose, and you could have done things the same way as you did withPHP, but I wanted to show different ways of tackling the same problem. The nextsection introduces some libraries that can help cut down the coding even more.

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 12 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 13: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Producing ODS through specific libraries

Creating the XML files by hand is interesting, but fortunately there are libraries thatcan produce ODS documents directly. I used ods-php, which even at version 0.1(and a release candidate at that) gets the job done. (You can also use this class forreading ODF files.) On the minus side, there's no documentation apart from the PHPcode itself, so getting this library to work entailed some guesswork.

Getting the data is the same as earlier. Producing an ODS file requires creating anewOds() object and adding cells to it with the addCell method. Cells areidentified by row and column, starting at 0; cell A1 would be row 0, column 0. Afterreadying the object, the saveOds method saves it to disk in the correct ODS format,and all that remains is putting out the appropriate headers, followed by the ODS filecontents, as shown in Listing 10. Cleaning up requires deleting the ODS file you justcreated.

Listing 10. Ods_1.php

// ...get the data...

// Create an ODS object and load data into it:

$object= newOds();for ($curRow=0; $row= mysql_fetch_assoc($cur); $curRow++){

$curCol= 0;foreach ($row as $value) {

$type= is_numeric($value) ? "float" : "string";$object->addCell(0, $curRow, $curCol, $value,

$type);$curCol++;

}}

// Write the object to a temporary file:

$tempname= tempnam("./", "odsphp");unlink($tempname);$tempname.= ".ods";saveOds($object, $tempname);

// ...send out the contents of the $tempname file, withappropriate headers...// ...clean up...

Python's Odfpy module provides a similar but more filled out library. You can buildall kinds of ODF files from scratch or load an existing document into memory,change it, and save it back again. After getting the data (in similar fashion asbefore), to create an ODS file, you must create a document withOpenDocumentSpreadsheet(); then, create and add a table to it, and finally,insert the data by first adding rows to the table, and then adding cells to the rows, asshown in Listing 11. The final part of the code should be familiar by now: Put out

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 13 of 23

Page 14: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

headers, get and put the contents of the produced ODS file, and clean up bydeleting the extra files.

Listing 11. Ods_2.py

def index(req):# ...imports...

# ...get the data...

# Build the ODS object, row by row and cell by cell:

doc= OpenDocumentSpreadsheet()table= Table(name="Results")

for cursorRow in cursor.fetchall():tr= TableRow()table.addElement(tr)for val in cursorRow:

tc= TableCell()tc.addElement(P(text=val))tr.addElement(tc)

doc.spreadsheet.addElement(table)myFile= tempfile.TemporaryFile()doc.write(myFile)

# ...clean up...

# ...send back the contents of myFile...# ...with headers identifying the data as ODS...

Check the odfpy package for more options. Specifically, you may be interested inthe xml2odf script, which can help produce the final ODS file. Now, let's startthinking about dressing up the ODS file a bit for a more appealing look.

Jazzing it up

So far, you've been successful in creating ODS files in several different ways, butthe results are—to put it mildly—plain (see Figure 3). So, let's examine two ways ofincluding styled text in your output: the plain way, by directly producing appropriateXML files in PHP, and a more sophisticated way using the Odfpy library in Python.

Figure 3. The results so far

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 14 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 15: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Using styles isn't terribly complicated, but there are many things to consider. In thiscase, I wanted a big, bold, blue style for a title, and a bold over grey style for thecolumn headings. I decided to go with automatic styles, which are easier to use;these styles are created automatically (hence the name) whenever you apply formatby hand to any cell and are included within the content.xml file instead of separately.The office:automatic-styles element of your document should looksomething like Listing 12.

Listing 12. Producing some extra XML for a jazzed-up spreadsheet

<office:automatic-styles>.<style:style

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 15 of 23

Page 16: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

style:name='bbb'style:family='table-cell'>.<style:text-properties

fo:font-weight='bold'fo:color='#0000ff'fo:font-size='15'/>

</style:style>

<style:stylestyle:name='bld'style:family='table-cell'><style:text-properties

fo:font-weight='bold'/><style:table-cell-properties

fo:background-color='#AEAEAE'/></style:style>

</office:automatic-styles>

Working in PHP, the code is practically the same as Listing 10, but you have tochange the XML document header to include the required automatic styles element.Note that I also defined two new cell prefixes, each including an appropriatetable:style-name attribute. Finally, it's just a matter of adding the new main title,an empty row for spacing, and a row with the column titles, as Listing 13 shows.

Listing 13. Xml_3.php

// ...everything is the same, just up to XML_START:

define(XML_AUTOMATIC_STYLES,"<office:automatic-styles>".

"<style:style style:name='bbb'style:display-name='bbb' ".

"style:family='table-cell'>"."<style:text-properties fo:font-weight='bold'

"."fo:color='#0000ff' fo:font-size='15'/>".

"</style:style>"."<style:style style:name='bld'

style:display-name='bld' "."style:family='table-cell'>"."<style:text-properties

fo:font-weight='bold'/>"."<style:table-cell-properties

fo:background-color='#AEAEAE'/>"."</style:style>".

"</office:automatic-styles>");

define(XML_START,"<?xml version='1.0' encoding='UTF-8'?>\n"."<office:document-content ".

//...many lines..."office:version='1.1'>".XML_AUTOMATIC_STYLES.

"<office:body>"."<office:spreadsheet>"."<table:table table:name='Results'>");

// ...more define() lines, as earlier, and two newdefinitions:

define(XML_BBB_CELL_START,"<table:table-cell table:style-name='bbb'><text:p>");

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 16 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 17: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

define(XML_BLD_CELL_START,"<table:table-cell table:style-name='bld'><text:p>");

// ...then, everything the same, up to:

$contents= XML_START;

// Add a big, bold, blue, title, and an empty line:

$contents.= XML_ROW_START;$contents.= XML_BBB_CELL_START;$contents.= "Cities whose name starts with '".$start."'";$contents.= XML_CELL_END;$contents.= XML_ROW_END;

$contents.= XML_ROW_START;$contents.= XML_ROW_END;

// Add some titles, in bold:

$contents.= XML_ROW_START;foreach(array("Country","","Region","","City","Pop","Lat","Long")as $title) {

$contents.= XML_BLD_CELL_START;$contents.= $title;$contents.= XML_CELL_END;

}$contents.= XML_ROW_END;

// ...everything is the same to the end

Turning to Python, creating styles with odfpy isn't difficult, but because thedocumentation isn't as helpful, I had to run several experiments and compare theresults I was getting with the contents of an OpenOffice.org Calc document. Youneed to create the new styles and add them to the automaticstyles part of thedocument. Given that, adding a title or column headers is easy: You just have tocreate a cell, specifying the desired stylename and nothing else (see Listing 14).Note that the rest of the code is more or less the same as Listing 11.

Listing 14. Ods_3.py

def index(req)# ...everything the same as in ods_a.py, up to

including these lines:

doc= OpenDocumentSpreadsheet()table= Table(name="Results")

# Define a "bold big blue" style, and a simple bold ongrey one:

bbb= Style(name="bbb", family="table-cell")bbb.addElement(TextProperties(fontweight="bold",

fontsize="13", color="#0000ff"))doc.automaticstyles.addElement(bbb)

bld= Style(name="bld", family="table-cell")bld.addElement(TextProperties(fontweight="bold"))

bld.addElement(TableCellProperties(backgroundcolor="#AEAEAE"))doc.automaticstyles.addElement(bld)

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 17 of 23

Page 18: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

# Add a listing description, in the bold big bluestyle, and skip a row:

tr= TableRow()table.addElement(tr)tc= TableCell(stylename="bbb")tc.addElement(P(text="Cities whose name starts with

'"+start+"'"))tr.addElement(tc)

table.addElement(TableRow())

# Add some column titles, in the simple bold style:

tr= TableRow()table.addElement(tr)for myText in ["Country", "", "Region", "", "City",

"Pop", "Lat", "Long"]:tc= TableCell(stylename="bld")tc.addElement(P(text=myText))tr.addElement(tc)

# ...add the data, create the ODS, clean up;everything the same from here onwards

The results of the styling are, although not probably deserving of a styling award, atleast better looking! See Figure 4.

Figure 4. Adding titles and some styling enhances the results.

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 18 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 19: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Now, you can start thinking about including multiple pages or sheets in the samespreadsheet, adding formulas, and even including graphs so that you can reallycrank up the level of your output!

Conclusion

This article examined several ways of producing tabular data in standard formats,from a basic CSV file to a full ODS file, doing the latter either by hand (by manuallyproducing all required files, directories, and zipped results), or by using appropriatelibraries. With a bit of extra work, you can also produce nice-looking spreadsheets.Users appreciate having their work made easier, and producing ready-to-use

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 19 of 23

Page 20: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

spreadsheets fits the bill. Now, you can start adding this functionality to your ownWeb pages and services!

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 20 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 21: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Downloads

Description Name Size Downloadmethod

All source files for this article source_files.zip 16KB HTTP

Information about download methods

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 21 of 23

Page 22: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

Resources

Learn

• "Patterns + GWT + Ajax = Usability!" (Federico Kereki, developerWorks, July2009): See another way to use the database presented in this article.

• Cities table: MaxMind offers this free table of the world's cities. ISO provides theISO 3166 family of codes, both for countries and for regions (3166-2). Get theFIPS region codes for an alternative identification scheme.

• ODF 1.1 specification (PDF): Get the current standard (until version 1.2 comesout).

• developerWorks Web development zone: The Web development zone ispacked with tools and information for Web 2.0 development.

• IBM technical events and webcasts: Stay current with developerWorks'technical events and webcasts.

• Check out My developerWorks: Find or create groups, blogs, and activitiesabout Web development or anything else that interests you.

Get products and technologies

• Odfpy: Odfpy simplifies the process of producing ODF files directly.

• ods-php: This package is similar to Odfpy (although with fewer options) forPHP.

• PyODConverter: This tool lets you convert CSV into ODS and do many moresimilar conversions, but you may have problems setting it up. Another possibilityis JodConverter, found at the same Web site.

• Python-UNO bridge: This tool provides another way to interact withOpenOffice.org Calc and produce an ODS file out of a CSV file, but you mayalso find installation and setup isn't straightforward because of versionproblems.

• IBM product evaluation versions: Download these versions today and get yourhands on application development tools and middleware products from DB2®,Lotus®, Rational®, Tivoli®, and WebSphere®.

About the author

Federico KerekiFederico Kereki is a Uruguayan systems engineer with more than 20 years ofexperience developing systems, doing consulting work, and teaching at universities.

developerWorks® ibm.com/developerWorks

Open output: Producing ODF spreadsheets from your Web servicesPage 22 of 23 © Copyright IBM Corporation 2009. All rights reserved.

Page 23: Open output: Producing ODF spreadsheets from your Web servicespublic.dhe.ibm.com/software/dw/web/wa-odf/wa-odf-pdf.pdf · Before you start Begin by getting some data. I worked with

He is currently working with a good jumble of acronyms: SOA, GWT, Ajax, PHP, andof course FLOSS! You can reach Federico at [email protected].

ibm.com/developerWorks developerWorks®

Open output: Producing ODF spreadsheets from your Web services© Copyright IBM Corporation 2009. All rights reserved. Page 23 of 23