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