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