最近更新: 2007-04-10

A note of creating XML document by SimpleXML

本文嚐試利用 PHP5 提供的 SimpleXML 函數組 ,建立一份可供 MS Excel 2000/XP 版本使用的 XML 文件。

需求起因於資料庫之資料匯出需求。以往大都採用 CSV 格式匯出,然而 CSV 文件用於保存 Big5 內碼編碼之資料時水土不服,容易發生分欄錯誤之情況。儘管我們將字元編碼改為 UTF-8 後便可避免此問題,但是卻又面臨 MS Excel 2000/XP 無法以正確之字元編碼讀取 CSV 文件的窘境。因此我需要一個可為 OpenOffice 以及 MS Excel 2000/XP 兩者共同辨識的資料格式。經我測試後,確認 MS Excel 2000/XP 之 XML 試算表格式符合此需求。

接下來將以 MS Excel 2000/XP 之 XML 格式為準,撰寫一個以 SimpleXML 建立 XML 文件的測試程式,並說明 SimpleXML 之部份使用事項。

下例是 MS Excel 2000/XP 最基本的 XML 試算表範例:

<?xml version="1.0" encoding="utf-8"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">
 <Worksheet ss:Name="Sheet1">
  <Table>
   <Row>
    <Cell><Data ss:Type="Number">1</Data></Cell>
    <Cell><Data ss:Type="String">測試</Data></Cell>
   </Row>
  </Table>
 </Worksheet>
</Workbook>

試算表中的工作表元素由 <Worksheet><Table> 組成,其中 <Worksheet> 必須具備屬性 ss:Name。工作表之下為「列(<Row>)」,列之下為「儲存格(<Cell>)」。

下列為利用 SimpleXML 建立 XML 文件的測試程式。

<?php
$doc = new SimpleXMLElement(
'<?xml version="1.0" encoding="utf-8"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40"></Workbook>'
);

//<Worksheet ss:Name="Sheet1">
//<Table>
$worksheetNode = $doc->addChild('Worksheet');

//$worksheetNode->addAttribute('ss:Name', 'sheet1');//BUG?
$worksheetNode['ss:Name'] = 'sheet1';

$worksheetNode->Table = '';//add a child with value '' by setter
//$tableNode = $worksheetNode->addChild('Table');/add a child by addChild()

//<Row>
$row = $worksheetNode->Table->addChild('Row');

//<Cell><Data ss:Type="Number">1</Data></Cell>
$cell = $row->addChild('Cell');

$cell->Data = 2; //shorthand
//$doc->Worksheet->Table->Row->Cell->Data = 2; //fullpath
//$doc->Worksheet->Table->Row->Cell[0]->Data = 2;
//$cell->addChild('Data', 2);

$cell->Data['ss:Type'] = 'String';//shorthand
//$doc->Worksheet->Table->Row->Cell->Data['ss:Type'] = 'String';//fullpath
//$cell->Data->addAttribute('ss:Type', 'String'); //BUG?

$cell = $row->addChild('Cell'); //shorthand
//$cell = $doc->Worksheet->Table->Row->addChild('Cell'); //fullpath

//$doc->Worksheet->Table->Row->Cell[1]->Data = 'hello';
//$row->Cell[1]->Data = 'hello';
$cell->Data = 'hello';
$cell->Data['ss:Type'] = 'String';

$cell = $row->addChild('Cell'); //shorthand
$cell->Data = iconv('big5', 'utf-8', '中文測試');
$cell->Data['ss:Type'] = 'String';

$doc->asXML('test1.xml');

?>

首先,我提供一份空白試算表的 XML 內容給 SimpleXMLElement 建立最本的 SimpleXML 文件結構。$doc 便指向此文件之根節點 <Workbook> 。接著我以 addChild() 在根節點之下添加子節點 <Worksheet>

由於 <Worksheet> 必須具有屬性 ss:Name ,所以我接下來嚐試以 addAttribute()<Worksheet> 設定屬性。然而此時我碰到了一個疑似 bug 的狀況。除了 addAttribute() 外,我們也可以透過陣列索引子,以 setter 直接增加新屬性。通常以 setter 直接設定新屬性之方式與 addAttribute() 之效果相同,但對於屬性名稱結合名稱空間之處理方式有所差異(Maybe a bug of addAttribute())。在此例中(attribute with namespace), addAttribute() 會忽略屬性名稱的名稱空間 'ss',故其輸出之 XMLString 的 <Worksheet> 之屬性名稱為 Type 而非 ss:Type。但以 setter 設定時,則可忠實保留屬性名稱及名稱空間。故我接下來皆以 setter 方式直接增加屬性。

子節點之增加,除了 addChild() 外,亦可以 setter 直接建立。故第18行直接以 setter 增加 <Table> 節點。但此方式僅限單一子節點。

第一個子節點可藉由 setter 直接建立,無須先以 addChild() 建立子節點。然而第二個以後之同名子節點便不能由 setter 直接建立,必須以 addChild() 添加。故第36及44行之第2及第3儲存格 <Cell> 皆須以 addChild() 方法加入。

使用 SimpleXML 存取節點之途徑很多。我們可透過類似 XPath 之語法描述之根節點到目標節點的路徑存取,也可以透過子節點之參照以速記方式存取子節點以下之目標節點。我將之並列於程式碼中供各位參考。

最後使用 asXML() 方法將 SimpleXML 個體轉成 XML 字串,並存入指定的檔案 test1.xml 中。該 XML 文件可以 MS Excel 2000/XP 與 OpenOffice 開啟,試算表中包含一行三列之內容。

本文是 php-SpreadsheetReader 專案之一部工作。
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2981347.html