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