source: Dev/trunk/rdfapi/rdql/RdqlMemEngine.php @ 12

Last change on this file since 12 was 12, checked in by basvannuland, 14 years ago

Added RAP RDF API
Added RDF reader writer for save and load survey

File size: 18.6 KB
Line 
1<?php
2
3// ----------------------------------------------------------------------------------
4// Class: RdqlMemEngine
5// ----------------------------------------------------------------------------------
6
7/**
8 * This class performes as RDQL query on a MemModel.
9 *
10 * Provided an rdql query parsed into an array of php variables and constraints
11 * at first the engine searches for tuples matching all patterns from the WHERE clause
12 * of the given RDQL query. Then the query result set is filtered with evaluated
13 * boolean expressions from the AND clause of the given RDQL query.
14 *
15 * @version  $Id: RdqlMemEngine.php 268 2006-05-15 05:28:09Z tgauss $
16 * @author   Radoslaw Oldakowski <radol@gmx.de>
17 *
18 * @package rdql
19 * @access public
20 */
21
22Class RdqlMemEngine extends RdqlEngine {
23
24
25/**
26 * Parsed query variables and constraints.
27 *
28 * @var     array   ['selectVars'][] = ?VARNAME
29 *                  ['sources'][] = URI
30 *                  ['patterns'][]['subject']['value'] = VARorURI
31 *                                ['predicate']['value'] = VARorURI
32 *                                ['object']['value'] = VARorURIorLiterl
33 *                                          ['is_literal'] = boolean
34 *                                          ['l_lang'] = string
35 *                                          ['l_dtype'] = string
36 *                  ['filters'][]['string'] = string
37 *                               ['evalFilterStr'] = string
38 *                               ['reqexEqExprs'][]['var'] = ?VARNAME
39 *                                                 ['operator'] = (eq | ne)
40 *                                                 ['regex'] = string
41 *                               ['strEqExprs'][]['var'] = ?VARNAME
42 *                                               ['operator'] = (eq | ne)
43 *                                               ['value'] = string
44 *                                               ['value_type'] = ('variable' | 'URI' | 'Literal')
45 *                                               ['value_lang'] = string
46 *                                               ['value_dtype'] = string
47 *                               ['numExpr']['vars'][] = ?VARNAME
48 *                         ( [] stands for an integer index - 0..N )
49 * @access      private
50 */
51 var $parsedQuery;
52
53
54 /**
55 * Perform an RDQL Query on the given MemModel.
56 *
57 * @param   object  MemModel &$memModel
58 * @param   array   &$parsedQuery  (the same format as $this->parsedQuery)
59 * @param   boolean $returnNodes
60 * @return  array   [][?VARNAME] = object Node  (if $returnNodes = TRUE)
61 *      OR  array   [][?VARNAME] = string
62 *
63 * @access  public
64 */
65 function & queryModel(&$memModel, &$parsedQuery, $returnNodes = TRUE) {
66
67   $this->parsedQuery = $parsedQuery;
68
69   // find tuples matching all patterns
70   $res = $this->findTuplesMatchingAllPatterns($memModel);
71
72   // filter tuples
73   if (isset($parsedQuery['filters']))
74      $res = $this->filterTuples($res);
75
76   // select variables to be returned
77   $res = $this->selectVariables($res);
78
79   if(!$returnNodes)
80     return $this->toString($res);
81
82   return $res;
83 }
84 
85
86 /**
87 * Find triples matching all patterns of an RDQL query and return an array
88 * with variables from all patterns and their corresponding values.
89 * The variable values returned are instances of object Node.
90 *
91 * @param   object  MemModel  &$memModel
92 * @return  array   [][?VARNAME] = object Node
93 *
94 * @access  private
95 */
96 function findTuplesMatchingAllPatterns(&$memModel) {
97
98   $resultSet = $this->findTuplesMatchingOnePattern($memModel, $this->parsedQuery['patterns'][0]);
99   for ($i=1; $i<count($this->parsedQuery['patterns']); $i++) {
100       $rs = $this->findTuplesMatchingOnePattern($memModel, $this->parsedQuery['patterns'][$i]);
101       $resultSet = $this->joinTuples($resultSet, $rs);
102   }
103   return $resultSet;
104 }
105
106
107/**
108 * Find tuples matching one pattern and return an array with pattern
109 * variables and their corresponding values (instances of object Node).
110 *
111 * @param   object  MemModel &$memModel
112 * @param   array  &$pattern ['subject']['value'] = VARorURI
113 *                           ['predicate']['value'] = VARorURI
114 *                           ['object']['value'] = VARorURIorLiterl
115 *                                     ['is_literal'] = boolean
116 *                                     ['l_lang'] = string
117 *                                     ['l_dtype'] = string
118 * @return  array   [][?VARNAME] = object Node
119 *
120 * @access  private
121 */
122 function findTuplesMatchingOnePattern(&$memModel, &$pattern) {
123
124   $resultSet = array();
125   $i = 0;
126   // parameters to be passed to the method findTriplesMatchingPattern
127   foreach ($pattern as $key => $v) {     
128     if ($v['value'] && $v['value']{0} == '?') {
129        if ($key == 'object') {
130            $param['object']['is_a'] = 'ANY';
131            $param['object']['string'] = 'ANY';
132            $param['object']['lang'] = NULL;
133            $param['object']['dtype'] = NULL;
134        } else         
135                $param[$key] = 'ANY';
136        $var[$i]['key'] = $key;
137        $var[$i++]['val'] = $v['value'];
138     }else
139        if (isset($v['is_literal'])) {
140          $param[$key]['is_a'] = 'Literal';
141          $param[$key]['string'] = $v['value'];
142          $param[$key]['lang'] = $v['l_lang'];
143          $param[$key]['dtype'] = $v['l_dtype'];
144        }else{
145          if ($key == 'object') {
146             $param[$key]['is_a'] = 'Resource';
147             $param[$key]['string'] = $v['value'];
148             $param[$key]['lang'] = NULL;
149             $param[$key]['dtype'] = NULL;
150          }else
151            $param[$key] = $v['value'];
152        }
153   }
154   
155   // find pattern internal bindings e.g. (?x, ?z, ?x)
156   $intBindings = NULL;
157   for ($i=0; $i<count($var); $i++)
158       foreach($var as $n => $v)
159         if ($i != $n && $var[$i]['val'] == $v['val'])
160            $intBindings[] = $var[$i]['key'];
161
162   // find triples of the $memModel matching $pattern
163   $resModel = $this->findTriplesMatchingPattern($memModel, $param['subject'],
164                                                            $param['predicate'],
165                                                            $param['object']['is_a'],
166                                                            $param['object']['string'],
167                                                            $param['object']['lang'],
168                                                            $param['object']['dtype'],
169                                                 $intBindings);
170
171   // set values of the pattern variables to be returned
172   if ($pattern['subject']['value']{0} == '?') {
173      $n = 0;
174      foreach ($resModel->triples as $triple)
175        $resultSet[$n++][$pattern['subject']['value']] = $triple->subj;
176   }
177   if ($pattern['predicate']['value']{0} == '?') {
178      $n = 0;
179      foreach ($resModel->triples as $triple)
180        $resultSet[$n++][$pattern['predicate']['value']] = $triple->pred;
181   }
182   if ($pattern['object']['value'] && $pattern['object']['value']{0} == '?') {
183      $n = 0;
184      foreach ($resModel->triples as $triple)
185        $resultSet[$n++][$pattern['object']['value']] = $triple->obj;
186   }
187   return $resultSet;
188 }
189
190
191/**
192 * Search in $memModel for triples matching one pattern from the WHERE clause.
193 * 'ANY' input for $subjLabel..$objLabel, $obj_is will match anything.
194 * NULL input for $objDtype will only match obj->dtype = NULL
195 * NULL input for $objLanguage will match obj->lang = NULL or anything if a
196 * literal is datatyped (except for XMLLiterals and plain literals)
197 * This method also checks internal bindings if provided.
198 *
199 * @param   object MemModel $memModel
200 * @param   string $subjLabel
201 * @param   string $predLabel
202 * @param   string $objLabel
203 * @param   string $obj_is
204 * @param   string $objLanguage
205 * @param   string $objDtype
206 * @param   array  $intBindings [] = string
207 * @return  object MemModel
208 * @access      private
209 */
210 function findTriplesMatchingPattern(&$memModel, $subjLabel, $predLabel, $obj_is,
211                                      $objLabel, $objLang, $objDtype, &$intBindings) {
212
213   $res = new MemModel();
214
215   if($memModel->isEmpty())
216    return $res;
217
218   if ($subjLabel=='ANY')
219   {
220       $subj=NULL;
221   } else
222   {
223       $subj=new Resource($subjLabel);
224   };
225   if ($predLabel=='ANY')
226   {
227       $pred=NULL;
228   } else
229   {
230       $pred=new Resource($predLabel);
231   };
232           
233   if ($objLabel=='ANY')
234   {
235       $obj=NULL;
236   } else
237   {
238       if ($obj_is == 'Literal')
239       {
240           $obj=new Literal($objLabel);
241           $obj->setDatatype($objDtype);
242           $obj->setLanguage($objLang);
243       } else {
244           $obj=new Resource($objLabel);
245       }
246   };
247   
248   $res=$memModel->find($subj,$pred,$obj);
249
250     if ($intBindings)
251              foreach ($res->triples as $triple)
252        {
253            if (!$this->_checkIntBindings($triple, $intBindings))
254            {
255                  $res->remove($triple);
256            }
257        }
258
259  return $res;
260}
261
262 
263/**
264 * Perform an SQL-like inner join on two resultSets.
265 *
266 * @param   array   &$finalRes [][?VARNAME] = object Node
267 * @param   array   &$res      [][?VARNAME] = object Node
268 * @return  array              [][?VARNAME] = object Node
269 *
270 * @access  private
271 */
272 function joinTuples(&$finalRes, &$res) {
273
274   if (count($finalRes) == 0 || count($res) == 0)
275      return array();
276
277   // find joint variables and new variables to be added to $finalRes
278   $jointVars = array();
279   $newVars = array();
280   foreach ($res[0] as $varname => $node) {
281     if (array_key_exists($varname, $finalRes[0]))
282        $jointVars[] = $varname;
283     else
284        $newVars[] = $varname;
285   }
286
287   // eliminate rows of $finalRes in which the values of $jointVars do not have
288   // a corresponding row in $res.
289   foreach ($finalRes as $n => $fRes) {
290     foreach ($res as $i => $r) {
291       $ok = TRUE;
292       foreach ($jointVars as $j_varname)
293         if ($r[$j_varname] != $fRes[$j_varname]) {
294            $ok = FALSE;
295            break;
296         }
297       if ($ok)
298          break;
299     }
300     if (!$ok)
301        unset($finalRes[$n]);
302   }
303
304   // join $res and $finalRes
305   $joinedRes = array();
306   foreach ($res as $i => $r) {
307     foreach ($finalRes as $n => $fRes) {
308       $ok = TRUE;
309       foreach ($jointVars as $j_varname)
310         if ($r[$j_varname] != $fRes[$j_varname]) {
311            $ok = FALSE;
312            break;
313         }
314       if ($ok) {
315          $joinedRow = $finalRes[$n];
316          foreach($newVars as $n_varname)
317            $joinedRow[$n_varname] = $r[$n_varname];
318          $joinedRes[] = $joinedRow;
319       }
320     }
321   }
322   
323   return $joinedRes;
324 }
325 
326
327/**
328 * Filter the result-set of query variables by evaluating each filter from the
329 * AND clause of the RDQL query.
330 *
331 * @param   array  &$finalRes  [][?VARNAME] = object Node
332 * @return  array  [][?VARNAME] = object Node
333 * @access      private
334 */
335 function filterTuples(&$finalRes) {
336
337   foreach ($this->parsedQuery['filters'] as $filter) {
338
339      foreach ($finalRes as $n => $fRes) {
340        $evalFilterStr = $filter['evalFilterStr'];
341
342        // evaluate regex equality expressions of each filter
343        foreach ($filter['regexEqExprs'] as $i => $expr) {
344
345          preg_match($expr['regex'], $fRes[$expr['var']]->getLabel(), $match);
346          $op = substr($expr['operator'], 0,1);
347          if (($op != '!' && !isset($match[0])) || ($op == '!' && isset($match[0])))
348             $evalFilterStr = str_replace("##RegEx_$i##", 'FALSE', $evalFilterStr);
349          else
350             $evalFilterStr = str_replace("##RegEx_$i##", 'TRUE', $evalFilterStr);
351
352        }
353
354        // evaluate string equality expressions
355        foreach ($filter['strEqExprs'] as $i => $expr) {
356
357          $exprBoolVal = 'FALSE';
358         
359          switch ($expr['value_type']) {
360
361            case 'variable':
362                 if (($fRes[$expr['var']] == $fRes[$expr['value']] && $expr['operator'] == 'eq') ||
363                     ($fRes[$expr['var']] != $fRes[$expr['value']] && $expr['operator'] == 'ne'))
364                    $exprBoolVal = 'TRUE';
365                 break;
366                 
367            case 'URI':
368
369                 if (is_a($fRes[$expr['var']], 'Literal')) {
370                    if ($expr['operator'] == 'ne')
371                       $exprBoolVal = 'TRUE';
372                    break;
373                 }
374
375                 if (($fRes[$expr['var']]->getLabel() == $expr['value'] && $expr['operator'] == 'eq') ||
376                     ($fRes[$expr['var']]->getLabel() != $expr['value'] && $expr['operator'] == 'ne'))
377                    $exprBoolVal = 'TRUE';
378                 break;
379                 
380            case 'Literal':
381
382                 if (!is_a($fRes[$expr['var']], 'Literal')) {
383                    if ($expr['operator'] == 'ne')
384                       $exprBoolVal = 'TRUE';
385                    break;
386                 }
387
388                 $filterLiteral= new Literal($expr['value'],$expr['value_lang']);
389                 $filterLiteral->setDatatype($expr['value_dtype']);
390                 
391                $equal=$fRes[$expr['var']]->equals($filterLiteral);
392/*                 if ($fRes[$expr['var']]->getLabel() == $expr['value'] &&
393                     $fRes[$expr['var']]->getDatatype() == $expr['value_dtype']) {
394                     
395                    $equal = TRUE;
396                     
397                    // Lang tags only differentiate literals in rdf:XMLLiterals and plain literals.
398                    // Therefore if a literal is datatyped ignore the language tag.
399                    if ((($expr['value_dtype'] == NULL) ||
400                         ($expr['value_dtype'] == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') ||
401                         ($expr['value_dtype'] == 'http://www.w3.org/2001/XMLSchema#string')) &&
402                        (($fRes[$expr['var']]->getLanguage() != $expr['value_lang'])))
403                       
404                        $equal = FALSE;
405                 }else
406                    $equal = FALSE;
407     */               
408                 if (($equal && $expr['operator'] == 'eq') ||
409                     (!$equal && $expr['operator'] == 'ne'))
410                    $exprBoolVal = 'TRUE';
411                 else
412                    $exprBoolVal = 'FALSE';
413
414          }
415          $evalFilterStr = str_replace("##strEqExpr_$i##", $exprBoolVal, $evalFilterStr);
416       }
417
418        // evaluate numerical expressions
419        foreach ($filter['numExprVars'] as $varName) {
420          $varValue = "'" .$fRes[$varName]->getLabel() ."'";
421          $evalFilterStr = str_replace($varName, $varValue, $evalFilterStr);
422        }
423
424        eval("\$filterBoolVal = $evalFilterStr; \$eval_filter_ok = TRUE;");
425        if (!isset($eval_filter_ok))
426           trigger_error(RDQL_AND_ERR ."'" .htmlspecialchars($filter['string']) ."'", E_USER_ERROR);
427
428        if (!$filterBoolVal)
429           unset($finalRes[$n]);
430      }
431   }
432
433   return $finalRes;
434 }
435
436
437/**
438 * Remove all conditional variables from the result-set and leave only variables
439 * specified in the SELECT clause of the RDQL query.
440 *
441 * @param   array  &$finalRes  [][?VARNAME] = object Node
442 * @return  array  [][?VARNAME] = object Node
443 * @access      private
444 */
445 function selectVariables(&$finalRes) {
446
447   // if nothing has been found return only one row of $finalRes
448   // with select variables having empty values
449   if (count($finalRes) == 0) {
450      foreach ($this->parsedQuery['selectVars'] as $selectVar)
451         $finalRes[0][$selectVar] = NULL;
452      return $finalRes;
453   }
454   
455   // return only selectVars in the same order as given in the RDQL query
456   // and reindex $finalRes.
457   $n = 0;
458   foreach($finalRes as $key => $val) {
459     foreach ($this->parsedQuery['selectVars'] as $selectVar)
460       $resultSet[$n][$selectVar] = $val[$selectVar];
461     unset($finalRes[$key]);
462     ++$n;
463   }
464
465   return $resultSet;
466 }
467
468
469/**
470 * Convert the variable values of $finalRes from objects to their string serialization.
471 *
472 * @param   array  &$finalRes  [][?VARNAME] = object Node
473 * @return  array  [][?VARNAME] = string
474 * @access      private
475 */
476 function toString(&$finalRes) {
477
478   foreach ($finalRes as $n => $tuple)
479     foreach ($tuple as $varname => $node) {
480       if (is_a($node, 'Resource'))
481          $res[$n][$varname] = '<' .$node->getLabel() .'>';
482       elseif (is_a($node, 'Literal')) {
483          $res[$n][$varname] = '"' .$node->getLabel() .'"';
484          if ($node->getLanguage())
485             $res[$n][$varname] .= ' (xml:lang="' .$node->getLanguage() .'")';
486          if ($node->getDatatype())
487             $res[$n][$varname] .= ' (rdf:datatype="' .$node->getDatatype() .'")';
488       }else
489          $res[$n][$varname] = $node;
490     }
491   return $res;
492 }
493
494
495/**
496 * Check if the given triple meets pattern internal bindings
497 * e.g. (?x, ?z, ?x) ==> statement subject must be identical with the statement object
498 *
499 * @param   object statement &$triple
500 * @param   array  &$intBindings [] = string
501 * @return  boolean
502 * @access      private
503 */
504 function _checkIntBindings (&$triple, &$intBindings) {
505
506   if (in_array('subject', $intBindings)) {
507      if (in_array('predicate', $intBindings))
508         if ($triple->subj != $triple->pred)
509            return FALSE;
510      if (in_array('object', $intBindings)) {
511         if (is_a($triple->obj, 'Literal'))
512            return FALSE;
513         elseif ($triple->subj != $triple->obj)
514            return FALSE;
515      }
516      return TRUE;
517   }
518   if (in_array('predicate', $intBindings)) {
519      if (is_a($triple->obj, 'Literal'))
520         return FALSE;
521      elseif ($triple->pred != $triple->obj)
522             return FALSE;
523      return TRUE;
524   }
525 }
526
527
528/**
529 * Check if the lang and dtype of the passed object Literal are equal $lang and $dtype
530 * !!! Language only differentiates literals in rdf:XMLLiterals and plain literals (xsd:string).
531 * !!! Therefore if a literal is datatyped ignore the language.
532 *
533 * @param  object  Literal $literal
534 * @param  string  $dtype1
535 * @param  string  $dtype2
536 * @return boolean
537 * @access private
538 */
539 function _equalsLangDtype ($literal, $lang, $dtype) {
540
541   if ($dtype == $literal->getDatatype()) {
542      if (($dtype == NULL ||
543           $dtype == 'http://www.w3.org/2001/XMLSchema#string' ||
544           $dtype == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') &&
545          ($lang != $literal->getLanguage()))
546         return FALSE;
547      return TRUE;
548   }
549   return FALSE;
550 }
551 
552 
553} // end: Class RdqlMemEngine
554
555?>
Note: See TracBrowser for help on using the repository browser.