EN
JavaScript - interpret URLs and emails in text replacing them with HTML link elements
5
points
In this short article, we would liek to show how to interpret URLs and emails replacing them with links using JavaScript.
Practical example
Presented solution escapes HTML special characters making produced HTML safe.
// ONLINE-RUNNER:browser;
<!doctype html>
<html>
<head>
<style>
.link { color: #5f5fd0; }
.email { color: #468b36; }
</style>
</head>
<body>
<div id="output"></div>
<script>
function RuleProcessor(expression, replacer) {
if (expression.sticky === false) {
throw new Error('Used regular expression does not have sticky flag.');
}
this.execute = function(index, text) {
expression.lastIndex = index;
return expression.exec(text);
};
this.process = function(match) {
return replacer.apply(null, match);
};
}
function TextProcessor(escape, rules) {
if (escape == null) {
escape = '\\';
} else {
if (escape.length !== 1) {
throw new Error('Escape parameter must be single character.');
}
}
this.processText = function(text) {
var index = 0;
var position = 0;
var omitting = false;
var result = '';
loop:
while (index < text.length) {
var character = text[index];
if (character === escape) {
if (position < index) {
omitting = true;
result += text.substring(position, index);
position = index;
} else {
if (omitting) {
omitting = false;
result += escape;
} else {
omitting = true;
}
}
position += 1;
index += 1;
} else {
for (var i = 0; i < rules.length; ++i) {
var rule = rules[i];
var match = rule.execute(index, text);
if (match) {
var entry = match[0];
if (omitting) {
omitting = false;
result += entry;
} else {
if (position < index) {
result += text.substring(position, index);
position = index;
}
result += rule.process(match);
}
position += entry.length;
index += entry.length;
continue loop;
}
}
index += 1;
}
}
if (position < index) {
result += text.substring(position, index);
}
return result;
};
}
// Helper logic:
function escapeHtml(html) {
var result = '';
var escape = '';
var helper = 0;
var index = 0;
for (; index < html.length; ++index) {
switch (html.charCodeAt(index)) {
case 34: escape = '"'; break; // " // "
case 38: escape = '&'; break; // & // &
case 39: escape = '''; break; // '
case 60: escape = '<'; break; // < // <
case 62: escape = '>'; break; // > // >
default: continue;
}
if (helper < index) {
result += html.substring(helper, index);
}
result += escape;
helper = index + 1;
}
if (helper < index) {
result += html.substring(helper, index);
}
return result;
}
// Hint: link and email patterns base on improved ANGUALR 1.5.8 patterns.
//
var ESCAPE_REGEX = /[&<>]/y;
var LINK_REGEX = /(?:\b[a-z][a-z\d.+-]*:\/{2,3}|\/\/(?![a-z][a-z\d.+-]*:\/{2,3})|\bwww\.)(?:[^:@\s]+(?::[^@\s]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#\s]*)?(?:\?[^#\s]*)?(?:#[^\s]*)?/y;
var EMAIL_REGEX = /(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(?:\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*/y; // 64 + @ + 255 = 320
function interpretEscapes(match) {
switch (match) {
case '&': return '&';
case '<': return '<';
case '>': return '>';
default: throw new Error('Not supported esacape character.');
}
}
function interpretLink(match) {
var href = escapeHtml(match);
return '<a class="link" href="' + href + '">' + href + '</a>';
}
function interpretEmail(match) {
var href = escapeHtml(match);
return '<a class="email" href="mailto:' + href + '">' + href + '</a>';
}
var rules = [
new RuleProcessor(ESCAPE_REGEX, interpretEscapes),
new RuleProcessor(LINK_REGEX, interpretLink),
new RuleProcessor(EMAIL_REGEX, interpretEmail)
];
var processor = new TextProcessor('\\', rules); // '\\' is used as escape character for detected rules in the processed text
// Usage example:
var text = 'This is my email: john@e-mail.com, This is my website: https://john.com';
var html = processor.processText(text);
var output = document.querySelector('#output');
output.innerHTML = html;
</script>
</body>
</html>