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 + -
显示快捷键?