source: Dev/branches/rest-dojo-ui/server/rdfapi/sparql/SparqlParser.php @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 50.0 KB
Line 
1<?php
2// ---------------------------------------------
3// Class: SparqlParser
4// ---------------------------------------------
5require_once RDFAPI_INCLUDE_DIR . 'model/Literal.php';
6require_once RDFAPI_INCLUDE_DIR . 'model/Resource.php';
7require_once RDFAPI_INCLUDE_DIR . 'sparql/Constraint.php';
8require_once RDFAPI_INCLUDE_DIR . 'sparql/Query.php';
9require_once RDFAPI_INCLUDE_DIR . 'sparql/QueryTriple.php';
10require_once RDFAPI_INCLUDE_DIR . 'sparql/SparqlParserException.php';
11
12/**
13* Parses a SPARQL Query string and returns a Query Object.
14*
15* @author   Tobias Gauss <tobias.gauss@web.de>
16* @author   Christian Weiske <cweiske@cweiske.de>
17* @version     $Id$
18* @license http://www.gnu.org/licenses/lgpl.html LGPL
19*
20* @package sparql
21*/
22class SparqlParser extends Object
23{
24
25    /**
26    * The query Object
27    * @var Query
28    */
29    protected $query;
30
31    /**
32    * The Querystring
33    * @var string
34    */
35    protected $queryString;
36
37    /**
38    * The tokenized Query
39    * @var array
40    */
41    protected $tokens = array();
42
43    /**
44    * Last parsed graphPattern
45    * @var int
46    */
47    protected $tmp;
48
49    /**
50    * Operators introduced by sparql
51    * @var array
52    */
53    protected static $sops = array(
54        'regex',
55        'bound',
56        'isuri',
57        'isblank',
58        'isliteral',
59        'str',
60        'lang',
61        'datatype',
62        'langmatches'
63    );
64
65    /**
66    *   Which order operators are to be treated.
67    *   (11.3 Operator Mapping)
68    *   @var array
69    */
70    protected static $operatorPrecedence = array(
71        '||'    => 0,
72        '&&'    => 1,
73        '='     => 2,
74        '!='    => 3,
75        '<'     => 4,
76        '>'     => 5,
77        '<='    => 6,
78        '>='    => 7,
79        '*'     => 0,
80        '/'     => 0,
81        '+'     => 0,
82        '-'     => 0,
83    );
84
85
86
87    /**
88    * Constructor of SparqlParser
89    */
90    public function SparqlParser()
91    {
92    }
93
94
95
96    /**
97    * Main function of SparqlParser. Parses a query string.
98    *
99    * @param  String $queryString The SPARQL query
100    * @return Query  The query object
101    * @throws SparqlParserException
102    */
103    public function parse($queryString = false)
104    {
105        $this->prepare();
106
107        if ($queryString) {
108            $this->query->setQueryString($queryString);
109            $uncommentedQuery  = $this->uncomment($queryString);
110            $this->queryString = $uncommentedQuery;
111            $this->tokens      = self::tokenize($uncommentedQuery);
112            $this->parseQuery();
113            if (!$this->query->isComplete()) {
114                throw new SparqlParserException(
115                    "Query is incomplete.",
116                    null,
117                    $queryString
118                );
119            }
120        } else {
121            throw new SparqlParserException(
122                "Querystring is empty.",
123                null,
124                key($this->tokens)
125            );
126            $this->query->isEmpty = true;
127        }
128        return $this->query;
129    }//public function parse($queryString = false)
130
131
132
133    /**
134    *   Set all internal variables to a clear state
135    *   before we start parsing.
136    */
137    protected function prepare()
138    {
139        $this->query          = new Query();
140        $this->queryString    = null;
141        $this->tokens         = array();
142        $this->tmp            = null;
143        // add the default prefixes defined in constants.php
144        global $default_prefixes;
145        $this->query->prefixes = $default_prefixes;
146    }//protected function prepare()
147
148
149
150    /**
151    * Tokenizes the query string into $tokens.
152    * The query may not contain any comments.
153    *
154    * @param  string $queryString Query to split into tokens
155    *
156    * @return array Tokens
157    */
158    public static function tokenize($queryString)
159    {
160        $queryString  = trim($queryString);
161        $specialChars = array(' ', "\t", "\r", "\n", ',', '\\', '(', ')','{','}','"',"'",';','[',']');
162        $len          = strlen($queryString);
163        $tokens       = array('');
164        $n            = 0;
165
166        for ($i = 0; $i < $len; ++$i) {
167            if (!in_array($queryString{$i}, $specialChars)) {
168                $tokens[$n] .= $queryString{$i};
169            } else {
170                if ($tokens[$n] != '') {
171                    ++$n;
172                    if (!isset($tokens[$n])) {
173                        $tokens[$n] = '';
174                    }
175                }
176                if ($queryString{$i} == "'" && $n > 1
177                  && $tokens[$n - 2] == "'" && $tokens[$n - 1] == "'"
178                ) {
179                    //special ''' quotation
180                    $tokens[$n - 2] = "'''";
181                    $tokens[$n - 1] = '';
182                    unset($tokens[$n]);
183                    --$n;
184                    continue;
185                } else if ($queryString{$i} == '"' && $n > 1
186                  && $tokens[$n - 2] == '"' && $tokens[$n - 1] == '"'
187                ) {
188                    //special """ quotation
189                    $tokens[$n - 2] = '"""';
190                    $tokens[$n - 1] = '';
191                    unset($tokens[$n]);
192                    --$n;
193                    continue;
194                } else if ($queryString{$i} == '\\') {
195                    $tokens[$n] .= substr($queryString, $i, 2);
196                    ++$i;
197                    continue;
198                }
199                $tokens[$n] = $queryString{$i};
200                $tokens[++$n] = '';
201            }
202        }
203//var_dump($tokens);
204        return $tokens;
205    }//public static function tokenize($queryString)
206
207
208
209    /**
210    * Removes comments in the query string. Comments are
211    * indicated by '#'.
212    *
213    * @param  String $queryString
214    * @return String The uncommented query string
215    */
216    protected function uncomment($queryString)
217    {
218        $regex ="/((\"[^\"]*\")|(\'[^\']*\')|(\<[^\>]*\>))|(#.*)/";
219        return preg_replace($regex,'\1',$queryString);
220    }//protected function uncomment($queryString)
221
222
223
224    /**
225    * Starts parsing the tokenized SPARQL Query.
226    *
227    * @return void
228    */
229    protected function parseQuery()
230    {
231        do {
232            switch (strtolower(current($this->tokens))) {
233                case "base":
234                    $this->parseBase();
235                    break;
236                case "prefix":
237                    $this->parsePrefix();
238                    break;
239                case "select":
240                    $this->parseSelect();
241                    break;
242                case "describe":
243                    $this->parseDescribe();
244                    break;
245                case "ask":
246                    $this->parseAsk('ask');
247                    break;
248                case "count":
249                    $this->parseAsk('count');
250                    break;
251                case "from":
252                    $this->parseFrom();
253                    break;
254                case "construct":
255                    $this->parseConstruct();
256                    break;
257                case "where":
258                    $this->parseWhere();
259                    $this->parseModifier();
260                    break;
261                case "{":
262                    prev($this->tokens);
263                    $this->parseWhere();
264                    $this->parseModifier();
265                    break;
266            }
267        } while (next($this->tokens));
268
269    }//protected function parseQuery()
270
271
272
273    /**
274    * Parses the BASE part of the query.
275    *
276    * @return void
277    * @throws SparqlParserException
278    */
279    protected function parseBase()
280    {
281        $this->_fastForward();
282        if ($this->iriCheck(current($this->tokens))) {
283            $this->query->setBase(current($this->tokens));
284        } else {
285            $msg = current($this->tokens);
286            $msg = preg_replace('/</', '&lt;', $msg);
287            throw new SparqlParserException(
288                "IRI expected",
289                null,
290                key($this->tokens)
291            );
292        }
293    }
294
295
296
297    /**
298    * Adds a new namespace prefix to the query object.
299    *
300    * @return void
301    * @throws SparqlParserException
302    */
303    protected function parsePrefix()
304    {
305        $this->_fastForward();
306        $prefix = substr(current($this->tokens), 0, -1);
307        $this->_fastForward();
308        if ($this->iriCheck(current($this->tokens))) {
309            $uri = substr(current($this->tokens), 1, -1);
310            $this->query->addPrefix($prefix, $uri);
311        } else {
312            $msg = current($this->tokens);
313            $msg = preg_replace('/</', '&lt;', $msg);
314            throw new SparqlParserException(
315                "IRI expected",
316                null,
317                key($this->tokens)
318            );
319        }
320    }
321
322
323
324    /**
325    * Parses the SELECT part of a query.
326    *
327    * @return void
328    * @throws SparqlParserException
329    */
330    protected function parseSelect()
331    {
332        $this->_fastForward();
333        $curLow = strtolower(current($this->tokens));
334        prev($this->tokens);
335        if ($curLow == 'distinct') {
336            $this->query->setResultForm('select distinct');
337        } else {
338            $this->query->setResultForm('select');
339        }
340
341        $currentVar = null;
342        $currentFunc = null;
343        $bWaitForRenaming = false;
344        while ($curLow != 'from' && $curLow != 'where' &&
345               $curLow != "{"
346        ){
347            $this->_fastForward();
348            $curTok = current($this->tokens);
349            $curLow = strtolower($curTok);
350
351            if ($this->varCheck($curTok) || $curLow == '*') {
352                if ($bWaitForRenaming) {
353                    $bWaitForRenaming = false;
354                    $currentVar->setAlias($curTok);
355                    if ($currentFunc != null) {
356                        $currentVar->setFunc($currentFunc);
357                    }
358                    $this->query->addResultVar($currentVar);
359                    $currentVar = null;
360                } else {
361                    if ($currentVar != null) {
362                        $this->query->addResultVar($currentVar);
363                        $currentVar = null;
364                    }
365                    $currentVar = new Query_ResultVariable($curTok);
366                    if ($currentFunc != null) {
367                        $currentVar->setFunc($currentFunc);
368                    }
369                }
370                $currentFunc = null;
371            } else if ($curLow == 'as') {
372                if ($currentVar === null) {
373                    throw new SparqlParserException(
374                        'AS requires a variable left and right',
375                        null,
376                        key($this->tokens)
377                    );
378                }
379                $bWaitForRenaming = true;
380            } else if (in_array($curLow, self::$sops)) {
381                $currentFunc = $curLow;
382            }
383
384            if (!current($this->tokens)) {
385                throw new SparqlParserException(
386                    "Unexpected end of File.",
387                    null,
388                    key($this->tokens)
389                );
390            }
391        }
392
393        if ($currentVar != null) {
394            $this->query->addResultVar($currentVar);
395        }
396        prev($this->tokens);
397
398        if (count($this->query->getResultVars()) == 0) {
399            throw new SparqlParserException(
400                "Variable or '*' expected.",
401                null,
402                key($this->tokens)
403            );
404        }
405    }//protected function parseSelect()
406
407
408    /**
409    * Adds a new variable to the query and sets result form to 'DESCRIBE'.
410    *
411    * @return void
412    */
413    protected function parseDescribe()
414    {
415        while(strtolower(current($this->tokens))!='from'& strtolower(current($this->tokens))!='where'){
416            $this->_fastForward();
417            if($this->varCheck(current($this->tokens))|$this->iriCheck(current($this->tokens))){
418                $this->query->addResultVar(current($this->tokens));
419                if(!$this->query->getResultForm())
420                    $this->query->setResultForm('describe');
421            }
422            if(!current($this->tokens))
423            break;
424        }
425        prev($this->tokens);
426    }
427
428    /**
429    * Sets result form to 'ASK' and 'COUNT'.
430    *
431    * @param string $form  if it's an ASK or COUNT query
432    * @return void
433    */
434    protected function parseAsk($form){
435        $this->query->setResultForm($form);
436        $this->_fastForward();
437        if(current($this->tokens)=="{")
438            $this->_rewind();
439        $this->parseWhere();
440        $this->parseModifier();
441    }
442
443    /**
444    * Parses the FROM clause.
445    *
446    * @return void
447    * @throws SparqlParserException
448    */
449    protected function parseFrom(){
450        $this->_fastForward();
451        if(strtolower(current($this->tokens))!='named'){
452            if($this->iriCheck(current($this->tokens))||$this->qnameCheck(current($this->tokens))){
453                $this->query->addFrom(new Resource(substr(current($this->tokens),1,-1)));
454            }else if($this->varCheck(current($this->tokens))){
455                $this->query->addFrom(current($this->tokens));
456            }else{
457                throw new SparqlParserException("Variable, Iri or qname expected in FROM ",null,key($this->tokens));
458            }
459            $this->query->addFrom(current($this->tokens));
460        }else{
461            $this->_fastForward();
462            if($this->iriCheck(current($this->tokens))||$this->qnameCheck(current($this->tokens))){
463                $this->query->addFromNamed(new Resource(substr(current($this->tokens),1,-1)));
464            }else if($this->varCheck(current($this->tokens))){
465                $this->query->addFromNamed(current($this->tokens));
466            }else{
467                throw new SparqlParserException("Variable, Iri or qname expected in FROM NAMED ",null,key($this->tokens));
468            }
469        }
470    }
471
472
473    /**
474    * Parses the CONSTRUCT clause.
475    *
476    * @return void
477    * @throws SparqlParserException
478    */
479    protected function parseConstruct(){
480        $this->_fastForward();
481        $this->query->setResultForm('construct');
482        if(current($this->tokens)=="{"){
483            $this->parseGraphPattern(false,false,false,true);
484        }else{
485            throw new SparqlParserException("Unable to parse CONSTRUCT part. '{' expected. ",null,key($this->tokens));
486        }
487        $this->parseWhere();
488        $this->parseModifier();
489    }
490
491
492    /**
493    * Parses the WHERE clause.
494    *
495    * @return void
496    * @throws SparqlParserException
497    */
498    protected function parseWhere(){
499        $this->_fastForward();
500        if(current($this->tokens)=="{"){
501            $this->parseGraphPattern();
502        }else{
503            throw new SparqlParserException("Unable to parse WHERE part. '{' expected in Query. ",null,key($this->tokens));
504        }
505    }
506
507
508
509    /**
510    * Checks if $token is a variable.
511    *
512    * @param  String  $token The token
513    * @return boolean TRUE if the token is a variable false if not
514    */
515    protected function varCheck($token)
516    {
517        if (isset($token[0]) && ($token{0} == '$' || $token{0} == '?')) {
518            $this->query->addUsedVar($token);
519            return true;
520        }
521        return false;
522    }
523
524    /**
525    * Checks if $token is an IRI.
526    *
527    * @param  String  $token The token
528    * @return boolean TRUE if the token is an IRI false if not
529    */
530    protected function iriCheck($token){
531        $pattern="/^<[^>]*>\.?$/";
532        if(preg_match($pattern,$token)>0)
533        return true;
534        return false;
535    }
536
537
538    /**
539    * Checks if $token is a Blanknode.
540    *
541    * @param  String  $token The token
542    * @return boolean TRUE if the token is BNode false if not
543    */
544    protected function bNodeCheck($token){
545        if($token{0} == "_")
546        return true;
547        else
548        return false;
549    }
550
551
552    /**
553    * Checks if $token is a qname.
554    *
555    * @param  String  $token The token
556    * @return boolean TRUE if the token is a qname false if not
557    * @throws SparqlParserException
558    */
559    protected function qnameCheck($token)
560    {
561        $pattern="/^([^:^\<]*):([^:]*)$/";
562        if (preg_match($pattern,$token,$hits)>0) {
563            $prefs = $this->query->getPrefixes();
564            if (isset($prefs{$hits{1}})) {
565                return true;
566            }
567            if ($hits{1} == "_") {
568                return true;
569            }
570            throw new SparqlParserException("Unbound Prefix: <i>".$hits{1}."</i>",null,key($this->tokens));
571        } else {
572            return false;
573        }
574    }
575
576
577
578    /**
579    * Checks if $token is a Literal.
580    *
581    * @param string $token The token
582    *
583    * @return boolean TRUE if the token is a Literal false if not
584    */
585    protected function literalCheck($token)
586    {
587        $pattern = "/^[\"\'].*$/";
588        if (preg_match($pattern,$token) > 0) {
589            return true;
590        }
591        return false;
592    }//protected function literalCheck($token)
593
594
595
596    /**
597    * FastForward until next token which is not blank.
598    *
599    * @return void
600    */
601    protected function _fastForward()
602    {
603        next($this->tokens);
604        while(current($this->tokens)==" "|current($this->tokens)==chr(10)|current($this->tokens)==chr(13)|current($this->tokens)==chr(9)){
605            next($this->tokens);
606        }
607    }//protected function _fastForward()
608
609
610
611    /**
612    * Rewind until next token which is not blank.
613    *
614    * @return void
615    */
616    protected function _rewind()
617    {
618        prev($this->tokens);
619        while(current($this->tokens)==" "|current($this->tokens)==chr(10)|current($this->tokens)==chr(13)|current($this->tokens)==chr(9)){
620            prev($this->tokens);
621        }
622        return;
623    }//protected function _rewind()
624
625
626
627    /**
628    * Parses a graph pattern.
629    *
630    * @param  int     $optional Optional graph pattern
631    * @param  int     $union    Union graph pattern
632    * @param  string  $graph    Graphname
633    * @param  boolean $constr   TRUE if the pattern is a construct pattern
634    * @param  boolean $external If the parsed pattern shall be returned
635    * @param  int     $subpattern If the new pattern is subpattern of the
636    *                               pattern with the given id
637    * @return void
638    */
639    protected function parseGraphPattern(
640      $optional = false, $union    = false, $graph = false,
641      $constr   = false, $external = false, $subpattern = false
642    ){
643        $pattern = $this->query->getNewPattern($constr);
644        if (is_int($optional)) {
645            $pattern->setOptional($optional);
646        } else {
647            $this->tmp = $pattern->getId();
648        }
649        if (is_int($union)) {
650            $pattern->setUnion($union);
651        }
652        if (is_int($subpattern)) {
653            $pattern->setSubpatternOf($subpattern);
654        }
655        if ($graph != false) {
656            $pattern->setGraphname($graph);
657        }
658
659        $this->_fastForward();
660
661        do {
662            switch (strtolower(current($this->tokens))) {
663                case "graph":
664                    $this->parseGraph();
665                    break;
666                case "union":
667                    $this->_fastForward();
668                    $this->parseGraphPattern(
669                        false, $this->tmp, false, false, false, $subpattern
670                    );
671                    break;
672                case "optional":
673                    $this->_fastForward();
674                    $this->parseGraphPattern(
675                        $this->tmp, false, false, false, false, $subpattern
676                    );
677                    break;
678                case "filter":
679                    $this->parseConstraint(
680                        $pattern, true, false, false, false, $subpattern
681                    );
682                    $this->_fastForward();
683                    break;
684                case ".":
685                    $this->_fastForward();
686                    break;
687                case "{":
688                    if (!is_int($subpattern)) {
689                        $subpattern = $pattern->getId();
690                    }
691
692                    $this->parseGraphPattern(
693                        false, false, false, false, false, $subpattern
694                    );
695                    break;
696                case "}":
697                    $pattern->open = false;
698                    break;
699                default:
700                    $this->parseTriplePattern($pattern);
701                    break;
702            }
703        } while ($pattern->open);
704
705        if ($external) {
706            return $pattern;
707        }
708        $this->_fastForward();
709    }
710
711    /**
712    * Parses a triple pattern.
713    *
714    * @param  GraphPattern $pattern
715    * @return void
716    */
717    protected function parseTriplePattern(&$pattern)
718    {
719        $trp      = array();
720        $prev     = false;
721        $prevPred = false;
722        $cont     = true;
723        $sub      = "";
724        $pre      = "";
725        $tmp      = "";
726        $tmpPred  = "";
727        $obj      = "";
728        do {
729//echo strtolower(current($this->tokens)) . "\n";
730            switch (strtolower(current($this->tokens))) {
731                case false:
732                    $cont          = false;
733                    $pattern->open = false;
734                    break;
735                case "filter":
736                    $this->parseConstraint($pattern,false);
737                    $this->_fastForward();
738                    break;
739                case "optional":
740                    $this->_fastForward();
741                    $this->parseGraphPattern($pattern->getId(),false);
742                    $cont = false;
743                    break;
744                case "union":
745                    $this->_fastForward();
746                    $this->parseGraphPattern(
747                        false, $this->tmp, false, false, false, $pattern->getId()
748                    );
749                    break;
750                case ";":
751                    $prev = true;
752                    $this->_fastForward();
753                    break;
754                case ".":
755                    $prev = false;
756                    $this->_fastForward();
757                    break;
758                case "graph":
759                    $this->parseGraph();
760                    break;
761                case ",":
762                    $prev     = true;
763                    $prevPred = true;
764                    $this->_fastForward();
765                    break;
766                case "}":
767                    $prev = false;
768                    $pattern->open = false;
769                    $cont = false;
770                    break;
771                case '{':
772                    //subpatterns opens
773                    $this->parseGraphPattern(
774                        false, false, false, false, false, $pattern->getId()
775                    );
776                    break;
777                case "[":
778                    $prev = true;
779                    $tmp  = $this->parseNode($this->query->getBlanknodeLabel());
780                    $this->_fastForward();
781                    break;
782                case "]":
783                    $prev = true;
784                    $this->_fastForward();
785                    break;
786                case "(":
787                    $prev = true;
788                    $tmp = $this->parseCollection($trp);
789                    $this->_fastForward();
790                    break;
791                case false:
792                    $cont = false;
793                    $pattern->open = false;
794                    break;
795                default:
796                    if ($prev) {
797                        $sub = $tmp;
798                    } else {
799                        $sub = $this->parseNode();
800                        $this->_fastForward();
801                        $tmp     = $sub;
802                    }
803                    if ($prevPred) {
804                        $pre = $tmpPred;
805                    } else {
806                        $pre = $this->parseNode();
807                        $this->_fastForward();
808                        $tmpPred = $pre;
809                    }
810                    if (current($this->tokens)=="[") {
811                        $tmp  = $this->parseNode($this->query->getBlanknodeLabel());
812                        $prev = true;
813                        $obj = $tmp;
814                    } else if (current($this->tokens)=="(") {
815                        $obj = $this->parseCollection($trp);
816                    } else {
817                        $obj = $this->parseNode();
818                    }
819                    $trp[] = new QueryTriple($sub,$pre,$obj);
820                    $this->_fastForward();
821                    break;
822
823            }
824        } while ($cont);
825
826        if (count($trp) > 0) {
827            $pattern->addTriplePatterns($trp);
828        }
829    }
830
831
832
833    /**
834    * Parses a value constraint.
835    *
836    * @param GraphPattern $pattern
837    * @param boolean $outer     If the constraint is an outer one.
838    * @return void
839    */
840    protected function parseConstraint(&$pattern, $outer)
841    {
842        $constraint = new Constraint();
843        $constraint->setOuterFilter($outer);
844        $this->_fastForward();
845        $this->_rewind();
846        $nBeginKey = key($this->tokens);
847        $constraint->setTree(
848            $t = $this->parseConstraintTree()
849        );
850
851        $nEndKey = key($this->tokens);
852        if (current($this->tokens) == '}') {
853            prev($this->tokens);
854        }
855
856        //for backwards compatibility with the normal sparql engine
857        // which does not use the tree array currently
858        $expression = trim(implode(
859            '',
860            array_slice(
861                    $this->tokens,
862                    $nBeginKey + 1,
863                    $nEndKey - $nBeginKey - 1
864            )
865        ));
866        if ($expression[0] == '(' && substr($expression, -1) == ')') {
867            $expression = trim(substr($expression, 1, -1));
868        }
869        $constraint->addExpression($expression);
870
871        $pattern->addConstraint($constraint);
872    }//protected function parseConstraint(&$pattern, $outer)
873
874
875
876    /**
877    *   Parses a constraint string recursively.
878    *
879    *   The result array is one "element" which may contain subelements.
880    *   All elements have one key "type" that determines which other
881    *   array keys the element array has. Valid types are:
882    *   - "value":
883    *       Just a plain value with a value key, nothing else
884    *   - "function"
885    *       A function has a name and an array of parameter(s). Each parameter
886    *       is an element.
887    *   - "equation"
888    *       An equation has an operator, and operand1 and operand2 which
889    *       are elements themselves
890    *   Any element may have the "negated" value set to true, which means
891    *   that is is - negated (!).
892    *
893    *   @internal The functionality of this method is being unit-tested
894    *   in testSparqlParserTests::testParseFilter()
895    *   "equation'-elements have another key "level" which is to be used
896    *   internally only.
897    *
898    *   @return array Nested tree array representing the filter
899    */
900    protected function parseConstraintTree($nLevel = 0, $bParameter = false)
901    {
902        $tree       = array();
903        $part       = array();
904        $chQuotes   = null;
905        $litQuotes  = null;
906        $strQuoted  = '';
907
908        while ($tok = next($this->tokens)) {
909//var_dump(array($tok, $tok[strlen($tok) - 1]));
910            if ($chQuotes !== null && $tok != $chQuotes) {
911                $strQuoted .= $tok;
912                continue;
913            } else if ($litQuotes !== null) {
914                $strQuoted .= $tok;
915                if ($tok[strlen($tok) - 1] == '>') {
916                    $tok = '>';
917                } else {
918                    continue;
919                }
920            } else if ($tok == ')' || $tok == '}' || $tok == '.') {
921                break;
922            }
923
924            switch ($tok) {
925                case '"':
926                case '\'':
927                    if ($chQuotes === null) {
928                        $chQuotes  = $tok;
929                        $strQuoted = '';
930                    } else {
931                        $chQuotes = null;
932                        $part[] = array(
933                            'type'  => 'value',
934                            'value' => $strQuoted,
935                            'quoted'=> true
936                        );
937                    }
938                    continue 2;
939                    break;
940
941                case '>':
942                    $litQuotes = null;
943                    $part[] = array(
944                        'type'  => 'value',
945                        'value' => $strQuoted,
946                        'quoted'=> false
947                    );
948                    continue 2;
949                    break;
950
951                case '(':
952                    $bFunc1 = isset($part[0]['type']) && $part[0]['type'] == 'value';
953                    $bFunc2 = isset($tree['type'])    && $tree['type']    == 'equation'
954                           && isset($tree['operand2']) && isset($tree['operand2']['value']);
955                    $part[] = $this->parseConstraintTree(
956                        $nLevel + 1,
957                        $bFunc1 || $bFunc2
958                    );
959
960                    if ($bFunc1) {
961                        $tree['type']       = 'function';
962                        $tree['name']       = $part[0]['value'];
963                        self::fixNegationInFuncName($tree);
964                        if (isset($part[1]['type'])) {
965                            $part[1] = array($part[1]);
966                        }
967                        $tree['parameter']  = $part[1];
968                        $part = array();
969                    } else if ($bFunc2) {
970                        $tree['operand2']['type']       = 'function';
971                        $tree['operand2']['name']       = $tree['operand2']['value'];
972                        self::fixNegationInFuncName($tree['operand2']);
973                        $tree['operand2']['parameter']  = $part[0];
974                        unset($tree['operand2']['value']);
975                        unset($tree['operand2']['quoted']);
976                        $part = array();
977                    }
978                    continue 2;
979                    break;
980
981                case ' ':
982                case "\t":
983                    continue 2;
984
985                case '=':
986                case '>':
987                case '<':
988                case '<=':
989                case '>=':
990                case '!=':
991                case '&&':
992                case '||':
993                    if (isset($tree['type']) && $tree['type'] == 'equation'
994                        && isset($tree['operand2'])) {
995                        //previous equation open
996                        $part = array($tree);
997                    } else if (isset($tree['type']) && $tree['type'] != 'equation') {
998                        $part = array($tree);
999                        $tree = array();
1000                    }
1001                    $tree['type']       = 'equation';
1002                    $tree['level']      = $nLevel;
1003                    $tree['operator']   = $tok;
1004                    $tree['operand1']   = $part[0];
1005                    unset($tree['operand2']);
1006                    $part = array();
1007                    continue 2;
1008                    break;
1009
1010                case '!':
1011                    if ($tree != array()) {
1012                        throw new SparqlParserException(
1013                            'Unexpected "!" negation in constraint.'
1014                        );
1015                    }
1016                    $tree['negated'] = true;
1017                    continue 2;
1018
1019                case ',':
1020                    //parameter separator
1021                    if (count($part) == 0 && !isset($tree['type'])) {
1022                        throw new SparqlParserException(
1023                            'Unexpected comma'
1024                        );
1025                    }
1026                    $bParameter = true;
1027                    if (count($part) == 0) {
1028                        $part[] = $tree;
1029                        $tree = array();
1030                    }
1031                    continue 2;
1032
1033                default:
1034                    break;
1035            }
1036
1037            if ($this->varCheck($tok)) {
1038                $part[] = array(
1039                    'type'      => 'value',
1040                    'value'     => $tok,
1041                    'quoted'    => false
1042                );
1043            } else if (substr($tok, 0, 2) == '^^') {
1044                $part[count($part) - 1]['datatype']
1045                    = $this->query->getFullUri(substr($tok, 2));
1046            } else if ($tok[0] == '@') {
1047                $part[count($part) - 1]['language'] = substr($tok, 1);
1048            } else if ($tok[0] == '<') {
1049                if ($tok[strlen($tok) - 1] == '>') {
1050                    //single-tokenized <> uris
1051                    $part[] = array(
1052                        'type'      => 'value',
1053                        'value'     => $tok,
1054                        'quoted'    => false
1055                    );
1056                } else {
1057                    //iris split over several tokens
1058                    $strQuoted = $tok;
1059                    $litQuotes = true;
1060                }
1061            } else if ($tok == 'true' || $tok == 'false') {
1062                $part[] = array(
1063                    'type'      => 'value',
1064                    'value'     => $tok,
1065                    'quoted'    => false,
1066                    'datatype'  => 'http://www.w3.org/2001/XMLSchema#boolean'
1067                );
1068            } else {
1069                $part[] = array(
1070                    'type'      => 'value',
1071                    'value'     => $tok,
1072                    'quoted'    => false
1073                );
1074            }
1075
1076            if (isset($tree['type']) && $tree['type'] == 'equation' && isset($part[0])) {
1077                $tree['operand2'] = $part[0];
1078                self::balanceTree($tree);
1079                $part = array();
1080            }
1081        }
1082
1083        if (!isset($tree['type']) && $bParameter) {
1084            return $part;
1085        } else if (isset($tree['type']) && $tree['type'] == 'equation'
1086            && isset($tree['operand1']) && !isset($tree['operand2'])
1087            && isset($part[0])) {
1088            $tree['operand2'] = $part[0];
1089            self::balanceTree($tree);
1090        }
1091
1092        if (!isset($tree['type']) && isset($part[0])) {
1093            if (isset($tree['negated'])) {
1094                $part[0]['negated'] = true;
1095            }
1096            return $part[0];
1097        }
1098
1099        return $tree;
1100    }//protected function parseConstraintTree($nLevel = 0, $bParameter = false)
1101
1102
1103
1104    /**
1105    *   "Balances" the filter tree in the way that operators on the same
1106    *   level are nested according to their precedence defined in
1107    *   $operatorPrecedence array.
1108    *
1109    *   @param array $tree  Tree to be modified
1110    */
1111    protected static function balanceTree(&$tree)
1112    {
1113        if (
1114            isset($tree['type']) && $tree['type'] == 'equation'
1115         && isset($tree['operand1']['type']) && $tree['operand1']['type'] == 'equation'
1116         && $tree['level'] == $tree['operand1']['level']
1117         && self::$operatorPrecedence[$tree['operator']] > self::$operatorPrecedence[$tree['operand1']['operator']]
1118        ) {
1119            $op2 = array(
1120                'type'      => 'equation',
1121                'level'     => $tree['level'],
1122                'operator'  => $tree['operator'],
1123                'operand1'  => $tree['operand1']['operand2'],
1124                'operand2'  => $tree['operand2']
1125            );
1126            $tree['operator']   = $tree['operand1']['operator'];
1127            $tree['operand1']   = $tree['operand1']['operand1'];
1128            $tree['operand2']   = $op2;
1129        }
1130    }//protected static function balanceTree(&$tree)
1131
1132
1133
1134    protected static function fixNegationInFuncName(&$tree)
1135    {
1136        if ($tree['type'] == 'function' && $tree['name'][0] == '!') {
1137            $tree['name'] = substr($tree['name'], 1);
1138            if (!isset($tree['negated'])) {
1139                $tree['negated'] = true;
1140            } else {
1141                unset($tree['negated']);
1142            }
1143            //perhaps more !!
1144            self::fixNegationInFuncName($tree);
1145        }
1146    }//protected static function fixNegationInFuncName(&$tree)
1147
1148
1149
1150    /**
1151    * Parses a bracketted expression.
1152    *
1153    * @param  Constraint $constraint
1154    * @return void
1155    * @throws SparqlParserException
1156    */
1157    protected function parseBrackettedExpression(&$constraint)
1158    {
1159        $open = 1;
1160        $exp = "";
1161        $this->_fastForward();
1162        while ($open != 0 && current($this->tokens)!= false) {
1163            switch (current($this->tokens)) {
1164                case "(":
1165                    $open++;
1166                    $exp = $exp . current($this->tokens);
1167                    break;
1168                case ")":
1169                    $open--;
1170                    if($open != 0){
1171                        $exp = $exp . current($this->tokens);
1172                    }
1173                    break;
1174                case false:
1175                    throw new SparqlParserException(
1176                        "Unexpected end of query.",
1177                        null,
1178                        key($this->tokens)
1179                    );
1180                default:
1181                    $exp = $exp . current($this->tokens);
1182                    break;
1183            }
1184            next($this->tokens);
1185        }
1186        $constraint->addExpression($exp);
1187    }
1188
1189
1190    /**
1191    * Parses an expression.
1192    *
1193    * @param  Constraint  $constrain
1194    * @return void
1195    * @throws SparqlParserException
1196    */
1197    protected function parseExpression(&$constraint)
1198    {
1199        $exp = "";
1200        while (current($this->tokens) != false && current($this->tokens) != "}") {
1201            switch (current($this->tokens)) {
1202                case false:
1203                    throw new SparqlParserException(
1204                        "Unexpected end of query.",
1205                        null,
1206                        key($this->tokens)
1207                    );
1208                case ".":
1209                    break;
1210                    break;
1211                default:
1212                    $exp = $exp . current($this->tokens);
1213                    break;
1214            }
1215            next($this->tokens);
1216        }
1217        $constraint->addExpression($exp);
1218    }
1219
1220    /**
1221    * Parses a GRAPH clause.
1222    *
1223    * @param  GraphPattern $pattern
1224    * @return void
1225    * @throws SparqlParserException
1226    */
1227    protected function parseGraph(){
1228        $this->_fastForward();
1229        $name = current($this->tokens);
1230        if(!$this->varCheck($name)&!$this->iriCheck($name)&&!$this->qnameCheck($name)){
1231            $msg = $name;
1232            $msg = preg_replace('/</', '&lt;', $msg);
1233            throw new SparqlParserException(" IRI or Var expected. ",null,key($this->tokens));
1234        }
1235        $this->_fastForward();
1236
1237        if($this->iriCheck($name)){
1238            $name = new Resource(substr($name,1,-1));
1239        }else if($this->qnameCheck($name)){
1240            $name = new Resource($this->query->getFullUri($name));
1241        }
1242        $this->parseGraphPattern(false,false,$name);
1243        if(current($this->tokens)=='.')
1244        $this->_fastForward();
1245    }
1246
1247    /**
1248    * Parses the solution modifiers of a query.
1249    *
1250    * @return void
1251    * @throws SparqlParserException
1252    */
1253    protected function parseModifier(){
1254        do{
1255            switch(strtolower(current($this->tokens))){
1256                case "order":
1257                $this->_fastForward();
1258                if(strtolower(current($this->tokens))=='by'){
1259                    $this->_fastForward();
1260                    $this->parseOrderCondition();
1261                }else{
1262                    throw new SparqlParserException("'BY' expected.",null,key($this->tokens));
1263                }
1264                break;
1265                case "limit":
1266                $this->_fastForward();
1267                $val = current($this->tokens);
1268                $this->query->setSolutionModifier('limit',$val);
1269                break;
1270                case "offset":
1271                $this->_fastForward();
1272                $val = current($this->tokens);
1273                $this->query->setSolutionModifier('offset',$val);
1274                break;
1275                default:
1276                break;
1277            }
1278        }while(next($this->tokens));
1279    }
1280
1281    /**
1282    * Parses order conditions of a query.
1283    *
1284    * @return void
1285    * @throws SparqlParserException
1286    */
1287    protected function parseOrderCondition(){
1288        $valList = array();
1289        $val = array();
1290        while(strtolower(current($this->tokens))!='limit'
1291        & strtolower(current($this->tokens))!= false
1292        & strtolower(current($this->tokens))!= 'offset'){
1293            switch (strtolower(current($this->tokens))){
1294                case "desc":
1295                $this->_fastForward();
1296                $this->_fastForward();
1297                if($this->varCheck(current($this->tokens))){
1298                    $val['val'] = current($this->tokens);
1299                }else{
1300                    throw new SparqlParserException("Variable expected in ORDER BY clause. ",null,key($this->tokens));
1301                }
1302                $this->_fastForward();
1303                if(current($this->tokens)!=')')
1304                throw new SparqlParserException("missing ')' in ORDER BY clause.",null,key($this->tokens));
1305                $val['type'] = 'desc';
1306                $this->_fastForward();
1307                break;
1308                case "asc" :
1309                $this->_fastForward();
1310                $this->_fastForward();
1311                if($this->varCheck(current($this->tokens))){
1312                    $val['val'] = current($this->tokens);
1313                }else{
1314                    throw new SparqlParserException("Variable expected in ORDER BY clause. ",null,key($this->tokens));
1315                }
1316                $this->_fastForward();
1317                if(current($this->tokens)!=')')
1318                throw new SparqlParserException("missing ')' in ORDER BY clause.",null,key($this->tokens));
1319                $val['type'] = 'asc';
1320                $this->_fastForward();
1321                break;
1322                default:
1323                if($this->varCheck(current($this->tokens))){
1324                    $val['val'] = current($this->tokens);
1325                    $val['type'] = 'asc';
1326                }else{
1327                    throw new SparqlParserException("Variable expected in ORDER BY clause. ",null,key($this->tokens));
1328                }
1329                $this->_fastForward();
1330                break;
1331            }
1332            $valList[] = $val;
1333        }
1334        prev($this->tokens);
1335        $this->query->setSolutionModifier('order by',$valList);
1336    }
1337
1338    /**
1339    * Parses a String to an RDF node.
1340    *
1341    * @param  String $node
1342    *
1343    * @return Node   The parsed RDF node
1344    * @throws SparqlParserException
1345    */
1346    protected function parseNode($node = false)
1347    {
1348        //$eon = false;
1349        if ($node) {
1350            $node = $node;
1351        } else {
1352            $node = current($this->tokens);
1353        }
1354        if ($node{strlen($node)-1} == '.') {
1355            $node = substr($node,0,-1);
1356        }
1357        if ($this->dtypeCheck($node)) {
1358            return $node;
1359        }
1360        if ($this->bNodeCheck($node)) {
1361            $node = '?'.$node;
1362            $this->query->addUsedVar($node);
1363            return $node;
1364        }
1365        if ($node == '[') {
1366            $node = '?' . substr($this->query->getBlanknodeLabel(), 1);
1367            $this->query->addUsedVar($node);
1368            $this->_fastForward();
1369            if(current($this->tokens)!=']') {
1370                prev($this->tokens);
1371            }
1372            return $node;
1373        }
1374        if ($this->iriCheck($node)){
1375            $base = $this->query->getBase();
1376            if ($base!=null) {
1377                $node = new Resource(substr(substr($base,0,-1).substr($node,1),1,-1));
1378            } else {
1379                $node = new Resource(substr($node,1,-1));
1380            }
1381            return $node;
1382        } else if ($this->qnameCheck($node)) {
1383            $node = $this->query->getFullUri($node);
1384            $node = new Resource($node);
1385            return $node;
1386        } else if ($this->literalCheck($node)) {
1387            $ch     = substr($node, 0, 1);
1388            $chLong = str_repeat($ch, 3);
1389            if (substr($node, 0, 3) == $chLong) {
1390                $ch = $chLong;
1391            }
1392            $this->parseLiteral($node, $ch);
1393        } else if ($this->varCheck($node)) {
1394            $pos = strpos($node,'.');
1395            if ($pos) {
1396                return substr($node,0,$pos);
1397            } else {
1398                return $node;
1399            }
1400        } else if ($node[0] == '<') {
1401            //partial IRI? loop tokens until we find a closing >
1402            while (next($this->tokens)) {
1403                $node .= current($this->tokens);
1404                if (substr($node, -1) == '>') {
1405                    break;
1406                }
1407            }
1408            if (substr($node, -1) != '>') {
1409                throw new SparqlParserException(
1410                    "Unclosed IRI: " . $node,
1411                    null,
1412                    key($this->tokens)
1413                );
1414            }
1415            return $this->parseNode($node);
1416        } else {
1417            throw new SparqlParserException(
1418                '"' . $node . '" is neither a valid rdf- node nor a variable.',
1419                null,
1420                key($this->tokens)
1421            );
1422        }
1423        return $node;
1424    }//protected function parseNode($node = false)
1425
1426
1427
1428    /**
1429    * Checks if there is a datatype given and appends it to the node.
1430    *
1431    * @param string $node Node to check
1432    *
1433    * @return void
1434    */
1435    protected function checkDtypeLang(&$node, $nSubstrLength = 1)
1436    {
1437        $this->_fastForward();
1438        switch (substr(current($this->tokens), 0, 1)) {
1439            case '^':
1440                if (substr(current($this->tokens),0,2)=='^^') {
1441                    $node = new Literal(substr($node,1,-1));
1442                    $node->setDatatype(
1443                        $this->query->getFullUri(
1444                            substr(current($this->tokens), 2)
1445                        )
1446                    );
1447                }
1448                break;
1449            case '@':
1450                $node = new Literal(
1451                    substr($node, $nSubstrLength, -$nSubstrLength),
1452                    substr(current($this->tokens), $nSubstrLength)
1453                );
1454                break;
1455            default:
1456                prev($this->tokens);
1457                $node = new Literal(substr($node, $nSubstrLength, -$nSubstrLength));
1458                break;
1459
1460        }
1461    }//protected function checkDtypeLang(&$node, $nSubstrLength = 1)
1462
1463
1464
1465    /**
1466    * Parses a literal.
1467    *
1468    * @param String $node
1469    * @param String $sep used separator " or '
1470    *
1471    * @return void
1472    */
1473    protected function parseLiteral(&$node, $sep)
1474    {
1475        do {
1476            next($this->tokens);
1477            $node = $node.current($this->tokens);
1478        } while (current($this->tokens) != $sep);
1479        $this->checkDtypeLang($node, strlen($sep));
1480    }//protected function parseLiteral(&$node, $sep)
1481
1482
1483
1484    /**
1485    * Checks if the Node is a typed Literal.
1486    *
1487    * @param String $node
1488    *
1489    * @return boolean TRUE if typed FALSE if not
1490    */
1491    protected function dtypeCheck(&$node)
1492    {
1493        $patternInt = "/^-?[0-9]+$/";
1494        $match = preg_match($patternInt,$node,$hits);
1495        if($match>0){
1496            $node = new Literal($hits[0]);
1497            $node->setDatatype(XML_SCHEMA.'integer');
1498            return true;
1499        }
1500        $patternBool = "/^(true|false)$/";
1501        $match = preg_match($patternBool,$node,$hits);
1502        if($match>0){
1503            $node = new Literal($hits[0]);
1504            $node->setDatatype(XML_SCHEMA.'boolean');
1505            return true;
1506        }
1507        $patternType = "/^a$/";
1508        $match = preg_match($patternType,$node,$hits);
1509        if($match>0){
1510            $node = new Resource(RDF_NAMESPACE_URI.'type');
1511            return true;
1512        }
1513        $patternDouble = "/^-?[0-9]+.[0-9]+[e|E]?-?[0-9]*/";
1514        $match = preg_match($patternDouble,$node,$hits);
1515        if($match>0){
1516            $node = new Literal($hits[0]);
1517            $node->setDatatype(XML_SCHEMA.'double');
1518            return true;
1519        }
1520        return false;
1521    }//protected function dtypeCheck(&$node)
1522
1523
1524
1525    /**
1526    * Parses an RDF collection.
1527    *
1528    * @param  TriplePattern $trp
1529    *
1530    * @return Node          The first parsed label
1531    */
1532    protected function parseCollection(&$trp)
1533    {
1534        $tmpLabel = $this->query->getBlanknodeLabel();
1535        $firstLabel = $this->parseNode($tmpLabel);
1536        $this->_fastForward();
1537        $i = 0;
1538        while (current($this->tokens)!=")") {
1539            if($i>0)
1540            $trp[] = new QueryTriple($this->parseNode($tmpLabel),new Resource("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"),$this->parseNode($tmpLabel = $this->query->getBlanknodeLabel()));
1541            $trp[] = new QueryTriple($this->parseNode($tmpLabel),new Resource("http://www.w3.org/1999/02/22-rdf-syntax-ns#first"),$this->parseNode());
1542            $this->_fastForward();
1543            $i++;
1544        }
1545        $trp[] = new QueryTriple($this->parseNode($tmpLabel),new Resource("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"),new Resource("http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"));
1546        return $firstLabel;
1547    }//protected function parseCollection(&$trp)
1548
1549}// end class: SparqlParser.php
1550
1551?>
Note: See TracBrowser for help on using the repository browser.