オープンな出力: Web サービスから ODF スプレッドシートを作成する

PHP とPython を使ってファイルを直接生成する

テキスト・フォーマットでデータを生成する Web サービスを作成するのはとても簡単ですが、ユーザーはしばしば、何らかの作業を直接行える形式 (例えばスプレッドシートのような形式) でデータを生成することを要求します。ODF スプレッドシートの作成は、それほど複雑ではありません。そこでこの記事では、PHP とPython を使って ODF スプレッドシートを作成する方法をいくつか紹介します。

Federico Kereki, Systems Engineer, Freelance

Photo of Federico KerekiFederico Kereki はウルグアイ人のシステム・エンジニアであり、システム開発やコンサルティング、大学での教育に 20 年を超える経験があります。現在はおなじみの頭字語、SOA、GWT、Ajax、PHP、そしてもちろん FLOSS を渾然一体に扱いながら業務を行っています。


developerWorks 貢献著者レベル

2009年 11月 17日

Web ページや Web サービスでデータが提供される場合、ユーザーはそのデータをスプレッドシート形式で、あるいは少なくともスプレッドシートに容易にロードできる形式で入手したいと望むことが多いものです。この記事では、ODF (Open Document Format) によるスプレッドシート・ファイル (つまり ODS) を作成する方法について、バイト単位で直接作成する方法 (そのためには ODS ファイルの内部構造を理解する必要があります) と、特定のライブラリーを利用して作業を単純化する方法を説明します。また、CSV ファイルを生成する方法についても簡単に説明します。これは、CSV ファイルが最もよく使われるデータ交換フォーマットであるという理由のみならず、CSV ファイルは自動的に ODS ファイルに変換できるということがあるからです。

よく使われる頭字語

  • CSV: Comma-Separated Values
  • IPS: Federal Information Processing Standards
  • ISO: International Organization for Standardization
  • XML: Extensible Markup Language

準備

まず、何らかのデータを入手します。私は単純なデータベースのデータを利用することにしました (リスト 1)。利用することにしたのは、世界の国 (countries)、地域 (regions)、都市 (cities) が含まれ、レコードは約 300 万あるデータベースです。私はまず、世界の都市を集めた無料の表 (リンクは「参考文献」を参照) を入手することから始め、それに ISO 3166 の国コードの表を追加し、さらに ISO 3166-2 の地域コードと FIPS 10-4 の地域コードの両方を追加しました。2 つの地域コードを追加した理由は、米国では (より標準的な FIPS 10-4 コードよりも) ISO 3166-2 のコードの方がよく使われているためです。ここでは単にコード・サンプルを単純にするために、completeCities ビューを追加しました。以上の内容は、基本的に次のようなものであると理解してください。

  • 国 (countries) はコード (例えば、ウルグアイ (Uruguay) の場合は “UY” など) によって識別され、名前を持っています。
  • 国 (countries) には複数の地域 (regions) があり、地域 (regions) は (その国で一意の) コードで識別され、また名前を持っています。
  • ある国 (countries) の、ある地域 (regions) には、複数の都市 (cities) があり、名前 (その国で使われているのではない ASCII のみで表現される名前と、その国で使われている英語以外の文字が含まれる名前の 2 つがあります) と、人口 (わかっている場合)、そして地理座標を持っています。
リスト 1. クエリー対象のビューを作成する
CREATE DATABASE world
  DEFAULT CHARACTER SET utf8
  COLLATE 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)
);

CREATE VIEW completeCities AS
  SELECT
    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 ci
    JOIN regions re ON re.countryCode=ci.countryCode
        AND re.regionCode=ci.regionCode
    JOIN countries co ON co.countryCode=re.countryCode
  ORDER BY 2,4,5;

データを入手したのに加え、提供する Web サービスをテストするための単純なページもセットアップしました。このページでストリングを入力すると、Web サービスは、そのストリングで名前が始まるすべての都市のデータを取得します (SELECT * FROM completeCities WHERE cityName LIKE '...%' を実行します)。このページ (図 1) は、これ以上単純にならないほど単純です。必要なものは、ストリング用のテキスト・ボックスと、各 Web サービス用のボタンのみです。

図 1. さまざまな Web サービスを呼び出すための単純なページ
このページの一番上には、「Start of city name (都市名の先頭文字)」というラベルの付いたテキスト・フィールドがあります。このフィールドへの入力と一致する都市について、そのデータを取得するためのさまざまな方法のボタンがそのフィールドの下にいくつもあります。これらのボタンには、「PHP generated CSV (PHP で生成された CSV)」、「PHP generated CSV variant (PHP で生成された CSV のバリエーション)」、「Python generated CSV (Python で生成された CSV)」などがあります。

これらのボタンのいずれかをクリックすると、対応する Web サービスが呼び出され、それによって CSV ファイルまたは ODS ファイルが生成されます (図 2)。ここでは安全のため、そして互換性の問題が起きないように、生成されたすべてのファイルを KOffice の KSpread と OpenOffice.org の Calc の両方で開くようにしました。

図 2. どのボタンでも同じ結果が生成されますが、生成方法は異なります
先ほどの、図 1 の画面が背景にあります。図 1 のページのボタンの 1 つがクリックされ、ポップアップ・ウィンドウが画面上に重ねて表示されています。ポップアップ・ウィンドウのタイトルは「Opening xml_1_php.ods (xml_1_php.ods を開く)」です。ユーザーは、「Cancel (キャンセル)」または「OK」をクリックすることができます。

CSV ファイルを生成する

ベース・システム

この記事のサンプルを作成するために私が使用したシステムは、OpenSUSE バージョン 10.3、Apache バージョン 2.2.4、MySQL バージョン 5.0.45、PHP バージョン 5.2.9、Python バージョン 2.5.1 です。

まず、単純な CSV ファイルを生成します。通常、CSV ファイルはあらゆる種類のソフトウェアで扱うことができ、また自動的に加工して ODS ファイルにすることができます (ただしセットアップは少しばかり面倒です)。

PHP で CSV ファイルを生成するのは非常に簡単です (リスト 2 のコードを参照)。必要なデータを取得したら、取得した結果に対して繰り返し処理を行い、一度に 1 フィールドずつ出力するだけのことです。ここでは SELECT 出力を 1,000 レコードに制限しましたが、OpenOffice.org の Calc の (そして偶然にも、Microsoft® Office Excel® の) 最大の行数である 65,536 まで、あるいは KOffice の KSpread の最大行数の 32,767 まで増やすこともできます。注意しなければならないことは、addslashes() を使用してフィールドの値をエスケープする必要があるということです。そうしないと、引用符を持つ値がある場合にコードが動作しなくなります。

リスト 2. csv_1.php によって単純な CSV ファイルを生成する
// 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";
}

このコードに fputcsv() を使い、フォーマットを設定する際の問題を処理することで、メイン・ループをもっとスマートにプログラミングすることができます (リスト 3)。また、tmpfile() を使用することで、数人のユーザーが同時に Web スクリプトを呼び出した場合に競合が発生するのを避けることができます。CSV ファイルが用意できると、リスト 2 と同じヘッダーが送信されるので、一時ファイルの内容を読み取り、その内容を出力する必要があります。

リスト 3. PHP の CSV 関数の 1 つである fputcsv を使用するバリエーション (csv_2.php)
// ...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;
}

// ...clean up code...

CSV から ODS へ

CSV ファイルを扱いたくない場合には、CSV ファイルを ODS に変換できるプログラムがいくつかあります。通常使われる方法は、OpenOffice.org の Calc を使用して、PyODConverter や PyUNO ブリッジなどを実行することで変換する方法です (「参考文献」を参照)。しかし、2 つの理由から、この方法は考え直した方がよいかもしれません。第 1 に、OpenOffice.org の Calc を (「ヘッドレス」モードで) 実行する必要がありますが、そうすると起動が遅くなる可能性があります。第 2 に、Python ライブラリーにはさまざまなバージョンがあるため、互換性のエラーが大量に発生し、インストール作業が大変になるかもしれません。そのため、この方法を試そうとする場合には、応答時間が長くなること、またプログラムを適切に実行させるために苦労することを覚悟する必要があります。

Python の csv モジュールを使用すると、さらに作業が容易になります (リスト 4)。このモジュールでデータを取得する方法は PHP の場合と似ています。CSV ファイルを作成するためには、どの区切り記号を使うか (カンマ [,])、またどのフィールドを含めるかを定義する必要があります。ここでは数値以外のすべてのフィールドを含めることにしました。TemporaryFile を使用すると、クリーンアップ・コードが不要になります。Python バージョン 2.6 を使用している場合は、SpooledTemporaryFile を使用すると、ファイルが大きくなりすぎない限りデータがメモリー内に保持されるため、さらに便利です。csv.writer メソッドは反復可能オブジェクトから CSV ファイルを生成します。cursor.fetchall() は優れたメソッドであり、たった 4 行で CSV を出力することができます。最後に、先ほどの PHP の場合と同じように、ヘッダーを出力するのに続けて一時ファイルからデータそのものを出力すればよいだけです。

リスト 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 the data as CSV:

    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()

ODS ファイルとはどんなものか

ODF ファイルは実際には ZIP ファイルであり、大量のファイルとディレクトリーを含んでいますが、すべての内容が必ず必要なわけではありません。一例として、私は単純なスプレッドシート (A1 セルに IBM というデータがあるのみ) を KOffice の KSpread と OpenOffice.org の Calc で作成し、それらによって作成される ODS ファイルを解凍し、その中に何が含まれるのかを調べてみました。リスト 5 は、その結果を示しています。

リスト 5. ODS ファイルの内容を検証する
# unzip -l kspread_ibm.ods
Archive:  kspread_ibm.ods
  Length     Date   Time    Name
 --------    ----   ----    ----
       46  08-21-09 14:00   mimetype
     2092  08-21-09 14:00   content.xml
     2631  08-21-09 14:00   styles.xml
     6342  08-21-09 14:00   settings.xml
      634  08-21-09 14:00   meta.xml
     1171  08-21-09 14:00   Thumbnails/thumbnail.png
      786  08-21-09 14:00   META-INF/manifest.xml
 --------                   -------
    13702                   7 files

# unzip -l openoffice_ibm.ods
Archive:  openoffice_ibm.ods
  Length     Date   Time    Name
 --------    ----   ----    ----
       46  08-21-09 17:00   mimetype
        0  08-21-09 17:00   Configurations2/statusbar/
        0  08-21-09 17:00   Configurations2/accelerator/current.xml
        0  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.xml
     6411  08-21-09 17:00   styles.xml
      876  08-21-09 17:00   meta.xml
     1012  08-21-09 17:00   Thumbnails/thumbnail.png
     7226  08-21-09 17:00   settings.xml
     1896  08-21-09 17:00   META-INF/manifest.xml
 --------                   -------
    21275                   15 files

どちらの場合も、最初に含まれているファイルは mimetype であり、mimetype には application/vnd.oasis.opendocument.spreadsheet という内容が含まれています。このファイルは、このパッケージの ZIP ファイルの先頭になければなりません。

もう 1 つ共通しているファイルが thumbnail.png です。ODF ファイルには、保存された文書のサムネイル表現 (128 x 128) が表示用に含まれています。しかし仕様では、サムネイル画像を含めることは必須ではないため、この例ではサムネイル画像を省略することができます。

同様に、他の大部分のファイルも省略できますが、(ZIP ファイルに含まれる他のすべてのファイルを記述する) manifest.xml ファイルを含む META-INF ディレクトリーと、スプレッドシートの実際の内容を保存する contents.xml ファイルは含める必要があります。ZIP ファイルにこれらのファイルを含め、ちょっと実験してみると、KOffice の KSpread も OpenOffice.org の Calc も、そうした最小限の内容を作成できることを確認できたので、以下の 3 つのファイルのみを作成すればよいことになります。

  • mimetype ファイルは定数が含まれているだけなので、生成するのは簡単です。
  • 内容を削減すると、manifest.xml ファイルは数行だけですみます (リスト 6)。
  • contents.xml はもっと複雑です。
リスト 6. 最小限の manifest.xml ファイル
<?xml version='1.0' encoding='UTF-8'?>
<manifest:manifest>
 <manifest:file-entry
   manifest: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>

基本的に、この XML コンテンツ文書には office:spreadsheet 要素が含まれており、その office:spreadsheet 要素にはスプレッドシートの個々のシートを表現する table:table 要素が含まれています。さらに、この table:table 要素には table:table-row 要素が (1 行当たり 1 要素) 含まれており、table:table-row 要素にはその行に含まれる連続したセルに対応する table:table-cell 要素が含まれています (リスト 7)。

リスト 7. セルを 1 つだけ持つコンテンツ・ファイルの例
<?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>
  </office:body>
</office:document-content>

このように最小限のコンテンツ・ファイルでは何もスタイルを追加できませんが、それについては後ほど説明します。では、実際に ODS ファイルを生成する作業を始めましょう。


XML を使用して直接 ODS ファイルを生成する。

XML ファイルはテキスト・ファイルであり、圧縮はコマンドラインから容易に行えるため、任意のスクリプト言語を使って簡単に ODS ファイルを生成することができます。この記事では、そのための 2 つの方法を紹介します。1 つは PHP を使った単純な方法、そしてもう 1 つはもっとスマートに、適切なモジュールを使って Python で行う方法です。(より洗練された方法で行いたい場合には、PHP 用の XML ファイルや ZIP パッケージをいくつか入手することができます。) まず、簡単なバージョンから始めることにしましょう (リスト 8)。データを (先ほどのリストと同じ方法で) 取得したら、contents.xml ファイルを作成する必要があります。固定ヘッダーを最初に含め、それに続いて結果データを、行ごとに、そしてセルごとに含め、最後にフッターを含めます。manifest.xml ファイルと mimetype ファイルは file_put_contents() を使うことで容易に生成することができます。次に、すべてのファイルを圧縮し、圧縮された ZIP ファイルの内容を適切なヘッダーを付けて出力し、余分なファイルとディレクトリーをすべて削除してクリーンアップします。

リスト 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>");

// 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} mimetype META-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 all created files

今度は Python を使った、モジュールによる方法に移ります。この方法では、メモリー内に作成した XML オブジェクトをファイルにしたら、zip モジュールを使って必要な ODS ファイルを作成します (リスト 9)。データを取得する方法はリスト 4 と同じです。manifestXml オブジェクトは内容が固定されているので、数行のコードのみで作成することができますが、contentXml オブジェクトはもっと大規模で構造もより複雑であるため、作成はもっと面倒です。カーソル行ごとにループ処理が必要であり (XML オブジェクトの中に行を作成します)、さらにまたデータ・フィールドごとに (その前に作成された各行にセルを追加する) ループ処理が必要なことに注意してください。すべてが用意できたら、あとは実際にファイルを作成するだけです。zip を使って必要な ZIP ファイルを作成し、出力ヘッダーを出力するのに続いて zip された構造の内容を出力して終了します。

リスト 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)
    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; each field, 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 into variable dataToReturn
    # ...clean up, by using os.remove() and os.rmdir()
    # ...send back dataToReturn, with appropriate headers

このコードは冗長であり、PHP の場合と同じ方法で行うこともできたのですが、ここでは同じ問題をさまざまな方法で対処できることを示すために、このようにしています。次のセクションでは、さらにコーディングを省略できるライブラリーをいくつか紹介します。


特定のライブラリーを使って ODS を生成する

XML ファイルを手動で生成するのは興味深い作業ですが、幸いなことに ODS 文書を直接生成できるライブラリーがいくつかあります。そのなかでも、私は ods-php を使用しました。ods-php はまだバージョン 0.1 ですが (リリース候補でもあり)、ODS 文書の生成に使用することができます。(このクラスを ODF ファイルの読み取りにも使用することができます。) 欠点として、PHP コード以外にはドキュメントがないため、このライブラリーを使いこなすためには少し想像力が必要です。

データの取得方法は先ほどと同じです。ODS ファイルを生成するためには、newOds() オブジェクトを作成し、そのオブジェクトに addCell メソッドを使ってセルを追加します。セルは行と列によって識別され、0 から始まります (セル A1 は 0 行 0 列です)。オブジェクトの用意ができたら、そのオブジェクトを saveOds メソッドによって適切な ODS フォーマットでディスクに保存します。あとは適切なヘッダーを出力してから ODS ファイルの内容を出力するだけです (リスト 10)。クリーンアップするためには、たった今作成した ODS ファイルを削除する必要があります。

リスト 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, with appropriate headers...
// ...clean up...

Python の Odfpy モジュールには、ODS-php と似たような、ただしもっと充実したライブラリーがあります。このライブラリーを使用すれば、あらゆる種類の ODF ファイルをゼロの状態から作成することができます。あるいは既存の文書をメモリーにロードして変更し、再度保存することもできます。データを (先ほどと同様の方法で) 取得した後、ODS ファイルを作成するためには、まずは OpenDocumentSpreadsheet() によって文書を作成します。それから表を作成してその文書に追加し、最後にデータを挿入します。データを挿入するためには、まず表に行を追加し、次に行にセルを追加します (リスト 11)。このコードの最後の部分は、今やおなじみのはずです。つまりヘッダーを出力し、生成された ODS ファイルの内容を取得して出力したら、余分なファイルを削除してクリーンアップします。

リスト 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...

Odfpy パッケージを調べると、他にも ODS ファイルを生成する際に使用できるものがあります。具体的には、最終的に ODS ファイルを生成する場合に役立つ xml2odf スクリプトなどは興味深いスクリプトかもしれません。次のセクションでは、ODS ファイルの外観を少し見栄えよくする方法について考えることにしましょう。


ODS ファイルを装飾する

ここまでの段階では、いくつかの異なる方法でODS ファイルを作成することができました。しかし作成されたファイルは (控えめに言うと) 飾りのない簡素なものでした (図 3)。そこで、出力をスタイル付きテキストにするための 2 つの方法を検証しましょう。1 つは適切な XML ファイルを PHP で直接生成する単純な方法、もう 1 つは Python の Odfpy ライブラリーを使って生成する高度な方法です。

図 3. この段階までの結果
結果を一覧表示した単純な見かけのスプレッドシート

スタイルを使うことが非常に複雑なわけではありませんが、多くのことを考慮する必要があります。ここではタイトル用に、フォント・サイズが大きく、文字色が青色で、太字に設定したスタイルと、列見出し用に、背景色が灰色で、太字に設定したスタイルが必要だとします。私は使いやすい自動スタイルを利用することにしました。自動スタイルでは、任意のセルに手動でフォーマットを適用すると自動的にスタイルが作成され (そのため自動スタイルという名前が付いています)、個別のファイルに保存されるのではなく、content.xml ファイルの中に保存されます。この場合の文書の office:automatic-styles 要素はリスト 12 のようになるはずです。

リスト 12. スプレッドシートを装飾するために追加の XML を作成する
<office:automatic-styles>.
    <style:style
        style: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:family='table-cell'>
        <style:text-properties
            fo:font-weight='bold'/>
        <style:table-cell-properties
            fo:background-color='#AEAEAE'/>
    </style:style>
</office:automatic-styles>

PHP を使う場合には、コードは実質的にリスト 10 と同じですが、XML 文書ヘッダーを変更し、必要な automatic-styles 要素を含める必要があります。ここではセル用に 2 つの新しい接頭辞も定義しており、それぞれの接頭辞には適切な table:style-name 属性が含まれていることにも注意してください。最後に、新しいメイン・タイトルと、スペースを空けるための空行と、列タイトルの行を追加すればよいだけです (リスト 13)。

リスト 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 new definitions:

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

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

Python の場合には、odfpy を使ってスタイルを作成することは難しくありません。しかしドキュメントがあまり親切ではないため、私は何度か実験を行い、実験の結果と OpenOffice.org の Calc 文書の内容とを比較しなければなりませんでした。結局のところ Python の場合には、新しいスタイルを作成し、それらのスタイルを Calc 文書の automaticstyles 部分に追加する必要があります。それをすれば、タイトルや列ヘッダーの追加は簡単です。単純にセルを作成し、必要な stylename を指定するだけで、他の作業は必要ありません (リスト 14)。コードの残りの部分がリスト 11 とほとんど同じであることに注意してください。

リスト 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 on grey 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)

    # Add a listing description, in the bold big blue style, 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

スタイルを追加した結果は、おそらくスタイルで賞をもらうことはないかもしれませんが、少なくとも見栄えがよくなっています。図 4 を見てください。

図 4. タイトルとスタイルを追加することで外観を改善する
よりスタイリッシュな結果。タイトルには青色の文字色が使われ、列見出しには灰色の背景色と太字の文字が使われています。

ここまで出来上がると、同じスプレッドシートに複数のページやシートを含める方法や、数式を追加する方法、さらにはグラフを追加して出力を大幅にわかりやすいものにする方法の検討を始めることができます。


まとめ

この記事では、基本的な CSV ファイルを生成する方法から完全な ODS ファイルを生成する方法まで、標準的なフォーマットで表データを生成するための方法をいくつか検証しました。ODS ファイルを生成する方法としては、手動で行う方法 (必要なファイルやディレクトリーの作成、結果の圧縮などをすべて手動で行う方法) と、適切なライブラリーを使用する方法を説明しました。また、少し追加の作業をすることで、見た目が整ったスプレッドシートを生成することもできます。作業が容易になればユーザーは喜びます。そのまま使用できるスプレッドシートを生成することで、こうしたユーザーの要求を満たすことができます。この記事を読んだ皆さんは、この記事で説明した機能をご自身の Web ページや Web サービスに追加する作業を始められるはずです。


ダウンロード

内容ファイル名サイズ
All source files for this articlesource_files.zip16KB

参考文献

学ぶために

  • パターン + GWT + Ajax = ユーザビリティー」(Federico Kereki 著、developerWorks、2009年7月) は、この記事で紹介したデータベースの別の使い方を説明しています。
  • MaxMind は世界の都市の表を無料で提供しています。ISO は ISO 3166 コード・ファミリーを、国と地域 (3166-2) の両方に関して提供しています。識別のための別の仕組みとして、FIPS による地域コードを入手してください。
  • ODF 1.1 仕様 (PDF) を入手してください。バージョン 1.2 がリリースされるまでは、1.1 が最新の標準です。
  • developerWorks の Web development ゾーンには、Web 2.0 開発のためのツールや情報が豊富に用意されています。
  • developerWorks の Technical events and webcasts で最新情報を入手してください。
  • My developerWorks を調べてみてください。そして Web 開発に関する、あるいは皆さんにとって関心のある任意の話題に関する、グループ、ブログ、アクティビティーを見つけ、あるいは作成してください。

製品や技術を入手するために

  • Odfpy を利用すると、ODF ファイルを直接生成するプロセスを単純化することができます。
  • ods-php パッケージは Odfpy と似ており、PHP 用です (ただし Odfpy ほどオプションは多くありません)。
  • PyODConverter を使うと CSV を ODS に変換することができ、さらに他の同様の変換も行うことができますが、セットアップで苦労するかもしれません。別の選択肢として、同じ Web サイトに JodConverter があります。
  • Python-UNO ブリッジは OpenOffice.org の Calc を操作して CSV ファイルから ODS ファイルを生成するためのツールの 1 つです。ただしこのツールの場合も、バージョンの問題から、インストールとセットアップが単純ではないかもしれません。
  • IBM 製品の評価版を今すぐダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品をお試しください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=455829
ArticleTitle=オープンな出力: Web サービスから ODF スプレッドシートを作成する
publish-date=11172009