PageRenderTime 129ms CodeModel.GetById 54ms app.highlight 61ms RepoModel.GetById 6ms app.codeStats 1ms

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