001package org.hl7.fhir.r4.terminologies; 002 003/*- 004 * #%L 005 * org.hl7.fhir.r4 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import java.io.FileInputStream; 025import java.io.FileNotFoundException; 026import java.io.FileOutputStream; 027import java.io.IOException; 028 029import javax.xml.parsers.DocumentBuilder; 030import javax.xml.parsers.DocumentBuilderFactory; 031import javax.xml.parsers.ParserConfigurationException; 032 033import org.hl7.fhir.exceptions.FHIRFormatError; 034import org.hl7.fhir.r4.formats.XmlParser; 035import org.hl7.fhir.r4.model.Bundle; 036import org.hl7.fhir.r4.model.Bundle.BundleType; 037import org.hl7.fhir.r4.model.CodeableConcept; 038import org.hl7.fhir.r4.model.Coding; 039import org.hl7.fhir.r4.model.DateTimeType; 040import org.hl7.fhir.r4.model.InstantType; 041import org.hl7.fhir.r4.model.Meta; 042import org.hl7.fhir.utilities.Utilities; 043import org.hl7.fhir.utilities.xml.XMLUtil; 044import org.w3c.dom.Document; 045import org.w3c.dom.Element; 046import org.xml.sax.SAXException; 047import org.xmlpull.v1.XmlPullParserException; 048 049/** 050 * This class converts the LOINC XML representation that the FHIR build tool uses internally to a set of DataElements in an atom feed 051 * 052 * @author Grahame 053 * 054 */ 055public class LoincToDEConvertor { 056 057 // C:\temp\LOINC.xml 058 public static void main(String[] args) throws FHIRFormatError, IOException, XmlPullParserException, SAXException, ParserConfigurationException { 059 if (args.length == 0) { 060 System.out.println("FHIR LOINC to CDE convertor. "); 061 System.out.println(""); 062 System.out.println("This tool converts from LOINC to A set of DataElement definitions."); 063 System.out.println(""); 064 System.out.println("Usage: [jar(path?)] [dest] (-defn [definitions]) where: "); 065 System.out.println("* [dest] is a file name of the bundle to produce"); 066 System.out.println("* [definitions] is the file name of a file produced by exporting the main LOINC table from the mdb to XML"); 067 System.out.println(""); 068 } else { 069 LoincToDEConvertor exe = new LoincToDEConvertor(); 070 exe.setDest(args[0]); 071 for (int i = 1; i < args.length; i++) { 072 if (args[i].equals("-defn")) 073 exe.setDefinitions(args[i+1]); 074 } 075 exe.process(); 076 } 077 078 } 079 080 private String dest; 081 private String definitions; 082 public String getDest() { 083 return dest; 084 } 085 public void setDest(String dest) { 086 this.dest = dest; 087 } 088 public String getDefinitions() { 089 return definitions; 090 } 091 public void setDefinitions(String definitions) { 092 this.definitions = definitions; 093 } 094 095 private Document xml; 096 private Bundle bundle; 097 private DateTimeType now; 098 099 public Bundle process(String sourceFile) throws FileNotFoundException, SAXException, IOException, ParserConfigurationException { 100 this.definitions = sourceFile; 101 log("Begin. Produce Loinc CDEs in "+dest+" from "+definitions); 102 loadLoinc(); 103 log("LOINC loaded"); 104 105 now = DateTimeType.now(); 106 107 bundle = new Bundle(); 108 bundle.setType(BundleType.COLLECTION); 109 bundle.setId("http://hl7.org/fhir/commondataelement/loinc"); 110 bundle.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 111 112 processLoincCodes(); 113 return bundle; 114 } 115 116 public void process() throws FHIRFormatError, IOException, XmlPullParserException, SAXException, ParserConfigurationException { 117 log("Begin. Produce Loinc CDEs in "+dest+" from "+definitions); 118 loadLoinc(); 119 log("LOINC loaded"); 120 121 now = DateTimeType.now(); 122 123 bundle = new Bundle(); 124 bundle.setId("http://hl7.org/fhir/commondataelement/loinc"); 125 bundle.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 126 127 processLoincCodes(); 128 if (dest != null) { 129 log("Saving..."); 130 saveBundle(); 131 } 132 log("Done"); 133 134 } 135 136 private void log(String string) { 137 System.out.println(string); 138 139 } 140 private void loadLoinc() throws FileNotFoundException, SAXException, IOException, ParserConfigurationException { 141 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 142 factory.setNamespaceAware(true); 143 DocumentBuilder builder = factory.newDocumentBuilder(); 144 145 xml = builder.parse(new FileInputStream(definitions)); 146 } 147 148 private void saveBundle() throws FHIRFormatError, IOException, XmlPullParserException { 149 XmlParser xml = new XmlParser(); 150 FileOutputStream s = new FileOutputStream(dest); 151 xml.compose(s, bundle, true); 152 s.close(); 153 } 154 155 private String col(Element row, String name) { 156 Element e = XMLUtil.getNamedChild(row, name); 157 if (e == null) 158 return null; 159 String text = e.getTextContent(); 160 return text; 161 } 162 163 private boolean hasCol(Element row, String name) { 164 return Utilities.noString(col(row, name)); 165 } 166 167 private void processLoincCodes() { 168 Element row = XMLUtil.getFirstChild(xml.getDocumentElement()); 169 int i = 0; 170 while (row != null) { 171 i++; 172 if (i % 1000 == 0) 173 System.out.print("."); 174 String code = col(row, "LOINC_NUM"); 175 String comp = col(row, "COMPONENT"); 176// DataElement de = new DataElement(); 177// de.setId("loinc-"+code); 178// de.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 179// bundle.getEntry().add(new BundleEntryComponent().setResource(de)); 180// Identifier id = new Identifier(); 181// id.setSystem("http://hl7.org/fhir/commondataelement/loinc"); 182// id.setValue(code); 183// de.addIdentifier(id); 184// de.setPublisher("Regenstrief + FHIR Project Team"); 185// if (!col(row, "STATUS").equals("ACTIVE")) 186// de.setStatus(PublicationStatus.DRAFT); // till we get good at this 187// else 188// de.setStatus(PublicationStatus.RETIRED); 189// de.setDateElement(DateTimeType.now()); 190// de.setName(comp); 191// ElementDefinition dee = de.addElement(); 192// 193// // PROPERTY ignore 194// // TIME_ASPCT 195// // SYSTEM 196// // SCALE_TYP 197// // METHOD_TYP 198// // dee.getCategory().add(new CodeableConcept().setText(col(row, "CLASS"))); 199// // SOURCE 200// // DATE_LAST_CHANGED - should be in ? 201// // CHNG_TYPE 202// dee.setComment(col(row , "COMMENTS")); 203// if (hasCol(row, "CONSUMER_NAME")) 204// dee.addAlias(col(row, "CONSUMER_NAME")); 205// // MOLAR_MASS 206// // CLASSTYPE 207// // FORMULA 208// // SPECIES 209// // EXMPL_ANSWERS 210// // ACSSYM 211// // BASE_NAME - ? this is a relationship 212// // NAACCR_ID 213// // ---------- CODE_TABLE todo 214// // SURVEY_QUEST_TEXT 215// // SURVEY_QUEST_SRC 216// if (hasCol(row, "RELATEDNAMES2")) { 217// String n = col(row, "RELATEDNAMES2"); 218// for (String s : n.split("\\;")) { 219// if (!Utilities.noString(s)) 220// dee.addAlias(s); 221// } 222// } 223// dee.addAlias(col(row, "SHORTNAME")); 224// // ORDER_OBS 225// // CDISC Code 226// // HL7_FIELD_SUBFIELD_ID 227// // ------------------ EXTERNAL_COPYRIGHT_NOTICE todo 228// dee.setDefinition(col(row, "LONG_COMMON_NAME")); 229// // HL7_V2_DATATYPE 230// String cc = makeType(col(row, "HL7_V3_DATATYPE"), code); 231// if (cc != null) 232// dee.addType().setCode(cc); 233// // todo... CURATED_RANGE_AND_UNITS 234// // todo: DOCUMENT_SECTION 235// // STATUS_REASON 236// // STATUS_TEXT 237// // CHANGE_REASON_PUBLIC 238// // COMMON_TEST_RANK 239// // COMMON_ORDER_RANK 240// // COMMON_SI_TEST_RANK 241// // HL7_ATTACHMENT_STRUCTURE 242// 243// // units: 244// // UNITSREQUIRED 245// // SUBMITTED_UNITS 246// ToolingExtensions.setAllowableUnits(dee, makeUnits(col(row, "EXAMPLE_UNITS"), col(row, "EXAMPLE_UCUM_UNITS"))); 247// // EXAMPLE_SI_UCUM_UNITS 248 249 row = XMLUtil.getNextSibling(row); 250 } 251 System.out.println("done"); 252 } 253 254 private String makeType(String type, String id) { 255 if (Utilities.noString(type)) 256 return null; 257 if (type.equals("PQ")) 258 return "Quantity"; 259 else if (type.equals("ED")) 260 return "Attachment"; 261 else if (type.equals("TS")) 262 return "dateTime"; 263 else if (type.equals("ST")) 264 return "string"; 265 else if (type.equals("II")) 266 return "Identifier"; 267 else if (type.equals("CWE")) 268 return "CodeableConcept"; 269 else if (type.equals("CD") || type.equals("CO")) 270 return "CodeableConcept"; 271 else if (type.equals("PN")) 272 return "HumanName"; 273 else if (type.equals("EN")) 274 return "HumanName"; 275 else if (type.equals("AD")) 276 return "Address"; 277 else if (type.equals("BL")) 278 return "boolean"; 279 else if (type.equals("GTS")) 280 return "Schedule"; 281 else if (type.equals("INT")) 282 return "integer"; 283 else if (type.equals("CS")) 284 return "code"; 285 else if (type.equals("IVL_TS")) 286 return "Period"; 287 else if (type.equals("MMAT") || type.equals("PRF") || type.equals("TX") || type.equals("DT") || type.equals("FT")) 288 return null; 289 else 290 throw new Error("unmapped type "+type+" for LOINC code "+id); 291 } // 18606-4: MMAT. 18665-0: PRF. 18671-8: TX. 55400-6: DT; 8251-1: FT 292 293 private CodeableConcept makeUnits(String text, String ucum) { 294 if (Utilities.noString(text) && Utilities.noString(ucum)) 295 return null; 296 CodeableConcept cc = new CodeableConcept(); 297 cc.setText(text); 298 cc.getCoding().add(new Coding().setCode(ucum).setSystem("http://unitsofmeasure.org")); 299 return cc; 300 } 301 public Bundle getBundle() { 302 return bundle; 303 } 304}