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