1<?php 2/** 3 * CommandEmbedding -- Base class for command embedding syntaxes. 4 * 5 * A command embedding is a type of syntax plugin whose syntax includes 6 * a compliant call string and optional content. A call string has the 7 * following syntax, expressed in W3C-style BNF: 8 * 9 * call_string ::= command_name ('?' params)? 10 * command_name ::= name 11 * params ::= param ('&' param)* 12 * param ::= value | name '=' value? 13 * name ::= [a-zA-Z] [a-zA-Z0-9_]* 14 * value ::= [a-zA-Z0-9_\-.]+ 15 * 16 * This class is a subclass of DokuWiki_Syntax_Plugin. It implements handle() 17 * and render() on behalf of the subclass, but the subclass is responsible for 18 * implementing all other required methods of DokuWiki_Syntax_Plugin. 19 * 20 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 21 * @author Joe Lapp <http://www.spiderjoe.com> 22 * 23 * Modification history: 24 * 25 * 8/26/05 - Reverted back to 'substition' to permit recursion. JTL 26 * 8/29/05 - Added $renderer parameter to runCommand(). JTL 27 * 8/29/05 - Fixed failure to load extension for pre-cached data. JTL 28 */ 29 30//// CONSTANTS //////////////////////////////////////////////////////////////// 31 32require_once(DOKU_PLUGIN.'syntax.php'); 33 34if(!defined('COMMANDPLUGIN_DEFS')) 35{ 36 define('COMMANDPLUGIN_EXT_PATH', DOKU_PLUGIN.'command/ext/'); 37 38 define('COMMANDPLUGIN_LEX_CMD', '[a-zA-Z][a-zA-Z0-9_]*'); 39 define('COMMANDPLUGIN_LEX_CMD_PARAMS', // lexer prohibits subpatterns 40 COMMANDPLUGIN_LEX_CMD.'\?[a-zA-Z0-9_.\-=&]*'); 41 42 $TMP_NAME = '[a-zA-Z][a-zA-Z0-9_\-]*'; 43 $TMP_VCHAR = '[a-zA-Z0-9_.\-]'; 44 $TMP_PARAM = '('.$TMP_VCHAR.'+|'.$TMP_NAME.'\='.$TMP_VCHAR.'*)'; 45 define('COMMANDPLUGIN_CALL_PREG', 46 '/^('.$TMP_NAME.')(\?'.$TMP_PARAM.'(&'.$TMP_PARAM.')*)?$/'); 47 48 define('COMMANDPLUGIN_NOT_FOUND', '##_COMMAND_NOT_FOUND_##'); 49 define('COMMANDPLUGIN_INVALID_SYNTAX', '##_INVALID_COMMAND_SYNTAX_##'); 50 51 define('COMMANDPLUGIN_DEFS', '*'); 52} 53 54/****************************************************************************** 55 CommandEmbedding 56******************************************************************************/ 57 58class CommandEmbedding extends DokuWiki_Syntax_Plugin 59{ 60 //// POLYMORPHIC METHODS ////////////////////////////////////////////////// 61 62 /** 63 * getEmbeddingType() returns a string that uniquely identifies the 64 * particular command embedding and ideally also suggests the behavior 65 * of the command embedding. This string is passed to the command 66 * extensions to allow them to behave in part according to the type of 67 * embedding the command was called from. A command typically only 68 * uses this information if it outputs text for inclusion on the page. 69 * 70 * Each subclass must override and implement this method. 71 */ 72 73 function getEmbeddingType() 74 { 75 return null; // subclass must return non-empty string 76 } 77 78 /** 79 * parseCommandSyntax() takes a string that contains the entire text of 80 * the command and parses out the call string and the content. It returns 81 * an array of the form array(callString, content). This function cannot 82 * return an error; it is presumed that if the lexer matched the syntax, 83 * then the embedding is able to extract the call string and the content. 84 * 85 * Each subclass must override and implement this method. 86 */ 87 88 function parseCommandSyntax($match) 89 { 90 return null; // subclass must return array(callString, content) 91 } 92 93 //// PUBLIC METHODS /////////////////////////////////////////////////////// 94 95 function handle($match, $state, $pos, &$handler) 96 { 97 // Parse the command. 98 99 $parse = $this->parseCommandSyntax($match); 100 list($callString, $content) = $parse; 101 102 $call = $this->parseCommandCall($callString); 103 if($call == null) 104 return array($state, array('=', COMMANDPLUGIN_INVALID_SYNTAX)); 105 106 $cmdName = $call[0]; 107 $params = $call[1]; 108 $paramHash = $call[2]; 109 110 // Load the command extension. 111 112 if(!$this->loadExtension($cmdName)) 113 return array($state, array('=', COMMANDPLUGIN_NOT_FOUND)); 114 115 // Invoke the command. 116 117 $embedding = $this->getEmbeddingType(); 118 $methodObj = $this->getMethodObject($cmdName, 'getCachedData', 119 '$p, $pH, $c, &$e', '"'.$embedding.'", $p, $pH, $c, $e'); 120 $errMsg = ''; 121 $cachedData = $methodObj($params, $paramHash, $content, $errMsg); 122 if(!empty($errMsg)) 123 return array($state, array('=', '##'.$errMsg.'##')); 124 125 // Return successful results. 126 127 return array($state, array($cmdName, $cachedData)); 128 } 129 130 function render($mode, &$renderer, $data) 131 { 132 if($mode == 'xhtml') 133 { 134 // Extract cached data. 135 136 list($state, $match) = $data; 137 list($cmdName, $cachedData) = $match; 138 139 // Output data raw, as when command not found or error. 140 141 if($cmdName == '=') 142 $renderer->doc .= htmlspecialchars($cachedData); 143 144 // Run the command with the cached data. 145 146 else 147 { 148 if(!$this->loadExtension($cmdName)) 149 { 150 $renderer->doc .= COMMANDPLUGIN_NOT_FOUND; 151 return true; 152 } 153 154 $embedding = $this->getEmbeddingType(); 155 $methodObj = $this->getMethodObject($cmdName, 'runCommand', 156 '$c, &$r, &$e', '"'.$embedding.'", $c, $r, $e'); 157 $errMsg = ''; 158 $output = $methodObj($cachedData, $renderer, $errMsg); 159 if(!empty($errMsg)) 160 $renderer->doc .= htmlspecialchars($errMsg); 161 else if($output !== null) 162 $renderer->doc .= $output; 163 } 164 return true; 165 } 166 return false; 167 } 168 169 //// PRIVATE METHODS ////////////////////////////////////////////////////// 170 171 /** 172 * parseCommandCall() validates and parses the call string portion of a 173 * command. This string includes the command name and all of its 174 * parameters, but excludes the content and the bounding syntax. 175 * 176 * Returns an array of the form array(cmd, params, hash): 177 * 178 * cmd: The name of the command in lowercase. 179 * 180 * params: An array of parameters, indexed by their order of occurrence 181 * in the parameter list. If the parameter was an assignment (using '='), 182 * the element will be an array of the form array(name, value). If the 183 * parameter was a simple value (no '='), the element will be a string 184 * containing that value. 185 * 186 * hash: An associative array indexed by parameter name (for assignment 187 * parameters) and by parameter value (for simple value parameters). If 188 * no value was assigned to a parameter, or if the parameter is itself 189 * just a value, the value for the key is an empty string. For example, 190 * the call string "do?1.5&1.5&a&b=&c=2" produces array('1.5' => '', 191 * 'a' => '', 'b' => '', 'c' => '1'). 192 * 193 * If there are no parameters, the method returns array(cmd, null, null). 194 */ 195 196 function parseCommandCall($callString) 197 { 198 $matches = array(); 199 if(!preg_match(COMMANDPLUGIN_CALL_PREG, $callString, $matches)) 200 return null; // syntax error 201 202 $cmdName = strtolower($matches[1]); 203 if(sizeof($matches) == 2) 204 return array($cmdName, array(), array()); // no parameters 205 206 $rawParams = explode('&', substr($matches[2], 1)); 207 $params = array(); 208 $paramHash = array(); 209 210 foreach($rawParams as $rawParam) 211 { 212 $equalsPos = strpos($rawParam, '='); 213 if($equalsPos === false) 214 { 215 $params[] = $rawParam; 216 $paramHash[$rawParam] = ''; 217 } 218 else 219 { 220 $paramName = substr($rawParam, 0, $equalsPos); 221 // next line works even if '=' is last char 222 $paramValue = substr($rawParam, $equalsPos + 1); 223 $params[] = array($paramName, $paramValue); 224 $paramHash[$paramName] = $paramValue; 225 } 226 } 227 return array($cmdName, $params, $paramHash); 228 } 229 230 /** 231 * loadExtension() loads the command extension. 232 */ 233 234 function loadExtension($cmdName) 235 { 236 $extFile = COMMANDPLUGIN_EXT_PATH.$cmdName.'.php'; 237 if(!file_exists($extFile)) // PHP caches the results, fortunately 238 return false; 239 240 require_once($extFile); 241 return true; 242 } 243 244 /** 245 * getMethodObject() returns a method object that the caller may use to 246 * invoke a method. The method object is cached as a function of 247 * command name and method name so that multiple calls use the same 248 * method object, saving clock cycles. 249 */ 250 251 function &getMethodObject($cmdName, $methodName, $methodSig, $callArgs) 252 { 253 static $methodObjects = array(); 254 255 $key = $cmdName.'*'.$methodName; 256 if(isset($methodObjects[$key])) 257 return $methodObjects[$key]; 258 259 $className = 'CommandPluginExtension_'.$cmdName; 260 $methodObj = create_function($methodSig, 261 'return '.$className.'::'.$methodName.'('.$callArgs.');'); 262 $methodObjects[$key] =& $methodObj; 263 return $methodObj; 264 } 265} 266 267?>