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