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