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