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