CMarkdownParser.php
7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
<?php
/**
* CMarkdownParser class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright © 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$
* @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->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;
}
}