Goedenavond allen,

Op 2 december j.l. heb ik alhier een template parser class geplaatst. Hierop heb ik van Jelmer feedback gekregen, welke ik wil verwerken, maar kom er niet uit.

Hergebruik instellingen
Ik wil graag ondervangen dat instellingen nogmaals gebruikt kunnen worden bij andere instellingen i.v.m. veiligheidsproblemen.

Voorbeeld:

<?php
$data = array(
   array(
      "username" => "Jan {password}",
      "password" => "henk24"
   )
);

// Uitkomst:
// Jan henk24
?> 


Hoe kan ik dit ondervangen??

Onbeperkt diepe array's
De instellingen (settings) en loops zijn respectievelijk maximaal 3 en 2 niveaus diep. Door middel van o.a. preg_replace_callback zou dit te ondervangen zijn. Hierdoor moet het dan ook mogelijk worden om loop in loop te maken.

Voorbeeld:

<?php //kleurtjes
<body>
   <div class="menu">

      <!-- loop menu start -->
         <div class="menu">

            <!-- loop item start -->
               <div class="item"></div>
            <!-- loop item start -->

         </div>
      <!-- loop menu end -->

   </div>
</body>
?>


Maar hoe?
[php]file[/php] ondersteunt ook fopen-wrappers. En die gebruik je niet wanneer je alleen maar lokaal een bestand wil inlezen (je gebruikt alleen wrappers wanneer je een protocol meegeeft zoals file://, http://, gzip://, phar://, etc)
Jelmer, je hebt helemaal gelijk! Vaag, voorheen kreeg ik altijd een error als ik file_get_contents gebruikte, i.p.v. implode("", file()).
Aansluitend op bovenstaande, wilde ik ervoor zorgen dat er DIV's genest kunnen worden. Nu wil 'ie geen data uit de array in de DIV zetten.

sTemplate.php
<?php
error_reporting(E_ALL);

class TemplateStack
{
   private $data = array();
    
   public function push(&$data)
   {
      return $this->data[] =& $data;
   }

   public function &pop()
   {
      $reference =& $this->top();

      // TemplateStack::top() verplaatst de cursor al naar het einde, dus key() zal de juiste key pakken.
      unset($this->data[key($this->data)]);

      return $reference;
   }

   public function &top()
   {
      // Cursor naar einde bewegen, zodat key() de index van het laatste element pakt.
      end($this->data);

      return $this->data[key($this->data)];
   }
}

class Template
{
   private $template;
   private $data_stack;

   public $dir = './';
   public $ext = '.tpl';
   public $atr = 'class';

   public function __construct($template, $file = true)
   {
      if ($file == true)
      {
         $path = $this->dir . $template . $this->ext;
         $this->template = file_get_contents($path);
      }
      else
      {
         $this->template = $template;
      }

      $this->data_stack = new TemplateStack();
   }
    
   public function apply($data)
   {
      return $this->parse($this->template, $data);
   }

   private function parse($template, $data)
   {
      // Push data op de stack, zodat de replace-functies weten in welke data ze moeten zoeken voor het vervangen van variabelen.
      $this->data_stack->push($data);

      // Parse alle blokken, recursief. Ieder blok komt weer door deze zelfde method heen.
      // Ieder geparsed block wordt onthouden aan een unieke placeholder. Deze wordt later weer teruggeplaatst
      // om er zo voor te zorgen dat bij het variabelen vervangen niet binnen dit al verwerkte blok wordt gezocht.
      $template = preg_replace_callback('{\<\!\-\-(\s?)loop (\w+?) start(\s?)\-\-\>(.+?)\<\!\-\-(\s?)loop (\\2) end(\s?)\-\-\>}', array($this, 'parse_block'), $template);

      // Parse de HTML elementen
      $template = preg_replace_callback('{\<([a-zA-Z0-9\"\'\.=\s]+)(.*?)'. $this->atr .'=(\'|\")(\w+)(\\3)(.*)>(.*)\</\\1>}', array($this, 'parse_nameholder'),  $template);

      // Werk van binnen naar buiten. Handel eerst de blokken af (hierboven) en dan pas de variabelen
      // De blokken zijn in $result vervangen door variabelen, en in $data zitten nu de geparste blokken als normale variabelen.
      $result = preg_replace_callback('{\{(\w+)\}}', array($this, 'parse_placeholder'), $template);

      // Haal de verwijzing naar de data weer weg, zodat we in het omliggende blok weer bij de goeie data komen.
      $this->data_stack->pop();

      return $result;
   }
    
   private function parse_block($matches)
   {
      list(,, $block_name,, $block_code) = $matches;

      $data =& $this->data_stack->top();
      $block_data = $data[$block_name];
      $results = array();

      foreach($block_data as $step_data)
      {
         $results[] = $this->parse($block_code, $step_data);
      }

      // Variabele-naam waar we nu het blok in stoppen.
      $block_placeholder = sprintf('block%s', uniqid());

      // Variabele-naam en de inhoud van dit blok terugstoppen in de data-array
      // Dit gaat lukken omdat $data een reference is naar de top van de stack.
      $data[$block_placeholder] = implode($results);

      return sprintf('{%s}', $block_placeholder);
   }

   private function parse_nameholder($matches)
   {
      list(, $tag_name, $a, $separator, $nameholder_name,, $b, $nameholder_code) = $matches;

      $data =& $this->data_stack->top();
      $nameholder_data = $data[$nameholder_name];
      $results = array();

      foreach($nameholder_data as $step_data)
      {
         $results[] = $this->parse($nameholder_code, $step_data);
      }

      // Variabele-naam waar we nu het blok in stoppen.
      $name_placeholder = sprintf('block%s', uniqid());

      // Variabele-naam en de inhoud van dit blok terugstoppen in de data-array
      // Dit gaat lukken omdat $data een reference is naar de top van de stack.
      $data[$name_placeholder] = implode($results);

      return '<'. $tag_name . $a . $this->atr .'='. $separator . $nameholder_name . $separator . $b .'>'. sprintf('{%s}', $name_placeholder) .'</'. $tag_name .'>';
   }

   private function parse_placeholder($matches)
   {
      list(, $placeholder_name) = $matches;
        
      $data = $this->data_stack->top();

      return $data[$placeholder_name];
   }
}

$data = array(
    'boo' => array(
       'block3' => 'nummer 3',
    ),
    'block1' => array(
        0 => array(
            'var_y' => 'XXvar_y1XX',
            'block2' => array(
                0 => array('var_x' => 'XXvar_x11'),
                1 => array('var_x' => 'XXvar_x12'),
                2 => array('var_x' => 'XXvar_x13'),
            )
        ),
        1 => array(
            'var_y' => 'XXvar_y2XX',
            'block2' => array(
                0 => array('var_x' => 'XXvar_x21'),
                1 => array('var_x' => '{var_y}'), // Deze verwijst terug naar 'var-y' hierboven. Zou niet moeten!
                2 => array('var_x' => 'XXvar_x23'),
            )
        )
   )
);

$x = new Template("test");
echo $x->apply($data);
?>


test.tpl

Hoi

<div class="boo">
   <div class="block3">
   </div>
</div>

<!-- loop block1 start -->
   (begin-block1) {var_y}

   <!-- loop block2 start -->
      (begin-block2) {var_x} (eind-block2)
   <!-- loop block2 end -->

   test2 (eind-block1)
<!-- loop block1 end -->
Ter verduidelijking: de functie parse_nameholder() moet een HTML element voorzien van content. Dit moet hij doen aan de hand van de class name en overeenkomend element uit een array.

Nu is het zo dat er geen content in de HTML elementen wordt gedropt. Ik kan niet ontdekken wat de oorzaak hiervan is.

@RichardvV: Dank voor de regex wiki, inderdaad een aantal verbeterpunten! Wat bedoel je met:
RichardvV schreef op 01.02.2010 17:27

if(class_exists('SplStack'))
    return;

class SplStack {

Dit zou alsnog een foutmelding moeten geven omdat de klasse alsnog gedefinieerd (ze worden "naar boven getrokken"). Wrap de hele klasse in een if en je bent er vanaf.


@Wim: Goede tutorial, ga ik zeker naar kijken. Maar volgens mij is dit probleem ook wel op te lossen zonder gebruik te maken van Singleton class?


edit: Lay-out schiet wel erg snel uit z'n voegen x|
Voting for freedom is like fucking for virginity
Revolution is commin schreef op 01.02.2010 18:29
Voting for freedom is like fucking for virginity

Hmmm, juist, heel veel toegevoegde waarde geeft dit!
HendelBerg schreef op 01.02.2010 18:32
[quote='Revolution is commin schreef op 01.02.2010 18:29']Voting for freedom is like fucking for virginity

Hmmm, juist, heel veel toegevoegde waarde geeft dit![/quote]
De pilletjes zullen wel op zijn.
RichardvV schreef op 01.02.2010 17:27
[offtopic]
if(class_exists('SplStack'))
    return;

class SplStack {

Dit zou alsnog een foutmelding moeten geven omdat de klasse alsnog gedefinieerd (ze worden "naar boven getrokken"). Wrap de hele klasse in een if en je bent er vanaf.


Geloof het of niet, maar dit trucje werkt echt. Maar het verschilt eigenlijk ook niets van jouw if-eromheen. Ook voor dat if-statement geldt dat eerst de if geƫvalueerd moet worden alvorens de class beschikbaar is. Apart is dan weer dit 2x true geeft:
<?php
var_dump(class_exists('X'));
class X{}
var_dump(class_exists('X'));
?>

Reageren