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