r55888 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r55887‎ | r55888 | r55889 >
Date:16:08, 6 September 2009
Author:mkroetzsch
Status:deferred
Tags:
Comment:
separated code for query parsing into its own file for clarity
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/includes/SMW_GlobalFunctions.php (modified) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/SMW_QueryParser.php (added) (history)
  • /trunk/extensions/SemanticMediaWiki/includes/SMW_QueryProcessor.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/includes/SMW_QueryParser.php
@@ -0,0 +1,664 @@
 2+<?php
 3+/**
 4+ * This file contains a class for parsing inline query strings.
 5+ * @file
 6+ * @ingroup SMWQuery
 7+ * @author Markus Krötzsch
 8+ */
 9+
 10+/**
 11+ * Objects of this class are in charge of parsing a query string in order
 12+ * to create an SMWDescription. The class and methods are not static in order
 13+ * to more cleanly store the intermediate state and progress of the parser.
 14+ * @ingroup SMWQuery
 15+ */
 16+class SMWQueryParser {
 17+
 18+ protected $m_sepstack; // list of open blocks ("parentheses") that need closing at current step
 19+ protected $m_curstring; // remaining string to be parsed (parsing eats query string from the front)
 20+ protected $m_errors; // empty array if all went right, array of strings otherwise
 21+ protected $m_label; //label of the main query result
 22+ protected $m_defaultns; //description of the default namespace restriction, or NULL if not used
 23+
 24+ protected $m_categoryprefix; // cache label of category namespace . ':'
 25+ protected $m_conceptprefix; // cache label of concept namespace . ':'
 26+ protected $m_queryfeatures; // query features to be supported, format similar to $smwgQFeatures
 27+
 28+ public function SMWQueryParser($queryfeatures = false) {
 29+ global $wgContLang, $smwgQFeatures;
 30+ $this->m_categoryprefix = $wgContLang->getNsText(NS_CATEGORY) . ':';
 31+ $this->m_conceptprefix = $wgContLang->getNsText(SMW_NS_CONCEPT) . ':';
 32+ $this->m_defaultns = NULL;
 33+ $this->m_queryfeatures = $queryfeatures===false?$smwgQFeatures:$queryfeatures;
 34+ }
 35+
 36+ /**
 37+ * Provide an array of namespace constants that are used as default restrictions.
 38+ * If NULL is given, no such default restrictions will be added (faster).
 39+ */
 40+ public function setDefaultNamespaces($nsarray) {
 41+ $this->m_defaultns = NULL;
 42+ if ($nsarray !== NULL) {
 43+ foreach ($nsarray as $ns) {
 44+ $this->m_defaultns = $this->addDescription($this->m_defaultns, new SMWNamespaceDescription($ns), false);
 45+ }
 46+ }
 47+ }
 48+
 49+ /**
 50+ * Compute an SMWDescription from a query string. Returns whatever descriptions could be
 51+ * wrestled from the given string (the most general result being SMWThingDescription if
 52+ * no meaningful condition was extracted).
 53+ */
 54+ public function getQueryDescription($querystring) {
 55+ wfProfileIn('SMWQueryParser::getQueryDescription (SMW)');
 56+ $this->m_errors = array();
 57+ $this->m_label = '';
 58+ $this->m_curstring = $querystring;
 59+ $this->m_sepstack = array();
 60+ $setNS = false;
 61+ $result = $this->getSubqueryDescription($setNS, $this->m_label);
 62+ if (!$setNS) { // add default namespaces if applicable
 63+ $result = $this->addDescription($this->m_defaultns, $result);
 64+ }
 65+ if ($result === NULL) { // parsing went wrong, no default namespaces
 66+ $result = new SMWThingDescription();
 67+ }
 68+ wfProfileOut('SMWQueryParser::getQueryDescription (SMW)');
 69+ return $result;
 70+ }
 71+
 72+ /**
 73+ * Return array of error messages (possibly empty).
 74+ */
 75+ public function getErrors() {
 76+ return $this->m_errors;
 77+ }
 78+
 79+ /**
 80+ * Return error message or empty string if no error occurred.
 81+ */
 82+ public function getErrorString() {
 83+ return smwfEncodeMessages($this->m_errors);
 84+ }
 85+
 86+ /**
 87+ * Return label for the results of this query (which
 88+ * might be empty if no such information was passed).
 89+ */
 90+ public function getLabel() {
 91+ return $this->m_label;
 92+ }
 93+
 94+
 95+ /**
 96+ * Compute an SMWDescription for current part of a query, which should
 97+ * be a standalone query (the main query or a subquery enclosed within
 98+ * "\<q\>...\</q\>". Recursively calls similar methods and returns NULL upon error.
 99+ *
 100+ * The call-by-ref parameter $setNS is a boolean. Its input specifies whether
 101+ * the query should set the current default namespace if no namespace restrictions
 102+ * were given. If false, the calling super-query is happy to set the required
 103+ * NS-restrictions by itself if needed. Otherwise the subquery has to impose the defaults.
 104+ * This is so, since outermost queries and subqueries of disjunctions will have to set
 105+ * their own default restrictions.
 106+ *
 107+ * The return value of $setNS specifies whether or not the subquery has a namespace
 108+ * specification in place. This might happen automatically if the query string imposes
 109+ * such restrictions. The return value is important for those callers that otherwise
 110+ * set up their own restrictions.
 111+ *
 112+ * Note that $setNS is no means to switch on or off default namespaces in general,
 113+ * but just controls query generation. For general effect, the default namespaces
 114+ * should be set to NULL.
 115+ *
 116+ * The call-by-ref parameter $label is used to append any label strings found.
 117+ */
 118+ protected function getSubqueryDescription(&$setNS, &$label) {
 119+ global $smwgQPrintoutLimit;
 120+ wfLoadExtensionMessages('SemanticMediaWiki');
 121+ $conjunction = NULL; // used for the current inner conjunction
 122+ $disjuncts = array(); // (disjunctive) array of subquery conjunctions
 123+ $printrequests = array(); // the printrequests found for this query level
 124+ $hasNamespaces = false; // does the current $conjnuction have its own namespace restrictions?
 125+ $mustSetNS = $setNS; // must ns restrictions be set? (may become true even if $setNS is false)
 126+
 127+ $continue = ($chunk = $this->readChunk()) != ''; // skip empty subquery completely, thorwing an error
 128+ while ($continue) {
 129+ $setsubNS = false;
 130+ switch ($chunk) {
 131+ case '[[': // start new link block
 132+ $ld = $this->getLinkDescription($setsubNS, $label);
 133+ if ($ld instanceof SMWPrintRequest) {
 134+ $printrequests[] = $ld;
 135+ } elseif ($ld instanceof SMWDescription) {
 136+ $conjunction = $this->addDescription($conjunction,$ld);
 137+ }
 138+ break;
 139+ case '<q>': // enter new subquery, currently irrelevant but possible
 140+ $this->pushDelimiter('</q>');
 141+ $conjunction = $this->addDescription($conjunction, $this->getSubqueryDescription($setsubNS, $label));
 142+ break;
 143+ case 'OR': case '||': case '': case '</q>': // finish disjunction and maybe subquery
 144+ if ($this->m_defaultns !== NULL) { // possibly add namespace restrictions
 145+ if ( $hasNamespaces && !$mustSetNS) {
 146+ // add ns restrictions to all earlier conjunctions (all of which did not have them yet)
 147+ $mustSetNS = true; // enforce NS restrictions from now on
 148+ $newdisjuncts = array();
 149+ foreach ($disjuncts as $conj) {
 150+ $newdisjuncts[] = $this->addDescription($conj, $this->m_defaultns);
 151+ }
 152+ $disjuncts = $newdisjuncts;
 153+ } elseif ( !$hasNamespaces && $mustSetNS) {
 154+ // add ns restriction to current result
 155+ $conjunction = $this->addDescription($conjunction, $this->m_defaultns);
 156+ }
 157+ }
 158+ $disjuncts[] = $conjunction;
 159+ // start anew
 160+ $conjunction = NULL;
 161+ $hasNamespaces = false;
 162+ // finish subquery?
 163+ if ($chunk == '</q>') {
 164+ if ($this->popDelimiter('</q>')) {
 165+ $continue = false; // leave the loop
 166+ } else {
 167+ $this->m_errors[] = wfMsgForContent('smw_toomanyclosing', $chunk);
 168+ return NULL;
 169+ }
 170+ } elseif ($chunk == '') {
 171+ $continue = false;
 172+ }
 173+ break;
 174+ case '+': // "... AND true" (ignore)
 175+ break;
 176+ default: // error: unexpected $chunk
 177+ $this->m_errors[] = wfMsgForContent('smw_unexpectedpart', $chunk);
 178+ //return NULL; // Try to go on, it can only get better ...
 179+ }
 180+ if ($setsubNS) { // namespace restrictions encountered in current conjunct
 181+ $hasNamespaces = true;
 182+ }
 183+ if ($continue) { // read on only if $continue remained true
 184+ $chunk = $this->readChunk();
 185+ }
 186+ }
 187+
 188+ if (count($disjuncts) > 0) { // make disjunctive result
 189+ $result = NULL;
 190+ foreach ($disjuncts as $d) {
 191+ if ($d === NULL) {
 192+ $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
 193+ $setNS = false;
 194+ return NULL;
 195+ } else {
 196+ $result = $this->addDescription($result, $d, false);
 197+ }
 198+ }
 199+ } else {
 200+ $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
 201+ $setNS = false;
 202+ return NULL;
 203+ }
 204+ $setNS = $mustSetNS; // NOTE: also false if namespaces were given but no default NS descs are available
 205+
 206+ $prcount = 0;
 207+ foreach ($printrequests as $pr) { // add printrequests
 208+ if ($prcount < $smwgQPrintoutLimit) {
 209+ $result->addPrintRequest($pr);
 210+ $prcount++;
 211+ } else {
 212+ $this->m_errors[] = wfMsgForContent('smw_overprintoutlimit');
 213+ break;
 214+ }
 215+ }
 216+ return $result;
 217+ }
 218+
 219+ /**
 220+ * Compute an SMWDescription for current part of a query, which should
 221+ * be the content of "[[ ... ]]". Alternatively, if the current syntax
 222+ * specifies a print request, return the print request object.
 223+ * Returns NULL upon error.
 224+ *
 225+ * Parameters $setNS and $label have the same use as in getSubqueryDescription().
 226+ */
 227+ protected function getLinkDescription(&$setNS, &$label) {
 228+ // This method is called when we encountered an opening '[['. The following
 229+ // block could be a Category-statement, fixed object, property statements,
 230+ // or according print statements.
 231+ $chunk = $this->readChunk('',true,false); // NOTE: untrimmed, initial " " escapes prop. chains
 232+ if ( (smwfNormalTitleText($chunk) == $this->m_categoryprefix) || // category statement or
 233+ (smwfNormalTitleText($chunk) == $this->m_conceptprefix) ) { // concept statement
 234+ return $this->getClassDescription($setNS, $label,
 235+ (smwfNormalTitleText($chunk) == $this->m_categoryprefix));
 236+ } else { // fixed subject, namespace restriction, property query, or subquery
 237+ $sep = $this->readChunk('',false); //do not consume hit, "look ahead"
 238+ if ( ($sep == '::') || ($sep == ':=') ) {
 239+ if ($chunk{0} !=':') { // property statement
 240+ return $this->getPropertyDescription($chunk, $setNS, $label);
 241+ } else { // escaped article description, read part after :: to get full contents
 242+ $chunk .= $this->readChunk('\[\[|\]\]|\|\||\|');
 243+ return $this->getArticleDescription(trim($chunk), $setNS, $label);
 244+ }
 245+ } else { // Fixed article/namespace restriction. $sep should be ]] or ||
 246+ return $this->getArticleDescription(trim($chunk), $setNS, $label);
 247+ }
 248+ }
 249+ }
 250+
 251+ /**
 252+ * Parse a category description (the part of an inline query that
 253+ * is in between "[[Category:" and the closing "]]" and create a
 254+ * suitable description.
 255+ */
 256+ protected function getClassDescription(&$setNS, &$label, $category=true) {
 257+ // note: no subqueries allowed here, inline disjunction allowed, wildcards allowed
 258+ $result = NULL;
 259+ $continue = true;
 260+ while ($continue) {
 261+ $chunk = $this->readChunk();
 262+ if ($chunk == '+') {
 263+ //wildcard, ignore for categories (semantically meaningless, everything is in some class)
 264+ } else { //assume category/concept title
 265+ /// NOTE: use m_c...prefix to prevent problems with, e.g., [[Category:Template:Test]]
 266+ $class = Title::newFromText(($category?$this->m_categoryprefix:$this->m_conceptprefix) . $chunk);
 267+ if ($class !== NULL) {
 268+ $desc = $category?new SMWClassDescription($class):new SMWConceptDescription($class);
 269+ $result = $this->addDescription($result, $desc, false);
 270+ }
 271+ }
 272+ $chunk = $this->readChunk();
 273+ $continue = ($chunk == '||') && $category; // disjunctions only for cateories
 274+ }
 275+
 276+ return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
 277+ }
 278+
 279+ /**
 280+ * Parse a property description (the part of an inline query that
 281+ * is in between "[[Some property::" and the closing "]]" and create a
 282+ * suitable description. The "::" is the first chunk on the current
 283+ * string.
 284+ */
 285+ protected function getPropertyDescription($propertyname, &$setNS, &$label) {
 286+ wfLoadExtensionMessages('SemanticMediaWiki');
 287+ $this->readChunk(); // consume separator ":=" or "::"
 288+ // first process property chain syntax (e.g. "property1.property2::value"):
 289+ if ($propertyname{0} == ' ') { // escape
 290+ $propertynames = array($propertyname);
 291+ } else {
 292+ $propertynames = explode('.', $propertyname);
 293+ }
 294+ $properties = array();
 295+ $typeid = '_wpg';
 296+ foreach ($propertynames as $name) {
 297+ if ($typeid != '_wpg') { // non-final property in chain was no wikipage: not allowed
 298+ $this->m_errors[] = wfMsgForContent('smw_valuesubquery', $prevname);
 299+ return NULL; ///TODO: read some more chunks and try to finish [[ ]]
 300+ }
 301+ $property = SMWPropertyValue::makeUserProperty($name);
 302+ if (!$property->isValid()) { // illegal property identifier
 303+ $this->m_errors = array_merge($this->m_errors, $property->getErrors());
 304+ return NULL; ///TODO: read some more chunks and try to finish [[ ]]
 305+ }
 306+ $typeid = $property->getPropertyTypeID();
 307+ $prevname = $name;
 308+ $properties[] = $property;
 309+ } ///NOTE: after iteration, $property and $typeid correspond to last value
 310+
 311+ $innerdesc = NULL;
 312+ $continue = true;
 313+ while ($continue) {
 314+ $chunk = $this->readChunk();
 315+ switch ($chunk) {
 316+ case '+': // wildcard, add namespaces for page-type properties
 317+ if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
 318+ $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
 319+ } else {
 320+ $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
 321+ }
 322+ $chunk = $this->readChunk();
 323+ break;
 324+ case '<q>': // subquery, set default namespaces
 325+ if ($typeid == '_wpg') {
 326+ $this->pushDelimiter('</q>');
 327+ $setsubNS = true;
 328+ $sublabel = '';
 329+ $innerdesc = $this->addDescription($innerdesc, $this->getSubqueryDescription($setsubNS, $sublabel), false);
 330+ } else { // no subqueries allowed for non-pages
 331+ $this->m_errors[] = wfMsgForContent('smw_valuesubquery', end($propertynames));
 332+ $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
 333+ }
 334+ $chunk = $this->readChunk();
 335+ break;
 336+ default: //normal object value or print statement
 337+ // read value(s), possibly with inner [[...]]
 338+ $open = 1;
 339+ $value = $chunk;
 340+ $continue2 = true;
 341+ // read value with inner [[, ]], ||
 342+ while ( ($open > 0) && ($continue2) ) {
 343+ $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
 344+ switch ($chunk) {
 345+ case '[[': // open new [[ ]]
 346+ $open++;
 347+ break;
 348+ case ']]': // close [[ ]]
 349+ $open--;
 350+ break;
 351+ case '|': case '||': // terminates only outermost [[ ]]
 352+ if ($open == 1) {
 353+ $open = 0;
 354+ }
 355+ break;
 356+ case '': ///TODO: report error; this is not good right now
 357+ $continue2 = false;
 358+ break;
 359+ }
 360+ if ($open != 0) {
 361+ $value .= $chunk;
 362+ }
 363+ } ///NOTE: at this point, we normally already read one more chunk behind the value
 364+
 365+ if ($typeid == '__nry') { // nary value
 366+ $dv = SMWDataValueFactory::newPropertyObjectValue($property);
 367+ $dv->acceptQuerySyntax();
 368+ $dv->setUserValue($value);
 369+ $vl = $dv->getValueList();
 370+ $pm = $dv->getPrintModifier();
 371+ if ($vl !== NULL) { // prefer conditions over print statements (only one possible right now)
 372+ $innerdesc = $this->addDescription($innerdesc, $vl, false);
 373+ } elseif ($pm !== false) {
 374+ if ($chunk == '|') {
 375+ $printlabel = $this->readChunk('\]\]');
 376+ if ($printlabel != ']]') {
 377+ $chunk = $this->readChunk('\]\]');
 378+ } else {
 379+ $printlabel = '';
 380+ $chunk = ']]';
 381+ }
 382+ } else {
 383+ $printlabel = $property->getWikiValue();
 384+ }
 385+ if ($chunk == ']]') {
 386+ return new SMWPrintRequest(SMWPrintRequest::PRINT_PROP, $printlabel, $property, $pm);
 387+ } else {
 388+ $this->m_errors[] = wfMsgForContent('smw_badprintout');
 389+ return NULL;
 390+ }
 391+ }
 392+ } else { // unary value
 393+ $comparator = SMW_CMP_EQ;
 394+ $printmodifier = '';
 395+ SMWQueryParser::prepareValue($value, $comparator, $printmodifier);
 396+ $dv = SMWDataValueFactory::newPropertyObjectValue($property, $value);
 397+ if (!$dv->isValid()) {
 398+ $this->m_errors = $this->m_errors + $dv->getErrors();
 399+ $vd = new SMWThingDescription();
 400+ } else {
 401+ $vd = new SMWValueDescription($dv, $comparator);
 402+ }
 403+ $innerdesc = $this->addDescription($innerdesc, $vd, false);
 404+ }
 405+ }
 406+ $continue = ($chunk == '||');
 407+ }
 408+
 409+ if ($innerdesc === NULL) { // make a wildcard search
 410+ if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
 411+ $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
 412+ } else {
 413+ $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
 414+ }
 415+ $this->m_errors[] = wfMsgForContent('smw_propvalueproblem', $property->getWikiValue());
 416+ }
 417+ $properties = array_reverse($properties);
 418+ foreach ($properties as $property) {
 419+ $innerdesc = new SMWSomeProperty($property,$innerdesc);
 420+ }
 421+ $result = $innerdesc;
 422+ return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
 423+ }
 424+
 425+
 426+ /**
 427+ * Prepare a single value string, possibly extracting comparators and
 428+ * printmodifier. $value is changed to consist only of the remaining
 429+ * effective value string, or of "*" for print statements.
 430+ */
 431+ static public function prepareValue(&$value, &$comparator, &$printmodifier) {
 432+ global $smwgQComparators;
 433+ $list = preg_split('/^(' . $smwgQComparators . ')/u',$value, 2, PREG_SPLIT_DELIM_CAPTURE);
 434+ $comparator = SMW_CMP_EQ;
 435+ if (count($list) == 3) { // initial comparator found ($list[1] should be empty)
 436+ switch ($list[1]) {
 437+ case '<':
 438+ $comparator = SMW_CMP_LEQ;
 439+ $value = $list[2];
 440+ break;
 441+ case '>':
 442+ $comparator = SMW_CMP_GEQ;
 443+ $value = $list[2];
 444+ break;
 445+ case '!':
 446+ $comparator = SMW_CMP_NEQ;
 447+ $value = $list[2];
 448+ break;
 449+ case '~':
 450+ $comparator = SMW_CMP_LIKE;
 451+ $value = $list[2];
 452+ break;
 453+ //default: not possible
 454+ }
 455+ }
 456+ }
 457+
 458+ /**
 459+ * Parse an article description (the part of an inline query that
 460+ * is in between "[[" and the closing "]]" assuming it is not specifying
 461+ * a category or property) and create a suitable description.
 462+ * The first chunk behind the "[[" has already been read and is
 463+ * passed as a parameter.
 464+ */
 465+ protected function getArticleDescription($firstchunk, &$setNS, &$label) {
 466+ wfLoadExtensionMessages('SemanticMediaWiki');
 467+ $chunk = $firstchunk;
 468+ $result = NULL;
 469+ $continue = true;
 470+ //$innerdesc = NULL;
 471+ while ($continue) {
 472+ if ($chunk == '<q>') { // no subqueries of the form [[<q>...</q>]] (not needed)
 473+ $this->m_errors[] = wfMsgForContent('smw_misplacedsubquery');
 474+ return NULL;
 475+ }
 476+ $list = preg_split('/:/', $chunk, 3); // ":Category:Foo" "User:bar" ":baz" ":+"
 477+ if ( ($list[0] == '') && (count($list)==3) ) {
 478+ $list = array_slice($list, 1);
 479+ }
 480+ if ( (count($list) == 2) && ($list[1] == '+') ) { // try namespace restriction
 481+ global $wgContLang;
 482+ $idx = $wgContLang->getNsIndex($list[0]);
 483+ if ($idx !== false) {
 484+ $result = $this->addDescription($result, new SMWNamespaceDescription($idx), false);
 485+ }
 486+ } else {
 487+ $value = SMWDataValueFactory::newTypeIDValue('_wpg', $chunk);
 488+ if ($value->isValid()) {
 489+ $result = $this->addDescription($result, new SMWValueDescription($value), false);
 490+ }
 491+ }
 492+
 493+ $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
 494+ if ($chunk == '||') {
 495+ $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
 496+ $continue = true;
 497+ } else {
 498+ $continue = false;
 499+ }
 500+ }
 501+
 502+ return $this->finishLinkDescription($chunk, true, $result, $setNS, $label);
 503+ }
 504+
 505+ protected function finishLinkDescription($chunk, $hasNamespaces, $result, &$setNS, &$label) {
 506+ wfLoadExtensionMessages('SemanticMediaWiki');
 507+ if ($result === NULL) { // no useful information or concrete error found
 508+ $this->m_errors[] = wfMsgForContent('smw_badqueryatom');
 509+ } elseif (!$hasNamespaces && $setNS && ($this->m_defaultns !== NULL) ) {
 510+ $result = $this->addDescription($result, $this->m_defaultns);
 511+ $hasNamespaces = true;
 512+ }
 513+ $setNS = $hasNamespaces;
 514+
 515+ // terminate link (assuming that next chunk was read already)
 516+ if ($chunk == '|') {
 517+ $chunk = $this->readChunk('\]\]');
 518+ if ($chunk != ']]') {
 519+ $label .= $chunk;
 520+ $chunk = $this->readChunk('\]\]');
 521+ } else { // empty label does not add to overall label
 522+ $chunk = ']]';
 523+ }
 524+ }
 525+ if ($chunk != ']]') {
 526+ // What happended? We found some chunk that could not be processed as
 527+ // link content (as in [[Category:Test<q>]]) and there was no label to
 528+ // eat it. Or the closing ]] are just missing entirely.
 529+ if ($chunk != '') {
 530+ $this->m_errors[] = wfMsgForContent('smw_misplacedsymbol', htmlspecialchars($chunk));
 531+ // try to find a later closing ]] to finish this misshaped subpart
 532+ $chunk = $this->readChunk('\]\]');
 533+ if ($chunk != ']]') {
 534+ $chunk = $this->readChunk('\]\]');
 535+ }
 536+ }
 537+ if ($chunk == '') {
 538+ $this->m_errors[] = wfMsgForContent('smw_noclosingbrackets');
 539+ }
 540+ }
 541+ return $result;
 542+ }
 543+
 544+ /**
 545+ * Get the next unstructured string chunk from the query string.
 546+ * Chunks are delimited by any of the special strings used in inline queries
 547+ * (such as [[, ]], <q>, ...). If the string starts with such a delimiter,
 548+ * this delimiter is returned. Otherwise the first string in front of such a
 549+ * delimiter is returned.
 550+ * Trailing and initial spaces are ignored if $trim is true, and chunks
 551+ * consisting only of spaces are not returned.
 552+ * If there is no more qurey string left to process, the empty string is
 553+ * returned (and in no other case).
 554+ *
 555+ * The stoppattern can be used to customise the matching, especially in order to
 556+ * overread certain special symbols.
 557+ *
 558+ * $consume specifies whether the returned chunk should be removed from the
 559+ * query string.
 560+ */
 561+ protected function readChunk($stoppattern = '', $consume=true, $trim=true) {
 562+ if ($stoppattern == '') {
 563+ $stoppattern = '\[\[|\]\]|::|:=|<q>|<\/q>|^' . $this->m_categoryprefix .
 564+ '|^' . $this->m_conceptprefix . '|\|\||\|';
 565+ }
 566+ $chunks = preg_split('/[\s]*(' . $stoppattern . ')/u', $this->m_curstring, 2, PREG_SPLIT_DELIM_CAPTURE);
 567+ if (count($chunks) == 1) { // no matches anymore, strip spaces and finish
 568+ if ($consume) {
 569+ $this->m_curstring = '';
 570+ }
 571+ return $trim?trim($chunks[0]):$chunks[0];
 572+ } elseif (count($chunks) == 3) { // this should generally happen if count is not 1
 573+ if ($chunks[0] == '') { // string started with delimiter
 574+ if ($consume) {
 575+ $this->m_curstring = $chunks[2];
 576+ }
 577+ return $trim?trim($chunks[1]):$chunks[1];
 578+ } else {
 579+ if ($consume) {
 580+ $this->m_curstring = $chunks[1] . $chunks[2];
 581+ }
 582+ return $trim?trim($chunks[0]):$chunks[0];
 583+ }
 584+ } else { return false; } //should never happen
 585+ }
 586+
 587+ /**
 588+ * Enter a new subblock in the query, which must at some time be terminated by the
 589+ * given $endstring delimiter calling popDelimiter();
 590+ */
 591+ protected function pushDelimiter($endstring) {
 592+ array_push($this->m_sepstack, $endstring);
 593+ }
 594+
 595+ /**
 596+ * Exit a subblock in the query ending with the given delimiter.
 597+ * If the delimiter does not match the top-most open block, false
 598+ * will be returned. Otherwise return true.
 599+ */
 600+ protected function popDelimiter($endstring) {
 601+ $topdelim = array_pop($this->m_sepstack);
 602+ return ($topdelim == $endstring);
 603+ }
 604+
 605+ /**
 606+ * Extend a given description by a new one, either by adding the new description
 607+ * (if the old one is a container description) or by creating a new container.
 608+ * The parameter $conjunction determines whether the combination of both descriptions
 609+ * should be a disjunction or conjunction.
 610+ *
 611+ * In the special case that the current description is NULL, the new one will just
 612+ * replace the current one.
 613+ *
 614+ * The return value is the expected combined description. The object $curdesc will
 615+ * also be changed (if it was non-NULL).
 616+ */
 617+ protected function addDescription($curdesc, $newdesc, $conjunction = true) {
 618+ wfLoadExtensionMessages('SemanticMediaWiki');
 619+ $notallowedmessage = 'smw_noqueryfeature';
 620+ if ($newdesc instanceof SMWSomeProperty) {
 621+ $allowed = $this->m_queryfeatures & SMW_PROPERTY_QUERY;
 622+ } elseif ($newdesc instanceof SMWClassDescription) {
 623+ $allowed = $this->m_queryfeatures & SMW_CATEGORY_QUERY;
 624+ } elseif ($newdesc instanceof SMWConceptDescription) {
 625+ $allowed = $this->m_queryfeatures & SMW_CONCEPT_QUERY;
 626+ } elseif ($newdesc instanceof SMWConjunction) {
 627+ $allowed = $this->m_queryfeatures & SMW_CONJUNCTION_QUERY;
 628+ $notallowedmessage = 'smw_noconjunctions';
 629+ } elseif ($newdesc instanceof SMWDisjunction) {
 630+ $allowed = $this->m_queryfeatures & SMW_DISJUNCTION_QUERY;
 631+ $notallowedmessage = 'smw_nodisjunctions';
 632+ } else {
 633+ $allowed = true;
 634+ }
 635+ if (!$allowed) {
 636+ $this->m_errors[] = wfMsgForContent($notallowedmessage, str_replace('[', '&#x005B;', $newdesc->getQueryString()));
 637+ return $curdesc;
 638+ }
 639+ if ($newdesc === NULL) {
 640+ return $curdesc;
 641+ } elseif ($curdesc === NULL) {
 642+ return $newdesc;
 643+ } else { // we already found descriptions
 644+ if ( (($conjunction) && ($curdesc instanceof SMWConjunction)) ||
 645+ ((!$conjunction) && ($curdesc instanceof SMWDisjunction)) ) { // use existing container
 646+ $curdesc->addDescription($newdesc);
 647+ return $curdesc;
 648+ } elseif ($conjunction) { // make new conjunction
 649+ if ($this->m_queryfeatures & SMW_CONJUNCTION_QUERY) {
 650+ return new SMWConjunction(array($curdesc,$newdesc));
 651+ } else {
 652+ $this->m_errors[] = wfMsgForContent('smw_noconjunctions', str_replace('[', '&#x005B;', $newdesc->getQueryString()));
 653+ return $curdesc;
 654+ }
 655+ } else { // make new disjunction
 656+ if ($this->m_queryfeatures & SMW_DISJUNCTION_QUERY) {
 657+ return new SMWDisjunction(array($curdesc,$newdesc));
 658+ } else {
 659+ $this->m_errors[] = wfMsgForContent('smw_nodisjunctions', str_replace('[', '&#x005B;', $newdesc->getQueryString()));
 660+ return $curdesc;
 661+ }
 662+ }
 663+ }
 664+ }
 665+}
Property changes on: trunk/extensions/SemanticMediaWiki/includes/SMW_QueryParser.php
___________________________________________________________________
Name: svn:eol-style
1666 + native
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_QueryProcessor.php
@@ -330,661 +330,3 @@
331331 }
332332
333333
334 -/**
335 - * Objects of this class are in charge of parsing a query string in order
336 - * to create an SMWDescription. The class and methods are not static in order
337 - * to more cleanly store the intermediate state and progress of the parser.
338 - * @ingroup SMWQuery
339 - */
340 -class SMWQueryParser {
341 -
342 - protected $m_sepstack; // list of open blocks ("parentheses") that need closing at current step
343 - protected $m_curstring; // remaining string to be parsed (parsing eats query string from the front)
344 - protected $m_errors; // empty array if all went right, array of strings otherwise
345 - protected $m_label; //label of the main query result
346 - protected $m_defaultns; //description of the default namespace restriction, or NULL if not used
347 -
348 - protected $m_categoryprefix; // cache label of category namespace . ':'
349 - protected $m_conceptprefix; // cache label of concept namespace . ':'
350 - protected $m_queryfeatures; // query features to be supported, format similar to $smwgQFeatures
351 -
352 - public function SMWQueryParser($queryfeatures = false) {
353 - global $wgContLang, $smwgQFeatures;
354 - $this->m_categoryprefix = $wgContLang->getNsText(NS_CATEGORY) . ':';
355 - $this->m_conceptprefix = $wgContLang->getNsText(SMW_NS_CONCEPT) . ':';
356 - $this->m_defaultns = NULL;
357 - $this->m_queryfeatures = $queryfeatures===false?$smwgQFeatures:$queryfeatures;
358 - }
359 -
360 - /**
361 - * Provide an array of namespace constants that are used as default restrictions.
362 - * If NULL is given, no such default restrictions will be added (faster).
363 - */
364 - public function setDefaultNamespaces($nsarray) {
365 - $this->m_defaultns = NULL;
366 - if ($nsarray !== NULL) {
367 - foreach ($nsarray as $ns) {
368 - $this->m_defaultns = $this->addDescription($this->m_defaultns, new SMWNamespaceDescription($ns), false);
369 - }
370 - }
371 - }
372 -
373 - /**
374 - * Compute an SMWDescription from a query string. Returns whatever descriptions could be
375 - * wrestled from the given string (the most general result being SMWThingDescription if
376 - * no meaningful condition was extracted).
377 - */
378 - public function getQueryDescription($querystring) {
379 - wfProfileIn('SMWQueryParser::getQueryDescription (SMW)');
380 - $this->m_errors = array();
381 - $this->m_label = '';
382 - $this->m_curstring = $querystring;
383 - $this->m_sepstack = array();
384 - $setNS = false;
385 - $result = $this->getSubqueryDescription($setNS, $this->m_label);
386 - if (!$setNS) { // add default namespaces if applicable
387 - $result = $this->addDescription($this->m_defaultns, $result);
388 - }
389 - if ($result === NULL) { // parsing went wrong, no default namespaces
390 - $result = new SMWThingDescription();
391 - }
392 - wfProfileOut('SMWQueryParser::getQueryDescription (SMW)');
393 - return $result;
394 - }
395 -
396 - /**
397 - * Return array of error messages (possibly empty).
398 - */
399 - public function getErrors() {
400 - return $this->m_errors;
401 - }
402 -
403 - /**
404 - * Return error message or empty string if no error occurred.
405 - */
406 - public function getErrorString() {
407 - return smwfEncodeMessages($this->m_errors);
408 - }
409 -
410 - /**
411 - * Return label for the results of this query (which
412 - * might be empty if no such information was passed).
413 - */
414 - public function getLabel() {
415 - return $this->m_label;
416 - }
417 -
418 -
419 - /**
420 - * Compute an SMWDescription for current part of a query, which should
421 - * be a standalone query (the main query or a subquery enclosed within
422 - * "\<q\>...\</q\>". Recursively calls similar methods and returns NULL upon error.
423 - *
424 - * The call-by-ref parameter $setNS is a boolean. Its input specifies whether
425 - * the query should set the current default namespace if no namespace restrictions
426 - * were given. If false, the calling super-query is happy to set the required
427 - * NS-restrictions by itself if needed. Otherwise the subquery has to impose the defaults.
428 - * This is so, since outermost queries and subqueries of disjunctions will have to set
429 - * their own default restrictions.
430 - *
431 - * The return value of $setNS specifies whether or not the subquery has a namespace
432 - * specification in place. This might happen automatically if the query string imposes
433 - * such restrictions. The return value is important for those callers that otherwise
434 - * set up their own restrictions.
435 - *
436 - * Note that $setNS is no means to switch on or off default namespaces in general,
437 - * but just controls query generation. For general effect, the default namespaces
438 - * should be set to NULL.
439 - *
440 - * The call-by-ref parameter $label is used to append any label strings found.
441 - */
442 - protected function getSubqueryDescription(&$setNS, &$label) {
443 - global $smwgQPrintoutLimit;
444 - wfLoadExtensionMessages('SemanticMediaWiki');
445 - $conjunction = NULL; // used for the current inner conjunction
446 - $disjuncts = array(); // (disjunctive) array of subquery conjunctions
447 - $printrequests = array(); // the printrequests found for this query level
448 - $hasNamespaces = false; // does the current $conjnuction have its own namespace restrictions?
449 - $mustSetNS = $setNS; // must ns restrictions be set? (may become true even if $setNS is false)
450 -
451 - $continue = ($chunk = $this->readChunk()) != ''; // skip empty subquery completely, thorwing an error
452 - while ($continue) {
453 - $setsubNS = false;
454 - switch ($chunk) {
455 - case '[[': // start new link block
456 - $ld = $this->getLinkDescription($setsubNS, $label);
457 - if ($ld instanceof SMWPrintRequest) {
458 - $printrequests[] = $ld;
459 - } elseif ($ld instanceof SMWDescription) {
460 - $conjunction = $this->addDescription($conjunction,$ld);
461 - }
462 - break;
463 - case '<q>': // enter new subquery, currently irrelevant but possible
464 - $this->pushDelimiter('</q>');
465 - $conjunction = $this->addDescription($conjunction, $this->getSubqueryDescription($setsubNS, $label));
466 - /// TODO: print requests from subqueries currently are ignored, should be moved down
467 - break;
468 - case 'OR': case '||': case '': case '</q>': // finish disjunction and maybe subquery
469 - if ($this->m_defaultns !== NULL) { // possibly add namespace restrictions
470 - if ( $hasNamespaces && !$mustSetNS) {
471 - // add ns restrictions to all earlier conjunctions (all of which did not have them yet)
472 - $mustSetNS = true; // enforce NS restrictions from now on
473 - $newdisjuncts = array();
474 - foreach ($disjuncts as $conj) {
475 - $newdisjuncts[] = $this->addDescription($conj, $this->m_defaultns);
476 - }
477 - $disjuncts = $newdisjuncts;
478 - } elseif ( !$hasNamespaces && $mustSetNS) {
479 - // add ns restriction to current result
480 - $conjunction = $this->addDescription($conjunction, $this->m_defaultns);
481 - }
482 - }
483 - $disjuncts[] = $conjunction;
484 - // start anew
485 - $conjunction = NULL;
486 - $hasNamespaces = false;
487 - // finish subquery?
488 - if ($chunk == '</q>') {
489 - if ($this->popDelimiter('</q>')) {
490 - $continue = false; // leave the loop
491 - } else {
492 - $this->m_errors[] = wfMsgForContent('smw_toomanyclosing', $chunk);
493 - return NULL;
494 - }
495 - } elseif ($chunk == '') {
496 - $continue = false;
497 - }
498 - break;
499 - case '+': // "... AND true" (ignore)
500 - break;
501 - default: // error: unexpected $chunk
502 - $this->m_errors[] = wfMsgForContent('smw_unexpectedpart', $chunk);
503 - //return NULL; // Try to go on, it can only get better ...
504 - }
505 - if ($setsubNS) { // namespace restrictions encountered in current conjunct
506 - $hasNamespaces = true;
507 - }
508 - if ($continue) { // read on only if $continue remained true
509 - $chunk = $this->readChunk();
510 - }
511 - }
512 -
513 - if (count($disjuncts) > 0) { // make disjunctive result
514 - $result = NULL;
515 - foreach ($disjuncts as $d) {
516 - if ($d === NULL) {
517 - $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
518 - $setNS = false;
519 - return NULL;
520 - } else {
521 - $result = $this->addDescription($result, $d, false);
522 - }
523 - }
524 - } else {
525 - $this->m_errors[] = wfMsgForContent('smw_emptysubquery');
526 - $setNS = false;
527 - return NULL;
528 - }
529 - $setNS = $mustSetNS; // NOTE: also false if namespaces were given but no default NS descs are available
530 -
531 - $prcount = 0;
532 - foreach ($printrequests as $pr) { // add printrequests
533 - if ($prcount < $smwgQPrintoutLimit) {
534 - $result->addPrintRequest($pr);
535 - $prcount++;
536 - } else {
537 - $this->m_errors[] = wfMsgForContent('smw_overprintoutlimit');
538 - break;
539 - }
540 - }
541 - return $result;
542 - }
543 -
544 - /**
545 - * Compute an SMWDescription for current part of a query, which should
546 - * be the content of "[[ ... ]]". Alternatively, if the current syntax
547 - * specifies a print request, return the print request object.
548 - * Returns NULL upon error.
549 - *
550 - * Parameters $setNS and $label have the same use as in getSubqueryDescription().
551 - */
552 - protected function getLinkDescription(&$setNS, &$label) {
553 - // This method is called when we encountered an opening '[['. The following
554 - // block could be a Category-statement, fixed object, property statements,
555 - // or according print statements.
556 - $chunk = $this->readChunk('',true,false); // NOTE: untrimmed, initial " " escapes prop. chains
557 - if ( (smwfNormalTitleText($chunk) == $this->m_categoryprefix) || // category statement or
558 - (smwfNormalTitleText($chunk) == $this->m_conceptprefix) ) { // concept statement
559 - return $this->getClassDescription($setNS, $label,
560 - (smwfNormalTitleText($chunk) == $this->m_categoryprefix));
561 - } else { // fixed subject, namespace restriction, property query, or subquery
562 - $sep = $this->readChunk('',false); //do not consume hit, "look ahead"
563 - if ( ($sep == '::') || ($sep == ':=') ) {
564 - if ($chunk{0} !=':') { // property statement
565 - return $this->getPropertyDescription($chunk, $setNS, $label);
566 - } else { // escaped article description, read part after :: to get full contents
567 - $chunk .= $this->readChunk('\[\[|\]\]|\|\||\|');
568 - return $this->getArticleDescription(trim($chunk), $setNS, $label);
569 - }
570 - } else { // Fixed article/namespace restriction. $sep should be ]] or ||
571 - return $this->getArticleDescription(trim($chunk), $setNS, $label);
572 - }
573 - }
574 - }
575 -
576 - /**
577 - * Parse a category description (the part of an inline query that
578 - * is in between "[[Category:" and the closing "]]" and create a
579 - * suitable description.
580 - */
581 - protected function getClassDescription(&$setNS, &$label, $category=true) {
582 - // note: no subqueries allowed here, inline disjunction allowed, wildcards allowed
583 - $result = NULL;
584 - $continue = true;
585 - while ($continue) {
586 - $chunk = $this->readChunk();
587 - if ($chunk == '+') {
588 - //wildcard, ignore for categories (semantically meaningless, everything is in some class)
589 - } else { //assume category/concept title
590 - /// NOTE: use m_c...prefix to prevent problems with, e.g., [[Category:Template:Test]]
591 - $class = Title::newFromText(($category?$this->m_categoryprefix:$this->m_conceptprefix) . $chunk);
592 - if ($class !== NULL) {
593 - $desc = $category?new SMWClassDescription($class):new SMWConceptDescription($class);
594 - $result = $this->addDescription($result, $desc, false);
595 - }
596 - }
597 - $chunk = $this->readChunk();
598 - $continue = ($chunk == '||') && $category; // disjunctions only for cateories
599 - }
600 -
601 - return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
602 - }
603 -
604 - /**
605 - * Parse a property description (the part of an inline query that
606 - * is in between "[[Some property::" and the closing "]]" and create a
607 - * suitable description. The "::" is the first chunk on the current
608 - * string.
609 - */
610 - protected function getPropertyDescription($propertyname, &$setNS, &$label) {
611 - wfLoadExtensionMessages('SemanticMediaWiki');
612 - $this->readChunk(); // consume separator ":=" or "::"
613 - // first process property chain syntax (e.g. "property1.property2::value"):
614 - if ($propertyname{0} == ' ') { // escape
615 - $propertynames = array($propertyname);
616 - } else {
617 - $propertynames = explode('.', $propertyname);
618 - }
619 - $properties = array();
620 - $typeid = '_wpg';
621 - foreach ($propertynames as $name) {
622 - if ($typeid != '_wpg') { // non-final property in chain was no wikipage: not allowed
623 - $this->m_errors[] = wfMsgForContent('smw_valuesubquery', $prevname);
624 - return NULL; ///TODO: read some more chunks and try to finish [[ ]]
625 - }
626 - $property = SMWPropertyValue::makeUserProperty($name);
627 - if (!$property->isValid()) { // illegal property identifier
628 - $this->m_errors = array_merge($this->m_errors, $property->getErrors());
629 - return NULL; ///TODO: read some more chunks and try to finish [[ ]]
630 - }
631 - $typeid = $property->getPropertyTypeID();
632 - $prevname = $name;
633 - $properties[] = $property;
634 - } ///NOTE: after iteration, $property and $typeid correspond to last value
635 -
636 - $innerdesc = NULL;
637 - $continue = true;
638 - while ($continue) {
639 - $chunk = $this->readChunk();
640 - switch ($chunk) {
641 - case '+': // wildcard, add namespaces for page-type properties
642 - if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
643 - $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
644 - } else {
645 - $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
646 - }
647 - $chunk = $this->readChunk();
648 - break;
649 - case '<q>': // subquery, set default namespaces
650 - if ($typeid == '_wpg') {
651 - $this->pushDelimiter('</q>');
652 - $setsubNS = true;
653 - $sublabel = '';
654 - $innerdesc = $this->addDescription($innerdesc, $this->getSubqueryDescription($setsubNS, $sublabel), false);
655 - } else { // no subqueries allowed for non-pages
656 - $this->m_errors[] = wfMsgForContent('smw_valuesubquery', end($propertynames));
657 - $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
658 - }
659 - $chunk = $this->readChunk();
660 - break;
661 - default: //normal object value or print statement
662 - // read value(s), possibly with inner [[...]]
663 - $open = 1;
664 - $value = $chunk;
665 - $continue2 = true;
666 - // read value with inner [[, ]], ||
667 - while ( ($open > 0) && ($continue2) ) {
668 - $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
669 - switch ($chunk) {
670 - case '[[': // open new [[ ]]
671 - $open++;
672 - break;
673 - case ']]': // close [[ ]]
674 - $open--;
675 - break;
676 - case '|': case '||': // terminates only outermost [[ ]]
677 - if ($open == 1) {
678 - $open = 0;
679 - }
680 - break;
681 - case '': ///TODO: report error; this is not good right now
682 - $continue2 = false;
683 - break;
684 - }
685 - if ($open != 0) {
686 - $value .= $chunk;
687 - }
688 - } ///NOTE: at this point, we normally already read one more chunk behind the value
689 -
690 - if ($typeid == '__nry') { // nary value
691 - $dv = SMWDataValueFactory::newPropertyObjectValue($property);
692 - $dv->acceptQuerySyntax();
693 - $dv->setUserValue($value);
694 - $vl = $dv->getValueList();
695 - $pm = $dv->getPrintModifier();
696 - if ($vl !== NULL) { // prefer conditions over print statements (only one possible right now)
697 - $innerdesc = $this->addDescription($innerdesc, $vl, false);
698 - } elseif ($pm !== false) {
699 - if ($chunk == '|') {
700 - $printlabel = $this->readChunk('\]\]');
701 - if ($printlabel != ']]') {
702 - $chunk = $this->readChunk('\]\]');
703 - } else {
704 - $printlabel = '';
705 - $chunk = ']]';
706 - }
707 - } else {
708 - $printlabel = $property->getWikiValue();
709 - }
710 - if ($chunk == ']]') {
711 - return new SMWPrintRequest(SMWPrintRequest::PRINT_PROP, $printlabel, $property, $pm);
712 - } else {
713 - $this->m_errors[] = wfMsgForContent('smw_badprintout');
714 - return NULL;
715 - }
716 - }
717 - } else { // unary value
718 - $comparator = SMW_CMP_EQ;
719 - $printmodifier = '';
720 - SMWQueryParser::prepareValue($value, $comparator, $printmodifier);
721 - $dv = SMWDataValueFactory::newPropertyObjectValue($property, $value);
722 - if (!$dv->isValid()) {
723 - $this->m_errors = $this->m_errors + $dv->getErrors();
724 - $vd = new SMWThingDescription();
725 - } else {
726 - $vd = new SMWValueDescription($dv, $comparator);
727 - }
728 - $innerdesc = $this->addDescription($innerdesc, $vd, false);
729 - }
730 - }
731 - $continue = ($chunk == '||');
732 - }
733 -
734 - if ($innerdesc === NULL) { // make a wildcard search
735 - if ( ($this->m_defaultns !== NULL) && ($typeid == '_wpg') ) {
736 - $innerdesc = $this->addDescription($innerdesc, $this->m_defaultns, false);
737 - } else {
738 - $innerdesc = $this->addDescription($innerdesc, new SMWThingDescription(), false);
739 - }
740 - $this->m_errors[] = wfMsgForContent('smw_propvalueproblem', $property->getWikiValue());
741 - }
742 - $properties = array_reverse($properties);
743 - foreach ($properties as $property) {
744 - $innerdesc = new SMWSomeProperty($property,$innerdesc);
745 - }
746 - $result = $innerdesc;
747 - return $this->finishLinkDescription($chunk, false, $result, $setNS, $label);
748 - }
749 -
750 -
751 - /**
752 - * Prepare a single value string, possibly extracting comparators and
753 - * printmodifier. $value is changed to consist only of the remaining
754 - * effective value string, or of "*" for print statements.
755 - */
756 - static public function prepareValue(&$value, &$comparator, &$printmodifier) {
757 - global $smwgQComparators;
758 - $list = preg_split('/^(' . $smwgQComparators . ')/u',$value, 2, PREG_SPLIT_DELIM_CAPTURE);
759 - $comparator = SMW_CMP_EQ;
760 - if (count($list) == 3) { // initial comparator found ($list[1] should be empty)
761 - switch ($list[1]) {
762 - case '<':
763 - $comparator = SMW_CMP_LEQ;
764 - $value = $list[2];
765 - break;
766 - case '>':
767 - $comparator = SMW_CMP_GEQ;
768 - $value = $list[2];
769 - break;
770 - case '!':
771 - $comparator = SMW_CMP_NEQ;
772 - $value = $list[2];
773 - break;
774 - case '~':
775 - $comparator = SMW_CMP_LIKE;
776 - $value = $list[2];
777 - break;
778 - //default: not possible
779 - }
780 - }
781 - }
782 -
783 - /**
784 - * Parse an article description (the part of an inline query that
785 - * is in between "[[" and the closing "]]" assuming it is not specifying
786 - * a category or property) and create a suitable description.
787 - * The first chunk behind the "[[" has already been read and is
788 - * passed as a parameter.
789 - */
790 - protected function getArticleDescription($firstchunk, &$setNS, &$label) {
791 - wfLoadExtensionMessages('SemanticMediaWiki');
792 - $chunk = $firstchunk;
793 - $result = NULL;
794 - $continue = true;
795 - //$innerdesc = NULL;
796 - while ($continue) {
797 - if ($chunk == '<q>') { // no subqueries of the form [[<q>...</q>]] (not needed)
798 - $this->m_errors[] = wfMsgForContent('smw_misplacedsubquery');
799 - return NULL;
800 - }
801 - $list = preg_split('/:/', $chunk, 3); // ":Category:Foo" "User:bar" ":baz" ":+"
802 - if ( ($list[0] == '') && (count($list)==3) ) {
803 - $list = array_slice($list, 1);
804 - }
805 - if ( (count($list) == 2) && ($list[1] == '+') ) { // try namespace restriction
806 - global $wgContLang;
807 - $idx = $wgContLang->getNsIndex($list[0]);
808 - if ($idx !== false) {
809 - $result = $this->addDescription($result, new SMWNamespaceDescription($idx), false);
810 - }
811 - } else {
812 - $value = SMWDataValueFactory::newTypeIDValue('_wpg', $chunk);
813 - if ($value->isValid()) {
814 - $result = $this->addDescription($result, new SMWValueDescription($value), false);
815 - }
816 - }
817 -
818 - $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
819 - if ($chunk == '||') {
820 - $chunk = $this->readChunk('\[\[|\]\]|\|\||\|');
821 - $continue = true;
822 - } else {
823 - $continue = false;
824 - }
825 - }
826 -
827 - return $this->finishLinkDescription($chunk, true, $result, $setNS, $label);
828 - }
829 -
830 - protected function finishLinkDescription($chunk, $hasNamespaces, $result, &$setNS, &$label) {
831 - wfLoadExtensionMessages('SemanticMediaWiki');
832 - if ($result === NULL) { // no useful information or concrete error found
833 - $this->m_errors[] = wfMsgForContent('smw_badqueryatom');
834 - } elseif (!$hasNamespaces && $setNS && ($this->m_defaultns !== NULL) ) {
835 - $result = $this->addDescription($result, $this->m_defaultns);
836 - $hasNamespaces = true;
837 - }
838 - $setNS = $hasNamespaces;
839 -
840 - // terminate link (assuming that next chunk was read already)
841 - if ($chunk == '|') {
842 - $chunk = $this->readChunk('\]\]');
843 - if ($chunk != ']]') {
844 - $label .= $chunk;
845 - $chunk = $this->readChunk('\]\]');
846 - } else { // empty label does not add to overall label
847 - $chunk = ']]';
848 - }
849 - }
850 - if ($chunk != ']]') {
851 - // What happended? We found some chunk that could not be processed as
852 - // link content (as in [[Category:Test<q>]]) and there was no label to
853 - // eat it. Or the closing ]] are just missing entirely.
854 - if ($chunk != '') {
855 - $this->m_errors[] = wfMsgForContent('smw_misplacedsymbol', htmlspecialchars($chunk));
856 - // try to find a later closing ]] to finish this misshaped subpart
857 - $chunk = $this->readChunk('\]\]');
858 - if ($chunk != ']]') {
859 - $chunk = $this->readChunk('\]\]');
860 - }
861 - }
862 - if ($chunk == '') {
863 - $this->m_errors[] = wfMsgForContent('smw_noclosingbrackets');
864 - }
865 - }
866 - return $result;
867 - }
868 -
869 - /**
870 - * Get the next unstructured string chunk from the query string.
871 - * Chunks are delimited by any of the special strings used in inline queries
872 - * (such as [[, ]], <q>, ...). If the string starts with such a delimiter,
873 - * this delimiter is returned. Otherwise the first string in front of such a
874 - * delimiter is returned.
875 - * Trailing and initial spaces are ignored if $trim is true, and chunks
876 - * consisting only of spaces are not returned.
877 - * If there is no more qurey string left to process, the empty string is
878 - * returned (and in no other case).
879 - *
880 - * The stoppattern can be used to customise the matching, especially in order to
881 - * overread certain special symbols.
882 - *
883 - * $consume specifies whether the returned chunk should be removed from the
884 - * query string.
885 - */
886 - protected function readChunk($stoppattern = '', $consume=true, $trim=true) {
887 - if ($stoppattern == '') {
888 - $stoppattern = '\[\[|\]\]|::|:=|<q>|<\/q>|^' . $this->m_categoryprefix .
889 - '|^' . $this->m_conceptprefix . '|\|\||\|';
890 - }
891 - $chunks = preg_split('/[\s]*(' . $stoppattern . ')/u', $this->m_curstring, 2, PREG_SPLIT_DELIM_CAPTURE);
892 - if (count($chunks) == 1) { // no matches anymore, strip spaces and finish
893 - if ($consume) {
894 - $this->m_curstring = '';
895 - }
896 - return $trim?trim($chunks[0]):$chunks[0];
897 - } elseif (count($chunks) == 3) { // this should generally happen if count is not 1
898 - if ($chunks[0] == '') { // string started with delimiter
899 - if ($consume) {
900 - $this->m_curstring = $chunks[2];
901 - }
902 - return $trim?trim($chunks[1]):$chunks[1];
903 - } else {
904 - if ($consume) {
905 - $this->m_curstring = $chunks[1] . $chunks[2];
906 - }
907 - return $trim?trim($chunks[0]):$chunks[0];
908 - }
909 - } else { return false; } //should never happen
910 - }
911 -
912 - /**
913 - * Enter a new subblock in the query, which must at some time be terminated by the
914 - * given $endstring delimiter calling popDelimiter();
915 - */
916 - protected function pushDelimiter($endstring) {
917 - array_push($this->m_sepstack, $endstring);
918 - }
919 -
920 - /**
921 - * Exit a subblock in the query ending with the given delimiter.
922 - * If the delimiter does not match the top-most open block, false
923 - * will be returned. Otherwise return true.
924 - */
925 - protected function popDelimiter($endstring) {
926 - $topdelim = array_pop($this->m_sepstack);
927 - return ($topdelim == $endstring);
928 - }
929 -
930 - /**
931 - * Extend a given description by a new one, either by adding the new description
932 - * (if the old one is a container description) or by creating a new container.
933 - * The parameter $conjunction determines whether the combination of both descriptions
934 - * should be a disjunction or conjunction.
935 - *
936 - * In the special case that the current description is NULL, the new one will just
937 - * replace the current one.
938 - *
939 - * The return value is the expected combined description. The object $curdesc will
940 - * also be changed (if it was non-NULL).
941 - */
942 - protected function addDescription($curdesc, $newdesc, $conjunction = true) {
943 - wfLoadExtensionMessages('SemanticMediaWiki');
944 - $notallowedmessage = 'smw_noqueryfeature';
945 - if ($newdesc instanceof SMWSomeProperty) {
946 - $allowed = $this->m_queryfeatures & SMW_PROPERTY_QUERY;
947 - } elseif ($newdesc instanceof SMWClassDescription) {
948 - $allowed = $this->m_queryfeatures & SMW_CATEGORY_QUERY;
949 - } elseif ($newdesc instanceof SMWConceptDescription) {
950 - $allowed = $this->m_queryfeatures & SMW_CONCEPT_QUERY;
951 - } elseif ($newdesc instanceof SMWConjunction) {
952 - $allowed = $this->m_queryfeatures & SMW_CONJUNCTION_QUERY;
953 - $notallowedmessage = 'smw_noconjunctions';
954 - } elseif ($newdesc instanceof SMWDisjunction) {
955 - $allowed = $this->m_queryfeatures & SMW_DISJUNCTION_QUERY;
956 - $notallowedmessage = 'smw_nodisjunctions';
957 - } else {
958 - $allowed = true;
959 - }
960 - if (!$allowed) {
961 - $this->m_errors[] = wfMsgForContent($notallowedmessage, str_replace('[', '&#x005B;', $newdesc->getQueryString()));
962 - return $curdesc;
963 - }
964 - if ($newdesc === NULL) {
965 - return $curdesc;
966 - } elseif ($curdesc === NULL) {
967 - return $newdesc;
968 - } else { // we already found descriptions
969 - if ( (($conjunction) && ($curdesc instanceof SMWConjunction)) ||
970 - ((!$conjunction) && ($curdesc instanceof SMWDisjunction)) ) { // use existing container
971 - $curdesc->addDescription($newdesc);
972 - return $curdesc;
973 - } elseif ($conjunction) { // make new conjunction
974 - if ($this->m_queryfeatures & SMW_CONJUNCTION_QUERY) {
975 - return new SMWConjunction(array($curdesc,$newdesc));
976 - } else {
977 - $this->m_errors[] = wfMsgForContent('smw_noconjunctions', str_replace('[', '&#x005B;', $newdesc->getQueryString()));
978 - return $curdesc;
979 - }
980 - } else { // make new disjunction
981 - if ($this->m_queryfeatures & SMW_DISJUNCTION_QUERY) {
982 - return new SMWDisjunction(array($curdesc,$newdesc));
983 - } else {
984 - $this->m_errors[] = wfMsgForContent('smw_nodisjunctions', str_replace('[', '&#x005B;', $newdesc->getQueryString()));
985 - return $curdesc;
986 - }
987 - }
988 - }
989 - }
990 -}
991 -
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_GlobalFunctions.php
@@ -159,7 +159,7 @@
160160 $wgAutoloadClasses['SMWExpResource'] = $smwgIP . '/includes/export/SMW_Exp_Element.php';
161161 //// stores & queries
162162 $wgAutoloadClasses['SMWQueryProcessor'] = $smwgIP . '/includes/SMW_QueryProcessor.php';
163 - $wgAutoloadClasses['SMWQueryParser'] = $smwgIP . '/includes/SMW_QueryProcessor.php';
 163+ $wgAutoloadClasses['SMWQueryParser'] = $smwgIP . '/includes/SMW_QueryParser.php';
164164 $wgAutoloadClasses['SMWQuery'] = $smwgIP . '/includes/storage/SMW_Query.php';
165165 $wgAutoloadClasses['SMWQueryResult'] = $smwgIP . '/includes/storage/SMW_QueryResult.php';
166166 $wgAutoloadClasses['SMWStore'] = $smwgIP . '/includes/storage/SMW_Store.php';

Status & tagging log