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