@stxt-lang/core 0.6.3 → 0.7.1

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/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. Mutable while parsing ({@link Node.addChild}/{@link Node.addTextLine}
10
- * are public); once the document is closed it must be treated as read-only. It represents both
11
- * INLINE nodes (with {@link Node.getValue}) and BLOCK text nodes (with {@link Node.getTextLines}),
12
- * as told apart by {@link Node.isTextNode}.
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
- * Creates a node with its full position in the document. This is the constructor the
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 of the node, or null/undefined if it has none.
23
- * @param textNode true if it is a text block node (BLOCK); false if it is INLINE.
24
- * @param value inline value of the node (INLINE node), ignored when it is BLOCK.
25
- * @throws ParseException if the name or the namespace are not valid.
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(line, level, name, namespace, textNode, value) {
28
- this.textLines = [];
29
- this.children = [];
30
- this.level = level;
40
+ constructor(name, namespace, line) {
41
+ this.parent = null;
31
42
  this.line = line;
32
- this.name = StringUtils_1.StringUtils.compactSpaces(name);
33
- this.normalizedName = StringUtils_1.StringUtils.normalize(name);
34
- this.namespace = StringUtils_1.StringUtils.lowerCase(namespace);
35
- this.value = (value ?? "").trim();
36
- this.textNode = textNode;
37
- NamespaceValidator_1.NamespaceValidator.validateNamespaceFormat(this.namespace, line);
38
- if (this.value.length > 0 && this.isTextNode()) {
39
- throw new RuntimeException_1.RuntimeException("INLINE_VALUE_NOT_VALID", "Not empty value with textNode");
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
- * Appends a text line to a BLOCK node.
54
+ * Renames the node. The canonical name is recomputed.
47
55
  *
48
- * @param line text line to append to a BLOCK node ({@link Node.isTextNode}).
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
- addTextLine(line) {
51
- this.textLines.push(line);
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 original name of the node as it appears in the document (with spaces compacted). */
54
- getName() {
55
- return this.name;
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
- /** @returns the canonical name of the node, used to compare/look up by structural identity. */
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.normalizedName;
77
+ return this.canonicalName;
60
78
  }
61
- /** @returns the canonical name prefixed by its namespace (`namespace:name`), or just the name when there is no namespace. */
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
- return this.namespace.length === 0
64
- ? this.normalizedName
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
- /** @returns the children of the node in order of appearance, as a read-only view. */
72
- getChildren() {
73
- return this.children;
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
- * Appends an already closed child to this node.
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 node already closed child to append at the end of this node's list of children.
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
- addChild(node) {
81
- this.children.push(node);
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 inline value of the node (INLINE node), or the empty string if it is a BLOCK node. */
84
- getValue() {
85
- return this.value;
86
- }
87
- /** @returns the text lines of a BLOCK node ({@link Node.isTextNode}), in order of appearance. */
88
- getTextLines() {
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
- /** @returns the line number of the document where this node was opened. */
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
- /** @returns the indentation level of the node (0 for root nodes). */
96
- getLevel() {
97
- return this.level;
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 true if the node is a text block (BLOCK, `>>`); false if it is INLINE. */
100
- isTextNode() {
101
- return this.textNode;
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
- /** @returns the textual content of the node: the text lines joined with '\n' if it is BLOCK, or the inline value otherwise. */
104
- getText() {
105
- return this.isTextNode() ? this.textLines.join("\n") : this.value;
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
- * Looks up the only direct child with that name.
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
- * @param cname name of the child to look for.
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
- getChild(cname, namespace) {
116
- const result = this.getChildrenByName(cname, namespace);
117
- if (result.length > 1) {
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 result[0];
150
+ return this.parent.removeChild(this);
124
151
  }
125
- // Fast access methods to children
126
152
  /**
127
- * Looks up every direct child with that name.
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
- getChildrenByName(cname, namespace) {
134
- const key = StringUtils_1.StringUtils.normalize(cname);
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 = "Node{";
147
- s += `line=${this.line}`;
148
- s += `, level=${this.level}`;
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
- if (this.textNode) {
158
- s += `, lines=${this.textLines.length}`;
165
+ s += `name='${this.name}'`;
166
+ const namespace = this.getNamespace();
167
+ if (namespace.length > 0) {
168
+ s += `, ns='${namespace}'`;
159
169
  }
160
- s += `, children=${this.children.length}`;
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 >>`) and resolving the namespace against the parent.
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
- * @param level indentation level of the node.
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, level: number, parent: Node | null): Node;
14
+ export declare function createNode(lineIndent: Line, lineNumber: number): Node;
@@ -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 Node_1 = require("./Node");
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 >>`) and resolving the namespace against the parent.
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
- * @param level indentation level of the node.
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, level, parent) {
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
- // Default namespace: inherited from the parent
53
- const nameNamespace = NameNamespaceParser_1.NameNamespaceParser.parse(name, parent ? parent.getNamespace() : null, lineNumber, line);
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 new Node_1.Node(lineNumber, level, name, namespace, textNode, value);
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
@@ -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, documents, 0, result);
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
- const lastLevel = lastNode ? lastNode.getLevel() : 0;
87
- const lastNodeText = lastNode ? lastNode.isTextNode() : false;
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.addTextLine(line.content);
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(lastNode, lineNumber, lineString, line);
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 attaches them to the parent/documents)
113
- this.closeToLevel(stack, documents, currentLevel, result);
114
- // Create the new node and leave it "open" on the stack (do NOT attach it yet)
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, currentLevel, parent);
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, documents, targetLevel, result) {
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: Node, lineNumber: number, lineString: string, line: Line): void;
37
+ onTextLine(node: TextNode, lineNumber: number, lineString: string, line: Line): void;
37
38
  }