00001 <?php
00015 class SMWQueryParser {
00016
00017 protected $m_sepstack;
00018 protected $m_curstring;
00019 protected $m_errors;
00020 protected $m_defaultns;
00021
00022 protected $m_categoryprefix;
00023 protected $m_conceptprefix;
00024 protected $m_categoryPrefixCannonical;
00025 protected $m_conceptPrefixCannonical;
00026 protected $m_queryfeatures;
00027
00028 public function __construct( $queryfeatures = false ) {
00029 global $wgContLang, $smwgQFeatures;
00030
00031 $this->m_categoryprefix = $wgContLang->getNsText( NS_CATEGORY ) . ':';
00032 $this->m_conceptprefix = $wgContLang->getNsText( SMW_NS_CONCEPT ) . ':';
00033 $this->m_categoryPrefixCannonical = 'Category:';
00034 $this->m_conceptPrefixCannonical = 'Concept:';
00035
00036 $this->m_defaultns = null;
00037 $this->m_queryfeatures = $queryfeatures === false ? $smwgQFeatures : $queryfeatures;
00038 }
00039
00044 public function setDefaultNamespaces( $nsarray ) {
00045 $this->m_defaultns = null;
00046
00047 if ( !is_null( $nsarray ) ) {
00048 foreach ( $nsarray as $ns ) {
00049 $this->m_defaultns = $this->addDescription( $this->m_defaultns, new SMWNamespaceDescription( $ns ), false );
00050 }
00051 }
00052 }
00053
00063 public function getQueryDescription( $querystring ) {
00064 wfProfileIn( 'SMWQueryParser::getQueryDescription (SMW)' );
00065
00066 $this->m_errors = array();
00067 $this->m_curstring = $querystring;
00068 $this->m_sepstack = array();
00069 $setNS = false;
00070 $result = $this->getSubqueryDescription( $setNS );
00071
00072 if ( !$setNS ) {
00073 $result = $this->addDescription( $this->m_defaultns, $result );
00074 }
00075
00076 if ( is_null( $result ) ) {
00077 $result = new SMWThingDescription();
00078 }
00079
00080 wfProfileOut( 'SMWQueryParser::getQueryDescription (SMW)' );
00081
00082 return $result;
00083 }
00084
00090 public function getErrors() {
00091 return $this->m_errors;
00092 }
00093
00099 public function getErrorString() {
00100 return smwfEncodeMessages( $this->m_errors );
00101 }
00102
00126 protected function getSubqueryDescription( &$setNS ) {
00127 $conjunction = null;
00128 $disjuncts = array();
00129 $hasNamespaces = false;
00130 $mustSetNS = $setNS;
00131
00132 $continue = ( $chunk = $this->readChunk() ) !== '';
00133
00134 while ( $continue ) {
00135 $setsubNS = false;
00136
00137 switch ( $chunk ) {
00138 case '[[':
00139 $ld = $this->getLinkDescription( $setsubNS );
00140
00141 if ( !is_null( $ld ) ) {
00142 $conjunction = $this->addDescription( $conjunction, $ld );
00143 }
00144 break;
00145 case '<q>':
00146 $this->pushDelimiter( '</q>' );
00147 $conjunction = $this->addDescription( $conjunction, $this->getSubqueryDescription( $setsubNS ) );
00148 break;
00149 case 'OR':
00150 case '||':
00151 case '':
00152 case '</q>':
00153 if ( !is_null( $this->m_defaultns ) ) {
00154 if ( $hasNamespaces && !$mustSetNS ) {
00155
00156 $mustSetNS = true;
00157 $newdisjuncts = array();
00158
00159 foreach ( $disjuncts as $conj ) {
00160 $newdisjuncts[] = $this->addDescription( $conj, $this->m_defaultns );
00161 }
00162
00163 $disjuncts = $newdisjuncts;
00164 } elseif ( !$hasNamespaces && $mustSetNS ) {
00165
00166 $conjunction = $this->addDescription( $conjunction, $this->m_defaultns );
00167 }
00168 }
00169
00170 $disjuncts[] = $conjunction;
00171
00172 $conjunction = null;
00173 $hasNamespaces = false;
00174
00175
00176 if ( $chunk == '</q>' ) {
00177 if ( $this->popDelimiter( '</q>' ) ) {
00178 $continue = false;
00179 } else {
00180 $this->m_errors[] = wfMsgForContent( 'smw_toomanyclosing', $chunk );
00181 return null;
00182 }
00183 } elseif ( $chunk === '' ) {
00184 $continue = false;
00185 }
00186 break;
00187 case '+':
00188 break;
00189 default:
00190 $this->m_errors[] = wfMsgForContent( 'smw_unexpectedpart', $chunk );
00191
00192 }
00193
00194 if ( $setsubNS ) {
00195 $hasNamespaces = true;
00196 }
00197
00198 if ( $continue ) {
00199 $chunk = $this->readChunk();
00200 }
00201 }
00202
00203 if ( count( $disjuncts ) > 0 ) {
00204 $result = null;
00205
00206 foreach ( $disjuncts as $d ) {
00207 if ( is_null( $d ) ) {
00208 $this->m_errors[] = wfMsgForContent( 'smw_emptysubquery' );
00209 $setNS = false;
00210 return null;
00211 } else {
00212 $result = $this->addDescription( $result, $d, false );
00213 }
00214 }
00215 } else {
00216 $this->m_errors[] = wfMsgForContent( 'smw_emptysubquery' );
00217 $setNS = false;
00218 return null;
00219 }
00220
00221 $setNS = $mustSetNS;
00222
00223 return $result;
00224 }
00225
00232 protected function getLinkDescription( &$setNS ) {
00233
00234
00235 $chunk = $this->readChunk( '', true, false );
00236
00237 if ( in_array( smwfNormalTitleText( $chunk ),
00238 array( $this->m_categoryprefix, $this->m_conceptprefix, $this->m_categoryPrefixCannonical, $this->m_conceptPrefixCannonical ) ) ) {
00239 return $this->getClassDescription( $setNS, (
00240 smwfNormalTitleText( $chunk ) == $this->m_categoryprefix || smwfNormalTitleText( $chunk ) == $this->m_categoryPrefixCannonical
00241 ) );
00242 } else {
00243 $sep = $this->readChunk( '', false );
00244
00245 if ( ( $sep == '::' ) || ( $sep == ':=' ) ) {
00246 if ( $chunk{0} != ':' ) {
00247 return $this->getPropertyDescription( $chunk, $setNS );
00248 } else {
00249 $chunk .= $this->readChunk( '\[\[|\]\]|\|\||\|' );
00250 return $this->getArticleDescription( trim( $chunk ), $setNS );
00251 }
00252 } else {
00253 return $this->getArticleDescription( trim( $chunk ), $setNS );
00254 }
00255 }
00256 }
00257
00263 protected function getClassDescription( &$setNS, $category = true ) {
00264
00265 $result = null;
00266 $continue = true;
00267
00268 while ( $continue ) {
00269 $chunk = $this->readChunk();
00270
00271 if ( $chunk == '+' ) {
00272
00273 } else {
00275 $title = Title::newFromText( ( $category ? $this->m_categoryprefix : $this->m_conceptprefix ) . $chunk );
00276
00277 if ( !is_null( $title ) ) {
00278 $diWikiPage = new SMWDIWikiPage( $title->getDBkey(), $title->getNameSpace(), '' );
00279 $desc = $category ? new SMWClassDescription( $diWikiPage ) : new SMWConceptDescription( $diWikiPage );
00280 $result = $this->addDescription( $result, $desc, false );
00281 }
00282 }
00283
00284 $chunk = $this->readChunk();
00285 $continue = ( $chunk == '||' ) && $category;
00286 }
00287
00288 return $this->finishLinkDescription( $chunk, false, $result, $setNS );
00289 }
00290
00297 protected function getPropertyDescription( $propertyname, &$setNS ) {
00298 $this->readChunk();
00299
00300
00301 $propertynames = ( $propertyname{0} == ' ' ) ? array( $propertyname ):explode( '.', $propertyname );
00302 $properties = array();
00303 $typeid = '_wpg';
00304 $inverse = false;
00305
00306 foreach ( $propertynames as $name ) {
00307 if ( $typeid != '_wpg' ) {
00308 $this->m_errors[] = wfMsgForContent( 'smw_valuesubquery', $name );
00309 return null;
00310 }
00311
00312 $property = SMWPropertyValue::makeUserProperty( $name );
00313
00314 if ( !$property->isValid() ) {
00315 $this->m_errors = array_merge( $this->m_errors, $property->getErrors() );
00316 return null;
00317 }
00318
00319 $typeid = $property->getDataItem()->findPropertyTypeID();
00320 $inverse = $property->isInverse();
00321 $properties[] = $property;
00322 }
00323
00324 $innerdesc = null;
00325 $continue = true;
00326
00327 while ( $continue ) {
00328 $chunk = $this->readChunk();
00329
00330 switch ( $chunk ) {
00331 case '+':
00332 if ( !is_null( $this->m_defaultns ) && ( ( $typeid == '_wpg' ) || $inverse ) ) {
00333 $innerdesc = $this->addDescription( $innerdesc, $this->m_defaultns, false );
00334 } else {
00335 $innerdesc = $this->addDescription( $innerdesc, new SMWThingDescription(), false );
00336 }
00337 $chunk = $this->readChunk();
00338 break;
00339 case '<q>':
00340 if ( ( $typeid == '_wpg' ) || $inverse ) {
00341 $this->pushDelimiter( '</q>' );
00342 $setsubNS = true;
00343 $innerdesc = $this->addDescription( $innerdesc, $this->getSubqueryDescription( $setsubNS ), false );
00344 } else {
00345 $this->m_errors[] = wfMsgForContent( 'smw_valuesubquery', end( $propertynames ) );
00346 $innerdesc = $this->addDescription( $innerdesc, new SMWThingDescription(), false );
00347 }
00348 $chunk = $this->readChunk();
00349 break;
00350 default:
00351
00352 $open = 1;
00353 $value = $chunk;
00354 $continue2 = true;
00355
00356 while ( ( $open > 0 ) && ( $continue2 ) ) {
00357 $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' );
00358 switch ( $chunk ) {
00359 case '[[':
00360 $open++;
00361 break;
00362 case ']]':
00363 $open--;
00364 break;
00365 case '|':
00366 case '||':
00367 if ( $open == 1 ) {
00368 $open = 0;
00369 }
00370 break;
00371 case '':
00372 $continue2 = false;
00373 break;
00374 }
00375 if ( $open != 0 ) {
00376 $value .= $chunk;
00377 }
00378 }
00379
00380 $dv = SMWDataValueFactory::newPropertyObjectValue( $property->getDataItem() );
00381 $vd = $dv->getQueryDescription( $value );
00382 $innerdesc = $this->addDescription( $innerdesc, $vd, false );
00383 $this->m_errors = $this->m_errors + $dv->getErrors();
00384 }
00385 $continue = ( $chunk == '||' );
00386 }
00387
00388 if ( is_null( $innerdesc ) ) {
00389 $innerdesc = ( !is_null( $this->m_defaultns ) && ( $typeid == '_wpg' ) ) ?
00390 $this->addDescription( $innerdesc, $this->m_defaultns, false ) :
00391 $this->addDescription( $innerdesc, new SMWThingDescription(), false );
00392 $this->m_errors[] = wfMsgForContent( 'smw_propvalueproblem', $property->getWikiValue() );
00393 }
00394
00395 $properties = array_reverse( $properties );
00396
00397 foreach ( $properties as $property ) {
00398 $innerdesc = new SMWSomeProperty( $property->getDataItem(), $innerdesc );
00399 }
00400
00401 $result = $innerdesc;
00402
00403 return $this->finishLinkDescription( $chunk, false, $result, $setNS );
00404 }
00405
00413 protected function getArticleDescription( $firstchunk, &$setNS ) {
00414 $chunk = $firstchunk;
00415 $result = null;
00416 $continue = true;
00417
00418
00419 while ( $continue ) {
00420 if ( $chunk == '<q>' ) {
00421 $this->m_errors[] = wfMsgForContent( 'smw_misplacedsubquery' );
00422 return null;
00423 }
00424
00425 $list = preg_split( '/:/', $chunk, 3 );
00426
00427 if ( ( $list[0] === '' ) && ( count( $list ) == 3 ) ) {
00428 $list = array_slice( $list, 1 );
00429 }
00430 if ( ( count( $list ) == 2 ) && ( $list[1] == '+' ) ) {
00431 global $wgContLang;
00432
00433 $idx = $wgContLang->getNsIndex( str_replace( ' ', '_', $list[0] ) );
00434
00435 if ( $idx !== false ) {
00436 $result = $this->addDescription( $result, new SMWNamespaceDescription( $idx ), false );
00437 }
00438 } else {
00439 $value = SMWDataValueFactory::newTypeIDValue( '_wpg', $chunk );
00440 if ( $value->isValid() ) {
00441 $result = $this->addDescription( $result, new SMWValueDescription( $value->getDataItem(), null ), false );
00442 }
00443 }
00444
00445 $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' );
00446
00447 if ( $chunk == '||' ) {
00448 $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' );
00449 $continue = true;
00450 } else {
00451 $continue = false;
00452 }
00453 }
00454
00455 return $this->finishLinkDescription( $chunk, true, $result, $setNS );
00456 }
00457
00458 protected function finishLinkDescription( $chunk, $hasNamespaces, $result, &$setNS ) {
00459 if ( is_null( $result ) ) {
00460 $this->m_errors[] = wfMsgForContent( 'smw_badqueryatom' );
00461 } elseif ( !$hasNamespaces && $setNS && !is_null( $this->m_defaultns ) ) {
00462 $result = $this->addDescription( $result, $this->m_defaultns );
00463 $hasNamespaces = true;
00464 }
00465
00466 $setNS = $hasNamespaces;
00467
00468 if ( $chunk == '|' ) {
00469
00470 $chunk = $this->readChunk( '\]\]' );
00471 $labelpart = '|';
00472 if ( $chunk != ']]' ) {
00473 $labelpart .= $chunk;
00474 $chunk = $this->readChunk( '\]\]' );
00475 }
00476 $this->m_errors[] = wfMsgForContent( 'smw_unexpectedpart', htmlspecialchars( $labelpart ) );
00477 }
00478
00479 if ( $chunk != ']]' ) {
00480
00481
00482
00483 if ( $chunk !== '' ) {
00484 $this->m_errors[] = wfMsgForContent( 'smw_misplacedsymbol', htmlspecialchars( $chunk ) );
00485
00486
00487 $chunk = $this->readChunk( '\]\]' );
00488
00489 if ( $chunk != ']]' ) {
00490 $chunk = $this->readChunk( '\]\]' );
00491 }
00492 }
00493 if ( $chunk === '' ) {
00494 $this->m_errors[] = wfMsgForContent( 'smw_noclosingbrackets' );
00495 }
00496 }
00497
00498 return $result;
00499 }
00500
00518 protected function readChunk( $stoppattern = '', $consume = true, $trim = true ) {
00519 if ( $stoppattern === '' ) {
00520 $stoppattern = '\[\[|\]\]|::|:=|<q>|<\/q>' .
00521 '|^' . $this->m_categoryprefix . '|^' . $this->m_categoryPrefixCannonical .
00522 '|^' . $this->m_conceptprefix . '|^' . $this->m_conceptPrefixCannonical .
00523 '|\|\||\|';
00524 }
00525 $chunks = preg_split( '/[\s]*(' . $stoppattern . ')/u', $this->m_curstring, 2, PREG_SPLIT_DELIM_CAPTURE );
00526 if ( count( $chunks ) == 1 ) {
00527 if ( $consume ) {
00528 $this->m_curstring = '';
00529 }
00530
00531 return $trim ? trim( $chunks[0] ) : $chunks[0];
00532 } elseif ( count( $chunks ) == 3 ) {
00533 if ( $chunks[0] === '' ) {
00534 if ( $consume ) {
00535 $this->m_curstring = $chunks[2];
00536 }
00537
00538 return $trim ? trim( $chunks[1] ) : $chunks[1];
00539 } else {
00540 if ( $consume ) {
00541 $this->m_curstring = $chunks[1] . $chunks[2];
00542 }
00543
00544 return $trim ? trim( $chunks[0] ) : $chunks[0];
00545 }
00546 } else {
00547 return false;
00548 }
00549 }
00550
00555 protected function pushDelimiter( $endstring ) {
00556 array_push( $this->m_sepstack, $endstring );
00557 }
00558
00564 protected function popDelimiter( $endstring ) {
00565 $topdelim = array_pop( $this->m_sepstack );
00566 return ( $topdelim == $endstring );
00567 }
00568
00581 protected function addDescription( $curdesc, $newdesc, $conjunction = true ) {
00582 $notallowedmessage = 'smw_noqueryfeature';
00583 if ( $newdesc instanceof SMWSomeProperty ) {
00584 $allowed = $this->m_queryfeatures & SMW_PROPERTY_QUERY;
00585 } elseif ( $newdesc instanceof SMWClassDescription ) {
00586 $allowed = $this->m_queryfeatures & SMW_CATEGORY_QUERY;
00587 } elseif ( $newdesc instanceof SMWConceptDescription ) {
00588 $allowed = $this->m_queryfeatures & SMW_CONCEPT_QUERY;
00589 } elseif ( $newdesc instanceof SMWConjunction ) {
00590 $allowed = $this->m_queryfeatures & SMW_CONJUNCTION_QUERY;
00591 $notallowedmessage = 'smw_noconjunctions';
00592 } elseif ( $newdesc instanceof SMWDisjunction ) {
00593 $allowed = $this->m_queryfeatures & SMW_DISJUNCTION_QUERY;
00594 $notallowedmessage = 'smw_nodisjunctions';
00595 } else {
00596 $allowed = true;
00597 }
00598
00599 if ( !$allowed ) {
00600 $this->m_errors[] = wfMsgForContent( $notallowedmessage, str_replace( '[', '[', $newdesc->getQueryString() ) );
00601 return $curdesc;
00602 }
00603
00604 if ( is_null( $newdesc ) ) {
00605 return $curdesc;
00606 } elseif ( is_null( $curdesc ) ) {
00607 return $newdesc;
00608 } else {
00609 if ( ( ( $conjunction ) && ( $curdesc instanceof SMWConjunction ) ) ||
00610 ( ( !$conjunction ) && ( $curdesc instanceof SMWDisjunction ) ) ) {
00611 $curdesc->addDescription( $newdesc );
00612 return $curdesc;
00613 } elseif ( $conjunction ) {
00614 if ( $this->m_queryfeatures & SMW_CONJUNCTION_QUERY ) {
00615 return new SMWConjunction( array( $curdesc, $newdesc ) );
00616 } else {
00617 $this->m_errors[] = wfMsgForContent( 'smw_noconjunctions', str_replace( '[', '[', $newdesc->getQueryString() ) );
00618 return $curdesc;
00619 }
00620 } else {
00621 if ( $this->m_queryfeatures & SMW_DISJUNCTION_QUERY ) {
00622 return new SMWDisjunction( array( $curdesc, $newdesc ) );
00623 } else {
00624 $this->m_errors[] = wfMsgForContent( 'smw_nodisjunctions', str_replace( '[', '[', $newdesc->getQueryString() ) );
00625 return $curdesc;
00626 }
00627 }
00628 }
00629 }
00630 }