Files
2023-11-24 22:35:41 +01:00

189 lines
7.8 KiB
JavaScript

(function(exportTo) {
"use strict";
var selfClosingTags = ["area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"];
/**
* Truncates a given HTML string to the specified length.
* @param {string} text This is the HTMl string to be truncated
* @param {object} options An options object defining how to truncate
* Default values:
* {
* TruncateBy : 'words', // Options are 'words', 'characters' or 'paragraphs'
* TruncateLength : 50, // The count to be used with TruncatedBy
* StripHTML : false, // Whether or not the truncated text should contain HTML tags
* Strict : true, // When set to false the truncated text finish at the end of the word
* Suffix : '...' // Text to be appended to the end of the truncated text
* }
* @return {string} This returns the provided string truncated to the
* length provided by the options. HTML tags may be stripped based
* on the given options.
*/
var truncatise = function(text,options) {
var options = options || {},
text = (text || "").trim(),
truncatedText = "",
currentState = 0,
isEndOfWord = false,
isTagOpen = false,
currentTag = "",
tagStack = [],
nextChar = "";
//Counters
var charCounter = 0,
wordCounter = 0,
paragraphCounter = 0;
//currentState values
var NOT_TAG = 0,
TAG_START = 1,
TAG_ATTRIBUTES = 2;
//Set default values
options.TruncateBy = (options.TruncateBy === undefined
|| typeof options.TruncateBy !== "string"
|| !options.TruncateBy.match(/(word(s)?|character(s)?|paragraph(s)?)/))
? 'words'
: options.TruncateBy.toLowerCase();
options.TruncateLength = (options.TruncateLength === undefined
|| typeof options.TruncateLength !== "number")
? 50
: options.TruncateLength;
options.StripHTML = (options.StripHTML === undefined
|| typeof options.StripHTML !== "boolean")
? false
: options.StripHTML;
options.Strict = (options.Strict === undefined
|| typeof options.Strict !== "boolean")
? true
: options.Strict;
options.Suffix = (options.Suffix === undefined
|| typeof options.Suffix !== "string")
? '...'
: options.Suffix;
if(text === "" || (text.length <= options.TruncateLength && options.StripHTML === false)){
return text;
}
if(options.StripHTML) {
text = String(text).replace(/<br( \/)?>/gi, ' ');
}
//If not splitting on paragraphs we can quickly remove tags using regex
if(options.StripHTML && !options.TruncateBy.match(/(paragraph(s)?)/)){
text = String(text).replace(/<!--(.*?)-->/gm, '').replace(/<\/?[^>]+>/gi, '');
}
//Remove newline seperating paragraphs
text = String(text).replace(/<\/p>(\r?\n)+<p>/gm, '</p><p>');
//Replace double newlines with paragraphs
if(options.StripHTML && String(text).match(/\r?\n\r?\n/)){
text = String(text).replace(/((.+)(\r?\n\r?\n|$))/gi, "<p>$2</p>");
}
for (var pointer = 0; pointer < text.length; pointer++ ) {
var currentChar = text[pointer];
switch(currentChar){
case "<":
if(currentState === NOT_TAG){
currentState = TAG_START;
currentTag = "";
}
if(!options.StripHTML){
truncatedText += currentChar;
}
break;
case ">":
if(currentState === TAG_START || currentState === TAG_ATTRIBUTES){
currentState = NOT_TAG;
currentTag = currentTag.toLowerCase();
if(currentTag === "/p"){
paragraphCounter++;
if(options.StripHTML){
truncatedText += " ";
}
}
// Ignore self-closing tags.
if ((selfClosingTags.indexOf(currentTag) === -1) && (selfClosingTags.indexOf(currentTag + '/') === -1)) {
if(currentTag.indexOf("/") >= 0){
tagStack.pop();
} else {
tagStack.push(currentTag);
}
}
}
if(!options.StripHTML){
truncatedText += currentChar;
}
break;
case " ":
if(currentState === TAG_START){
currentState = TAG_ATTRIBUTES;
}
if(currentState === NOT_TAG){
wordCounter++;
charCounter++;
}
if(currentState === NOT_TAG || !options.StripHTML){
truncatedText += currentChar;
}
break;
default:
if(currentState === NOT_TAG){
charCounter++;
}
if(currentState === TAG_START){
currentTag += currentChar;
}
if(currentState === NOT_TAG || !options.StripHTML){
truncatedText += currentChar;
}
break;
}
nextChar = text[pointer + 1] || "";
isEndOfWord = options.Strict ? true : (!currentChar.match(/[a-zA-ZÇ-Ü']/i) || !nextChar.match(/[a-zA-ZÇ-Ü']/i));
if(options.TruncateBy.match(/word(s)?/i) && options.TruncateLength <= wordCounter){
truncatedText = truncatedText.replace(/\s+$/, '');
break;
}
if(options.TruncateBy.match(/character(s)?/i) && options.TruncateLength <= charCounter && isEndOfWord){
break;
}
if(options.TruncateBy.match(/paragraph(s)?/i) && options.TruncateLength === paragraphCounter){
break;
}
}
if(!options.StripHTML && tagStack.length > 0){
while(tagStack.length > 0){
var tag = tagStack.pop();
if(tag!=="!--"){
truncatedText += "</"+tag+">";
}
}
}
if(pointer < text.length - 1) {
if(truncatedText.match(/<\/p>$/gi)){
truncatedText = truncatedText.replace(/(<\/p>)$/gi, options.Suffix + "$1");
}else{
truncatedText = truncatedText + options.Suffix;
}
}
return truncatedText.trim();
};
// Export to node
if (typeof module !== 'undefined' && module.exports){
return module.exports = truncatise;
}
// Nope, export to the browser instead.
exportTo.truncatise = truncatise;
}(this));