Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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.

5 changes: 5 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,11 @@
"alias": "md",
"owner": "Golmote"
},
"markup-attributes": {
"title": "Markup attributes",
"require": "markup",
"owner": "RunDevelopment"
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If what we're doing now is designed primarily for Vue, would it be worth just making this a full-fledged Vue language for Vue SFCs? If we need this more generally for multiple languages, we can extract it then, but markup-attributes isn't terribly clear as to what the languages actually does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the modification it makes to markup justifies it being a "language". It would be kinda weird if Vue added support for plain text scripts in HTML. Honestly, the whole thing should be split even further: 1 "language" with just the attributes function and 1 language that adds support for plain text scripts.

Also, it's not like the whole thing is useful for just Vue and HTML. ASP.net could also profit from it but I'll need to make some more adjustments for this.

but markup-attributes isn't terribly clear as to what the languages actually does.

True. I can't think of a better name thou...

"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.

221 changes: 136 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,62 @@ 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.markup;
Prism.languages.mathml = Prism.languages.markup;
Prism.languages.svg = Prism.languages.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.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