Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components.js

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,12 @@
"alias": "md",
"owner": "Golmote"
},
"markup-attributes": {
"title": "Markup attributes",
"require": "markup",
"modify": "markup",
"owner": "RunDevelopment"
},
"markup-templating": {
"title": "Markup templating",
"require": "markup",
Expand Down
92 changes: 92 additions & 0 deletions components/prism-markup-attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
(function (Prism) {

var tag = Prism.languages.markup['tag'];

// A general tag attribute.
var TAG_ATTR = tag.TAG_ATTR;

/**
* Replaces all occurrences of `<KEY>` with the value of `replacements[KEY]`.
*
* @param {string} string
* @param {string[] | Object<string, string>} replacements
* @returns {string}
*/
function replace(string, replacements) {
return string.replace(/<(\w+)>/g, function (m, key) { return '(?:' + replacements[key] + ')'; });
}

/**
* @typedef TagAttributes
* @property {string} attr
* @property {string | undefined} [before=""]
*/

/**
* @param {Attribute[]} attributes
* @returns {TagAttributes | undefined}
*
* @typedef Attribute
* @property {RegExp} name
* @property {RegExp} value
* @property {boolean} [optional=false]
*/
function specificAttr(attributes) {
if (attributes.length === 0) {
return undefined;
}

// We have to do two things: 1) guarantee that non-optional attributes are present and 2) of the attributes that
// are present, we have to guarantee that they have the correct value.
//
// Implementing 1) is relatively easy. After the tag name, we add a lookahead for each non-optional attribute
// checking that the attribute is present. (We do not check the value here.)
//
// Implementing 2) is harder. The basic loop that is used to consume markup attribute looks roughly like the
// following. In the pseudo pattern, AN is a general pattern of an attribute name and AV is a general attribute
// value:
// (\s+AN(=AV)?)*
// What we need here, is a conditional, so we can treat our target attributes differently and to do this, we use
// lookaheads again. In the following, AN<i> will i-th target attribute name and AV<i> will be the i-th
// attribute value:
// (\s+(AN1=AV1|AN2=AV2|...|(?!AN1|AN2|...)AN(=AV)?))*

/**
* @param {Attribute} attr
*/
function getNameSource(attr) {
return attr.name.source;
}

// Implement 1)
var required = '';
for (var attr, i = 0; attr = attributes[i++];) {
if (!attr.optional) {
required += '(?=(?:\\s*(?:' + TAG_ATTR + '))*?\\s*(?:' + getNameSource(attr) + ')\\s*=)';
}
}

// Implement 2)
var attrChoices = '(?!(?:' + attributes.map(getNameSource).join('|') + ')[\\s/>=])(?:' + TAG_ATTR + ')';
for (var attr, i = 0; attr = attributes[i++];) {
var value = replace(
/\s*=\s*(?:"(?=<VALUE>")[^"]*"|'(?=<VALUE>')[^']*'|(?=<VALUE>[\s/>])[^\s'"/>=]+(?=[\s/>]))/.source,
{ VALUE: attr.value.source }
);
attrChoices += '|' + getNameSource(attr) + value;
}

// create the new opening tag pattern
return {
attr: attrChoices,
before: required
};
}

Object.defineProperties(tag, {
specificAttr: { value: specificAttr },
});

tag.addInlined('script', 'none', specificAttr([{ name: /type/, value: /text\/plain/ }]));

}(Prism));
1 change: 1 addition & 0 deletions components/prism-markup-attributes.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

216 changes: 131 additions & 85 deletions components/prism-markup.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,86 @@
Prism.languages.markup = {
'comment': /<!--[\s\S]*?-->/,
'prolog': /<\?[\s\S]+?\?>/,
'doctype': {
// https://www.w3.org/TR/xml/#NT-doctypedecl
pattern: /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,
greedy: true,
inside: {
'internal-subset': {
pattern: /(\[)[\s\S]+(?=\]>$)/,
lookbehind: true,
greedy: true,
inside: null // see below
},
'string': {
pattern: /"[^"]*"|'[^']*'/,
greedy: true
},
'punctuation': /^<!|>$|[[\]]/,
'doctype-tag': /^DOCTYPE/,
'name': /[^\s<>'"]+/
}
},
'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
'tag': {
pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,
greedy: true,
inside: {
'tag': {
pattern: /^<\/?[^\s>\/]+/,
inside: {
'punctuation': /^<\/?/,
'namespace': /^[^\s>\/:]+:/
}
},
'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
inside: {
'punctuation': [
{
pattern: /^=/,
alias: 'attr-equals'
},
/"|'/
]
}
},
'punctuation': /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
'namespace': /^[^\s>\/:]+:/
}
(function (Prism) {

// A general tag attribute.
var TAG_ATTR = /[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'"/>=]+(?=[\s/>]))|(?=[\s/>]))/.source;

Prism.languages.markup = {
'comment': /<!--[\s\S]*?-->/,
'prolog': /<\?[\s\S]+?\?>/,
'doctype': {
// https://www.w3.org/TR/xml/#NT-doctypedecl
pattern: /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,
greedy: true,
inside: {
'internal-subset': {
pattern: /(\[)[\s\S]+(?=\]>$)/,
lookbehind: true,
greedy: true,
inside: null // see below
},
'string': {
pattern: /"[^"]*"|'[^']*'/,
greedy: true
},
'punctuation': /^<!|>$|[[\]]/,
'doctype-tag': /^DOCTYPE/,
'name': /[^\s<>'"]+/
}
},
'cdata': /<!\[CDATA\[[\s\S]*?]]>/i,
'tag': {
pattern: RegExp(/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*<TAG>)+)?\s*\/?>/.source.replace(/<TAG>/g, function () { return TAG_ATTR; })),
greedy: true,
inside: {
'tag': {
pattern: /^<\/?[^\s>\/]+/,
inside: {
'punctuation': /^<\/?/,
'namespace': /^[^\s>\/:]+:/
}
},
'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
inside: {
'punctuation': [
{
pattern: /^=/,
alias: 'attr-equals'
},
/"|'/
]
}
},
'punctuation': /\/?>/,
'attr-name': {
pattern: /[^\s>\/]+/,
inside: {
'namespace': /^[^\s>\/:]+:/
}
}

}
},
'entity': [
{
pattern: /&[\da-z]{1,8};/i,
alias: 'named-entity'
}
},
/&#x?[\da-f]{1,8};/i
]
};
'entity': [
{
pattern: /&[\da-z]{1,8};/i,
alias: 'named-entity'
},
/&#x?[\da-f]{1,8};/i
]
};

Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] =
Prism.languages.markup['entity'];
Prism.languages.markup['doctype'].inside['internal-subset'].inside = Prism.languages.markup;
Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] =
Prism.languages.markup['entity'];
Prism.languages.markup['doctype'].inside['internal-subset'].inside = Prism.languages.markup;

// Plugin to make entity title show the real entity, idea by Roman Komarov
Prism.hooks.add('wrap', function (env) {
// Plugin to make entity title show the real entity, idea by Roman Komarov
Prism.hooks.add('wrap', function (env) {
if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});

if (env.type === 'entity') {
env.attributes['title'] = env.content.replace(/&amp;/, '&');
}
});

Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
/**
* Adds an inlined language to markup.
*
Expand All @@ -85,10 +89,18 @@ Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
* @param {string} tagName The name of the tag that contains the inlined language. This name will be treated as
* case insensitive.
* @param {string} lang The language key.
* @param {TagAttributes} [attributes] An optional record of attributes that have to have a certain value.
* @param {string[]} [before] An optional list of languages. This inline language will be checked before
* the given languages.
* @example
* addInlined('style', 'css');
* addInlined('script', 'none', specificAttr([{ name: 'type': value: /text\/plain/ }]));
*
* @typedef TagAttributes
* @property {string} attr
* @property {string | undefined} [before=""]
*/
value: function addInlined(tagName, lang) {
function addInlined(tagName, lang, attributes, before) {
var includedCdataInside = {};
includedCdataInside['language-' + lang] = {
pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
Expand All @@ -108,23 +120,57 @@ Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
inside: Prism.languages[lang]
};

var def = {};
def[tagName] = {
pattern: RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g, function () { return tagName; }), 'i'),
attributes = attributes || { attr: TAG_ATTR };

// /<<TAG><BEFORE>(\s(\s*<ATTR>)+)?\s*>/
var openingTag = '<'
+ tagName // <TAG>
+ '(?:' + (attributes.before || '') + ')' // <BEFORE>
+ '(?:\\s(?:\\s*(?:' + attributes.attr + '))+)?' // (\s(\s*<ATTR>)+)?
+ '\\s*>';
var closingTag = '</' + tagName + '>';

var pattern = '(' + openingTag + ')'
+ /(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?/.source
+ '(?=' + closingTag + ')';

var token = {
pattern: RegExp(pattern, 'i'),
lookbehind: true,
greedy: true,
inside: inside
};

Prism.languages.insertBefore('markup', 'cdata', def);
/** @type {Array} */
var existingTokens = Prism.languages.markup[tagName];
if (!existingTokens) {
// no existing tokens, so we can just insert the new one before 'cdata'
var def = {};
def[tagName] = [token];
Prism.languages.insertBefore('markup', 'cdata', def);
} else {
// there are some existing tokens for this tag.
var index = existingTokens.length;
if (before) {
// instead of appending this token, we might want to insert it before some existing ones
for (var i = 0, t; t = existingTokens[i++];) {
if (before.some(function (b) { return ('language-' + b) in t.inside; })) {
index = i;
break;
}
}
}
existingTokens.splice(index, 0, token);
}
}
});

Prism.languages.html = Prism.languages.markup;
Prism.languages.mathml = Prism.languages.markup;
Prism.languages.svg = Prism.languages.markup;
Object.defineProperties(Prism.languages.markup['tag'], {
addInlined: { value: addInlined },
TAG_ATTR: { value: TAG_ATTR },
});

Prism.languages.html = Prism.languages.mathml = Prism.languages.svg = Prism.languages.markup;

Prism.languages.rss = Prism.languages.atom = Prism.languages.ssml = Prism.languages.xml = Prism.languages.extend('markup', {});

Prism.languages.xml = Prism.languages.extend('markup', {});
Prism.languages.ssml = Prism.languages.xml;
Prism.languages.atom = Prism.languages.xml;
Prism.languages.rss = Prism.languages.xml;
}(Prism));
2 changes: 1 addition & 1 deletion components/prism-markup.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions plugins/autoloader/prism-autoloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"less": "css",
"lilypond": "scheme",
"markdown": "markup",
"markup-attributes": "markup",
"markup-templating": "markup",
"mongodb": "javascript",
"n4js": "javascript",
Expand Down
Loading