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

Last change on this file since 312 was 312, checked in by jkraaijeveld, 13 years ago
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.