diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java index f9241f29c90..43ed66b672b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/AbstractImportExportCsvConceptMapCommand.java @@ -27,10 +27,19 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import java.util.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -64,6 +73,29 @@ public abstract class AbstractImportExportCsvConceptMapCommand extends BaseComma addRequiredOption(theOptions, FHIR_VERSION_PARAM, FHIR_VERSION_PARAM_LONGOPT, FHIR_VERSION_PARAM_NAME, FHIR_VERSION_PARAM_DESC + versions); } + protected BufferedReader getBufferedReader() throws IOException { + return new BufferedReader(getInputStreamReader()); + } + + protected InputStreamReader getInputStreamReader() throws IOException { + return new InputStreamReader(getBOMInputStream()); + } + + protected BOMInputStream getBOMInputStream() throws IOException { + return new BOMInputStream( + getInputStream(), + false, + ByteOrderMark.UTF_8, + ByteOrderMark.UTF_16BE, + ByteOrderMark.UTF_16LE, + ByteOrderMark.UTF_32BE, + ByteOrderMark.UTF_32LE); + } + + protected InputStream getInputStream() throws IOException { + return Files.newInputStream(Paths.get(file), StandardOpenOption.READ); + } + @Override public void run(CommandLine theCommandLine) throws ParseException, ExecutionException { parseFhirContext(theCommandLine); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java index dc075491f4c..d8ae19af62b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommand.java @@ -39,8 +39,6 @@ import org.hl7.fhir.r4.model.UriType; import java.io.IOException; import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -166,7 +164,7 @@ public class ImportCsvToConceptMapCommand extends AbstractImportExportCsvConcept ourLog.info("Converting CSV to ConceptMap..."); ConceptMap retVal = new ConceptMap(); try ( - Reader reader = Files.newBufferedReader(Paths.get(file)); + Reader reader = getBufferedReader(); CSVParser csvParser = new CSVParser( reader, CSVFormat diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommandR4Test.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommandR4Test.java index bb3072b4fad..7c468bb32a2 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommandR4Test.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/java/ca/uhn/fhir/cli/ImportCsvToConceptMapCommandR4Test.java @@ -369,4 +369,79 @@ public class ImportCsvToConceptMapCommandR4Test { assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId()); } + + @Test + public void testImportCsvToConceptMapCommandWithByteOrderMark() throws FHIRException { + ClassLoader classLoader = getClass().getClassLoader(); + File fileToImport = new File(classLoader.getResource("loinc-to-phenx.csv").getFile()); + ImportCsvToConceptMapCommandR4Test.file = fileToImport.getAbsolutePath(); + + App.main(new String[] {"import-csv-to-conceptmap", + "-v", ourVersion, + "-t", ourBase, + "-u", "http://loinc.org/cm/loinc-to-phenx", + "-i", "http://loinc.org", + "-o", "http://phenxtoolkit.org", + "-f", file, + "-l"}); + + Bundle response = ourClient + .search() + .forResource(ConceptMap.class) + .where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx")) + .returnBundle(Bundle.class) + .execute(); + + ConceptMap conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); + + ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap)); + + assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/1", conceptMap.getId()); + + assertEquals("http://loinc.org/cm/loinc-to-phenx", conceptMap.getUrl()); + assertEquals("http://loinc.org", conceptMap.getSourceUriType().getValueAsString()); + assertEquals("http://phenxtoolkit.org", conceptMap.getTargetUriType().getValueAsString()); + + assertEquals(1, conceptMap.getGroup().size()); + + ConceptMapGroupComponent group = conceptMap.getGroup().get(0); + assertEquals("http://loinc.org", group.getSource()); + assertNull(group.getSourceVersion()); + assertEquals("http://phenxtoolkit.org", group.getTarget()); + assertNull(group.getTargetVersion()); + + assertEquals(1, group.getElement().size()); + + SourceElementComponent source = group.getElement().get(0); + assertEquals("65191-9", source.getCode()); + assertEquals("During the past 30 days, about how often did you feel restless or fidgety [Kessler 6 Distress]", source.getDisplay()); + + assertEquals(1, source.getTarget().size()); + + TargetElementComponent target = source.getTarget().get(0); + assertEquals("PX121301010300", target.getCode()); + assertEquals("PX121301_Restless", target.getDisplay()); + assertEquals(ConceptMapEquivalence.EQUIVALENT, target.getEquivalence()); + assertNull(target.getComment()); + + App.main(new String[] {"import-csv-to-conceptmap", + "-v", ourVersion, + "-t", ourBase, + "-u", "http://loinc.org/cm/loinc-to-phenx", + "-i", "http://loinc.org", + "-o", "http://phenxtoolkit.org", + "-f", file, + "-l"}); + + response = ourClient + .search() + .forResource(ConceptMap.class) + .where(ConceptMap.URL.matches().value("http://loinc.org/cm/loinc-to-phenx")) + .returnBundle(Bundle.class) + .execute(); + + conceptMap = (ConceptMap) response.getEntryFirstRep().getResource(); + + assertEquals("http://localhost:" + ourPort + "/ConceptMap/1/_history/2", conceptMap.getId()); + } } diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/loinc-to-phenx.csv b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/loinc-to-phenx.csv new file mode 100644 index 00000000000..ec2fb63c1b9 --- /dev/null +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/test/resources/loinc-to-phenx.csv @@ -0,0 +1,2 @@ +SOURCE_CODE_SYSTEM,SOURCE_CODE_SYSTEM_VERSION,TARGET_CODE_SYSTEM,TARGET_CODE_SYSTEM_VERSION,SOURCE_CODE,SOURCE_DISPLAY,TARGET_CODE,TARGET_DISPLAY,EQUIVALENCE,COMMENT +http://loinc.org,,http://phenxtoolkit.org,,65191-9,"During the past 30 days, about how often did you feel restless or fidgety [Kessler 6 Distress]",PX121301010300,PX121301_Restless,equivalent, \ No newline at end of file diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3932477a6e1..58d55321298 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -16,6 +16,10 @@ The JPA server did not correctly index Timing fields where the timing contained a period but no individual events. This has been corrected. + + The HAPI FHIR CLI import-csv-to-conceptmap command was not accounting for byte order marks in + CSV files (e.g. some Excel CSV files). This has been fixed. +