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