diff options
Diffstat (limited to 'extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php')
-rw-r--r-- | extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php | 211 |
1 files changed, 176 insertions, 35 deletions
diff --git a/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php b/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php index 186b99ac..7318574d 100644 --- a/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php +++ b/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php @@ -1,7 +1,6 @@ <?php class SyntaxHighlight_GeSHi { - /** * Has GeSHi been initialised this session? */ @@ -9,6 +8,7 @@ class SyntaxHighlight_GeSHi { /** * List of languages available to GeSHi + * @var array */ private static $languages = null; @@ -21,12 +21,19 @@ class SyntaxHighlight_GeSHi { * @return string */ public static function parserHook( $text, $args = array(), $parser ) { - global $wgSyntaxHighlightDefaultLang; + global $wgSyntaxHighlightDefaultLang, $wgUseSiteCss, $wgUseTidy; wfProfileIn( __METHOD__ ); self::initialise(); $text = rtrim( $text ); // Don't trim leading spaces away, just the linefeeds $text = preg_replace( '/^\n+/', '', $text ); + + if( $wgUseTidy ) { + // HTML Tidy will convert tabs to spaces incorrectly (bug 30930). + // Preemptively replace the spaces in a more controlled fashion. + $text = self::tabsToSpaces( $text ); + } + // Validate language if( isset( $args['lang'] ) && $args['lang'] ) { $lang = $args['lang']; @@ -62,7 +69,7 @@ class SyntaxHighlight_GeSHi { // Highlighting specific lines if( isset( $args['highlight'] ) ) { $lines = self::parseHighlightLines( $args['highlight'] ); - if ( count($lines) ) { + if ( count( $lines ) ) { $geshi->highlight_lines_extra( $lines ); } } @@ -91,10 +98,15 @@ class SyntaxHighlight_GeSHi { return $error; } // Armour for Parser::doBlockLevels() - if( $enclose === GESHI_HEADER_DIV ) + if( $enclose === GESHI_HEADER_DIV ) { $out = str_replace( "\n", '', $out ); + } // Register CSS - $parser->mOutput->addHeadItem( self::buildHeadItem( $geshi ), "source-{$lang}" ); + $parser->getOutput()->addHeadItem( self::buildHeadItem( $geshi ), "source-{$lang}" ); + + if ( $wgUseSiteCss ) { + $parser->getOutput()->addModuleStyles( 'ext.geshi.local' ); + } $encloseTag = $enclose === GESHI_HEADER_NONE ? 'span' : 'div'; $attribs = Sanitizer::validateTagAttributes( $args, $encloseTag ); @@ -105,19 +117,22 @@ class SyntaxHighlight_GeSHi { if ( $enclose === GESHI_HEADER_NONE ) { $attribs = self::addAttribute( $attribs, 'class', 'mw-geshi ' . $lang . ' source-' . $lang ); } else { - if ( !isset( $attribs['dir'] ) ) { - $attribs = self::addAttribute( $attribs, 'dir', 'ltr' ); - } - - $attribs = self::addAttribute( $attribs, 'class', 'mw-geshi' ); - $attribs = self::addAttribute( $attribs, 'style', 'text-align: left;' ); + // Default dir="ltr" (but allow dir="rtl", although unsure if needed) + $attribs['dir'] = isset( $attribs['dir'] ) && $attribs['dir'] === 'rtl' ? 'rtl' : 'ltr'; + $attribs = self::addAttribute( $attribs, 'class', 'mw-geshi mw-code mw-content-' . $attribs['dir'] ); } - $out = Xml::tags( $encloseTag, $attribs, $out ); + $out = Html::rawElement( $encloseTag, $attribs, $out ); wfProfileOut( __METHOD__ ); return $out; } + /** + * @param $attribs array + * @param $name string + * @param $value string + * @return array + */ private static function addAttribute( $attribs, $name, $value ) { if( isset( $attribs[$name] ) ) { $attribs[$name] = $value . ' ' . $attribs[$name]; @@ -133,7 +148,7 @@ class SyntaxHighlight_GeSHi { * * Input is comma-separated list of lines or line ranges. * - * @input string + * @param $arg string * @return array of ints */ protected static function parseHighlightLines( $arg ) { @@ -160,6 +175,9 @@ class SyntaxHighlight_GeSHi { /** * Validate a provided input range + * @param $start + * @param $end + * @return bool */ protected static function validHighlightRange( $start, $end ) { // Since we're taking this tiny range and producing a an @@ -176,6 +194,10 @@ class SyntaxHighlight_GeSHi { $end - $start < $arbitrarilyLargeConstant; } + /** + * @param $args array + * @return int + */ static function getEncloseType( $args ) { // Since version 1.0.8 geshi can produce valid pre, but we need to check for it if ( defined('GESHI_HEADER_PRE_VALID') ) { @@ -205,7 +227,9 @@ class SyntaxHighlight_GeSHi { /** * Hook into Article::view() to provide syntax highlighting for - * custom CSS and JavaScript pages + * custom CSS and JavaScript pages. + * + * B/C for MW 1.20 and before. 1.21 and later use renderHook() instead. * * @param string $text * @param Title $title @@ -213,10 +237,11 @@ class SyntaxHighlight_GeSHi { * @return bool */ public static function viewHook( $text, $title, $output ) { + global $wgUseSiteCss; // Determine the language $matches = array(); preg_match( '!\.(css|js)$!u', $title->getText(), $matches ); - $lang = $matches[1] == 'css' ? 'css' : 'javascript'; + $lang = isset( $matches[1] ) && $matches[1] == 'css' ? 'css' : 'javascript'; // Attempt to format $geshi = self::prepare( $text, $lang ); if( $geshi instanceof GeSHi ) { @@ -225,9 +250,68 @@ class SyntaxHighlight_GeSHi { // Done $output->addHeadItem( "source-$lang", self::buildHeadItem( $geshi ) ); $output->addHTML( "<div dir=\"ltr\">{$out}</div>" ); + if( $wgUseSiteCss ) { + $output->addModuleStyles( 'ext.geshi.local' ); + } + return false; + } + } + // Bottle out + return true; + } + + /** + * Hook into Content::getParserOutput to provide syntax highlighting for + * script content. + * + * @return bool + * @since MW 1.21 + */ + public static function renderHook( Content $content, Title $title, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + + global $wgSyntaxHighlightModels, $wgUseSiteCss; + + // Determine the language + $model = $content->getModel(); + if ( !isset( $wgSyntaxHighlightModels[$model] ) ) { + // We don't care about this model, carry on. + return true; + } + + if ( !$generateHtml ) { + // Nothing to do. + return false; + } + + // Hope that $wgSyntaxHighlightModels does not contain silly types. + $text = Contenthandler::getContentText( $content ); + + if ( $text === null || $text === false ) { + // Oops! Non-text content? + return false; + } + + $lang = $wgSyntaxHighlightModels[$model]; + + // Attempt to format + $geshi = self::prepare( $text, $lang ); + if( $geshi instanceof GeSHi ) { + + $out = $geshi->parse_code(); + if( !$geshi->error() ) { + // Done + $output->addHeadItem( self::buildHeadItem( $geshi ), "source-$lang" ); + $output->setText( "<div dir=\"ltr\">{$out}</div>" ); + + if( $wgUseSiteCss ) { + $output->addModuleStyles( 'ext.geshi.local' ); + } return false; } } + // Bottle out return true; } @@ -236,11 +320,16 @@ class SyntaxHighlight_GeSHi { * Initialise a GeSHi object to format some code, performing * common setup for all our uses of it * + * @note Used only until MW 1.20 + * * @param string $text * @param string $lang * @return GeSHi */ - private static function prepare( $text, $lang ) { + public static function prepare( $text, $lang ) { + + global $wgSyntaxHighlightKeywordLinks; + self::initialise(); $geshi = new GeSHi( $text, $lang ); if( $geshi->error() == GESHI_ERROR_NO_SUCH_LANG ) { @@ -249,7 +338,18 @@ class SyntaxHighlight_GeSHi { $geshi->set_encoding( 'UTF-8' ); $geshi->enable_classes(); $geshi->set_overall_class( "source-$lang" ); - $geshi->enable_keyword_links( false ); + $geshi->enable_keyword_links( $wgSyntaxHighlightKeywordLinks ); + + // If the source code is over 100 kB, disable higlighting of symbols. + // If over 200 kB, disable highlighting of strings too. + $bytes = strlen( $text ); + if ( $bytes > 102400 ) { + $geshi->set_symbols_highlighting( false ); + if ( $bytes > 204800 ) { + $geshi->set_strings_highlighting( false ); + } + } + return $geshi; } @@ -260,8 +360,20 @@ class SyntaxHighlight_GeSHi { * @param GeSHi $geshi * @return string */ - private static function buildHeadItem( $geshi ) { - global $wgUseSiteCss, $wgSquidMaxage; + public static function buildHeadItem( $geshi ) { + /** + * Geshi comes by default with a font-family set to monospace which + * ends ultimately ends up causing the font-size to be smaller than + * one would expect (causing bug 26204). + * We append to the default geshi style a CSS hack which is to specify + * monospace twice which "reset" the browser font-size specified for monospace. + * + * The hack is documented in MediaWiki core under + * docs/uidesign/monospace.html and in bug 33496. + */ + $geshi->set_code_style( 'font-family: monospace, monospace;', + /** preserve defaults */ true ); + $lang = $geshi->language; $css = array(); $css[] = '<style type="text/css">/*<![CDATA[*/'; @@ -272,14 +384,6 @@ class SyntaxHighlight_GeSHi { $css[] = $geshi->get_stylesheet( false ); $css[] = '/*]]>*/'; $css[] = '</style>'; - if( $wgUseSiteCss ) { - $title = Title::makeTitle( NS_MEDIAWIKI, 'Geshi.css' ); - $q = "usemsgcache=yes&action=raw&ctype=text/css&smaxage={$wgSquidMaxage}"; - $css[] = '<style type="text/css">/*<![CDATA[*/'; - $css[] = '@import "' . $title->getLocalUrl( $q ) . '";'; - $css[] = '/*]]>*/'; - $css[] = '</style>'; - } return implode( "\n", $css ); } @@ -291,7 +395,8 @@ class SyntaxHighlight_GeSHi { * @return string HTML fragment */ private static function formatLanguageError( $text ) { - $error = self::formatError( htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-language' ) ), $text ); + $msg = wfMessage( 'syntaxhighlight-err-language' )->inContentLanguage()->escaped(); + $error = self::formatError( $msg, $text ); return $error . '<pre>' . htmlspecialchars( $text ) . '</pre>'; } @@ -306,10 +411,10 @@ class SyntaxHighlight_GeSHi { if( $error ) { $html .= "<p>{$error}</p>"; } - $html .= '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-specify' ) ) + $html .= '<p>' . wfMessage( 'syntaxhighlight-specify')->inContentLanguage()->escaped() . ' <samp><source lang="html4strict">...</source></samp></p>' - . '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-supported' ) ) . '</p>' - . self::formatLanguages(); + . '<p>' . wfMessage( 'syntaxhighlight-supported' )->inContentLanguage()->escaped() + . '</p>' . self::formatLanguages(); return "<div style=\"border: solid red 1px; padding: .5em;\">{$html}</div>"; } @@ -327,7 +432,7 @@ class SyntaxHighlight_GeSHi { } return '<p class="mw-collapsible mw-collapsed" style="padding: 0em 1em;">' . implode( ', ', $list ) . '</p><br style="clear: all"/>'; } else { - return '<p>' . htmlspecialchars( wfMsgForContent( 'syntaxhighlight-err-loading' ) ) . '</p>'; + return '<p>' . wfMessage( 'syntaxhighlight-err-loading' )->inContentLanguage()->escaped() . '</p>'; } } @@ -350,11 +455,12 @@ class SyntaxHighlight_GeSHi { /** * Initialise messages and ensure the GeSHi class is loaded + * @return bool */ private static function initialise() { if( !self::$initialised ) { if( !class_exists( 'GeSHi' ) ) { - require( 'geshi/geshi.php' ); + require( dirname( __FILE__ ) . '/geshi/geshi.php' ); } self::$initialised = true; } @@ -363,6 +469,8 @@ class SyntaxHighlight_GeSHi { /** * Get the GeSHI's version information while Special:Version is read. + * @param $extensionTypes + * @return bool */ public static function hSpecialVersion_GeSHi( &$extensionTypes ) { global $wgExtensionCredits; @@ -373,9 +481,42 @@ class SyntaxHighlight_GeSHi { /** * @see SyntaxHighlight_GeSHi::hSpecialVersion_GeSHi + * @param $sp + * @param $extensionTypes + * @return bool */ public static function hOldSpecialVersion_GeSHi( &$sp, &$extensionTypes ) { return self::hSpecialVersion_GeSHi( $extensionTypes ); } - -}
\ No newline at end of file + + /** + * Convert tabs to spaces + * + * @param string $text + * @return string + */ + private static function tabsToSpaces( $text ) { + $lines = explode( "\n", $text ); + $lines = array_map( array( __CLASS__, 'tabsToSpacesLine' ), $lines ); + return implode( "\n", $lines ); + } + + /** + * Convert tabs to spaces for a single line + * + * @param $line + * @internal param string $text + * @return string + */ + private static function tabsToSpacesLine( $line ) { + $parts = explode( "\t", $line ); + $width = 8; // To match tidy's config & typical browser defaults + $out = $parts[0]; + foreach( array_slice( $parts, 1 ) as $chunk ) { + $spaces = $width - (strlen( $out ) % $width); + $out .= str_repeat( ' ', $spaces ); + $out .= $chunk; + } + return $out; + } +} |