/[drupal]/contributions/modules/geshifilter/geshifilter.pages.inc
ViewVC logotype

Contents of /contributions/modules/geshifilter/geshifilter.pages.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.13 - (show annotations) (download) (as text)
Mon Aug 17 11:54:38 2009 UTC (3 months, 1 week ago) by soxofaan
Branch: MAIN
CVS Tags: HEAD
Changes since 1.12: +8 -2 lines
File MIME type: text/x-php
workaround for lost highlighting of inline code with GeSHi lib 1.0.8 and later
1 <?php
2 // $Id: geshifilter.pages.inc,v 1.12 2009/07/04 11:05:56 soxofaan Exp $
3
4 /**
5 * @file
6 * Functions that handle the actual GeSHi processing.
7 *
8 * The actual GeSHi filter stuff: parsing the text to filter,
9 * configure the GeSHi parser, feed the code to the GeSHi parser and put the
10 * result back in the text.
11 */
12
13 require_once drupal_get_path('module', 'geshifilter') .'/geshifilter.inc';
14
15 /**
16 * Helper function for parsing the attributes of GeSHi code tags
17 * to get the settings for language and line numbers.
18 *
19 * @param $attributes string with the attributes
20 * @param $format the concerning input format
21 * @return array of settings with fields 'language', 'line_numbering', 'linenumbers_start'
22 */
23 function _geshifilter_parse_attributes($attributes, $format) {
24 // Initial values.
25 $lang = NULL;
26 $line_numbering = NULL;
27 $linenumbers_start = NULL;
28
29 // Get the possible tags and languages.
30 list($generic_code_tags, $language_tags, $tag_to_lang) = _geshifilter_get_tags($format);
31
32 $language_attributes = _geshifilter_whitespace_explode(GESHIFILTER_ATTRIBUTES_LANGUAGE);
33 $attributes_preg_string = implode('|', array_merge(
34 $language_attributes,
35 array(GESHIFILTER_ATTRIBUTE_LINE_NUMBERING, GESHIFILTER_ATTRIBUTE_LINE_NUMBERING_START, GESHIFILTER_ATTRIBUTE_FANCY_N)
36 ));
37 $enabled_languages = _geshifilter_get_enabled_languages();
38
39 // Parse $attributes to an array $attribute_matches with:
40 // $attribute_matches[0][xx] .... fully matched string, e.g. 'language="python"'
41 // $attribute_matches[1][xx] .... param name, e.g. 'language'
42 // $attribute_matches[2][xx] .... param value, e.g. 'python'
43 preg_match_all('#('. $attributes_preg_string .')="?([^\s"]*)"?#', $attributes, $attribute_matches);
44
45 foreach ($attribute_matches[1] as $a_key => $att_name) {
46 // get attribute value
47 $att_value = $attribute_matches[2][$a_key];
48
49 // Check for the language attributes.
50 if (in_array($att_name, $language_attributes)) {
51 // Try first to map the attribute value to geshi language code.
52 if (in_array($att_value, $language_tags)) {
53 $att_value = $tag_to_lang[$att_value];
54 }
55 // Set language if extracted language is an enabled language.
56 if (array_key_exists($att_value, $enabled_languages)) {
57 $lang = $att_value;
58 }
59 }
60
61 // Check for line numbering related attributes.
62 // $line_numbering defines the line numbering mode:
63 // 0: no line numbering
64 // 1: normal line numbering
65 // n>= 2: fancy line numbering every nth line
66 elseif ($att_name == GESHIFILTER_ATTRIBUTE_LINE_NUMBERING) {
67 switch (strtolower($att_value)) {
68 case "off":
69 $line_numbering = 0;
70 break;
71 case "normal":
72 $line_numbering = 1;
73 break;
74 case "fancy":
75 $line_numbering = 5;
76 break;
77 }
78 }
79 elseif ($att_name == GESHIFILTER_ATTRIBUTE_FANCY_N) {
80 $att_value = (int)($att_value);
81 if ($att_value >= 2) {
82 $line_numbering = $att_value;
83 }
84 }
85 elseif ($att_name == GESHIFILTER_ATTRIBUTE_LINE_NUMBERING_START) {
86 if ($line_numbering < 1) {
87 $line_numbering = 1;
88 }
89 $linenumbers_start = (int)($att_value);
90 }
91 }
92 // Return parsed results.
93 return array('language' => $lang, 'line_numbering' => $line_numbering, 'linenumbers_start' => $linenumbers_start);
94 }
95
96 /**
97 * geshifilter_filter callback for preparing input text.
98 */
99 function _geshifilter_prepare($format, $text) {
100 // get the available tags
101 list($generic_code_tags, $language_tags, $tag_to_lang) = _geshifilter_get_tags($format);
102 $tags = array_merge($generic_code_tags, $language_tags);
103 // escape special (regular expression) characters in tags (for tags like 'c++' and 'c#')
104 $tags = preg_replace('#(\\+|\\#)#', '\\\\$1', $tags);
105 $tags_string = implode('|', $tags);
106 // Pattern for matching "<code>...</code>" like stuff
107 // Also matches "<code>...$" where "$" refers to end of string, not end of
108 // line (because PCRE_MULTILINE (modifier 'm') is not enabled), so matching
109 // still works when teaser view trims inside the source code.
110
111 // Replace the code container tag brackets
112 // and prepare the container content (newline and angle bracket protection).
113 // @todo: make sure that these replacements can be done in serie.
114 $tag_styles = array_filter(_geshifilter_tag_styles($format));
115 if (in_array(GESHIFILTER_BRACKETS_ANGLE, $tag_styles)) {
116 // Prepare <foo>..</foo> blocks.
117 $pattern = '#(<)('. $tags_string .')((\s+[^>]*)*)(>)(.*?)(</\2\s*>|$)#s';
118 $text = preg_replace_callback($pattern, create_function('$match', "return _geshifilter_prepare_callback(\$match, $format);"), $text);
119 }
120 if (in_array(GESHIFILTER_BRACKETS_SQUARE, $tag_styles)) {
121 // Prepare [foo]..[/foo] blocks.
122 $pattern = '#((?<!\[)\[)('. $tags_string .')((\s+[^\]]*)*)(\](?!\[))(.*?)((?<!\[)\[/\2\s*\](?<!\[)|$)#s';
123 $text = preg_replace_callback($pattern, create_function('$match', "return _geshifilter_prepare_callback(\$match, $format);"), $text);
124 }
125 if (in_array(GESHIFILTER_BRACKETS_DOUBLESQUARE, $tag_styles)) {
126 // Prepare [[foo]]..[[/foo]] blocks.
127 $pattern = '#(\[\[)('. $tags_string .')((\s+[^\]]*)*)(\]\])(.*?)(\[\[/\2\s*\]\]|$)#s';
128 $text = preg_replace_callback($pattern, create_function('$match', "return _geshifilter_prepare_callback(\$match, $format);"), $text);
129 }
130 if (in_array(GESHIFILTER_BRACKETS_PHPBLOCK, $tag_styles)) {
131 // Prepare < ?php ... ? > blocks.
132 $pattern = '#[\[<](\?php|\?PHP|%)(.+?)((\?|%)[\]>]|$)#s';
133 $text = preg_replace_callback($pattern, '_geshifilter_prepare_php_callback', $text);
134 }
135 return $text;
136 }
137
138 /**
139 * _geshifilter_prepare callback for preparing input text.
140 * Replaces the code tags brackets with geshifilter specific ones to prevent
141 * possible messing up by other filters, e.g.
142 * '[python]foo[/python]' to '[geshifilter-python]foo[/geshifilter-python]'.
143 * Replaces newlines with "&#10;" to prevent issues with the line break filter
144 * Escapes the tricky characters like angle brackets with check_plain() to
145 * prevent messing up by other filters like the HTML filter.
146 */
147 function _geshifilter_prepare_callback($match, $format) {
148 // $match[0]: complete matched string
149 // $match[1]: opening bracket ('<' or '[')
150 // $match[2]: tag
151 // $match[3] and $match[4]: attributes
152 // $match[5]: closing bracket
153 // $match[6]: source code
154 // $match[7]: closing tag
155 $tag_name = $match[2];
156 $tag_attributes = $match[3];
157 $content = $match[6];
158
159 // get the default highlighting mode
160 $lang = variable_get('geshifilter_default_highlighting', GESHIFILTER_DEFAULT_PLAINTEXT);
161 if ($lang == GESHIFILTER_DEFAULT_DONOTHING) {
162 // If the default highlighting mode is GESHIFILTER_DEFAULT_DONOTHING
163 // and there is no language set (with language tag or language attribute),
164 // we should not do any escaping in this prepare phase,
165 // so that other filters can do their thing.
166 $enabled_languages = _geshifilter_get_enabled_languages();
167
168 // Usage of language tag?
169 list($generic_code_tags, $language_tags, $tag_to_lang) = _geshifilter_get_tags($format);
170 if (isset($tag_to_lang[$tag_name]) && isset($enabled_languages[$tag_to_lang[$tag_name]])) {
171 $lang = $tag_to_lang[$tag_name];
172 }
173 // Usage of language attribute?
174 else {
175 // Get additional settings from the tag attributes.
176 $settings = _geshifilter_parse_attributes($tag_attributes, $format);
177 if ($settings['language'] && isset($enabled_languages[$settings['language']])) {
178 $lang = $settings['language'];
179 }
180 }
181 // If no language was set: prevent escaping and return original string
182 if ($lang == GESHIFILTER_DEFAULT_DONOTHING) {
183 return $match[0];
184 }
185 }
186 // return escaped code block
187 return '[geshifilter-'. $tag_name . $tag_attributes .']'
188 . str_replace(array("\r", "\n"), array('', '&#10;'), check_plain($content))
189 .'[/geshifilter-'. $tag_name .']';
190 }
191
192 /**
193 * _geshifilter_prepare callback for < ?php ... ? > blocks
194 */
195 function _geshifilter_prepare_php_callback($match) {
196 return '[geshifilter-questionmarkphp]'
197 . str_replace(array("\r", "\n"), array('', '&#10;'), check_plain($match[2]))
198 .'[/geshifilter-questionmarkphp]';
199 }
200
201 /**
202 * geshifilter_filter callback for processing input text.
203 */
204 function _geshifilter_process($format, $text) {
205 // load GeSHi library (if not already)
206 $geshi_library = _geshifilter_check_geshi_library();
207 if (!$geshi_library['success']) {
208 drupal_set_message($geshi_library['message'], 'error');
209 return $text;
210 }
211 // get the available tags
212 list($generic_code_tags, $language_tags, $tag_to_lang) = _geshifilter_get_tags($format);
213 if (in_array(GESHIFILTER_BRACKETS_PHPBLOCK, array_filter(_geshifilter_tag_styles($format)))) {
214 $language_tags[] = 'questionmarkphp';
215 $tag_to_lang['questionmarkphp'] = 'php';
216 }
217 $tags = array_merge($generic_code_tags, $language_tags);
218 // escape special (regular expression) characters in tags (for tags like 'c++' and 'c#')
219 $tags = preg_replace('#(\\+|\\#)#', '\\\\$1', $tags);
220 $tags_string = implode('|', $tags);
221 // Pattern for matching the prepared "<code>...</code>" stuff
222 $pattern = '#\\[geshifilter-('. $tags_string .')([^\\]]*)\\](.*?)(\\[/geshifilter-\1\\])#s';
223 $text = preg_replace_callback($pattern, create_function('$match', "return _geshifilter_replace_callback(\$match, $format);"), $text);
224 return $text;
225 }
226
227
228 /**
229 * preg_replace_callback callback
230 */
231 function _geshifilter_replace_callback($match, $format) {
232 // $match[0]: complete matched string
233 // $match[1]: tag name
234 // $match[2]: tag attributes
235 // $match[3]: tag content
236 $complete_match = $match[0];
237 $tag_name = $match[1];
238 $tag_attributes = $match[2];
239 $source_code = $match[3];
240
241 // Undo linebreak and escaping from preparation phase.
242 $source_code = decode_entities($source_code);
243
244 // Initialize to default settings.
245 $lang = variable_get('geshifilter_default_highlighting', GESHIFILTER_DEFAULT_PLAINTEXT);
246 $line_numbering = variable_get('geshifilter_default_line_numbering', GESHIFILTER_LINE_NUMBERS_DEFAULT_NONE);
247 $linenumbers_start = 1;
248
249 // Determine language based on tag name if possible.
250 list($generic_code_tags, $language_tags, $tag_to_lang) = _geshifilter_get_tags($format);
251 if (in_array(GESHIFILTER_BRACKETS_PHPBLOCK, array_filter(_geshifilter_tag_styles($format)))) {
252 $language_tags[] = 'questionmarkphp';
253 $tag_to_lang['questionmarkphp'] = 'php';
254 }
255 if (isset($tag_to_lang[$tag_name])) {
256 $lang = $tag_to_lang[$tag_name];
257 }
258
259 // Get additional settings from the tag attributes.
260 $settings = _geshifilter_parse_attributes($tag_attributes, $format);
261 if (isset($settings['language'])) {
262 $lang = $settings['language'];
263 }
264 if (isset($settings['line_numbering'])) {
265 $line_numbering = $settings['line_numbering'];
266 }
267 if (isset($settings['linenumbers_start'])) {
268 $linenumbers_start = $settings['linenumbers_start'];
269 }
270
271 if ($lang == GESHIFILTER_DEFAULT_DONOTHING) {
272 // Do nothing, and return the original.
273 return $complete_match;
274 }
275 if ($lang == GESHIFILTER_DEFAULT_PLAINTEXT) {
276 // Use plain text 'highlighting'
277 $lang = 'text';
278 }
279 $inline_mode = (strpos($source_code, "\n") === FALSE);
280 // process and return
281 return geshifilter_process($source_code, $lang, $line_numbering, $linenumbers_start, $inline_mode);
282 }
283
284 /**
285 * Helper function for overriding some GeSHi defaults
286 */
287 function _geshifilter_override_geshi_defaults(&$geshi, $langcode) {
288 // override the some default GeSHi styles (e.g. GeSHi uses Courier by default, which is ugly)
289 $geshi->set_line_style('font-family: monospace; font-weight: normal;', 'font-family: monospace; font-weight: bold; font-style: italic;');
290 $geshi->set_code_style('font-family: monospace; font-weight: normal; font-style: normal');
291 // overall class needed for CSS
292 $geshi->set_overall_class('geshifilter-'. $langcode);
293 // set keyword linking
294 $geshi->enable_keyword_links(variable_get('geshifilter_enable_keyword_urls', TRUE));
295 }
296
297 /**
298 * General geshifilter processing function
299 */
300 function geshifilter_process($source_code, $lang, $line_numbering=0, $linenumbers_start=1, $inline_mode=FALSE) {
301 // process
302 if ($lang == 'php' && variable_get('geshifilter_use_highlight_string_for_php', FALSE)) {
303 return geshifilter_highlight_string_process($source_code, $inline_mode);
304 }
305 else {
306 // process with GeSHi
307 return geshifilter_geshi_process($source_code, $lang, $line_numbering, $linenumbers_start, $inline_mode);
308 }
309 }
310
311 /**
312 * geshifilter wrapper for GeSHi processing.
313 */
314 function geshifilter_geshi_process($source_code, $lang, $line_numbering=0, $linenumbers_start=1, $inline_mode=FALSE) {
315 // load GeSHi library (if not already)
316 $geshi_library = _geshifilter_check_geshi_library();
317 if (!$geshi_library['loaded']) {
318 drupal_set_message($geshi_library['message'], 'error');
319 return $source_code;
320 }
321 // remove leading/trailing newlines
322 $source_code = trim($source_code, "\n\r");
323 // create GeSHi object
324 $geshi = _geshifilter_geshi_factory($source_code, $lang);
325
326 // CSS mode
327 $ccs_mode = variable_get('geshifilter_css_mode', GESHIFILTER_CSS_INLINE);
328 if ($ccs_mode == GESHIFILTER_CSS_CLASSES_AUTOMATIC || $ccs_mode == GESHIFILTER_CSS_CLASSES_ONLY) {
329 $geshi->enable_classes(TRUE);
330 }
331 _geshifilter_override_geshi_defaults($geshi, $lang);
332 // some more GeSHi settings and parsing
333 if ($inline_mode) {
334 // inline source code mode
335 $geshi->set_header_type(GESHI_HEADER_NONE);
336 // To make highlighting work we have to manually set a class on the code
337 // element we will wrap the code in.
338 // To counter a change between GeSHi version 1.0.7.22 and 1.0.8 (svn
339 // commit 1610), we use both the language and overall_class for the class,
340 // to mimic the 1.0.8 behavior, which is backward compatible.
341 $code_class = "{$geshi->language} {$geshi->overall_class}";
342 $source_code = '<span class="geshifilter"><code class="'. $code_class .'">'. $geshi->parse_code() .'</code></span>';
343 }
344 else {
345 // block source code mode
346 $geshi->set_header_type((int)variable_get('geshifilter_code_container', GESHI_HEADER_PRE));
347 if ($line_numbering == 1) {
348 $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
349 $geshi->start_line_numbers_at($linenumbers_start);
350 }
351 elseif ($line_numbering >= 2) {
352 $geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS, $line_numbering);
353 $geshi->start_line_numbers_at($linenumbers_start);
354 }
355 $source_code = '<div class="geshifilter">'. $geshi->parse_code() .'</div>';
356 }
357 return $source_code;
358 }
359
360 /**
361 * geshifilter wrapper for highlight_string() processing of PHP
362 */
363 function geshifilter_highlight_string_process($source_code, $inline_mode) {
364 // Make sure that the source code starts with < ?php and ends with ? >
365 $text = trim($source_code);
366 if (substr($text, 0, 5) != '<?php') {
367 $source_code = '<?php'. $source_code;
368 }
369 if (substr($text, -2) != '?>') {
370 $source_code = $source_code .'?>';
371 }
372 // Use the right container
373 $container = $inline_mode ? 'span' : 'div';
374 // Process with highlight_string()
375 $text = '<'. $container .' class="codeblock geshifilter">'. highlight_string($source_code, TRUE) .'</'. $container .'>';
376 // Remove newlines (added by highlight_string()) to avoid issues with the linebreak filter
377 $text = str_replace("\n", '', $text);
378 return $text;
379 }

  ViewVC Help
Powered by ViewVC 1.1.2