commonmark
Advanced tools
Comparing version 0.27.0 to 0.28.0
@@ -20,2 +20,4 @@ [0.27.0] | ||
* Removed unused definition. | ||
* Update README.md on overriding softbreak and escaping in | ||
renderer (#118). | ||
@@ -22,0 +24,0 @@ [0.26.0] |
@@ -433,3 +433,3 @@ "use strict"; | ||
container._string_content = | ||
parser.currentLine.slice(parser.offset).replace(/^ *#+ *$/, '').replace(/ +#+ *$/, ''); | ||
parser.currentLine.slice(parser.offset).replace(/^[ \t]*#+[ \t]*$/, '').replace(/[ \t]+#+[ \t]*$/, ''); | ||
parser.advanceOffset(parser.currentLine.length - parser.offset); | ||
@@ -436,0 +436,0 @@ return 2; |
@@ -14,7 +14,5 @@ "use strict"; | ||
module.exports.version = '0.27.0'; | ||
module.exports.Node = require('./node'); | ||
module.exports.Parser = require('./blocks'); | ||
// module.exports.HtmlRenderer = require('./html'); | ||
module.exports.HtmlRenderer = require('./render/html'); | ||
module.exports.XmlRenderer = require('./xml'); | ||
module.exports.XmlRenderer = require('./render/xml'); |
@@ -35,4 +35,2 @@ "use strict"; | ||
var ESCAPED_CHAR = '\\\\' + ESCAPABLE; | ||
var REG_CHAR = '[^\\\\()\\x00-\\x20]'; | ||
var IN_PARENS_NOSP = '\\((' + REG_CHAR + '|' + ESCAPED_CHAR + '|\\\\)*\\)'; | ||
@@ -42,3 +40,3 @@ var ENTITY = common.ENTITY; | ||
var rePunctuation = new RegExp(/[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/); | ||
var rePunctuation = new RegExp(/[!"#$%&'()*+,\-./:;<=>?@\[\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/); | ||
@@ -55,5 +53,2 @@ var reLinkTitle = new RegExp( | ||
var reLinkDestination = new RegExp( | ||
'^(?:' + REG_CHAR + '+|' + ESCAPED_CHAR + '|\\\\|' + IN_PARENS_NOSP + ')*'); | ||
var reEscapable = new RegExp('^' + ESCAPABLE); | ||
@@ -83,4 +78,2 @@ | ||
var reUnicodeWhitespace = /\s+/g; | ||
var reFinalSpace = / *$/; | ||
@@ -268,5 +261,5 @@ | ||
left_flanking = !after_is_whitespace && | ||
!(after_is_punctuation && !before_is_whitespace && !before_is_punctuation); | ||
(!after_is_punctuation || before_is_whitespace || before_is_punctuation); | ||
right_flanking = !before_is_whitespace && | ||
!(before_is_punctuation && !after_is_whitespace && !after_is_punctuation); | ||
(!before_is_punctuation || after_is_whitespace || after_is_punctuation); | ||
if (cc === C_UNDERSCORE) { | ||
@@ -314,2 +307,3 @@ can_open = left_flanking && | ||
numdelims: numdelims, | ||
origdelims: numdelims, | ||
node: node, | ||
@@ -379,3 +373,3 @@ previous: this.delimiters, | ||
odd_match = (closer.can_open || opener.can_close) && | ||
(opener.numdelims + closer.numdelims) % 3 === 0; | ||
(opener.origdelims + closer.origdelims) % 3 === 0; | ||
if (opener.cc === closer.cc && opener.can_open && !odd_match) { | ||
@@ -394,8 +388,4 @@ opener_found = true; | ||
// calculate actual number of delimiters used from closer | ||
if (closer.numdelims < 3 || opener.numdelims < 3) { | ||
use_delims = closer.numdelims <= opener.numdelims ? | ||
closer.numdelims : opener.numdelims; | ||
} else { | ||
use_delims = closer.numdelims % 2 === 0 ? 2 : 1; | ||
} | ||
use_delims = | ||
(closer.numdelims >= 2 && opener.numdelims >= 2) ? 2 : 1; | ||
@@ -501,8 +491,30 @@ opener_inl = opener.node; | ||
if (res === null) { | ||
res = this.match(reLinkDestination); | ||
if (res === null) { | ||
return null; | ||
} else { | ||
return normalizeURI(unescapeString(res)); | ||
// TODO handrolled parser; res should be null or the string | ||
var savepos = this.pos; | ||
var openparens = 0; | ||
var c; | ||
while ((c = this.peek()) !== -1) { | ||
if (c === C_BACKSLASH) { | ||
this.pos += 1; | ||
if (this.peek() !== -1) { | ||
this.pos += 1; | ||
} | ||
} else if (c === C_OPEN_PAREN) { | ||
this.pos += 1; | ||
openparens += 1; | ||
} else if (c === C_CLOSE_PAREN) { | ||
if (openparens < 1) { | ||
break; | ||
} else { | ||
this.pos += 1; | ||
openparens -= 1; | ||
} | ||
} else if (reWhitespaceChar.exec(fromCodePoint(c)) !== null) { | ||
break; | ||
} else { | ||
this.pos += 1; | ||
} | ||
} | ||
res = this.subject.substr(savepos, this.pos - savepos); | ||
return normalizeURI(unescapeString(res)); | ||
} else { // chop off surrounding <..>: | ||
@@ -516,3 +528,5 @@ return normalizeURI(unescapeString(res.substr(1, res.length - 2))); | ||
var m = this.match(reLinkLabel); | ||
if (m === null || m.length > 1001) { | ||
// Note: our regex will allow something of form [..\]; | ||
// we disallow it here rather than using lookahead in the regex: | ||
if (m === null || m.length > 1001 || /[^\\]\\\]$/.exec(m)) { | ||
return 0; | ||
@@ -519,0 +533,0 @@ } else { |
@@ -5,4 +5,2 @@ "use strict"; | ||
var esc = require('../common').escapeXml; | ||
var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i; | ||
@@ -12,4 +10,4 @@ var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i; | ||
var potentiallyUnsafe = function(url) { | ||
return reUnsafeProtocol.test(url) && | ||
!reSafeDataProtocol.test(url); | ||
return reUnsafeProtocol.test(url) && | ||
!reSafeDataProtocol.test(url); | ||
}; | ||
@@ -19,22 +17,21 @@ | ||
function tag(name, attrs, selfclosing) { | ||
if (this.disableTags > 0) { | ||
return; | ||
if (this.disableTags > 0) { | ||
return; | ||
} | ||
this.buffer += ('<' + name); | ||
if (attrs && attrs.length > 0) { | ||
var i = 0; | ||
var attrib; | ||
while ((attrib = attrs[i]) !== undefined) { | ||
this.buffer += (' ' + attrib[0] + '="' + attrib[1] + '"'); | ||
i++; | ||
} | ||
this.buffer += ('<' + name); | ||
if (attrs && attrs.length > 0) { | ||
var i = 0; | ||
var attrib; | ||
while ((attrib = attrs[i]) !== undefined) { | ||
this.buffer += (' ' + attrib[0] + '="' + attrib[1] + '"'); | ||
i++; | ||
} | ||
} | ||
if (selfclosing) { | ||
this.buffer += ' /'; | ||
} | ||
this.buffer += '>'; | ||
this.lastOut = '>'; | ||
} | ||
if (selfclosing) { | ||
this.buffer += ' /'; | ||
} | ||
this.buffer += '>'; | ||
this.lastOut = '>'; | ||
} | ||
function HtmlRenderer(options) { | ||
@@ -70,11 +67,11 @@ options = options || {}; | ||
if (entering) { | ||
if (!(this.options.safe && potentiallyUnsafe(node.destination))) { | ||
attrs.push(['href', esc(node.destination, true)]); | ||
} | ||
if (node.title) { | ||
attrs.push(['title', esc(node.title, true)]); | ||
} | ||
this.tag('a', attrs); | ||
if (!(this.options.safe && potentiallyUnsafe(node.destination))) { | ||
attrs.push(['href', this.esc(node.destination, true)]); | ||
} | ||
if (node.title) { | ||
attrs.push(['title', this.esc(node.title, true)]); | ||
} | ||
this.tag('a', attrs); | ||
} else { | ||
this.tag('/a'); | ||
this.tag('/a'); | ||
} | ||
@@ -85,20 +82,19 @@ } | ||
if (entering) { | ||
if (this.disableTags === 0) { | ||
if (this.options.safe && | ||
potentiallyUnsafe(node.destination)) { | ||
this.lit('<img src="" alt="'); | ||
} else { | ||
this.lit('<img src="' + esc(node.destination, true) + | ||
'" alt="'); | ||
} | ||
if (this.disableTags === 0) { | ||
if (this.options.safe && potentiallyUnsafe(node.destination)) { | ||
this.lit('<img src="" alt="'); | ||
} else { | ||
this.lit('<img src="' + this.esc(node.destination, true) + | ||
'" alt="'); | ||
} | ||
this.disableTags += 1; | ||
} | ||
this.disableTags += 1; | ||
} else { | ||
this.disableTags -= 1; | ||
if (this.disableTags === 0) { | ||
if (node.title) { | ||
this.lit('" title="' + esc(node.title, true)); | ||
} | ||
this.lit('" />'); | ||
this.disableTags -= 1; | ||
if (this.disableTags === 0) { | ||
if (node.title) { | ||
this.lit('" title="' + this.esc(node.title, true)); | ||
} | ||
this.lit('" />'); | ||
} | ||
} | ||
@@ -119,13 +115,13 @@ } | ||
if (grandparent !== null && | ||
grandparent.type === 'list') { | ||
if (grandparent.listTight) { | ||
return; | ||
} | ||
grandparent.type === 'list') { | ||
if (grandparent.listTight) { | ||
return; | ||
} | ||
} | ||
if (entering) { | ||
this.cr(); | ||
this.tag('p', attrs); | ||
this.cr(); | ||
this.tag('p', attrs); | ||
} else { | ||
this.tag('/p'); | ||
this.cr(); | ||
this.tag('/p'); | ||
this.cr(); | ||
} | ||
@@ -138,7 +134,7 @@ } | ||
if (entering) { | ||
this.cr(); | ||
this.tag(tagname, attrs); | ||
this.cr(); | ||
this.tag(tagname, attrs); | ||
} else { | ||
this.tag('/' + tagname); | ||
this.cr(); | ||
this.tag('/' + tagname); | ||
this.cr(); | ||
} | ||
@@ -157,3 +153,3 @@ } | ||
if (info_words.length > 0 && info_words[0].length > 0) { | ||
attrs.push(['class', 'language-' + esc(info_words[0], true)]); | ||
attrs.push(['class', 'language-' + this.esc(info_words[0], true)]); | ||
} | ||
@@ -179,9 +175,9 @@ this.cr(); | ||
if (entering) { | ||
this.cr(); | ||
this.tag('blockquote', attrs); | ||
this.cr(); | ||
this.cr(); | ||
this.tag('blockquote', attrs); | ||
this.cr(); | ||
} else { | ||
this.cr(); | ||
this.tag('/blockquote'); | ||
this.cr(); | ||
this.cr(); | ||
this.tag('/blockquote'); | ||
this.cr(); | ||
} | ||
@@ -195,13 +191,13 @@ } | ||
if (entering) { | ||
var start = node.listStart; | ||
if (start !== null && start !== 1) { | ||
attrs.push(['start', start.toString()]); | ||
} | ||
this.cr(); | ||
this.tag(tagname, attrs); | ||
this.cr(); | ||
var start = node.listStart; | ||
if (start !== null && start !== 1) { | ||
attrs.push(['start', start.toString()]); | ||
} | ||
this.cr(); | ||
this.tag(tagname, attrs); | ||
this.cr(); | ||
} else { | ||
this.cr(); | ||
this.tag('/' + tagname); | ||
this.cr(); | ||
this.cr(); | ||
this.tag('/' + tagname); | ||
this.cr(); | ||
} | ||
@@ -213,6 +209,6 @@ } | ||
if (entering) { | ||
this.tag('li', attrs); | ||
this.tag('li', attrs); | ||
} else { | ||
this.tag('/li'); | ||
this.cr(); | ||
this.tag('/li'); | ||
this.cr(); | ||
} | ||
@@ -223,5 +219,5 @@ } | ||
if (this.options.safe) { | ||
this.lit('<!-- raw HTML omitted -->'); | ||
this.lit('<!-- raw HTML omitted -->'); | ||
} else { | ||
this.lit(node.literal); | ||
this.lit(node.literal); | ||
} | ||
@@ -233,5 +229,5 @@ } | ||
if (this.options.safe) { | ||
this.lit('<!-- raw HTML omitted -->'); | ||
this.lit('<!-- raw HTML omitted -->'); | ||
} else { | ||
this.lit(node.literal); | ||
this.lit(node.literal); | ||
} | ||
@@ -243,5 +239,5 @@ this.cr(); | ||
if (entering && node.onEnter) { | ||
this.lit(node.onEnter); | ||
this.lit(node.onEnter); | ||
} else if (!entering && node.onExit) { | ||
this.lit(node.onExit); | ||
this.lit(node.onExit); | ||
} | ||
@@ -253,5 +249,5 @@ } | ||
if (entering && node.onEnter) { | ||
this.lit(node.onEnter); | ||
this.lit(node.onEnter); | ||
} else if (!entering && node.onExit) { | ||
this.lit(node.onExit); | ||
this.lit(node.onExit); | ||
} | ||
@@ -264,3 +260,3 @@ this.cr(); | ||
function out(s) { | ||
this.lit(esc(s, false)); | ||
this.lit(this.esc(s, false)); | ||
} | ||
@@ -271,8 +267,8 @@ | ||
if (this.options.sourcepos) { | ||
var pos = node.sourcepos; | ||
if (pos) { | ||
att.push(['data-sourcepos', String(pos[0][0]) + ':' + | ||
String(pos[0][1]) + '-' + String(pos[1][0]) + ':' + | ||
String(pos[1][1])]); | ||
} | ||
var pos = node.sourcepos; | ||
if (pos) { | ||
att.push(['data-sourcepos', String(pos[0][0]) + ':' + | ||
String(pos[0][1]) + '-' + String(pos[1][0]) + ':' + | ||
String(pos[1][1])]); | ||
} | ||
} | ||
@@ -305,2 +301,4 @@ return att; | ||
HtmlRenderer.prototype.esc = require('../common').escapeXml; | ||
HtmlRenderer.prototype.out = out; | ||
@@ -307,0 +305,0 @@ HtmlRenderer.prototype.tag = tag; |
@@ -37,6 +37,9 @@ "use strict"; | ||
/** | ||
* Output a newline to the buffer. | ||
*/ | ||
function cr() { | ||
if (this.lastOut !== '\n') { | ||
this.lit('\n'); | ||
} | ||
if (this.lastOut !== '\n') { | ||
this.lit('\n'); | ||
} | ||
} | ||
@@ -55,2 +58,14 @@ | ||
/** | ||
* Escape a string for the target renderer. | ||
* | ||
* Abstract function that should be implemented by concrete | ||
* renderer implementations. | ||
* | ||
* @param str {String} The string to escape. | ||
*/ | ||
function esc(str) { | ||
return str; | ||
} | ||
Renderer.prototype.render = render; | ||
@@ -60,3 +75,4 @@ Renderer.prototype.out = out; | ||
Renderer.prototype.cr = cr; | ||
Renderer.prototype.esc = esc; | ||
module.exports = Renderer; |
{ "name": "commonmark", | ||
"description": "a strongly specified, highly compatible variant of Markdown", | ||
"version": "0.27.0", | ||
"version": "0.28.0", | ||
"homepage": "http://commonmark.org", | ||
@@ -10,5 +10,3 @@ "keywords": | ||
"stmd" ], | ||
"repository": | ||
{ "type": "git", | ||
"url": "https://github.com/jgm/commonmark.js.git" }, | ||
"repository": "jgm/commonmark.js", | ||
"author": "John MacFarlane", | ||
@@ -15,0 +13,0 @@ "bugs": { "url": "https://github.com/jgm/commonmark.js/issues" }, |
@@ -97,10 +97,9 @@ commonmark.js | ||
be replaced with empty strings. | ||
- `softbreak`: specify raw string to be used for a softbreak. | ||
- `esc`: specify a function to be used to escape strings. | ||
It is also possible to override the `escape` and `softbreak` | ||
properties of a renderer. So, to make soft breaks render as hard | ||
breaks in HTML: | ||
For example, to make soft breaks render as hard breaks in HTML: | ||
``` js | ||
var writer = new commonmark.HtmlRenderer; | ||
writer.softbreak = "<br />"; | ||
var writer = new commonmark.HtmlRenderer({softbreak: "<br />"}); | ||
``` | ||
@@ -111,9 +110,9 @@ | ||
``` js | ||
writer.softbreak = " "; | ||
var writer = new commonmark.HtmlRenderer({softbreak: " "}); | ||
``` | ||
To override `escape`, pass it a function with two parameters: | ||
To override `esc`, pass it a function with two parameters: | ||
the first is the string to be escaped, the second is a boolean | ||
that is `true` if the escaped string is to be included in an | ||
attribute. | ||
that is `true` if entities in the string to be escaped should | ||
be preserved. | ||
@@ -120,0 +119,0 @@ In addition to the `HtmlRenderer`, there is an `XmlRenderer`, which |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
429054
17
6216
348
5