@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.
- package/README.md +3 -3
- package/out/core/InlineNode.js +1 -0
- package/out/core/LineParser.js +1 -1
- package/out/core/NameNamespace.d.ts +5 -5
- package/out/core/NameNamespace.js +5 -5
- package/out/core/NamespaceValidator.d.ts +7 -7
- package/out/core/NamespaceValidator.js +7 -7
- package/out/core/Parser.d.ts +5 -0
- package/out/core/Parser.js +22 -17
- package/out/core/StringUtils.d.ts +0 -7
- package/out/core/StringUtils.js +0 -10
- package/out/core/TextNode.d.ts +7 -0
- package/out/core/TextNode.js +11 -0
- package/out/discovery/DiscoveryResolver.d.ts +1 -1
- package/out/discovery/DiscoveryResolver.js +36 -17
- package/out/discovery/DiscoveryResult.js +6 -4
- package/out/exceptions/ParseException.d.ts +8 -1
- package/out/exceptions/ParseException.js +7 -0
- package/out/runtime/Formatter.d.ts +4 -4
- package/out/runtime/Formatter.js +15 -8
- package/out/runtime/NodeWriter.js +10 -2
- package/out/runtime/UnifiedSchemaProvider.d.ts +1 -3
- package/out/runtime/UnifiedSchemaProvider.js +14 -32
- package/out/schema/DefinitionCompiler.d.ts +27 -0
- package/out/schema/DefinitionCompiler.js +53 -0
- package/out/schema/NodeDefinition.js +6 -3
- package/out/schema/Schema.d.ts +2 -0
- package/out/schema/Schema.js +7 -4
- package/out/schema/SchemaParser.js +13 -12
- package/out/schema/SchemaProviderMemory.js +3 -19
- package/out/schema/SchemaProviderMeta.d.ts +7 -1
- package/out/schema/SchemaProviderMeta.js +10 -9
- package/out/schema/SchemaValidator.js +5 -3
- package/out/schema/type/BASE64.d.ts +4 -2
- package/out/schema/type/BASE64.js +34 -10
- package/out/schema/type/MARKDOWN.d.ts +2 -1
- package/out/schema/type/MARKDOWN.js +4 -8
- package/out/template/ChildLineParser.js +12 -9
- package/out/template/MetaTemplateSchemaProvider.d.ts +3 -1
- package/out/template/MetaTemplateSchemaProvider.js +12 -11
- package/out/template/TemplateParser.d.ts +1 -2
- package/out/template/TemplateParser.js +116 -89
- package/out/template/TemplateSchemaProviderMemory.js +3 -19
- package/package.json +2 -3
|
@@ -13,9 +13,8 @@ const ChildLineParser_1 = require("./ChildLineParser");
|
|
|
13
13
|
const ParseException_1 = require("../exceptions/ParseException");
|
|
14
14
|
const TypeRegistry_1 = require("../schema/TypeRegistry");
|
|
15
15
|
const NamespaceValidator_1 = require("../core/NamespaceValidator");
|
|
16
|
-
/** Namespace of the template language itself
|
|
17
|
-
|
|
18
|
-
exports.TEMPLATE_NAMESPACE = "@stxt.template";
|
|
16
|
+
/** Namespace of the template language itself; the canonical constant is {@link Schema.TEMPLATE_NAMESPACE}. */
|
|
17
|
+
exports.TEMPLATE_NAMESPACE = Schema_1.Schema.TEMPLATE_NAMESPACE;
|
|
19
18
|
/**
|
|
20
19
|
* Turns the tree of an already parsed `@stxt.template` document into an equivalent {@link Schema}.
|
|
21
20
|
*
|
|
@@ -32,7 +31,7 @@ function transformTemplateNodeToSchema(node) {
|
|
|
32
31
|
}
|
|
33
32
|
// The target namespace: required, and with a valid format
|
|
34
33
|
const targetNamespace = StringUtils_1.StringUtils.lowerCase(node.getText());
|
|
35
|
-
if (!targetNamespace ||
|
|
34
|
+
if (!targetNamespace || StringUtils_1.StringUtils.trim(targetNamespace).length === 0) {
|
|
36
35
|
throw new ValidationException_1.ValidationException(node.getLine(), "TEMPLATE_NAMESPACE_EMPTY", "Template namespace is empty");
|
|
37
36
|
}
|
|
38
37
|
if (!NamespaceValidator_1.NamespaceValidator.isValid(targetNamespace)) {
|
|
@@ -89,116 +88,144 @@ function transformTemplateNodeToSchema(node) {
|
|
|
89
88
|
// Return the result
|
|
90
89
|
return result;
|
|
91
90
|
}
|
|
92
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Adds to the schema the definition a node of the structure declares, along with its children.
|
|
93
|
+
*
|
|
94
|
+
* Only the orchestration lives here; each of the three shapes a Structure line can take has its
|
|
95
|
+
* own helper below: a node of an external namespace ({@link validateExternalNode}, nothing is
|
|
96
|
+
* created), a name seen for the first time ({@link createDefinition}) or a reappearance
|
|
97
|
+
* ({@link validateReference}, nothing is created). The children of a definition are declared
|
|
98
|
+
* and recursed by {@link addChildren}.
|
|
99
|
+
*/
|
|
93
100
|
function addToSchema(schema, node) {
|
|
94
101
|
// A Structure line must use the template grammar's ':' form. The core parser
|
|
95
102
|
// also accepts BLOCK nodes here, so reject them explicitly (STXT-TEMPLATE-SPEC 6.3).
|
|
96
103
|
if (!(node instanceof InlineNode_1.InlineNode)) {
|
|
97
104
|
throw new ValidationException_1.ValidationException(node.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
|
|
98
105
|
}
|
|
99
|
-
//
|
|
106
|
+
// Parse the RuleSpec (cardinality / type / values) from the inline value
|
|
107
|
+
const cl = ChildLineParser_1.ChildLineParser.parse(node.getValue(), node.getLine());
|
|
108
|
+
// No explicit namespace => the target namespace of the template
|
|
100
109
|
let namespace = node.getNamespace();
|
|
101
|
-
const name = node.getName();
|
|
102
|
-
// Look at the data
|
|
103
|
-
let cl = ChildLineParser_1.ChildLineParser.parse(node.getValue(), node.getLine());
|
|
104
110
|
if (!namespace || namespace === "") {
|
|
105
111
|
namespace = schema.getNamespace();
|
|
106
112
|
}
|
|
107
113
|
if (namespace !== schema.getNamespace()) {
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
const type = cl.getType();
|
|
111
|
-
if (type && type.trim().length > 0) {
|
|
112
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed type definition in external namespaces");
|
|
113
|
-
}
|
|
114
|
-
const values = cl.getValues();
|
|
115
|
-
if (values) {
|
|
116
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", `Not allowed values in external namespaces (node ${node.getName()})`);
|
|
117
|
-
}
|
|
118
|
-
if (node.getChildren().length > 0) {
|
|
119
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", `Not allowed children in external namespaces (node ${node.getName()})`);
|
|
120
|
-
}
|
|
121
|
-
// Nodes that do not belong to @stxt.template create nothing here!!
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
// Check whether it is new and add it to the list
|
|
125
|
-
let schemaNode = schema.getNodeDefinition(name);
|
|
126
|
-
if (!schemaNode) {
|
|
127
|
-
// New one
|
|
128
|
-
const type = cl.getType() ?? "INLINE";
|
|
129
|
-
// At this point the schema already holds both the previous definitions, already closed,
|
|
130
|
-
// and the open ancestors, so a reference that does not resolve here resolves to
|
|
131
|
-
// nothing (STXT-TEMPLATE-SPEC 6.4 and 14.11)
|
|
132
|
-
if (type.startsWith("@")) {
|
|
133
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_NOT_FOUND", `Reference '${type}' does not point to a previous definition or an open ancestor`);
|
|
134
|
-
}
|
|
135
|
-
schemaNode = new NodeDefinition_1.NodeDefinition(node.getName(), type, node.getLine(), undefined);
|
|
136
|
-
schema.addNodeDefinition(schemaNode);
|
|
137
|
-
if (!TypeRegistry_1.TypeRegistry.get(type)) {
|
|
138
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_VALID", `Type not valid: ${type}`);
|
|
139
|
-
}
|
|
140
|
-
const values = cl.getValues();
|
|
141
|
-
if (values) {
|
|
142
|
-
if (type !== "ENUM") {
|
|
143
|
-
// Same code as SchemaParser: a template is sugar equivalent to a schema
|
|
144
|
-
// (STXT-TEMPLATE-SPEC 13), so the same condition must not change its code
|
|
145
|
-
// depending on the entry point
|
|
146
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
|
|
147
|
-
}
|
|
148
|
-
for (const v of values) {
|
|
149
|
-
schemaNode.addValue(v, node.getLine());
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// An ENUM with no list of values is an invalid template (STXT-TEMPLATE-SPEC 9 and 13.7)
|
|
153
|
-
if (type === "ENUM" && (!values || values.length === 0)) {
|
|
154
|
-
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
|
|
155
|
-
}
|
|
114
|
+
validateExternalNode(node, cl);
|
|
115
|
+
return; // no definitions are created for nodes of other namespaces
|
|
156
116
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
117
|
+
// New definition or a reappearance (reference)?
|
|
118
|
+
let schemaNode = schema.getNodeDefinition(node.getName());
|
|
119
|
+
if (schemaNode) {
|
|
120
|
+
validateReference(node, cl);
|
|
121
|
+
return; // valid reference: nothing is redefined, no children are processed
|
|
122
|
+
}
|
|
123
|
+
schemaNode = createDefinition(schema, node, cl);
|
|
124
|
+
addChildren(schema, schemaNode, node);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Cross-namespace node (STXT-TEMPLATE-SPEC 6.4, 10 and 14.15): not defined locally; it may only
|
|
128
|
+
* declare cardinality — no type, no ENUM values and no children.
|
|
129
|
+
*/
|
|
130
|
+
function validateExternalNode(node, cl) {
|
|
131
|
+
const type = cl.getType();
|
|
132
|
+
if (type && StringUtils_1.StringUtils.trim(type).length > 0) {
|
|
133
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", "Not allowed type definition in external namespaces");
|
|
134
|
+
}
|
|
135
|
+
if (cl.getValues()) {
|
|
136
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", `Not allowed values in external namespaces (node ${node.getName()})`);
|
|
137
|
+
}
|
|
138
|
+
if (node.getChildren().length > 0) {
|
|
139
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED_IN_EXTERNAL_NAMESPACE", `Not allowed children in external namespaces (node ${node.getName()})`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* First appearance of a name: creates its {@link NodeDefinition} (with its type and its ENUM
|
|
144
|
+
* values, when it has them) and registers it in the schema.
|
|
145
|
+
*/
|
|
146
|
+
function createDefinition(schema, node, cl) {
|
|
147
|
+
const type = cl.getType() ?? "INLINE";
|
|
148
|
+
// At this point the schema already holds both the previous definitions, already closed,
|
|
149
|
+
// and the open ancestors, so a reference that does not resolve here resolves to
|
|
150
|
+
// nothing (STXT-TEMPLATE-SPEC 6.4 and 14.11)
|
|
151
|
+
if (type.startsWith("@")) {
|
|
152
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_NOT_FOUND", `Reference '${type}' does not point to a previous definition or an open ancestor`);
|
|
153
|
+
}
|
|
154
|
+
const schemaNode = new NodeDefinition_1.NodeDefinition(node.getName(), type, node.getLine(), undefined);
|
|
155
|
+
schema.addNodeDefinition(schemaNode);
|
|
156
|
+
if (!TypeRegistry_1.TypeRegistry.get(type)) {
|
|
157
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "TYPE_NOT_VALID", `Type not valid: ${type}`);
|
|
158
|
+
}
|
|
159
|
+
const values = cl.getValues();
|
|
160
|
+
if (values) {
|
|
161
|
+
if (type !== "ENUM") {
|
|
162
|
+
// Same code as SchemaParser: a template is sugar equivalent to a schema
|
|
163
|
+
// (STXT-TEMPLATE-SPEC 13), so the same condition must not change its code
|
|
164
|
+
// depending on the entry point
|
|
165
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_FOR_TYPE", `Values only supported for type ENUM, not for type ${type}`);
|
|
176
166
|
}
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
for (const v of values) {
|
|
168
|
+
schemaNode.addValue(v, node.getLine());
|
|
179
169
|
}
|
|
180
|
-
return; // OK Definition
|
|
181
170
|
}
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
171
|
+
// An ENUM with no list of values is an invalid template (STXT-TEMPLATE-SPEC 9 and 13.7)
|
|
172
|
+
if (type === "ENUM" && (!values || values.length === 0)) {
|
|
173
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_REQUIRED", "ENUM Type must include values");
|
|
174
|
+
}
|
|
175
|
+
return schemaNode;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Reappearance of an already defined name: it must be a `@Node Name` reference, and a reference
|
|
179
|
+
* may override the cardinality but may redefine neither the ENUM values nor the children
|
|
180
|
+
* (STXT-TEMPLATE-SPEC 6.4, 14.12 and 14.13).
|
|
181
|
+
*/
|
|
182
|
+
function validateReference(node, cl) {
|
|
183
|
+
const type = cl.getType();
|
|
184
|
+
// A reappearance without "@" would redefine an existing node: error.
|
|
185
|
+
if (!type || !type.startsWith("@")) {
|
|
186
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_REQUIRED", `Multiple node reference must start with @: ${node.getName()}`);
|
|
187
|
+
}
|
|
188
|
+
const reference = StringUtils_1.StringUtils.trim(type.substring(1));
|
|
189
|
+
// Reference and explicit type on the same line (STXT-TEMPLATE-SPEC 14.13)
|
|
190
|
+
const explicitType = referenceType(reference, node.getCanonicalName());
|
|
191
|
+
if (explicitType) {
|
|
192
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_WITH_TYPE_NOT_ALLOWED", `Reference '@${node.getName()}' can not declare a type: ${explicitType}`);
|
|
193
|
+
}
|
|
194
|
+
// The name of the reference must match (canonically) the one of the line (14.12)
|
|
195
|
+
if (StringUtils_1.StringUtils.normalize(reference) !== node.getCanonicalName()) {
|
|
196
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "REFERENCE_NAME_NOT_VALID", `Reference must be '@${node.getName()}', not '${reference}'`);
|
|
197
|
+
}
|
|
198
|
+
if (cl.getValues()) {
|
|
199
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "VALUES_NOT_ALLOWED_IN_REFERENCE", `Reference '@${node.getName()}' can not redefine ENUM values`);
|
|
200
|
+
}
|
|
201
|
+
if (node.getChildren().length > 0) {
|
|
202
|
+
throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED_IN_REFERENCE", `Reference '@${node.getName()}' can not redefine children`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Declares every direct child of a definition as a {@link ChildDefinition} (with its cardinality)
|
|
207
|
+
* and recurses into each one as a definition/reference of its own.
|
|
208
|
+
*/
|
|
209
|
+
function addChildren(schema, schemaNode, node) {
|
|
210
|
+
const children = node.getChildren();
|
|
211
|
+
// Template error 14.9: children under an effective type that does not admit them
|
|
212
|
+
if (children.length > 0 && !TypeRegistry_1.TypeRegistry.admitsChildren(schemaNode.getType())) {
|
|
186
213
|
throw new ValidationException_1.ValidationException(node.getLine(), "CHILDREN_NOT_ALLOWED_FOR_TYPE", `Type ${schemaNode.getType()} does not allow children (node ${node.getName()})`);
|
|
187
214
|
}
|
|
188
|
-
|
|
189
|
-
for (const child of childrenNode) {
|
|
215
|
+
for (const child of children) {
|
|
190
216
|
// STXT-TEMPLATE-SPEC 6.3: every Structure line uses ':', so a child is inline too
|
|
191
217
|
if (!(child instanceof InlineNode_1.InlineNode)) {
|
|
192
218
|
throw new ValidationException_1.ValidationException(child.getLine(), "STRUCTURE_LINE_NOT_VALID", "Template Structure lines must use ':'");
|
|
193
219
|
}
|
|
194
|
-
|
|
195
|
-
const childName = child.getName();
|
|
220
|
+
const childCl = ChildLineParser_1.ChildLineParser.parse(child.getValue(), child.getLine());
|
|
196
221
|
let childNamespace = child.getNamespace();
|
|
197
222
|
if (!childNamespace || childNamespace === "") {
|
|
198
223
|
childNamespace = schema.getNamespace();
|
|
199
224
|
}
|
|
200
|
-
|
|
225
|
+
// The child is declared as a Child (with its cardinality) in the current definition
|
|
226
|
+
const schChild = new ChildDefinition_1.ChildDefinition(child.getName(), childNamespace, childCl.getMin(), childCl.getMax(), child.getLine());
|
|
201
227
|
schemaNode.addChildDefinition(schChild);
|
|
228
|
+
// And processed recursively as a definition/reference
|
|
202
229
|
addToSchema(schema, child);
|
|
203
230
|
}
|
|
204
231
|
}
|
|
@@ -214,7 +241,7 @@ function referenceType(reference, normalizedName) {
|
|
|
214
241
|
if (cut < 0) {
|
|
215
242
|
return null;
|
|
216
243
|
}
|
|
217
|
-
const candidate = reference.substring(cut + 1)
|
|
244
|
+
const candidate = StringUtils_1.StringUtils.trim(reference.substring(cut + 1));
|
|
218
245
|
const rest = reference.substring(0, cut);
|
|
219
246
|
if (TypeRegistry_1.TypeRegistry.get(candidate) && StringUtils_1.StringUtils.normalize(rest) === normalizedName) {
|
|
220
247
|
return candidate;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// TemplateSchemaProvider.ts
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.TemplateSchemaProviderMemory = void 0;
|
|
5
|
-
const
|
|
6
|
-
const SchemaValidator_1 = require("../schema/SchemaValidator");
|
|
7
|
-
const ValidationException_1 = require("../exceptions/ValidationException");
|
|
4
|
+
const DefinitionCompiler_1 = require("../schema/DefinitionCompiler");
|
|
8
5
|
const MetaTemplateSchemaProvider_1 = require("./MetaTemplateSchemaProvider");
|
|
9
6
|
const SchemaProviderMemory_1 = require("../schema/SchemaProviderMemory");
|
|
10
7
|
const TemplateParser_1 = require("./TemplateParser");
|
|
@@ -36,21 +33,8 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
|
|
|
36
33
|
* template does not validate against the template meta-schema.
|
|
37
34
|
*/
|
|
38
35
|
addTemplate(template) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
if (nodes.length !== 1) {
|
|
42
|
-
throw new ValidationException_1.ValidationException(0, "TEMPLATE_MULTIPLE_ROOTS", `A template document must hold exactly 1 root node, got ${nodes.length}`);
|
|
43
|
-
}
|
|
44
|
-
// A template that does not validate against the template meta-schema must not
|
|
45
|
-
// be registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
|
|
46
|
-
const schemaValidator = new SchemaValidator_1.SchemaValidator(new MetaTemplateSchemaProvider_1.MetaTemplateSchemaProvider(), true);
|
|
47
|
-
const errors = schemaValidator.validate(nodes[0]);
|
|
48
|
-
if (errors.length > 0) {
|
|
49
|
-
throw errors[0];
|
|
50
|
-
}
|
|
51
|
-
// Build the schema out of the template
|
|
52
|
-
const sch = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
|
|
53
|
-
this.schemas.set(sch.getNamespace(), sch);
|
|
36
|
+
const schema = (0, DefinitionCompiler_1.compileDefinitionDocument)(template, new MetaTemplateSchemaProvider_1.MetaTemplateSchemaProvider(), TemplateParser_1.transformTemplateNodeToSchema, "TEMPLATE_MULTIPLE_ROOTS", "template");
|
|
37
|
+
this.schemas.set(schema.getNamespace(), schema);
|
|
54
38
|
}
|
|
55
39
|
}
|
|
56
40
|
exports.TemplateSchemaProviderMemory = TemplateSchemaProviderMemory;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stxt-lang/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Parser and schema validator for STXT, an indentation-based structured-text
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"description": "Parser and schema validator for STXT, an indentation-based structured-text language.",
|
|
5
5
|
"main": "out/all.js",
|
|
6
6
|
"types": "out/all.d.ts",
|
|
7
7
|
"publishConfig": {
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"stxt",
|
|
37
|
-
"semantic text",
|
|
38
37
|
"parser",
|
|
39
38
|
"schema",
|
|
40
39
|
"validation",
|