@stxt-lang/core 0.9.0 → 0.9.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/LineParser.d.ts +1 -1
- package/out/core/LineParser.js +4 -4
- package/out/core/NamespaceValidator.d.ts +7 -0
- package/out/core/NamespaceValidator.js +9 -0
- package/out/core/NodeCreator.js +1 -1
- package/out/core/Parser.d.ts +5 -0
- package/out/core/Parser.js +12 -8
- package/out/schema/NodeDefinition.d.ts +1 -1
- package/out/schema/NodeDefinition.js +2 -2
- package/out/schema/Schema.d.ts +1 -1
- package/out/schema/Schema.js +2 -2
- package/out/schema/SchemaParser.d.ts +2 -1
- package/out/schema/SchemaParser.js +19 -9
- package/out/schema/SchemaProviderMemory.d.ts +2 -1
- package/out/schema/SchemaProviderMemory.js +8 -2
- package/out/schema/SchemaValidator.js +7 -7
- package/out/schema/TypeRegistry.js +1 -1
- package/out/schema/type/DATE.d.ts +1 -1
- package/out/schema/type/DATE.js +4 -3
- package/out/schema/type/ENUM.js +1 -1
- package/out/schema/type/GROUP.js +1 -1
- package/out/schema/type/INLINE.js +1 -1
- package/out/schema/type/MARKDOWN.js +1 -1
- package/out/schema/type/TEXT.js +1 -1
- package/out/schema/type/TIME.d.ts +1 -1
- package/out/schema/type/TIME.js +4 -3
- package/out/schema/type/TIMESTAMP.d.ts +4 -1
- package/out/schema/type/TIMESTAMP.js +9 -3
- package/out/schema/type/URL.d.ts +10 -3
- package/out/schema/type/URL.js +11 -24
- package/out/schema/type/dateTime.d.ts +9 -0
- package/out/schema/type/dateTime.js +23 -0
- package/out/schema/type/rangeType.d.ts +11 -0
- package/out/schema/type/rangeType.js +30 -0
- package/out/schema/type/regexType.js +1 -1
- package/out/template/ChildLineParser.d.ts +1 -1
- package/out/template/ChildLineParser.js +4 -4
- package/out/template/TemplateParser.d.ts +3 -2
- package/out/template/TemplateParser.js +30 -14
- package/out/template/TemplateSchemaProviderMemory.d.ts +4 -3
- package/out/template/TemplateSchemaProviderMemory.js +5 -8
- package/package.json +1 -1
package/out/core/LineParser.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { Line } from "./Line";
|
|
|
16
16
|
* @param numLine line number, for the error messages.
|
|
17
17
|
* @param validate false to split the line without enforcing the indentation rules.
|
|
18
18
|
* @returns the line already split into indentation and content.
|
|
19
|
-
* @throws ParseException with code `
|
|
19
|
+
* @throws ParseException with code `INDENTATION_MIXED`, `INDENTATION_SPACES_NOT_VALID` or
|
|
20
20
|
* `INDENTATION_LEVEL_NOT_VALID` if the indentation is not valid.
|
|
21
21
|
*/
|
|
22
22
|
export declare function parseLine(line: string, lastNodeBlock: boolean, lastLevel: number, numLine: number, validate?: boolean): Line;
|
package/out/core/LineParser.js
CHANGED
|
@@ -22,7 +22,7 @@ const Line_1 = require("./Line");
|
|
|
22
22
|
* @param numLine line number, for the error messages.
|
|
23
23
|
* @param validate false to split the line without enforcing the indentation rules.
|
|
24
24
|
* @returns the line already split into indentation and content.
|
|
25
|
-
* @throws ParseException with code `
|
|
25
|
+
* @throws ParseException with code `INDENTATION_MIXED`, `INDENTATION_SPACES_NOT_VALID` or
|
|
26
26
|
* `INDENTATION_LEVEL_NOT_VALID` if the indentation is not valid.
|
|
27
27
|
*/
|
|
28
28
|
function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
|
|
@@ -66,7 +66,7 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
|
|
|
66
66
|
// The prefix covering the block level must be homogeneous (spec 10.2, rule 2);
|
|
67
67
|
// empty lines are always preserved and are exempt from it (spec 10.3)
|
|
68
68
|
if (validate && sawSpace && sawTab && text.length > 0) {
|
|
69
|
-
throw new ParseException_1.ParseException(numLine, "
|
|
69
|
+
throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
|
|
70
70
|
}
|
|
71
71
|
return new Line_1.Line(level, text, false, true, pointer);
|
|
72
72
|
}
|
|
@@ -83,11 +83,11 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
|
|
|
83
83
|
}
|
|
84
84
|
// Tabs and spaces mixed in the indentation (spec 8.1 and 8.3)
|
|
85
85
|
if (validate && sawSpace && sawTab) {
|
|
86
|
-
throw new ParseException_1.ParseException(numLine, "
|
|
86
|
+
throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
|
|
87
87
|
}
|
|
88
88
|
// Indentation with spaces that is not a multiple of 4
|
|
89
89
|
if (validate && spaces > 0) {
|
|
90
|
-
throw new ParseException_1.ParseException(numLine, "
|
|
90
|
+
throw new ParseException_1.ParseException(numLine, "INDENTATION_SPACES_NOT_VALID", `There are ${spaces} spaces before node`);
|
|
91
91
|
}
|
|
92
92
|
// Validate the level (spec 11.3). Comments included (spec 9): lastLevel is the level of the
|
|
93
93
|
// last NODE, a comment never becomes the reference.
|
|
@@ -18,5 +18,12 @@ export declare class NamespaceValidator {
|
|
|
18
18
|
* @param lineNumber line number, for the error message.
|
|
19
19
|
* @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
|
|
20
20
|
*/
|
|
21
|
+
/**
|
|
22
|
+
* Tells whether a namespace matches the format, without throwing.
|
|
23
|
+
*
|
|
24
|
+
* @param namespace already normalized namespace to check.
|
|
25
|
+
* @returns true if it matches the format; false when it is null, empty or malformed.
|
|
26
|
+
*/
|
|
27
|
+
static isValid(namespace: string | null | undefined): boolean;
|
|
21
28
|
static validateNamespaceFormat(namespace: string | null | undefined, lineNumber: number): void;
|
|
22
29
|
}
|
|
@@ -11,6 +11,15 @@ class NamespaceValidator {
|
|
|
11
11
|
* @param lineNumber line number, for the error message.
|
|
12
12
|
* @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Tells whether a namespace matches the format, without throwing.
|
|
16
|
+
*
|
|
17
|
+
* @param namespace already normalized namespace to check.
|
|
18
|
+
* @returns true if it matches the format; false when it is null, empty or malformed.
|
|
19
|
+
*/
|
|
20
|
+
static isValid(namespace) {
|
|
21
|
+
return !!namespace && NamespaceValidator.NAMESPACE_FORMAT.test(namespace);
|
|
22
|
+
}
|
|
14
23
|
static validateNamespaceFormat(namespace, lineNumber) {
|
|
15
24
|
if (!namespace) {
|
|
16
25
|
return;
|
package/out/core/NodeCreator.js
CHANGED
|
@@ -49,7 +49,7 @@ function createNode(lineIndent, lineNumber) {
|
|
|
49
49
|
value = line.substring(nodeIndex + Constants_1.Constants.SEP_NODE.length);
|
|
50
50
|
}
|
|
51
51
|
if (textNode && StringUtils_1.StringUtils.trim(value).length > 0) {
|
|
52
|
-
throw new ParseException_1.ParseException(lineNumber, "
|
|
52
|
+
throw new ParseException_1.ParseException(lineNumber, "BLOCK_VALUE_NOT_ALLOWED", `Line not valid: ${line}`);
|
|
53
53
|
}
|
|
54
54
|
// The namespace the line declares, if any (empty when it inherits)
|
|
55
55
|
const nameNamespace = NameNamespaceParser_1.NameNamespaceParser.parse(name, null, lineNumber, line);
|
package/out/core/Parser.d.ts
CHANGED
|
@@ -41,6 +41,11 @@ export declare class Parser {
|
|
|
41
41
|
*/
|
|
42
42
|
parseResult(content: string): ParseResult;
|
|
43
43
|
private processLine;
|
|
44
|
+
/**
|
|
45
|
+
* Records an error raised while parsing or validating a line. Typed exceptions travel as they
|
|
46
|
+
* are; anything else is wrapped under the code `UNEXPECTED_ERROR` (as a ValidationException
|
|
47
|
+
* when it was raised by a validator, so that the subtype still tells the phase apart).
|
|
48
|
+
*/
|
|
44
49
|
private handleError;
|
|
45
50
|
private closeToLevel;
|
|
46
51
|
private removeUTF8BOM;
|
package/out/core/Parser.js
CHANGED
|
@@ -6,6 +6,7 @@ const LineParser_1 = require("./LineParser");
|
|
|
6
6
|
const NodeCreator_1 = require("./NodeCreator");
|
|
7
7
|
const ParseResult_1 = require("./ParseResult");
|
|
8
8
|
const ParseException_1 = require("../exceptions/ParseException");
|
|
9
|
+
const ValidationException_1 = require("../exceptions/ValidationException");
|
|
9
10
|
/**
|
|
10
11
|
* Line-by-line STXT parsing engine. It knows nothing about schemas: semantic validation is
|
|
11
12
|
* plugged in through {@link Parser.registerValidator} and {@link Parser.registerObserver}.
|
|
@@ -145,17 +146,20 @@ class Parser {
|
|
|
145
146
|
this.handleError(e, lineNumber, result);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Records an error raised while parsing or validating a line. Typed exceptions travel as they
|
|
151
|
+
* are; anything else is wrapped under the code `UNEXPECTED_ERROR` (as a ValidationException
|
|
152
|
+
* when it was raised by a validator, so that the subtype still tells the phase apart).
|
|
153
|
+
*/
|
|
154
|
+
handleError(e, line, result, validating = false) {
|
|
149
155
|
if (e instanceof ParseException_1.ParseException) {
|
|
150
156
|
result.addError(e);
|
|
151
157
|
}
|
|
152
|
-
else if (e instanceof Error) {
|
|
153
|
-
// Turn generic errors into a ParseException
|
|
154
|
-
result.addError(new ParseException_1.ParseException(line, errorCode, e.message));
|
|
155
|
-
}
|
|
156
158
|
else {
|
|
157
|
-
|
|
158
|
-
result.addError(
|
|
159
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
160
|
+
result.addError(validating
|
|
161
|
+
? new ValidationException_1.ValidationException(line, "UNEXPECTED_ERROR", message)
|
|
162
|
+
: new ParseException_1.ParseException(line, "UNEXPECTED_ERROR", message));
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
closeToLevel(stack, targetLevel, result) {
|
|
@@ -170,7 +174,7 @@ class Parser {
|
|
|
170
174
|
});
|
|
171
175
|
}
|
|
172
176
|
catch (e) {
|
|
173
|
-
this.handleError(e, completed.getLine(), result,
|
|
177
|
+
this.handleError(e, completed.getLine(), result, true);
|
|
174
178
|
}
|
|
175
179
|
});
|
|
176
180
|
// Hand it over to the observers
|
|
@@ -45,7 +45,7 @@ export declare class NodeDefinition {
|
|
|
45
45
|
* Adds the definition of an expected child.
|
|
46
46
|
*
|
|
47
47
|
* @param childDefinition definition of the child to add.
|
|
48
|
-
* @throws ValidationException with code `
|
|
48
|
+
* @throws ValidationException with code `CHILD_DUPLICATED` if a definition for that child already existed.
|
|
49
49
|
*/
|
|
50
50
|
addChildDefinition(childDefinition: ChildDefinition): void;
|
|
51
51
|
/**
|
|
@@ -67,12 +67,12 @@ class NodeDefinition {
|
|
|
67
67
|
* Adds the definition of an expected child.
|
|
68
68
|
*
|
|
69
69
|
* @param childDefinition definition of the child to add.
|
|
70
|
-
* @throws ValidationException with code `
|
|
70
|
+
* @throws ValidationException with code `CHILD_DUPLICATED` if a definition for that child already existed.
|
|
71
71
|
*/
|
|
72
72
|
addChildDefinition(childDefinition) {
|
|
73
73
|
const qname = childDefinition.getQualifiedName();
|
|
74
74
|
if (this.children.has(qname)) {
|
|
75
|
-
throw new ValidationException_1.ValidationException(0, "
|
|
75
|
+
throw new ValidationException_1.ValidationException(0, "CHILD_DUPLICATED", `Exists a previous node definition with: ${qname}`);
|
|
76
76
|
}
|
|
77
77
|
this.children.set(qname, childDefinition);
|
|
78
78
|
}
|
package/out/schema/Schema.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare class Schema {
|
|
|
28
28
|
* Adds the definition of a node to this schema.
|
|
29
29
|
*
|
|
30
30
|
* @param nodeDefinition node definition to add.
|
|
31
|
-
* @throws ValidationException with code `
|
|
31
|
+
* @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
|
|
32
32
|
*/
|
|
33
33
|
addNodeDefinition(nodeDefinition: NodeDefinition): void;
|
|
34
34
|
/** @returns the namespace this schema applies to. */
|
package/out/schema/Schema.js
CHANGED
|
@@ -37,12 +37,12 @@ class Schema {
|
|
|
37
37
|
* Adds the definition of a node to this schema.
|
|
38
38
|
*
|
|
39
39
|
* @param nodeDefinition node definition to add.
|
|
40
|
-
* @throws ValidationException with code `
|
|
40
|
+
* @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
|
|
41
41
|
*/
|
|
42
42
|
addNodeDefinition(nodeDefinition) {
|
|
43
43
|
const qname = nodeDefinition.getCanonicalName();
|
|
44
44
|
if (this.nodes.has(qname)) {
|
|
45
|
-
throw new ValidationException_1.ValidationException(0, "
|
|
45
|
+
throw new ValidationException_1.ValidationException(0, "NODE_DUPLICATED", `Exists a previous node definition with: ${qname}`);
|
|
46
46
|
}
|
|
47
47
|
this.nodes.set(qname, nodeDefinition);
|
|
48
48
|
}
|
|
@@ -5,6 +5,7 @@ import { Node } from "../core/Node";
|
|
|
5
5
|
*
|
|
6
6
|
* @param node root node of the schema document, `Schema (@stxt.schema): ...`.
|
|
7
7
|
* @returns the schema the document defines.
|
|
8
|
-
* @throws ValidationException
|
|
8
|
+
* @throws ValidationException with `SCHEMA_ROOT_NOT_VALID`, `SCHEMA_NAMESPACE_EMPTY`, `SCHEMA_NODE_NOT_INLINE`
|
|
9
|
+
* or the code of the first rule of the schema language the document breaks, if the document is not a valid `@stxt.schema` one.
|
|
9
10
|
*/
|
|
10
11
|
export declare function transformNodeToSchema(node: Node): Schema;
|
|
@@ -6,7 +6,8 @@ const NodeDefinition_1 = require("./NodeDefinition");
|
|
|
6
6
|
const ChildDefinition_1 = require("./ChildDefinition");
|
|
7
7
|
const InlineNode_1 = require("../core/InlineNode");
|
|
8
8
|
const ValidationException_1 = require("../exceptions/ValidationException");
|
|
9
|
-
const
|
|
9
|
+
const NamespaceValidator_1 = require("../core/NamespaceValidator");
|
|
10
|
+
const StringUtils_1 = require("../core/StringUtils");
|
|
10
11
|
const NameNamespaceParser_1 = require("../core/NameNamespaceParser");
|
|
11
12
|
const TypeRegistry_1 = require("./TypeRegistry");
|
|
12
13
|
/**
|
|
@@ -14,7 +15,8 @@ const TypeRegistry_1 = require("./TypeRegistry");
|
|
|
14
15
|
*
|
|
15
16
|
* @param node root node of the schema document, `Schema (@stxt.schema): ...`.
|
|
16
17
|
* @returns the schema the document defines.
|
|
17
|
-
* @throws ValidationException
|
|
18
|
+
* @throws ValidationException with `SCHEMA_ROOT_NOT_VALID`, `SCHEMA_NAMESPACE_EMPTY`, `SCHEMA_NODE_NOT_INLINE`
|
|
19
|
+
* or the code of the first rule of the schema language the document breaks, if the document is not a valid `@stxt.schema` one.
|
|
18
20
|
*/
|
|
19
21
|
function transformNodeToSchema(node) {
|
|
20
22
|
// Node name
|
|
@@ -22,12 +24,20 @@ function transformNodeToSchema(node) {
|
|
|
22
24
|
const namespaceSchema = node.getNamespace();
|
|
23
25
|
// Get the name and the namespace
|
|
24
26
|
if (nodeName !== "schema" || namespaceSchema !== Schema_1.Schema.SCHEMA_NAMESPACE) {
|
|
25
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "
|
|
27
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_ROOT_NOT_VALID", `Expected schema(${Schema_1.Schema.SCHEMA_NAMESPACE}) but got ${nodeName}(${namespaceSchema})`);
|
|
26
28
|
}
|
|
27
29
|
const root = inline(node);
|
|
30
|
+
// The target namespace: required, and with a valid format
|
|
31
|
+
const targetNamespace = StringUtils_1.StringUtils.lowerCase(root.getValue());
|
|
32
|
+
if (!targetNamespace || targetNamespace.trim().length === 0) {
|
|
33
|
+
throw new ValidationException_1.ValidationException(root.getLine(), "SCHEMA_NAMESPACE_EMPTY", "Schema namespace is empty");
|
|
34
|
+
}
|
|
35
|
+
if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
|
|
36
|
+
throw new ValidationException_1.ValidationException(root.getLine(), "SCHEMA_ROOT_NOT_VALID", `Schema namespace not valid: ${targetNamespace}`);
|
|
37
|
+
}
|
|
28
38
|
// Get the description
|
|
29
39
|
const descrip = root.getChild("description")?.getText();
|
|
30
|
-
const schema = new Schema_1.Schema(
|
|
40
|
+
const schema = new Schema_1.Schema(targetNamespace, root.getLine(), descrip);
|
|
31
41
|
// Used to check that every child is defined
|
|
32
42
|
const allNames = new Set();
|
|
33
43
|
// Get the nodes
|
|
@@ -55,7 +65,7 @@ function inline(node) {
|
|
|
55
65
|
if (node instanceof InlineNode_1.InlineNode) {
|
|
56
66
|
return node;
|
|
57
67
|
}
|
|
58
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "
|
|
68
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "SCHEMA_NODE_NOT_INLINE", `Node '${node.getName()}' must be inline in a schema`);
|
|
59
69
|
}
|
|
60
70
|
/** Builds the definition of a node from a `Node:` entry of the schema document. */
|
|
61
71
|
function createFrom(node, namespace) {
|
|
@@ -82,10 +92,10 @@ function createFrom(node, namespace) {
|
|
|
82
92
|
let valuesNodes = n.getChildrenByName("values");
|
|
83
93
|
if (valuesNodes && valuesNodes.length > 0) {
|
|
84
94
|
if (type !== "ENUM") {
|
|
85
|
-
throw new ValidationException_1.ValidationException(n.getLine(), "
|
|
95
|
+
throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
|
|
86
96
|
}
|
|
87
97
|
if (valuesNodes.length > 1) {
|
|
88
|
-
throw new
|
|
98
|
+
throw new ValidationException_1.ValidationException(valuesNodes[1].getLine(), "VALUES_DUPLICATED", `Node '${n.getValue()}' defines 'Values' ${valuesNodes.length} times`);
|
|
89
99
|
}
|
|
90
100
|
const valuesNode = valuesNodes[0];
|
|
91
101
|
const values = inline(valuesNode).getChildrenByName("value");
|
|
@@ -97,7 +107,7 @@ function createFrom(node, namespace) {
|
|
|
97
107
|
}
|
|
98
108
|
// Look at the enum
|
|
99
109
|
if (type === "ENUM" && (!valuesNodes || valuesNodes.length === 0)) {
|
|
100
|
-
throw new ValidationException_1.ValidationException(n.getLine(), "
|
|
110
|
+
throw new ValidationException_1.ValidationException(n.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
|
|
101
111
|
}
|
|
102
112
|
return result;
|
|
103
113
|
}
|
|
@@ -126,7 +136,7 @@ function getInteger(node, name) {
|
|
|
126
136
|
const raw = n.getText();
|
|
127
137
|
const parsed = Number.parseInt(raw, 10);
|
|
128
138
|
if (Number.isNaN(parsed)) {
|
|
129
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "
|
|
139
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "CARDINALITY_NOT_VALID", `Integer not valid: ${raw}`);
|
|
130
140
|
}
|
|
131
141
|
return parsed;
|
|
132
142
|
}
|
|
@@ -28,7 +28,8 @@ export declare class SchemaProviderMemory implements SchemaProvider {
|
|
|
28
28
|
* own namespace.
|
|
29
29
|
*
|
|
30
30
|
* @param txt text of the `@stxt.schema` document.
|
|
31
|
-
* @throws ParseException or ValidationException if the document is not a valid schema
|
|
31
|
+
* @throws ParseException or ValidationException if the document is not a valid schema; in
|
|
32
|
+
* particular `SCHEMA_MULTIPLE_ROOTS` if it does not hold exactly one root node.
|
|
32
33
|
*/
|
|
33
34
|
addSchema(txt: string): void;
|
|
34
35
|
/** Removes every schema registered in this provider (the parent one is left untouched). */
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SchemaProviderMemory = void 0;
|
|
4
4
|
const Parser_1 = require("../core/Parser");
|
|
5
5
|
const StringUtils_1 = require("../core/StringUtils");
|
|
6
|
+
const ValidationException_1 = require("../exceptions/ValidationException");
|
|
6
7
|
const SchemaParser_1 = require("./SchemaParser");
|
|
7
8
|
const SchemaProviderMeta_1 = require("./SchemaProviderMeta");
|
|
8
9
|
const SchemaValidator_1 = require("./SchemaValidator");
|
|
@@ -47,11 +48,16 @@ class SchemaProviderMemory {
|
|
|
47
48
|
* own namespace.
|
|
48
49
|
*
|
|
49
50
|
* @param txt text of the `@stxt.schema` document.
|
|
50
|
-
* @throws ParseException or ValidationException if the document is not a valid schema
|
|
51
|
+
* @throws ParseException or ValidationException if the document is not a valid schema; in
|
|
52
|
+
* particular `SCHEMA_MULTIPLE_ROOTS` if it does not hold exactly one root node.
|
|
51
53
|
*/
|
|
52
54
|
addSchema(txt) {
|
|
53
55
|
const parser = new Parser_1.Parser();
|
|
54
|
-
const
|
|
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];
|
|
55
61
|
// A schema that does not validate against its meta-schema must not be
|
|
56
62
|
// registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
|
|
57
63
|
const schemaValidator = new SchemaValidator_1.SchemaValidator(new SchemaProviderMeta_1.SchemaProviderMeta(), true);
|
|
@@ -65,7 +65,7 @@ class SchemaValidator {
|
|
|
65
65
|
const schemaNode = schema.getNodeDefinition(node.getCanonicalName());
|
|
66
66
|
if (!schemaNode) {
|
|
67
67
|
const error = `NOT EXIST NODE ${node.getCanonicalName()} for namespace ${schema.getNamespace()}`;
|
|
68
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
68
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "NODE_NOT_DEFINED_IN_SCHEMA", error));
|
|
69
69
|
return errors;
|
|
70
70
|
}
|
|
71
71
|
errors.push(...SchemaValidator.validateValue(schemaNode, node));
|
|
@@ -93,7 +93,7 @@ class SchemaValidator {
|
|
|
93
93
|
const nodeType = nodeDef.getType();
|
|
94
94
|
const validator = TypeRegistry_1.TypeRegistry.get(nodeType);
|
|
95
95
|
if (!validator) {
|
|
96
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
96
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_VALID", `Node type not supported: ${nodeType}`));
|
|
97
97
|
return errors;
|
|
98
98
|
}
|
|
99
99
|
try {
|
|
@@ -104,10 +104,10 @@ class SchemaValidator {
|
|
|
104
104
|
errors.push(e);
|
|
105
105
|
}
|
|
106
106
|
else if (e instanceof Error) {
|
|
107
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
107
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "UNEXPECTED_ERROR", e.message));
|
|
108
108
|
}
|
|
109
109
|
else {
|
|
110
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
110
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "UNEXPECTED_ERROR", String(e)));
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
return errors;
|
|
@@ -135,14 +135,14 @@ class SchemaValidator {
|
|
|
135
135
|
const min = childDef.getMin(); // number | null
|
|
136
136
|
const max = childDef.getMax(); // number | null
|
|
137
137
|
if (min !== null && childCount < min) {
|
|
138
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
138
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "TOO_FEW_CHILDREN", `${childCount} nodes of '${childDef.getQualifiedName()}' and min is ${min}`));
|
|
139
139
|
}
|
|
140
140
|
if (max !== null && childCount > max) {
|
|
141
141
|
// Error on the parent
|
|
142
|
-
errors.push(new ValidationException_1.ValidationException(node.getLine(), "
|
|
142
|
+
errors.push(new ValidationException_1.ValidationException(node.getLine(), "TOO_MANY_CHILDREN", `${childCount} nodes of '${childDef.getQualifiedName()}' and max is ${max}`));
|
|
143
143
|
// Error on each child node beyond the allowed maximum
|
|
144
144
|
for (const child of children) {
|
|
145
|
-
errors.push(new ValidationException_1.ValidationException(child.getLine(), "
|
|
145
|
+
errors.push(new ValidationException_1.ValidationException(child.getLine(), "TOO_MANY_CHILDREN", `Too many '${childDef.getQualifiedName()}' nodes: found ${childCount}, max is ${max}`));
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
return errors;
|
|
@@ -47,7 +47,7 @@ class TypeRegistry {
|
|
|
47
47
|
static register(instance) {
|
|
48
48
|
const name = instance.getName();
|
|
49
49
|
if (this.REGISTRY.has(name)) {
|
|
50
|
-
throw new RuntimeException_1.RuntimeException("
|
|
50
|
+
throw new RuntimeException_1.RuntimeException("TYPE_DUPLICATED", `Type already defined: ${name}`);
|
|
51
51
|
}
|
|
52
52
|
this.REGISTRY.set(name, instance);
|
|
53
53
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** `DATE` type:
|
|
1
|
+
/** `DATE` type: `YYYY-MM-DD`, an existing date of the proleptic Gregorian calendar (STXT-SCHEMA-SPEC 9.4). */
|
|
2
2
|
export declare const DATE: import("../Type").Type;
|
package/out/schema/type/DATE.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DATE = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const rangeType_1 = require("./rangeType");
|
|
5
|
+
const dateTime_1 = require("./dateTime");
|
|
6
|
+
/** `DATE` type: `YYYY-MM-DD`, an existing date of the proleptic Gregorian calendar (STXT-SCHEMA-SPEC 9.4). */
|
|
7
|
+
exports.DATE = (0, rangeType_1.rangeType)("DATE", /^(\d{4})-(\d{2})-(\d{2})$/, m => (0, dateTime_1.isValidDate)(Number(m[1]), Number(m[2]), Number(m[3])), "Invalid date");
|
|
7
8
|
//# sourceMappingURL=DATE.js.map
|
package/out/schema/type/ENUM.js
CHANGED
|
@@ -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(), "
|
|
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>
|
package/out/schema/type/GROUP.js
CHANGED
|
@@ -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(), "
|
|
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(), "
|
|
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(), "
|
|
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
|
};
|
package/out/schema/type/TEXT.js
CHANGED
|
@@ -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(), "
|
|
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:
|
|
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;
|
package/out/schema/type/TIME.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TIME = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
/**
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
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
|
package/out/schema/type/URL.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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;
|
package/out/schema/type/URL.js
CHANGED
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.URL = void 0;
|
|
4
|
-
const
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
@@ -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(), "
|
|
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 `
|
|
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 `
|
|
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, "
|
|
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, "
|
|
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);
|
|
@@ -97,7 +97,7 @@ class ChildLineParser {
|
|
|
97
97
|
// num, min and max must be non-negative integers, with no trailing text (STXT-TEMPLATE-SPEC 7.1)
|
|
98
98
|
static parseCount(num, count, rawLine, lineNumber) {
|
|
99
99
|
if (!/^\d+$/.test(num)) {
|
|
100
|
-
throw new ValidationException_1.ValidationException(lineNumber, "
|
|
100
|
+
throw new ValidationException_1.ValidationException(lineNumber, "CARDINALITY_NOT_VALID", `Invalid count ${count} in line: ${rawLine}`);
|
|
101
101
|
}
|
|
102
102
|
return parseInt(num, 10);
|
|
103
103
|
}
|
|
@@ -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
|
|
9
|
-
*
|
|
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
|
|
20
|
-
*
|
|
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(
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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 `
|
|
21
|
-
* one
|
|
22
|
-
*
|
|
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 `
|
|
34
|
-
* one
|
|
35
|
-
*
|
|
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, "
|
|
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
|
}
|