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('[', '[', $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('[', '[', $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('[', '[', $newdesc->getQueryString())); |
| 660 | + return $curdesc; |
| 661 | + } |
| 662 | + } |
| 663 | + } |
| 664 | + } |
| 665 | +} |
Property changes on: trunk/extensions/SemanticMediaWiki/includes/SMW_QueryParser.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 666 | + native |
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_QueryProcessor.php |
— | — | @@ -330,661 +330,3 @@ |
331 | 331 | } |
332 | 332 | |
333 | 333 | |
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('[', '[', $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('[', '[', $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('[', '[', $newdesc->getQueryString())); |
985 | | - return $curdesc; |
986 | | - } |
987 | | - } |
988 | | - } |
989 | | - } |
990 | | -} |
991 | | - |
Index: trunk/extensions/SemanticMediaWiki/includes/SMW_GlobalFunctions.php |
— | — | @@ -159,7 +159,7 @@ |
160 | 160 | $wgAutoloadClasses['SMWExpResource'] = $smwgIP . '/includes/export/SMW_Exp_Element.php'; |
161 | 161 | //// stores & queries |
162 | 162 | $wgAutoloadClasses['SMWQueryProcessor'] = $smwgIP . '/includes/SMW_QueryProcessor.php'; |
163 | | - $wgAutoloadClasses['SMWQueryParser'] = $smwgIP . '/includes/SMW_QueryProcessor.php'; |
| 163 | + $wgAutoloadClasses['SMWQueryParser'] = $smwgIP . '/includes/SMW_QueryParser.php'; |
164 | 164 | $wgAutoloadClasses['SMWQuery'] = $smwgIP . '/includes/storage/SMW_Query.php'; |
165 | 165 | $wgAutoloadClasses['SMWQueryResult'] = $smwgIP . '/includes/storage/SMW_QueryResult.php'; |
166 | 166 | $wgAutoloadClasses['SMWStore'] = $smwgIP . '/includes/storage/SMW_Store.php'; |