001package org.hl7.fhir.validation.instance.utils; 002 003import java.util.ArrayList; 004import java.util.HashMap; 005import java.util.List; 006import java.util.Map; 007 008import org.hl7.fhir.r5.context.IWorkerContext; 009import org.hl7.fhir.r5.elementmodel.Element; 010import org.hl7.fhir.r5.model.ElementDefinition; 011import org.hl7.fhir.r5.model.StructureDefinition; 012import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 013import org.hl7.fhir.utilities.Utilities; 014 015public class NodeStack { 016 017 protected IWorkerContext context; 018 private ElementDefinition definition; 019 private Element element; 020 private ElementDefinition extension; 021 private String literalPath; // xpath format 022 private List<String> logicalPaths; // dotted format, various entry points 023 private NodeStack parent; 024 private ElementDefinition type; 025 private String workingLang; 026 private Map<String, Element> ids; 027 private boolean resetPoint = false; 028 private boolean contained = false; 029 030 public NodeStack(IWorkerContext context) { 031 this.context = context; 032 } 033 034 public NodeStack(IWorkerContext context, String initialPath, Element element, String validationLanguage) { 035 this.context = context; 036 ids = new HashMap<>(); 037 this.element = element; 038 literalPath = (initialPath == null ? "" : initialPath+".") + element.getPath(); 039 workingLang = validationLanguage; 040 if (!element.getName().equals(element.fhirType())) { 041 logicalPaths = new ArrayList<>(); 042 logicalPaths.add(element.fhirType()); 043 } 044 } 045 046 public NodeStack(IWorkerContext context, Element element, String refPath, String validationLanguage) { 047 this.context = context; 048 ids = new HashMap<>(); 049 this.element = element; 050 literalPath = refPath + "->" + element.getName(); 051 workingLang = validationLanguage; 052 } 053 054 public String addToLiteralPath(String... path) { 055 StringBuilder b = new StringBuilder(); 056 b.append(getLiteralPath()); 057 for (String p : path) { 058 if (p.startsWith(":")) { 059 b.append("["); 060 b.append(p.substring(1)); 061 b.append("]"); 062 } else { 063 b.append("."); 064 b.append(p); 065 } 066 } 067 return b.toString(); 068 } 069 070 private ElementDefinition getDefinition() { 071 return definition; 072 } 073 074 public Element getElement() { 075 return element; 076 } 077 078 public String getLiteralPath() { 079 return literalPath == null ? "" : literalPath; 080 } 081 082 public List<String> getLogicalPaths() { 083 return logicalPaths == null ? new ArrayList<String>() : logicalPaths; 084 } 085 086 private ElementDefinition getType() { 087 return type; 088 } 089 090 public NodeStack pushTarget(Element element, int count, ElementDefinition definition, ElementDefinition type) { 091 return pushInternal(element, count, definition, type, "->"); 092 } 093 094 public NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) { 095 return pushInternal(element, count, definition, type, "."); 096 } 097 098 private NodeStack pushInternal(Element element, int count, ElementDefinition definition, ElementDefinition type, String sep) { 099 NodeStack res = new NodeStack(context); 100 res.ids = ids; 101 res.parent = this; 102 res.workingLang = this.workingLang; 103 res.element = element; 104 res.definition = definition; 105 res.contained = contained; 106 res.literalPath = getLiteralPath() + sep + element.getName(); 107 if (count > -1) 108 res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]"; 109 else if (element.getSpecial() == null && element.getProperty().isList()) 110 res.literalPath = res.literalPath + "[0]"; 111 else if (element.getProperty().isChoice()) { 112 String n = res.literalPath.substring(res.literalPath.lastIndexOf(".") + 1); 113 String en = element.getProperty().getName(); 114 en = en.substring(0, en.length() - 3); 115 String t = n.substring(en.length()); 116 if (isPrimitiveType(Utilities.uncapitalize(t))) 117 t = Utilities.uncapitalize(t); 118 res.literalPath = res.literalPath.substring(0, res.literalPath.lastIndexOf(".")) + "." + en + ".ofType(" + t + ")"; 119 } 120 res.logicalPaths = new ArrayList<String>(); 121 if (type != null) { 122 // type will be null if we on a stitching point of a contained resource, or if.... 123 res.type = type; 124 String tn = res.type.getPath(); 125 String t = tail(definition.getPath()); 126 if ("Resource".equals(tn)) { 127 tn = element.fhirType(); 128 } 129 for (String lp : getLogicalPaths()) { 130 res.logicalPaths.add(lp + "." + t); 131 if (t.endsWith("[x]")) { 132 res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + ".ofType("+type.getPath()+")"); 133 res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath()); 134 } 135 } 136 res.logicalPaths.add(tn); 137 } else if (definition != null) { 138 for (String lp : getLogicalPaths()) { 139 res.logicalPaths.add(lp + "." + element.getName()); 140 } 141 res.logicalPaths.add(definition.typeSummary()); 142 } else 143 res.logicalPaths.addAll(getLogicalPaths()); 144 return res; 145 } 146 147 private void setType(ElementDefinition type) { 148 this.type = type; 149 } 150 151 public NodeStack resetIds() { 152 ids = new HashMap<>(); 153 resetPoint = true; 154 return this; 155 } 156 public Map<String, Element> getIds() { 157 return ids; 158 } 159 private String tail(String path) { 160 return path.substring(path.lastIndexOf(".") + 1); 161 } 162 163 public boolean isPrimitiveType(String code) { 164 StructureDefinition sd = context.fetchTypeDefinition(code); 165 return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 166 } 167 168 public String getWorkingLang() { 169 return workingLang; 170 } 171 172 public void setWorkingLang(String workingLang) { 173 this.workingLang = workingLang; 174 } 175 176 public NodeStack getParent() { 177 return parent; 178 } 179 180 public void qualifyPath(String qualifier) { 181 literalPath = literalPath + qualifier; 182 183 } 184 185 public boolean isResetPoint() { 186 return resetPoint; 187 } 188 189 @Override 190 public String toString() { 191 return literalPath; 192 } 193 194 public int depth() { 195 if (parent == null) { 196 return 0; 197 } else { 198 return parent.depth()+1; 199 } 200 } 201 202 public boolean isContained() { 203 return contained; 204 } 205 206 public void setContained(boolean contained) { 207 this.contained = contained; 208 } 209 210 211}