@stxt-lang/core 0.9.0 → 0.10.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.
Files changed (54) hide show
  1. package/out/all.d.ts +6 -0
  2. package/out/all.js +8 -1
  3. package/out/core/Constants.d.ts +7 -0
  4. package/out/core/Constants.js +7 -0
  5. package/out/core/LineParser.d.ts +1 -1
  6. package/out/core/LineParser.js +4 -4
  7. package/out/core/NamespaceValidator.d.ts +7 -0
  8. package/out/core/NamespaceValidator.js +9 -0
  9. package/out/core/NodeCreator.js +1 -1
  10. package/out/core/Parser.d.ts +5 -0
  11. package/out/core/Parser.js +12 -8
  12. package/out/exceptions/ParseException.d.ts +7 -1
  13. package/out/exceptions/ParseException.js +8 -2
  14. package/out/exceptions/RuntimeException.d.ts +6 -1
  15. package/out/exceptions/RuntimeException.js +7 -3
  16. package/out/schema/NodeDefinition.d.ts +1 -1
  17. package/out/schema/NodeDefinition.js +2 -2
  18. package/out/schema/Schema.d.ts +1 -1
  19. package/out/schema/Schema.js +2 -2
  20. package/out/schema/SchemaParser.d.ts +2 -1
  21. package/out/schema/SchemaParser.js +24 -9
  22. package/out/schema/SchemaProviderMemory.d.ts +2 -1
  23. package/out/schema/SchemaProviderMemory.js +8 -2
  24. package/out/schema/SchemaValidator.js +7 -7
  25. package/out/schema/TypeRegistry.js +1 -1
  26. package/out/schema/type/BASE64.d.ts +9 -0
  27. package/out/schema/type/BASE64.js +32 -15
  28. package/out/schema/type/DATE.d.ts +1 -1
  29. package/out/schema/type/DATE.js +4 -3
  30. package/out/schema/type/ENUM.js +1 -1
  31. package/out/schema/type/GROUP.js +1 -1
  32. package/out/schema/type/INLINE.js +1 -1
  33. package/out/schema/type/MARKDOWN.js +1 -1
  34. package/out/schema/type/TEXT.js +1 -1
  35. package/out/schema/type/TIME.d.ts +1 -1
  36. package/out/schema/type/TIME.js +4 -3
  37. package/out/schema/type/TIMESTAMP.d.ts +4 -1
  38. package/out/schema/type/TIMESTAMP.js +9 -3
  39. package/out/schema/type/URL.d.ts +10 -3
  40. package/out/schema/type/URL.js +11 -24
  41. package/out/schema/type/binaryValue.d.ts +6 -4
  42. package/out/schema/type/binaryValue.js +8 -8
  43. package/out/schema/type/dateTime.d.ts +9 -0
  44. package/out/schema/type/dateTime.js +23 -0
  45. package/out/schema/type/rangeType.d.ts +11 -0
  46. package/out/schema/type/rangeType.js +30 -0
  47. package/out/schema/type/regexType.js +1 -1
  48. package/out/template/ChildLineParser.d.ts +1 -1
  49. package/out/template/ChildLineParser.js +10 -4
  50. package/out/template/TemplateParser.d.ts +3 -2
  51. package/out/template/TemplateParser.js +30 -14
  52. package/out/template/TemplateSchemaProviderMemory.d.ts +4 -3
  53. package/out/template/TemplateSchemaProviderMemory.js +5 -8
  54. package/package.json +1 -1
@@ -10,7 +10,7 @@ exports.ENUM = {
10
10
  validate(nodeDef, node) {
11
11
  // INLINE value form (STXT-SCHEMA-SPEC 9.3): the block '>>' form is not allowed
12
12
  if (node.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  const value = node.getText();
16
16
  const allowed = nodeDef.getValues(); // ReadonlySet<string>
@@ -10,7 +10,7 @@ exports.GROUP = {
10
10
  validate(nodeDef, node) {
11
11
  // NONE value form (STXT-SCHEMA-SPEC 9.2): neither an inline value nor a '>>' block
12
12
  if (node.isTextNode() || node.getText().length > 0) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_VALUE", `Node '${node.getName()}' has to be empty`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUE_NOT_ALLOWED", `Node '${node.getName()}' has to be empty`);
14
14
  }
15
15
  },
16
16
  };
@@ -10,7 +10,7 @@ exports.INLINE = {
10
10
  validate(nodeDef, node) {
11
11
  // INLINE value form (STXT-SCHEMA-SPEC 9.2): the block '>>' form is not allowed
12
12
  if (node.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  },
16
16
  };
@@ -13,7 +13,7 @@ exports.MARKDOWN = {
13
13
  },
14
14
  validate(nodeDef, node) {
15
15
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
16
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_CHILDREN_TEXT", `Not allowed children nodes in node ${node.getQualifiedName()}`);
16
+ throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED", `Not allowed children nodes in node ${node.getQualifiedName()}`);
17
17
  }
18
18
  },
19
19
  };
@@ -10,7 +10,7 @@ exports.TEXT = {
10
10
  },
11
11
  validate(nodeDef, node) {
12
12
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
13
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_CHILDREN_TEXT", `Not allowed children nodes in node ${node.getQualifiedName()}`);
13
+ throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED", `Not allowed children nodes in node ${node.getQualifiedName()}`);
14
14
  }
15
15
  },
16
16
  };
@@ -1,2 +1,2 @@
1
- /** `TIME` type: checks the `HH:MM:SS` format. */
1
+ /** `TIME` type: `hh:mm:ss` in range (00-23, 00-59, 00-59); no fraction, no zone (STXT-SCHEMA-SPEC 9.4). */
2
2
  export declare const TIME: import("../Type").Type;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIME = void 0;
4
- const regexType_1 = require("./regexType");
5
- /** `TIME` type: checks the `HH:MM:SS` format. */
6
- exports.TIME = (0, regexType_1.regexType)("TIME", /^\d{2}:\d{2}:\d{2}$/, "Invalid time");
4
+ const rangeType_1 = require("./rangeType");
5
+ const dateTime_1 = require("./dateTime");
6
+ /** `TIME` type: `hh:mm:ss` in range (00-23, 00-59, 00-59); no fraction, no zone (STXT-SCHEMA-SPEC 9.4). */
7
+ exports.TIME = (0, rangeType_1.rangeType)("TIME", /^(\d{2}):(\d{2}):(\d{2})$/, m => (0, dateTime_1.isValidTime)(Number(m[1]), Number(m[2]), Number(m[3])), "Invalid time");
7
8
  //# sourceMappingURL=TIME.js.map
@@ -1,2 +1,5 @@
1
- /** `TIMESTAMP` type: checks an ISO-8601 timestamp. */
1
+ /**
2
+ * `TIMESTAMP` type: `DATE "T" hh:mm [":" ss ["." digits]] ["Z" | sign hh:mm]` (STXT-SCHEMA-SPEC
3
+ * 9.4). Date, time and offset in range; seconds, fraction (one or more digits) and zone optional.
4
+ */
2
5
  export declare const TIMESTAMP: import("../Type").Type;
@@ -1,7 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIMESTAMP = void 0;
4
- const regexType_1 = require("./regexType");
5
- /** `TIMESTAMP` type: checks an ISO-8601 timestamp. */
6
- exports.TIMESTAMP = (0, regexType_1.regexType)("TIMESTAMP", /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[+-]\d{2}:\d{2})?$/, "Invalid timestamp");
4
+ const rangeType_1 = require("./rangeType");
5
+ const dateTime_1 = require("./dateTime");
6
+ /**
7
+ * `TIMESTAMP` type: `DATE "T" hh:mm [":" ss ["." digits]] ["Z" | sign hh:mm]` (STXT-SCHEMA-SPEC
8
+ * 9.4). Date, time and offset in range; seconds, fraction (one or more digits) and zone optional.
9
+ */
10
+ exports.TIMESTAMP = (0, rangeType_1.rangeType)("TIMESTAMP", /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))?$/, m => (0, dateTime_1.isValidDate)(Number(m[1]), Number(m[2]), Number(m[3]))
11
+ && (0, dateTime_1.isValidTime)(Number(m[4]), Number(m[5]), m[6] === undefined ? 0 : Number(m[6]))
12
+ && (m[7] === undefined || (0, dateTime_1.isValidTime)(Number(m[7]), Number(m[8]), 0)), "Invalid timestamp");
7
13
  //# sourceMappingURL=TIMESTAMP.js.map
@@ -1,3 +1,10 @@
1
- import { Type } from "../Type";
2
- /** `URL` type: checks that the value is a syntactically valid URI/URL. */
3
- export declare const URL: Type;
1
+ /**
2
+ * `URL` type: an absolute URL with a mandatory scheme and host, following the grammar of
3
+ * STXT-SCHEMA-SPEC 9.4 — `scheme "://" [userinfo "@"] host [":" port] ["/" path] ["?" query]
4
+ * ["#" fragment]` — and not the platform URL parser, so every port accepts exactly the same
5
+ * values. Any scheme of the form letter + letters/digits/`+`/`-`/`.` is accepted; the host is
6
+ * non-empty (no TLD required, IPv6 in brackets, non-ASCII kept as it is); a value without a
7
+ * scheme, a scheme without `//` and host (`mailto:`, `urn:`, `file:///`), inner blanks or a
8
+ * non-numeric port are rejected. Nothing is resolved or normalised.
9
+ */
10
+ export declare const URL: import("../Type").Type;
@@ -1,28 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.URL = void 0;
4
- const ValidationException_1 = require("../../exceptions/ValidationException");
5
- /** `URL` type: checks that the value is a syntactically valid URI/URL. */
6
- exports.URL = {
7
- getName() {
8
- return "URL";
9
- },
10
- validate(ndef, n) {
11
- // INLINE value form (STXT-SCHEMA-SPEC 9.4): the block '>>' form is not allowed
12
- if (n.isTextNode()) {
13
- throw new ValidationException_1.ValidationException(n.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${n.getQualifiedName()}`);
14
- }
15
- const url = n.getText();
16
- try {
17
- const parsed = new globalThis.URL(url);
18
- const ok = !!parsed.protocol && !!parsed.hostname;
19
- if (!ok) {
20
- throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_URL_STRUCTURE", `Invalid URL: ${url}`);
21
- }
22
- }
23
- catch {
24
- throw new ValidationException_1.ValidationException(n.getLine(), "INVALID_VALUE", `Invalid URL: ${url}`);
25
- }
26
- },
27
- };
4
+ const regexType_1 = require("./regexType");
5
+ /**
6
+ * `URL` type: an absolute URL with a mandatory scheme and host, following the grammar of
7
+ * STXT-SCHEMA-SPEC 9.4 — `scheme "://" [userinfo "@"] host [":" port] ["/" path] ["?" query]
8
+ * ["#" fragment]` — and not the platform URL parser, so every port accepts exactly the same
9
+ * values. Any scheme of the form letter + letters/digits/`+`/`-`/`.` is accepted; the host is
10
+ * non-empty (no TLD required, IPv6 in brackets, non-ASCII kept as it is); a value without a
11
+ * scheme, a scheme without `//` and host (`mailto:`, `urn:`, `file:///`), inner blanks or a
12
+ * non-numeric port are rejected. Nothing is resolved or normalised.
13
+ */
14
+ exports.URL = (0, regexType_1.regexType)("URL", /^[A-Za-z][A-Za-z0-9+.-]*:\/\/(?:[^ \t/?#@]+@)?(?:\[[0-9A-Fa-f:.]+\]|[^ \t/?#@:[\]]+)(?::[0-9]+)?(?:\/[^ \t?#]*)?(?:\?[^ \t#]*)?(?:#[^ \t]*)?$/, "Invalid URL");
28
15
  //# sourceMappingURL=URL.js.map
@@ -1,11 +1,13 @@
1
1
  import { Node } from "../../core/Node";
2
2
  /**
3
3
  * STXT-SCHEMA-SPEC 9.5: effective value for the INLINE/BLOCK binary types
4
- * (HEXADECIMAL, BINARY, BASE64). In BLOCK form, validation applies to the
5
- * concatenation of the lines of the block, ignoring line breaks, empty lines
6
- * and the leading and trailing spaces or tabs of each line.
4
+ * (HEXADECIMAL, BINARY, BASE64). Every blank (U+0020 space, U+0009 tab) is
5
+ * removed wherever it is, in both forms; in BLOCK form the lines are
6
+ * concatenated first, which also drops line breaks and empty lines. So
7
+ * `DE AD BE EF`, `1010 1010` and Base64 wrapped at 76 columns validate. No
8
+ * other character is removed: `DE:AD` or `DE-AD` stay invalid.
7
9
  *
8
10
  * @param node node whose value is wanted.
9
- * @returns the inline value, or the lines of the block already concatenated.
11
+ * @returns the value with no blanks, ready for the grammar of the type.
10
12
  */
11
13
  export declare function binaryValue(node: Node): string;
@@ -4,17 +4,17 @@ exports.binaryValue = binaryValue;
4
4
  const TextNode_1 = require("../../core/TextNode");
5
5
  /**
6
6
  * STXT-SCHEMA-SPEC 9.5: effective value for the INLINE/BLOCK binary types
7
- * (HEXADECIMAL, BINARY, BASE64). In BLOCK form, validation applies to the
8
- * concatenation of the lines of the block, ignoring line breaks, empty lines
9
- * and the leading and trailing spaces or tabs of each line.
7
+ * (HEXADECIMAL, BINARY, BASE64). Every blank (U+0020 space, U+0009 tab) is
8
+ * removed wherever it is, in both forms; in BLOCK form the lines are
9
+ * concatenated first, which also drops line breaks and empty lines. So
10
+ * `DE AD BE EF`, `1010 1010` and Base64 wrapped at 76 columns validate. No
11
+ * other character is removed: `DE:AD` or `DE-AD` stay invalid.
10
12
  *
11
13
  * @param node node whose value is wanted.
12
- * @returns the inline value, or the lines of the block already concatenated.
14
+ * @returns the value with no blanks, ready for the grammar of the type.
13
15
  */
14
16
  function binaryValue(node) {
15
- if (!(node instanceof TextNode_1.TextNode)) {
16
- return node.getText();
17
- }
18
- return node.getTextLines().map((line) => line.trim()).join("");
17
+ const raw = node instanceof TextNode_1.TextNode ? node.getTextLines().join("") : node.getText();
18
+ return raw.replace(/[ \t]/g, "");
19
19
  }
20
20
  //# sourceMappingURL=binaryValue.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Calendar and clock ranges shared by `DATE`, `TIME` and `TIMESTAMP` (STXT-SCHEMA-SPEC 9.4):
3
+ * the shape of a value is checked by each type's regular expression, the ranges by these
4
+ * helpers. Never `Date.parse`, which rolls `2026-02-30` into March.
5
+ */
6
+ /** True if the year-month-day exists in the proleptic Gregorian calendar (year 0000-9999). */
7
+ export declare function isValidDate(year: number, month: number, day: number): boolean;
8
+ /** True if hour 00-23, minute 00-59, second 00-59 (no leap second). */
9
+ export declare function isValidTime(hour: number, minute: number, second: number): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * Calendar and clock ranges shared by `DATE`, `TIME` and `TIMESTAMP` (STXT-SCHEMA-SPEC 9.4):
4
+ * the shape of a value is checked by each type's regular expression, the ranges by these
5
+ * helpers. Never `Date.parse`, which rolls `2026-02-30` into March.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isValidDate = isValidDate;
9
+ exports.isValidTime = isValidTime;
10
+ /** True if the year-month-day exists in the proleptic Gregorian calendar (year 0000-9999). */
11
+ function isValidDate(year, month, day) {
12
+ if (month < 1 || month > 12 || day < 1) {
13
+ return false;
14
+ }
15
+ const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
16
+ const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
17
+ return day <= daysInMonth[month - 1];
18
+ }
19
+ /** True if hour 00-23, minute 00-59, second 00-59 (no leap second). */
20
+ function isValidTime(hour, minute, second) {
21
+ return hour <= 23 && minute <= 59 && second <= 59;
22
+ }
23
+ //# sourceMappingURL=dateTime.js.map
@@ -0,0 +1,11 @@
1
+ import { Type } from "../Type";
2
+ /**
3
+ * A type whose value must match a regular expression and whose captured groups must then pass
4
+ * a range check (the calendar and clock types of STXT-SCHEMA-SPEC 9.4). INLINE value form only.
5
+ *
6
+ * @param name name of the type.
7
+ * @param pattern shape of the value, with the groups `inRange` reads.
8
+ * @param inRange true if the captured groups are in range.
9
+ * @param error text of the `INVALID_VALUE` message.
10
+ */
11
+ export declare function rangeType(name: string, pattern: RegExp, inRange: (m: RegExpExecArray) => boolean, error: string): Type;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rangeType = rangeType;
4
+ const ValidationException_1 = require("../../exceptions/ValidationException");
5
+ /**
6
+ * A type whose value must match a regular expression and whose captured groups must then pass
7
+ * a range check (the calendar and clock types of STXT-SCHEMA-SPEC 9.4). INLINE value form only.
8
+ *
9
+ * @param name name of the type.
10
+ * @param pattern shape of the value, with the groups `inRange` reads.
11
+ * @param inRange true if the captured groups are in range.
12
+ * @param error text of the `INVALID_VALUE` message.
13
+ */
14
+ function rangeType(name, pattern, inRange, error) {
15
+ return {
16
+ getName: () => name,
17
+ validate(nodeDef, node) {
18
+ // INLINE value form (STXT-SCHEMA-SPEC 9.4): the block '>>' form is not allowed
19
+ if (node.isTextNode()) {
20
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
21
+ }
22
+ const value = node.getText();
23
+ const m = pattern.exec(value);
24
+ if (m === null || !inRange(m)) {
25
+ throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_VALUE", `${node.getName()}: ${error} (${value})`);
26
+ }
27
+ },
28
+ };
29
+ }
30
+ //# sourceMappingURL=rangeType.js.map
@@ -16,7 +16,7 @@ function regexType(name, pattern, error) {
16
16
  validate(nodeDef, node) {
17
17
  // INLINE value form (STXT-SCHEMA-SPEC 9.3/9.4): the block '>>' form is not allowed
18
18
  if (node.isTextNode()) {
19
- throw new ValidationException_1.ValidationException(node.getLine(), "NOT_ALLOWED_TEXT", `Not allowed text in node ${node.getQualifiedName()}`);
19
+ throw new ValidationException_1.ValidationException(node.getLine(), "BLOCK_FORM_NOT_ALLOWED", `Not allowed text in node ${node.getQualifiedName()}`);
20
20
  }
21
21
  const value = node.getText();
22
22
  if (!pattern.test(value)) {
@@ -9,7 +9,7 @@ export declare class ChildLineParser {
9
9
  * @param rawLine inline value of the node, `(min,max) TYPE [values]`.
10
10
  * @param lineNumber line number, for the error messages.
11
11
  * @returns the line already split into type, cardinality and values.
12
- * @throws ValidationException with code `INVALID_CHILD_LINE`, `INVALID_CHILD_COUNT`,
12
+ * @throws ValidationException with code `STRUCTURE_LINE_NOT_VALID`, `CARDINALITY_NOT_VALID`,
13
13
  * `MIN_GREATER_THAN_MAX` or `VALUE_DUPLICATED` if the line is not valid.
14
14
  */
15
15
  static parse(rawLine: string, lineNumber: number): ChildLine;
@@ -12,7 +12,7 @@ class ChildLineParser {
12
12
  * @param rawLine inline value of the node, `(min,max) TYPE [values]`.
13
13
  * @param lineNumber line number, for the error messages.
14
14
  * @returns the line already split into type, cardinality and values.
15
- * @throws ValidationException with code `INVALID_CHILD_LINE`, `INVALID_CHILD_COUNT`,
15
+ * @throws ValidationException with code `STRUCTURE_LINE_NOT_VALID`, `CARDINALITY_NOT_VALID`,
16
16
  * `MIN_GREATER_THAN_MAX` or `VALUE_DUPLICATED` if the line is not valid.
17
17
  */
18
18
  static parse(rawLine, lineNumber) {
@@ -21,7 +21,7 @@ class ChildLineParser {
21
21
  }
22
22
  const m = ChildLineParser.CHILD_LINE_PATTERN.exec(rawLine);
23
23
  if (!m) {
24
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_LINE", `Line not valid: ${rawLine}`);
24
+ throw new ValidationException_1.ValidationException(lineNumber, "STRUCTURE_LINE_NOT_VALID", `Line not valid: ${rawLine}`);
25
25
  }
26
26
  // m[1]=count, m[2]=type, m[3]=values
27
27
  let type = m[2]?.trim() ?? "";
@@ -54,7 +54,7 @@ class ChildLineParser {
54
54
  else if (count.includes(",")) {
55
55
  const parts = count.split(",");
56
56
  if (parts.length !== 2) {
57
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_COUNT", `Invalid count ${count} in line: ${rawLine}`);
57
+ throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
58
58
  }
59
59
  const aNum = ChildLineParser.parseCount(parts[0].trim(), count, rawLine, lineNumber);
60
60
  const bNum = ChildLineParser.parseCount(parts[1].trim(), count, rawLine, lineNumber);
@@ -77,6 +77,12 @@ class ChildLineParser {
77
77
  const list = [];
78
78
  for (let part of parts) {
79
79
  part = part.trim();
80
+ // An empty item ("[a, , b]", "[a, b,]") is an error, as an empty Value: is in a
81
+ // schema (STXT-TEMPLATE-SPEC 14.14). Only the whole list may be empty ("[]"),
82
+ // which the template parser reports as VALUES_REQUIRED.
83
+ if (part.length === 0 && parts.length > 1) {
84
+ throw new ValidationException_1.ValidationException(lineNumber, "VALUE_EMPTY", `Empty ENUM value in ${valuesStr}`);
85
+ }
80
86
  if (part.length === 0) {
81
87
  continue;
82
88
  }
@@ -97,7 +103,7 @@ class ChildLineParser {
97
103
  // num, min and max must be non-negative integers, with no trailing text (STXT-TEMPLATE-SPEC 7.1)
98
104
  static parseCount(num, count, rawLine, lineNumber) {
99
105
  if (!/^\d+$/.test(num)) {
100
- throw new ValidationException_1.ValidationException(lineNumber, "INVALID_CHILD_COUNT", `Invalid count ${count} in line: ${rawLine}`);
106
+ throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
101
107
  }
102
108
  return parseInt(num, 10);
103
109
  }
@@ -5,7 +5,8 @@ import { Schema } from "../schema/Schema";
5
5
  *
6
6
  * @param node root of the already parsed `@stxt.template` document.
7
7
  * @returns the resulting {@link Schema}.
8
- * @throws ValidationException if the template is not valid, with the line already shifted to the
9
- * one of the original document.
8
+ * @throws ValidationException with `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if the root is
9
+ * not `Template (@stxt.template): ns`, or with the code of the rule the template breaks, with the
10
+ * line already shifted to the one of the original document.
10
11
  */
11
12
  export declare function transformTemplateNodeToSchema(node: Node): Schema;
@@ -11,17 +11,33 @@ const StringUtils_1 = require("../core/StringUtils");
11
11
  const ChildLineParser_1 = require("./ChildLineParser");
12
12
  const ParseException_1 = require("../exceptions/ParseException");
13
13
  const TypeRegistry_1 = require("../schema/TypeRegistry");
14
+ const NamespaceValidator_1 = require("../core/NamespaceValidator");
15
+ /** Namespace of the template language itself, `@stxt.template`. */
16
+ const TEMPLATE_NAMESPACE = "@stxt.template";
14
17
  /**
15
18
  * Turns the tree of an already parsed `@stxt.template` document into an equivalent {@link Schema}.
16
19
  *
17
20
  * @param node root of the already parsed `@stxt.template` document.
18
21
  * @returns the resulting {@link Schema}.
19
- * @throws ValidationException if the template is not valid, with the line already shifted to the
20
- * one of the original document.
22
+ * @throws ValidationException with `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if the root is
23
+ * not `Template (@stxt.template): ns`, or with the code of the rule the template breaks, with the
24
+ * line already shifted to the one of the original document.
21
25
  */
22
26
  function transformTemplateNodeToSchema(node) {
27
+ // The root must be `Template (@stxt.template): ns`
28
+ if (node.getCanonicalName() !== "template" || node.getNamespace() !== TEMPLATE_NAMESPACE) {
29
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_ROOT_NOT_VALID", `Expected template(${TEMPLATE_NAMESPACE}) but got ${node.getCanonicalName()}(${node.getNamespace()})`);
30
+ }
31
+ // The target namespace: required, and with a valid format
32
+ const targetNamespace = StringUtils_1.StringUtils.lowerCase(node.getText());
33
+ if (!targetNamespace || targetNamespace.trim().length === 0) {
34
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_NAMESPACE_EMPTY", "Template namespace is empty");
35
+ }
36
+ if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
37
+ throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_ROOT_NOT_VALID", `Template namespace not valid: ${targetNamespace}`);
38
+ }
23
39
  // Set the namespace
24
- const result = new Schema_1.Schema(node.getText(), node.getLine(), undefined);
40
+ const result = new Schema_1.Schema(targetNamespace, node.getLine(), undefined);
25
41
  // Look for the structure node (a template is an inline root; a text root has none)
26
42
  const structure = node instanceof InlineNode_1.InlineNode ? node.getChild("structure") : null;
27
43
  if (!structure) {
@@ -76,7 +92,7 @@ function addToSchema(schema, node) {
76
92
  // A Structure line must use the template grammar's ':' form. The core parser
77
93
  // also accepts BLOCK nodes here, so reject them explicitly (STXT-TEMPLATE-SPEC 6.3).
78
94
  if (!(node instanceof InlineNode_1.InlineNode)) {
79
- throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_CHILD_LINE", "Template Structure lines must use ':'");
95
+ throw new ValidationException_1.ValidationException(node.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
80
96
  }
81
97
  // Get the qualified name
82
98
  let namespace = node.getNamespace();
@@ -91,7 +107,7 @@ function addToSchema(schema, node) {
91
107
  // and no children (STXT-TEMPLATE-SPEC 6.4, 10 and 14.15)
92
108
  const type = cl.getType();
93
109
  if (type && type.trim().length > 0) {
94
- throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_DEFINITION_NOT_ALLOWED", "Not allowed type definition in external namespaces");
110
+ throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed type definition in external namespaces");
95
111
  }
96
112
  const values = cl.getValues();
97
113
  if (values) {
@@ -125,7 +141,7 @@ function addToSchema(schema, node) {
125
141
  // Same code as SchemaParser: a template is sugar equivalent to a schema
126
142
  // (STXT-TEMPLATE-SPEC 13), so the same condition must not change its code
127
143
  // depending on the entry point
128
- throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_ONLY_SUPPORTED_BY_ENUM", `Values only supported for type ENUM, not for type ${type}`);
144
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
129
145
  }
130
146
  for (const v of values) {
131
147
  schemaNode.addValue(v, node.getLine());
@@ -133,13 +149,13 @@ function addToSchema(schema, node) {
133
149
  }
134
150
  // An ENUM with no list of values is an invalid template (STXT-TEMPLATE-SPEC 9 and 13.7)
135
151
  if (type === "ENUM" && (!values || values.length === 0)) {
136
- throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_EMPTY_FOR_ENUM", "ENUM Type must include values");
152
+ throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
137
153
  }
138
154
  }
139
155
  else {
140
156
  const type = cl.getType();
141
157
  if (!type || !type.startsWith("@")) {
142
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_DEFINED_MULTIPLE_TIMES", `Multiple node reference must start with @: ${node.getName()}`);
158
+ throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_REQUIRED", `Multiple node reference must start with @: ${node.getName()}`);
143
159
  }
144
160
  const reference = type.substring(1).trim();
145
161
  // Reference and explicit type on the same line (STXT-TEMPLATE-SPEC 14.13)
@@ -148,7 +164,7 @@ function addToSchema(schema, node) {
148
164
  throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_WITH_TYPE_NOT_ALLOWED", `Reference '@${node.getName()}' can not declare a type: ${explicitType}`);
149
165
  }
150
166
  if (StringUtils_1.StringUtils.normalize(reference) !== node.getCanonicalName()) {
151
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_REFERENCE_NOT_VALID", `Reference must be '@${node.getName()}', not '${reference}'`);
167
+ throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_NAME_NOT_VALID", `Reference must be '@${node.getName()}', not '${reference}'`);
152
168
  }
153
169
  // A reference may override the cardinality, but it may redefine neither the ENUM
154
170
  // values nor the children (STXT-TEMPLATE-SPEC 6.4)
@@ -171,7 +187,7 @@ function addToSchema(schema, node) {
171
187
  for (const child of childrenNode) {
172
188
  // STXT-TEMPLATE-SPEC 6.3: every Structure line uses ':', so a child is inline too
173
189
  if (!(child instanceof InlineNode_1.InlineNode)) {
174
- throw new ValidationException_1.ValidationException(child.getLine(), "INVALID_CHILD_LINE", "Template Structure lines must use ':'");
190
+ throw new ValidationException_1.ValidationException(child.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
175
191
  }
176
192
  cl = ChildLineParser_1.ChildLineParser.parse(child.getValue(), child.getLine());
177
193
  const childName = child.getName();
@@ -213,20 +229,20 @@ function addDescriptions(schema, nodes) {
213
229
  }
214
230
  // No external descriptions
215
231
  if (namespace !== schema.getNamespace()) {
216
- throw new ValidationException_1.ValidationException(node.getLine(), "EXTERNAL_DESCRIPTION_NOT_ALLOWED", "Not allowed description in external namespaces");
232
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed description in external namespaces");
217
233
  }
218
234
  // No children either
219
235
  if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
220
- throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_DESCRIPTION_NOT_ALLOWED", "Not allowed children in description");
236
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_CHILDREN_NOT_ALLOWED", "Not allowed children in description");
221
237
  }
222
238
  // Look for the node in the schema
223
239
  const nodeDef = schema.getNodeDefinition(node.getName());
224
240
  if (!nodeDef) {
225
- throw new ValidationException_1.ValidationException(node.getLine(), "NODE_NOT_FOUND", `Not found node with name: ${node.getName()}`);
241
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_NODE_NOT_FOUND", `Not found node with name: ${node.getName()}`);
226
242
  }
227
243
  // No more than one entry per node is allowed (STXT-TEMPLATE-SPEC 12)
228
244
  if (nodeDef.getDescription() !== undefined) {
229
- throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_ALREADY_DEFINED", `Exists a previous description for node: ${node.getName()}`);
245
+ throw new ValidationException_1.ValidationException(node.getLine(), "DESCRIPTION_DUPLICATED", `Exists a previous description for node: ${node.getName()}`);
230
246
  }
231
247
  nodeDef.setDescription(node.getText());
232
248
  });
@@ -17,9 +17,10 @@ export declare class TemplateSchemaProviderMemory extends SchemaProviderMemory {
17
17
  * schema it produces.
18
18
  *
19
19
  * @param template text of the `@stxt.template` document.
20
- * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
21
- * one template or the resulting schema has no namespace, or the first validation error
22
- * if the template does not validate against the template meta-schema.
20
+ * @throws ValidationException with code `TEMPLATE_MULTIPLE_ROOTS` if the document does not hold
21
+ * exactly one root node, `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if that
22
+ * root is not `Template (@stxt.template): ns`, or the first validation error if the
23
+ * template does not validate against the template meta-schema.
23
24
  */
24
25
  addTemplate(template: string): void;
25
26
  }
@@ -30,15 +30,16 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
30
30
  * schema it produces.
31
31
  *
32
32
  * @param template text of the `@stxt.template` document.
33
- * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
34
- * one template or the resulting schema has no namespace, or the first validation error
35
- * if the template does not validate against the template meta-schema.
33
+ * @throws ValidationException with code `TEMPLATE_MULTIPLE_ROOTS` if the document does not hold
34
+ * exactly one root node, `TEMPLATE_ROOT_NOT_VALID` or `TEMPLATE_NAMESPACE_EMPTY` if that
35
+ * root is not `Template (@stxt.template): ns`, or the first validation error if the
36
+ * template does not validate against the template meta-schema.
36
37
  */
37
38
  addTemplate(template) {
38
39
  const parser = new Parser_1.Parser();
39
40
  const nodes = parser.parse(template);
40
41
  if (nodes.length !== 1) {
41
- throw new ValidationException_1.ValidationException(0, "INVALID_SCHEMA", `There are ${nodes.length}, and expected is 1`);
42
+ throw new ValidationException_1.ValidationException(0, "TEMPLATE_MULTIPLE_ROOTS", `A template document must hold exactly 1 root node, got ${nodes.length}`);
42
43
  }
43
44
  // A template that does not validate against the template meta-schema must not
44
45
  // be registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
@@ -49,10 +50,6 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
49
50
  }
50
51
  // Build the schema out of the template
51
52
  const sch = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
52
- // Minimum safety check (Java checked the expected namespace here too)
53
- if (!sch.getNamespace() || sch.getNamespace().trim().length === 0) {
54
- throw new ValidationException_1.ValidationException(0, "INVALID_SCHEMA", "Schema namespace is empty");
55
- }
56
53
  this.schemas.set(sch.getNamespace(), sch);
57
54
  }
58
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
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",