Socket
Socket
Sign inDemoInstall

htmljs-parser

Package Overview
Dependencies
Maintainers
5
Versions
109
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

htmljs-parser - npm Package Compare versions

Comparing version 3.2.1 to 3.2.2

4

dist/core/Parser.d.ts

@@ -1,2 +0,2 @@

import { STATE, Range, ParserOptions as Options } from "../internal";
import { STATE, Range, ParserOptions as Options, ErrorCode } from "../internal";
export interface Meta extends Range {

@@ -59,3 +59,3 @@ parent: Meta;

beginHtmlBlock(delimiter: string | undefined, singleLine: boolean): void;
emitError(range: number | Range, code: string, message: string): void;
emitError(range: number | Range, code: ErrorCode, message: string): void;
closeTag(start: number, end: number, value: Range | undefined): void;

@@ -62,0 +62,0 @@ consumeWhitespaceIfBefore(str: string, start?: number): boolean;

import { type ParserOptions, type Range } from "./internal";
export { TagType, type ParserOptions as Handlers, type Position, type Location, type Ranges, type Range, } from "./internal";
export { TagType, ErrorCode, type ParserOptions as Handlers, type Position, type Location, type Ranges, type Range, } from "./internal";
/**

@@ -18,3 +18,3 @@ * Creates a new Marko parser.

*/
positionAt(index: number): import("./internal").Position;
positionAt(offset: number): import("./internal").Position;
/**

@@ -21,0 +21,0 @@ * Given a offset range in the current source code, returns a Location object with a start & end position information.

@@ -22,2 +22,3 @@ var __defProp = Object.defineProperty;

__export(src_exports, {
ErrorCode: () => ErrorCode,
TagType: () => TagType,

@@ -29,2 +30,31 @@ createParser: () => createParser

// src/util/constants.ts
var ErrorCode = /* @__PURE__ */ ((ErrorCode3) => {
ErrorCode3[ErrorCode3["EXTRA_CLOSING_TAG"] = 0] = "EXTRA_CLOSING_TAG";
ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_ARGUMENT"] = 1] = "INVALID_ATTRIBUTE_ARGUMENT";
ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_NAME"] = 2] = "INVALID_ATTRIBUTE_NAME";
ErrorCode3[ErrorCode3["INVALID_ATTRIBUTE_VALUE"] = 3] = "INVALID_ATTRIBUTE_VALUE";
ErrorCode3[ErrorCode3["INVALID_CHARACTER"] = 4] = "INVALID_CHARACTER";
ErrorCode3[ErrorCode3["INVALID_CODE_AFTER_SEMICOLON"] = 5] = "INVALID_CODE_AFTER_SEMICOLON";
ErrorCode3[ErrorCode3["INVALID_EXPRESSION"] = 6] = "INVALID_EXPRESSION";
ErrorCode3[ErrorCode3["INVALID_INDENTATION"] = 7] = "INVALID_INDENTATION";
ErrorCode3[ErrorCode3["INVALID_LINE_START"] = 8] = "INVALID_LINE_START";
ErrorCode3[ErrorCode3["INVALID_REGULAR_EXPRESSION"] = 9] = "INVALID_REGULAR_EXPRESSION";
ErrorCode3[ErrorCode3["INVALID_STRING"] = 10] = "INVALID_STRING";
ErrorCode3[ErrorCode3["INVALID_TAG_ARGUMENT"] = 11] = "INVALID_TAG_ARGUMENT";
ErrorCode3[ErrorCode3["INVALID_TAG_SHORTHAND"] = 12] = "INVALID_TAG_SHORTHAND";
ErrorCode3[ErrorCode3["INVALID_TEMPLATE_STRING"] = 13] = "INVALID_TEMPLATE_STRING";
ErrorCode3[ErrorCode3["MALFORMED_CDATA"] = 14] = "MALFORMED_CDATA";
ErrorCode3[ErrorCode3["MALFORMED_CLOSE_TAG"] = 15] = "MALFORMED_CLOSE_TAG";
ErrorCode3[ErrorCode3["MALFORMED_COMMENT"] = 16] = "MALFORMED_COMMENT";
ErrorCode3[ErrorCode3["MALFORMED_DECLARATION"] = 17] = "MALFORMED_DECLARATION";
ErrorCode3[ErrorCode3["MALFORMED_DOCUMENT_TYPE"] = 18] = "MALFORMED_DOCUMENT_TYPE";
ErrorCode3[ErrorCode3["MALFORMED_OPEN_TAG"] = 19] = "MALFORMED_OPEN_TAG";
ErrorCode3[ErrorCode3["MALFORMED_PLACEHOLDER"] = 20] = "MALFORMED_PLACEHOLDER";
ErrorCode3[ErrorCode3["MISMATCHED_CLOSING_TAG"] = 21] = "MISMATCHED_CLOSING_TAG";
ErrorCode3[ErrorCode3["MISSING_END_TAG"] = 22] = "MISSING_END_TAG";
ErrorCode3[ErrorCode3["MISSING_TAG_VARIABLE"] = 23] = "MISSING_TAG_VARIABLE";
ErrorCode3[ErrorCode3["RESERVED_TAG_NAME"] = 24] = "RESERVED_TAG_NAME";
ErrorCode3[ErrorCode3["ROOT_TAG_ONLY"] = 25] = "ROOT_TAG_ONLY";
return ErrorCode3;
})(ErrorCode || {});
var TagType = /* @__PURE__ */ ((TagType2) => {

@@ -78,3 +108,3 @@ TagType2[TagType2["html"] = 0] = "html";

} else {
return this.emitError(this.activeTag, "MISSING_END_TAG", 'Missing ending "' + this.read(this.activeTag.tagName) + '" tag');
return this.emitError(this.activeTag, 22 /* MISSING_END_TAG */, 'Missing ending "' + this.read(this.activeTag.tagName) + '" tag');
}

@@ -449,3 +479,3 @@ }

} else {
this.emitError(attr, "MALFORMED_OPEN_TAG", 'EOF reached while parsing attribute "' + (attr.name ? this.read(attr.name) : "default") + '" for the "' + this.read(this.activeTag.tagName) + '" tag');
this.emitError(attr, 19 /* MALFORMED_OPEN_TAG */, 'EOF reached while parsing attribute "' + (attr.name ? this.read(attr.name) : "default") + '" for the "' + this.read(this.activeTag.tagName) + '" tag');
}

@@ -466,3 +496,3 @@ },

if (attr.args) {
this.emitError(child, "ILLEGAL_ATTRIBUTE_ARGUMENT", "An attribute can only have one set of arguments");
this.emitError(child, 1 /* INVALID_ATTRIBUTE_ARGUMENT */, "An attribute can only have one set of arguments");
return;

@@ -514,3 +544,3 @@ }

if (child.start === child.end) {
return this.emitError(child, "ILLEGAL_ATTRIBUTE_VALUE", "Missing value for attribute");
return this.emitError(child, 3 /* INVALID_ATTRIBUTE_VALUE */, "Missing value for attribute");
}

@@ -616,3 +646,3 @@ if (attr.spread) {

} else {
parser.emitError(parser.pos, "INVALID_CHARACTER", "A concise mode closing block delimiter can only be followed by whitespace.");
parser.emitError(parser.pos, 4 /* INVALID_CHARACTER */, "A concise mode closing block delimiter can only be followed by whitespace.");
}

@@ -663,3 +693,3 @@ } else if (parser.lookAheadFor(indent, parser.pos + newLineLength)) {

eof(cdata) {
this.emitError(cdata, "MALFORMED_CDATA", "EOF reached while parsing CDATA");
this.emitError(cdata, 14 /* MALFORMED_CDATA */, "EOF reached while parsing CDATA");
},

@@ -703,3 +733,3 @@ return() {

eof(closeTag) {
this.emitError(closeTag, "MALFORMED_CLOSE_TAG", "EOF reached while parsing closing tag");
this.emitError(closeTag, 15 /* MALFORMED_CLOSE_TAG */, "EOF reached while parsing closing tag");
},

@@ -741,3 +771,3 @@ return() {

if (!activeTag) {
parser.emitError(closeTag, "EXTRA_CLOSING_TAG", 'The closing "' + parser.read({ start: closeTagNameStart, end: closeTagNameEnd }) + '" tag was not expected');
parser.emitError(closeTag, 0 /* EXTRA_CLOSING_TAG */, 'The closing "' + parser.read({ start: closeTagNameStart, end: closeTagNameEnd }) + '" tag was not expected');
return false;

@@ -755,3 +785,3 @@ }

})) {
parser.emitError(closeTag, "MISMATCHED_CLOSING_TAG", 'The closing "' + parser.read(closeTagNamePos) + '" tag does not match the corresponding opening "' + (parser.read(activeTag.tagName) || "div") + '" tag');
parser.emitError(closeTag, 21 /* MISMATCHED_CLOSING_TAG */, 'The closing "' + parser.read(closeTagNamePos) + '" tag does not match the corresponding opening "' + (parser.read(activeTag.tagName) || "div") + '" tag');
return false;

@@ -792,3 +822,3 @@ }

if (!parentTag && curIndent) {
this.emitError(this.pos, "BAD_INDENTATION", "Line has extra indentation at the beginning");
this.emitError(this.pos, 7 /* INVALID_INDENTATION */, "Line has extra indentation at the beginning");
return;

@@ -798,3 +828,3 @@ }

if (parentTag.type === 1 /* text */ && code !== 45 /* HTML_BLOCK_DELIMITER */) {
this.emitError(this.pos, "ILLEGAL_LINE_START", 'A line within a tag that only allows text content must begin with a "-" character');
this.emitError(this.pos, 8 /* INVALID_LINE_START */, 'A line within a tag that only allows text content must begin with a "-" character');
return;

@@ -805,3 +835,3 @@ }

} else if (parentTag.nestedIndent !== this.indent) {
this.emitError(this.pos, "BAD_INDENTATION", "Line indentation does match indentation of previous line");
this.emitError(this.pos, 7 /* INVALID_INDENTATION */, "Line indentation does match indentation of previous line");
return;

@@ -828,3 +858,3 @@ }

} else {
this.emitError(this.pos, "ILLEGAL_LINE_START", 'A line in concise mode cannot start with a single hyphen. Use "--" instead. See: https://github.com/marko-js/htmljs-parser/issues/43');
this.emitError(this.pos, 8 /* INVALID_LINE_START */, 'A line in concise mode cannot start with a single hyphen. Use "--" instead. See: https://github.com/marko-js/htmljs-parser/issues/43');
}

@@ -843,3 +873,3 @@ return;

default:
this.emitError(this.pos, "ILLEGAL_LINE_START", 'A line in concise mode cannot start with "/" unless it starts a "//" or "/*" comment');
this.emitError(this.pos, 8 /* INVALID_LINE_START */, 'A line in concise mode cannot start with "/" unless it starts a "//" or "/*" comment');
return;

@@ -881,3 +911,3 @@ }

if (!this.consumeWhitespaceOnLine(0)) {
this.emitError(this.pos, "INVALID_CHARACTER", "In concise mode a javascript comment block can only be followed by whitespace characters and a newline.");
this.emitError(this.pos, 4 /* INVALID_CHARACTER */, "In concise mode a javascript comment block can only be followed by whitespace characters and a newline.");
}

@@ -916,3 +946,3 @@ break;

eof(declaration) {
this.emitError(declaration, "MALFORMED_DECLARATION", "EOF reached while parsing declaration");
this.emitError(declaration, 17 /* MALFORMED_DECLARATION */, "EOF reached while parsing declaration");
},

@@ -968,3 +998,3 @@ return() {

eof(documentType) {
this.emitError(documentType, "MALFORMED_DOCUMENT_TYPE", "EOF reached while parsing document type");
this.emitError(documentType, 18 /* MALFORMED_DOCUMENT_TYPE */, "EOF reached while parsing document type");
},

@@ -1049,7 +1079,7 @@ return() {

if (!expression.groupStack.length) {
return this.emitError(expression, "INVALID_EXPRESSION", 'Mismatched group. A closing "' + String.fromCharCode(code) + '" character was found but it is not matched with a corresponding opening character.');
return this.emitError(expression, 6 /* INVALID_EXPRESSION */, 'Mismatched group. A closing "' + String.fromCharCode(code) + '" character was found but it is not matched with a corresponding opening character.');
}
const expectedCode = expression.groupStack.pop();
if (expectedCode !== code) {
return this.emitError(expression, "INVALID_EXPRESSION", 'Mismatched group. A "' + String.fromCharCode(code) + '" character was found when "' + String.fromCharCode(expectedCode) + '" was expected.');
return this.emitError(expression, 6 /* INVALID_EXPRESSION */, 'Mismatched group. A "' + String.fromCharCode(code) + '" character was found when "' + String.fromCharCode(expectedCode) + '" was expected.');
}

@@ -1074,12 +1104,12 @@ break;

if (!attr.spread && !attr.name) {
return this.emitError(expression, "MALFORMED_OPEN_TAG", 'EOF reached while parsing attribute name for the "' + this.read(this.activeTag.tagName) + '" tag');
return this.emitError(expression, 19 /* MALFORMED_OPEN_TAG */, 'EOF reached while parsing attribute name for the "' + this.read(this.activeTag.tagName) + '" tag');
}
return this.emitError(expression, "MALFORMED_OPEN_TAG", `EOF reached while parsing attribute value for the ${attr.spread ? "..." : attr.name ? `"${this.read(attr.name)}"` : `"default"`} attribute`);
return this.emitError(expression, 19 /* MALFORMED_OPEN_TAG */, `EOF reached while parsing attribute value for the ${attr.spread ? "..." : attr.name ? `"${this.read(attr.name)}"` : `"default"`} attribute`);
}
case states_exports.TAG_NAME:
return this.emitError(expression, "MALFORMED_OPEN_TAG", "EOF reached while parsing tag name");
return this.emitError(expression, 19 /* MALFORMED_OPEN_TAG */, "EOF reached while parsing tag name");
case states_exports.PLACEHOLDER:
return this.emitError(expression, "MALFORMED_PLACEHOLDER", "EOF reached while parsing placeholder");
return this.emitError(expression, 20 /* MALFORMED_PLACEHOLDER */, "EOF reached while parsing placeholder");
}
return this.emitError(expression, "INVALID_EXPRESSION", "EOF reached while parsing expression");
return this.emitError(expression, 6 /* INVALID_EXPRESSION */, "EOF reached while parsing expression");
}

@@ -1176,3 +1206,3 @@ },

eof(comment) {
this.emitError(comment, "MALFORMED_COMMENT", "EOF reached while parsing comment");
this.emitError(comment, 16 /* MALFORMED_COMMENT */, "EOF reached while parsing comment");
},

@@ -1342,3 +1372,3 @@ return() {

eof(comment) {
this.emitError(comment, "MALFORMED_COMMENT", "EOF reached while parsing multi-line JavaScript comment");
this.emitError(comment, 16 /* MALFORMED_COMMENT */, "EOF reached while parsing multi-line JavaScript comment");
},

@@ -1466,3 +1496,3 @@ return() {

if (child.start === child.end) {
this.emitError(child, "PLACEHOLDER_EXPRESSION_REQUIRED", "Invalid placeholder, the expression cannot be missing");
this.emitError(child, 20 /* MALFORMED_PLACEHOLDER */, "Invalid placeholder, the expression cannot be missing");
}

@@ -1541,6 +1571,6 @@ this.pos++;

eol(_, regExp) {
this.emitError(regExp, "INVALID_REGULAR_EXPRESSION", "EOL reached while parsing regular expression");
this.emitError(regExp, 9 /* INVALID_REGULAR_EXPRESSION */, "EOL reached while parsing regular expression");
},
eof(regExp) {
this.emitError(regExp, "INVALID_REGULAR_EXPRESSION", "EOF reached while parsing regular expression");
this.emitError(regExp, 9 /* INVALID_REGULAR_EXPRESSION */, "EOF reached while parsing regular expression");
},

@@ -1579,3 +1609,3 @@ return() {

eof(string) {
this.emitError(string, "INVALID_STRING", "EOF reached while parsing string expression");
this.emitError(string, 10 /* INVALID_STRING */, "EOF reached while parsing string expression");
},

@@ -1608,3 +1638,3 @@ return() {

if (this.activeTag.hasShorthandId) {
return this.emitError(tagName, "INVALID_TAG_SHORTHAND", "Multiple shorthand ID parts are not allowed on the same tag");
return this.emitError(tagName, 12 /* INVALID_TAG_SHORTHAND */, "Multiple shorthand ID parts are not allowed on the same tag");
}

@@ -1641,6 +1671,6 @@ this.activeTag.hasShorthandId = true;

if (!tag.concise) {
return this.emitError(tagName, "RESERVED_TAG_NAME", `The "${this.read(tagName)}" tag is reserved and cannot be used as an HTML tag.`);
return this.emitError(tagName, 24 /* RESERVED_TAG_NAME */, `The "${this.read(tagName)}" tag is reserved and cannot be used as an HTML tag.`);
}
if (tag.parentTag) {
return this.emitError(tagName, "ROOT_TAG_ONLY", `"${this.read(tagName)}" can only be used at the root of the template.`);
return this.emitError(tagName, 25 /* ROOT_TAG_ONLY */, `"${this.read(tagName)}" can only be used at the root of the template.`);
}

@@ -1680,3 +1710,3 @@ this.enterState(states_exports.EXPRESSION).terminatedByEOL = true;

if (child.start === child.end) {
this.emitError(child, "PLACEHOLDER_EXPRESSION_REQUIRED", "Invalid placeholder, the expression cannot be missing");
this.emitError(child, 20 /* MALFORMED_PLACEHOLDER */, "Invalid placeholder, the expression cannot be missing");
}

@@ -1686,3 +1716,3 @@ const { quasis, expressions } = tagName;

const end = ++this.pos;
const nextStart = end + 1;
const nextStart = end;
expressions.push({

@@ -1730,3 +1760,3 @@ start,

eof(templateString) {
this.emitError(templateString, "INVALID_TEMPLATE_STRING", "EOF reached while parsing template string expression");
this.emitError(templateString, 13 /* INVALID_TEMPLATE_STRING */, "EOF reached while parsing template string expression");
},

@@ -1737,3 +1767,3 @@ eol() {

if (child.start === child.end) {
this.emitError(child, "PLACEHOLDER_EXPRESSION_REQUIRED", "Invalid placeholder, the expression cannot be missing");
this.emitError(child, 20 /* MALFORMED_PLACEHOLDER */, "Invalid placeholder, the expression cannot be missing");
}

@@ -1820,3 +1850,3 @@ this.pos++;

if (tag.stage === 4 /* ATTR_GROUP */) {
this.emitError(tag, "MALFORMED_OPEN_TAG", 'EOF reached while within an attribute group (e.g. "[ ... ]").');
this.emitError(tag, 19 /* MALFORMED_OPEN_TAG */, 'EOF reached while within an attribute group (e.g. "[ ... ]").');
return;

@@ -1826,3 +1856,3 @@ }

} else {
this.emitError(tag, "MALFORMED_OPEN_TAG", "EOF reached while parsing open tag");
this.emitError(tag, 19 /* MALFORMED_OPEN_TAG */, "EOF reached while parsing open tag");
}

@@ -1857,3 +1887,3 @@ },

}
this.emitError(this.pos, "INVALID_CODE_AFTER_SEMICOLON", "A semicolon indicates the end of a line. Only comments may follow it.");
this.emitError(this.pos, 5 /* INVALID_CODE_AFTER_SEMICOLON */, "A semicolon indicates the end of a line. Only comments may follow it.");
}

@@ -1864,7 +1894,7 @@ return;

if (this.lookAtCharCodeAhead(1) !== 45 /* HTML_BLOCK_DELIMITER */) {
this.emitError(tag, "MALFORMED_OPEN_TAG", '"-" not allowed as first character of attribute name');
this.emitError(tag, 19 /* MALFORMED_OPEN_TAG */, '"-" not allowed as first character of attribute name');
return;
}
if (tag.stage === 4 /* ATTR_GROUP */) {
this.emitError(this.pos, "MALFORMED_OPEN_TAG", "Attribute group was not properly ended");
this.emitError(this.pos, 19 /* MALFORMED_OPEN_TAG */, "Attribute group was not properly ended");
return;

@@ -1894,3 +1924,3 @@ }

if (tag.stage === 4 /* ATTR_GROUP */) {
this.emitError(this.pos, "MALFORMED_OPEN_TAG", 'Unexpected "[" character within open tag.');
this.emitError(this.pos, 19 /* MALFORMED_OPEN_TAG */, 'Unexpected "[" character within open tag.');
return;

@@ -1902,3 +1932,3 @@ }

if (tag.stage !== 4 /* ATTR_GROUP */) {
this.emitError(this.pos, "MALFORMED_OPEN_TAG", 'Unexpected "]" character within open tag.');
this.emitError(this.pos, 19 /* MALFORMED_OPEN_TAG */, 'Unexpected "]" character within open tag.');
return;

@@ -1920,3 +1950,3 @@ }

if (code === 60 /* OPEN_ANGLE_BRACKET */) {
return this.emitError(this.pos, "ILLEGAL_ATTRIBUTE_NAME", 'Invalid attribute name. Attribute name cannot begin with the "<" character.');
return this.emitError(this.pos, 2 /* INVALID_ATTRIBUTE_NAME */, 'Invalid attribute name. Attribute name cannot begin with the "<" character.');
}

@@ -1943,3 +1973,3 @@ if (code === 47 /* FORWARD_SLASH */ && this.lookAtCharCodeAhead(1) === 42 /* ASTERISK */) {

if (tag.hasArgs) {
this.emitError(this.pos, "ILLEGAL_TAG_ARGUMENT", "A tag can only have one argument");
this.emitError(this.pos, 11 /* INVALID_TAG_ARGUMENT */, "A tag can only have one argument");
return;

@@ -1980,3 +2010,3 @@ }

if (child.start === child.end) {
return this.emitError(child, "MISSING_TAG_VARIABLE", "A slash was found that was not followed by a variable name or lhs expression");
return this.emitError(child, 23 /* MISSING_TAG_VARIABLE */, "A slash was found that was not followed by a variable name or lhs expression");
}

@@ -2045,4 +2075,4 @@ (_b = (_a = this.options).onTagVar) == null ? void 0 : _b.call(_a, {

},
positionAt(index) {
return parser.positionAt(index);
positionAt(offset) {
return parser.positionAt(offset);
},

@@ -2056,4 +2086,5 @@ locationAt(range) {

0 && (module.exports = {
ErrorCode,
TagType,
createParser
});

@@ -73,3 +73,3 @@ export declare const enum CODE {

interface Error extends Range {
code: string;
code: ErrorCode;
message: string;

@@ -100,2 +100,30 @@ }

}
export declare enum ErrorCode {
EXTRA_CLOSING_TAG = 0,
INVALID_ATTRIBUTE_ARGUMENT = 1,
INVALID_ATTRIBUTE_NAME = 2,
INVALID_ATTRIBUTE_VALUE = 3,
INVALID_CHARACTER = 4,
INVALID_CODE_AFTER_SEMICOLON = 5,
INVALID_EXPRESSION = 6,
INVALID_INDENTATION = 7,
INVALID_LINE_START = 8,
INVALID_REGULAR_EXPRESSION = 9,
INVALID_STRING = 10,
INVALID_TAG_ARGUMENT = 11,
INVALID_TAG_SHORTHAND = 12,
INVALID_TEMPLATE_STRING = 13,
MALFORMED_CDATA = 14,
MALFORMED_CLOSE_TAG = 15,
MALFORMED_COMMENT = 16,
MALFORMED_DECLARATION = 17,
MALFORMED_DOCUMENT_TYPE = 18,
MALFORMED_OPEN_TAG = 19,
MALFORMED_PLACEHOLDER = 20,
MISMATCHED_CLOSING_TAG = 21,
MISSING_END_TAG = 22,
MISSING_TAG_VARIABLE = 23,
RESERVED_TAG_NAME = 24,
ROOT_TAG_ONLY = 25
}
export declare const enum TagType {

@@ -110,2 +138,3 @@ html = 0,

onText?(data: Range): void;
onPlaceholder?(data: Ranges.Placeholder): void;
onComment?(data: Ranges.Value): void;

@@ -116,3 +145,2 @@ onCDATA?(data: Ranges.Value): void;

onScriptlet?(data: Ranges.Scriptlet): void;
onPlaceholder?(data: Ranges.Placeholder): void;
onTagName?(data: Ranges.TagName): TagType | void;

@@ -119,0 +147,0 @@ onTagShorthandId?(data: Ranges.Template): void;

import { type Parser } from "../internal";
import type { Location, Position, Range } from "./constants";
import { Location, Position, Range } from "./constants";
export declare function isWhitespaceCode(code: number): boolean;

@@ -4,0 +4,0 @@ export declare function getLoc(lines: number[], range: Range): Location;

{
"name": "htmljs-parser",
"description": "An HTML parser recognizes content and string placeholders and allows JavaScript expressions as attribute values",
"version": "3.2.1",
"version": "3.2.2",
"author": "Phillip Gates-Idem <phillip.idem@gmail.com>",

@@ -6,0 +6,0 @@ "devDependencies": {

@@ -1,591 +0,397 @@

# htmljs-parser
<h1 align="center">
<!-- Logo -->
<br/>
htmljs-parser
<br/>
HTML parsers written according to the HTML spec will interpret all
attribute values as strings which makes it challenging to properly
describe a value's type (boolean, string, number, array, etc.)
or to provide a complex JavaScript expression as a value.
The ability to describe JavaScript expressions within attributes
is important for HTML-based template compilers.
<!-- Format -->
<a href="https://github.com/prettier/prettier">
<img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg" alt="Styled with prettier"/>
</a>
<!-- CI -->
<a href="https://github.com/marko-js/htmljs-parser/actions/workflows/ci.yml">
<img src="https://github.com/marko-js/htmljs-parser/actions/workflows/ci.yml/badge.svg" alt="Build status"/>
</a>
<!-- Coverage -->
<a href="https://codecov.io/gh/marko-js/htmljs-parser">
<img src="https://codecov.io/gh/marko-js/htmljs-parser/branch/main/graph/badge.svg?token=Sv8ePs16ix" alt="Code Coverage"/>
</a>
<!-- NPM Version -->
<a href="https://npmjs.org/package/htmljs-parser">
<img src="https://img.shields.io/npm/v/htmljs-parser.svg" alt="NPM version"/>
</a>
<!-- Downloads -->
<a href="https://npmjs.org/package/htmljs-parser">
<img src="https://img.shields.io/npm/dm/htmljs-parser.svg" alt="Downloads"/>
</a>
</h1>
For example, consider a HTML-based template that wishes to
support a custom tag named `<say-hello>` that supports an
attribute named `message` that can be a string literal or a JavaScript expression.
An HTML parser with super powers used by [Marko](https://markojs.com/docs/syntax/).
Ideally, the template compiler should be able to handle any of the following:
# Installation
```html
<say-hello message="Hello world!" />
<say-hello message=("Hello " + personName + "!") />
<say-hello message="Hello ${personName}!" />
```console
npm install htmljs-parser
```
This parser extends the HTML grammar to add these important features:
# Creating A Parser
- JavaScript expressions as attribute values
First we must create a parser instance and pass it some handlers for the various parse events shown below.
```html
<say-hello message=("Hello " + personName) count=2+2 large=true />
```
Each parse event is called a `Range` and is an object with start and end properties which are zero-based offsets from the beginning of th parsed code.
- Placeholders in the content of an element
Additional meta data and nested ranges are exposed on some events shown below.
```html
<div>Hello ${personName}</div>
```
You can get the raw string from any range using `parser.read(range)`.
- Placeholders within attribute value strings
```javascript
import { createParser, ErrorCode, TagType } from "htmljs-parser";
```html
<div data-message="Hello ${personName}!"></div>
```
const parser = createParser({
/**
* Called when the parser encounters an error.
*
* @example
* 1╭─ <a><b
* ╰─ ╰─ error(code: 19, message: "EOF reached while parsing open tag")
*/
onError(range) {
range.code; // An error code id. You can see the list of error codes in ErrorCode imported above.
range.message; // A human readable (hopefully) error message.
},
- JavaScript flow-control statements within HTML elements
/**
* Called when some static text is parsed within some body content.
*
* @example
* 1╭─ <div>Hi</div>
* ╰─ ╰─ text "Hi"
*/
onText(range) {},
```html
<div for(a in b) />
<div if(a === b) />
```
/**
* Called after parsing a placeholder within body content.
*
* @example
* 1╭─ <div>${hello} $!{world}</div>
* │ │ │ │ ╰─ placeholder.value "world"
* │ │ │ ╰─ placeholder "$!{world}"
* │ │ ╰─ placeholder:escape.value "hello"
* ╰─ ╰─ placeholder:escape "${hello}"
*/
onPlaceholder(range) {
range.escape; // true for ${} placeholders and false for $!{} placeholders.
range.value; // Another range that includes only the placeholder value itself without the wrapping braces.
},
- JavaScript flow-control statements as elements
```html
<for (a in b)> <if (a in b)></if></for>
```
# Installation
```bash
npm install htmljs-parser
```
# Usage
```javascript
var parser = require("htmljs-parser").createParser({
onText: function (event) {
// Text within an HTML element
var value = event.value;
/**
* Called when we find a comment at the root of the document or within a tags contents.
* It will not be fired for comments within expressions, such as attribute values.
*
* @example
* 1╭─ <!-- hi -->
* │ │ ╰─ comment.value " hi "
* ╰─ ╰─ comment "<!-- hi -->"
* 2╭─ // hi
* │ │ ╰─ comment.value " hi"
* ╰─ ╰─ comment "// hi"
*/
onComment(range) {
range.value; // Another range that only includes the contents of the comment.
},
onPlaceholder: function (event) {
// ${<value>]} // escape = true
// $!{<value>]} // escape = false
var value = event.value; // String
var escaped = event.escaped; // boolean
var withinBody = event.withinBody; // boolean
var withinAttribute = event.withinAttribute; // boolean
var withinOpenTag = event.withinOpenTag; // boolean
var pos = event.pos; // Integer
/**
* Called after parsing a CDATA section.
* // https://developer.mozilla.org/en-US/docs/Web/API/CDATASection
*
* @example
* 1╭─ <![CDATA[hi]]>
* │ │ ╰─ cdata.value "hi"
* ╰─ ╰─ cdata "<![CDATA[hi]]>"
*/
onCDATA(range) {
range.value; // Another range that only includes the contents of the CDATA.
},
onString: function (event) {
// Text within ""
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called after parsing a DocType comment.
* https://developer.mozilla.org/en-US/docs/Web/API/DocumentType
*
* @example
* 1╭─ <!DOCTYPE html>
* │ │ ╰─ doctype.value "DOCTYPE html"
* ╰─ ╰─ doctype "<!DOCTYPE html>"
*/
onDoctype(range) {
range.value; // Another range that only includes the contents of the DocType.
},
onCDATA: function (event) {
// <![CDATA[<value>]]>
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called after parsing an XML declaration.
* https://developer.mozilla.org/en-US/docs/Web/XML/XML_introduction#xml_declaration
*
* @example
* 1╭─ <?xml version="1.0" encoding="UTF-8"?>
* │ │ ╰─ declaration.value "xml version=\"1.0\" encoding=\"UTF-8\""
* ╰─ ╰─ declaration "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
*/
onDeclaration(range) {
range.value; // Another range that only includes the contents of the declaration.
},
onOpenTag: function (event) {
var tagName = event.tagName; // String
var attributes = event.attributes; // Array
var argument = event.argument; // Object
var pos = event.pos; // Integer
/**
* Called after parsing a scriptlet (new line followed by a $).
*
* @example
* 1╭─ $ foo();
* │ │╰─ scriptlet.value "foo();"
* ╰─ ╰─ scriptlet " foo();"
* 2╭─ $ { bar(); }
* │ │ ╰─ scriptlet:block.value " bar(); "
* ╰─ ╰─ scriptlet:block " { bar(); }"
*/
onScriptlet(range) {
range.block; // true if the scriptlet was contained within braces.
range.value; // Another range that includes only the value itself without the leading $ or surrounding braces (if applicable).
},
onCloseTag: function (event) {
// close tag
var tagName = event.tagName; // String
var pos = event.pos; // Integer
/**
* Called when a tag name, which can include placeholders, has been parsed.
*
* @example
* 1╭─ <div/>
* ╰─ ╰─ tagName "div"
* 2╭─ <hello-${test}-again/>
* │ │ │ ╰─ tagName.quasis[1] "-again"
* │ │ ╰─ tagName.expressions[0] "${test}"
* │ ├─ tagName.quasis[0] "hello-"
* ╰─ ╰─ tagName "hello-${test}-again"
*/
onTagName(range) {
range.concise; // true if this tag is a concise mode tag.
range.quasis; // An array of ranges that indicate the string literal parts of the tag name.
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
// Return a different tag type enum value to enter a different parse mode.
// Below is approximately what Marko uses:
switch (parser.read(range)) {
case "area":
case "base":
case "br":
case "col":
case "embed":
case "hr":
case "img":
case "input":
case "link":
case "meta":
case "param":
case "source":
case "track":
case "wbr":
// TagType.void makes this a void element (cannot have children).
return TagType.void;
case "html-comment":
case "script":
case "style":
case "textarea":
// TagType.text makes the child content text only (with placeholders).
return TagType.text;
case "class":
case "export":
case "import":
case "static":
// TagType.statement makes this a statement tag where the content following the tag name will be parsed as script code until we reach a new line, eg for `import x from "y"`).
return TagType.statement;
}
// TagType.html is the default which allows child content as html with placeholders.
return TagType.html;
},
onDocumentType: function (event) {
// Document Type/DTD
// <!<value>>
// Example: <!DOCTYPE html>
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called when a shorthand id, which can include placeholders, has been parsed.
*
* @example
* 1╭─ <div#hello-${test}-again/>
* │ ││ │ ╰─ tagShorthandId.quasis[1] "-again"
* │ ││ ╰─ tagShorthandId.expressions[0] "${test}"
* │ │╰─ tagShorthandId.quasis[0] "hello-"
* ╰─ ╰─ tagShorthandId "#hello-${test}-again"
*/
onTagShorthandId(range) {
range.quasis; // An array of ranges that indicate the string literal parts of the shorthand id name.
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
},
onDeclaration: function (event) {
// Declaration
// <?<value>?>
// Example: <?xml version="1.0" encoding="UTF-8" ?>
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called when a shorthand class name, which can include placeholders, has been parsed.
* Note there can be multiple of these.
*
* @example
* 1╭─ <div.hello-${test}-again/>
* │ ││ │ ╰─ tagShorthandClassName.quasis[1] "-again"
* │ ││ ╰─ tagShorthandClassName.expressions[0] "${test}"
* │ │╰─ tagShorthandClassName.quasis[0] "hello-"
* ╰─ ╰─ tagShorthandClassName "#hello-${test}-again"
*/
onTagShorthandClass(range) {
range.quasis; // An array of ranges that indicate the string literal parts of the shorthand id name.
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
},
onComment: function (event) {
// Text within XML comment
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called after a tag variable has been parsed.
*
* @example
* 1╭─ <div/el/>
* │ │╰─ tagVar.value "el"
* ╰─ ╰─ tagVar "/el"
*/
onTagVar(range) {
range.value; // Another range that includes only the tag var itself and not the leading slash.
},
onScriptlet: function (event) {
// Text within <% %>
var value = event.value; // String
var pos = event.pos; // Integer
/**
* Called after tag arguments have been parsed.
*
* @example
* 1╭─ <if(x)>
* │ │╰─ tagArgs.value "x"
* ╰─ ╰─ tagArgs "(x)"
*/
onTagArgs(range) {
range.value; // Another range that includes only the args themselves and not the outer parenthesis.
},
onError: function (event) {
// Error
var message = event.message; // String
var code = event.code; // String
var pos = event.pos; // Integer
/**
* Called after tag parameters have been parsed.
*
* @example
* 1╭─ <for|item| of=list>
* │ │╰─ tagParams.value "item"
* ╰─ ╰─ tagParams "|item|"
*/
onTagParams(range) {
range.value; // Another range that includes only the params themselves and not the outer pipes.
},
});
parser.parse(str);
```
/**
* Called after an attribute name as been parsed.
* Note this may be followed by the related AttrArgs, AttrValue or AttrMethod. It can also be directly followed by another AttrName, AttrSpread or the OpenTagEnd if this is a boolean attribute.
*
* @example
* 1╭─ <div class="hi">
* ╰─ ╰─ attrName "class"
*/
onAttrName(range) {},
## Content Parsing Modes
/**
* Called after attr arguments have been parsed.
*
* @example
* 1╭─ <div if(x)>
* │ │╰─ attrArgs.value "x"
* ╰─ ╰─ attrArgs "(x)"
*/
onAttrArgs(range) {
range.value; // Another range that includes only the args themselves and not the outer parenthesis.
},
The parser, by default, will look for HTML tags within content. This behavior
might not be desirable for certain tags, so the parser allows the parsing mode
to be changed (usually in response to an `onOpenTag` event).
/**
* Called after an attr value has been parsed.
*
* @example
* 1╭─ <input name="hi" value:=x>
* │ ││ │ ╰─ attrValue:bound.value
* │ ││ ╰─ attrValue:bound ":=x"
* │ │╰─ attrValue.value "\"hi\""
* ╰─ ╰─ attrValue "=\"hi\""
*/
onAttrValue(range) {
range.bound; // true if the attribute value was preceded by :=.
range.value; // Another range that includes only the value itself without the leading = or :=.
},
There are three content parsing modes:
/**
* Called after an attribute method shorthand has been parsed.
*
* @example
* 1╭─ <div onClick(ev) { foo(); }>
* │ ││ │╰─ attrMethod.body.value " foo(); "
* │ ││ ╰─ attrMethod.body "{ foo(); }"
* │ │╰─ attrMethod.params.value "ev"
* │ ├─ attrMethod.params "(ev)"
* ╰─ ╰─ attrMethod "(ev) { foo(); }"
*/
onAttrMethod(range) {
range.params; // Another range which includes the params for the method.
range.params.value; // Another range which includes the method params without outer parenthesis.
- **HTML Content (DEFAULT):**
The parser will look for any HTML tag and content placeholders while in
this mode and parse opening and closing tags accordingly.
range.body; // Another range which includes the entire body block.
range.body.value; // Another range which includes the body block without outer braces.
},
- **Parsed Text Content**: The parser will look for the closing tag that matches
the current open tag as well as content placeholders but all other content
will be interpreted as text.
/**
* Called after we've parsed a spread attribute.
*
* @example
* 1╭─ <div ...attrs>
* │ │ ╰─ attrSpread.value "attrs"
* ╰─ ╰─ attrSpread "...attrs"
*/
onAttrSpread(range) {
range.value; // Another range that includes only the value itself without the leading ...
},
- **Static Text Content**: The parser will look for the closing tag that matches
the current open tag but all other content will be interpreted as raw text.
/**
* Called once we've completed parsing the open tag.
*
* @example
* 1╭─ <div><span/></div>
* │ │ ╰─ openTagEnd:selfClosed "/>"
* ╰─ ╰─ openTagEnd ">"
*/
onOpenTagEnd(range) {
range.selfClosed; // true if this tag was self closed (onCloseTag would not be called if so).
},
```javascript
var htmljs = require('htmljs-parser');
var parser = htmljs.createParser({
onOpenTag: function(event) {
// open tag
switch(event.tagName) {
case 'textarea':
//fall through
case 'script':
//fall through
case 'style':
// parse the content within these tags but only
// look for placeholders and the closing tag.
parser.enterParsedTextContentState();
break;
case 'dummy'
// treat content within <dummy>...</dummy> as raw
// text and ignore other tags and placeholders
parser.enterStaticTextContentState();
break;
default:
// The parser will switch to HTML content parsing mode
// if the parsing mode is not explicitly changed by
// "onOpenTag" function.
}
}
/**
* Called once the closing tag (or in concise mode an outdent or eof) is parsed.
* Note this is not called for selfClosed, void or statement tags.
*
* @example
* 1╭─ <div><span/></div>
* │ │ ╰─ closeTag(div).value "div"
* ╰─ ╰─ closeTag(div) "</div>"
*/
onCloseTag(range) {
range.value; // The raw content of the closing tag (undefined in concise mode).
},
});
parser.parse(str);
```
## Parsing Events
Finally after setting up the parser with it's handlers, it's time to pass in some source code to parse.
The `htmljs-parser` is an event-based parser which means that it will emit
events as it is parsing the document. Events are emitted via calls
to `on<eventname>` function which are supplied as properties in the options
via call to `require('htmljs-parser').createParser(options)`.
### onOpenTag
The `onOpenTag` function will be called each time an opening tag is
encountered.
**EXAMPLE: Simple tag**
INPUT:
```html
<div></div>
```
OUTPUT EVENT:
```javascript
{
type: 'openTag',
tagName: 'div',
attributes: []
}
parser.parse("<div></div>");
```
**EXAMPLE: Tag with literal attribute values**
# Parser Helpers
INPUT:
The parser instance provides a few helpers to make it easier to work with the parsed content.
```html
<div class="demo" disabled="false" data-number="123"></div>
```
OUTPUT EVENT:
```javascript
{
type: 'openTag',
tagName: 'div',
attributes: [
{
name: 'class',
value: '"demo"'
},
{
name: 'disabled',
value: 'false'
},
{
name: 'data-number',
value: '123'
}
]
}
```
// Pass any range object into this method to get the raw string from the source for the range.
parser.read(range);
**EXAMPLE: Tag with expression attribute**
// Given an zero based offset within the source code, returns a position object that contains line and column properties.
parser.positionAt(offset);
INPUT:
```html
<say-something message=("Hello "+data.name)/>
// Given a range object returns a location object with start and end properties which are each position objects as returned from the "positionAt" api.
parser.locationAt(range);
```
OUTPUT EVENT:
# Code of Conduct
```javascript
{
type: 'openTag',
tagName: 'div',
attributes: [
{
name: 'message',
value: '"Hello "+data.name'
}
]
}
```
**EXAMPLE: Tag with an argument**
INPUT:
```html
<for(var i="0;" i < 10; i++)></for(var>
```
OUTPUT EVENT:
```javascript
{
type: 'openTag',
tagName: 'for',
argument: {
value: 'var i = 0; i < 10; i++',
pos: ... // Integer
},
attributes: []
}
```
**EXAMPLE: Attribute with an argument**
INPUT:
```html
<div if(x>y)></div>
```
OUTPUT EVENT:
```javascript
{
type: 'openTag',
tagName: 'div',
attributes: [
{
name: 'if',
argument: {
value: 'x > y',
pos: ... // Integer
}
}
]
}
```
### onCloseTag
The `onCloseTag` function will be called each time a closing tag is
encountered.
**EXAMPLE: Simple close tag**
INPUT:
```html
</div>
```
OUTPUT EVENT:
```javascript
{
type: 'closeTag',
tagName: 'div'
}
```
### onText
The `onText` function will be called each time within an element
when textual data is encountered.
**NOTE:** Text within `<![CDATA[` `]]>` will be emitted via call
to `onCDATA`.
**EXAMPLE**
In the following example code, the `TEXT` sequences will be emitted as
text events.
INPUT:
```html
Simple text
```
OUTPUT EVENT:
```javascript
{
type: 'text',
value: 'Simple text'
}
```
### onCDATA
The `onCDATA` function will be called when text within `<![CDATA[` `]]>`
is encountered.
**EXAMPLE:**
INPUT:
```html
<![CDATA[This is text]]>
```
OUTPUT EVENT:
```javascript
{
type: 'cdata',
value: 'This is text'
}
```
### onPlaceholder
The `onPlaceholder` function will be called each time a placeholder
is encountered.
If the placeholder starts with the `$!{` sequence then `event.escape`
will be `false`.
If the placeholder starts with the `${` sequence then `event.escape` will be
`true`.
Text within `<![CDATA[` `]]>` and `<!--` `-->` will not be parsed so you
cannot use placeholders for these blocks of code.
**EXAMPLE:**
INPUT:
```html
${"This is an escaped placeholder"} $!{"This is a non-escaped placeholder"}
```
OUTPUT EVENTS
```html
${name}
```
```javascript
{
type: 'placeholder',
value: 'name',
escape: true
}
```
---
```html
$!{name}
```
```javascript
{
type: 'placeholder',
value: 'name',
escape: true
}
```
**NOTE:**
The `escape` flag is merely informational. The application code is responsible
for interpreting this flag to properly escape the expression.
Here's an example of modifying the expression based on the `event.escape` flag:
```javascript
onPlaceholder: function(event) {
if (event.escape) {
event.value = 'escapeXml(' + event.value + ')';
}
}
```
### onDocumentType
The `onDocumentType` function will be called when the document type declaration
is encountered _anywhere_ in the content.
**EXAMPLE:**
INPUT:
```html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN">
```
OUTPUT EVENT:
```javascript
{
type: 'documentType',
value: 'DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN"'
}
```
### onDeclaration
The `onDeclaration` function will be called when an XML declaration
is encountered _anywhere_ in the content.
**EXAMPLE:**
INPUT:
```html
<?xml version="1.0" encoding="UTF-8"?>
```
OUTPUT EVENT:
```javascript
{
type: 'declaration',
value: 'xml version="1.0" encoding="UTF-8"'
}
```
### onComment
The `onComment` function will be called when text within `<!--` `-->`
is encountered.
**EXAMPLE:**
INPUT:
```html
<!--This is a comment-->
```
OUTPUT EVENT:
```javascript
{
type: 'comment',
value: 'This is a comment'
}
```
### onScriptlet
The `onScriptlet` function will be called when text within `<%` `%>`
is encountered.
**EXAMPLE:**
INPUT:
```html
<% console.log("Hello World!"); %>
```
OUTPUT EVENT:
```javascript
{
type: 'scriptlet',
value: ' console.log("Hello World!"); '
}
```
### onError
The `onError` function will be called when malformed content is detected.
The most common cause for an error is due to reaching the end of the
input while still parsing an open tag, close tag, XML comment, CDATA section,
DTD, XML declaration, or placeholder.
Possible error codes:
- `MISSING_END_TAG`
- `MISSING_END_DELIMITER`
- `MALFORMED_OPEN_TAG`
- `MALFORMED_CLOSE_TAG`
- `MALFORMED_CDATA`
- `MALFORMED_PLACEHOLDER`
- `MALFORMED_DOCUMENT_TYPE`
- `MALFORMED_DECLARATION`
- `MALFORMED_COMMENT`
- `EXTRA_CLOSING_TAG`
- `MISMATCHED_CLOSING_TAG`
- ...
**EXAMPLE:**
INPUT:
```html
<a href="
```
OUTPUT EVENT:
```javascript
{
type: 'error',
code: 'MALFORMED_OPEN_TAG',
message: 'EOF reached while parsing open tag.',
pos: 0,
endPos: 9
}
```
This project adheres to the [eBay Code of Conduct](./.github/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc