* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ require_once(Yii::getPathOfAlias('system.vendors.markdown.markdown').'.php'); if(!class_exists('HTMLPurifier_Bootstrap',false)) { require_once(Yii::getPathOfAlias('system.vendors.htmlpurifier').DIRECTORY_SEPARATOR.'HTMLPurifier.standalone.php'); HTMLPurifier_Bootstrap::registerAutoload(); } /** * CMarkdownParser is a wrapper of {@link http://michelf.com/projects/php-markdown/extra/ MarkdownExtra_Parser}. * * CMarkdownParser extends MarkdownExtra_Parser by using Text_Highlighter * to highlight code blocks with specific language syntax. * In particular, if a code block starts with the following: *
 * [language]
 * 
* The syntax for the specified language will be used to highlight * code block. The languages supported include (case-insensitive): * ABAP, CPP, CSS, DIFF, DTD, HTML, JAVA, JAVASCRIPT, * MYSQL, PERL, PHP, PYTHON, RUBY, SQL, XML * * You can also specify options to be passed to the syntax highlighter. For example: *
 * [php showLineNumbers=1]
 * 
* which will show line numbers in each line of the code block. * * For details about the standard markdown syntax, please check the following: * * * @property string $defaultCssFile The default CSS file that is used to highlight code blocks. * * @author Qiang Xue * @package system.utils * @since 1.0 */ class CMarkdownParser extends MarkdownExtra_Parser { /** * @var string the css class for the div element containing * the code block that is highlighted. Defaults to 'hl-code'. */ public $highlightCssClass='hl-code'; /** * @var mixed the options to be passed to {@link http://htmlpurifier.org HTML Purifier}. * This can be a HTMLPurifier_Config object, an array of directives (Namespace.Directive => Value) * or the filename of an ini file. * This property is used only when {@link safeTransform} is invoked. * @see http://htmlpurifier.org/live/configdoc/plain.html * @since 1.1.4 */ public $purifierOptions=null; /** * Transforms the content and purifies the result. * This method calls the transform() method to convert * markdown content into HTML content. It then * uses {@link CHtmlPurifier} to purify the HTML content * to avoid XSS attacks. * @param string $content the markdown content * @return string the purified HTML content */ public function safeTransform($content) { $content=$this->transform($content); $purifier=new HTMLPurifier($this->purifierOptions); $purifier->config->set('Cache.SerializerPath',Yii::app()->getRuntimePath()); return $purifier->purify($content); } /** * @return string the default CSS file that is used to highlight code blocks. */ public function getDefaultCssFile() { return Yii::getPathOfAlias('system.vendors.TextHighlighter.highlight').'.css'; } /** * Callback function when a code block is matched. * @param array $matches matches * @return string the highlighted code block */ public function _doCodeBlocks_callback($matches) { $codeblock = $this->outdent($matches[1]); if(($codeblock = $this->highlightCodeBlock($codeblock)) !== null) return "\n\n".$this->hashBlock($codeblock)."\n\n"; else return parent::_doCodeBlocks_callback($matches); } /** * Callback function when a fenced code block is matched. * @param array $matches matches * @return string the highlighted code block */ public function _doFencedCodeBlocks_callback($matches) { return "\n\n".$this->hashBlock($this->highlightCodeBlock($matches[2]))."\n\n"; } /** * Highlights the code block. * @param string $codeblock the code block * @return string the highlighted code block. Null if the code block does not need to highlighted */ protected function highlightCodeBlock($codeblock) { if(($tag=$this->getHighlightTag($codeblock))!==null && ($highlighter=$this->createHighLighter($tag))) { $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock); $tagLen = strpos($codeblock, $tag)+strlen($tag); $codeblock = ltrim(substr($codeblock, $tagLen)); $output=preg_replace('/]*>(\s*)<\/span>/', '\1', $highlighter->highlight($codeblock)); return "
highlightCssClass}\">".$output."
"; } else return "
".CHtml::encode($codeblock)."
"; } /** * Returns the user-entered highlighting options. * @param string $codeblock code block with highlighting options. * @return string the user-entered highlighting options. Null if no option is entered. */ protected function getHighlightTag($codeblock) { $str = trim(current(preg_split("/\r|\n/", $codeblock,2))); if(strlen($str) > 2 && $str[0] === '[' && $str[strlen($str)-1] === ']') return $str; } /** * Creates a highlighter instance. * @param string $options the user-entered options * @return Text_Highlighter the highlighter instance */ protected function createHighLighter($options) { if(!class_exists('Text_Highlighter', false)) { require_once(Yii::getPathOfAlias('system.vendors.TextHighlighter.Text.Highlighter').'.php'); require_once(Yii::getPathOfAlias('system.vendors.TextHighlighter.Text.Highlighter.Renderer.Html').'.php'); } $lang = current(preg_split('/\s+/', substr(substr($options,1), 0,-1),2)); $highlighter = Text_Highlighter::factory($lang); if($highlighter) $highlighter->setRenderer(new Text_Highlighter_Renderer_Html($this->getHighlightConfig($options))); return $highlighter; } /** * Generates the config for the highlighter. * @param string $options user-entered options * @return array the highlighter config */ public function getHighlightConfig($options) { $config = array('use_language'=>true); if( $this->getInlineOption('showLineNumbers', $options, false) ) $config['numbers'] = HL_NUMBERS_LI; $config['tabsize'] = $this->getInlineOption('tabSize', $options, 4); return $config; } /** * Generates the config for the highlighter. * * NOTE: This method is deprecated due to a mistake in the method name. * Use {@link getHighlightConfig} instead of this. * * @param string $options user-entered options * @return array the highlighter config */ public function getHiglightConfig($options) { return $this->getHighlightConfig($options); } /** * Retrieves the specified configuration. * @param string $name the configuration name * @param string $str the user-entered options * @param mixed $defaultValue default value if the configuration is not present * @return mixed the configuration value */ protected function getInlineOption($name, $str, $defaultValue) { if(preg_match('/'.$name.'(\s*=\s*(\d+))?/i', $str, $v) && count($v) > 2) return $v[2]; else return $defaultValue; } }