PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/crayon_langs.class.php

https://github.com/xzf158/crayon-syntax-highlighter
PHP | 533 lines | 426 code | 60 blank | 47 comment | 132 complexity | a0f7326dc5791d89829fccc8d8772be0 MD5 | raw file
  1. <?php
  2. require_once ('global.php');
  3. require_once (CRAYON_RESOURCE_PHP);
  4. class CrayonLangsResourceType {
  5. const EXTENSION = 0;
  6. const ALIAS = 1;
  7. const DELIMITER = 2;
  8. }
  9. /* Manages languages once they are loaded. The parser directly loads them, saves them here. */
  10. class CrayonLangs extends CrayonResourceCollection {
  11. // Properties and Constants ===============================================
  12. // CSS classes for known elements
  13. private static $known_elements = array('COMMENT' => 'c', 'PREPROCESSOR' => 'p', 'STRING' => 's', 'KEYWORD' => 'k',
  14. 'STATEMENT' => 'st', 'RESERVED' => 'r', 'TYPE' => 't', 'TAG' => 'ta', 'MODIFIER' => 'm', 'IDENTIFIER' => 'i',
  15. 'ENTITY' => 'e', 'VARIABLE' => 'v', 'CONSTANT' => 'cn', 'OPERATOR' => 'o', 'SYMBOL' => 'sy',
  16. 'NOTATION' => 'n', 'FADED' => 'f', CrayonParser::HTML_CHAR => 'h', CrayonParser::CRAYON_ELEMENT => 'crayon-internal-element');
  17. const DEFAULT_LANG = 'default';
  18. const DEFAULT_LANG_NAME = 'Default';
  19. const RESOURCE_TYPE = 'CrayonLangsResourceType';
  20. // Used to cache the objects, since they are unlikely to change during a single run
  21. private static $resource_cache = array();
  22. // Methods ================================================================
  23. public function __construct() {
  24. $this->set_default(self::DEFAULT_LANG, self::DEFAULT_LANG_NAME);
  25. $this->directory(CRAYON_LANG_PATH);
  26. }
  27. // XXX Override
  28. public function path($id) {
  29. return CRAYON_LANG_PATH . $id . "/$id.txt";
  30. }
  31. // XXX Override
  32. public function load_process() {
  33. parent::load_process();
  34. $this->load_exts();
  35. $this->load_aliases();
  36. $this->load_delimiters(); // TODO check for setting?
  37. }
  38. // XXX Override
  39. public function resource_instance($id, $name = NULL) {
  40. return new CrayonLang($id, $name);
  41. }
  42. // XXX Override
  43. public function add_default() {
  44. $result = parent::add_default();
  45. if ($this->is_state_loading() && !$result) {
  46. // Default not added, must already be loaded, ready to parse
  47. CrayonParser::parse(self::DEFAULT_LANG);
  48. }
  49. }
  50. /* Attempts to detect the language based on extension, otherwise falls back to fallback language given.
  51. * Returns a CrayonLang object. */
  52. public function detect($path, $fallback_id = NULL) {
  53. $this->load();
  54. extract(pathinfo($path));
  55. // If fallback id if given
  56. if ($fallback_id == NULL) {
  57. // Otherwise use global fallback
  58. $fallback_id = CrayonGlobalSettings::get(CrayonSettings::FALLBACK_LANG);
  59. }
  60. // Attempt to use fallback
  61. $fallback = $this->get($fallback_id);
  62. // Use extension before trying fallback
  63. $extension = isset($extension) ? $extension : '';
  64. if ( !empty($extension) && ($lang = $this->ext($extension)) || ($lang = $this->get($extension)) ) {
  65. // If extension is found, attempt to find a language for it.
  66. // If that fails, attempt to load a language with the same id as the extension.
  67. return $lang;
  68. } else if ($fallback != NULL || $fallback = $this->get_default()) {
  69. // Resort to fallback if loaded, or fallback to default
  70. return $fallback;
  71. } else {
  72. // No language found
  73. return NULL;
  74. }
  75. }
  76. /* Load all extensions and add them into each language. */
  77. private function load_exts() {
  78. // Load only once
  79. if (!$this->is_state_loading()) {
  80. return;
  81. }
  82. if ( ($lang_exts = self::load_attr_file(CRAYON_LANG_EXT)) !== FALSE ) {
  83. foreach ($lang_exts as $lang_id=>$exts) {
  84. $lang = $this->get($lang_id);
  85. $lang->ext($exts);
  86. }
  87. }
  88. }
  89. /* Load all extensions and add them into each language. */
  90. private function load_aliases() {
  91. // Load only once
  92. if (!$this->is_state_loading()) {
  93. return;
  94. }
  95. if ( ($lang_aliases = self::load_attr_file(CRAYON_LANG_ALIAS)) !== FALSE ) {
  96. foreach ($lang_aliases as $lang_id=>$aliases) {
  97. $lang = $this->get($lang_id);
  98. $lang->alias($aliases);
  99. }
  100. }
  101. }
  102. /* Load all extensions and add them into each language. */
  103. private function load_delimiters() {
  104. // Load only once
  105. if (!$this->is_state_loading()) {
  106. return;
  107. }
  108. if ( ($lang_delims = self::load_attr_file(CRAYON_LANG_DELIM)) !== FALSE ) {
  109. foreach ($lang_delims as $lang_id=>$delims) {
  110. $lang = $this->get($lang_id);
  111. $lang->delimiter($delims);
  112. }
  113. }
  114. }
  115. // Used to load aliases and extensions to languages
  116. private function load_attr_file($path) {
  117. if ( ($lines = CrayonUtil::lines($path, 'lwc')) !== FALSE) {
  118. $attributes = array(); // key = language id, value = array of attr
  119. foreach ($lines as $line) {
  120. preg_match('#^[\t ]*([^\r\n\t ]+)[\t ]+([^\r\n]+)#', $line, $matches);
  121. if (count($matches) == 3 && $lang = $this->get($matches[1])) {
  122. // If the langauges of the attribute exists, return it in an array
  123. // TODO merge instead of replace key?
  124. $attributes[$matches[1]] = explode(' ', $matches[2]);
  125. }
  126. }
  127. return $attributes;
  128. } else {
  129. CrayonLog::syslog('Could not load attr file: ' . $path);
  130. return FALSE;
  131. }
  132. }
  133. /* Returns the CrayonLang for the given extension */
  134. public function ext($ext) {
  135. $this->load();
  136. foreach ($this->get() as $lang) {
  137. if ($lang->has_ext($ext)) {
  138. return $lang;
  139. }
  140. }
  141. return FALSE;
  142. }
  143. /* Returns the CrayonLang for the given alias */
  144. public function alias($alias) {
  145. $this->load();
  146. foreach ($this->get() as $lang) {
  147. if ($lang->has_alias($alias)) {
  148. return $lang;
  149. }
  150. }
  151. return FALSE;
  152. }
  153. /* Fetches a resource. Type is an int from CrayonLangsResourceType. */
  154. public function fetch($type, $reload = FALSE, $keep_empty_fetches = FALSE) {
  155. $this->load();
  156. if (!array_key_exists($type, self::$resource_cache) || $reload) {
  157. $fetches = array();
  158. foreach ($this->get() as $lang) {
  159. switch ($type) {
  160. case CrayonLangsResourceType::EXTENSION:
  161. $fetch = $lang->ext();
  162. break;
  163. case CrayonLangsResourceType::ALIAS:
  164. $fetch = $lang->alias();
  165. break;
  166. case CrayonLangsResourceType::DELIMITER:
  167. $fetch = $lang->delimiter();
  168. break;
  169. default:
  170. return FALSE;
  171. }
  172. if ( !empty($fetch) || $keep_empty_fetches ) {
  173. $fetches[$lang->id()] = $fetch;
  174. }
  175. }
  176. self::$resource_cache[$type] = $fetches;
  177. }
  178. return self::$resource_cache[$type];
  179. }
  180. public function extensions($reload = FALSE) {
  181. return $this->fetch(CrayonLangsResourceType::EXTENSION, $reload);
  182. }
  183. public function aliases($reload = FALSE) {
  184. return $this->fetch(CrayonLangsResourceType::ALIAS, $reload);
  185. }
  186. public function delimiters($reload = FALSE) {
  187. return $this->fetch(CrayonLangsResourceType::DELIMITER, $reload);
  188. }
  189. public function extensions_inverted($reload = FALSE) {
  190. $extensions = $this->extensions($reload);
  191. $inverted = array();
  192. foreach ($extensions as $lang=>$exts) {
  193. foreach ($exts as $ext) {
  194. $inverted[$ext] = $lang;
  195. }
  196. }
  197. return $inverted;
  198. }
  199. public function ids_and_aliases($reload = FALSE) {
  200. $fetch = $this->fetch(CrayonLangsResourceType::ALIAS, $reload, TRUE);
  201. foreach ($fetch as $id=>$alias_array) {
  202. $ids_and_aliases[] = $id;
  203. foreach ($alias_array as $alias) {
  204. $ids_and_aliases[] = $alias;
  205. }
  206. }
  207. return $ids_and_aliases;
  208. }
  209. /* Return the array of valid elements or a particular element value */
  210. public static function known_elements($name = NULL) {
  211. if ($name === NULL) {
  212. return self::$known_elements;
  213. } else if (is_string($name) && array_key_exists($name, self::$known_elements)) {
  214. return self::$known_elements[$name];
  215. } else {
  216. return FALSE;
  217. }
  218. }
  219. /* Verify an element is valid */
  220. public static function is_known_element($name) {
  221. return self::known_elements($name) !== FALSE;
  222. }
  223. /* Compare two languages by name */
  224. public static function langcmp($a, $b) {
  225. $a = strtolower($a->name());
  226. $b = strtolower($b->name());
  227. if ($a == $b) {
  228. return 0;
  229. } else {
  230. return ($a < $b) ? -1 : 1;
  231. }
  232. }
  233. public static function sort_by_name($langs) {
  234. // Sort by name
  235. usort($langs, 'CrayonLangs::langcmp');
  236. $sorted_lags = array();
  237. foreach ($langs as $lang) {
  238. $sorted_lags[$lang->id()] = $lang;
  239. }
  240. return $sorted_lags;
  241. }
  242. public function is_loaded($id) {
  243. if (is_string($id)) {
  244. return array_key_exists($id, $this->get());
  245. }
  246. return FALSE;
  247. }
  248. public function is_parsed($id = NULL) {
  249. if ($id === NULL) {
  250. // Determine if all langs are successfully parsed
  251. foreach ($this->get() as $lang) {
  252. if ($lang->state() != CrayonLang::PARSED_SUCCESS) {
  253. return FALSE;
  254. }
  255. }
  256. return TRUE;
  257. } else if (($lang = $this->get($id)) != FALSE) {
  258. return $lang->is_parsed();
  259. }
  260. return FALSE;
  261. }
  262. public function is_default($id) {
  263. if (($lang = $this->get($id)) != FALSE) {
  264. return $lang->is_default();
  265. }
  266. return FALSE;
  267. }
  268. }
  269. /* Individual language. */
  270. class CrayonLang extends CrayonVersionResource {
  271. private $ext = array();
  272. private $aliases = array();
  273. private $delimiters = '';
  274. // Associative array of CrayonElement objects
  275. private $elements = array();
  276. //private $regex = '';
  277. private $state = self::UNPARSED;
  278. private $modes = array();
  279. // Whether this language allows Multiple Highlighting from other languages
  280. const PARSED_ERRORS = -1;
  281. const UNPARSED = 0;
  282. const PARSED_SUCCESS = 1;
  283. function __construct($id, $name = NULL) {
  284. parent::__construct($id, $name);
  285. $this->modes = CrayonParser::modes();
  286. }
  287. // Override
  288. function clean_id($id) {
  289. $id = CrayonUtil::space_to_hyphen( strtolower(trim($id)) );
  290. return preg_replace('/[^\w-+#]/msi', '', $id);
  291. }
  292. function ext($ext = NULL) {
  293. if ($ext === NULL) {
  294. return $this->ext;
  295. } else if (is_array($ext) && !empty($ext)) {
  296. foreach ($ext as $e) {
  297. $this->ext($e);
  298. }
  299. } else if (is_string($ext) && !empty($ext) && !in_array($ext, $this->ext)) {
  300. $ext = strtolower($ext);
  301. $ext = str_replace('.', '', $ext);
  302. $this->ext[] = $ext;
  303. }
  304. }
  305. function has_ext($ext) {
  306. return is_string($ext) && in_array($ext, $this->ext);
  307. }
  308. function alias($alias = NULL) {
  309. if ($alias === NULL) {
  310. return $this->aliases;
  311. } else if (is_array($alias) && !empty($alias)) {
  312. foreach ($alias as $a) {
  313. $this->alias($a);
  314. }
  315. } else if (is_string($alias) && !empty($alias) && !in_array($alias, $this->aliases)) {
  316. $alias = strtolower($alias);
  317. $this->aliases[] = $alias;
  318. }
  319. }
  320. function has_alias($alias) {
  321. return is_string($alias) && in_array($alias, $this->aliases);
  322. }
  323. function delimiter($delim = NULL) {
  324. if ($delim === NULL) {
  325. return $this->delimiters;
  326. // Convert to regex for capturing delimiters
  327. } else if (is_string($delim) && !empty($delim)) {
  328. $this->delimiters = '(?:'.$delim.')';
  329. } else if (is_array($delim) && !empty($delim)) {
  330. for ($i = 0; $i < count($delim); $i++) {
  331. $delim[$i] = CrayonUtil::esc_atomic($delim[$i]);
  332. }
  333. $this->delimiters = '(?:'.implode(')|(?:', $delim).')';
  334. }
  335. }
  336. function regex($element = NULL) {
  337. if ($element == NULL) {
  338. $regexes = array();
  339. foreach ($this->elements as $element) {
  340. $regexes[] = $element->regex();
  341. }
  342. return '#' . '(?:('. implode(')|(', array_values($regexes)) . '))' . '#' .
  343. ($this->mode(CrayonParser::CASE_INSENSITIVE) ? 'i' : '') .
  344. ($this->mode(CrayonParser::MULTI_LINE) ? 'm' : '') .
  345. ($this->mode(CrayonParser::SINGLE_LINE) ? 's' : '');
  346. } else if (is_string($element) && array_key_exists($element, $this->elements)) {
  347. return $this->elements[$element]->regex();
  348. }
  349. }
  350. // Retrieve by element name or set by CrayonElement
  351. function element($name, $element = NULL) {
  352. if (is_string($name)) {
  353. $name = trim(strtoupper($name));
  354. if (array_key_exists($name, $this->elements) && $element === NULL) {
  355. return $this->elements[$name];
  356. } else if (@get_class($element) == CRAYON_ELEMENT_CLASS) {
  357. $this->elements[$name] = $element;
  358. }
  359. }
  360. }
  361. function elements() {
  362. return $this->elements;
  363. }
  364. function mode($name = NULL, $value = NULL) {
  365. if (is_string($name) && CrayonParser::is_mode($name)) {
  366. $name = trim(strtoupper($name));
  367. if ($value == NULL && array_key_exists($name, $this->modes)) {
  368. return $this->modes[$name];
  369. } else if (is_string($value)) {
  370. if (CrayonUtil::str_equal_array(trim($value), array('ON', 'YES', '1'))) {
  371. $this->modes[$name] = TRUE;
  372. } else if (CrayonUtil::str_equal_array(trim($value), array('OFF', 'NO', '0'))) {
  373. $this->modes[$name] = FALSE;
  374. }
  375. }
  376. } else {
  377. return $this->modes;
  378. }
  379. }
  380. function state($state = NULL) {
  381. if ($state === NULL) {
  382. return $this->state;
  383. } else if (is_int($state)) {
  384. if ($state < 0) {
  385. $this->state = self::PARSED_ERRORS;
  386. } else if ($state > 0) {
  387. $this->state = self::PARSED_SUCCESS;
  388. } else if ($state == 0) {
  389. $this->state = self::UNPARSED;
  390. }
  391. }
  392. }
  393. function state_info() {
  394. switch ($this->state) {
  395. case self::PARSED_ERRORS :
  396. return 'Parsed With Errors';
  397. case self::PARSED_SUCCESS :
  398. return 'Successfully Parsed';
  399. case self::UNPARSED :
  400. return 'Not Parsed';
  401. default :
  402. return 'Undetermined';
  403. }
  404. }
  405. function is_parsed() {
  406. return ($this->state != self::UNPARSED);
  407. }
  408. function is_default() {
  409. return $this->id() == CrayonLangs::DEFAULT_LANG;
  410. }
  411. }
  412. class CrayonElement {
  413. // The pure regex syntax without any modifiers or delimiters
  414. private $name = '';
  415. private $css = '';
  416. private $regex = '';
  417. private $fallback = '';
  418. private $path = '';
  419. function __construct($name, $path, $regex = '') {
  420. $this->name($name);
  421. $this->path($path);
  422. $this->regex($regex);
  423. }
  424. function __toString() {
  425. return $this->regex();
  426. }
  427. function name($name = NULL) {
  428. if ($name == NULL) {
  429. return $this->name;
  430. } else if (is_string($name)) {
  431. $name = trim(strtoupper($name));
  432. if (CrayonLangs::is_known_element($name)) {
  433. // If known element, set CSS to known class
  434. $this->css(CrayonLangs::known_elements($name));
  435. }
  436. $this->name = $name;
  437. }
  438. }
  439. function regex($regex = NULL) {
  440. if ($regex == NULL) {
  441. return $this->regex;
  442. } else if (is_string($regex)) {
  443. if (($result = CrayonParser::validate_regex($regex, $this)) !== FALSE) {
  444. $this->regex = $result;
  445. } else {
  446. return FALSE;
  447. }
  448. }
  449. }
  450. // Expects: 'class1 class2 class3'
  451. function css($css = NULL) {
  452. if ($css == NULL) {
  453. return $this->css;
  454. } else if (is_string($css)) {
  455. $this->css = CrayonParser::validate_css($css);
  456. }
  457. }
  458. function fallback($fallback = NULL) {
  459. if ($fallback == NULL) {
  460. return $this->fallback;
  461. } else if (is_string($fallback) && CrayonLangs::is_known_element($fallback)) {
  462. $this->fallback = $fallback;
  463. }
  464. }
  465. function path($path = NULL) {
  466. if ($path == NULL) {
  467. return $this->path;
  468. } else if (is_string($path) && @file_exists($path)) {
  469. $this->path = $path;
  470. }
  471. }
  472. }
  473. ?>