r100109 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r100108‎ | r100109 | r100110 >
Date:07:33, 18 October 2011
Author:questpc
Status:deferred (Comments)
Tags:
Comment:
Fixing edge cases with improperly nested category definitions / templates in question type text. Added message documentation to the most of newly introduced messages.
Modified paths:
  • /trunk/extensions/QPoll/ctrl/question/qp_textquestion.php (modified) (history)
  • /trunk/extensions/QPoll/i18n/qp.i18n.php (modified) (history)

Diff [purge]

Index: trunk/extensions/QPoll/i18n/qp.i18n.php
@@ -157,18 +157,32 @@
158158 'pollresults' => 'Special page name in [[Special:SpecialPages]]',
159159 'qpollwebinstall' => 'Special page name in [[Special:SpecialPages]]',
160160 'qp_desc' => '{{desc}} Important notice: Categories can be grouped into category groups, which are internally referred as "spans". Such grouped categories become "subcategories". While the extension evolved, these groups were consequentially called as "spans", "metacategories", "category groups". Please read the on-line documentation carefully before translating.',
 161+ 'qp_desc-sp' => 'Description of extension\'s special page at Special:Version page.',
 162+ 'qp_result_NA' => '{{Identical|Not answered}}',
161163 'qp_result_error' => '{{Identical|Syntax error}}',
162164 'qp_vote_button' => '{{Identical|Vote}}',
 165+ 'qp_vote_again_button' => 'When the user already submitted the poll / quiz, text of submit button title changes to indicate that the data will be re-submitted again.',
 166+ 'qp_submit_attempts_left' => 'How many attempts of poll / quiz submissions are left to the current user.',
 167+ 'qp_polls_list' => 'A link title in the header of [[Special:Pollresults]] which displays list of polls at current wiki site.',
 168+ 'qp_users_list' => 'A link title in the header of [[Special:Pollresults]] which displays list of poll participants at current wiki site.',
 169+ 'qp_browse_to_poll' => 'A link title at [[Special:Pollresults]] which opens wiki page where the selected poll was defined.',
 170+ 'qp_browse_to_user' => 'A link title at [[Special:Pollresults]] which opens user page of selected poll\'s voter (participant).',
 171+ 'qp_browse_to_interpretation' => 'A link title at [[Special:Pollresults]] which opens wiki page where the interpretation script of selected poll was defined.',
 172+ 'qp_votes_count' => 'Displays how many times the user re-submitted the poll. $1 is number of poll submissions.',
163173 'qp_source_link' => '"Source" is the link text for a link to the page where the poll is defined.
164174 {{Identical|Source}}',
165175 'qp_stats_link' => '{{Identical|Statistics}}',
166176 'qp_users_link' => '{{Identical|User}}',
 177+ 'qp_voice_link' => 'A link title at [[Special:Pollresults]] which displays user vote (selected categories) for the selected poll.',
167178 'qp_voice_link_inv' => 'At the moment of query generation there was no user answer for the selected poll yet. Question mark encourages wiki administrator to re-submit the query again.',
168179 'qp_user_polls_link' => 'Parameters:
169180 * $1 is the number of polls participated in.
170181 * $2 is the name of the user this message refers to (optional - use for GENDER)',
171182 'qp_user_missing_polls_link' => 'Parameters:
172183 * $1 is the name of the user this message refers to (optional - use for GENDER)',
 184+ 'qp_not_participated_link' => 'A link title at [[Special:Pollresults]] which displays the list of users which participated in another polls but did not participate in the selected poll.',
 185+ 'qp_order_by_username' => 'Order the list of polls at [[Special:Pollresults]] by username.',
 186+ 'qp_order_by_polls_count' => 'Order the list of polls at [[Special:Pollresults]] by number of their voters (popularity of the polls).',
173187 'qp_results_line_qupl' => 'Parameters:
174188 * $1 is a link to the page page name the poll is on with the page title as link label
175189 * $2 is the poll name in plain text
@@ -180,18 +194,52 @@
181195 * $4 is a link to the poll statistics with link label {{msg-mw|qp_stats_link}}
182196 * $5 is a link to the users that participated in the poll with link label {{msg-mw|qp_users_link}}
183197 * $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}',
 198+ 'qp_header_line_qpul' => 'Parameters:
 199+* $1 is a link to the [[Special:Pollresults]] which displays either the list of users which participated in current poll, or the list of users that participated in another polls but this one
 200+* $2 is a link to the page title where the poll is defined
 201+* $3 is the poll name (poll identifier) in plain text',
 202+ 'qp_results_line_qpl' => 'Parameters:
 203+* $1 is a link to the page page name the poll is on with the page title as link label
 204+* $2 is the poll name in plain text
 205+* $3 is a link to the poll with link label {{msg-mw|qp_source_link}}
 206+* $4 is a link to the poll statistics with link label {{msg-mw|qp_stats_link}}
 207+* $5 is a link to the users that participated in the poll with link label {{msg-mw|qp_users_link}}
 208+* $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}',
184209 'qp_results_submit_attempts' => 'Parameters:
185210 * $1 is the number of submit attempts',
186 - 'qp_results_short_interpretation' => 'No parameters',
187 - 'qp_results_long_interpretation' => 'No parameters',
 211+ 'qp_results_interpretation_header' => 'Since v0.8.0 polls may have interpretation scripts defined at separate wiki pages, which allows to use the extension for quizes. Interpretation scripts return the following types of interpretation results: global error message, proposal error, proposal/category error, short interpretation, long interpretation, structured interpretation. This message is the header of the block of these results displayed both to end-user and to poll admin at [[Special:Pollresults]] page.',
 212+ 'qp_results_short_interpretation' => 'Short interpretation header. Short interpretation will contain small sortable string in separate block. No parameters.',
 213+ 'qp_results_long_interpretation' => 'Long interpretation header. Long interpretation will contain long text (usually displayed to end-user) in separate block. No parameters.',
 214+ 'qp_results_structured_interpretation' => 'Structured interpretation header. This type of interpretation is used to store measurable accountable structured data in interpretation scripts. Structured interpretation also can be read by another poll\'s interpretation scripts at later time and exported into XLS format. No parameters.',
188215 'qp_error_missed_dependance_poll' => 'Parameters:
189216 * $1 is the poll ID of the poll having an error.
190217 * $2 is a link to the page with the poll, that this erroneous poll depends on.
191218 * $3 is the poll ID of the poll, which this erroneous poll depends on.',
192219 'qp_error_too_many_spans' => 'There cannot be more category groups defined than the total count of subcategories.',
193220 'qp_error_too_few_spans' => 'Every category group should include at least two subcategories',
194 - 'qp_error_no_interpretation' => 'Title of interpretation script was specified in poll header, however no article was found with that title. Either remove "interpretation" xml attribute of poll or create the title specified by "interpretation" attribute.',
 221+ 'qp_error_no_interpretation' => 'Title of interpretation script was specified in poll header, but no article was found with that title. Either remove "interpretation" xml attribute of poll or create the title specified by "interpretation" attribute.',
 222+ 'qp_interpetation_wrong_answer' => 'Indicates that user-submitted choices of categories are considered incorrect by poll\'s interpretation script.',
 223+ 'qp_export_to_xls' => 'Poll average statistics will be exported into XLS file.',
 224+ 'qp_voices_to_xls' => 'Poll user category choices will be exported into XLS file.',
 225+ 'qp_interpretation_results_to_xls' => 'Poll\`s structured interpetation results will be exported into XLS file.',
 226+ 'qp_users_answered_questions' => 'Writes the message how many users answered to the poll questions in XLS file cell.',
 227+ 'qp_func_no_such_poll' => 'qpuserchoice parser function did not found the poll specified in it\'s parameter.
 228+* $1 is the poll address (page title + hash sign + qp prefix + poll id)',
 229+ 'qp_func_missing_question_id' => 'qpuserchoice parser function call should have question_id parameter specified.',
 230+ 'qp_func_invalid_question_id' => 'qpuserchoice parser function question_id call value should be integer number, [1..n].',
 231+ 'qp_func_missing_proposal_id' => 'qpuserchoice parser function call should have proposal id parameter specified.',
 232+ 'qp_func_invalid_proposal_id' => 'qpuserchoice parser function proposal id call value should be integer number, [0..n].',
 233+ 'qp_error_no_such_poll' => 'Poll statistics cannot be displayed because incorrect / missing poll address was specified in qpoll tag statistical mode.',
 234+ 'qp_error_in_question_header' => 'Main question header (common question or xml-like attributes) has syntax error.
 235+* $1 is the source text of header.',
 236+ 'qp_error_id_in_stats_mode' => 'Poll "id" attribute is meaningless in statistical display mode.',
 237+ 'qp_error_dependance_in_stats_mode' => 'Poll "dependance" attribute is meaningless in statistical display mode.',
 238+ 'qp_error_address_in_decl_mode' => 'Poll "address" attribute is meaningless in poll declaration / voting mode.',
 239+ 'qp_error_question_not_implemented' => 'Invalid value of qustion xml-like "type" attribute was specified. There is no such type of question. Please read the manual for list of valid question types.',
 240+ 'qp_error_invalid_question_type' => '{{Identical|Invalid value of qustion xml-like "type" attribute was specified. There is no such type of question. Please read the manual for list of valid question types.}}',
195241 'qp_error_interpretation_no_return' => 'Interpretation script missed an return statement.',
 242+ 'qp_error_type_in_stats_mode' => 'Question\'s "type" xml-like attribute is meaningless in statistical display mode.',
 243+ 'qp_error_no_poll_id' => 'Every poll definition in declaration / voting mode must have "id" attribute.',
196244 'qp_error_structured_interpretation_is_too_long' => "Structured interpretation is serialized string containing scalar value or an associative array stored into database table field. It's purpose is to have measurable, easily processable interpretation result for the particular poll which then can be processed by external tools (via XLS export) or, to be read and processed by next poll interpretation script (data import and in the future maybe an export as well). When the serialized string is too long, it should never be stored, otherwise it will be truncated by DBMS so it cannot be properly unserialized later.",
197245 'qp_error_eval_missed_lang_attr' => '{{doc-important|Do not translate "lang" as it is the name of an XML attribute that is not localised.}}',
198246 'qp_error_eval_mix_languages' => 'Parameters:
Index: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
@@ -161,6 +161,24 @@
162162 # regexp for separation of proposal line tokens
163163 static $propCatPattern = null;
164164
 165+ # source "raw" tokens (preg_split)
 166+ var $rawtokens;
 167+
 168+ /**
 169+ * array with parsed braces pairs
 170+ * every element may have the following keys:
 171+ * 'type' : type of brace string _key_values_ of self::$matching_braces
 172+ * 'closed_at' : indicates opening brace;
 173+ * false when no matching closing brace was found
 174+ * int key of $this->rawtokens a "link" to matching closing brace
 175+ * 'opened_at' : indicates closing brace;
 176+ * false when no matching opening brace was found
 177+ * int key of $this->rawtokens a "link" to matching opening brace
 178+ * 'iscat' : indicates brace that belongs to self::$input_braces_types AND
 179+ * has a proper match (both 'closed_at' and 'opened_at' are int)
 180+ */
 181+ var $brace_matches;
 182+
165183 # $propview is an instance of qp_TextQuestionProposalView
166184 # which contains parsed tokens for combined
167185 # proposal/category view
@@ -258,6 +276,111 @@
259277 }
260278
261279 /**
 280+ * Builds $this->brace_matches array which contains the list of matching braces
 281+ * from $this->rawtokens array.
 282+ */
 283+ private function findMatchingBraces() {
 284+ $brace_stack = array();
 285+ $this->brace_matches = array();
 286+ $matching_closed_brace = '';
 287+ # building $this->brace_matches
 288+ foreach ( $this->rawtokens as $tkey => $token ) {
 289+ if ( array_key_exists( $token, self::$matching_braces ) ) {
 290+ # opening braces
 291+ $this->brace_matches[$tkey] = array(
 292+ 'closed_at' => false,
 293+ 'type' => $token
 294+ );
 295+ $match = self::$matching_braces[$token];
 296+ # create new brace_stack element:
 297+ $last_brace_def = array(
 298+ 'match' => $match,
 299+ 'idx' => $tkey
 300+ );
 301+ if ( array_key_exists( $token, self::$input_braces_types ) &&
 302+ count( $brace_stack ) == 0 ) {
 303+ # will try to start category definiton (on closing)
 304+ $matching_closed_brace = $match;
 305+ }
 306+ array_push( $brace_stack, $last_brace_def );
 307+ } elseif ( in_array( $token, self::$matching_braces ) ) {
 308+ # closing braces
 309+ $this->brace_matches[$tkey] = array(
 310+ 'opened_at' => false,
 311+ # we always put opening brace in 'type'
 312+ 'type' => array_search( $token, self::$matching_braces, true )
 313+ );
 314+ if ( count( $brace_stack ) > 0 ) {
 315+ $last_brace_def = array_pop( $brace_stack );
 316+ if ( $last_brace_def['match'] != $token ) {
 317+ # braces didn't match
 318+ array_push( $brace_stack, $last_brace_def );
 319+ continue;
 320+ }
 321+ $idx = $last_brace_def['idx'];
 322+ # link opening / closing braces to each other
 323+ $this->brace_matches[$tkey]['opened_at'] = $idx;
 324+ $this->brace_matches[$idx]['closed_at'] = $tkey;
 325+ if ( count( $brace_stack ) > 0 || $token !== $matching_closed_brace ) {
 326+ # brace does not belong to self::$input_braces_types
 327+ continue;
 328+ }
 329+ # stack level 1 and found a matching_closed_brace;
 330+ # indicate end of category in $this->brace_matches
 331+ $this->brace_matches[$tkey]['iscat'] = true;
 332+ # indicate begin of category in $this->brace_matches
 333+ $this->brace_matches[$idx]['iscat'] = true;
 334+ # clear match
 335+ $matching_closed_brace = '';
 336+ }
 337+ }
 338+ }
 339+ # trying to backtrack non-closed braces only these which belong to
 340+ # self::$input_braces_types
 341+ $brace_keys = array_keys( $this->brace_matches, true );
 342+ for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) {
 343+ $brace_match = &$this->brace_matches[$brace_keys[$i]];
 344+ # match non-closed brace which belongs to self::$input_braces_types
 345+ # (non-closed category definitions)
 346+ if ( array_key_exists( 'opened_at', $brace_match ) &&
 347+ $brace_match['opened_at'] === false &&
 348+ array_key_exists( $brace_match['type'], self::$input_braces_types ) ) {
 349+ # try to find matching opening brace for current non-closed closing brace
 350+ for ( $j = $i - 1; $j >= 0; $j-- ) {
 351+ $checked_brace = &$this->brace_matches[$brace_keys[$j]];
 352+ if ( array_key_exists( 'iscat', $checked_brace ) ) {
 353+ # category definitions cannot be nested
 354+ break;
 355+ }
 356+ if ( array_key_exists( 'closed_at', $checked_brace ) ) {
 357+ # opening brace
 358+ if ( $checked_brace['closed_at'] === false ) {
 359+ # opening brace that has no matching closing brace
 360+ if ( $checked_brace['type'] === $brace_match['type'] ) {
 361+ # opening brace of the same type that $brace_match have
 362+ # link $brace_match and $checked_brace to each other:
 363+ # found matching non-closed opening brace; "link" both to each other
 364+ $brace_match['opened_at'] = $brace_keys[$j];
 365+ $brace_match['iscat'] = true;
 366+ $checked_brace['closed_at'] = $brace_keys[$i];
 367+ $checked_brace['iscat'] = true;
 368+ break;
 369+ }
 370+ } elseif ( $checked_brace['closed_at'] > $i ) {
 371+ # found opening brace that is closed at higher position than our
 372+ # $brace_match has;
 373+ # cross-closing is not allowed, the following code cannot be
 374+ # category definition and template at the same time:
 375+ # "<[ {{ ]> }}"
 376+ break;
 377+ }
 378+ }
 379+ }
 380+ }
 381+ }
 382+ }
 383+
 384+ /**
262385 * Creates question view which should be renreded and
263386 * also may be altered during the poll generation
264387 */
@@ -275,44 +398,41 @@
276399 $this->dbtokens = $brace_stack = array();
277400 $catId = 0;
278401 $last_brace = '';
279 - $tokens = preg_split( self::$propCatPattern, $raw, -1, PREG_SPLIT_DELIM_CAPTURE );
 402+ $this->rawtokens = preg_split( self::$propCatPattern, $raw, -1, PREG_SPLIT_DELIM_CAPTURE );
280403 $matching_closed_brace = '';
281 - foreach ( $tokens as $token ) {
282 - try {
283 - # $toBeStored == true when current $token has to be stored into
284 - # category / proposal list (depending on $opt->isCatDef)
285 - $toBeStored = true;
286 - if ( $token === '|' ) {
287 - # parameters separator
288 - if ( $opt->isCatDef ) {
289 - if ( count( $brace_stack ) == 1 && $brace_stack[0] === $matching_closed_brace ) {
290 - # pipe char starts new option only at top brace level,
291 - # with matching input brace
292 - $opt->addEmptyOption();
293 - $toBeStored = false;
294 - }
 404+ $this->findMatchingBraces();
 405+ foreach ( $this->rawtokens as $tkey => $token ) {
 406+ # $toBeStored == true when current $token has to be stored into
 407+ # category / proposal list (depending on $opt->isCatDef)
 408+ $toBeStored = true;
 409+ if ( $token === '|' ) {
 410+ # parameters separator
 411+ if ( $opt->isCatDef ) {
 412+ if ( count( $brace_stack ) == 1 && $brace_stack[0] === $matching_closed_brace ) {
 413+ # pipe char starts new option only at top brace level,
 414+ # with matching input brace
 415+ $opt->addEmptyOption();
 416+ $toBeStored = false;
295417 }
296 - } elseif ( array_key_exists( $token, self::$matching_braces ) ) {
297 - # opening braces
 418+ }
 419+ } elseif ( array_key_exists( $tkey, $this->brace_matches ) ) {
 420+ # brace
 421+ $brace_match = &$this->brace_matches[$tkey];
 422+ if ( array_key_exists( 'closed_at', $brace_match ) &&
 423+ $brace_match['closed_at'] !== false ) {
 424+ # valid opening brace
298425 array_push( $brace_stack, self::$matching_braces[$token] );
299 - if ( array_key_exists( $token, self::$input_braces_types ) &&
300 - count( $brace_stack ) == 1 ) {
301 - # start category definiton
 426+ if ( array_key_exists( 'iscat', $brace_match ) ) {
 427+ # start category definition
302428 $matching_closed_brace = self::$matching_braces[$token];
303429 $opt->startOptionsList( self::$input_braces_types[$token] );
304430 $toBeStored = false;
305431 }
306 - } elseif ( in_array( $token, self::$matching_braces ) ) {
307 - # closing braces
308 - if ( count( $brace_stack ) > 0 ) {
309 - $last_brace = array_pop( $brace_stack );
310 - if ( $last_brace != $token ) {
311 - array_push( $brace_stack, $last_brace );
312 - throw new Exception( 'break' );
313 - }
314 - if ( count( $brace_stack ) > 0 || $token !== $matching_closed_brace ) {
315 - throw new Exception( 'break' );
316 - }
 432+ } elseif ( array_key_exists( 'opened_at', $brace_match ) &&
 433+ $brace_match['opened_at'] !== false ) {
 434+ # valid closing brace
 435+ array_pop( $brace_stack );
 436+ if ( array_key_exists( 'iscat', $brace_match ) ) {
317437 $matching_closed_brace = '';
318438 # add new category input options for the storage
319439 $this->dbtokens[] = $opt->input_options;
@@ -325,10 +445,6 @@
326446 $toBeStored = false;
327447 }
328448 }
329 - } catch ( Exception $e ) {
330 - if ( $e->getMessage() !== 'break' ) {
331 - throw new MWException( $e->getMessage() );
332 - }
333449 }
334450 if ( $toBeStored ) {
335451 if ( $opt->isCatDef ) {

Comments

#Comment by Raymond (talk | contribs)   12:00, 18 October 2011

Thank you very much in the name of our translators!

#Comment by QuestPC (talk | contribs)   04:38, 19 October 2011

I appreciate the work of translators. It is nice to have messages translated to many languages used around the world. I hope I'll be able to go from alpha stage to beta at least and to update documentation as well.

Status & tagging log