parser.php

来自「php 开发的内容管理系统」· PHP 代码 · 共 2,095 行 · 第 1/5 页

PHP
2,095
字号
		return $retVal;	}	/**	 * Render a forced-blue link inline; protect against double expansion of	 * URLs if we're in a mode that prepends full URL prefixes to internal links.	 * Since this little disaster has to split off the trail text to avoid	 * breaking URLs in the following text without breaking trails on the	 * wiki links, it's been made into a horrible function.	 *	 * @param Title $nt	 * @param string $text	 * @param string $query	 * @param string $trail	 * @param string $prefix	 * @return string HTML-wikitext mix oh yuck	 */	function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {		list( $inside, $trail ) = Linker::splitTrail( $trail );		$sk =& $this->mOptions->getSkin();		$link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix );		return $this->armorLinks( $link ) . $trail;	}	/**	 * Insert a NOPARSE hacky thing into any inline links in a chunk that's	 * going to go through further parsing steps before inline URL expansion.	 *	 * In particular this is important when using action=render, which causes	 * full URLs to be included.	 *	 * Oh man I hate our multi-layer parser!	 *	 * @param string more-or-less HTML	 * @return string less-or-more HTML with NOPARSE bits	 */	function armorLinks( $text ) {		return preg_replace( "/\b(" . wfUrlProtocols() . ')/',			"{$this->mUniqPrefix}NOPARSE$1", $text );	}	/**	 * Return true if subpage links should be expanded on this page.	 * @return bool	 */	function areSubpagesAllowed() {		# Some namespaces don't allow subpages		global $wgNamespacesWithSubpages;		return !empty($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]);	}	/**	 * Handle link to subpage if necessary	 * @param string $target the source of the link	 * @param string &$text the link text, modified as necessary	 * @return string the full name of the link	 * @private	 */	function maybeDoSubpageLink($target, &$text) {		# Valid link forms:		# Foobar -- normal		# :Foobar -- override special treatment of prefix (images, language links)		# /Foobar -- convert to CurrentPage/Foobar		# /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text		# ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage		# ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage		$fname = 'Parser::maybeDoSubpageLink';		wfProfileIn( $fname );		$ret = $target; # default return value is no change		# Some namespaces don't allow subpages,		# so only perform processing if subpages are allowed		if( $this->areSubpagesAllowed() ) {			# Look at the first character			if( $target != '' && $target{0} == '/' ) {				# / at end means we don't want the slash to be shown				if( substr( $target, -1, 1 ) == '/' ) {					$target = substr( $target, 1, -1 );					$noslash = $target;				} else {					$noslash = substr( $target, 1 );				}				$ret = $this->mTitle->getPrefixedText(). '/' . trim($noslash);				if( '' === $text ) {					$text = $target;				} # this might be changed for ugliness reasons			} else {				# check for .. subpage backlinks				$dotdotcount = 0;				$nodotdot = $target;				while( strncmp( $nodotdot, "../", 3 ) == 0 ) {					++$dotdotcount;					$nodotdot = substr( $nodotdot, 3 );				}				if($dotdotcount > 0) {					$exploded = explode( '/', $this->mTitle->GetPrefixedText() );					if( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page						$ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );						# / at the end means don't show full path						if( substr( $nodotdot, -1, 1 ) == '/' ) {							$nodotdot = substr( $nodotdot, 0, -1 );							if( '' === $text ) {								$text = $nodotdot;							}						}						$nodotdot = trim( $nodotdot );						if( $nodotdot != '' ) {							$ret .= '/' . $nodotdot;						}					}				}			}		}		wfProfileOut( $fname );		return $ret;	}	/**#@+	 * Used by doBlockLevels()	 * @private	 */	/* private */ function closeParagraph() {		$result = '';		if ( '' != $this->mLastSection ) {			$result = '</' . $this->mLastSection  . ">\n";		}		$this->mInPre = false;		$this->mLastSection = '';		return $result;	}	# getCommon() returns the length of the longest common substring	# of both arguments, starting at the beginning of both.	#	/* private */ function getCommon( $st1, $st2 ) {		$fl = strlen( $st1 );		$shorter = strlen( $st2 );		if ( $fl < $shorter ) { $shorter = $fl; }		for ( $i = 0; $i < $shorter; ++$i ) {			if ( $st1{$i} != $st2{$i} ) { break; }		}		return $i;	}	# These next three functions open, continue, and close the list	# element appropriate to the prefix character passed into them.	#	/* private */ function openList( $char ) {		$result = $this->closeParagraph();		if ( '*' == $char ) { $result .= '<ul><li>'; }		else if ( '#' == $char ) { $result .= '<ol><li>'; }		else if ( ':' == $char ) { $result .= '<dl><dd>'; }		else if ( ';' == $char ) {			$result .= '<dl><dt>';			$this->mDTopen = true;		}		else { $result = '<!-- ERR 1 -->'; }		return $result;	}	/* private */ function nextItem( $char ) {		if ( '*' == $char || '#' == $char ) { return '</li><li>'; }		else if ( ':' == $char || ';' == $char ) {			$close = '</dd>';			if ( $this->mDTopen ) { $close = '</dt>'; }			if ( ';' == $char ) {				$this->mDTopen = true;				return $close . '<dt>';			} else {				$this->mDTopen = false;				return $close . '<dd>';			}		}		return '<!-- ERR 2 -->';	}	/* private */ function closeList( $char ) {		if ( '*' == $char ) { $text = '</li></ul>'; }		else if ( '#' == $char ) { $text = '</li></ol>'; }		else if ( ':' == $char ) {			if ( $this->mDTopen ) {				$this->mDTopen = false;				$text = '</dt></dl>';			} else {				$text = '</dd></dl>';			}		}		else {	return '<!-- ERR 3 -->'; }		return $text."\n";	}	/**#@-*/	/**	 * Make lists from lines starting with ':', '*', '#', etc.	 *	 * @private	 * @return string the lists rendered as HTML	 */	function doBlockLevels( $text, $linestart ) {		$fname = 'Parser::doBlockLevels';		wfProfileIn( $fname );		# Parsing through the text line by line.  The main thing		# happening here is handling of block-level elements p, pre,		# and making lists from lines starting with * # : etc.		#		$textLines = explode( "\n", $text );		$lastPrefix = $output = '';		$this->mDTopen = $inBlockElem = false;		$prefixLength = 0;		$paragraphStack = false;		if ( !$linestart ) {			$output .= array_shift( $textLines );		}		foreach ( $textLines as $oLine ) {			$lastPrefixLength = strlen( $lastPrefix );			$preCloseMatch = preg_match('/<\\/pre/i', $oLine );			$preOpenMatch = preg_match('/<pre/i', $oLine );			if ( !$this->mInPre ) {				# Multiple prefixes may abut each other for nested lists.				$prefixLength = strspn( $oLine, '*#:;' );				$pref = substr( $oLine, 0, $prefixLength );				# eh?				$pref2 = str_replace( ';', ':', $pref );				$t = substr( $oLine, $prefixLength );				$this->mInPre = !empty($preOpenMatch);			} else {				# Don't interpret any other prefixes in preformatted text				$prefixLength = 0;				$pref = $pref2 = '';				$t = $oLine;			}			# List generation			if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) {				# Same as the last item, so no need to deal with nesting or opening stuff				$output .= $this->nextItem( substr( $pref, -1 ) );				$paragraphStack = false;				if ( substr( $pref, -1 ) == ';') {					# The one nasty exception: definition lists work like this:					# ; title : definition text					# So we check for : in the remainder text to split up the					# title and definition, without b0rking links.					$term = $t2 = '';					if ($this->findColonNoLinks($t, $term, $t2) !== false) {						$t = $t2;						$output .= $term . $this->nextItem( ':' );					}				}			} elseif( $prefixLength || $lastPrefixLength ) {				# Either open or close a level...				$commonPrefixLength = $this->getCommon( $pref, $lastPrefix );				$paragraphStack = false;				while( $commonPrefixLength < $lastPrefixLength ) {					$output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} );					--$lastPrefixLength;				}				if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {					$output .= $this->nextItem( $pref{$commonPrefixLength-1} );				}				while ( $prefixLength > $commonPrefixLength ) {					$char = substr( $pref, $commonPrefixLength, 1 );					$output .= $this->openList( $char );					if ( ';' == $char ) {						# FIXME: This is dupe of code above						if ($this->findColonNoLinks($t, $term, $t2) !== false) {							$t = $t2;							$output .= $term . $this->nextItem( ':' );						}					}					++$commonPrefixLength;				}				$lastPrefix = $pref2;			}			if( 0 == $prefixLength ) {				wfProfileIn( "$fname-paragraph" );				# No prefix (not in list)--go to paragraph mode				// XXX: use a stack for nestable elements like span, table and div				$openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<li|<\\/center|<\\/tr|<\\/td|<\\/th)/iS', $t );				$closematch = preg_match(					'/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.					'<td|<th|<div|<\\/div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<center)/iS', $t );				if ( $openmatch or $closematch ) {					$paragraphStack = false;					#聽TODO bug 5718: paragraph closed					$output .= $this->closeParagraph();					if ( $preOpenMatch and !$preCloseMatch ) {						$this->mInPre = true;					}					if ( $closematch ) {						$inBlockElem = false;					} else {						$inBlockElem = true;					}				} else if ( !$inBlockElem && !$this->mInPre ) {					if ( ' ' == $t{0} and ( $this->mLastSection == 'pre' or trim($t) != '' ) ) {						// pre						if ($this->mLastSection != 'pre') {							$paragraphStack = false;							$output .= $this->closeParagraph().'<pre>';							$this->mLastSection = 'pre';						}						$t = substr( $t, 1 );					} else {						// paragraph						if ( '' == trim($t) ) {							if ( $paragraphStack ) {								$output .= $paragraphStack.'<br />';								$paragraphStack = false;								$this->mLastSection = 'p';							} else {								if ($this->mLastSection != 'p' ) {									$output .= $this->closeParagraph();									$this->mLastSection = '';									$paragraphStack = '<p>';								} else {									$paragraphStack = '</p><p>';								}							}						} else {							if ( $paragraphStack ) {								$output .= $paragraphStack;								$paragraphStack = false;								$this->mLastSection = 'p';							} else if ($this->mLastSection != 'p') {								$output .= $this->closeParagraph().'<p>';								$this->mLastSection = 'p';							}						}					}				}				wfProfileOut( "$fname-paragraph" );			}			// somewhere above we forget to get out of pre block (bug 785)			if($preCloseMatch && $this->mInPre) {				$this->mInPre = false;			}			if ($paragraphStack === false) {				$output .= $t."\n";			}		}		while ( $prefixLength ) {			$output .= $this->closeList( $pref2{$prefixLength-1} );			--$prefixLength;		}		if ( '' != $this->mLastSection ) {			$output .= '</' . $this->mLastSection . '>';			$this->mLastSection = '';		}		wfProfileOut( $fname );		return $output;	}	/**	 * Split up a string on ':', ignoring any occurences inside tags	 * to prevent illegal overlapping.	 * @param string $str the string to split	 * @param string &$before set to everything before the ':'	 * @param string &$after set to everything after the ':'	 * return string the position of the ':', or false if none found	 */	function findColonNoLinks($str, &$before, &$after) {		$fname = 'Parser::findColonNoLinks';		wfProfileIn( $fname );				$pos = strpos( $str, ':' );		if( $pos === false ) {			// Nothing to find!			wfProfileOut( $fname );			return false;		}				$lt = strpos( $str, '<' );		if( $lt === false || $lt > $pos ) {			// Easy; no tag nesting to worry about			$before = substr( $str, 0, $pos );			$after = substr( $str, $pos+1 );			wfProfileOut( $fname );			return $pos;		}				// Ugly state machine to walk through avoiding tags.		$state = MW_COLON_STATE_TEXT;		$stack = 0;		$len = strlen( $str );		for( $i = 0; $i < $len; $i++ ) {			$c = $str{$i};						switch( $state ) {			// (Using the number is a performance hack for common cases)			case 0: // MW_COLON_STATE_TEXT:				switch( $c ) {				case "<":					// Could be either a <start> tag or an </end> tag					$state = MW_COLON_STATE_TAGSTART;					break;				case ":":					if( $stack == 0 ) {						// We found it!						$before = substr( $str, 0, $i );						$after = substr( $str, $i + 1 );						wfProfileOut( $fname );						return $i;					}					// Embedded in a tag; don't break it.					break;				default:					// Skip ahead looking 

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?