用PHP解析XSL

发表于:2007-07-14来源:作者:点击数: 标签:
在 php 的应用当中,为做到数据和代码分离需要使用模板技术。pear、phplib及不少公司都提供了相关的模板。但他们有一个共同的缺点:就是没有统一的规范,给使用者带来很多不便。另外有关的教程和范例较少,也太初浅,不易做深层次的 开发 应用。 XSL是W3C组
    在php的应用当中,为做到数据和代码分离需要使用模板技术。pear、phplib及不少公司都提供了相关的模板。但他们有一个共同的缺点:就是没有统一的规范,给使用者带来很多不便。另外有关的教程和范例较少,也太初浅,不易做深层次的开发应用。
    XSL是W3C组织的规范标准,随着XML的应用而发展起来。其教程随处可见,只要你有ie5就可使用。当然由于是新技术,在支持程度上尚显不足。
    这里给大家介绍一种用用PHP解析XSL的方法。该方法仅使用PHP提供的XML函数,无须难以配置的XSLT。
    先看一下例子。
    将以下内容保存为resume.xml
<?xml version="1.0" encoding="GB2312"?>
<?xml:stylesheet type="text/xsl" href="resume2.xsl"?>
<document>
<resume>
  <alias>唠叨</alias>
  <name>徐祖宁</name>
  <sex>男</sex>
  <birthday>1948.10</birthday>
  <addr>安徽</addr>
  <email>czjsz_ah@stats.gov.cn</email>
  <icq> </icq>
  <oicq> </oicq>
  <skill>C/C++、VFP、PHP、JavaScript</skill>
  <homepage> </homepage>
  <date>2001-7-19</date>
</resume>
<resume>
  <alias>刁馋</alias>
  <name>保密</name>
  <sex>男</sex>
  <birthday> </birthday>
  <addr>黑龙江</addr>
  <email>yuepengfei@mail.banner.com.cn</email>
  <icq>166581208</icq>
  <oicq>7665656</oicq>
  <skill> </skill>
  <homepage> </homepage>
  <date>2001-8-15</date>
</resume>
<resume>
  <alias>sports98</alias>
  <name>保密</name>
  <sex>男</sex>
  <birthday> </birthday>
  <addr>四川</addr>
  <email>flyruns@hotmail.com</email>
  <icq>15787767</icq>
  <oicq>11599322</oicq>
  <skill> </skill>
  <homepage>http://www.hiviresearch.com/cgi/report/</homepage>
  <date>2002-1-5</date>
</resume>
</document>

    将以下内容保存为resume1.xsl
<?xml version="1.0" encoding="GB2312"?>
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<TITLE>个人简历</TITLE>
</HEAD><BODY>
<TABLE border="1" cellspacing="0" style="font-size:10pt">
<CAPTION style="font-size: 110%; font-weight: bold">
版主信息
</CAPTION>
<xsl:for-each select="document">
<TR>
<TH>别名</TH>
<TH>姓名</TH>
<TH>性别</TH>
<TH>所在地</TH>
<TH>专长</TH>
</TR>
<xsl:for-each select="resume">
<TR>
<TD><xsl:value-of select="alias"/></TD>
<TD><xsl:value-of select="name"/></TD>
<TD><xsl:value-of select="sex"/></TD>
<TD><xsl:value-of select="addr"/></TD>
<TD><xsl:value-of select="skill"/></TD>
</TR>
</xsl:for-each>
</xsl:for-each>
</TABLE>
</BODY>
</HTML>

    将以下内容保存为resume2.xsl
<?xml version="1.0" encoding="GB2312"?>
<HTML xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<HEAD>
<TITLE>个人简历</TITLE>
</HEAD><BODY>
<xsl:for-each select="document">
<xsl:for-each select="resume">
<TABLE border="1" cellspacing="0" style="font-size:10pt">
<CAPTION style="font-size: 110%; font-weight: bold">
版主信息
</CAPTION>
<TR>
<TH>别名</TH><TD><xsl:value-of select="alias"/></TD>
<TH>姓名</TH><TD><xsl:value-of select="name"/></TD>
<TH>性别</TH><TD><xsl:value-of select="sex"/></TD>
<TH>所在地</TH><TD><xsl:value-of select="addr"/></TD>
</TR>
<TR>
<TH>加入时间</TH>
<TD colspan="7"><xsl:value-of select="date"/></TD>
</TR>
<TR>
<TH>专长</TH>
<TD colspan="7"><xsl:value-of select="skill"/></TD>
</TR>
<TR>
<TH>ICQ</TH>
<TD colspan="7"><xsl:value-of select="icq"/></TD>
</TR>
<TR>
<TH>OICQ</TH>
<TD colspan="7"><xsl:value-of select="oicq"/></TD>
</TR>
<TR>
<TH>主页</TH>
<TD colspan="7"><xsl:value-of select="homepage"/></TD>
</TR>
</TABLE>
</xsl:for-each>
</xsl:for-each>
</BODY>
</HTML>

    在ie5以上浏览器上查看resume.xml,并可修改resume.xml中<?xml:stylesheet type="text/xsl" href="resume2.xsl"?> 的resume2.xsl为resume1.xsl,可看到页面的变化。当然由于不是所有的浏览器都支持这个转换,所以需要在服务器上进行转换。

    将以下内容保存为xmltest.php
<?php
require_once "xsl_class.php";
$xml = new XML;
$p = new XSL;
$p->parser("resume2.xsl",$xml->parser("resume.xml"));
$p->display();
?>
    变换其中的resume2.xsl,我们仍将看到不同的页面,只是以转变成HTML格式了。

   相关的类:
   类xml_class解析xml文档产生一个类似于domxml的结构
   类xsl_class派生于xml_class,用于解析xsl文档并模拟xsl函数,其中template尚未实现。
*****************
   xml_class.php
*****************
<?php
class Element {
  var $Element;  // 这种节点用于文档中的任何元素。元素节点的子节点可以是其内容的元素节点、注释节点、处理信息节点以及文本节点。
  var $Text;  // 文档中出现的所有文本,都分组归入到文本节点中。文本节点不可以有同为文本节点的紧接着的前或后的兄弟节点。
  var $Attribute; // 每一个元素节点都有一套自己附加的属性节点。默认的属性值以与指定属性一样的方法来处理。这些节点都没有子节点。
  var $Namespace; // 对于每一个以xlmns:和属性节点开头的元素,都有一个名称空格节点。这些节点没有子节点。
  var $ProcessingInstruction; // 每一个处理指令都有一个单独的节点。这些节点都没有子节点。
  var $Comment; // 每一个都有一个注释节点。这些节点都没有子节点。
  var $parents = array();  
  var $childs = array();  
}

class xml {
  var $tm = array();
  var $xml_parser;
  var $data = array();
  var $element = ""; // 当前节点
  var $stack = array(); // 缓存当前标头的相关参数
  var $type;

  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) {
    if($this->element != "") {
      array_push($this->stack,$this->element);
    }
    $this->element = array(Name => $name);
    if(sizeof($attribs)) {
      $this->element[Attribute] = $attribs;
    }
  }

  //处理元素的结束标头
  function endElement($parser, $name) {
    $element = array_pop($this->stack);
    if(is_array($element)) {
      $element[Element][] = $this->element;
      $this->element = $element;
    }else {
      $this->data[Root] = $this->element;
      $this->element = "";
    }
  }

  //处理字元资料标头
  function characterData($parser, $data) {
    $data = eregi_replace("^ +","",$data);
    $data = eregi_replace("^\n+","",$data);
    if(strlen($data) > 0) {
      $this->element[Text] .= $data;
    }
  }

  //处理指令标头
  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($this->trustedFile($parser_file[$parser])) {
          eval($data);
        } else {
          $this->tm[] = sprintf("Untrusted PHP code: <i>%s</i>",
                  htmlspecialchars($data));
        }
        break;
      default:
//        echo $target;
//        echo "==".$data;
//        echo printf("%s %s",$target,$data);
        break;
    }
  }

  //处理内定标头
  function defaultHandler($parser, $data) {
    if(substr($data, 0, 1) == "&" && substr($data, -1, 1) == ";") {
      $this->tm[] = sprintf('<font color="#aa00aa">%s</font>',
              htmlspecialchars($data));
    }else {
      $this->tm[] = sprintf('<font size="-1">%s</font>',
              htmlspecialchars($data));
    }
  }

  //处理外部实体参引标头
  function externalEntityRefHandler($parser, $openEntityNames, $base, $systemId, $publicId) {
    if ($systemId) {
      $p = new xml;
      return $p->parser($systemId);
    }
    return false;
  }

  function parser($file) {
    global $parser_file;

    if(!($fp = @fopen($file, "r"))) {
      return false;
    }
    $this->xml_parser = xml_parser_create();
    xml_set_object($this->xml_parser, &$this);  //使 XML 剖析器用对象

    xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, 1);
    xml_set_element_handler($this->xml_parser, "startElement", "endElement");
    xml_set_character_data_handler($this->xml_parser, "characterData");
    xml_set_processing_instruction_handler($this->xml_parser, "PIHandler");
    xml_set_default_handler($this->xml_parser, "defaultHandler");
    xml_set_external_entity_ref_handler($this->xml_parser, "externalEntityRefHandler");
    
    $this->type = xml_parser_get_option($this->xml_parser, XML_OPTION_CASE_FOLDING);
    while($data = fread($fp, 4096)) {
      if(!xml_parse($this->xml_parser, $data, feof($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)));
        return false;
      }
    }
    xml_parser_free($this->xml_parser);
    return $this->data;
  }
}
?>

********************
    xsl_class.php
********************

<?php
require_once "xml_class.php";

class xsl extends xml {
  var $datastack = array();
  var $sp;
  function parser($file,$dsn=null) {
    parent::parser($file);
    if($dsn != null) {
      $this->dsn[Element][0] = $dsn[Root];
    }
  }
  //处理元素的开始标头
  function startElement($parser, $name, $attribs) {
    if(eregi("^XSL:",$name)) {
      $ar = split(":",$name);
      return array_push($this->data,array(xsl => $ar[1],command => $attribs));
    }
    if(sizeof($attribs)) {
      $att = "";
      while(list($k, $v) = each($attribs)) {
        $att .= " $k=\"$v\"";
      }
      array_push($this->data,array(tag => "$name$att"));
    }else
      array_push($this->data,array(tag => "$name"));
  }

  //处理元素的结束标头
  function endElement($parser, $name) {
    if(!eregi("^XSL:",$name)) {
      array_push($this->data,array(tag => "/$name"));
    }else {
      $ar = split(":",$name);
      array_push($this->data,array(xsl => "/$ar[1]"));
    }
  }

  //处理字元资料标头
  function characterData($parser, $data) {
    $data = eregi_replace("^[ \n]+","",$data);
    if(strlen($data) > 0) {
      array_push($this->data,array(text => "$data"));
    }
  }

  //处理指令标头
//  function PIHandler($parser, $target, $data) {
//  }

  //处理内定标头
  function defaultHandler($parser, $data) {
  }

  //处理外部实体参引标头
//  function externalEntityRefHandler($parser, $openEntityNames, $base, $systemId, $publicId) {
//  }

  // XSL指令解析
  function xsl_parser($i) {
    for(;$i<count($this->data);$i++) {
      $key = $this->data[$i];
      if(isset($key[xsl]))
        if(eregi("/xsl",$key[xsl]))
          return $i;
    }
  }

  // 从数据源读取数据
  function get_data($ps) {
    if(! eregi("/",$ps)) {
      // 若是默认的层次
      $ps = join("/",$this->datastack)."/$ps";
    }
    return "[$ps]";
  }

  // 输出结果
  function display() {
    $this->stack = array();  //初始化控制栈
    $this->datastack = array();  //初始化数据栈

    $type = true;  //用于控制text项的输出
    for($id=0;$id<count($this->data);$id++) {
      $expr = $this->data[$id];
      list($key,$value) = each($expr);
      switch($key) {
        case "tag":
          echo "<".$value.">";
          if(eregi("^/",$value))
            echo "\n";
          break;
        case "text":
          if($type)
            echo $value;
          break;
        case "xsl":
//          echo $value;
          list(,$command) = each($expr); // 取得操作集
          $value = eregi_replace("[/-]","_",strtolower($value));
          if(eregi("eval",$value))
            $value = eregi_replace("eval","xsl_eval",$value);
          $this->$value($command,$id);
          break;
      }
    }
  }
  // 检索数据,$dsn开始的节点,$field节点名,$n匹配次数
  function find($dsn,$field,$n=0) {
    if(! isset($dsn[Element]))
      return false;
    $root = $dsn[Element];
    for($i=0;$i<count($root);$i++) {
      if($this->type) {
        if(eregi("^".$field."$",$root[$i][Name])) {
          if(!$n--)
            return $root[$i];
        }
      }else {
        if(ereg("^".$field."$",$root[$i][Name])) {
          if(!$n--)
            return $root[$i];
        }
      }
    }
    for($i=0;$i<count($root);$i++) {
      if($ar = $this->find($root[$i],$field,&$n))
        return $ar;
    }
    return false;
  }

  function for_each($command,&$id) {
    // 循环,将当前id压入堆栈
    array_push($this->stack,array($id,$command[SELECT],1));
    // 检索数据指针
    $data = $this->find($this->dsn,$command[SELECT]);
    // 数据指针压入堆栈
    array_push($this->datastack,$data);
  }
  function _for_each($command,&$id) {
    // 取得入口地址
    $ar = array_pop($this->stack);
    // 抛弃当前数据指针
    array_pop($this->datastack);
    // 检查是否为嵌套
    if(count($this->datastack) > 0) {
      $dsn = array_pop($this->datastack);
      array_push($this->datastack,$dsn);
    }else
      $dsn = $this->dsn;
    $n = $ar[2];
    // 检索数据指针
    $data = $this->find($dsn,$ar[1],$n);
    if($data) {
      // 如检索到,则循环
      $ar[2]++;
      array_push($this->datastack,$data);
      array_push($this->stack,$ar);
      $id = $ar[0];
    }
  }
  function value_of($command) {
    // 取得数据指针
    if(eregi("/",$command[SELECT])) {
    }else {
      if(count($this->datastack) > 0) {
        $dsn = array_pop($this->datastack);
        array_push($this->datastack,$dsn);
      }else
        $dsn = $this->dsn;
      $data = $this->find($dsn,$command[SELECT]);
    }
    print $data[Text];
  }
  function _value_of() {
  }
  function stylesheet() {
  }
  function _stylesheet() {
  }
  function template($command) {
echo join(" ",$command)."<br>";
  }
  function _template() {
  }
  function apply_templates($command) {
echo join(" ",$command)."<br>";
  }
  function _apply_templates() {
  }
  function xsl_eval() {
  }
  function _xsl_eval() {
  }
}

/**** 附录 ****
数据元素节点
Array
(
  [Name] // 节点名
  [Text]
  [Attribute]
  [Namespace]
  [Comment]
  [ProcessingInstruction]
  [Element] => Array()
)
*************/
?>

原文转自:http://www.ltesting.net