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

/** clipboard.php
  PmWiki module that allows you to define 'clips'. You can cut, paste and copy clips
  and extract & sort their content. 
  Copyright (C) 2004  Jan Meijer <commentgg@hotmail.com> 
  Uses the same license as pmwiki. I live mostly offline.. 
  
  Version: 0.5,  october 4th, 2005


Some description..
  
 Adds: (:cut name:) .. (:cutend:), (:copy name [sourceclip]:) .. (:copyend:)  and (:paste name:)  .. (:pasteend:) markups. 
 The (:xxxend:) can take the name parameter to match: (:xxxend clipname:) allowing nesting. 

 clipname is lowercase or {$Targets}; (:copy:) and (:paste:) can take additional (processed in this order):
 * sourceclip parameter => gets clip from another page or clip
 * extract=regular expression => extracts parts denominated in regexpr
 * id=name => extracts lines beginning with 'name:' ; sections beginning with '! id' ; tables beginning with '||id'
 * lines=3..5 parameter  => takes lines 3..5 only
 * 'sort' and 'reverse' parameter (sort only works on textual level: confused by markup or HTML)
 * list=bullet, list=enumerated => output list in some format
 * columns=4 parameter => output table

 Adds: (:for name1,name2,name3:)  text with (:item:) substitution (:end:)  no substitution (:begin:)  again with (:forend:)  
 Adds: (:foreach sourceclip:)  text with (:item:) substitution (:end:)  no substitution (:begin:)  again with (:forend:)  
 both do line-based sustitutions: all (:item:) is replaced with the line, all (:index:) with the index number
 sourceclip as above, taking the same parameters

 Clips are stored in $XL, so XLPages can define resources: 'clip-clipname' => 'clipcontent'.  
 So you don't have to use the (:if:) syntax when a clip isn't available, just prepare a default. 
 Skin or other templates can access clips directly through the $[..] syntax. 
 Tip: use (:nl:) to insert linebreaks. 
 It is even possible to redefine skin-elements that are $[..]. But beware of opening up vulnerabilities! 

*/
  

$ClipNamePattern='(?:[a-z]+|[A-Z][A-Za-z0-9_.]+(?:[#][a-z]+)?)';

Markup('cut','<if'  # (:cut clipname:) .. (:cutend:)
  ,"/[(]:cut(?: ([a-z]+))?:[)]\\s*\n?(.+?)\n?[(]:cutend( \\1)?:[)]/sme"
  ,"PRR(PZZ(\$GLOBALS['XL']['clip']['clip-$1']=PSS('$2')))");

Markup('paste','<include' # (:paste [Group.page#]clipname [columns=3] [lines=3..8]:)
  ,"/[(]:paste(?: +($ClipNamePattern)(?: +(.*?))?)?:[)]/sme" 
  ,"MorphedClip('$1',PSS('$2'))");
#short version: Markup('paste','<include',"/[(]:paste(?: ([a-z]+))?:[)]/e" ,"PRR().\$GLOBALS['XL']['clip']['clip-$1']");
  
Markup('import','<if' # (:copy clipname Group.Page#clipname:)
  ,"/[(]:copy ([a-z]+)[ =][ ]*($ClipNamePattern)(.*?):[)]/sme" 
  ,"PZZ(\$GLOBALS['XL']['clip']['clip-$1']=MorphedClip('$2',PSS('$3')))");

Markup('copy','<if'  # (:copy:) .. (:copyend:)
  ,"/[(]:copy(?: ([a-z]+))?:[)]\\s*\n?(.+?)\n?[(]:copyend( \\1)?:[)]/sme"
  ,"(\$GLOBALS['XL']['clip']['clip-$1']=PSS('$2'))");

$Conditions['clip'] = 'isset($GLOBALS["XL"]["clip"]["clip-$condparm"])';

  # allow requests to post data of vars that begin with 'clip-'
foreach($_REQUEST as $k=>$v)
  if(!strncmp($k,'clip-',5))
    $XL['clip'][$k]=htmlspecialchars($v);



    
function MorphedClip($clip,$parms){
  global $XL;
  if(!$clip and !$parms) {# template filling
    $c=$XL['clip']["clip-"];
    $r=substr($c,0,strpos_($c,"\n"));
    $XL['clip']["clip-"]=substr($c,strlen($r)+1);
    return $r;
  }
  $clip=GetClip($clip);
  if(!$parms) return $clip;
  if(preg_match("/(?:extract=| )?(([!@#%^\\/]).*?\\2[a-z]*)/s",$parms,$m))
    $parms=str_replace($m[0],' ',$parms);
  $parms=ParseArgs($parms);
  if($b=$parms['id']) {
    preg_match("/^[:]?[ ]*{$b}[ ]*[:\n][ ]*(.*?)[ ]*$|^[|][|][ ]*{$b}[ ]*[|][|][ ]*(.*?)[ |]*$|!+[ ]*{$b}[ ]*\n[ ]*(.*)[ ]*(?:\n!|(?-m)$)/mi",$clip,$im);
    $clip=@$im[1];
  }
  $clip=explode("\n",$clip);
  if($b=@$parms['lines']) {
    list($b,$e)=explode('..',$b);
    if(!$e) { $e=$b; $b=0; }
    $clip=array_slice($clip,$b,$e-$b);
  }
  if($m[1]) $clip=MatchList($m[1],$clip);
  foreach((array)$parms[''] as $p) { 
    if($p=='sort') sort($clip); # doesn't function on links unless written [[Tag -> Link]] format!! Better yet, links could carry a sort info parameter
    elseif($p=='reverse') $clip=array_reverse($clip);
  }
  if($b=$parms['list']) {
    $e=array('bullet'=>'*','enum'=>'#','enumerated'=>'#');
    if(!$e=$e[$b]) $e=$b;
    $clip=array_map(create_function('$c',"return '$e '.\$c;"),$clip);
  }
  if(!$c=$parms['columns']) return implode("\n",$clip);
  return FmtAsTable($clip,$c,'width=100%');
}
  
function GetClip($clip) {
  global $XL,$pagename,$GroupAttributesFmt;
  $GLOBALS['RedoMarkupLine']++; 
  list($pg,$clip)=explode('#',$clip);
  if(!$clip and !trim($pg,'a..z')) 
    return $XL['clip']["clip-$pg"];
  # remainder gets a clip from another page, much like (:include:)
  if(count($pg=explode('.',$pg))==1)
    array_unshift($pg,substr($pagename,0,strpos($pagename,'.')));
  $page=ReadPage("$pg[0].$pg[1]", READPAGE_CURRENT);  #!#
  if(!$clp=rtrim(" $clip")) return $page['text'];
  $pat="/[(]:(copy|cut)$clp:[)]\\s*\n?(.+?)\n?[(]:\\1end(?: $clip)?:[)]/sme";
  if(preg_match($pat,$page['text'],$m)) return @$m[2];
  return @$XL['clip']["clip-$clip"]; # there may be a default set in XLPage
}

function FmtAsTable($list,$columns=1,$tattr='') { 
  if(!$n = floor((count($list)+$columns-1) / $columns)) # number per column
    return "<table $attr><tr><td>...</td></tr></table>"; 
  $list=array_chunk($list,$n);
  foreach(range(0,$n) as $c) {
    $r='';
    foreach($list as $l) { 
      $d=$l[$c]; $r.="<td>$d</td>"; }
    $t.="  <tr>$r</tr>\n";
  }    
  return "<table $tattr>\n$t\n</table>";
}

function MatchList($pat,$txt) {
  if(!$pat) return $txt;
  $z=array();
  foreach((array) $txt as $t) 
    if(preg_match_all($pat,$t,$m,PREG_PATTERN_ORDER)) {
        unset($m[0]);
      foreach($m as $p) {
        array_splice($z,count($z),0,$p);
      }
    }
  return $z;
}

function strpos_($h,$n) { return (false!==$i=strpos($h,$n)) ? $i : strlen($h); }


#=============================================================== (:for:) extension


  # template generation (:for clip do clip:)
Markup('for','>if' 
  ,"/[(]:for ([a-zA-Z0-9]+(?:,[-a-zA-Z_0-9]+)*|each $ClipNamePattern||[{][$].*?[}]) +do +($ClipNamePattern):[)]/sme"
  ,"ForExpand('$1','$2')");

  # debugging aid: (:viewfor:)
Markup('viewfor','<split','/^(.*)[(]:viewfor:[)](.*)$/se'
  ,'PSS(\'$1$2\')."<hr>".str_replace("\n","\n ",(htmlspecialchars(PSS(\'$1$2\'))))');
 
function ForExpand($l,$txt) {
  global $PCache,$pagename;
  $GLOBALS['RedoMarkupLine']++; 
  if(!is_array($l)) {
    if(!strncmp('each ',$l,5)) $l=explode("\n",GetClip(substr($l,5)));
    else {
      if($l=='{$Targets}') 
        $l=$PCache[$pagename]['targets'];
      $l=explode(',',$l);
    }
  }
  $txt=GetClip($txt);
  if(substr($txt,-9)=='<:vspace>') $txt=substr($txt,0,-9);
  $limit=array();
  $txt=preg_replace("/[(]:begin(?: max=([0-9]+))?:[)]/e","'(:begin:)'.PZZ(\$limit[]=0$1)",$txt);
  $txt=preg_split("/(^ +)?[(]:begin:[)]( *\n)?/sm",$txt,PREG_SPLIT_DELIM_CAPTURE);
  $r='';
  if((count($txt) > 1) and false===strpos($txt[0],'(:end:)')) $r=array_shift($txt);
  foreach($txt as $n=>$t) {
    $t=preg_split("/(^[ ]+)?[(]:end:[)]([ ]*\n)?/sm",$t);
    $x=array_shift($t);
    foreach($l as $k=>$i) { 
      $r.=str_replace(array('(:item:)','(:index:)'),array($i,$k+1),$x);
    }
    $r.=implode($t);
  }
  return $r;
} 

?>