summaryrefslogtreecommitdiff
path: root/framework/utils/CMarkdownParser.php
diff options
context:
space:
mode:
Diffstat (limited to 'framework/utils/CMarkdownParser.php')
-rw-r--r--framework/utils/CMarkdownParser.php195
1 files changed, 195 insertions, 0 deletions
diff --git a/framework/utils/CMarkdownParser.php b/framework/utils/CMarkdownParser.php
new file mode 100644
index 0000000..bbdc9bc
--- /dev/null
+++ b/framework/utils/CMarkdownParser.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * CMarkdownParser class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 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:
+ * <pre>
+ * [language]
+ * </pre>
+ * 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:
+ * <pre>
+ * [php showLineNumbers=1]
+ * </pre>
+ * which will show line numbers in each line of the code block.
+ *
+ * For details about the standard markdown syntax, please check the following:
+ * <ul>
+ * <li>{@link http://daringfireball.net/projects/markdown/syntax official markdown syntax}</li>
+ * <li>{@link http://michelf.com/projects/php-markdown/extra/ markdown extra syntax}</li>
+ * </ul>
+ *
+ * @property string $defaultCssFile The default CSS file that is used to highlight code blocks.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: CMarkdownParser.php 3515 2011-12-28 12:29:24Z mdomba $
+ * @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('/<span\s+[^>]*>(\s*)<\/span>/', '\1', $highlighter->highlight($codeblock));
+ return "<div class=\"{$this->highlightCssClass}\">".$output."</div>";
+ }
+ else
+ return "<pre>".CHtml::encode($codeblock)."</pre>";
+ }
+
+ /**
+ * 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->getHiglightConfig($options)));
+ return $highlighter;
+ }
+
+ /**
+ * Generates the config for the highlighter.
+ * @param string $options user-entered options
+ * @return array the highlighter config
+ */
+ public function getHiglightConfig($options)
+ {
+ $config['use_language'] = true;
+ if( $this->getInlineOption('showLineNumbers', $options, false) )
+ $config['numbers'] = HL_NUMBERS_LI;
+ $config['tabsize'] = $this->getInlineOption('tabSize', $options, 4);
+ return $config;
+ }
+
+ /**
+ * 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;
+ }
+}