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