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