@stxt-lang/core 0.6.2 → 0.7.0
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/README.md +42 -6
- package/out/all.d.ts +2 -0
- package/out/all.js +5 -1
- package/out/core/InlineNode.d.ts +126 -0
- package/out/core/InlineNode.js +152 -0
- package/out/core/Node.d.ts +77 -59
- package/out/core/Node.js +124 -112
- package/out/core/NodeCreator.d.ts +5 -5
- package/out/core/NodeCreator.js +12 -9
- package/out/core/Parser.js +22 -16
- package/out/core/TextNode.d.ts +63 -0
- package/out/core/TextNode.js +79 -0
- package/out/processors/Observer.d.ts +2 -1
- package/out/runtime/NodeWriter.js +14 -10
- package/out/runtime/TreeJson.js +9 -7
- package/out/schema/ChildDefinition.d.ts +5 -0
- package/out/schema/ChildDefinition.js +7 -0
- package/out/schema/NodeDefinition.d.ts +5 -0
- package/out/schema/NodeDefinition.js +7 -0
- package/out/schema/Schema.js +1 -1
- package/out/schema/SchemaParser.js +24 -18
- package/out/schema/SchemaProviderMeta.d.ts +7 -3
- package/out/schema/SchemaProviderMeta.js +7 -4
- package/out/schema/SchemaValidator.d.ts +1 -0
- package/out/schema/SchemaValidator.js +11 -6
- package/out/schema/type/ENUM.js +1 -1
- package/out/schema/type/GROUP.js +1 -1
- package/out/schema/type/MARKDOWN.js +2 -1
- package/out/schema/type/TEXT.js +2 -1
- package/out/schema/type/URL.js +1 -1
- package/out/schema/type/binaryValue.js +3 -2
- package/out/template/MetaTemplateSchemaProvider.d.ts +6 -3
- package/out/template/MetaTemplateSchemaProvider.js +6 -4
- package/out/template/TemplateParser.js +12 -7
- package/package.json +1 -1
package/out/core/Node.js
CHANGED
|
@@ -2,165 +2,177 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Node = void 0;
|
|
4
4
|
const ParseException_1 = require("../exceptions/ParseException");
|
|
5
|
-
const RuntimeException_1 = require("../exceptions/RuntimeException");
|
|
6
5
|
const NamespaceValidator_1 = require("./NamespaceValidator");
|
|
7
6
|
const StringUtils_1 = require("./StringUtils");
|
|
8
7
|
/**
|
|
9
|
-
* Node of the STXT tree
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Node of the STXT tree: what INLINE nodes ({@link InlineNode}) and BLOCK text nodes
|
|
9
|
+
* ({@link TextNode}) have in common. Those two are the only forms, and each one owns what is
|
|
10
|
+
* really its own — only an `InlineNode` has a value and children (and so the child lookups); only
|
|
11
|
+
* a `TextNode` has text lines. Code that walks a tree asks for the form
|
|
12
|
+
* (`node instanceof InlineNode`), the same way the canonical tree of STXT-TREE-SPEC has
|
|
13
|
+
* `children` only for inline nodes.
|
|
14
|
+
*
|
|
15
|
+
* Nodes are mutable, and the tree keeps its own integrity: a node knows its
|
|
16
|
+
* {@link Node.getParent | parent} (always an `InlineNode`), {@link InlineNode.addChild} links
|
|
17
|
+
* both ends and refuses a node that already has a parent, and {@link InlineNode.removeChild} /
|
|
18
|
+
* {@link Node.detach} undo it. The {@link Node.getLevel | level} is derived from the chain of
|
|
19
|
+
* parents, never stored.
|
|
20
|
+
*
|
|
21
|
+
* The namespace a node *declares* ({@link Node.getDeclaredNamespace}) and the one that *applies*
|
|
22
|
+
* to it ({@link Node.getNamespace}) are different things: the effective namespace is the declared
|
|
23
|
+
* one or, failing that, the parent's effective namespace (STXT-SPEC: namespaces are inherited
|
|
24
|
+
* vertically). Changing the declared namespace of a node therefore changes the effective
|
|
25
|
+
* namespace of the whole subtree that inherited it, and so does moving a subtree.
|
|
26
|
+
*
|
|
27
|
+
* The source line ({@link Node.getLine}) is optional: the parser sets it, code that builds trees
|
|
28
|
+
* usually does not ({@link Node.NO_LINE}).
|
|
13
29
|
*/
|
|
14
30
|
class Node {
|
|
15
31
|
/**
|
|
16
|
-
*
|
|
17
|
-
* {@link Parser} uses while parsing.
|
|
32
|
+
* Common initialisation, for the two concrete forms.
|
|
18
33
|
*
|
|
19
|
-
* @param line line number of the document where the node opens.
|
|
20
|
-
* @param level indentation level of the node (0 for root nodes).
|
|
21
34
|
* @param name name of the node.
|
|
22
|
-
* @param namespace namespace
|
|
23
|
-
* @param
|
|
24
|
-
* @
|
|
25
|
-
*
|
|
35
|
+
* @param namespace namespace the node declares, or null/undefined/empty if it declares none.
|
|
36
|
+
* @param line source line, or {@link Node.NO_LINE}.
|
|
37
|
+
* @throws ParseException with code `INVALID_NODE_NAME` if the name is not a valid STXT node
|
|
38
|
+
* name, or if the namespace does not have a valid format.
|
|
26
39
|
*/
|
|
27
|
-
constructor(
|
|
28
|
-
this.
|
|
29
|
-
this.children = [];
|
|
30
|
-
this.level = level;
|
|
40
|
+
constructor(name, namespace, line) {
|
|
41
|
+
this.parent = null;
|
|
31
42
|
this.line = line;
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!StringUtils_1.StringUtils.isValidNodeName(this.name)) {
|
|
42
|
-
throw new ParseException_1.ParseException(line, "INVALID_NODE_NAME", `Node name not valid: ${name}`);
|
|
43
|
-
}
|
|
43
|
+
this.setName(name);
|
|
44
|
+
this.setNamespace(namespace);
|
|
45
|
+
}
|
|
46
|
+
// ----------------------------------------------------------------
|
|
47
|
+
// Name
|
|
48
|
+
// ----------------------------------------------------------------
|
|
49
|
+
/** @returns the original name of the node as it appears in the document (with spaces compacted). */
|
|
50
|
+
getName() {
|
|
51
|
+
return this.name;
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
46
|
-
*
|
|
54
|
+
* Renames the node. The canonical name is recomputed.
|
|
47
55
|
*
|
|
48
|
-
* @param
|
|
56
|
+
* @param name new name of the node.
|
|
57
|
+
* @throws ParseException with code `INVALID_NODE_NAME` if it is not a valid STXT node name.
|
|
49
58
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
setName(name) {
|
|
60
|
+
const compacted = StringUtils_1.StringUtils.compactSpaces(name);
|
|
61
|
+
if (!StringUtils_1.StringUtils.isValidNodeName(compacted)) {
|
|
62
|
+
throw new ParseException_1.ParseException(this.line, "INVALID_NODE_NAME", `Node name not valid: ${name}`);
|
|
63
|
+
}
|
|
64
|
+
this.name = compacted;
|
|
65
|
+
this.canonicalName = StringUtils_1.StringUtils.normalize(name);
|
|
52
66
|
}
|
|
53
|
-
/** @returns the
|
|
54
|
-
|
|
55
|
-
return this.
|
|
67
|
+
/** @returns the canonical name of the node (STXT-SPEC §4.3), used to compare/look up by structural identity. */
|
|
68
|
+
getCanonicalName() {
|
|
69
|
+
return this.canonicalName;
|
|
56
70
|
}
|
|
57
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* @returns the canonical name of the node.
|
|
73
|
+
* @deprecated since 0.7.0, use {@link Node.getCanonicalName}; "canonical name" is the term of
|
|
74
|
+
* the specifications. To be removed in a later version.
|
|
75
|
+
*/
|
|
58
76
|
getNormalizedName() {
|
|
59
|
-
return this.
|
|
77
|
+
return this.canonicalName;
|
|
60
78
|
}
|
|
61
|
-
/** @returns the canonical name prefixed by
|
|
79
|
+
/** @returns the canonical name prefixed by the effective namespace (`namespace:name`), or just the canonical name when there is no namespace. */
|
|
62
80
|
getQualifiedName() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
: `${this.namespace}:${this.normalizedName}`;
|
|
66
|
-
}
|
|
67
|
-
/** @returns the effective namespace of the node (its own or inherited from the parent), lower-cased, or the empty string if it has none. */
|
|
68
|
-
getNamespace() {
|
|
69
|
-
return this.namespace;
|
|
81
|
+
const namespace = this.getNamespace();
|
|
82
|
+
return namespace.length === 0 ? this.canonicalName : `${namespace}:${this.canonicalName}`;
|
|
70
83
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
// ----------------------------------------------------------------
|
|
85
|
+
// Namespace
|
|
86
|
+
// ----------------------------------------------------------------
|
|
87
|
+
/** @returns the namespace this node declares itself, lower-cased, or the empty string if it declares none (and so inherits the parent's). */
|
|
88
|
+
getDeclaredNamespace() {
|
|
89
|
+
return this.declaredNamespace;
|
|
74
90
|
}
|
|
75
91
|
/**
|
|
76
|
-
*
|
|
92
|
+
* Sets the namespace this node declares. The empty string (or null/undefined) means "none":
|
|
93
|
+
* the node then inherits the effective namespace of its parent.
|
|
77
94
|
*
|
|
78
|
-
* @param
|
|
95
|
+
* @param namespace namespace to declare, or null/undefined/empty for none.
|
|
96
|
+
* @throws ParseException if the namespace does not have a valid format (STXT-SPEC §7).
|
|
79
97
|
*/
|
|
80
|
-
|
|
81
|
-
|
|
98
|
+
setNamespace(namespace) {
|
|
99
|
+
const lower = StringUtils_1.StringUtils.lowerCase(namespace);
|
|
100
|
+
NamespaceValidator_1.NamespaceValidator.validateNamespaceFormat(lower, this.line);
|
|
101
|
+
this.declaredNamespace = lower;
|
|
82
102
|
}
|
|
83
|
-
/** @returns the
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return this.textLines;
|
|
103
|
+
/** @returns the effective namespace of the node: the one it declares or, failing that, the effective namespace of its parent; the empty string if there is none. */
|
|
104
|
+
getNamespace() {
|
|
105
|
+
if (this.declaredNamespace.length > 0) {
|
|
106
|
+
return this.declaredNamespace;
|
|
107
|
+
}
|
|
108
|
+
return this.parent ? this.parent.getNamespace() : "";
|
|
90
109
|
}
|
|
91
|
-
|
|
110
|
+
// ----------------------------------------------------------------
|
|
111
|
+
// Position in the source
|
|
112
|
+
// ----------------------------------------------------------------
|
|
113
|
+
/** @returns the line number of the document where this node was opened, or {@link Node.NO_LINE} if unknown. */
|
|
92
114
|
getLine() {
|
|
93
115
|
return this.line;
|
|
94
116
|
}
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Sets the source line of the node.
|
|
119
|
+
*
|
|
120
|
+
* @param line line number, or {@link Node.NO_LINE} if unknown.
|
|
121
|
+
*/
|
|
122
|
+
setLine(line) {
|
|
123
|
+
this.line = line;
|
|
98
124
|
}
|
|
99
|
-
/** @returns
|
|
100
|
-
|
|
101
|
-
|
|
125
|
+
/** @returns the depth of the node in its tree: 0 for a root node, 1 for its children, and so on. */
|
|
126
|
+
getLevel() {
|
|
127
|
+
let level = 0;
|
|
128
|
+
for (let p = this.parent; p !== null; p = p.getParent()) {
|
|
129
|
+
level++;
|
|
130
|
+
}
|
|
131
|
+
return level;
|
|
102
132
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
133
|
+
// ----------------------------------------------------------------
|
|
134
|
+
// Tree
|
|
135
|
+
// ----------------------------------------------------------------
|
|
136
|
+
/** @returns the parent of this node, or null if it is a root node. */
|
|
137
|
+
getParent() {
|
|
138
|
+
return this.parent;
|
|
106
139
|
}
|
|
107
140
|
/**
|
|
108
|
-
*
|
|
141
|
+
* Removes this node from its parent, if it has one. Afterwards the node is a root, and its
|
|
142
|
+
* effective namespace is the one it declares.
|
|
109
143
|
*
|
|
110
|
-
* @
|
|
111
|
-
* @param namespace namespace to search in; this node's own namespace when omitted.
|
|
112
|
-
* @returns the only direct child with that name, or null if there is none.
|
|
113
|
-
* @throws RuntimeException with code `AMBIGUOUS_CHILD` if there is more than one; use {@link Node.getChildrenByName} then.
|
|
144
|
+
* @returns true if the node had a parent and was detached; false if it was already a root.
|
|
114
145
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
throw new RuntimeException_1.RuntimeException("AMBIGUOUS_CHILD", "More than 1 child. Use getChildren");
|
|
119
|
-
}
|
|
120
|
-
if (result.length === 0) {
|
|
121
|
-
return null;
|
|
146
|
+
detach() {
|
|
147
|
+
if (this.parent === null) {
|
|
148
|
+
return false;
|
|
122
149
|
}
|
|
123
|
-
return
|
|
150
|
+
return this.parent.removeChild(this);
|
|
124
151
|
}
|
|
125
|
-
// Fast access methods to children
|
|
126
152
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* @param cname name of the child to look for.
|
|
130
|
-
* @param namespace namespace to search in; this node's own namespace when omitted.
|
|
131
|
-
* @returns every direct child with that name in the given namespace, in order of appearance.
|
|
153
|
+
* Both ends of the link are kept in sync by {@link InlineNode}; nobody else calls this.
|
|
154
|
+
* @internal
|
|
132
155
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const targetNamespace = namespace !== undefined ? namespace : this.namespace;
|
|
136
|
-
const result = [];
|
|
137
|
-
for (const child of this.children) {
|
|
138
|
-
if (child.getNormalizedName() === key && child.getNamespace() === targetNamespace) {
|
|
139
|
-
result.push(child);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return result;
|
|
156
|
+
_setParent(parent) {
|
|
157
|
+
this.parent = parent;
|
|
143
158
|
}
|
|
144
159
|
/** @returns a readable representation of the node, for debugging and error messages. */
|
|
145
160
|
toString() {
|
|
146
|
-
let s =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
s += `, name='${this.name}'`;
|
|
150
|
-
if (this.namespace.length > 0) {
|
|
151
|
-
s += `, ns='${this.namespace}'`;
|
|
152
|
-
}
|
|
153
|
-
s += `, text=${this.textNode}`;
|
|
154
|
-
if (!this.textNode && this.value.length > 0) {
|
|
155
|
-
s += `, value='${this.value}'`;
|
|
161
|
+
let s = `${this.constructor.name}{`;
|
|
162
|
+
if (this.line !== Node.NO_LINE) {
|
|
163
|
+
s += `line=${this.line}, `;
|
|
156
164
|
}
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
s += `name='${this.name}'`;
|
|
166
|
+
const namespace = this.getNamespace();
|
|
167
|
+
if (namespace.length > 0) {
|
|
168
|
+
s += `, ns='${namespace}'`;
|
|
159
169
|
}
|
|
160
|
-
s +=
|
|
170
|
+
s += this.describe();
|
|
161
171
|
s += "}";
|
|
162
172
|
return s;
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
175
|
exports.Node = Node;
|
|
176
|
+
/** Value of {@link Node.getLine} when the node has no known position in a document. */
|
|
177
|
+
Node.NO_LINE = -1;
|
|
166
178
|
//# sourceMappingURL=Node.js.map
|
|
@@ -2,13 +2,13 @@ import { Line } from "./Line";
|
|
|
2
2
|
import { Node } from "./Node";
|
|
3
3
|
/**
|
|
4
4
|
* Builds the node a line opens, telling apart the INLINE form (`Name: value`) from the BLOCK one
|
|
5
|
-
* (`Name >>`)
|
|
5
|
+
* (`Name >>`). The node gets the namespace the line *declares*, if any; inheritance from the
|
|
6
|
+
* parent is resolved by the node itself through its parent link once it is attached
|
|
7
|
+
* ({@link Node.getNamespace}).
|
|
6
8
|
*
|
|
7
9
|
* @param lineIndent line already split into indentation and content.
|
|
8
10
|
* @param lineNumber line number of the document where the node opens.
|
|
9
|
-
* @
|
|
10
|
-
* @param parent node currently open, whose namespace is inherited, or null at root level.
|
|
11
|
-
* @returns the node the line opens, still with no children and no text lines.
|
|
11
|
+
* @returns the node the line opens, still detached, with no children and no text lines.
|
|
12
12
|
* @throws ParseException if the line is not a valid node declaration.
|
|
13
13
|
*/
|
|
14
|
-
export declare function createNode(lineIndent: Line, lineNumber: number
|
|
14
|
+
export declare function createNode(lineIndent: Line, lineNumber: number): Node;
|
package/out/core/NodeCreator.js
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createNode = createNode;
|
|
4
4
|
const NameNamespaceParser_1 = require("./NameNamespaceParser");
|
|
5
|
-
const
|
|
5
|
+
const InlineNode_1 = require("./InlineNode");
|
|
6
|
+
const TextNode_1 = require("./TextNode");
|
|
6
7
|
const ParseException_1 = require("../exceptions/ParseException");
|
|
7
8
|
const Constants_1 = require("./Constants");
|
|
8
9
|
/**
|
|
9
10
|
* Builds the node a line opens, telling apart the INLINE form (`Name: value`) from the BLOCK one
|
|
10
|
-
* (`Name >>`)
|
|
11
|
+
* (`Name >>`). The node gets the namespace the line *declares*, if any; inheritance from the
|
|
12
|
+
* parent is resolved by the node itself through its parent link once it is attached
|
|
13
|
+
* ({@link Node.getNamespace}).
|
|
11
14
|
*
|
|
12
15
|
* @param lineIndent line already split into indentation and content.
|
|
13
16
|
* @param lineNumber line number of the document where the node opens.
|
|
14
|
-
* @
|
|
15
|
-
* @param parent node currently open, whose namespace is inherited, or null at root level.
|
|
16
|
-
* @returns the node the line opens, still with no children and no text lines.
|
|
17
|
+
* @returns the node the line opens, still detached, with no children and no text lines.
|
|
17
18
|
* @throws ParseException if the line is not a valid node declaration.
|
|
18
19
|
*/
|
|
19
|
-
function createNode(lineIndent, lineNumber
|
|
20
|
+
function createNode(lineIndent, lineNumber) {
|
|
20
21
|
const line = lineIndent.content;
|
|
21
22
|
let name;
|
|
22
23
|
let value;
|
|
@@ -49,8 +50,8 @@ function createNode(lineIndent, lineNumber, level, parent) {
|
|
|
49
50
|
if (textNode && value.trim().length > 0) {
|
|
50
51
|
throw new ParseException_1.ParseException(lineNumber, "INLINE_VALUE_NOT_VALID", `Line not valid: ${line}`);
|
|
51
52
|
}
|
|
52
|
-
//
|
|
53
|
-
const nameNamespace = NameNamespaceParser_1.NameNamespaceParser.parse(name,
|
|
53
|
+
// The namespace the line declares, if any (empty when it inherits)
|
|
54
|
+
const nameNamespace = NameNamespaceParser_1.NameNamespaceParser.parse(name, null, lineNumber, line);
|
|
54
55
|
name = nameNamespace.getName();
|
|
55
56
|
const namespace = nameNamespace.getNamespace();
|
|
56
57
|
// Validate the name
|
|
@@ -58,6 +59,8 @@ function createNode(lineIndent, lineNumber, level, parent) {
|
|
|
58
59
|
throw new ParseException_1.ParseException(lineNumber, "INVALID_LINE", `Line not valid: ${line}`);
|
|
59
60
|
}
|
|
60
61
|
// Create the node
|
|
61
|
-
return
|
|
62
|
+
return textNode
|
|
63
|
+
? new TextNode_1.TextNode(name, namespace, null, lineNumber)
|
|
64
|
+
: new InlineNode_1.InlineNode(name, namespace, value, lineNumber);
|
|
62
65
|
}
|
|
63
66
|
//# sourceMappingURL=NodeCreator.js.map
|
package/out/core/Parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Parser = void 0;
|
|
4
|
+
const TextNode_1 = require("./TextNode");
|
|
4
5
|
const LineParser_1 = require("./LineParser");
|
|
5
6
|
const NodeCreator_1 = require("./NodeCreator");
|
|
6
7
|
const ParseResult_1 = require("./ParseResult");
|
|
@@ -72,7 +73,7 @@ class Parser {
|
|
|
72
73
|
this.processLine(line, lineNumber, stack, documents, result);
|
|
73
74
|
}
|
|
74
75
|
// Close every node still open at EOF
|
|
75
|
-
this.closeToLevel(stack,
|
|
76
|
+
this.closeToLevel(stack, 0, result);
|
|
76
77
|
// Add the nodes to the result
|
|
77
78
|
for (const doc of documents) {
|
|
78
79
|
result.addNode(doc);
|
|
@@ -83,8 +84,9 @@ class Parser {
|
|
|
83
84
|
processLine(lineString, lineNumber, stack, documents, result) {
|
|
84
85
|
try {
|
|
85
86
|
const lastNode = stack.length === 0 ? null : stack[stack.length - 1];
|
|
86
|
-
|
|
87
|
-
const
|
|
87
|
+
// The stack holds the open nodes, one per level: its size is the level of the next line's parent
|
|
88
|
+
const lastLevel = lastNode ? stack.length - 1 : 0;
|
|
89
|
+
const lastNodeText = lastNode instanceof TextNode_1.TextNode;
|
|
88
90
|
// Parse the line
|
|
89
91
|
const line = (0, LineParser_1.parseLine)(lineString, lastNodeText, lastLevel, lineNumber);
|
|
90
92
|
if (line.isComment) {
|
|
@@ -98,10 +100,11 @@ class Parser {
|
|
|
98
100
|
// When we are inside a text node and the level says it is still text,
|
|
99
101
|
// append the text line instead of creating a node.
|
|
100
102
|
if (line.isBlock) {
|
|
101
|
-
lastNode
|
|
103
|
+
const textNode = lastNode;
|
|
104
|
+
textNode.addTextLine(line.content);
|
|
102
105
|
// Notify the observers about the text line
|
|
103
106
|
this.observers.forEach(observer => {
|
|
104
|
-
observer.onTextLine(
|
|
107
|
+
observer.onTextLine(textNode, lineNumber, lineString, line);
|
|
105
108
|
});
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
@@ -109,11 +112,20 @@ class Parser {
|
|
|
109
112
|
if (line.isEmpty()) {
|
|
110
113
|
return;
|
|
111
114
|
}
|
|
112
|
-
// Close the nodes down to the current level (this "finishes" them and
|
|
113
|
-
this.closeToLevel(stack,
|
|
114
|
-
// Create the new node
|
|
115
|
+
// Close the nodes down to the current level (this "finishes" them: validators and observers run)
|
|
116
|
+
this.closeToLevel(stack, currentLevel, result);
|
|
117
|
+
// Create the new node, attach it to its parent (or to the documents if it is a root)
|
|
118
|
+
// and leave it "open" on the stack. Attaching links both ends: the node already knows
|
|
119
|
+
// its parent, and so its effective namespace and its level, when the observers see it.
|
|
120
|
+
// The parent is always an InlineNode: a TextNode on top of the stack only takes text lines.
|
|
115
121
|
const parent = stack.length === 0 ? null : stack[stack.length - 1];
|
|
116
|
-
const node = (0, NodeCreator_1.createNode)(line, lineNumber
|
|
122
|
+
const node = (0, NodeCreator_1.createNode)(line, lineNumber);
|
|
123
|
+
if (parent === null) {
|
|
124
|
+
documents.push(node);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
parent.addChild(node);
|
|
128
|
+
}
|
|
117
129
|
// Hand it over to the observers
|
|
118
130
|
this.observers.forEach(observer => {
|
|
119
131
|
observer.onCreate(node, lineString);
|
|
@@ -138,7 +150,7 @@ class Parser {
|
|
|
138
150
|
result.addError(new ParseException_1.ParseException(line, unknownErrorCode, String(e)));
|
|
139
151
|
}
|
|
140
152
|
}
|
|
141
|
-
closeToLevel(stack,
|
|
153
|
+
closeToLevel(stack, targetLevel, result) {
|
|
142
154
|
while (stack.length > targetLevel) {
|
|
143
155
|
const completed = stack.pop();
|
|
144
156
|
// Hand it over to the validators
|
|
@@ -153,12 +165,6 @@ class Parser {
|
|
|
153
165
|
this.handleError(e, completed.getLine(), result, "VALIDATION_ERROR", "UNKNOWN_VALIDATION_ERROR");
|
|
154
166
|
}
|
|
155
167
|
});
|
|
156
|
-
if (stack.length === 0) {
|
|
157
|
-
documents.push(completed);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
stack[stack.length - 1].addChild(completed);
|
|
161
|
-
}
|
|
162
168
|
// Hand it over to the observers
|
|
163
169
|
this.observers.forEach(observer => {
|
|
164
170
|
observer.onFinish(completed);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Node } from "./Node";
|
|
2
|
+
/**
|
|
3
|
+
* BLOCK text node of the STXT tree (`Name >>`): an ordered list of literal text lines. It has no
|
|
4
|
+
* inline value and no children; its content is only text.
|
|
5
|
+
*
|
|
6
|
+
* Overloads with two strings always take the second one as the *content* (the text); the
|
|
7
|
+
* namespace only exists in the three-argument forms.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TextNode extends Node {
|
|
10
|
+
private readonly lines;
|
|
11
|
+
/**
|
|
12
|
+
* Creates an empty text node with no declared namespace and no known source line.
|
|
13
|
+
*
|
|
14
|
+
* @param name name of the node.
|
|
15
|
+
*/
|
|
16
|
+
constructor(name: string);
|
|
17
|
+
/**
|
|
18
|
+
* Creates a text node with its text, no declared namespace and no known source line.
|
|
19
|
+
*
|
|
20
|
+
* @param name name of the node.
|
|
21
|
+
* @param text text of the node (split into lines at every line break), or its lines.
|
|
22
|
+
*/
|
|
23
|
+
constructor(name: string, text: string | ReadonlyArray<string> | null | undefined);
|
|
24
|
+
/**
|
|
25
|
+
* Creates a text node with a declared namespace and its text; the source line is optional.
|
|
26
|
+
* This is the form the {@link Parser} uses (with no text yet: it appends the lines with
|
|
27
|
+
* {@link TextNode.addTextLine}).
|
|
28
|
+
*
|
|
29
|
+
* @param name name of the node.
|
|
30
|
+
* @param namespace namespace the node declares, or null/undefined/empty for none.
|
|
31
|
+
* @param text text of the node (split into lines at every line break), or its lines.
|
|
32
|
+
* @param line source line, or {@link Node.NO_LINE} (the default).
|
|
33
|
+
* @throws ParseException if the name or the namespace are not valid.
|
|
34
|
+
*/
|
|
35
|
+
constructor(name: string, namespace: string | null | undefined, text: string | ReadonlyArray<string> | null | undefined, line?: number);
|
|
36
|
+
/** @returns the text lines of the node, in order, as a read-only view. */
|
|
37
|
+
getTextLines(): ReadonlyArray<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Replaces the whole text of the node.
|
|
40
|
+
*
|
|
41
|
+
* @param text new text, split into lines at every line break (LF or CRLF), or the lines
|
|
42
|
+
* themselves; null/undefined empties the node.
|
|
43
|
+
*/
|
|
44
|
+
setText(text: string | ReadonlyArray<string> | null | undefined): void;
|
|
45
|
+
/**
|
|
46
|
+
* Replaces the whole text of the node with the given lines.
|
|
47
|
+
*
|
|
48
|
+
* @param lines new text lines; null/undefined empties the node.
|
|
49
|
+
*/
|
|
50
|
+
setTextLines(lines: ReadonlyArray<string> | null | undefined): void;
|
|
51
|
+
/**
|
|
52
|
+
* Appends a text line.
|
|
53
|
+
*
|
|
54
|
+
* @param line text line to append.
|
|
55
|
+
*/
|
|
56
|
+
addTextLine(line: string): void;
|
|
57
|
+
/** Removes every text line. */
|
|
58
|
+
clearText(): void;
|
|
59
|
+
getText(): string;
|
|
60
|
+
isTextNode(): boolean;
|
|
61
|
+
private static splitLines;
|
|
62
|
+
protected describe(): string;
|
|
63
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TextNode = void 0;
|
|
4
|
+
const Node_1 = require("./Node");
|
|
5
|
+
/**
|
|
6
|
+
* BLOCK text node of the STXT tree (`Name >>`): an ordered list of literal text lines. It has no
|
|
7
|
+
* inline value and no children; its content is only text.
|
|
8
|
+
*
|
|
9
|
+
* Overloads with two strings always take the second one as the *content* (the text); the
|
|
10
|
+
* namespace only exists in the three-argument forms.
|
|
11
|
+
*/
|
|
12
|
+
class TextNode extends Node_1.Node {
|
|
13
|
+
constructor(name, ...rest) {
|
|
14
|
+
// With two arguments the second one is the text; the namespace only exists with three or more
|
|
15
|
+
const [namespace, text, line] = rest.length <= 1
|
|
16
|
+
? [null, rest[0], Node_1.Node.NO_LINE]
|
|
17
|
+
: [rest[0], rest[1], rest[2] ?? Node_1.Node.NO_LINE];
|
|
18
|
+
super(name, namespace, line);
|
|
19
|
+
this.lines = [];
|
|
20
|
+
this.setText(text);
|
|
21
|
+
}
|
|
22
|
+
// ----------------------------------------------------------------
|
|
23
|
+
// Text
|
|
24
|
+
// ----------------------------------------------------------------
|
|
25
|
+
/** @returns the text lines of the node, in order, as a read-only view. */
|
|
26
|
+
getTextLines() {
|
|
27
|
+
return this.lines;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Replaces the whole text of the node.
|
|
31
|
+
*
|
|
32
|
+
* @param text new text, split into lines at every line break (LF or CRLF), or the lines
|
|
33
|
+
* themselves; null/undefined empties the node.
|
|
34
|
+
*/
|
|
35
|
+
setText(text) {
|
|
36
|
+
this.lines.length = 0;
|
|
37
|
+
if (typeof text === "string") {
|
|
38
|
+
this.lines.push(...TextNode.splitLines(text));
|
|
39
|
+
}
|
|
40
|
+
else if (text) {
|
|
41
|
+
this.lines.push(...text);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Replaces the whole text of the node with the given lines.
|
|
46
|
+
*
|
|
47
|
+
* @param lines new text lines; null/undefined empties the node.
|
|
48
|
+
*/
|
|
49
|
+
setTextLines(lines) {
|
|
50
|
+
this.setText(lines);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Appends a text line.
|
|
54
|
+
*
|
|
55
|
+
* @param line text line to append.
|
|
56
|
+
*/
|
|
57
|
+
addTextLine(line) {
|
|
58
|
+
this.lines.push(line);
|
|
59
|
+
}
|
|
60
|
+
/** Removes every text line. */
|
|
61
|
+
clearText() {
|
|
62
|
+
this.lines.length = 0;
|
|
63
|
+
}
|
|
64
|
+
getText() {
|
|
65
|
+
return this.lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
isTextNode() {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
// LF or CRLF; the trailing part after the last break is a line too (possibly empty)
|
|
71
|
+
static splitLines(text) {
|
|
72
|
+
return text.split(/\r?\n/);
|
|
73
|
+
}
|
|
74
|
+
describe() {
|
|
75
|
+
return `, lines=${this.lines.length}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.TextNode = TextNode;
|
|
79
|
+
//# sourceMappingURL=TextNode.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Line } from "../core/Line";
|
|
2
2
|
import { Node } from "../core/Node";
|
|
3
|
+
import { TextNode } from "../core/TextNode";
|
|
3
4
|
/**
|
|
4
5
|
* Process hook notified by the {@link Parser} while parsing: when each node is opened and closed,
|
|
5
6
|
* and for every comment and text line it reads. Register it with {@link Parser.registerObserver}.
|
|
@@ -33,5 +34,5 @@ export interface Observer {
|
|
|
33
34
|
* @param lineString source line, as it appears in the document.
|
|
34
35
|
* @param line the same line already split into indentation and content.
|
|
35
36
|
*/
|
|
36
|
-
onTextLine(node:
|
|
37
|
+
onTextLine(node: TextNode, lineNumber: number, lineString: string, line: Line): void;
|
|
37
38
|
}
|