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