例如 OpenOffice.org (現為 Apache OpenOffice), LibreOffice, KOffice 等辦公室軟件
閉源的 Microsoft Office 由 2007 開始 及 線上辦公室應用的 Google Docs (現為 Google Drive 的一部分)
亦支援 OpenDocument Format
OpenDocument Format 由於使用 XML ,檔案於資料交換上非常簡單及方便
OpenDocument Format 將文件內容,文件風格,圖像等分開保存,再利用 Zip 壓縮技術將所有檔案包裝成一個獨立檔案
所以以任何文字編輯器觀察文件,會發現文件頭 2 個位元組是 PK (即 Phil Katz Zip 的作者)
在 Windows 環境下強行將副案名改為 zip 便可以將檔案壓縮
這篇文章主要介紹只使用 XML 製作 OpenDocument Spreadsheet (ods) 格式
為甚麼要使用 ods ? 而不使用更簡單的 Comma Separated Values (csv) 或 Plain Text (txt)
預設的 Spreadsheet ,csv 以逗號分隔,而 txt 以 tab 分隔
其他開源辦公室軟件,還可以自訂欄位分隔號及文字分隔符
兩者雖然非常簡單方便,但兩者都不能加入風格,例如文字顏色、欄位顏色、邊界顏色等
雖然 ods 不及 csv 及 txt 簡單,但 ods 仍然是一種不錯的 Spreadsheet 格式
若果只製作純資料的 OpenDocument Spreadsheet 文件
文件配置上只需要有 META-INF/manifest.xml, content.xml 這兩個文件
META-INF/manifest.xml 為指示以哪一種 OpenDocument Format 開啟及處理方式,因此這個檔案非常重要
(注意,META-INF 為全大寫字母)
content.xml 包含文件版面設定、字型、風格,及最主要的內容
META-INF/manifest.xml 內容大致如下,使用最基本的 OpenDocument Format 資訊
1 2 3 4 5 | <? xml version = "1.0" encoding = "UTF-8" ?> <!-- --> < manifest:manifest xmlns:manifest = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" > <!-- --> < 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 > |
content.xml 內容大致如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <? xml version = "1.0" encoding = "UTF-8" ?> <!-- --> < office:document-content xmlns:office = "urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text = "urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table = "urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:style = "urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" office:version = "1.0" > <!-- --> < office:body > <!-- --> < office:spreadsheet > <!-- --> < table:table table:name = "Sheet1" > <!-- --> < table:table-row > <!-- --> < table:table-cell office:value-type = "string" > <!-- --> < text:p > <![CDATA[hello, world]]> </ text:p > <!-- --> </ table:table-cell > <!-- --> </ table:table-row > <!-- --> </ table:table > <!-- --> </ office:spreadsheet > <!-- --> </ office:body > <!-- --> </ office:document-content > |
當中的
1 2 3 4 5 6 7 | < table:table table:name = "Sheet1" > <!-- --> < table:table-row > <!-- --> < table:table-cell office:value-type = "string" > <!-- --> < text:p > <![CDATA[hello, world]]> </ text:p > <!-- --> </ table:table-cell > <!-- --> </ table:table-row > <!-- --> </ table:table > |
大致上與 HTML 的 table tr td 差不多
<![CDATA[hello, world]]> 這種寫法是避免文字包含「&」、「>」、「<」這類 XML 特殊字元而發生問題
另外常用的跨列跨欄屬性 table:number-rows-spanned, table:number-columns-spanned 及 文字屬性 office:value-type
有 string (字串), float (浮點數), boolean (真假值) 等常用文字屬性
*註
由於由開源辦公室軟件所編製的 ods 不會包含 newline 及 tab 而是用單行編寫
使用者閱讀上相當困難,尤其資料非常龐大時,文字編輯器對單行編寫的閱讀能力亦會下降
而在下使用了大量 <!----> 是為了避免 XML 解析 newline 及 tab 卻保留閱讀性
但這種寫法會令文件體積增加 (單是一組 <!----> 已經 7 個位元組,還未計算 newline 及 tab 數量)
然後編製 ZipFile.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | <?php class ZipFile{ private $datasec = array (); private $ctrl_dir = array (); private $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00" ; private $old_offset = 0; private function unix2DosTime( $unixtime = 0){ $timearray = (( $unixtime == 0) ? getdate () : getdate ( $unixtime )); if ( $timearray [ 'year' ] < 1980){ $timearray [ 'year' ] = 1980; $timearray [ 'mon' ] = 1; $timearray [ 'mday' ] = 1; $timearray [ 'hours' ] = 0; $timearray [ 'minutes' ] = 0; $timearray [ 'seconds' ] = 0; } return (( $timearray [ 'year' ] - 1980) << 25) | ( $timearray [ 'mon' ] << 21) | ( $timearray [ 'mday' ] << 16) | ( $timearray [ 'hours' ] << 11) | ( $timearray [ 'minutes' ] << 5) | ( $timearray [ 'seconds' ] >> 1); } public function addFile( $data , $name , $time = 0){ $name = str_replace ( '\\' , '/' , $name ); $dtime = substr ( '00000000' . dechex ( $this ->unix2DosTime( $time )), -8); $hexdtime = '\x' . $dtime [6] . $dtime [7] . '\x' . $dtime [4] . $dtime [5] . '\x' . $dtime [2] . $dtime [3] . '\x' . $dtime [0] . $dtime [1]; eval ( '$hexdtime = "' . $hexdtime . '";' ); $fr = "\x50\x4b\x03\x04" ; $fr .= "\x14\x00" ; $fr .= "\x00\x00" ; $fr .= "\x08\x00" ; $fr .= $hexdtime ; $unc_len = strlen ( $data ); $crc = crc32( $data ); $zdata = gzcompress( $data ); $zdata = substr ( substr ( $zdata , 0, strlen ( $zdata ) - 4), 2); $c_len = strlen ( $zdata ); $fr .= pack( 'V' , $crc ); $fr .= pack( 'V' , $c_len ); $fr .= pack( 'V' , $unc_len ); $fr .= pack( 'v' , strlen ( $name )); $fr .= pack( 'v' , 0); $fr .= $name ; $fr .= $zdata ; $this ->datasec[] = $fr ; $cdrec = "\x50\x4b\x01\x02" ; $cdrec .= "\x00\x00" ; $cdrec .= "\x14\x00" ; $cdrec .= "\x00\x00" ; $cdrec .= "\x08\x00" ; $cdrec .= $hexdtime ; $cdrec .= pack( 'V' , $crc ); $cdrec .= pack( 'V' , $c_len ); $cdrec .= pack( 'V' , $unc_len ); $cdrec .= pack( 'v' , strlen ( $name )); $cdrec .= pack( 'v' , 0); $cdrec .= pack( 'v' , 0); $cdrec .= pack( 'v' , 0); $cdrec .= pack( 'v' , 0); $cdrec .= pack( 'V' , 32); $cdrec .= pack( 'V' , $this ->old_offset); $this ->old_offset += strlen ( $fr ); $cdrec .= $name ; $this ->ctrl_dir[] = $cdrec ; } public function file(){ $data = implode( '' , $this ->datasec); $ctrldir = implode( '' , $this ->ctrl_dir); return $data . $ctrldir . $this ->eof_ctrl_dir . pack( 'v' , sizeof( $this ->ctrl_dir)) . pack( 'v' , sizeof( $this ->ctrl_dir)) . pack( 'V' , strlen ( $ctrldir )) . pack( 'V' , strlen ( $data )) . "\x00\x00" ; } } ?> |
這個類別可能閣下已經見過,這個類別其實來自 phpMyAdmin
由於減少多餘的資料,在下將原來的 comment 全部刪除,若果想查看原文件,請到 phpmyadmin/libraries/zip.lib.php
(注意:不同的 phpmyadmin 設定有機會不同,如 NAS, Windows, Linux 等,本文使用 LinuxMint)
這個類別的好處是不需要創建額外檔案便可以直接輸出 zip 檔

1 2 3 4 5 6 7 8 9 10 | <?php // require('ZipFile.php'); header( 'Content-Type: application/zip; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="myzip.zip"' ); $zip = new ZipFile(); $zip ->addFile( 'my content' , 'content.txt' ); echo $zip ->file(); ?> |


這樣便可以輸出 zip 檔
同樣道理,由於 ods 只是 zip
只要將輸出 MIME 改寫為 application/vnd.oasis.opendocument.spreadsheet
及加載檔案名為 META-INF/manifest.xml 及 content.xml 即可

1 2 3 4 5 6 7 8 9 10 11 | <?php // require('ZipFile.php'); header( 'Content-Type: application/vnd.oasis.opendocument.spreadsheet; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="myods.ods"' ); $zip = new ZipFile(); $zip ->addFile( $manifest_string , 'META-INF/manifest.xml' ); $zip ->addFile( $content_string , 'content.xml' ); echo $zip ->file(); ?> |



另外還可以透過各類壓縮軟件,直接將檔案壓縮為 zip
以 7-Zip 為例子,由於 7-Zip 為跨平台軟件,利用 7-Zip 以 zip 壓縮文件可以無視不同平台的問題
1 | 7z a -tzip -mx9 myods.ods content.xml META-INF/ |
關於 7-Zip指令 請查看在下另一篇文章

在 GUI 的操作下,使用 Windows 內建的 zip 或 Linux / Unix / Mac 的 Archive Manager 選擇 zip
亦可以達至相同效果
透過具 OpenDocument Spreadsheet 分析功能既辦公室軟件開啟
沒有留言 :
張貼留言