UNPKG

17.8 kBJavaScriptView Raw
1var _self = (typeof window !== 'undefined')
2 ? window // if in browser
3 : (
4 (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
5 ? self // if in worker
6 : {} // if in node js
7 );
8
9/**
10 * Prism: Lightweight, robust, elegant syntax highlighting
11 * MIT license http://www.opensource.org/licenses/mit-license.php/
12 * @author Lea Verou http://lea.verou.me
13 */
14
15var Prism = (function (_self){
16
17// Private helper vars
18var lang = /\blang(?:uage)?-([\w-]+)\b/i;
19var uniqueId = 0;
20
21
22var _ = {
23 manual: _self.Prism && _self.Prism.manual,
24 disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
25 util: {
26 encode: function encode(tokens) {
27 if (tokens instanceof Token) {
28 return new Token(tokens.type, encode(tokens.content), tokens.alias);
29 } else if (Array.isArray(tokens)) {
30 return tokens.map(encode);
31 } else {
32 return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
33 }
34 },
35
36 type: function (o) {
37 return Object.prototype.toString.call(o).slice(8, -1);
38 },
39
40 objId: function (obj) {
41 if (!obj['__id']) {
42 Object.defineProperty(obj, '__id', { value: ++uniqueId });
43 }
44 return obj['__id'];
45 },
46
47 // Deep clone a language definition (e.g. to extend it)
48 clone: function deepClone(o, visited) {
49 var clone, id, type = _.util.type(o);
50 visited = visited || {};
51
52 switch (type) {
53 case 'Object':
54 id = _.util.objId(o);
55 if (visited[id]) {
56 return visited[id];
57 }
58 clone = {};
59 visited[id] = clone;
60
61 for (var key in o) {
62 if (o.hasOwnProperty(key)) {
63 clone[key] = deepClone(o[key], visited);
64 }
65 }
66
67 return clone;
68
69 case 'Array':
70 id = _.util.objId(o);
71 if (visited[id]) {
72 return visited[id];
73 }
74 clone = [];
75 visited[id] = clone;
76
77 o.forEach(function (v, i) {
78 clone[i] = deepClone(v, visited);
79 });
80
81 return clone;
82
83 default:
84 return o;
85 }
86 },
87
88 /**
89 * Returns the Prism language of the given element set by a `language-xxxx` or `lang-xxxx` class.
90 *
91 * If no language is set for the element or the element is `null` or `undefined`, `none` will be returned.
92 *
93 * @param {Element} element
94 * @returns {string}
95 */
96 getLanguage: function (element) {
97 while (element && !lang.test(element.className)) {
98 element = element.parentElement;
99 }
100 if (element) {
101 return (element.className.match(lang) || [, 'none'])[1].toLowerCase();
102 }
103 return 'none';
104 },
105
106 /**
107 * Returns the script element that is currently executing.
108 *
109 * This does __not__ work for line script element.
110 *
111 * @returns {HTMLScriptElement | null}
112 */
113 currentScript: function () {
114 if (typeof document === 'undefined') {
115 return null;
116 }
117 if ('currentScript' in document) {
118 return document.currentScript;
119 }
120
121 // IE11 workaround
122 // we'll get the src of the current script by parsing IE11's error stack trace
123 // this will not work for inline scripts
124
125 try {
126 throw new Error();
127 } catch (err) {
128 // Get file src url from stack. Specifically works with the format of stack traces in IE.
129 // A stack will look like this:
130 //
131 // Error
132 // at _.util.currentScript (http://localhost/components/prism-core.js:119:5)
133 // at Global code (http://localhost/components/prism-core.js:606:1)
134
135 var src = (/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(err.stack) || [])[1];
136 if (src) {
137 var scripts = document.getElementsByTagName('script');
138 for (var i in scripts) {
139 if (scripts[i].src == src) {
140 return scripts[i];
141 }
142 }
143 }
144 return null;
145 }
146 }
147 },
148
149 languages: {
150 extend: function (id, redef) {
151 var lang = _.util.clone(_.languages[id]);
152
153 for (var key in redef) {
154 lang[key] = redef[key];
155 }
156
157 return lang;
158 },
159
160 /**
161 * Insert a token before another token in a language literal
162 * As this needs to recreate the object (we cannot actually insert before keys in object literals),
163 * we cannot just provide an object, we need an object and a key.
164 * @param inside The key (or language id) of the parent
165 * @param before The key to insert before.
166 * @param insert Object with the key/value pairs to insert
167 * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
168 */
169 insertBefore: function (inside, before, insert, root) {
170 root = root || _.languages;
171 var grammar = root[inside];
172 var ret = {};
173
174 for (var token in grammar) {
175 if (grammar.hasOwnProperty(token)) {
176
177 if (token == before) {
178 for (var newToken in insert) {
179 if (insert.hasOwnProperty(newToken)) {
180 ret[newToken] = insert[newToken];
181 }
182 }
183 }
184
185 // Do not insert token which also occur in insert. See #1525
186 if (!insert.hasOwnProperty(token)) {
187 ret[token] = grammar[token];
188 }
189 }
190 }
191
192 var old = root[inside];
193 root[inside] = ret;
194
195 // Update references in other language definitions
196 _.languages.DFS(_.languages, function(key, value) {
197 if (value === old && key != inside) {
198 this[key] = ret;
199 }
200 });
201
202 return ret;
203 },
204
205 // Traverse a language definition with Depth First Search
206 DFS: function DFS(o, callback, type, visited) {
207 visited = visited || {};
208
209 var objId = _.util.objId;
210
211 for (var i in o) {
212 if (o.hasOwnProperty(i)) {
213 callback.call(o, i, o[i], type || i);
214
215 var property = o[i],
216 propertyType = _.util.type(property);
217
218 if (propertyType === 'Object' && !visited[objId(property)]) {
219 visited[objId(property)] = true;
220 DFS(property, callback, null, visited);
221 }
222 else if (propertyType === 'Array' && !visited[objId(property)]) {
223 visited[objId(property)] = true;
224 DFS(property, callback, i, visited);
225 }
226 }
227 }
228 }
229 },
230 plugins: {},
231
232 highlightAll: function(async, callback) {
233 _.highlightAllUnder(document, async, callback);
234 },
235
236 highlightAllUnder: function(container, async, callback) {
237 var env = {
238 callback: callback,
239 container: container,
240 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
241 };
242
243 _.hooks.run('before-highlightall', env);
244
245 env.elements = Array.prototype.slice.apply(env.container.querySelectorAll(env.selector));
246
247 _.hooks.run('before-all-elements-highlight', env);
248
249 for (var i = 0, element; element = env.elements[i++];) {
250 _.highlightElement(element, async === true, env.callback);
251 }
252 },
253
254 highlightElement: function(element, async, callback) {
255 // Find language
256 var language = _.util.getLanguage(element);
257 var grammar = _.languages[language];
258
259 // Set language on the element, if not present
260 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
261
262 // Set language on the parent, for styling
263 var parent = element.parentNode;
264 if (parent && parent.nodeName.toLowerCase() === 'pre') {
265 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
266 }
267
268 var code = element.textContent;
269
270 var env = {
271 element: element,
272 language: language,
273 grammar: grammar,
274 code: code
275 };
276
277 function insertHighlightedCode(highlightedCode) {
278 env.highlightedCode = highlightedCode;
279
280 _.hooks.run('before-insert', env);
281
282 env.element.innerHTML = env.highlightedCode;
283
284 _.hooks.run('after-highlight', env);
285 _.hooks.run('complete', env);
286 callback && callback.call(env.element);
287 }
288
289 _.hooks.run('before-sanity-check', env);
290
291 if (!env.code) {
292 _.hooks.run('complete', env);
293 callback && callback.call(env.element);
294 return;
295 }
296
297 _.hooks.run('before-highlight', env);
298
299 if (!env.grammar) {
300 insertHighlightedCode(_.util.encode(env.code));
301 return;
302 }
303
304 if (async && _self.Worker) {
305 var worker = new Worker(_.filename);
306
307 worker.onmessage = function(evt) {
308 insertHighlightedCode(evt.data);
309 };
310
311 worker.postMessage(JSON.stringify({
312 language: env.language,
313 code: env.code,
314 immediateClose: true
315 }));
316 }
317 else {
318 insertHighlightedCode(_.highlight(env.code, env.grammar, env.language));
319 }
320 },
321
322 highlight: function (text, grammar, language) {
323 var env = {
324 code: text,
325 grammar: grammar,
326 language: language
327 };
328 _.hooks.run('before-tokenize', env);
329 env.tokens = _.tokenize(env.code, env.grammar);
330 _.hooks.run('after-tokenize', env);
331 return Token.stringify(_.util.encode(env.tokens), env.language);
332 },
333
334 tokenize: function(text, grammar) {
335 var rest = grammar.rest;
336 if (rest) {
337 for (var token in rest) {
338 grammar[token] = rest[token];
339 }
340
341 delete grammar.rest;
342 }
343
344 var tokenList = new LinkedList();
345 addAfter(tokenList, tokenList.head, text);
346
347 matchGrammar(text, tokenList, grammar, tokenList.head, 0);
348
349 return toArray(tokenList);
350 },
351
352 hooks: {
353 all: {},
354
355 add: function (name, callback) {
356 var hooks = _.hooks.all;
357
358 hooks[name] = hooks[name] || [];
359
360 hooks[name].push(callback);
361 },
362
363 run: function (name, env) {
364 var callbacks = _.hooks.all[name];
365
366 if (!callbacks || !callbacks.length) {
367 return;
368 }
369
370 for (var i=0, callback; callback = callbacks[i++];) {
371 callback(env);
372 }
373 }
374 },
375
376 Token: Token
377};
378
379_self.Prism = _;
380
381function Token(type, content, alias, matchedStr, greedy) {
382 this.type = type;
383 this.content = content;
384 this.alias = alias;
385 // Copy of the full string this token was created from
386 this.length = (matchedStr || '').length|0;
387 this.greedy = !!greedy;
388}
389
390Token.stringify = function stringify(o, language) {
391 if (typeof o == 'string') {
392 return o;
393 }
394 if (Array.isArray(o)) {
395 var s = '';
396 o.forEach(function (e) {
397 s += stringify(e, language);
398 });
399 return s;
400 }
401
402 var env = {
403 type: o.type,
404 content: stringify(o.content, language),
405 tag: 'span',
406 classes: ['token', o.type],
407 attributes: {},
408 language: language
409 };
410
411 var aliases = o.alias;
412 if (aliases) {
413 if (Array.isArray(aliases)) {
414 Array.prototype.push.apply(env.classes, aliases);
415 } else {
416 env.classes.push(aliases);
417 }
418 }
419
420 _.hooks.run('wrap', env);
421
422 var attributes = '';
423 for (var name in env.attributes) {
424 attributes += ' ' + name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
425 }
426
427 return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + attributes + '>' + env.content + '</' + env.tag + '>';
428};
429
430/**
431 * @param {string} text
432 * @param {LinkedList<string | Token>} tokenList
433 * @param {any} grammar
434 * @param {LinkedListNode<string | Token>} startNode
435 * @param {number} startPos
436 * @param {boolean} [oneshot=false]
437 * @param {string} [target]
438 */
439function matchGrammar(text, tokenList, grammar, startNode, startPos, oneshot, target) {
440 for (var token in grammar) {
441 if (!grammar.hasOwnProperty(token) || !grammar[token]) {
442 continue;
443 }
444
445 var patterns = grammar[token];
446 patterns = Array.isArray(patterns) ? patterns : [patterns];
447
448 for (var j = 0; j < patterns.length; ++j) {
449 if (target && target == token + ',' + j) {
450 return;
451 }
452
453 var pattern = patterns[j],
454 inside = pattern.inside,
455 lookbehind = !!pattern.lookbehind,
456 greedy = !!pattern.greedy,
457 lookbehindLength = 0,
458 alias = pattern.alias;
459
460 if (greedy && !pattern.pattern.global) {
461 // Without the global flag, lastIndex won't work
462 var flags = pattern.pattern.toString().match(/[imsuy]*$/)[0];
463 pattern.pattern = RegExp(pattern.pattern.source, flags + 'g');
464 }
465
466 pattern = pattern.pattern || pattern;
467
468 for ( // iterate the token list and keep track of the current token/string position
469 var currentNode = startNode.next, pos = startPos;
470 currentNode !== tokenList.tail;
471 pos += currentNode.value.length, currentNode = currentNode.next
472 ) {
473
474 var str = currentNode.value;
475
476 if (tokenList.length > text.length) {
477 // Something went terribly wrong, ABORT, ABORT!
478 return;
479 }
480
481 if (str instanceof Token) {
482 continue;
483 }
484
485 var removeCount = 1; // this is the to parameter of removeBetween
486
487 if (greedy && currentNode != tokenList.tail.prev) {
488 pattern.lastIndex = pos;
489 var match = pattern.exec(text);
490 if (!match) {
491 break;
492 }
493
494 var from = match.index + (lookbehind && match[1] ? match[1].length : 0);
495 var to = match.index + match[0].length;
496 var p = pos;
497
498 // find the node that contains the match
499 p += currentNode.value.length;
500 while (from >= p) {
501 currentNode = currentNode.next;
502 p += currentNode.value.length;
503 }
504 // adjust pos (and p)
505 p -= currentNode.value.length;
506 pos = p;
507
508 // the current node is a Token, then the match starts inside another Token, which is invalid
509 if (currentNode.value instanceof Token) {
510 continue;
511 }
512
513 // find the last node which is affected by this match
514 for (
515 var k = currentNode;
516 k !== tokenList.tail && (p < to || (typeof k.value === 'string' && !k.prev.value.greedy));
517 k = k.next
518 ) {
519 removeCount++;
520 p += k.value.length;
521 }
522 removeCount--;
523
524 // replace with the new match
525 str = text.slice(pos, p);
526 match.index -= pos;
527 } else {
528 pattern.lastIndex = 0;
529
530 var match = pattern.exec(str);
531 }
532
533 if (!match) {
534 if (oneshot) {
535 break;
536 }
537
538 continue;
539 }
540
541 if (lookbehind) {
542 lookbehindLength = match[1] ? match[1].length : 0;
543 }
544
545 var from = match.index + lookbehindLength,
546 match = match[0].slice(lookbehindLength),
547 to = from + match.length,
548 before = str.slice(0, from),
549 after = str.slice(to);
550
551 var removeFrom = currentNode.prev;
552
553 if (before) {
554 removeFrom = addAfter(tokenList, removeFrom, before);
555 pos += before.length;
556 }
557
558 removeRange(tokenList, removeFrom, removeCount);
559
560 var wrapped = new Token(token, inside ? _.tokenize(match, inside) : match, alias, match, greedy);
561 currentNode = addAfter(tokenList, removeFrom, wrapped);
562
563 if (after) {
564 addAfter(tokenList, currentNode, after);
565 }
566
567
568 if (removeCount > 1)
569 matchGrammar(text, tokenList, grammar, currentNode.prev, pos, true, token + ',' + j);
570
571 if (oneshot)
572 break;
573 }
574 }
575 }
576}
577
578/**
579 * @typedef LinkedListNode
580 * @property {T} value
581 * @property {LinkedListNode<T> | null} prev The previous node.
582 * @property {LinkedListNode<T> | null} next The next node.
583 * @template T
584 */
585
586/**
587 * @template T
588 */
589function LinkedList() {
590 /** @type {LinkedListNode<T>} */
591 var head = { value: null, prev: null, next: null };
592 /** @type {LinkedListNode<T>} */
593 var tail = { value: null, prev: head, next: null };
594 head.next = tail;
595
596 /** @type {LinkedListNode<T>} */
597 this.head = head;
598 /** @type {LinkedListNode<T>} */
599 this.tail = tail;
600 this.length = 0;
601}
602
603/**
604 * Adds a new node with the given value to the list.
605 * @param {LinkedList<T>} list
606 * @param {LinkedListNode<T>} node
607 * @param {T} value
608 * @returns {LinkedListNode<T>} The added node.
609 * @template T
610 */
611function addAfter(list, node, value) {
612 // assumes that node != list.tail && values.length >= 0
613 var next = node.next;
614
615 var newNode = { value: value, prev: node, next: next };
616 node.next = newNode;
617 next.prev = newNode;
618 list.length++;
619
620 return newNode;
621}
622/**
623 * Removes `count` nodes after the given node. The given node will not be removed.
624 * @param {LinkedList<T>} list
625 * @param {LinkedListNode<T>} node
626 * @param {number} count
627 * @template T
628 */
629function removeRange(list, node, count) {
630 var next = node.next;
631 for (var i = 0; i < count && next !== list.tail; i++) {
632 next = next.next;
633 }
634 node.next = next;
635 next.prev = node;
636 list.length -= i;
637}
638/**
639 * @param {LinkedList<T>} list
640 * @returns {T[]}
641 * @template T
642 */
643function toArray(list) {
644 var array = [];
645 var node = list.head.next;
646 while (node !== list.tail) {
647 array.push(node.value);
648 node = node.next;
649 }
650 return array;
651}
652
653
654if (!_self.document) {
655 if (!_self.addEventListener) {
656 // in Node.js
657 return _;
658 }
659
660 if (!_.disableWorkerMessageHandler) {
661 // In worker
662 _self.addEventListener('message', function (evt) {
663 var message = JSON.parse(evt.data),
664 lang = message.language,
665 code = message.code,
666 immediateClose = message.immediateClose;
667
668 _self.postMessage(_.highlight(code, _.languages[lang], lang));
669 if (immediateClose) {
670 _self.close();
671 }
672 }, false);
673 }
674
675 return _;
676}
677
678//Get current script and highlight
679var script = _.util.currentScript();
680
681if (script) {
682 _.filename = script.src;
683
684 if (script.hasAttribute('data-manual')) {
685 _.manual = true;
686 }
687}
688
689function highlightAutomaticallyCallback() {
690 if (!_.manual) {
691 _.highlightAll();
692 }
693}
694
695if (!_.manual) {
696 // If the document state is "loading", then we'll use DOMContentLoaded.
697 // If the document state is "interactive" and the prism.js script is deferred, then we'll also use the
698 // DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they
699 // might take longer one animation frame to execute which can create a race condition where only some plugins have
700 // been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded.
701 // See https://github.com/PrismJS/prism/issues/2102
702 var readyState = document.readyState;
703 if (readyState === 'loading' || readyState === 'interactive' && script && script.defer) {
704 document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback);
705 } else {
706 if (window.requestAnimationFrame) {
707 window.requestAnimationFrame(highlightAutomaticallyCallback);
708 } else {
709 window.setTimeout(highlightAutomaticallyCallback, 16);
710 }
711 }
712}
713
714return _;
715
716})(_self);
717
718if (typeof module !== 'undefined' && module.exports) {
719 module.exports = Prism;
720}
721
722// hack for components to work correctly in node.js
723if (typeof global !== 'undefined') {
724 global.Prism = Prism;
725}