001package org.hl7.fhir.convertors.misc;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.FileOutputStream;
006import java.io.IOException;
007import java.util.ArrayList;
008import java.util.HashSet;
009import java.util.Set;
010
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.context.CanonicalResourceManager;
013import org.hl7.fhir.r5.formats.IParser.OutputStyle;
014import org.hl7.fhir.r5.formats.JsonParser;
015import org.hl7.fhir.r5.formats.XmlParser;
016import org.hl7.fhir.r5.model.CanonicalResource;
017import org.hl7.fhir.r5.model.CodeSystem;
018import org.hl7.fhir.r5.model.ElementDefinition;
019import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
020import org.hl7.fhir.r5.model.StructureDefinition;
021import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
022import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
023import org.hl7.fhir.r5.model.ValueSet;
024import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
025import org.hl7.fhir.utilities.Utilities;
026import org.hl7.fhir.utilities.json.model.JsonObject;
027import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
028import org.hl7.fhir.utilities.npm.NpmPackage;
029import org.hl7.fhir.utilities.xml.XMLUtil;
030import org.w3c.dom.Element;
031
032public class ExtensionExtractor {
033
034  public static void main(String[] args) throws FHIRFormatError, FileNotFoundException, IOException {
035    new ExtensionExtractor().process(args[0]);
036  }
037
038  private void process(String dst) throws IOException {
039    Set<String> ids = new HashSet<>();
040    FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true);
041    NpmPackage r5 = pcm.loadPackage("hl7.fhir.r5.core",  "current");
042    CanonicalResourceManager<CodeSystem> cslist = new CanonicalResourceManager<CodeSystem>(true, false);
043    for (String r : r5.listResources("CodeSystem")) {
044      CodeSystem cs = (CodeSystem) new JsonParser().parse(r5.load(r));
045      cslist.see(cs, null);
046    }
047    CanonicalResourceManager<ValueSet> vslist = new CanonicalResourceManager<ValueSet>(true, false);
048    for (String r : r5.listResources("ValueSet")) {
049      ValueSet vs = (ValueSet) new JsonParser().parse(r5.load(r));
050      vslist.see(vs, null);
051    }
052    for (ValueSet vs : vslist.getList()) {
053      for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
054        CodeSystem cs = cslist.get(inc.getSystem());
055        if (cs != null) {
056          if (!cs.hasUserData("vsl")) {
057            cs.setUserData("vsl", new ArrayList<ValueSet>()); 
058          }
059          ((ArrayList<ValueSet>) cs.getUserData("vsl")).add(vs);
060        }
061      }
062    }
063    for (String r : r5.listResources("StructureDefinition")) {
064      StructureDefinition sd = (StructureDefinition) new JsonParser().parse(r5.load(r));
065      if (sd.getType().equals("Extension") && sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
066        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
067          seeBinding(ed.getBinding().getValueSet(), vslist, "ext", sd);
068          for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
069            seeBinding(ab.getValueSet(), vslist, "ext", sd);
070          }
071        }
072        sd.setSnapshot(null);
073        String fn;
074        if (sd.getContext().size() == 0) {
075          save(sd, dst,"none", ids);
076        } else if (sd.getContext().size() > 1) {
077          boolean dt = true;
078          for (StructureDefinitionContextComponent x : sd.getContext()) {
079            String s = extractType(x.getExpression());
080            dt = dt && isDataType(s);
081          }
082          if (dt) {
083            save(sd, dst,"datatypes", ids);
084          } else {
085            save(sd, dst,"multiple", ids);
086          }
087        } else {
088          String s = extractType(sd.getContextFirstRep().getExpression());
089          if (isDataType(s)) {
090            save(sd, dst,"datatypes", ids);
091          } else {
092            save(sd, dst,s, ids);
093          }
094        }
095      } else {
096        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
097          seeBinding(ed.getBinding().getValueSet(), vslist, "core", sd);
098          for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
099            seeBinding(ab.getValueSet(), vslist, "core", sd);
100          }
101        }
102      }
103    }
104    for (ValueSet vs : vslist.getList()) {
105      if (vs.hasUserData("core") || !vs.hasUserData("ext") || vs.getUrl().startsWith("http://terminology.")) {
106        vslist.drop(vs.getId());
107      }
108    }
109    for (CodeSystem cs : cslist.getList()) {
110      boolean keep = false;
111      if (cs.hasUserData("vsl") && !cs.getUrl().startsWith("http://terminology.")) {
112        keep = true;
113        for (ValueSet vs : (ArrayList<ValueSet>) cs.getUserData("vsl")) {
114          if (!vslist.has(vs.getUrl())) {
115            keep = false;
116          }
117        }
118      }
119      if (!keep) {
120        cslist.drop(cs.getId());
121      }
122    }
123    for (ValueSet vs : vslist.getList()) {
124      StructureDefinition sd = (StructureDefinition) vs.getUserData("ext");
125      String s = sd.getUserString("folder");
126      save(vs, dst, s, ids);
127    }
128    for (CodeSystem cs : cslist.getList()) {
129      ValueSet vs = ((ArrayList<ValueSet>) cs.getUserData("vsl")).get(0);
130      String s = vs.getUserString("folder");
131      save(cs, dst,s, ids);
132    }
133    
134    deleteMatchingResources(ids, new File("/Users/grahamegrieve/work/r5/source"));
135  }
136
137  private void deleteMatchingResources(Set<String> ids, File folder) {
138    for (File f : folder.listFiles()) {
139      if (f.isDirectory()) {
140        deleteMatchingResources(ids, f);
141      } else if (f.getName().endsWith(".json")) {
142        try {
143          JsonObject json = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(f);
144          if (json.has("resourceType") && json.has("id") && ids.contains(json.asString("id"))) {
145            System.out.println("Delete "+f.getAbsolutePath());
146            f.delete();
147          }
148        } catch (Exception e) {
149          // nothing
150        }
151      } else if (f.getName().endsWith(".xml")) {
152        try {
153          Element xml = XMLUtil.parseFileToDom(f.getAbsolutePath()).getDocumentElement();
154          if (XMLUtil.hasNamedChild(xml, "id") && ids.contains(XMLUtil.getNamedChildValue(xml, "id"))) {
155            System.out.println("Delete "+f.getAbsolutePath());
156            f.delete();
157          }
158        } catch (Exception e) {
159          // nothing
160        }
161      }
162    }
163    
164  }
165
166  private void save(CanonicalResource cr, String dst, String folder, Set<String> ids) throws IOException {
167    // TODO Auto-generated method stub
168    cr.setText(null);
169    if (!cr.hasTitle()) {
170      cr.setTitle(Utilities.unCamelCase(cr.getName()));
171    }
172    ids.add(cr.getId());
173    String fn = Utilities.path(dst, folder, cr.fhirType()+"-"+cr.getId()+".xml"); 
174    cr.setUserData("folder", folder);
175    if (!new File(fn).exists()) {
176      Utilities.createDirectory(Utilities.getDirectoryForFile(fn));
177      new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(fn), cr);
178    }
179  }
180
181  private void seeBinding(String url, CanonicalResourceManager<ValueSet> vslist, String name, StructureDefinition sd) {
182    ValueSet vs = vslist.get(url);
183    if (vs != null) { 
184      vs.setUserData(name, sd);
185    }    
186  }
187
188  private boolean isDataType(String s) {
189    return Utilities.existsInList(s,
190        "PrimitiveType", "instant", "time", "date", "dateTime", "decimal", "boolean", "integer", "string",
191        "uri", "base64Binary", "code", "id", "oid", "unsignedInt", "positiveInt", "markdown", "url", "canonical",
192        "uuid", "integer64", "DataType", "BackboneType", "Identifier", "HumanName", "Address", "ContactPoint",
193        "Timing", "Quantity", "SimpleQuantity", "Attachment", "Range", "Period", "Ratio", "RatioRange", "CodeableConcept",
194        "Coding", "SampledData", "Age", "Distance", "Duration", "Count", "Money", "MoneyQuantity", "Annotation", "Signature", "DataType",
195        "ContactDetail", "Contributor", "DataRequirement", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext",
196        "Expression", "ExtendedContactDetail", "VirtualServiceDetail", "Availability", "MonetaryComponent", "DataType",
197        "BackboneType", "Reference", "Narrative", "Extension", "Meta", "ElementDefinition", "Dosage", "xhtml", "CodeableReference");
198  }
199
200  private String extractType(String x) {
201    String s = x;
202    if (s.contains(".")) {
203      s = s.substring(0, s.indexOf("."));
204    }
205    return s;
206  }
207}