@stxt-lang/core 0.14.1 → 0.16.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 (44) hide show
  1. package/README.md +3 -3
  2. package/out/core/InlineNode.js +1 -0
  3. package/out/core/LineParser.js +1 -1
  4. package/out/core/NameNamespace.d.ts +5 -5
  5. package/out/core/NameNamespace.js +5 -5
  6. package/out/core/NamespaceValidator.d.ts +7 -7
  7. package/out/core/NamespaceValidator.js +7 -7
  8. package/out/core/Parser.d.ts +5 -0
  9. package/out/core/Parser.js +22 -17
  10. package/out/core/StringUtils.d.ts +0 -7
  11. package/out/core/StringUtils.js +0 -10
  12. package/out/core/TextNode.d.ts +7 -0
  13. package/out/core/TextNode.js +11 -0
  14. package/out/discovery/DiscoveryResolver.d.ts +1 -1
  15. package/out/discovery/DiscoveryResolver.js +36 -17
  16. package/out/discovery/DiscoveryResult.js +6 -4
  17. package/out/exceptions/ParseException.d.ts +8 -1
  18. package/out/exceptions/ParseException.js +7 -0
  19. package/out/runtime/Formatter.d.ts +4 -4
  20. package/out/runtime/Formatter.js +15 -8
  21. package/out/runtime/NodeWriter.js +10 -2
  22. package/out/runtime/UnifiedSchemaProvider.d.ts +1 -3
  23. package/out/runtime/UnifiedSchemaProvider.js +14 -32
  24. package/out/schema/DefinitionCompiler.d.ts +27 -0
  25. package/out/schema/DefinitionCompiler.js +53 -0
  26. package/out/schema/NodeDefinition.js +6 -3
  27. package/out/schema/Schema.d.ts +2 -0
  28. package/out/schema/Schema.js +7 -4
  29. package/out/schema/SchemaParser.js +13 -12
  30. package/out/schema/SchemaProviderMemory.js +3 -19
  31. package/out/schema/SchemaProviderMeta.d.ts +7 -1
  32. package/out/schema/SchemaProviderMeta.js +10 -9
  33. package/out/schema/SchemaValidator.js +5 -3
  34. package/out/schema/type/BASE64.d.ts +4 -2
  35. package/out/schema/type/BASE64.js +34 -10
  36. package/out/schema/type/MARKDOWN.d.ts +2 -1
  37. package/out/schema/type/MARKDOWN.js +4 -8
  38. package/out/template/ChildLineParser.js +12 -9
  39. package/out/template/MetaTemplateSchemaProvider.d.ts +3 -1
  40. package/out/template/MetaTemplateSchemaProvider.js +12 -11
  41. package/out/template/TemplateParser.d.ts +1 -2
  42. package/out/template/TemplateParser.js +116 -89
  43. package/out/template/TemplateSchemaProviderMemory.js +3 -19
  44. package/package.json +2 -3
@@ -0,0 +1,27 @@
1
+ import { Node } from "../core/Node";
2
+ import { Schema } from "./Schema";
3
+ import { SchemaProvider } from "./SchemaProvider";
4
+ /**
5
+ * Validates one root node against the meta provider of its kind and compiles it into a
6
+ * {@link Schema}.
7
+ *
8
+ * @param node root node of the definition (`Schema (@stxt.schema)` or `Template (@stxt.template)`).
9
+ * @param meta provider of the meta-schema of the kind.
10
+ * @param transform function that turns the validated node into a Schema.
11
+ * @returns the compiled schema.
12
+ * @throws ValidationException the first validation finding, if the node does not validate.
13
+ */
14
+ export declare function compileDefinitionNode(node: Node, meta: SchemaProvider, transform: (node: Node) => Schema): Schema;
15
+ /**
16
+ * Parses a whole document that must hold exactly one definition, and compiles it.
17
+ *
18
+ * @param text text of the definition document.
19
+ * @param meta provider of the meta-schema of the kind.
20
+ * @param transform function that turns the validated root into a Schema.
21
+ * @param multipleRootsCode error code when the document does not hold exactly one root
22
+ * (`SCHEMA_MULTIPLE_ROOTS` for schemas, `TEMPLATE_MULTIPLE_ROOTS` for templates).
23
+ * @param kind word naming the kind in the error message (`schema` or `template`).
24
+ * @returns the compiled schema.
25
+ * @throws ParseException or ValidationException if the document is not a valid definition.
26
+ */
27
+ export declare function compileDefinitionDocument(text: string, meta: SchemaProvider, transform: (node: Node) => Schema, multipleRootsCode: string, kind: string): Schema;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileDefinitionNode = compileDefinitionNode;
4
+ exports.compileDefinitionDocument = compileDefinitionDocument;
5
+ const Parser_1 = require("../core/Parser");
6
+ const ParseException_1 = require("../exceptions/ParseException");
7
+ const ValidationException_1 = require("../exceptions/ValidationException");
8
+ const SchemaValidator_1 = require("./SchemaValidator");
9
+ /*
10
+ * The one pipeline every definition loader shares, whatever the store: the in-memory
11
+ * providers (a document each), UnifiedSchemaProvider (several roots per file) and
12
+ * discovery. A definition node is validated against the meta-schema of its kind and,
13
+ * only when valid, transformed into a Schema; a definition that does not validate is
14
+ * never registered anywhere — the first validation finding is thrown instead.
15
+ * Mirrors stxt-impl/schema/definition_compiler.txt.
16
+ */
17
+ /**
18
+ * Validates one root node against the meta provider of its kind and compiles it into a
19
+ * {@link Schema}.
20
+ *
21
+ * @param node root node of the definition (`Schema (@stxt.schema)` or `Template (@stxt.template)`).
22
+ * @param meta provider of the meta-schema of the kind.
23
+ * @param transform function that turns the validated node into a Schema.
24
+ * @returns the compiled schema.
25
+ * @throws ValidationException the first validation finding, if the node does not validate.
26
+ */
27
+ function compileDefinitionNode(node, meta, transform) {
28
+ const errors = new SchemaValidator_1.SchemaValidator(meta, true).validate(node);
29
+ if (errors.length > 0) {
30
+ throw errors[0];
31
+ }
32
+ return transform(node);
33
+ }
34
+ /**
35
+ * Parses a whole document that must hold exactly one definition, and compiles it.
36
+ *
37
+ * @param text text of the definition document.
38
+ * @param meta provider of the meta-schema of the kind.
39
+ * @param transform function that turns the validated root into a Schema.
40
+ * @param multipleRootsCode error code when the document does not hold exactly one root
41
+ * (`SCHEMA_MULTIPLE_ROOTS` for schemas, `TEMPLATE_MULTIPLE_ROOTS` for templates).
42
+ * @param kind word naming the kind in the error message (`schema` or `template`).
43
+ * @returns the compiled schema.
44
+ * @throws ParseException or ValidationException if the document is not a valid definition.
45
+ */
46
+ function compileDefinitionDocument(text, meta, transform, multipleRootsCode, kind) {
47
+ const nodes = new Parser_1.Parser().parse(text);
48
+ if (nodes.length !== 1) {
49
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, multipleRootsCode, `A ${kind} document must hold exactly 1 root node, got ${nodes.length}`);
50
+ }
51
+ return compileDefinitionNode(nodes[0], meta, transform);
52
+ }
53
+ //# sourceMappingURL=DefinitionCompiler.js.map
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NodeDefinition = void 0;
4
+ const ParseException_1 = require("../exceptions/ParseException");
4
5
  const ValidationException_1 = require("../exceptions/ValidationException");
5
6
  const StringUtils_1 = require("../core/StringUtils");
6
7
  /**
@@ -65,7 +66,7 @@ class NodeDefinition {
65
66
  addChildDefinition(childDefinition) {
66
67
  const qname = childDefinition.getQualifiedName();
67
68
  if (this.children.has(qname)) {
68
- throw new ValidationException_1.ValidationException(0, "CHILD_DUPLICATED", `Exists a previous node definition with: ${qname}`);
69
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "CHILD_DUPLICATED", `A child declaration with the same name already exists: ${qname}`);
69
70
  }
70
71
  this.children.set(qname, childDefinition);
71
72
  }
@@ -80,9 +81,11 @@ class NodeDefinition {
80
81
  * @throws ValidationException with code `VALUE_DUPLICATED` if the value (once trimmed) had already been added.
81
82
  */
82
83
  addValue(value, line) {
83
- const trimmed = value?.trim() ?? "";
84
+ // Language blanks only (U+0020/U+0009): any other whitespace (NBSP...) is part of
85
+ // the value, so `x` and `x<NBSP>` are two different ENUM values, as in every port.
86
+ const trimmed = StringUtils_1.StringUtils.trim(value ?? "");
84
87
  if (this.values.has(trimmed)) {
85
- throw new ValidationException_1.ValidationException(line ?? 0, "VALUE_DUPLICATED", `The values ${trimmed} is duplicated`);
88
+ throw new ValidationException_1.ValidationException(line ?? ParseException_1.ParseException.NO_LINE, "VALUE_DUPLICATED", `The value ${trimmed} is duplicated`);
86
89
  }
87
90
  this.values.add(trimmed);
88
91
  }
@@ -3,6 +3,8 @@ import { NodeDefinition } from "./NodeDefinition";
3
3
  export declare class Schema {
4
4
  /** Namespace of the schema language itself, `@stxt.schema`. */
5
5
  static readonly SCHEMA_NAMESPACE = "@stxt.schema";
6
+ /** Namespace of the template language, `@stxt.template`. */
7
+ static readonly TEMPLATE_NAMESPACE = "@stxt.template";
6
8
  private readonly nodes;
7
9
  private readonly namespace;
8
10
  private readonly description;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Schema = void 0;
4
4
  const NamespaceValidator_1 = require("../core/NamespaceValidator");
5
5
  const StringUtils_1 = require("../core/StringUtils");
6
+ const ParseException_1 = require("../exceptions/ParseException");
6
7
  const ValidationException_1 = require("../exceptions/ValidationException");
7
8
  /** Schema of a namespace: the set of {@link NodeDefinition} valid for the nodes of that namespace. */
8
9
  class Schema {
@@ -44,11 +45,11 @@ class Schema {
44
45
  * @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
45
46
  */
46
47
  addNodeDefinition(nodeDefinition) {
47
- const qname = nodeDefinition.getCanonicalName();
48
- if (this.nodes.has(qname)) {
49
- throw new ValidationException_1.ValidationException(0, "NODE_DUPLICATED", `Exists a previous node definition with: ${qname}`);
48
+ const canonicalName = nodeDefinition.getCanonicalName();
49
+ if (this.nodes.has(canonicalName)) {
50
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "NODE_DUPLICATED", `A node definition with the same name already exists: ${canonicalName}`);
50
51
  }
51
- this.nodes.set(qname, nodeDefinition);
52
+ this.nodes.set(canonicalName, nodeDefinition);
52
53
  }
53
54
  /** @returns the namespace this schema applies to. */
54
55
  getNamespace() {
@@ -69,4 +70,6 @@ class Schema {
69
70
  exports.Schema = Schema;
70
71
  /** Namespace of the schema language itself, `@stxt.schema`. */
71
72
  Schema.SCHEMA_NAMESPACE = "@stxt.schema";
73
+ /** Namespace of the template language, `@stxt.template`. */
74
+ Schema.TEMPLATE_NAMESPACE = "@stxt.template";
72
75
  //# sourceMappingURL=Schema.js.map
@@ -5,6 +5,7 @@ const Schema_1 = require("./Schema");
5
5
  const NodeDefinition_1 = require("./NodeDefinition");
6
6
  const ChildDefinition_1 = require("./ChildDefinition");
7
7
  const InlineNode_1 = require("../core/InlineNode");
8
+ const ParseException_1 = require("../exceptions/ParseException");
8
9
  const ValidationException_1 = require("../exceptions/ValidationException");
9
10
  const NamespaceValidator_1 = require("../core/NamespaceValidator");
10
11
  const StringUtils_1 = require("../core/StringUtils");
@@ -27,9 +28,11 @@ function transformNodeToSchema(node) {
27
28
  throw new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_ROOT_NOT_VALID", `Expected schema(${Schema_1.Schema.SCHEMA_NAMESPACE}) but got ${nodeName}(${namespaceSchema})`);
28
29
  }
29
30
  const root = inline(node);
30
- // The target namespace: required, and with a valid format
31
+ // The target namespace: required, and with a valid format. The value arrives already
32
+ // trimmed of language blanks by the parser; any other whitespace (NBSP...) is content
33
+ // and must fall through to the format check, exactly as in the other ports.
31
34
  const targetNamespace = StringUtils_1.StringUtils.lowerCase(root.getValue());
32
- if (!targetNamespace || targetNamespace.trim().length === 0) {
35
+ if (!targetNamespace) {
33
36
  throw new ValidationException_1.ValidationException(root.getLine(), "SCHEMA_NAMESPACE_EMPTY", "Schema namespace is empty");
34
37
  }
35
38
  if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
@@ -53,7 +56,7 @@ function transformNodeToSchema(node) {
53
56
  if (schChild.getNamespace() === schema.getNamespace()) {
54
57
  const childNorm = schChild.getCanonicalName();
55
58
  if (!allNames.has(childNorm)) {
56
- throw new ValidationException_1.ValidationException(0, "CHILD_NOT_DEFINED", `Child ${childNorm} not defined in ${schema.getNamespace()}`);
59
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "CHILD_NOT_DEFINED", `Child ${childNorm} not defined in ${schema.getNamespace()}`);
57
60
  }
58
61
  }
59
62
  }
@@ -88,8 +91,9 @@ function createFrom(node, namespace) {
88
91
  putChildToSchemaNode(result, child, namespace);
89
92
  }
90
93
  }
91
- // Look at the values
92
- let valuesNodes = n.getChildrenByName("values");
94
+ // Allowed values: only valid for the ENUM type
95
+ const valuesNodes = n.getChildrenByName("values"); // the "Values:" containers
96
+ let valueEntries = []; // the "Value:" entries inside
93
97
  if (valuesNodes && valuesNodes.length > 0) {
94
98
  if (type !== "ENUM") {
95
99
  throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
@@ -97,9 +101,8 @@ function createFrom(node, namespace) {
97
101
  if (valuesNodes.length > 1) {
98
102
  throw new ValidationException_1.ValidationException(valuesNodes[1].getLine(), "VALUES_DUPLICATED", `Node '${n.getValue()}' defines 'Values' ${valuesNodes.length} times`);
99
103
  }
100
- const valuesNode = valuesNodes[0];
101
- const values = inline(valuesNode).getChildrenByName("value");
102
- for (const v of values) {
104
+ valueEntries = inline(valuesNodes[0]).getChildrenByName("value");
105
+ for (const v of valueEntries) {
103
106
  // An empty Value: is a schema error (STXT-SCHEMA-SPEC 7.2, condition 14 of section 13):
104
107
  // an enumeration whose only valid value is the empty string makes no sense
105
108
  if (v.getText().length === 0) {
@@ -107,11 +110,9 @@ function createFrom(node, namespace) {
107
110
  }
108
111
  result.addValue(v.getText(), v.getLine());
109
112
  }
110
- // For the final ENUM check
111
- valuesNodes = values;
112
113
  }
113
- // Look at the enum
114
- if (type === "ENUM" && (!valuesNodes || valuesNodes.length === 0)) {
114
+ // An ENUM must declare at least one value (a "Values:" with no entries included)
115
+ if (type === "ENUM" && valueEntries.length === 0) {
115
116
  throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
116
117
  }
117
118
  return result;
@@ -1,12 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SchemaProviderMemory = void 0;
4
- const Parser_1 = require("../core/Parser");
5
4
  const StringUtils_1 = require("../core/StringUtils");
6
- const ValidationException_1 = require("../exceptions/ValidationException");
5
+ const DefinitionCompiler_1 = require("./DefinitionCompiler");
7
6
  const SchemaParser_1 = require("./SchemaParser");
8
7
  const SchemaProviderMeta_1 = require("./SchemaProviderMeta");
9
- const SchemaValidator_1 = require("./SchemaValidator");
10
8
  /**
11
9
  * In-memory {@link SchemaProvider}: it keeps the schemas added with {@link SchemaProviderMemory.addSchema}
12
10
  * indexed by namespace, and falls back to a parent provider (the meta-schema by default) for the
@@ -52,22 +50,8 @@ class SchemaProviderMemory {
52
50
  * particular `SCHEMA_MULTIPLE_ROOTS` if it does not hold exactly one root node.
53
51
  */
54
52
  addSchema(txt) {
55
- const parser = new Parser_1.Parser();
56
- const nodes = parser.parse(txt);
57
- if (nodes.length !== 1) {
58
- throw new ValidationException_1.ValidationException(0, "SCHEMA_MULTIPLE_ROOTS", `A schema document must hold exactly 1 root node, got ${nodes.length}`);
59
- }
60
- const node = nodes[0];
61
- // A schema that does not validate against its meta-schema must not be
62
- // registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
63
- const schemaValidator = new SchemaValidator_1.SchemaValidator(new SchemaProviderMeta_1.SchemaProviderMeta(), true);
64
- const errors = schemaValidator.validate(node);
65
- if (errors.length > 0) {
66
- throw errors[0];
67
- }
68
- const schema = (0, SchemaParser_1.transformNodeToSchema)(node);
69
- const key = schema.getNamespace();
70
- this.schemas.set(key, schema);
53
+ const schema = (0, DefinitionCompiler_1.compileDefinitionDocument)(txt, new SchemaProviderMeta_1.SchemaProviderMeta(), SchemaParser_1.transformNodeToSchema, "SCHEMA_MULTIPLE_ROOTS", "schema");
54
+ this.schemas.set(schema.getNamespace(), schema);
71
55
  }
72
56
  /** Removes every schema registered in this provider (the parent one is left untouched). */
73
57
  clear() {
@@ -6,9 +6,15 @@ import { SchemaProvider } from "./SchemaProvider";
6
6
  */
7
7
  export declare class SchemaProviderMeta implements SchemaProvider {
8
8
  private static readonly META_TEXT;
9
+ /**
10
+ * The meta-schema is immutable, so it is compiled once per process, lazily, and every
11
+ * instance serves this same schema (constructing these providers is common: every
12
+ * `addSchema()` and every discovery compilation builds one).
13
+ */
14
+ private static compiledMeta;
9
15
  private readonly meta;
10
16
  /**
11
- * Parses the meta-schema and keeps it ready to be served.
17
+ * Compiles the meta-schema the first time and keeps it ready to be served.
12
18
  *
13
19
  * @throws ValidationException with code `META_SCHEMA_INVALID` if the meta-schema does not produce exactly one document.
14
20
  */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SchemaProviderMeta = void 0;
4
4
  const Schema_1 = require("./Schema");
5
5
  const Parser_1 = require("../core/Parser");
6
+ const ParseException_1 = require("../exceptions/ParseException");
6
7
  const ValidationException_1 = require("../exceptions/ValidationException");
7
8
  const SchemaParser_1 = require("./SchemaParser");
8
9
  /**
@@ -11,17 +12,20 @@ const SchemaParser_1 = require("./SchemaParser");
11
12
  */
12
13
  class SchemaProviderMeta {
13
14
  /**
14
- * Parses the meta-schema and keeps it ready to be served.
15
+ * Compiles the meta-schema the first time and keeps it ready to be served.
15
16
  *
16
17
  * @throws ValidationException with code `META_SCHEMA_INVALID` if the meta-schema does not produce exactly one document.
17
18
  */
18
19
  constructor() {
19
- const parser = new Parser_1.Parser();
20
- const nodes = parser.parse(SchemaProviderMeta.META_TEXT);
21
- if (nodes.length !== 1) {
22
- throw new ValidationException_1.ValidationException(0, "META_SCHEMA_INVALID", `Meta schema must produce exactly 1 document, got ${nodes.length}`);
20
+ if (!SchemaProviderMeta.compiledMeta) {
21
+ const parser = new Parser_1.Parser();
22
+ const nodes = parser.parse(SchemaProviderMeta.META_TEXT);
23
+ if (nodes.length !== 1) {
24
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "META_SCHEMA_INVALID", `Meta schema must produce exactly 1 document, got ${nodes.length}`);
25
+ }
26
+ SchemaProviderMeta.compiledMeta = (0, SchemaParser_1.transformNodeToSchema)(nodes[0]);
23
27
  }
24
- this.meta = (0, SchemaParser_1.transformNodeToSchema)(nodes[0]);
28
+ this.meta = SchemaProviderMeta.compiledMeta;
25
29
  }
26
30
  /**
27
31
  * Serves the meta-schema of the schema language.
@@ -38,9 +42,6 @@ class SchemaProviderMeta {
38
42
  if (namespace !== Schema_1.Schema.SCHEMA_NAMESPACE) {
39
43
  return null;
40
44
  }
41
- if (!this.meta) {
42
- throw new ValidationException_1.ValidationException(0, "META_SCHEMA_NOT_AVAILABLE", "Meta schema not available");
43
- }
44
45
  return this.meta;
45
46
  }
46
47
  }
@@ -119,10 +119,12 @@ class SchemaValidator {
119
119
  for (const child of SchemaValidator.childrenOf(node)) {
120
120
  const childName = child.getQualifiedName();
121
121
  count.set(childName, (count.get(childName) ?? 0) + 1);
122
- if (!childrenByType.has(childName)) {
123
- childrenByType.set(childName, []);
122
+ let sameName = childrenByType.get(childName);
123
+ if (!sameName) {
124
+ sameName = [];
125
+ childrenByType.set(childName, sameName);
124
126
  }
125
- childrenByType.get(childName).push(child);
127
+ sameName.push(child);
126
128
  }
127
129
  for (const childDef of nodeDef.getChildren().values()) {
128
130
  const qname = childDef.getQualifiedName();
@@ -1,8 +1,10 @@
1
1
  import { Type } from "../Type";
2
2
  /**
3
3
  * STXT-SCHEMA-SPEC 9.5: standard Base64 (not URL-safe), padding optional, no leftover bits,
4
- * never empty. The check is a regular expression plus the leftover-bits rule, never the
5
- * platform decoder, which silently ignores characters outside the alphabet.
4
+ * never empty. The shape is checked in linear time — strip the optional trailing padding,
5
+ * enforce the padding/length rule, verify the core belongs to the standard alphabet — plus
6
+ * the leftover-bits rule; never the platform decoder, which silently ignores characters
7
+ * outside the alphabet.
6
8
  *
7
9
  * @param value value already stripped of blanks.
8
10
  * @returns whether it is valid Base64.
@@ -7,30 +7,54 @@ const binaryValue_1 = require("./binaryValue");
7
7
  /** Standard alphabet of RFC 4648 section 4, used to check the leftover bits of the last character. */
8
8
  const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
9
  /**
10
- * Shape of a Base64 value (STXT-SCHEMA-SPEC 9.5): groups of four characters of the standard
11
- * alphabet, with an optional final group of two or three characters whose `=` padding may be
12
- * omitted. The empty string matches here and is rejected separately.
10
+ * Membership test for the standard alphabet, applied to the padding-stripped core. A plain
11
+ * character class with a single `*`, which matches in linear time with no backtracking
12
+ * unlike a grouped-repetition shape such as `(?:[A-Za-z0-9+/]{4})*`, whose backtracking state
13
+ * overflows V8's call stack (`RangeError`) on inputs of a few million characters, still inside
14
+ * the default `maxInputSize`.
13
15
  */
14
- const SHAPE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}(?:==)?|[A-Za-z0-9+/]{3}=?)?$/;
16
+ const ALPHABET_ONLY = /^[A-Za-z0-9+/]*$/;
15
17
  /**
16
18
  * STXT-SCHEMA-SPEC 9.5: standard Base64 (not URL-safe), padding optional, no leftover bits,
17
- * never empty. The check is a regular expression plus the leftover-bits rule, never the
18
- * platform decoder, which silently ignores characters outside the alphabet.
19
+ * never empty. The shape is checked in linear time — strip the optional trailing padding,
20
+ * enforce the padding/length rule, verify the core belongs to the standard alphabet — plus
21
+ * the leftover-bits rule; never the platform decoder, which silently ignores characters
22
+ * outside the alphabet.
19
23
  *
20
24
  * @param value value already stripped of blanks.
21
25
  * @returns whether it is valid Base64.
22
26
  */
23
27
  function isValidBase64(value) {
24
- if (value.length === 0 || !SHAPE.test(value)) {
28
+ if (value.length === 0) {
29
+ return false;
30
+ }
31
+ // Strip the optional final padding: 0, 1 or 2 '=' signs, only at the very end of the
32
+ // value. Three or more, or an '=' anywhere else, is rejected below by the alphabet check.
33
+ let core = value;
34
+ let padding = 0;
35
+ if (value.endsWith("==")) {
36
+ core = value.slice(0, -2);
37
+ padding = 2;
38
+ }
39
+ else if (value.endsWith("=")) {
40
+ core = value.slice(0, -1);
41
+ padding = 1;
42
+ }
43
+ if (!ALPHABET_ONLY.test(core)) {
44
+ return false;
45
+ }
46
+ const rest = core.length % 4;
47
+ // A trailing group of a single character never occurs in Base64. Two '=' close a
48
+ // two-character group, one '=' a three-character group; with no padding the core is
49
+ // whole groups plus an optional final group of two or three characters.
50
+ if (padding === 2 ? rest !== 2 : padding === 1 ? rest !== 3 : rest === 1) {
25
51
  return false;
26
52
  }
27
- const data = value.replace(/=+$/, "");
28
- const rest = data.length % 4;
29
53
  if (rest === 0) {
30
54
  return true;
31
55
  }
32
56
  // The last character encodes 6 bits; with 2 characters 4 of them are leftover, with 3, 2 of them.
33
- const last = ALPHABET.indexOf(data.charAt(data.length - 1));
57
+ const last = ALPHABET.indexOf(core.charAt(core.length - 1));
34
58
  const mask = rest === 2 ? 0x0f : 0x03;
35
59
  return (last & mask) === 0;
36
60
  }
@@ -1,6 +1,7 @@
1
1
  import { Type } from "../Type";
2
2
  /**
3
3
  * `MARKDOWN` type. STXT-SCHEMA-SPEC 9.7: for validation purposes it is equivalent to TEXT
4
- * (any content is valid Markdown); only children are forbidden.
4
+ * (any content is valid Markdown; only children are forbidden), so it shares its validation
5
+ * and only the name differs.
5
6
  */
6
7
  export declare const MARKDOWN: Type;
@@ -1,20 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MARKDOWN = void 0;
4
- const InlineNode_1 = require("../../core/InlineNode");
5
- const ValidationException_1 = require("../../exceptions/ValidationException");
4
+ const TEXT_1 = require("./TEXT");
6
5
  /**
7
6
  * `MARKDOWN` type. STXT-SCHEMA-SPEC 9.7: for validation purposes it is equivalent to TEXT
8
- * (any content is valid Markdown); only children are forbidden.
7
+ * (any content is valid Markdown; only children are forbidden), so it shares its validation
8
+ * and only the name differs.
9
9
  */
10
10
  exports.MARKDOWN = {
11
11
  getName() {
12
12
  return "MARKDOWN";
13
13
  },
14
- validate(nodeDef, node) {
15
- if (node instanceof InlineNode_1.InlineNode && node.getChildren().length > 0) {
16
- throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED", `Not allowed children nodes in node ${node.getQualifiedName()}`);
17
- }
18
- },
14
+ validate: TEXT_1.TEXT.validate,
19
15
  };
20
16
  //# sourceMappingURL=MARKDOWN.js.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChildLineParser = void 0;
4
4
  const ValidationException_1 = require("../exceptions/ValidationException");
5
+ const StringUtils_1 = require("../core/StringUtils");
5
6
  const ChildLine_1 = require("./ChildLine");
6
7
  /** Parses the inline value of a child node inside an `@stxt.template`, shaped as `(min,max) TYPE [values]`. */
7
8
  class ChildLineParser {
@@ -16,7 +17,7 @@ class ChildLineParser {
16
17
  * `MIN_GREATER_THAN_MAX` or `VALUE_DUPLICATED` if the line is not valid.
17
18
  */
18
19
  static parse(rawLine, lineNumber) {
19
- if (rawLine.trim().length === 0) {
20
+ if (StringUtils_1.StringUtils.trim(rawLine).length === 0) {
20
21
  return new ChildLine_1.ChildLine(null, null, null, null);
21
22
  }
22
23
  const m = ChildLineParser.CHILD_LINE_PATTERN.exec(rawLine);
@@ -24,11 +25,11 @@ class ChildLineParser {
24
25
  throw new ValidationException_1.ValidationException(lineNumber, "STRUCTURE_LINE_NOT_VALID", `Line not valid: ${rawLine}`);
25
26
  }
26
27
  // m[1]=count, m[2]=type, m[3]=values
27
- let type = m[2]?.trim() ?? "";
28
+ let type = StringUtils_1.StringUtils.trim(m[2]);
28
29
  if (type.length === 0) {
29
30
  type = null;
30
31
  }
31
- const count = (m[1] ?? "").trim();
32
+ const count = StringUtils_1.StringUtils.trim(m[1]);
32
33
  let min = null;
33
34
  let max = null;
34
35
  if (count.length === 0 || count === "*") {
@@ -56,8 +57,8 @@ class ChildLineParser {
56
57
  if (parts.length !== 2) {
57
58
  throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
58
59
  }
59
- const aNum = ChildLineParser.parseCount(parts[0].trim(), count, rawLine, lineNumber);
60
- const bNum = ChildLineParser.parseCount(parts[1].trim(), count, rawLine, lineNumber);
60
+ const aNum = ChildLineParser.parseCount(StringUtils_1.StringUtils.trim(parts[0]), count, rawLine, lineNumber);
61
+ const bNum = ChildLineParser.parseCount(StringUtils_1.StringUtils.trim(parts[1]), count, rawLine, lineNumber);
61
62
  // Invalid cardinality when min > max (STXT-TEMPLATE-SPEC 7.1)
62
63
  if (aNum > bNum) {
63
64
  throw new ValidationException_1.ValidationException(lineNumber, "MIN_GREATER_THAN_MAX", `Min ${aNum} greater than Max ${bNum} in line: ${rawLine}`);
@@ -76,7 +77,7 @@ class ChildLineParser {
76
77
  const parts = valuesStr.split(",");
77
78
  const list = [];
78
79
  for (let part of parts) {
79
- part = part.trim();
80
+ part = StringUtils_1.StringUtils.trim(part);
80
81
  // An empty item ("[a, , b]", "[a, b,]") is an error, as an empty Value: is in a
81
82
  // schema (STXT-TEMPLATE-SPEC 14.14). Only the whole list may be empty ("[]"),
82
83
  // which the template parser reports as VALUES_REQUIRED.
@@ -97,8 +98,7 @@ class ChildLineParser {
97
98
  // treated as a real definition/redefinition (ported from stxt-java).
98
99
  values = list;
99
100
  }
100
- // type is string|null in our class
101
- return new ChildLine_1.ChildLine(type ?? null, min, max, values);
101
+ return new ChildLine_1.ChildLine(type, min, max, values);
102
102
  }
103
103
  // num, min and max must be non-negative integers, with no trailing text (STXT-TEMPLATE-SPEC 7.1)
104
104
  static parseCount(num, count, rawLine, lineNumber) {
@@ -109,5 +109,8 @@ class ChildLineParser {
109
109
  }
110
110
  }
111
111
  exports.ChildLineParser = ChildLineParser;
112
- ChildLineParser.CHILD_LINE_PATTERN = /^\s*(?:\(\s*([^()\s][^)]*?)\s*\)\s*)?([^()[\]]*)?(?:\[\s*([^]*?)\s*\]\s*)?\s*$/;
112
+ // STXT-TEMPLATE-SPEC 6.2/9: the trim in the template grammar is the language blank
113
+ // (U+0020/U+0009) only, never the platform's \s (which also swallows NBSP, U+3000...).
114
+ // So every \s here is [ \t], including inside the negated class.
115
+ ChildLineParser.CHILD_LINE_PATTERN = /^[ \t]*(?:\([ \t]*([^() \t][^)]*?)[ \t]*\)[ \t]*)?([^()[\]]*)?(?:\[[ \t]*([^]*?)[ \t]*\][ \t]*)?[ \t]*$/;
113
116
  //# sourceMappingURL=ChildLineParser.js.map
@@ -6,9 +6,11 @@ import { SchemaProvider } from "../schema/SchemaProvider";
6
6
  */
7
7
  export declare class MetaTemplateSchemaProvider implements SchemaProvider {
8
8
  private static readonly META_TEXT;
9
+ /** Compiled once per process and shared between instances, exactly like {@link SchemaProviderMeta}. */
10
+ private static compiledMeta;
9
11
  private readonly meta;
10
12
  /**
11
- * Parses the meta-template and keeps the schema it produces ready to be served.
13
+ * Compiles the meta-template the first time and keeps the schema it produces ready to be served.
12
14
  *
13
15
  * @throws ValidationException with code `META_SCHEMA_INVALID` if the meta-template does not produce exactly one document.
14
16
  */
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MetaTemplateSchemaProvider = void 0;
4
4
  const Parser_1 = require("../core/Parser");
5
+ const Schema_1 = require("../schema/Schema");
6
+ const ParseException_1 = require("../exceptions/ParseException");
5
7
  const ValidationException_1 = require("../exceptions/ValidationException");
6
8
  const TemplateParser_1 = require("./TemplateParser");
7
9
  /**
@@ -10,17 +12,20 @@ const TemplateParser_1 = require("./TemplateParser");
10
12
  */
11
13
  class MetaTemplateSchemaProvider {
12
14
  /**
13
- * Parses the meta-template and keeps the schema it produces ready to be served.
15
+ * Compiles the meta-template the first time and keeps the schema it produces ready to be served.
14
16
  *
15
17
  * @throws ValidationException with code `META_SCHEMA_INVALID` if the meta-template does not produce exactly one document.
16
18
  */
17
19
  constructor() {
18
- const parser = new Parser_1.Parser();
19
- const nodes = parser.parse(MetaTemplateSchemaProvider.META_TEXT);
20
- if (nodes.length !== 1) {
21
- throw new ValidationException_1.ValidationException(0, "META_SCHEMA_INVALID", `Meta schema must produce exactly 1 document, got ${nodes.length}`);
20
+ if (!MetaTemplateSchemaProvider.compiledMeta) {
21
+ const parser = new Parser_1.Parser();
22
+ const nodes = parser.parse(MetaTemplateSchemaProvider.META_TEXT);
23
+ if (nodes.length !== 1) {
24
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "META_SCHEMA_INVALID", `Meta schema must produce exactly 1 document, got ${nodes.length}`);
25
+ }
26
+ MetaTemplateSchemaProvider.compiledMeta = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
22
27
  }
23
- this.meta = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
28
+ this.meta = MetaTemplateSchemaProvider.compiledMeta;
24
29
  }
25
30
  /**
26
31
  * Serves the meta-schema of the template language.
@@ -33,13 +38,9 @@ class MetaTemplateSchemaProvider {
33
38
  * @returns the meta-schema of the template language, or `null` for any other namespace.
34
39
  */
35
40
  getSchema(namespace) {
36
- if (namespace !== "@stxt.template") {
41
+ if (namespace !== Schema_1.Schema.TEMPLATE_NAMESPACE) {
37
42
  return null;
38
43
  }
39
- // meta always exists once the constructor finished, but this mirrors the Java version
40
- if (!this.meta) {
41
- throw new ValidationException_1.ValidationException(0, "META_SCHEMA_NOT_AVAILABLE", "Meta schema not available");
42
- }
43
44
  return this.meta;
44
45
  }
45
46
  }
@@ -1,7 +1,6 @@
1
1
  import { Node } from "../core/Node";
2
2
  import { Schema } from "../schema/Schema";
3
- /** Namespace of the template language itself, `@stxt.template`. */
4
- /** Namespace of the template language itself, `@stxt.template`. */
3
+ /** Namespace of the template language itself; the canonical constant is {@link Schema.TEMPLATE_NAMESPACE}. */
5
4
  export declare const TEMPLATE_NAMESPACE = "@stxt.template";
6
5
  /**
7
6
  * Turns the tree of an already parsed `@stxt.template` document into an equivalent {@link Schema}.