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