*/ if (!defined('PmWiki')) exit(); /* Copyright 2007-2016 by D.Faure (dominique.faure@gmail.com) and Patrick R. Michaud (pmichaud@pobox.com) Thanks to Steve Levithan for the unvaluable regex trick (http://blog.stevenlevithan.com/archives/mimic-atomic-groups/). This file would surely made be part of PmWiki; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This script extends the original MarkupExpressions recipe (core built- in since version 2.2.0-beta43) essentially by enabling *real* expression nesting (ie. able to handle arithmetic operations). It also defines various extra functions ranging from basic math to advanced string manipulation. The operations defined by this recipe include: add, sub, mul, div, mod - arithmetic operators rev - reverse string rot13 - rotate characters urlencode, urldecode - url formatting reg_replace - regexp find/replace wikiword - build a wikiword from a string test - evaluate a wiki condition if - conditionally returns its arguments sprintf - string formatting nomarkup - keep text from a markup string unaccent - accents removal (utf-8 compliant) The original "ftime" expression is also patched to enable some of the format specifier missing on Win32 platforms. See http://www.pmwiki.org/wiki/Cookbook/MarkupExprPlus for more info. */ $RecipeInfo['MarkupExprPlus']['Version'] = '2016-05-24'; if (! IsEnabled($EnableMarkupExpressions, 1)) return; if (!function_exists('Markup_e')) Abort("?MarkupExprPlus require PmWiki 2.2.58 and newer, PHP 5.5 compatible"); SDVA($MarkupExpr, array( 'add' => 'MEP_arith("+", PPRE($rpat, $rrep, $params))', 'sub' => 'MEP_arith("-", PPRE($rpat, $rrep, $params))', 'mul' => 'MEP_arith("*", PPRE($rpat, $rrep, $params))', 'div' => 'MEP_arith("/", PPRE($rpat, $rrep, $params))', 'mod' => '0 + ($args[0] % $args[1])', 'rev' => 'strrev($args[0])', 'urlencode' => 'rawurlencode($args[0])', 'urldecode' => 'rawurldecode($args[0])', 'rot13' => 'str_rot13($args[0])', 'wikiword' => 'PPRA($GLOBALS["MakePageNamePatterns"],' . 'PPRE($rpat, $rrep, $params))', 'reg_replace' => 'MEP_reg_replace($args[0], $args[1], $args[2])', 'test' => 'CondText($pagename, "if " . PPRE($rpat, $rrep, $params), "TRUE") ? "1" : "0"', 'if' => '($args[0]) ? $args[1] : (($args[2]) ? $args[2] : "")', 'sprintf' => 'call_user_func_array("sprintf", $args)', 'nomarkup' => 'preg_replace("/(<[^>]+>|\r\n?|\n\r?)/", "",' . ' MarkupToHTML($pagename, $args[0], array("escape" => 0)))', 'unaccent' => 'MEP_unaccent($args[0])', )); function MEP_nums($params) { $params = preg_split('/\\s+/', $params); $args = array(); foreach($params as $p) { list($d) = sscanf($p, "%g"); if (is_numeric($d)) $args[] = $d; } return $args; } ## MEP_arith handles {(add ...)}, {(sub ...)}, {(mul ...)} and {(div ...)} ## expressions. ## function MEP_arith($op, $params) { $expr = implode(")$op(", MEP_nums($params)); return $expr ? eval("return 0 + ($expr);") : ''; } if (!IsEnabled($EnableExprOriginalFtime, 0)) { $MarkupExpr['ftime'] = 'MEP_ftime($args, $argp)'; function MEP_strftime($fmt, $ts = null, $lc = null, $gm = false) { if (!$ts) $ts = time(); if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $date = $gm ? 'gmdate' : 'date'; $map = array( '%C' => sprintf("%02d", $date("Y", $ts) / 100), '%D' => '%m/%d/%y', '%e' => sprintf("%' 2d", $date("j", $ts)), '%g' => sprintf("%02d", $date("o", $ts) % 100), '%G' => $date("o", $ts), '%h' => '%b', '%n' => "\n", '%r' => $date("h:i:s", $ts) . " %p", '%R' => $date("H:i", $ts), '%t' => "\t", '%T' => '%H:%M:%S', '%u' => ($w = $date("w", $ts)) ? $w : 7, '%V' => $date("W", $ts), '%z' => substr($date("O", $ts), 0, 3) . ":" . substr($date("O", $ts), 3), ); $fmt = str_replace(array_keys($map), array_values($map), $fmt); } ## make sure we have %F available for ISO dates $fmt = str_replace(array('%F', '%s'), array('%Y-%m-%d', $ts), $fmt); $strftime = $gm ? 'gmstrftime' : 'strftime'; if (isset($lc)) { $oldlc = setlocale(LC_ALL, '0'); setlocale(LC_ALL, $lc); } $ret = $strftime($fmt, $ts); if (isset($lc)) setlocale(LC_ALL, $oldlc); return $ret; } ## MEP_ftime handles {(ftime ...)} expressions. ## function MEP_ftime($args, $argp = NULL) { global $TimeFmt, $Now, $FTimeFmt; ## get the format string if (@$argp['fmt']) $fmt = $argp['fmt']; elseif (strpos(@$args[0], '%') !== false) $fmt = array_shift($args); elseif (strpos(@$args[1], '%') !== false) list($fmt) = array_splice($args, 1, 1); else { SDV($FTimeFmt, $TimeFmt); $fmt = $FTimeFmt; } ## determine the timestamp if (isset($argp['when'])) list($time, $x) = DRange($argp['when']); elseif (@$args[0] > '') list($time, $x) = DRange(array_shift($args)); else $time = $Now; ## get the locale $locale = isset($argp['lc']) ? $argp['lc'] : array_shift($args); return MEP_strftime($fmt, $time, $locale); } } ## MEP_reg_replace handles {(reg_replace /regexp/opt replacement string)} ## function MEP_reg_replace($pat, $repl, $str) { if (!preg_match('/^(.)([^\\1]*)(\\1)(\\w*)$/', $pat, $m)) return $str; $m[4] = str_replace('e', '', $m[4]); return preg_replace(implode('', array_slice($m, 1)), $repl, $str); } ## MEP_unaccent_utf8 handles {(unaccent string)} ## function MEP_unaccent($s) { global $MEP_Encoding, $Charset; SDV($MEP_Encoding, $Charset); if (strpos($s = htmlentities($s, ENT_QUOTES, $MEP_Encoding), '&') !== false) $s = html_entity_decode(preg_replace('/&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|tilde|uml);/i', '$1', $s), ENT_QUOTES, $MEP_Encoding); return $s; } ## The esotheric stuff starts here... if (IsEnabled($EnableExprMultiline, 0)) { SDVA($MEP_Cfg, array( 'MarkupRe' => '/\\{(\\(\\w+\\b.*?\\))\\}/s', 'KeepRe' => '/([\'"])(.*?)\\1/s', 'FuncRe' => '/\((?=((\\w+)(\\s[^()]*)?))\1\)/s', 'ParseArgs' => 'MEP_ParseArgs', )); ## Multiline variant function MEP_ParseArgs($x, $optpat = '(?>(\\w+)[:=])') { $z = array(); preg_match_all("/($optpat|[-+])?(\"[^\"]*\"|'[^']*'|\\S+)/s", $x, $terms, PREG_SET_ORDER); foreach ($terms as $t) { $v = preg_replace('/^([\'"])?(.*)\\1$/', '$2', $t[3]); if ($t[2]) { $z['#'][] = $t[2]; $z[$t[2]] = $v; } else { $z['#'][] = $t[1]; $z[$t[1]][] = $v; } $z['#'][] = $v; } return $z; } $MarkupTable['{(']['pat'] = $MEP_Cfg['MarkupRe']; $MarkupTable['{(']['rep'] = PCCF("MarkupExpressionPlus(\$pagename, \$m[1])", 'markup_e'); } if (IsEnabled($EnableExprVarManip, 0)) { SDVA($MEP_Cfg, array( 'MarkupRe' => '/\\{(\\(\\w+\\b.*?\\))\\}/', 'KeepRe' => '/([\'"])(.*?)\\1/', 'FuncRe' => '/\((?=((\\w+)(\\s[^()]*)?))\1\)/', 'ParseArgs' => 'ParseArgs', 'httpvars' => '{$?|!@^~var}1', )); DisableMarkup('{('); Markup_e('{(+', isset($MarkupTable[$MEP_Cfg['httpvars']]) ? '<'.$MEP_Cfg['httpvars'] : '<{$var}', $MEP_Cfg['MarkupRe'], "MarkupExpressionPlus(\$pagename, \$m[1])"); SDVA($MarkupExpr, array( 'set' => 'MEP_setvar($pagename, true, @$args[0], @$args[1], $argp)', 'setq' => 'MEP_setvar($pagename, false, @$args[0], @$args[1], $argp)', )); ## MEP_setvar handles {(set ...)}/{(setq ...)} expressions. ## function MEP_setvar($pagename, $parm, $arg0, $arg1, $argp = NULL) { global $FmtPV; $n = (@$argp['var']) ? $argp['var'] : $arg0; $v = (@$argp['value']) ? $argp['value'] : $arg1; if ($n) $FmtPV["\${$n}"] = "'" . str_replace("'", "\\'", $v) . "'"; return $parm ? $v : ''; } } if(IsEnabled($EnableExprMultiline, 0) || IsEnabled($EnableExprVarManip, 0)) { ## Replace PV's, PTV's with their values. ## function MEP_parse_vars($pagename, $markupName, $expr) { global $MarkupTable; $pat = $MarkupTable[$markupName]['pat']; $rep = $MarkupTable[$markupName]['rep']; while (preg_match($pat, $expr)) $expr = is_callable($rep) ? preg_replace_callback($pat, $rep, $expr) : preg_replace($pat, $rep, $expr); return $expr; } function MarkupExpressionPlus($pagename, $expr) { global $EnableExprVarManip, $MarkupTable, $KeepToken, $KPV, $MarkupExpr, $MEP_Cfg; $rpat = "/$KeepToken(\\d+P)$KeepToken/"; $rrep = '$GLOBALS["KPV"][$m[1]]'; if (IsEnabled($EnableExprVarManip, 0)) { if (isset($MarkupTable[$MEP_Cfg['httpvars']])) $expr = MEP_parse_vars($pagename, $MEP_Cfg['httpvars'], $expr); $expr = MEP_parse_vars($pagename, '{$var}', $expr); } $expr = PPRE($MEP_Cfg['KeepRe'], "Keep(\$m[2],'P')", $expr); #$expr = PPRE('/\\(\\W/', "Keep(\$m[0],'P')", $expr); while (preg_match($MEP_Cfg['FuncRe'], $expr, $match)) { list($repl, $dummy, $func, $params) = $match; $code = @$MarkupExpr[$func]; ## if not a valid function, save this string as-is and exit if (!$code) break; ## if the code uses '$params', we just evaluate directly if (strpos($code, '$params') !== false) { $out = eval("return ({$code});"); if ($expr == $repl) { $expr = $out; break; } $expr = str_replace($repl, $out, $expr); continue; } ## otherwise, we parse arguments into $args before evaluating $argp = $MEP_Cfg['ParseArgs']($params); $x = $argp['#']; $args = array(); while ($x) { list($k, $v) = array_splice($x, 0, 2); if ($k == '' || $k == '+' || $k == '-') $args[] = $k.PPRE($rpat, $rrep, $v); } ## fix any quoted arguments foreach ($argp as $k => $v) if (!is_array($v)) $argp[$k] = PPRE($rpat, $rrep, $v); $out = eval("return ({$code});"); if ($expr == $repl) { $expr = $out; break; } $expr = str_replace($repl, Keep($out, 'P'), $expr); } return PPRE($rpat, $rrep, $expr); } }