3 * ARC2 SPARQLScript Processor
5 * @author Benjamin Nowack <bnowack@semsol.com>
6 * @license http://arc.semsol.org/license
13 class ARC2_SPARQLScriptProcessor extends ARC2_Class {
15 function __construct($a = '', &$caller) {
16 parent::__construct($a, $caller);
19 function ARC2_SPARQLScriptProcessor ($a = '', &$caller) {
20 $this->__construct($a, $caller);
25 $this->max_operations = $this->v('sparqlscript_max_operations', 0, $this->a);
26 $this->max_queries = $this->v('sparqlscript_max_queries', 0, $this->a);
28 $this->script_hash = '';
33 'operation_count' => 0,
35 'query_log' => array()
45 function processScript($s) {
46 $this->script_hash = abs(crc32($s));
47 $parser = $this->getParser();
49 $blocks = $parser->getScriptBlocks();
50 if ($parser->getErrors()) return 0;
51 foreach ($blocks as $block) {
52 $this->processBlock($block);
53 if ($this->return) return 0;
54 if ($this->getErrors()) return 0;
58 function getResult() {
60 return $this->getVarValue('__return_value__');
63 return $this->env['output'];
69 function getParser() {
70 ARC2::inc('SPARQLScriptParser');
71 return new ARC2_SPARQLScriptParser($this->a, $this);
76 function setVar($name, $val, $type = 'literal', $meta = '') {
77 /* types: literal, var, rows, bool, doc, http_response, undefined, ? */
78 $this->env['vars'][$name] = array(
79 'value_type' => $type,
81 'meta' => $meta ? $meta : array()
85 function getVar($name) {
86 return isset($this->env['vars'][$name]) ? $this->env['vars'][$name] : '';
89 function getVarValue($name) {
90 return ($v = $this->getVar($name)) ? (isset($v['value']) ? $v['value'] : $v ) : '';
95 function replacePlaceholders($val, $context = '', $return_string = 1, $loop = 0) {
98 if (preg_match_all('/(\{(?:[^{}]+|(?R))*\})/', $val, $m)) {
99 foreach ($m[1] as $match) {
100 if (strpos($val, '$' . $match) === false) {/* just some container brackets, recurse */
101 $val = str_replace($match, '{' . $this->replacePlaceholders(substr($match, 1, -1), $context, $return_string, $loop + 1) . '}', $val);
104 $ph = substr($match, 1, -1);
105 $sub_val = $this->getPlaceholderValue($ph);
106 if (is_array($sub_val)) {
107 $sub_val = $this->getArraySerialization($sub_val, $context);
109 $val = str_replace('${' . $ph . '}', $sub_val, $val);
113 } while (($old_val != $val) && ($loop < 10));
117 function getPlaceholderValue($ph) {
119 if (isset($this->env['vars'][$ph])) {
120 return $this->v('value', $this->env['vars'][$ph], $this->env['vars'][$ph]);
123 if (preg_match('/^(GET|POST)\.([^\.]+)(.*)$/', $ph, $m)) {
124 $vals = strtoupper($m[1]) == 'GET' ? $_GET : $POST;
125 $r = isset($vals[$m[2]]) ? $vals[$m[2]] : '';
126 return $m[3] ? $this->getPropertyValue(array('value' => $r, 'value_type' => '?'), ltrim($m[3], '.')) : $r;
129 if (preg_match('/^NOW(.*)$/', $ph, $m)) {
131 /* may have sub-phs */
132 $rest = $this->replacePlaceholders($rest);
141 if (preg_match('/(\+|\-)\s*([0-9]+)(y|mo|d|h|mi|s)[a-z]*(.*)/is', trim($rest), $m2)) {
142 eval('$r_struct[$m2[3]] ' . $m2[1] . '= (int)' . $m2[2] . ';');
145 $uts = mktime($r_struct['h'], $r_struct['mi'], $r_struct['s'], $r_struct['mo'], $r_struct['d'], $r_struct['y']);
146 $uts -= date('Z', $uts); /* timezone offset */
147 $r = date('Y-m-d\TH:i:s\Z', $uts);
148 if (preg_match('/^\.(.+)$/', $rest, $m)) {
149 return $this->getPropertyValue(array('value' => $r), $m[1]);
154 if (preg_match('/^([^\.]+)\.(.+)$/', $ph, $m)) {
155 list($var, $path) = array($m[1], $m[2]);
156 if (isset($this->env['vars'][$var])) {
157 return $this->getPropertyValue($this->env['vars'][$var], $path);
163 function getPropertyValue($obj, $path) {
164 $val = isset($obj['value']) ? $obj['value'] : $obj;
165 $path = $this->replacePlaceholders($path, 'property_value', 0);
167 if ($path == 'size') {
168 if ($obj['value_type'] == 'rows') return count($val);
169 if ($obj['value_type'] == 'literal') return strlen($val);
171 if (preg_match('/^replace\([\'\"](\/.*\/[a-z]*)[\'\"],\s*[\'\"](.*)[\'\"]\)$/is', $path, $m)) {
172 return @preg_replace($m[1], str_replace('$', '\\', $m[2]), $val);
174 if (preg_match('/^match\([\'\"](\/.*\/[a-z]*)[\'\"]\)$/is', $path, $m)) {
175 return @preg_match($m[1], $val, $m) ? $m : '';
177 if (preg_match('/^urlencode\([\'\"]?(get|post|.*)[\'\"]?\)$/is', $path, $m)) {
178 return (strtolower($m[1]) == 'post') ? rawurlencode($val) : urlencode($val);
180 if (preg_match('/^toDataURI\([^\)]*\)$/is', $path, $m)) {
181 return 'data:text/plain;charset=utf-8,' . rawurlencode($val);
183 if (preg_match('/^fromDataURI\([^\)]*\)$/is', $path, $m)) {
184 return rawurldecode(str_replace('data:text/plain;charset=utf-8,', '', $val));
186 if (preg_match('/^toPrettyDate\([^\)]*\)$/is', $path, $m)) {
187 $uts = strtotime(preg_replace('/(T|\+00\:00)/', ' ', $val));
188 return date('D j M H:i', $uts);
190 if (preg_match('/^render\(([^\)]*)\)$/is', $path, $m)) {
191 $src_format = trim($m[1], '"\'');
192 return $this->render($val, $src_format);
195 if (is_array($val)) {
196 if (isset($val[$path])) return $val[$path];
197 $exp_path = $this->expandPName($path);
198 if (isset($val[$exp_path])) return $val[$exp_path];
199 if (preg_match('/^([^\.]+)\.(.+)$/', $path, $m)) {
200 list($var, $path) = array($m[1], $m[2]);
201 if (isset($val[$var])) {
202 return $this->getPropertyValue(array('value' => $val[$var]), $path);
205 $exp_var = $this->expandPName($var);
206 if (isset($val[$exp_var])) {
207 return $this->getPropertyValue(array('value' => $val[$exp_var]), $path);
213 if (preg_match('/^\_/', $path) && isset($obj['meta']) && isset($obj['meta'][substr($path, 1)])) {
214 return $obj['meta'][substr($path, 1)];
219 function render($val, $src_format = '') {
221 $mthd = 'render' . $this->camelCase($src_format);
222 if (method_exists($this, $mthd)) {
223 return $this->$mthd($val);
226 return 'No rendering method found for "' . $src_format. '"';
230 return $this->getArraySerialization($val);
233 function renderObjects($os) {
235 foreach ($os as $o) {
236 $r .= $r ? ', ' : '';
244 function getArraySerialization($v, $context) {
245 $v_type = ARC2::getStructType($v);/* string|array|triples|index */
246 $pf = ARC2::getPreferredFormat();
248 if ($v_type == 'string') return $v;
249 /* simple array (e.g. from SELECT) */
250 if ($v_type == 'array') {
251 return join(', ', $v);
252 $m = method_exists($this, 'toLegacy' . $pf) ? 'toLegacy' . $pf : 'toLegacyXML';
255 if (($v_type == 'triples') || ($v_type == 'index')) {
256 $m = method_exists($this, 'to' . $pf) ? 'to' . $pf : ($context == 'query' ? 'toNTriples' : 'toRDFXML');
259 return $this->$m($v);
264 function processBlock($block) {
265 if ($this->max_operations && ($this->env['operation_count'] >= $this->max_operations)) return $this->addError('Number of ' . $this->max_operations . ' allowed operations exceeded.');
266 if ($this->return) return 0;
267 $this->env['operation_count']++;
268 $type = $block['type'];
269 $m = 'process' . $this->camelCase($type) . 'Block';
270 if (method_exists($this, $m)) {
271 return $this->$m($block);
273 return $this->addError('Unsupported block type "' . $type . '"');
278 function processEndpointDeclBlock($block) {
279 $this->env['endpoint'] = $block['endpoint'];
285 function processQueryBlock($block) {
286 if ($this->max_queries && ($this->env['query_count'] >= $this->max_queries)) return $this->addError('Number of ' . $this->max_queries . ' allowed queries exceeded.');
287 $this->env['query_count']++;
288 $ep_uri = $this->replacePlaceholders($this->env['endpoint'], 'endpoint');
290 $prologue = 'BASE <' . $block['base']. '>';
291 $q = $this->replacePlaceholders($block['query'], 'query');
293 $ns = isset($this->a['ns']) ? array_merge($this->a['ns'], $block['prefixes']) : $block['prefixes'];
294 $q = $prologue . "\n" . $this->completeQuery($q, $ns);
295 $this->env['query_log'][] = '(' . $ep_uri . ') ' . $q;
296 if ($store = $this->getStore($ep_uri)) {
297 $sub_r = $this->v('is_remote', '', $store) ? $store->query($q, '', $ep_uri) : $store->query($q);
298 /* ignore socket errors */
299 if (($errs = $this->getErrors()) && preg_match('/socket/', $errs[0])) {
300 $this->warnings[] = $errs[0];
301 $this->errors = array();
307 return $this->addError("no store (" . $ep_uri . ")");
311 function getStore($ep_uri) {
313 if ((!$ep_uri || $ep_uri == ARC2::getScriptURI()) && ($this->v('sparqlscript_default_endpoint', '', $this->a) == 'local')) {
314 if (!isset($this->local_store)) $this->local_store = ARC2::getStore($this->a);/* @@todo error checking */
315 return $this->local_store;
318 ARC2::inc('RemoteStore');
319 $conf = array_merge($this->a, array('remote_store_endpoint' => $ep_uri, 'reader_timeout' => 10));
320 return new ARC2_RemoteStore($conf, $this);
327 function processAssignmentBlock($block) {
328 $sub_type = $block['sub_type'];
329 $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock';
330 if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
331 return $this->$m($block);
334 function processQueryAssignmentBlock($block) {
335 $qr = $this->processQueryBlock($block['query']);
336 if ($this->getErrors() || !isset($qr['query_type'])) return 0;
337 $qt = $qr['query_type'];
338 $vts = array('ask' => 'bool', 'select' => 'rows', 'desribe' => 'doc', 'construct' => 'doc');
340 'value_type' => isset($vts[$qt]) ? $vts[$qt] : $qt . ' result',
341 'value' => ($qt == 'select') ? $this->v('rows', array(), $qr['result']) : $qr['result'],
343 $this->env['vars'][$block['var']['value']] = $r;
346 function processStringAssignmentBlock($block) {
347 $r = array('value_type' => 'literal', 'value' => $this->replacePlaceholders($block['string']['value']));
348 $this->env['vars'][$block['var']['value']] = $r;
351 function processVarAssignmentBlock($block) {
352 if (isset($this->env['vars'][$block['var2']['value']])) {
353 $this->env['vars'][$block['var']['value']] = $this->env['vars'][$block['var2']['value']];
356 $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => '');
360 function processPlaceholderAssignmentBlock($block) {
361 $ph_val = $this->getPlaceholderValue($block['placeholder']['value']);
362 $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => $ph_val);
365 function processVarMergeAssignmentBlock($block) {
366 $val1 = isset($this->env['vars'][$block['var2']['value']]) ? $this->env['vars'][$block['var2']['value']] : array('value_type' => 'undefined', 'value' => '');
367 $val2 = isset($this->env['vars'][$block['var3']['value']]) ? $this->env['vars'][$block['var3']['value']] : array('value_type' => 'undefined', 'value' => '');
368 if (is_array($val1) && is_array($val2)) {
369 $this->env['vars'][$block['var']['value']] = array('value_type' => $val2['value_type'], 'value' => array_merge($val1['value'], $val2['value']));
371 elseif (is_numeric($val1) && is_numeric($val2)) {
372 $this->env['vars'][$block['var']['value']] = $val1 + $val2;
376 function processFunctionCallAssignmentBlock($block) {
377 $sub_r = $this->processFunctionCallBlock($block['function_call']);
378 if ($this->getErrors()) return 0;
379 $this->env['vars'][$block['var']['value']] = $sub_r;
384 function processReturnBlock($block) {
385 $sub_type = $block['sub_type'];
386 $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock';
387 if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
388 $sub_r = $this->$m($block);
395 function processIfblockBlock($block) {
396 if ($this->testCondition($block['condition'])) {
397 $blocks = $block['blocks'];
400 $blocks = $block['else_blocks'];
402 foreach ($blocks as $block) {
403 $sub_r = $this->processBlock($block);
404 if ($this->getErrors()) return 0;
408 function testCondition($cond) {
409 $m = 'test' . $this->camelCase($cond['type']) . 'Condition';
410 if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
411 return $this->$m($cond);
414 function testVarCondition($cond) {
416 $vn = $cond['value'];
417 if (isset($this->env['vars'][$vn])) $r = $this->env['vars'][$vn]['value'];
418 $op = $this->v('operator', '', $cond);
419 if ($op == '!') $r = !$r;
420 return $r ? true : false;
423 function testPlaceholderCondition($cond) {
424 $val = $this->getPlaceholderValue($cond['value']);
425 $r = $val ? true : false;
426 $op = $this->v('operator', '', $cond);
427 if ($op == '!') $r = !$r;
431 function testExpressionCondition($cond) {
432 $m = 'test' . $this->camelCase($cond['sub_type']) . 'ExpressionCondition';
433 if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
434 return $this->$m($cond);
437 function testRelationalExpressionCondition($cond) {
438 $op = $cond['operator'];
439 if ($op == '=') $op = '==';
440 $val1 = $this->getPatternValue($cond['patterns'][0]);
441 $val2 = $this->getPatternValue($cond['patterns'][1]);
442 eval('$result = ($val1 ' . $op . ' $val2) ? 1 : 0;');
446 function testAndExpressionCondition($cond) {
447 foreach ($cond['patterns'] as $pattern) {
448 if (!$this->testCondition($pattern)) return false;
453 function getPatternValue($pattern) {
454 $m = 'get' . $this->camelCase($pattern['type']) . 'PatternValue';
455 if (!method_exists($this, $m)) return '';
456 return $this->$m($pattern);
459 function getLiteralPatternValue($pattern) {
460 return $pattern['value'];
463 function getPlaceholderPatternValue($pattern) {
464 return $this->getPlaceholderValue($pattern['value']);
469 function processForblockBlock($block) {
470 $set = $this->v($block['set'], array('value' => array()), $this->env['vars']);
471 $entries = isset($set['value']) ? $set['value'] : $set;
472 $iterator = $block['iterator'];
473 $blocks = $block['blocks'];
474 if (!is_array($entries)) return 0;
475 $rc = count($entries);
476 foreach ($entries as $i => $entry) {
477 $val_type = $this->v('value_type', 'set', $set) . ' entry';
478 $this->env['vars'][$iterator] = array(
480 'value_type' => $val_type,
483 'odd_even' => ($i % 2) ? 'even' : 'odd'
486 foreach ($blocks as $block) {
487 $this->processBlock($block);
488 if ($this->getErrors()) return 0;
495 function processLiteralBlock($block) {
496 $this->env['output'] .= $this->replacePlaceholders($block['value'], 'output');
501 function processFunctionCallBlock($block) {
502 $uri = $this->replacePlaceholders($block['uri'], 'function_call');
504 if (strpos($uri, $this->a['ns']['sps']) === 0) {
505 return $this->processBuiltinFunctionCallBlock($block);
507 /* remote functions */
510 function processBuiltinFunctionCallBlock($block) {
511 $fnc_uri = $this->replacePlaceholders($block['uri'], 'function_call');
512 $fnc_name = substr($fnc_uri, strlen($this->a['ns']['sps']));
513 if (preg_match('/^(get|post)$/i', $fnc_name, $m)) {
514 return $this->processHTTPCall($block, strtoupper($m[1]));
516 if ($fnc_name == 'eval') {
517 return $this->processEvalCall($block);
521 function processEvalCall($block) {
522 if (!$block['args']) return 0;
523 $arg = $block['args'][0];
525 if ($arg['type'] == 'placeholder') $script = $this->getPlaceholderValue($arg['value']);
526 if ($arg['type'] == 'literal') $script = $arg['value'];
527 if ($arg['type'] == 'var') $script = $this->getVarValue($arg['value']);
528 //echo "\n" . $script . $arg['type'];
529 $this->processScript($script);
532 function processHTTPCall($block, $mthd = 'GET') {
534 $reader =& new ARC2_Reader($this->a, $this);
535 $url = $this->replacePlaceholders($block['args'][0]['value'], 'function_call');
536 if ($mthd != 'GET') {
537 $reader->setHTTPMethod($mthd);
538 $reader->setCustomHeaders("Content-Type: application/x-www-form-urlencoded");
540 $to = $this->v('remote_call_timeout', 0, $this->a);
541 $reader->activate($url, '', 0, $to);
542 $format = $reader->getFormat();
544 while ($d = $reader->readStream()) {
547 $reader->closeStream();
548 unset($this->reader);
549 return array('value_type' => 'http_response', 'value' => $resp);
554 function extractVars($pattern, $input = '') {
556 /* replace PHs, track ()s */
559 if (preg_match_all('/([\?\$]\{([^\}]+)\}|\([^\)]+\))/', $regex, $m)) {
562 foreach ($matches as $i => $match) {
563 $vars[] = $pre_vars[$i];
564 if ($pre_vars[$i]) {/* placeholder */
565 $regex = str_replace($match, '(.+)', $regex);
567 else {/* parentheses, but may contain placeholders */
569 while (preg_match('/([\?\$]\{([^\}]+)\})/', $sub_regex, $m)) {
570 $sub_regex = str_replace($m[1], '(.+)', $sub_regex);
573 $regex = str_replace($match, $sub_regex, $regex);
577 if (@preg_match('/' . $regex . '/is', $input, $m)) {
583 for ($i = 0; $i < count($vars); $i++) {
585 $this->setVar($vars[$i], isset($vals[$i + 1]) ? $vals[$i + 1] : '');
590 /* no placeholders */
591 return ($pattern == $input) ? 1 : 0;