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