
001package org.hl7.fhir.r5.renderers; 002 003import java.io.ByteArrayOutputStream; 004import java.io.IOException; 005import java.io.UnsupportedEncodingException; 006import java.nio.charset.Charset; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Map; 012import java.util.Set; 013 014import org.hl7.fhir.exceptions.DefinitionException; 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.exceptions.FHIRFormatError; 017import org.hl7.fhir.r5.context.ContextUtilities; 018import org.hl7.fhir.r5.extensions.ExtensionConstants; 019import org.hl7.fhir.r5.model.CanonicalType; 020import org.hl7.fhir.r5.model.Element; 021import org.hl7.fhir.r5.model.Enumerations; 022import org.hl7.fhir.r5.model.ExampleScenario; 023import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioActorComponent; 024import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceComponent; 025import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceContainedInstanceComponent; 026import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceVersionComponent; 027import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessComponent; 028import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepAlternativeComponent; 029import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepComponent; 030import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepOperationComponent; 031import org.hl7.fhir.r5.model.Resource; 032import org.hl7.fhir.r5.model.StructureDefinition; 033import org.hl7.fhir.r5.renderers.utils.RenderingContext; 034import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 035import org.hl7.fhir.r5.renderers.utils.Resolver; 036import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 037import org.hl7.fhir.r5.utils.EOperationOutcome; 038import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 039import org.hl7.fhir.utilities.Utilities; 040import org.hl7.fhir.utilities.xhtml.XhtmlDocument; 041import org.hl7.fhir.utilities.xhtml.XhtmlNode; 042 043import net.sourceforge.plantuml.FileFormat; 044import net.sourceforge.plantuml.FileFormatOption; 045import net.sourceforge.plantuml.SourceStringReader; 046 047@MarkedToMoveToAdjunctPackage 048public class ExampleScenarioRenderer extends TerminologyRenderer { 049 050 private Set<String> stepPrefixes = new HashSet<>(); 051 052 public ExampleScenarioRenderer(RenderingContext context) { 053 super(context); 054 } 055 056 @Override 057 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 058 if (r.isDirect()) { 059 renderResourceTechDetails(r, x); 060 genSummaryTable(status, x, (ExampleScenario) r.getBase()); 061 render(status, x, (ExampleScenario) r.getBase(), r); 062 } else { 063 // the intention is to change this in the future 064 x.para().tx("ExampleScenarioRenderer only renders native resources directly"); 065 } 066 } 067 068 @Override 069 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 070 return canonicalTitle(r); 071 } 072 073 public void render(RenderingStatus status, XhtmlNode x, ExampleScenario scen, ResourceWrapper res) throws FHIRException { 074 075 Map<Element, String> prefixes = new HashMap<>(); 076 assignProcessPrefixes(prefixes, scen.getProcess(), 1); 077 078 try { 079 if (context.getScenarioMode() == null) { 080 renderActors(status, res, x, scen); 081 } else { 082 switch (context.getScenarioMode()) { 083 case ACTORS: 084 renderActors(status, res, x, scen); 085 break; 086 case INSTANCES: 087 renderInstances(status, res, x, scen); 088 break; 089 case PROCESSES: 090 renderProcesses(prefixes, status, x, scen); 091 break; 092 default: 093 throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_UN, context.getScenarioMode()) + " "); 094 } 095 } 096 } catch (Exception e) { 097 e.printStackTrace(); 098 throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_ERR_REN, scen.getUrl(), e) + " "); 099 } 100 } 101 102 public String renderDiagram(RenderingStatus status, ResourceWrapper res, ExampleScenario scen) throws IOException { 103 104 Map<Element, String> prefixes = new HashMap<>(); 105 assignProcessPrefixes(prefixes, scen.getProcess(), 1); 106 107 try { 108 String plantUml = toPlantUml(prefixes, status, res, scen); 109 SourceStringReader reader = new SourceStringReader(plantUml); 110 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 111 reader.outputImage(os, new FileFormatOption(FileFormat.SVG)); 112 os.close(); 113 114 final String svg = new String(os.toByteArray(), Charset.forName("UTF-8")); 115 return svg; 116 } catch (Exception e) { 117 return "<p style=\"color: maroon\"><b>"+Utilities.escapeXml(e.getMessage())+"</b></p>"; 118 } 119 } 120 121 protected String toPlantUml(Map<Element, String> prefixes, RenderingStatus status, ResourceWrapper res, ExampleScenario scen) throws IOException { 122 String plantUml = "@startuml\r\n"; 123 plantUml += "Title " + (scen.hasTitle() ? scen.getTitle() : scen.getName()) + "\r\n\r\n"; 124 Map<String, String> actorKeys = new HashMap<String, String>(); 125 126 for (ExampleScenarioActorComponent actor: scen.getActor()) { 127 String actorType = actor.getType().equals(Enumerations.ExampleScenarioActorType.PERSON) ? "actor" : "participant"; 128 actorKeys.put(actor.getKey(), escapeKey(actor.getKey())); 129 plantUml += actorType + " \"" + creolLink(actor.getTitle(), "#a_" + actor.getKey(), actor.getDescription()) + "\" as " + actorKeys.get(actor.getKey()) + "\r\n"; 130 } 131 plantUml += "\r\n"; 132 133 for (ExampleScenarioProcessComponent process: scen.getProcess()) { 134 plantUml += toPlantUml(prefixes, status, res, process, scen, actorKeys); 135 } 136 plantUml += "@enduml"; 137 138 return plantUml; 139 } 140 141 private String escapeKey(String origKey) { 142 char[] chars = origKey.toCharArray(); 143 for (int i=0; i<chars.length; i++) { 144 char c = chars[i]; 145 if (!((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='@' || c=='.')) 146 chars[i] = '_'; 147 } 148 return new String(chars); 149 } 150 151 protected String toPlantUml(Map<Element, String> prefixes, RenderingStatus status, ResourceWrapper res, ExampleScenarioProcessComponent process, ExampleScenario scen, Map<String, String> actorKeys) throws IOException { 152 String plantUml = "group " + process.getTitle() + " " + creolLink("details", prefixes.get(process), process.getDescription()) + "\r\n"; 153 154 Map<String,Boolean> actorsActive = new HashMap<String, Boolean>(); 155 for (ExampleScenarioActorComponent actor : scen.getActor()) { 156 actorsActive.put(actor.getKey(), Boolean.FALSE); 157 } 158 159 for (ExampleScenarioProcessStepComponent step: process.getStep()) { 160 plantUml += toPlantUml(prefixes, status, res, step, scen, actorsActive, actorKeys); 161 if (step.getPause()) 162 plantUml += context.formatPhrase(RenderingContext.EX_SCEN_TIME)+"\n"; 163 } 164 165 plantUml += "end\r\n\r\n"; 166 return plantUml; 167 } 168 169 protected String toPlantUml(Map<Element, String> prefixes, RenderingStatus status, ResourceWrapper res, ExampleScenarioProcessStepComponent step, ExampleScenario scen, Map<String,Boolean> actorsActive, Map<String, String> actorKeys) throws IOException { 170 String plantUml = ""; 171 if (step.hasWorkflow()) { 172 XhtmlNode n = new XhtmlDocument(); 173 renderCanonical(status, res, n, Resource.class, step.getWorkflowElement()); 174 XhtmlNode ref = n.getChildNodes().get(0); 175 plantUml += noteOver(scen.getActor(), context.formatPhrase(RenderingContext.EXAMPLE_SCEN_STEP_SCEN, creolLink((ref.getContent()), ref.getAttribute("href")))); 176 } else if (step.hasProcess()) 177 plantUml += toPlantUml(prefixes, status, res, step.getProcess(), scen, actorKeys); 178 else { 179 // Operation 180 plantUml += toPlantUml(prefixes, step.getOperation(), scen, actorsActive, actorKeys); 181 } 182 183 return plantUml; 184 } 185 186 protected String toPlantUml(Map<Element, String> prefixes, ExampleScenarioProcessStepOperationComponent op, ExampleScenario scen, Map<String,Boolean> actorsActive, Map<String, String> actorKeys) { 187 StringBuilder plantUml = new StringBuilder(); 188 plantUml.append(handleActivation(op.getInitiator(), op.getInitiatorActive(), actorsActive, actorKeys)); 189 plantUml.append(handleActivation(op.getReceiver(), op.getReceiverActive(), actorsActive, actorKeys)); 190 plantUml.append(actorKeys.get(op.getInitiator()) + " -> " + actorKeys.get(op.getReceiver()) + ": "); 191 plantUml.append(creolLink(op.getTitle(), prefixes.get(op), op.getDescription())); 192 if (op.hasRequest()) { 193 plantUml.append(" (" + creolLink("payload", linkForInstance(op.getRequest())) + ")\r\n"); 194 } 195 if (op.hasResponse()) { 196 plantUml.append("activate " + actorKeys.get(op.getReceiver()) + "\r\n"); 197 plantUml.append(actorKeys.get(op.getReceiver()) + " --> " + actorKeys.get(op.getInitiator()) + ": "); 198 plantUml.append(creolLink("response", prefixes.get(op), op.getDescription())); 199 plantUml.append(" (" + creolLink("payload", linkForInstance(op.getResponse())) + ")\r\n"); 200 plantUml.append("deactivate " + actorKeys.get(op.getReceiver()) + "\r\n"); 201 } 202 plantUml.append(handleDeactivation(op.getInitiator(), op.getInitiatorActive(), actorsActive, actorKeys)); 203 plantUml.append(handleDeactivation(op.getReceiver(), op.getReceiverActive(), actorsActive, actorKeys)); 204 205 return plantUml.toString(); 206 } 207 208 private String handleActivation(String actorId, boolean active, Map<String,Boolean> actorsActive, Map<String, String> actorKeys) { 209 String plantUml = ""; 210 Boolean actorWasActive = actorsActive.get(actorId); 211 if (active && !actorWasActive) { 212 plantUml += "activate " + actorKeys.get(actorId) + "\r\n"; 213 } 214 return plantUml; 215 } 216 217 private String handleDeactivation(String actorId, boolean active, Map<String,Boolean> actorsActive, Map<String, String> actorKeys) { 218 String plantUml = ""; 219 Boolean actorWasActive = actorsActive.get(actorId); 220 if (actorWasActive != null) { 221 if (!active && actorWasActive) { 222 plantUml += "deactivate " + actorKeys.get(actorId) + "\r\n"; 223 } 224 if (active != actorWasActive) { 225 actorsActive.remove(actorId); 226 actorsActive.put(actorId, Boolean.valueOf(active)); 227 } 228 } 229 return plantUml; 230 } 231 232 private String linkForInstance(ExampleScenarioInstanceContainedInstanceComponent ref) { 233 String plantUml = "#i_" + ref.getInstanceReference(); 234 if (ref.hasVersionReference()) 235 plantUml += "v_" + ref.getVersionReference(); 236 return plantUml; 237 } 238 239 private String noteOver(List<ExampleScenarioActorComponent> actors, String text) { 240 String plantUml = "Note over "; 241 List actorKeys = new ArrayList<String>(); 242 for (ExampleScenarioActorComponent actor: actors) { 243 actorKeys.add(actor.getKey()); 244 } 245 plantUml += String.join(", ", actorKeys); 246 plantUml += " " + text; 247 return plantUml; 248 } 249 250 private String creolLink(String text, String url) { 251 return creolLink(text, url, null); 252 } 253 254 private String creolLink(String text, String url, String flyover) { 255 String s = "[[" + url; 256 if (flyover!=null) 257 s += "{" + flyover + "}"; 258 s += " " + text + "]]"; 259 return s; 260 } 261 262 public boolean renderActors(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ExampleScenario scen) throws IOException { 263 XhtmlNode tbl = x.table("table-striped table-bordered", false); 264 XhtmlNode thead = tbl.tr(); 265 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME)); 266 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 267 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC)); 268 for (ExampleScenarioActorComponent actor : scen.getActor()) { 269 XhtmlNode tr = tbl.tr(); 270 XhtmlNode nameCell = tr.td(); 271 nameCell.an(context.prefixAnchor("a_" + actor.getKey())); 272 nameCell.tx(actor.getTitle()); 273 tr.td().tx(actor.getType().getDisplay()); 274 addMarkdown(tr.td().style("overflow-wrap:break-word"), actor.getDescription()); 275 } 276 return true; 277 } 278 279 public boolean renderInstances(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ExampleScenario scen) throws IOException { 280 XhtmlNode tbl = x.table("table-striped table-bordered", false); 281 XhtmlNode thead = tbl.tr(); 282 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME)); 283 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 284 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONTENT)); 285 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC)); 286 287 Map<String, String> instanceNames = new HashMap<String, String>(); 288 for (ExampleScenarioInstanceComponent instance : scen.getInstance()) { 289 instanceNames.put("i_" + instance.getKey(), instance.getTitle()); 290 if (instance.hasVersion()) { 291 for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) { 292 instanceNames.put("i_" + instance.getKey() + "v_" + version.getKey(), version.getTitle()); 293 } 294 } 295 } 296 297 for (ExampleScenarioInstanceComponent instance : scen.getInstance()) { 298 XhtmlNode row = tbl.tr(); 299 XhtmlNode nameCell = row.td(); 300 nameCell.an(context.prefixAnchor("i_" + instance.getKey())); 301 nameCell.tx(instance.getTitle()); 302 XhtmlNode typeCell = row.td(); 303 if (instance.hasVersion()) 304 typeCell.attribute("rowSpan", Integer.toString(instance.getVersion().size()+1)); 305 306 if (!instance.hasStructureVersion() || instance.getStructureType().getSystem().equals("")) { 307 if (instance.hasStructureVersion()) 308 typeCell.tx((context.formatPhrase(RenderingContext.EX_SCEN_FVER, instance.getStructureVersion()) + " ") + " "); 309 if (instance.hasStructureProfileCanonicalType()) { 310 renderCanonical(status, res, typeCell, StructureDefinition.class, instance.getStructureProfileCanonicalType()); 311 } else if (instance.hasStructureProfileUriType()) { 312 renderBase(status, typeCell, instance.getStructureProfileUriType()); 313 } else { 314 CanonicalType ct = new CanonicalType("http://hl7.org/fhir/StructureDefinition/" + instance.getStructureType().getCode()); 315 renderCanonical(status, res, typeCell, StructureDefinition.class, ct); 316 } 317 } else { 318 renderDataType(status, typeCell, wrapWC(res, instance.getStructureVersionElement())); 319 typeCell.tx(" "+(context.formatPhrase(RenderingContext.GENERAL_VER_LOW, instance.getStructureVersion())+" ")); 320 if (instance.hasStructureProfile()) { 321 typeCell.tx(" "); 322 if (instance.hasStructureProfileCanonicalType()) { 323 renderCanonical(status, res, typeCell, StructureDefinition.class, instance.getStructureProfileCanonicalType()); 324 } 325 } 326 } 327 if (instance.hasContent() && instance.getContent().hasReference()) { 328 // Force end-user mode to avoid ugly references 329 RenderingContext.ResourceRendererMode mode = context.getMode(); 330 context.setMode(RenderingContext.ResourceRendererMode.END_USER); 331 renderReference(status, row.td(), wrapWC(res, instance.getContent().copy().setDisplay("here"))); 332 context.setMode(mode); 333 } else 334 row.td(); 335 336 XhtmlNode descCell = row.td(); 337 addMarkdown(descCell, instance.getDescription()); 338 if (instance.hasContainedInstance()) { 339 descCell.b().tx(context.formatPhrase(RenderingContext.EX_SCEN_CONTA) + " "); 340 boolean first = true; 341 for (ExampleScenarioInstanceContainedInstanceComponent contained: instance.getContainedInstance()) { 342 if (first) first = false; else descCell.tx(", "); 343 String key = "i_" + contained.getInstanceReference(); 344 if (contained.hasVersionReference()) 345 key += "v_" + contained.getVersionReference(); 346 String description = instanceNames.get(key); 347 if (description==null) 348 throw new FHIRException("Unable to find contained instance " + key + " under " + instance.getKey()); 349 descCell.ah(context.prefixLocalHref("#" + key)).tx(description); 350 } 351 } 352 353 for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) { 354 row = tbl.tr(); 355 nameCell = row.td().style("padding-left: 10px;"); 356 nameCell.an("i_" + instance.getKey() + "v_" + version.getKey()); 357 XhtmlNode nameItem = nameCell.ul().li(); 358 nameItem.tx(version.getTitle()); 359 360 if (version.hasContent() && version.getContent().hasReference()) { 361 // Force end-user mode to avoid ugly references 362 RenderingContext.ResourceRendererMode mode = context.getMode(); 363 context.setMode(RenderingContext.ResourceRendererMode.END_USER); 364 renderReference(status, row.td(), wrapWC(res, version.getContent().copy().setDisplay("here"))); 365 context.setMode(mode); 366 } else 367 row.td(); 368 369 descCell = row.td(); 370 addMarkdown(descCell, instance.getDescription()); 371 } 372 } 373 return true; 374 } 375 376 public boolean renderProcesses(Map<Element, String> prefixes, RenderingStatus status, XhtmlNode x, ExampleScenario scen) throws IOException { 377 Map<String, ExampleScenarioActorComponent> actors = new HashMap<>(); 378 for (ExampleScenarioActorComponent actor: scen.getActor()) { 379 actors.put(actor.getKey(), actor); 380 } 381 382 Map<String, ExampleScenarioInstanceComponent> instances = new HashMap<>(); 383 for (ExampleScenarioInstanceComponent instance: scen.getInstance()) { 384 instances.put(instance.getKey(), instance); 385 } 386 387 for (ExampleScenarioProcessComponent process : scen.getProcess()) { 388 renderProcess(prefixes, status, x, process, actors, instances); 389 } 390 return true; 391 } 392 393 private int assignProcessPrefixes(Map<Element, String> prefixes, List<ExampleScenarioProcessComponent> processes, int i) { 394 for (ExampleScenarioProcessComponent process : processes) { 395 prefixes.put(process, "node"+i); 396 i++; 397 } 398 399 for (ExampleScenarioProcessComponent process : processes) { 400 i = assignStepPrefixes(prefixes, process.getStep(), i); 401 } 402 return i; 403 } 404 405 private int assignStepPrefixes(Map<Element, String> prefixes, List<ExampleScenarioProcessStepComponent> steps, int i) { 406 for (ExampleScenarioProcessStepComponent step : steps) { 407 prefixes.put(step, "node"+i); 408 i++; 409 } 410 411 for (ExampleScenarioProcessStepComponent step : steps) { 412 if (step.hasProcess()) { 413 prefixes.put(step.getProcess(), "node"+i); 414 i++; 415 i = assignStepPrefixes(prefixes, step.getProcess().getStep(), i); 416 } 417 i = assignAlternativePrefixes(prefixes, step.getAlternative(), i); 418 if (step.hasOperation()) { 419 prefixes.put(step.getOperation(), "node"+i); 420 i++; 421 } 422 } 423 return i; 424 } 425 426 private int assignAlternativePrefixes(Map<Element, String> prefixes, List<ExampleScenarioProcessStepAlternativeComponent> list, int i) { 427 for (ExampleScenarioProcessStepAlternativeComponent alt : list) { 428 prefixes.put(alt, "node"+i); 429 i++; 430 } 431 432 for (ExampleScenarioProcessStepAlternativeComponent alt : list) { 433 i = assignStepPrefixes(prefixes, alt.getStep(), i); 434 } 435 return i; 436 } 437 438 public void renderProcess(Map<Element, String> prefixes, RenderingStatus status, XhtmlNode x, ExampleScenarioProcessComponent process, Map<String, ExampleScenarioActorComponent> actors, Map<String, ExampleScenarioInstanceComponent> instances) throws IOException { 439 XhtmlNode div = x.div(); 440 div.an(context.prefixAnchor(prefixes.get(process))); 441 div.b().tx(context.formatPhrase(RenderingContext.EX_SCEN_PROC, process.getTitle())+" "); 442 if (process.hasDescription()) 443 addMarkdown(div, process.getDescription()); 444 if (process.hasPreConditions()) { 445 div.para().b().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_PRECON)); 446 addMarkdown(div, process.getPreConditions()); 447 } 448 if (process.hasPostConditions()) { 449 div.para().b().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_POSTCON)); 450 addMarkdown(div, process.getPostConditions()); 451 } 452 XhtmlNode tbl = div.table("table-striped table-bordered", false).style("width:100%"); 453 XhtmlNode thead = tbl.tr(); 454 thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_STEP)); 455 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME)); 456 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC)); 457 thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_IN)); 458 thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_REC)); 459 thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_REQUEST)); 460 thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_RES)); 461 int stepCount = 1; 462 for (ExampleScenarioProcessStepComponent step: process.getStep()) { 463 renderStep(prefixes, status, tbl, step, actors, instances); 464 stepCount++; 465 } 466 467 // Now go through the steps again and spit out any child processes 468 stepCount = 1; 469 for (ExampleScenarioProcessStepComponent step: process.getStep()) { 470 stepSubProcesses(prefixes, status, tbl, step, actors, instances); 471 stepCount++; 472 } 473 } 474 475 private void stepSubProcesses(Map<Element, String> prefixes, RenderingStatus status, XhtmlNode x, ExampleScenarioProcessStepComponent step, Map<String, ExampleScenarioActorComponent> actors, Map<String, ExampleScenarioInstanceComponent> instances) throws IOException { 476 if (step.hasProcess()) 477 renderProcess(prefixes, status, x, step.getProcess(), actors, instances); 478 if (step.hasAlternative()) { 479 int altNum = 1; 480 for (ExampleScenarioProcessStepAlternativeComponent alt: step.getAlternative()) { 481 int stepCount = 1; 482 for (ExampleScenarioProcessStepComponent altStep: alt.getStep()) { 483 stepSubProcesses(prefixes, status, x, altStep, actors, instances); 484 stepCount++; 485 } 486 altNum++; 487 } 488 } 489 } 490 491 private boolean renderStep(Map<Element, String> prefixes, RenderingStatus status, XhtmlNode tbl, ExampleScenarioProcessStepComponent step, Map<String, ExampleScenarioActorComponent> actors, Map<String, ExampleScenarioInstanceComponent> instances) throws IOException { 492 XhtmlNode row = tbl.tr(); 493 if (step.getPause()) { 494 var td = row.td().colspan(7); 495 td.style("opacity: 0.7"); 496 td.an(context.prefixAnchor(prefixes.get(step))); 497 td.tx("Pause"); 498 } else { 499 XhtmlNode prefixCell = row.td(); 500 prefixCell.an(context.prefixAnchor(prefixes.get(step))); 501 prefixCell.tx(step.getNumber()); 502 if (step.hasProcess()) { 503 XhtmlNode n = row.td().colspan(6); 504 n.tx(context.formatPhrase(RenderingContext.EX_SCEN_SEE)); 505 n.ah(context.prefixLocalHref("#" + context.prefixAnchor(prefixes.get(step)))).tx(step.getProcess().getTitle()); 506 n.tx(" "+ context.formatPhrase(RenderingContext.EX_SCEN_BEL)); 507 508 } else if (step.hasWorkflow()) { 509 XhtmlNode n = row.td().colspan(6); 510 n.an(context.prefixAnchor(prefixes.get(step))); 511 n.tx(context.formatPhrase(RenderingContext.EX_SCEN_OTH)); 512 String link = new ContextUtilities(context.getWorker()).getLinkForUrl(context.getLink(KnownLinkType.SPEC, true), step.getWorkflow()); 513 String title = "Unknown title"; 514 if (step.getWorkflowElement().hasExtension(ExtensionConstants.EXT_DISPLAY_NAME)) { 515 title = step.getWorkflowElement().getExtensionString(ExtensionConstants.EXT_DISPLAY_NAME); 516 } else { 517 Resolver.ResourceWithReference rres = context.getResolver().resolve(context, step.getWorkflow(), null); 518 if (rres != null && rres.getResource() != null && rres.getResource().has("title")) 519 title = rres.getResource().primitiveValue("title"); 520 } 521 if (link!= null) 522 n.ah(context.prefixLocalHref(link)).tx(title); 523 else 524 n.addText(title); 525 526 } else { 527 // Must be an operation 528 ExampleScenarioProcessStepOperationComponent op = step.getOperation(); 529 XhtmlNode name = row.td(); 530 name.tx(op.getTitle()); 531 if (op.hasType()) { 532 name.tx(" - "); 533 renderCoding(status, name, wrapNC(op.getType())); 534 } 535 XhtmlNode descCell = row.td(); 536 addMarkdown(descCell, op.getDescription()); 537 538 addActor(row, op.getInitiator(), actors); 539 addActor(row, op.getReceiver(), actors); 540 addInstance(row, op.getRequest(), instances); 541 addInstance(row, op.getResponse(), instances); 542 } 543 544 int altNum = 1; 545 for (ExampleScenarioProcessStepAlternativeComponent alt : step.getAlternative()) { 546 XhtmlNode altHeading = tbl.tr().colspan(7).td(); 547 altHeading.para().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_ALT, alt.getTitle())+" "); 548 if (alt.hasDescription()) 549 addMarkdown(altHeading, alt.getDescription()); 550 int stepCount = 1; 551 for (ExampleScenarioProcessStepComponent subStep : alt.getStep()) { 552 renderStep(prefixes, status, tbl, subStep, actors, instances); 553 stepCount++; 554 } 555 altNum++; 556 } 557 } 558 559 return true; 560 } 561 562 private void addActor(XhtmlNode row, String actorId, Map<String, ExampleScenarioActorComponent> actors) throws FHIRException { 563 XhtmlNode actorCell = row.td(); 564 if (actorId==null) 565 return; 566 ExampleScenarioActorComponent actor = actors.get(actorId); 567 if (actor==null) 568 throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_UN_ACT, actorId)+" "); 569 actorCell.ah("#a_" + actor.getKey(), actor.getDescription()).tx(actor.getTitle()); 570 } 571 572 private void addInstance(XhtmlNode row, ExampleScenarioInstanceContainedInstanceComponent instanceRef, Map<String, ExampleScenarioInstanceComponent> instances) { 573 XhtmlNode instanceCell = row.td(); 574 if (instanceRef==null || instanceRef.getInstanceReference()==null) 575 return; 576 ExampleScenarioInstanceComponent instance = instances.get(instanceRef.getInstanceReference()); 577 if (instance==null) { 578 instanceCell.b().tx("Bad reference: "+instanceRef.getInstanceReference()); 579 } else if (instanceRef.hasVersionReference()) { 580 ExampleScenarioInstanceVersionComponent theVersion = null; 581 for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) { 582 if (version.getKey().equals(instanceRef.getVersionReference())) { 583 theVersion = version; 584 break; 585 } 586 } 587 if (theVersion==null) 588 throw new FHIRException("Unable to find referenced version " + instanceRef.getVersionReference() + " within instance " + instanceRef.getInstanceReference()); 589 instanceCell.ah(context.prefixLocalHref("#i_" + instance.getKey() + "v_"+ theVersion.getKey()) , theVersion.getDescription()).tx(theVersion.getTitle()); 590 591 } else 592 instanceCell.ah(context.prefixLocalHref("#i_" + instance.getKey()), instance.getDescription()).tx(instance.getTitle()); 593 } 594}