What’s new in EFX 2

This is a pre-release version of this document. The eForms SDK 2.0.0 is still under development. This notice will be removed when the document is finalized.

EFX-2 is the version of EFX released with SDK 2.0.0. EFX-2 has two distinct language flavors — EFX Templates and EFX Rules — both built on the shared EFX Expressions syntax which they extend for their specific applications.

Backward compatibility

EFX-2 contains new features that are not supported by EFX-1. Most EFX-1 expressions are still valid EFX-2 expressions, but several changes require modifications:

  • Sequence literals now use […​] instead of (…​).

  • Codelist identifiers now use […​codelistName] instead of (codelistName). The #codelistName notation is still supported but will be removed by beta.1.

  • The measure type has been renamed to duration, and related functions have been renamed accordingly (add-measureadd-duration, subtract-measuresubtract-duration).

  • XPath axis specifiers have been removed.

  • Cross-notice references (notice(…​)) have been removed, replaced by dynamic API functions in EFX Rules.

  • The EFX-1 expression syntax ({context} ${expression}) is still supported but will likely be removed by beta.1 in favour of WITH context COMPUTE expression.

  • The EFX-1 template line syntax ({context} template) is still supported but will likely be removed by beta.1 in favour of WITH context DISPLAY template;.

EFX Expressions

EFX Expressions are the shared building blocks used in both EFX Templates and EFX Rules.

WITH ... COMPUTE syntax

Expressions can now be written as WITH context COMPUTE expression, improving readability over the EFX-1 {context} ${expression} syntax.

// EFX-1 style:
{BT-23-Procedure} ${BT-23-Procedure in #main-activity}

// EFX-2 style:
WITH BT-23-Procedure COMPUTE BT-23-Procedure in [...main-activity]

Improved type safety

The grammar has been reorganised to enforce a stricter distinction between scalar and sequence types. Ambiguities in late-binding have been removed. Variables and parameters can now be declared with sequence types using the type* notation:

// Sequence parameter declaration
text*:$codes

// Sequence variable declaration
text*:$lot-ids = [for text:$id in BT-137-Lot return $id]

Working with sequences

Sequence literal syntax now uses […​] instead of (…​) to remove ambiguity with parenthesised expressions:

// EFX-1 style:
BT-23-Procedure in ('works', 'supplies', 'services')

// EFX-2 style:
BT-23-Procedure in ['works', 'supplies', 'services']

The codelist expansion operator has been changed to […​codelistName] for consistency. The #codelistName notation is still supported but will be removed by beta.1.

// Legacy notation (to be removed):
BT-23-Procedure in #main-activity

// New codelist expansion:
BT-23-Procedure in [...main-activity]

New conditions allow testing sequences directly:

  • sequence is [not] empty — tests whether a sequence has any elements.

  • sequence has [no] duplicates — tests whether all elements in a sequence are distinct.

  • expression is [not] unique in sequence — tests whether a value appears exactly once in a sequence.

Iterations now support a DISTINCT modifier to deduplicate results, and can return sequences to concatenate results:

// Deduplicated iteration results:
for text:$code in BT-263-Procedure return distinct $code

// Returning sequences from iterations (concatenation):
// Each lot contains multiple additional classifications (repeatable node),
// so accessing BT-263-Lot returns a sequence that gets concatenated.
for context:$lot in ND-Lot return $lot::BT-263-Lot

Node presence testing

Presence conditions (is present / is not present) can now be applied to node references in addition to field references:

ND-Lot is present
ND-Part is not present

Field context indexing

Field contexts now support indexers, allowing expressions to select a specific occurrence of a repeatable field context by position:

// Access the second lot's procurement scope:
ND-Lot[2]::BT-262-Lot

Field properties

EFX-2 introduces the ability to access properties of a field using the field:property syntax. Three categories of field properties are available:

Privacy controls

Expressions can access privacy-related properties of fields withheld from publication. These properties allow rules and templates to reason about the privacy status of fields without having to reference the underlying privacy fields (e.g. BT-197, BT-198) by name:

  • field:privacyCode — the privacy code of the field.

  • field:publicationDate — the publication date for a withheld field.

  • field:justificationCode — the justification code for withholding.

  • field:justificationDescription — the justification description.

  • field:wasWithheld, field:isWithheld, field:isWithholdable, field:isDisclosed, field:isMasked — boolean properties indicating privacy status.

// Access the publication date of BT-105-Procedure without referencing BT-197 directly:
BT-105-Procedure:publicationDate

// Access the justification code:
BT-105-Procedure:justificationCode

Raw value access

field:rawValue provides access to the raw string value of a field, bypassing type-specific parsing:

BT-05(a)-notice:rawValue

New built-in functions

Working with numbers

min, max, average — aggregate functions on numeric sequences.
absolute — absolute value.
round, round-down, round-up — rounding functions.

Working with strings

substring-before, substring-after — split strings around a delimiter.
normalize-space — collapse and trim whitespace.
trim, trim-left, trim-right — whitespace trimming.
pad-left, pad-right — string padding.
repeat — repeat a string N times.
replace, replace-regex — string replacement (literal and regex).
split — split a string into a sequence by delimiter.
url-encode — URL encoding.
capitalize-first — capitalize the first character.
index-of-substring — find the position of a substring within a string.
empty — test whether a string is empty.

Working with dates, times and durations

year, month, day — extract components from a date.
hours, minutes, seconds — extract components from a time.
years, months, days — extract components from a duration.
current-date, current-time — return the current date or time.

Working with sequences

sort, reverse — sequence ordering.
subsequence — extract a sub-range from a sequence.
index-of — find the position of an element in a sequence.
get-duplicates — extract duplicate values from a sequence.
count-duplicates — count the number of duplicate values.

Type conversions

text(boolean), text(date), text(time), text(duration) — convert values to string.
number(boolean) — convert boolean to number.
indicator(number) — convert number to boolean.

measure renamed to duration

The measure type in EFX-1 was only used for durations, which are a special case because they carry the unit in their value (e.g. P10D) to allow calculations. Renaming to duration frees the measure keyword for other types of measure that can be treated as simple numbers. Related function names have also been updated: add-measureadd-duration, subtract-measuresubtract-duration.

XPath axes removed

Axis specifiers (preceding, following, ancestor, descendant, etc.) were hard to translate to non-XPath target languages and did not add real value to the expressivity of EFX. They have been removed.

EFX Templates

The template language has been enhanced in three areas: more expressive template logic, better reuse and maintainability, and richer output capabilities.

Template syntax and logic

EFX-2 template syntax

Template lines now use WITH context DISPLAY template; instead of the EFX-1 {context} template syntax. Template lines are now semicolon-terminated, and the context declaration is optional — allowing template lines that display content without iterating over a context.

// EFX-1 style:
{BT-22-Procedure} #{name}: $value

// EFX-2 style:
WITH BT-22-Procedure DISPLAY #{name}: $value;

// Without context (static content):
2 DISPLAY #{auxiliary|text|procedure};

The EFX-1 syntax is still supported for backward compatibility but will likely be removed by beta.1.

Conditional templates

Template lines can now conditionally render different content using WHEN / OTHERWISE:

WITH BT-105-Procedure
  WHEN BT-105-Procedure:isMasked DISPLAY #{name}: #{auxiliary|text|unpublished}
  OTHERWISE DISPLAY #{name}: #{BT-105-Procedure};

Navigation and Summary sections

View templates can now be structured with ---- NAVIGATION ---- and ---- SUMMARY ---- section headers, separating the main content from navigation aids and summary views.

Reuse and maintainability

Callable templates

Templates can be defined once and reused across the template file:

// Declaration:
LET template:role-and-name(text:$label-id, text:$org-tpo-id)
  WHEN $org-tpo-id in OPT-200-Organization-Company
    DISPLAY #{${$label-id}}: ${BT-500-Organization-Company[OPT-200-Organization-Company == $org-tpo-id]:preferredLanguageText}
  OTHERWISE
    DISPLAY #{${$label-id}}: ${BT-500-Organization-TouchPoint[OPT-201-Organization-TouchPoint == $org-tpo-id]:preferredLanguageText};

// Invocation:
WITH OPT-301-Lot-ReviewOrg
  INVOKE role-and-name('auxiliary|text|organisation-review', OPT-301-Lot-ReviewOrg);

User-defined functions

Reusable typed functions can be declared with scalar or sequence return types:

LET indicator:?exists-common-value(text*:$seq1, text*:$seq2) =
  count(value-intersect($seq1, $seq2)) > 0;

Functions are invoked using the ?name(args) syntax.

Dictionaries

Dictionaries pre-compute key-value lookups, improving performance by avoiding repeated traversals:

LET $lot-name-by-lot-id INDEX BT-21-Lot BY BT-137-Lot;

// Later, look up the lot name by its identifier:
$lot-name-by-lot-id[BT-13713-LotResult]

Richer output

Template content can be linked to URLs using the @{url} syntax:

WITH BT-505-Organization-Company DISPLAY $value @{$value};

Character references and escape sequences

Template text now supports HTML character references (&, ©, ©) and escape sequences.

Formatting functions

Locale-aware date/time formatting is available through format-short, format-medium, and format-long (template-only functions):

// Format a date for display:
format-medium(BT-05(a)-notice)

// Format a date and time together:
format-short(BT-05(a)-notice, BT-05(b)-notice)

EFX Rules

EFX-2 introduces a complete rules language, allowing business rules to be authored directly in EFX.

Rules file structure

A rules file consists of optional global declarations followed by one or more validation stages:

#include "stage-1a.efx"

---- STAGE 2a ----

WITH ND-AuctionTerms
  ASSERT BT-122-Lot is not present
    AS ERROR R-J6A-ZY4
    FOR BT-122-Lot
    IN 1-6, 15, 23-40, CEI, E1-E2, E4-E6, T01-T02, X01-X02
  ASSERT BT-767-Lot is present
    AS ERROR R-LTD-XCL
    FOR BT-767-Lot
    IN 16-18, 22, 29-31

Validation stages

Rules are organised into stages (---- STAGE stage-id ----), each representing a validation phase (e.g. "1a", "3b", "4"). Each stage contains optional stage-level variable declarations and one or more rule-sets.

Dynamic rules

Dynamic rules allow validation to consider information retrieved from other notices through a specialised API. This replaces the EFX-1 cross-notice reference syntax (notice(…​)) which was impractical and never used.

ENDPOINT "ted-api" AT "https://api.ted.europa.eu/v1";
LET dynamic:?checkPreviousNotice(text:$noticeId) CALL API "ted-api" ON ERROR WARN;
  • API endpoint declarations: ENDPOINT "name" AT "url" for named API endpoints, overridable at runtime.

  • Dynamic/API functions: LET dynamic:?name(params) CALL API "endpoint" with ON ERROR WARN|REJECT clauses.

Rule syntax

Rule-sets

Multiple rules can share the same context via a single WITH clause, avoiding repetition of context declarations.

ASSERT and REPORT

Two rule types with distinct semantics:

  • ASSERT defines a condition that must be true for the rule to pass. The rule fires (reports a violation) when the condition is false.

  • REPORT defines a condition that, when true, triggers a report.

Conditional rules

Rules can be conditionally applied using WHEN / OTHERWISE:

WITH ND-Root
  WHEN ND-Lot is present
    ASSERT BT-137-Lot is present
      AS ERROR R-ABC-123 FOR BT-137-Lot IN 7-40
  OTHERWISE
    ASSERT ND-Part is present
      AS ERROR R-ABC-124 FOR ND-Part IN 4-6, E2;

Severity and rule ID

Each rule specifies a severity (ERROR, WARNING, INFO) and an identifier following the R-XXX-XXX pattern, used for error message translation lookup:

AS ERROR R-001-001
AS WARNING R-002-003
AS INFO R-003-001

FOR clause

FOR field|node specifies which field or node the rule validates. This is used to organise validators by target for efficient lookup.

Notice type targeting

IN specifies which notice types the rule applies to. Supports individual types, ranges, and wildcards:

IN 1-3, 10-13, E1-E3    // Explicit ranges
IN *                      // All notice types
IN ANY                    // All notice types

Scope annotations

SCOPE specifies rule applicability and categorisation:

  • @PRE — rule applies only during live validation (while editing).

  • @POST — rule applies only after submission.

  • #flag — categorisation flag (e.g. #lawfulness), carried over to SVRL output.

Omitting @PRE/@POST means the rule applies in both contexts.

Shared between EFX Templates and EFX Rules

Include directives

Template and rules files can be composed from fragments using #include "path/to/file.efx".

Variable declarations

LET type:$var = expression for declaring typed variables (both scalar and sequence types), available as global declarations in templates and as global or stage-level declarations in rules.

Context declaration with inline variables

The context declaration block (WITH) now supports local variable declarations before and/or after the context, allowing variables to be computed and reused within the scope of a template-block or rule-set.

WITH ND-Organization,
  text:$orgid = OPT-200-Organization-Company,
  text*:$tpoid = OPT-201-Organization-TouchPoint
  DISPLAY ${BT-500-Organization-Company:preferredLanguageText};

Comments

// comments are now supported everywhere (expressions, templates, rules).