Source for file SparqlParser.php

Documentation is available at SparqlParser.php

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

Documentation generated on Fri, 1 Jun 2007 16:52:17 +0200 by phpDocumentor 1.3.2