CLXII. XML 語法解析函數

簡介

XML(eXtensible Markup Language,可增加旗標語系)是一種在 web 上進行文件交換的資料格式。該語系是由 W3C(World Wide Web Concortium,世界萬維網組織)定義的一種標準。可以訪問 http://www.w3.org/XML/ 以取得關於 XML 及其關聯技術的更多訊息。

本增加模組可為 James Clark 的 expat 提供支援。該工具包說明解析 XML 文件(而非 XML 文件的有效化)。它支援三種原始碼的編碼模式,這三種編碼模式也被 PHP 本身所支援,它們分別是:US-ASCIIISO-8859-1UTF-8。本系統尚不支援 UTF-16

本增加模組使會員能夠建立 XML 語法解析器,並對不同的 XML 事件定義對應的處理器。每個 XML 語法解析器都有若干個可根據需要調整的參數

需求

本增加模組預設使用 expat compat layer。它也可以使用 expat,可以訪問 http://www.jclark.com/xml/expat.html 來取得。expat 自帶的 Makefile 檔案不會建立預設的增加庫,可以使用以下的建立規則來實現:
libexpat.a: $(OBJS)
    ar -rc $@ $(OBJS)
    ranlib $@

請訪問 http://sourceforge.net/projects/expat/ 以取得 expat 源檔案的 RPM 包。

安裝

這些函數預設為有效的,它們使用了捆綁的 expat 庫。您可以通過參數 --disable-xml 來屏蔽 XML 的支援。若果您將 PHP 編譯為 Apache 1.3.9 或更高版本的一個模組, PHP 將自動使用 Apache 捆綁的 expat 庫。若果您不希望使用該捆綁的 expat 庫,請在運行 PHP 的 configure 配置腳本時使用參數 --with-expat-dir=DIR,其中 DIR 應該指向 expat 安裝的根目錄。

PHP 的 Windows 版本已經內建該增加模組的支援。無需加載任何附加增加庫即可使用這些函數。

運行時配置

本增加模組在 php.ini 中未定義任何配置選項。

資源類型

xml

xml_parser_create()xml_parser_create_ns() 返回的 xml 資源引用了一個 XML 解析器案例,將被用在本增加庫提供的函數中。

預定義常量

以下常量由本增加模組定義,因此只有在本增加模組被編譯到 PHP 中,或是在運行時被動態加載後才有效。

XML_ERROR_NONE (integer)

XML_ERROR_NO_MEMORY (integer)

XML_ERROR_SYNTAX (integer)

XML_ERROR_NO_ELEMENTS (integer)

XML_ERROR_INVALID_TOKEN (integer)

XML_ERROR_UNCLOSED_TOKEN (integer)

XML_ERROR_PARTIAL_CHAR (integer)

XML_ERROR_TAG_MISMATCH (integer)

XML_ERROR_DUPLICATE_ATTRIBUTE (integer)

XML_ERROR_JUNK_AFTER_DOC_ELEMENT (integer)

XML_ERROR_PARAM_ENTITY_REF (integer)

XML_ERROR_UNDEFINED_ENTITY (integer)

XML_ERROR_RECURSIVE_ENTITY_REF (integer)

XML_ERROR_ASYNC_ENTITY (integer)

XML_ERROR_BAD_CHAR_REF (integer)

XML_ERROR_BINARY_ENTITY_REF (integer)

XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF (integer)

XML_ERROR_MISPLACED_XML_PI (integer)

XML_ERROR_UNKNOWN_ENCODING (integer)

XML_ERROR_INCORRECT_ENCODING (integer)

XML_ERROR_UNCLOSED_CDATA_SECTION (integer)

XML_ERROR_EXTERNAL_ENTITY_HANDLING (integer)

XML_OPTION_CASE_FOLDING (integer)

XML_OPTION_TARGET_ENCODING (integer)

XML_OPTION_SKIP_TAGSTART (integer)

XML_OPTION_SKIP_WHITE (integer)

事件處理器

XML 事件處理器定義如下:

表格 1. 已支援的 XML 事件處理器

用來設定處理器的 PHP 函數事件描述
xml_set_element_handler() 元素事件(Element events)將在 XML 解析器遇到旗標符的起始符或是終止符時發生。另外,對於起始符和終止符也有獨立的處理器。
xml_set_character_data_handler() 粗略的說,字元資料(Character data)是指 XML 文件中所有旗標符以外的內容,內含旗標符之間的空格。需要注意的是 XML 語法解析器不會加上或是去掉任何空格。空格的取捨將由套用程式(也就是你自己)來決定。
xml_set_processing_instruction_handler() PHP 程式員對「處理指令」(Processing Instructions,PI)應該已經很熟悉了。<?php ?> 就是一個處理指令,其中 php 被稱為「PI target」。除了以「XML」開頭的 PI target 已被保留以外,對這些 PI 的處理將由套用程式來完成。
xml_set_default_handler() 所有無法被其它處理器處理的事件將由預設處理器來處理。這些事件內含諸如 XML 和文件類型聲明等內容。
xml_set_unparsed_entity_decl_handler() 該處理器將在遇到無法解析的實體名稱(NDATA)聲明時被呼叫。
xml_set_notation_decl_handler() 該處理器將在聲明一個注解時被呼叫。
xml_set_external_entity_ref_handler() 當 XML 解析器遇到指向外部解析的一般實體名時,該處理器將被呼叫。該指向的目的可以是一個檔案,也可以是 URL。請參閱「外部實體名範例」。

大小寫折疊(Case Folding)

元素處理函數可能會導致元素名稱「大小寫折疊」(case-folded)。「大小寫折疊」被 XML 標準定義為「一個套用於一系列字元的過程,在該過程中,這些字元中的所有的非大寫字元將被置換成它們對應大寫等價字元」。換句話說,對於 XML,「大小寫折疊」就是指將字串轉換成大寫字元。

所有被傳遞給處理器函數的元素名稱將預設的發生「大小寫折疊」。該過程可以分別被 xml_parser_get_option()xml_parser_set_option() 函數查詢和控制。

錯誤代碼

以下常量被定義為 XML 的錯誤代碼,將由 xml_parse() 返回:

XML_ERROR_NONE
XML_ERROR_NO_MEMORY
XML_ERROR_SYNTAX
XML_ERROR_NO_ELEMENTS
XML_ERROR_INVALID_TOKEN
XML_ERROR_UNCLOSED_TOKEN
XML_ERROR_PARTIAL_CHAR
XML_ERROR_TAG_MISMATCH
XML_ERROR_DUPLICATE_ATTRIBUTE
XML_ERROR_JUNK_AFTER_DOC_ELEMENT
XML_ERROR_PARAM_ENTITY_REF
XML_ERROR_UNDEFINED_ENTITY
XML_ERROR_RECURSIVE_ENTITY_REF
XML_ERROR_ASYNC_ENTITY
XML_ERROR_BAD_CHAR_REF
XML_ERROR_BINARY_ENTITY_REF
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF
XML_ERROR_MISPLACED_XML_PI
XML_ERROR_UNKNOWN_ENCODING
XML_ERROR_INCORRECT_ENCODING
XML_ERROR_UNCLOSED_CDATA_SECTION
XML_ERROR_EXTERNAL_ENTITY_HANDLING

字元編碼

PHP 的 XML 增加庫支援不同字元編碼(character encoding)的 Unicode 字集。字元編碼有兩種形式,它們分別是「源編碼」(source encoding)和「目的編碼」(target encoding)。PHP 對文件內定表示的編碼模式是 UTF-8

源編碼將在 XML 文件被解析後完成。源編碼可在建立一個 XML 解析器時指明(該編碼模式在 XML 解析器的生命週期中不能被再次改變)。支援的編碼模式內含 ISO-8859-1US-ASCIIUTF-8。前兩種為單位元組編碼,即每個字元被一個單一的位元組表示。UTF-8 支援 1 至 4 個位元組的多 bit(最多 12)字元編碼。PHP 預設使用 ISO-8859-1 作為源編碼模式。

目的編碼將在 PHP 向 XML 處理器函數傳輸資料時被完成。當 XML 解析器被建立後,目的編碼將被設定成與源編碼相同的編碼模式,但該模式可在任何時候被變更。目的編碼將影響字元資料、旗標符名稱以及處理指令目的(PI target)。

若果 XML 解析器遇到其源編碼模式表示能力之外的字元,它將返回一個錯誤。

當 PHP 在被解析的 XML 文件中遇到現用的目的編碼無法表示的字元時,這些字元將被「降級」。簡單的說,這些字元將被問號置換。

範例

以下是 PHP 腳本解析 XML 文件的一些範例。

XML 元素結構範例

第一個範例用縮進格式顯示一個文件中起始元素的結構。

例子 1. 顯示 XML 元素結構

<?php
$file 
"data.xml";
$depth = array();

function 
startElement($parser$name$attrs)
{
    global 
$depth;
    for (
$i 0$i $depth[$parser]; $i++) {
        echo 
"  ";
    }
    echo 
"$name\n";
    
$depth[$parser]++;
}

function 
endElement($parser$name)
{
    global 
$depth;
    
$depth[$parser]--;
}

$xml_parser xml_parser_create();
xml_set_element_handler($xml_parser"startElement""endElement");
if (!(
$fp fopen($file"r"))) {
    die(
"could not open XML input");
}

while (
$data fread($fp4096)) {
    if (!
xml_parse($xml_parser$datafeof($fp))) {
        die(
sprintf("XML error: %s at line %d",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
xml_parser_free($xml_parser);
?>

XML 旗標符映射範例

例子 2. 將 XML 映射為 HTML

以下範例將 XML 文件中的旗標符直接映射成 HTML 旗標符。在「映射陣列」中不存在的元素將被忽略。當然,該範例將只對一個特定的 XML 文件有效。

<?php
$file 
"data.xml";
$map_array = array(
    
"BOLD"     => "B",
    
"EMPHASIS" => "I",
    
"LITERAL"  => "TT"
);

function 
startElement($parser$name$attrs)
{
    global 
$map_array;
    if (isset(
$map_array[$name])) {
        echo 
"<$map_array[$name]>";
    }
}

function 
endElement($parser$name)
{
    global 
$map_array;
    if (isset(
$map_array[$name])) {
        echo 
"</$map_array[$name]>";
    }
}

function 
characterData($parser$data)
{
    echo 
$data;
}

$xml_parser xml_parser_create();
// 使用大小寫折疊來保證我們能在元素陣列中找到這些元素名稱
xml_parser_set_option($xml_parserXML_OPTION_CASE_FOLDINGtrue);
xml_set_element_handler($xml_parser"startElement""endElement");
xml_set_character_data_handler($xml_parser"characterData");
if (!(
$fp fopen($file"r"))) {
    die(
"could not open XML input");
}

while (
$data fread($fp4096)) {
    if (!
xml_parse($xml_parser$datafeof($fp))) {
        die(
sprintf("XML error: %s at line %d",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
xml_parser_free($xml_parser);
?>

XML 外部實體範例

該範例能夠高亮顯示 XML 原始碼。它將說明如何外部實體指向處理器來包括和解析其它文件,如何處理 PIs,以及一種確定包括有 PIs 的代碼的可信度。

能被該範例使用的的 XML 文件(xmltest.xmlxmltest2.xml)被列在該範例之後。

例子 3. 外部實體範例

<?php
$file 
"xmltest.xml";

function 
trustedFile($file)
{
    
// only trust local files owned by ourselves
    
if (!eregi("^([a-z]+)://"$file)
        && 
fileowner($file) == getmyuid()) {
            return 
true;
    }
    return 
false;
}

function 
startElement($parser$name$attribs)
{
    echo 
"&lt;<font color=\"#0000cc\">$name</font>";
            if (
count($attribs)) {
                foreach (
$attribs as $k => $v) {
            echo 
" <font color=\"#009900\">$k</font>=\"<font
                   color=\"#990000\">$v</font>\""
;
        }
    }
    echo 
"&gt;";
}

function 
endElement($parser$name)
{
    echo 
"&lt;/<font color=\"#0000cc\">$name</font>&gt;";
}

function 
characterData($parser$data)
{
    echo 
"<b>$data</b>";
}

function 
PIHandler($parser$target$data)
{
    switch (
strtolower($target)) {
        case 
"php":
            global 
$parser_file;
            
// If the parsed document is "trusted", we say it is safe
            // to execute PHP code inside it.  If not, display the code
            // instead.
            
if (trustedFile($parser_file[$parser])) {
                eval(
$data);
            } else {
                
printf("Untrusted PHP code: <i>%s</i>",
                        
htmlspecialchars($data));
            }
            break;
    }
}

function 
defaultHandler($parser$data)
{
    if (
substr($data01) == "&" && substr($data, -11) == ";") {
        
printf('<font color="#aa00aa">%s</font>',
                
htmlspecialchars($data));
    } else {
        
printf('<font size="-1">%s</font>',
                
htmlspecialchars($data));
    }
}

function 
externalEntityRefHandler($parser$openEntityNames$base$systemId,
                                  
$publicId) {
    if (
$systemId) {
        if (!list(
$parser$fp) = new_xml_parser($systemId)) {
            
printf("Could not open entity %s at %s\n"$openEntityNames,
                   
$systemId);
            return 
false;
        }
        while (
$data fread($fp4096)) {
            if (!
xml_parse($parser$datafeof($fp))) {
                
printf("XML error: %s at line %d while parsing entity %s\n",
                       
xml_error_string(xml_get_error_code($parser)),
                       
xml_get_current_line_number($parser), $openEntityNames);
                
xml_parser_free($parser);
                return 
false;
            }
        }
        
xml_parser_free($parser);
        return 
true;
    }
    return 
false;
}

function 
new_xml_parser($file)
{
    global 
$parser_file;

    
$xml_parser xml_parser_create();
    
xml_parser_set_option($xml_parserXML_OPTION_CASE_FOLDING1);
    
xml_set_element_handler($xml_parser"startElement""endElement");
    
xml_set_character_data_handler($xml_parser"characterData");
    
xml_set_processing_instruction_handler($xml_parser"PIHandler");
    
xml_set_default_handler($xml_parser"defaultHandler");
    
xml_set_external_entity_ref_handler($xml_parser"externalEntityRefHandler");

    if (!(
$fp = @fopen($file"r"))) {
        return 
false;
    }
    if (!
is_array($parser_file)) {
        
settype($parser_file"array");
    }
    
$parser_file[$xml_parser] = $file;
    return array(
$xml_parser$fp);
}

if (!(list(
$xml_parser$fp) = new_xml_parser($file))) {
    die(
"could not open XML input");
}

echo 
"<pre>";
while (
$data fread($fp4096)) {
    if (!
xml_parse($xml_parser$datafeof($fp))) {
        die(
sprintf("XML error: %s at line %d\n",
                    
xml_error_string(xml_get_error_code($xml_parser)),
                    
xml_get_current_line_number($xml_parser)));
    }
}
echo 
"</pre>";
echo 
"parse complete\n";
xml_parser_free($xml_parser);

?>

例子 4. xmltest.xml

<?xml version='1.0'?>
<!DOCTYPE chapter SYSTEM "/just/a/test.dtd" [
<!ENTITY plainEntity "FOO entity">
<!ENTITY systemEntity SYSTEM "xmltest2.xml">
]>
<chapter>
 <TITLE>Title &plainEntity;</TITLE>
 <para>
  <informaltable>
   <tgroup cols="3">
    <tbody>
     <row><entry>a1</entry><entry morerows="1">b1</entry><entry>c1</entry></row>
     <row><entry>a2</entry><entry>c2</entry></row>
     <row><entry>a3</entry><entry>b3</entry><entry>c3</entry></row>
    </tbody>
   </tgroup>
  </informaltable>
 </para>
 &systemEntity;
 <section id="about">
  <title>About this Document</title>
  <para>
   <!-- this is a comment -->
   <?php echo 'Hi!  This is PHP version ' . phpversion(); ?>
  </para>
 </section>
</chapter>

以下文件將被 xmltest.xml 檔案呼叫:

例子 5. xmltest2.xml

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY testEnt "test entity">
]>
<foo>
   <element attrib="value"/>
   &testEnt;
   <?php echo "This is some more PHP code being executed."; ?>
</foo>

目錄
utf8_decode --  將用 UTF-8 模式編碼的 ISO-8859-1 字串轉換成單位元組的 ISO-8859-1 字串。
utf8_encode -- 將 ISO-8859-1 編碼的字串轉換為 UTF-8 編碼
xml_error_string -- 取得 XML 解析器的錯誤字串
xml_get_current_byte_index -- 取得 XML 解析器的現用的位元組索引
xml_get_current_column_number --  取得 XML 解析器的現用的列號
xml_get_current_line_number -- 取得 XML 解析器的現用的行號
xml_get_error_code -- 取得 XML 解析器錯誤代碼
xml_parse_into_struct -- 將 XML 資料解析到陣列中
xml_parse -- 開始解析一個 XML 文件
xml_parser_create_ns --  建立一個支援命名空間的 XML 解析器
xml_parser_create -- 建立一個 XML 解析器
xml_parser_free -- 釋放特殊的 XML 解析器
xml_parser_get_option -- 從 XML 解析器取得選項設定訊息
xml_parser_set_option -- 為指定 XML 解析進行選項設定
xml_set_character_data_handler -- 建立字元資料處理器
xml_set_default_handler -- 建立預設處理器
xml_set_element_handler -- 建立起始和終止元素處理器
xml_set_end_namespace_decl_handler --  建立終止命名空間聲明處理器
xml_set_external_entity_ref_handler -- 建立外部實體指向處理器
xml_set_notation_decl_handler -- 建立注解聲明處理器
xml_set_object -- 在物件中使用 XML 解析器
xml_set_processing_instruction_handler --  建立處理指令(PI)處理器
xml_set_start_namespace_decl_handler --  建立起始命名空間聲明處理器
xml_set_unparsed_entity_decl_handler --  建立未解析實體定義聲明處理器