Updates dockerfile
All checks were successful
Build and Push / build (push) Successful in 55s

This commit is contained in:
2026-02-16 15:09:37 -05:00
parent 8346776f2a
commit d181f77fb2
14943 changed files with 2078509 additions and 16 deletions

21
node_modules/@emmetio/html-matcher/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

50
node_modules/@emmetio/html-matcher/README.md generated vendored Normal file
View File

@@ -0,0 +1,50 @@
# Small and fast HTML matcher
Finds matching opening and closing tag pair for given location in HTML/XML source:
```js
import match from '@emmetio/html-matcher';
const content = '<div><a href="http://emmet.io">Example</a></div>';
// Find tag pair at character 35
const tag = match(content, 35);
console.log(tag.name); // Name of matched tag: "a"
console.log(tag.open); // Range of opening tag: [5, 31]
console.log(tag.end); // Range of closing tag: [38, 42]
// List of attributes found in opening tag
console.log(tag.attributes);
```
By default, matcher works in HTML, which means if it finds tag name which is known to be empty (for example, `<img>`) it will not search for its closing part. However, such behavior might be unexpected for XML syntaxes where all tags should be either self-closed or provide closing part. In this case, you should pass `xml: true` option to properly handle XML mode:
```js
import match from '@emmetio/html-matcher';
const content = '<div><img>Caption</img></div>';
const html = match(content, 8);
const xml = match(content, 8, { xml: true });
console.log(html.name); // "img"
console.log(html.open); // [5, 10]
console.log(html.close); // undefined
console.log(xml.name); // "img"
console.log(xml.open); // [5, 10]
console.log(xml.close); // [17, 23]
```
## Special tags
In HTML, some tags has special meaning. For example, a `<script>` tag: its contents should be completely ignored until we find closing `</script>` tag. But, if `<script>` tag contains unknown `type` attribute value, we should consider it as a regular tag. By default, matcher understands `script` and `style` tags as “special” but you can override them with `special` option:
```js
import match from '@emmetio/html-matcher';
// Treat `<foo-bar>` tag as ”special”: skip its content until `</foo-bar>`. Note that this option overwrites default value with `['script', 'style']` value
match('...', 10, { special: { 'foo-bar': null } });
```
The `special` option is an object where key is a tag name and value is an array of `type` attribute values which, if present in tag, will make it special. If array is not provided, all instances of tag with given name will be considered as special.

View File

@@ -0,0 +1,29 @@
import Scanner from '@emmetio/scanner';
export interface AttributeToken {
name: string;
value?: string;
nameStart: number;
nameEnd: number;
valueStart?: number;
valueEnd?: number;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
export default function attributes(src: string, name?: string): AttributeToken[];
/**
* Consumes attribute name from given scanner context
*/
export declare function attributeName(scanner: Scanner): boolean;
/**
* Consumes attribute value
*/
export declare function attributeValue(scanner: Scanner): true | undefined;
/**
* Returns clean (unquoted) value of `name` attribute
*/
export declare function getAttributeValue(attrs: AttributeToken[], name: string): string | undefined;

View File

@@ -0,0 +1,641 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var Scanner = require('@emmetio/scanner');
var Scanner__default = _interopDefault(Scanner);
const defaultOptions = {
xml: false,
allTokens: false,
special: {
style: null,
script: ['', 'text/javascript', 'application/x-javascript', 'javascript', 'typescript', 'ts', 'coffee', 'coffeescript']
},
empty: ['img', 'meta', 'link', 'br', 'base', 'hr', 'area', 'wbr', 'col', 'embed', 'input', 'param', 'source', 'track']
};
/** Options for `Scanner` utils */
const opt = { throws: false };
function createOptions(options = {}) {
return Object.assign(Object.assign({}, defaultOptions), options);
}
/**
* Converts given string into array of character codes
*/
function toCharCodes(str) {
return str.split('').map(ch => ch.charCodeAt(0));
}
/**
* Consumes array of character codes from given scanner
*/
function consumeArray(scanner, codes) {
const start = scanner.pos;
for (let i = 0; i < codes.length; i++) {
if (!scanner.eat(codes[i])) {
scanner.pos = start;
return false;
}
}
scanner.start = start;
return true;
}
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
function consumeSection(scanner, open, close, allowUnclosed) {
const start = scanner.pos;
if (consumeArray(scanner, open)) {
// consumed `<!--`, read next until we find ending part or reach the end of input
while (!scanner.eof()) {
if (consumeArray(scanner, close)) {
scanner.start = start;
return true;
}
scanner.pos++;
}
// unclosed section is allowed
if (allowUnclosed) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
// unable to find section, revert to initial position
scanner.pos = start;
return false;
}
/**
* Check if given character can be used as a start of tag name or attribute
*/
function nameStartChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameStartChar
return Scanner.isAlpha(ch) || ch === 58 /* Colon */ || ch === 95 /* Underscore */
|| (ch >= 0xC0 && ch <= 0xD6)
|| (ch >= 0xD8 && ch <= 0xF6)
|| (ch >= 0xF8 && ch <= 0x2FF)
|| (ch >= 0x370 && ch <= 0x37D)
|| (ch >= 0x37F && ch <= 0x1FFF);
}
/**
* Check if given character can be used in a tag or attribute name
*/
function nameChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameChar
return nameStartChar(ch) || ch === 45 /* Dash */ || ch === 46 /* Dot */ || Scanner.isNumber(ch)
|| ch === 0xB7
|| (ch >= 0x0300 && ch <= 0x036F);
}
/**
* Consumes identifier from given scanner
*/
function ident(scanner) {
const start = scanner.pos;
if (scanner.eat(nameStartChar)) {
scanner.eatWhile(nameChar);
scanner.start = start;
return true;
}
return false;
}
/**
* Check if given code is tag terminator
*/
function isTerminator(code) {
return code === 62 /* RightAngle */ || code === 47 /* Slash */;
}
/**
* Check if given character code is valid unquoted value
*/
function isUnquoted(code) {
return !isNaN(code) && !Scanner.isQuote(code) && !Scanner.isSpace(code) && !isTerminator(code);
}
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
function consumePaired(scanner) {
return Scanner.eatPair(scanner, 60 /* LeftAngle */, 62 /* RightAngle */, opt)
|| Scanner.eatPair(scanner, 40 /* LeftRound */, 41 /* RightRound */, opt)
|| Scanner.eatPair(scanner, 91 /* LeftSquare */, 93 /* RightSquare */, opt)
|| Scanner.eatPair(scanner, 123 /* LeftCurly */, 125 /* RightCurly */, opt);
}
/**
* Returns unquoted value of given string
*/
function getUnquotedValue(value) {
// Trim quotes
if (Scanner.isQuote(value.charCodeAt(0))) {
value = value.slice(1);
}
if (Scanner.isQuote(value.charCodeAt(value.length - 1))) {
value = value.slice(0, -1);
}
return value;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
function attributes(src, name) {
const result = [];
let start = 0;
let end = src.length;
if (name) {
start = name.length + 1;
end -= src.slice(-2) === '/>' ? 2 : 1;
}
const scanner = new Scanner__default(src, start, end);
while (!scanner.eof()) {
scanner.eatWhile(Scanner.isSpace);
if (attributeName(scanner)) {
const token = {
name: scanner.current(),
nameStart: scanner.start,
nameEnd: scanner.pos
};
if (scanner.eat(61 /* Equals */) && attributeValue(scanner)) {
token.value = scanner.current();
token.valueStart = scanner.start;
token.valueEnd = scanner.pos;
}
result.push(token);
}
else {
// Do not break on invalid attributes: we are not validating parser
scanner.pos++;
}
}
return result;
}
/**
* Consumes attribute name from given scanner context
*/
function attributeName(scanner) {
const start = scanner.pos;
if (scanner.eat(42 /* Asterisk */) || scanner.eat(35 /* Hash */)) {
// Angular-style directives: `<section *ngIf="showSection">`, `<video #movieplayer ...>`
ident(scanner);
scanner.start = start;
return true;
}
// Attribute name could be a regular name or expression:
// React-style `<div {...props}>`
// Angular-style `<div [ng-for]>` or `<div *ng-for>`
return consumePaired(scanner) || ident(scanner);
}
/**
* Consumes attribute value
*/
function attributeValue(scanner) {
// Supported attribute values are quoted, React-like expressions (`{foo}`)
// or unquoted literals
return Scanner.eatQuoted(scanner, opt) || consumePaired(scanner) || unquoted(scanner);
}
/**
* Returns clean (unquoted) value of `name` attribute
*/
function getAttributeValue(attrs, name) {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name === name) {
return attr.value && getUnquotedValue(attr.value);
}
}
}
/**
* Consumes unquoted value
*/
function unquoted(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isUnquoted)) {
scanner.start = start;
return true;
}
}
const cdataOpen = toCharCodes('<![CDATA[');
const cdataClose = toCharCodes(']]>');
const commentOpen = toCharCodes('<!--');
const commentClose = toCharCodes('-->');
const piStart = toCharCodes('<?');
const piEnd = toCharCodes('?>');
const erbStart = toCharCodes('<%');
const erbEnd = toCharCodes('%>');
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
function scan(source, callback, options) {
const scanner = new Scanner__default(source);
const special = options ? options.special : null;
const allTokens = options ? options.allTokens : false;
let type;
let name;
let nameStart;
let nameEnd;
let nameCodes;
let found = false;
let piName = null;
while (!scanner.eof()) {
const start = scanner.pos;
if (cdata(scanner)) {
if (allTokens && callback('#cdata', 4 /* CData */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (comment(scanner)) {
if (allTokens && callback('#comment', 6 /* Comment */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (erb(scanner)) {
if (allTokens && callback('#erb', 7 /* ERB */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (piName = processingInstruction(scanner)) {
if (allTokens && callback(piName, 5 /* ProcessingInstruction */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (scanner.eat(60 /* LeftAngle */)) {
// Maybe a tag name?
type = scanner.eat(47 /* Slash */) ? 2 /* Close */ : 1 /* Open */;
nameStart = scanner.pos;
if (ident(scanner)) {
// Consumed tag name
nameEnd = scanner.pos;
if (type !== 2 /* Close */) {
skipAttributes(scanner);
scanner.eatWhile(Scanner.isSpace);
if (scanner.eat(47 /* Slash */)) {
type = 3 /* SelfClose */;
}
}
if (scanner.eat(62 /* RightAngle */)) {
// Tag properly closed
name = scanner.substring(nameStart, nameEnd);
if (callback(name, type, start, scanner.pos) === false) {
break;
}
if (type === 1 /* Open */ && special && isSpecial(special, name, source, start, scanner.pos)) {
// Found opening tag of special element: we should skip
// scanner contents until we find closing tag
nameCodes = toCharCodes(name);
found = false;
while (!scanner.eof()) {
if (consumeClosing(scanner, nameCodes)) {
found = true;
break;
}
scanner.pos++;
}
if (found && callback(name, 2 /* Close */, scanner.start, scanner.pos) === false) {
break;
}
}
}
}
}
else {
scanner.pos++;
}
}
}
/**
* Skips attributes in current tag context
*/
function skipAttributes(scanner) {
while (!scanner.eof()) {
scanner.eatWhile(Scanner.isSpace);
if (attributeName(scanner)) {
if (scanner.eat(61 /* Equals */)) {
attributeValue(scanner);
}
}
else if (isTerminator(scanner.peek())) {
break;
}
else {
scanner.pos++;
}
}
}
/**
* Consumes closing tag with given name from scanner
*/
function consumeClosing(scanner, name) {
const start = scanner.pos;
if (scanner.eat(60 /* LeftAngle */) && scanner.eat(47 /* Slash */) && consumeArray(scanner, name) && scanner.eat(62 /* RightAngle */)) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Consumes CDATA from given scanner
*/
function cdata(scanner) {
return consumeSection(scanner, cdataOpen, cdataClose, true);
}
/**
* Consumes comments from given scanner
*/
function comment(scanner) {
return consumeSection(scanner, commentOpen, commentClose, true);
}
/**
* Consumes processing instruction from given scanner. If consumed, returns
* processing instruction name
*/
function processingInstruction(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, piStart) && ident(scanner)) {
const name = scanner.current();
while (!scanner.eof()) {
if (consumeArray(scanner, piEnd)) {
break;
}
Scanner.eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return name;
}
scanner.pos = start;
return null;
}
/**
* Consumes ERB-style entity: `<% ... %>` or `<%= ... %>`
*/
function erb(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, erbStart)) {
while (!scanner.eof()) {
if (consumeArray(scanner, erbEnd)) {
break;
}
Scanner.eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Check if given tag name should be considered as special
*/
function isSpecial(special, name, source, start, end) {
if (name in special) {
const typeValues = special[name];
if (!Array.isArray(typeValues)) {
return true;
}
const attrs = attributes(source.substring(start + name.length + 1, end - 1));
return typeValues.includes(getAttributeValue(attrs, 'type') || '');
}
return false;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
function match(source, pos, opt) {
// Since we expect large input document, well use pooling technique
// for storing tag data to reduce memory pressure and improve performance
const pool = [];
const stack = [];
const options = createOptions(opt);
let result = null;
scan(source, (name, type, start, end) => {
if (type === 1 /* Open */ && isSelfClose(name, options)) {
// Found empty element in HTML mode, mark is as self-closing
type = 3 /* SelfClose */;
}
if (type === 1 /* Open */) {
// Allocate tag object from pool
stack.push(allocTag(pool, name, start, end));
}
else if (type === 3 /* SelfClose */) {
if (start < pos && pos < end) {
// Matched given self-closing tag
result = {
name,
attributes: getAttributes(source, start, end, name),
open: [start, end]
};
return false;
}
}
else {
const tag = last(stack);
if (tag && tag.name === name) {
// Matching closing tag found
if (tag.start < pos && pos < end) {
result = {
name,
attributes: getAttributes(source, tag.start, tag.end, name),
open: [tag.start, tag.end],
close: [start, end]
};
return false;
}
else if (stack.length) {
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
function balancedOutward(source, pos, opt) {
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
const tag = last(stack);
if (tag && tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.start < pos && pos < end) {
result.push({
name,
open: [tag.start, tag.end],
close: [start, end]
});
}
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag
result.push({ name, open: [start, end] });
}
}
else {
stack.push(allocTag(pool, name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
function balancedInward(source, pos, opt) {
// Collecting tags for inward balancing is a bit trickier: we have to store
// first child of every matched tag until we find the one that matches given
// location
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
const alloc = (name, start, end) => {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.ranges.push(start, end);
return tag;
}
return { name, ranges: [start, end] };
};
const release = (tag) => {
tag.ranges.length = 0;
tag.firstChild = void 0;
pool.push(tag);
};
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
if (!stack.length) {
// Some sort of lone closing tag, ignore it
return;
}
let tag = last(stack);
if (tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.ranges[0] <= pos && pos <= end) {
result.push({
name,
open: tag.ranges.slice(0, 2),
close: [start, end]
});
while (tag.firstChild) {
const child = tag.firstChild;
const res = {
name: child.name,
open: child.ranges.slice(0, 2)
};
if (child.ranges.length > 2) {
res.close = child.ranges.slice(2, 4);
}
result.push(res);
release(tag);
tag = child;
}
return false;
}
else {
stack.pop();
const parent = last(stack);
if (parent && !parent.firstChild) {
// No first child in parent node: store current tag
tag.ranges.push(start, end);
parent.firstChild = tag;
}
else {
release(tag);
}
}
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag, no need to look further
result.push({ name, open: [start, end] });
return false;
}
const parent = last(stack);
if (parent && !parent.firstChild) {
parent.firstChild = alloc(name, start, end);
}
}
else {
stack.push(alloc(name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
function allocTag(pool, name, start, end) {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.start = start;
tag.end = end;
return tag;
}
return { name, start, end };
}
function releaseTag(pool, tag) {
pool.push(tag);
}
/**
* Returns parsed attributes from given source
*/
function getAttributes(source, start, end, name) {
const tokens = attributes(source.slice(start, end), name);
tokens.forEach(attr => {
attr.nameStart += start;
attr.nameEnd += start;
if (attr.value != null) {
attr.valueStart += start;
attr.valueEnd += start;
}
});
return tokens;
}
/**
* Check if given tag is self-close for current parsing context
*/
function isSelfClose(name, options) {
return !options.xml && options.empty.includes(name);
}
function last(arr) {
return arr.length ? arr[arr.length - 1] : null;
}
exports.attributes = attributes;
exports.balancedInward = balancedInward;
exports.balancedOutward = balancedOutward;
exports.createOptions = createOptions;
exports.default = match;
exports.scan = scan;
//# sourceMappingURL=html-matcher.cjs.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,630 @@
import Scanner, { eatPair, isAlpha, isNumber, isQuote, isSpace, eatQuoted } from '@emmetio/scanner';
const defaultOptions = {
xml: false,
allTokens: false,
special: {
style: null,
script: ['', 'text/javascript', 'application/x-javascript', 'javascript', 'typescript', 'ts', 'coffee', 'coffeescript']
},
empty: ['img', 'meta', 'link', 'br', 'base', 'hr', 'area', 'wbr', 'col', 'embed', 'input', 'param', 'source', 'track']
};
/** Options for `Scanner` utils */
const opt = { throws: false };
function createOptions(options = {}) {
return Object.assign(Object.assign({}, defaultOptions), options);
}
/**
* Converts given string into array of character codes
*/
function toCharCodes(str) {
return str.split('').map(ch => ch.charCodeAt(0));
}
/**
* Consumes array of character codes from given scanner
*/
function consumeArray(scanner, codes) {
const start = scanner.pos;
for (let i = 0; i < codes.length; i++) {
if (!scanner.eat(codes[i])) {
scanner.pos = start;
return false;
}
}
scanner.start = start;
return true;
}
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
function consumeSection(scanner, open, close, allowUnclosed) {
const start = scanner.pos;
if (consumeArray(scanner, open)) {
// consumed `<!--`, read next until we find ending part or reach the end of input
while (!scanner.eof()) {
if (consumeArray(scanner, close)) {
scanner.start = start;
return true;
}
scanner.pos++;
}
// unclosed section is allowed
if (allowUnclosed) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
// unable to find section, revert to initial position
scanner.pos = start;
return false;
}
/**
* Check if given character can be used as a start of tag name or attribute
*/
function nameStartChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameStartChar
return isAlpha(ch) || ch === 58 /* Colon */ || ch === 95 /* Underscore */
|| (ch >= 0xC0 && ch <= 0xD6)
|| (ch >= 0xD8 && ch <= 0xF6)
|| (ch >= 0xF8 && ch <= 0x2FF)
|| (ch >= 0x370 && ch <= 0x37D)
|| (ch >= 0x37F && ch <= 0x1FFF);
}
/**
* Check if given character can be used in a tag or attribute name
*/
function nameChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameChar
return nameStartChar(ch) || ch === 45 /* Dash */ || ch === 46 /* Dot */ || isNumber(ch)
|| ch === 0xB7
|| (ch >= 0x0300 && ch <= 0x036F);
}
/**
* Consumes identifier from given scanner
*/
function ident(scanner) {
const start = scanner.pos;
if (scanner.eat(nameStartChar)) {
scanner.eatWhile(nameChar);
scanner.start = start;
return true;
}
return false;
}
/**
* Check if given code is tag terminator
*/
function isTerminator(code) {
return code === 62 /* RightAngle */ || code === 47 /* Slash */;
}
/**
* Check if given character code is valid unquoted value
*/
function isUnquoted(code) {
return !isNaN(code) && !isQuote(code) && !isSpace(code) && !isTerminator(code);
}
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
function consumePaired(scanner) {
return eatPair(scanner, 60 /* LeftAngle */, 62 /* RightAngle */, opt)
|| eatPair(scanner, 40 /* LeftRound */, 41 /* RightRound */, opt)
|| eatPair(scanner, 91 /* LeftSquare */, 93 /* RightSquare */, opt)
|| eatPair(scanner, 123 /* LeftCurly */, 125 /* RightCurly */, opt);
}
/**
* Returns unquoted value of given string
*/
function getUnquotedValue(value) {
// Trim quotes
if (isQuote(value.charCodeAt(0))) {
value = value.slice(1);
}
if (isQuote(value.charCodeAt(value.length - 1))) {
value = value.slice(0, -1);
}
return value;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
function attributes(src, name) {
const result = [];
let start = 0;
let end = src.length;
if (name) {
start = name.length + 1;
end -= src.slice(-2) === '/>' ? 2 : 1;
}
const scanner = new Scanner(src, start, end);
while (!scanner.eof()) {
scanner.eatWhile(isSpace);
if (attributeName(scanner)) {
const token = {
name: scanner.current(),
nameStart: scanner.start,
nameEnd: scanner.pos
};
if (scanner.eat(61 /* Equals */) && attributeValue(scanner)) {
token.value = scanner.current();
token.valueStart = scanner.start;
token.valueEnd = scanner.pos;
}
result.push(token);
}
else {
// Do not break on invalid attributes: we are not validating parser
scanner.pos++;
}
}
return result;
}
/**
* Consumes attribute name from given scanner context
*/
function attributeName(scanner) {
const start = scanner.pos;
if (scanner.eat(42 /* Asterisk */) || scanner.eat(35 /* Hash */)) {
// Angular-style directives: `<section *ngIf="showSection">`, `<video #movieplayer ...>`
ident(scanner);
scanner.start = start;
return true;
}
// Attribute name could be a regular name or expression:
// React-style `<div {...props}>`
// Angular-style `<div [ng-for]>` or `<div *ng-for>`
return consumePaired(scanner) || ident(scanner);
}
/**
* Consumes attribute value
*/
function attributeValue(scanner) {
// Supported attribute values are quoted, React-like expressions (`{foo}`)
// or unquoted literals
return eatQuoted(scanner, opt) || consumePaired(scanner) || unquoted(scanner);
}
/**
* Returns clean (unquoted) value of `name` attribute
*/
function getAttributeValue(attrs, name) {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name === name) {
return attr.value && getUnquotedValue(attr.value);
}
}
}
/**
* Consumes unquoted value
*/
function unquoted(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isUnquoted)) {
scanner.start = start;
return true;
}
}
const cdataOpen = toCharCodes('<![CDATA[');
const cdataClose = toCharCodes(']]>');
const commentOpen = toCharCodes('<!--');
const commentClose = toCharCodes('-->');
const piStart = toCharCodes('<?');
const piEnd = toCharCodes('?>');
const erbStart = toCharCodes('<%');
const erbEnd = toCharCodes('%>');
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
function scan(source, callback, options) {
const scanner = new Scanner(source);
const special = options ? options.special : null;
const allTokens = options ? options.allTokens : false;
let type;
let name;
let nameStart;
let nameEnd;
let nameCodes;
let found = false;
let piName = null;
while (!scanner.eof()) {
const start = scanner.pos;
if (cdata(scanner)) {
if (allTokens && callback('#cdata', 4 /* CData */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (comment(scanner)) {
if (allTokens && callback('#comment', 6 /* Comment */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (erb(scanner)) {
if (allTokens && callback('#erb', 7 /* ERB */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (piName = processingInstruction(scanner)) {
if (allTokens && callback(piName, 5 /* ProcessingInstruction */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (scanner.eat(60 /* LeftAngle */)) {
// Maybe a tag name?
type = scanner.eat(47 /* Slash */) ? 2 /* Close */ : 1 /* Open */;
nameStart = scanner.pos;
if (ident(scanner)) {
// Consumed tag name
nameEnd = scanner.pos;
if (type !== 2 /* Close */) {
skipAttributes(scanner);
scanner.eatWhile(isSpace);
if (scanner.eat(47 /* Slash */)) {
type = 3 /* SelfClose */;
}
}
if (scanner.eat(62 /* RightAngle */)) {
// Tag properly closed
name = scanner.substring(nameStart, nameEnd);
if (callback(name, type, start, scanner.pos) === false) {
break;
}
if (type === 1 /* Open */ && special && isSpecial(special, name, source, start, scanner.pos)) {
// Found opening tag of special element: we should skip
// scanner contents until we find closing tag
nameCodes = toCharCodes(name);
found = false;
while (!scanner.eof()) {
if (consumeClosing(scanner, nameCodes)) {
found = true;
break;
}
scanner.pos++;
}
if (found && callback(name, 2 /* Close */, scanner.start, scanner.pos) === false) {
break;
}
}
}
}
}
else {
scanner.pos++;
}
}
}
/**
* Skips attributes in current tag context
*/
function skipAttributes(scanner) {
while (!scanner.eof()) {
scanner.eatWhile(isSpace);
if (attributeName(scanner)) {
if (scanner.eat(61 /* Equals */)) {
attributeValue(scanner);
}
}
else if (isTerminator(scanner.peek())) {
break;
}
else {
scanner.pos++;
}
}
}
/**
* Consumes closing tag with given name from scanner
*/
function consumeClosing(scanner, name) {
const start = scanner.pos;
if (scanner.eat(60 /* LeftAngle */) && scanner.eat(47 /* Slash */) && consumeArray(scanner, name) && scanner.eat(62 /* RightAngle */)) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Consumes CDATA from given scanner
*/
function cdata(scanner) {
return consumeSection(scanner, cdataOpen, cdataClose, true);
}
/**
* Consumes comments from given scanner
*/
function comment(scanner) {
return consumeSection(scanner, commentOpen, commentClose, true);
}
/**
* Consumes processing instruction from given scanner. If consumed, returns
* processing instruction name
*/
function processingInstruction(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, piStart) && ident(scanner)) {
const name = scanner.current();
while (!scanner.eof()) {
if (consumeArray(scanner, piEnd)) {
break;
}
eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return name;
}
scanner.pos = start;
return null;
}
/**
* Consumes ERB-style entity: `<% ... %>` or `<%= ... %>`
*/
function erb(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, erbStart)) {
while (!scanner.eof()) {
if (consumeArray(scanner, erbEnd)) {
break;
}
eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Check if given tag name should be considered as special
*/
function isSpecial(special, name, source, start, end) {
if (name in special) {
const typeValues = special[name];
if (!Array.isArray(typeValues)) {
return true;
}
const attrs = attributes(source.substring(start + name.length + 1, end - 1));
return typeValues.includes(getAttributeValue(attrs, 'type') || '');
}
return false;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
function match(source, pos, opt) {
// Since we expect large input document, well use pooling technique
// for storing tag data to reduce memory pressure and improve performance
const pool = [];
const stack = [];
const options = createOptions(opt);
let result = null;
scan(source, (name, type, start, end) => {
if (type === 1 /* Open */ && isSelfClose(name, options)) {
// Found empty element in HTML mode, mark is as self-closing
type = 3 /* SelfClose */;
}
if (type === 1 /* Open */) {
// Allocate tag object from pool
stack.push(allocTag(pool, name, start, end));
}
else if (type === 3 /* SelfClose */) {
if (start < pos && pos < end) {
// Matched given self-closing tag
result = {
name,
attributes: getAttributes(source, start, end, name),
open: [start, end]
};
return false;
}
}
else {
const tag = last(stack);
if (tag && tag.name === name) {
// Matching closing tag found
if (tag.start < pos && pos < end) {
result = {
name,
attributes: getAttributes(source, tag.start, tag.end, name),
open: [tag.start, tag.end],
close: [start, end]
};
return false;
}
else if (stack.length) {
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
function balancedOutward(source, pos, opt) {
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
const tag = last(stack);
if (tag && tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.start < pos && pos < end) {
result.push({
name,
open: [tag.start, tag.end],
close: [start, end]
});
}
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag
result.push({ name, open: [start, end] });
}
}
else {
stack.push(allocTag(pool, name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
function balancedInward(source, pos, opt) {
// Collecting tags for inward balancing is a bit trickier: we have to store
// first child of every matched tag until we find the one that matches given
// location
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
const alloc = (name, start, end) => {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.ranges.push(start, end);
return tag;
}
return { name, ranges: [start, end] };
};
const release = (tag) => {
tag.ranges.length = 0;
tag.firstChild = void 0;
pool.push(tag);
};
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
if (!stack.length) {
// Some sort of lone closing tag, ignore it
return;
}
let tag = last(stack);
if (tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.ranges[0] <= pos && pos <= end) {
result.push({
name,
open: tag.ranges.slice(0, 2),
close: [start, end]
});
while (tag.firstChild) {
const child = tag.firstChild;
const res = {
name: child.name,
open: child.ranges.slice(0, 2)
};
if (child.ranges.length > 2) {
res.close = child.ranges.slice(2, 4);
}
result.push(res);
release(tag);
tag = child;
}
return false;
}
else {
stack.pop();
const parent = last(stack);
if (parent && !parent.firstChild) {
// No first child in parent node: store current tag
tag.ranges.push(start, end);
parent.firstChild = tag;
}
else {
release(tag);
}
}
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag, no need to look further
result.push({ name, open: [start, end] });
return false;
}
const parent = last(stack);
if (parent && !parent.firstChild) {
parent.firstChild = alloc(name, start, end);
}
}
else {
stack.push(alloc(name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
function allocTag(pool, name, start, end) {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.start = start;
tag.end = end;
return tag;
}
return { name, start, end };
}
function releaseTag(pool, tag) {
pool.push(tag);
}
/**
* Returns parsed attributes from given source
*/
function getAttributes(source, start, end, name) {
const tokens = attributes(source.slice(start, end), name);
tokens.forEach(attr => {
attr.nameStart += start;
attr.nameEnd += start;
if (attr.value != null) {
attr.valueStart += start;
attr.valueEnd += start;
}
});
return tokens;
}
/**
* Check if given tag is self-close for current parsing context
*/
function isSelfClose(name, options) {
return !options.xml && options.empty.includes(name);
}
function last(arr) {
return arr.length ? arr[arr.length - 1] : null;
}
export default match;
export { attributes, balancedInward, balancedOutward, createOptions, scan };
//# sourceMappingURL=html-matcher.es.js.map

File diff suppressed because one or more lines are too long

38
node_modules/@emmetio/html-matcher/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,38 @@
import { ScannerOptions } from './utils';
import scan from './scan';
import attributes, { AttributeToken } from './attributes';
export { scan, attributes, AttributeToken };
export { createOptions, ScannerOptions, ElementType, FastScanCallback } from './utils';
declare type TagRange = [number, number];
export interface MatchedTag {
/** Name of matched tag */
name: string;
/** List of tag attributes */
attributes: AttributeToken[];
/** Range of opening tag */
open: TagRange;
/** Range of closing tag. If absent, tag is self-closing */
close?: TagRange;
}
export interface BalancedTag {
/** Name of balanced tag */
name: string;
/** Range of opening tag */
open: TagRange;
/** Range of closing tag. If absent, tag is self-closing */
close?: TagRange;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
export default function match(source: string, pos: number, opt?: Partial<ScannerOptions>): MatchedTag | null;
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
export declare function balancedOutward(source: string, pos: number, opt?: Partial<ScannerOptions>): BalancedTag[];
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
export declare function balancedInward(source: string, pos: number, opt?: Partial<ScannerOptions>): BalancedTag[];

11
node_modules/@emmetio/html-matcher/dist/scan.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import { FastScanCallback, ScannerOptions } from './utils';
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
export default function scan(source: string, callback: FastScanCallback, options?: ScannerOptions): void;

123
node_modules/@emmetio/html-matcher/dist/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,123 @@
import Scanner from '@emmetio/scanner';
export declare type FastScanCallback = (name: string, type: ElementType, start: number, end: number) => false | any;
export declare const enum ElementType {
Open = 1,
Close = 2,
SelfClose = 3,
CData = 4,
ProcessingInstruction = 5,
Comment = 6,
ERB = 7
}
export interface SpecialType {
[tagName: string]: string[] | null;
}
export declare const enum Chars {
/** `-` character */
Dash = 45,
/** `.` character */
Dot = 46,
/** `/` character */
Slash = 47,
/** `:` character */
Colon = 58,
/** `<` character */
LeftAngle = 60,
/** `>` character */
RightAngle = 62,
/** `(` character */
LeftRound = 40,
/** `)` character */
RightRound = 41,
/** `[` character */
LeftSquare = 91,
/** `]` character */
RightSquare = 93,
/** `{` character */
LeftCurly = 123,
/** `}` character */
RightCurly = 125,
/** `_` character */
Underscore = 95,
/** `=` character */
Equals = 61,
/** `*` character */
Asterisk = 42,
/** `#` character */
Hash = 35
}
export interface ScannerOptions {
/**
* Parses given source as XML document. It alters how should-be-empty
* elements are treated: for example, in XML mode parser will try to locate
* closing pair for `<br>` tag
*/
xml: boolean;
/**
* List of tags that should have special parsing rules, e.g. should not parse
* inner content and skip to closing tag. Key is a tag name that should be
* considered special and value is either empty (always mark element as special)
* or list of `type` attribute values, which, if present with one of this value,
* make element special
*/
special: SpecialType;
/**
* List of elements that should be treated as empty (e.g. without closing tag)
* in non-XML syntax
*/
empty: string[];
/**
* If enabled, scanner callback will receive XML tokes, including comment, cdata
* and processing instructions. If disabled, only tags are emitted
*/
allTokens: boolean;
}
/** Options for `Scanner` utils */
export declare const opt: {
throws: boolean;
};
export declare function createOptions(options?: Partial<ScannerOptions>): ScannerOptions;
/**
* Converts given string into array of character codes
*/
export declare function toCharCodes(str: string): number[];
/**
* Consumes array of character codes from given scanner
*/
export declare function consumeArray(scanner: Scanner, codes: number[]): boolean;
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
export declare function consumeSection(scanner: Scanner, open: number[], close: number[], allowUnclosed?: boolean): boolean;
/**
* Check if given character can be used as a start of tag name or attribute
*/
export declare function nameStartChar(ch: number): boolean;
/**
* Check if given character can be used in a tag or attribute name
*/
export declare function nameChar(ch: number): boolean;
/**
* Consumes identifier from given scanner
*/
export declare function ident(scanner: Scanner): boolean;
/**
* Check if given code is tag terminator
*/
export declare function isTerminator(code: number): boolean;
/**
* Check if given character code is valid unquoted value
*/
export declare function isUnquoted(code: number): boolean;
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
export declare function consumePaired(scanner: Scanner): boolean;
/**
* Returns unquoted value of given string
*/
export declare function getUnquotedValue(value: string): string;

47
node_modules/@emmetio/html-matcher/package.json generated vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"name": "@emmetio/html-matcher",
"version": "1.3.0",
"description": "Minimalistic and ultra-fast HTML parser & matcher",
"main": "./dist/html-matcher.cjs.js",
"module": "./dist/html-matcher.es.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@emmetio/scanner": "^1.0.0"
},
"devDependencies": {
"@types/mocha": "^7.0.2",
"@types/node": "^12.7.5",
"mocha": "^7.1.1",
"rollup": "^2.6.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.27.0",
"ts-node": "^8.8.2",
"tslint": "^6.1.1",
"typescript": "^3.8.3"
},
"scripts": {
"test": "mocha",
"lint": "tslint ./src/*.ts",
"build": "rollup -c",
"clean": "rm -rf ./dist",
"prepare": "npm run lint && npm test && npm run clean && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/html-matcher.git"
},
"keywords": [],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "ISC",
"bugs": {
"url": "https://github.com/emmetio/html-matcher/issues"
},
"homepage": "https://github.com/emmetio/html-matcher#readme",
"directories": {
"test": "test"
},
"mocha": {
"require": "ts-node/register",
"spec": "./test/*.ts"
}
}