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