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