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