Skip to content

Commit

Permalink
linkify-html: don't convert & -> &
Browse files Browse the repository at this point in the history
  • Loading branch information
nfrasser committed Nov 22, 2023
1 parent a126aa0 commit ae47bae
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 108 deletions.
7 changes: 1 addition & 6 deletions packages/linkify-html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@
"url": "git+https://github.com/Hypercontext/linkifyjs.git",
"directory": "packages/linkify-html"
},
"keywords": [
"link",
"autolink",
"url",
"email"
],
"keywords": ["link", "autolink", "url", "email"],
"author": "Hypercontext",
"license": "MIT",
"bugs": {
Expand Down
80 changes: 42 additions & 38 deletions packages/linkify-html/src/linkify-html.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { tokenize as htmlTokenize } from '@nfrasser/simple-html-tokenizer';
import { tokenize, Options} from 'linkifyjs';
import { tokenize, Options } from 'linkifyjs';

const LinkifyResult = 'LinkifyResult';
const StartTag = 'StartTag';
Expand Down Expand Up @@ -32,7 +32,9 @@ export default function linkifyHtml(str, opts = {}) {
// Ignore all the contents of ignored tags
const tagName = token.tagName.toUpperCase();
const isIgnored = tagName === 'A' || options.ignoreTags.indexOf(tagName) >= 0;
if (!isIgnored) { continue; }
if (!isIgnored) {
continue;
}

let preskipLen = linkifiedTokens.length;
skipTagTokens(tagName, tokens, ++i, linkifiedTokens);
Expand All @@ -51,36 +53,42 @@ export default function linkifyHtml(str, opts = {}) {
for (let i = 0; i < linkifiedTokens.length; i++) {
const token = linkifiedTokens[i];
switch (token.type) {
case LinkifyResult:
linkified.push(token.rendered);
break;
case StartTag: {
let link = '<' + token.tagName;
if (token.attributes.length > 0) {
link += ' ' + attributeArrayToStrings(token.attributes).join(' ');
case LinkifyResult:
linkified.push(token.rendered);
break;
case StartTag: {
let link = '<' + token.tagName;
if (token.attributes.length > 0) {
link += ' ' + attributeArrayToStrings(token.attributes).join(' ');
}
if (token.selfClosing) {
link += ' /';
}
link += '>';
linkified.push(link);
break;
}
case EndTag:
linkified.push(`</${token.tagName}>`);
break;
case Chars:
linkified.push(escapeText(token.chars));
break;
case Comment:
linkified.push(`<!--${escapeText(token.chars)}-->`);
break;
case Doctype: {
let doctype = `<!DOCTYPE ${token.name}`;
if (token.publicIdentifier) {
doctype += ` PUBLIC "${token.publicIdentifier}"`;
}
if (token.systemIdentifier) {
doctype += ` "${token.systemIdentifier}"`;
}
doctype += '>';
linkified.push(doctype);
break;
}
if (token.selfClosing) { link += ' /'; }
link += '>';
linkified.push(link);
break;
}
case EndTag:
linkified.push(`</${token.tagName}>`);
break;
case Chars:
linkified.push(escapeText(token.chars));
break;
case Comment:
linkified.push(`<!--${escapeText(token.chars)}-->`);
break;
case Doctype: {
let doctype = `<!DOCTYPE ${token.name}`;
if (token.publicIdentifier) { doctype += ` PUBLIC "${token.publicIdentifier}"`; }
if (token.systemIdentifier) { doctype += ` "${token.systemIdentifier}"`; }
doctype += '>';
linkified.push(doctype);
break;
}
}
}

Expand All @@ -104,14 +112,14 @@ function linkifyChars(str, options) {
type: StartTag,
tagName: 'br',
attributes: [],
selfClosing: true
selfClosing: true,
});
} else if (!token.isLink || !options.check(token)) {
result.push({ type: Chars, chars: token.toString() });
} else {
result.push({
type: LinkifyResult,
rendered: options.render(token)
rendered: options.render(token),
});
}
}
Expand All @@ -134,7 +142,6 @@ function linkifyChars(str, options) {
* Will track whether there is a nested tag of the same type
*/
function skipTagTokens(tagName, tokens, i, skippedTokens) {

// number of tokens of this type on the [fictional] stack
let stackCount = 1;

Expand Down Expand Up @@ -162,10 +169,7 @@ function defaultRender({ tagName, attributes, content }) {
}

function escapeText(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function escapeAttr(attr) {
Expand Down
132 changes: 68 additions & 64 deletions test/spec/linkify-html.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,77 @@ const svg = [
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 801.197 614.273">',
'<rect height="304" width="554" y="50" x="50" stroke="#000" fill="#ff0000" />',
'<rect height="304" width="554" y="150" x="131" stroke="#000" fill="#fff" />',
'</svg>'
'</svg>',
].join('');

describe('linkify-html', () => {

// For each element in this array
// [0] - Original text
// [1] - Linkified with default options
// [2] - Linkified with new options
const tests = [
['Test with no links', 'Test with no links', 'Test with no links'],
[
'Test with no links',
'Test with no links',
'Test with no links'
], [
'The URL is google.com and the email is <strong>[email protected]</strong><br>',
'The URL is <a href="http://google.com">google.com</a> and the email is <strong><a href="mailto:[email protected]">[email protected]</a></strong><br>',
'The URL is <span href="https://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">google.com</span> and the email is <strong><span href="mailto:[email protected]?subject=Hello%20from%20Linkify" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">[email protected]</span></strong><br>'
], [
'The URL is <span href="https://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">google.com</span> and the email is <strong><span href="mailto:[email protected]?subject=Hello%20from%20Linkify" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">[email protected]</span></strong><br>',
],
[
'Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: [email protected]!',
'Super long maps URL <a href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en">https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en</a>, a #hash-tag, and an email: <a href="mailto:[email protected]">[email protected]</a>!',
'Super long maps URL <span href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://www.google.ca/maps/@43.472082,-8…</span>, a #hash-tag, and an email: <span href="mailto:[email protected]?subject=Hello%20from%20Linkify" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">[email protected]</span>!',
], [
],
[
'This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt http://github.com</h1><br />',
'This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt <a href="http://github.com">http://github.com</a></h1><br />',
'This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt <span href="http://github.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://github.com</span></h1><br />'
], [
'This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt <span href="http://github.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://github.com</span></h1><br />',
],
[
'Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/',
'Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/',
'Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/',
'Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/'
], [
],
[
'Ignore tags like <script>const a = {}; a.ca = "Hello";</script> and <style>b.com {color: blue;}</style>',
'Ignore tags like <script>const a = {}; <a href="http://a.ca">a.ca</a> = "Hello";</script> and <style><a href="http://b.com">b.com</a> {color: blue;}</style>',
'Ignore tags like <script>const a = {}; a.ca = "Hello";</script> and <style>b.com {color: blue;}</style>'
], [
'Ignore tags like <script>const a = {}; a.ca = "Hello";</script> and <style>b.com {color: blue;}</style>',
],
[
'Link followed by nbsp escape sequence https://github.com&nbsp;',
'Link followed by nbsp escape sequence <a href="https://github.com">https://github.com</a>\u00a0',
'Link followed by nbsp escape sequence <span href="https://github.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://github.com</span>\u00a0'
], [
'Link followed by nbsp escape sequence <span href="https://github.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://github.com</span>\u00a0',
],
[
'Link surrounded by encoded quotes &quot;http://google.com&quot;',
'Link surrounded by encoded quotes "<a href="http://google.com">http://google.com</a>"',
'Link surrounded by encoded quotes "<span href="http://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://google.com</span>"'
], [
'Link surrounded by encoded quotes "<span href="http://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://google.com</span>"',
],
[
'https:&#x2F;&#x2F;html5-chat.com&#x2F;',
'<a href="https://html5-chat.com/">https://html5-chat.com/</a>',
'<span href="https://html5-chat.com/" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://html5-chat.com/</span>'
], [
'<span href="https://html5-chat.com/" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://html5-chat.com/</span>',
],
[
'Surrounded by lt/gt symbols &lt;http://nu.nl&gt;',
'Surrounded by lt/gt symbols &lt;<a href="http://nu.nl">http://nu.nl</a>&gt;',
'Surrounded by lt/gt symbols &lt;<span href="http://nu.nl" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://nu.nl</span>&gt;'
], [
'Surrounded by lt/gt symbols &lt;<span href="http://nu.nl" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://nu.nl</span>&gt;',
],
[
'http://xml.example.com/pub.dtd?a=1&b=2',
'<a href="http://xml.example.com/pub.dtd?a=1&b=2">http://xml.example.com/pub.dtd?a=1&amp;b=2</a>',
'<span href="http://xml.example.com/pub.dtd?a=1&b=2" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://xml.example.com/pub.dtd?a=1&amp;b=2</span>'
], [
svg,
svg,
svg
], [
'<a href="http://xml.example.com/pub.dtd?a=1&b=2">http://xml.example.com/pub.dtd?a=1&b=2</a>',
'<span href="http://xml.example.com/pub.dtd?a=1&b=2" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">http://xml.example.com/pub.dtd?a=1&b=2</span>',
],
[svg, svg, svg],
[
'Does nl2br.com work?\nYes',
'Does <a href="http://nl2br.com">nl2br.com</a> work?\nYes',
'Does <span href="https://nl2br.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">nl2br.com</span> work?<br />Yes',
]
],
[
'<p>Here is a link and an extra space: google.com &nbsp;</p><p>Here is a link and a greater-than: google.com &gt;</p><p>Here is a link and an ellipsis: google.com &hellip;</p>',
'<p>Here is a link and an extra space: <a href="http://google.com">google.com</a> \u00a0</p><p>Here is a link and a greater-than: <a href="http://google.com">google.com</a> &gt;</p><p>Here is a link and an ellipsis: <a href="http://google.com">google.com</a> &hellip;</p>',
'<p>Here is a link and an extra space: <span href="https://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">google.com</span> \u00a0</p><p>Here is a link and a greater-than: <span href="https://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">google.com</span> &gt;</p><p>Here is a link and an ellipsis: <span href="https://google.com" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">google.com</span> &hellip;</p>',
],
];

let options;
Expand All @@ -80,18 +88,15 @@ describe('linkify-html', () => {
defaultProtocol: 'https',
rel: 'nofollow',
attributes: {
onclick: 'console.log(\'Hello World!\')'
onclick: "console.log('Hello World!')",
},
format(val) {
return val.truncate(40);
},
formatHref: {
email: (href) => href + '?subject=Hello%20from%20Linkify'
email: (href) => href + '?subject=Hello%20from%20Linkify',
},
ignoreTags: [
'script',
'style'
]
ignoreTags: ['script', 'style'],
};
});

Expand All @@ -110,43 +115,44 @@ describe('linkify-html', () => {
it('Works with truncate options (truncate has priority in formatting chars)', () => {
options.truncate = 30;

expect(linkifyHtml(
'Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en',
options
)).to.be.eql(
'Super long maps URL <span href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://www.google.ca/maps/@43…</span>'
expect(
linkifyHtml('Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en', options),
).to.be.eql(
'Super long maps URL <span href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en" class="my-linkify-class" target="_parent" rel="nofollow" onclick="console.log(\'Hello World!\')">https://www.google.ca/maps/@43…</span>',
);
});

it('Works with overriden options (validate)', () => {
const optionsValidate = {
validate: {
url: function (text) {
return /^(http|ftp)s?:\/\//.test(text) || text.slice(0,3) === 'www';
}
}
return /^(http|ftp)s?:\/\//.test(text) || text.slice(0, 3) === 'www';
},
},
};

const testsValidate = [
['1.Test with no links', '1.Test with no links'],
[
'1.Test with no links',
'1.Test with no links'
], [
'2.The URL is google.com and the email is <strong>[email protected]</strong>',
'2.The URL is google.com and the email is <strong><a href="mailto:[email protected]">[email protected]</a></strong>'
], [
'2.The URL is google.com and the email is <strong><a href="mailto:[email protected]">[email protected]</a></strong>',
],
[
'3.Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: [email protected]!',
'3.Super long maps URL <a href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en">https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en</a>, a #hash-tag, and an email: <a href="mailto:[email protected]">[email protected]</a>!'
], [
'3.Super long maps URL <a href="https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en">https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en</a>, a #hash-tag, and an email: <a href="mailto:[email protected]">[email protected]</a>!',
],
[
'4a.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt http://github.com</h1>',
'4a.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt <a href="http://github.com">http://github.com</a></h1>'
], [
'4a.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt <a href="http://github.com">http://github.com</a></h1>',
],
[
'4b.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt github.com</h1>',
'4b.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt github.com</h1>',
'4b.This link is already in an anchor tag <a href="#bro">google.com</a> LOL and this one <h1>isnt github.com</h1>'
], [
],
[
'5.Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/',
'5.Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/'
]
'5.Unterminated anchor tag <a href="http://google.com"> This <em>is a link google.com</em> and this works!! https://reddit.com/r/photography/',
],
];

testsValidate.map(function (test) {
Expand All @@ -160,15 +166,12 @@ describe('linkify-html', () => {
});

it('Works with HTML and overriden options', () => {
const linkified = linkifyHtml(
htmlOptions.original,
htmlOptions.altOptions
);
const linkified = linkifyHtml(htmlOptions.original, htmlOptions.altOptions);
expect(linkified).to.be.oneOf(htmlOptions.linkifiedAlt);
});

it('Treats null target options properly', () => {
let linkified = linkifyHtml('http://google.com', { target: { url: null }});
let linkified = linkifyHtml('http://google.com', { target: { url: null } });
expect(linkified).to.be.eql('<a href="http://google.com">http://google.com</a>');

linkified = linkifyHtml('http://google.com', { target: null });
Expand Down Expand Up @@ -196,7 +199,8 @@ describe('linkify-html', () => {
});

it('Handles mixed-language content', () => {
const input = '這禮拜是我們新的循環 (3/23-4/19), 我將於這週日給 Jeffrey 補課,並且我們會在這期間選另外一個可以上課的日期。';
const input =
'這禮拜是我們新的循環 (3/23-4/19), 我將於這週日給 Jeffrey 補課,並且我們會在這期間選另外一個可以上課的日期。';
expect(linkifyHtml(input)).to.be.ok;
});

Expand Down

0 comments on commit ae47bae

Please sign in to comment.