Location: PHPKode > scripts > dm.IS Layout > dm-is-layout/class.IS_Layout.php
<?php

//
// Edit History:
//
//  Dick Munroe hide@address.com 16-Dec-2004
//	Make the ':' in blocks optional.
//	Allow simple single dimension arrays in loop replace.
//
//  Dick Munroe hide@address.com 22-Dec-2004
//	Trim \n off of blocks.
//
//  Dick Munroe hide@address.com 11-Jan-2005
//	Pass replacement data by reference.
//	Add interfaces that allow passing data by value.
//	Eliminate use of regular expression replacements as they seem to fail
//	on large replacements or with many usages.  There may be a leak.
//
//  Dick Munroe hide@address.com 18-Jan-2005
//	Remove two dimension limitation on arrays.
//
//  Dick Munroe (hide@address.com) 18-Mar-2006
//      Seems that is_file can generate error text when checking for a file
//      in the constructor.  Make it silent.
//
//  Dick Munroe (hide@address.com) 20-Mar-2006
//      Removal of unused tags didn't have valid regular expression.
//
//  Dick Munroe (hide@address.com) 21-Mar-2006
//      Fixed the end of line characters to be "unix" format.
//      Turns out PREG_CAPTURE_OFFSET is only available from PHP 4.3.0 on.
//      The current _replace member uses it so make the version independent
//      _replace was changed to fix a memory footprint problem stumbled on
//      in one of the 4.x versions so there will be a version test and
//      for early version of PHP, preg_replace_all will be used.
//
//  Dick Munroe (hide@address.com) 30-Nov-2006
//      PHP 5 compatibility.
//
//	Dick Munroe (hide@address.com) 03-Dec-2007
//		Add tagged end blocks to all nested replacements.
//		Add qualified replacements to blocks to make nested replacements
//		work well.
//

/**
* Show erros constant
* @const SHOW_ERRORS
*/
define("SHOW_ERRORS",true);

/**
* HTML error renderer
* @const ERROR_CODE
*/
define("ERROR_CODE",'<font face="verdana, arial" size="1"><b>Layout Error: <font color="#000080">[error]</font></b></font><br>');



/**
* Read and process HTML files
*
* Template tags appear within square brackets, e.g.
*
*	[date]
*
* would be a tag.  Tags are replaced by the {@link IS_Layout::replace()}
* method.  Normally tags are simple scaler replacements, but
* tags may also be multi dimension arrays, e.g.:
*
*	[data,date]
*
*  or
*
* 	[date,today,day]
*
* In fact, the arrays can be of any dimension and the tags that
* get replace are of the form:
*
*	[tag,key0,key1,key2,...]
*
* There are two "block" oriented tokens that get used during
* template processing.  The first is used to control looping
* while doing template processing of arrays.  It has the form:
*
*	<!-- replace id="variable name" -->
*	    text within which to do template processing.
*	<!-- end replace -->
*
* It is possible to nest replacements by tagging the end replace
* statement:
*
* 	<!-- replace id="v1" -->
* 		<!-- replace id="v2" -->
* 		<!-- end replace id="v2" -->
* 	<!-- end replace id="v1" -->
*
* Note that the tags delimiting the block are actually html
* comments, so that the template can be editted in any html
* aware editor with no problem.
*
* Loop replacement occurs through the use of the {@link IS_Layout::loop_replace()}
* method.
*
* For both replacement methods, the arrays passed in may be
* either numeric or associative.  In both cases, the natural
* binding to tag name is made, through the index whether that
* be a number or key, e.g.:
*
*	[0]
*
*   or
*
*	[key]
*
* If a qualified end is used, then the variable replacement must be qualified by the
* block id.  This is necessary to make nested replacement blocks work.
*
* @author Daniel Chaves <hide@address.com>;
* @author Dick Munroe <hide@address.com>
* @copyright copyright (c) Dick Munroe, 2004-2007, All rights reserved.
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version 1.3
* @access public
* @package dm.IS Layout
*
* All code added or modified by Dick Munroe is being released under
* the GPL.  This in no way affects or changes any prior license or
* copyright.
*
* I (Dick Munroe) attempted to contact the original author to get him to include
* my changes but got no response, which is why I started this fork.
*/

//
// Edit History:
//
//  Dick Munroe hide@address.com 03-Dec-2004
//		Add documentation of usage.
//
//  Dick Munroe hide@address.com 14-Dec-2004
//		Add member to return all tags in a file.
//		Code for identifying code sections didn't
//		match the example files.
//
//	Dick Munroe (hide@address.com) 03-Dec-2007
//		Add capabilities for nested replacement blocks.


class IS_Layout
{
    /**
    * Contains the html´s souce code
    * @var  string
    * @access   private
    */
    VAR $_code;

    /**
    * Informs if the lines are set
    * @var bool
    * @access private
    */
    VAR $_linesSet=False;

    /**
    * IS_Layout Constructor
    * @param string $arg path to html file or the html lines
    * @access public
    * @return void
    */

    function IS_Layout($arg=NULL)
    {
        if ($arg !== NULL)
        {
            if(@is_file($arg))
            {
                return $this->openFile($arg);
            }
            else if(is_string($arg))
            {
                $this->setCode($arg);
            }
        }
    }

    /**
    * Opens the HTML file
    * @param string $file Path to HTML file
    * @return mixed boolean true, if successful or error message (see const SHOW_ERRORS)
    * @access public
    */
    function openFile($file)
    {
        $res=@fopen($file,"r");

        if(!is_resource($res)){
            return $this->_error('Fail to open file "'.$file.'"');
        }

        $code='';
        while(!feof($res))
            $code.=fgets($res,1024);

        fclose($res);
        $this->setCode($code);
        return True;
    }

    /**
    * Sets the HTML lines
    * @param string $str Lines of current HTML code
    * @access public
    */
    function setCode($str)
    {
        $this->_code=$str;
        $this->_linesSet=True;
    }

    /**
    * Parse errors
    * @param string $error Error Message
    * @return string error message or displayed message
    * @access private
    * @see define SHOW_ERROR
    */
    function _error($error)
    {
        $this->setCode(ERROR_CODE);
        $this->replace('error',$error);

        if(SHOW_ERRORS)
            return $this->display();
        else
            return $this->toHtml();
    }

    /**
    * Replace variables on HTML code
    *
    * @param string $sId Identification of element
    * @param mixed $items [constant] String or Array of values
    * @access public
    */

    function replaceConstant($sid, $items)
      {
	$this->replace($sid, $items) ;
      }

    /**
    * Replace variables on HTML code
    *
    * @param string $sId Identification of element
    * @param mixed $items [reference] String or Array of values
    * @access public
    */

    function replace($sId, &$items)
    {
      if(is_array($items))
	{
	  foreach($items as $k=>$v)
	    {
	      $this->replace("$sId,$k", $v) ;
            }
        }else{
            $this->_doReplace($sId,$items);
        }
    }

    /**
     * Provide loop replacement for things whose values are
     * constants.
     *
     * @see IS_Layout::loop_replace()
     */

    function loop_replaceConstant($Id, $Values)
      {
	$this->loop_replace($Id, $Values) ;
      }

    /**
    * Do a loop in part of HTML code replacing variables
    *
    * The $Values array is usually an array of arrays.  In this case,
    * the replacement tags are the array indices, e.g.,
    *
    *	array(array('one', 'two'))
    *
    * would replace tags [0] and [1] in the loop replacement block.
    * The array could also be an associative array, e.g.,
    *
    *	array(array('a' => 'one', 'b' => 'two'))
    *
    * in which case the tags would be [a] and [b].  As a convenience
    * arrays of scalars are allowed and are treated as if they were
    * an array of arrays with only one item in the second dimension.
    * For these arrays, the replaced tag is [0], always.
    *
    * This changes slightly if you nest replacement blocks.  Inside a
    * nested block, you need to qualify the replacement value as
    * follows:
    *
    * 	<!-- replace id="v" -->
    * 		[v.0]
    * 	<!-- end replace id="v" -->
    *
    * [v.0] is the equivalent of an unqualified loop as [0].
    *
    * @param string $Id Identification of element
    * @param array $Values [reference] Array of values
    * @access public
    */

    function loop_replace($Id, &$Values)
    {
        $arReturn=$this->_getElement('replace',"id=\"$Id\"");
        $sReplaceLines=$arReturn[0];

        $sReturn = "" ;

        foreach($Values as $aValue)
		{
            $sTempValue=$sReplaceLines;
            if (is_array($aValue))
			{
                foreach($aValue as $key=>$value)
                {
					/*
					 * If the block was terminated by a named end, then the
					 * replacement is of the form:
					 *
					 * 	[$id.key]
					 *
					 * instead of:
					 *
					 * 	[key]
					 */

					if ($arReturn[2])
					{
						$this->__replace('|' . quotemeta("[$Id.$key]") . '|s', $value, $sTempValue);
					}
					else
					{
						$this->__replace('|' . quotemeta("[$key]") . '|s', $value, $sTempValue);
					}
                }
            }
            else
            {
				if ($arReturn[2])
				{
					$this->__replace('|' . quotemeta("[$Id.0]") . '|s', $aValue, $sTempValue);
				}
				else
				{
					$this->__replace('|' . quotemeta("[0]") . '|s', $aValue, $sTempValue);
				}
            }

            $sReturn.=$sTempValue;
        }

        $this->_Element_replace('replace',"id=\"$Id\"",$sReturn);
    }

    /**
    * Get an Element of HTML Code
    *
    * Elements are defined as everything that appears between:
    *
    * 	<!-- $Name[:] $args -->
    *
    * and
    *
    * 	<!-- end $Name[:][ $args] -->
    *
    * If the end tag isn't named, then the nearest end un-named end tag is used.
    *
    * @param string $Name Identification of element
    * @param mixed $args String or Array of values passed in identification
    * @access private
    */
    function _getElement($Name, $args=NULL)
    {
        if(is_array($args))
		{
            $comp="\s{1,}";
            foreach($args as $key=>$value)
			{
                $comp.="$key=\"$value\"\s{0,}";
			}
        }
		elseif ($args != NULL)
		{
            $comp="\s{0,}".$args;
            $comp=preg_replace("[\s{1,}]","\s{1,}",$comp);
        }

        $return = array(NULL, NULL, TRUE);

		/*
		 * Construct a match for a tagged end block, e.g.:
		 *
		 * 	<!-- replace id="foo" -->
		 * 		...
		 * 	<!-- end replace id="foo" -->
		 *
		 * If no tagged end is found, then the nearest untagged end is used.
		 */

		$preg =
			sprintf("|<!--\s+%s:{0,1}\s+%s(.*?)-->\n*(.*?)<!--\s+end\s+%s:{0,1}\s+%s\s+-->|s",
				$Name,
				$comp,
				$Name,
				$comp) ;

		if (preg_match($preg, $this->_code, $ret) == 0)
		{
			$preg =
				sprintf("|<!--\s+%s:{0,1}\s+%s(.*?)-->\n*(.*?)<!--\s+end\s+%s\s+-->|s",
					$Name,
					$comp,
					$Name) ;

			preg_match($preg, $this->_code, $ret) ;

			$return[2] = FALSE ;
		}

        $return[0]=$ret[2];
        $ret[1] = trim($ret[1]) ;

        if(!empty($ret[1])){
            $args=explode(" ",$ret[1]);
            foreach($args as $v){
                $v=trim($v);
                $v=ereg_replace(quotemeta('"'),'',$v);
                list($a,$b)=explode("=",$v);
                if(!empty($a))
                    $return[1][$a]=$b;
            }
        }

        return $return;
    }

    /**
    * Replace an Element of HTML Code
    * @param string $Name Identification of element
    * @param mixed $args String or Array of values passed in identification
    * @param string $el HTML code for substitute the code between the element
    * @access private
    */
    function _Element_replace($Name, $args=NULL, $el=NULL)
    {
        if(is_array($args))
		{
            $comp="\s{1,}";
            foreach($args as $key=>$value)
			{
                $comp.="$key=\"$value\"\s{0,}";
			}
        }
		elseif ($args != NULL)
		{
            $comp="\s{0,}".$args;
            $comp=preg_replace("[\s{1,}]","\s{1,}",$comp);
        }

		/*
		 * Allow tagged ends of blocks.
	 */

        $preg=sprintf("|<!--\s+%s:{0,1}\s+%s(.*?)-->(.*?)<!--\s+end\s+%s:{0,1}\s+%s\s+-->|s",
		      $Name,
		      $comp,
		      $Name,
			  $comp) ;

		if (preg_match($preg, $this->_code) == 0)
		{
			/*
			 * If this block doesn't have a tagged end, switch back to the general end
			 */

			$preg=sprintf("|<!--\s+%s:{0,1}\s+%s(.*?)-->(.*?)<!--\s+end\s+%s\s+-->|s",
				  $Name,
				  $comp,
				  $Name) ;
		}

        $this->__replace($preg, $el, $this->_code);
    }

    /**
    * Get part of main HTML code
    * @param string $Id Identification of element
    * @return object Object contains the main code
    * @access public
    */
    function getCode($Id)
    {
        $code=$this->_getElement('code', "id=\"$Id\"");
        return new IS_Layout($code[0]);
    }

    /**
     * Provide a call point for replacement of HTML code by a
     * constant.
     * @see IS_Layout::code_replace()
     */

    function code_replaceConstant($Id, $Code)
      {
	$this->code_replace($Id, $Code) ;
      }

    /**
    * Remove part of main HTML code
    * @param string $Id Identification of element
    * @param string $Code HTML code to replace
    * @access public
    */

    function code_replace($Id,&$Code)
    {
        $this->_Element_replace('code', "id=\"$Id\"",$Code);
    }

    /**
    * Remove part of main HTML code
    * @param string $Id Identification of element
    * @access public
    */
    function remove($Id)
    {
        $this->_Element_replace('remove', "id=\"$Id\"");
    }

    /**
    * Include code to main HTML
    * @param object IS_Layout $Inc Object of layout
    * @param string [optional] Tag name to be replaced, default is 'include'.
    * @access public
    */

    function inc(&$Inc, $theTagName='include')
    {
        $this->replace($theTagName, $Inc->toHtml());
    }

    /**
     * Do a substring replacement of a regular expression with a constant value.
     * @see IS_Layout::__replace()
     */

    function &__replaceConstant($theRegex, $theValue, &$theString)
	{
		return $this->__replace($theRegex, $theValue, $theString) ;
	}

    /**
     * Do a substring replacement of a regular expression.
     *
     * @param string $theRegex the regular expression to be searched for.
     * @param string $theValue the value to be inserted.
     * @param string $theString [reference] the string to be procesed.
     * @return string $theString [reference] the string with the regular
     *                expression replaced.
     * @access private
     */

    function &__replace($theRegex, &$theValue, &$theString)
	{
		if (version_compare('4.3.0', PHP_VERSION, 'le'))
		{
			/*
             * There was a bug using preg_replace directly, especially
             * with big strings and replacements.  That drove the decision
             * to rewrite it this way.  Unfortunately PREG_OFFSET_CAPTURE
             * isn't available for versions of php prior to 4.3.0 so to
             * [most of the time] avoid the bug I hit, the replacement will
             * be done both ways.
             */

			preg_match_all($theRegex, $theString, $theMatches, PREG_OFFSET_CAPTURE) ;

			$theMatches = array_reverse($theMatches[0]) ;

			foreach ($theMatches as $theMatch)
			{
				$theString =
					substr($theString, 0, $theMatch[1])
					. (string)$theValue
					. substr($theString, $theMatch[1] + strlen($theMatch[0])) ;
			}
		}
		else
		{
			//
			// This is an enormously subtle bug possibility.  For performance
			// reasons, the rest of this code assumes that the replace operation
			// is performed on the variable passed in, thus the pass by
			// reference.  ALL replacements MUST be assigned by value to the
			// $theString formal parameter of this routine or things will screw
			// up big time.
			//

			$theString = preg_replace($theRegex, (string)$theValue, $theString) ;
		}

        return $theString ;
	}

    /**
    * Replace a variable in HTML code
    * @param string $Id Identification of element
    * @param string $Item code
    * @access private
    */
    function _doReplace($Id, &$Item)
      {
	$this->__replace('|' . quotemeta("[$Id]") . '|s', $Item, $this->_code) ;
      }

    /**
    * Replace non used elements
    * @access private
    */

    function _replace_none()
    {
        $this->__replaceConstant("|\\[([^\\[]*),([^\\[]*)\\]|",'',$this->_code);
        $this->__replaceConstant("|\\[([^\\[]*),([^\\[]*),([^\\[]*)\\]|",'',$this->_code);
        $this->__replaceConstant("|<!--\s+code(.*?)-->|s",'',$this->_code);
        $this->__replaceConstant("|<!--\s+end\s+code\s+-->|s",'',$this->_code);
        $this->_Element_replace('replace');
    }

    /**
     * Return an array containing the tags and the type of tags
     * in the current page.
     *
     * @access public
     * @return array $theTags An associative array, keyed by tag, with additional
     *               typing information.
     */

    function &getTags()
      {
	$theCode = $this->_code ;

	$theTags = array() ;

	//
	// Strip out the loop replacement blocks, the ID is the tag value.
	//

	preg_match_all('|<!--\s+replace:{0,1}\s+id="(.*?)".*?-->(.*?)<!--\s+end\s+replace\s+-->|s',
		       $theCode, $theMatchArray) ;

	//
	// Organize and remove the loop replacement blocks
	// from the code body.
	//

	foreach ($theMatchArray[1] as $theIndex=>$theVariable)
	  {
	    $theTags[$theVariable]['type'] = 'loop' ;
	    $theTags[$theVariable]['body'] = $theMatchArray[2][$theIndex] ;
	    $theCode = $this->__replaceConstant("|" . quotemeta($theMatchArray[0][$theIndex]) . "|s",
						'', $theCode) ;

	    preg_match_all('|\[([^,\]]+?)\]|s',
			   $theMatchArray[0][$theIndex],
			   $theMatchArray2) ;
	    if (empty($theMatchArray[0]))
	      {
		//
		// The loop replacement block didn't have any fields defined
		// in it, get rid of it.
		//
		unset($theTags[$theVariable]) ;
	      }
	    else
	      {
		$theTags[$theVariable]['index'] = $theMatchArray2[1] ;
	      }
	  }

	//
	// Strip out the code replacement blocks, the ID is the tag value.
	//

	preg_match_all('|<!--\s+code:{0,1}\s+id="(.*?)".*?-->(.*?)<!--\s+end\s+code\s+-->|s',
		       $theCode, $theMatchArray) ;

	//
	// Organize and remove the code replacement blocks
	// from the code body.
	//

	foreach ($theMatchArray[1] as $theIndex=>$theVariable)
	  {
	    $theTags[$theVariable]['type'] = 'code' ;
	    $theTags[$theVariable]['body'] = $theMatchArray[2][$theIndex] ;
	    $theCode = $this->__replaceConstant("|" . quotemeta($theMatchArray[0][$theIndex]) . "|s",
						'', $theCode) ;
	  }

	//
	// Extract all references of the form:
	//
	//	[variable]
	//	[variable,index]
	//	[variable,index1,index2,...]
	//

	preg_match_all('|\[([^,\]]+?(:?,[^,\]]+?){0,})\]|s', $theCode, $theMatchArray) ;

	foreach ($theMatchArray[1] as $theReference)
	  {
	    $xxx = explode(',', $theReference) ;
	    $theTags[$xxx[0]]['type'] = (count($xxx) == 1 ? 'scalar' : 'array') ;
	    if (count($xxx) > 1)
	      {
		$theTags[$xxx[0]]['index'] = array_slice($xxx, 1) ;
	      }
	  }

	return $theTags ;
      }

  /**
    * Return HTML code
    * @return string $Code Code HTML
    * @access public
    */

    function toHtmlConstant()
      {
        $this->_replace_none();
        return $this->_code;
      }

    function &toHtml()
    {
        $this->_replace_none();
        return $this->_code;
    }

    function toRawHtmlConstant()
      {
	return $this->_code ;
      }

    /**
     * Return HTML code, including the leftover tags.
     *
     * @access public
     * @return string $code Code HTML and leftover tags.
     */

    function &toRawHtml()
      {
	return $this->_code ;
      }

    /**
    * Print HTML code
    * @access public
    */
    function display()
    {
        if(!$this->_linesSet)
            return False;

        echo $this->toHtml();
    }
}

?>
Return current item: dm.IS Layout