From 981d201046b22d2b50a111476751303e129bc466 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 25 Feb 2024 19:12:16 +1100 Subject: [PATCH] WIP: major refactor of cross version analysis --- .../misc/XVerExtensionPackageGenerator.java | 2217 ++++++++++------- 1 file changed, 1366 insertions(+), 851 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerExtensionPackageGenerator.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerExtensionPackageGenerator.java index b69333169..0cc11b69e 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerExtensionPackageGenerator.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/XVerExtensionPackageGenerator.java @@ -13,100 +13,270 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; -import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.terminologies.ConceptMapUtilities; -import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.TranslatedCode; -import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.FhirPublication; -import org.hl7.fhir.utilities.StringPair; -import org.hl7.fhir.utilities.TextFile; -import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.VersionUtilities; -import org.hl7.fhir.utilities.json.JsonException; -import org.hl7.fhir.utilities.json.model.JsonObject; -import org.hl7.fhir.utilities.json.model.JsonProperty; -import org.hl7.fhir.utilities.json.parser.JsonParser; -import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; -import org.hl7.fhir.utilities.npm.NpmPackage; -import org.hl7.fhir.utilities.xhtml.NodeType; -import org.hl7.fhir.utilities.xhtml.XhtmlComposer; -import org.hl7.fhir.utilities.xhtml.XhtmlNode; -import org.checkerframework.checker.units.qual.s; +import org.fhir.ucum.Concept; import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50; -import org.hl7.fhir.convertors.misc.XVerExtensionPackageGenerator.ElementDefinitionPair; -import org.hl7.fhir.convertors.misc.XVerExtensionPackageGenerator.SourcedStructureDefinition; -import org.hl7.fhir.convertors.misc.XVerExtensionPackageGenerator.TheChainSorter; -import org.hl7.fhir.convertors.misc.XVerExtensionPackageGenerator.TranslatePack; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.ConceptMap; import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; +import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode; import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.Enumerations.BindingStrength; +import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; -import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; -import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.renderers.ConceptMapRenderer; +import org.hl7.fhir.r5.renderers.ConceptMapRenderer.IConceptMapInformationProvider; +import org.hl7.fhir.r5.renderers.ConceptMapRenderer.RenderMultiRowSortPolicy; +import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.r5.terminologies.ConceptMapUtilities; +import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.TranslatedCode; +import org.hl7.fhir.r5.terminologies.ValueSetUtilities; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.DebugUtilities; +import org.hl7.fhir.utilities.FhirPublication; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; +import org.hl7.fhir.utilities.npm.NpmPackage; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlComposer; +import org.hl7.fhir.utilities.xhtml.XhtmlDocument; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; -public class XVerExtensionPackageGenerator { +import net.sourceforge.plantuml.abel.Link; - public static class ElementDefinitionPair { +/** + * This class runs as a pre-compile step for the xversion IG + * + * It takes one parameter, the root directory of the cross version IG git repo. + * It loads R2-R5 definitions, reads all the conceptmaps in the IG source, and then + * generates the following: + * + * - page fragments in input/includes with HTML summaries of the content + * - extension definitions in input/extensions + * - + */ +public class XVerExtensionPackageGenerator implements IConceptMapInformationProvider { + public class ColumnEntry { + + + private ElementDefinitionLink link; + private ElementDefinition ed; + + public ColumnEntry(ElementDefinitionLink link, ElementDefinition ed) { + this.link = link; + this.ed = ed; + } + + } + + public class ColumnSorter implements Comparator { + + @Override + public int compare(StructureDefinitionColumn o1, StructureDefinitionColumn o2) { + String s1 = o1.sd.getFhirVersion().toCode()+":"+o1.sd.getName(); + String s2 = o2.sd.getFhirVersion().toCode()+":"+o2.sd.getName(); + return s1.compareTo(s2); + } + + } + + public class SourcedElementDefinitionSorter implements Comparator { + + @Override + public int compare(SourcedElementDefinition o1, SourcedElementDefinition o2) { + String s1 = o1.toString(); + String s2 = o2.toString(); + return s1.compareTo(s2); + } + + } + + public enum MakeLinkMode { + INWARD, OUTWARD, CHAIN, ORIGIN_CHAIN + + } + + public enum XVersions { + VER_2_3, VER_3_4, VER_4_4B, VER_4B_5; + } + + private static class ElementDefinitionPair { + ElementDefinition focus; + ElementDefinition anchor; + protected ElementDefinitionPair(ElementDefinition focus, ElementDefinition anchor) { + super(); + this.focus = focus; + this.anchor = anchor; + } + + } + + private static class StructureDefinitionColumn { + public StructureDefinitionColumn(StructureDefinition sd, boolean root) { + this.sd = sd; + this.root = root; + } private StructureDefinition sd; + private boolean root; + public List elements = new ArrayList<>(); + public List entries = new ArrayList(); + + public void clear() { + entries.clear(); + } + + public int rowCount() { + int c = 0; + for (ColumnEntry entry : entries) { + if (entry.link == null) { + c = c + 1; + } else { + c = c + entry.link.leftWidth; + } + } + return c; + } + + } + + public static class CodeChainsSorter implements Comparator> { + + @Override + public int compare(List o1, List o2) { + + return maxSize(o1) - maxSize(o2); + } + + private int maxSize(List list) { + + int i = 0; + for (ElementDefinitionLink link : list) { + if (link.nextCM != null) { + i = Integer.max(i, ConceptMapUtilities.mapCount(link.nextCM)); + } + } + return i; + } + + } + + public class VSPair { + + private ValueSet vs; + private CodeSystem cs; + private String version; + + public VSPair(String version, ValueSet vs, CodeSystem cs) { + this.version = version; + this.vs = vs; + this.cs = cs; + } + + public String getVersion() { + return version; + } + + public ValueSet getVs() { + return vs; + } + + public CodeSystem getCs() { + return cs; + } + } + + public static class MatchedElementDefinition { + private ElementDefinition ed; private ConceptMapRelationship rel; + + public MatchedElementDefinition(ElementDefinition ed, ConceptMapRelationship rel) { + this.ed = ed; + this.rel = rel; + } + + } + + public static class SourcedElementDefinition { + private StructureDefinition sd; + private ElementDefinition ed; + private boolean valid; private String statusReason; private String ver; private String startVer; private String stopVer; private String verList; - private ElementDefinitionPair repeater; + private SourcedElementDefinition repeater; - public ElementDefinitionPair(StructureDefinition sd, ElementDefinition ed, ConceptMapRelationship relationship) { + public SourcedElementDefinition(StructureDefinition sd, ElementDefinition ed) { this.sd = sd; - this.ed = ed; - this.rel = relationship; + this.ed = ed; this.ver = sd.getFhirVersion().toCode(); } + + @Override + public String toString() { + return ed.getPath()+" ("+sd.getFhirVersion().toCode()+")"; + } } - public static class TheChainSorter implements Comparator { + public static class ElementDefinitionLink { + private XVersions versions; + private ConceptMapRelationship rel; + private SourcedElementDefinition next; + private SourcedElementDefinition prev; + private ConceptMap nextCM; + private ConceptMap prevCM; + private int leftWidth; + private Set chainIds = new HashSet<>(); @Override - public int compare(ElementChain o1, ElementChain o2) { - return o1.elements.get(0).ed.getPath().compareTo(o2.elements.get(0).ed.getPath()); + public String toString() { + return versions+": "+prev.toString()+" "+rel.getSymbol()+" "+next.toString()+" ["+chainIds.toString()+"]"; } } public class SourcedStructureDefinition { - private TranslatePack tp; + private VersionDefinitions definitions; private StructureDefinition structureDefinition; private ConceptMapRelationship relationship; - protected SourcedStructureDefinition(TranslatePack tp, StructureDefinition structureDefinition, ConceptMapRelationship relationship) { + protected SourcedStructureDefinition(VersionDefinitions definitions, StructureDefinition structureDefinition, ConceptMapRelationship relationship) { super(); - this.tp = tp; + this.definitions = definitions; this.structureDefinition = structureDefinition; this.relationship = relationship; } - public TranslatePack getTp() { - return tp; + public VersionDefinitions getDefinitions() { + return definitions; } public StructureDefinition getStructureDefinition() { return structureDefinition; @@ -117,106 +287,11 @@ public class XVerExtensionPackageGenerator { } - public class TranslatePack { - - private String fn; - private VersionDefinitions definitions; - private ConceptMap resMap; - private ConceptMap elementMap; - public boolean buildTheChain; - - protected TranslatePack(String fn, VersionDefinitions definitions, ConceptMap resMap, ConceptMap elementMap, boolean buildTheChain) { - super(); - this.fn = fn; - this.definitions = definitions; - this.resMap = resMap; - this.elementMap = elementMap; - this.buildTheChain = buildTheChain; - } - public String getFn() { - return fn; - } - public VersionDefinitions getDefinitions() { - return definitions; - } - public ConceptMap getResMap() { - return resMap; - } - public ConceptMap getElementMap() { - return elementMap; - } - } - public static class ElementChain { private int id; - private List elements = new ArrayList(); - public ElementChain(int id, StructureDefinition sd, ElementDefinition ed) { - super(); - this.id = id; - elements.add(new ElementDefinitionPair(sd, ed, null)); - } - public String summary() { - CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (ElementDefinitionPair p : elements) { - String s = VersionUtilities.getNameForVersion(p.ver); - if (p.rel == null) { - b.append(s); - } else { - switch (p.rel) { - case EQUIVALENT: - b.append("="+s); - break; - case NOTRELATEDTO: - b.append("!="+s); - break; - case RELATEDTO: - b.append("!"+s); - break; - case SOURCEISBROADERTHANTARGET: - b.append(">"+s); - break; - case SOURCEISNARROWERTHANTARGET: - b.append("<"+s); - break; - default: - b.append(s); - break; - } - } - } - return elements.get(0).ed.getPath()+" ("+b.toString()+")"; - } - public boolean hasAllVersions() { - boolean r2 = false; - boolean r3 = false; - boolean r4 = false; - boolean r4b = false; - boolean r5 = false; - for (ElementDefinitionPair p : elements) { - switch (p.ver) { - case "1.0.2" : r2 = true; break; - case "3.0.2" : r3 = true; break; - case "4.0.1" : r4 = true; break; - case "4.3.0" : r4b = true; break; - case "5.0.0" : r5 = true; break; - default: - throw new Error("What? "+p.ver); - } - } - return r2 && r3 && r4 && r4b && r5; - } - public String id() { - return elements.get(0).ed.getPath(); - } - public boolean hasValid() { + private String name; // debugging only + private List links = new ArrayList(); - for (ElementDefinitionPair p : elements) { - if (p.valid) { - return true; - } - } - return false; - } } public static class VersionDefinitions { @@ -227,8 +302,14 @@ public class XVerExtensionPackageGenerator { public void add(CanonicalResource cr) { if (cr instanceof CodeSystem) { codeSystems.put(cr.getUrl(), (CodeSystem) cr); + if (cr.hasVersion()) { + codeSystems.put(cr.getUrl()+"|"+cr.getVersion(), (CodeSystem) cr); + } } else if (cr instanceof ValueSet) { valueSets.put(cr.getUrl(), (ValueSet) cr); + if (cr.hasVersion()) { + valueSets.put(cr.getUrl()+"|"+cr.getVersion(), (ValueSet) cr); + } } else if (cr instanceof StructureDefinition) { structures.put(cr.getName(), (StructureDefinition) cr); } @@ -238,83 +319,68 @@ public class XVerExtensionPackageGenerator { } } + private static final boolean OUT = false; + private static final boolean IN = true; + public static void main(String[] args) throws Exception { new XVerExtensionPackageGenerator().execute(args); } private List chains = new ArrayList<>(); private Map versions = new HashMap<>(); + private Map conceptMap = new HashMap<>(); + private VersionDefinitions vdr2; + private VersionDefinitions vdr3; + private VersionDefinitions vdr4; + private VersionDefinitions vdr4b; + private VersionDefinitions vdr5; + private List allLinks = new ArrayList<>(); + private List terminatingElements = new ArrayList<>(); + private List origins = new ArrayList<>(); private void execute(String[] args) throws FHIRException, IOException { - FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build(); - VersionDefinitions vdr2 = loadR2(pcm); - VersionDefinitions vdr3 = loadR3(pcm); - VersionDefinitions vdr4 = loadR4(pcm); - VersionDefinitions vdr4b = loadR4B(pcm); - VersionDefinitions vdr5 = loadR5(pcm); + loadVersions(); + loadConceptMaps(args[0]); - versions.put("r2", vdr2); - versions.put("r3", vdr3); - versions.put("r4", vdr4); - versions.put("r4b", vdr4b); - versions.put("r5", vdr5); + System.out.println("Checking Maps"); + // 1. sanity check on resource and element maps + checkMaps(); - ConceptMap cmr23 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap2to3.json"))); - ConceptMap cmr32 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap3to2.json"))); - ConceptMap cmr34 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap3to4.json"))); - ConceptMap cmr43 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap4to3.json"))); - ConceptMap cmr45 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap4to5.json"))); - ConceptMap cmr54 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap5to4.json"))); - ConceptMap cmr44b = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap4to4B.json"))); - ConceptMap cmr4b4 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap4Bto4.json"))); - ConceptMap cmr54b = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap5to4B.json"))); - ConceptMap cmr4b5 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap4Bto5.json"))); - ConceptMap cme23 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap2to3.json"))); - ConceptMap cme32 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap3to2.json"))); - ConceptMap cme34 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap3to4.json"))); - ConceptMap cme43 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap4to3.json"))); - ConceptMap cme45 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap4to5.json"))); - ConceptMap cme54 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap5to4.json"))); - ConceptMap cme44b = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap4to4B.json"))); - ConceptMap cme4b4 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap4Bto4.json"))); - ConceptMap cme4b5 = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap4Bto5.json"))); - ConceptMap cme54b = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap5to4B.json"))); - ConceptMap cmr53 = ConceptMapUtilities.collapse("resourcemap5to3", "http://hl7.org/fhir/interversion/resourcemap5to3", false, cmr54, cmr43); - new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(args[0], "R5-ConceptMap-resourcemap5to3.json")), cmr53); - ConceptMap cmr52 = ConceptMapUtilities.collapse("resourcemap5to2", "http://hl7.org/fhir/interversion/resourcemap5to2", false, cmr54, cmr43, cmr32); - ConceptMap cme53 = ConceptMapUtilities.collapse("elementmap5to3", "http://hl7.org/fhir/interversion/elementmap5to3", true, cme54, cme43); - new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(args[0], "R5-ConceptMap-elementmap5to3.json")), cme53); - ConceptMap cme52 = ConceptMapUtilities.collapse("elementmap5to2", "http://hl7.org/fhir/interversion/elementmap5to2", true, cme54, cme43, cme32); + System.out.println("Building Links"); + // 2. build all the links. At the end, chains are all the terminating elements + buildLinks(XVersions.VER_2_3, vdr2, cm("resources-2to3"), cm("elements-2to3"), vdr3); + buildLinks(XVersions.VER_3_4, vdr3, cm("resources-3to4"), cm("elements-3to4"), vdr4); + buildLinks(XVersions.VER_4_4B, vdr4, cm("resources-4to4b"), cm("elements-4to4b"), vdr4b); + buildLinks(XVersions.VER_4B_5, vdr4b, cm("resources-4bto5"), cm("elements-4bto5"), vdr5); - // - // for (File f : new File(args[0]).listFiles()) { - // if (f.getName().startsWith("R5-ConceptMap-")) { - // System.out.println("Load "+f.getAbsolutePath()); - // nameMaps.add( - // } - // } + System.out.println("Building Chains"); + findTerminalElements(); + for (SourcedElementDefinition te : terminatingElements) { + identifyChain(te); + } + checkAllLinksInChains(); + System.out.println(""+terminatingElements.size()+" terminating elements found"); - // first, generate html rendition of the maps - genVersionHtml(args[1], "r2", vdr2, new TranslatePack("r3", vdr3, cmr23, cme23, true)); - genVersionHtml(args[1], "r3", vdr3, new TranslatePack("r2", vdr2, cmr32, cme32, false), new TranslatePack("r4", vdr4, cmr34, cme34, true)); - genVersionHtml(args[1], "r4", vdr4, new TranslatePack("r3", vdr3, cmr43, cme43, false), new TranslatePack("r4b", vdr4b, cmr44b, cme44b, true), new TranslatePack("r5", vdr5, cmr45, cme45, true)); - genVersionHtml(args[1], "r4b", vdr4b, new TranslatePack("r4", vdr4, cmr4b4, cme4b4, false), new TranslatePack("r5", vdr5, cmr4b5, cme4b5, false)); - genVersionHtml(args[1], "r5", vdr5, new TranslatePack("r4", vdr4, cmr54, cme54, false), new TranslatePack("r4b", vdr4b, cmr54b, cme54b, false)); - genVersionHtml(args[1], "r5x", vdr5, - new TranslatePack("r4b", vdr4b, cmr54b, cme54b, false), - new TranslatePack("r4", vdr4, cmr54, cme54, false), - new TranslatePack("r3", vdr3, cmr53, cme53, false), - new TranslatePack("r2", vdr2, cmr52, cme52, false)); + for (SourcedElementDefinition te : terminatingElements) { + scanChainElements(te); + } - for (ElementChain chain : chains) { - if (chain.elements.get(0).ed.getPath().contains(".")) { - scanChainElements(chain.elements); + Collections.sort(origins, new SourcedElementDefinitionSorter()); + genChainsHtml(args[0], "cross-version-chains-all", false, false); + genChainsHtml(args[0], "cross-version-chains-valid", true, false); + genChainsHtml(args[0], "cross-version-chains-min", true, true); + + for (String name : Utilities.sorted(vdr5.structures.keySet())) { + StructureDefinition sd = vdr5.structures.get(name); + if ((sd.getKind() == StructureDefinitionKind.COMPLEXTYPE || sd.getKind() == StructureDefinitionKind.RESOURCE) && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { + genVersionType(args[0], sd); } } - genChainsHtml(args[1], "chains-all", false, false); - genChainsHtml(args[1], "chains-valid", true, false); - genChainsHtml(args[1], "chains-min", true, true); + // System.out.println("Done"); + + + genSummaryPage(args[0]); // for (VersionDefinitions src : versions) { // for (VersionDefinitions dst : versions) { // if (src != dst) { @@ -327,107 +393,337 @@ public class XVerExtensionPackageGenerator { // checkCM(cm); // } + // for (ConceptMap cm : conceptMap.values()) { + // if (cm.hasUserData("cm.used") && "false".equals(cm.getUserString("cm.used"))) { + // if (!cm.getId().contains("4to5") && !cm.getId().contains("5to4")) { + // System.out.println("Unused conceptmap: "+cm.getId()); + // } + // } + // } System.out.println("Finished"); + } + + private void findTerminalElements() { + // At this point, the only listed terminal elements are any elements that never had any links at all + // check that + for (SourcedElementDefinition te : terminatingElements) { + List links = makeEDLinks(te, MakeLinkMode.OUTWARD); + if (links.size() > 0) { + throw new Error("Logic error - "+te.toString()+" has outbound links"); + } + } + for (ElementDefinitionLink link : allLinks) { + SourcedElementDefinition tgt = link.next; + List links = makeEDLinks(tgt.ed, MakeLinkMode.OUTWARD); + if (links.size() == 0 && !terminatingElements.contains(tgt)) { + terminatingElements.add(tgt); + } + } + } + + + private void checkAllLinksInChains() { + for (ElementDefinitionLink link : allLinks) { + if (link.chainIds.isEmpty()) { + System.out.println("Link not in chain: "+link.toString()); + } + } + } + + + private void identifyChain(SourcedElementDefinition te) { + String id = VersionUtilities.getNameForVersion(te.sd.getFhirVersion().toCode())+"."+te.ed.getPath(); + + Queue processList = new ConcurrentLinkedQueue(); + List processed = makeEDLinks(te.ed, MakeLinkMode.CHAIN); + for (ElementDefinitionLink link : makeEDLinks(te.ed, MakeLinkMode.INWARD)) { + processList.add(link); + + link.leftWidth = findLeftWidth(link.prev); + } + while (!processList.isEmpty()) { + ElementDefinitionLink link = processList.remove(); + processed.add(link); + link.chainIds.add(id); + for (ElementDefinitionLink olink : makeEDLinks(link.prev.ed, MakeLinkMode.INWARD)) { + if (!processed.contains(olink)) { + processList.add(olink); + } + } + } + } + + + private int findLeftWidth(SourcedElementDefinition node) { + List links = makeEDLinks(node, MakeLinkMode.INWARD); + if (links.size() == 0) { + return 1; // just this entry + } else { + // we group incoming links by source. + Map counts = new HashMap<>(); + for (ElementDefinitionLink link : links) { + Integer c = counts.get(link.prev.sd); + if (c == null) { + c = 0; + } + //if (link.leftWidth == 0) { + link.leftWidth = findLeftWidth(link.prev); + //} + c = c + link.leftWidth; + counts.put(link.prev.sd, c); + } + int res = 1; + for (Integer c : counts.values()) { + res = Integer.max(res, c); + } + return res; + } + } + + + private void checkMaps() throws FileNotFoundException, IOException { + checkMapsReciprocal(cm("resources-2to3"), cm("resources-3to2"), "resources"); + checkMapsReciprocal(cm("resources-3to4"), cm("resources-4to3"), "resources"); + checkMapsReciprocal(cm("resources-4to4b"), cm("resources-4bto4"), "resources"); + checkMapsReciprocal(cm("resources-4bto5"), cm("resources-5to4b"), "resources"); + checkMapsReciprocal(cm("elements-2to3"), cm("elements-3to2"), "elements"); + checkMapsReciprocal(cm("elements-3to4"), cm("elements-4to3"), "elements"); + checkMapsReciprocal(cm("elements-4to4b"), cm("elements-4bto4"), "elements"); + checkMapsReciprocal(cm("elements-4bto5"), cm("elements-5to4b"), "elements"); + } + + + private void checkMapsReciprocal(ConceptMap left, ConceptMap right, String folder) throws FileNotFoundException, IOException { + List issues = new ArrayList(); + if (ConceptMapUtilities.checkReciprocal(left, right, issues)) { + // wipes formatting in files + // new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/"+folder+"/ConceptMap-"+left.getId()+".json"), left); + // new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/"+folder+"/ConceptMap-"+right.getId()+".json"), right); + } + if (!issues.isEmpty()) { + System.out.println("Found issues checking reciprocity of "+left.getId()+" and "+right.getId()); + for (String s : issues) { + System.out.println(" "+s); + } + } + } + + + private void genSummaryPage(String path) throws IOException { + List maps = new ArrayList<>(); + maps.add(cm("resources-2to3")); + maps.add(cm("resources-3to4")); + maps.add(cm("resources-4to4b")); + maps.add(cm("resources-4bto5")); + XhtmlNode page = ConceptMapRenderer.renderMultipleMaps("Resource", maps, this, RenderMultiRowSortPolicy.FIRST_COL); + + TextFile.stringToFile(new XhtmlComposer(false, false).compose(page), Utilities.path(path, "input", "includes", "cross-version-summary.xhtml")); + TextFile.stringToFile(new XhtmlComposer(false, false).compose(wrapPage(page, "Resource Map")), Utilities.path(path, "input", "qa", "cross-version-summary.html")); + + } + + private boolean isResourceTypeMap(ConceptMap cm) { + if (cm.getGroup().size() != 1) { + return false; + } + return cm.getGroupFirstRep().getSource().contains("resource-type") || cm.getGroupFirstRep().getTarget().contains("resource-type"); + } + + private ConceptMap cm(String name) { + ConceptMap cm = conceptMap.get(name); + if (cm == null) { + throw new Error("Concept Map "+name+" not found"); + } + return cm; + } + + private void loadConceptMaps(String dir) throws FHIRFormatError, FileNotFoundException, IOException { + loadConceptMaps(new File(Utilities.path(dir, "input", "resources")), false); + loadConceptMaps(new File(Utilities.path(dir, "input", "elements")), false); + loadConceptMaps(new File(Utilities.path(dir, "input", "codes")), true); + loadConceptMaps(new File(Utilities.path(dir, "input", "search-params")), false); + } + + private void loadConceptMaps(File file, boolean track) throws FHIRFormatError, FileNotFoundException, IOException { + System.out.print("Load ConceptMaps from "+file.getAbsolutePath()+": "); + int i = 0; + for (File f : file.listFiles()) { + if (f.getName().startsWith("ConceptMap-")) { + ConceptMap cm = null; + String id = null; + try { + cm = (ConceptMap) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(f)); + id = f.getName().replace("ConceptMap-", "").replace(".json", ""); + if (!cm.getId().equals(id)) { + throw new Error("id mismatch - is "+cm.getId()+", should be "+id); + } + String url = "http://hl7.org/fhir/cross-version/ConceptMap/"+id; + if (!cm.getUrl().equals(url)) { + throw new Error("url mismatch - is "+cm.getUrl()+", should be "+url); + } + } catch (Exception e) { + throw new Error("Error parsing "+f.getAbsolutePath()+": "+e.getMessage(), e); + } + if (track) { + cm.setUserData("cm.used", "false"); + } + cm.setWebPath("ConceptMap-"+cm.getId()+".html"); + conceptMap.put(id.toLowerCase(), cm); + i++; + } + } + System.out.println(" "+i+" loaded"); } private void genChainsHtml(String path, String filename, boolean validOnly, boolean itemsOnly) throws IOException { System.out.println("Create "+filename); - XhtmlNode page = new XhtmlNode(NodeType.Element, "html"); - XhtmlNode head = page.head(); - head.link("stylesheet", "fhir.css"); - head.title("FHIR Cross Version Extensions"); - XhtmlNode body = page.body(); - body.style("background-color: white"); + + XhtmlNode body = new XhtmlNode(NodeType.Element, "div"); body.h1().tx("FHIR Cross Version Extensions"); body.para().tx("something"); - Collections.sort(chains, new TheChainSorter()); body.h2().tx("Element Chains"); XhtmlNode ul = null; - for (ElementChain chain : chains) { - String n = chain.elements.get(0).ed.getPath(); + for (SourcedElementDefinition origin : origins) { + List links = makeEDLinks(origin, MakeLinkMode.ORIGIN_CHAIN); + String n = origin.ed.getPath(); if (!n.contains(".")) { body.para().tx(n); ul = null; - } else if (!validOnly || chain.hasValid()) { + } else if (!validOnly || hasValid(links)) { if (ul == null) { ul = body.ul(); } XhtmlNode li = ul.li(); - li.tx(chain.id()); + li.tx(origin.toString()); XhtmlNode uli = li.ul(); - for (ElementDefinitionPair ed : chain.elements) { - if (!itemsOnly || ed.valid) { - renderElementDefinition(uli.li(), ed); + renderElementDefinition(uli.li(), origin); + for (ElementDefinitionLink link : links) { + if (!itemsOnly || link.next.valid) { + renderElementDefinition(uli.li(), link.next); } } } } - - TextFile.stringToFile(new XhtmlComposer(false, false).compose(page), Utilities.path(path, filename+".html")); - + TextFile.stringToFile(new XhtmlComposer(false, false).compose(body), Utilities.path(path, "input", "includes", filename+".xhtml")); + TextFile.stringToFile(new XhtmlComposer(false, false).compose(wrapPage(body, "FHIR Cross Version Extensions")), Utilities.path(path, "input", "qa", filename+".html")); } - private void scanChainElements(List elements) { - ElementDefinitionPair template = null; - for (ElementDefinitionPair element : elements) { - if (template == null) { - element.statusReason = null; + private boolean hasValid(List links) { + for (ElementDefinitionLink link : links) { + if (link.next.valid) { + return true; + } + } + return false; + } + + + private XhtmlNode wrapPage(XhtmlNode content, String title) { + XhtmlNode page = new XhtmlNode(NodeType.Element, "html"); + XhtmlNode head = page.head(); + head.link("stylesheet", "fhir.css"); + head.title(title); + XhtmlNode body = page.body(); + body.style("background-color: white"); + body.add(content); + return page; + } + + private void scanChainElements(SourcedElementDefinition terminus) throws FileNotFoundException, IOException { + List chain = makeEDLinks(terminus, MakeLinkMode.CHAIN); + + // this chain can include multiple logical subchains that all terminate at the same point. + // we're going to make a list of all origins, and then build a chain for each origin that only contains links in this chain (because links can be in more than one chain) + List origins = new ArrayList<>(); + for (ElementDefinitionLink link : chain) { + List links = makeEDLinks(link.prev, MakeLinkMode.INWARD); + if (links.size() == 0) { + origins.add(link.prev); + } + } + for (SourcedElementDefinition origin : origins) { + if (this.origins.contains(origin)) { + System.out.println("Now what? - ignoring duplicate report origin "+origin.toString()); + } else { + this.origins.add(origin); + List originChain = makeEDLinks(origin, MakeLinkMode.ORIGIN_CHAIN); + buildSubChain(originChain, origin, chain); + scanChainElements(origin, originChain); + } + } + } + + private void buildSubChain(List subChain, SourcedElementDefinition node, List chain) { + List links = makeEDLinks(node, MakeLinkMode.OUTWARD); + for (ElementDefinitionLink link : links) { + if (chain.contains(link)) { + subChain.add(link); + buildSubChain(subChain, link.next, chain); + } + } + } + + private void scanChainElements(SourcedElementDefinition origin, List links) throws FileNotFoundException, IOException { + // now we have a nice single chain across a set of versions + List all = new ArrayList(); + + origin.statusReason = null; + origin.valid = true; + origin.startVer = origin.ver; + origin.stopVer = origin.ver; + all.add(origin); + + SourcedElementDefinition template = origin; + for (ElementDefinitionLink link : links) { + SourcedElementDefinition element = link.next; + all.add(element); + if (link.rel != ConceptMapRelationship.EQUIVALENT) { + element.statusReason = "Not Equivalent"; element.valid = true; - template = element; + template = element; + template.startVer = element.ver; + template.stopVer = element.ver; + } else if (!template.ed.repeats() && element.ed.repeats()) { + element.statusReason = "Element repeats"; + element.valid = true; + template.repeater = element; + template = element; template.startVer = element.ver; template.stopVer = element.ver; } else { - if (element.rel != ConceptMapRelationship.EQUIVALENT) { - element.statusReason = "Not Equivalent"; + List newTypes = findNewTypes(template.ed, element.ed); + if (!newTypes.isEmpty()) { + element.statusReason = "New Types "+CommaSeparatedStringBuilder.join("|", newTypes); element.valid = true; template = element; template.startVer = element.ver; template.stopVer = element.ver; - } else if (!template.ed.repeats() && element.ed.repeats()) { - element.statusReason = "Element repeats"; - element.valid = true; - template.repeater = element; - template = element; - template.startVer = element.ver; - template.stopVer = element.ver; } else { - List newTypes = findNewTypes(template.ed, element.ed); - if (!newTypes.isEmpty()) { - element.statusReason = "New Types "+CommaSeparatedStringBuilder.join("|", newTypes); + List newTargets = findNewTargets(template.ed, element.ed); + if (!newTargets.isEmpty()) { + element.statusReason = "New Targets "+CommaSeparatedStringBuilder.join("|", newTargets); element.valid = true; template = element; template.startVer = element.ver; template.stopVer = element.ver; } else { - List newTargets = findNewTargets(template.ed, element.ed); - if (!newTargets.isEmpty()) { - element.statusReason = "New Targets "+CommaSeparatedStringBuilder.join("|", newTargets); - element.valid = true; - template = element; - template.startVer = element.ver; - template.stopVer = element.ver; - } else { - element.statusReason = "No Change"; - element.valid = false; - template.stopVer = element.ver; - } + element.statusReason = "No Change"; + element.valid = false; + template.stopVer = element.ver; } } - } + } } - if (template == elements.get(0) && elements.size() == 5) { - template.valid = false; - template.statusReason = "has existed in all versions"; - } - for (ElementDefinitionPair element : elements) { + + for (SourcedElementDefinition element : all) { if (element.valid) { CommaSeparatedStringBuilder vers = new CommaSeparatedStringBuilder(); String bv = element.startVer; String ev = element.repeater != null ? element.repeater.stopVer : element.stopVer; - + if (!VersionUtilities.includedInRange(bv, ev, "1.0.2")) { vers.append("R2"); } @@ -450,9 +746,310 @@ public class XVerExtensionPackageGenerator { element.verList = vers.toString(); } } - - } + + for (ElementDefinitionLink link : links) { + VSPair l = isEnum(link.prev); + VSPair r = isEnum(link.next); + if (l != null && r != null) { + if (l.getCs().getUrl().contains("resource-types") || r.getCs().getUrl().contains("resource-types")) { + String idF = "resources-"+l.getVersion()+"to"+r.getVersion(); + String idR = "resources-"+r.getVersion()+"to"+l.getVersion(); + link.nextCM = conceptMap.get(idF); + link.prevCM = conceptMap.get(idR); + } else { + String idF = link.next.ed.getPath().equals(link.prev.ed.getPath()) ? link.next.ed.getPath()+"-"+l.getVersion()+"to"+r.getVersion() : link.prev.ed.getPath()+"-"+link.next.ed.getPath()+"-"+l.getVersion()+"to"+r.getVersion(); + String idR = link.next.ed.getPath().equals(link.prev.ed.getPath()) ? link.next.ed.getPath()+"-"+r.getVersion()+"to"+l.getVersion() : link.next.ed.getPath()+"-"+link.prev.ed.getPath()+"-"+r.getVersion()+"to"+l.getVersion(); + ConceptMap cmF = conceptMap.get(idF.toLowerCase()); + ConceptMap cmR = conceptMap.get(idR.toLowerCase()); + Set lset = CodeSystemUtilities.codes(l.cs); + Set rset = CodeSystemUtilities.codes(r.cs); + Set lvset = ValueSetUtilities.codes(l.vs, l.cs); + Set rvset = ValueSetUtilities.codes(r.vs, r.cs); + + if (cmF != null) { + checkCM(cmF, link.prev, link.next, l, r, lset, rset, lvset, rvset); + } else { // if (!rset.containsAll(lset)) { + cmF = makeCM(idF, link.prev, link.next, l, r, lset, rset, lvset, rvset); + } + + if (cmR != null) { + checkCM(cmR, link.next, link.prev, r, l, rset, lset, rvset, lvset); + } else { // if (!lset.containsAll(rset)) { + cmR = makeCM(idR, link.next, link.prev, r, l, rset, lset, rvset, lvset); + } + if (cmF != null && cmR != null) { + List errs = new ArrayList(); + boolean altered = ConceptMapUtilities.checkReciprocal(cmF, cmR, errs); + for (String s : errs) { + System.out.println("Error between "+cmF.getId()+" and "+cmR.getId()+" maps: "+s); + } + if (altered) { + new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/codes/ConceptMap-"+cmR.getId()+".json"), cmR); + new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/codes/ConceptMap-"+cmF.getId()+".json"), cmF); + } + } + link.nextCM = cmF; + link.prevCM = cmR; + + } + } + } + } + + private ConceptMap makeCM(String id, SourcedElementDefinition se, SourcedElementDefinition de, VSPair s, VSPair d, Set source, Set dest, Set lvset, Set rvset) throws FileNotFoundException, IOException { + ConceptMap cm = new ConceptMap(); + cm.setId(id); + cm.setUrl("http://hl7.org/fhir/cross-version/ConceptMap/"+id); + cm.setName(id.replace("-", "")); + if (se.ed.getPath().equals(de.ed.getPath())) { + cm.setTitle("Mapping for "+se.ed.getPath()+" from "+se.ver+" to "+de.ver); + } else { + cm.setTitle("Mapping for "+se.ed.getPath()+"/"+de.ed.getPath()+" from "+se.ver+" to "+de.ver); + } + String scopeUri = "http://hl7.org/fhir/"+VersionUtilities.getMajMin(se.ver)+"/StructureDefinition/"+se.sd.getName()+"#"+se.ed.getPath(); + cm.setSourceScope(new UriType(scopeUri)); + String targetUri = "http://hl7.org/fhir/"+VersionUtilities.getMajMin(de.ver)+"/StructureDefinition/"+de.sd.getName()+"#"+de.ed.getPath(); + cm.setTargetScope(new UriType(targetUri)); + ConceptMapGroupComponent g = cm.addGroup(); + scopeUri = injectVersionToUri(s.cs.getUrl(), VersionUtilities.getMajMin(se.ver)); + targetUri = injectVersionToUri(d.cs.getUrl(), VersionUtilities.getMajMin(de.ver)); + g.setSource(scopeUri); + g.setTarget(targetUri); + Set unmapped = new HashSet<>(); + for (String c : dest) { + if (!source.contains(c)) { + unmapped.add(c); + } + } + boolean review = false; + for (String c : source) { + SourceElementComponent src = g.addElement(); + src.setCode(c); + if (!dest.contains(c)) { + src.setNoMap(true); + review = true; + src.setDisplay("CHECK! missed = "+CommaSeparatedStringBuilder.join(",", unmapped)+"; all = "+CommaSeparatedStringBuilder.join(",", dest)); + TargetElementComponent tgt = src.addTarget(); + tgt.setCode("CHECK!"); + tgt.setRelationship(ConceptMapRelationship.EQUIVALENT); + } else { + TargetElementComponent tgt = src.addTarget(); + tgt.setCode(c); + tgt.setRelationship(ConceptMapRelationship.EQUIVALENT); + } + } + if (review) { + cm.getMeta().addTag("http://something", "review-needed", null); + } + new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/codes/ConceptMap-"+cm.getId()+".json"), cm); + return cm; + } + + private void checkCM(ConceptMap cm, SourcedElementDefinition se, SourcedElementDefinition de, VSPair s, VSPair d, Set source, Set dest, Set lvset, Set rvset) throws FileNotFoundException, IOException { + cm.setUserData("cm.used", "true"); + boolean changed = false; + boolean review = false; + if (cm.getMeta().getTag().removeIf(c -> c.getCode().equals("review-needed"))) { + changed = true; + } + String scopeUri = "http://hl7.org/fhir/"+VersionUtilities.getMajMin(se.ver)+"/StructureDefinition/"+se.sd.getName()+"#"+se.ed.getPath(); + String targetUri = "http://hl7.org/fhir/"+VersionUtilities.getMajMin(de.ver)+"/StructureDefinition/"+de.sd.getName()+"#"+de.ed.getPath(); + if (!cm.hasSourceScopeUriType()) { + System.out.println("Concept Map "+cm.getId()+" does not have a uri source scope"); + cm.setSourceScope(new UriType(scopeUri)); + changed = true; + } else if (!scopeUri.equals(cm.getSourceScopeUriType().primitiveValue())) { + System.out.println("Concept Map "+cm.getId()+" source scope is wrong - is "+cm.getSourceScopeUriType().primitiveValue()+" should be "+scopeUri); + cm.setSourceScope(new UriType(scopeUri)); + changed = true; + } + if (!cm.hasTargetScopeUriType()) { + System.out.println("Concept Map "+cm.getId()+" does not have a uri target scope"); + cm.setTargetScope(new UriType(targetUri)); + changed = true; + } else if (!targetUri.equals(cm.getTargetScopeUriType().primitiveValue())) { + System.out.println("Concept Map "+cm.getId()+" target scope is wrong - is "+cm.getTargetScopeUriType().primitiveValue()+" should be "+targetUri); + cm.setTargetScope(new UriType(targetUri)); + changed = true; + } + if (cm.getGroup().size() != 1) { + System.out.println("Concept Map "+cm.getId()+" should have one and only one group"); + } else { + ConceptMapGroupComponent g = cm.getGroupFirstRep(); + for (SourceElementComponent src : g.getElement()) { + if (src.hasDisplay()) { + src.setDisplay(null); + changed = true; + } + for (TargetElementComponent tgt : src.getTarget()) { + if (tgt.hasDisplay()) { + tgt.setDisplay(null); + changed = true; + } + } + if (src.hasTarget() && src.getNoMap()) { + src.setNoMapElement(null); + changed = true; + } + } + + scopeUri = injectVersionToUri(s.cs.getUrl(), VersionUtilities.getMajMin(se.ver)); + targetUri = injectVersionToUri(d.cs.getUrl(), VersionUtilities.getMajMin(de.ver)); + if (!scopeUri.equals(g.getSource())) { + System.out.println("Concept Map "+cm.getId()+" source scope system is wrong - is "+g.getSource()+" should be "+scopeUri); + g.setSource(scopeUri); + changed = true; + } + if (!targetUri.equals(g.getTarget())) { + System.out.println("Concept Map "+cm.getId()+" target scope system is wrong - is "+g.getTarget()+" should be "+targetUri); + g.setTarget(targetUri); + changed = true; + } + Set missed = new HashSet<>(); + Set invalid = new HashSet<>(); + Set mapped = new HashSet<>(); + for (String c : source) { + SourceElementComponent src = getSource(g, c); + if (src != null) { + for (TargetElementComponent tgt : src.getTarget()) { + if (!dest.contains(tgt.getCode())) { + invalid.add(tgt.getCode()); + } else { + mapped.add(tgt.getCode()); + } + if (tgt.hasComment() && tgt.getComment().contains("s:")) { + changed = true; + tgt.setComment(null); + } + } + } else if (!dest.contains(c) || !hasUnMapped(g)) { + missed.add(c); + } + } + if (!missed.isEmpty()) { + Set amissed = new HashSet<>(); + for (String c : missed) { + if ((lvset.isEmpty() || lvset.contains(c)) && !c.startsWith("_") && !c.equals("null") && !CodeSystemUtilities.isNotSelectable(d.cs, c)) { + g.addElement().setCode(c).setNoMap(true); + changed = true; + review = true; + amissed.add(c); + } + } + if (!amissed.isEmpty()) { + System.out.println("Concept Map "+cm.getId()+" is missing mappings for "+CommaSeparatedStringBuilder.join(",", amissed)); + } + } + if (!invalid.isEmpty()) { + System.out.println("Concept Map "+cm.getId()+" has invalid mappings to "+CommaSeparatedStringBuilder.join(",", invalid)); + Set unmapped = new HashSet<>(); + for (String c : dest) { + if (!mapped.contains(c)) { + unmapped.add(c); + } + } + for (String c : source) { + SourceElementComponent src = getSource(g, c); + if (src != null) { + for (TargetElementComponent tgt : src.getTarget()) { + if (!dest.contains(tgt.getCode())) { + tgt.setDisplay("INVALID!"); + tgt.setComment("missed: "+CommaSeparatedStringBuilder.join(",", unmapped)); + changed = true; + review = true; + } + } + if (src.getNoMap()) { + src.setDisplay("missed: "+CommaSeparatedStringBuilder.join(",", unmapped)); + changed = true; + review = true; + } + } + } + } + invalid.clear(); + for (SourceElementComponent t : g.getElement()) { + if (!source.contains(t.getCode())) { + invalid.add(t.getCode()); + t.setDisplay("INVALID!"); + changed = true; + review = true; + } + } + if (!invalid.isEmpty()) { + System.out.println("Concept Map "+cm.getId()+" has invalid mappings from "+CommaSeparatedStringBuilder.join(",", invalid)); + } + if (!lvset.isEmpty()) { + if (g.getElement().removeIf(src -> !lvset.contains(src.getCode()))) { + changed = true; + } + } + if (!rvset.isEmpty()) { + for (SourceElementComponent src : g.getElement()) { + for (TargetElementComponent tgt : src.getTarget()) { + if (!"CHECK!".equals(tgt.getCode())) { + if (!dest.contains(tgt.getCode())) { + tgt.setComment("The code "+tgt.getCode()+" is not in the target code system - FIX"); + changed = true; + review = true; + } else if (!rvset.contains(tgt.getCode())) { + tgt.setComment("The code "+tgt.getCode()+" is not in the target value set, but is the correct translation - CHECK"); + changed = true; + review = true; + } + } + } + } + } + } + + if (changed) { + if (review) { + boolean tagged = false; + for (Coding c : cm.getMeta().getTag()) { + if (c.getCode().equals("review-needed")) { + tagged = true; + } + } + if (!tagged) { + cm.getMeta().addTag("http://something", "review-needed", null); + } + } + new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream("/Users/grahamegrieve/work/fhir-cross-version/input/codes/ConceptMap-"+cm.getId()+".json"), cm); + } + } + + + private boolean hasUnMapped(ConceptMapGroupComponent g) { + return g.hasUnmapped() && g.getUnmapped().getMode() == ConceptMapGroupUnmappedMode.USESOURCECODE; + } + + private SourceElementComponent getSource(ConceptMapGroupComponent g, String c) { + for (SourceElementComponent t : g.getElement()) { + if (c.equals(t.getCode())) { + return t; + } + } + return null; + } + private String injectVersionToUri(String url, String ver) { + return url.replace("http://hl7.org/fhir", "http://hl7.org/fhir/"+ver); + } + + private VSPair isEnum(SourcedElementDefinition pair) { + String v = VersionUtilities.getNameForVersion(pair.ver).toLowerCase(); + VersionDefinitions vd = versions.get(v); + if (pair.ed.getBinding().getStrength() == BindingStrength.REQUIRED || pair.ed.getBinding().getStrength() == BindingStrength.EXTENSIBLE) { + ValueSet vs = vd.valueSets.get(pair.ed.getBinding().getValueSet()); + if (vs != null && vs.getCompose().getInclude().size() == 1) { + CodeSystem cs = vd.codeSystems.get(vs.getCompose().getIncludeFirstRep().getSystem()); + if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) { + return new VSPair(v.substring(1), vs, cs); + } + } + } + return null; } private List findNewTypes(ElementDefinition template, ElementDefinition element) { @@ -488,7 +1085,7 @@ public class XVerExtensionPackageGenerator { return res; } - private void renderElementDefinition(XhtmlNode x, ElementDefinitionPair ed) { + private void renderElementDefinition(XhtmlNode x, SourcedElementDefinition ed) { String ver = VersionUtilities.getMajMin(ed.ver); String prefix = VersionUtilities.getNameForVersion(ver).toLowerCase(); Map structures = versions.get(prefix).structures; @@ -522,7 +1119,6 @@ public class XVerExtensionPackageGenerator { x1.tx(".."); x1.tx(ed.ed.getMax()); x1.tx("]."); - if (ed.valid) { x.tx(" Valid versions: "+ed.verList); } else { @@ -530,203 +1126,412 @@ public class XVerExtensionPackageGenerator { } } - private void genVersionHtml(String path, String filename, VersionDefinitions definitions, TranslatePack... translations) throws IOException { - System.out.println("Create "+filename); - XhtmlNode page = new XhtmlNode(NodeType.Element, "html"); - XhtmlNode head = page.head(); - head.link("stylesheet", "fhir.css"); - head.title("FHIR Version "+definitions.version.toCode()); - XhtmlNode body = page.body(); - body.style("background-color: white"); - body.h1().tx("FHIR Version "+definitions.version.toCode()); - body.para().tx("something"); - XhtmlNode p = body.para(); - p.ah("#primitives").tx("Primitives"); - p.tx(" | "); - p.ah("#types").tx("Data Types"); - p.tx(" | "); - p.ah("#resources").tx("Resources"); + private void buildLinks(XVersions ver, VersionDefinitions defsPrev, ConceptMap resFwd, ConceptMap elementFwd, VersionDefinitions defsNext) { + System.out.println("Build links between "+defsPrev.version.toCode()+" and "+defsNext.version.toCode()); - body.h2().tx("Primitive Types"); - body.an("primitives"); - XhtmlNode tbl = body.table("grid"); - XhtmlNode tr = tbl.tr(); - tr.th().tx("Primitive Type"); - for (TranslatePack tp : translations) { - tr.th().ah(tp.getFn()+".html#primitives").tx(tp.definitions.version.toCode()); - } - for (String name : Utilities.sorted(definitions.structures.keySet())) { - StructureDefinition sd = definitions.structures.get(name); - if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { - tr = tbl.tr(); - tr.td().tx(name); - for (TranslatePack tp : translations) { - tr.td().tx(tp.definitions.structures.containsKey(name) ? name : null); - } - } - } - body.an("types"); - body.h2().tx("Complex Types"); - body.para().tx("Index"); - XhtmlNode ndx = body.ul().style("column-count: 5"); - - for (String name : Utilities.sorted(definitions.structures.keySet())) { - StructureDefinition sd = definitions.structures.get(name); - if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { - ndx.li().ah("#"+name).tx(name); - body.an(name); - body.h3().tx(name); - tbl = body.table("grid"); - tr = tbl.tr(); - tr.th().tx(VersionUtilities.getNameForVersion(sd.getFhirVersion().toCode())+" Element"); + for (String name : Utilities.sorted(defsPrev.structures.keySet())) { + StructureDefinition sd = defsPrev.structures.get(name); + if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && (!sd.getAbstract() || Utilities.existsInList(sd.getName(), "Quantity")) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { List matches = new ArrayList<>(); - for (TranslatePack tp : translations) { - matches.add(new SourcedStructureDefinition(tp, tp.getDefinitions().structures.get(name), ConceptMapRelationship.EQUIVALENT)); - } - for (SourcedStructureDefinition ssd : matches) { - tr.th().ah(ssd.tp.getFn()+".html").tx(ssd.tp.definitions.version.toCode()); - } - for (ElementDefinition ed : sd.getDifferential().getElement()) { - tr = tbl.tr(); - rendererElement(tr.td(), ed, "", definitions.structures, null, null); - for (SourcedStructureDefinition ssd : matches) { - if (ssd.structureDefinition != null) { - List edtl = findTranslatedElements(ed, ssd.structureDefinition, ssd.tp.elementMap, ssd.tp.elementMap.getId(), ssd.relationship); - if (edtl.isEmpty()) { - if (ssd.tp.buildTheChain) { - if (!ed.hasUserData("chains")) { - ElementChain chain = new ElementChain(chains.size()+1, sd, ed); - chains.add(chain); - List echains = new ArrayList(); - echains.add(chain); - ed.setUserData("chains", echains); - } - } - tr.td().style("background-color: #eeeeee"); - } else { - XhtmlNode td = tr.td(); - boolean first = true; - for (ElementDefinitionPair edt : edtl) { - if (ssd.tp.buildTheChain) { - if (!ed.hasUserData("chains")) { - ElementChain chain = new ElementChain(chains.size()+1, sd, ed); - chains.add(chain); - List echains = new ArrayList(); - echains.add(chain); - ed.setUserData("chains", echains); - } - List echains = (List) edt.ed.getUserData("chains"); - if (echains == null) { - echains = new ArrayList(); - edt.ed.setUserData("chains", echains); - } - for (ElementChain chain : (List) ed.getUserData("chains")) { - chain.elements.add(edt); - if (!echains.contains(chain)) { - echains.add(chain); - } - } - } - if (first) first = false; else td.br(); - rendererElement(td, edt.ed, ssd.tp.fn+".html", ssd.tp.definitions.structures, ed.getPath(), edt.rel); - } - } - } else if (ed.getPath().contains(".")) { - tr.td().tx(""); - } else { - tr.td().style("background-color: #eeeeee"); - } - } - } + matches.add(new SourcedStructureDefinition(defsNext, defsNext.structures.get(name), ConceptMapRelationship.EQUIVALENT)); + buildLinksForElements(ver, elementFwd, sd, matches); } } - body.an("resources"); - body.h2().tx("Resources"); - body.para().tx("Index"); - ndx = body.ul().style("column-count: 5"); - for (String name : Utilities.sorted(definitions.structures.keySet())) { - StructureDefinition sd = definitions.structures.get(name); + for (String name : Utilities.sorted(defsPrev.structures.keySet())) { + StructureDefinition sd = defsPrev.structures.get(name); if (sd.getKind() == StructureDefinitionKind.RESOURCE && !sd.getAbstract() && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { - ndx.li().ah("#"+name).tx(name); - body.an(name); - body.h3().tx(name); - tbl = body.table("grid"); - tr = tbl.tr(); - tr.th().tx("Element"); - List matches = new ArrayList<>(); - for (TranslatePack tp : translations) { - List names = translateResourceName(tp.getResMap(), name); - if (names.isEmpty()) { - matches.add(new SourcedStructureDefinition(tp, null, null)); + List names = translateResourceName(resFwd, name); + if (names.isEmpty()) { + matches.add(new SourcedStructureDefinition(defsNext, null, null)); + } else { + for (TranslatedCode n : names) { + matches.add(new SourcedStructureDefinition(defsNext, defsNext.structures.get(n.getCode()), n.getRelationship())); + } + } + buildLinksForElements(ver, elementFwd, sd, matches); + } + } + } + + + private void buildLinksForElements(XVersions ver, ConceptMap elementFwd, StructureDefinition sd, List matches) { + for (ElementDefinition ed : sd.getDifferential().getElement()) { + List links = makeEDLinks(ed, MakeLinkMode.OUTWARD); + for (SourcedStructureDefinition ssd : matches) { + if (ssd.structureDefinition != null) { + List edtl = findTranslatedElements(ed, ssd.structureDefinition, elementFwd, ssd.relationship); + if (edtl.isEmpty()) { + // no links } else { - for (TranslatedCode n : names) { - matches.add(new SourcedStructureDefinition(tp, tp.getDefinitions().structures.get(n.getCode()), n.getRelationship())); + for (MatchedElementDefinition edt : edtl) { + ElementDefinitionLink link = new ElementDefinitionLink(); + link.versions = ver; + link.prev = makeSED(sd, ed); + link.next = makeSED(ssd.structureDefinition, edt.ed); + link.rel = edt.rel; + allLinks.add(link); + links.add(link); + List linksOther = makeEDLinks(edt.ed, MakeLinkMode.INWARD); + linksOther.add(link); } } - } - for (SourcedStructureDefinition ssd : matches) { - tr.th().ah(ssd.tp.getFn()+".html").tx(ssd.tp.definitions.version.toCode()); - } - for (ElementDefinition ed : sd.getDifferential().getElement()) { - tr = tbl.tr(); - rendererElement(tr.td(), ed, "", definitions.structures, null, null); - for (SourcedStructureDefinition ssd : matches) { - if (ssd.structureDefinition != null) { - List edtl = findTranslatedElements(ed, ssd.structureDefinition, ssd.tp.elementMap, ssd.tp.elementMap.getId(), ssd.relationship); - if (edtl.isEmpty()) { - if (ssd.tp.buildTheChain) { - if (!ed.hasUserData("chains")) { - ElementChain chain = new ElementChain(chains.size()+1, sd, ed); - chains.add(chain); - List echains = new ArrayList(); - echains.add(chain); - ed.setUserData("chains", echains); - } - } - tr.td().style("background-color: #eeeeee"); - } else { - XhtmlNode td = tr.td(); - boolean first = true; - for (ElementDefinitionPair edt : edtl) { - if (ssd.tp.buildTheChain) { - if (!ed.hasUserData("chains")) { - ElementChain chain = new ElementChain(chains.size()+1, sd, ed); - chains.add(chain); - List echains = new ArrayList(); - echains.add(chain); - ed.setUserData("chains", echains); - } - List echains = (List) edt.ed.getUserData("chains"); - if (echains == null) { - echains = new ArrayList(); - edt.ed.setUserData("chains", echains); - } - for (ElementChain chain : (List) ed.getUserData("chains")) { - chain.elements.add(edt); - if (!echains.contains(chain)) { - echains.add(chain); - } - } - } - if (first) first = false; else td.br(); - rendererElement(td, edt.ed, ssd.tp.fn+".html", ssd.tp.definitions.structures, ed.getPath(), edt.rel); - } - } - } else if (ed.getPath().contains(".")) { - tr.td().tx(""); - } else { - tr.td().style("background-color: #eeeeee"); - } - } - } + } + } + if (links.size() == 0) { + terminatingElements.add(makeSED(sd, ed)); } } - - TextFile.stringToFile(new XhtmlComposer(false, false).compose(page), Utilities.path(path, filename+".html")); } + private SourcedElementDefinition makeSED(StructureDefinition sd, ElementDefinition ed) { + SourcedElementDefinition sed = (SourcedElementDefinition) ed.getUserData("sed"); + if (sed == null) { + sed = new SourcedElementDefinition(sd, ed); + ed.setUserData("sed", sed); + } + return sed; + } + + + private List makeEDLinks(SourcedElementDefinition sed, MakeLinkMode mode) { + return makeEDLinks(sed.ed, mode); + } + + private List makeEDLinks(ElementDefinition ed, MakeLinkMode mode) { + String id = "links."+mode; + List links = (List) ed.getUserData(id); + if (links == null) { + links = new ArrayList<>(); + ed.setUserData(id, links); + } + return links; + } + + + private void genVersionType(String path, StructureDefinition sd) throws IOException { + XhtmlNode body = new XhtmlNode(NodeType.Element, "div"); + body.h1().tx(sd.getName()); + body.para().tx("something"); + XhtmlNode tbl = body.table("grid"); + XhtmlNode tr = tbl.tr(); + + // we're going to generate a table with a column for each target structure definition in the chains that terminate in the provided sd + List columns = new ArrayList(); + for (StructureDefinition sdt : findLinkedStructures(sd)) { + columns.add(new StructureDefinitionColumn(sdt, false)); + } + Collections.sort(columns, new ColumnSorter()); + columns.add(new StructureDefinitionColumn(sd, true)); + + for (StructureDefinitionColumn col : columns) { + tr.th().colspan(col.root ? 1 : 2).tx(col.sd.getName()+" ("+col.sd.getFhirVersion().toCode()+")"); + } + List> codeChains = new ArrayList<>(); + + for (ElementDefinition ed : sd.getDifferential().getElement()) { + for (StructureDefinitionColumn col : columns) { + col.clear(); + } + StructureDefinitionColumn rootCol = getColumn(columns, sd); + rootCol.entries.add(new ColumnEntry(null, ed)); + List links = makeEDLinks(ed, MakeLinkMode.CHAIN); + // multiple fields might collapse into one. So there might be more than one row per target structure. + // if there are, we add additional rows, and set the existing cells to span the multiple rows + // we already know what rowspan is needed. + for (ElementDefinitionLink link : links) { + StructureDefinitionColumn col = getColumn(columns, link.prev.sd); + col.entries.add(new ColumnEntry(link, link.prev.ed)); + // while we're at it, scan for origin chains with translations + checkForCodeTranslations(codeChains, link); + } + int rowCount = 0; + for (StructureDefinitionColumn col : columns) { + rowCount = Integer.max(rowCount, col.rowCount()); + } + List rows = new ArrayList(); + for (int i = 0; i < rowCount; i++) { + rows.add(tbl.tr()); + } + ed.setUserData("rows", rows); + renderElementRow(sd, columns, ed, rowCount, rows); + } + + // ok, now we look for any other terminating elements that didn't make it into the R5 structure, and inject them into the table + List missingChains = findMissingTerminatingElements(sd, columns); + for (SourcedElementDefinition m : missingChains) { + StructureDefinitionColumn rootCol = getColumn(columns, m.sd); + ElementDefinition prevED = findPreviousElement(m, rootCol.elements); // find the anchor that knows which rows we'll be inserting under + List rows = (List) prevED.getUserData("rows"); + XhtmlNode lastTR = rows.get(rows.size()-1); + for (StructureDefinitionColumn col : columns) { + col.clear(); + } + rootCol.entries.add(new ColumnEntry(null, m.ed)); + List links = makeEDLinks(m.ed, MakeLinkMode.CHAIN); + // multiple fields might collapse into one. So there might be more than one row per target structure. + // if there are, we add additional rows, and set the existing cells to span the multiple rows + // we already know what rowspan is needed. + for (ElementDefinitionLink link : links) { + StructureDefinitionColumn col = getColumn(columns, link.prev.sd); + col.entries.add(new ColumnEntry(link, link.prev.ed)); + // while we're at it, scan for origin chains with translations + checkForCodeTranslations(codeChains, link); + } + int rowCount = 0; + for (StructureDefinitionColumn col : columns) { + rowCount = Integer.max(rowCount, col.rowCount()); + } + rows = new ArrayList(); + for (int i = 0; i < rowCount; i++) { + rows.add(tbl.tr(lastTR)); + } + m.ed.setUserData("rows", rows); + renderElementRow(m.sd, columns, m.ed, rowCount, rows); + } + + Collections.sort(codeChains, new CodeChainsSorter()); + for (List links : codeChains) { + String name = null; // links.get(links.size() -1).next.ed.getPath(); + List maps = new ArrayList<>(); + for (ElementDefinitionLink link : links) { + if (link.nextCM != null) { + if (name == null) { + String srcscope = link.nextCM.getSourceScope().primitiveValue(); + name = VersionUtilities.getNameForVersion(srcscope)+" "+srcscope.substring(srcscope.lastIndexOf("#")+1); + } + String tgtscope = link.nextCM.getTargetScope().primitiveValue(); + link.nextCM.setUserData("presentation", + VersionUtilities.getNameForVersion(tgtscope)+" "+tgtscope.substring(tgtscope.lastIndexOf("#")+1)); + maps.add(link.nextCM); + } + } + body.hr(); + body.add(ConceptMapRenderer.renderMultipleMaps(name, maps, this, RenderMultiRowSortPolicy.UNSORTED)); + } + + TextFile.stringToFile(new XhtmlComposer(false, false).compose(body), Utilities.path(path, "input", "includes", "cross-version-"+sd.getName()+".xhtml")); + TextFile.stringToFile(new XhtmlComposer(false, false).compose(wrapPage(body, sd.getName())), Utilities.path(path, "input", "qa", "cross-version-"+sd.getName()+".html")); + } + + + private void checkForCodeTranslations(List> codeChains, ElementDefinitionLink link) { + if (link.prev.ed.hasUserData("links."+MakeLinkMode.ORIGIN_CHAIN)) { + List originChain = makeEDLinks(link.prev.ed, MakeLinkMode.ORIGIN_CHAIN); + boolean mappings = false; + for (ElementDefinitionLink olink : originChain) { + mappings = mappings || olink.nextCM != null; + } + if (mappings) { + codeChains.add(originChain); + } + } + } + + + private void renderElementRow(StructureDefinition sd, List columns, ElementDefinition ed, int rowCount, List rows) { + for (StructureDefinitionColumn col : columns) { + int i = 0; + for (ColumnEntry entry : col.entries) { + XhtmlNode ctr = rows.get(i); + i = i + (entry.link == null ? 1 : entry.link.leftWidth); + col.elements.add(new ElementDefinitionPair(entry.ed, ed)); + XhtmlNode td1 = rendererElementForType(ctr.td(), col.root ? sd : col.sd, entry.ed, col.root ? vdr5.structures : versions.get(VersionUtilities.getNameForVersion(col.sd.getFhirVersion().toCode()).toLowerCase()).structures, ed.getPath()); + if (entry.link != null && entry.link.leftWidth > 1) { + td1.rowspan(entry.link.leftWidth); + } + if (!col.root) { + if (entry.link != null) { + XhtmlNode td = ctr.td().style("background-color: LightGrey; text-align: center; vertical-align: middle; color: white"); // .tx(entry.link.rel.getSymbol()); + if (entry.link.leftWidth > 1) { + td.rowspan(entry.link.leftWidth); + } + if (entry.link.nextCM != null) { + td.tx("☰"); + } else if (entry.link.rel == null) { + td.tx("?"); + } else switch (entry.link.rel ) { + case EQUIVALENT: + td.tx("="); + break; + case NOTRELATEDTO: + td.tx("!="); + break; + case RELATEDTO: + td.tx("~"); + break; + case SOURCEISBROADERTHANTARGET: + td.tx("<"); + break; + case SOURCEISNARROWERTHANTARGET: + td.tx(">"); + break; + } + } else { + ctr.td().style("background-color: #eeeeee"); + } + } + } + for (int j = i; j < rowCount; j++ ) { + rows.get(j).td().style("background-color: #eeeeee"); + if (!col.root) { + rows.get(j).td().style("background-color: #eeeeee"); + } + } + } + } + + private ElementDefinition findPreviousElement(SourcedElementDefinition m, List elements) { + // latest possible parent + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + int parent = 0; + for (int i = elements.size()-1; i >= 0; i--) { + b.append(elements.get(i).focus.getPath()); + if (m.ed.getPath().startsWith(elements.get(i).focus.getPath()+".")) { + parent = i; + } + } + // last descendant of parent + String path = elements.get(parent).focus.getPath()+"."; + for (int i = elements.size()-1; i >= parent; i--) { + if (elements.get(i).focus.getPath().startsWith(path)) { + return elements.get(i).anchor; + } + } + return elements.get(parent).anchor; + } + + + private List findMissingTerminatingElements(StructureDefinition base, List columns) { + List res = new ArrayList(); + for (SourcedElementDefinition t : terminatingElements) { + if (t.sd != base) { // already rendered them + for (StructureDefinitionColumn col : columns) { + if (t.sd == col.sd) { + res.add(t); + } + } + } + } + return res; + } + + private StructureDefinitionColumn getColumn(List columns, StructureDefinition sd) { + for (StructureDefinitionColumn col : columns) { + if (col.sd == sd) { + return col; + } + } + throw new Error("not found"); + } + + + private Set findLinkedStructures(StructureDefinition sd) { + Set res = new HashSet<>(); + for (ElementDefinition ed : sd.getDifferential().getElement()) { + SourcedElementDefinition sed = makeSED(sd, ed); + findLinkedStructures(res, sed); + } + return res; + } + + + private void findLinkedStructures(Set res, SourcedElementDefinition sed) { + + List links = makeEDLinks(sed, MakeLinkMode.CHAIN); + for (ElementDefinitionLink link : links) { + res.add(link.prev.sd); + } + + } + + + private XhtmlNode rendererElementForType(XhtmlNode td, StructureDefinition sdt, ElementDefinition ed, Map structures, String origName) { + if (ed.getPath().contains(".")) { + XhtmlNode span = td.span().attribute("title", ed.getPath()); + if (origName != null && !origName.equals(ed.getPath())) { + span.style("color: maroon; font-weight: bold"); + } + String[] p = ed.getPath().split("\\."); + for (int i = 0; i < p.length; i++) { + if (i == p.length - 1) { + span.tx(p[i]); + } else { + span.tx(p[i].substring(0, 1)); + span.tx("."); + } + } + boolean first = true; + for (TypeRefComponent t : ed.getType()) { + StructureDefinition sd = structures.get(t.getWorkingCode()); + if (sd != null && !sd.getAbstract()) { + if (first) {td.tx(" : "); first = false; } else { td.tx("|"); td.wbr(); } + td.ah(linkforType(t.getWorkingCode())).tx(t.getWorkingCode()); + if (t.hasTargetProfile()) { + td.tx("("); + boolean tfirst = true; + for (CanonicalType u : t.getTargetProfile()) { + if (tfirst) {tfirst = false; } else { td.tx("|"); td.wbr(); } + String rt = tail(u.getValue()); + td.ah(linkforType(rt)).tx(rt); + } + td.tx(")"); + } + } + } + td.tx(" : "); + td.tx("["); + td.tx(ed.getMin()); + td.tx(".."); + td.tx(ed.getMax()); + td.tx("]"); + } else { + XhtmlNode ah = td.ah(pathForType(sdt, ed.getPath())); + if (origName != null && !origName.equals(ed.getPath())) { + ah.style("color: maroon; font-weight: bold"); + } + ah.tx(ed.getPath()); + } + return td; + } + + private String linkforType(String type) { + if (Utilities.existsInList(type, "instant", "time", "date", "dateTime", "decimal", "boolean", "integer", "string", "uri", "base64Binary", "code", "id", "oid", + "unsignedInt", "positiveInt", "markdown", "url", "canonical", "uuid", "integer64")) { + return "cross-version-primitives.html"; + } else { + return "cross-version-"+type+".html"; + } + } + + + private String pathForType(StructureDefinition sdt, String type) { + String sp = VersionUtilities.getSpecUrl(sdt.getFhirVersion().toCode()); + if (Utilities.existsInList(type, "instant", "time", "date", "dateTime", "decimal", "boolean", "integer", "string", "uri", "base64Binary", "code", "id", "oid", + "unsignedInt", "positiveInt", "markdown", "url", "canonical", "uuid", "integer64", "Identifier", "HumanName", "Address", "ContactPoint", + "Timing", "Quantity", "SimpleQuantity", "Attachment", "Range", "Period", "Ratio", "RatioRange", "CodeableConcept", "Coding", "SampledData", + "Age", "Distance", "Duration", "Count", "Money", "MoneyQuantity", "Annotation", "Signature")) { + return Utilities.pathURL(sp, "datatypes.html#"+type); + } else if (Utilities.existsInList(type, "ContactDetail", "Contributor", "DataRequirement", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", + "UsageContext", "Expression", "ExtendedContactDetail", "VirtualServiceDetail", "Availability", "MonetaryComponent")) { + return Utilities.pathURL(sp, "metadatatypes.html#"+type); + } else if (Utilities.existsInList(type, "DataType", "BackboneType", "BackboneElement", "Element")) { + return Utilities.pathURL(sp, "types.html#"+type); + } else if (Utilities.existsInList(type, "Extension")) { + return Utilities.pathURL(sp, "extensions.html#"+type); + } else if (Utilities.existsInList(type, "Narrative", "xhtml")) { + return Utilities.pathURL(sp, "narrative.html#"+type); + } else if (Utilities.existsInList(type, "Meta")) { + return Utilities.pathURL(sp, "resource.html#"+type); + } else if (Utilities.existsInList(type, "Reference", "CodeableReference")) { + return Utilities.pathURL(sp, "references.html#"+type); + } else { + return Utilities.pathURL(sp, type.toLowerCase()+".html#"+type); + } + } + + private List translateResourceName(ConceptMap map, String name) { List res = new ArrayList<>(); for (ConceptMapGroupComponent g : map.getGroup()) { @@ -746,16 +1551,17 @@ public class XVerExtensionPackageGenerator { } return res; } - - private List findTranslatedElements(ElementDefinition eds, StructureDefinition structureDefinition, ConceptMap elementMap, String tpid, ConceptMapRelationship resrel) { - List res = new ArrayList(); + // + private List findTranslatedElements(ElementDefinition eds, StructureDefinition structureDefinition, ConceptMap elementMap, ConceptMapRelationship resrel) { + // String ver = structureDefinition.getFhirVersion().toCode(); + List res = new ArrayList(); String path = eds.getPath(); String epath = path.contains(".") ? structureDefinition.getName()+path.substring(path.indexOf(".") ): structureDefinition.getName(); List names = translateElementName(path, elementMap, epath); for (TranslatedCode n : names) { ElementDefinition ed = structureDefinition.getDifferential().getElementByPath(n.getCode()); if (ed != null) { - res.add(new ElementDefinitionPair(structureDefinition, ed, ConceptMapUtilities.combineRelationships(resrel, n.getRelationship()))); + res.add(new MatchedElementDefinition(ed, !ed.getPath().contains(".") ? resrel : n.getRelationship())); } } return res; @@ -782,96 +1588,26 @@ public class XVerExtensionPackageGenerator { return res; } - private void rendererElement(XhtmlNode td, ElementDefinition ed, String prefix, Map structures, String origName, ConceptMapRelationship relationship) { - if (relationship != null) { - switch (relationship) { - case EQUIVALENT: - td.tx("="); - break; - case NOTRELATEDTO: - td.tx("!="); - break; - case RELATEDTO: - td.tx("~"); - break; - case SOURCEISBROADERTHANTARGET: - td.tx(">"); - break; - case SOURCEISNARROWERTHANTARGET: - td.tx("<"); - break; - } - } - - if (ed.getPath().contains(".")) { - XhtmlNode span = td.span().attribute("title", ed.getPath()); - if (origName != null && !origName.equals(ed.getPath())) { - span.style("color: maroon; font-weight: bold"); - } - String[] p = ed.getPath().split("\\."); - for (int i = 0; i < p.length; i++) { - if (i == p.length - 1) { - span.tx(p[i]); - } else { - span.tx(p[i].substring(0, 1)); - span.tx("."); - } - } - boolean first = true; - for (TypeRefComponent t : ed.getType()) { - StructureDefinition sd = structures.get(t.getWorkingCode()); - if (sd != null && !sd.getAbstract()) { - if (first) {td.tx(" : "); first = false; } else { td.tx("|"); td.wbr(); } - td.ah(prefix+"#"+t.getWorkingCode()).tx(t.getWorkingCode()); - if (t.hasTargetProfile()) { - td.tx("("); - boolean tfirst = true; - for (CanonicalType u : t.getTargetProfile()) { - if (tfirst) {tfirst = false; } else { td.tx("|"); td.wbr(); } - String rt = tail(u.getValue()); - td.ah(prefix+"#"+rt).tx(rt); - } - - td.tx(")"); - } - } - } - td.tx(" : "); - td.tx("["); - td.tx(ed.getMin()); - td.tx(".."); - td.tx(ed.getMax()); - td.tx("]"); - } else { - if (Utilities.noString(prefix)) { - td.tx(ed.getPath()); - } else { - XhtmlNode ah = td.ah(prefix+"#"+ed.getPath()); - if (origName != null && !origName.equals(ed.getPath())) { - ah.style("color: maroon; font-weight: bold"); - } - ah.tx(ed.getPath()); - } - } - } - private String tail(String value) { return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value; } - private void checkCM(ConceptMap cm) { - System.out.println("Check: "+cm.getId()); - Set src = new HashSet<>(); - for (ConceptMapGroupComponent g : cm.getGroup()) { - for (SourceElementComponent e : g.getElement()) { - if (!e.hasUserData("used")) { - System.out.println(" Unused code: "+e.getCode()); - } - src.add(e.getCode()); - } - } + + private void loadVersions() throws IOException { + FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager.Builder().build(); + vdr2 = loadR2(pcm); + versions.put("r2", vdr2); + vdr3 = loadR3(pcm); + versions.put("r3", vdr3); + vdr4 = loadR4(pcm); + versions.put("r4", vdr4); + vdr4b = loadR4B(pcm); + versions.put("r4b", vdr4b); + vdr5 = loadR5(pcm); + versions.put("r5", vdr5); } + private VersionDefinitions loadR2(FilesystemPackageCacheManager pcm) throws FHIRException, IOException { VersionDefinitions vd = new VersionDefinitions(); vd.version = FhirPublication.DSTU2; @@ -941,349 +1677,128 @@ public class XVerExtensionPackageGenerator { return vd; } - private void generate(VersionDefinitions src, VersionDefinitions dst, String dstDir) throws IOException { - String fn = "hl7.fhir.extensions."+VersionUtilities.getNameForVersion(src.version.toCode())+":"+dst.version.toCode()+".tgz"; - String fnt = VersionUtilities.getNameForVersion(src.version.toCode())+"-"+VersionUtilities.getNameForVersion(dst.version.toCode())+".txt"; - StringBuilder s = new StringBuilder(); - s.append("Paths to generate Extensions for:\r\n"); - System.out.println("Generate "+fn); - Map structures = new HashMap<>(); - Map valueSets = new HashMap<>(); - Map codeSystems = new HashMap<>(); - for (String name : Utilities.sorted(src.structures.keySet())) { - StructureDefinition sd = src.structures.get(name); - lookForExtensions(sd, src, dst, structures, valueSets, codeSystems, s); - } - TextFile.stringToFile(s.toString(), Utilities.path(dstDir, fnt)); - buildOutputPackage(fn, structures, valueSets, codeSystems); - } - - private void lookForExtensions(StructureDefinition sd, VersionDefinitions src, VersionDefinitions dst, Map structures, Map valueSets, Map codeSystems, StringBuilder s) { - if ((sd.getKind() == StructureDefinitionKind.RESOURCE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getAbstract() - && !Utilities.existsInList(sd.getName(), "Element", "BackboneElement", "Resource", "DomainResource")) { - String name = getTargetResourceName(sd.getName(), src.version, dst.version); - StructureDefinition sdt = dst.structures.get(name); - for (ElementDefinition ed : sd.getSnapshot().getElement()) { - if (!ed.getPath().endsWith(".id") && !ed.getPath().endsWith(".extension") && !ed.getPath().endsWith(".modifierExtension") && !Utilities.existsInList(ed.typeSummary(), "Element", "BackboneElement")) { - ElementDefinition md = getMatchingDefinition(sd, sdt, name, ed, dst.structures, src.version, dst.version); - if (md == null) { - System.out.println(" xve: "+ed.getPath()); - s.append(" { \"code\" : \""+ed.getPath()+"\", \"noMap\" : true },\r\n"); - } else { - for (TypeRefComponent tr : ed.getType()) { - if (Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) { - boolean typeFound = false; - for (TypeRefComponent dtr : md.getType()) { - if (typeMatches(dtr.getWorkingCode(), tr.getWorkingCode())) { - typeFound = true; - break; - } - } - if (!typeFound) { - System.out.println(" xve: "+ed.getPath()+" for "+tr.getCode()); - // s.append(ed.getPath()+" for "+tr.getCode()+"\r\n"); - } - } - } - } - } - } - } - } - - private boolean typeMatches(String c1, String c2) { - if (c1.equals(c2)) { - return true; - } - if (Utilities.existsInList(c1, "string", "markdown") && Utilities.existsInList(c2, "string", "markdown")) { - return true; - } - if (Utilities.existsInList(c1, "url", "uri", "canonical") && Utilities.existsInList(c2, "url", "uri", "canonical")) { - return true; - } - return false; - } - - private ElementDefinition getMatchingDefinition(StructureDefinition sd, StructureDefinition sdt, String name, ElementDefinition ed, Map structures, FhirPublication srcVer, FhirPublication dstVer) { - if (sdt == null) { + @Override + public String getLink(String system, String code) { + if (system == null) { return null; } - // the resource renaming and element renaming information is a little disjunct :-( - // so we try it twice - String ename = getTargetElementName(ed.getPath(), srcVer, dstVer); - if (ename != null) { - ElementDefinition edt = getElementBypath(ename, sdt, structures); - if (edt != null) { - return edt; - } + switch (system) { + case "http://hl7.org/fhir/1.0/resource-types" : return determineResourceLink("1.0", code); + case "http://hl7.org/fhir/3.0/resource-types" : return determineResourceLink("3.0", code); + case "http://hl7.org/fhir/4.0/resource-types" : return determineResourceLink("4.0", code); + case "http://hl7.org/fhir/4.3/resource-types" : return determineResourceLink("4.3", code); + case "http://hl7.org/fhir/5.0/resource-types" : return determineResourceLink("5.0", code); + default: + return null; } - if (ed.getPath().contains(".")) { - ename = name+ed.getPath().substring(ed.getPath().indexOf(".")); - } else { - ename = name; - } - ename = getTargetElementName(ename, srcVer, dstVer); - return ename == null ? null : getElementBypath(ename, sdt, structures); } - private ElementDefinition getElementBypath(String ename, StructureDefinition sdt, Map structures) { - ElementDefinition ed = sdt.getSnapshot().getElementByPath(ename); - if (ed != null) { - return ed; + private String determineResourceLink(String version, String code) { + if ("5.0".equals(version)) { + return "cross-version-"+code+".html"; + } else { + if (vdr5.structures.containsKey(code)) { + return "cross-version-"+code+".html"; + } + List codes = new ArrayList<>(); + codes.add(code); + if ("1.0".equals(version)) { + codes = translateResourceName(codes, cm("resources-2to3")); + version = "3.0"; + } + if ("3.0".equals(version)) { + codes = translateResourceName(codes, cm("resources-3to4")); + version = "4.0"; + } + if ("4.0".equals(version)) { + codes = translateResourceName(codes, cm("resources-4to4b")); + version = "4.3"; + } + if ("4.3".equals(version)) { + codes = translateResourceName(codes, cm("resources-4bto5")); + } + for (String c : codes) { + if (vdr5.structures.containsKey(c)) { + return "cross-version-"+c+".html"; + } + } } - String n = head(ename); - while (n.contains(".")) { - ed = sdt.getSnapshot().getElementByPath(n); - if (ed != null) { - if (ed.getType().size() != 1) { - return null; + return null; + } + + private List translateResourceName(List codes, ConceptMap cm) { + List res = new ArrayList(); + for (ConceptMapGroupComponent grp : cm.getGroup()) { + for (SourceElementComponent src : grp.getElement()) { + if (codes.contains(src.getCode())) { + for (TargetElementComponent tgt : src.getTarget()) { + if (tgt.getRelationship() == ConceptMapRelationship.EQUIVALENT || tgt.getRelationship() == ConceptMapRelationship.RELATEDTO || + tgt.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET || tgt.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) { + res.add(tgt.getCode()); + } + } } - StructureDefinition sd = structures.get(ed.getTypeFirstRep().getCode()); - if (sd != null && !sd.getAbstract() && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { - return getElementBypath(sd.getName()+"."+ename.substring(n.length()+1), sd, structures); - } else { - return null; + } + } + return res; + } + + + @Override + public List getMembers(String uri) { + String version = VersionUtilities.getNameForVersion(uri); + VersionDefinitions vd = versions.get(version.toLowerCase()); + if (vd != null) { + if (uri.contains("#")) { + String ep = uri.substring(uri.indexOf("#")+1); + String base = uri.substring(0, uri.indexOf("#")); + String name = base.substring(44); + StructureDefinition sd = vd.structures.get(name); + if (sd != null) { + ElementDefinition ed = sd.getDifferential().getElementByPath(ep); + return processVS(vd, ed.getBinding().getValueSet()); } } else { - n = head(n); + // todo } } - return null; + return null; } - private String head(String n) { - if (n.contains(".")) { - return n.substring(0, n.lastIndexOf(".")); - } else { - return n; - } - } - - - private String getTargetElementName(String name, FhirPublication srcVer, FhirPublication dstVer) { - switch (srcVer) { - case DSTU2: - switch (dstVer) { - case DSTU2: - return name; - case STU3: - return tn(name, true, "elementmap2to3"); - case R4: - return tn(name, true, "elementmap2to3", "elementmap3to4"); - case R4B: - return tn(name, true, "elementmap2to3", "elementmap3to4", "elementmap4to4B"); - case R5: - return tn(name, true, "elementmap2to3", "elementmap3to4", "elementmap4to5"); - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case STU3: - switch (dstVer) { - case DSTU2: - return tn(name, true, "-elementmap2to3"); - case STU3: - return name; - case R4: - return tn(name, true, "elementmap3to4"); - case R4B: - return tn(name, true, "elementmap3to4", "elementmap4to4B"); - case R5: - return tn(name, true, "elementmap3to4", "elementmap4to5"); - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R4: - switch (dstVer) { - case DSTU2: - return tn(name, true, "-elementmap3to4", "-elementmap2to3"); - case STU3: - return tn(name, true, "-elementmap3to4"); - case R4: - return name; - case R4B: - return tn(name, true, "elementmap4to4B"); - case R5: - return tn(name, true, "elementmap4to5"); - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R4B: - switch (dstVer) { - case DSTU2: - return tn(name, true, "-elementmap4to4B", "-elementmap3to4", "-elementmap2to3"); - case STU3: - return tn(name, true, "-elementmap4to4B", "-elementmap3to4"); - case R4: - return tn(name, true, "-elementmap4to4B"); - case R4B: - return name; - case R5: - return tn(name, true, "elementmap4Bto5"); - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R5: - switch (dstVer) { - case DSTU2: - return tn(name, true, "-elementmap4to5", "-elementmap3to4", "-elementmap2to3"); - case STU3: - return tn(name, true, "-elementmap4to5", "-elementmap3to4"); - case R4: - return tn(name, true, "-elementmap4to5"); - case R4B: - return tn(name, true, "-elementmap4Bto5"); - case R5: - return name; - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - default: - throw new Error("Unable to map element name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - } - private String getTargetResourceName(String name, FhirPublication srcVer, FhirPublication dstVer) { - if (Utilities.existsInList(name, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", - "string", "time", "unsignedInt", "uri", "url", "uuid", "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "CodeableReference", "Coding", - "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "RatioRange", "Reference", - "SampledData", "Signature", "Timing", "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", - "TriggerDefinition", "UsageContext", "Availability", "ExtendedContactDetail", "Dosage", "Meta", "Extension", "ElementDefinition", "Narrative", - "MarketingStatus", "ProductShelfLife", "ProdCharacteristic", "Population")) { - return name; // data types that haven't been renamed - } - switch (srcVer) { - case DSTU2: - switch (dstVer) { - case DSTU2: - return name; - case STU3: - return tn(name, false, "resourcemap2to3"); - case R4: - return tn(name, false, "resourcemap2to3", "resourcemap3to4"); - case R4B: - return tn(name, false, "resourcemap2to3", "resourcemap3to4", "resourcemap4to4B"); - case R5: - return tn(name, false, "resourcemap2to3", "resourcemap3to4", "resourcemap4to5"); - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case STU3: - switch (dstVer) { - case DSTU2: - return tn(name, false, "resourcemap3to2"); - case STU3: - return name; - case R4: - return tn(name, false, "resourcemap3to4"); - case R4B: - return tn(name, false, "resourcemap3to4", "resourcemap4to4B"); - case R5: - return tn(name, false, "resourcemap3to4", "resourcemap4to5"); - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R4: - switch (dstVer) { - case DSTU2: - return tn(name, false, "resourcemap4to3", "resourcemap3to2"); - case STU3: - return tn(name, false, "resourcemap4to3"); - case R4: - return name; - case R4B: - return tn(name, false, "resourcemap4to4B"); - case R5: - return tn(name, false, "resourcemap4to5"); - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R4B: - switch (dstVer) { - case DSTU2: - return tn(name, false, "resourcemap4Bto4", "resourcemap4to3", "resourcemap3to2"); - case STU3: - return tn(name, false, "resourcemap4Bto4", "resourcemap4to3"); - case R4: - return tn(name, false, "resourcemap4Bto4"); - case R4B: - return name; - case R5: - return tn(name, false, "resourcemap4Bto5"); - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - case R5: - switch (dstVer) { - case DSTU2: - return tn(name, false, "resourcemap5to4", "resourcemap4to3", "resourcemap3to2"); - case STU3: - return tn(name, false, "resourcemap5to4", "resourcemap4to3"); - case R4: - return tn(name, false, "resourcemap5to4"); - case R4B: - return tn(name, false, "resourcemap5to4B"); - case R5: - return name; - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - default: - throw new Error("Unable to map resource name from "+srcVer.toCode()+" to "+dstVer.toCode()); - } - } - - private String tn(String name, boolean defToName, String... names) { - String n = name; - for (String id : names) { - boolean reverse = false; - if (id.startsWith("-")) { - reverse = true; - id = id.substring(1); - } - if (n != null) { - ConceptMap cm = getCM(id); - n = translate(cm, n, defToName, reverse); - } - } - return n; - } - - private String translate(ConceptMap cm, String n, boolean defToName, boolean reverse) { - for (ConceptMapGroupComponent g : cm.getGroup()) { - for (SourceElementComponent t : g.getElement()) { - if (reverse) { - for (TargetElementComponent m : t.getTarget()) { - if (n.equals(m.getCode())) { - t.setUserData("used", true); - return t.getCode(); - } + private List processVS(VersionDefinitions vd, String url) { + ValueSet vs = vd.valueSets.get(url); + if (vs != null && vs.hasCompose() && !vs.getCompose().hasExclude()) { + List list = new ArrayList<>(); + for (ConceptSetComponent inc : vs.getCompose().getInclude()) { + if (inc.hasValueSet() || inc.hasFilter()) { + return null; + } + String system = inc.getSystem().replace("http://hl7.org/fhir/", "http://hl7.org/fhir/"+VersionUtilities.getMajMin(vd.version.toCode())+"/"); + String vn = VersionUtilities.getNameForVersion(vd.version.toCode()); + if (inc.hasConcept()) { + for (ConceptReferenceComponent cc : inc.getConcept()) { + list.add(new Coding().setSystem(system).setCode(cc.getCode()).setDisplay(cc.getDisplay()+" ("+vn+")")); } } else { - if (n.equals(t.getCode())) { - t.setUserData("used", true); - for (TargetElementComponent m : t.getTarget()) { - return m.getCode(); - } + CodeSystem cs = vd.codeSystems.get(inc.getSystem()); + if (cs == null) { return null; + } else { + addCodings(system, vn, cs.getConcept(), list); } } } + return list; + } else { + return null; } - return defToName ? n : null; } - private ConceptMap getCM(String id) { - // - // for (ConceptMap cm : nameMaps) { - // if (cm.getId().equals(id)) { - // return cm; - // } - // } - throw new Error("Unable to resolve map "+id); - } - - private void buildOutputPackage(String fn, Map structures, - Map valueSets, Map codeSystems) { - // TODO Auto-generated method stub + private void addCodings(String system, String vn, List concepts, List list) { + for (ConceptDefinitionComponent cd : concepts) { + list.add(new Coding().setSystem(system).setCode(cd.getCode()).setDisplay(cd.getDisplay()+" ("+vn+")")); + addCodings(system, vn, cd.getConcept(), list); + } }