ibs-format
Version:
Detect the user-defined identifiers in the text and convert them into HTML tags like bold, italic, strike, and many more having XSS (Cross-site scripting) security with escaping functionality, also detect the links like URLs, email, and IP addresses and w
420 lines (387 loc) • 13.8 kB
JavaScript
function ibsFormat(value, arr, linky, escaping) {
const originalArr = JSON.parse(JSON.stringify(arr));
let output = null;
escaping = escaping && escaping.allowXssEscaping == false ? false : true;
if (value) {
if (escaping) {
value = value.replace(/</g, "<");
value = value.replace(/>/g, ">");
}
value = value.replace(/\n/g, " <br> ");
}
if (value != "" && value != null && value != undefined && arr && arr.length > 0) {
// Preprocess to handle empty formatting markers
if (arr[0].constructor === Array) {
arr.forEach(function (formatConfig) {
let marker = formatConfig[1];
// Create regex to match empty formatting markers (marker followed by optional whitespace then same marker)
let escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
let emptyMarkerRegex = new RegExp(escapedMarker + '\\s*' + escapedMarker, 'g');
// Find all empty marker pairs and track their positions
let matches = [];
let match;
while ((match = emptyMarkerRegex.exec(value)) !== null) {
matches.push({
start: match.index,
end: match.index + match[0].length,
content: match[0]
});
// Reset lastIndex to avoid infinite loop with zero-length matches
if (match.index === emptyMarkerRegex.lastIndex) {
emptyMarkerRegex.lastIndex++;
}
}
// Process matches from end to start to avoid index shifting
for (let i = matches.length - 1; i >= 0; i--) {
let matchObj = matches[i];
let beforeMatch = value.substring(0, matchObj.start);
let afterMatch = value.substring(matchObj.end);
// Replace with just the markers (no formatting)
value = beforeMatch + marker + marker + afterMatch;
}
});
} else {
let marker = arr[1];
let escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
let emptyMarkerRegex = new RegExp(escapedMarker + '\\s*' + escapedMarker, 'g');
let matches = [];
let match;
while ((match = emptyMarkerRegex.exec(value)) !== null) {
matches.push({
start: match.index,
end: match.index + match[0].length,
content: match[0]
});
if (match.index === emptyMarkerRegex.lastIndex) {
emptyMarkerRegex.lastIndex++;
}
}
for (let i = matches.length - 1; i >= 0; i--) {
let matchObj = matches[i];
let beforeMatch = value.substring(0, matchObj.start);
let afterMatch = value.substring(matchObj.end);
value = beforeMatch + marker + marker + afterMatch;
}
}
if (arr[0].constructor === Array) {
arr.map(function (e) {
e[2] = (e[0].length + 1).toString();
if (e[1] == "*") {
value = astericHandler(value, e[0], e[1], parseInt(e[2]), " ");
} else if (e[1] == '**') {
value = doubleAstericHandler(value, e[0], e[1], parseInt(e[2]), " ");
} else {
value = getFormat(value, e[0], e[1], parseInt(e[2]), " ");
}
});
} else {
arr[2] = (arr[0].length + 1).toString();
if (arr[1] == "*") {
value = astericHandler(value, arr[0], arr[1], parseInt(arr[2]), " ");
} else if (arr[1] == '**') {
value = doubleAstericHandler(value, arr[0], arr[1], parseInt(arr[2]), " ");
} else {
value = getFormat(value, arr[0], arr[1], parseInt(arr[2]), " ");
}
}
output = value;
} else {
output = null;
}
if (value != "" && value != null && value != undefined && linky && linky.detectLinks == true) {
let targ;
if (linky.target != null && linky.target != "") {
targ = linky.target;
} else {
targ = "_self";
}
output = linkfy(value, targ);
}
if (output) {
output = output.replace(/ <br> /g, "<br>");
}
output = transformContentInsideEm(output, originalArr);
return output ? output.trim() : "";
}
function transformContentInsideEm(inputString, originalArr) {
// Define the tag-to-replacement mapping as an array with 3 elements: [tag, symbol, priority]
// Match the <em> tag and its content
return inputString.replace(/<em>(.*?)<\/em>/gs, function(match, content) {
// Perform tag-to-symbol replacement inside the <em> content
originalArr.forEach(([tag, replacement]) => {
// Replace opening and closing tags of each mapped tag with the symbol
const openTag = new RegExp(`<${tag}>`, 'g');
const closeTag = new RegExp(`</${tag}>`, 'g');
content = content.replace(openTag, replacement).replace(closeTag, replacement);
});
// Return the modified <em> tag with converted content
return `<em>${content}</em>`;
});
}
function doubleAstericHandler(text, tag, iden, trim, space) {
let box = [];
let arr = [];
let finalText = "";
let a = text.split(" ");
let singleStericCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(/\*\*/g) || []).length == 1
) {
++singleStericCounter;
}
});
singleStericCounter = singleStericCounter - 1;
let flag = "1";
let loopCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(/\*\*/g) || []).length == 1
) {
if (loopCounter == singleStericCounter && loopCounter % 2 == 0) {
} else {
if (flag == "1") {
e = e.replace(/\*\*/g, "<" + tag + ">");
flag = "2";
} else {
e = e.replace(/\*\*/g, "</" + tag + ">");
flag = "1";
}
++loopCounter;
}
}
if (e.length > 1 && (e.match(/\*\*/g) || []).length > 1) {
const n = (e.match(/\*\*/g) || []).length;
for (let i = 0; i < n; i++) {
if (i == 0) {
box.push(e.indexOf(iden, i));
} else {
let len = box.length - 1;
let v = box[len];
v = v + 1;
box.push(e.indexOf(iden, v));
}
}
// Check if there's content between the ** markers
let firstIndex = box[0];
let lastIndex = box[box.length - 1];
let contentBetween = e.substring(firstIndex + 2, lastIndex);
if (contentBetween.trim().length > 0) {
e = e.replace('**', "<" + tag + ">");
e = e.replace('**', "</" + tag + ">");
}
box = [];
}
arr.push(e);
});
arr.forEach(function (e) {
if (e == "") {
finalText = finalText + space;
} else {
finalText = finalText + e + space;
}
});
return finalText;
}
function astericHandler(text, tag, iden, trim, space) {
let box = [];
let arr = [];
let finalText = "";
let a = text.split(" ");
let singleStericCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(/\x2a/g) || []).length == 1
) {
++singleStericCounter;
}
});
singleStericCounter = singleStericCounter - 1;
let flag = "1";
let loopCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(/\x2a/g) || []).length == 1
) {
if (loopCounter == singleStericCounter && loopCounter % 2 == 0) {
} else {
// Check if there's actual content between paired asterisks across words
if (flag == "1") {
e = e.replace(/\x2a/g, "<" + tag + ">");
flag = "2";
} else {
e = e.replace(/\x2a/g, "</" + tag + ">");
flag = "1";
}
++loopCounter;
}
}
if (e.length > 1 && (e.match(/\x2a/g) || []).length > 1) {
const n = (e.match(/\x2a/g) || []).length;
for (let i = 0; i < n; i++) {
if (i == 0) {
box.push(e.indexOf(iden, i));
} else {
let len = box.length - 1;
let v = box[len];
v = v + 1;
box.push(e.indexOf(iden, v));
}
}
let firstIndex = box[0];
let lastIndex = box[box.length - 1];
// Check if there's content between the * markers
let contentBetween = e.substring(firstIndex + 1, lastIndex);
if (contentBetween.trim().length > 0) {
e = replaceChar(e, "<" + tag + ">", firstIndex);
e = replaceChar(e, "</" + tag + ">", lastIndex + trim);
}
box = [];
}
arr.push(e);
});
arr.forEach(function (e) {
if (e == "") {
finalText = finalText + space;
} else {
finalText = finalText + e + space;
}
});
function replaceChar(origString, replaceChar, index) {
let firstPart = origString.substr(0, index);
let lastPart = origString.substr(index + 1);
let newString = firstPart + replaceChar + lastPart;
return newString;
}
return finalText;
}
function getFormat(text, tag, iden, trim, space) {
let box = [];
let arr = [];
let finalText = "";
let a = text.split(" ");
let singleStericCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(new RegExp(iden, "g")) || []).length == 1
) {
++singleStericCounter;
}
});
singleStericCounter = singleStericCounter - 1;
let flag = "1";
let loopCounter = 0;
a.forEach(function (e) {
if (
e.length > 1 &&
e.includes(iden) &&
(e.match(new RegExp(iden, "g")) || []).length == 1
) {
if (loopCounter == singleStericCounter && loopCounter % 2 == 0) {
} else {
if (flag == "1") {
e = e.replace(new RegExp(iden, "g"), "<" + tag + ">");
flag = "2";
} else {
e = e.replace(new RegExp(iden, "g"), "</" + tag + ">");
flag = "1";
}
++loopCounter;
}
}
if (e.length > 1 && (e.match(new RegExp(iden, "g")) || []).length > 1) {
const n = (e.match(new RegExp(iden, "g")) || []).length;
for (let i = 0; i < n; i++) {
if (i == 0) {
box.push(e.indexOf(iden, i));
} else {
let len = box.length - 1;
let v = box[len];
v = v + 1;
box.push(e.indexOf(iden, v));
}
}
let firstIndex = box[0];
let lastIndex = box[box.length - 1];
// Check if there's content between the formatting markers
let contentBetween = e.substring(firstIndex + iden.length, lastIndex);
if (contentBetween.trim().length > 0) {
e = replaceChar(e, "<" + tag + ">", firstIndex);
e = replaceChar(e, "</" + tag + ">", lastIndex + trim);
}
box = [];
}
arr.push(e);
});
arr.forEach(function (e) {
if (e == "") {
finalText = finalText + space;
} else {
finalText = finalText + e + space;
}
});
function replaceChar(origString, replaceChar, index) {
let firstPart = origString.substr(0, index);
let lastPart = origString.substr(index + 1);
let newString = firstPart + replaceChar + lastPart;
return newString;
}
return finalText;
}
function linkfy(text, target) {
let strictUrlExpression =/^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.(?![\p{Extended_Pictographic}])\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.(?![\p{Extended_Pictographic}])\d{1,3}){2})(?!172\.(?![\p{Extended_Pictographic}])(?:1[6-9]|2\d|3[0-1])(?:\.(?![\p{Extended_Pictographic}])\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?![\p{Extended_Pictographic}])(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?![\p{Extended_Pictographic}])(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?![\p{Extended_Pictographic}])(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?![\p{Extended_Pictographic}])(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/giu;
let looseUrlExpression = /^https?\:\/\/[^\/\s]+(\/.*)?$/;
let ip4Expression = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):[0-9]+\b(\/[-a-zA-Z0-9@:%_\+.~#?&\/=]*)?/g;
let emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
let httpVerify = /^((http|https|ftp):\/\/)/;
let strictUrlRegex = new RegExp(strictUrlExpression);
let looseUrlRegex = new RegExp(looseUrlExpression);
let ip4Regex = new RegExp(ip4Expression);
let emailRegexx = new RegExp(emailRegex);
let a = text.split(" ");
let finalText = "";
a.map(function (part, index) {
if (part != " " && part != "" && part != undefined && part != "<br>") {
if (part.match(emailRegexx)) {
let ref = part;
ref = ref.replace(/<[^>]*>?/gm, "");
a[index] = "<a href='mailto:" + ref + "' target='" + target + "'>" + part + "</a>";
} else if (part.match(looseUrlRegex)) {
let ref = part;
ref = ref.replace(/<[^>]*>?/gm, "");
a[index] = "<a href='" + ref + "' target='" + target + "'>" + part + "</a>";
} else if (part.match(strictUrlRegex)) {
let ref = part;
ref = ref.replace(/<[^>]*>?/gm, "");
if (ref.match(httpVerify)) {
a[index] = "<a href='" + ref + "' target='" + target + "'>" + part + "</a>";
} else {
a[index] = "<a href='http://" + ref + "' target='" + target + "'>" + part + "</a>";
}
} else if (part.match(ip4Regex)) {
let ref = part;
ref = ref.replace(/<[^>]*>?/gm, "");
a[index] = "<a href='http://" + ref + "' target='" + target + "'>" + part + "</a>";
}
}
});
a.forEach(function (e) {
if (e == "") {
finalText = finalText + " ";
} else {
finalText = finalText + e + " ";
}
});
return finalText;
}
module.exports.ibsFormat = ibsFormat;