File Source for geshi.php

# Source for file geshi.php

Documentation is available at geshi.php

1. <?php
2. /**
3. * GeSHi - Generic Syntax Highlighter
4. *
5. * The GeSHi class for Generic Syntax Highlighting. Please refer to the
6. * documentation at http://qbnz.com/highlighter/documentation.php for more
7. * information about how to use this class.
8. *
9. * For changes, release notes, TODOs etc, see the relevant files in the docs/
10. * directory.
11. *
12. * This file is part of GeSHi.
13. *
14. * GeSHi is free software; you can redistribute it and/or modify
15. * it under the terms of the GNU General Public License as published by
16. * the Free Software Foundation; either version 2 of the License, or
17. * (at your option) any later version.
18. *
19. * GeSHi is distributed in the hope that it will be useful,
20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22. * GNU General Public License for more details.
23. *
24. * You should have received a copy of the GNU General Public License
25. * along with GeSHi; if not, write to the Free Software
26. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27. *
28. * @package geshi
29. * @subpackage core
30. * @author Nigel McNie <nigel@geshi.org>, Benny Baumann <BenBE@omorphia.de>
31. * @copyright (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
32. * @license http://gnu.org/copyleft/gpl.html GNU GPL
33. *
34. */
36. //
37. // GeSHi Constants
38. // You should use these constant names in your programs instead of
39. // their values - you never know when a value may change in a future
40. // version
41. //
43. /** The version of this GeSHi file */
44. define('GESHI_VERSION', '1.0.8.2',);
46. // Define the root directory for the GeSHi code tree
47. if (!defined('GESHI_ROOT')) {
48. /** The root directory for GeSHi */
49. define('GESHI_ROOT', dirname(__FILE__) . DIRECTORY_SEPARATOR);
50. }
51. /** The language file directory for GeSHi
52. @access private */
53. define('GESHI_LANG_ROOT', GESHI_ROOT . 'geshi' . DIRECTORY_SEPARATOR);
55. // Define if GeSHi should be paranoid about security
56. if (!defined('GESHI_SECURITY_PARANOID')) {
57. /** Tells GeSHi to be paranoid about security settings */
58. define('GESHI_SECURITY_PARANOID', false);
59. }
61. // Line numbers - use with enable_line_numbers()
62. /** Use no line numbers when building the result */
63. define('GESHI_NO_LINE_NUMBERS', 0);
64. /** Use normal line numbers when building the result */
65. define('GESHI_NORMAL_LINE_NUMBERS', 1);
66. /** Use fancy line numbers when building the result */
67. define('GESHI_FANCY_LINE_NUMBERS', 2);
69. // Container HTML type
70. /** Use nothing to surround the source */
71. define('GESHI_HEADER_NONE', 0);
72. /** Use a "div" to surround the source */
73. define('GESHI_HEADER_DIV', 1);
74. /** Use a "pre" to surround the source */
75. define('GESHI_HEADER_PRE', 2);
76. /** Use a pre to wrap lines when line numbers are enabled or to wrap the whole code. */
77. define('GESHI_HEADER_PRE_VALID', 3);
78. /**
79. * Use a "table" to surround the source:
80. *
81. * <table>
82. * <thead><tr><td colspan="2">$header</td></tr></thead>
83. * <tbody><tr><td><pre>$linenumbers</pre></td><td><pre>$code></pre></td></tr></tbody>
84. * <tfooter><tr><td colspan="2">$footer</td></tr></tfoot>
85. * </table>
86. *
87. * this is essentially only a workaround for Firefox, see sf#1651996 or take a look at
88. * https://bugzilla.mozilla.org/show_bug.cgi?id=365805
89. * @note when linenumbers are disabled this is essentially the same as GESHI_HEADER_PRE
90. */
91. define('GESHI_HEADER_PRE_TABLE', 4);
93. // Capatalisation constants
94. /** Lowercase keywords found */
95. define('GESHI_CAPS_NO_CHANGE', 0);
96. /** Uppercase keywords found */
97. define('GESHI_CAPS_UPPER', 1);
98. /** Leave keywords found as the case that they are */
99. define('GESHI_CAPS_LOWER', 2);
101. // Link style constants
102. /** Links in the source in the :link state */
103. define('GESHI_LINK', 0);
104. /** Links in the source in the :hover state */
105. define('GESHI_HOVER', 1);
106. /** Links in the source in the :active state */
107. define('GESHI_ACTIVE', 2);
108. /** Links in the source in the :visited state */
109. define('GESHI_VISITED', 3);
111. // Important string starter/finisher
112. // Note that if you change these, they should be as-is: i.e., don't
113. // write them as if they had been run through htmlentities()
114. /** The starter for important parts of the source */
115. define('GESHI_START_IMPORTANT', '<BEGIN GeSHi>');
116. /** The ender for important parts of the source */
117. define('GESHI_END_IMPORTANT', '<END GeSHi>');
119. /**#@+
120. * @access private
121. */
122. // When strict mode applies for a language
123. /** Strict mode never applies (this is the most common) */
124. define('GESHI_NEVER', 0);
125. /** Strict mode *might* apply, and can be enabled or
126. disabled by {@link GeSHi->enable_strict_mode()} */
127. define('GESHI_MAYBE', 1);
128. /** Strict mode always applies */
129. define('GESHI_ALWAYS', 2);
131. // Advanced regexp handling constants, used in language files
132. /** The key of the regex array defining what to search for */
133. define('GESHI_SEARCH', 0);
134. /** The key of the regex array defining what bracket group in a
135. matched search to use as a replacement */
136. define('GESHI_REPLACE', 1);
137. /** The key of the regex array defining any modifiers to the regular expression */
138. define('GESHI_MODIFIERS', 2);
139. /** The key of the regex array defining what bracket group in a
140. matched search to put before the replacement */
141. define('GESHI_BEFORE', 3);
142. /** The key of the regex array defining what bracket group in a
143. matched search to put after the replacement */
144. define('GESHI_AFTER', 4);
145. /** The key of the regex array defining a custom keyword to use
146. for this regexp's html tag class */
147. define('GESHI_CLASS', 5);
149. /** Used in language files to mark comments */
150. define('GESHI_COMMENTS', 0);
152. /** Used to work around missing PHP features **/
153. define('GESHI_PHP_PRE_433', !(version_compare(PHP_VERSION, '4.3.3') === 1));
155. /** make sure we can call stripos **/
156. if (!function_exists('stripos')) {
157. // the offset param of preg_match is not supported below PHP 4.3.3
158. if (GESHI_PHP_PRE_433) {
159. /**
160. * @ignore
161. */
162. function stripos($haystack, $needle, $offset = null) {
163. if (!is_null($offset)) {
164. $haystack = substr($haystack, $offset);
165. }
166. if (preg_match('/'. preg_quote($needle, '/') . '/', $haystack, $match, PREG_OFFSET_CAPTURE)) {
167. return $match[0][1];
168. }
169. return false;
170. }
171. }
172. else {
173. /**
174. * @ignore
175. */
176. function stripos($haystack, $needle, $offset = null) {
177. if (preg_match('/'. preg_quote($needle, '/') . '/', $haystack, $match, PREG_OFFSET_CAPTURE, $offset)) {
178. return $match[0][1];
179. }
180. return false;
181. }
182. }
183. }
185. /** some old PHP / PCRE subpatterns only support up to xxx subpatterns in
186. regular expressions. Set this to false if your PCRE lib is up to date
187. @see GeSHi->optimize_regexp_list()
188. **/
190. define('GESHI_MAX_PCRE_SUBPATTERNS', 500);
191. /** it's also important not to generate too long regular expressions
192. be generous here... but keep in mind, that when reaching this limit we
193. still have to close open patterns. 12k should do just fine on a 16k limit.
194. @see GeSHi->optimize_regexp_list()
195. **/
197. define('GESHI_MAX_PCRE_LENGTH', 12288);
199. //Number format specification
200. /** Basic number format for integers */
201. define('GESHI_NUMBER_INT_BASIC', 1); //Default integers \d+
202. /** Enhanced number format for integers like seen in C */
203. define('GESHI_NUMBER_INT_CSTYLE', 2); //Default C-Style \d+[lL]?
204. /** Number format to highlight binary numbers with a suffix "b" */
205. define('GESHI_NUMBER_BIN_SUFFIX', 16); //[01]+[bB]
206. /** Number format to highlight binary numbers with a prefix % */
207. define('GESHI_NUMBER_BIN_PREFIX_PERCENT', 32); //%[01]+
208. /** Number format to highlight binary numbers with a prefix 0b (C) */
209. define('GESHI_NUMBER_BIN_PREFIX_0B', 64); //0b[01]+
210. /** Number format to highlight octal numbers with a leading zero */
211. define('GESHI_NUMBER_OCT_PREFIX', 256); //0[0-7]+
212. /** Number format to highlight octal numbers with a suffix of o */
213. define('GESHI_NUMBER_OCT_SUFFIX', 512); //[0-7]+[oO]
214. /** Number format to highlight hex numbers with a prefix 0x */
215. define('GESHI_NUMBER_HEX_PREFIX', 4096); //0x[0-9a-fA-F]+
216. /** Number format to highlight hex numbers with a suffix of h */
217. define('GESHI_NUMBER_HEX_SUFFIX', 8192); //[0-9][0-9a-fA-F]*h
218. /** Number format to highlight floating-point numbers without support for scientific notation */
219. define('GESHI_NUMBER_FLT_NONSCI', 65536); //\d+\.\d+
220. /** Number format to highlight floating-point numbers without support for scientific notation */
221. define('GESHI_NUMBER_FLT_NONSCI_F', 131072); //\d+(\.\d+)?f
222. /** Number format to highlight floating-point numbers with support for scientific notation (E) and optional leading zero */
223. define('GESHI_NUMBER_FLT_SCI_SHORT', 262144); //\.\d+e\d+
224. /** Number format to highlight floating-point numbers with support for scientific notation (E) and required leading digit */
225. define('GESHI_NUMBER_FLT_SCI_ZERO', 524288); //\d+(\.\d+)?e\d+
226. //Custom formats are passed by RX array
228. // Error detection - use these to analyse faults
229. /** No sourcecode to highlight was specified
230. * @deprecated
231. */
232. define('GESHI_ERROR_NO_INPUT', 1);
233. /** The language specified does not exist */
234. define('GESHI_ERROR_NO_SUCH_LANG', 2);
235. /** GeSHi could not open a file for reading (generally a language file) */
236. define('GESHI_ERROR_FILE_NOT_READABLE', 3);
237. /** The header type passed to {@link GeSHi->set_header_type()} was invalid */
238. define('GESHI_ERROR_INVALID_HEADER_TYPE', 4);
239. /** The line number type passed to {@link GeSHi->enable_line_numbers()} was invalid */
240. define('GESHI_ERROR_INVALID_LINE_NUMBER_TYPE', 5);
241. /**#@-*/
244. /**
245. * The GeSHi Class.
246. *
247. * Please refer to the documentation for GeSHi 1.0.X that is available
248. * at http://qbnz.com/highlighter/documentation.php for more information
249. * about how to use this class.
250. *
251. * @package geshi
252. * @author Nigel McNie <nigel@geshi.org>, Benny Baumann <BenBE@omorphia.de>
253. * @copyright (C) 2004 - 2007 Nigel McNie, (C) 2007 - 2008 Benny Baumann
254. */
255. class GeSHi {
256. /**#@+
257. * @access private
258. */
259. /**
260. * The source code to highlight
261. * @var string
262. */
263. var $source = '';
265. /**
266. * The language to use when highlighting
267. * @var string
268. */
269. var $language = '';
271. /**
272. * The data for the language used
273. * @var array
274. */
275. var $language_data = array();
277. /**
278. * The path to the language files
279. * @var string
280. */
281. var $language_path = GESHI_LANG_ROOT;
283. /**
284. * The error message associated with an error
285. * @var string
286. * @todo check err reporting works
287. */
288. var $error = false;
290. /**
291. * Possible error messages
292. * @var array
293. */
294. var $error_messages = array(
295. GESHI_ERROR_NO_SUCH_LANG => 'GeSHi could not find the language {LANGUAGE} (using path {PATH})',
296. GESHI_ERROR_FILE_NOT_READABLE => 'The file specified for load_from_file was not readable',
297. GESHI_ERROR_INVALID_HEADER_TYPE => 'The header type specified is invalid',
298. GESHI_ERROR_INVALID_LINE_NUMBER_TYPE => 'The line number type specified is invalid'
299. );
301. /**
302. * Whether highlighting is strict or not
303. * @var boolean
304. */
305. var $strict_mode = false;
307. /**
308. * Whether to use CSS classes in output
309. * @var boolean
310. */
311. var $use_classes = false;
313. /**
314. * The type of header to use. Can be one of the following
315. * values:
316. *
317. * - GESHI_HEADER_PRE: Source is outputted in a "pre" HTML element.
318. * - GESHI_HEADER_DIV: Source is outputted in a "div" HTML element.
319. * - GESHI_HEADER_NONE: No header is outputted.
320. *
321. * @var int
322. */
323. var $header_type = GESHI_HEADER_PRE;
325. /**
326. * Array of permissions for which lexics should be highlighted
327. * @var array
328. */
329. var $lexic_permissions = array(
330. 'KEYWORDS' => array(),
331. 'COMMENTS' => array('MULTI' => true),
332. 'REGEXPS' => array(),
333. 'ESCAPE_CHAR' => true,
334. 'BRACKETS' => true,
335. 'SYMBOLS' => false,
336. 'STRINGS' => true,
337. 'NUMBERS' => true,
338. 'METHODS' => true,
339. 'SCRIPT' => true
340. );
342. /**
343. * The time it took to parse the code
344. * @var double
345. */
346. var $time = 0;
348. /**
349. * The content of the header block
350. * @var string
351. */
352. var $header_content = '';
354. /**
355. * The content of the footer block
356. * @var string
357. */
358. var $footer_content = '';
360. /**
361. * The style of the header block
362. * @var string
363. */
364. var $header_content_style = '';
366. /**
367. * The style of the footer block
368. * @var string
369. */
370. var $footer_content_style = '';
372. /**
373. * Tells if a block around the highlighted source should be forced
374. * if not using line numbering
375. * @var boolean
376. */
377. var $force_code_block = false;
379. /**
380. * The styles for hyperlinks in the code
381. * @var array
382. */
383. var $link_styles = array();
385. /**
386. * Whether important blocks should be recognised or not
387. * @var boolean
388. * @deprecated
389. * @todo REMOVE THIS FUNCTIONALITY!
390. */
391. var $enable_important_blocks = false;
393. /**
394. * Styles for important parts of the code
395. * @var string
396. * @deprecated
397. * @todo As above - rethink the whole idea of important blocks as it is buggy and
398. * will be hard to implement in 1.2
399. */
400. var $important_styles = 'font-weight: bold; color: red;'; // Styles for important parts of the code
402. /**
403. * Whether CSS IDs should be added to the code
404. * @var boolean
405. */
406. var $add_ids = false;
408. /**
409. * Lines that should be highlighted extra
410. * @var array
411. */
412. var $highlight_extra_lines = array();
414. /**
415. * Styles of lines that should be highlighted extra
416. * @var array
417. */
418. var $highlight_extra_lines_styles = array();
420. /**
421. * Styles of extra-highlighted lines
422. * @var string
423. */
424. var $highlight_extra_lines_style = 'background-color: #ffc;';
426. /**
427. * The line ending
428. * If null, nl2br() will be used on the result string.
429. * Otherwise, all instances of \n will be replaced with $line_ending
430. * @var string
431. */
432. var $line_ending = null;
434. /**
435. * Number at which line numbers should start at
436. * @var int
437. */
438. var $line_numbers_start = 1;
440. /**
441. * The overall style for this code block
442. * @var string
443. */
444. var $overall_style = 'font-family:monospace;';
446. /**
447. * The style for the actual code
448. * @var string
449. */
450. var $code_style = 'font: normal normal 1em/1.2em monospace; margin:0; padding:0; background:none; vertical-align:top;';
452. /**
453. * The overall class for this code block
454. * @var string
455. */
456. var $overall_class = '';
458. /**
459. * The overall ID for this code block
460. * @var string
461. */
462. var $overall_id = '';
464. /**
465. * Line number styles
466. * @var string
467. */
468. var $line_style1 = 'font-weight: normal; vertical-align:top;';
470. /**
471. * Line number styles for fancy lines
472. * @var string
473. */
474. var $line_style2 = 'font-weight: bold; vertical-align:top;';
476. /**
477. * Style for line numbers when GESHI_HEADER_PRE_TABLE is chosen
478. * @var string
479. */
480. var $table_linenumber_style = 'width:1px;text-align:right;margin:0;padding:0 2px;vertical-align:top;';
482. /**
483. * Flag for how line numbers are displayed
484. * @var boolean
485. */
486. var $line_numbers = GESHI_NO_LINE_NUMBERS;
488. /**
489. * Flag to decide if multi line spans are allowed. Set it to false to make sure
490. * each tag is closed before and reopened after each linefeed.
491. * @var boolean
492. */
493. var $allow_multiline_span = true;
495. /**
496. * The "nth" value for fancy line highlighting
497. * @var int
498. */
499. var $line_nth_row = 0;
501. /**
502. * The size of tab stops
503. * @var int
504. */
505. var $tab_width = 8;
507. /**
508. * Should we use language-defined tab stop widths?
509. * @var int
510. */
511. var $use_language_tab_width = false;
513. /**
514. * Default target for keyword links
515. * @var string
516. */
517. var $link_target = '';
519. /**
520. * The encoding to use for entity encoding
521. * NOTE: Used with Escape Char Sequences to fix UTF-8 handling (cf. SF#2037598)
522. * @var string
523. */
524. var $encoding = 'utf-8';
526. /**
527. * Should keywords be linked?
528. * @var boolean
529. */
530. var $keyword_links = true;
532. /**
533. * Currently loaded language file
534. * @var string
535. * @since 1.0.7.22
536. */
537. var $loaded_language = '';
539. /**
540. * Wether the caches needed for parsing are built or not
541. *
542. * @var bool
543. * @since 1.0.8
544. */
545. var $parse_cache_built = false;
547. /**
548. * Work around for Suhosin Patch with disabled /e modifier
549. *
550. * Note from suhosins author in config file:
551. * <blockquote>
552. * The /e modifier inside <code>preg_replace()</code> allows code execution.
553. * Often it is the cause for remote code execution exploits. It is wise to
554. * deactivate this feature and test where in the application it is used.
555. * The developer using the /e modifier should be made aware that he should
556. * use <code>preg_replace_callback()</code> instead
557. * </blockquote>
558. *
559. * @var array
560. * @since 1.0.8
561. */
562. var $_kw_replace_group = 0;
563. var $_rx_key = 0;
565. /**
566. * some "callback parameters" for handle_multiline_regexps
567. *
568. * @since 1.0.8
569. * @access private
570. * @var string
571. */
572. var $_hmr_before = '';
573. var $_hmr_replace = '';
574. var $_hmr_after = '';
575. var $_hmr_key = 0;
577. /**#@-*/
579. /**
580. * Creates a new GeSHi object, with source and language
581. *
582. * @param string The source code to highlight
583. * @param string The language to highlight the source with
584. * @param string The path to the language file directory. <b>This
585. * is deprecated!</b> I've backported the auto path
586. * detection from the 1.1.X dev branch, so now it
587. * should be automatically set correctly. If you have
588. * renamed the language directory however, you will
589. * still need to set the path using this parameter or
590. * {@link GeSHi->set_language_path()}
591. * @since 1.0.0
592. */
593. function GeSHi($source = '', $language = '', $path = '') {
594. if (!empty($source)) {
595. $this->set_source($source);
596. }
597. if (!empty($language)) {
598. $this->set_language($language);
599. }
600. $this->set_language_path($path);
601. }
603. /**
604. * Returns an error message associated with the last GeSHi operation,
605. * or false if no error has occured
606. *
607. * @return string|falseAn error message if there has been an error, else false
608. * @since 1.0.0
609. */
610. function error() {
611. if ($this->error) {
612. //Put some template variables for debugging here ...
613. $debug_tpl_vars = array(
614. '{LANGUAGE}' => $this->language,
615. '{PATH}' => $this->language_path
616. );
617. $msg = str_replace(
618. array_keys($debug_tpl_vars),
619. array_values($debug_tpl_vars),
620. $this->error_messages[$this->error]);
622. return "<br /><strong>GeSHi Error:</strong> $msg (code {$this->error})<br />";
623. }
624. return false;
625. }
627. /**
628. * Gets a human-readable language name (thanks to Simon Patterson
629. * for the idea :))
630. *
631. * @return string The name for the current language
632. * @since 1.0.2
633. */
634. function get_language_name() {
635. if (GESHI_ERROR_NO_SUCH_LANG == $this->error) {
636. return $this->language_data['LANG_NAME'] . ' (Unknown Language)';
637. }
638. return $this->language_data['LANG_NAME'];
639. }
641. /**
642. * Sets the source code for this object
643. *
644. * @param string The source code to highlight
645. * @since 1.0.0
646. */
647. function set_source($source) {
648. $this->source = $source;
649. $this->highlight_extra_lines = array();
650. }
652. /**
653. * Sets the language for this object
654. *
655. * @note since 1.0.8 this function won't reset language-settings by default anymore!
656. * if you need this set $force_reset = true
657. *
658. * @param string The name of the language to use
659. * @since 1.0.0
660. */
661. function set_language($language, $force_reset = false) {
662. if ($force_reset) {
663. $this->loaded_language = false;
664. }
666. //Clean up the language name to prevent malicious code injection
667. $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
669. $language = strtolower($language);
671. //Retreive the full filename
672. $file_name = $this->language_path . $language . '.php';
673. if ($file_name == $this->loaded_language) {
674. // this language is already loaded!
675. return;
676. }
678. $this->language = $language;
680. $this->error = false;
681. $this->strict_mode = GESHI_NEVER;
683. //Check if we can read the desired file
684. if (!is_readable($file_name)) {
685. $this->error = GESHI_ERROR_NO_SUCH_LANG;
686. return;
687. }
689. // Load the language for parsing
690. $this->load_language($file_name);
691. }
693. /**
694. * Sets the path to the directory containing the language files. Note
695. * that this path is relative to the directory of the script that included
696. * geshi.php, NOT geshi.php itself.
697. *
698. * @param string The path to the language directory
699. * @since 1.0.0
700. * @deprecated The path to the language files should now be automatically
701. * detected, so this method should no longer be needed. The
702. * 1.1.X branch handles manual setting of the path differently
703. * so this method will disappear in 1.2.0.
704. */
705. function set_language_path($path) {
706. if(strpos($path,':')) {
707. //Security Fix to prevent external directories using fopen wrappers.
708. if(DIRECTORY_SEPARATOR == "\\") {
709. if(!preg_match('#^[a-zA-Z]:#', $path) || false !== strpos($path, ':', 2)) {
710. return;
711. }
712. } else {
713. return;
714. }
715. }
716. if(preg_match('#[^/a-zA-Z0-9_\.\-\\\s:]#', $path)) {
717. //Security Fix to prevent external directories using fopen wrappers.
718. return;
719. }
720. if(<a href="../geshi/core/_geshi.php.html#defineGESHI_SECURITY_PARANOID">GESHI_SECURITY_PARANOID</a> && false !== strpos($path, '/.')) {
721. //Security Fix to prevent external directories using fopen wrappers.
722. return;
723. }
724. if(<a href="../geshi/core/_geshi.php.html#defineGESHI_SECURITY_PARANOID">GESHI_SECURITY_PARANOID</a> && false !== strpos($path, '..')) {
725. //Security Fix to prevent external directories using fopen wrappers.
726. return;
727. }
728. if ($path) {
729. $this->language_path = ('/' == $path[strlen($path) - 1]) ? $path : $path . '/';
730. $this->set_language($this->language); // otherwise set_language_path has no effect
731. }
732. }
734. /**
735. * Sets the type of header to be used.
736. *
737. * If GESHI_HEADER_DIV is used, the code is surrounded in a "div".This
738. * means more source code but more control over tab width and line-wrapping.
739. * GESHI_HEADER_PRE means that a "pre" is used - less source, but less
740. * control. Default is GESHI_HEADER_PRE.
741. *
742. * From 1.0.7.2, you can use GESHI_HEADER_NONE to specify that no header code
743. * should be outputted.
744. *
745. * @param int The type of header to be used
746. * @since 1.0.0
747. */
748. function set_header_type($type) {
749. //Check if we got a valid header type
750. if (!in_array($type, array(<a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_NONE">GESHI_HEADER_NONE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_DIV">GESHI_HEADER_DIV</a>,
751. <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>))) {
752. $this->error = GESHI_ERROR_INVALID_HEADER_TYPE;
753. return;
754. }
756. //Set that new header type
757. $this->header_type = $type;
758. }
760. /**
761. * Sets the styles for the code that will be outputted
762. * when this object is parsed. The style should be a
763. * string of valid stylesheet declarations
764. *
765. * @param string The overall style for the outputted code block
766. * @param boolean Whether to merge the styles with the current styles or not
767. * @since 1.0.0
768. */
769. function set_overall_style($style, $preserve_defaults = false) {
770. if (!$preserve_defaults) {
771. $this->overall_style = $style;
772. } else {
773. $this->overall_style .= $style;
774. }
775. }
777. /**
778. * Sets the overall classname for this block of code. This
779. * class can then be used in a stylesheet to style this object's
780. * output
781. *
782. * @param string The class name to use for this block of code
783. * @since 1.0.0
784. */
785. function set_overall_class($class) {
786. $this->overall_class = $class;
787. }
789. /**
790. * Sets the overall id for this block of code. This id can then
791. * be used in a stylesheet to style this object's output
792. *
793. * @param string The ID to use for this block of code
794. * @since 1.0.0
795. */
796. function set_overall_id($id) {
797. $this->overall_id = $id;
798. }
800. /**
801. * Sets whether CSS classes should be used to highlight the source. Default
802. * is off, calling this method with no arguments will turn it on
803. *
804. * @param boolean Whether to turn classes on or not
805. * @since 1.0.0
806. */
807. function enable_classes($flag = true) {
808. $this->use_classes = ($flag) ? true : false;
809. }
811. /**
812. * Sets the style for the actual code. This should be a string
813. * containing valid stylesheet declarations. If $preserve_defaults is
814. * true, then styles are merged with the default styles, with the
815. * user defined styles having priority
816. *
817. * Note: Use this method to override any style changes you made to
818. * the line numbers if you are using line numbers, else the line of
819. * code will have the same style as the line number! Consult the
820. * GeSHi documentation for more information about this.
821. *
822. * @param string The style to use for actual code
823. * @param boolean Whether to merge the current styles with the new styles
824. * @since 1.0.2
825. */
826. function set_code_style($style, $preserve_defaults = false) {
827. if (!$preserve_defaults) {
828. $this->code_style = $style;
829. } else {
830. $this->code_style .= $style;
831. }
832. }
834. /**
835. * Sets the styles for the line numbers.
836. *
837. * @param string The style for the line numbers that are "normal"
838. * @param string|booleanIf a string, this is the style of the line
839. * numbers that are "fancy", otherwise if boolean then this
840. * defines whether the normal styles should be merged with the
841. * new normal styles or not
842. * @param boolean If set, is the flag for whether to merge the "fancy"
843. * styles with the current styles or not
844. * @since 1.0.2
845. */
846. function set_line_style($style1, $style2 = '', $preserve_defaults = false) {
847. //Check if we got 2 or three parameters
848. if (is_bool($style2)) {
849. $preserve_defaults = $style2;
850. $style2 = '';
851. }
853. //Actually set the new styles
854. if (!$preserve_defaults) {
855. $this->line_style1 = $style1;
856. $this->line_style2 = $style2;
857. } else {
858. $this->line_style1 .= $style1;
859. $this->line_style2 .= $style2;
860. }
861. }
863. /**
864. * Sets whether line numbers should be displayed.
865. *
866. * Valid values for the first parameter are:
867. *
868. * - GESHI_NO_LINE_NUMBERS: Line numbers will not be displayed
869. * - GESHI_NORMAL_LINE_NUMBERS: Line numbers will be displayed
870. * - GESHI_FANCY_LINE_NUMBERS: Fancy line numbers will be displayed
871. *
872. * For fancy line numbers, the second parameter is used to signal which lines
873. * are to be fancy. For example, if the value of this parameter is 5 then every
874. * 5th line will be fancy.
875. *
876. * @param int How line numbers should be displayed
877. * @param int Defines which lines are fancy
878. * @since 1.0.0
879. */
880. function enable_line_numbers($flag, $nth_row = 5) {
881. if (<a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> != $flag && <a href="../geshi/core/_geshi.php.html#defineGESHI_NORMAL_LINE_NUMBERS">GESHI_NORMAL_LINE_NUMBERS</a> != $flag
882. && <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> != $flag) {
883. $this->error = GESHI_ERROR_INVALID_LINE_NUMBER_TYPE;
884. }
885. $this->line_numbers = $flag;
886. $this->line_nth_row = $nth_row;
887. }
889. /**
890. * Sets wether spans and other HTML markup generated by GeSHi can
891. * span over multiple lines or not. Defaults to true to reduce overhead.
892. * Set it to false if you want to manipulate the output or manually display
893. * the code in an ordered list.
894. *
895. * @param boolean Wether multiline spans are allowed or not
896. * @since 1.0.7.22
897. */
898. function enable_multiline_span($flag) {
899. $this->allow_multiline_span = (bool) $flag;
900. }
902. /**
903. * Get current setting for multiline spans, see GeSHi->enable_multiline_span().
904. *
905. * @see enable_multiline_span
906. * @return bool
907. */
908. function get_multiline_span() {
909. return $this->allow_multiline_span;
910. }
912. /**
913. * Sets the style for a keyword group. If $preserve_defaults is
914. * true, then styles are merged with the default styles, with the
915. * user defined styles having priority
916. *
917. * @param int The key of the keyword group to change the styles of
918. * @param string The style to make the keywords
919. * @param boolean Whether to merge the new styles with the old or just
920. * to overwrite them
921. * @since 1.0.0
922. */
923. function set_keyword_group_style($key, $style, $preserve_defaults = false) {
924. //Set the style for this keyword group
925. if (!$preserve_defaults) {
926. $this->language_data['STYLES']['KEYWORDS'][$key] = $style;
927. } else {
928. $this->language_data['STYLES']['KEYWORDS'][$key] .= $style;
929. }
931. //Update the lexic permissions
932. if (!isset($this->lexic_permissions['KEYWORDS'][$key])) {
933. $this->lexic_permissions['KEYWORDS'][$key] = true;
934. }
935. }
937. /**
938. * Turns highlighting on/off for a keyword group
939. *
940. * @param int The key of the keyword group to turn on or off
941. * @param boolean Whether to turn highlighting for that group on or off
942. * @since 1.0.0
943. */
944. function set_keyword_group_highlighting($key, $flag = true) {
945. $this->lexic_permissions['KEYWORDS'][$key] = ($flag) ? true : false;
946. }
948. /**
949. * Sets the styles for comment groups. If $preserve_defaults is
950. * true, then styles are merged with the default styles, with the
951. * user defined styles having priority
952. *
953. * @param int The key of the comment group to change the styles of
954. * @param string The style to make the comments
955. * @param boolean Whether to merge the new styles with the old or just
956. * to overwrite them
957. * @since 1.0.0
958. */
959. function set_comments_style($key, $style, $preserve_defaults = false) {
960. if (!$preserve_defaults) {
961. $this->language_data['STYLES']['COMMENTS'][$key] = $style;
962. } else {
963. $this->language_data['STYLES']['COMMENTS'][$key] .= $style;
964. }
965. }
967. /**
968. * Turns highlighting on/off for comment groups
969. *
970. * @param int The key of the comment group to turn on or off
971. * @param boolean Whether to turn highlighting for that group on or off
972. * @since 1.0.0
973. */
974. function set_comments_highlighting($key, $flag = true) {
975. $this->lexic_permissions['COMMENTS'][$key] = ($flag) ? true : false;
976. }
978. /**
979. * Sets the styles for escaped characters. If $preserve_defaults is
980. * true, then styles are merged with the default styles, with the
981. * user defined styles having priority
982. *
983. * @param string The style to make the escape characters
984. * @param boolean Whether to merge the new styles with the old or just
985. * to overwrite them
986. * @since 1.0.0
987. */
988. function set_escape_characters_style($style, $preserve_defaults = false) {
989. if (!$preserve_defaults) {
990. $this->language_data['STYLES']['ESCAPE_CHAR'][0] = $style;
991. } else {
992. $this->language_data['STYLES']['ESCAPE_CHAR'][0] .= $style;
993. }
994. }
996. /**
997. * Turns highlighting on/off for escaped characters
998. *
999. * @param boolean Whether to turn highlighting for escape characters on or off
1000. * @since 1.0.0
1001. */
1002. function set_escape_characters_highlighting($flag = true) {
1003. $this->lexic_permissions['ESCAPE_CHAR'] = ($flag) ? true : false;
1004. }
1006. /**
1007. * Sets the styles for brackets. If $preserve_defaults is
1008. * true, then styles are merged with the default styles, with the
1009. * user defined styles having priority
1010. *
1011. * This method is DEPRECATED: use set_symbols_style instead.
1012. * This method will be removed in 1.2.X
1013. *
1014. * @param string The style to make the brackets
1015. * @param boolean Whether to merge the new styles with the old or just
1016. * to overwrite them
1017. * @since 1.0.0
1018. * @deprecated In favour of set_symbols_style
1019. */
1020. function set_brackets_style($style, $preserve_defaults = false) {
1021. if (!$preserve_defaults) {
1022. $this->language_data['STYLES']['BRACKETS'][0] = $style;
1023. } else {
1024. $this->language_data['STYLES']['BRACKETS'][0] .= $style;
1025. }
1026. }
1028. /**
1029. * Turns highlighting on/off for brackets
1030. *
1031. * This method is DEPRECATED: use set_symbols_highlighting instead.
1032. * This method will be remove in 1.2.X
1033. *
1034. * @param boolean Whether to turn highlighting for brackets on or off
1035. * @since 1.0.0
1036. * @deprecated In favour of set_symbols_highlighting
1037. */
1038. function set_brackets_highlighting($flag) {
1039. $this->lexic_permissions['BRACKETS'] = ($flag) ? true : false;
1040. }
1042. /**
1043. * Sets the styles for symbols. If $preserve_defaults is
1044. * true, then styles are merged with the default styles, with the
1045. * user defined styles having priority
1046. *
1047. * @param string The style to make the symbols
1048. * @param boolean Whether to merge the new styles with the old or just
1049. * to overwrite them
1050. * @param int Tells the group of symbols for which style should be set.
1051. * @since 1.0.1
1052. */
1053. function set_symbols_style($style, $preserve_defaults = false, $group = 0) {
1054. // Update the style of symbols
1055. if (!$preserve_defaults) {
1056. $this->language_data['STYLES']['SYMBOLS'][$group] = $style;
1057. } else {
1058. $this->language_data['STYLES']['SYMBOLS'][$group] .= $style;
1059. }
1061. // For backward compatibility
1062. if (0 == $group) {
1063. $this->set_brackets_style ($style, $preserve_defaults);
1064. }
1065. }
1067. /**
1068. * Turns highlighting on/off for symbols
1069. *
1070. * @param boolean Whether to turn highlighting for symbols on or off
1071. * @since 1.0.0
1072. */
1073. function set_symbols_highlighting($flag) {
1074. // Update lexic permissions for this symbol group
1075. $this->lexic_permissions['SYMBOLS'] = ($flag) ? true : false;
1077. // For backward compatibility
1078. $this->set_brackets_highlighting ($flag);
1079. }
1081. /**
1082. * Sets the styles for strings. If $preserve_defaults is
1083. * true, then styles are merged with the default styles, with the
1084. * user defined styles having priority
1085. *
1086. * @param string The style to make the escape characters
1087. * @param boolean Whether to merge the new styles with the old or just
1088. * to overwrite them
1089. * @since 1.0.0
1090. */
1091. function set_strings_style($style, $preserve_defaults = false) {
1092. if (!$preserve_defaults) {
1093. $this->language_data['STYLES']['STRINGS'][0] = $style;
1094. } else {
1095. $this->language_data['STYLES']['STRINGS'][0] .= $style;
1096. }
1097. }
1099. /**
1100. * Turns highlighting on/off for strings
1101. *
1102. * @param boolean Whether to turn highlighting for strings on or off
1103. * @since 1.0.0
1104. */
1105. function set_strings_highlighting($flag) {
1106. $this->lexic_permissions['STRINGS'] = ($flag) ? true : false;
1107. }
1109. /**
1110. * Sets the styles for numbers. If $preserve_defaults is
1111. * true, then styles are merged with the default styles, with the
1112. * user defined styles having priority
1113. *
1114. * @param string The style to make the numbers
1115. * @param boolean Whether to merge the new styles with the old or just
1116. * to overwrite them
1117. * @since 1.0.0
1118. */
1119. function set_numbers_style($style, $preserve_defaults = false) {
1120. if (!$preserve_defaults) {
1121. $this->language_data['STYLES']['NUMBERS'][0] = $style;
1122. } else {
1123. $this->language_data['STYLES']['NUMBERS'][0] .= $style;
1124. }
1125. }
1127. /**
1128. * Turns highlighting on/off for numbers
1129. *
1130. * @param boolean Whether to turn highlighting for numbers on or off
1131. * @since 1.0.0
1132. */
1133. function set_numbers_highlighting($flag) {
1134. $this->lexic_permissions['NUMBERS'] = ($flag) ? true : false;
1135. }
1137. /**
1138. * Sets the styles for methods. $key is a number that references the
1139. * appropriate "object splitter" - see the language file for the language
1140. * you are highlighting to get this number. If $preserve_defaults is
1141. * true, then styles are merged with the default styles, with the
1142. * user defined styles having priority
1143. *
1144. * @param int The key of the object splitter to change the styles of
1145. * @param string The style to make the methods
1146. * @param boolean Whether to merge the new styles with the old or just
1147. * to overwrite them
1148. * @since 1.0.0
1149. */
1150. function set_methods_style($key, $style, $preserve_defaults = false) {
1151. if (!$preserve_defaults) {
1152. $this->language_data['STYLES']['METHODS'][$key] = $style;
1153. } else {
1154. $this->language_data['STYLES']['METHODS'][$key] .= $style;
1155. }
1156. }
1158. /**
1159. * Turns highlighting on/off for methods
1160. *
1161. * @param boolean Whether to turn highlighting for methods on or off
1162. * @since 1.0.0
1163. */
1164. function set_methods_highlighting($flag) {
1165. $this->lexic_permissions['METHODS'] = ($flag) ? true : false;
1166. }
1168. /**
1169. * Sets the styles for regexps. If $preserve_defaults is
1170. * true, then styles are merged with the default styles, with the
1171. * user defined styles having priority
1172. *
1173. * @param string The style to make the regular expression matches
1174. * @param boolean Whether to merge the new styles with the old or just
1175. * to overwrite them
1176. * @since 1.0.0
1177. */
1178. function set_regexps_style($key, $style, $preserve_defaults = false) {
1179. if (!$preserve_defaults) {
1180. $this->language_data['STYLES']['REGEXPS'][$key] = $style;
1181. } else {
1182. $this->language_data['STYLES']['REGEXPS'][$key] .= $style;
1183. }
1184. }
1186. /**
1187. * Turns highlighting on/off for regexps
1188. *
1189. * @param int The key of the regular expression group to turn on or off
1190. * @param boolean Whether to turn highlighting for the regular expression group on or off
1191. * @since 1.0.0
1192. */
1193. function set_regexps_highlighting($key, $flag) {
1194. $this->lexic_permissions['REGEXPS'][$key] = ($flag) ? true : false;
1195. }
1197. /**
1198. * Sets whether a set of keywords are checked for in a case sensitive manner
1199. *
1200. * @param int The key of the keyword group to change the case sensitivity of
1201. * @param boolean Whether to check in a case sensitive manner or not
1202. * @since 1.0.0
1203. */
1204. function set_case_sensitivity($key, $case) {
1205. $this->language_data['CASE_SENSITIVE'][$key] = ($case) ? true : false;
1206. }
1208. /**
1209. * Sets the case that keywords should use when found. Use the constants:
1210. *
1211. * - GESHI_CAPS_NO_CHANGE: leave keywords as-is
1212. * - GESHI_CAPS_UPPER: convert all keywords to uppercase where found
1213. * - GESHI_CAPS_LOWER: convert all keywords to lowercase where found
1214. *
1215. * @param int A constant specifying what to do with matched keywords
1216. * @since 1.0.1
1217. */
1218. function set_case_keywords($case) {
1219. if (in_array($case, array(
1220. <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_NO_CHANGE">GESHI_CAPS_NO_CHANGE</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_UPPER">GESHI_CAPS_UPPER</a>, <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_LOWER">GESHI_CAPS_LOWER</a>))) {
1221. $this->language_data['CASE_KEYWORDS'] = $case;
1222. }
1223. }
1225. /**
1226. * Sets how many spaces a tab is substituted for
1227. *
1228. * Widths below zero are ignored
1229. *
1230. * @param int The tab width
1231. * @since 1.0.0
1232. */
1233. function set_tab_width($width) {
1234. $this->tab_width = intval($width);
1236. //Check if it fit's the constraints:
1237. if ($this->tab_width < 1) {
1238. //Return it to the default
1239. $this->tab_width = 8;
1240. }
1241. }
1243. /**
1244. * Sets whether or not to use tab-stop width specifed by language
1245. *
1246. * @param boolean Whether to use language-specific tab-stop widths
1247. * @since 1.0.7.20
1248. */
1249. function set_use_language_tab_width($use) {
1250. $this->use_language_tab_width = (bool) $use;
1251. }
1253. /**
1254. * Returns the tab width to use, based on the current language and user
1255. * preference
1256. *
1257. * @return int Tab width
1258. * @since 1.0.7.20
1259. */
1260. function get_real_tab_width() {
1261. if (!$this->use_language_tab_width ||
1262. !isset($this->language_data['TAB_WIDTH'])) {
1263. return $this->tab_width;
1264. } else {
1265. return $this->language_data['TAB_WIDTH'];
1266. }
1267. }
1269. /**
1270. * Enables/disables strict highlighting. Default is off, calling this
1271. * method without parameters will turn it on. See documentation
1272. * for more details on strict mode and where to use it.
1273. *
1274. * @param boolean Whether to enable strict mode or not
1275. * @since 1.0.0
1276. */
1277. function enable_strict_mode($mode = true) {
1278. if (GESHI_MAYBE == $this->language_data['STRICT_MODE_APPLIES']) {
1279. $this->strict_mode = ($mode) ? GESHI_ALWAYS : GESHI_NEVER;
1280. }
1281. }
1283. /**
1284. * Disables all highlighting
1285. *
1286. * @since 1.0.0
1287. * @todo Rewrite with array traversal
1288. * @deprecated In favour of enable_highlighting
1289. */
1290. function disable_highlighting() {
1291. $this->enable_highlighting(false);
1292. }
1294. /**
1295. * Enables all highlighting
1296. *
1297. * The optional flag parameter was added in version 1.0.7.21 and can be used
1298. * to enable (true) or disable (false) all highlighting.
1299. *
1300. * @since 1.0.0
1301. * @param boolean A flag specifying whether to enable or disable all highlighting
1302. * @todo Rewrite with array traversal
1303. */
1304. function enable_highlighting($flag = true) {
1305. $flag = $flag ? true : false;
1306. foreach ($this->lexic_permissions as $key => $value) {
1307. if (is_array($value)) {
1308. foreach ($value as $k => $v) {
1309. $this->lexic_permissions[$key][$k] = $flag;
1310. }
1311. } else {
1312. $this->lexic_permissions[$key] = $flag;
1313. }
1314. }
1316. // Context blocks
1317. $this->enable_important_blocks = $flag;
1318. }
1320. /**
1321. * Given a file extension, this method returns either a valid geshi language
1322. * name, or the empty string if it couldn't be found
1323. *
1324. * @param string The extension to get a language name for
1325. * @param array A lookup array to use instead of the default one
1326. * @since 1.0.5
1327. * @todo Re-think about how this method works (maybe make it private and/or make it
1328. * a extension->lang lookup?)
1329. * @todo static?
1330. */
1331. function get_language_name_from_extension( $extension, $lookup = array() ) {
1332. if ( !is_array($lookup) || empty($lookup)) {
1333. $lookup = array(
1334. 'actionscript' => array('as'),
1335. 'ada' => array('a', 'ada', 'adb', 'ads'),
1336. 'apache' => array('conf'),
1337. 'asm' => array('ash', 'asm', 'inc'),
1338. 'asp' => array('asp'),
1339. 'bash' => array('sh'),
1340. 'bf' => array('bf'),
1341. 'c' => array('c', 'h'),
1342. 'c_mac' => array('c', 'h'),
1343. 'caddcl' => array(),
1344. 'cadlisp' => array(),
1345. 'cdfg' => array('cdfg'),
1346. 'cobol' => array('cbl'),
1347. 'cpp' => array('cpp', 'hpp', 'C', 'H', 'CPP', 'HPP'),
1348. 'csharp' => array('cs'),
1349. 'css' => array('css'),
1350. 'd' => array('d'),
1351. 'delphi' => array('dpk', 'dpr', 'pp', 'pas'),
1352. 'diff' => array('diff', 'patch'),
1353. 'dos' => array('bat', 'cmd'),
1354. 'gettext' => array('po', 'pot'),
1355. 'gml' => array('gml'),
1356. 'gnuplot' => array('plt'),
1357. 'groovy' => array('groovy'),
1358. 'haskell' => array('hs'),
1359. 'html4strict' => array('html', 'htm'),
1360. 'ini' => array('ini', 'desktop'),
1361. 'java' => array('java'),
1362. 'javascript' => array('js'),
1363. 'klonec' => array('kl1'),
1364. 'klonecpp' => array('klx'),
1365. 'latex' => array('tex'),
1366. 'lisp' => array('lisp'),
1367. 'lua' => array('lua'),
1368. 'matlab' => array('m'),
1369. 'mpasm' => array(),
1370. 'mysql' => array('sql'),
1371. 'nsis' => array(),
1372. 'objc' => array(),
1373. 'oobas' => array(),
1374. 'oracle8' => array(),
1375. 'oracle10' => array(),
1376. 'pascal' => array('pas'),
1377. 'perl' => array('pl', 'pm'),
1378. 'php' => array('php', 'php5', 'phtml', 'phps'),
1379. 'povray' => array('pov'),
1380. 'providex' => array('pvc', 'pvx'),
1381. 'prolog' => array('pl'),
1382. 'python' => array('py'),
1383. 'qbasic' => array('bi'),
1384. 'reg' => array('reg'),
1385. 'ruby' => array('rb'),
1386. 'sas' => array('sas'),
1387. 'scala' => array('scala'),
1388. 'scheme' => array('scm'),
1389. 'scilab' => array('sci'),
1390. 'smalltalk' => array('st'),
1391. 'smarty' => array(),
1392. 'tcl' => array('tcl'),
1393. 'vb' => array('bas'),
1394. 'vbnet' => array(),
1395. 'visualfoxpro' => array(),
1396. 'whitespace' => array('ws'),
1397. 'xml' => array('xml', 'svg'),
1398. 'z80' => array('z80', 'asm', 'inc')
1399. );
1400. }
1402. foreach ($lookup as $lang => $extensions) {
1403. if (in_array($extension, $extensions)) {
1404. return $lang;
1405. }
1406. }
1407. return '';
1408. }
1410. /**
1411. * Given a file name, this method loads its contents in, and attempts
1412. * to set the language automatically. An optional lookup table can be
1413. * passed for looking up the language name. If not specified a default
1414. * table is used
1415. *
1416. * The language table is in the form
1417. * <pre>array(
1418. * 'lang_name' => array('extension', 'extension', ...),
1419. * 'lang_name' ...
1420. * );</pre>
1421. *
1422. * @param string The filename to load the source from
1423. * @param array A lookup array to use instead of the default one
1424. * @todo Complete rethink of this and above method
1425. * @since 1.0.5
1426. */
1427. function load_from_file($file_name, $lookup = array()) {
1428. if (is_readable($file_name)) {
1429. $this->set_source(file_get_contents($file_name));
1430. $this->set_language($this->get_language_name_from_extension(substr(strrchr($file_name, '.'), 1), $lookup));
1431. } else {
1432. $this->error = GESHI_ERROR_FILE_NOT_READABLE;
1433. }
1434. }
1436. /**
1437. * Adds a keyword to a keyword group for highlighting
1438. *
1439. * @param int The key of the keyword group to add the keyword to
1440. * @param string The word to add to the keyword group
1441. * @since 1.0.0
1442. */
1443. function add_keyword($key, $word) {
1444. if (!in_array($word, $this->language_data['KEYWORDS'][$key])) {
1445. $this->language_data['KEYWORDS'][$key][] = $word;
1447. //NEW in 1.0.8 don't recompile the whole optimized regexp, simply append it
1448. if ($this->parse_cache_built) {
1449. $subkey = count($this->language_data['CACHED_KEYWORD_LISTS'][$key]) - 1;
1450. $this->language_data['CACHED_KEYWORD_LISTS'][$key][$subkey] .= '|' . preg_quote($word, '/');
1451. }
1452. }
1453. }
1455. /**
1456. * Removes a keyword from a keyword group
1457. *
1458. * @param int The key of the keyword group to remove the keyword from
1459. * @param string The word to remove from the keyword group
1460. * @param bool Wether to automatically recompile the optimized regexp list or not.
1461. * Note: if you set this to false and @see GeSHi->parse_code() was already called once,
1462. * for the current language, you have to manually call @see GeSHi->optimize_keyword_group()
1463. * or the removed keyword will stay in cache and still be highlighted! On the other hand
1464. * it might be too expensive to recompile the regexp list for every removal if you want to
1465. * remove a lot of keywords.
1466. * @since 1.0.0
1467. */
1468. function remove_keyword($key, $word, $recompile = true) {
1469. $key_to_remove = array_search($word, $this->language_data['KEYWORDS'][$key]);
1470. if ($key_to_remove !== false) {
1471. unset($this->language_data['KEYWORDS'][$key][$key_to_remove]);
1473. //NEW in 1.0.8, optionally recompile keyword group
1474. if ($recompile && $this->parse_cache_built) {
1475. $this->optimize_keyword_group($key);
1476. }
1477. }
1478. }
1480. /**
1481. * Creates a new keyword group
1482. *
1483. * @param int The key of the keyword group to create
1484. * @param string The styles for the keyword group
1485. * @param boolean Whether the keyword group is case sensitive ornot
1486. * @param array The words to use for the keyword group
1487. * @since 1.0.0
1488. */
1489. function add_keyword_group($key, $styles, $case_sensitive = true, $words = array()) {
1490. $words = (array) $words;
1491. if (empty($words)) {
1492. // empty word lists mess up highlighting
1493. return false;
1494. }
1496. //Add the new keyword group internally
1497. $this->language_data['KEYWORDS'][$key] = $words;
1498. $this->lexic_permissions['KEYWORDS'][$key] = true;
1499. $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive;
1500. $this->language_data['STYLES']['KEYWORDS'][$key] = $styles;
1502. //NEW in 1.0.8, cache keyword regexp
1503. if ($this->parse_cache_built) {
1504. $this->optimize_keyword_group($key);
1505. }
1506. }
1508. /**
1509. * Removes a keyword group
1510. *
1511. * @param int The key of the keyword group to remove
1512. * @since 1.0.0
1513. */
1514. function remove_keyword_group ($key) {
1515. //Remove the keyword group internally
1516. unset($this->language_data['KEYWORDS'][$key]);
1517. unset($this->lexic_permissions['KEYWORDS'][$key]);
1518. unset($this->language_data['CASE_SENSITIVE'][$key]);
1519. unset($this->language_data['STYLES']['KEYWORDS'][$key]);
1521. //NEW in 1.0.8
1522. unset($this->language_data['CACHED_KEYWORD_LISTS'][$key]);
1523. }
1525. /**
1526. * compile optimized regexp list for keyword group
1527. *
1528. * @param int The key of the keyword group to compile & optimize
1529. * @since 1.0.8
1530. */
1531. function optimize_keyword_group($key) {
1532. $this->language_data['CACHED_KEYWORD_LISTS'][$key] =
1533. $this->optimize_regexp_list($this->language_data['KEYWORDS'][$key]);
1534. }
1536. /**
1537. * Sets the content of the header block
1538. *
1539. * @param string The content of the header block
1540. * @since 1.0.2
1541. */
1542. function set_header_content($content) {
1543. $this->header_content = $content;
1544. }
1546. /**
1547. * Sets the content of the footer block
1548. *
1549. * @param string The content of the footer block
1550. * @since 1.0.2
1551. */
1552. function set_footer_content($content) {
1553. $this->footer_content = $content;
1554. }
1556. /**
1557. * Sets the style for the header content
1558. *
1559. * @param string The style for the header content
1560. * @since 1.0.2
1561. */
1562. function set_header_content_style($style) {
1563. $this->header_content_style = $style;
1564. }
1566. /**
1567. * Sets the style for the footer content
1568. *
1569. * @param string The style for the footer content
1570. * @since 1.0.2
1571. */
1572. function set_footer_content_style($style) {
1573. $this->footer_content_style = $style;
1574. }
1576. /**
1577. * Sets whether to force a surrounding block around
1578. * the highlighted code or not
1579. *
1580. * @param boolean Tells whether to enable or disable this feature
1581. * @since 1.0.7.20
1582. */
1583. function enable_inner_code_block($flag) {
1584. $this->force_code_block = (bool)$flag;
1585. }
1587. /**
1588. * Sets the base URL to be used for keywords
1589. *
1590. * @param int The key of the keyword group to set the URL for
1591. * @param string The URL to set for the group. If {FNAME} is in
1592. * the url somewhere, it is replaced by the keyword
1593. * that the URL is being made for
1594. * @since 1.0.2
1595. */
1596. function set_url_for_keyword_group($group, $url) {
1597. $this->language_data['URLS'][$group] = $url;
1598. }
1600. /**
1601. * Sets styles for links in code
1602. *
1603. * @param int A constant that specifies what state the style is being
1604. * set for - e.g. :hover or :visited
1605. * @param string The styles to use for that state
1606. * @since 1.0.2
1607. */
1608. function set_link_styles($type, $styles) {
1609. $this->link_styles[$type] = $styles;
1610. }
1612. /**
1613. * Sets the target for links in code
1614. *
1615. * @param string The target for links in the code, e.g. _blank
1616. * @since 1.0.3
1617. */
1618. function set_link_target($target) {
1619. if (!$target) {
1620. $this->link_target = '';
1621. } else {
1622. $this->link_target = ' target="' . $target . '" ';
1623. }
1624. }
1626. /**
1627. * Sets styles for important parts of the code
1628. *
1629. * @param string The styles to use on important parts of the code
1630. * @since 1.0.2
1631. */
1632. function set_important_styles($styles) {
1633. $this->important_styles = $styles;
1634. }
1636. /**
1637. * Sets whether context-important blocks are highlighted
1638. *
1639. * @param boolean Tells whether to enable or disable highlighting of important blocks
1640. * @todo REMOVE THIS SHIZ FROM GESHI!
1641. * @deprecated
1642. * @since 1.0.2
1643. */
1644. function enable_important_blocks($flag) {
1645. $this->enable_important_blocks = ( $flag ) ? true : false;
1646. }
1648. /**
1649. * Whether CSS IDs should be added to each line
1650. *
1651. * @param boolean If true, IDs will be added to each line.
1652. * @since 1.0.2
1653. */
1654. function enable_ids($flag = true) {
1655. $this->add_ids = ($flag) ? true : false;
1656. }
1658. /**
1659. * Specifies which lines to highlight extra
1660. *
1661. * The extra style parameter was added in 1.0.7.21.
1662. *
1663. * @param mixed An array of line numbers to highlight, or just a line
1664. * number on its own.
1665. * @param string A string specifying the style to use for this line.
1666. * If null is specified, the default style is used.
1667. * If false is specified, the line will be removed from
1668. * special highlighting
1669. * @since 1.0.2
1670. * @todo Some data replication here that could be cut down on
1671. */
1672. function highlight_lines_extra($lines, $style = null) {
1673. if (is_array($lines)) {
1674. //Split up the job using single lines at a time
1675. foreach ($lines as $line) {
1676. $this->highlight_lines_extra($line, $style);
1677. }
1678. } else {
1679. //Mark the line as being highlighted specially
1680. $lines = intval($lines);
1681. $this->highlight_extra_lines[$lines] = $lines;
1683. //Decide on which style to use
1684. if ($style === null) { //Check if we should use default style
1685. unset($this->highlight_extra_lines_styles[$lines]);
1686. } else if ($style === false) { //Check if to remove this line
1687. unset($this->highlight_extra_lines[$lines]);
1688. unset($this->highlight_extra_lines_styles[$lines]);
1689. } else {
1690. $this->highlight_extra_lines_styles[$lines] = $style;
1691. }
1692. }
1693. }
1695. /**
1696. * Sets the style for extra-highlighted lines
1697. *
1698. * @param string The style for extra-highlighted lines
1699. * @since 1.0.2
1700. */
1701. function set_highlight_lines_extra_style($styles) {
1702. $this->highlight_extra_lines_style = $styles;
1703. }
1705. /**
1706. * Sets the line-ending
1707. *
1708. * @param string The new line-ending
1709. * @since 1.0.2
1710. */
1711. function set_line_ending($line_ending) {
1712. $this->line_ending = (string)$line_ending;
1713. }
1715. /**
1716. * Sets what number line numbers should start at. Should
1717. * be a positive integer, and will be converted to one.
1718. *
1719. * <b>Warning:</b> Using this method will add the "start"
1720. * attribute to the &lt;ol&gt; that is used for line numbering.
1721. * This is <b>not</b> valid XHTML strict, so if that's what you
1722. * care about then don't use this method. Firefox is getting
1723. * support for the CSS method of doing this in 1.1 and Opera
1724. * has support for the CSS method, but (of course) IE doesn't
1725. * so it's not worth doing it the CSS way yet.
1726. *
1727. * @param int The number to start line numbers at
1728. * @since 1.0.2
1729. */
1730. function start_line_numbers_at($number) {
1731. $this->line_numbers_start = abs(intval($number));
1732. }
1734. /**
1735. * Sets the encoding used for htmlspecialchars(), for international
1736. * support.
1737. *
1738. * NOTE: This is not needed for now because htmlspecialchars() is not
1739. * being used (it has a security hole in PHP4 that has not been patched).
1740. * Maybe in a future version it may make a return for speed reasons, but
1741. * I doubt it.
1742. *
1743. * @param string The encoding to use for the source
1744. * @since 1.0.3
1745. */
1746. function set_encoding($encoding) {
1747. if ($encoding) {
1748. $this->encoding = strtolower($encoding);
1749. }
1750. }
1752. /**
1753. * Turns linking of keywords on or off.
1754. *
1755. * @param boolean If true, links will be added to keywords
1756. * @since 1.0.2
1757. */
1758. function enable_keyword_links($enable = true) {
1759. $this->keyword_links = (bool) $enable;
1760. }
1762. /**
1763. * Setup caches needed for styling. This is automatically called in
1764. * parse_code() and get_stylesheet() when appropriate. This function helps
1765. * stylesheet generators as they rely on some style information being
1766. * preprocessed
1767. *
1768. * @since 1.0.8
1769. * @access private
1770. */
1771. function build_style_cache() {
1772. //Build the style cache needed to highlight numbers appropriate
1773. if($this->lexic_permissions['NUMBERS']) {
1774. //First check what way highlighting information for numbers are given
1775. if(!isset($this->language_data['NUMBERS'])) {
1776. $this->language_data['NUMBERS'] = 0;
1777. }
1779. if(is_array($this->language_data['NUMBERS'])) {
1780. $this->language_data['NUMBERS_CACHE'] = $this->language_data['NUMBERS'];
1781. } else {
1782. $this->language_data['NUMBERS_CACHE'] = array();
1783. if(!$this->language_data['NUMBERS']) {
1784. $this->language_data['NUMBERS'] =
1785. GESHI_NUMBER_INT_BASIC |
1786. GESHI_NUMBER_FLT_NONSCI;
1787. }
1789. for($i = 0, $j = $this->language_data['NUMBERS']; $j > 0; ++$i, $j>>=1) {
1790. //Rearrange style indices if required ...
1791. if(isset($this->language_data['STYLES']['NUMBERS'][1<<$i])) {
1792. $this->language_data['STYLES']['NUMBERS'][$i] =
1793. $this->language_data['STYLES']['NUMBERS'][1<<$i];
1794. unset($this->language_data['STYLES']['NUMBERS'][1<<$i]);
1795. }
1797. //Check if this bit is set for highlighting
1798. if($j&1) {
1799. //So this bit is set ...
1800. //Check if it belongs to group 0 or the actual stylegroup
1801. if(isset($this->language_data['STYLES']['NUMBERS'][$i])) {
1802. $this->language_data['NUMBERS_CACHE'][$i] = 1 << $i;
1803. } else {
1804. if(!isset($this->language_data['NUMBERS_CACHE'][0])) {
1805. $this->language_data['NUMBERS_CACHE'][0] = 0;
1806. }
1807. $this->language_data['NUMBERS_CACHE'][0] |= 1 << $i;
1808. }
1809. }
1810. }
1811. }
1812. }
1813. }
1815. /**
1816. * Setup caches needed for parsing. This is automatically called in parse_code() when appropriate.
1817. * This function makes stylesheet generators much faster as they do not need these caches.
1818. *
1819. * @since 1.0.8
1820. * @access private
1821. */
1822. function build_parse_cache() {
1823. // cache symbol regexp
1824. //As this is a costy operation, we avoid doing it for multiple groups ...
1825. //Instead we perform it for all symbols at once.
1826. //
1827. //For this to work, we need to reorganize the data arrays.
1828. if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
1829. $this->language_data['MULTIPLE_SYMBOL_GROUPS'] = count($this->language_data['STYLES']['SYMBOLS']) > 1;
1831. $this->language_data['SYMBOL_DATA'] = array();
1832. $symbol_preg_multi = array(); // multi char symbols
1833. $symbol_preg_single = array(); // single char symbols
1834. foreach ($this->language_data['SYMBOLS'] as $key => $symbols) {
1835. if (is_array($symbols)) {
1836. foreach ($symbols as $sym) {
1837. $sym = $this->hsc($sym);
1838. if (!isset($this->language_data['SYMBOL_DATA'][$sym])) {
1839. $this->language_data['SYMBOL_DATA'][$sym] = $key;
1840. if (isset($sym[1])) { // multiple chars
1841. $symbol_preg_multi[] = preg_quote($sym, '/');
1842. } else { // single char
1843. if ($sym == '-') {
1844. // don't trigger range out of order error
1845. $symbol_preg_single[] = '\-';
1846. } else {
1847. $symbol_preg_single[] = preg_quote($sym, '/');
1848. }
1849. }
1850. }
1851. }
1852. } else {
1853. $symbols = $this->hsc($symbols);
1854. if (!isset($this->language_data['SYMBOL_DATA'][$symbols])) {
1855. $this->language_data['SYMBOL_DATA'][$symbols] = 0;
1856. if (isset($symbols[1])) { // multiple chars
1857. $symbol_preg_multi[] = preg_quote($symbols, '/');
1858. } else if ($symbols == '-') {
1859. // don't trigger range out of order error
1860. $symbol_preg_single[] = '\-';
1861. } else { // single char
1862. $symbol_preg_single[] = preg_quote($symbols, '/');
1863. }
1864. }
1865. }
1866. }
1868. //Now we have an array with each possible symbol as the key and the style as the actual data.
1869. //This way we can set the correct style just the moment we highlight ...
1870. //
1871. //Now we need to rewrite our array to get a search string that
1872. $symbol_preg = array();
1873. if (!empty($symbol_preg_multi)) {
1874. rsort($symbol_preg_multi);
1875. $symbol_preg[] = implode('|', $symbol_preg_multi);
1876. }
1877. if (!empty($symbol_preg_single)) {
1878. rsort($symbol_preg_single);
1879. $symbol_preg[] = '[' . implode('', $symbol_preg_single) . ']';
1880. }
1881. $this->language_data['SYMBOL_SEARCH'] = implode("|", $symbol_preg);
1882. }
1884. // cache optimized regexp for keyword matching
1885. // remove old cache
1886. $this->language_data['CACHED_KEYWORD_LISTS'] = array();
1887. foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
1888. if (!isset($this->lexic_permissions['KEYWORDS'][$key]) ||
1889. $this->lexic_permissions['KEYWORDS'][$key]) {
1890. $this->optimize_keyword_group($key);
1891. }
1892. }
1894. // brackets
1895. if ($this->lexic_permissions['BRACKETS']) {
1896. $this->language_data['CACHE_BRACKET_MATCH'] = array('[', ']', '(', ')', '{', '}');
1897. if (!$this->use_classes && isset($this->language_data['STYLES']['BRACKETS'][0])) {
1898. $this->language_data['CACHE_BRACKET_REPLACE'] = array(
1899. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#91;|>',
1900. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#93;|>',
1901. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#40;|>',
1902. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#41;|>',
1903. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#123;|>',
1904. '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">&#125;|>',
1905. );
1906. }
1907. else {
1908. $this->language_data['CACHE_BRACKET_REPLACE'] = array(
1909. '<| class="br0">&#91;|>',
1910. '<| class="br0">&#93;|>',
1911. '<| class="br0">&#40;|>',
1912. '<| class="br0">&#41;|>',
1913. '<| class="br0">&#123;|>',
1914. '<| class="br0">&#125;|>',
1915. );
1916. }
1917. }
1919. //Build the parse cache needed to highlight numbers appropriate
1920. if($this->lexic_permissions['NUMBERS']) {
1921. //Check if the style rearrangements have been processed ...
1922. //This also does some preprocessing to check which style groups are useable ...
1923. if(!isset($this->language_data['NUMBERS_CACHE'])) {
1924. $this->build_style_cache();
1925. }
1927. //Number format specification
1928. //All this formats are matched case-insensitively!
1929. static $numbers_format = array(
1930. GESHI_NUMBER_INT_BASIC =>
1931. '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])([1-9]\d*?|0)(?![0-9a-z\.])',
1932. GESHI_NUMBER_INT_CSTYLE =>
1933. '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])([1-9]\d*?|0)l(?![0-9a-z\.])',
1934. GESHI_NUMBER_BIN_SUFFIX =>
1935. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[01]+?b(?![0-9a-z\.])',
1936. GESHI_NUMBER_BIN_PREFIX_PERCENT =>
1937. '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])%[01]+?(?![0-9a-z\.])',
1938. GESHI_NUMBER_BIN_PREFIX_0B =>
1939. '(?<![0-9a-z_\.%])(?<![\d\.]e[+\-])0b[01]+?(?![0-9a-z\.])',
1940. GESHI_NUMBER_OCT_PREFIX =>
1941. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0[0-7]+?(?![0-9a-z\.])',
1942. GESHI_NUMBER_OCT_SUFFIX =>
1943. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])[0-7]+?o(?![0-9a-z\.])',
1944. GESHI_NUMBER_HEX_PREFIX =>
1945. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])0x[0-9a-f]+?(?![0-9a-z\.])',
1946. GESHI_NUMBER_HEX_SUFFIX =>
1947. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d[0-9a-f]*?h(?![0-9a-z\.])',
1948. GESHI_NUMBER_FLT_NONSCI =>
1949. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\d+?\.\d+?(?![0-9a-z\.])',
1950. GESHI_NUMBER_FLT_NONSCI_F =>
1951. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)f(?![0-9a-z\.])',
1952. GESHI_NUMBER_FLT_SCI_SHORT =>
1953. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])\.\d+?(?:e[+\-]?\d+?)?(?![0-9a-z\.])',
1954. GESHI_NUMBER_FLT_SCI_ZERO =>
1955. '(?<![0-9a-z_\.])(?<![\d\.]e[+\-])(?:\d+?(?:\.\d*?)?|\.\d+?)(?:e[+\-]?\d+?)?(?![0-9a-z\.])'
1956. );
1958. //At this step we have an associative array with flag groups for a
1959. //specific style or an string denoting a regexp given its index.
1960. $this->language_data['NUMBERS_RXCACHE'] = array();
1961. foreach($this->language_data['NUMBERS_CACHE'] as $key => $rxdata) {
1962. if(is_string($rxdata)) {
1963. $regexp = $rxdata;
1964. } else {
1965. //This is a bitfield of number flags to highlight:
1966. //Build an array, implode them together and make this the actual RX
1967. $rxuse = array();
1968. for($i = 1; $i <= $rxdata; $i<<=1) {
1969. if($rxdata & $i) {
1970. $rxuse[] = $numbers_format[$i];
1971. }
1972. }
1973. $regexp = implode("|", $rxuse);
1974. }
1976. $this->language_data['NUMBERS_RXCACHE'][$key] =
1977. "/(?<!<\|\/NUM!)(?<!\d\/>)($regexp)(?!\|>)/i";
1978. }
1979. }
1981. $this->parse_cache_built = true;
1982. }
1984. /**
1985. * Returns the code in $this->source, highlighted and surrounded by the
1986. * nessecary HTML.
1987. *
1988. * This should only be called ONCE, cos it's SLOW! If you want to highlight
1989. * the same source multiple times, you're better off doing a whole lot of
1990. * str_replaces to replace the &lt;span&gt;s
1991. *
1992. * @since 1.0.0
1993. */
1994. function parse_code () {
1995. // Start the timer
1996. $start_time = microtime();
1998. // Firstly, if there is an error, we won't highlight
1999. if ($this->error) {
2000. //Escape the source for output
2001. $result = $this->hsc($this->source);
2003. //This fix is related to SF#1923020, but has to be applied regardless of
2004. //actually highlighting symbols.
2005. $result = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $result);
2007. // Timing is irrelevant
2008. $this->set_time($start_time, $start_time);
2009. $this->finalise($result);
2010. return $result;
2011. }
2013. // make sure the parse cache is up2date
2014. if (!$this->parse_cache_built) {
2015. $this->build_parse_cache();
2016. }
2018. // Replace all newlines to a common form.
2019. $code = str_replace("\r\n", "\n", $this->source);
2020. $code = str_replace("\r", "\n", $code);
2022. // Add spaces for regular expression matching and line numbers
2023. // $code = "\n" . $code . "\n";
2025. // Initialise various stuff
2026. $length = strlen($code);
2027. $COMMENT_MATCHED = false;
2028. $stuff_to_parse = '';
2029. $endresult = '';
2031. // "Important" selections are handled like multiline comments
2032. // @todo GET RID OF THIS SHIZ
2033. if ($this->enable_important_blocks) {
2034. $this->language_data['COMMENT_MULTI'][<a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>] = <a href="../geshi/core/_geshi.php.html#defineGESHI_END_IMPORTANT">GESHI_END_IMPORTANT</a>;
2035. }
2037. if ($this->strict_mode) {
2038. // Break the source into bits. Each bit will be a portion of the code
2039. // within script delimiters - for example, HTML between < and >
2040. $k = 0;
2041. $parts = array();
2042. $matches = array();
2043. $next_match_pointer = null;
2044. // we use a copy to unset delimiters on demand (when they are not found)
2045. $delim_copy = $this->language_data['SCRIPT_DELIMITERS'];
2046. $i = 0;
2047. while ($i < $length) {
2048. $next_match_pos = $length + 1; // never true
2049. foreach ($delim_copy as $dk => $delimiters) {
2050. if(is_array($delimiters)) {
2051. foreach ($delimiters as $open => $close) {
2052. // make sure the cache is setup properly
2053. if (!isset($matches[$dk][$open])) {
2054. $matches[$dk][$open] = array(
2055. 'next_match' => -1,
2056. 'dk' => $dk,
2058. 'open' => $open, // needed for grouping of adjacent code blocks (see below)
2059. 'open_strlen' => strlen($open),
2061. 'close' => $close,
2062. 'close_strlen' => strlen($close),
2063. );
2064. }
2065. // Get the next little bit for this opening string
2066. if ($matches[$dk][$open]['next_match'] < $i) {
2067. // only find the next pos if it was not already cached
2068. $open_pos = strpos($code, $open, $i);
2069. if ($open_pos === false) {
2070. // no match for this delimiter ever
2071. unset($delim_copy[$dk][$open]);
2072. continue;
2073. }
2074. $matches[$dk][$open]['next_match'] = $open_pos;
2075. }
2076. if ($matches[$dk][$open]['next_match'] < $next_match_pos) {
2077. //So we got a new match, update the close_pos
2078. $matches[$dk][$open]['close_pos'] =
2079. strpos($code, $close, $matches[$dk][$open]['next_match']+1);
2081. $next_match_pointer =& $matches[$dk][$open];
2082. $next_match_pos = $matches[$dk][$open]['next_match'];
2083. }
2084. }
2085. } else {
2086. //So we should match an RegExp as Strict Block ...
2087. /**
2088. * The value in $delimiters is expected to be an RegExp
2089. * containing exactly 2 matching groups:
2090. * - Group 1 is the opener
2091. * - Group 2 is the closer
2092. */
2093. if(!GESHI_PHP_PRE_433 && //Needs proper rewrite to work with PHP >=4.3.0; 4.3.3 is guaranteed to work.
2094. preg_match($delimiters, $code, $matches_rx, PREG_OFFSET_CAPTURE, $i)) {
2095. //We got a match ...
2096. $matches[$dk] = array(
2097. 'next_match' => $matches_rx[1][1],
2098. 'dk' => $dk,
2100. 'close_strlen' => strlen($matches_rx[2][0]),
2101. 'close_pos' => $matches_rx[2][1],
2102. );
2103. } else {
2104. // no match for this delimiter ever
2105. unset($delim_copy[$dk]);
2106. continue;
2107. }
2109. if ($matches[$dk]['next_match'] <= $next_match_pos) {
2110. $next_match_pointer =& $matches[$dk];
2111. $next_match_pos = $matches[$dk]['next_match'];
2112. }
2113. }
2114. }
2115. // non-highlightable text
2116. $parts[$k] = array(
2117. 1 => substr($code, $i, $next_match_pos - $i)
2118. );
2119. ++$k;
2121. if ($next_match_pos > $length) {
2122. // out of bounds means no next match was found
2123. break;
2124. }
2126. // highlightable code
2127. $parts[$k][0] = $next_match_pointer['dk'];
2129. //Only combine for non-rx script blocks
2130. if(is_array($delim_copy[$next_match_pointer['dk']])) {
2131. // group adjacent script blocks, e.g. <foobar><asdf> should be one block, not three!
2132. $i = $next_match_pos + $next_match_pointer['open_strlen'];
2133. while (true) {
2134. $close_pos = strpos($code, $next_match_pointer['close'], $i);
2135. if ($close_pos == false) {
2136. break;
2137. }
2138. $i = $close_pos + $next_match_pointer['close_strlen'];
2139. if ($i == $length) {
2140. break;
2141. }
2142. if ($code[$i] == $next_match_pointer['open'][0] && ($next_match_pointer['open_strlen'] == 1 ||
2143. substr($code, $i, $next_match_pointer['open_strlen']) == $next_match_pointer['open'])) {
2144. // merge adjacent but make sure we don't merge things like <tag><!-- comment -->
2145. foreach ($matches as $submatches) {
2146. foreach ($submatches as $match) {
2147. if ($match['next_match'] == $i) {
2148. // a different block already matches here!
2149. break 3;
2150. }
2151. }
2152. }
2153. } else {
2154. break;
2155. }
2156. }
2157. } else {
2158. $close_pos = $next_match_pointer['close_pos'] + $next_match_pointer['close_strlen'];
2159. $i = $close_pos;
2160. }
2162. if ($close_pos === false) {
2163. // no closing delimiter found!
2164. $parts[$k][1] = substr($code, $next_match_pos);
2165. ++$k;
2166. break;
2167. } else {
2168. $parts[$k][1] = substr($code, $next_match_pos, $i - $next_match_pos);
2169. ++$k;
2170. }
2171. }
2172. unset($delim_copy, $next_match_pointer, $next_match_pos, $matches);
2173. $num_parts = $k;
2175. if ($num_parts == 1 && $this->strict_mode == GESHI_MAYBE) {
2176. // when we have only one part, we don't have anything to highlight at all.
2177. // if we have a "maybe" strict language, this should be handled as highlightable code
2178. $parts = array(
2179. 0 => array(
2180. 0 => '',
2181. 1 => ''
2182. ),
2183. 1 => array(
2184. 0 => null,
2185. 1 => $parts[0][1]
2186. )
2187. );
2188. $num_parts = 2;
2189. }
2191. } else {
2192. // Not strict mode - simply dump the source into
2193. // the array at index 1 (the first highlightable block)
2194. $parts = array(
2195. 0 => array(
2196. 0 => '',
2197. 1 => ''
2198. ),
2199. 1 => array(
2200. 0 => null,
2201. 1 => $code
2202. )
2203. );
2204. $num_parts = 2;
2205. }
2207. //Unset variables we won't need any longer
2208. unset($code);
2210. //Preload some repeatedly used values regarding hardquotes ...
2211. $hq = isset($this->language_data['HARDQUOTE']) ? $this->language_data['HARDQUOTE'][0] : false;
2212. $hq_strlen = strlen($hq);
2214. //Preload if line numbers are to be generated afterwards
2215. //Added a check if line breaks should be forced even without line numbers, fixes SF#1727398
2216. $check_linenumbers = $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> ||
2217. !empty($this->highlight_extra_lines) || !$this->allow_multiline_span;
2219. //preload the escape char for faster checking ...
2220. $escaped_escape_char = $this->hsc($this->language_data['ESCAPE_CHAR']);
2222. // this is used for single-line comments
2223. $sc_disallowed_before = "";
2224. $sc_disallowed_after = "";
2226. if (isset($this->language_data['PARSER_CONTROL'])) {
2227. if (isset($this->language_data['PARSER_CONTROL']['COMMENTS'])) {
2228. if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'])) {
2229. $sc_disallowed_before = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_BEFORE'];
2230. }
2231. if (isset($this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'])) {
2232. $sc_disallowed_after = $this->language_data['PARSER_CONTROL']['COMMENTS']['DISALLOWED_AFTER'];
2233. }
2234. }
2235. }
2237. //Fix for SF#1932083: Multichar Quotemarks unsupported
2238. $is_string_starter = array();
2239. if ($this->lexic_permissions['STRINGS']) {
2240. foreach ($this->language_data['QUOTEMARKS'] as $quotemark) {
2241. if (!isset($is_string_starter[$quotemark[0]])) {
2242. $is_string_starter[$quotemark[0]] = (string)$quotemark;
2243. } else if (is_string($is_string_starter[$quotemark[0]])) {
2244. $is_string_starter[$quotemark[0]] = array(
2245. $is_string_starter[$quotemark[0]],
2246. $quotemark);
2247. } else {
2248. $is_string_starter[$quotemark[0]][] = $quotemark;
2249. }
2250. }
2251. }
2253. // Now we go through each part. We know that even-indexed parts are
2254. // code that shouldn't be highlighted, and odd-indexed parts should
2255. // be highlighted
2256. for ($key = 0; $key < $num_parts; ++$key) {
2257. $STRICTATTRS = '';
2259. // If this block should be highlighted...
2260. if (!($key & 1)) {
2261. // Else not a block to highlight
2262. $endresult .= $this->hsc($parts[$key][1]);
2263. unset($parts[$key]);
2264. continue;
2265. }
2267. $result = '';
2268. $part = $parts[$key][1];
2270. $highlight_part = true;
2271. if ($this->strict_mode && !is_null($parts[$key][0])) {
2272. // get the class key for this block of code
2273. $script_key = $parts[$key][0];
2274. $highlight_part = $this->language_data['HIGHLIGHT_STRICT_BLOCK'][$script_key];
2275. if ($this->language_data['STYLES']['SCRIPT'][$script_key] != '' &&
2276. $this->lexic_permissions['SCRIPT']) {
2277. // Add a span element around the source to
2278. // highlight the overall source block
2279. if (!$this->use_classes &&
2280. $this->language_data['STYLES']['SCRIPT'][$script_key] != '') {
2281. $attributes = ' style="' . $this->language_data['STYLES']['SCRIPT'][$script_key] . '"';
2282. } else {
2283. $attributes = ' class="sc' . $script_key . '"';
2284. }
2285. $result .= "<span$attributes>";
2286. $STRICTATTRS = $attributes;
2287. }
2288. }
2290. if ($highlight_part) {
2291. // Now, highlight the code in this block. This code
2292. // is really the engine of GeSHi (along with the method
2293. // parse_non_string_part).
2295. // cache comment regexps incrementally
2296. $next_comment_regexp_key = '';
2297. $next_comment_regexp_pos = -1;
2298. $next_comment_multi_pos = -1;
2299. $next_comment_single_pos = -1;
2300. $comment_regexp_cache_per_key = array();
2301. $comment_multi_cache_per_key = array();
2302. $comment_single_cache_per_key = array();
2303. $next_open_comment_multi = '';
2304. $next_comment_single_key = '';
2305. $escape_regexp_cache_per_key = array();
2306. $next_escape_regexp_key = '';
2307. $next_escape_regexp_pos = -1;
2309. $length = strlen($part);
2310. for ($i = 0; $i < $length; ++$i) {
2311. // Get the next char
2312. $char = $part[$i];
2313. $char_len = 1;
2315. // update regexp comment cache if needed
2316. if (isset($this->language_data['COMMENT_REGEXP']) && $next_comment_regexp_pos < $i) {
2317. $next_comment_regexp_pos = $length;
2318. foreach ($this->language_data['COMMENT_REGEXP'] as $comment_key => $regexp) {
2319. $match_i = false;
2320. if (isset($comment_regexp_cache_per_key[$comment_key]) &&
2321. ($comment_regexp_cache_per_key[$comment_key]['pos'] >= $i ||
2322. $comment_regexp_cache_per_key[$comment_key]['pos'] === false)) {
2323. // we have already matched something
2324. if ($comment_regexp_cache_per_key[$comment_key]['pos'] === false) {
2325. // this comment is never matched
2326. continue;
2327. }
2328. $match_i = $comment_regexp_cache_per_key[$comment_key]['pos'];
2329. } else if (
2330. //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
2331. (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $i), $match, PREG_OFFSET_CAPTURE)) ||
2332. (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $i))
2333. ) {
2334. $match_i = $match[0][1];
2335. if (GESHI_PHP_PRE_433) {
2336. $match_i += $i;
2337. }
2339. $comment_regexp_cache_per_key[$comment_key] = array(
2340. 'key' => $comment_key,
2341. 'length' => strlen($match[0][0]),
2342. 'pos' => $match_i
2343. );
2344. } else {
2345. $comment_regexp_cache_per_key[$comment_key]['pos'] = false;
2346. continue;
2347. }
2349. if ($match_i !== false && $match_i < $next_comment_regexp_pos) {
2350. $next_comment_regexp_pos = $match_i;
2351. $next_comment_regexp_key = $comment_key;
2352. if ($match_i === $i) {
2353. break;
2354. }
2355. }
2356. }
2357. }
2359. $string_started = false;
2361. if (isset($is_string_starter[$char])) {
2362. // Possibly the start of a new string ...
2364. //Check which starter it was ...
2365. //Fix for SF#1932083: Multichar Quotemarks unsupported
2366. if (is_array($is_string_starter[$char])) {
2367. $char_new = '';
2368. foreach ($is_string_starter[$char] as $testchar) {
2369. if ($testchar === substr($part, $i, strlen($testchar)) &&
2370. strlen($testchar) > strlen($char_new)) {
2371. $char_new = $testchar;
2372. $string_started = true;
2373. }
2374. }
2375. if ($string_started) {
2376. $char = $char_new;
2377. }
2378. } else {
2379. $testchar = $is_string_starter[$char];
2380. if ($testchar === substr($part, $i, strlen($testchar))) {
2381. $char = $testchar;
2382. $string_started = true;
2383. }
2384. }
2385. $char_len = strlen($char);
2386. }
2388. if ($string_started && $i != $next_comment_regexp_pos) {
2389. // Hand out the correct style information for this string
2390. $string_key = array_search($char, $this->language_data['QUOTEMARKS']);
2391. if (!isset($this->language_data['STYLES']['STRINGS'][$string_key]) ||
2392. !isset($this->language_data['STYLES']['ESCAPE_CHAR'][$string_key])) {
2393. $string_key = 0;
2394. }
2396. // parse the stuff before this
2397. $result .= $this->parse_non_string_part($stuff_to_parse);
2398. $stuff_to_parse = '';
2400. if (!$this->use_classes) {
2401. $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][$string_key] . '"';
2402. } else {
2403. $string_attributes = ' class="st'.$string_key.'"';
2404. }
2406. // now handle the string
2407. $string = "<span$string_attributes>" . GeSHi::hsc($char);
2408. $start = $i + $char_len;
2409. $string_open = true;
2411. if(empty($this->language_data['ESCAPE_REGEXP'])) {
2412. $next_escape_regexp_pos = $length;
2413. }
2415. do {
2416. //Get the regular ending pos ...
2417. $close_pos = strpos($part, $char, $start);
2418. if(false === $close_pos) {
2419. $close_pos = $length;
2420. }
2422. if($this->lexic_permissions['ESCAPE_CHAR']) {
2423. // update escape regexp cache if needed
2424. if (isset($this->language_data['ESCAPE_REGEXP']) && $next_escape_regexp_pos < $start) {
2425. $next_escape_regexp_pos = $length;
2426. foreach ($this->language_data['ESCAPE_REGEXP'] as $escape_key => $regexp) {
2427. $match_i = false;
2428. if (isset($escape_regexp_cache_per_key[$escape_key]) &&
2429. ($escape_regexp_cache_per_key[$escape_key]['pos'] >= $start ||
2430. $escape_regexp_cache_per_key[$escape_key]['pos'] === false)) {
2431. // we have already matched something
2432. if ($escape_regexp_cache_per_key[$escape_key]['pos'] === false) {
2433. // this comment is never matched
2434. continue;
2435. }
2436. $match_i = $escape_regexp_cache_per_key[$escape_key]['pos'];
2437. } else if (
2438. //This is to allow use of the offset parameter in preg_match and stay as compatible with older PHP versions as possible
2439. (GESHI_PHP_PRE_433 && preg_match($regexp, substr($part, $start), $match, PREG_OFFSET_CAPTURE)) ||
2440. (!GESHI_PHP_PRE_433 && preg_match($regexp, $part, $match, PREG_OFFSET_CAPTURE, $start))
2441. ) {
2442. $match_i = $match[0][1];
2443. if (GESHI_PHP_PRE_433) {
2444. $match_i += $start;
2445. }
2447. $escape_regexp_cache_per_key[$escape_key] = array(
2448. 'key' => $escape_key,
2449. 'length' => strlen($match[0][0]),
2450. 'pos' => $match_i
2451. );
2452. } else {
2453. $escape_regexp_cache_per_key[$escape_key]['pos'] = false;
2454. continue;
2455. }
2457. if ($match_i !== false && $match_i < $next_escape_regexp_pos) {
2458. $next_escape_regexp_pos = $match_i;
2459. $next_escape_regexp_key = $escape_key;
2460. if ($match_i === $start) {
2461. break;
2462. }
2463. }
2464. }
2465. }
2467. //Find the next simple escape position
2468. if('' != $this->language_data['ESCAPE_CHAR']) {
2469. $simple_escape = strpos($part, $this->language_data['ESCAPE_CHAR'], $start);
2470. if(false === $simple_escape) {
2471. $simple_escape = $length;
2472. }
2473. } else {
2474. $simple_escape = $length;
2475. }
2476. } else {
2477. $next_escape_regexp_pos = $length;
2478. $simple_escape = $length;
2479. }
2481. if($simple_escape < $next_escape_regexp_pos &&
2482. $simple_escape < $length &&
2483. $simple_escape < $close_pos) {
2484. //The nexxt escape sequence is a simple one ...
2485. $es_pos = $simple_escape;
2487. //Add the stuff not in the string yet ...
2488. $string .= $this->hsc(substr($part, $start, $es_pos - $start));
2490. //Get the style for this escaped char ...
2491. if (!$this->use_classes) {
2492. $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][0] . '"';
2493. } else {
2494. $escape_char_attributes = ' class="es0"';
2495. }
2497. //Add the style for the escape char ...
2498. $string .= "<span$escape_char_attributes>" .
2499. GeSHi::hsc($this->language_data['ESCAPE_CHAR']);
2501. //Get the byte AFTER the ESCAPE_CHAR we just found
2502. $es_char = $part[$es_pos + 1];
2503. if ($es_char == "\n") {
2504. // don't put a newline around newlines
2505. $string .= "</span>\n";
2506. $start = $es_pos + 2;
2507. } else if (ord($es_char) >= 128) {
2508. //This is an non-ASCII char (UTF8 or single byte)
2509. //This code tries to work around SF#2037598 ...
2510. if(function_exists('mb_substr')) {
2511. $es_char_m = mb_substr(substr($part, $es_pos+1, 16), 0, 1, $this->encoding);
2512. $string .= $es_char_m . '</span>';
2513. } else if (!GESHI_PHP_PRE_433 && 'utf-8' == $this->encoding) {
2514. if(preg_match("/[\xC2-\xDF][\x80-\xBF]".
2515. "|\xE0[\xA0-\xBF][\x80-\xBF]".
2516. "|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}".
2517. "|\xED[\x80-\x9F][\x80-\xBF]".
2518. "|\xF0[\x90-\xBF][\x80-\xBF]{2}".
2519. "|[\xF1-\xF3][\x80-\xBF]{3}".
2520. "|\xF4[\x80-\x8F][\x80-\xBF]{2}/s",
2521. $part, $es_char_m, null, $es_pos + 1)) {
2522. $es_char_m = $es_char_m[0];
2523. } else {
2524. $es_char_m = $es_char;
2525. }
2526. $string .= $this->hsc($es_char_m) . '</span>';
2527. } else {
2528. $es_char_m = $this->hsc($es_char);
2529. }
2530. $start = $es_pos + strlen($es_char_m) + 1;
2531. } else {
2532. $string .= $this->hsc($es_char) . '</span>';
2533. $start = $es_pos + 2;
2534. }
2535. } else if ($next_escape_regexp_pos < $length &&
2536. $next_escape_regexp_pos < $close_pos) {
2537. $es_pos = $next_escape_regexp_pos;
2538. //Add the stuff not in the string yet ...
2539. $string .= $this->hsc(substr($part, $start, $es_pos - $start));
2541. //Get the key and length of this match ...
2542. $escape = $escape_regexp_cache_per_key[$next_escape_regexp_key];
2543. $escape_str = substr($part, $es_pos, $escape['length']);
2544. $escape_key = $escape['key'];
2546. //Get the style for this escaped char ...
2547. if (!$this->use_classes) {
2548. $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][$escape_key] . '"';
2549. } else {
2550. $escape_char_attributes = ' class="es' . $escape_key . '"';
2551. }
2553. //Add the style for the escape char ...
2554. $string .= "<span$escape_char_attributes>" .
2555. $this->hsc($escape_str) . '</span>';
2557. $start = $es_pos + $escape['length'];
2558. } else {
2559. //Copy the remainder of the string ...
2560. $string .= $this->hsc(substr($part, $start, $close_pos - $start + $char_len)) . '</span>';
2561. $start = $close_pos + $char_len;
2562. $string_open = false;
2563. }
2564. } while($string_open);
2566. if ($check_linenumbers) {
2567. // Are line numbers used? If, we should end the string before
2568. // the newline and begin it again (so when <li>s are put in the source
2569. // remains XHTML compliant)
2570. // note to self: This opens up possibility of config files specifying
2571. // that languages can/cannot have multiline strings???
2572. $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
2573. }
2575. $result .= $string;
2576. $string = '';
2577. $i = $start - 1;
2578. continue;
2579. } else if ($this->lexic_permissions['STRINGS'] && $hq && $hq[0] == $char &&
2580. substr($part, $i, $hq_strlen) == $hq) {
2581. // The start of a hard quoted string
2582. if (!$this->use_classes) {
2583. $string_attributes = ' style="' . $this->language_data['STYLES']['STRINGS']['HARD'] . '"';
2584. $escape_char_attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR']['HARD'] . '"';
2585. } else {
2586. $string_attributes = ' class="st_h"';
2587. $escape_char_attributes = ' class="es_h"';
2588. }
2589. // parse the stuff before this
2590. $result .= $this->parse_non_string_part($stuff_to_parse);
2591. $stuff_to_parse = '';
2593. // now handle the string
2594. $string = '';
2596. // look for closing quote
2597. $start = $i + $hq_strlen;
2598. while ($close_pos = strpos($part, $this->language_data['HARDQUOTE'][1], $start)) {
2599. $start = $close_pos + 1;
2600. if ($this->lexic_permissions['ESCAPE_CHAR'] && $part[$close_pos - 1] == $this->language_data['ESCAPE_CHAR']) {
2601. // make sure this quote is not escaped
2602. foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
2603. if (substr($part, $close_pos - 1, strlen($hardescape)) == $hardescape) {
2604. // check wether this quote is escaped or if it is something like '\\'
2605. $escape_char_pos = $close_pos - 1;
2606. while ($escape_char_pos > 0
2607. && $part[$escape_char_pos - 1] == $this->language_data['ESCAPE_CHAR']) {
2608. --$escape_char_pos;
2609. }
2610. if (($close_pos - $escape_char_pos) & 1) {
2611. // uneven number of escape chars => this quote is escaped
2612. continue 2;
2613. }
2614. }
2615. }
2616. }
2618. // found closing quote
2619. break;
2620. }
2622. //Found the closing delimiter?
2623. if (!$close_pos) {
2624. // span till the end of this $part when no closing delimiter is found
2625. $close_pos = $length;
2626. }
2628. //Get the actual string
2629. $string = substr($part, $i, $close_pos - $i + 1);
2630. $i = $close_pos;
2632. // handle escape chars and encode html chars
2633. // (special because when we have escape chars within our string they may not be escaped)
2634. if ($this->lexic_permissions['ESCAPE_CHAR'] && $this->language_data['ESCAPE_CHAR']) {
2635. $start = 0;
2636. $new_string = '';
2637. while ($es_pos = strpos($string, $this->language_data['ESCAPE_CHAR'], $start)) {
2638. // hmtl escape stuff before
2639. $new_string .= $this->hsc(substr($string, $start, $es_pos - $start));
2640. // check if this is a hard escape
2641. foreach ($this->language_data['HARDESCAPE'] as $hardescape) {
2642. if (substr($string, $es_pos, strlen($hardescape)) == $hardescape) {
2643. // indeed, this is a hardescape
2644. $new_string .= "<span$escape_char_attributes>" .
2645. $this->hsc($hardescape) . '</span>';
2646. $start = $es_pos + strlen($hardescape);
2647. continue 2;
2648. }
2649. }
2650. // not a hard escape, but a normal escape
2651. // they come in pairs of two
2652. $c = 0;
2653. while (isset($string[$es_pos + $c]) && isset($string[$es_pos + $c + 1])
2654. && $string[$es_pos + $c] == $this->language_data['ESCAPE_CHAR']
2655. && $string[$es_pos + $c + 1] == $this->language_data['ESCAPE_CHAR']) {
2656. $c += 2;
2657. }
2658. if ($c) {
2659. $new_string .= "<span$escape_char_attributes>" .
2660. str_repeat($escaped_escape_char, $c) .
2661. '</span>';
2662. $start = $es_pos + $c;
2663. } else {
2664. // this is just a single lonely escape char...
2665. $new_string .= $escaped_escape_char;
2666. $start = $es_pos + 1;
2667. }
2668. }
2669. $string = $new_string . $this->hsc(substr($string, $start));
2670. } else {
2671. $string = $this->hsc($string);
2672. }
2674. if ($check_linenumbers) {
2675. // Are line numbers used? If, we should end the string before
2676. // the newline and begin it again (so when <li>s are put in the source
2677. // remains XHTML compliant)
2678. // note to self: This opens up possibility of config files specifying
2679. // that languages can/cannot have multiline strings???
2680. $string = str_replace("\n", "</span>\n<span$string_attributes>", $string);
2681. }
2683. $result .= "<span$string_attributes>" . $string . '</span>';
2684. $string = '';
2685. continue;
2686. } else {
2687. //Have a look for regexp comments
2688. if ($i == $next_comment_regexp_pos) {
2689. $COMMENT_MATCHED = true;
2690. $comment = $comment_regexp_cache_per_key[$next_comment_regexp_key];
2691. $test_str = $this->hsc(substr($part, $i, $comment['length']));
2693. //@todo If remove important do remove here
2694. if ($this->lexic_permissions['COMMENTS']['MULTI']) {
2695. if (!$this->use_classes) {
2696. $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment['key']] . '"';
2697. } else {
2698. $attributes = ' class="co' . $comment['key'] . '"';
2699. }
2701. $test_str = "<span$attributes>" . $test_str . "</span>";
2703. // Short-cut through all the multiline code
2704. if ($check_linenumbers) {
2705. // strreplace to put close span and open span around multiline newlines
2706. $test_str = str_replace(
2707. "\n", "</span>\n<span$attributes>",
2708. str_replace("\n ", "\n&nbsp;", $test_str)
2709. );
2710. }
2711. }
2713. $i += $comment['length'] - 1;
2715. // parse the rest
2716. $result .= $this->parse_non_string_part($stuff_to_parse);
2717. $stuff_to_parse = '';
2718. }
2720. // If we haven't matched a regexp comment, try multi-line comments
2721. if (!$COMMENT_MATCHED) {
2722. // Is this a multiline comment?
2723. if (!empty($this->language_data['COMMENT_MULTI']) && $next_comment_multi_pos < $i) {
2724. $next_comment_multi_pos = $length;
2725. foreach ($this->language_data['COMMENT_MULTI'] as $open => $close) {
2726. $match_i = false;
2727. if (isset($comment_multi_cache_per_key[$open]) &&
2728. ($comment_multi_cache_per_key[$open] >= $i ||
2729. $comment_multi_cache_per_key[$open] === false)) {
2730. // we have already matched something
2731. if ($comment_multi_cache_per_key[$open] === false) {
2732. // this comment is never matched
2733. continue;
2734. }
2735. $match_i = $comment_multi_cache_per_key[$open];
2736. } else if (($match_i = stripos($part, $open, $i)) !== false) {
2737. $comment_multi_cache_per_key[$open] = $match_i;
2738. } else {
2739. $comment_multi_cache_per_key[$open] = false;
2740. continue;
2741. }
2742. if ($match_i !== false && $match_i < $next_comment_multi_pos) {
2743. $next_comment_multi_pos = $match_i;
2744. $next_open_comment_multi = $open;
2745. if ($match_i === $i) {
2746. break;
2747. }
2748. }
2749. }
2750. }
2751. if ($i == $next_comment_multi_pos) {
2752. $open = $next_open_comment_multi;
2753. $close = $this->language_data['COMMENT_MULTI'][$open];
2754. $open_strlen = strlen($open);
2755. $close_strlen = strlen($close);
2756. $COMMENT_MATCHED = true;
2757. $test_str_match = $open;
2758. //@todo If remove important do remove here
2759. if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
2760. $open == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
2761. if ($open != <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
2762. if (!$this->use_classes) {
2763. $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS']['MULTI'] . '"';
2764. } else {
2765. $attributes = ' class="coMULTI"';
2766. }
2767. $test_str = "<span$attributes>" . $this->hsc($open);
2768. } else {
2769. if (!$this->use_classes) {
2770. $attributes = ' style="' . $this->important_styles . '"';
2771. } else {
2772. $attributes = ' class="imp"';
2773. }
2775. // We don't include the start of the comment if it's an
2776. // "important" part
2777. $test_str = "<span$attributes>";
2778. }
2779. } else {
2780. $test_str = $this->hsc($open);
2781. }
2783. $close_pos = strpos( $part, $close, $i + $open_strlen );
2785. if ($close_pos === false) {
2786. $close_pos = $length;
2787. }
2789. // Short-cut through all the multiline code
2790. $rest_of_comment = $this->hsc(substr($part, $i + $open_strlen, $close_pos - $i - $open_strlen + $close_strlen));
2791. if (($this->lexic_permissions['COMMENTS']['MULTI'] ||
2792. $test_str_match == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) &&
2793. $check_linenumbers) {
2795. // strreplace to put close span and open span around multiline newlines
2796. $test_str .= str_replace(
2797. "\n", "</span>\n<span$attributes>",
2798. str_replace("\n ", "\n&nbsp;", $rest_of_comment)
2799. );
2800. } else {
2801. $test_str .= $rest_of_comment;
2802. }
2804. if ($this->lexic_permissions['COMMENTS']['MULTI'] ||
2805. $test_str_match == <a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>) {
2806. $test_str .= '</span>';
2807. }
2809. $i = $close_pos + $close_strlen - 1;
2811. // parse the rest
2812. $result .= $this->parse_non_string_part($stuff_to_parse);
2813. $stuff_to_parse = '';
2814. }
2815. }
2817. // If we haven't matched a multiline comment, try single-line comments
2818. if (!$COMMENT_MATCHED) {
2819. // cache potential single line comment occurances
2820. if (!empty($this->language_data['COMMENT_SINGLE']) && $next_comment_single_pos < $i) {
2821. $next_comment_single_pos = $length;
2822. foreach ($this->language_data['COMMENT_SINGLE'] as $comment_key => $comment_mark) {
2823. $match_i = false;
2824. if (isset($comment_single_cache_per_key[$comment_key]) &&
2825. ($comment_single_cache_per_key[$comment_key] >= $i ||
2826. $comment_single_cache_per_key[$comment_key] === false)) {
2827. // we have already matched something
2828. if ($comment_single_cache_per_key[$comment_key] === false) {
2829. // this comment is never matched
2830. continue;
2831. }
2832. $match_i = $comment_single_cache_per_key[$comment_key];
2833. } else if (
2834. // case sensitive comments
2835. ($this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
2836. ($match_i = stripos($part, $comment_mark, $i)) !== false) ||
2837. // non case sensitive
2838. (!$this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] &&
2839. (($match_i = strpos($part, $comment_mark, $i)) !== false))) {
2840. $comment_single_cache_per_key[$comment_key] = $match_i;
2841. } else {
2842. $comment_single_cache_per_key[$comment_key] = false;
2843. continue;
2844. }
2845. if ($match_i !== false && $match_i < $next_comment_single_pos) {
2846. $next_comment_single_pos = $match_i;
2847. $next_comment_single_key = $comment_key;
2848. if ($match_i === $i) {
2849. break;
2850. }
2851. }
2852. }
2853. }
2854. if ($next_comment_single_pos == $i) {
2855. $comment_key = $next_comment_single_key;
2856. $comment_mark = $this->language_data['COMMENT_SINGLE'][$comment_key];
2857. $com_len = strlen($comment_mark);
2859. // This check will find special variables like $# in bash
2860. // or compiler directives of Delphi beginning {$
2861. if ((empty($sc_disallowed_before) || ($i == 0) ||
2862. (false === strpos($sc_disallowed_before, $part[$i-1]))) &&
2863. (empty($sc_disallowed_after) || ($length <= $i + $com_len) ||
2864. (false === strpos($sc_disallowed_after, $part[$i + $com_len]))))
2865. {
2866. // this is a valid comment
2867. $COMMENT_MATCHED = true;
2868. if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
2869. if (!$this->use_classes) {
2870. $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment_key] . '"';
2871. } else {
2872. $attributes = ' class="co' . $comment_key . '"';
2873. }
2874. $test_str = "<span$attributes>" . $this->hsc($this->change_case($comment_mark));
2875. } else {
2876. $test_str = $this->hsc($comment_mark);
2877. }
2879. //Check if this comment is the last in the source
2880. $close_pos = strpos($part, "\n", $i);
2881. $oops = false;
2882. if ($close_pos === false) {
2883. $close_pos = $length;
2884. $oops = true;
2885. }
2886. $test_str .= $this->hsc(substr($part, $i + $com_len, $close_pos - $i - $com_len));
2887. if ($this->lexic_permissions['COMMENTS'][$comment_key]) {
2888. $test_str .= "</span>";
2889. }
2891. // Take into account that the comment might be the last in the source
2892. if (!$oops) {
2893. $test_str .= "\n";
2894. }
2896. $i = $close_pos;
2898. // parse the rest
2899. $result .= $this->parse_non_string_part($stuff_to_parse);
2900. $stuff_to_parse = '';
2901. }
2902. }
2903. }
2904. }
2906. // Where are we adding this char?
2907. if (!$COMMENT_MATCHED) {
2908. $stuff_to_parse .= $char;
2909. } else {
2910. $result .= $test_str;
2911. unset($test_str);
2912. $COMMENT_MATCHED = false;
2913. }
2914. }
2915. // Parse the last bit
2916. $result .= $this->parse_non_string_part($stuff_to_parse);
2917. $stuff_to_parse = '';
2918. } else {
2919. $result .= $this->hsc($part);
2920. }
2921. // Close the <span> that surrounds the block
2922. if ($STRICTATTRS != '') {
2923. $result = str_replace("\n", "</span>\n<span$STRICTATTRS>", $result);
2924. $result .= '</span>';
2925. }
2927. $endresult .= $result;
2928. unset($part, $parts[$key], $result);
2929. }
2931. //This fix is related to SF#1923020, but has to be applied regardless of
2932. //actually highlighting symbols.
2933. /** NOTE: memorypeak #3 */
2934. $endresult = str_replace(array('<SEMI>', '<PIPE>'), array(';', '|'), $endresult);
2936. // // Parse the last stuff (redundant?)
2937. // $result .= $this->parse_non_string_part($stuff_to_parse);
2939. // Lop off the very first and last spaces
2940. // $result = substr($result, 1, -1);
2942. // We're finished: stop timing
2943. $this->set_time($start_time, microtime());
2945. $this->finalise($endresult);
2946. return $endresult;
2947. }
2949. /**
2950. * Swaps out spaces and tabs for HTML indentation. Not needed if
2951. * the code is in a pre block...
2952. *
2953. * @param string The source to indent (reference!)
2954. * @since 1.0.0
2955. * @access private
2956. */
2957. function indent(&$result) {
2958. /// Replace tabs with the correct number of spaces
2959. if (false !== strpos($result, "\t")) {
2960. $lines = explode("\n", $result);
2961. $result = null;//Save memory while we process the lines individually
2962. $tab_width = $this->get_real_tab_width();
2963. $tab_string = '&nbsp;' . str_repeat(' ', $tab_width);
2965. for ($key = 0, $n = count($lines); $key < $n; $key++) {
2966. $line = $lines[$key];
2967. if (false === strpos($line, "\t")) {
2968. continue;
2969. }
2971. $pos = 0;
2972. $length = strlen($line);
2973. $lines[$key] = ''; // reduce memory
2975. $IN_TAG = false;
2976. for ($i = 0; $i < $length; ++$i) {
2977. $char = $line[$i];
2978. // Simple engine to work out whether we're in a tag.
2979. // If we are we modify $pos. This is so we ignore HTML
2980. // in the line and only workout the tab replacement
2981. // via the actual content of the string
2982. // This test could be improved to include strings in the
2983. // html so that < or > would be allowed in user's styles
2984. // (e.g. quotes: '<' '>'; or similar)
2985. if ($IN_TAG) {
2986. if ('>' == $char) {
2987. $IN_TAG = false;
2988. }
2989. $lines[$key] .= $char;
2990. } else if ('<' == $char) {
2991. $IN_TAG = true;
2992. $lines[$key] .= '<';
2993. } else if ('&' == $char) {
2994. $substr = substr($line, $i + 3, 5);
2995. $posi = strpos($substr, ';');
2996. if (false === $posi) {
2997. ++$pos;
2998. } else {
2999. $pos -= $posi+2;
3000. }
3001. $lines[$key] .= $char;
3002. } else if ("\t" == $char) {
3003. $str = '';
3004. // OPTIMISE - move $strs out. Make an array:
3005. // $tabs = array(
3006. // 1 => '&nbsp;',
3007. // 2 => '&nbsp; ',
3008. // 3 => '&nbsp; &nbsp;' etc etc
3009. // to use instead of building a string every time
3010. $tab_end_width = $tab_width - ($pos % $tab_width); //Moved out of the look as it doesn't change within the loop
3011. if (($pos & 1) || 1 == $tab_end_width) {
3012. $str .= substr($tab_string, 6, $tab_end_width);
3013. } else {
3014. $str .= substr($tab_string, 0, $tab_end_width+5);
3015. }
3016. $lines[$key] .= $str;
3017. $pos += $tab_end_width;
3019. if (false === strpos($line, "\t", $i + 1)) {
3020. $lines[$key] .= substr($line, $i + 1);
3021. break;
3022. }
3023. } else if (0 == $pos && ' ' == $char) {
3024. $lines[$key] .= '&nbsp;';
3025. ++$pos;
3026. } else {
3027. $lines[$key] .= $char;
3028. ++$pos;
3029. }
3030. }
3031. }
3032. $result = implode("\n", $lines);
3033. unset($lines);//We don't need the lines separated beyond this --- free them!
3034. }
3035. // Other whitespace
3036. // BenBE: Fix to reduce the number of replacements to be done
3037. $result = preg_replace('/^ /m', '&nbsp;', $result);
3038. $result = str_replace(' ', ' &nbsp;', $result);
3040. if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3041. if ($this->line_ending === null) {
3042. $result = nl2br($result);
3043. } else {
3044. $result = str_replace("\n", $this->line_ending, $result);
3045. }
3046. }
3047. }
3049. /**
3050. * Changes the case of a keyword for those languages where a change is asked for
3051. *
3052. * @param string The keyword to change the case of
3053. * @return string The keyword with its case changed
3054. * @since 1.0.0
3055. * @access private
3056. */
3057. function change_case($instr) {
3058. switch ($this->language_data['CASE_KEYWORDS']) {
3059. case <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_UPPER">GESHI_CAPS_UPPER</a>:
3060. return strtoupper($instr);
3061. case <a href="../geshi/core/_geshi.php.html#defineGESHI_CAPS_LOWER">GESHI_CAPS_LOWER</a>:
3062. return strtolower($instr);
3063. default:
3064. return $instr;
3065. }
3066. }
3068. /**
3069. * Handles replacements of keywords to include markup and links if requested
3070. *
3071. * @param string The keyword to add the Markup to
3072. * @return The HTML for the match found
3073. * @since 1.0.8
3074. * @access private
3075. *
3076. * @todo Get rid of ender in keyword links
3077. */
3078. function handle_keyword_replace($match) {
3079. $k = $this->_kw_replace_group;
3080. $keyword = $match[0];
3082. $before = '';
3083. $after = '';
3085. if ($this->keyword_links) {
3086. // Keyword links have been ebabled
3088. if (isset($this->language_data['URLS'][$k]) &&
3089. $this->language_data['URLS'][$k] != '') {
3090. // There is a base group for this keyword
3092. // Old system: strtolower
3093. //$keyword = ( $this->language_data['CASE_SENSITIVE'][$group] ) ? $keyword : strtolower($keyword);
3094. // New system: get keyword from language file to get correct case
3095. if (!$this->language_data['CASE_SENSITIVE'][$k] &&
3096. strpos($this->language_data['URLS'][$k], '{FNAME}') !== false) {
3097. foreach ($this->language_data['KEYWORDS'][$k] as $word) {
3098. if (strcasecmp($word, $keyword) == 0) {
3099. break;
3100. }
3101. }
3102. } else {
3103. $word = $keyword;
3104. }
3106. $before = '<|UR1|"' .
3107. str_replace(
3108. array(
3109. '{FNAME}',
3110. '{FNAMEL}',
3111. '{FNAMEU}',
3112. '.'),
3113. array(
3114. str_replace('+', '%20', urlencode($this->hsc($word))),
3115. str_replace('+', '%20', urlencode($this->hsc(strtolower($word)))),
3116. str_replace('+', '%20', urlencode($this->hsc(strtoupper($word)))),
3117. '<DOT>'),
3118. $this->language_data['URLS'][$k]
3119. ) . '">';
3120. $after = '</a>';
3121. }
3122. }
3124. return $before . '<|/'. $k .'/>' . $this->change_case($keyword) . '|>' . $after;
3125. }
3127. /**
3128. * handles regular expressions highlighting-definitions with callback functions
3129. *
3130. * @note this is a callback, don't use it directly
3131. *
3132. * @param array the matches array
3133. * @return The highlighted string
3134. * @since 1.0.8
3135. * @access private
3136. */
3137. function handle_regexps_callback($matches) {
3138. // before: "' style=\"' . call_user_func(\"$func\", '\\1') . '\"\\1|>'",
3139. return ' style="' . call_user_func($this->language_data['STYLES']['REGEXPS'][$this->_rx_key], $matches[1]) . '"'. $matches[1] . '|>';
3140. }
3142. /**
3143. * handles newlines in REGEXPS matches. Set the _hmr_* vars before calling this
3144. *
3145. * @note this is a callback, don't use it directly
3146. *
3147. * @param array the matches array
3148. * @return string
3149. * @since 1.0.8
3150. * @access private
3151. */
3152. function handle_multiline_regexps($matches) {
3153. $before = $this->_hmr_before;
3154. $after = $this->_hmr_after;
3155. if ($this->_hmr_replace) {
3156. $replace = $this->_hmr_replace;
3157. $search = array();
3159. foreach (array_keys($matches) as $k) {
3160. $search[] = '\\' . $k;
3161. }
3163. $before = str_replace($search, $matches, $before);
3164. $after = str_replace($search, $matches, $after);
3165. $replace = str_replace($search, $matches, $replace);
3166. } else {
3167. $replace = $matches[0];
3168. }
3169. return $before
3170. . '<|!REG3XP' . $this->_hmr_key .'!>'
3171. . str_replace("\n", "|>\n<|!REG3XP" . $this->_hmr_key . '!>', $replace)
3172. . '|>'
3173. . $after;
3174. }
3176. /**
3177. * Takes a string that has no strings or comments in it, and highlights
3178. * stuff like keywords, numbers and methods.
3179. *
3180. * @param string The string to parse for keyword, numbers etc.
3181. * @since 1.0.0
3182. * @access private
3183. * @todo BUGGY! Why? Why not build string and return?
3184. */
3185. function parse_non_string_part($stuff_to_parse) {
3186. $stuff_to_parse = ' ' . $this->hsc($stuff_to_parse);
3188. // Regular expressions
3189. foreach ($this->language_data['REGEXPS'] as $key => $regexp) {
3190. if ($this->lexic_permissions['REGEXPS'][$key]) {
3191. if (is_array($regexp)) {
3192. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3193. // produce valid HTML when we match multiple lines
3194. $this->_hmr_replace = $regexp[GESHI_REPLACE];
3195. $this->_hmr_before = $regexp[GESHI_BEFORE];
3196. $this->_hmr_key = $key;
3197. $this->_hmr_after = $regexp[GESHI_AFTER];
3198. $stuff_to_parse = preg_replace_callback(
3199. "/" . $regexp[GESHI_SEARCH] . "/{$regexp[GESHI_MODIFIERS]}",
3200. array($this, 'handle_multiline_regexps'),
3201. $stuff_to_parse);
3202. $this->_hmr_replace = false;
3203. $this->_hmr_before = '';
3204. $this->_hmr_after = '';
3205. } else {
3206. $stuff_to_parse = preg_replace(
3207. '/' . $regexp[GESHI_SEARCH] . '/' . $regexp[GESHI_MODIFIERS],
3208. $regexp[GESHI_BEFORE] . '<|!REG3XP'. $key .'!>' . $regexp[GESHI_REPLACE] . '|>' . $regexp[GESHI_AFTER],
3209. $stuff_to_parse);
3210. }
3211. } else {
3212. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3213. // produce valid HTML when we match multiple lines
3214. $this->_hmr_key = $key;
3215. $stuff_to_parse = preg_replace_callback( "/(" . $regexp . ")/",
3216. array($this, 'handle_multiline_regexps'), $stuff_to_parse);
3217. $this->_hmr_key = '';
3218. } else {
3219. $stuff_to_parse = preg_replace( "/(" . $regexp . ")/", "<|!REG3XP$key!>\\1|>", $stuff_to_parse);
3220. }
3221. }
3222. }
3223. }
3225. // Highlight numbers. As of 1.0.8 we support diffent types of numbers
3226. $numbers_found = false;
3227. if ($this->lexic_permissions['NUMBERS'] && preg_match('#\d#', $stuff_to_parse )) {
3228. $numbers_found = true;
3230. //For each of the formats ...
3231. foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
3232. //Check if it should be highlighted ...
3233. $stuff_to_parse = preg_replace($regexp, "<|/NUM!$id/>\\1|>", $stuff_to_parse);
3234. }
3235. }
3237. // Highlight keywords
3238. $disallowed_before = "(?<![a-zA-Z0-9\$_\|\#;>|^&";
3239. $disallowed_after = "(?![a-zA-Z0-9_\|%\\-&;";
3240. if ($this->lexic_permissions['STRINGS']) {
3241. $quotemarks = preg_quote(implode($this->language_data['QUOTEMARKS']), '/');
3242. $disallowed_before .= $quotemarks;
3243. $disallowed_after .= $quotemarks;
3244. }
3245. $disallowed_before .= "])";
3246. $disallowed_after .= "])";
3248. $parser_control_pergroup = false;
3249. if (isset($this->language_data['PARSER_CONTROL'])) {
3250. if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'])) {
3251. $x = 0; // check wether per-keyword-group parser_control is enabled
3252. if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'])) {
3253. $disallowed_before = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_BEFORE'];
3254. ++$x;
3255. }
3256. if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'])) {
3257. $disallowed_after = $this->language_data['PARSER_CONTROL']['KEYWORDS']['DISALLOWED_AFTER'];
3258. ++$x;
3259. }
3260. $parser_control_pergroup = (count($this->language_data['PARSER_CONTROL']['KEYWORDS']) - $x) > 0;
3261. }
3262. }
3264. // if this is changed, don't forget to change it below
3265. // if (!empty($disallowed_before)) {
3266. // $disallowed_before = "(?<![$disallowed_before])";
3267. // }
3268. // if (!empty($disallowed_after)) {
3269. // $disallowed_after = "(?![$disallowed_after])";
3270. // }
3272. foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
3273. if (!isset($this->lexic_permissions['KEYWORDS'][$k]) ||
3274. $this->lexic_permissions['KEYWORDS'][$k]) {
3276. $case_sensitive = $this->language_data['CASE_SENSITIVE'][$k];
3277. $modifiers = $case_sensitive ? '' : 'i';
3279. // NEW in 1.0.8 - per-keyword-group parser control
3280. $disallowed_before_local = $disallowed_before;
3281. $disallowed_after_local = $disallowed_after;
3282. if ($parser_control_pergroup && isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k])) {
3283. if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'])) {
3284. $disallowed_before_local =
3285. $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_BEFORE'];
3286. }
3288. if (isset($this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'])) {
3289. $disallowed_after_local =
3290. $this->language_data['PARSER_CONTROL']['KEYWORDS'][$k]['DISALLOWED_AFTER'];
3291. }
3292. }
3294. $this->_kw_replace_group = $k;
3296. //NEW in 1.0.8, the cached regexp list
3297. // since we don't want PHP / PCRE to crash due to too large patterns we split them into smaller chunks
3298. for ($set = 0, $set_length = count($this->language_data['CACHED_KEYWORD_LISTS'][$k]); $set < $set_length; ++$set) {
3299. $keywordset =& $this->language_data['CACHED_KEYWORD_LISTS'][$k][$set];
3300. // Might make a more unique string for putting the number in soon
3301. // Basically, we don't put the styles in yet because then the styles themselves will
3302. // get highlighted if the language has a CSS keyword in it (like CSS, for example ;))
3303. $stuff_to_parse = preg_replace_callback(
3304. "/$disallowed_before_local({$keywordset})(?!\<DOT\>(?:htm|php))$disallowed_after_local/$modifiers",
3305. array($this, 'handle_keyword_replace'),
3306. $stuff_to_parse
3307. );
3308. }
3309. }
3310. }
3312. //
3313. // Now that's all done, replace /[number]/ with the correct styles
3314. //
3315. foreach (array_keys($this->language_data['KEYWORDS']) as $k) {
3316. if (!$this->use_classes) {
3317. $attributes = ' style="' .
3318. (isset($this->language_data['STYLES']['KEYWORDS'][$k]) ?
3319. $this->language_data['STYLES']['KEYWORDS'][$k] : "") . '"';
3320. } else {
3321. $attributes = ' class="kw' . $k . '"';
3322. }
3323. $stuff_to_parse = str_replace("<|/$k/>", "<|$attributes>", $stuff_to_parse);
3324. }
3326. if ($numbers_found) {
3327. // Put number styles in
3328. foreach($this->language_data['NUMBERS_RXCACHE'] as $id => $regexp) {
3329. //Commented out for now, as this needs some review ...
3330. // if ($numbers_permissions & $id) {
3331. //Get the appropriate style ...
3332. //Checking for unset styles is done by the style cache builder ...
3333. if (!$this->use_classes) {
3334. $attributes = ' style="' . $this->language_data['STYLES']['NUMBERS'][$id] . '"';
3335. } else {
3336. $attributes = ' class="nu'.$id.'"';
3337. }
3339. //Set in the correct styles ...
3340. $stuff_to_parse = str_replace("/NUM!$id/", $attributes, $stuff_to_parse);
3341. // }
3342. }
3343. }
3345. // Highlight methods and fields in objects
3346. if ($this->lexic_permissions['METHODS'] && $this->language_data['OOLANG']) {
3347. $oolang_spaces = "[\s]*";
3348. $oolang_before = "";
3349. $oolang_after = "[a-zA-Z][a-zA-Z0-9_]*";
3350. if (isset($this->language_data['PARSER_CONTROL'])) {
3351. if (isset($this->language_data['PARSER_CONTROL']['OOLANG'])) {
3352. if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'])) {
3353. $oolang_before = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_BEFORE'];
3354. }
3355. if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'])) {
3356. $oolang_after = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_AFTER'];
3357. }
3358. if (isset($this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'])) {
3359. $oolang_spaces = $this->language_data['PARSER_CONTROL']['OOLANG']['MATCH_SPACES'];
3360. }
3361. }
3362. }
3364. foreach ($this->language_data['OBJECT_SPLITTERS'] as $key => $splitter) {
3365. if (false !== strpos($stuff_to_parse, $splitter)) {
3366. if (!$this->use_classes) {
3367. $attributes = ' style="' . $this->language_data['STYLES']['METHODS'][$key] . '"';
3368. } else {
3369. $attributes = ' class="me' . $key . '"';
3370. }
3371. $stuff_to_parse = preg_replace("/($oolang_before)(" . preg_quote($this->language_data['OBJECT_SPLITTERS'][$key], '/') . ")($oolang_spaces)($oolang_after)/", "\\1\\2\\3<|$attributes>\\4|>", $stuff_to_parse);
3372. }
3373. }
3374. }
3376. //
3377. // Highlight brackets. Yes, I've tried adding a semi-colon to this list.
3378. // You try it, and see what happens ;)
3379. // TODO: Fix lexic permissions not converting entities if shouldn't
3380. // be highlighting regardless
3381. //
3382. if ($this->lexic_permissions['BRACKETS']) {
3383. $stuff_to_parse = str_replace( $this->language_data['CACHE_BRACKET_MATCH'],
3384. $this->language_data['CACHE_BRACKET_REPLACE'], $stuff_to_parse );
3385. }
3388. //FIX for symbol highlighting ...
3389. if ($this->lexic_permissions['SYMBOLS'] && !empty($this->language_data['SYMBOLS'])) {
3390. //Get all matches and throw away those witin a block that is already highlighted... (i.e. matched by a regexp)
3391. $n_symbols = preg_match_all("/<\|(?:<DOT>|[^>])+>(?:(?!\|>).*?)\|>|<\/a>|(?:" . $this->language_data['SYMBOL_SEARCH'] . ")+/", $stuff_to_parse, $pot_symbols, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
3392. $global_offset = 0;
3393. for ($s_id = 0; $s_id < $n_symbols; ++$s_id) {
3394. $symbol_match = $pot_symbols[$s_id][0][0];
3395. if (strpos($symbol_match, '<') !== false || strpos($symbol_match, '>') !== false) {
3396. // already highlighted blocks _must_ include either < or >
3397. // so if this conditional applies, we have to skip this match
3398. // BenBE: UNLESS the block contains <SEMI> or <PIPE>
3399. if(strpos($symbol_match, '<SEMI>') === false &&
3400. strpos($symbol_match, '<PIPE>') === false) {
3401. continue;
3402. }
3403. }
3405. // if we reach this point, we have a valid match which needs to be highlighted
3407. $symbol_length = strlen($symbol_match);
3408. $symbol_offset = $pot_symbols[$s_id][0][1];
3409. unset($pot_symbols[$s_id]);
3410. $symbol_end = $symbol_length + $symbol_offset;
3411. $symbol_hl = "";
3413. // if we have multiple styles, we have to handle them properly
3414. if ($this->language_data['MULTIPLE_SYMBOL_GROUPS']) {
3415. $old_sym = -1;
3416. // Split the current stuff to replace into its atomic symbols ...
3417. preg_match_all("/" . $this->language_data['SYMBOL_SEARCH'] . "/", $symbol_match, $sym_match_syms, PREG_PATTERN_ORDER);
3418. foreach ($sym_match_syms[0] as $sym_ms) {
3419. //Check if consequtive symbols belong to the same group to save output ...
3420. if (isset($this->language_data['SYMBOL_DATA'][$sym_ms])
3421. && ($this->language_data['SYMBOL_DATA'][$sym_ms] != $old_sym)) {
3422. if (-1 != $old_sym) {
3423. $symbol_hl .= "|>";
3424. }
3425. $old_sym = $this->language_data['SYMBOL_DATA'][$sym_ms];
3426. if (!$this->use_classes) {
3427. $symbol_hl .= '<| style="' . $this->language_data['STYLES']['SYMBOLS'][$old_sym] . '">';
3428. } else {
3429. $symbol_hl .= '<| class="sy' . $old_sym . '">';
3430. }
3431. }
3432. $symbol_hl .= $sym_ms;
3433. }
3434. unset($sym_match_syms);
3436. //Close remaining tags and insert the replacement at the right position ...
3437. //Take caution if symbol_hl is empty to avoid doubled closing spans.
3438. if (-1 != $old_sym) {
3439. $symbol_hl .= "|>";
3440. }
3441. } else {
3442. if (!$this->use_classes) {
3443. $symbol_hl = '<| style="' . $this->language_data['STYLES']['SYMBOLS'][0] . '">';
3444. } else {
3445. $symbol_hl = '<| class="sy0">';
3446. }
3447. $symbol_hl .= $symbol_match . '|>';
3448. }
3450. $stuff_to_parse = substr_replace($stuff_to_parse, $symbol_hl, $symbol_offset + $global_offset, $symbol_length);
3452. // since we replace old text with something of different size,
3453. // we'll have to keep track of the differences
3454. $global_offset += strlen($symbol_hl) - $symbol_length;
3455. }
3456. }
3457. //FIX for symbol highlighting ...
3459. // Add class/style for regexps
3460. foreach (array_keys($this->language_data['REGEXPS']) as $key) {
3461. if ($this->lexic_permissions['REGEXPS'][$key]) {
3462. if (is_callable($this->language_data['STYLES']['REGEXPS'][$key])) {
3463. $this->_rx_key = $key;
3464. $stuff_to_parse = preg_replace_callback("/!REG3XP$key!(.*)\|>/U",
3465. array($this, 'handle_regexps_callback'),
3466. $stuff_to_parse);
3467. } else {
3468. if (!$this->use_classes) {
3469. $attributes = ' style="' . $this->language_data['STYLES']['REGEXPS'][$key] . '"';
3470. } else {
3471. if (is_array($this->language_data['REGEXPS'][$key]) &&
3472. array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$key])) {
3473. $attributes = ' class="' .
3474. $this->language_data['REGEXPS'][$key][GESHI_CLASS] . '"';
3475. } else {
3476. $attributes = ' class="re' . $key . '"';
3477. }
3478. }
3479. $stuff_to_parse = str_replace("!REG3XP$key!", "$attributes", $stuff_to_parse);
3480. }
3481. }
3482. }
3484. // Replace <DOT> with . for urls
3485. $stuff_to_parse = str_replace('<DOT>', '.', $stuff_to_parse);
3486. // Replace <|UR1| with <a href= for urls also
3487. if (isset($this->link_styles[<a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>])) {
3488. if ($this->use_classes) {
3489. $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
3490. } else {
3491. $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' style="' . $this->link_styles[<a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>] . '" href=', $stuff_to_parse);
3492. }
3493. } else {
3494. $stuff_to_parse = str_replace('<|UR1|', '<a' . $this->link_target . ' href=', $stuff_to_parse);
3495. }
3497. //
3498. // NOW we add the span thingy ;)
3499. //
3501. $stuff_to_parse = str_replace('<|', '<span', $stuff_to_parse);
3502. $stuff_to_parse = str_replace ( '|>', '</span>', $stuff_to_parse );
3503. return substr($stuff_to_parse, 1);
3504. }
3506. /**
3507. * Sets the time taken to parse the code
3508. *
3509. * @param microtime The time when parsing started
3510. * @param microtime The time when parsing ended
3511. * @since 1.0.2
3512. * @access private
3513. */
3514. function set_time($start_time, $end_time) {
3515. $start = explode(' ', $start_time);
3516. $end = explode(' ', $end_time);
3517. $this->time = $end[0] + $end[1] - $start[0] - $start[1];
3518. }
3520. /**
3521. * Gets the time taken to parse the code
3522. *
3523. * @return double The time taken to parse the code
3524. * @since 1.0.2
3525. */
3526. function get_time() {
3527. return $this->time;
3528. }
3530. /**
3531. * Merges arrays recursively, overwriting values of the first array with values of later arrays
3532. *
3533. * @since 1.0.8
3534. * @access private
3535. */
3536. function merge_arrays() {
3537. $arrays = func_get_args();
3538. $narrays = count($arrays);
3540. // check arguments
3541. // comment out if more performance is necessary (in this case the foreach loop will trigger a warning if the argument is not an array)
3542. for ($i = 0; $i < $narrays; $i ++) {
3543. if (!is_array($arrays[$i])) {
3544. // also array_merge_recursive returns nothing in this case
3545. trigger_error('Argument #' . ($i+1) . ' is not an array - trying to merge array with scalar! Returning false!', E_USER_WARNING);
3546. return false;
3547. }
3548. }
3550. // the first array is in the output set in every case
3551. $ret = $arrays[0];
3553. // merege $ret with the remaining arrays
3554. for ($i = 1; $i < $narrays; $i ++) {
3555. foreach ($arrays[$i] as $key => $value) {
3556. if (is_array($value) && isset($ret[$key])) {
3557. // if $ret[$key] is not an array you try to merge an scalar value with an array - the result is not defined (incompatible arrays)
3558. // in this case the call will trigger an E_USER_WARNING and the $ret[$key] will be false.
3559. $ret[$key] = $this->merge_arrays($ret[$key], $value);
3560. } else {
3561. $ret[$key] = $value;
3562. }
3563. }
3564. }
3566. return $ret;
3567. }
3569. /**
3570. * Gets language information and stores it for later use
3571. *
3572. * @param string The filename of the language file you want to load
3573. * @since 1.0.0
3574. * @access private
3575. * @todo Needs to load keys for lexic permissions for keywords, regexps etc
3576. */
3577. function load_language($file_name) {
3578. if ($file_name == $this->loaded_language) {
3579. // this file is already loaded!
3580. return;
3581. }
3583. //Prepare some stuff before actually loading the language file
3584. $this->loaded_language = $file_name;
3585. $this->parse_cache_built = false;
3586. $this->enable_highlighting();
3587. $language_data = array();
3589. //Load the language file
3590. require $file_name;
3592. // Perhaps some checking might be added here later to check that
3593. // $language data is a valid thing but maybe not
3594. $this->language_data = $language_data;
3596. // Set strict mode if should be set
3597. $this->strict_mode = $this->language_data['STRICT_MODE_APPLIES'];
3599. // Set permissions for all lexics to true
3600. // so they'll be highlighted by default
3601. foreach (array_keys($this->language_data['KEYWORDS']) as $key) {
3602. if (!empty($this->language_data['KEYWORDS'][$key])) {
3603. $this->lexic_permissions['KEYWORDS'][$key] = true;
3604. } else {
3605. $this->lexic_permissions['KEYWORDS'][$key] = false;
3606. }
3607. }
3609. foreach (array_keys($this->language_data['COMMENT_SINGLE']) as $key) {
3610. $this->lexic_permissions['COMMENTS'][$key] = true;
3611. }
3612. foreach (array_keys($this->language_data['REGEXPS']) as $key) {
3613. $this->lexic_permissions['REGEXPS'][$key] = true;
3614. }
3616. // for BenBE and future code reviews:
3617. // we can use empty here since we only check for existance and emptiness of an array
3618. // if it is not an array at all but rather false or null this will work as intended as well
3619. // even if $this->language_data['PARSER_CONTROL'] is undefined this won't trigger a notice
3620. if (!empty($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'])) {
3621. foreach ($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS'] as $flag => $value) {
3622. // it's either true or false and maybe is true as well
3623. $perm = $value !== GESHI_NEVER;
3624. if ($flag == 'ALL') {
3625. $this->enable_highlighting($perm);
3626. continue;
3627. }
3628. if (!isset($this->lexic_permissions[$flag])) {
3629. // unknown lexic permission
3630. continue;
3631. }
3632. if (is_array($this->lexic_permissions[$flag])) {
3633. foreach ($this->lexic_permissions[$flag] as $key => $val) {
3634. $this->lexic_permissions[$flag][$key] = $perm;
3635. }
3636. } else {
3637. $this->lexic_permissions[$flag] = $perm;
3638. }
3639. }
3640. unset($this->language_data['PARSER_CONTROL']['ENABLE_FLAGS']);
3641. }
3643. //NEW in 1.0.8: Allow styles to be loaded from a separate file to override defaults
3644. $style_filename = substr($file_name, 0, -4) . '.style.php';
3645. if (is_readable($style_filename)) {
3646. //Clear any style_data that could have been set before ...
3647. if (isset($style_data)) {
3648. unset($style_data);
3649. }
3651. //Read the Style Information from the style file
3652. include $style_filename;
3654. //Apply the new styles to our current language styles
3655. if (isset($style_data) && is_array($style_data)) {
3656. $this->language_data['STYLES'] =
3657. $this->merge_arrays($this->language_data['STYLES'], $style_data);
3658. }
3659. }
3660. }
3662. /**
3663. * Takes the parsed code and various options, and creates the HTML
3664. * surrounding it to make it look nice.
3665. *
3666. * @param string The code already parsed (reference!)
3667. * @since 1.0.0
3668. * @access private
3669. */
3670. function finalise(&$parsed_code) {
3671. // Remove end parts of important declarations
3672. // This is BUGGY!! My fault for bad code: fix coming in 1.2
3673. // @todo Remove this crap
3674. if ($this->enable_important_blocks &&
3675. (strpos($parsed_code, $this->hsc(<a href="../geshi/core/_geshi.php.html#defineGESHI_START_IMPORTANT">GESHI_START_IMPORTANT</a>)) === false)) {
3676. $parsed_code = str_replace($this->hsc(<a href="../geshi/core/_geshi.php.html#defineGESHI_END_IMPORTANT">GESHI_END_IMPORTANT</a>), '', $parsed_code);
3677. }
3679. // Add HTML whitespace stuff if we're using the <div> header
3680. if ($this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
3681. $this->indent($parsed_code);
3682. }
3684. // purge some unnecessary stuff
3685. /** NOTE: memorypeak #1 */
3686. $parsed_code = preg_replace('#<span[^>]+>(\s*)</span>#', '\\1', $parsed_code);
3688. // If we are using IDs for line numbers, there needs to be an overall
3689. // ID set to prevent collisions.
3690. if ($this->add_ids && !$this->overall_id) {
3691. $this->overall_id = 'geshi-' . substr(md5(microtime()), 0, 4);
3692. }
3694. // Get code into lines
3695. /** NOTE: memorypeak #2 */
3696. $code = explode("\n", $parsed_code);
3697. $parsed_code = $this->header();
3699. // If we're using line numbers, we insert <li>s and appropriate
3700. // markup to style them (otherwise we don't need to do anything)
3701. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
3702. // If we're using the <pre> header, we shouldn't add newlines because
3703. // the <pre> will line-break them (and the <li>s already do this for us)
3704. $ls = ($this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> && $this->header_type != <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) ? "\n" : '';
3706. // Set vars to defaults for following loop
3707. $i = 0;
3709. // Foreach line...
3710. for ($i = 0, $n = count($code); $i < $n;) {
3711. //Reset the attributes for a new line ...
3712. $attrs = array();
3714. // Make lines have at least one space in them if they're empty
3715. // BenBE: Checking emptiness using trim instead of relying on blanks
3716. if ('' == trim($code[$i])) {
3717. $code[$i] = '&nbsp;';
3718. }
3720. // If this is a "special line"...
3721. if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
3722. $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
3723. // Set the attributes to style the line
3724. if ($this->use_classes) {
3725. //$attr = ' class="li2"';
3726. $attrs['class'][] = 'li2';
3727. $def_attr = ' class="de2"';
3728. } else {
3729. //$attr = ' style="' . $this->line_style2 . '"';
3730. $attrs['style'][] = $this->line_style2;
3731. // This style "covers up" the special styles set for special lines
3732. // so that styles applied to special lines don't apply to the actual
3733. // code on that line
3734. $def_attr = ' style="' . $this->code_style . '"';
3735. }
3736. } else {
3737. if ($this->use_classes) {
3738. //$attr = ' class="li1"';
3739. $attrs['class'][] = 'li1';
3740. $def_attr = ' class="de1"';
3741. } else {
3742. //$attr = ' style="' . $this->line_style1 . '"';
3743. $attrs['style'][] = $this->line_style1;
3744. $def_attr = ' style="' . $this->code_style . '"';
3745. }
3746. }
3748. //Check which type of tag to insert for this line
3749. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
3750. $start = "<pre$def_attr>";
3751. $end = '</pre>';
3752. } else {
3753. // Span or div?
3754. $start = "<div$def_attr>";
3755. $end = '</div>';
3756. }
3758. ++$i;
3760. // Are we supposed to use ids? If so, add them
3761. if ($this->add_ids) {
3762. $attrs['id'][] = "$this->overall_id-$i";
3763. }
3765. //Is this some line with extra styles???
3766. if (in_array($i, $this->highlight_extra_lines)) {
3767. if ($this->use_classes) {
3768. if (isset($this->highlight_extra_lines_styles[$i])) {
3769. $attrs['class'][] = "lx$i";
3770. } else {
3771. $attrs['class'][] = "ln-xtra";
3772. }
3773. } else {
3774. array_push($attrs['style'], $this->get_line_style($i));
3775. }
3776. }
3778. // Add in the line surrounded by appropriate list HTML
3779. $attr_string = '';
3780. foreach ($attrs as $key => $attr) {
3781. $attr_string .= ' ' . $key . '="' . implode(' ', $attr) . '"';
3782. }
3784. $parsed_code .= "<li$attr_string>$start{$code[$i-1]}$end</li>$ls";
3785. unset($code[$i - 1]);
3786. }
3787. } else {
3788. $n = count($code);
3789. if ($this->use_classes) {
3790. $attributes = ' class="de1"';
3791. } else {
3792. $attributes = ' style="'. $this->code_style .'"';
3793. }
3794. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
3795. $parsed_code .= '<pre'. $attributes .'>';
3796. } elseif ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
3797. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3798. if ($this->use_classes) {
3799. $attrs = ' class="ln"';
3800. } else {
3801. $attrs = ' style="'. $this->table_linenumber_style .'"';
3802. }
3803. $parsed_code .= '<td'.$attrs.'><pre'.$attributes.'>';
3804. // get linenumbers
3805. // we don't merge it with the for below, since it should be better for
3806. // memory consumption this way
3807. // @todo: but... actually it would still be somewhat nice to merge the two loops
3808. // the mem peaks are at different positions
3809. for ($i = 0; $i < $n; ++$i) {
3810. $close = 0;
3811. // fancy lines
3812. if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
3813. $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
3814. // Set the attributes to style the line
3815. if ($this->use_classes) {
3816. $parsed_code .= '<span class="xtra li2"><span class="de2">';
3817. } else {
3818. // This style "covers up" the special styles set for special lines
3819. // so that styles applied to special lines don't apply to the actual
3820. // code on that line
3821. $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
3822. .'<span style="' . $this->code_style .'">';
3823. }
3824. $close += 2;
3825. }
3826. //Is this some line with extra styles???
3827. if (in_array($i + 1, $this->highlight_extra_lines)) {
3828. if ($this->use_classes) {
3829. if (isset($this->highlight_extra_lines_styles[$i])) {
3830. $parsed_code .= "<span class=\"xtra lx$i\">";
3831. } else {
3832. $parsed_code .= "<span class=\"xtra ln-xtra\">";
3833. }
3834. } else {
3835. $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
3836. }
3837. ++$close;
3838. }
3839. $parsed_code .= $this->line_numbers_start + $i;
3840. if ($close) {
3841. $parsed_code .= str_repeat('</span>', $close);
3842. } else if ($i != $n) {
3843. $parsed_code .= "\n";
3844. }
3845. }
3846. $parsed_code .= '</pre></td><td'.$attributes.'>';
3847. }
3848. $parsed_code .= '<pre'. $attributes .'>';
3849. }
3850. // No line numbers, but still need to handle highlighting lines extra.
3851. // Have to use divs so the full width of the code is highlighted
3852. $close = 0;
3853. for ($i = 0; $i < $n; ++$i) {
3854. // Make lines have at least one space in them if they're empty
3855. // BenBE: Checking emptiness using trim instead of relying on blanks
3856. if ('' == trim($code[$i])) {
3857. $code[$i] = '&nbsp;';
3858. }
3859. // fancy lines
3860. if ($this->line_numbers == <a href="../geshi/core/_geshi.php.html#defineGESHI_FANCY_LINE_NUMBERS">GESHI_FANCY_LINE_NUMBERS</a> &&
3861. $i % $this->line_nth_row == ($this->line_nth_row - 1)) {
3862. // Set the attributes to style the line
3863. if ($this->use_classes) {
3864. $parsed_code .= '<span class="xtra li2"><span class="de2">';
3865. } else {
3866. // This style "covers up" the special styles set for special lines
3867. // so that styles applied to special lines don't apply to the actual
3868. // code on that line
3869. $parsed_code .= '<span style="display:block;' . $this->line_style2 . '">'
3870. .'<span style="' . $this->code_style .'">';
3871. }
3872. $close += 2;
3873. }
3874. //Is this some line with extra styles???
3875. if (in_array($i + 1, $this->highlight_extra_lines)) {
3876. if ($this->use_classes) {
3877. if (isset($this->highlight_extra_lines_styles[$i])) {
3878. $parsed_code .= "<span class=\"xtra lx$i\">";
3879. } else {
3880. $parsed_code .= "<span class=\"xtra ln-xtra\">";
3881. }
3882. } else {
3883. $parsed_code .= "<span style=\"display:block;" . $this->get_line_style($i) . "\">";
3884. }
3885. ++$close;
3886. }
3888. $parsed_code .= $code[$i];
3890. if ($close) {
3891. $parsed_code .= str_repeat('</span>', $close);
3892. $close = 0;
3893. }
3894. elseif ($i + 1 < $n) {
3895. $parsed_code .= "\n";
3896. }
3897. unset($code[$i]);
3898. }
3900. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a>) {
3901. $parsed_code .= '</pre>';
3902. }
3903. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_TABLE">GESHI_HEADER_PRE_TABLE</a> && $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3904. $parsed_code .= '</td>';
3905. }
3906. }
3908. $parsed_code .= $this->footer();
3909. }
3911. /**
3912. * Creates the header for the code block (with correct attributes)
3913. *
3914. * @return string The header for the code block
3915. * @since 1.0.0
3916. * @access private
3917. */
3918. function header() {
3919. // Get attributes needed
3920. /**
3921. * @todo Document behaviour change - class is outputted regardless of whether
3922. * we're using classes or not. Same with style
3923. */
3924. $attributes = ' class="' . $this->language;
3925. if ($this->overall_class != '') {
3926. $attributes .= " ".$this->overall_class;
3927. }
3928. $attributes .= '"';
3930. if ($this->overall_id != '') {
3931. $attributes .= " id=\"{$this->overall_id}\"";
3932. }
3933. if ($this->overall_style != '') {
3934. $attributes .= ' style="' . $this->overall_style . '"';
3935. }
3937. $ol_attributes = '';
3939. if ($this->line_numbers_start != 1) {
3940. $ol_attributes .= ' start="' . $this->line_numbers_start . '"';
3941. }
3943. // Get the header HTML
3944. $header = $this->header_content;
3945. if ($header) {
3946. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
3947. $header = str_replace("\n", '', $header);
3948. }
3949. $header = $this->replace_keywords($header);
3951. if ($this->use_classes) {
3952. $attr = ' class="head"';
3953. } else {
3954. $attr = " style=\"{$this->header_content_style}\"";
3955. }
3956. if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->line_numbers != GESHI_NO_LINE_NUMBERS) {
3957. $header = "<thead><tr><td colspan=\"2\" $attr>$header</td></tr></thead>";
3958. } else {
3959. $header = "<div$attr>$header</div>";
3960. }
3961. }
3963. if (GESHI_HEADER_NONE == $this->header_type) {
3964. if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
3965. return "$header<ol$attributes$ol_attributes>";
3966. }
3967. return $header . ($this->force_code_block ? '<div>' : '');
3968. }
3970. // Work out what to return and do it
3971. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
3972. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>) {
3973. return "<pre$attributes>$header<ol$ol_attributes>";
3974. } else if ($this->header_type == GESHI_HEADER_DIV ||
3975. $this->header_type == GESHI_HEADER_PRE_VALID) {
3976. return "<div$attributes>$header<ol$ol_attributes>";
3977. } else if ($this->header_type == GESHI_HEADER_PRE_TABLE) {
3978. return "<table$attributes>$header<tbody><tr class=\"li1\">";
3979. }
3980. } else {
3981. if ($this->header_type == GESHI_HEADER_PRE) {
3982. return "<pre$attributes>$header" .
3983. ($this->force_code_block ? '<div>' : '');
3984. } else {
3985. return "<div$attributes>$header" .
3986. ($this->force_code_block ? '<div>' : '');
3987. }
3988. }
3989. }
3991. /**
3992. * Returns the footer for the code block.
3993. *
3994. * @return string The footer for the code block
3995. * @since 1.0.0
3996. * @access private
3997. */
3998. function footer() {
3999. $footer = $this->footer_content;
4000. if ($footer) {
4001. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE">GESHI_HEADER_PRE</a>) {
4002. $footer = str_replace("\n", '', $footer);;
4003. }
4004. $footer = $this->replace_keywords($footer);
4006. if ($this->use_classes) {
4007. $attr = ' class="foot"';
4008. } else {
4009. $attr = " style=\"{$this->footer_content_style}\"";
4010. }
4011. if ($this->header_type == GESHI_HEADER_PRE_TABLE && $this->linenumbers != GESHI_NO_LINE_NUMBERS) {
4012. $footer = "<tfoot><tr><td colspan=\"2\">$footer</td></tr></tfoot>";
4013. } else {
4014. $footer = "<div$attr>$footer</div>";
4015. }
4016. }
4018. if (GESHI_HEADER_NONE == $this->header_type) {
4019. return ($this->line_numbers != GESHI_NO_LINE_NUMBERS) ? '</ol>' . $footer : $footer;
4020. }
4022. if ($this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_DIV">GESHI_HEADER_DIV</a> || $this->header_type == <a href="../geshi/core/_geshi.php.html#defineGESHI_HEADER_PRE_VALID">GESHI_HEADER_PRE_VALID</a>) {
4023. if ($this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
4024. return "</ol>$footer</div>";
4025. }
4026. return ($this->force_code_block ? '</div>' : '') .
4027. "$footer</div>";
4028. }
4029. elseif ($this->header_type == GESHI_HEADER_PRE_TABLE) {
4030. if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
4031. return "</tr></tbody>$footer</table>";
4032. }
4033. return ($this->force_code_block ? '</div>' : '') .
4034. "$footer</div>";
4035. }
4036. else {
4037. if ($this->line_numbers != GESHI_NO_LINE_NUMBERS) {
4038. return "</ol>$footer</pre>";
4039. }
4040. return ($this->force_code_block ? '</div>' : '') .
4041. "$footer</pre>";
4042. }
4043. }
4045. /**
4046. * Replaces certain keywords in the header and footer with
4047. * certain configuration values
4048. *
4049. * @param string The header or footer content to do replacement on
4050. * @return string The header or footer with replaced keywords
4051. * @since 1.0.2
4052. * @access private
4053. */
4054. function replace_keywords($instr) {
4055. $keywords = $replacements = array();
4057. $keywords[] = '<TIME>';
4058. $keywords[] = '{TIME}';
4059. $replacements[] = $replacements[] = number_format($time = $this->get_time(), 3);
4061. $keywords[] = '<LANGUAGE>';
4062. $keywords[] = '{LANGUAGE}';
4063. $replacements[] = $replacements[] = $this->language_data['LANG_NAME'];
4065. $keywords[] = '<VERSION>';
4066. $keywords[] = '{VERSION}';
4067. $replacements[] = $replacements[] = <a href="../geshi/core/_geshi.php.html#defineGESHI_VERSION">GESHI_VERSION</a>;
4069. $keywords[] = '<SPEED>';
4070. $keywords[] = '{SPEED}';
4071. if ($time <= 0) {
4072. $speed = 'N/A';
4073. } else {
4074. $speed = strlen($this->source) / $time;
4075. if ($speed >= 1024) {
4076. $speed = sprintf("%.2f KB/s", $speed / 1024.0);
4077. } else {
4078. $speed = sprintf("%.0f B/s", $speed);
4079. }
4080. }
4081. $replacements[] = $replacements[] = $speed;
4083. return str_replace($keywords, $replacements, $instr);
4084. }
4086. /**
4087. * Secure replacement for PHP built-in function htmlspecialchars().
4088. *
4089. * See ticket #427 (http://wush.net/trac/wikka/ticket/427) for the rationale
4090. * for this replacement function.
4091. *
4092. * The INTERFACE for this function is almost the same as that for
4093. * htmlspecialchars(), with the same default for quote style; however, there
4094. * is no 'charset' parameter. The reason for this is as follows:
4095. *
4096. * The PHP docs say:
4097. * "The third argument charset defines character set used in conversion."
4098. *
4099. * I suspect PHP's htmlspecialchars() is working at the byte-value level and
4100. * thus _needs_ to know (or asssume) a character set because the special
4101. * characters to be replaced could exist at different code points in
4102. * different character sets. (If indeed htmlspecialchars() works at
4103. * byte-value level that goes some way towards explaining why the
4104. * vulnerability would exist in this function, too, and not only in
4105. * htmlentities() which certainly is working at byte-value level.)
4106. *
4107. * This replacement function however works at character level and should
4108. * therefore be "immune" to character set differences - so no charset
4109. * parameter is needed or provided. If a third parameter is passed, it will
4110. * be silently ignored.
4111. *
4112. * In the OUTPUT there is a minor difference in that we use '&#39;' instead
4113. * of PHP's '&#039;' for a single quote: this provides compatibility with
4114. * get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)
4115. * (see comment by mikiwoz at yahoo dot co dot uk on
4116. * http://php.net/htmlspecialchars); it also matches the entity definition
4117. * for XML 1.0
4118. * (http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters).
4119. * Like PHP we use a numeric character reference instead of '&apos;' for the
4120. * single quote. For the other special characters we use the named entity
4121. * references, as PHP is doing.
4122. *
4123. * @author {@link http://wikkawiki.org/JavaWoman Marjolein Katsma}
4124. *
4125. * @license http://www.gnu.org/copyleft/lgpl.html
4126. * GNU Lesser General Public License
4127. * @copyright Copyright 2007, {@link http://wikkawiki.org/CreditsPage
4128. * Wikka Development Team}
4129. *
4130. * @access private
4131. * @param string $string string to be converted
4132. * @param integer $quote_style
4133. * - ENT_COMPAT: escapes &, <, > and double quote (default)
4134. * - ENT_NOQUOTES: escapes only &, < and >
4135. * - ENT_QUOTES: escapes &, <, >, double and single quotes
4136. * @return string converted string
4137. * @since 1.0.7.18
4138. */
4139. function hsc($string, $quote_style = ENT_COMPAT) {
4140. // init
4141. static $aTransSpecchar = array(
4142. '&' => '&amp;',
4143. '"' => '&quot;',
4144. '<' => '&lt;',
4145. '>' => '&gt;',
4147. //This fix is related to SF#1923020, but has to be applied
4148. //regardless of actually highlighting symbols.
4150. //Circumvent a bug with symbol highlighting
4151. //This is required as ; would produce undesirable side-effects if it
4152. //was not to be processed as an entity.
4153. ';' => '<SEMI>', // Force ; to be processed as entity
4154. '|' => '<PIPE>' // Force | to be processed as entity
4155. ); // ENT_COMPAT set
4157. switch ($quote_style) {
4158. case ENT_NOQUOTES: // don't convert double quotes
4159. unset($aTransSpecchar['"']);
4160. break;
4161. case ENT_QUOTES: // convert single quotes as well
4162. $aTransSpecchar["'"] = '&#39;'; // (apos) htmlspecialchars() uses '&#039;'
4163. break;
4164. }
4166. // return translated string
4167. return strtr($string, $aTransSpecchar);
4168. }
4170. /**
4171. * Returns a stylesheet for the highlighted code. If $economy mode
4172. * is true, we only return the stylesheet declarations that matter for
4173. * this code block instead of the whole thing
4174. *
4175. * @param boolean Whether to use economy mode or not
4176. * @return string A stylesheet built on the data for the current language
4177. * @since 1.0.0
4178. */
4179. function get_stylesheet($economy_mode = true) {
4180. // If there's an error, chances are that the language file
4181. // won't have populated the language data file, so we can't
4182. // risk getting a stylesheet...
4183. if ($this->error) {
4184. return '';
4185. }
4187. //Check if the style rearrangements have been processed ...
4188. //This also does some preprocessing to check which style groups are useable ...
4189. if(!isset($this->language_data['NUMBERS_CACHE'])) {
4190. $this->build_style_cache();
4191. }
4193. // First, work out what the selector should be. If there's an ID,
4194. // that should be used, the same for a class. Otherwise, a selector
4195. // of '' means that these styles will be applied anywhere
4196. if ($this->overall_id) {
4197. $selector = '#' . $this->overall_id;
4198. } else {
4199. $selector = '.' . $this->language;
4200. if ($this->overall_class) {
4201. $selector .= '.' . $this->overall_class;
4202. }
4203. }
4204. $selector .= ' ';
4206. // Header of the stylesheet
4207. if (!$economy_mode) {
4208. $stylesheet = "/**\n".
4209. " * GeSHi Dynamically Generated Stylesheet\n".
4210. " * --------------------------------------\n".
4211. " * Dynamically generated stylesheet for {$this->language}\n".
4212. " * CSS class: {$this->overall_class}, CSS id: {$this->overall_id}\n".
4213. " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
4214. " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
4215. " * --------------------------------------\n".
4216. " */\n";
4217. } else {
4218. $stylesheet = "/**\n".
4219. " * GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann\n" .
4220. " * (http://qbnz.com/highlighter/ and http://geshi.org/)\n".
4221. " */\n";
4222. }
4224. // Set the <ol> to have no effect at all if there are line numbers
4225. // (<ol>s have margins that should be destroyed so all layout is
4226. // controlled by the set_overall_style method, which works on the
4227. // <pre> or <div> container). Additionally, set default styles for lines
4228. if (!$economy_mode || $this->line_numbers != <a href="../geshi/core/_geshi.php.html#defineGESHI_NO_LINE_NUMBERS">GESHI_NO_LINE_NUMBERS</a>) {
4229. //$stylesheet .= "$selector, {$selector}ol, {$selector}ol li {margin: 0;}\n";
4230. $stylesheet .= "$selector.de1, $selector.de2 {{$this->code_style}}\n";
4231. }
4233. // Add overall styles
4234. // note: neglect economy_mode, empty styles are meaningless
4235. if ($this->overall_style != '') {
4236. $stylesheet .= "$selector {{$this->overall_style}}\n";
4237. }
4239. // Add styles for links
4240. // note: economy mode does not make _any_ sense here
4241. // either the style is empty and thus no selector is needed
4242. // or the appropriate key is given.
4243. foreach ($this->link_styles as $key => $style) {
4244. if ($style != '') {
4245. switch ($key) {
4246. case <a href="../geshi/core/_geshi.php.html#defineGESHI_LINK">GESHI_LINK</a>:
4247. $stylesheet .= "{$selector}a:link {{$style}}\n";
4248. break;
4249. case GESHI_HOVER:
4250. $stylesheet .= "{$selector}a:hover {{$style}}\n";
4251. break;
4252. case GESHI_ACTIVE:
4253. $stylesheet .= "{$selector}a:active {{$style}}\n";
4254. break;
4255. case GESHI_VISITED:
4256. $stylesheet .= "{$selector}a:visited {{$style}}\n";
4257. break;
4258. }
4259. }
4260. }
4262. // Header and footer
4263. // note: neglect economy_mode, empty styles are meaningless
4264. if ($this->header_content_style != '') {
4265. $stylesheet .= "$selector.head {{$this->header_content_style}}\n";
4266. }
4267. if ($this->footer_content_style != '') {
4268. $stylesheet .= "$selector.foot {{$this->footer_content_style}}\n";
4269. }
4271. // Styles for important stuff
4272. // note: neglect economy_mode, empty styles are meaningless
4273. if ($this->important_styles != '') {
4274. $stylesheet .= "$selector.imp {{$this->important_styles}}\n";
4275. }
4277. // Simple line number styles
4278. if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->line_style1 != '') {
4279. $stylesheet .= "{$selector}li, {$selector}.li1 {{$this->line_style1}}\n";
4280. }
4281. if ((!$economy_mode || $this->line_numbers != GESHI_NO_LINE_NUMBERS) && $this->table_linenumber_style != '') {
4282. $stylesheet .= "{$selector}.ln {{$this->table_linenumber_style}}\n";
4283. }
4284. // If there is a style set for fancy line numbers, echo it out
4285. if ((!$economy_mode || $this->line_numbers == GESHI_FANCY_LINE_NUMBERS) && $this->line_style2 != '') {
4286. $stylesheet .= "{$selector}.li2 {{$this->line_style2}}\n";
4287. }
4289. // note: empty styles are meaningless
4290. foreach ($this->language_data['STYLES']['KEYWORDS'] as $group => $styles) {
4291. if ($styles != '' && (!$economy_mode ||
4292. (isset($this->lexic_permissions['KEYWORDS'][$group]) &&
4293. $this->lexic_permissions['KEYWORDS'][$group]))) {
4294. $stylesheet .= "$selector.kw$group {{$styles}}\n";
4295. }
4296. }
4297. foreach ($this->language_data['STYLES']['COMMENTS'] as $group => $styles) {
4298. if ($styles != '' && (!$economy_mode ||
4299. (isset($this->lexic_permissions['COMMENTS'][$group]) &&
4300. $this->lexic_permissions['COMMENTS'][$group]) ||
4301. (!empty($this->language_data['COMMENT_REGEXP']) &&
4302. !empty($this->language_data['COMMENT_REGEXP'][$group])))) {
4303. $stylesheet .= "$selector.co$group {{$styles}}\n";
4304. }
4305. }
4306. foreach ($this->language_data['STYLES']['ESCAPE_CHAR'] as $group => $styles) {
4307. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['ESCAPE_CHAR'])) {
4308. // NEW: since 1.0.8 we have to handle hardescapes
4309. if ($group === 'HARD') {
4310. $group = '_h';
4311. }
4312. $stylesheet .= "$selector.es$group {{$styles}}\n";
4313. }
4314. }
4315. foreach ($this->language_data['STYLES']['BRACKETS'] as $group => $styles) {
4316. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['BRACKETS'])) {
4317. $stylesheet .= "$selector.br$group {{$styles}}\n";
4318. }
4319. }
4320. foreach ($this->language_data['STYLES']['SYMBOLS'] as $group => $styles) {
4321. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['SYMBOLS'])) {
4322. $stylesheet .= "$selector.sy$group {{$styles}}\n";
4323. }
4324. }
4325. foreach ($this->language_data['STYLES']['STRINGS'] as $group => $styles) {
4326. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['STRINGS'])) {
4327. // NEW: since 1.0.8 we have to handle hardquotes
4328. if ($group === 'HARD') {
4329. $group = '_h';
4330. }
4331. $stylesheet .= "$selector.st$group {{$styles}}\n";
4332. }
4333. }
4334. foreach ($this->language_data['STYLES']['NUMBERS'] as $group => $styles) {
4335. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['NUMBERS'])) {
4336. $stylesheet .= "$selector.nu$group {{$styles}}\n";
4337. }
4338. }
4339. foreach ($this->language_data['STYLES']['METHODS'] as $group => $styles) {
4340. if ($styles != '' && (!$economy_mode || $this->lexic_permissions['METHODS'])) {
4341. $stylesheet .= "$selector.me$group {{$styles}}\n";
4342. }
4343. }
4344. // note: neglect economy_mode, empty styles are meaningless
4345. foreach ($this->language_data['STYLES']['SCRIPT'] as $group => $styles) {
4346. if ($styles != '') {
4347. $stylesheet .= "$selector.sc$group {{$styles}}\n";
4348. }
4349. }
4350. foreach ($this->language_data['STYLES']['REGEXPS'] as $group => $styles) {
4351. if ($styles != '' && (!$economy_mode ||
4352. (isset($this->lexic_permissions['REGEXPS'][$group]) &&
4353. $this->lexic_permissions['REGEXPS'][$group]))) {
4354. if (is_array($this->language_data['REGEXPS'][$group]) &&
4355. array_key_exists(GESHI_CLASS, $this->language_data['REGEXPS'][$group])) {
4356. $stylesheet .= "$selector.";
4357. $stylesheet .= $this->language_data['REGEXPS'][$group][GESHI_CLASS];
4358. $stylesheet .= " {{$styles}}\n";
4359. } else {
4360. $stylesheet .= "$selector.re$group {{$styles}}\n";
4361. }
4362. }
4363. }
4364. // Styles for lines being highlighted extra
4365. if (!$economy_mode || (count($this->highlight_extra_lines)!=count($this->highlight_extra_lines_styles))) {
4366. $stylesheet .= "{$selector}.ln-xtra, {$selector}li.ln-xtra, {$selector}div.ln-xtra {{$this->highlight_extra_lines_style}}\n";
4367. }
4368. $stylesheet .= "{$selector}span.xtra { display:block; }\n";
4369. foreach ($this->highlight_extra_lines_styles as $lineid => $linestyle) {
4370. $stylesheet .= "{$selector}.lx$lineid, {$selector}li.lx$lineid, {$selector}div.lx$lineid {{$linestyle}}\n";
4371. }
4373. return $stylesheet;
4374. }
4376. /**
4377. * Get's the style that is used for the specified line
4378. *
4379. * @param int The line number information is requested for
4380. * @access private
4381. * @since 1.0.7.21
4382. */
4383. function get_line_style($line) {
4384. //$style = null;
4385. $style = null;
4386. if (isset($this->highlight_extra_lines_styles[$line])) {
4387. $style = $this->highlight_extra_lines_styles[$line];
4388. } else { // if no "extra" style assigned
4389. $style = $this->highlight_extra_lines_style;
4390. }
4392. return $style;
4393. }
4395. /**
4396. * this functions creates an optimized regular expression list
4397. * of an array of strings.
4398. *
4399. * Example:
4400. * <code>$list = array('faa', 'foo', 'foobar');
4401. * => string 'f(aa|oo(bar)?)'</code>
4402. *
4403. * @param $list array of (unquoted) strings
4404. * @param $regexp_delimiter your regular expression delimiter, @see preg_quote()
4405. * @return string for regular expression
4406. * @author Milian Wolff <mail@milianw.de>
4407. * @since 1.0.8
4408. * @access private
4409. */
4410. function optimize_regexp_list($list, $regexp_delimiter = '/') {
4411. $regex_chars = array('.', '\\', '+', '*', '?', '[', '^', ']', '$',
4412. '(', ')', '{', '}', '=', '!', '<', '>', '|', ':', $regexp_delimiter);
4413. sort($list);
4414. $regexp_list = array('');
4415. $num_subpatterns = 0;
4416. $list_key = 0;
4418. // the tokens which we will use to generate the regexp list
4419. $tokens = array();
4420. $prev_keys = array();
4421. // go through all entries of the list and generate the token list
4422. $cur_len = 0;
4423. for ($i = 0, $i_max = count($list); $i < $i_max; ++$i) {
4424. if ($cur_len > GESHI_MAX_PCRE_LENGTH) {
4425. // seems like the length of this pcre is growing exorbitantly
4426. $regexp_list[++$list_key] = $this->_optimize_regexp_list_tokens_to_string($tokens);
4427. $num_subpatterns = substr_count($regexp_list[$list_key], '(?:');
4428. $tokens = array();
4429. $cur_len = 0;
4430. }
4431. $level = 0;
4432. $entry = preg_quote((string) $list[$i], $regexp_delimiter);
4433. $pointer = &$tokens;
4434. // properly assign the new entry to the correct position in the token array
4435. // possibly generate smaller common denominator keys
4436. while (true) {
4437. // get the common denominator
4438. if (isset($prev_keys[$level])) {
4439. if ($prev_keys[$level] == $entry) {
4440. // this is a duplicate entry, skip it
4441. continue 2;
4442. }
4443. $char = 0;
4444. while (isset($entry[$char]) && isset($prev_keys[$level][$char])
4445. && $entry[$char] == $prev_keys[$level][$char]) {
4446. ++$char;
4447. }
4448. if ($char > 0) {
4449. // this entry has at least some chars in common with the current key
4450. if ($char == strlen($prev_keys[$level])) {
4451. // current key is totally matched, i.e. this entry has just some bits appended
4452. $pointer = &$pointer[$prev_keys[$level]];
4453. } else {
4454. // only part of the keys match
4455. $new_key_part1 = substr($prev_keys[$level], 0, $char);
4456. $new_key_part2 = substr($prev_keys[$level], $char);
4458. if (in_array($new_key_part1[0], $regex_chars)
4459. || in_array($new_key_part2[0], $regex_chars)) {
4460. // this is bad, a regex char as first character
4461. $pointer[$entry] = array('' => true);
4462. array_splice($prev_keys, $level, count($prev_keys), $entry);
4463. $cur_len += strlen($entry);
4464. continue;
4465. } else {
4466. // relocate previous tokens
4467. $pointer[$new_key_part1] = array($new_key_part2 => $pointer[$prev_keys[$level]]);
4468. unset($pointer[$prev_keys[$level]]);
4469. $pointer = &$pointer[$new_key_part1];
4470. // recreate key index
4471. array_splice($prev_keys, $level, count($prev_keys), array($new_key_part1, $new_key_part2));
4472. $cur_len += strlen($new_key_part2);
4473. }
4474. }
4475. ++$level;
4476. $entry = substr($entry, $char);
4477. continue;
4478. }
4479. // else: fall trough, i.e. no common denominator was found
4480. }
4481. if ($level == 0 && !empty($tokens)) {
4482. // we can dump current tokens into the string and throw them away afterwards
4483. $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens);
4484. $new_subpatterns = substr_count($new_entry, '(?:');
4485. if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + $new_subpatterns > GESHI_MAX_PCRE_SUBPATTERNS) {
4486. $regexp_list[++$list_key] = $new_entry;
4487. $num_subpatterns = $new_subpatterns;
4488. } else {
4489. if (!empty($regexp_list[$list_key])) {
4490. $new_entry = '|' . $new_entry;
4491. }
4492. $regexp_list[$list_key] .= $new_entry;
4493. $num_subpatterns += $new_subpatterns;
4494. }
4495. $tokens = array();
4496. $cur_len = 0;
4497. }
4498. // no further common denominator found
4499. $pointer[$entry] = array('' => true);
4500. array_splice($prev_keys, $level, count($prev_keys), $entry);
4502. $cur_len += strlen($entry);
4503. break;
4504. }
4505. unset($list[$i]);
4506. }
4507. // make sure the last tokens get converted as well
4508. $new_entry = $this->_optimize_regexp_list_tokens_to_string($tokens);
4509. if (GESHI_MAX_PCRE_SUBPATTERNS && $num_subpatterns + substr_count($new_entry, '(?:') > GESHI_MAX_PCRE_SUBPATTERNS) {
4510. $regexp_list[++$list_key] = $new_entry;
4511. } else {
4512. if (!empty($regexp_list[$list_key])) {
4513. $new_entry = '|' . $new_entry;
4514. }
4515. $regexp_list[$list_key] .= $new_entry;
4516. }
4517. return $regexp_list;
4518. }
4519. /**
4520. * this function creates the appropriate regexp string of an token array
4521. * you should not call this function directly, @see $this->optimize_regexp_list().
4522. *
4523. * @param &$tokens array of tokens
4524. * @param $recursed bool to know wether we recursed or not
4525. * @return string
4526. * @author Milian Wolff <mail@milianw.de>
4527. * @since 1.0.8
4528. * @access private
4529. */
4530. function _optimize_regexp_list_tokens_to_string(&$tokens, $recursed = false) {
4531. $list = '';
4532. foreach ($tokens as $token => $sub_tokens) {
4533. $list .= $token;
4534. $close_entry = isset($sub_tokens['']);
4535. unset($sub_tokens['']);
4536. if (!empty($sub_tokens)) {
4537. $list .= '(?:' . $this->_optimize_regexp_list_tokens_to_string($sub_tokens, true) . ')';
4538. if ($close_entry) {
4539. // make sub_tokens optional
4540. $list .= '?';
4541. }
4542. }
4543. $list .= '|';
4544. }
4545. if (!$recursed) {
4546. // do some optimizations
4547. // common trailing strings
4548. // BUGGY!
4549. //$list = preg_replace_callback('#(?<=^|\:|\|)\w+?(\w+)(?:\|.+\1)+(?=\|)#', create_function(
4550. // '$matches', 'return "(?:" . preg_replace("#" . preg_quote($matches[1], "#") . "(?=\||$)#", "", $matches[0]) . ")" . $matches[1];'), $list);
4551. // (?:p)? => p?
4552. $list = preg_replace('#\(\?\:(.)\)\?#', '\1?', $list);
4553. // (?:a|b|c|d|...)? => [abcd...]?
4554. // TODO: a|bb|c => [ac]|bb
4555. static $callback_2;
4556. if (!isset($callback_2)) {
4557. $callback_2 = create_function('$matches', 'return "[" . str_replace("|", "", $matches[1]) . "]";');
4558. }
4559. $list = preg_replace_callback('#\(\?\:((?:.\|)+.)\)#', $callback_2, $list);
4560. }
4561. // return $list without trailing pipe
4562. return substr($list, 0, -1);
4563. }
4564. } // End Class GeSHi
4567. if (!function_exists('geshi_highlight')) {
4568. /**
4569. * Easy way to highlight stuff. Behaves just like highlight_string
4570. *
4571. * @param string The code to highlight
4572. * @param string The language to highlight the code in
4573. * @param string The path to the language files. You can leave this blank if you need
4574. * as from version 1.0.7 the path should be automatically detected
4575. * @param boolean Whether to return the result or to echo
4576. * @return string The code highlighted (if $return is true)
4577. * @since 1.0.2
4578. */
4579. function geshi_highlight($string, $language, $path = null, $return = false) {
4580. $geshi = new GeSHi($string, $language, $path);
4581. $geshi->set_header_type(GESHI_HEADER_NONE);
4583. if ($return) {
4584. return '<code>' . $geshi->parse_code() . '</code>';
4585. }
4587. echo '<code>' . $geshi->parse_code() . '</code>';
4589. if ($geshi->error()) {
4590. return false;
4591. }
4592. return true;
4593. }
4594. }
4596. ?>

Documentation generated on Thu, 25 Dec 2008 14:34:52 +0100 by phpDocumentor 1.4.2