Skip to content

Commit 4fc03c7

Browse files
Merge pull request #32 from Nictiz/ConvertBundles
Add script to convert fixtures from XML to JSON
2 parents 4c63abd + 93539d4 commit 4fc03c7

File tree

8 files changed

+280
-7
lines changed

8 files changed

+280
-7
lines changed

convertXmlToJson/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ConvertXmlToJson
2+
3+
Java based tool to convert fixtures in XML format to JSON. This is done using the [HAPI FHIR library](https://hapifhir.io/).
4+
5+
A JDK version 11 or higher is required.
6+
7+
For more info on how to use the tool, please check `ant/build.xml`.
8+
9+
This tool can neatly be used together with the NTS build tool for autoconverting XML fixtures to JSON. To do so, pass the `convert.to.json.file` parameter to the NTS build tool as the path to a file where all XML fixtures can be collected that need to be converted to JSON. This parameter can then be passed to this build file.

convertXmlToJson/ant/build.xml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- Common ANT build script for Nictiz NTS projects.
3+
4+
# Use of this build script
5+
6+
The normal use of this build script is to import it from another ANT build script.
7+
8+
# Parameters
9+
10+
To run it, the following parameters are expected:
11+
- convert.to.json.file : The path to a file, which contains a comma separated list of XML fixtures to convert. If
12+
this parameter is empty, the file doesn't exist or is empty, this is taken as a signal that no conversion needs
13+
to be done.
14+
- fhir.version: The FHIR version the fixtures are in, either "STU3" or "R4".
15+
16+
-->
17+
<project xmlns:ivy="antlib:org.apache.ivy.ant" basedir=".." name="convertToJson" default="build">
18+
<!-- Set testscripttools.dir if it is not supplied by the including build file. -->
19+
<dirname property="testscripttools.dir" file="${ant.file.convertToJson}/.."/>
20+
21+
<property name="ivy.install.version" value="2.5.0-rc1" />
22+
<property name="ivy.jar.file" value="${ant.library.dir}/ivy.jar" />
23+
<property name="ivy.dep.file" value="${testscripttools.dir}/ant/ivy.xml" />
24+
25+
<property name="lib.dir" value="${basedir}/lib"/>
26+
<property name="java.dir" value="${testscripttools.dir}/java" />
27+
<property name="dependency.dir" value="${lib.dir}/ant-dependencies"/>
28+
<property name="ivy.local.jar.file" value="${dependency.dir}/ivy.jar" />
29+
30+
<!-- IVY -->
31+
32+
<target name="check-ivy">
33+
<condition property="ivy.exists">
34+
<or>
35+
<available file="${ivy.jar.file}" type="file"/>
36+
<available file="${ivy.local.jar.file}" type="file"/>
37+
</or>
38+
</condition>
39+
<mkdir dir="${dependency.dir}"/>
40+
</target>
41+
42+
<target name="download-ivy" depends="check-ivy" unless="ivy.exists">
43+
<echo message="Installing Ivy"/>
44+
<get src="https://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
45+
dest="${ivy.local.jar.file}" usetimestamp="true"/>
46+
</target>
47+
48+
<target name="install-ivy" depends="download-ivy" unless="ivy.exists, ivy.loaded">
49+
<path id="ivy.lib.path">
50+
<fileset dir="${dependency.dir}" includes="*.jar"/>
51+
</path>
52+
<taskdef resource="org/apache/ivy/ant/antlib.xml" uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
53+
<property name="ivy.loaded" value="true" />
54+
</target>
55+
56+
<target name="retrieve" depends="check-if-conversion-is-needed, check-ivy, install-ivy" if="should.convert">
57+
<ivy:settings file="${testscripttools.dir}/ant/ivysettings.xml"/>
58+
<ivy:retrieve/>
59+
<ivy:artifactproperty name="[module].[artifact]" value="${dependency.dir}/[type]/[artifact]-[revision].[ext]" />
60+
<ivy:artifactproperty name="[artifact].[type]" value="${dependency.dir}/[type]/[artifact]-[revision].[ext]" />
61+
</target>
62+
63+
<target name="check-convert-file">
64+
<condition property="exists.convert.to.json.file">
65+
<and>
66+
<available file="${convert.to.json.file}"/>
67+
<length file="${convert.to.json.file}" when="greater" length="0"/>
68+
</and>
69+
</condition>
70+
</target>
71+
72+
<target name="check-valid-fhir-version" depends="check-convert-file" if="exists.convert.to.json.file">
73+
<sequential>
74+
<condition property="isvalid.fhir.version">
75+
<or>
76+
<equals arg1="${fhir.version}" arg2="STU3"/>
77+
<equals arg1="${fhir.version}" arg2="R4"/>
78+
</or>
79+
</condition>
80+
<fail unless="isvalid.fhir.version" message="There are fixtures to convert from XML to JSON, but in order to do so, the fhir.version property must be set to either 'STU3' or 'R4'."/>
81+
</sequential>
82+
</target>
83+
84+
<target name="check-if-conversion-is-needed" depends="check-convert-file, check-valid-fhir-version" if="exists.convert.to.json.file">
85+
<property name="should.convert" value="true"/>
86+
</target>
87+
88+
<target name="build" depends="retrieve, check-if-conversion-is-needed" if="should.convert">
89+
<sequential>
90+
<echo message="Converting XML fixtures to JSON"/>
91+
<java sourcefile="${testscripttools.dir}/java/ConvertToJson.java" fork="true" outputProperty="result">
92+
<classpath>
93+
<pathelement path="${classpath}"/>
94+
<fileset dir="lib">
95+
<include name="**/*.jar"/>
96+
</fileset>
97+
</classpath>
98+
<arg value="${fhir.version}"/>
99+
<arg file="${convert.to.json.file}"/>
100+
</java>
101+
<echo message="${result}"/>
102+
</sequential>
103+
</target>
104+
</project>

convertXmlToJson/ant/ivy.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<ivy-module version="2.0">
2+
<info organisation="nl.nictiz" module="convertToJson"/>
3+
<dependencies>
4+
<dependency org="ca.uhn.hapi.fhir" name="hapi-fhir-base" rev="6.1.2">
5+
<exclude org="com.helger"/>
6+
</dependency>
7+
<dependency org="ca.uhn.hapi.fhir" name="hapi-fhir-structures-dstu3" rev="6.1.2">
8+
<exclude org="com.helger"/>
9+
</dependency>
10+
<dependency org="ca.uhn.hapi.fhir" name="hapi-fhir-structures-r4" rev="6.1.2">
11+
<exclude org="com.helger"/>
12+
</dependency>
13+
<dependency org="com.guicedee.services" name="sl4j" rev="1.0.13.5"/>
14+
</dependencies>
15+
</ivy-module>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<ivysettings>
2+
<properties file="build.properties" />
3+
<settings defaultResolver="local-chain"/>
4+
<resolvers>
5+
<ibiblio name="ibiblio-maven2" m2compatible="true"/>
6+
<ibiblio name="java-net-maven2" root="http://download.java.net/maven/2/" m2compatible="true" />
7+
<ibiblio name="maven" root="http://mvnrepository.com/artifact/" m2compatible="true" />
8+
<chain name="local-chain">
9+
<resolver ref="maven"/>
10+
<resolver ref="ibiblio-maven2"/>
11+
<resolver ref="java-net-maven2"/>
12+
</chain>
13+
</resolvers>
14+
</ivysettings>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import ca.uhn.fhir.context.FhirContext;
2+
import ca.uhn.fhir.parser.IParser;
3+
4+
import java.io.FileWriter;
5+
import java.io.IOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.regex.Matcher;
11+
import java.util.regex.Pattern;
12+
13+
public class ConvertToJson {
14+
15+
private static String loadMaskedXML(String filePath, List<String> dtPlaceholders) {
16+
// First, load the raw XML
17+
String XML;
18+
try {
19+
XML = Files.readString(Path.of(filePath));
20+
} catch (IOException e) {
21+
System.err.println(e.toString());
22+
return "";
23+
}
24+
25+
// Collect and then replace all the datetime placeholders.
26+
Pattern dtPlaceholderPattern = Pattern.compile("\\$\\{(CURRENT)?DATE.*?\\}");
27+
Matcher dtPlaceholderMatcher = dtPlaceholderPattern.matcher(XML);
28+
while (dtPlaceholderMatcher.find()) {
29+
dtPlaceholders.add(dtPlaceholderMatcher.group());
30+
}
31+
32+
// Now replace all datetime placeholders with our known pattern.
33+
XML = dtPlaceholderMatcher.replaceAll("0001-01-01");
34+
return XML;
35+
}
36+
37+
private static String unmaskJSON(String maskedJSON, List<String> dtPlaceholders) {
38+
// And restore all placeholders
39+
String unmaskedJSON = "";
40+
Pattern dtMaskPattern = Pattern.compile("0001-01-01");
41+
Matcher dtMaskMatcher = dtMaskPattern.matcher(maskedJSON);
42+
int pos = 0;
43+
int placeholderNum = 0;
44+
while (dtMaskMatcher.find()) {
45+
unmaskedJSON += maskedJSON.substring(pos, dtMaskMatcher.start()) + dtPlaceholders.get(placeholderNum);
46+
pos = dtMaskMatcher.end();
47+
}
48+
unmaskedJSON += maskedJSON.substring(pos);
49+
return unmaskedJSON;
50+
}
51+
52+
public static void main(String[] args) {
53+
String version = args[0];
54+
55+
String fileList;
56+
try {
57+
fileList = Files.readString(Path.of(args[1]));
58+
} catch (IOException e) {
59+
System.err.println(e.toString());
60+
System.err.println("File containing the list of fixtures to convert couldn't be read.");
61+
return;
62+
}
63+
64+
Object xmlParser = null;
65+
FhirContext context = null;
66+
if (version.equals("STU3")) {
67+
context = FhirContext.forDstu3();
68+
xmlParser = new org.hl7.fhir.dstu3.formats.XmlParser();
69+
} else if (version.equals("R4")) {
70+
context = FhirContext.forR4();
71+
xmlParser = new org.hl7.fhir.r4.formats.XmlParser();
72+
}
73+
74+
IParser jsonParser = context.newJsonParser();
75+
jsonParser.setPrettyPrint(true);
76+
77+
String[] fixtures = fileList.split(" ");
78+
for (String fixture: fixtures) {
79+
List<String>dtPlaceholders = new ArrayList<String>();
80+
String XML = loadMaskedXML(fixture, dtPlaceholders);
81+
String JSON = "";
82+
try {
83+
if (version.equals("STU3")) {
84+
org.hl7.fhir.dstu3.model.Resource resource = ((org.hl7.fhir.dstu3.formats.XmlParser)xmlParser).parse(XML);
85+
JSON = jsonParser.encodeResourceToString(resource);
86+
} else if (version.equals("R4")) {
87+
org.hl7.fhir.r4.model.Resource resource = ((org.hl7.fhir.r4.formats.XmlParser)xmlParser).parse(XML);
88+
JSON = jsonParser.encodeResourceToString(resource);
89+
}
90+
} catch (IOException e) {
91+
System.out.print("Input file could not be parsed: " + fixture);
92+
return;
93+
}
94+
JSON = unmaskJSON(JSON, dtPlaceholders);
95+
96+
// And write the result to disk
97+
String base_name = fixture.substring(0, fixture.lastIndexOf("."));
98+
String out_path = base_name + ".json";
99+
try {
100+
FileWriter jsonWriter = new FileWriter(out_path);
101+
jsonWriter.write(JSON);
102+
jsonWriter.close();
103+
} catch (IOException e) {
104+
System.out.print("Output file could not be opened: " + out_path);
105+
}
106+
107+
System.out.println("Converted fixture " + fixture + " to JSON");
108+
}
109+
}
110+
}

generate/README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@ Profiles may be declared using:
143143

144144
#### Fixtures
145145

146-
Within the `@href` attribute of `nts:fixture`, the parameter `{$_FORMAT}` can be used to automatically output the format(either `xml` or `json`) the fixture is expected to be in.
146+
Within the `@href` attribute of `nts:fixture`, the parameter `{$_FORMAT}` can be used to automatically output the format (either `xml` or `json`) the fixture is expected to be in.
147147

148-
A LoadResources script is generated for all fixtures in the "_reference"-folder. See the section on building on how to exclude files and/or folders from being added the LoadResources script.
148+
During building, a check will be done on the existence of fixtures that are directly referenced from the TestScripts. It is possible to make an exception for the special situation where a fixture is declared in JSON format but only exists in XML format by setting the `convert.to.json.file` build parameter to the path of a readable file. All the XML version of JSON fixtures will be written to this file, so they can be converted later on. A separate script for this is available in the folder "convertXmlToJson".
149149

150-
#### Including fixtures in other fixtures
150+
A LoadResources script is generated for all fixtures in the "_reference"-folder. See the section on building on how to exclude files and/or folders from being added the LoadResources script. Usually, fixtures that are only used for sending need to be excluded from the LoadResources script.
151+
152+
##### Including fixtures in other fixtures
151153

152154
With a fixture, the inclusion of another fixtures is declared using:
153155

@@ -333,7 +335,8 @@ The following optional parameters may be used:
333335
The TestScript resources can use the `nts:in-targets` to define which element should be included in a target (see above). Multiple extra targets may be separated using comma's.
334336
Note: if there are subfolders in the folder on which an additional target is defined, each variant of the input folder will contain the full set of subfolders (but with slightly different content, of course).
335337
- `targets`: This parameter contains the default target '#default', to which the targets defined in `targets.additional` are added. Used when building the default target is unwanted.
336-
- `version.addition`: a string that will be added verbatim to the value in the `TestScript.version` from the input file. If this element is absent, it will be populated with this value.
338+
- `version.addition`: a string that will be added verbatim to the value in the `TestScript.version` from the input file. If this element is absent, it will be populated with this value.
339+
- `convert.to.json.file`: the path of a writable file where all referenced JSON fixtures are collected that don't exists, but for which an XML counterpart exists. This file can be used for the 'convertXmlToJson' script in this repo). If this parameter is not set, this situation will be treated like any other missing fixture and the build will fail.
337340

338341
### Building multiple projects
339342

@@ -354,6 +357,9 @@ Because of the verbosity of the ANT build, the logging level is set to 1 (warnin
354357

355358
## Changelog
356359

360+
### 2.4.0
361+
- Add an extra script to convert XML fixtures to JSON. This can be used separately or in conjunction with the NTS build script.
362+
357363
### 2.3.0
358364
- Add the option to include fixtures in other fixtures, which are resolved during build. When used in batch/transation Bundles, references are checked and edited where neccessary.
359365
- (Further) parametrize targets and reference directory to be able to overrule these properies in specific builds.

generate/ant/build-multiple.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<property name="lib.dir" value="${lib.dir}"/>
2727
<property name="commoncomponents.dir" value="${commoncomponents.dir}"/>
2828
<property name="version.addition" value="${version.addition}"/>
29+
<property name="convert.to.json.file" value="${convert.to.json.file}"/>
2930
</ant>
3031
</sequential>
3132
</for>

generate/ant/build.xml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272

7373
<property name="targets.additional" value=""/> <!-- By default, we don't build additional targets -->
7474
<property name="targets" value="#default,${targets.additional}"/>
75-
75+
7676
<!-- Set the verbosity level of the output. A higher value means more verbose output -->
7777
<property name="outputLevel" value="1" />
7878
<script language="javascript"> <!-- Gives a script warning. It is advised to switch to Groovy, haven't figured that one out. -->
@@ -293,15 +293,29 @@
293293
<local name="rules"/>
294294
<property file="@{references.file}"/>
295295

296-
<!-- Check if all declared fixtures exist -->
296+
<!-- Check if all declared fixtures exist or, when it is a JSON fixture, an XML equivalent exists. -->
297297
<for param="fixture" list="${fixtures}" delimiter=";">
298298
<sequential>
299+
<local name="fixture.asxml"/>
300+
<propertyregex property="fixture.asxml" input="@{fixture}" regexp="\.json" replace=".xml" defaultValue="@{fixture}"/>
299301
<if>
300302
<not>
301303
<available file="${input.dir.abs}/${reference.subdir}/@{fixture}"/>
302304
</not>
303305
<then>
304-
<fail message="Fixture '@{fixture}' is referenced but doesn't exist"/>
306+
<if>
307+
<and>
308+
<available file="${input.dir.abs}/${reference.subdir}/${fixture.asxml}"/>
309+
<isset property="convert.to.json.file"/>
310+
</and>
311+
<then>
312+
<echo message="@{fixture} will need to be generated from its XML counterpart."/>
313+
<echo message="${output.dir.abs}/${reference.subdir}/${fixture.asxml} " file="${convert.to.json.file}" append="true"/>
314+
</then>
315+
<else>
316+
<fail message="Fixture '@{fixture}' is referenced but doesn't exist"/>
317+
</else>
318+
</if>
305319
</then>
306320
</if>
307321
</sequential>

0 commit comments

Comments
 (0)