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}