tutanota/libs/linkify-html.js
hrb-hub 45bbfe3135 Bump linkifyjs and linkify-html to 4.3.1
This version has a fix for breaking mails that contain HTML comments
closed with +2 dashes.

HTML comments closed with more than two dashes weren't handled correctly
by linkify. Causing the comments' closing `>` to be escaped and replaced
by `>`
This resulted in actual markup being commented out.

Example:
`<div><!--comment--->Actual markup</div>`
becomes
`<div><!--comment---&gt;Actual markup&lt;/div&gt;-->`
2025-05-15 11:36:46 +02:00

1004 lines
34 KiB
JavaScript
Vendored
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Options, tokenize as tokenize$1 } from 'linkifyjs';
/**
* generated from https://raw.githubusercontent.com/w3c/html/26b5126f96f736f796b9e29718138919dd513744/entities.json
* do not edit
*/
var HTML5NamedCharRefs = {
// We don't need the complete named character reference because linkifyHtml
// does not modify the escape sequences. We do need &nbsp; so that
// whitespace is parsed properly. Other types of whitespace should already
// be accounted for. &gt; &lt; and &quot; are also frequently relevant ones
amp: "&",
gt: ">",
lt: "<",
nbsp: " ",
quot: "\""
};
var HEXCHARCODE = /^#[xX]([A-Fa-f0-9]+)$/;
var CHARCODE = /^#([0-9]+)$/;
var NAMED = /^([A-Za-z0-9]+)$/;
var EntityParser = /** @class */function () {
function EntityParser(named) {
this.named = named;
}
EntityParser.prototype.parse = function (entity) {
if (!entity) {
return;
}
var matches = entity.match(HEXCHARCODE);
if (matches) {
return String.fromCharCode(parseInt(matches[1], 16));
}
matches = entity.match(CHARCODE);
if (matches) {
return String.fromCharCode(parseInt(matches[1], 10));
}
matches = entity.match(NAMED);
if (matches) {
return this.named[matches[1]] || "&" + matches[1] + ";";
}
};
return EntityParser;
}();
var WSP = /[\t\n\f ]/;
var ALPHA = /[A-Za-z]/;
var CRLF = /\r\n?/g;
function isSpace(char) {
return WSP.test(char);
}
function isAlpha(char) {
return ALPHA.test(char);
}
function preprocessInput(input) {
return input.replace(CRLF, '\n');
}
var EventedTokenizer = /** @class */function () {
function EventedTokenizer(delegate, entityParser, mode) {
if (mode === void 0) {
mode = 'precompile';
}
this.delegate = delegate;
this.entityParser = entityParser;
this.mode = mode;
this.state = "beforeData" /* beforeData */;
this.line = -1;
this.column = -1;
this.input = '';
this.index = -1;
this.tagNameBuffer = '';
this.states = {
beforeData: function () {
var char = this.peek();
if (char === '<' && !this.isIgnoredEndTag()) {
this.transitionTo("tagOpen" /* tagOpen */);
this.markTagStart();
this.consume();
} else {
if (this.mode === 'precompile' && char === '\n') {
var tag = this.tagNameBuffer.toLowerCase();
if (tag === 'pre' || tag === 'textarea') {
this.consume();
}
}
this.transitionTo("data" /* data */);
this.delegate.beginData();
}
},
data: function () {
var char = this.peek();
var tag = this.tagNameBuffer;
if (char === '<' && !this.isIgnoredEndTag()) {
this.delegate.finishData();
this.transitionTo("tagOpen" /* tagOpen */);
this.markTagStart();
this.consume();
} else if (char === '&' && tag !== 'script' && tag !== 'style') {
this.consume();
this.delegate.appendToData(this.consumeCharRef() || '&');
} else {
this.consume();
this.delegate.appendToData(char);
}
},
tagOpen: function () {
var char = this.consume();
if (char === '!') {
this.transitionTo("markupDeclarationOpen" /* markupDeclarationOpen */);
} else if (char === '/') {
this.transitionTo("endTagOpen" /* endTagOpen */);
} else if (char === '@' || char === ':' || isAlpha(char)) {
this.transitionTo("tagName" /* tagName */);
this.tagNameBuffer = '';
this.delegate.beginStartTag();
this.appendToTagName(char);
}
},
markupDeclarationOpen: function () {
var char = this.consume();
if (char === '-' && this.peek() === '-') {
this.consume();
this.transitionTo("commentStart" /* commentStart */);
this.delegate.beginComment();
} else {
var maybeDoctype = char.toUpperCase() + this.input.substring(this.index, this.index + 6).toUpperCase();
if (maybeDoctype === 'DOCTYPE') {
this.consume();
this.consume();
this.consume();
this.consume();
this.consume();
this.consume();
this.transitionTo("doctype" /* doctype */);
if (this.delegate.beginDoctype) this.delegate.beginDoctype();
}
}
},
doctype: function () {
var char = this.consume();
if (isSpace(char)) {
this.transitionTo("beforeDoctypeName" /* beforeDoctypeName */);
}
},
beforeDoctypeName: function () {
var char = this.consume();
if (isSpace(char)) {
return;
} else {
this.transitionTo("doctypeName" /* doctypeName */);
if (this.delegate.appendToDoctypeName) this.delegate.appendToDoctypeName(char.toLowerCase());
}
},
doctypeName: function () {
var char = this.consume();
if (isSpace(char)) {
this.transitionTo("afterDoctypeName" /* afterDoctypeName */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
if (this.delegate.appendToDoctypeName) this.delegate.appendToDoctypeName(char.toLowerCase());
}
},
afterDoctypeName: function () {
var char = this.consume();
if (isSpace(char)) {
return;
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
var nextSixChars = char.toUpperCase() + this.input.substring(this.index, this.index + 5).toUpperCase();
var isPublic = nextSixChars.toUpperCase() === 'PUBLIC';
var isSystem = nextSixChars.toUpperCase() === 'SYSTEM';
if (isPublic || isSystem) {
this.consume();
this.consume();
this.consume();
this.consume();
this.consume();
this.consume();
}
if (isPublic) {
this.transitionTo("afterDoctypePublicKeyword" /* afterDoctypePublicKeyword */);
} else if (isSystem) {
this.transitionTo("afterDoctypeSystemKeyword" /* afterDoctypeSystemKeyword */);
}
}
},
afterDoctypePublicKeyword: function () {
var char = this.peek();
if (isSpace(char)) {
this.transitionTo("beforeDoctypePublicIdentifier" /* beforeDoctypePublicIdentifier */);
this.consume();
} else if (char === '"') {
this.transitionTo("doctypePublicIdentifierDoubleQuoted" /* doctypePublicIdentifierDoubleQuoted */);
this.consume();
} else if (char === "'") {
this.transitionTo("doctypePublicIdentifierSingleQuoted" /* doctypePublicIdentifierSingleQuoted */);
this.consume();
} else if (char === '>') {
this.consume();
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
}
},
doctypePublicIdentifierDoubleQuoted: function () {
var char = this.consume();
if (char === '"') {
this.transitionTo("afterDoctypePublicIdentifier" /* afterDoctypePublicIdentifier */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
if (this.delegate.appendToDoctypePublicIdentifier) this.delegate.appendToDoctypePublicIdentifier(char);
}
},
doctypePublicIdentifierSingleQuoted: function () {
var char = this.consume();
if (char === "'") {
this.transitionTo("afterDoctypePublicIdentifier" /* afterDoctypePublicIdentifier */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
if (this.delegate.appendToDoctypePublicIdentifier) this.delegate.appendToDoctypePublicIdentifier(char);
}
},
afterDoctypePublicIdentifier: function () {
var char = this.consume();
if (isSpace(char)) {
this.transitionTo("betweenDoctypePublicAndSystemIdentifiers" /* betweenDoctypePublicAndSystemIdentifiers */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else if (char === '"') {
this.transitionTo("doctypeSystemIdentifierDoubleQuoted" /* doctypeSystemIdentifierDoubleQuoted */);
} else if (char === "'") {
this.transitionTo("doctypeSystemIdentifierSingleQuoted" /* doctypeSystemIdentifierSingleQuoted */);
}
},
betweenDoctypePublicAndSystemIdentifiers: function () {
var char = this.consume();
if (isSpace(char)) {
return;
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else if (char === '"') {
this.transitionTo("doctypeSystemIdentifierDoubleQuoted" /* doctypeSystemIdentifierDoubleQuoted */);
} else if (char === "'") {
this.transitionTo("doctypeSystemIdentifierSingleQuoted" /* doctypeSystemIdentifierSingleQuoted */);
}
},
doctypeSystemIdentifierDoubleQuoted: function () {
var char = this.consume();
if (char === '"') {
this.transitionTo("afterDoctypeSystemIdentifier" /* afterDoctypeSystemIdentifier */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
if (this.delegate.appendToDoctypeSystemIdentifier) this.delegate.appendToDoctypeSystemIdentifier(char);
}
},
doctypeSystemIdentifierSingleQuoted: function () {
var char = this.consume();
if (char === "'") {
this.transitionTo("afterDoctypeSystemIdentifier" /* afterDoctypeSystemIdentifier */);
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
} else {
if (this.delegate.appendToDoctypeSystemIdentifier) this.delegate.appendToDoctypeSystemIdentifier(char);
}
},
afterDoctypeSystemIdentifier: function () {
var char = this.consume();
if (isSpace(char)) {
return;
} else if (char === '>') {
if (this.delegate.endDoctype) this.delegate.endDoctype();
this.transitionTo("beforeData" /* beforeData */);
}
},
commentStart: function () {
var char = this.consume();
if (char === '-' && this.peek() === '-') {
this.transitionTo("commentStartDash" /* commentStartDash */);
} else if (char === '>') {
this.delegate.finishComment();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.delegate.appendToCommentData(char);
this.transitionTo("comment" /* comment */);
}
},
commentStartDash: function () {
var char = this.consume();
if (char === '-') {
this.transitionTo("commentEnd" /* commentEnd */);
} else if (char === '>') {
this.delegate.finishComment();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.delegate.appendToCommentData('-');
this.transitionTo("comment" /* comment */);
}
},
comment: function () {
var char = this.consume();
if (char === '-') {
this.transitionTo("commentEndDash" /* commentEndDash */);
} else {
this.delegate.appendToCommentData(char);
}
},
commentEndDash: function () {
var char = this.consume();
if (char === '-' && this.peek() === '-') {
this.delegate.appendToCommentData(char);
} else if (char === '-') {
this.transitionTo("commentEnd" /* commentEnd */);
} else {
this.delegate.appendToCommentData('-' + char);
this.transitionTo("comment" /* comment */);
}
},
commentEnd: function () {
var char = this.consume();
if (char === '>') {
this.delegate.finishComment();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.delegate.appendToCommentData('--' + char);
this.transitionTo("comment" /* comment */);
}
},
tagName: function () {
var char = this.consume();
if (isSpace(char)) {
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
} else if (char === '/') {
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
} else if (char === '>') {
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.appendToTagName(char);
}
},
endTagName: function () {
var char = this.consume();
if (isSpace(char)) {
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
this.tagNameBuffer = '';
} else if (char === '/') {
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
this.tagNameBuffer = '';
} else if (char === '>') {
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
this.tagNameBuffer = '';
} else {
this.appendToTagName(char);
}
},
beforeAttributeName: function () {
var char = this.peek();
if (isSpace(char)) {
this.consume();
return;
} else if (char === '/') {
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
this.consume();
} else if (char === '>') {
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else if (char === '=') {
this.delegate.reportSyntaxError('attribute name cannot start with equals sign');
this.transitionTo("attributeName" /* attributeName */);
this.delegate.beginAttribute();
this.consume();
this.delegate.appendToAttributeName(char);
} else {
this.transitionTo("attributeName" /* attributeName */);
this.delegate.beginAttribute();
}
},
attributeName: function () {
var char = this.peek();
if (isSpace(char)) {
this.transitionTo("afterAttributeName" /* afterAttributeName */);
this.consume();
} else if (char === '/') {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.consume();
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
} else if (char === '=') {
this.transitionTo("beforeAttributeValue" /* beforeAttributeValue */);
this.consume();
} else if (char === '>') {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else if (char === '"' || char === "'" || char === '<') {
this.delegate.reportSyntaxError(char + ' is not a valid character within attribute names');
this.consume();
this.delegate.appendToAttributeName(char);
} else {
this.consume();
this.delegate.appendToAttributeName(char);
}
},
afterAttributeName: function () {
var char = this.peek();
if (isSpace(char)) {
this.consume();
return;
} else if (char === '/') {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.consume();
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
} else if (char === '=') {
this.consume();
this.transitionTo("beforeAttributeValue" /* beforeAttributeValue */);
} else if (char === '>') {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.transitionTo("attributeName" /* attributeName */);
this.delegate.beginAttribute();
this.consume();
this.delegate.appendToAttributeName(char);
}
},
beforeAttributeValue: function () {
var char = this.peek();
if (isSpace(char)) {
this.consume();
} else if (char === '"') {
this.transitionTo("attributeValueDoubleQuoted" /* attributeValueDoubleQuoted */);
this.delegate.beginAttributeValue(true);
this.consume();
} else if (char === "'") {
this.transitionTo("attributeValueSingleQuoted" /* attributeValueSingleQuoted */);
this.delegate.beginAttributeValue(true);
this.consume();
} else if (char === '>') {
this.delegate.beginAttributeValue(false);
this.delegate.finishAttributeValue();
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.transitionTo("attributeValueUnquoted" /* attributeValueUnquoted */);
this.delegate.beginAttributeValue(false);
this.consume();
this.delegate.appendToAttributeValue(char);
}
},
attributeValueDoubleQuoted: function () {
var char = this.consume();
if (char === '"') {
this.delegate.finishAttributeValue();
this.transitionTo("afterAttributeValueQuoted" /* afterAttributeValueQuoted */);
} else if (char === '&') {
this.delegate.appendToAttributeValue(this.consumeCharRef() || '&');
} else {
this.delegate.appendToAttributeValue(char);
}
},
attributeValueSingleQuoted: function () {
var char = this.consume();
if (char === "'") {
this.delegate.finishAttributeValue();
this.transitionTo("afterAttributeValueQuoted" /* afterAttributeValueQuoted */);
} else if (char === '&') {
this.delegate.appendToAttributeValue(this.consumeCharRef() || '&');
} else {
this.delegate.appendToAttributeValue(char);
}
},
attributeValueUnquoted: function () {
var char = this.peek();
if (isSpace(char)) {
this.delegate.finishAttributeValue();
this.consume();
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
} else if (char === '/') {
this.delegate.finishAttributeValue();
this.consume();
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
} else if (char === '&') {
this.consume();
this.delegate.appendToAttributeValue(this.consumeCharRef() || '&');
} else if (char === '>') {
this.delegate.finishAttributeValue();
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.consume();
this.delegate.appendToAttributeValue(char);
}
},
afterAttributeValueQuoted: function () {
var char = this.peek();
if (isSpace(char)) {
this.consume();
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
} else if (char === '/') {
this.consume();
this.transitionTo("selfClosingStartTag" /* selfClosingStartTag */);
} else if (char === '>') {
this.consume();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
}
},
selfClosingStartTag: function () {
var char = this.peek();
if (char === '>') {
this.consume();
this.delegate.markTagAsSelfClosing();
this.delegate.finishTag();
this.transitionTo("beforeData" /* beforeData */);
} else {
this.transitionTo("beforeAttributeName" /* beforeAttributeName */);
}
},
endTagOpen: function () {
var char = this.consume();
if (char === '@' || char === ':' || isAlpha(char)) {
this.transitionTo("endTagName" /* endTagName */);
this.tagNameBuffer = '';
this.delegate.beginEndTag();
this.appendToTagName(char);
}
}
};
this.reset();
}
EventedTokenizer.prototype.reset = function () {
this.transitionTo("beforeData" /* beforeData */);
this.input = '';
this.tagNameBuffer = '';
this.index = 0;
this.line = 1;
this.column = 0;
this.delegate.reset();
};
EventedTokenizer.prototype.transitionTo = function (state) {
this.state = state;
};
EventedTokenizer.prototype.tokenize = function (input) {
this.reset();
this.tokenizePart(input);
this.tokenizeEOF();
};
EventedTokenizer.prototype.tokenizePart = function (input) {
this.input += preprocessInput(input);
while (this.index < this.input.length) {
var handler = this.states[this.state];
if (handler !== undefined) {
handler.call(this);
} else {
throw new Error("unhandled state " + this.state);
}
}
};
EventedTokenizer.prototype.tokenizeEOF = function () {
this.flushData();
};
EventedTokenizer.prototype.flushData = function () {
if (this.state === 'data') {
this.delegate.finishData();
this.transitionTo("beforeData" /* beforeData */);
}
};
EventedTokenizer.prototype.peek = function () {
return this.input.charAt(this.index);
};
EventedTokenizer.prototype.consume = function () {
var char = this.peek();
this.index++;
if (char === '\n') {
this.line++;
this.column = 0;
} else {
this.column++;
}
return char;
};
EventedTokenizer.prototype.consumeCharRef = function () {
var endIndex = this.input.indexOf(';', this.index);
if (endIndex === -1) {
return;
}
var entity = this.input.slice(this.index, endIndex);
var chars = this.entityParser.parse(entity);
if (chars) {
var count = entity.length;
// consume the entity chars
while (count) {
this.consume();
count--;
}
// consume the `;`
this.consume();
return chars;
}
};
EventedTokenizer.prototype.markTagStart = function () {
this.delegate.tagOpen();
};
EventedTokenizer.prototype.appendToTagName = function (char) {
this.tagNameBuffer += char;
this.delegate.appendToTagName(char);
};
EventedTokenizer.prototype.isIgnoredEndTag = function () {
var tag = this.tagNameBuffer;
return tag === 'title' && this.input.substring(this.index, this.index + 8) !== '</title>' || tag === 'style' && this.input.substring(this.index, this.index + 8) !== '</style>' || tag === 'script' && this.input.substring(this.index, this.index + 9) !== '</script>';
};
return EventedTokenizer;
}();
var Tokenizer = /** @class */function () {
function Tokenizer(entityParser, options) {
if (options === void 0) {
options = {};
}
this.options = options;
this.token = null;
this.startLine = 1;
this.startColumn = 0;
this.tokens = [];
this.tokenizer = new EventedTokenizer(this, entityParser, options.mode);
this._currentAttribute = undefined;
}
Tokenizer.prototype.tokenize = function (input) {
this.tokens = [];
this.tokenizer.tokenize(input);
return this.tokens;
};
Tokenizer.prototype.tokenizePart = function (input) {
this.tokens = [];
this.tokenizer.tokenizePart(input);
return this.tokens;
};
Tokenizer.prototype.tokenizeEOF = function () {
this.tokens = [];
this.tokenizer.tokenizeEOF();
return this.tokens[0];
};
Tokenizer.prototype.reset = function () {
this.token = null;
this.startLine = 1;
this.startColumn = 0;
};
Tokenizer.prototype.current = function () {
var token = this.token;
if (token === null) {
throw new Error('token was unexpectedly null');
}
if (arguments.length === 0) {
return token;
}
for (var i = 0; i < arguments.length; i++) {
if (token.type === arguments[i]) {
return token;
}
}
throw new Error("token type was unexpectedly " + token.type);
};
Tokenizer.prototype.push = function (token) {
this.token = token;
this.tokens.push(token);
};
Tokenizer.prototype.currentAttribute = function () {
return this._currentAttribute;
};
Tokenizer.prototype.addLocInfo = function () {
if (this.options.loc) {
this.current().loc = {
start: {
line: this.startLine,
column: this.startColumn
},
end: {
line: this.tokenizer.line,
column: this.tokenizer.column
}
};
}
this.startLine = this.tokenizer.line;
this.startColumn = this.tokenizer.column;
};
// Data
Tokenizer.prototype.beginDoctype = function () {
this.push({
type: "Doctype" /* Doctype */,
name: ''
});
};
Tokenizer.prototype.appendToDoctypeName = function (char) {
this.current("Doctype" /* Doctype */).name += char;
};
Tokenizer.prototype.appendToDoctypePublicIdentifier = function (char) {
var doctype = this.current("Doctype" /* Doctype */);
if (doctype.publicIdentifier === undefined) {
doctype.publicIdentifier = char;
} else {
doctype.publicIdentifier += char;
}
};
Tokenizer.prototype.appendToDoctypeSystemIdentifier = function (char) {
var doctype = this.current("Doctype" /* Doctype */);
if (doctype.systemIdentifier === undefined) {
doctype.systemIdentifier = char;
} else {
doctype.systemIdentifier += char;
}
};
Tokenizer.prototype.endDoctype = function () {
this.addLocInfo();
};
Tokenizer.prototype.beginData = function () {
this.push({
type: "Chars" /* Chars */,
chars: ''
});
};
Tokenizer.prototype.appendToData = function (char) {
this.current("Chars" /* Chars */).chars += char;
};
Tokenizer.prototype.finishData = function () {
this.addLocInfo();
};
// Comment
Tokenizer.prototype.beginComment = function () {
this.push({
type: "Comment" /* Comment */,
chars: ''
});
};
Tokenizer.prototype.appendToCommentData = function (char) {
this.current("Comment" /* Comment */).chars += char;
};
Tokenizer.prototype.finishComment = function () {
this.addLocInfo();
};
// Tags - basic
Tokenizer.prototype.tagOpen = function () {};
Tokenizer.prototype.beginStartTag = function () {
this.push({
type: "StartTag" /* StartTag */,
tagName: '',
attributes: [],
selfClosing: false
});
};
Tokenizer.prototype.beginEndTag = function () {
this.push({
type: "EndTag" /* EndTag */,
tagName: ''
});
};
Tokenizer.prototype.finishTag = function () {
this.addLocInfo();
};
Tokenizer.prototype.markTagAsSelfClosing = function () {
this.current("StartTag" /* StartTag */).selfClosing = true;
};
// Tags - name
Tokenizer.prototype.appendToTagName = function (char) {
this.current("StartTag" /* StartTag */, "EndTag" /* EndTag */).tagName += char;
};
// Tags - attributes
Tokenizer.prototype.beginAttribute = function () {
this._currentAttribute = ['', '', false];
};
Tokenizer.prototype.appendToAttributeName = function (char) {
this.currentAttribute()[0] += char;
};
Tokenizer.prototype.beginAttributeValue = function (isQuoted) {
this.currentAttribute()[2] = isQuoted;
};
Tokenizer.prototype.appendToAttributeValue = function (char) {
this.currentAttribute()[1] += char;
};
Tokenizer.prototype.finishAttributeValue = function () {
this.current("StartTag" /* StartTag */).attributes.push(this._currentAttribute);
};
Tokenizer.prototype.reportSyntaxError = function (message) {
this.current().syntaxError = message;
};
return Tokenizer;
}();
function tokenize(input, options) {
var tokenizer = new Tokenizer(new EntityParser(HTML5NamedCharRefs), options);
return tokenizer.tokenize(input);
}
const LinkifyResult = 'LinkifyResult';
const StartTag = 'StartTag';
const EndTag = 'EndTag';
const Chars = 'Chars';
const Comment = 'Comment';
const Doctype = 'Doctype';
/**
* @param {string} str html string to link
* @param {import('linkifyjs').Opts} [opts] linkify options
* @returns {string} resulting string
*/
function linkifyHtml(str, opts = {}) {
// `tokens` and `token` in this section refer to tokens generated by the
// HTML parser, not linkify's parser
const tokens = tokenize(str);
const linkifiedTokens = [];
const linkified = [];
const options = new Options(opts, defaultRender);
// Linkify the tokens given by the parser
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.type === StartTag) {
linkifiedTokens.push(token);
// Ignore all the contents of ignored tags
const tagName = token.tagName.toUpperCase();
const isIgnored = tagName === 'A' || options.ignoreTags.indexOf(tagName) >= 0;
if (!isIgnored) {
continue;
}
let preskipLen = linkifiedTokens.length;
skipTagTokens(tagName, tokens, ++i, linkifiedTokens);
i += linkifiedTokens.length - preskipLen - 1;
} else if (token.type !== Chars) {
// Skip this token, it's not important
linkifiedTokens.push(token);
} else {
// Valid text token, linkify it!
const linkifedChars = linkifyChars(token.chars, options);
linkifiedTokens.push.apply(linkifiedTokens, linkifedChars);
}
}
// Convert the tokens back into a string
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(' ');
}
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;
}
}
}
return linkified.join('');
}
/**
`tokens` and `token` in this section referes to tokens returned by
`linkify.tokenize`. `linkified` will contain HTML Parser-style tokens
@param {string}
@param {import('linkifyjs').Options}
*/
function linkifyChars(str, options) {
const tokens = tokenize$1(str);
const result = [];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token.t === 'nl' && options.get('nl2br')) {
result.push({
type: StartTag,
tagName: 'br',
attributes: [],
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)
});
}
}
return result;
}
/**
Returns a list of tokens skipped until the closing tag of tagName.
* `tagName` is the closing tag which will prompt us to stop skipping
* `tokens` is the array of tokens generated by HTML5Tokenizer which
* `i` is the index immediately after the opening tag to skip
* `skippedTokens` is an array which skipped tokens are being pushed into
Caveats
* Assumes that i is the first token after the given opening tagName
* The closing tag will be skipped, but nothing after it
* 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;
while (i < tokens.length && stackCount > 0) {
let token = tokens[i];
if (token.type === StartTag && token.tagName.toUpperCase() === tagName) {
// Nested tag of the same type, "add to stack"
stackCount++;
} else if (token.type === EndTag && token.tagName.toUpperCase() === tagName) {
// Closing tag
stackCount--;
}
skippedTokens.push(token);
i++;
}
// Note that if stackCount > 0 here, the HTML is probably invalid
return skippedTokens;
}
function defaultRender({
tagName,
attributes,
content
}) {
return `<${tagName} ${attributesToString(attributes)}>${escapeText(content)}</${tagName}>`;
}
function escapeText(text) {
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function escapeAttr(attr) {
return attr.replace(/"/g, '&quot;');
}
function attributesToString(attributes) {
const result = [];
for (const attr in attributes) {
const val = attributes[attr] + '';
result.push(`${attr}="${escapeAttr(val)}"`);
}
return result.join(' ');
}
function attributeArrayToStrings(attrs) {
const attrStrs = [];
for (let i = 0; i < attrs.length; i++) {
const name = attrs[i][0];
const value = attrs[i][1] + '';
attrStrs.push(`${name}="${escapeAttr(value)}"`);
}
return attrStrs;
}
export { linkifyHtml as default };