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