Revamping to matrix style
This commit is contained in:
21
node_modules/@emmetio/html-matcher/LICENSE
generated
vendored
Normal file
21
node_modules/@emmetio/html-matcher/LICENSE
generated
vendored
Normal 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
50
node_modules/@emmetio/html-matcher/README.md
generated
vendored
Normal 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 it’s 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.
|
||||
29
node_modules/@emmetio/html-matcher/dist/attributes.d.ts
generated
vendored
Normal file
29
node_modules/@emmetio/html-matcher/dist/attributes.d.ts
generated
vendored
Normal 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;
|
||||
641
node_modules/@emmetio/html-matcher/dist/html-matcher.cjs.js
generated
vendored
Normal file
641
node_modules/@emmetio/html-matcher/dist/html-matcher.cjs.js
generated
vendored
Normal 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 doesn’t 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
|
||||
* it’s 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, we’ll 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
|
||||
1
node_modules/@emmetio/html-matcher/dist/html-matcher.cjs.js.map
generated
vendored
Normal file
1
node_modules/@emmetio/html-matcher/dist/html-matcher.cjs.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
630
node_modules/@emmetio/html-matcher/dist/html-matcher.es.js
generated
vendored
Normal file
630
node_modules/@emmetio/html-matcher/dist/html-matcher.es.js
generated
vendored
Normal 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 doesn’t 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
|
||||
* it’s 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, we’ll 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
|
||||
1
node_modules/@emmetio/html-matcher/dist/html-matcher.es.js.map
generated
vendored
Normal file
1
node_modules/@emmetio/html-matcher/dist/html-matcher.es.js.map
generated
vendored
Normal file
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
38
node_modules/@emmetio/html-matcher/dist/index.d.ts
generated
vendored
Normal 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
11
node_modules/@emmetio/html-matcher/dist/scan.d.ts
generated
vendored
Normal 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 doesn’t 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
|
||||
* it’s 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
123
node_modules/@emmetio/html-matcher/dist/utils.d.ts
generated
vendored
Normal 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
47
node_modules/@emmetio/html-matcher/package.json
generated
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user