--- Cite.php (revision 14725) +++ Cite.php (working copy) @@ -35,13 +35,15 @@ 'CITE_ERROR_STR_INVALID', 'CITE_ERROR_KEY_INVALID_1', 'CITE_ERROR_KEY_INVALID_2', + 'CITE_ERROR_GROUP_INVALID_1', 'CITE_ERROR_STACK_INVALID_INPUT' ), 'user' => array( 'CITE_ERROR_REF_NUMERIC_KEY', 'CITE_ERROR_REF_NO_KEY', - 'CITE_ERROR_REF_TOO_MANY_KEYS', + 'CITE_ERROR_REF_BAD_ARGS', 'CITE_ERROR_REF_NO_INPUT', + 'CITE_ERROR_REF_BAD_GROUP', 'CITE_ERROR_REFERENCES_INVALID_INPUT', 'CITE_ERROR_REFERENCES_INVALID_PARAMETERS', 'CITE_ERROR_REFERENCES_NO_BACKLINK_LABEL' @@ -63,45 +65,47 @@ /* Debug & errors */ // Internal errors 'cite_croak' => 'Cite croaked; $1: $2', 'cite_error_' . CITE_ERROR_STR_INVALID => 'Internal error; invalid $str', 'cite_error_' . CITE_ERROR_KEY_INVALID_1 => 'Internal error; invalid key', 'cite_error_' . CITE_ERROR_KEY_INVALID_2 => 'Internal error; invalid key', + 'cite_error_' . CITE_ERROR_GROUP_INVALID_1 => 'Internal error; invalid group', 'cite_error_' . CITE_ERROR_STACK_INVALID_INPUT => 'Internal error; invalid stack key', // User errors 'cite_error' => 'Cite error $1; $2', 'cite_error_' . CITE_ERROR_REF_NUMERIC_KEY => 'Invalid call; expecting a non-integer key', 'cite_error_' . CITE_ERROR_REF_NO_KEY => 'Invalid call; no key specified', - 'cite_error_' . CITE_ERROR_REF_TOO_MANY_KEYS => 'Invalid call; invalid keys, e.g. too many or wrong key specified', + 'cite_error_' . CITE_ERROR_REF_BAD_ARGS => 'Invalid arguments defined for <ref>: exactly one name and/or one group are allowed', 'cite_error_' . CITE_ERROR_REF_NO_INPUT => 'Invalid call; no input specified', + 'cite_error_' . CITE_ERROR_REF_BAD_GROUP => "Invalid group label defined for <ref>: 'ungrouped' is reserved", 'cite_error_' . CITE_ERROR_REFERENCES_INVALID_INPUT => 'Invalid input; expecting none', - 'cite_error_' . CITE_ERROR_REFERENCES_INVALID_PARAMETERS => 'Invalid parameters; expecting none', + 'cite_error_' . CITE_ERROR_REFERENCES_INVALID_PARAMETERS => 'Invalid parameters defined for <references>: only "group" is valid', 'cite_error_' . CITE_ERROR_REFERENCES_NO_BACKLINK_LABEL => "Ran out of custom backlink labels, define more in the \"''cite_references_link_many_format_backlink_labels''\" message", /* Output formatting */ 'cite_reference_link_key_with_num' => '$1_$2', // Ids produced by 'cite_reference_link_prefix' => '_ref-', 'cite_reference_link_suffix' => '', // Ids produced by 'cite_references_link_prefix' => '_note-', 'cite_references_link_suffix' => '', 'cite_reference_link' => '[[#$2|[$3]]]', 'cite_references_link_one' => '
  • [[#$2|G??]] $3
  • ', 'cite_references_link_many' => '
  • G?? $2 $3
  • ', 'cite_references_link_many_format' => '[[#$1|$2]]', // An item from this set is passed as $3 in the message above 'cite_references_link_many_format_backlink_labels' => 'a b c d e f g h i j k l m n o p q r s t u v w x y z', 'cite_references_link_many_sep' => "\xc2\xa0", //   'cite_references_link_many_and' => "\xc2\xa0", // &nbps; // Although I could just use # instead of
  • above and nothing here that // will break on input that contains linebreaks @@ -109,36 +113,43 @@ 'cite_references_suffix' => '', ) ); class Cite { /**#@+ * @access private */ + + /** * Datastructure representing input, in the format of: * * array( + * 'ungrouped' => array( - * 'user supplied' => array( + * 'user supplied' => array( - * 'text' => 'user supplied reference & key', + * 'text' => 'user supplied reference & key', - * 'count' => 1, // occurs twice + * 'count' => 1, // occurs twice - * 'number' => 1, // The first reference, we want + * 'number' => 1, // The first reference, we want - * // all occourances of it to + * // all occurrences of it to - * // use the same number + * // all occurrences of it to - * ), + * ), - * 0 => 'Anonymous reference', + * 0 => 'Anonymous reference', - * 1 => 'Another anonymous reference', + * 1 => 'Another anonymous reference', - * 'some key' => array( + * 'some key' => array( - * 'text' => 'this one occurs once' + * 'text' => 'this one occurs once' - * 'count' => 0, + * 'count' => 0, - * 'number' => 4 + * 'number' => 4 - * ), + * ), - * 3 => 'more stuff' + * 3 => 'more stuff' + * ), + * 'custom_group' => array( + * ... + * ) * ); * * * This works because: * * PHP's datastructures are guarenteed to be returned in the * order that things are inserted into them (unless you mess * with that) * * User supplied keys can't be integers, therefore avoiding @@ -147,22 +158,23 @@ * @var array **/ var $mRefs = array(); /** * Count for user displayed output (ref[1], ref[2], ...) + * Broken up into pieces for different groups, default is 'ungrouped' * * @var int */ - var $mOutCnt = 0; + var $mOutCnt = array(); /** * Internal counter for anonymous references, seperate from * $mOutCnt because anonymous references won't increment it, * but will incremement $mOutCnt * * @var int */ - var $mInCnt = 0; + var $mInCnt = array(); /** * The backlinks, in order, to pass as $3 to @@ -215,42 +227,48 @@ return $ret; } } function guardedRef( $str, $argv, $parser ) { $this->mParser = $parser; - $key = $this->refArg( $argv ); + $args = $this->refArg( $argv ); + $key = $args['name']; + $group = $args['group']; - if ( $str !== null ) { + if ( $str !== null ) { // s have some content--not - if ( $str === '' ) + if ( $str === '' ) // return $this->error( CITE_ERROR_REF_NO_INPUT ); - if ( is_string( $key ) ) + elseif ( is_string( $key ) || $key === null ) // I don't want keys in the form of /^[0-9]+$/ because they would // conflict with the php datastructure I'm using, besides, why specify // a manual key if it's just going to be any old integer? if ( sprintf( '%d', $key ) === (string)$key ) return $this->error( CITE_ERROR_REF_NUMERIC_KEY ); - else + elseif ($group === false) // $group === 'ungrouped' + $this->error( CITE_ERROR_REF_BAD_GROUP ); + elseif ( !is_string( $group ) && $group !== null ) + $this->croak( CITE_ERROR_GROUP_INVALID_1, serialize( $group ) ); + else // $key is valid, $group is valid: hopefully we end up here - return $this->stack( $str, $key); + return $this->stack( $str, $key, $group ); - else if ( $key === null ) - return $this->stack( $str ); - else if ( $key === false ) + elseif ( $key === false ) // refArg determined $key is bad - return $this->error( CITE_ERROR_REF_TOO_MANY_KEYS ); + return $this->error( CITE_ERROR_REF_BAD_ARGS ); else $this->croak( CITE_ERROR_KEY_INVALID_1,serialize( $key ) ); - } else if ( $str === null ) { + } elseif ( $str === null ) { // if ( is_string( $key ) ) if ( sprintf( '%d', $key ) === (string)$key ) return $this->error( CITE_ERROR_REF_NUMERIC_KEY ); - else + elseif ($group === false) // $group === 'ungrouped' + $this->error( CITE_ERROR_REF_BAD_GROUP ); + else // $key is valid, $group is valid: hopefully we end up here - return $this->stack( $str, $key); + return $this->stack( $str, $key, $group ); - else if ( $key === false ) + elseif ( $key === false ) - return $this->error( CITE_ERROR_REF_TOO_MANY_KEYS ); + return $this->error( CITE_ERROR_REF_BAD_ARGS ); - else if ( $key === null ) + elseif ( $key === null ) return $this->error( CITE_ERROR_REF_NO_KEY ); else $this->croak( CITE_ERROR_KEY_INVALID_2,serialize( $key ) ); - } else + } else // If we reach here, something is seriously wrong. $this->croak( CITE_ERROR_STR_INVALID, serialize( $str ) ); } @@ -260,28 +278,41 @@ * @static * * @param array $argv The argument vector - * @return mixed false on invalid input, a string on valid - * input and null on no input + * @return array: each element is false on invalid input, a string on + * valid input and null on no input (or 'ungrouped' for + * groups, since that's less confusing as an array key) */ function refArg( $argv ) { $cnt = count( $argv ); - if ( $cnt > 1 ) + if ( $cnt > 2 ) - // There should only be one key + // There should never be more than two arguments - return false; + return array('name' => false, 'group' => false); else if ( $cnt == 1 ) if ( isset( $argv['name'] ) ) // Key given. - return $this->validateName( array_shift( $argv ) ); + return array('name' => $this->validateName($argv['name']), + 'group' => 'ungrouped' ); + elseif ( isset( $argv['group'] ) ) + // Group given. + return array('name' => null, + 'group' => $this->validateGroup($argv['group']) ); else - // Invalid key + // Invalid arg - return false; + return array('name' => false, 'group' => false); + elseif ( $cnt == 2 ) + if ( isset( $argv['name'] ) && isset( $argv['group'] ) ) + // Key and group given. + return array('name' => $this->validateName($argv['name']), + 'group' => $this->validateGroup($argv['group']) ); + else + // Invalid arg(s) + return array('name' => false, 'group' => false); else - // No key + // No key or group - return null; + return array('name' => null, 'group' => 'ungrouped'); } /** * Since the key name is used in an XHTML id attribute, it must * conform to the validity rules. The restriction to begin with @@ -309,44 +340,67 @@ } + + /** + * Since the group name is used as an array key, make sure it's not + * screwy. NUL, \, ', ", and $ are all probably Bad. + * + * @return string + */ + function validateGroup( $group ) { + if( $group === 'ungrouped' ) + return false; + elseif( !get_magic_quotes_gpc() ) + return addcslashes($group,'\0\\\'"$'); + else + return $group; + } /** * Populate $this->mRefs based on input and arguments to * * @param string $str Input from the tag - * @param mixed $key Argument to the tag as returned by $this->refArg() + * @param mixed $key Key for the tag as returned by $this->refArg() + * (either a string or null) + * @param mixed $group Which grouping of references this is part of * @return string */ - function stack( $str, $key = null ) { + function stack( $str, $key = null, $group = 'ungrouped' ) { + if ($this->mInCnt[$group] === null) + $this->mInCnt[$group] = 0; + if ($this->mOutCnt[$group] === null) + $this->mOutCnt[$group] = 0; if ( $key === null ) { // No key - $this->mRefs[] = $str; + $this->mRefs[$group][] = $str; - return $this->linkRef( $this->mInCnt++ ); + return $this->linkRef( $this->mInCnt[$group]++,$group ); - } else if ( is_string( $key ) ) + } elseif ( is_string( $key ) ) { // Valid key - if ( ! @is_array( $this->mRefs[$key] ) ) { + if ( ! @is_array( $this->mRefs[$group][$key] ) ) { // First occourance - $this->mRefs[$key] = array( + $this->mRefs[$group][$key] = array( 'text' => $str, 'count' => 0, - 'number' => ++$this->mOutCnt + 'number' => ++$this->mOutCnt[$group] ); return $this->linkRef( $key, + $group, - $this->mRefs[$key]['count'], + $this->mRefs[$group][$key]['count'], - $this->mRefs[$key]['number'] + $this->mRefs[$group][$key]['number'] ); } else // We've been here before return $this->linkRef( $key, + $group, - ++$this->mRefs[$key]['count'], + ++$this->mRefs[$group][$key]['count'], - $this->mRefs[$key]['number'] + $this->mRefs[$group][$key]['number'] ); - else + } else $this->croak( CITE_ERROR_STACK_INVALID_INPUT, serialize( array( $key, $str ) ) ); } /** * Callback function for * @@ -368,64 +422,99 @@ return $ret; } } function guardedReferences( $str, $argv, $parser ) { $this->mParser = $parser; + $group = $this->referencesArg( $argv ); + if ( $str !== null ) return $this->error( CITE_ERROR_REFERENCES_INVALID_INPUT ); - else if ( count( $argv ) ) + elseif ( $group === false ) return $this->error( CITE_ERROR_REFERENCES_INVALID_PARAMETERS ); else - return $this->referencesFormat(); + return $this->referencesFormat( $group ); } + + /** + * Parse the arguments to the tag + * + * @static + * + * @param array $argv The argument vector + * @return mixed false on invalid input, a string on valid + * or no input + */ + function referencesArg( $argv ) { + $cnt = count( $argv ); + + if ( $cnt > 1 ) + // There should never be more than one argument + return false; + elseif ( $cnt == 1 ) { + if ( isset( $argv['group'] ) ) + // Group given. + return $this->validateGroup( $argv['group'] ); + else + // Not a group + return false; + } else + // No group + return 'ungrouped'; + } /** * Make output to be returned from the references() function * + * @param string $group The group of * @return string XHTML ready for output */ - function referencesFormat() { + function referencesFormat( $group = 'ungrouped' ) { $ent = array(); - foreach ( $this->mRefs as $k => $v ) + foreach ( $this->mRefs[$group] as $k => $v ) + // $k: name of a named ref, number of an anon + // $v: contents of the ref - $ent[] = $this->referencesFormatEntry( $k, $v ); + $ent[] = $this->referencesFormatEntry( $k, $v, $group ); $prefix = wfMsgForContentNoTrans( 'cite_references_prefix' ); $suffix = wfMsgForContentNoTrans( 'cite_references_suffix' ); $content = implode( "\n", $ent ); // Live hack: parse() adds two newlines on WM, can't reproduce it locally -+?var return rtrim( $this->parse( $prefix . $content . $suffix ), "\n" ); } /** * Format a single entry for the referencesFormat() function * * @param string $key The key of the reference * @param mixed $val The value of the reference, string for anonymous * references, array for user-suppplied + * @param string $group The group that this whole belongs + * to * @return string Wikitext */ - function referencesFormatEntry( $key, $val ) { + function referencesFormatEntry( $key, $val, $group = 'ungrouped' ) { // Anonymous reference if ( ! is_array( $val ) ) return wfMsgForContentNoTrans( 'cite_references_link_one', - $this->referencesKey( $key ), + $this->referencesKey( $key, $group ), - $this->refKey( $key ), + $this->refKey( $key, $group ), $val ); // Standalone named reference, I want to format this like an // anonymous reference because displaying "1. 1.1 Ref text" is // overkill and users frequently use named references when they // don't need them for convenience else if ( $val['count'] === 0 ) return wfMsgForContentNoTrans( 'cite_references_link_one', - $this->referencesKey( $key ), + $this->referencesKey( $key, $group ), - $this->refKey( $key, $val['count'] ), + $this->refKey( $key, $group, $val['count'] ), $val['text'] ); // Named references with >1 occurrences @@ -435,7 +524,7 @@ for ( $i = 0; $i <= $val['count']; ++$i ) { $links[] = wfMsgForContentNoTrans( 'cite_references_link_many_format', - $this->refKey( $key, $i), + $this->refKey( $key, $group, $i ), $this->referencesFormatEntryNumericBacklinkLabel( $val['number'], $i, $val['count'] ), $this->referencesFormatEntryAlternateBacklinkLabel( $i ) ); @@ -445,7 +534,7 @@ return wfMsgForContentNoTrans( 'cite_references_link_many', - $this->referencesKey( $key ), + $this->referencesKey( $key, $group ), $list, $val['text'] ); @@ -500,14 +589,17 @@ * * @param string $key The key * @param int $num The number of the key + * @param string $group The group * @return string A key for use in wikitext */ - function refKey( $key, $num = null ) { + function refKey( $key, $group = 'ungrouped', $num = null ) { $prefix = wfMsgForContent( 'cite_reference_link_prefix'); $suffix = wfMsgForContent( 'cite_reference_link_suffix'); + if ( $group !== 'ungrouped' ) + $key .= '-'.$group; if ( isset( $num ) ) $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num ); return $prefix . $key . $suffix; } @@ -522,12 +614,14 @@ * @param int $num The number of the key * @return string A key for use in wikitext */ - function referencesKey( $key, $num = null ) { + function referencesKey( $key, $group = 'ungrouped', $num = null ) { $prefix = wfMsgForContent( 'cite_references_link_prefix' ); $suffix = wfMsgForContent( 'cite_references_link_suffix' ); + if ( $group !== 'ungrouped' ) + $key .= '-'.$group; if ( isset( $num ) ) $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num ); return $prefix . $key . $suffix; } @@ -537,22 +631,24 @@ * * @param string $key The key for the link * @param int $count The # of the key, used for distinguishing * multiple occourances of the same key * @param int $label The label to use for the link, I want to * use the same label for all occourances of * the same named reference. + * @param string $group The link's group (affects numbering and + * link name) * @return string */ - function linkRef( $key, $count = null, $label = null ) { + function linkRef( $key, $group = 'ungrouped', $count = null, $label = null ) { global $wgContLang; return $this->parse( wfMsgForContentNoTrans( 'cite_reference_link', - $this->refKey( $key, $count ), + $this->refKey( $key, $group, $count ), - $this->referencesKey( $key ), + $this->referencesKey( $key, $group ), - $wgContLang->formatNum( is_null( $label ) ? ++$this->mOutCnt : $label ) + $wgContLang->formatNum( is_null( $label ) ? ++$this->mOutCnt[$group] : $label ) ) ); } @@ -647,7 +743,7 @@ * want the counts to transcend pages and other instances */ function clearState() { - $this->mOutCnt = $this->mInCnt = 0; + $this->mOutCnt = $this->mInCnt = array(); $this->mRefs = array(); return true;