@accordproject/template-engine 2.9.0 → 3.1.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/dist/index.d.ts +20 -8
- package/dist/index.js +140 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +140 -85
- package/dist/index.mjs.map +1 -1
- package/dist/worker.js +6 -4
- package/dist/worker.js.map +1 -1
- package/package.json +11 -10
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ModelManager, ClassDeclaration } from '@accordproject/concerto-core';
|
|
2
2
|
import { Template } from '@accordproject/cicero-core';
|
|
3
|
+
import { TwoSlashReturn } from '@typescript/twoslash';
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -133,6 +134,7 @@ type InitResponse = {
|
|
|
133
134
|
*/
|
|
134
135
|
declare class TemplateArchiveProcessor {
|
|
135
136
|
template: Template;
|
|
137
|
+
private compiledLogicCache?;
|
|
136
138
|
/**
|
|
137
139
|
* Creates a template archive processor
|
|
138
140
|
* @param {Template} template - the template to be used by the processor
|
|
@@ -148,21 +150,31 @@ declare class TemplateArchiveProcessor {
|
|
|
148
150
|
*/
|
|
149
151
|
draft(data: any, format: string, options: any, currentTime?: string): Promise<any>;
|
|
150
152
|
/**
|
|
151
|
-
*
|
|
153
|
+
* Compile the logic of a template
|
|
154
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to cache the compiled logic for future use
|
|
155
|
+
* @returns {Promise<Record<string, TwoSlashReturn>>} the compiled code for each typescript file
|
|
156
|
+
*/
|
|
157
|
+
compileLogic(enableCompiledLogicCache?: boolean): Promise<Record<string, TwoSlashReturn>>;
|
|
158
|
+
/**
|
|
159
|
+
* Trigger the logic of a template.
|
|
160
|
+
* @param {object} data - the data for the template
|
|
152
161
|
* @param {object} request - the request to send to the template logic
|
|
153
162
|
* @param {object} state - the current state of the template
|
|
154
163
|
* @param {[string]} currentTime - the current time, defaults to now
|
|
155
|
-
* @param {[number]} utcOffset - the UTC
|
|
156
|
-
* @
|
|
164
|
+
* @param {[number]} utcOffset - the UTC offset, defaults to zero
|
|
165
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
|
|
166
|
+
* @returns {Promise<TriggerResponse>} the response and any events
|
|
157
167
|
*/
|
|
158
|
-
trigger(data: any, request: any, state?: any, currentTime?: string, utcOffset?: number): Promise<TriggerResponse>;
|
|
168
|
+
trigger(data: any, request: any, state?: any, currentTime?: string, utcOffset?: number, enableCompiledLogicCache?: boolean): Promise<TriggerResponse>;
|
|
159
169
|
/**
|
|
160
|
-
* Init the logic of a template
|
|
170
|
+
* Init the logic of a template.
|
|
171
|
+
* @param {object} data - the data for the template
|
|
161
172
|
* @param {[string]} currentTime - the current time, defaults to now
|
|
162
|
-
* @param {[number]} utcOffset - the UTC
|
|
163
|
-
* @
|
|
173
|
+
* @param {[number]} utcOffset - the UTC offset, defaults to zero
|
|
174
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
|
|
175
|
+
* @returns {Promise<InitResponse>} the new state
|
|
164
176
|
*/
|
|
165
|
-
init(data: any, currentTime?: string, utcOffset?: number): Promise<InitResponse>;
|
|
177
|
+
init(data: any, currentTime?: string, utcOffset?: number, enableCompiledLogicCache?: boolean): Promise<InitResponse>;
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
declare function ensureDirSync(path: string): void;
|
package/dist/index.js
CHANGED
|
@@ -529,17 +529,31 @@ class JavaScriptEvaluator {
|
|
|
529
529
|
const end = (/* @__PURE__ */ new Date()).getTime();
|
|
530
530
|
resolve({ result, elapsed: end - start });
|
|
531
531
|
}).catch((err) => {
|
|
532
|
-
console.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
console.error(err);
|
|
533
|
+
if (err instanceof Error) {
|
|
534
|
+
reject({
|
|
535
|
+
message: err.message,
|
|
536
|
+
stack: err.stack
|
|
537
|
+
});
|
|
538
|
+
} else {
|
|
539
|
+
reject({
|
|
540
|
+
message: String(err)
|
|
541
|
+
});
|
|
542
|
+
}
|
|
536
543
|
});
|
|
537
544
|
}
|
|
538
545
|
} catch (err) {
|
|
539
|
-
console.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
546
|
+
console.error(err);
|
|
547
|
+
if (err instanceof Error) {
|
|
548
|
+
reject({
|
|
549
|
+
message: err.message,
|
|
550
|
+
stack: err.stack
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
reject({
|
|
554
|
+
message: String(err)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
543
557
|
}
|
|
544
558
|
});
|
|
545
559
|
}
|
|
@@ -561,6 +575,7 @@ class JavaScriptEvaluator {
|
|
|
561
575
|
};
|
|
562
576
|
if (this.queue.length >= this.options.maxQueueDepth) {
|
|
563
577
|
reject({ maxQueueDepthExceeded: true, elapsed: 0 });
|
|
578
|
+
return;
|
|
564
579
|
}
|
|
565
580
|
this.queue.push(workItem);
|
|
566
581
|
this.processQueue(options);
|
|
@@ -601,7 +616,7 @@ class JavaScriptEvaluator {
|
|
|
601
616
|
}
|
|
602
617
|
const workerPath = this.getWorkerPath();
|
|
603
618
|
const worker = child_process.fork(workerPath, { timeout: options.timeout, env: {} });
|
|
604
|
-
if (
|
|
619
|
+
if (worker.pid === void 0 || worker.pid === null) {
|
|
605
620
|
throw new Error("Failed to fork child process");
|
|
606
621
|
}
|
|
607
622
|
this.workers.push(worker);
|
|
@@ -610,7 +625,18 @@ class JavaScriptEvaluator {
|
|
|
610
625
|
worker.on("error", (err) => {
|
|
611
626
|
this.workers = this.workers.filter((w) => w.pid !== worker.pid);
|
|
612
627
|
const end = (/* @__PURE__ */ new Date()).getTime();
|
|
613
|
-
|
|
628
|
+
if (err instanceof Error) {
|
|
629
|
+
reject({
|
|
630
|
+
message: err.message,
|
|
631
|
+
stack: err.stack,
|
|
632
|
+
elapsed: end - start
|
|
633
|
+
});
|
|
634
|
+
} else {
|
|
635
|
+
reject({
|
|
636
|
+
message: String(err),
|
|
637
|
+
elapsed: end - start
|
|
638
|
+
});
|
|
639
|
+
}
|
|
614
640
|
});
|
|
615
641
|
worker.on("message", (msg) => {
|
|
616
642
|
result = msg;
|
|
@@ -714,7 +740,7 @@ function getJsonPath(rootData, currentNode, paths) {
|
|
|
714
740
|
}
|
|
715
741
|
}
|
|
716
742
|
}
|
|
717
|
-
if (currentNode.name !== "this") {
|
|
743
|
+
if (currentNode.name !== "this" && currentNode.name !== "top") {
|
|
718
744
|
withPath.push(`['${currentNode.name}']`);
|
|
719
745
|
}
|
|
720
746
|
return withPath.length > 0 ? `$${withPath.join("")}` : "$";
|
|
@@ -793,13 +819,17 @@ async function generateRecursiveBlocks(modelManager, clauseLibrary, templateMark
|
|
|
793
819
|
if (!Array.isArray(arrayData)) {
|
|
794
820
|
throw new Error(`Values found for path '${path}' in data ${data} is not an array: ${arrayData}.`);
|
|
795
821
|
} else {
|
|
822
|
+
const firstChild = context.nodes[0];
|
|
823
|
+
const hasListWrapper = firstChild && firstChild.$class === `${markdownCommon.CommonMarkModel.NAMESPACE}.List` && Array.isArray(firstChild.nodes) && firstChild.nodes.length > 0;
|
|
824
|
+
const itemTemplate = hasListWrapper ? firstChild.nodes[0] : firstChild;
|
|
796
825
|
const nodes = [];
|
|
797
826
|
for (let n2 = 0; n2 < arrayData.length; n2++) {
|
|
798
827
|
const arrayItem = arrayData[n2];
|
|
799
|
-
const subResult = await generateAgreement(modelManager, clauseLibrary,
|
|
828
|
+
const subResult = await generateAgreement(modelManager, clauseLibrary, itemTemplate, arrayItem, options);
|
|
829
|
+
const childNodes = hasListWrapper ? subResult.nodes ? subResult.nodes : [] : [subResult];
|
|
800
830
|
nodes.push({
|
|
801
831
|
$class: childNodeClass,
|
|
802
|
-
nodes:
|
|
832
|
+
nodes: childNodes
|
|
803
833
|
});
|
|
804
834
|
}
|
|
805
835
|
result[thisPath.join("/")] = nodes;
|
|
@@ -904,8 +934,17 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
|
|
|
904
934
|
}
|
|
905
935
|
} else if (CONDITIONAL_DEFINITION_RE.test(nodeClass)) {
|
|
906
936
|
if (context.condition) {
|
|
907
|
-
const
|
|
908
|
-
|
|
937
|
+
const key = this.path.join("/");
|
|
938
|
+
const resultStr = userCodeResults[key];
|
|
939
|
+
if (resultStr === void 0 || resultStr === null) {
|
|
940
|
+
context.isTrue = false;
|
|
941
|
+
} else {
|
|
942
|
+
try {
|
|
943
|
+
context.isTrue = !!JSON.parse(resultStr);
|
|
944
|
+
} catch (err) {
|
|
945
|
+
throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
909
948
|
} else {
|
|
910
949
|
const path = getJsonPath(templateMark, context, this.path);
|
|
911
950
|
const variableValues = jp.query(data, path, 1);
|
|
@@ -926,16 +965,27 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
|
|
|
926
965
|
} else if (CLAUSE_DEFINITION_RE.test(nodeClass)) {
|
|
927
966
|
const path = getJsonPath(templateMark, context, this.path);
|
|
928
967
|
const variableValues = jp.query(data, path, 1);
|
|
929
|
-
if (context.
|
|
930
|
-
delete context.nodes;
|
|
931
|
-
stopHere = true;
|
|
932
|
-
} else if (context.condition) {
|
|
968
|
+
if (context.condition) {
|
|
933
969
|
checkCode(context.condition);
|
|
934
|
-
const
|
|
970
|
+
const key = this.path.join("/");
|
|
971
|
+
const resultStr = userCodeResults[key];
|
|
972
|
+
let result = false;
|
|
973
|
+
if (resultStr === void 0 || resultStr === null) {
|
|
974
|
+
result = false;
|
|
975
|
+
} else {
|
|
976
|
+
try {
|
|
977
|
+
result = !!JSON.parse(resultStr);
|
|
978
|
+
} catch (err) {
|
|
979
|
+
throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
935
982
|
if (!result) {
|
|
936
983
|
delete context.nodes;
|
|
937
984
|
stopHere = true;
|
|
938
985
|
}
|
|
986
|
+
} else if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
|
|
987
|
+
delete context.nodes;
|
|
988
|
+
stopHere = true;
|
|
939
989
|
}
|
|
940
990
|
delete context.condition;
|
|
941
991
|
delete context.functionName;
|
|
@@ -984,14 +1034,14 @@ class TemplateMarkInterpreter {
|
|
|
984
1034
|
* @throws {Error} if the templateMark document is invalid
|
|
985
1035
|
*/
|
|
986
1036
|
checkTypes(templateMark) {
|
|
987
|
-
const modelManager = new concertoCore.ModelManager(
|
|
1037
|
+
const modelManager = new concertoCore.ModelManager();
|
|
988
1038
|
modelManager.addCTOModel(markdownCommon.ConcertoMetaModel.MODEL, "concertometamodel.cto");
|
|
989
1039
|
modelManager.addCTOModel(markdownCommon.CommonMarkModel.MODEL, "commonmark.cto");
|
|
990
1040
|
modelManager.addCTOModel(markdownCommon.TemplateMarkModel.MODEL, "templatemark.cto");
|
|
991
1041
|
const factory = new concertoCore.Factory(modelManager);
|
|
992
|
-
const serializer = new concertoCore.Serializer(factory, modelManager);
|
|
1042
|
+
const serializer = new concertoCore.Serializer(factory, modelManager, {});
|
|
993
1043
|
try {
|
|
994
|
-
serializer.fromJSON(templateMark);
|
|
1044
|
+
serializer.fromJSON(templateMark, {});
|
|
995
1045
|
} catch (err) {
|
|
996
1046
|
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
|
|
997
1047
|
}
|
|
@@ -1063,22 +1113,22 @@ class TemplateMarkInterpreter {
|
|
|
1063
1113
|
return compiler.compile(templateMark);
|
|
1064
1114
|
}
|
|
1065
1115
|
validateCiceroMark(ciceroMark) {
|
|
1066
|
-
const modelManager = new concertoCore.ModelManager(
|
|
1116
|
+
const modelManager = new concertoCore.ModelManager();
|
|
1067
1117
|
modelManager.addCTOModel(markdownCommon.ConcertoMetaModel.MODEL, "concertometamodel.cto");
|
|
1068
1118
|
modelManager.addCTOModel(markdownCommon.CommonMarkModel.MODEL, "commonmark.cto");
|
|
1069
1119
|
modelManager.addCTOModel(markdownCommon.CiceroMarkModel.MODEL, "ciceromark.cto");
|
|
1070
1120
|
const factory = new concertoCore.Factory(modelManager);
|
|
1071
|
-
const serializer = new concertoCore.Serializer(factory, modelManager);
|
|
1121
|
+
const serializer = new concertoCore.Serializer(factory, modelManager, {});
|
|
1072
1122
|
try {
|
|
1073
|
-
return serializer.fromJSON(ciceroMark);
|
|
1123
|
+
return serializer.fromJSON(ciceroMark, {});
|
|
1074
1124
|
} catch (err) {
|
|
1075
1125
|
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(ciceroMark, null, 2)}`);
|
|
1076
1126
|
}
|
|
1077
1127
|
}
|
|
1078
1128
|
async generate(templateMark, data, options) {
|
|
1079
1129
|
const factory = new concertoCore.Factory(this.modelManager);
|
|
1080
|
-
const serializer = new concertoCore.Serializer(factory, this.modelManager);
|
|
1081
|
-
const templateData = serializer.fromJSON(data);
|
|
1130
|
+
const serializer = new concertoCore.Serializer(factory, this.modelManager, {});
|
|
1131
|
+
const templateData = serializer.fromJSON(data, {});
|
|
1082
1132
|
if (templateData.getFullyQualifiedType() !== this.templateClass.getFullyQualifiedName()) {
|
|
1083
1133
|
throw new Error(`Template data must be of type '${this.templateClass.getFullyQualifiedName()}'.`);
|
|
1084
1134
|
}
|
|
@@ -1123,14 +1173,14 @@ class TemplateArchiveProcessor {
|
|
|
1123
1173
|
return result;
|
|
1124
1174
|
}
|
|
1125
1175
|
/**
|
|
1126
|
-
*
|
|
1127
|
-
* @param {
|
|
1128
|
-
* @
|
|
1129
|
-
* @param {[string]} currentTime - the current time, defaults to now
|
|
1130
|
-
* @param {[number]} utcOffset - the UTC offer, defaults to zero
|
|
1131
|
-
* @returns {Promise} the response and any events
|
|
1176
|
+
* Compile the logic of a template
|
|
1177
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to cache the compiled logic for future use
|
|
1178
|
+
* @returns {Promise<Record<string, TwoSlashReturn>>} the compiled code for each typescript file
|
|
1132
1179
|
*/
|
|
1133
|
-
async
|
|
1180
|
+
async compileLogic(enableCompiledLogicCache = false) {
|
|
1181
|
+
if (enableCompiledLogicCache && this.compiledLogicCache) {
|
|
1182
|
+
return this.compiledLogicCache;
|
|
1183
|
+
}
|
|
1134
1184
|
const logicManager = this.template.getLogicManager();
|
|
1135
1185
|
if (logicManager.getLanguage() === "typescript") {
|
|
1136
1186
|
const compiledCode = {};
|
|
@@ -1147,65 +1197,70 @@ class TemplateArchiveProcessor {
|
|
|
1147
1197
|
const result = compiler.compile(code);
|
|
1148
1198
|
compiledCode[tsFile.getIdentifier()] = result;
|
|
1149
1199
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
templateLogic: true,
|
|
1153
|
-
verbose: false,
|
|
1154
|
-
functionName: "trigger",
|
|
1155
|
-
code: compiledCode["logic/logic.ts"].code,
|
|
1156
|
-
// TODO DCS - how to find the code to run?
|
|
1157
|
-
argumentNames: ["data", "request", "state"],
|
|
1158
|
-
arguments: [data, request, state, currentTime, utcOffset]
|
|
1159
|
-
});
|
|
1160
|
-
if (evalResponse.result) {
|
|
1161
|
-
return evalResponse.result;
|
|
1162
|
-
} else {
|
|
1163
|
-
throw new Error("Trigger failed with message: " + evalResponse.message);
|
|
1200
|
+
if (enableCompiledLogicCache) {
|
|
1201
|
+
this.compiledLogicCache = compiledCode;
|
|
1164
1202
|
}
|
|
1203
|
+
return compiledCode;
|
|
1165
1204
|
} else {
|
|
1166
1205
|
throw new Error("Only TypeScript is supported at this time");
|
|
1167
1206
|
}
|
|
1168
1207
|
}
|
|
1169
1208
|
/**
|
|
1170
|
-
*
|
|
1209
|
+
* Trigger the logic of a template.
|
|
1210
|
+
* @param {object} data - the data for the template
|
|
1211
|
+
* @param {object} request - the request to send to the template logic
|
|
1212
|
+
* @param {object} state - the current state of the template
|
|
1171
1213
|
* @param {[string]} currentTime - the current time, defaults to now
|
|
1172
|
-
* @param {[number]} utcOffset - the UTC
|
|
1173
|
-
* @
|
|
1214
|
+
* @param {[number]} utcOffset - the UTC offset, defaults to zero
|
|
1215
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
|
|
1216
|
+
* @returns {Promise<TriggerResponse>} the response and any events
|
|
1174
1217
|
*/
|
|
1175
|
-
async
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
const evaluator = new JavaScriptEvaluator();
|
|
1193
|
-
const evalResponse = await evaluator.evalDangerously({
|
|
1194
|
-
templateLogic: true,
|
|
1195
|
-
verbose: false,
|
|
1196
|
-
functionName: "init",
|
|
1197
|
-
code: compiledCode["logic/logic.ts"].code,
|
|
1198
|
-
// TODO DCS - how to find the code to run?
|
|
1199
|
-
argumentNames: ["data"],
|
|
1200
|
-
arguments: [data, currentTime, utcOffset]
|
|
1201
|
-
});
|
|
1202
|
-
if (evalResponse.result) {
|
|
1203
|
-
return evalResponse.result;
|
|
1204
|
-
} else {
|
|
1205
|
-
throw new Error("Init failed with message: " + evalResponse.message);
|
|
1206
|
-
}
|
|
1218
|
+
async trigger(data, request, state, currentTime, utcOffset, enableCompiledLogicCache) {
|
|
1219
|
+
const compiledCode = await this.compileLogic(enableCompiledLogicCache);
|
|
1220
|
+
const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1221
|
+
const resolvedOffset = utcOffset ?? 0;
|
|
1222
|
+
const evaluator = new JavaScriptEvaluator();
|
|
1223
|
+
const evalResponse = await evaluator.evalDangerously({
|
|
1224
|
+
templateLogic: true,
|
|
1225
|
+
verbose: false,
|
|
1226
|
+
functionName: "trigger",
|
|
1227
|
+
code: compiledCode["logic/logic.ts"].code,
|
|
1228
|
+
// TODO DCS - how to find the code to run?
|
|
1229
|
+
argumentNames: ["data", "request", "state"],
|
|
1230
|
+
arguments: [data, request, state, resolvedTime, resolvedOffset]
|
|
1231
|
+
});
|
|
1232
|
+
if (evalResponse.result) {
|
|
1233
|
+
return evalResponse.result;
|
|
1207
1234
|
} else {
|
|
1208
|
-
throw new Error("
|
|
1235
|
+
throw new Error("Trigger failed with message: " + evalResponse.message);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Init the logic of a template.
|
|
1240
|
+
* @param {object} data - the data for the template
|
|
1241
|
+
* @param {[string]} currentTime - the current time, defaults to now
|
|
1242
|
+
* @param {[number]} utcOffset - the UTC offset, defaults to zero
|
|
1243
|
+
* @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
|
|
1244
|
+
* @returns {Promise<InitResponse>} the new state
|
|
1245
|
+
*/
|
|
1246
|
+
async init(data, currentTime, utcOffset, enableCompiledLogicCache) {
|
|
1247
|
+
const compiledCode = await this.compileLogic(enableCompiledLogicCache);
|
|
1248
|
+
const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1249
|
+
const resolvedOffset = utcOffset ?? 0;
|
|
1250
|
+
const evaluator = new JavaScriptEvaluator();
|
|
1251
|
+
const evalResponse = await evaluator.evalDangerously({
|
|
1252
|
+
templateLogic: true,
|
|
1253
|
+
verbose: false,
|
|
1254
|
+
functionName: "init",
|
|
1255
|
+
code: compiledCode["logic/logic.ts"].code,
|
|
1256
|
+
// TODO DCS - how to find the code to run?
|
|
1257
|
+
argumentNames: ["data"],
|
|
1258
|
+
arguments: [data, resolvedTime, resolvedOffset]
|
|
1259
|
+
});
|
|
1260
|
+
if (evalResponse.result) {
|
|
1261
|
+
return evalResponse.result;
|
|
1262
|
+
} else {
|
|
1263
|
+
throw new Error("Init failed with message: " + evalResponse.message);
|
|
1209
1264
|
}
|
|
1210
1265
|
}
|
|
1211
1266
|
}
|