001package org.hl7.fhir.r4.terminologies; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.io.FileInputStream; 033import java.io.FileNotFoundException; 034import java.io.FileOutputStream; 035import java.io.IOException; 036 037import javax.xml.parsers.DocumentBuilder; 038import javax.xml.parsers.DocumentBuilderFactory; 039import javax.xml.parsers.ParserConfigurationException; 040 041import org.hl7.fhir.exceptions.FHIRFormatError; 042import org.hl7.fhir.r4.formats.XmlParser; 043import org.hl7.fhir.r4.model.Bundle; 044import org.hl7.fhir.r4.model.Bundle.BundleType; 045import org.hl7.fhir.r4.model.CodeableConcept; 046import org.hl7.fhir.r4.model.Coding; 047import org.hl7.fhir.r4.model.DateTimeType; 048import org.hl7.fhir.r4.model.InstantType; 049import org.hl7.fhir.r4.model.Meta; 050import org.hl7.fhir.utilities.Utilities; 051import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 052import org.hl7.fhir.utilities.xml.XMLUtil; 053import org.w3c.dom.Document; 054import org.w3c.dom.Element; 055import org.xml.sax.SAXException; 056import org.xmlpull.v1.XmlPullParserException; 057 058/** 059 * This class converts the LOINC XML representation that the FHIR build tool 060 * uses internally to a set of DataElements in an atom feed 061 * 062 * @author Grahame 063 * 064 */ 065public class LoincToDEConvertor { 066 067 public static void main(String[] args) 068 throws FHIRFormatError, IOException, XmlPullParserException, SAXException, ParserConfigurationException { 069 if (args.length == 0) { 070 System.out.println("FHIR LOINC to CDE convertor. "); 071 System.out.println(""); 072 System.out.println("This tool converts from LOINC to A set of DataElement definitions."); 073 System.out.println(""); 074 System.out.println("Usage: [jar(path?)] [dest] (-defn [definitions]) where: "); 075 System.out.println("* [dest] is a file name of the bundle to produce"); 076 System.out.println( 077 "* [definitions] is the file name of a file produced by exporting the main LOINC table from the mdb to XML"); 078 System.out.println(""); 079 } else { 080 LoincToDEConvertor exe = new LoincToDEConvertor(); 081 exe.setDest(args[0]); 082 for (int i = 1; i < args.length; i++) { 083 if (args[i].equals("-defn")) 084 exe.setDefinitions(args[i + 1]); 085 } 086 exe.process(); 087 } 088 089 } 090 091 private String dest; 092 private String definitions; 093 094 public String getDest() { 095 return dest; 096 } 097 098 public void setDest(String dest) { 099 this.dest = dest; 100 } 101 102 public String getDefinitions() { 103 return definitions; 104 } 105 106 public void setDefinitions(String definitions) { 107 this.definitions = definitions; 108 } 109 110 private Document xml; 111 private Bundle bundle; 112 private DateTimeType now; 113 114 public Bundle process(String sourceFile) 115 throws FileNotFoundException, SAXException, IOException, ParserConfigurationException { 116 this.definitions = sourceFile; 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.setType(BundleType.COLLECTION); 125 bundle.setId("http://hl7.org/fhir/commondataelement/loinc"); 126 bundle.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 127 128 processLoincCodes(); 129 return bundle; 130 } 131 132 public void process() 133 throws FHIRFormatError, IOException, XmlPullParserException, SAXException, ParserConfigurationException { 134 log("Begin. Produce Loinc CDEs in " + dest + " from " + definitions); 135 loadLoinc(); 136 log("LOINC loaded"); 137 138 now = DateTimeType.now(); 139 140 bundle = new Bundle(); 141 bundle.setId("http://hl7.org/fhir/commondataelement/loinc"); 142 bundle.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 143 144 processLoincCodes(); 145 if (dest != null) { 146 log("Saving..."); 147 saveBundle(); 148 } 149 log("Done"); 150 151 } 152 153 private void log(String string) { 154 System.out.println(string); 155 156 } 157 158 private void loadLoinc() throws FileNotFoundException, SAXException, IOException, ParserConfigurationException { 159 DocumentBuilderFactory factory = XMLUtil.newXXEProtectedDocumentBuilderFactory(); 160 factory.setNamespaceAware(true); 161 DocumentBuilder builder = factory.newDocumentBuilder(); 162 163 xml = builder.parse(ManagedFileAccess.inStream(definitions)); 164 } 165 166 private void saveBundle() throws FHIRFormatError, IOException, XmlPullParserException { 167 XmlParser xml = new XmlParser(); 168 FileOutputStream s = ManagedFileAccess.outStream(dest); 169 xml.compose(s, bundle, true); 170 s.close(); 171 } 172 173 private String col(Element row, String name) { 174 Element e = XMLUtil.getNamedChild(row, name); 175 if (e == null) 176 return null; 177 String text = e.getTextContent(); 178 return text; 179 } 180 181 private boolean hasCol(Element row, String name) { 182 return Utilities.noString(col(row, name)); 183 } 184 185 private void processLoincCodes() { 186 Element row = XMLUtil.getFirstChild(xml.getDocumentElement()); 187 int i = 0; 188 while (row != null) { 189 i++; 190 if (i % 1000 == 0) 191 System.out.print("."); 192 String code = col(row, "LOINC_NUM"); 193 String comp = col(row, "COMPONENT"); 194// DataElement de = new DataElement(); 195// de.setId("loinc-"+code); 196// de.setMeta(new Meta().setLastUpdatedElement(InstantType.now())); 197// bundle.getEntry().add(new BundleEntryComponent().setResource(de)); 198// Identifier id = new Identifier(); 199// id.setSystem("http://hl7.org/fhir/commondataelement/loinc"); 200// id.setValue(code); 201// de.addIdentifier(id); 202// de.setPublisher("Regenstrief + FHIR Project Team"); 203// if (!col(row, "STATUS").equals("ACTIVE")) 204// de.setStatus(PublicationStatus.DRAFT); // till we get good at this 205// else 206// de.setStatus(PublicationStatus.RETIRED); 207// de.setDateElement(DateTimeType.now()); 208// de.setName(comp); 209// ElementDefinition dee = de.addElement(); 210// 211// // PROPERTY ignore 212// // TIME_ASPCT 213// // SYSTEM 214// // SCALE_TYP 215// // METHOD_TYP 216// // dee.getCategory().add(new CodeableConcept().setText(col(row, "CLASS"))); 217// // SOURCE 218// // DATE_LAST_CHANGED - should be in ? 219// // CHNG_TYPE 220// dee.setComment(col(row , "COMMENTS")); 221// if (hasCol(row, "CONSUMER_NAME")) 222// dee.addAlias(col(row, "CONSUMER_NAME")); 223// // MOLAR_MASS 224// // CLASSTYPE 225// // FORMULA 226// // SPECIES 227// // EXMPL_ANSWERS 228// // ACSSYM 229// // BASE_NAME - ? this is a relationship 230// // NAACCR_ID 231// // ---------- CODE_TABLE todo 232// // SURVEY_QUEST_TEXT 233// // SURVEY_QUEST_SRC 234// if (hasCol(row, "RELATEDNAMES2")) { 235// String n = col(row, "RELATEDNAMES2"); 236// for (String s : n.split("\\;")) { 237// if (!Utilities.noString(s)) 238// dee.addAlias(s); 239// } 240// } 241// dee.addAlias(col(row, "SHORTNAME")); 242// // ORDER_OBS 243// // CDISC Code 244// // HL7_FIELD_SUBFIELD_ID 245// // ------------------ EXTERNAL_COPYRIGHT_NOTICE todo 246// dee.setDefinition(col(row, "LONG_COMMON_NAME")); 247// // HL7_V2_DATATYPE 248// String cc = makeType(col(row, "HL7_V3_DATATYPE"), code); 249// if (cc != null) 250// dee.addType().setCode(cc); 251// // todo... CURATED_RANGE_AND_UNITS 252// // todo: DOCUMENT_SECTION 253// // STATUS_REASON 254// // STATUS_TEXT 255// // CHANGE_REASON_PUBLIC 256// // COMMON_TEST_RANK 257// // COMMON_ORDER_RANK 258// // COMMON_SI_TEST_RANK 259// // HL7_ATTACHMENT_STRUCTURE 260// 261// // units: 262// // UNITSREQUIRED 263// // SUBMITTED_UNITS 264// ToolingExtensions.setAllowableUnits(dee, makeUnits(col(row, "EXAMPLE_UNITS"), col(row, "EXAMPLE_UCUM_UNITS"))); 265// // EXAMPLE_SI_UCUM_UNITS 266 267 row = XMLUtil.getNextSibling(row); 268 } 269 System.out.println("done"); 270 } 271 272 private String makeType(String type, String id) { 273 if (Utilities.noString(type)) 274 return null; 275 if (type.equals("PQ")) 276 return "Quantity"; 277 else if (type.equals("ED")) 278 return "Attachment"; 279 else if (type.equals("TS")) 280 return "dateTime"; 281 else if (type.equals("ST")) 282 return "string"; 283 else if (type.equals("II")) 284 return "Identifier"; 285 else if (type.equals("CWE")) 286 return "CodeableConcept"; 287 else if (type.equals("CD") || type.equals("CO")) 288 return "CodeableConcept"; 289 else if (type.equals("PN")) 290 return "HumanName"; 291 else if (type.equals("EN")) 292 return "HumanName"; 293 else if (type.equals("AD")) 294 return "Address"; 295 else if (type.equals("BL")) 296 return "boolean"; 297 else if (type.equals("GTS")) 298 return "Schedule"; 299 else if (type.equals("INT")) 300 return "integer"; 301 else if (type.equals("CS")) 302 return "code"; 303 else if (type.equals("IVL_TS")) 304 return "Period"; 305 else if (type.equals("MMAT") || type.equals("PRF") || type.equals("TX") || type.equals("DT") || type.equals("FT")) 306 return null; 307 else 308 throw new Error("unmapped type " + type + " for LOINC code " + id); 309 } // 18606-4: MMAT. 18665-0: PRF. 18671-8: TX. 55400-6: DT; 8251-1: FT 310 311 private CodeableConcept makeUnits(String text, String ucum) { 312 if (Utilities.noString(text) && Utilities.noString(ucum)) 313 return null; 314 CodeableConcept cc = new CodeableConcept(); 315 cc.setText(text); 316 cc.getCoding().add(new Coding().setCode(ucum).setSystem("http://unitsofmeasure.org")); 317 return cc; 318 } 319 320 public Bundle getBundle() { 321 return bundle; 322 } 323}