clarity-pattern-parser 11.0.21 → 11.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grammar/Grammar.d.ts +0 -1
- package/dist/index.browser.js +280 -256
- package/dist/index.browser.js.map +1 -1
- package/dist/index.esm.js +280 -256
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +280 -256
- package/dist/index.js.map +1 -1
- package/dist/patterns/Sequence.d.ts +2 -1
- package/dist/patterns/generate_error_message.d.ts +3 -0
- package/package.json +1 -1
- package/src/grammar/Grammar.ts +11 -22
- package/src/patterns/Literal.ts +4 -0
- package/src/patterns/Reference.ts +3 -1
- package/src/patterns/Sequence.test.ts +11 -0
- package/src/patterns/Sequence.ts +13 -4
- package/src/patterns/generate_error_message.ts +33 -0
- package/src/query/query.test.ts +174 -0
- package/src/query/query.ts +152 -16
- package/src/query/selector.test.ts +262 -0
- package/src/query/selector.ts +280 -0
- package/src/query/selector_parser.ts +44 -3
|
@@ -24,7 +24,8 @@ export declare class Sequence implements Pattern {
|
|
|
24
24
|
parse(cursor: Cursor): Node | null;
|
|
25
25
|
private tryToParse;
|
|
26
26
|
private getLastValidNode;
|
|
27
|
-
private
|
|
27
|
+
private _areAllPatternsOptional;
|
|
28
|
+
private _areRemainingPatternsOptional;
|
|
28
29
|
private createNode;
|
|
29
30
|
getTokens(): string[];
|
|
30
31
|
getTokensAfter(childReference: Pattern): string[];
|
package/package.json
CHANGED
package/src/grammar/Grammar.ts
CHANGED
|
@@ -8,11 +8,11 @@ import { Options } from "../patterns/Options";
|
|
|
8
8
|
import { Not } from "../patterns/Not";
|
|
9
9
|
import { Sequence } from "../patterns/Sequence";
|
|
10
10
|
import { Repeat, RepeatOptions } from "../patterns/Repeat";
|
|
11
|
-
import { AutoComplete } from "../intellisense/AutoComplete";
|
|
12
11
|
import { Optional } from "../patterns/Optional";
|
|
13
12
|
import { Context } from "../patterns/Context";
|
|
14
13
|
import { Expression } from "../patterns/Expression";
|
|
15
14
|
import { RightAssociated } from "../patterns/RightAssociated";
|
|
15
|
+
import { generateErrorMessage } from "../patterns/generate_error_message";
|
|
16
16
|
|
|
17
17
|
let anonymousIndexId = 0;
|
|
18
18
|
|
|
@@ -55,16 +55,12 @@ export class Grammar {
|
|
|
55
55
|
private _originResource?: string | null;
|
|
56
56
|
private _resolveImport: (resource: string, originResource: string | null) => Promise<GrammarFile>;
|
|
57
57
|
private _parseContext: ParseContext;
|
|
58
|
-
private _autoComplete: AutoComplete;
|
|
59
58
|
|
|
60
59
|
constructor(options: GrammarOptions = {}) {
|
|
61
60
|
this._params = options?.params == null ? [] : options.params;
|
|
62
61
|
this._originResource = options?.originResource == null ? null : options.originResource;
|
|
63
62
|
this._resolveImport = options.resolveImport == null ? defaultImportResolver : options.resolveImport;
|
|
64
63
|
this._parseContext = new ParseContext(this._params);
|
|
65
|
-
this._autoComplete = new AutoComplete(grammar, {
|
|
66
|
-
greedyPatternNames: ["spaces", "optional-spaces", "whitespace", "new-line"],
|
|
67
|
-
});
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
async import(path: string) {
|
|
@@ -113,22 +109,14 @@ export class Grammar {
|
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
private _tryToParse(expression: string): Node {
|
|
116
|
-
const { ast, cursor
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const foundText = text.slice(Math.max(index - 10, 0), index + 10);
|
|
122
|
-
const expectedTexts = "'" + options.map(o => {
|
|
123
|
-
const startText = text.slice(Math.max(o.startIndex - 10), o.startIndex);
|
|
124
|
-
return startText + o.text;
|
|
125
|
-
}).join("' or '") + "'";
|
|
126
|
-
const message = `[Parse Error] Found: '${foundText}', expected: ${expectedTexts}.`;
|
|
127
|
-
throw new Error(message);
|
|
112
|
+
const { ast, cursor } = grammar.exec(expression, true);
|
|
113
|
+
|
|
114
|
+
if (ast == null) {
|
|
115
|
+
const message = generateErrorMessage(grammar, cursor);
|
|
116
|
+
throw new Error(`[Invalid Grammar] ${message}`);
|
|
128
117
|
}
|
|
129
118
|
|
|
130
|
-
|
|
131
|
-
return ast as Node;
|
|
119
|
+
return ast;
|
|
132
120
|
}
|
|
133
121
|
|
|
134
122
|
private _hasImports(ast: Node) {
|
|
@@ -291,9 +279,10 @@ export class Grammar {
|
|
|
291
279
|
const firstChild = pattern.children[0];
|
|
292
280
|
const lastChild = pattern.children[pattern.children.length - 1];
|
|
293
281
|
const isLongEnough = pattern.children.length >= 2;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
(
|
|
282
|
+
|
|
283
|
+
return pattern.type === "sequence" && isLongEnough &&
|
|
284
|
+
(firstChild.name === name ||
|
|
285
|
+
lastChild.name === name);
|
|
297
286
|
}
|
|
298
287
|
|
|
299
288
|
private _buildPattern(node: Node): Pattern {
|
package/src/patterns/Literal.ts
CHANGED
|
@@ -94,6 +94,10 @@ export class Literal implements Pattern {
|
|
|
94
94
|
let passed = false;
|
|
95
95
|
const literalRuneLength = this._runes.length;
|
|
96
96
|
|
|
97
|
+
if (!cursor.hasNext){
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
for (let i = 0; i < literalRuneLength; i++) {
|
|
98
102
|
const literalRune = this._runes[i];
|
|
99
103
|
const cursorRune = cursor.currentChar;
|
|
@@ -86,6 +86,8 @@ export class Reference implements Pattern {
|
|
|
86
86
|
|
|
87
87
|
private _cacheAncestors(id: string) {
|
|
88
88
|
if (!this._cachedAncestors) {
|
|
89
|
+
this._cachedAncestors = true;
|
|
90
|
+
|
|
89
91
|
let pattern: Pattern | null = this.parent;
|
|
90
92
|
|
|
91
93
|
while (pattern != null) {
|
|
@@ -101,7 +103,7 @@ export class Reference implements Pattern {
|
|
|
101
103
|
let depth = 0;
|
|
102
104
|
|
|
103
105
|
for (let pattern of this._recursiveAncestors) {
|
|
104
|
-
if (pattern.
|
|
106
|
+
if (pattern.startedOnIndex === this.startedOnIndex) {
|
|
105
107
|
depth++;
|
|
106
108
|
|
|
107
109
|
if (depth > 0) {
|
|
@@ -323,4 +323,15 @@ describe("Sequence", () => {
|
|
|
323
323
|
expect(nextPatterns.length).toBe(0);
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
+
test("All Patterns are Optional", () => {
|
|
327
|
+
const sequence = new Sequence("sequence", [
|
|
328
|
+
new Optional("optional-a", new Literal("a", "A")),
|
|
329
|
+
new Optional("optional-b", new Literal("b", "B"))
|
|
330
|
+
]);
|
|
331
|
+
const result = sequence.exec("");
|
|
332
|
+
|
|
333
|
+
expect(result.ast).toBe(null);
|
|
334
|
+
expect(result.cursor.hasError).toBeFalsy();
|
|
335
|
+
});
|
|
336
|
+
|
|
326
337
|
});
|
package/src/patterns/Sequence.ts
CHANGED
|
@@ -83,8 +83,8 @@ export class Sequence implements Pattern {
|
|
|
83
83
|
this._firstIndex = cursor.index;
|
|
84
84
|
this._nodes = [];
|
|
85
85
|
const passed = this.tryToParse(cursor);
|
|
86
|
-
|
|
87
86
|
if (passed) {
|
|
87
|
+
|
|
88
88
|
const node = this.createNode(cursor);
|
|
89
89
|
|
|
90
90
|
if (node !== null) {
|
|
@@ -121,7 +121,7 @@ export class Sequence implements Pattern {
|
|
|
121
121
|
} else {
|
|
122
122
|
// We are at the end of the text, it may still be valid, if all the
|
|
123
123
|
// following patterns are optional.
|
|
124
|
-
if (this.
|
|
124
|
+
if (this._areRemainingPatternsOptional(i)) {
|
|
125
125
|
passed = true;
|
|
126
126
|
break;
|
|
127
127
|
}
|
|
@@ -139,7 +139,7 @@ export class Sequence implements Pattern {
|
|
|
139
139
|
} else {
|
|
140
140
|
// If we don't have any results from what we parsed then record error.
|
|
141
141
|
const lastNode = this.getLastValidNode();
|
|
142
|
-
if (lastNode === null) {
|
|
142
|
+
if (lastNode === null && !this._areAllPatternsOptional()) {
|
|
143
143
|
cursor.recordErrorAt(this._firstIndex, cursor.index, this);
|
|
144
144
|
break;
|
|
145
145
|
}
|
|
@@ -168,7 +168,11 @@ export class Sequence implements Pattern {
|
|
|
168
168
|
return nodes[nodes.length - 1];
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
private
|
|
171
|
+
private _areAllPatternsOptional() {
|
|
172
|
+
return this._areRemainingPatternsOptional(-1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private _areRemainingPatternsOptional(fromIndex: number): boolean {
|
|
172
176
|
const startOnIndex = fromIndex + 1;
|
|
173
177
|
const length = this._children.length;
|
|
174
178
|
|
|
@@ -185,6 +189,11 @@ export class Sequence implements Pattern {
|
|
|
185
189
|
private createNode(cursor: Cursor): Node | null {
|
|
186
190
|
const children = filterOutNull(this._nodes);
|
|
187
191
|
|
|
192
|
+
if (children.length === 0) {
|
|
193
|
+
cursor.moveTo(this._firstIndex);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
188
197
|
const lastIndex = children[children.length - 1].lastIndex;
|
|
189
198
|
|
|
190
199
|
cursor.moveTo(lastIndex);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Cursor } from "./Cursor";
|
|
2
|
+
import { Pattern } from "./Pattern";
|
|
3
|
+
|
|
4
|
+
export function generateErrorMessage(pattern: Pattern, cursor: Cursor) {
|
|
5
|
+
const furthestMatch = cursor.leafMatch;
|
|
6
|
+
|
|
7
|
+
if (furthestMatch == null || furthestMatch.node == null || furthestMatch.pattern == null) {
|
|
8
|
+
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
|
|
9
|
+
return `Error at line 1, column 1. Hint: ${suggestions}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const endIndex = furthestMatch.node.endIndex;
|
|
13
|
+
|
|
14
|
+
if (endIndex === 0) {
|
|
15
|
+
const suggestions = cleanSuggestions(pattern.getTokens()).join(", ");
|
|
16
|
+
return `Error at line 1, column 1. Hint: ${suggestions}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const lastPattern = furthestMatch.pattern as Pattern;
|
|
20
|
+
const suggestions = cleanSuggestions(lastPattern.getTokens());
|
|
21
|
+
const strUpToError = cursor.getChars(0, endIndex);
|
|
22
|
+
const lines = strUpToError.split("\n");
|
|
23
|
+
const lastLine = lines[lines.length - 1];
|
|
24
|
+
const line = lines.length;
|
|
25
|
+
const column = lastLine.length;
|
|
26
|
+
|
|
27
|
+
return `Error at line ${line}, column ${column}. Hint: ${suggestions}`;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cleanSuggestions(suggestions: string[]) {
|
|
32
|
+
return suggestions.map(s => s.trim()).filter(s => s.length > 0);
|
|
33
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Node } from "../ast/Node";
|
|
2
|
+
import { $ } from "./query";
|
|
3
|
+
|
|
4
|
+
function createNodeTree() {
|
|
5
|
+
const node = Node.createNode("node", "root", [
|
|
6
|
+
Node.createNode("node", "grand-parent", [
|
|
7
|
+
Node.createNode("node", "parent", [
|
|
8
|
+
Node.createValueNode("node", 'child', "1"),
|
|
9
|
+
Node.createValueNode("node", 'child', "2"),
|
|
10
|
+
Node.createValueNode("node", 'child', "3"),
|
|
11
|
+
Node.createValueNode("node", 'child', "4"),
|
|
12
|
+
]),
|
|
13
|
+
Node.createNode("node", "parent", [
|
|
14
|
+
Node.createValueNode("node", 'child', "5"),
|
|
15
|
+
Node.createValueNode("node", 'child', "6"),
|
|
16
|
+
Node.createValueNode("node", 'child', "7"),
|
|
17
|
+
Node.createValueNode("node", 'child', "8"),
|
|
18
|
+
]),
|
|
19
|
+
Node.createNode("node", "parent", [
|
|
20
|
+
Node.createValueNode("node", 'child', "9"),
|
|
21
|
+
Node.createValueNode("node", 'child', "10"),
|
|
22
|
+
Node.createValueNode("node", 'child', "11"),
|
|
23
|
+
Node.createValueNode("node", 'child', "12"),
|
|
24
|
+
])
|
|
25
|
+
]),
|
|
26
|
+
Node.createNode("node", "grand-parent", [
|
|
27
|
+
Node.createNode("node", "parent", [
|
|
28
|
+
Node.createValueNode("node", 'child', "13"),
|
|
29
|
+
Node.createValueNode("node", 'child', "14"),
|
|
30
|
+
Node.createValueNode("node", 'child', "15"),
|
|
31
|
+
Node.createValueNode("node", 'child', "16"),
|
|
32
|
+
]),
|
|
33
|
+
Node.createNode("node", "parent", [
|
|
34
|
+
Node.createValueNode("node", 'child', "17"),
|
|
35
|
+
Node.createValueNode("node", 'child', "18"),
|
|
36
|
+
Node.createValueNode("node", 'child', "19"),
|
|
37
|
+
Node.createValueNode("node", 'child', "20"),
|
|
38
|
+
]),
|
|
39
|
+
Node.createNode("node", "parent", [
|
|
40
|
+
Node.createValueNode("node", 'child', "21"),
|
|
41
|
+
Node.createValueNode("node", 'child', "22"),
|
|
42
|
+
Node.createValueNode("node", 'child', "23"),
|
|
43
|
+
Node.createValueNode("node", 'child', "24"),
|
|
44
|
+
])
|
|
45
|
+
])
|
|
46
|
+
]);
|
|
47
|
+
node.normalize();
|
|
48
|
+
return node;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("Query", () => {
|
|
52
|
+
test("Construct", () => {
|
|
53
|
+
const root = createNodeTree();
|
|
54
|
+
|
|
55
|
+
const children = $(root, "child");
|
|
56
|
+
const result = children.toArray().map(n => n.value).join(",");
|
|
57
|
+
|
|
58
|
+
expect(result).toBe("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("First", () => {
|
|
62
|
+
const root = createNodeTree();
|
|
63
|
+
|
|
64
|
+
const children = $(root, "child").first();
|
|
65
|
+
const result = children.toArray().map(n => n.value).join(",");
|
|
66
|
+
|
|
67
|
+
expect(result).toBe("1");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("Last", () => {
|
|
71
|
+
const root = createNodeTree();
|
|
72
|
+
|
|
73
|
+
const children = $(root, "child").last();
|
|
74
|
+
const result = children.toArray().map(n => n.value).join(",");
|
|
75
|
+
|
|
76
|
+
expect(result).toBe("24");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("Parent", () => {
|
|
80
|
+
const root = createNodeTree();
|
|
81
|
+
|
|
82
|
+
const children = $(root, "child").parent();
|
|
83
|
+
const result = children.toArray();
|
|
84
|
+
|
|
85
|
+
expect(result.length).toBe(6);
|
|
86
|
+
expect(result.map(n => n.value).join(",")).toBe("1234,5678,9101112,13141516,17181920,21222324");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("Parents", () => {
|
|
90
|
+
const root = createNodeTree();
|
|
91
|
+
|
|
92
|
+
const children = $(root, "child").parents("grand-parent parent[value='1234']");
|
|
93
|
+
const result = children.toArray();
|
|
94
|
+
|
|
95
|
+
expect(result.length).toBe(1);
|
|
96
|
+
expect(result.map(n => n.value).join(",")).toBe("1234");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("Filter", () => {
|
|
100
|
+
const root = createNodeTree();
|
|
101
|
+
|
|
102
|
+
const children = $(root, "parent").filter("grand-parent parent[value='1234']");
|
|
103
|
+
const result = children.toArray();
|
|
104
|
+
|
|
105
|
+
expect(result.length).toBe(1);
|
|
106
|
+
expect(result.map(n => n.value).join(",")).toBe("1234");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("Not", () => {
|
|
110
|
+
const root = createNodeTree();
|
|
111
|
+
|
|
112
|
+
const children = $(root, "parent").not("grand-parent parent[value='1234']");
|
|
113
|
+
const result = children.toArray();
|
|
114
|
+
|
|
115
|
+
expect(result.length).toBe(5);
|
|
116
|
+
expect(result.map(n => n.value).join(",")).toBe("5678,9101112,13141516,17181920,21222324");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("First Before", () => {
|
|
120
|
+
const root = createNodeTree();
|
|
121
|
+
|
|
122
|
+
$(root, "child").first().before(() => Node.createValueNode("node", "adopted-child", "0"));
|
|
123
|
+
|
|
124
|
+
const adoptedChild = root.find(n => n.name === "adopted-child");
|
|
125
|
+
const firstChild = root.find(n => n.value === "1");
|
|
126
|
+
expect(adoptedChild?.nextSibling()).toBe(firstChild);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("Last After", () => {
|
|
130
|
+
const root = createNodeTree();
|
|
131
|
+
|
|
132
|
+
$(root, "child").last().after(() => Node.createValueNode("node", "adopted-child", "25"));
|
|
133
|
+
|
|
134
|
+
const adoptedChild = root.find(n => n.name === "adopted-child");
|
|
135
|
+
const lastChild = root.find(n => n.value === "24");
|
|
136
|
+
expect(adoptedChild?.previousSibling()).toBe(lastChild);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("Append", () => {
|
|
140
|
+
const root = createNodeTree();
|
|
141
|
+
|
|
142
|
+
const parents = $(root, "parent").append((n) => {
|
|
143
|
+
const lastChild = n.children[n.children.length - 1];
|
|
144
|
+
const value = Number(lastChild.value) + 1;
|
|
145
|
+
return Node.createValueNode("node", "adopted-child", String(value));
|
|
146
|
+
}).toArray();
|
|
147
|
+
|
|
148
|
+
const haveFiveChildren = parents.every(n => n.children.length === 5);
|
|
149
|
+
expect(haveFiveChildren).toBeTruthy();
|
|
150
|
+
expect(parents.map(n => n.toString()).join(",")).toBe("12345,56789,910111213,1314151617,1718192021,2122232425");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("Prepend", () => {
|
|
154
|
+
const root = createNodeTree();
|
|
155
|
+
|
|
156
|
+
const parents = $(root, "parent").prepend((n) => {
|
|
157
|
+
const firstChild = n.children[0];
|
|
158
|
+
const value = Number(firstChild.value) - 1;
|
|
159
|
+
return Node.createValueNode("node", "adopted-child", String(value));
|
|
160
|
+
}).toArray();
|
|
161
|
+
|
|
162
|
+
const haveFiveChildren = parents.every(n => n.children.length === 5);
|
|
163
|
+
expect(haveFiveChildren).toBeTruthy();
|
|
164
|
+
expect(parents.map(n => n.toString()).join(",")).toBe("01234,45678,89101112,1213141516,1617181920,2021222324");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("ReplaceWith", () => {
|
|
168
|
+
const root = createNodeTree();
|
|
169
|
+
|
|
170
|
+
$(root, "child").replaceWith(() => Node.createValueNode("node","child","b"));
|
|
171
|
+
|
|
172
|
+
expect(root.toString()).toBe("bbbbbbbbbbbbbbbbbbbbbbbb");
|
|
173
|
+
});
|
|
174
|
+
});
|
package/src/query/query.ts
CHANGED
|
@@ -1,41 +1,168 @@
|
|
|
1
|
+
import { Node } from "../ast/Node";
|
|
2
|
+
import { Selector } from "./selector";
|
|
3
|
+
|
|
1
4
|
export class Query {
|
|
2
5
|
private _context: Node[];
|
|
3
6
|
private _prevQuery: Query | null;
|
|
4
7
|
|
|
5
|
-
constructor(context: Node[],
|
|
8
|
+
constructor(context: Node[], prevQuery: Query | null = null) {
|
|
6
9
|
this._context = context;
|
|
7
10
|
this._prevQuery = prevQuery;
|
|
11
|
+
}
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
toArray() {
|
|
14
|
+
return this._context.slice();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Modifiers
|
|
18
|
+
append(visitor: (node: Node) => Node) {
|
|
19
|
+
this._context.forEach(n => {
|
|
20
|
+
const parent = n.parent;
|
|
21
|
+
|
|
22
|
+
if (parent == null) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const newNode = visitor(n);
|
|
26
|
+
n.appendChild(newNode);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
prepend(visitor: (node: Node) => Node) {
|
|
33
|
+
this._context.forEach(n => {
|
|
34
|
+
const parent = n.parent;
|
|
35
|
+
|
|
36
|
+
if (parent == null) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const newNode = visitor(n);
|
|
40
|
+
n.insertBefore(newNode, n.children[0]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
after(visitor: (node: Node) => Node) {
|
|
47
|
+
this._context.forEach(n => {
|
|
48
|
+
const parent = n.parent;
|
|
49
|
+
|
|
50
|
+
if (parent == null) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const index = parent.findChildIndex(n);
|
|
54
|
+
const newNode = visitor(n);
|
|
55
|
+
|
|
56
|
+
parent.spliceChildren(index + 1, 0, newNode);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
before(visitor: (node: Node) => Node) {
|
|
63
|
+
this._context.forEach(n => {
|
|
64
|
+
const parent = n.parent;
|
|
65
|
+
|
|
66
|
+
if (parent == null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const index = parent.findChildIndex(n);
|
|
70
|
+
const newNode = visitor(n);
|
|
71
|
+
|
|
72
|
+
parent.spliceChildren(index, 0, newNode);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
replaceWith(visitor: (node: Node) => Node) {
|
|
79
|
+
this._context.forEach(n => {
|
|
80
|
+
const newNode = visitor(n);
|
|
81
|
+
n.replaceWith(newNode);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
compact() {
|
|
88
|
+
this._context.forEach(n => {
|
|
89
|
+
n.compact();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
remove() {
|
|
96
|
+
this._context.forEach(n => {
|
|
97
|
+
n.remove();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return this;
|
|
12
101
|
}
|
|
13
102
|
|
|
103
|
+
// Filters from the currently matched nodes
|
|
14
104
|
slice(start: number, end?: number) {
|
|
15
105
|
return new Query(this._context.slice(start, end));
|
|
16
106
|
}
|
|
17
107
|
|
|
18
|
-
|
|
19
|
-
|
|
108
|
+
filter(selectorString: string) {
|
|
109
|
+
const selector = new Selector(selectorString);
|
|
110
|
+
const newContext = selector.filter(this._context);
|
|
111
|
+
|
|
112
|
+
return new Query(newContext, this);
|
|
113
|
+
}
|
|
20
114
|
|
|
21
|
-
|
|
115
|
+
// Selects out of all descedants of currently matched nodes
|
|
116
|
+
find(selectorString: string) {
|
|
117
|
+
const selector = new Selector(selectorString);
|
|
118
|
+
const newContext = selector.find(this._context);
|
|
22
119
|
|
|
23
|
-
|
|
120
|
+
return new Query(newContext, this);
|
|
121
|
+
}
|
|
24
122
|
|
|
25
|
-
|
|
123
|
+
// Remove nodes from the set of matched nodes.
|
|
124
|
+
not(selectorString: string) {
|
|
125
|
+
const selector = new Selector(selectorString);
|
|
126
|
+
const newContext = selector.not(this._context);
|
|
26
127
|
|
|
128
|
+
return new Query(newContext, this);
|
|
129
|
+
}
|
|
27
130
|
|
|
28
|
-
//
|
|
29
|
-
|
|
131
|
+
// Select the parent of currently matched nodes
|
|
132
|
+
parent() {
|
|
133
|
+
const parents = this._context.map(n => n.parent);
|
|
134
|
+
const result = new Set<Node>();
|
|
135
|
+
parents.forEach((n) => {
|
|
136
|
+
if (n != null) {
|
|
137
|
+
result.add(n);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return new Query(Array.from(result), this);
|
|
142
|
+
}
|
|
30
143
|
|
|
31
|
-
|
|
144
|
+
// Select the ancestors of currently matched nodes
|
|
145
|
+
parents(selectorString: string) {
|
|
146
|
+
const selector = new Selector(selectorString);
|
|
147
|
+
const newContext = selector.parents(this._context);
|
|
32
148
|
|
|
33
|
-
|
|
149
|
+
const result = new Set<Node>();
|
|
150
|
+
newContext.forEach((n) => {
|
|
151
|
+
if (n != null) {
|
|
152
|
+
result.add(n);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
34
155
|
|
|
35
|
-
|
|
156
|
+
return new Query(Array.from(result), this);
|
|
157
|
+
}
|
|
36
158
|
|
|
37
|
-
|
|
159
|
+
first() {
|
|
160
|
+
return new Query(this._context.slice(0, 1), this);
|
|
161
|
+
}
|
|
38
162
|
|
|
163
|
+
last() {
|
|
164
|
+
return new Query(this._context.slice(-1), this);
|
|
165
|
+
}
|
|
39
166
|
|
|
40
167
|
// Pop query stack
|
|
41
168
|
end() {
|
|
@@ -45,4 +172,13 @@ export class Query {
|
|
|
45
172
|
return this;
|
|
46
173
|
}
|
|
47
174
|
|
|
48
|
-
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function $(root: Node, selector?: string) {
|
|
178
|
+
if (selector == null) {
|
|
179
|
+
return new Query([root]);
|
|
180
|
+
} else {
|
|
181
|
+
return new Query([root]).find(selector);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
}
|