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