Skip to content
nonblockingPublic

About

FML (FHIR Mapping Language) to Java transpiler

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FML2Java Transpiler

Overview

fml2java is a command line tool to transform FML maps into Java code.

The FML (FHIR Mapping Language) is used to define mappings between different healthcare data formats, such as CDA (Continuity of Care Document) and FHIR (Fast Healthcare Interoperability Resources). FML is specified here: https://build.fhir.org/ig/HL7/mapping-language-ig/en/fml.html

Disclaimer: This implementation has only been tested with Austrian (ELGA) CDA documents – the laboratory reports and immunization status documents in test/input – and the CDA2FHIR maps of HL7 Austria in test/maps. The supported subset of FML and FHIRPath, the CDA model (ELGA extensions of CDA R2) and the reproduced behaviour of the reference implementation are driven by these maps and documents. Other maps or documents may use constructs that are not supported or behave differently; check the results carefully before using it for anything else.

The CLI tool can be called like this:

java -jar fml2java.jar -m test/maps/CdaToFhirBundle.4.map -o src/main/java/at/nonblocking/CdaToFhirBundle.java -p at.nonblocking

and outputs a single Java Class with the following signature (the class name is taken from the output file name):

package at.nonblocking;

import java.io.InputStream;
import org.hl7.fhir.r4.model.Bundle;

public class CdaToFhirBundle {

    /**
     * CDA XML 2 FHIR Mapper
     * 
     * @param cda Input CDA XML
     * @return FHIR R4 Bundle
     * @throws Exception
     */
    public Bundle mapCDA2FHIR(InputStream cda) throws Exception {
        // ... generated mapping
    }
}

Command line options

Option Description
-m, --map <file> Entry FML map. Maps it imports are looked up by their canonical URL (map "<url>" = …) in the directory of the entry map.
-i, --import-path <dir> Additional directory to search for imported maps (can be repeated).
-o, --output <file> Java file to generate; the class name is derived from the file name. Missing directories are created.
-p, --package <name> Java package of the generated class.
--method <name> Name of the mapping method (default: derived from the models, mapCDA2FHIR).
--dump Print the resolved maps, groups and default groups instead of generating code (no -o/-p needed).
-h, --help Print the usage.

Errors in the maps are reported with file:line:column; the exit code is 0 on success, 1 on errors and 2 on invalid usage.

Using the generated class

The generated class is self-contained: the CDA parser, the FHIRPath runtime, the concept maps and the CDA type metadata are embedded. Its only dependency is the HL7 FHIR R4 model in the version used by fml2java:

implementation 'ca.uhn.hapi.fhir:org.hl7.fhir.r4:6.10.4'
// only needed to serialize bundles with org.hl7.fhir.r4.formats.JsonParser (an optional dependency of the R4 model)
implementation 'com.google.code.gson:gson:2.13.1'
var mapper = new CdaToFhirBundle();
try (var cda = Files.newInputStream(Path.of("document.xml"))) {
    Bundle bundle = mapper.mapCDA2FHIR(cda);
    String json = new JsonParser().setOutputStyle(IParser.OutputStyle.PRETTY).composeString(bundle);
}
  • Thread-safe: the generated class has no mutable state; one instance can be used concurrently (verified by EndToEndTest.isThreadSafe).
  • Performance: the 15 test documents are mapped in ~0.4 s including class loading and ~85 ms warm (~6 ms per document). For comparison, the Python code generated by MaLaC-HD needs ~7 s for them (one Python process per document).
  • Readable: every rule is preceded by a comment with its FML source position (// CdaToFhirBundle.4.map:901:3 "rule name").

Building

Requires Java 25 or newer.

./gradlew build        # fat jar build/libs/fml2java.jar, unit tests and end-to-end test
./gradlew test         # unit tests only
./gradlew e2eTest      # end-to-end test only

The end-to-end test (EndToEndTest, see AGENTS.md) generates the mapper for test/maps/CdaToFhirBundle.4.map, compiles it, transforms all test/input/**/*.xml and compares the bundles with test/output/**/*.json (UUIDs and the bundle timestamp normalized). Outputs and normalized copies for diffing are written to build/e2e-output.

The reference outputs in test/output are produced by MaLaC-HD 1.8.0. After changing the maps, regenerate them with:

cd test/python-maps
./bin/malac-hd -m ../maps/CdaToFhirBundle.4.map -co CdaToFhirBundle.4.py -s   # requires the virtualenv created by transform.sh
zsh ./transform.sh

Supported FML

The FML subset used by the test maps:

  • map, uses, imports (also with wildcards), let, inline conceptmaps (==, !=, <=, >=, ~, -)
  • groups with typed or untyped parameters, extends, <<types>> default groups
  • rules with one source: src.element : Type first|not_first|last|not_last as v where (…); where also without parentheses
  • targets tgt.element = value as v, tgt.element as v, implicit copies (src.a -> tgt.b), choice elements with type suffix (valueQuantity), tgt = (expr) on primitives
  • transforms create, copy (implicit), append, uuid, cast, truncate, translate (code, Coding, CodeableConcept) and FHIRPath expressions
  • dependent rules then { … } and group invocations then Group(a, b)
  • FHIRPath: paths, indexers, $this, %var, literals, and/or/xor/implies, =/!=/</<=/>/>=, +, -, &, |, in, contains and the functions exists, empty, not, where, count, first, last, descendants, startsWith, endsWith, contains, matches, indexOf, substring, lower, upper, length, toString, now

Not supported (reported as error with the source position): several sources per rule, check, log, default, only_one, target list modes (share, collate, …), <<type+>> groups, the transforms reference, evaluate, cc, c, qty, id, cp, dateOp, escape, pointer, FHIRPath type operators (is, as), arithmetic (*, /, div, mod) and date/time literals.

The source model is CDA R2 with the ELGA extensions (src/main/resources/at/nonblocking/fml2java/cda/cda-types.txt, extracted from the CDA model of MaLaC-HD with tools/extract_cda_types.py); the target model is FHIR R4.

Differences to MaLaC-HD

The generated mappers reproduce the output of MaLaC-HD 1.8.0 for the test data (see test/), except where MaLaC-HD contradicts the FHIR specification or fails:

  • Decimal precision: decimals keep the precision of the input (1.00, as recommended by FHIR); MaLaC-HD writes the Python float representation (1.0).
  • Extensions on repeating primitives: the _x array is aligned with the value array as required by the FHIR JSON format ("given": ["a", "b"], "_given": [{...}, null]); MaLaC-HD only writes the extensions ("_given": [{...}]).
  • HL7 v3 timestamps that MaLaC-HD can not parse (times with hours only, fractional seconds, a time zone without time) or returns unconverted (negative time zones such as -0500) are converted to valid FHIR date/time values.
  • then Group(...) { rules }: the nested rules run for each source item (FML semantics); MaLaC-HD runs them once after all items, with the values of the last item.
  • Bundle.timestamp (now()) has millisecond precision; MaLaC-HD writes microseconds.

Behaviour of MaLaC-HD that is reproduced although it is unusual: the string value of a CDA element is MaLaC-HD's XML serialization of it (e.g. ServiceRequest.reasonCode.text), CDA booleans are written as True/False by cast(…, 'string'), and translating a code with more than one equivalent mapping fails.

About

FML (FHIR Mapping Language) to Java transpiler

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages