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