PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/ARC2/sparqlscript/ARC2_SPARQLScriptProcessor.php

https://code.google.com/p/ontowiki/
PHP | 592 lines | 555 code | 18 blank | 19 comment | 28 complexity | 7ef2367c5a1d6d4d9587fc9f2d134e95 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * ARC2 SPARQLScript Processor
  4. *
  5. * @author Benjamin Nowack <bnowack@semsol.com>
  6. * @license http://arc.semsol.org/license
  7. * @package ARC2
  8. * @version 2010-07-06
  9. */
  10. ARC2::inc('Class');
  11. class ARC2_SPARQLScriptProcessor extends ARC2_Class {
  12. function __construct($a = '', &$caller) {
  13. parent::__construct($a, $caller);
  14. }
  15. function __init() {
  16. parent::__init();
  17. $this->max_operations = $this->v('sparqlscript_max_operations', 0, $this->a);
  18. $this->max_queries = $this->v('sparqlscript_max_queries', 0, $this->a);
  19. $this->return = 0;
  20. $this->script_hash = '';
  21. $this->env = array(
  22. 'endpoint' => '',
  23. 'vars' => array(),
  24. 'output' => '',
  25. 'operation_count' => 0,
  26. 'query_count' => 0,
  27. 'query_log' => array()
  28. );
  29. }
  30. function reset() {
  31. $this->__init();
  32. }
  33. /* */
  34. function processScript($s) {
  35. $this->script_hash = abs(crc32($s));
  36. $parser = $this->getParser();
  37. $parser->parse($s);
  38. $blocks = $parser->getScriptBlocks();
  39. if ($parser->getErrors()) return 0;
  40. foreach ($blocks as $block) {
  41. $this->processBlock($block);
  42. if ($this->return) return 0;
  43. if ($this->getErrors()) return 0;
  44. }
  45. }
  46. function getResult() {
  47. if ($this->return) {
  48. return $this->getVarValue('__return_value__');
  49. }
  50. else {
  51. return $this->env['output'];
  52. }
  53. }
  54. /* */
  55. function getParser() {
  56. ARC2::inc('SPARQLScriptParser');
  57. return new ARC2_SPARQLScriptParser($this->a, $this);
  58. }
  59. /* */
  60. function setVar($name, $val, $type = 'literal', $meta = '') {
  61. /* types: literal, var, rows, bool, doc, http_response, undefined, ? */
  62. $this->env['vars'][$name] = array(
  63. 'value_type' => $type,
  64. 'value' => $val,
  65. 'meta' => $meta ? $meta : array()
  66. );
  67. }
  68. function getVar($name) {
  69. return isset($this->env['vars'][$name]) ? $this->env['vars'][$name] : '';
  70. }
  71. function getVarValue($name) {
  72. return ($v = $this->getVar($name)) ? (isset($v['value']) ? $v['value'] : $v ) : '';
  73. }
  74. /* */
  75. function replacePlaceholders($val, $context = '', $return_string = 1, $loop = 0) {
  76. do {
  77. $old_val = $val;
  78. if (preg_match_all('/(\{(?:[^{}]+|(?R))*\})/', $val, $m)) {
  79. foreach ($m[1] as $match) {
  80. if (strpos($val, '$' . $match) === false) {/* just some container brackets, recurse */
  81. $val = str_replace($match, '{' . $this->replacePlaceholders(substr($match, 1, -1), $context, $return_string, $loop + 1) . '}', $val);
  82. }
  83. else {
  84. $ph = substr($match, 1, -1);
  85. $sub_val = $this->getPlaceholderValue($ph);
  86. if (is_array($sub_val)) {
  87. $sub_val = $this->getArraySerialization($sub_val, $context);
  88. }
  89. $val = str_replace('${' . $ph . '}', $sub_val, $val);
  90. }
  91. }
  92. }
  93. } while (($old_val != $val) && ($loop < 10));
  94. return $val;
  95. }
  96. function getPlaceholderValue($ph) {
  97. /* simple vars */
  98. if (isset($this->env['vars'][$ph])) {
  99. return $this->v('value', $this->env['vars'][$ph], $this->env['vars'][$ph]);
  100. }
  101. /* GET/POST */
  102. if (preg_match('/^(GET|POST)\.([^\.]+)(.*)$/', $ph, $m)) {
  103. $vals = strtoupper($m[1]) == 'GET' ? $_GET : $POST;
  104. $r = isset($vals[$m[2]]) ? $vals[$m[2]] : '';
  105. return $m[3] ? $this->getPropertyValue(array('value' => $r, 'value_type' => '?'), ltrim($m[3], '.')) : $r;
  106. }
  107. /* NOW */
  108. if (preg_match('/^NOW(.*)$/', $ph, $m)) {
  109. $rest = $m[1];
  110. /* may have sub-phs */
  111. $rest = $this->replacePlaceholders($rest);
  112. $r_struct = array(
  113. 'y' => date('Y'),
  114. 'mo' => date('m'),
  115. 'd' => date('d'),
  116. 'h' => date('H'),
  117. 'mi' => date('i'),
  118. 's' => date('s')
  119. );
  120. if (preg_match('/(\+|\-)\s*([0-9]+)(y|mo|d|h|mi|s)[a-z]*(.*)/is', trim($rest), $m2)) {
  121. eval('$r_struct[$m2[3]] ' . $m2[1] . '= (int)' . $m2[2] . ';');
  122. $rest = $m2[4];
  123. }
  124. $uts = mktime($r_struct['h'], $r_struct['mi'], $r_struct['s'], $r_struct['mo'], $r_struct['d'], $r_struct['y']);
  125. $uts -= date('Z', $uts); /* timezone offset */
  126. $r = date('Y-m-d\TH:i:s\Z', $uts);
  127. if (preg_match('/^\.(.+)$/', $rest, $m)) {
  128. return $this->getPropertyValue(array('value' => $r), $m[1]);
  129. }
  130. return $r;
  131. }
  132. /* property */
  133. if (preg_match('/^([^\.]+)\.(.+)$/', $ph, $m)) {
  134. list($var, $path) = array($m[1], $m[2]);
  135. if (isset($this->env['vars'][$var])) {
  136. return $this->getPropertyValue($this->env['vars'][$var], $path);
  137. }
  138. }
  139. return '';
  140. }
  141. function getPropertyValue($obj, $path) {
  142. $val = isset($obj['value']) ? $obj['value'] : $obj;
  143. $path = $this->replacePlaceholders($path, 'property_value', 0);
  144. /* reserved */
  145. if ($path == 'size') {
  146. if ($obj['value_type'] == 'rows') return count($val);
  147. if ($obj['value_type'] == 'literal') return strlen($val);
  148. }
  149. if (preg_match('/^replace\([\'\"](\/.*\/[a-z]*)[\'\"],\s*[\'\"](.*)[\'\"]\)$/is', $path, $m)) {
  150. return @preg_replace($m[1], str_replace('$', '\\', $m[2]), $val);
  151. }
  152. if (preg_match('/^match\([\'\"](\/.*\/[a-z]*)[\'\"]\)$/is', $path, $m)) {
  153. return @preg_match($m[1], $val, $m) ? $m : '';
  154. }
  155. if (preg_match('/^urlencode\([\'\"]?(get|post|.*)[\'\"]?\)$/is', $path, $m)) {
  156. return (strtolower($m[1]) == 'post') ? rawurlencode($val) : urlencode($val);
  157. }
  158. if (preg_match('/^toDataURI\([^\)]*\)$/is', $path, $m)) {
  159. return 'data:text/plain;charset=utf-8,' . rawurlencode($val);
  160. }
  161. if (preg_match('/^fromDataURI\([^\)]*\)$/is', $path, $m)) {
  162. return rawurldecode(str_replace('data:text/plain;charset=utf-8,', '', $val));
  163. }
  164. if (preg_match('/^toPrettyDate\([^\)]*\)$/is', $path, $m)) {
  165. $uts = strtotime(preg_replace('/(T|\+00\:00)/', ' ', $val));
  166. return date('D j M H:i', $uts);
  167. }
  168. if (preg_match('/^render\(([^\)]*)\)$/is', $path, $m)) {
  169. $src_format = trim($m[1], '"\'');
  170. return $this->render($val, $src_format);
  171. }
  172. /* struct */
  173. if (is_array($val)) {
  174. if (isset($val[$path])) return $val[$path];
  175. $exp_path = $this->expandPName($path);
  176. if (isset($val[$exp_path])) return $val[$exp_path];
  177. if (preg_match('/^([^\.]+)\.(.+)$/', $path, $m)) {
  178. list($var, $path) = array($m[1], $m[2]);
  179. if (isset($val[$var])) {
  180. return $this->getPropertyValue(array('value' => $val[$var]), $path);
  181. }
  182. /* qname */
  183. $exp_var = $this->expandPName($var);
  184. if (isset($val[$exp_var])) {
  185. return $this->getPropertyValue(array('value' => $val[$exp_var]), $path);
  186. }
  187. return '';
  188. }
  189. }
  190. /* meta */
  191. if (preg_match('/^\_/', $path) && isset($obj['meta']) && isset($obj['meta'][substr($path, 1)])) {
  192. return $obj['meta'][substr($path, 1)];
  193. }
  194. return '';
  195. }
  196. function render($val, $src_format = '') {
  197. if ($src_format) {
  198. $mthd = 'render' . $this->camelCase($src_format);
  199. if (method_exists($this, $mthd)) {
  200. return $this->$mthd($val);
  201. }
  202. else {
  203. return 'No rendering method found for "' . $src_format. '"';
  204. }
  205. }
  206. /* try RDF */
  207. return $this->getArraySerialization($val);
  208. }
  209. function renderObjects($os) {
  210. $r = '';
  211. foreach ($os as $o) {
  212. $r .= $r ? ', ' : '';
  213. $r .= $o['value'];
  214. }
  215. return $r;
  216. }
  217. /* */
  218. function getArraySerialization($v, $context) {
  219. $v_type = ARC2::getStructType($v);/* string|array|triples|index */
  220. $pf = ARC2::getPreferredFormat();
  221. /* string */
  222. if ($v_type == 'string') return $v;
  223. /* simple array (e.g. from SELECT) */
  224. if ($v_type == 'array') {
  225. return join(', ', $v);
  226. $m = method_exists($this, 'toLegacy' . $pf) ? 'toLegacy' . $pf : 'toLegacyXML';
  227. }
  228. /* rdf */
  229. if (($v_type == 'triples') || ($v_type == 'index')) {
  230. $m = method_exists($this, 'to' . $pf) ? 'to' . $pf : ($context == 'query' ? 'toNTriples' : 'toRDFXML');
  231. }
  232. /* else */
  233. return $this->$m($v);
  234. }
  235. /* */
  236. function processBlock($block) {
  237. if ($this->max_operations && ($this->env['operation_count'] >= $this->max_operations)) return $this->addError('Number of ' . $this->max_operations . ' allowed operations exceeded.');
  238. if ($this->return) return 0;
  239. $this->env['operation_count']++;
  240. $type = $block['type'];
  241. $m = 'process' . $this->camelCase($type) . 'Block';
  242. if (method_exists($this, $m)) {
  243. return $this->$m($block);
  244. }
  245. return $this->addError('Unsupported block type "' . $type . '"');
  246. }
  247. /* */
  248. function processEndpointDeclBlock($block) {
  249. $this->env['endpoint'] = $block['endpoint'];
  250. return $this->env;
  251. }
  252. /* */
  253. function processQueryBlock($block) {
  254. if ($this->max_queries && ($this->env['query_count'] >= $this->max_queries)) return $this->addError('Number of ' . $this->max_queries . ' allowed queries exceeded.');
  255. $this->env['query_count']++;
  256. $ep_uri = $this->replacePlaceholders($this->env['endpoint'], 'endpoint');
  257. /* q */
  258. $prologue = 'BASE <' . $block['base']. '>';
  259. $q = $this->replacePlaceholders($block['query'], 'query');
  260. /* prefixes */
  261. $ns = isset($this->a['ns']) ? array_merge($this->a['ns'], $block['prefixes']) : $block['prefixes'];
  262. $q = $prologue . "\n" . $this->completeQuery($q, $ns);
  263. $this->env['query_log'][] = '(' . $ep_uri . ') ' . $q;
  264. if ($store = $this->getStore($ep_uri)) {
  265. $sub_r = $this->v('is_remote', '', $store) ? $store->query($q, '', $ep_uri) : $store->query($q);
  266. /* ignore socket errors */
  267. if (($errs = $this->getErrors()) && preg_match('/socket/', $errs[0])) {
  268. $this->warnings[] = $errs[0];
  269. $this->errors = array();
  270. $sub_r = array();
  271. }
  272. return $sub_r;
  273. }
  274. else {
  275. return $this->addError("no store (" . $ep_uri . ")");
  276. }
  277. }
  278. function getStore($ep_uri) {
  279. /* local store */
  280. if ((!$ep_uri || $ep_uri == ARC2::getScriptURI()) && ($this->v('sparqlscript_default_endpoint', '', $this->a) == 'local')) {
  281. if (!isset($this->local_store)) $this->local_store = ARC2::getStore($this->a);/* @@todo error checking */
  282. return $this->local_store;
  283. }
  284. elseif ($ep_uri) {
  285. ARC2::inc('RemoteStore');
  286. $conf = array_merge($this->a, array('remote_store_endpoint' => $ep_uri, 'reader_timeout' => 10));
  287. return new ARC2_RemoteStore($conf, $this);
  288. }
  289. return 0;
  290. }
  291. /* */
  292. function processAssignmentBlock($block) {
  293. $sub_type = $block['sub_type'];
  294. $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock';
  295. if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
  296. return $this->$m($block);
  297. }
  298. function processQueryAssignmentBlock($block) {
  299. $qr = $this->processQueryBlock($block['query']);
  300. if ($this->getErrors() || !isset($qr['query_type'])) return 0;
  301. $qt = $qr['query_type'];
  302. $vts = array('ask' => 'bool', 'select' => 'rows', 'desribe' => 'doc', 'construct' => 'doc');
  303. $r = array(
  304. 'value_type' => isset($vts[$qt]) ? $vts[$qt] : $qt . ' result',
  305. 'value' => ($qt == 'select') ? $this->v('rows', array(), $qr['result']) : $qr['result'],
  306. );
  307. $this->env['vars'][$block['var']['value']] = $r;
  308. }
  309. function processStringAssignmentBlock($block) {
  310. $r = array('value_type' => 'literal', 'value' => $this->replacePlaceholders($block['string']['value']));
  311. $this->env['vars'][$block['var']['value']] = $r;
  312. }
  313. function processVarAssignmentBlock($block) {
  314. if (isset($this->env['vars'][$block['var2']['value']])) {
  315. $this->env['vars'][$block['var']['value']] = $this->env['vars'][$block['var2']['value']];
  316. }
  317. else {
  318. $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => '');
  319. }
  320. }
  321. function processPlaceholderAssignmentBlock($block) {
  322. $ph_val = $this->getPlaceholderValue($block['placeholder']['value']);
  323. $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => $ph_val);
  324. }
  325. function processVarMergeAssignmentBlock($block) {
  326. $val1 = isset($this->env['vars'][$block['var2']['value']]) ? $this->env['vars'][$block['var2']['value']] : array('value_type' => 'undefined', 'value' => '');
  327. $val2 = isset($this->env['vars'][$block['var3']['value']]) ? $this->env['vars'][$block['var3']['value']] : array('value_type' => 'undefined', 'value' => '');
  328. if (is_array($val1) && is_array($val2)) {
  329. $this->env['vars'][$block['var']['value']] = array('value_type' => $val2['value_type'], 'value' => array_merge($val1['value'], $val2['value']));
  330. }
  331. elseif (is_numeric($val1) && is_numeric($val2)) {
  332. $this->env['vars'][$block['var']['value']] = $val1 + $val2;
  333. }
  334. }
  335. function processFunctionCallAssignmentBlock($block) {
  336. $sub_r = $this->processFunctionCallBlock($block['function_call']);
  337. if ($this->getErrors()) return 0;
  338. $this->env['vars'][$block['var']['value']] = $sub_r;
  339. }
  340. /* */
  341. function processReturnBlock($block) {
  342. $sub_type = $block['sub_type'];
  343. $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock';
  344. if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
  345. $sub_r = $this->$m($block);
  346. $this->return = 1;
  347. return $sub_r;
  348. }
  349. /* */
  350. function processIfblockBlock($block) {
  351. if ($this->testCondition($block['condition'])) {
  352. $blocks = $block['blocks'];
  353. }
  354. else {
  355. $blocks = $block['else_blocks'];
  356. }
  357. foreach ($blocks as $block) {
  358. $sub_r = $this->processBlock($block);
  359. if ($this->getErrors()) return 0;
  360. }
  361. }
  362. function testCondition($cond) {
  363. $m = 'test' . $this->camelCase($cond['type']) . 'Condition';
  364. if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
  365. return $this->$m($cond);
  366. }
  367. function testVarCondition($cond) {
  368. $r = 0;
  369. $vn = $cond['value'];
  370. if (isset($this->env['vars'][$vn])) $r = $this->env['vars'][$vn]['value'];
  371. $op = $this->v('operator', '', $cond);
  372. if ($op == '!') $r = !$r;
  373. return $r ? true : false;
  374. }
  375. function testPlaceholderCondition($cond) {
  376. $val = $this->getPlaceholderValue($cond['value']);
  377. $r = $val ? true : false;
  378. $op = $this->v('operator', '', $cond);
  379. if ($op == '!') $r = !$r;
  380. return $r;
  381. }
  382. function testExpressionCondition($cond) {
  383. $m = 'test' . $this->camelCase($cond['sub_type']) . 'ExpressionCondition';
  384. if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"');
  385. return $this->$m($cond);
  386. }
  387. function testRelationalExpressionCondition($cond) {
  388. $op = $cond['operator'];
  389. if ($op == '=') $op = '==';
  390. $val1 = $this->getPatternValue($cond['patterns'][0]);
  391. $val2 = $this->getPatternValue($cond['patterns'][1]);
  392. eval('$result = ($val1 ' . $op . ' $val2) ? 1 : 0;');
  393. return $result;
  394. }
  395. function testAndExpressionCondition($cond) {
  396. foreach ($cond['patterns'] as $pattern) {
  397. if (!$this->testCondition($pattern)) return false;
  398. }
  399. return true;
  400. }
  401. function getPatternValue($pattern) {
  402. $m = 'get' . $this->camelCase($pattern['type']) . 'PatternValue';
  403. if (!method_exists($this, $m)) return '';
  404. return $this->$m($pattern);
  405. }
  406. function getLiteralPatternValue($pattern) {
  407. return $pattern['value'];
  408. }
  409. function getPlaceholderPatternValue($pattern) {
  410. return $this->getPlaceholderValue($pattern['value']);
  411. }
  412. /* */
  413. function processForblockBlock($block) {
  414. $set = $this->v($block['set'], array('value' => array()), $this->env['vars']);
  415. $entries = isset($set['value']) ? $set['value'] : $set;
  416. $iterator = $block['iterator'];
  417. $blocks = $block['blocks'];
  418. if (!is_array($entries)) return 0;
  419. $rc = count($entries);
  420. foreach ($entries as $i => $entry) {
  421. $val_type = $this->v('value_type', 'set', $set) . ' entry';
  422. $this->env['vars'][$iterator] = array(
  423. 'value' => $entry,
  424. 'value_type' => $val_type,
  425. 'meta' => array(
  426. 'pos' => $i,
  427. 'odd_even' => ($i % 2) ? 'even' : 'odd'
  428. )
  429. );
  430. foreach ($blocks as $block) {
  431. $this->processBlock($block);
  432. if ($this->getErrors()) return 0;
  433. }
  434. }
  435. }
  436. /* */
  437. function processLiteralBlock($block) {
  438. $this->env['output'] .= $this->replacePlaceholders($block['value'], 'output');
  439. }
  440. /* */
  441. function processFunctionCallBlock($block) {
  442. $uri = $this->replacePlaceholders($block['uri'], 'function_call');
  443. /* built-ins */
  444. if (strpos($uri, $this->a['ns']['sps']) === 0) {
  445. return $this->processBuiltinFunctionCallBlock($block);
  446. }
  447. /* remote functions */
  448. }
  449. function processBuiltinFunctionCallBlock($block) {
  450. $fnc_uri = $this->replacePlaceholders($block['uri'], 'function_call');
  451. $fnc_name = substr($fnc_uri, strlen($this->a['ns']['sps']));
  452. if (preg_match('/^(get|post)$/i', $fnc_name, $m)) {
  453. return $this->processHTTPCall($block, strtoupper($m[1]));
  454. }
  455. if ($fnc_name == 'eval') {
  456. return $this->processEvalCall($block);
  457. }
  458. }
  459. function processEvalCall($block) {
  460. if (!$block['args']) return 0;
  461. $arg = $block['args'][0];
  462. $script = '';
  463. if ($arg['type'] == 'placeholder') $script = $this->getPlaceholderValue($arg['value']);
  464. if ($arg['type'] == 'literal') $script = $arg['value'];
  465. if ($arg['type'] == 'var') $script = $this->getVarValue($arg['value']);
  466. //echo "\n" . $script . $arg['type'];
  467. $this->processScript($script);
  468. }
  469. function processHTTPCall($block, $mthd = 'GET') {
  470. ARC2::inc('Reader');
  471. $reader = new ARC2_Reader($this->a, $this);
  472. $url = $this->replacePlaceholders($block['args'][0]['value'], 'function_call');
  473. if ($mthd != 'GET') {
  474. $reader->setHTTPMethod($mthd);
  475. $reader->setCustomHeaders("Content-Type: application/x-www-form-urlencoded");
  476. }
  477. $to = $this->v('remote_call_timeout', 0, $this->a);
  478. $reader->activate($url, '', 0, $to);
  479. $format = $reader->getFormat();
  480. $resp = '';
  481. while ($d = $reader->readStream()) {
  482. $resp .= $d;
  483. }
  484. $reader->closeStream();
  485. unset($this->reader);
  486. return array('value_type' => 'http_response', 'value' => $resp);
  487. }
  488. /* */
  489. function extractVars($pattern, $input = '') {
  490. $vars = array();
  491. /* replace PHs, track ()s */
  492. $regex = $pattern;
  493. $vars = array();
  494. if (preg_match_all('/([\?\$]\{([^\}]+)\}|\([^\)]+\))/', $regex, $m)) {
  495. $matches = $m[1];
  496. $pre_vars = $m[2];
  497. foreach ($matches as $i => $match) {
  498. $vars[] = $pre_vars[$i];
  499. if ($pre_vars[$i]) {/* placeholder */
  500. $regex = str_replace($match, '(.+)', $regex);
  501. }
  502. else {/* parentheses, but may contain placeholders */
  503. $sub_regex = $match;
  504. while (preg_match('/([\?\$]\{([^\}]+)\})/', $sub_regex, $m)) {
  505. $sub_regex = str_replace($m[1], '(.+)', $sub_regex);
  506. $vars[] = $m[2];
  507. }
  508. $regex = str_replace($match, $sub_regex, $regex);
  509. }
  510. }
  511. /* eval regex */
  512. if (@preg_match('/' . $regex . '/is', $input, $m)) {
  513. $vals = $m;
  514. }
  515. else {
  516. return 0;
  517. }
  518. for ($i = 0; $i < count($vars); $i++) {
  519. if ($vars[$i]) {
  520. $this->setVar($vars[$i], isset($vals[$i + 1]) ? $vals[$i + 1] : '');
  521. }
  522. }
  523. return 1;
  524. }
  525. /* no placeholders */
  526. return ($pattern == $input) ? 1 : 0;
  527. }
  528. /* */
  529. }