<?php if (!defined('PmWiki')) exit();

## FPLTemplate2: an alternative FPLTemplate function
## version 2009-02-16

## Use template found in first of template pages:
SDV($FPLTemplatePageFmt, array('{$FullName}',
  '{$SiteGroup}.LocalTemplates', '{$SiteGroup}.PageListTemplates'));

## Set $FPLFormatOpt array defaults to use FPLTemplate2:
## $FPLFormatOpt is a list of options associated with fmt=
## values.  'default' is used for any undefined values of fmt=.
SDVA($FPLFormatOpt, array(
  'default' => array('fn' => 'FPLTemplate2', 'fmt' => '#default'),
  'bygroup' => array('fn' => 'FPLTemplate', 'template' => '#bygroup',
                     'class' => 'fplbygroup'),
  'simple'  => array('fn' => 'FPLTemplate2', 'template' => '#simple',
                     'class' => 'fplsimple'),
  'group'   => array('fn' => 'FPLTemplate2', 'template' => '#group',
                     'class' => 'fplgroup'),
  'title'   => array('fn' => 'FPLTemplate2', 'template' => '#title',
                     'class' => 'fpltitle', 'order' => 'title'),
  'count'   => array('fn' => 'FPLCountA'),
  ));

## Default chain of functions called by FPLTemplate2 (defined below)
SDV($FPLTemplateFunctions, array(
  'FPLTemplateLoad','FPLTemplateDefaults','FPLTemplatePageList','FPLTemplateSliceList','FPLTemplateFormat')); 

/* Comment:
The original FPLTemplate function has been broken into five logical parts,
each becoming a seperate function called in sequence as defined 
in array $FPLTemplateFunctions by main FPLTemplate2() function.
Each function called can return HTML formatted output, but only the last, 
FPLTemplateFormat(), does so.
Each function changes relevant arrays passed by reference:
pagelist $matches; pagelist options $opt; template parts $tparts. 
*/
## Main function: call each of $FPLTemplateFunctions in turn
function FPLTemplate2($pagename, &$matches, $opt) {
  global $FPLTemplateFunctions;
  #echo "FPLTemplate2: Entering<br>\n";
  StopWatch("FPLTemplate: Chain begin");
  $fnlist = $FPLTemplateFunctions;
  $out = array();
  foreach((array)$fnlist as $fn) {
    StopWatch("FPLTemplate: $fn");
    $out[] = $fn($pagename, $matches, $opt, $tparts);
  }
  StopWatch("FPLTemplate: Chain end ");
  #echo "FPLTemplate2: Exiting<br>\n";
  return implode('',$out);
} //}}}

# This function parses and takes into account parentheses
# The value returned is identical to ParseArgs() except with an additional
# dimension:
#   array(array('or', PARSEARGS_RETURN),
#         array('and', PARSEARGS_RETURN),
#         array('not', PARSEARGS_RETURN))
# Theoretically this could become a recursive structure where PARSEARGS_RETURN
# became PARSEARGS2_RETURN.  However that becomes pretty complicated to use 
# afterwards...  For now let's just consider the single level...
function ParseArgs2($x, $optpat = '(?>(\\w+)[:=])') {
  $dbg = false;
  $z = array();
  # Keep any quoted expressions
  if ($dbg) echo "Processing \"$x\"<br>\n";
  $args = preg_replace('/([\'"])(.*?)\\1/e', "Keep(PSS('$2'),'A')", $x);
  if ($dbg) echo "Processing kept \"$args\"<br>\n";
  return(ParseArgs2Recurse($args, $optpat));
}
function ParseArgs2Recurse($x, $optpat)
{
  global $KeepToken, $KPV;
  $dbg = false;
  $oneterm="(?P<prefix>$optpat|[-+])?(?P<expr2>\\S+)";
  if ($dbg) echo "oneterm=$oneterm<br>\n";
  $parenexpr="(?P<parenexpr>(?P<op>[,|+&^!-])?(?P<paren>\\()(?P<expr>(?:(?>[^()]*)|(?P>parenexpr))*)\\))";
  $first = true;
  while (preg_match("/^\\s*(?:$parenexpr|$oneterm)/", $x, $t))
  {
    if ($dbg) echo "MATCH: $x -> $t[0]<br>\n";
    if ($dbg) echo "parenexpr=$t[parenexpr], paren=$t[paren], expr=$t[expr]<br>\n";
    if ($dbg) echo "t=<pre>".print_r($t,true)."</pre><br>\n";
    if ($t['paren']) {
      if ($dbg) echo "Got paren<br>\n";
      switch ($t['op']) {
      case ',':
      case '|':
      case '+':
        $op = 'or';
        break;
      case '-':
      case '!':
        $op = 'not';
        break;
      case '^':
      case '&':
        $op = 'and';
        break;
      default:
        if ($first) $op = 'or';
        else $op = 'and';
        break;
      }
      if ($dbg) echo "Calling PA2 with \"$t[expr]\"<br>\n";
      $z[] = array($op, ParseArgs2Recurse($t['expr'], $optpat));
    } else {
      #$z = ParseArgs($t['expr2'], $optpat);
      if ($dbg) echo "NO paren, expr2=$t[expr2]<br>\n";
      $v = preg_replace("/$KeepToken(\\d.*?)$KeepToken/e", "\$KPV['$1']", $t['expr2']);
      if ($dbg) echo "v=$v<br>\n";
      if ($dbg) echo "optname=$t[6], prefix=$t[prefix], v=$v<br>\n";
      if ($t['6']) { $z['#'][] = $t['6']; $z[$t['6']] = $v; }
      else { $z['#'][] = $t['prefix']; $z[$t['prefix']][] = $v; }
      $z['#'][] = $v;
      if ($dbg) echo "z=<pre>".print_r($z,true)."</pre><br>\n";
    }
  $x = substr($x, strlen($t[0]));
  $first = false;
  if ($dbg) echo "EOL: x=$x<br>\n";
  }
  if ($dbg) echo "returning <pre>".print_r($z,true)."</pre><br>\n";
  return $z;
}

## load pagelist template from page
function FPLTemplateLoad($pagename, $matches, $opt, &$tparts) {
  global $Cursor, $FPLTemplatePageFmt, $RASPageName, $PageListArgPattern, $FmtV, $FmtPV;
  $template = @$opt['template'];
  if (!$template) $template = @$opt['fmt'];
  $ttext = RetrieveAuthSection($pagename, $template, $FPLTemplatePageFmt);
  $ttext = PVSE(Qualify($RASPageName, $ttext));
  
  ##  save any escapes
  $ttext = MarkupEscape($ttext);
  ##  remove any anchor markups to avoid duplications
  $ttext = preg_replace('/\\[\\[#[A-Za-z][-.:\\w]*\\]\\]/', '', $ttext);
  
  ##  extract portions of template
  $tparts = preg_split('/\\(:(template)\\s+(\\w+)\\s*(.*?):\\)/i', $ttext, -1,
                   PREG_SPLIT_DELIM_CAPTURE);
} //}}}


## extract args from (:template defaults .....:) and merge with options from (:pagelist :)
function FPLTemplateDefaults($pagename, $matches, &$opt, &$tparts) {
  global $PageListArgPattern;
  $i = 0;
  while ($i < count($tparts)) {
    if ($tparts[$i] != 'template') { $i++; continue; }
    if ($tparts[$i+1] != 'defaults' && $tparts[$i+1] != 'default') { $i+=4; continue; }
    $opt = array_merge(ParseArgs($tparts[$i+2], $PageListArgPattern), $opt);
    array_splice($tparts, $i, 3);
  }
  SDVA($opt, array('class' => 'fpltemplate', 'wrap' => 'div'));  
} //}}}

/* Comment:
Added capability of merging pagelist with any list supplied previously
by custom function.
*/
## get list of pages by calling MakePageList; 
## merge lists if any $matches are supplied previously   
function FPLTemplatePageList($pagename, &$matches, $opt, $tparts) {
  global $MakePageListOpt;
  $dbg = false;
  # if we have parens then we do all MakePageList() work within parentheses.
  # There is no capability of mixing parenthesis and non-parentheses.
  if (is_array($opt[0])) {
	if ($dbg) echo "opt[0] is an array...<br>\n";
    foreach ($opt as $k => $newopt) {
      if (is_numeric($k)) {
        if ($dbg) echo "NEWOPT: <pre>".print_r($newopt,true)."</pre><br>\n";
        if ($dbg) echo "PRE-MATCHES: <pre>".(sizeof($matches)>10?'('.sizeof($matches).')':print_r($matches,true))."</pre><br>\n";
        if (is_array($newopt[1][0])) {
          if ($dbg) echo "Calling recursively...<br>\n";
          FPLTemplatePageList($pagename, $matches, $newopt[1], $tparts);
        }
        if ($newopt[1]['#']) {
          $newmatch = MakePageList($pagename, $newopt[1], 0);
          switch ($newopt[0]) {
          case 'and':
            $matches = array_intersect($matches, $newmatch); 
            break;
          case 'not':
            $matches = array_diff($matches, $newmatch);
            break;
          case 'or':
          default:
            $matches = array_unique(array_merge($matches, $newmatch)); 
            break;
          }
          if ($dbg) echo "NEWMATCHES: <pre>".(sizeof($newmatch)>10?'('.sizeof($newmatch).')':print_r($newmatch,true))."</pre><br>\n";
          if ($dbg) echo "POST-MATCHES: <pre>".(sizeof($matches)>10?'('.sizeof($matches).')':print_r($matches,true))."</pre><br>\n";
        }
      }
    }
    SDVA($MakePageListOpt, array('list' => 'default'));
    $opt = array_merge((array)$MakePageListOpt, (array)$opt);
    if (!@$opt['order'] && !@$opt['trail']) $opt['order'] = 'name';
    $opt['=phase'] = PAGELIST_PRE;
    PageListSort($matches, $opt, $pagename, $page);
    if ($dbg) echo "after pre-sort: <pre>".print_r($opt,true)."</pre><br>\n";
    $opt['=phase'] = PAGELIST_POST;
    PageListSort($matches, $opt, $pagename, $page);
    $matches = array_values($matches); // re-set indices
    if ($dbg) echo "after post-sort: <pre>".print_r($opt,true)."</pre><br>\n";
  } elseif ($opt['#']) {
	if ($dbg) echo "no parens...<br>\n";
    if (is_array($matches) && !empty($matches))
      $matches = array_intersect($matches, MakePageList($pagename, $opt, 0));
    else
      $matches = array_merge($matches, MakePageList($pagename, $opt, 0));
  }
} //}}}


/* Comment:
Added a start= parameter for (:pagelist .. :)
Added a {$$PageListCount} template variable, 
for total $matches count before any slicing of $matches.
*/
## slice matches according to count= and start= 
function FPLTemplateSliceList($pagename, &$matches, &$opt, $tparts) {
  ## count matches before any slicing and save value as template var {$$PageListCount}
  $opt['PageListCount'] = $pagelistcount = count($matches);
  
  ##  extract page subset according to 'start=' parameter
  if (@$opt['start']) {
    if ($opt['start'] >= 0)
      $matches = array_slice($matches, $opt['start']-1);
    else 
      $matches = array_slice($matches, $pagelistcount+$opt['start']);
  }
  ##  further extract page subset according to 'count=' parameter
  if (@$opt['count']) {
    list($r0, $r1) = CalcRange($opt['count'], $pagelistcount);
    if ($r1 < $r0) 
      $matches = array_reverse(array_slice($matches, $r1-1, $r0-$r1+1));
    else 
      $matches = array_slice($matches, $r0-1, $r1-$r0+1);
  }
} //}}}


/* Comment:
Added of (:template none:) optional template section.
The control structure for the main loop has been changed 
to allow handling of 'no matches' cases.
*/
## format items according to template 
function FPLTemplateFormat($pagename, &$matches, &$opt, &$tparts) {
  global $Cursor;
#echo "FPLTemplateFormat: matches=<pre>".print_r($matches,true)."</pre><br>\n";
  $savecursor = $Cursor;
  $pagecount = 0; $groupcount = 0; $grouppagecount = 0;
  $pseudovars = array('{$$PageCount}' => &$pagecount, 
                     '{$$GroupCount}' => &$groupcount, 
                     '{$$GroupPageCount}' => &$grouppagecount);

  foreach(preg_grep('/^[\\w$]/', array_keys($opt)) as $k) 
    if (!is_array($opt[$k]))
      $pseudovars["{\$\$$k}"] = htmlspecialchars($opt[$k], ENT_NOQUOTES);

  $vk = array_keys($pseudovars);
  $vv = array_values($pseudovars);
  
  $lgroup = ''; $out = '';
  $mcount = count($matches);
  for($i=0; $i <= $mcount; $i++) {
    if($i>0 && $i==$mcount) break;
    $pn = $matches[$i];
    $group = PageVar($pn, '$Group');
    if ($group != $lgroup) { $groupcount++; $grouppagecount = 0; $lgroup = $group; }
    $grouppagecount++; $pagecount++;
    $t = 0;
    while ($t < count($tparts)) {
      if ($tparts[$t] != 'template') { 
        if ($mcount!=0) $item = $tparts[$t]; 
        $t++;
      }
      elseif ($mcount==0) {
        if ($tparts[$t] == 'template' && $tparts[$t+1] == 'none')
          $item = $tparts[$t+3];
        $t+=4;
      }
      else {
        list($when, $control, $item) = array_slice($tparts, $t+1, 3); $t+=4;
        if (!$control) {
          if ($when == 'none') continue;
          if ($when == 'first' && $i != 0) continue;
          if ($when == 'last' && $i != $mcount - 1) continue;
        } else {
          if ($when == 'none') continue;
          if ($when == 'first' || !isset($last[$t])) {
            $Cursor['<'] = $Cursor['&lt;'] = (string)@$matches[$i-1];
            $Cursor['='] = $pn;
            $Cursor['>'] = $Cursor['&gt;'] = (string)@$matches[$i+1];
            $curr = str_replace($vk, $vv, $control);
            $curr = preg_replace('/\\{(=|&[lg]t;)(\\$:?\\w+)\\}/e',
                        "PageVar(\$pn, '$2', '$1')", $curr);
            if ($when == 'first' && $i > 0 && $last[$t] == $curr) continue;
            $last[$t] = $curr;
          }
          if ($when == 'last') {
            $Cursor['<'] = $Cursor['&lt;'] = $pn;
            $Cursor['='] = (string)@$matches[$i+1];
            $Cursor['>'] = $Cursor['&gt;'] = (string)@$matches[$i+2];
            $next = str_replace($vk, $vv, $control);
            $next = preg_replace('/\\{(=|&[lg]t;)(\\$:?\\w+)\\}/e',
                        "PageVar(\$pn, '$2', '$1')", $next);
            if ($next == $last[$t] && $i != count($matches) - 1) continue;
            $last[$t] = $next;
          }
        }
      }
      $Cursor['<'] = $Cursor['&lt;'] = (string)@$matches[$i-1];
      $Cursor['='] = $pn;
      $Cursor['>'] = $Cursor['&gt;'] = (string)@$matches[$i+1];
      $item = str_replace($vk, $vv, $item);
      $item = preg_replace('/\\{(=|&[lg]t;)(\\$:?\\w+)\\}/e',
                  "PVSE(PageVar(\$pn, '$2', '$1'))", $item);
      $out .= MarkupRestore($item);
    }
  }
  $class = preg_replace('/[^-a-zA-Z0-9\\x80-\\xff]/', ' ', @$opt['class']);
  if ($class) $class = " class='$class'";
  $wrap = @$opt['wrap'];
  if ($wrap != 'inline') {
    $out = MarkupToHTML($pagename, $out, array('escape' => 0, 'redirect'=>1));
    if ($wrap != 'none') $out = "<div$class>$out</div>";
  }
  $Cursor = $savecursor;
  return $out;
} //}}}