用 PHP 讀取和編寫 XML DOM

 

PHP 讀取和編寫 XML DOM

使用 DOM 庫、SAX 解析器和正則表達式

2006 2 06

有許多技術可用於用 PHP 讀取和編寫 XML。本文提供了三種方法讀取 XML:使用 DOM 庫、使用 SAX 解析器和使用正則表達式。還介紹了使用 DOM PHP 文本模板編寫 XML

PHP 讀取和編寫可擴展標記語言(XML)看起來可能有點恐怖。實際上,XML 和它的所有相關技術可能是恐怖的,但是用 PHP 讀取和編寫 XML 不一定是項恐怖的任務。首先,需要學習一點關於 XML 的知識 —— 它是什麼,用它做什麼。然後,需要學習如何用 PHP 讀取和編寫 XML,而有許多種方式可以做這件事。

本文提供了 XML 的簡短入門,然後解釋如何用 PHP 讀取和編寫 XML

什麼是 XML

XML 是一種數據存儲格式。它沒有定義保存什麼數據,也沒有定義數據的格式。XML 只是定義了標記和這些標記的屬性。格式良好的 XML 標記看起來像這樣:

<name>Jack Herrington</name>

這個 <name> 標記包含一些文本:Jack Herrington

不包含文本的 XML 標記看起來像這樣:

<powerUp />

XML 對某件事進行編寫的方式不止一種。例如,這個標記形成的輸出與前一個標記相同:

<powerUp></powerUp>

也可以向 XML 標記添加屬性。例如,這個 <name> 標記包含 first last 屬性:

<name first="Jack" last="Herrington" />

也可以用 XML 對特殊字符進行編碼。例如,& 符號可以像這樣編碼:

&

包含標記和屬性的 XML 文件如果像示例一樣格式化,就是格式良好的,這意味着標記是對稱的,字符的編碼正確。清單 1 是一份格式良好的 XML 的示例。


清單 1. XML 圖書列表示例

 

  <books>

  <book>

  <author>Jack Herrington</author>

  <title>PHP Hacks</title>

  <publisher>O'Reilly</publisher>

  </book>

  <book>

  <author>Jack Herrington</author>

  <title>Podcasting Hacks</title>

  <publisher>O'Reilly</publisher>

  </book>

  </books>

 

清單 1 中的 XML 包含一個圖書列表。父標記 <books> 包含一組 <book> 標記,每個 <book> 標記又包含 <author><title> <publisher> 標記。

XML 文檔的標記結構和內容得到外部模式文件的驗證後,XML 文檔就是正確的。模式文件可以用不同的格式指定。對於本文來說,所需要的只是格式良好的 XML

如果覺得 XML 看起來很像超文本標記語言(HTML),那麼就對了。XML HTML 都是基於標記的語言,它們有許多相似之處。但是,要着重指出的是:雖然 XML 文檔可能是格式良好的 HTML,但不是所有的 HTML 文檔都是格式良好的 XML。換行標記(br)是 XML HTML 之間區別的一個好例子。這個換行標記是格式良好的 HTML,但不是格式良好的 XML

<p>This is a paragraph<br>
With a line break</p>

這個換行標記是格式良好的 XML HTML

<p>This is a paragraph<br />
With a line break</p>

如果要把 HTML 編寫成同樣是格式良好的 XML,請遵循 W3C 委員會的可擴展超文本標記語言(XHTML)標準(參見 參考資料)。所有現代的瀏覽器都能呈現 XHTML。而且,還可以用 XML 工具讀取 XHTML 並找出文檔中的數據,這比解析 HTML 容易得多。

 

 

使用 DOM 庫讀取 XML

讀取格式良好的 XML 文件最容易的方式是使用編譯成某些 PHP 安裝的文檔對象模型 DOM)庫。DOM 庫把整個 XML 文檔讀入內存,並用節點樹表示它,如圖 1 所示。


1. 圖書 XML XML DOM

樹頂部的 books 節點有兩個 book 子標記。在每本書中,有 authorpublisher title 幾個節點。authorpublisher title 節點分別有包含文本的文本子節點。

讀取圖書 XML 文件並用 DOM 顯示內容的代碼如清單 2 所示。

清單 2. DOM 讀取圖書 XML

 

  <?php

  $doc = new DOMDocument();

  $doc->load( 'books.xml' );

 

  $books = $doc->getElementsByTagName( "book" );

  foreach( $books as $book )

  {

  $authors = $book->getElementsByTagName( "author" );

  $author = $authors->item(0)->nodeValue;

 

  $publishers = $book->getElementsByTagName( "publisher" );

  $publisher = $publishers->item(0)->nodeValue;

 

  $titles = $book->getElementsByTagName( "title" );

  $title = $titles->item(0)->nodeValue;

 

  echo "$title - $author - $publisher/n";

  }

  ?>

 

 

腳本首先創建一個 new DOMdocument 對象,用 load 方法把圖書 XML 裝入這個對象。之後,腳本用 getElementsByName 方法得到指定名稱下的所有元素的列表。

book 節點的循環中,腳本用 getElementsByName 方法獲得 authorpublisher title 標記的 nodeValuenodeValue 是節點中的文本。腳本然後顯示這些值。

可以在命令行上像這樣運行 PHP 腳本:

% php e1.php
PHP Hacks - Jack Herrington - O'Reilly
Podcasting Hacks - Jack Herrington - O'Reilly
%

可以看到,每個圖書塊輸出一行。這是一個良好的開始。但是,如果不能訪問 XML DOM 庫該怎麼辦?

 

SAX 解析器讀取 XML

讀取 XML 的另一種方法是使用 XML Simple APISAX)解析器。PHP 的大多數安裝都包含 SAX 解析器。SAX 解析器運行在回調模型上。每次打開或關閉一個標記時,或者每次解析器看到文本時,就用節點或文本的信息回調用戶定義的函數。

SAX 解析器的優點是,它是真正輕量級的。解析器不會在內存中長期保持內容,所以可以用於非常巨大的文件。缺點是編寫 SAX 解析器回調是件非常麻煩的事。清單 3 顯示了使用 SAX 讀取圖書 XML 文件並顯示內容的代碼。

清單 3. SAX 解析器讀取圖書 XML

 

  <?php

  $g_books = array();

  $g_elem = null;

 

  function startElement( $parser, $name, $attrs )

  {

  global $g_books, $g_elem;

  if ( $name == 'BOOK' ) $g_books []= array();

  $g_elem = $name;

  }

 

  function endElement( $parser, $name )

  {

  global $g_elem;

  $g_elem = null;

  }

 

  function textData( $parser, $text )

  {

  global $g_books, $g_elem;

  if ( $g_elem == 'AUTHOR' ||

  $g_elem == 'PUBLISHER' ||

  $g_elem == 'TITLE' )

  {

  $g_books[ count( $g_books ) - 1 ][ $g_elem ] = $text;

  }

  }

 

  $parser = xml_parser_create();

 

  xml_set_element_handler( $parser, "startElement", "endElement" );

  xml_set_character_data_handler( $parser, "textData" );

 

  $f = fopen( 'books.xml', 'r' );

 

  while( $data = fread( $f, 4096 ) )

  {

  xml_parse( $parser, $data );

  }

 

  xml_parser_free( $parser );

 

  foreach( $g_books as $book )

  {

  echo $book['TITLE']." - ".$book['AUTHOR']." - ";

  echo $book['PUBLISHER']."/n";

  }

  ?>

 

 

腳本首先設置 g_books 數組,它在內存中容納所有圖書和圖書信息,g_elem 變量保存腳本目前正在處理的標記的名稱。然後腳本定義回調函數。在這個示例中,回調函數是 startElementendElement textData。在打開和關閉標記的時候,分別調用 startElement endElement 函數。在開始和結束標記之間的文本上面,調用 textData

在這個示例中,startElement 標記查找 book 標記,在 book 數組中開始一個新元素。然後,textData 函數查看當前元素,看它是不是 publishertitle author 標記。如果是,函數就把當前文本放入當前圖書。

爲了讓解析繼續,腳本用 xml_parser_create 函數創建解析器。然後,設置回調句柄。之後,腳本讀取文件並把文件的大塊內容發送到解析器。在文件讀取之後,xml_parser_free 函數刪除解析器。腳本的末尾輸出 g_books 數組的內容。

可以看到,這比編寫 DOM 的同樣功能要困難得多。如果沒有 DOM 庫也沒有 SAX 庫該怎麼辦?還有替代方案麼?

 

用正則表達式解析 XML

可以肯定,即使提到這個方法,有些工程師也會批評我,但是確實可以用正則表達式解析 XML。清單 4 顯示了使用 preg_ 函數讀取圖書文件的示例。
清單 4. 用正則表達式讀取 XML

 

  <?php

  $xml = "";

  $f = fopen( 'books.xml', 'r' );

  while( $data = fread( $f, 4096 ) ) { $xml .= $data; }

  fclose( $f );

 

  preg_match_all( "//<book/>(.*?)/<//book/>/s",

  $xml, $bookblocks );

 

  foreach( $bookblocks[1] as $block )

  {

  preg_match_all( "//<author/>(.*?)/<//author/>/",

  $block, $author );

  preg_match_all( "//<title/>(.*?)/<//title/>/",

  $block, $title );

  preg_match_all( "//<publisher/>(.*?)/<//publisher/>/",

  $block, $publisher );

  echo( $title[1][0]." - ".$author[1][0]." - ".

  $publisher[1][0]."/n" );

  }

  ?>

 

請注意這個代碼有多短。開始時,它把文件讀進一個大的字符串。然後用一個 regex 函數讀取每個圖書項目。最後用 foreach 循環,在每個圖書塊間循環,並提取出 authortitle publisher

那麼,缺陷在哪呢?使用正則表達式代碼讀取 XML 的問題是,它並沒先進行檢查,確保 XML 的格式良好。這意味着在讀取之前,無法知道 XML 是否格式良好。而且,有些格式正確的 XML 可能與正則表達式不匹配,所以日後必須修改它們。

我從不建議使用正則表達式讀取 XML,但是有時它是兼容性最好的方式,因爲正則表達式函數總是可用的。不要用正則表達式讀取直接來自用戶的 XML,因爲無法控制這類 XML 的格式或結構。應當一直用 DOM 庫或 SAX 解析器讀取來自用戶的 XML

 

DOM 編寫 XML

讀取 XML 只是公式的一部分。該怎樣編寫 XML 呢?編寫 XML 最好的方式就是用 DOM。清單 5 顯示了 DOM 構建圖書 XML 文件的方式。

清單 5. DOM 編寫圖書 XML

 

  <?php

  $books = array();

  $books [] = array(

  'title' => 'PHP Hacks',

  'author' => 'Jack Herrington',

  'publisher' => "O'Reilly"

  );

  $books [] = array(

  'title' => 'Podcasting Hacks',

  'author' => 'Jack Herrington',

  'publisher' => "O'Reilly"

  );

 

  $doc = new DOMDocument();

  $doc->formatOutput = true;

 

  $r = $doc->createElement( "books" );

  $doc->appendChild( $r );

 

  foreach( $books as $book )

  {

  $b = $doc->createElement( "book" );

 

  $author = $doc->createElement( "author" );

  $author->appendChild(

  $doc->createTextNode( $book['author'] )

  );

  $b->appendChild( $author );

 

  $title = $doc->createElement( "title" );

  $title->appendChild(

  $doc->createTextNode( $book['title'] )

  );

  $b->appendChild( $title );

 

  $publisher = $doc->createElement( "publisher" );

  $publisher->appendChild(

  $doc->createTextNode( $book['publisher'] )

  );

  $b->appendChild( $publisher );

 

  $r->appendChild( $b );

  }

 

  echo $doc->saveXML();

  ?>

 

在腳本的頂部,用一些示例圖書裝入了 books 數組。這個數據可以來自用戶也可以來自數據庫。

示例圖書裝入之後,腳本創建一個 new DOMDocument,並把根節點 books 添加到它。然後腳本爲每本書的 authortitle publisher 創建節點,併爲每個節點添加文本節點。每個 book 節點的最後一步是重新把它添加到根節點 books

腳本的末尾用 saveXML 方法把 XML 輸出到控制檯。(也可以用 save 方法創建一個 XML 文件。)腳本的輸出如清單 6 所示。
清單 6. DOM 構建腳本的輸出

 

  % php e4.php

  <?xml version="1.0"?>

  <books>

  <book>

  <author>Jack Herrington</author>

  <title>PHP Hacks</title>

  <publisher>O'Reilly</publisher>

  </book>

  <book>

  <author>Jack Herrington</author>

  <title>Podcasting Hacks</title>

  <publisher>O'Reilly</publisher>

  </book>

  </books>

  %

 

使用 DOM 的真正價值在於它創建的 XML 總是格式正確的。但是如果不能用 DOM 創建 XML 時該怎麼辦?

 

PHP 編寫 XML

如果 DOM 不可用,可以用 PHP 的文本模板編寫 XML。清單 7 顯示了 PHP 如何構建圖書 XML 文件。


清單 7. PHP 編寫圖書 XML

 

  <?php

  $books = array();

  $books [] = array(

  'title' => 'PHP Hacks',

  'author' => 'Jack Herrington',

  'publisher' => "O'Reilly"

  );

  $books [] = array(

  'title' => 'Podcasting Hacks',

  'author' => 'Jack Herrington',

  'publisher' => "O'Reilly"

  );

  ?>

  <books>

  <?php

 

  foreach( $books as $book )

  {

  ?>

  <book>

  <title><?php echo( $book['title'] ); ?></title>

  <author><?php echo( $book['author'] ); ?>

  </author>

  <publisher><?php echo( $book['publisher'] ); ?>

  </publisher>

  </book>

  <?php

  }

  ?>

  </books>

 

腳本的頂部與 DOM 腳本類似。腳本的底部打開 books 標記,然後在每個圖書中迭代,創建 book 標記和所有的內部 titleauthor publisher 標記。

這種方法的問題是對實體進行編碼。爲了確保實體編碼正確,必須在每個項目上調用 htmlentities 函數,如清單 8 所示。

清單 8. 使用 htmlentities 函數對實體編碼

 

  <books>

  <?php

 

  foreach( $books as $book )

  {

  $title = htmlentities( $book['title'], ENT_QUOTES );

  $author = htmlentities( $book['author'], ENT_QUOTES );

  $publisher = htmlentities( $book['publisher'], ENT_QUOTES );

  ?>

  <book>

  <title><?php echo( $title ); ?></title>

  <author><?php echo( $author ); ?> </author>

  <publisher><?php echo( $publisher ); ?>

  </publisher>

  </book>

  <?php

  }

  ?>

  </books>

 

這就是用基本的 PHP 編寫 XML 的煩人之處。您以爲自己創建了完美的 XML,但是在試圖使用數據的時候,馬上就會發現某些元素的編碼不正確。

 

結束語

XML 周圍總有許多誇大之處和混淆之處。但是,並不像您想像的那麼難 —— 特別是在 PHP 這樣優秀的語言中。在理解並正確地實現了 XML 之後,就會發現有許多強大的工具可以使用。XPath XSLT 就是這樣兩個值得研究的工具。

 

參考資料

學習


獲得產品和技術

  • 請訪問 PHP.net,瞭解關於 PHP 的最新新聞、找到下載,並向其他用戶學習。

  • 瞭解 Expat XML Parser,這個解析器用來向 PHP 提供 SAX 解析器功能。

  • 利用 IBM 試用軟件 改造您的下一個開放源碼開發項目,可以下載也可以通過 DVD 得到。


討論

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章