@stxt-lang/core 0.7.1 → 0.8.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.
@@ -37,7 +37,7 @@ class InlineNode extends Node_1.Node {
37
37
  * @param value new value, or null/undefined for none. It is trimmed.
38
38
  */
39
39
  setValue(value) {
40
- this.value = (value ?? "").trim();
40
+ this.value = StringUtils_1.StringUtils.trim(value);
41
41
  }
42
42
  getText() {
43
43
  return this.value;
@@ -24,6 +24,6 @@ export declare class Line {
24
24
  * @param indentLength number of characters the indentation took up.
25
25
  */
26
26
  constructor(level: number, content: string, isComment: boolean, isBlock: boolean, indentLength: number);
27
- /** @returns true if the line has no content beyond whitespace. */
27
+ /** @returns true if the line has no content beyond blanks (space/tab only, spec 4). */
28
28
  isEmpty(): boolean;
29
29
  }
package/out/core/Line.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Line = void 0;
4
+ const StringUtils_1 = require("./StringUtils");
4
5
  /**
5
6
  * A source line already split into its indentation and its content, as produced by
6
7
  * {@link parseLine}. It is what tells the {@link Parser} whether the line opens a node, continues
@@ -23,9 +24,9 @@ class Line {
23
24
  this.isBlock = isBlock;
24
25
  this.indentLength = indentLength;
25
26
  }
26
- /** @returns true if the line has no content beyond whitespace. */
27
+ /** @returns true if the line has no content beyond blanks (space/tab only, spec 4). */
27
28
  isEmpty() {
28
- return this.content.trim() === "";
29
+ return StringUtils_1.StringUtils.trim(this.content) === "";
29
30
  }
30
31
  }
31
32
  exports.Line = Line;
@@ -84,6 +84,7 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
84
84
  throw new ParseException_1.ParseException(numLine, "INDENTATION_LEVEL_NOT_VALID", `Level of indent incorrect: ${level}`);
85
85
  }
86
86
  // General case: return the line without the indentation already consumed
87
- return new Line_1.Line(level, line.substring(pointer).trim(), false, false, pointer);
87
+ // Blank-only trim (spec 4): an NBSP after the value is part of it
88
+ return new Line_1.Line(level, StringUtils_1.StringUtils.trim(line.substring(pointer)), false, false, pointer);
88
89
  }
89
90
  //# sourceMappingURL=LineParser.js.map
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NameNamespaceParser = void 0;
4
4
  const ParseException_1 = require("../exceptions/ParseException");
5
5
  const NameNamespace_1 = require("./NameNamespace");
6
+ const StringUtils_1 = require("./StringUtils");
6
7
  /** Extracts the name and the namespace `(a.b.c)` from the left-hand side of an STXT line. */
7
8
  class NameNamespaceParser {
8
9
  constructor() {
@@ -21,7 +22,7 @@ class NameNamespaceParser {
21
22
  if (rawName === null || rawName === undefined) {
22
23
  throw new ParseException_1.ParseException(lineNumber, "INVALID_LINE", `Line not valid: ${fullLine}`);
23
24
  }
24
- rawName = rawName.trim();
25
+ rawName = StringUtils_1.StringUtils.trim(rawName);
25
26
  const startIndex = rawName.indexOf("(");
26
27
  const endIndex = rawName.indexOf(")");
27
28
  let name;
@@ -31,7 +32,7 @@ class NameNamespaceParser {
31
32
  if (startIndex > endIndex || endIndex !== rawName.length - 1) {
32
33
  throw new ParseException_1.ParseException(lineNumber, "INVALID_NAMESPACE", `Line not valid: ${fullLine}`);
33
34
  }
34
- name = rawName.substring(0, startIndex).trim();
35
+ name = StringUtils_1.StringUtils.trim(rawName.substring(0, startIndex));
35
36
  // No trim: the grammar (STXT-SPEC 7/16) does not allow spaces inside '( )'
36
37
  namespace = rawName.substring(startIndex + 1, endIndex);
37
38
  if (namespace.length === 0) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createNode = createNode;
4
+ const StringUtils_1 = require("./StringUtils");
4
5
  const NameNamespaceParser_1 = require("./NameNamespaceParser");
5
6
  const InlineNode_1 = require("./InlineNode");
6
7
  const TextNode_1 = require("./TextNode");
@@ -47,7 +48,7 @@ function createNode(lineIndent, lineNumber) {
47
48
  name = line.substring(0, nodeIndex);
48
49
  value = line.substring(nodeIndex + Constants_1.Constants.SEP_NODE.length);
49
50
  }
50
- if (textNode && value.trim().length > 0) {
51
+ if (textNode && StringUtils_1.StringUtils.trim(value).length > 0) {
51
52
  throw new ParseException_1.ParseException(lineNumber, "INLINE_VALUE_NOT_VALID", `Line not valid: ${line}`);
52
53
  }
53
54
  // The namespace the line declares, if any (empty when it inherits)
@@ -90,6 +90,12 @@ class Parser {
90
90
  // Parse the line
91
91
  const line = (0, LineParser_1.parseLine)(lineString, lastNodeText, lastLevel, lineNumber);
92
92
  if (line.isComment) {
93
+ // A comment at the level of an open block node (or shallower) closes the block
94
+ // (spec 6.1 and 9.1): a block is a literal and cannot be commented from inside.
95
+ // Only the block closes; the comment does not touch the rest of the hierarchy.
96
+ if (lastNodeText) {
97
+ this.closeToLevel(stack, stack.length - 1, result);
98
+ }
93
99
  // Hand it over to the observers
94
100
  this.observers.forEach(observer => {
95
101
  observer.onComment(lineNumber, lineString);
@@ -1,12 +1,30 @@
1
1
  /** String normalization helpers used for names, namespaces and values. */
2
2
  export declare class StringUtils {
3
3
  private static readonly NODE_NAME;
4
+ private static readonly NODE_NAME_LETTER_OR_DIGIT;
5
+ private static readonly LEADING_BLANKS;
6
+ private static readonly TRAILING_BLANKS;
7
+ private static readonly BLANK_RUN;
4
8
  private constructor();
5
9
  /**
6
- * Removes the trailing whitespace of a string.
10
+ * Tells whether a character is an STXT blank (STXT-SPEC 4): space or tab.
7
11
  *
8
- * @param s string to strip the trailing spaces from.
9
- * @returns the string without trailing whitespace; null/undefined is treated as the empty string.
12
+ * @param c single character.
13
+ * @returns true for U+0020 and U+0009 only.
14
+ */
15
+ static isBlank(c: string): boolean;
16
+ /**
17
+ * Removes the leading and trailing blanks (space and tab only, STXT-SPEC 4) of a string.
18
+ *
19
+ * @param s string to trim.
20
+ * @returns the trimmed string; null/undefined is treated as the empty string.
21
+ */
22
+ static trim(s: string | null | undefined): string;
23
+ /**
24
+ * Removes the trailing blanks (space and tab only, STXT-SPEC 4, 10.2) of a string.
25
+ *
26
+ * @param s string to strip the trailing blanks from.
27
+ * @returns the string without trailing blanks; null/undefined is treated as the empty string.
10
28
  */
11
29
  static rightTrim(s: string | null | undefined): string;
12
30
  /**
@@ -24,10 +42,10 @@ export declare class StringUtils {
24
42
  */
25
43
  static lowerCase(input: string | null | undefined): string;
26
44
  /**
27
- * Trims a string and collapses its inner whitespace.
45
+ * Trims a string and collapses its inner runs of blanks (space and tab only).
28
46
  *
29
47
  * @param s string to compact.
30
- * @returns the string with the outer spaces trimmed and the inner ones collapsed into a single one; null/undefined is treated as the empty string.
48
+ * @returns the string with the outer blanks trimmed and the inner runs collapsed into a single space; null/undefined is treated as the empty string.
31
49
  */
32
50
  static compactSpaces(s: string | null | undefined): string;
33
51
  /**
@@ -5,20 +5,33 @@ exports.StringUtils = void 0;
5
5
  class StringUtils {
6
6
  constructor() {
7
7
  }
8
+ /**
9
+ * Tells whether a character is an STXT blank (STXT-SPEC 4): space or tab.
10
+ *
11
+ * @param c single character.
12
+ * @returns true for U+0020 and U+0009 only.
13
+ */
14
+ static isBlank(c) {
15
+ return c === " " || c === "\t";
16
+ }
17
+ /**
18
+ * Removes the leading and trailing blanks (space and tab only, STXT-SPEC 4) of a string.
19
+ *
20
+ * @param s string to trim.
21
+ * @returns the trimmed string; null/undefined is treated as the empty string.
22
+ */
23
+ static trim(s) {
24
+ return (s ?? "").replace(this.LEADING_BLANKS, "").replace(this.TRAILING_BLANKS, "");
25
+ }
8
26
  // Used for name>> nodes
9
27
  /**
10
- * Removes the trailing whitespace of a string.
28
+ * Removes the trailing blanks (space and tab only, STXT-SPEC 4, 10.2) of a string.
11
29
  *
12
- * @param s string to strip the trailing spaces from.
13
- * @returns the string without trailing whitespace; null/undefined is treated as the empty string.
30
+ * @param s string to strip the trailing blanks from.
31
+ * @returns the string without trailing blanks; null/undefined is treated as the empty string.
14
32
  */
15
33
  static rightTrim(s) {
16
- const value = s ?? "";
17
- let i = value.length - 1;
18
- while (i >= 0 && /\s/.test(value.charAt(i))) {
19
- i--;
20
- }
21
- return value.substring(0, i + 1);
34
+ return (s ?? "").replace(this.TRAILING_BLANKS, "");
22
35
  }
23
36
  // Used for BASE64 and HEXADECIMAL nodes
24
37
  /**
@@ -43,13 +56,13 @@ class StringUtils {
43
56
  }
44
57
  // Used for the name of the nodes
45
58
  /**
46
- * Trims a string and collapses its inner whitespace.
59
+ * Trims a string and collapses its inner runs of blanks (space and tab only).
47
60
  *
48
61
  * @param s string to compact.
49
- * @returns the string with the outer spaces trimmed and the inner ones collapsed into a single one; null/undefined is treated as the empty string.
62
+ * @returns the string with the outer blanks trimmed and the inner runs collapsed into a single space; null/undefined is treated as the empty string.
50
63
  */
51
64
  static compactSpaces(s) {
52
- return (s ?? "").trim().replace(/\s+/g, " ");
65
+ return this.trim(s).replace(this.BLANK_RUN, " ");
53
66
  }
54
67
  /**
55
68
  * Tells whether a value is a valid STXT node name.
@@ -62,7 +75,7 @@ class StringUtils {
62
75
  */
63
76
  static isValidNodeName(input) {
64
77
  const nfc = this.compactSpaces(input).normalize("NFC");
65
- return this.NODE_NAME.test(nfc) && this.normalize(nfc).length > 0;
78
+ return this.NODE_NAME.test(nfc) && this.NODE_NAME_LETTER_OR_DIGIT.test(nfc);
66
79
  }
67
80
  // Used for the normalized name of the nodes (STXT-SPEC 4.3): NFC + lower case,
68
81
  // keeping diacritics and non-Latin alphabets (IDN model)
@@ -73,21 +86,29 @@ class StringUtils {
73
86
  * @returns the canonical name of a node: NFC + lower case, with separators collapsed into '-'; null/undefined is treated as the empty string.
74
87
  */
75
88
  static normalize(input) {
76
- let s = (input ?? "").trim();
89
+ let s = this.trim(input);
77
90
  if (s.length === 0) {
78
91
  return "";
79
92
  }
80
93
  s = s.normalize("NFC");
81
94
  s = s.toLowerCase();
82
- // every run of separators ('-', '_', spaces) => a single '-'
83
- s = s.replace(/[-_\s]+/g, "-");
95
+ // every run of separators ('-', '_', blanks) => a single '-'
96
+ s = s.replace(/[-_ \t]+/g, "-");
84
97
  // trim the '-'
85
98
  s = s.replace(/^-+|-+$/g, "");
86
99
  return s;
87
100
  }
88
101
  }
89
102
  exports.StringUtils = StringUtils;
90
- // STXT-SPEC 4.2 / 4.3: validate the logical name after NFC so that a
103
+ // STXT-SPEC 4.2 / 4.3: letters, decimal digits, combining marks (Mn, Mc) and the three
104
+ // separators, with at least one letter or digit. Validated after NFC so that a
91
105
  // decomposed spelling such as "e" + combining acute is accepted as "é".
92
- StringUtils.NODE_NAME = /^[\p{L}\p{Nd}\-_ ]+$/u;
106
+ StringUtils.NODE_NAME = /^[\p{L}\p{Nd}\p{Mn}\p{Mc}\-_ ]+$/u;
107
+ StringUtils.NODE_NAME_LETTER_OR_DIGIT = /[\p{L}\p{Nd}]/u;
108
+ // STXT-SPEC 4: a blank is exactly U+0020 or U+0009. Every trim in the core works on
109
+ // these two characters only; String.prototype.trim and /\s/ are deliberately avoided
110
+ // because they also remove NBSP, U+3000, U+2028... which STXT treats as content.
111
+ StringUtils.LEADING_BLANKS = /^[ \t]+/;
112
+ StringUtils.TRAILING_BLANKS = /[ \t]+$/;
113
+ StringUtils.BLANK_RUN = /[ \t]+/g;
93
114
  //# sourceMappingURL=StringUtils.js.map
@@ -5,6 +5,10 @@ import { ValidationException } from "../exceptions/ValidationException";
5
5
  /**
6
6
  * Wrapper around a {@link SchemaValidator} that only validates namespaced nodes, so that a
7
7
  * document mixing schema-bound and free nodes does not report the free ones as unknown.
8
+ *
9
+ * @deprecated since 0.8.0: {@link SchemaValidator} applies this rule itself (STXT-SCHEMA-SPEC 5,
10
+ * the empty namespace is never validated), so the wrapper adds nothing. Register the
11
+ * `SchemaValidator` directly. Kept for compatibility; to be removed in 1.0.
8
12
  */
9
13
  export declare class ConditionalValidator implements Validator {
10
14
  private readonly schemaValidator;
@@ -4,6 +4,10 @@ exports.ConditionalValidator = void 0;
4
4
  /**
5
5
  * Wrapper around a {@link SchemaValidator} that only validates namespaced nodes, so that a
6
6
  * document mixing schema-bound and free nodes does not report the free ones as unknown.
7
+ *
8
+ * @deprecated since 0.8.0: {@link SchemaValidator} applies this rule itself (STXT-SCHEMA-SPEC 5,
9
+ * the empty namespace is never validated), so the wrapper adds nothing. Register the
10
+ * `SchemaValidator` directly. Kept for compatibility; to be removed in 1.0.
7
11
  */
8
12
  class ConditionalValidator {
9
13
  /**
@@ -26,6 +26,18 @@ class SchemaValidator {
26
26
  const errors = [];
27
27
  // Get the namespace
28
28
  const namespace = node.getNamespace();
29
+ // The empty namespace is never validated (STXT-SCHEMA-SPEC 5): a node that neither
30
+ // declares nor inherits a namespace is valid by definition, no schema is looked up for
31
+ // it and SCHEMA_NOT_FOUND is never reported for it. Its children are still walked when
32
+ // recursive, because one of them may declare a namespace of its own.
33
+ if (namespace === "") {
34
+ if (this.recursiveValidation && node instanceof InlineNode_1.InlineNode) {
35
+ for (const childNode of node.getChildren()) {
36
+ errors.push(...this.validate(childNode));
37
+ }
38
+ }
39
+ return errors;
40
+ }
29
41
  const schema = this.schemaProvider.getSchema(namespace);
30
42
  if (!schema) {
31
43
  errors.push(new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_NOT_FOUND", `Not found schema: ${namespace}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "Parser and schema validator for STXT, an indentation-based structured-text format.",
5
5
  "main": "out/all.js",
6
6
  "types": "out/all.d.ts",