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