http://www.mediawiki.org/wiki/Special:Code/MediaWiki/100109

Revision: 100109
Author:   questpc
Date:     2011-10-18 07:33:10 +0000 (Tue, 18 Oct 2011)
Log Message:
-----------
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
    trunk/extensions/QPoll/i18n/qp.i18n.php

Modified: trunk/extensions/QPoll/ctrl/question/qp_textquestion.php
===================================================================
--- trunk/extensions/QPoll/ctrl/question/qp_textquestion.php    2011-10-18 
04:11:24 UTC (rev 100108)
+++ trunk/extensions/QPoll/ctrl/question/qp_textquestion.php    2011-10-18 
07:33:10 UTC (rev 100109)
@@ -161,6 +161,24 @@
        # regexp for separation of proposal line tokens
        static $propCatPattern = null;
 
+       # source "raw" tokens (preg_split)
+       var $rawtokens;
+
+       /**
+        * array with parsed braces pairs
+        * every element may have the following keys:
+        *   'type' : type of brace string _key_values_ of 
self::$matching_braces
+        *   'closed_at' : indicates opening brace;
+        *     false when no matching closing brace was found
+        *     int  key of $this->rawtokens a "link" to matching closing brace
+        *   'opened_at' : indicates closing brace;
+        *     false when no matching opening brace was found
+        *     int  key of $this->rawtokens a "link" to matching opening brace
+        *   'iscat'     : indicates brace that belongs to 
self::$input_braces_types AND
+        *                 has a proper match (both 'closed_at' and 'opened_at' 
are int)
+        */
+       var $brace_matches;
+
        # $propview is an instance of qp_TextQuestionProposalView
        #             which contains parsed tokens for combined
        #             proposal/category view
@@ -258,6 +276,111 @@
        }
 
        /**
+        * Builds $this->brace_matches array which contains the list of 
matching braces
+        * from $this->rawtokens array.
+        */
+       private function findMatchingBraces() {
+               $brace_stack = array();
+               $this->brace_matches = array();
+               $matching_closed_brace = '';
+               # building $this->brace_matches
+               foreach ( $this->rawtokens as $tkey => $token ) {
+                       if ( array_key_exists( $token, self::$matching_braces ) 
) {
+                               # opening braces
+                               $this->brace_matches[$tkey] = array(
+                                       'closed_at' => false,
+                                       'type' => $token
+                               );
+                               $match = self::$matching_braces[$token];
+                               # create new brace_stack element:
+                               $last_brace_def = array(
+                                       'match' => $match,
+                                       'idx' => $tkey
+                               );
+                               if ( array_key_exists( $token, 
self::$input_braces_types ) &&
+                                               count( $brace_stack ) == 0 ) {
+                                       # will try to start category definiton 
(on closing)
+                                       $matching_closed_brace = $match;
+                               }
+                               array_push( $brace_stack, $last_brace_def );
+                       } elseif ( in_array( $token, self::$matching_braces ) ) 
{
+                               # closing braces
+                               $this->brace_matches[$tkey] = array(
+                                       'opened_at' => false,
+                                       # we always put opening brace in 'type'
+                                       'type' => array_search( $token, 
self::$matching_braces, true )
+                               );
+                               if ( count( $brace_stack ) > 0 ) {
+                                       $last_brace_def = array_pop( 
$brace_stack );
+                                       if ( $last_brace_def['match'] != $token 
) {
+                                               # braces didn't match
+                                               array_push( $brace_stack, 
$last_brace_def );
+                                               continue;
+                                       }
+                                       $idx = $last_brace_def['idx'];
+                                       # link opening / closing braces to each 
other
+                                       
$this->brace_matches[$tkey]['opened_at'] = $idx;
+                                       $this->brace_matches[$idx]['closed_at'] 
= $tkey;
+                                       if ( count( $brace_stack ) > 0 || 
$token !== $matching_closed_brace ) {
+                                               # brace does not belong to 
self::$input_braces_types
+                                               continue;
+                                       }
+                                       # stack level 1 and found a 
matching_closed_brace;
+                                       # indicate end of category in 
$this->brace_matches
+                                       $this->brace_matches[$tkey]['iscat'] = 
true;
+                                       # indicate begin of category in 
$this->brace_matches
+                                       $this->brace_matches[$idx]['iscat'] = 
true;
+                                       # clear match
+                                       $matching_closed_brace = '';
+                               }
+                       }
+               }
+               # trying to backtrack non-closed braces only these which belong 
to
+               # self::$input_braces_types
+               $brace_keys = array_keys( $this->brace_matches, true );
+               for ( $i = count( $brace_keys ) - 1; $i >= 0; $i-- ) {
+                       $brace_match = &$this->brace_matches[$brace_keys[$i]];
+                       # match non-closed brace which belongs to 
self::$input_braces_types
+                       # (non-closed category definitions)
+                       if ( array_key_exists( 'opened_at', $brace_match ) &&
+                                       $brace_match['opened_at'] === false &&
+                                       array_key_exists( $brace_match['type'], 
self::$input_braces_types ) ) {
+                               # try to find matching opening brace for 
current non-closed closing brace
+                               for ( $j = $i - 1; $j >= 0; $j-- ) {
+                                       $checked_brace = 
&$this->brace_matches[$brace_keys[$j]];
+                                       if ( array_key_exists( 'iscat', 
$checked_brace ) ) {
+                                               # category definitions cannot 
be nested
+                                               break;
+                                       }
+                                       if ( array_key_exists( 'closed_at', 
$checked_brace ) ) {
+                                               # opening brace
+                                               if ( 
$checked_brace['closed_at'] === false ) {
+                                                       # opening brace that 
has no matching closing brace
+                                                       if ( 
$checked_brace['type'] === $brace_match['type'] ) {
+                                                               # opening brace 
of the same type that $brace_match have
+                                                               # link 
$brace_match and $checked_brace to each other:
+                                                               # found 
matching non-closed opening brace; "link" both to each other
+                                                               
$brace_match['opened_at'] = $brace_keys[$j];
+                                                               
$brace_match['iscat'] = true;
+                                                               
$checked_brace['closed_at'] = $brace_keys[$i];
+                                                               
$checked_brace['iscat'] = true;
+                                                               break;
+                                                       }
+                                               } elseif ( 
$checked_brace['closed_at'] > $i ) {
+                                                       # found opening brace 
that is closed at higher position than our
+                                                       # $brace_match has;
+                                                       # cross-closing is not 
allowed, the following code cannot be
+                                                       # category definition 
and template at the same time:
+                                                       # "<[ {{ ]> }}"
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
         * Creates question view which should be renreded and
         * also may be altered during the poll generation
         */
@@ -275,44 +398,41 @@
                        $this->dbtokens = $brace_stack = array();
                        $catId = 0;
                        $last_brace = '';
-                       $tokens = preg_split( self::$propCatPattern, $raw, -1, 
PREG_SPLIT_DELIM_CAPTURE );
+                       $this->rawtokens = preg_split( self::$propCatPattern, 
$raw, -1, PREG_SPLIT_DELIM_CAPTURE );
                        $matching_closed_brace = '';
-                       foreach ( $tokens as $token ) {
-                               try {
-                                       # $toBeStored == true when current 
$token has to be stored into
-                                       # category / proposal list (depending 
on $opt->isCatDef)
-                                       $toBeStored = true;
-                                       if ( $token === '|' ) {
-                                               # parameters separator
-                                               if ( $opt->isCatDef ) {
-                                                       if ( count( 
$brace_stack ) == 1 && $brace_stack[0] === $matching_closed_brace ) {
-                                                               # pipe char 
starts new option only at top brace level,
-                                                               # with matching 
input brace
-                                                               
$opt->addEmptyOption();
-                                                               $toBeStored = 
false;
-                                                       }
+                       $this->findMatchingBraces();
+                       foreach ( $this->rawtokens as $tkey => $token ) {
+                               # $toBeStored == true when current $token has 
to be stored into
+                               # category / proposal list (depending on 
$opt->isCatDef)
+                               $toBeStored = true;
+                               if ( $token === '|' ) {
+                                       # parameters separator
+                                       if ( $opt->isCatDef ) {
+                                               if ( count( $brace_stack ) == 1 
&& $brace_stack[0] === $matching_closed_brace ) {
+                                                       # pipe char starts new 
option only at top brace level,
+                                                       # with matching input 
brace
+                                                       $opt->addEmptyOption();
+                                                       $toBeStored = false;
                                                }
-                                       } elseif ( array_key_exists( $token, 
self::$matching_braces ) ) {
-                                               # opening braces
+                                       }
+                               } elseif ( array_key_exists( $tkey, 
$this->brace_matches ) ) {
+                                       # brace
+                                       $brace_match = 
&$this->brace_matches[$tkey];
+                                       if ( array_key_exists( 'closed_at', 
$brace_match ) &&
+                                                       
$brace_match['closed_at'] !== false ) {
+                                               # valid opening brace
                                                array_push( $brace_stack, 
self::$matching_braces[$token] );
-                                               if ( array_key_exists( $token, 
self::$input_braces_types ) &&
-                                                               count( 
$brace_stack ) == 1 ) {
-                                                       # start category 
definiton
+                                               if ( array_key_exists( 'iscat', 
$brace_match ) ) {
+                                                       # start category 
definition
                                                        $matching_closed_brace 
= self::$matching_braces[$token];
                                                        $opt->startOptionsList( 
self::$input_braces_types[$token] );
                                                        $toBeStored = false;
                                                }
-                                       } elseif ( in_array( $token, 
self::$matching_braces ) ) {
-                                               # closing braces
-                                               if ( count( $brace_stack ) > 0 
) {
-                                                       $last_brace = 
array_pop( $brace_stack );
-                                                       if ( $last_brace != 
$token ) {
-                                                               array_push( 
$brace_stack, $last_brace );
-                                                               throw new 
Exception( 'break' );
-                                                       }
-                                                       if ( count( 
$brace_stack ) > 0 || $token !== $matching_closed_brace ) {
-                                                               throw new 
Exception( 'break' );
-                                                       }
+                                       } elseif ( array_key_exists( 
'opened_at', $brace_match ) &&
+                                               $brace_match['opened_at'] !== 
false ) {
+                                               # valid closing brace
+                                               array_pop( $brace_stack );
+                                               if ( array_key_exists( 'iscat', 
$brace_match ) ) {
                                                        $matching_closed_brace 
= '';
                                                        # add new category 
input options for the storage
                                                        $this->dbtokens[] = 
$opt->input_options;
@@ -325,10 +445,6 @@
                                                        $toBeStored = false;
                                                }
                                        }
-                               } catch ( Exception $e ) {
-                                       if ( $e->getMessage() !== 'break' ) {
-                                               throw new MWException( 
$e->getMessage() );
-                                       }
                                }
                                if ( $toBeStored ) {
                                        if ( $opt->isCatDef ) {

Modified: trunk/extensions/QPoll/i18n/qp.i18n.php
===================================================================
--- trunk/extensions/QPoll/i18n/qp.i18n.php     2011-10-18 04:11:24 UTC (rev 
100108)
+++ trunk/extensions/QPoll/i18n/qp.i18n.php     2011-10-18 07:33:10 UTC (rev 
100109)
@@ -157,18 +157,32 @@
        'pollresults' => 'Special page name in [[Special:SpecialPages]]',
        'qpollwebinstall' => 'Special page name in [[Special:SpecialPages]]',
        '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.',
+       'qp_desc-sp' => 'Description of extension\'s special page at 
Special:Version page.',
+       'qp_result_NA' => '{{Identical|Not answered}}',
        'qp_result_error' => '{{Identical|Syntax error}}',
        'qp_vote_button' => '{{Identical|Vote}}',
+       '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.',
+       'qp_submit_attempts_left' => 'How many attempts of poll / quiz 
submissions are left to the current user.',
+       'qp_polls_list' => 'A link title in the header of 
[[Special:Pollresults]] which displays list of polls at current wiki site.',
+       'qp_users_list' => 'A link title in the header of 
[[Special:Pollresults]] which displays list of poll participants at current 
wiki site.',
+       'qp_browse_to_poll' => 'A link title at [[Special:Pollresults]] which 
opens wiki page where the selected poll was defined.',
+       'qp_browse_to_user' => 'A link title at [[Special:Pollresults]] which 
opens user page of selected poll\'s voter (participant).',
+       'qp_browse_to_interpretation' => 'A link title at 
[[Special:Pollresults]] which opens wiki page where the interpretation script 
of selected poll was defined.',
+       'qp_votes_count' => 'Displays how many times the user re-submitted the 
poll. $1 is number of poll submissions.',
        'qp_source_link' => '"Source" is the link text for a link to the page 
where the poll is defined.
 {{Identical|Source}}',
        'qp_stats_link' => '{{Identical|Statistics}}',
        'qp_users_link' => '{{Identical|User}}',
+       'qp_voice_link' => 'A link title at [[Special:Pollresults]] which 
displays user vote (selected categories) for the selected poll.',
        '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.',
        'qp_user_polls_link' => 'Parameters:
 * $1 is the number of polls participated in.
 * $2 is the name of the user this message refers to (optional - use for 
GENDER)',
        'qp_user_missing_polls_link' => 'Parameters:
 * $1 is the name of the user this message refers to (optional - use for 
GENDER)',
+       '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.',
+       'qp_order_by_username' => 'Order the list of polls at 
[[Special:Pollresults]] by username.',
+       'qp_order_by_polls_count' => 'Order the list of polls at 
[[Special:Pollresults]] by number of their voters (popularity of the polls).',
        'qp_results_line_qupl' => 'Parameters:
 * $1 is a link to the page page name the poll is on with the page title as 
link label
 * $2 is the poll name in plain text
@@ -180,18 +194,52 @@
 * $4 is a link to the poll statistics with link label {{msg-mw|qp_stats_link}}
 * $5 is a link to the users that participated in the poll with link label 
{{msg-mw|qp_users_link}}
 * $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}',
+       'qp_header_line_qpul' => 'Parameters:
+* $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
+* $2 is a link to the page title where the poll is defined
+* $3 is the poll name (poll identifier) in plain text',
+       'qp_results_line_qpl' => 'Parameters:
+* $1 is a link to the page page name the poll is on with the page title as 
link label
+* $2 is the poll name in plain text
+* $3 is a link to the poll with link label {{msg-mw|qp_source_link}}
+* $4 is a link to the poll statistics with link label {{msg-mw|qp_stats_link}}
+* $5 is a link to the users that participated in the poll with link label 
{{msg-mw|qp_users_link}}
+* $6 is a link to the with link label {{msg-mw|qp_not_participated_link}}',
        'qp_results_submit_attempts' => 'Parameters:
 * $1 is the number of submit attempts',
-       'qp_results_short_interpretation' => 'No parameters',
-       'qp_results_long_interpretation' => 'No parameters',
+       '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.',
+       'qp_results_short_interpretation' => 'Short interpretation header. 
Short interpretation will contain small sortable string in separate block. No 
parameters.',
+       'qp_results_long_interpretation' => 'Long interpretation header. Long 
interpretation will contain long text (usually displayed to end-user) in 
separate block. No parameters.',
+       '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.',
        'qp_error_missed_dependance_poll' => 'Parameters:
 * $1 is the poll ID of the poll having an error.
 * $2 is a link to the page with the poll, that this erroneous poll depends on.
 * $3 is the poll ID of the poll, which this erroneous poll depends on.',
        'qp_error_too_many_spans' => 'There cannot be more category groups 
defined than the total count of subcategories.',
        'qp_error_too_few_spans' => 'Every category group should include at 
least two subcategories',
-       '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.',
+       '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.',
+       'qp_interpetation_wrong_answer' => 'Indicates that user-submitted 
choices of categories are considered incorrect by poll\'s interpretation 
script.',
+       'qp_export_to_xls' => 'Poll average statistics will be exported into 
XLS file.',
+       'qp_voices_to_xls' => 'Poll user category choices will be exported into 
XLS file.',
+       'qp_interpretation_results_to_xls' => 'Poll\`s structured interpetation 
results will be exported into XLS file.',
+       'qp_users_answered_questions' => 'Writes the message how many users 
answered to the poll questions in XLS file cell.',
+       'qp_func_no_such_poll' => 'qpuserchoice parser function did not found 
the poll specified in it\'s parameter.
+* $1 is the poll address (page title + hash sign + qp prefix + poll id)',
+       'qp_func_missing_question_id' => 'qpuserchoice parser function call 
should have question_id parameter specified.',
+       'qp_func_invalid_question_id' => 'qpuserchoice parser function 
question_id call value should be integer number, [1..n].',
+       'qp_func_missing_proposal_id' => 'qpuserchoice parser function call 
should have proposal id parameter specified.',
+       'qp_func_invalid_proposal_id' => 'qpuserchoice parser function proposal 
id call value should be integer number, [0..n].',
+       'qp_error_no_such_poll' => 'Poll statistics cannot be displayed because 
incorrect / missing poll address was specified in qpoll tag statistical mode.',
+       'qp_error_in_question_header' => 'Main question header (common question 
or xml-like attributes) has syntax error.
+* $1 is the source text of header.',
+       'qp_error_id_in_stats_mode' => 'Poll "id" attribute is meaningless in 
statistical display mode.',
+       'qp_error_dependance_in_stats_mode' => 'Poll "dependance" attribute is 
meaningless in statistical display mode.',
+       'qp_error_address_in_decl_mode' => 'Poll "address" attribute is 
meaningless in poll declaration / voting mode.',
+       '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.',
+       '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.}}',
        'qp_error_interpretation_no_return' => 'Interpretation script missed an 
return statement.',
+       'qp_error_type_in_stats_mode' => 'Question\'s "type" xml-like attribute 
is meaningless in statistical display mode.',
+       'qp_error_no_poll_id' => 'Every poll definition in declaration / voting 
mode must have "id" attribute.',
        '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.",
        '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.}}',
        'qp_error_eval_mix_languages' => 'Parameters:


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to