User:Tóraí/Cite.php

The following allows for templated references when using. The code below is a demonstration of the concept and the template is rudimentary. The principle is that a user may use the  tag as normal or may use attributes such as   to define the reference data. If the contents of the reference tag is left blank i.e. ( or  ) then the extension will try to build a templated reference from these attributes.

The attributes,  ,  ,  ,  ,   and   are accepted by the example code below. The format of the template in the example below is as follows:


 * F. Last. Title. Publisher: Location. Year. Page Page. ISBN: ISBN

Wikicode example
The is what to type in wikicode:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris nulla erat, tincidunt et elementum quis, porta vel risus. Sed id dapibus libero. Nunc convallis rutrum vestibulum. Quisque quis orci magna.

Install instructions
Working off the latest development release, set  in Cite.php and replace the Cite_body.php with the following:

<?php

/**#@+ * A parser extension that adds two tags, and *	 * 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 *  conflict with anonymous keys *	 * @var array **/	var $mRefs = array; /**	 * Count for user displayed output (ref[1], ref[2], ...) *	 * @var int */	var $mOutCnt = 0; var $mGroupCnt = array;

/**	 * Internal counter for anonymous references, separate from * $mOutCnt because anonymous references won't increment it, * but will incremement $mOutCnt *	 * @var int */	var $mInCnt = 0;

/**	 * The backlinks, in order, to pass as $3 to	 * 'cite_references_link_many_format', defined in	 * 'cite_references_link_many_format_backlink_labels *	 * @var array */	var $mBacklinkLabels; /**	 * @var object */	var $mParser; /**	 * True when a tag is being processed. * Used to avoid infinite recursion * 	 * @var boolean */	var $mInCite = false;

/**	 * True when a 		# if( $this->mInReferences ) { if( $group != $this->mReferencesGroup ) { # and " );			}		} else {			$this->mInReferences = true;			$ret = $this->guardedReferences( $str, $argv, $parser );			$this->mInReferences = false;			return $ret;		}	}

function guardedReferences( $str, $argv, $parser, $group = CITE_DEFAULT_GROUP ) { global $wgAllowCiteGroups;

$this->mParser = $parser; if ( isset( $argv['group'] ) and $wgAllowCiteGroups) { $group = $argv['group']; unset ($argv['group']); }		if ( strval( $str ) !== '' ) { $this->mReferencesGroup = $group; # Detect whether we were sent already rendered s			# Mostly a side effect of using #tag to call references $count = substr_count( $str, $parser->uniqPrefix . "-ref-" ); for( $i = 1; $i <= $count; $i++ ) { if( count( $this->mRefCallStack ) < 1 ) break;

# The following assumes that the parsed s sent within # the block were the most recent calls to # . This assumption is true for all known use cases, # but not strictly enforced by the parser. It is possible # that some unusual combination of #tag, and # conditional parser functions could be created that would # lead to malformed references here. $call = array_pop( $this->mRefCallStack ); if( $call !== false ) { list($type, $ref_argv, $ref_str, 						$ref_key, $ref_group, $ref_index) = $call;

# Undo effects of calling while unaware of containing $this->rollbackRef( $type, $ref_key, $ref_group, $ref_index );

# Rerun call now that mInReferences is set. $this->guardedRef( $ref_str, $ref_argv, $parser ); }			}

# Parse $str to process any unparsed tags. $parser->recursiveTagParse( $str );

# Reset call stack $this->mRefCallStack = array; }

if ( count( $argv ) && $wgAllowCiteGroups ) return $this->error( 'cite_error_references_invalid_parameters_group' ); elseif ( count( $argv ) ) return $this->error( 'cite_error_references_invalid_parameters' ); else { $s = $this->referencesFormat( $group ); if ( $parser->getOptions->getIsSectionPreview ) return $s; # Append errors generated while processing if ( count( $this->mReferencesErrors ) > 0 ) { $s .= "\n". implode( " \n", $this->mReferencesErrors ); $this->mReferencesErrors = array; }			return $s; }	}

/**	 * Make output to be returned from the references function *	 * @return string XHTML ready for output */	function referencesFormat($group) { if (( count( $this->mRefs ) == 0 ) or (empty( $this->mRefs[$group] ) )) return ''; wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__ .'-entries' ); $ent = array; foreach ( $this->mRefs[$group] as $k => $v ) $ent[] = $this->referencesFormatEntry( $k, $v ); $prefix = wfMsgForContentNoTrans( 'cite_references_prefix' ); $suffix = wfMsgForContentNoTrans( 'cite_references_suffix' ); $content = implode( "\n", $ent );

// Let's try to cache it. $parserInput = $prefix. $content. $suffix; global $wgMemc; $cacheKey = wfMemcKey( 'citeref', md5($parserInput), $this->mParser->Title->getArticleID );

wfProfileOut( __METHOD__ .'-entries' ); global $wgCiteCacheReferences; if ( $wgCiteCacheReferences ) { wfProfileIn( __METHOD__.'-cache-get' ); $data = $wgMemc->get( $cacheKey ); wfProfileOut( __METHOD__.'-cache-get' ); }		if ( empty($data) ) { wfProfileIn( __METHOD__ .'-parse' ); // Live hack: parse adds two newlines on WM, can't reproduce it locally -ævar $ret = rtrim( $this->parse( $parserInput ), "\n" ); if ( $wgCiteCacheReferences ) { $serData = $this->mParser->serialiseHalfParsedText( $ret ); $wgMemc->set( $cacheKey, $serData, 86400 ); }			wfProfileOut( __METHOD__ .'-parse' ); } else { $ret = $this->mParser->unserialiseHalfParsedText( $data ); }

wfProfileOut( __METHOD__ ); //done, clean up so we can reuse the group unset ($this->mRefs[$group]); unset($this->mGroupCnt[$group]); return $ret; }

/**	 * 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 * @return string Wikitext */	function referencesFormatEntry( $key, $val ) { // Anonymous reference if ( ! is_array( $val ) ) return wfMsgForContentNoTrans(					'cite_references_link_one',					$this->referencesKey( $key ),					$this->refKey( $key ),					$val				); else if ($val['text']=='') return wfMsgForContentNoTrans(					'cite_references_link_one',					$this->referencesKey( $key ),					$this->refKey( $key, $val['count'] ),					$this->error( 'cite_error_references_no_text', $key )				); if ( $val['count'] < 0 ) return wfMsgForContentNoTrans(					'cite_references_link_one',					$this->referencesKey( $val['key'] ),					#$this->refKey( $val['key'], $val['count'] ),					$this->refKey( $val['key'] ),

( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) ) );		// 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 ."-" . $val['key'] ), #$this->refKey( $key, $val['count'] ), $this->refKey( $key, $val['key']."-".$val['count'] ), ( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) ) );		// Named references with >1 occurrences		else {			$links = array; //for group handling, we have an extra key here.			for ( $i = 0; $i <= $val['count']; ++$i ) {				$links[] = wfMsgForContentNoTrans( 'cite_references_link_many_format', $this->refKey( $key, $val['key']."-$i" ), $this->referencesFormatEntryNumericBacklinkLabel( $val['number'], $i, $val['count'] ), $this->referencesFormatEntryAlternateBacklinkLabel( $i ) );			}

$list = $this->listToText( $links );

return wfMsgForContentNoTrans( 'cite_references_link_many',					$this->referencesKey( $key ."-". $val['key'] ),					$list,					( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) )				); }	}

/**	 * Generate a numeric backlink given a base number and an * offset, e.g. $base = 1, $offset = 2; = 1.2 * Since bug #5525, it correctly does 1.9 -> 1.10 as well as 1.099 -> 1.100 *	 * @static *	 * @param int $base The base * @param int $offset The offset * @param int $max Maximum value expected. * @return string */	function referencesFormatEntryNumericBacklinkLabel( $base, $offset, $max ) { global $wgContLang; $scope = strlen( $max ); $ret = $wgContLang->formatNum(			sprintf("%s.%0{$scope}s", $base, $offset)		); return $ret; }

/**	 * Generate a custom format backlink given an offset, e.g.	 * $offset = 2; = c if $this->mBacklinkLabels = array( 'a',	 * 'b', 'c', ...). Return an error if the offset > the # of	 * array items *	 * @param int $offset The offset *	 * @return string */	function referencesFormatEntryAlternateBacklinkLabel( $offset ) { if ( !isset( $this->mBacklinkLabels ) ) { $this->genBacklinkLabels; }		if ( isset( $this->mBacklinkLabels[$offset] ) ) { return $this->mBacklinkLabels[$offset]; } else { // Feed me! return $this->error( 'cite_error_references_no_backlink_label' ); }	}

/**	 * Return an id for use in wikitext output based on a key and * optionally the number of it, used in, not * (since otherwise it would link to itself) *	 * @static *	 * @param string $key The key * @param int $num The number of the key * @return string A key for use in wikitext */	function refKey( $key, $num = null ) { $prefix = wfMsgForContent( 'cite_reference_link_prefix' ); $suffix = wfMsgForContent( 'cite_reference_link_suffix' ); if ( isset( $num ) ) $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num ); return $prefix. $key. $suffix; }

/**	 * Return an id for use in wikitext output based on a key and * optionally the number of it, used in, not * (since otherwise it would link to itself) *	 * @static *	 * @param string $key The key * @param int $num The number of the key * @return string A key for use in wikitext */	function referencesKey( $key, $num = null ) { $prefix = wfMsgForContent( 'cite_references_link_prefix' ); $suffix = wfMsgForContent( 'cite_references_link_suffix' ); if ( isset( $num ) ) $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num ); return $prefix. $key. $suffix; }

/**	 * Generate a link (<sup ...) for the element from a key * and return XHTML ready for output *	 * @param string $key The key for the link * @param int $count The index of the key, used for distinguishing *                  multiple occurances 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. * @return string */	function linkRef( $group, $key, $count = null, $label = null, $subkey = '' ) { global $wgContLang; return $this->parse(				wfMsgForContentNoTrans( 'cite_reference_link', $this->refKey( $key, $count ), $this->referencesKey( $key . $subkey ), (($group == CITE_DEFAULT_GROUP)?'':"$group ").$wgContLang->formatNum( is_null( $label ) ? ++$this->mGroupCnt[$group] : $label ) )			);	}

/**	 * This does approximately the same thing as	 * Language::listToText but due to this being used for a	 * slightly different purpose (people might not want, as the	 * first separator and not 'and' as the second, and this has to	 * use messages from the content language) I'm rolling my own. *	 * @static *	 * @param array $arr The array to format * @return string */	function listToText( $arr ) { $cnt = count( $arr );

$sep = wfMsgForContentNoTrans( 'cite_references_link_many_sep' ); $and = wfMsgForContentNoTrans( 'cite_references_link_many_and' );

if ( $cnt == 1 ) // Enforce always returning a string return (string)$arr[0]; else { $t = array_slice( $arr, 0, $cnt - 1 ); return implode( $sep, $t ). $and. $arr[$cnt - 1]; }	}

/**	 * Parse a given fragment and fix up Tidy's trail of blood on * it... *	 * @param string $in The text to parse * @return string The parsed text */	function parse( $in ) { if ( method_exists( $this->mParser, 'recursiveTagParse' ) ) { // New fast method return $this->mParser->recursiveTagParse( $in ); } else { // Old method $ret = $this->mParser->parse(				$in,				$this->mParser->mTitle,				$this->mParser->mOptions,				// Avoid whitespace buildup				false,				// Important, otherwise $this->clearState				// would get run every time or				// is called, fucking the whole				// thing up.				false			); $text = $ret->getText; return $this->fixTidy( $text ); }	}

/**	 * Tidy treats all input as a block, it will e.g. wrap most * input in if it isn't already, fix that and return the fixed text *	 * @static *	 * @param string $text The text to fix * @return string The fixed text */	function fixTidy( $text ) { global $wgUseTidy;

if ( ! $wgUseTidy ) return $text; else { $text = preg_replace( '~^ \s*~', '', $text ); $text = preg_replace( '~\s* \s*~', '', $text ); $text = preg_replace( '~\n$~', '', $text ); return $text; }	}

/**	 * Generate the labels to pass to the * 'cite_references_link_many_format' message, the format is an	 * arbitary number of tokens separated by [\t\n ] */	function genBacklinkLabels { wfProfileIn( __METHOD__ ); $text = wfMsgForContentNoTrans( 'cite_references_link_many_format_backlink_labels' ); $this->mBacklinkLabels = preg_split( '#[\n\t ]#', $text ); wfProfileOut( __METHOD__ ); }

/**	 * Gets run when Parser::clearState gets run, since we don't	 * want the counts to transcend pages and other instances */	function clearState { # Don't clear state when we're in the middle of parsing # a tag if( $this->mInCite || $this->mInReferences ) return true; $this->mGroupCnt = array; $this->mOutCnt = -1; $this->mInCnt = 0; $this->mRefs = array; $this->mReferencesErrors = array; $this->mRefCallStack = array;

return true; }

/**	 * Called at the end of page processing to append an error if refs were * used without a references tag. */	function checkRefsNoReferences(&$parser, &$text){ if ( $parser->getOptions->getIsSectionPreview ) return true;

foreach ( $this->mRefs as $group => $refs ) { if ( count( $refs ) == 0 ) continue; $text .= "\n "; if ( $group == CITE_DEFAULT_GROUP ) { $text .= $this->error( 'cite_error_refs_without_references' ); } else { $text .= $this->error( 'cite_error_group_refs_without_references', htmlspecialchars( $group ) ); }		}		return true; }

/**	 * Initialize the parser hooks */	function setHooks { global $wgParser, $wgHooks;

$wgParser->setHook( 'ref', array( &$this, 'ref' ) ); $wgParser->setHook( 'references', array( &$this, 'references' ) );

$wgHooks['ParserClearState'][] = array( &$this, 'clearState' ); $wgHooks['ParserBeforeTidy'][] = array( &$this, 'checkRefsNoReferences' ); }

/**	 * Return an error message based on an error ID	 * * @param string $key  Message name for the error * @param string $param Parameter to pass to the message * @return string XHTML ready for output */	function error( $key, $param=null ) { # We rely on the fact that PHP is okay with passing unused argu- # ments to functions. If $1 is not used in the message, wfMsg will # just ignore the extra parameter. return $this->parse(				' ' .				wfMsgNoTrans( 'cite_error', wfMsgNoTrans( $key, $param ) ) .				' '			); }

/**	 * Die with a backtrace if something happens in the code which * shouldn't have *	 * @param int $error ID for the error * @param string $data Serialized error data */	function croak( $error, $data ) { wfDebugDieBacktrace( wfMsgForContent( 'cite_croak', $this->error( $error ), $data ) ); }

function generateRefFromTemplate($argv){ global $wgAllowCiteTemplates; $str = ""; if (!$wgAllowCiteTemplates) return $str; // accepts: first, last, title, year, publisher, isbn, page if ( isset($argv['first']) && isset($argv['last']) ) $str .= substr($argv['first'], 0, 1) .". ". $argv['last'] .". "; if ( isset($argv['title']) ) $str .= "''". $argv['title'] ."''. "; if ( isset($argv['publisher']) && isset($argv['location']) ) $str .= $argv['publisher'] .": ". $argv['location'] .". "; if ( isset($argv['year']) ) $str .= $argv['year'] .". "; if ( isset($argv['page']) ) $str .= "Page ". $argv['page'] .". "; if ( isset($argv['isbn']) ) $str .= "ISBN: ". $argv['isbn']; return $str; }	/**#@-*/ }