Skip to content

Commit f2e1a58

Browse files
Merge pull request #157 from OP-TED/TEDEFO-4978-efx-preferred-language-improvements
TEDEFO-4978 Preferred-language properties, fieldContext indexer, priv…
2 parents 866dc4c + f7615eb commit f2e1a58

13 files changed

Lines changed: 1465 additions & 105 deletions

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<version.jackson>2.18.3</version.jackson>
5959
<version.jackson-databind>2.18.3</version.jackson-databind>
6060
<version.junit-jupiter>5.7.2</version.junit-jupiter>
61+
<version.saxon-he>11.3</version.saxon-he>
6162
<version.slf4j>2.0.13</version.slf4j>
6263

6364
<!-- Versions - Plugins -->
@@ -127,6 +128,11 @@
127128
<artifactId>freemarker</artifactId>
128129
<version>${version.freemarker}</version>
129130
</dependency>
131+
<dependency>
132+
<groupId>net.sf.saxon</groupId>
133+
<artifactId>Saxon-HE</artifactId>
134+
<version>${version.saxon-he}</version>
135+
</dependency>
130136
</dependencies>
131137
</dependencyManagement>
132138

@@ -147,6 +153,11 @@
147153
<artifactId>junit-jupiter-params</artifactId>
148154
<scope>test</scope>
149155
</dependency>
156+
<dependency>
157+
<groupId>net.sf.saxon</groupId>
158+
<artifactId>Saxon-HE</artifactId>
159+
<scope>test</scope>
160+
</dependency>
150161
<dependency>
151162
<groupId>org.antlr</groupId>
152163
<artifactId>antlr4-runtime</artifactId>

src/main/java/eu/europa/ted/eforms/sdk/SdkSymbolResolver.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,11 @@ private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkF
442442
return !sdkField.equals(context);
443443
}
444444

445+
// Multilingual fields have multiple XML elements (one per language)
446+
if (FieldTypes.TEXT_MULTILINGUAL.getName().equals(sdkField.getType())) {
447+
return true;
448+
}
449+
445450
// Use cached ancestry from node
446451
List<String> contextAncestry = context != null
447452
? context.getParentNode().getAncestry()
@@ -480,6 +485,11 @@ private boolean isFieldRepeatableFromContext(final SdkField sdkField, final SdkN
480485
return true;
481486
}
482487

488+
// Multilingual fields have multiple XML elements (one per language)
489+
if (FieldTypes.TEXT_MULTILINGUAL.getName().equals(sdkField.getType())) {
490+
return true;
491+
}
492+
483493
// Use cached ancestry from node
484494
List<String> contextAncestry = context != null
485495
? context.getAncestry()

src/main/java/eu/europa/ted/efx/autocomplete/EfxLinkedProperty.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ public enum EfxLinkedProperty {
2424
IS_MASKED(":isMasked", INDICATOR),
2525

2626
// Raw value (textExpression rule)
27-
RAW_VALUE(":rawValue", TEXT);
27+
RAW_VALUE(":rawValue", TEXT),
28+
29+
// Preferred language properties (stringExpression rule, template-only)
30+
PREFERRED_LANGUAGE(":preferredLanguage", TEXT),
31+
PREFERRED_LANGUAGE_TEXT(":preferredLanguageText", TEXT);
2832

2933
private final String label;
3034
private final EfxDataType dataType;

src/main/java/eu/europa/ted/efx/exceptions/TypeMismatchException.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public enum ErrorCode {
2929
CANNOT_COMPARE,
3030
INCOMPATIBLE_OPERANDS,
3131
FIELD_MAY_REPEAT,
32+
FIELD_IS_MULTILINGUAL,
3233
NODE_CONTEXT_AS_VALUE,
3334
IDENTIFIER_IS_SEQUENCE,
3435
IDENTIFIER_IS_SCALAR,
@@ -39,6 +40,7 @@ public enum ErrorCode {
3940
private static final String CANNOT_COMPARE = "Type mismatch. Cannot compare values of different types: %s and %s.";
4041
private static final String INCOMPATIBLE_OPERANDS = "Type mismatch. Operator '%s' cannot be applied to %s and %s.";
4142
private static final String FIELD_MAY_REPEAT = "Type mismatch. Field '%s' may return multiple values from context '%s', but is used as a scalar. Use a sequence expression or change the context.";
43+
private static final String FIELD_IS_MULTILINGUAL = "Type mismatch. Field '%s' is multilingual and has multiple values (one per language), but is used as a scalar. Use :preferredLanguageText to select the preferred language, or an indexer [n] to select a specific element.";
4244
private static final String NODE_CONTEXT_AS_VALUE = "Type mismatch. Context variable '$%s' refers to node '%s', but is used as a value. Only field context variables can be used in value expressions.";
4345
private static final String IDENTIFIER_IS_SEQUENCE = "Type mismatch. Variable '$%s' is declared as a sequence, but is used as a scalar.";
4446
private static final String IDENTIFIER_IS_SCALAR = "Type mismatch. Variable '$%s' is declared as a scalar, but is used where a sequence is expected. To use it as a single-element sequence, wrap it in square brackets: [$%s].";
@@ -90,6 +92,10 @@ public static TypeMismatchException fieldMayRepeat(ParserRuleContext ctx, String
9092
contextSymbol != null ? contextSymbol : "root");
9193
}
9294

95+
public static TypeMismatchException fieldIsMultilingual(ParserRuleContext ctx, String fieldId) {
96+
return new TypeMismatchException(ErrorCode.FIELD_IS_MULTILINGUAL, ctx, FIELD_IS_MULTILINGUAL, fieldId);
97+
}
98+
9399
public static TypeMismatchException nodeContextUsedAsValue(ParserRuleContext ctx, String variableName, String nodeId) {
94100
return new TypeMismatchException(ErrorCode.NODE_CONTEXT_AS_VALUE, ctx, NODE_CONTEXT_AS_VALUE, variableName, nodeId);
95101
}

src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ public void exitContextIteratorExpression(ContextIteratorExpressionContext ctx)
14351435
final String contextFieldId = getFieldId(ctx.fieldContext());
14361436
this.efxContext.declareContextVariable(variable.name,
14371437
new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId),
1438-
this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol())));
1438+
path));
14391439
} else if (ctx.nodeContext() != null) {
14401440
final String contextNodeId =
14411441
getNodeId(ctx.nodeContext());
@@ -1793,6 +1793,20 @@ public void exitAbsoluteFieldReference(AbsoluteFieldReferenceContext ctx) {
17931793
}
17941794
}
17951795

1796+
@Override
1797+
public void exitFieldContext(FieldContextContext ctx) {
1798+
if (ctx.indexer() != null) {
1799+
NumericExpression index = this.stack.pop(NumericExpression.class);
1800+
final TypedExpression top = this.stack.peekType();
1801+
if (!(top instanceof PathExpression)) {
1802+
throw TypeMismatchException.cannotConvert(PathExpression.class, top.getClass());
1803+
}
1804+
PathExpression fieldPath = (PathExpression) this.stack.pop(top.getClass());
1805+
this.stack.push(this.script.composeIndexer(fieldPath.asSequence(), index,
1806+
ScalarPath.fromEfxDataType.get(EfxTypeLattice.toPrimitive(fieldPath.getDataType()))));
1807+
}
1808+
}
1809+
17961810
@Override
17971811
public void enterAbsoluteNodeReference(AbsoluteNodeReferenceContext ctx) {
17981812
if (ctx.Slash() != null) {
@@ -1956,16 +1970,16 @@ private void resolveAndPushFieldReference(ParserRuleContext ctx, PathExpression
19561970
this.stack.push(result);
19571971
break;
19581972
case RESOLVE_SCALAR:
1959-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
1960-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
1973+
if (this.fieldMayReturnMultipleValues(fieldId)) {
1974+
throw this.fieldMayReturnMultipleValuesException(ctx, fieldId);
19611975
}
19621976
this.stack.push(result);
19631977
break;
19641978
case RESOLVE_SEQUENCE:
19651979
this.stack.push(result.asSequence());
19661980
break;
19671981
case RESOLVE_EITHER:
1968-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
1982+
if (this.fieldMayReturnMultipleValues(fieldId)) {
19691983
this.stack.push(result.asSequence());
19701984
} else {
19711985
this.stack.push(result);
@@ -1975,11 +1989,13 @@ private void resolveAndPushFieldReference(ParserRuleContext ctx, PathExpression
19751989
}
19761990

19771991
/**
1978-
* Returns true if a field is repeatable from the current EFX context.
1992+
* Returns true if a field may return multiple values from the current EFX context.
1993+
* This includes fields that are repeatable (from SDK metadata or node hierarchy)
1994+
* and multilingual fields (which have multiple XML elements, one per language).
19791995
* A field is not considered repeatable if it IS the current context (e.g., inside a WITH block
19801996
* on this field, we're referencing the current element being iterated, not the whole sequence).
19811997
*/
1982-
private boolean isFieldRepeatableFromCurrentContext(String fieldId) {
1998+
private boolean fieldMayReturnMultipleValues(String fieldId) {
19831999
if (!this.efxContext.isEmpty()
19842000
&& this.efxContext.peek() != null
19852001
&& this.efxContext.peek().isFieldContext()
@@ -1989,6 +2005,18 @@ private boolean isFieldRepeatableFromCurrentContext(String fieldId) {
19892005
return this.symbols.isFieldRepeatableFromContext(fieldId, this.getContextNodeId());
19902006
}
19912007

2008+
/**
2009+
* Creates the appropriate exception for a field that may return multiple values but is used
2010+
* as a scalar. Returns {@link TypeMismatchException#fieldIsMultilingual} for multilingual fields,
2011+
* or {@link TypeMismatchException#fieldMayRepeat} for structurally repeatable fields.
2012+
*/
2013+
private TypeMismatchException fieldMayReturnMultipleValuesException(ParserRuleContext ctx, String fieldId) {
2014+
if (FieldTypes.TEXT_MULTILINGUAL.getName().equals(this.symbols.getTypeOfField(fieldId))) {
2015+
return TypeMismatchException.fieldIsMultilingual(ctx, fieldId);
2016+
}
2017+
return TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2018+
}
2019+
19922020
/**
19932021
* Gets the node ID of the current context for use with
19942022
* {@link SymbolResolver#isFieldRepeatableFromContext}.
@@ -2018,12 +2046,11 @@ private String getContextNodeId() {
20182046
*/
20192047
@Override
20202048
public void exitContextFieldSpecifier(ContextFieldSpecifierContext ctx) {
2021-
this.stack.pop(PathExpression.class); // Discard the PathExpression placed in the stack for
2022-
// the context field.
2049+
final PathExpression contextFieldPath = this.stack.pop(PathExpression.class);
20232050
final String contextFieldId = getFieldId(ctx.fieldContext());
20242051
this.efxContext
20252052
.push(new FieldContext(contextFieldId, this.symbols.getAbsolutePathOfField(contextFieldId),
2026-
this.symbols.getRelativePathOfField(contextFieldId, this.efxContext.symbol())));
2053+
contextFieldPath));
20272054
}
20282055

20292056

@@ -2518,10 +2545,6 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) {
25182545
@Override
25192546
public void exitFieldWasWithheldProperty(FieldWasWithheldPropertyContext ctx) {
25202547
final String fieldId = getFieldId(ctx.fieldMention());
2521-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
2522-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2523-
}
2524-
25252548
final String privacyCode = this.symbols.getPrivacyCodeOfField(fieldId);
25262549
if (privacyCode == null || privacyCode.isEmpty()) {
25272550
throw InvalidUsageException.fieldNotWithholdable(ctx, fieldId);
@@ -2533,10 +2556,6 @@ public void exitFieldWasWithheldProperty(FieldWasWithheldPropertyContext ctx) {
25332556
@Override
25342557
public void exitFieldIsWithheldProperty(FieldIsWithheldPropertyContext ctx) {
25352558
final String fieldId = getFieldId(ctx.fieldMention());
2536-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
2537-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2538-
}
2539-
25402559
final String privacyCode = this.symbols.getPrivacyCodeOfField(fieldId);
25412560
if (privacyCode == null || privacyCode.isEmpty()) {
25422561
throw InvalidUsageException.fieldNotWithholdable(ctx, fieldId);
@@ -2558,10 +2577,6 @@ public void exitFieldIsWithholdableProperty(FieldIsWithholdablePropertyContext c
25582577
@Override
25592578
public void exitFieldIsDisclosedProperty(FieldIsDisclosedPropertyContext ctx) {
25602579
final String fieldId = getFieldId(ctx.fieldMention());
2561-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
2562-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2563-
}
2564-
25652580
final String privacyCode = this.symbols.getPrivacyCodeOfField(fieldId);
25662581
if (privacyCode == null || privacyCode.isEmpty()) {
25672582
throw InvalidUsageException.fieldNotWithholdable(ctx, fieldId);
@@ -2572,16 +2587,12 @@ public void exitFieldIsDisclosedProperty(FieldIsDisclosedPropertyContext ctx) {
25722587
this.script.composeLogicalAnd(
25732588
this.composeWasWithheldCondition(fieldId, privacyCode),
25742589
this.script.composeLogicalNot(this.composeStillWithheldCondition(fieldId))),
2575-
this.script.composeLogicalNot(this.composeIsMaskedCondition(ctx, fieldId))));
2590+
this.script.composeLogicalNot(this.composeIsMaskedCondition(fieldId))));
25762591
}
25772592

25782593
@Override
25792594
public void exitFieldIsMaskedProperty(FieldIsMaskedPropertyContext ctx) {
25802595
final String fieldId = getFieldId(ctx.fieldMention());
2581-
if (this.isFieldRepeatableFromCurrentContext(fieldId)) {
2582-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2583-
}
2584-
25852596
final String privacyCode = this.symbols.getPrivacyCodeOfField(fieldId);
25862597
if (privacyCode == null || privacyCode.isEmpty()) {
25872598
throw InvalidUsageException.fieldNotWithholdable(ctx, fieldId);
@@ -2590,7 +2601,7 @@ public void exitFieldIsMaskedProperty(FieldIsMaskedPropertyContext ctx) {
25902601
// "isMasked" = was withheld AND field value equals the privacy mask
25912602
this.stack.push(this.script.composeLogicalAnd(
25922603
this.composeWasWithheldCondition(fieldId, privacyCode),
2593-
this.composeIsMaskedCondition(ctx, fieldId)));
2604+
this.composeIsMaskedCondition(fieldId)));
25942605
}
25952606

25962607
@Override
@@ -2604,25 +2615,15 @@ public void exitFieldPrivacyCodeProperty(FieldPrivacyCodePropertyContext ctx) {
26042615
}
26052616

26062617
@Override
2607-
public void exitFieldRawValueProperty(FieldRawValuePropertyContext ctx) {
2618+
public void exitRawValueReference(RawValueReferenceContext ctx) {
26082619
final TypedExpression top = this.stack.peekType();
26092620
if (!(top instanceof PathExpression)) {
26102621
throw TypeMismatchException.cannotConvert(PathExpression.class, top.getClass());
26112622
}
2612-
if (top instanceof SequenceExpression) {
2613-
final String fieldId = getFieldId(ctx.fieldReferenceWithVariableContextOverride()
2614-
.reference.reference.reference);
2615-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2616-
}
2617-
final PathExpression fieldPath = (PathExpression) this.stack.pop(top.getClass());
2618-
this.stack.push(this.script.composeFieldRawValueReference(fieldPath));
2619-
}
2620-
2621-
@Override
2622-
public void exitFieldRawValuePropertySequence(FieldRawValuePropertySequenceContext ctx) {
2623-
final TypedExpression top = this.stack.peekType();
2624-
if (!(top instanceof PathExpression)) {
2625-
throw TypeMismatchException.cannotConvert(PathExpression.class, top.getClass());
2623+
if (top instanceof SequenceExpression
2624+
&& this.currentCardinalityResolutionContext() == CardinalityResolutionContext.RESOLVE_SCALAR) {
2625+
final String fieldId = getFieldId(ctx.fieldContext());
2626+
throw this.fieldMayReturnMultipleValuesException(ctx, fieldId);
26262627
}
26272628
final PathExpression fieldPath = (PathExpression) this.stack.pop(top.getClass());
26282629
this.stack.push(this.script.composeFieldRawValueReference(fieldPath));
@@ -2660,14 +2661,10 @@ private BooleanExpression composeStillWithheldCondition(String fieldId) {
26602661
BooleanExpression.class);
26612662
}
26622663

2663-
private BooleanExpression composeIsMaskedCondition(ParserRuleContext ctx, String fieldId) {
2664+
private BooleanExpression composeIsMaskedCondition(String fieldId) {
26642665
final String maskingValue = this.symbols.getPrivacyMask(fieldId);
26652666
final PathExpression fieldValue = this.script.composeFieldValueReference(this.symbols.getRelativePathOfField(fieldId, this.efxContext.symbol()));
26662667

2667-
if (!(fieldValue instanceof ScalarExpression)) {
2668-
throw TypeMismatchException.fieldMayRepeat(ctx, fieldId, this.efxContext.symbol());
2669-
}
2670-
26712668
return this.script.composeComparisonOperation(
26722669
TypedExpression.from(fieldValue, ScalarExpression.class),
26732670
"==",
@@ -3165,6 +3162,16 @@ public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionConte
31653162
throw InvalidUsageException.templateOnlyFunction(ctx, "preferred-language-text");
31663163
}
31673164

3165+
@Override
3166+
public void exitFieldPreferredLanguageProperty(FieldPreferredLanguagePropertyContext ctx) {
3167+
throw InvalidUsageException.templateOnlyFunction(ctx, ":preferredLanguage");
3168+
}
3169+
3170+
@Override
3171+
public void exitFieldPreferredLanguageTextProperty(FieldPreferredLanguageTextPropertyContext ctx) {
3172+
throw InvalidUsageException.templateOnlyFunction(ctx, ":preferredLanguageText");
3173+
}
3174+
31683175
@Override
31693176
public void exitDictionaryLookup(DictionaryLookupContext ctx) {
31703177
if (this.currentCardinalityResolutionContext() == CardinalityResolutionContext.RESOLVE_SCALAR) {

src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import eu.europa.ted.efx.exceptions.InvalidIndentationException;
4343
import eu.europa.ted.efx.interfaces.Argument;
4444
import eu.europa.ted.efx.interfaces.EfxTemplateTranslator;
45-
import eu.europa.ted.efx.interfaces.IncludedFileResolver;
4645
import eu.europa.ted.efx.interfaces.MarkupGenerator;
4746
import eu.europa.ted.efx.interfaces.ScriptGenerator;
4847
import eu.europa.ted.efx.interfaces.SymbolResolver;
@@ -60,10 +59,8 @@
6059
import eu.europa.ted.efx.model.expressions.scalar.BooleanExpression;
6160
import eu.europa.ted.efx.model.expressions.scalar.DateExpression;
6261
import eu.europa.ted.efx.model.expressions.scalar.DurationExpression;
63-
import eu.europa.ted.efx.model.expressions.scalar.NodePath;
6462
import eu.europa.ted.efx.model.expressions.scalar.NumericExpression;
6563
import eu.europa.ted.efx.model.expressions.scalar.ScalarExpression;
66-
import eu.europa.ted.efx.model.expressions.scalar.ScalarPath;
6764
import eu.europa.ted.efx.model.expressions.scalar.StringExpression;
6865
import eu.europa.ted.efx.model.expressions.scalar.StringPath;
6966
import eu.europa.ted.efx.model.expressions.scalar.TimeExpression;
@@ -82,7 +79,6 @@
8279
import eu.europa.ted.efx.model.templates.TemplateInvocation;
8380
import eu.europa.ted.efx.model.templates.Markup;
8481
import eu.europa.ted.efx.model.types.EfxDataType;
85-
import eu.europa.ted.efx.model.types.FieldTypes;
8682
import eu.europa.ted.efx.model.variables.Dictionary;
8783
import eu.europa.ted.efx.model.variables.Function;
8884
import eu.europa.ted.efx.model.variables.StrictArguments;
@@ -92,7 +88,7 @@
9288
import eu.europa.ted.efx.model.variables.Template;
9389
import eu.europa.ted.efx.model.variables.Variable;
9490
import eu.europa.ted.efx.model.variables.Variables;
95-
import eu.europa.ted.efx.model.expressions.scalar.MultilingualStringPath;
91+
import eu.europa.ted.efx.model.expressions.sequence.MultilingualStringSequencePath;
9692
import eu.europa.ted.efx.sdk2.EfxParser.*;
9793

9894
/**
@@ -978,12 +974,22 @@ public void exitFormatLongDateTimeFunction(FormatLongDateTimeFunctionContext ctx
978974

979975
@Override
980976
public void exitPreferredLanguageFunction(PreferredLanguageFunctionContext ctx) {
981-
this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringPath.class)));
977+
this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringSequencePath.class)));
982978
}
983979

984980
@Override
985981
public void exitPreferredLanguageTextFunction(PreferredLanguageTextFunctionContext ctx) {
986-
this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringPath.class)));
982+
this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringSequencePath.class)));
983+
}
984+
985+
@Override
986+
public void exitFieldPreferredLanguageProperty(FieldPreferredLanguagePropertyContext ctx) {
987+
this.stack.push(this.script.getPreferredLanguage(this.stack.pop(MultilingualStringSequencePath.class)));
988+
}
989+
990+
@Override
991+
public void exitFieldPreferredLanguageTextProperty(FieldPreferredLanguageTextPropertyContext ctx) {
992+
this.stack.push(this.script.getTextInPreferredLanguage(this.stack.pop(MultilingualStringSequencePath.class)));
987993
}
988994

989995
// #endregion Preferred language functions -------------------------------------

src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public BooleanExpression composeAnySatisfies(
257257
public <T extends TypedExpression> T composeConditionalExpression(BooleanExpression condition,
258258
T whenTrue, T whenFalse, Class<T> type) {
259259
return Expression.instantiate(
260-
"(if " + condition.getScript() + " then " + whenTrue.getScript() + " else " + whenFalse.getScript() + ")",
260+
"(if (" + condition.getScript() + ") then " + whenTrue.getScript() + " else " + whenFalse.getScript() + ")",
261261
type);
262262
}
263263

0 commit comments

Comments
 (0)