Page 1 of 1

preg_replace not finding tags

Posted: Fri May 04, 2007 11:39 am
by mattcooper
Hi all,

I'm using the following methods to look for instances of tags in a template. When I use them outside of object context, everything is fine.

Code: Select all

private function find_objects() {
		
			
			$templateData = file_get_contents($this->templateLocation);
			
			if(!empty($templateData)) {
				$templateData = '<% hello %>';
				/* @- find <% tags %> and load them into an array */
				$objectArr = array();
				preg_replace("/<\%[[]](.*)[[]]\%>/isUe", "$this->get_object_name('\\1')", $templateData);

			}
			
			else echo 'Error: template not found';		
		
		}	
	
		private function get_object_name($name) {
				
			global $objectArr;
			$objectArr[] = $name; print_r($objectArr);
			return true;
		
		}
		
		private function examineTemplate() {
			
			/* @- loop through the template data and find the tags */
			$this->find_objects();
			print_r($objectArr);
		}
However, when placed in the class, they do not work - ie, <% tags %> are not found. Here's the full class as it is at the moment:

Code: Select all

class templateParser {
		
		/* @- CLASS NOTES
		   This class is for parsing tags with the following structures:
		   <% simpletagname %> and <% tagname:function(optional-args) %>
		   We use output from a db table to populate and replace the simple tags,
		   and do the same for the complex tag, but apply any special treatment
		   requested in "function" before outputting it. This can currently be
		   any native PHP string function defined in $allowedFunctions plus date().
		   
		   Its purpose is to keep PHP out of templates but still allow some
		   basic formatting/parsing of text.
	
		/* @- the location of the template file to parse */
		var $templateLocation = null;
		
		/* @- OUTPUT MODE
		/* @- this will be either "single" or "loop".
		/* @- "single" produces one instance of a template
		/* @- "loop" produces multiple instances */
		var $outputMode = null;
		
		/* @- DEFINE ALLOWED FUNCTIONS
		/* @- This array stores the names of all of the functions
		/* @- that can be applied to the output of the tag */
		private $allowedFunctions = array('ucfirst','ucwords','strtolower','strtoupper','nl2br','str_replace','strlen','substr','date');		

		private function find_objects() {
		
			
			$templateData = file_get_contents($this->templateLocation);
			
			if(!empty($templateData)) {
				$templateData = '<% hello %>';
				/* @- find <% tags %> and load them into an array */
				$objectArr = array();
				preg_replace("/<\%[[]](.*)[[]]\%>/isUe", "$this->get_object_name('\\1')", $templateData);

			}
			
			else echo 'Error: template not found';		
		
		}	
	
		private function get_object_name($name) {
				
			global $objectArr;
			$objectArr[] = $name; print_r($objectArr);
			return true;
		
		}
		
		private function examineTemplate() {
			
			/* @- loop through the template data and find the tags */
			$this->find_objects();
			#print_r($objectArr);
		}
									  
		private function examineTag($tagData) {
			
			/* @- break up a tag and assess its structure
			/* @- return tag string that has had a function applied to it, or
			/* @- just the string if it is a simple tag */
			
			
			# temporary:
			# first, remove the tag wrappers <% %>
			$tagStr = ereg_replace('<% ','',$tagData);
			$tagStr = ereg_replace(' %>','',$tagStr);
			
			# we should now have raw data from inside the tags
			# and next we need to separate tagname and attributes
			# from any special functionality, if specified
			
			# look for an instance of the : (colon) symbol and break the
			# string on it if it is present
			if(ereg(':',$tagStr)) {
				
				$tagArray = explode(':',$tagStr);
				# we now have an array with $tagArray[0] holding name
				# and $tagArray[1] holding the special function and args				
				
				# now let's see what the function is, and check that it's in the functions array
				if($tagArray['1']) {
				
					$function = $tagArray['1'];
					# are there any args?
					# the quickest way to find out (but not the most elegant)
					# is to look for and break the string up on "(" and then ditch the 
					# closing paranthesis with substr()
					if(ereg(',',$function)) {
						$tempArgs = explode('(',$function);
						$function = $tempArgs['0'];
						$args = substr($tempArgs['1'],0,-1);
					}
					
					# now we can apply the function to the tag it came with
					# but first need to check that the function is allowed
					if(!in_array($function, $this->allowedFunctions)) {
						# return the text in the tag but also
						# warn the developer by adding a flag
						return $tagArray['0'] .'[E]';
					}
					# now we have our function and args (if any) as separate vars,
					# check for the existence of args
					if(!empty($args)) {

						# look at the args... bust them up on the commas
						$argsArray = explode(',',$args);
						/* @- The two functions that will have multiple args are str_replace and substr.
						/* @- The position of $tagStr is different in each, so let's tell the function what to do */
						if($function == 'substr') {
						
							# this one has the string on the first arg position
							$finalOP = substr($tagArray['0'],$argsArray['0'],$argsArray['1']);
						}
						if($function == 'str_replace') {
						
							# this one has the string in the final arg position
							$finalOP = str_replace($argsArray['0'],$argsArray['1'],$tagArray['0']);
						}
						if($function == 'date') { 
							
							/* @- various options for date exist, parse them according to specification
							/* @- IF WE WANT OT PARSE THE DATE INTO A READABLE FORMAT:::
							/* @- use the tag structure <% date_row:date(formatpattern,true) %>
							/* @- otherwise you can return the raw integer for processing by using
							/* @- the tag format <% date_row:date(format,false) */
							switch($argsArray['1']) {
								case 'format' : # parse the date into readable format
								switch($argsArray['0']) {
									case 'fulldatetime' : # return F j, Y, g:i a
									$finalOP = date("F j, Y, g:i a", $tagArray['0']);
									break;
									case 'day' : # return l
									$finalOP = date("l", $tagArray['0']);
									break;
									case 'basic' : # return F j, Y
									$finalOP = date("F j, Y", $tagArray['0']);
									break;
									case 'date' : # return current date F j, Y
									$finalOP = date("F j, Y");
									break;
								}
								break;
								case 'raw' : # return the raw data
								$finalOP = $tagArray['0'];
								break;
							}
						}
					}
					else {
						
						# if $args is empty, we're using a function that just requires the string to be passed to it
						$finalOP = $function($tagArray['0']);
					}
				}
				return $finalOP;
			}

			# if we've found that this is a simple tag, just return the data that was passed to the function
			else return $tagStr;
		}
		
		public function outputData() {
			
			global $objectArr;
			$this->examineTemplate();
			print_r($objectArr);
			#echo $this->examineTag($this->examineTemplate());
		}
	} # end object
	
	$op = new templateParser;
	$op->templateLocation = $_SERVER['DOCUMENT_ROOT'] .'podcast/templates/user/userProfile.php';	
	$op->outputData();
What is wrong with this? Any help would be very much appreciated!

Posted: Fri May 04, 2007 1:47 pm
by RobertGonzalez
So are you just getting the error, or not getting anything?

Posted: Fri May 04, 2007 5:46 pm
by mattcooper
Sorry, I should know better by now! I'm not getting anything at all: not even using the string I've put in the variable to bypass the template file (<% hello %>).

very baffling...

Posted: Fri May 04, 2007 6:04 pm
by RobertGonzalez
I think you can use a callback function so you can pass that data to your method. preg_replace_callback().

Posted: Fri May 04, 2007 6:19 pm
by RobertGonzalez
PS Here is a little cleaner version of your code:

Code: Select all

<?php
class templateParser {
    /* @- CLASS NOTES
       This class is for parsing tags with the following structures:
       <% simpletagname %> and <% tagname:function(optional-args) %>
       We use output from a db table to populate and replace the simple tags,
       and do the same for the complex tag, but apply any special treatment
       requested in "function" before outputting it. This can currently be
       any native PHP string function defined in $allowedFunctions plus date().
      
       Its purpose is to keep PHP out of templates but still allow some
       basic formatting/parsing of text.
    
    /* @- the location of the template file to parse */
    var $templateLocation = null;
    
    /* @- OUTPUT MODE
    /* @- this will be either "single" or "loop".
    /* @- "single" produces one instance of a template
    /* @- "loop" produces multiple instances */
    var $outputMode = null;
    
    /* @- DEFINE ALLOWED FUNCTIONS
    /* @- This array stores the names of all of the functions
    /* @- that can be applied to the output of the tag */
    private $allowedFunctions = array('ucfirst','ucwords','strtolower','strtoupper','nl2br','str_replace','strlen','substr','date');               
    
    private function find_objects() {
        $templateData = file_get_contents($this->templateLocation);
               
        if(!empty($templateData)) {
            $templateData = '<% hello %>';
            /* @- find <% tags %> and load them into an array */
            $objectArr = array();
            preg_replace("/<\%[[]](.*)[[]]\%>/isUe", "$this->get_object_name('\\1')", $templateData);
        } else echo 'Error: template not found';   
    }       

    private function get_object_name($name) {
        global $objectArr;
        $objectArr[] = $name; print_r($objectArr);
        return true;
    }
       
    private function examineTemplate() {
        /* @- loop through the template data and find the tags */
        $this->find_objects();
        #print_r($objectArr);
    }
                                                                  
    private function examineTag($tagData) {
        /* @- break up a tag and assess its structure
        /* @- return tag string that has had a function applied to it, or
        /* @- just the string if it is a simple tag */
        # temporary:
        # first, remove the tag wrappers <% %>
        $tagStr = ereg_replace('<% ','',$tagData);
        $tagStr = ereg_replace(' %>','',$tagStr);
               
        # we should now have raw data from inside the tags
        # and next we need to separate tagname and attributes
        # from any special functionality, if specified
        
        # look for an instance of the : (colon) symbol and break the
        # string on it if it is present
        if(ereg(':',$tagStr)) {
            $tagArray = explode(':',$tagStr);
            # we now have an array with $tagArray[0] holding name
            # and $tagArray[1] holding the special function and args                               
            # now let's see what the function is, and check that it's in the functions array
            if($tagArray['1']) {
                $function = $tagArray['1'];
                # are there any args?
                # the quickest way to find out (but not the most elegant)
                # is to look for and break the string up on "(" and then ditch the
                # closing paranthesis with substr()
                if(ereg(',',$function)) {
                    $tempArgs = explode('(',$function);
                    $function = $tempArgs['0'];
                    $args = substr($tempArgs['1'],0,-1);
                }
                               
                # now we can apply the function to the tag it came with
                # but first need to check that the function is allowed
                if(!in_array($function, $this->allowedFunctions)) {
                    # return the text in the tag but also
                    # warn the developer by adding a flag
                    return $tagArray['0'] .'[E]';
                }
                # now we have our function and args (if any) as separate vars,
                # check for the existence of args
                if(!empty($args)) {
                    # look at the args... bust them up on the commas
                    $argsArray = explode(',',$args);
                    /* @- The two functions that will have multiple args are str_replace and substr.
                    /* @- The position of $tagStr is different in each, so let's tell the function what to do */
                    if($function == 'substr') {
                        # this one has the string on the first arg position
                        $finalOP = substr($tagArray['0'],$argsArray['0'],$argsArray['1']);
                    }
                    if($function == 'str_replace') {
                        # this one has the string in the final arg position
                        $finalOP = str_replace($argsArray['0'],$argsArray['1'],$tagArray['0']);
                    }
                    if($function == 'date') {
                        /* @- various options for date exist, parse them according to specification
                        /* @- IF WE WANT OT PARSE THE DATE INTO A READABLE FORMAT:::
                        /* @- use the tag structure <% date_row:date(formatpattern,true) %>
                        /* @- otherwise you can return the raw integer for processing by using
                        /* @- the tag format <% date_row:date(format,false) */
                        switch($argsArray['1']) {
                            case 'format' : # parse the date into readable format
                                switch($argsArray['0']) {
                                    case 'fulldatetime' : # return F j, Y, g:i a
                                        $finalOP = date("F j, Y, g:i a", $tagArray['0']);
                                        break;
                                    
                                    case 'day' : # return l
                                        $finalOP = date("l", $tagArray['0']);
                                        break;
                                    
                                    case 'basic' : # return F j, Y
                                        $finalOP = date("F j, Y", $tagArray['0']);
                                        break;
                                        
                                    case 'date' : # return current date F j, Y
                                        $finalOP = date("F j, Y");
                                        break;
                                }
                                break;
                                
                            case 'raw' : # return the raw data
                                $finalOP = $tagArray['0'];
                                break;
                        }
                    }
                } else {
                    # if $args is empty, we're using a function that just requires the string to be passed to it
                    $finalOP = $function($tagArray['0']);
                }
            }
            return $finalOP;
        }

        # if we've found that this is a simple tag, just return the data that was passed to the function
        else return $tagStr;
    }
       
    public function outputData() {
        global $objectArr;
        $this->examineTemplate();
        print_r($objectArr);
        #echo $this->examineTag($this->examineTemplate());
    }
} # end object

$op = new templateParser;
$op->templateLocation = $_SERVER['DOCUMENT_ROOT'] .'podcast/templates/user/userProfile.php';   
$op->outputData();
?>

Posted: Fri May 04, 2007 6:39 pm
by mattcooper
Thank you for cleaning up, it was a bit of a pain to read! I'm using this principle in a PHP4 application that is running nicely: here's a snippet from one of the classes:

Code: Select all

class clientData {
	
		function templateParse($templateLocation,$dataSource,$dataField) {
			
			# vars - $templateLocation : where the template to be parsed lives
			# $dataSource : 'media','user' - used for the switch
			# $dataField : the key we're using for the query in whatever class method is called
			
			# deals with SINGLE ITEM OUTPUT ONLY at the moment [TODO] - erm, do we want multiple template parsing?
			
			global $object_arr,$configs;
			
			if(!function_exists('find_objects')) {
				function find_objects($data) {
				
				# hunt for navigation objects
				$object_arr = array();
					# for <% ASP-style %> tags
					preg_replace("/<\%[[]](.*)[[]]\%>/isUe", "get_object_name('\\1')", $data);
							
				}
			}
			
			if(!function_exists('get_object_name')) {
				function get_object_name($name) {
						
					# companion fuction to "find_objects()" 
					global $object_arr;
					$object_arr[] = $name;
					return true;
				
				}
			}
			# open a template and replace tags with live content
			# get the template
			$bufferData = file_get_contents($templateLocation);
			# look for instances of <% tags %>
			$arr = find_objects($bufferData);
			
			switch($dataSource) {
				
				case 'user' :
				# instantiate the person class to extract data
				$p = new person();
				$p->username = $dataField; # try usinng the session to debug this if there are errors	
				$data = $p->person(); # we should now have an array of user vars
				break;
				
				case 'media' :
				# instantiate the media class to extract data
				$m = new media();
				$m->mediaId = $dataField;
				$data = $m->getMediaData();
				break;
				
			}
			
			# replace tag contents with $p data
			foreach($object_arr as $tagVal) {

				$bufferData = str_replace("<% ". $tagVal ." %>",stripslashes($data[$tagVal]),$bufferData);
				
			}
			
			return eval('?>'. $bufferData .'<?');
		}
}
Apologies for the code format again, my editor chucks it out like this! Anyway, it does the job correctly - however, I'm using functions within the method in this case. How does this differ from separating the methods out as in the new code example? I'm still rather new to OOP, having been knocking out PHP4 apps for ages, and am really trying hard to convert up to v5 but facing such difficulties on rather a regular basis.

Not too sure that I need to use the callback function, bearing in mind what I've said... what do you reckon?

Cheers in advance!

Posted: Fri May 04, 2007 11:23 pm
by RobertGonzalez
Usually in a case like that instead of passing a function name you pass an array containing $this and the method name. Though that is typical for callback function in which instance the method receives an array of matches to work with.

Posted: Sat May 05, 2007 4:25 am
by mattcooper
I understand. However, the point I was trying to make was that the regex doesn't appear to be finding the instances of the tags at all: so, if I try to echo $name from the get_object_name() callback, there's nothing there while in object context, but it does work when used outsde of a class environment.

I'm going to experiment with doing it another way, keep you posted ;)

Thanks for your input!