jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
function Utility() {}
/**
* @static
* @method truncate
* @param {String} string string needs to be truncated
* @param {Number} maxLength length of truncated string
* @param {Object} options (optional)
* @param {Boolean} [options.keepImageTag] flag to specify if keep image tag, false by default
* @param {Boolean|String} [options.ellipsis] omission symbol for truncated string, '...' by default
* @param {Boolean} [options.truncateLastWord] truncates last word, true by default
* @return {String} truncated string
*/
Utility.truncate = function (string, maxLength, options) {
var EMPTY_OBJECT = {},
EMPTY_STRING = '',
DEFAULT_TRUNCATE_SYMBOL = '...',
EXCLUDE_TAGS = ['img'], // non-closed tags
items = [], // stack for saving tags
total = 0, // record how many characters we traced so far
content = EMPTY_STRING, // truncated text storage
KEY_VALUE_REGEX = '(\\w+\\s*=\\s*"[^"]*"\\s*)*',
IS_CLOSE_REGEX = '\\s*\\/?\\s*',
CLOSE_REGEX = '\\s*\\/\\s*',
SELF_CLOSE_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + CLOSE_REGEX + '>'),
HTML_TAG_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'),
IMAGE_TAG_REGEX = new RegExp('<img\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'),
matches = true,
result,
index,
tail,
tag,
selfClose;
/**
* @private
* @method _removeImageTag
* @param {String} string not-yet-processed string
* @description helper to dump all close tags and append to truncated content while reaching upperbound
* @return {String} string without image tags
*/
function _removeImageTag(string) {
var match = IMAGE_TAG_REGEX.exec(string),
index,
len;
if (!match) {
return string;
}
index = match.index;
len = match[0].length;
return string.substring(0, index) + string.substring(index + len);
}
/**
* @private
* @method _dumpCloseTag
* @param {String[]} tags a list of tags which should be closed
* @description helper to dump all close tags and append to truncated content while reaching upperbound
* @return {String} well-formatted html
*/
function _dumpCloseTag(tags) {
var html = '';
tags.reverse().forEach(function (tag, index) {
// dump non-excluded tags only
if (-1 === EXCLUDE_TAGS.indexOf(tag)) {
html += '</' + tag + '>';
}
});
return html;
}
/**
* @private
* @method _getTag
* @param {String} string original html
* @description processed tag string to get pure tag name
* @return {String} tag name
*/
function _getTag(string) {
var tail = string.indexOf(' ');
// TODO:
// we have to figure out how to handle non-well-formatted HTML case
if (-1 === tail) {
tail = string.indexOf('>');
if (-1 === tail) {
throw new Error('HTML tag is not well-formed : ' + string);
}
}
return string.substring(1, tail);
}
options = options || EMPTY_OBJECT;
options.ellipsis = options.ellipsis || DEFAULT_TRUNCATE_SYMBOL;
options.truncateLastWord = (options.truncateLastWord === undefined) ? true : options.truncateLastWord;
while (matches) {
matches = HTML_TAG_REGEX.exec(string);
if (!matches) {
if (total < maxLength) {
content += string.substring(0, maxLength - total);
}
break;
}
result = matches[0];
index = matches.index;
if (total + index > maxLength) {
// exceed given `maxLength`, dump everything to clear stack
content += (string.substring(0, maxLength - total));
break;
} else {
total += index;
content += string.substring(0, index);
}
if ('/' === result[1]) {
// move out open tag
items.pop();
} else {
selfClose = SELF_CLOSE_REGEX.exec(result);
if (!selfClose) {
tag = _getTag(result);
items.push(tag);
}
}
if (selfClose) {
content += selfClose[0];
} else {
content += result;
}
string = string.substring(index + result.length);
}
if (string.length > maxLength && options.ellipsis) {
if (options.truncateLastWord) {
content += options.ellipsis;
} else {
content = content.replace(/ \w*$/, options.ellipsis);
}
}
content += _dumpCloseTag(items);
if (!options.keepImageTag) {
content = _removeImageTag(content);
}
return content;
};
var htmlTextTruncate = function(text, limit, postfix, forceClosingTags) {
if (!limit || text.length < limit) return text;
var tags = [], count = 0, finalPos = 0;
for (var i = 0; i < text.length; i++) {
var symbol = text.charAt(i);
if (symbol === "<") {
var tail = text.indexOf(">", i);
if (tail < 0) return text;
var source = text.substring(i + 1, tail);
var tag = {"name": "", "closing": false};
if (source.charAt(0) === "/") {
tag.closing = true;
source = source.substring(1);
}
tag.name = source.match(/(\w)+/)[0];
if (tag.closing) {
var current = tags.pop();
if (!current || current.name !== tag.name) return text;
}
i = tail;
} else if (symbol === "&" && text.substring(i).match(/^(\S)+;/)) {
i = text.indexOf(";", i);
} else {
if (count === limit) {
finalPos = i;
break;
}
count++;
}
}
if (finalPos || forceClosingTags) {
if (finalPos) {
text = text.substring(0, finalPos) + (postfix || "");
}
for (var i = tags.length - 1; i >= 0; i--) {
text += "</" + tags[i].name + ">";
}
}
return text;
};
var htmlTextTruncate2 = function(text, limit, postfix, forceClosingTags) {
if (!limit || text.length < limit) return text;
var state = {"tags": [], "count": 0, "position": 0, "lastWordChar": 0};
var finalState;
var copyState = function(state) {
return {
"position": state.position,
"count": state.count,
"lastWordChar": state.lastWordChar,
"tags": state.tags.slice()
};
};
var checkWordEnd = function(state, symbol) {
if (
(symbol.length !== 1 && symbol !== " ")
|| /^\w$/.test(symbol)
) {
state.lastWordChar = state.position;
if (state.position === text.length - 1) {
state.position++;
finalState = copyState(state);
return true;
}
} else if (state.lastWordChar == state.position - 1) {
finalState = copyState(state);
return true;
}
return false;
}
for (var i = 0; i < text.length; i++) {
var symbol = text.charAt(i);
state.position = i;
if (symbol === "<") {
//checkWordEnd(state, symbol, text);
var tail = text.indexOf(">", i);
if (tail < 0) return text;
var source = text.substring(i + 1, tail);
var tag = {"name": "", "closing": false};
if (source.charAt(0) === "/") {
tag.closing = true;
source = source.substring(1);
}
tag.name = source.match(/(\w)+/)[0];
if (tag.closing) {
var current = state.tags.pop();
if (!current || current.name !== tag.name) return text;
}
i = tail;
} else if (symbol === "&" && text.substring(i).test(/^(\S)+;/)) {
var _t = text.indexOf(";", i);
//checkWordEnd(state, text.substring(i, _t));
i = _t;
state.count++;
} else {
if (state.count === limit) {
break;
}
//checkWordEnd(state, symbol);
state.count++;
}
}
if ((state.count === limit && state.position < text.length) || forceClosingTags) {
state = finalState || state;
if (state.position) {
text = text.substring(0, state.position) + (postfix || "");
}
for (var i = state.tags.length - 1; i >= 0; i--) {
text += "</" + state.tags[i].name + ">";
}
}
return text;
};
var html = "\n <div class=\"echo-item-text\">#nyc #broadway</div>\n <div class=\"echo-item-files\">\n <div class=\"echo-item-photo\">\n <a _target=\"blank\" href=\"http://media.getchute.com/m/64LtQwiql/c/2318453\">\n <img width=\"200\" data-src-full=\"http://media.getchute.com/m/64LtQwiql/c/2318453\" data-src-web=\"http://media.getchute.com/m/64LtQwiql/c/2318453\" alt=\"http://media.getchute.com/m/64LtQwiql/c/2318453\" data-src-preview=\"http://media.getchute.com/media/64LtQwiql/200x200\" src=\"http://media.getchute.com/m/64LtQwiql/c/2318453\" title=\"http://media.getchute.com/m/64LtQwiql/c/2318453\">\n </a>\n </div>\n <div>alskdjfalskdjfla;sd alkdsjfalskdjf alskdjflaksdjflkasjdfl;kj asldkjflaskdjfl;askdjflk laskdjflaksjdflkjasdlkfj alsdkjflaskdjflkasdjfl;kasd salkdjflaskdjflkasdj aldskjfalsdkjflaksd laskdjflaskdjflaskdjf alsdkjflasdkjflas;kdj asldkfjsalkjflk asldkjflasdkjflk asdlkfjalsdkjfla;skd asldkfjalsdkjfl;asdf alkfjalsdkjf asdflkjasdlkjf aldskfjalsdkjfa aldsfkja;sdkfj al;sdkfjalsdkfjlk;a alskdfjalskdjfa; alkfjalsdkjflaksdfj alkdsjfalsdkjflaks alskdfjlaskdjflkasj alsdkjflaskdjflask asldkjfalskjfdalskdj asdlkfjalsdjflaskdjf asdlkfjalskdfjlaskd alsdkfjalsdkjflaksdjf asldkjf lkjasldfkjalskj dlaskdjflaskdj ljadfslkjfl jsaldkjflaksdjflaskdj lkafdsjl fkjasdlf </div></div>\n ";
Ready to run.
Test | Ops/sec | |
---|---|---|
Utility.truncate |
| ready |
htmlTextTruncate |
| ready |
htmlTextTruncate2 |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.