PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/Erfurt/Sparql/Constraint.php

http://github.com/AKSW/Erfurt
PHP | 407 lines | 287 code | 41 blank | 79 comment | 95 complexity | df470997430048179e22b126b91e4743 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the {@link http://aksw.org/Projects/Erfurt Erfurt} project.
  4. *
  5. * @copyright Copyright (c) 2012, {@link http://aksw.org AKSW}
  6. * @license http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
  7. */
  8. /**
  9. * Represents a constraint. A value constraint is a boolean- valued expression
  10. * of variables and RDF Terms.
  11. *
  12. * This class was originally adopted from rdfapi-php (@link http://sourceforge.net/projects/rdfapi-php/).
  13. * It was modified and extended in order to fit into Erfurt.
  14. *
  15. * @package Erfurt_Sparql
  16. * @author Tobias Gauss <tobias.gauss@web.de>
  17. * @author Philipp Frischmuth <pfrischmuth@googlemail.com>
  18. * @license http://www.gnu.org/licenses/lgpl.html LGPL
  19. */
  20. class Erfurt_Sparql_Constraint
  21. {
  22. // ------------------------------------------------------------------------
  23. // --- Protected properties -----------------------------------------------
  24. // ------------------------------------------------------------------------
  25. /**
  26. * The expression string.
  27. *
  28. * @var string
  29. */
  30. protected $expression;
  31. /**
  32. * True if it is an outer filter, false if not.
  33. *
  34. * @var boolean
  35. */
  36. protected $outer;
  37. /**
  38. * The expression tree
  39. *
  40. * @var array
  41. */
  42. protected $tree = null;
  43. /**
  44. * Contains all variables that are used in the constraint expression (recursivly).
  45. * This array is calculated once, if the tree is set in order to avoid multiple calculation.^
  46. *
  47. * @var array
  48. */
  49. protected $_usedVars = null;
  50. protected $_tokens = null;
  51. // ------------------------------------------------------------------------
  52. // --- Public methods -----------------------------------------------------
  53. // ------------------------------------------------------------------------
  54. /**
  55. * Adds an expression string.
  56. *
  57. * @param String $exp the expression String
  58. * @return void
  59. */
  60. public function addExpression($exp)
  61. {
  62. $this->expression = $exp;
  63. }
  64. /**
  65. * Returns the expression string.
  66. *
  67. * @return String the expression String
  68. */
  69. public function getExpression()
  70. {
  71. return $this->expression;
  72. }
  73. public function getTree()
  74. {
  75. return $this->tree;
  76. }
  77. public function getUsedVars()
  78. {
  79. if (null === $this->_usedVars) {
  80. $this->_usedVars = array_unique($this->_resolveUsedVarsRecursive($this->tree));
  81. }
  82. return $this->_usedVars;
  83. }
  84. /**
  85. * Returns true if this constraint is an outer filter- false if not.
  86. *
  87. * @return boolean
  88. */
  89. public function isOuterFilter()
  90. {
  91. return $this->outer;
  92. }
  93. /**
  94. * Sets the filter type to outer or inner filter.
  95. * True for outer false for inner.
  96. *
  97. * @param boolean $boolean
  98. * @return void
  99. */
  100. public function setOuterFilter($boolean)
  101. {
  102. $this->outer = $boolean;
  103. }
  104. public function setTree($tree)
  105. {
  106. $this->tree = $tree;
  107. // If the tree is set or reset, the used variables need to be resolved again.
  108. $this->_usedVars = null;
  109. }
  110. public function parse()
  111. {
  112. $this->_tokens = Erfurt_Sparql_Parser::tokenize($this->expression);
  113. $this->setOuterFilter(true);
  114. $this->setTree($this->_parseConstraintTree());
  115. $this->_tokens = null;
  116. }
  117. // ------------------------------------------------------------------------
  118. // --- Protected methods --------------------------------------------------
  119. // ------------------------------------------------------------------------
  120. protected function _resolveUsedVarsRecursive($tree)
  121. {
  122. $usedVars = array();
  123. if ($tree['type'] === 'function') {
  124. foreach ($tree['parameter'] as $paramArray) {
  125. if ($paramArray['type'] === 'value') {
  126. if (is_string($paramArray['value']) &&
  127. ($paramArray['value'][0] === '?' || $paramArray['value'][0] === '$')) {
  128. $usedVars[] = $paramArray['value'];
  129. }
  130. } else if ($paramArray['type'] === 'function') {
  131. foreach ($paramArray['parameter'] as $p) {
  132. if (is_string($p['value']) &&
  133. ($p['value'][0] === '?' || $p['value'][0] === '$')) {
  134. $usedVars[] = $p['value'];
  135. }
  136. }
  137. }
  138. }
  139. } else if ($tree['type'] === 'value') {
  140. if ($tree['value'][0] === '?' || $tree['value'][0] === '$') {
  141. $usedVars[] = $tree['value'];
  142. }
  143. } else {
  144. $usedVars = array_merge($usedVars, $this->_resolveUsedVarsRecursive($tree['operand1']));
  145. $usedVars = array_merge($usedVars, $this->_resolveUsedVarsRecursive($tree['operand2']));
  146. }
  147. return $usedVars;
  148. }
  149. protected function _parseConstraintTree($nLevel = 0, $bParameter = false)
  150. {
  151. $tree = array();
  152. $part = array();
  153. $chQuotes = null;
  154. $litQuotes = null;
  155. $strQuoted = '';
  156. $parens = false;
  157. while ($tok = next($this->_tokens)) {
  158. if ($chQuotes !== null && $tok != $chQuotes) {
  159. $strQuoted .= $tok;
  160. continue;
  161. } else if ($litQuotes !== null) {
  162. $strQuoted .= $tok;
  163. if ($tok[strlen($tok) - 1] === '>') {
  164. $tok = '>';
  165. } else {
  166. continue;
  167. }
  168. } else if ($tok === ')' || $tok === '}' || $tok === '.') {
  169. break;
  170. } else if (strtolower($tok) === 'filter' || strtolower($tok) === 'optional') {
  171. break;
  172. }
  173. switch ($tok) {
  174. case '"':
  175. case '\'':
  176. if ($chQuotes === null) {
  177. $chQuotes = $tok;
  178. $strQuoted = '';
  179. } else {
  180. $chQuotes = null;
  181. $part[] = array(
  182. 'type' => 'value',
  183. 'value' => $strQuoted,
  184. 'quoted'=> true
  185. );
  186. }
  187. continue 2;
  188. break;
  189. case '>':
  190. $litQuotes = null;
  191. $part[] = array(
  192. 'type' => 'value',
  193. 'value' => $strQuoted,
  194. 'quoted'=> false
  195. );
  196. continue 2;
  197. break;
  198. case '(':
  199. $parens = true;
  200. $bFunc1 = isset($part[0]['type']) && $part[0]['type'] === 'value';
  201. $bFunc2 = isset($tree['type']) && $tree['type'] === 'equation'
  202. && isset($tree['operand2']) && isset($tree['operand2']['value']);
  203. $part[] = $this->_parseConstraintTree(
  204. $nLevel + 1,
  205. $bFunc1 || $bFunc2
  206. );
  207. if ($bFunc1) {
  208. $tree['type'] = 'function';
  209. $tree['name'] = $part[0]['value'];
  210. Erfurt_Sparql_Parser::fixNegationInFuncName($tree);
  211. if (isset($part[1]['type'])) {
  212. $part[1] = array($part[1]);
  213. }
  214. $tree['parameter'] = $part[1];
  215. $part = array();
  216. } else if ($bFunc2) {
  217. $tree['operand2']['type'] = 'function';
  218. $tree['operand2']['name'] = $tree['operand2']['value'];
  219. Erfurt_Sparql_Parser::fixNegationInFuncName($tree['operand2']);
  220. $tree['operand2']['parameter'] = $part[0];
  221. unset($tree['operand2']['value']);
  222. unset($tree['operand2']['quoted']);
  223. $part = array();
  224. }
  225. if (current($this->_tokens) === ')') {
  226. if (substr(next($this->_tokens), 0, 2) === '_:') {
  227. // filter ends here
  228. prev($this->_tokens);
  229. break 2;
  230. } else {
  231. prev($this->_tokens);
  232. }
  233. }
  234. continue 2;
  235. break;
  236. case ' ':
  237. case "\t":
  238. continue 2;
  239. case '=':
  240. case '>':
  241. case '<':
  242. case '<=':
  243. case '>=':
  244. case '!=':
  245. case '&&':
  246. case '||':
  247. if (isset($tree['type']) && $tree['type'] === 'equation' && isset($tree['operand2'])) {
  248. //previous equation open
  249. $part = array($tree);
  250. } else if (isset($tree['type']) && $tree['type'] !== 'equation') {
  251. $part = array($tree);
  252. $tree = array();
  253. }
  254. $tree['type'] = 'equation';
  255. $tree['level'] = $nLevel;
  256. $tree['operator'] = $tok;
  257. $tree['operand1'] = $part[0];
  258. unset($tree['operand2']);
  259. $part = array();
  260. continue 2;
  261. break;
  262. case '!':
  263. if ($tree != array()) {
  264. require_once 'Erfurt/Sparql/ParserException.php';
  265. throw new Erfurt_Sparql_ParserException(
  266. 'Unexpected "!" negation in constraint.', -1, current($this->_tokens));
  267. }
  268. $tree['negated'] = true;
  269. continue 2;
  270. case ',':
  271. //parameter separator
  272. if (count($part) == 0 && !isset($tree['type'])) {
  273. throw new SparqlParserException(
  274. 'Unexpected comma'
  275. );
  276. }
  277. $bParameter = true;
  278. if (count($part) === 0) {
  279. $part[] = $tree;
  280. $tree = array();
  281. }
  282. continue 2;
  283. default:
  284. break;
  285. }
  286. if ($this->_varCheck($tok)) {
  287. if (!$parens && $nLevel === 0) {
  288. // Variables need parenthesizes first
  289. require_once 'Erfurt/Sparql/ParserException.php';
  290. throw new Erfurt_Sparql_ParserException('FILTER expressions that start with a variable need parenthesizes.', -1, current($this->_tokens));
  291. }
  292. $part[] = array(
  293. 'type' => 'value',
  294. 'value' => $tok,
  295. 'quoted' => false
  296. );
  297. } else if (substr($tok, 0, 2) === '_:') {
  298. // syntactic blank nodes not allowed in filter
  299. require_once 'Erfurt/Sparql/ParserException.php';
  300. throw new Erfurt_Sparql_ParserException('Syntactic Blanknodes not allowed in FILTER.', -1,
  301. current($this->_tokens));
  302. } else if (substr($tok, 0, 2) === '^^') {
  303. $part[count($part) - 1]['datatype'] = $this->_query->getFullUri(substr($tok, 2));
  304. } else if ($tok[0] === '@') {
  305. $part[count($part) - 1]['language'] = substr($tok, 1);
  306. } else if ($tok[0] === '<') {
  307. if ($tok[strlen($tok) - 1] === '>') {
  308. //single-tokenized <> uris
  309. $part[] = array(
  310. 'type' => 'value',
  311. 'value' => $tok,
  312. 'quoted' => false
  313. );
  314. } else {
  315. //iris split over several tokens
  316. $strQuoted = $tok;
  317. $litQuotes = true;
  318. }
  319. } else if ($tok === 'true' || $tok === 'false') {
  320. $part[] = array(
  321. 'type' => 'value',
  322. 'value' => $tok,
  323. 'quoted' => false,
  324. 'datatype' => 'http://www.w3.org/2001/XMLSchema#boolean'
  325. );
  326. } else {
  327. $part[] = array(
  328. 'type' => 'value',
  329. 'value' => $tok,
  330. 'quoted' => false
  331. );
  332. }
  333. if (isset($tree['type']) && $tree['type'] === 'equation' && isset($part[0])) {
  334. $tree['operand2'] = $part[0];
  335. Erfurt_Sparql_Parser::balanceTree($tree);
  336. $part = array();
  337. }
  338. }
  339. if (!isset($tree['type']) && $bParameter) {
  340. return $part;
  341. } else if (isset($tree['type']) && $tree['type'] === 'equation'
  342. && isset($tree['operand1']) && !isset($tree['operand2'])
  343. && isset($part[0])) {
  344. $tree['operand2'] = $part[0];
  345. Erfurt_Sparql_Parser::balanceTree($tree);
  346. }
  347. if ((count($tree) === 0) && (count($part) > 1)) {
  348. require_once 'Erfurt/Sparql/ParserException.php';
  349. throw new Erfurt_Sparql_ParserException('Failed to parse constraint.', -1, current($this->_tokens));
  350. }
  351. if (!isset($tree['type']) && isset($part[0])) {
  352. if (isset($tree['negated'])) {
  353. $part[0]['negated'] = true;
  354. }
  355. return $part[0];
  356. }
  357. return $tree;
  358. }
  359. protected function _varCheck($token)
  360. {
  361. if (isset($token[0]) && ($token{0} == '$' || $token{0} == '?')) {
  362. return true;
  363. }
  364. return false;
  365. }
  366. }