001package org.hl7.fhir.r5.renderers; 
002
003import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
004import static java.time.temporal.ChronoField.YEAR;
005
006import java.io.IOException;
007import java.math.BigDecimal;
008import java.text.NumberFormat;
009import java.time.LocalDate;
010import java.time.ZoneId;
011import java.time.ZonedDateTime;
012import java.time.format.DateTimeFormatter;
013import java.time.format.DateTimeFormatterBuilder;
014import java.time.format.FormatStyle;
015import java.time.format.SignStyle;
016import java.util.Currency;
017import java.util.List;
018
019import org.hl7.fhir.exceptions.DefinitionException;
020import org.hl7.fhir.exceptions.FHIRException;
021import org.hl7.fhir.exceptions.FHIRFormatError;
022import org.hl7.fhir.r5.context.IWorkerContext;
023import org.hl7.fhir.r5.model.BackboneType;
024import org.hl7.fhir.r5.model.Base;
025import org.hl7.fhir.r5.model.BaseDateTimeType;
026import org.hl7.fhir.r5.model.CanonicalResource;
027import org.hl7.fhir.r5.model.CodeSystem;
028import org.hl7.fhir.r5.model.CodeableConcept;
029import org.hl7.fhir.r5.model.Coding;
030import org.hl7.fhir.r5.model.ContactPoint;
031import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
032import org.hl7.fhir.r5.model.DataType;
033import org.hl7.fhir.r5.model.DateTimeType;
034import org.hl7.fhir.r5.model.ElementDefinition;
035import org.hl7.fhir.r5.model.Extension;
036import org.hl7.fhir.r5.model.NamingSystem;
037import org.hl7.fhir.r5.model.PrimitiveType;
038import org.hl7.fhir.r5.model.Quantity;
039import org.hl7.fhir.r5.model.Resource;
040import org.hl7.fhir.r5.model.StructureDefinition;
041import org.hl7.fhir.r5.model.ValueSet;
042import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
043import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent;
044import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
045import org.hl7.fhir.r5.renderers.utils.RenderingContext;
046import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
047import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
048import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
049import org.hl7.fhir.r5.terminologies.JurisdictionUtilities;
050import org.hl7.fhir.r5.terminologies.utilities.SnomedUtilities;
051import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
052import org.hl7.fhir.r5.utils.ToolingExtensions;
053import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
054import org.hl7.fhir.utilities.Utilities;
055import org.hl7.fhir.utilities.VersionUtilities;
056import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
057import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
058import org.hl7.fhir.utilities.xhtml.NodeType;
059import org.hl7.fhir.utilities.xhtml.XhtmlNode;
060import org.hl7.fhir.utilities.xhtml.XhtmlParser;
061
062import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 
063
064public class DataRenderer extends Renderer implements CodeResolver { 
065
066  // -- 1. context -------------------------------------------------------------- 
067
068  public DataRenderer(RenderingContext context) { 
069    super(context); 
070  } 
071
072  public DataRenderer(IWorkerContext worker) { 
073    super(worker); 
074  } 
075
076  // -- 2. Markdown support ------------------------------------------------------- 
077
078  public static String processRelativeUrls(String markdown, String path) { 
079    if (markdown == null) { 
080      return ""; 
081    } 
082    if (!Utilities.isAbsoluteUrl(path)) { 
083      return markdown; 
084    } 
085    String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; 
086    StringBuilder b = new StringBuilder(); 
087    int i = 0; 
088    while (i < markdown.length()) { 
089      if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 
090        int j = i + 2; 
091        while (j < markdown.length() && markdown.charAt(j) != ')') 
092          j++; 
093        if (j < markdown.length()) { 
094          String url = markdown.substring(i+2, j); 
095          if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 
096            // it's relative - so it's relative to the base URL 
097            b.append("]("); 
098            b.append(basePath); 
099          } else { 
100            b.append("]("); 
101          } 
102          i = i + 1; 
103        } else  
104          b.append(markdown.charAt(i)); 
105      } else { 
106        b.append(markdown.charAt(i)); 
107      } 
108      i++; 
109    } 
110    return b.toString(); 
111  } 
112
113  protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { 
114    addMarkdown(x, processRelativeUrls(text, path)); 
115  } 
116
117  protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 
118    if (text != null) { 
119      // 1. custom FHIR extensions 
120      while (text.contains("[[[")) { 
121        String left = text.substring(0, text.indexOf("[[[")); 
122        String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 
123        String right = text.substring(text.indexOf("]]]")+3); 
124        String path = null; 
125        String url = link; 
126        String[] parts = link.split("\\#"); 
127        if (parts[0].contains(".")) { 
128          path = parts[0]; 
129          parts[0] = parts[0].substring(0, parts[0].indexOf(".")); 
130        } 
131        StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]); 
132        if (p == null) { 
133          p = getContext().getWorker().fetchTypeDefinition(parts[0]); 
134        } 
135        if (context.getTypeMap().containsKey(parts[0])) { 
136          p = getContext().getWorker().fetchTypeDefinition(context.getTypeMap().get(parts[0]));           
137        } 
138        if (p == null) { 
139          p = getContext().getWorker().fetchResource(StructureDefinition.class, link); 
140        } 
141        if (p != null) { 
142          if ("Extension".equals(p.getType())) { 
143            path = null; 
144          } else if (p.hasSnapshot()) { 
145            path = p.getSnapshot().getElementFirstRep().getPath(); 
146          } else if (Utilities.isAbsoluteUrl(path)) { 
147            path = null; 
148          } 
149          url = p.getWebPath(); 
150          if (url == null) { 
151            url = p.getUserString("filename"); 
152          } 
153        } else { 
154          throw new DefinitionException(context.formatPhrase(RenderingContext.DATA_REND_MKDWN_LNK, link) + " "); 
155        } 
156
157        text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right; 
158      } 
159
160      // 2. markdown 
161      String s = getContext().getMarkdown().process(text, "narrative generator"); 
162      XhtmlParser p = new XhtmlParser(); 
163      XhtmlNode m; 
164      try { 
165        m = p.parse("<div>"+s+"</div>", "div"); 
166      } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 
167        throw new FHIRFormatError(e.getMessage(), e); 
168      } 
169      x.addChildNodes(m.getChildNodes()); 
170    } 
171  } 
172
173  protected void smartAddText(XhtmlNode p, String text) { 
174    if (text == null) 
175      return; 
176
177    String[] lines = text.split("\\r\\n"); 
178    for (int i = 0; i < lines.length; i++) { 
179      if (i > 0) 
180        p.br(); 
181      p.addText(lines[i]); 
182    } 
183  } 
184
185  // -- 3. General Purpose Terminology Support ----------------------------------------- 
186
187  private static String snMonth(String m) { 
188    switch (m) { 
189    case "1" : return "Jan"; 
190    case "2" : return "Feb"; 
191    case "3" : return "Mar"; 
192    case "4" : return "Apr"; 
193    case "5" : return "May"; 
194    case "6" : return "Jun"; 
195    case "7" : return "Jul"; 
196    case "8" : return "Aug"; 
197    case "9" : return "Sep"; 
198    case "10" : return "Oct"; 
199    case "11" : return "Nov"; 
200    case "12" : return "Dec"; 
201    default: return null; 
202    } 
203  } 
204
205  public static String describeVersion(String version) { 
206    if (version.startsWith("http://snomed.info/sct")) { 
207      String[] p = version.split("\\/"); 
208      String ed = null; 
209      String dt = ""; 
210
211      if (p[p.length-2].equals("version")) { 
212        ed = p[p.length-3]; 
213        String y = p[p.length-3].substring(4, 8); 
214        String m = p[p.length-3].substring(2, 4);  
215        dt = " rel. "+snMonth(m)+" "+y; 
216      } else { 
217        ed = p[p.length-1]; 
218      } 
219      switch (ed) { 
220      case "900000000000207008": return "Intl"+dt;  
221      case "731000124108": return "US"+dt;  
222      case "32506021000036107": return "AU"+dt;  
223      case "449081005": return "ES"+dt;  
224      case "554471000005108": return "DK"+dt;  
225      case "11000146104": return "NL"+dt;  
226      case "45991000052106": return "SE"+dt;  
227      case "999000041000000102": return "UK"+dt;  
228      case "20611000087101": return "CA"+dt;  
229      case "11000172109": return "BE"+dt;  
230      default: return "??"+dt;  
231      }       
232    } else { 
233      return version; 
234    } 
235  } 
236
237  public String displaySystem(String system) { 
238    if (system == null) 
239      return (context.formatPhrase(RenderingContext.DATA_REND_NOT_STAT)); 
240    if (system.equals("http://loinc.org")) 
241      return (context.formatPhrase(RenderingContext.DATA_REND_LOINC)); 
242    if (system.startsWith("http://snomed.info")) 
243      return (context.formatPhrase(RenderingContext.DATA_REND_SNOMED)); 
244    if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 
245      return (context.formatPhrase(RenderingContext.DATA_REND_RXNORM)); 
246    if (system.equals("http://hl7.org/fhir/sid/icd-9")) 
247      return (context.formatPhrase(RenderingContext.DATA_REND_ICD)); 
248    if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 
249      return (context.formatPhrase(RenderingContext.DATA_REND_DICOM)); 
250    if (system.equals("http://unitsofmeasure.org")) 
251      return (context.formatPhrase(RenderingContext.GENERAL_UCUM)); 
252    if (system.equals("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl")) 
253      return (context.formatPhrase(RenderingContext.GENERAL_NCI_THES)); 
254    
255
256    CodeSystem cs = context.getContext().fetchCodeSystem(system); 
257    if (cs != null) { 
258      return crPresent(cs); 
259    } 
260    return tails(system); 
261  } 
262
263  private String crPresent(CanonicalResource cr) { 
264    if (cr.hasUserData("presentation")) { 
265      return cr.getUserString("presentation"); 
266    } 
267    if (cr.hasTitle()) 
268      return context.getTranslated(cr.getTitleElement()); 
269    if (cr.hasName()) 
270      return context.getTranslated(cr.getNameElement()); 
271    return cr.toString(); 
272  } 
273
274  private String tails(String system) { 
275    if (system.contains("/")) { 
276      return system.substring(system.lastIndexOf("/")+1); 
277    } else { 
278      return (context.formatPhrase(RenderingContext.DATA_REND_UNKNWN)); 
279    } 
280  } 
281
282  protected String makeAnchor(String codeSystem, String code) { 
283    String s = codeSystem+'-'+code; 
284    StringBuilder b = new StringBuilder(); 
285    for (char c : s.toCharArray()) { 
286      if (Utilities.isValidHtmlAnchorChar(c)) {
287        b.append(c);
288      } else {
289        b.append("|"+Integer.toHexString(c)); // not % to save double coding confusing users
290      }
291    } 
292    return b.toString(); 
293  } 
294
295  private String lookupCode(String system, String version, String code) { 
296    if (JurisdictionUtilities.isJurisdiction(system)) { 
297      return JurisdictionUtilities.displayJurisdiction(system+"#"+code); 
298    } 
299    ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withLanguage(context.getLocale().toString().replace("_", "-")).withVersionFlexible(true), system, version, code, null); 
300
301    if (t != null && t.getDisplay() != null) 
302      return t.getDisplay(); 
303    else 
304      return code; 
305  } 
306
307  protected String describeLang(String lang) { 
308    // special cases: 
309    if ("fr-CA".equals(lang)) { 
310      return "French (Canadian)"; // this one was omitted from the value set 
311    } 
312    ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 
313    if (v != null) { 
314      ConceptReferenceComponent l = null; 
315      for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
316        if (cc.getCode().equals(lang)) 
317          l = cc; 
318      } 
319      if (l == null) { 
320        if (lang.contains("-")) { 
321          lang = lang.substring(0, lang.indexOf("-")); 
322        } 
323        for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
324          if (cc.getCode().equals(lang)) { 
325            l = cc; 
326            break; 
327          } 
328        } 
329        if (l == null) { 
330          for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
331            if (cc.getCode().startsWith(lang+"-")) { 
332              l = cc; 
333              break; 
334            } 
335          } 
336        } 
337      } 
338      if (l != null) { 
339        if (lang.contains("-")) 
340          lang = lang.substring(0, lang.indexOf("-")); 
341        String en = l.getDisplay(); 
342        String nativelang = null; 
343        for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 
344          if (cd.getLanguage().equals(lang)) 
345            nativelang = cd.getValue(); 
346        } 
347        if (nativelang == null) 
348          return en+" ("+lang+")"; 
349        else 
350          return nativelang+" ("+en+", "+lang+")"; 
351      } 
352    } 
353    return lang; 
354  } 
355
356  private boolean isCanonical(String path) { 
357    if (!path.endsWith(".url"))  
358      return false; 
359    String t = path.substring(0, path.length()-4); 
360    StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t); 
361    if (sd == null) 
362      return false; 
363    if (VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()).contains(t)) { 
364      return true; 
365    } 
366    if (Utilities.existsInList(t,  
367        "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", 
368        "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", 
369        "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" 
370        )) 
371      return true; 
372    return false; 
373  } 
374
375  // -- 4. Language support ------------------------------------------------------ 
376
377  public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { 
378    return context.getTranslated(value); 
379  } 
380
381  public String gt(ResourceWrapper value) { 
382    return context.getTranslated(value); 
383  } 
384
385  // -- 6. General purpose extension rendering ----------------------------------------------  
386
387  public boolean hasRenderableExtensions(DataType element) { 
388    for (Extension ext : element.getExtension()) { 
389      if (canRender(ext)) { 
390        return true; 
391      } 
392    } 
393    return false; 
394  } 
395
396  public boolean hasRenderableExtensions(BackboneType element) { 
397    for (Extension ext : element.getExtension()) { 
398      if (canRender(ext)) { 
399        return true; 
400      } 
401    } 
402    return element.hasModifierExtension();   
403  } 
404
405  public boolean hasRenderableExtensions(ResourceWrapper element) { 
406    for (ResourceWrapper ext : element.extensions()) { 
407      if (canRender(ext)) { 
408        return true; 
409      } 
410    } 
411    return false; 
412  } 
413
414  private String getExtensionLabel(Extension ext) { 
415    StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.getUrl()); 
416    if (sd != null && ext.hasValue() && ext.getValue().isPrimitive() && sd.hasSnapshot()) { 
417      for (ElementDefinition ed : sd.getSnapshot().getElement()) { 
418        if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 
419          return context.getTranslated(ed.getLabelElement()); 
420        } 
421      } 
422    } 
423    return null;     
424  } 
425
426  private String getExtensionLabel(ResourceWrapper ext) { 
427    StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 
428    if (sd != null && ext.has("value") && ext.child("value").isPrimitive() && sd.hasSnapshot()) { 
429      for (ElementDefinition ed : sd.getSnapshot().getElement()) { 
430        if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 
431          return context.getTranslated(ed.getLabelElement()); 
432        } 
433      } 
434    } 
435    return null;     
436  } 
437
438  private boolean canRender(Extension ext) { 
439    return getExtensionLabel(ext) != null; 
440  } 
441
442
443  private boolean canRender(ResourceWrapper ext) { 
444    StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 
445    if (sd != null && ext.has("value") && ext.isPrimitive("value") && sd.hasSnapshot()) { 
446      for (ElementDefinition ed : sd.getSnapshot().getElement()) { 
447        if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 
448          return true; 
449        } 
450      } 
451    } 
452    return false;     
453  }
454
455  public void renderExtensionsInList(RenderingStatus status, XhtmlNode ul, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException { 
456    for (ResourceWrapper ext : element.extensions()) { 
457      if (canRender(ext)) { 
458        String lbl = getExtensionLabel(ext); 
459        XhtmlNode li = ul.li(); 
460        li.tx(lbl); 
461        li.tx(": "); 
462        renderDataType(status, li, ext.child("value")); 
463      } 
464    } 
465  } 
466
467  //  public void renderExtensionsInList(XhtmlNode ul, BackboneType element) throws FHIRFormatError, DefinitionException, IOException { 
468  //    for (Extension ext : element.getModifierExtension()) { 
469  //      if (canRender(ext)) { 
470  //        String lbl = getExtensionLabel(ext); 
471  //        XhtmlNode li = ul.li(); 
472  //        li = li.b(); 
473  //        li.tx(lbl); 
474  //        li.tx(": ");         
475  //        render(li, ext.getValue()); 
476  //      } else { 
477  //        // somehow have to do better than this  
478  //        XhtmlNode li = ul.li(); 
479  //        li.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 
480  //      } 
481  //    } 
482  //    for (Extension ext : element.getExtension()) { 
483  //      if (canRender(ext)) { 
484  //        String lbl = getExtensionLabel(ext); 
485  //        XhtmlNode li = ul.li(); 
486  //        li.tx(lbl); 
487  //        li.tx(": "); 
488  //        render(li, ext.getValue()); 
489  //      } 
490  //    } 
491  //  } 
492  //   
493  public void renderExtensionsInText(RenderingStatus status, XhtmlNode x, ResourceWrapper element, String sep) throws FHIRFormatError, DefinitionException, IOException { 
494    boolean first = true; 
495    for (ResourceWrapper ext : element.extensions()) { 
496      if (canRender(ext)) { 
497        status.setExtensions(true);
498        if (first) { 
499          first = false; 
500        } else { 
501          x.tx(sep); 
502          x.tx(" "); 
503        } 
504
505        String lbl = getExtensionLabel(ext); 
506        x.tx(lbl); 
507        x.tx(": "); 
508        renderDataType(status, x, ext.child("value")); 
509      } 
510    } 
511  } 
512  
513
514  protected void checkRenderExtensions(RenderingStatus status, XhtmlNode x, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException {
515    if (element.has("extension")) {
516      boolean someCanRender = false;
517      for (ResourceWrapper ext : element.children("extension")) {
518        ResourceWrapper value = ext.child("value");
519        if (canRender(ext) && value.isPrimitive()) {
520          someCanRender = true;
521        }
522      }
523      if (someCanRender) {
524        status.setExtensions(true);
525        x.tx(" (");
526        renderExtensionsInText(status, x, element, ", ");
527        x.tx(")");
528      } 
529    }
530
531  }
532
533  //  public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 
534  //    boolean first = true; 
535  //    for (Extension ext : element.getModifierExtension()) { 
536  //      if (first) { 
537  //        first = false; 
538  //      } else { 
539  //        div.tx(sep); 
540  //        div.tx(" "); 
541  //      } 
542  //      if (canRender(ext)) { 
543  //        String lbl = getExtensionLabel(ext); 
544  //        XhtmlNode b = div.b(); 
545  //        b.tx(lbl); 
546  //        b.tx(": "); 
547  //        render(div, ext.getValue()); 
548  //      } else { 
549  //        // somehow have to do better than this  
550  //        div.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 
551  //      } 
552  //    } 
553  //    for (Extension ext : element.getExtension()) { 
554  //      if (canRender(ext)) { 
555  //        if (first) { 
556  //          first = false; 
557  //        } else { 
558  //          div.tx(sep); 
559  //          div.tx(" "); 
560  //        } 
561  //          
562  //        String lbl = getExtensionLabel(ext); 
563  //        div.tx(lbl); 
564  //        div.tx(": "); 
565  //        render(div, ext.getValue()); 
566  //      } 
567  //    } 
568  // 
569  //  } 
570
571  // -- 6. Data type Rendering ----------------------------------------------  
572
573  public static String display(IWorkerContext context, DataType type) { 
574    return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE)).displayDataType(type); 
575  } 
576
577  public String displayBase(Base b) { 
578    if (b instanceof DataType) { 
579      return displayDataType((DataType) b); 
580    } else { 
581      return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " ");       
582    } 
583  } 
584
585  public String displayDataType(DataType type) { 
586    if (type == null) {
587      return "";
588    }
589    return displayDataType(wrapNC(type));
590  }
591
592  public String displayDataType(ResourceWrapper type) { 
593    if (type == null || type.isEmpty()) { 
594      return ""; 
595    } 
596
597    switch (type.fhirType()) {
598    case "Coding": return displayCoding(type); 
599    case "CodeableConcept": return displayCodeableConcept(type); 
600    case "CodeableReference": return displayCodeableReference(type); 
601    case "Identifier": return displayIdentifier(type); 
602    case "HumanName": return displayHumanName(type); 
603    case "Address": return displayAddress(type); 
604    case "ContactPoint": return displayContactPoint(type); 
605    case "Quantity": return displayQuantity(type); 
606    case "Range": return displayRange(type); 
607    case "Period": return displayPeriod(type); 
608    case "Timing": return displayTiming(type); 
609    case "SampledData": return displaySampledData(type); 
610    case "ContactDetail": return displayContactDetail(type); 
611    case "Annotation":  return displayAnnotation(type);
612    case "Ratio":  return displayRatio(type);
613    case "Reference" : return displayReference(type);
614    case "Money" : return displayMoney(type);
615    case "dateTime":
616    case "date" : 
617    case "instant" :
618      return displayDateTime(type);
619    default:
620      if (type.isPrimitive()) { 
621        return context.getTranslated(type); 
622      } else if (Utilities.existsInList(type.fhirType(),  "Meta", "Dosage", "Signature", "UsageContext", "RelatedArtifact", "ElementDefinition", "Base64BinaryType", "Attachment")) {
623        return "";
624      } else if ("Extension".equals(type.fhirType())) {
625        return displayDataType(type.child("value"));
626      } else {
627        return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 
628      }
629    }
630  } 
631
632  private String displayMoney(ResourceWrapper type) {
633    String currency = type.primitiveValue("currency");
634    String value = type.primitiveValue("value");
635    return context.formatPhrase(RenderingContext.DATA_REND_CURRENCY, currency, value);
636  }
637
638  private String displayAnnotation(ResourceWrapper type) {
639    return type.primitiveValue("text");
640  }
641
642  private String displayCodeableReference(ResourceWrapper type) {
643    if (type.has("reference")) {
644      return displayReference(type.child("reference"));
645    } else {
646      return displayCodeableConcept(type.child("concept"));
647    }
648  }
649
650
651  protected String displayReference(ResourceWrapper type) {
652    if (type.has("display")) {
653      return type.primitiveValue("display");
654    } else if (type.has("reference")) {
655      //      ResourceWithReference tr = resolveReference(res, r.getReference());
656      //      x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference()));
657      return "?ngen-3";
658    } else {
659      return "?ngen-4?";
660    }
661  }
662
663  private String displayRatio(ResourceWrapper type) {
664    return displayQuantity(type.child("numerator"))+" / "+displayQuantity(type.child("denominator"));
665  }
666
667  protected String displayDateTime(ResourceWrapper type) { 
668    if (!type.hasPrimitiveValue()) { 
669      return ""; 
670    } 
671
672    BaseDateTimeType t = new DateTimeType(type.primitiveValue());
673    // relevant inputs in rendering context: 
674    // timeZone, dateTimeFormat, locale, mode 
675    //   timezone - application specified timezone to use.  
676    //        null = default to the time of the date/time itself 
677    //   dateTimeFormat - application specified format for date times 
678    //        null = default to ... depends on mode 
679    //   mode - if rendering mode is technical, format defaults to XML format 
680    //   locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale)   
681    if (isOnlyDate(t.getPrecision())) { 
682
683      DateTimeFormatter fmt = getDateFormatForPrecision(t);       
684      LocalDate date = LocalDate.of(t.getYear(), t.getMonth()+1, t.getDay()); 
685      return fmt.format(date); 
686    } 
687
688    DateTimeFormatter fmt = context.getDateTimeFormat(); 
689    if (fmt == null) { 
690      if (context.isTechnicalMode()) { 
691        fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 
692      } else { 
693        fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 
694      } 
695    } 
696    ZonedDateTime zdt = ZonedDateTime.parse(t.primitiveValue()); 
697    ZoneId zone = context.getTimeZoneId(); 
698    if (zone != null) { 
699      zdt = zdt.withZoneSameInstant(zone); 
700    } 
701    return fmt.format(zdt); 
702  } 
703
704  private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 
705    DateTimeFormatter fmt = getContextDateFormat(type); 
706    if (fmt != null) { 
707      return fmt; 
708    } 
709    if (context.isTechnicalMode()) { 
710      switch (type.getPrecision()) { 
711      case YEAR: 
712        return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 
713      case MONTH: 
714        return  new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 
715      default: 
716        return DateTimeFormatter.ISO_DATE; 
717      } 
718    } else { 
719      switch (type.getPrecision()) { 
720      case YEAR: 
721        return DateTimeFormatter.ofPattern("uuuu"); 
722      case MONTH: 
723        return DateTimeFormatter.ofPattern("MMM uuuu"); 
724      default: 
725        return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 
726      } 
727    } 
728  } 
729
730  private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 
731    switch (type.getPrecision()) { 
732    case YEAR: 
733      return context.getDateYearFormat(); 
734    case MONTH: 
735      return context.getDateYearMonthFormat(); 
736    default: 
737      return context.getDateFormat(); 
738    } 
739  }    
740
741  private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 
742    return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 
743  } 
744
745
746  //  public void renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException  { 
747  ////    Base base = null; 
748  ////    try { 
749  ////      base = type.getBase(); 
750  ////    } catch (FHIRException | IOException e) { 
751  ////      x.tx(context.formatPhrase(RenderingContext.DATA_REND_ERROR, e.getMessage()) + " "); // this shouldn't happen - it's an error in the library itself 
752  ////      return; 
753  ////    } 
754  ////    if (base instanceof DataType) { 
755  ////      render(x, (DataType) base); 
756  ////    } else { 
757  ////      x.tx(context.formatPhrase(RenderingContext.DATA_REND_TO_DO, base.fhirType())); 
758  ////    } 
759  //  } 
760
761  public void renderBase(RenderingStatus status, XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 
762    if (b instanceof DataType) { 
763      renderDataType(status, x, wrapNC((DataType) b)); 
764    } else { 
765      x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " ");       
766    } 
767  } 
768  
769  public boolean canRenderDataType(String type) {
770    return context.getContextUtilities().isPrimitiveType(type) ||  Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept",  "Identifier", "HumanName", "Address", "Dosage",
771          "Expression",  "Money", "ContactPoint",  "Quantity",  "Range",  "Period", "Timing", "SampledData",  "Reference", "UsageContext",  "ContactDetail",  "Ratio",  "Attachment",  "CodeableReference");
772  }
773
774  public boolean renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
775    return renderDataType(status, null, x, type);
776  }
777  
778  public boolean renderDataType(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 
779    if (type == null) {
780      return false;
781    }
782    switch (type.fhirType()) {
783    case "dateTime":
784    case "date" : 
785    case "instant" :
786      renderDateTime(status, x, type);
787      break;
788    case "uri" :
789    case "url" :
790      renderUri(status, x, type); 
791      break;
792    case "canonical" :
793      renderCanonical(status, x, type); 
794      break;
795    case "Annotation": 
796      renderAnnotation(status, x, type); 
797      break;
798    case "Coding": 
799      renderCodingWithDetails(status, x, type); 
800      break;
801    case "CodeableConcept": 
802      renderCodeableConcept(status, x, type); 
803      break;
804    case "Identifier": 
805      renderIdentifier(status, x, type); 
806      break;
807    case "HumanName": 
808      renderHumanName(status, x, type); 
809      break;
810    case "Address": 
811      renderAddress(status, x, type); 
812      break;
813    case "Expression": 
814      renderExpression(status, x, type); 
815      break;
816    case "Money": 
817      renderMoney(status, x, type); 
818      break;
819    case "ContactPoint": 
820      renderContactPoint(status, x, type); 
821      break;
822    case "Quantity": 
823    case "Age":
824      renderQuantity(status, x, type); 
825      break;
826    case "Range": 
827      renderRange(status, x, type); 
828      break;
829    case "Period": 
830      renderPeriod(status, x, type); 
831      break;
832    case "Timing": 
833      renderTiming(status, x, type); 
834      break;
835    case "SampledData": 
836      renderSampledData(status, x, type); 
837      break;
838    case "Reference": 
839      renderReference(status, x, type); 
840      break;
841    case "UsageContext": 
842      renderUsageContext(status, x, type); 
843      break;
844    case "ContactDetail": 
845      renderContactDetail(status, x, type); 
846      break;
847    case "Ratio": 
848      renderRatio(status, x, type); 
849      break;
850    case "Attachment": 
851      renderAttachment(status, x, type); 
852      break;
853    case "CodeableReference": 
854      if (type.has("concept")) { 
855        renderCodeableConcept(status, x, type.child("concept")); 
856      } else {  
857        renderReference(status, x, type.child("reference")); 
858      } 
859      break;
860    case "code": 
861      x.tx(getTranslatedCode(type)); 
862      break;
863    case "markdown": 
864      addMarkdown(parent == null ? x : parent, context.getTranslated(type)); // note parent not focus, because of paragraph issues and markdown 
865      break;
866    case "base64Binary":
867      int length = type.primitiveValue().length();
868      if (length >= context.getBase64Limit()) {
869        x.tx(context.formatPhrase(RenderingContext.DATA_REND_BASE64, length));
870      } else {
871        x.code(type.primitiveValue());
872      }
873      break;
874    default:
875      if (type.isPrimitive()) { 
876        if (!renderPrimitiveWithNoValue(status, x, type)) {
877          x.tx(context.getTranslated(type));
878        }
879      } else { 
880        x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 
881        return false;
882      }
883    } 
884    return true;
885  } 
886
887  // overide in ResourceRenderer
888  protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
889    renderUri(status, x, type);
890  }
891
892  private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
893    renderQuantity(status, x, type.child("numerator"));
894    x.tx("/");
895    renderQuantity(status, x, type.child("denominator"));      
896    checkRenderExtensions(status, x, type);
897  }
898
899  private void renderAttachment(RenderingStatus status, XhtmlNode x, ResourceWrapper att) {
900    String ct = att.primitiveValue("contentType");
901    if (att.has("url")) {
902      x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_URL, ct, att.primitiveValue("url")));
903    } else if (att.has("data")) {
904      x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_DATA, ct, displayDataType(att.child("data"))));      
905    }    
906  }
907
908  private void renderContactDetail(RenderingStatus status, XhtmlNode x, ResourceWrapper cd) {
909    if (cd.has("name")) {
910      x.tx(cd.primitiveValue("name")+": ");
911    }
912    boolean first = true;
913    for (ResourceWrapper c : cd.children("telecom")) {
914      if (first) first = false; else x.tx(",");
915      renderContactPoint(status, x, c);
916    }
917  }
918
919  private void renderDateTime(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
920    if (!renderPrimitiveWithNoValue(status, x, type)) {
921      x.tx(displayDateTime(type));
922      checkRenderExtensions(status, x, type);
923    }
924  }
925
926  /** 
927   * this is overriden in ResourceRenderer where a better rendering is performed
928   * @param status
929   * @param x
930   * @param ref
931   * @throws IOException 
932   * @throws DefinitionException 
933   * @throws FHIRFormatError 
934   */
935  protected void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper ref) throws FHIRFormatError, DefinitionException, IOException { 
936    if (ref.has("display")) { 
937      x.tx(context.getTranslated(ref.child("display"))); 
938    } else if (ref.has("reference")) { 
939      x.tx(ref.primitiveValue("reference")); 
940    } else { 
941      x.tx("??"); 
942    } 
943  } 
944  // 
945  //  public void renderDateTime(RenderingStatus status, XhtmlNode x, Base e) { 
946  //    if (e.hasPrimitiveValue()) { 
947  //      x.addText(displayDateTime((DateTimeType) e)); 
948  //    } 
949  //  } 
950  // 
951  //  public void renderDate(RenderingStatus status, XhtmlNode x, Base e) { 
952  //    if (e.hasPrimitiveValue()) { 
953  //      x.addText(displayDateTime((DateType) e)); 
954  //    } 
955  //  } 
956  // 
957  //  public void renderDateTime(XhtmlNode x, String s) { 
958  //    if (s != null) { 
959  //      DateTimeType dt = new DateTimeType(s); 
960  //      x.addText(displayDateTime(dt)); 
961  //    } 
962  //  } 
963
964
965  protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, PrimitiveType<?> prim) throws FHIRFormatError, DefinitionException, IOException {
966    if (prim.hasPrimitiveValue()) {
967      return false;
968    }
969    boolean first = true;
970    for (Extension ext : prim.getExtension()) {
971      if (first) first = false; else x.tx(", ");
972      String url = ext.getUrl();
973      if (url.equals(ToolingExtensions.EXT_DAR)) {
974        x.tx("Absent because : ");
975        displayCode(x, wrapNC(ext.getValue()));
976      } else if (url.equals(ToolingExtensions.EXT_NF)) {
977        x.tx("Null because: ");
978        displayCode(x, wrapNC(ext.getValue()));
979      } else if (url.equals(ToolingExtensions.EXT_OT)) {
980        x.code().tx("Text: ");
981        displayCode(x, wrapNC(ext.getValue()));
982      } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) {
983        x.code().tx("Value calculated by: ");
984        renderExpression(status, x, wrapNC(ext.getValue()));
985      } else {
986        StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url);
987        if (def == null) {
988          x.code().tx(tail(url)+": ");
989        } else {
990          x.code().tx(def.present()+": ");
991        }
992        renderDataType(status, x, wrapNC(ext.getValue()));
993      }
994    }
995    status.setExtensions(true);
996    return true;
997  }
998
999  protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, ResourceWrapper prim) throws FHIRFormatError, DefinitionException, IOException {
1000    if (prim.hasPrimitiveValue()) {
1001      return false;
1002    }
1003    boolean first = true;
1004    for (ResourceWrapper ext : prim.extensions()) {
1005      if (first) first = false; else x.tx(", ");
1006      String url = ext.primitiveValue("url");
1007      if (url.equals(ToolingExtensions.EXT_DAR)) {
1008        x.tx("Absent because : ");
1009        displayCode(x, ext.child("value"));
1010      } else if (url.equals(ToolingExtensions.EXT_NF)) {
1011        x.tx("Null because: ");
1012        displayCode(x, ext.child("value"));
1013      } else if (url.equals(ToolingExtensions.EXT_OT)) {
1014        x.code().tx("Text: ");
1015        displayCode(x, ext.child("value"));
1016      } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) {
1017        x.code().tx("Value calculated by: ");
1018        renderExpression(status, x, ext.child("value"));
1019      } else {
1020        StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url);
1021        if (def == null) {
1022          x.code().tx(tail(url)+": ");
1023        } else {
1024          x.code().tx(def.present()+": ");
1025        }
1026        renderDataType(status, x, ext.child("value"));
1027      }
1028    }
1029    status.setExtensions(true);
1030    return true;
1031  }
1032
1033  protected String tail(String url) {
1034    return url.substring(url.lastIndexOf(".")+1);
1035  }
1036
1037  protected String utail(String url) {
1038    return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url;
1039  }
1040
1041  private void displayCode(XhtmlNode x, ResourceWrapper code) {
1042    x.tx(code.primitiveValue());
1043  }
1044
1045  protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 
1046    if (!renderPrimitiveWithNoValue(status, x, uri)) {
1047      String v = uri.primitiveValue();
1048
1049      if (context.getContextUtilities().isResource(v)) {
1050        v = "http://hl7.org/fhir/"+v;
1051      }
1052      if (v.startsWith("mailto:")) { 
1053        x.ah(v).addText(v.substring(7)); 
1054      } else { 
1055        Resource r = context.getContext().fetchResource(Resource.class, v); 
1056        if (r != null && r.getWebPath() != null) { 
1057          if (r instanceof CanonicalResource) { 
1058            x.ah(context.prefixLocalHref(r.getWebPath())).addText(crPresent((CanonicalResource) r));           
1059          } else { 
1060            x.ah(context.prefixLocalHref(r.getWebPath())).addText(v);           
1061          } 
1062        } else { 
1063          String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 
1064          if (url != null) {           
1065            x.ah(context.prefixLocalHref(url)).addText(v); 
1066          } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 
1067            x.ah(context.prefixLocalHref(v)).addText(v); 
1068          } else { 
1069            x.addText(v); 
1070          } 
1071        } 
1072      } 
1073    }      
1074    checkRenderExtensions(status, x, uri);
1075  } 
1076  
1077  protected void renderAnnotation(RenderingStatus status, XhtmlNode x, ResourceWrapper a) throws FHIRException { 
1078    StringBuilder b = new StringBuilder(); 
1079    if (a.has("text")) { 
1080      b.append(context.getTranslated(a.child("text"))); 
1081    } 
1082
1083    if (a.has("text") && (a.has("author") || a.has("time"))) { 
1084      b.append(" ("); 
1085    } 
1086
1087    if (a.has("author")) { 
1088      b.append(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 
1089      ResourceWrapper auth = a.child("author");
1090      if (auth.fhirType().equals("Reference")) { 
1091        b.append(auth.primitiveValue("reference")); 
1092      } else if (auth.fhirType().equals("string")) { 
1093        b.append(context.getTranslated(auth)); 
1094      } 
1095    } 
1096
1097
1098    if (a.has("time")) { 
1099      if (b.length() > 0) { 
1100        b.append(" "); 
1101      } 
1102      b.append("@").append(displayDateTime(a.child("time"))); 
1103    } 
1104    if (a.has("text") && (a.has("author") || a.has("time"))) { 
1105      b.append(")"); 
1106    } 
1107
1108
1109    x.addText(b.toString()); 
1110  } 
1111
1112  public String displayCoding(ResourceWrapper c) { 
1113    String s = ""; 
1114    if (context.isTechnicalMode()) { 
1115      s = context.getTranslated(c.child("display")); 
1116      if (Utilities.noString(s)) { 
1117        s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));         
1118      } 
1119      if (Utilities.noString(s)) { 
1120        s = displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1121      } else if (c.has("system")) { 
1122        s = s + " ("+displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"))+")"; 
1123      } else if (c.has("code")) { 
1124        s = s + " ("+c.primitiveValue("code")+")"; 
1125      } 
1126    } else { 
1127      if (c.has("display")) 
1128        return context.getTranslated(c.child("display")); 
1129      if (Utilities.noString(s)) 
1130        s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1131      if (Utilities.noString(s)) 
1132        s = c.primitiveValue("code"); 
1133    } 
1134    return s; 
1135  } 
1136
1137  private String displayCodeSource(String system, String version) { 
1138    String s = displaySystem(system); 
1139    if (version != null) { 
1140      s = s + "["+describeVersion(version)+"]"; 
1141    } 
1142    return s;     
1143  } 
1144
1145  private String displayCodeTriple(String system, String version, String code) { 
1146    if (system == null) { 
1147      if (code == null) { 
1148        return ""; 
1149      } else { 
1150        return "#"+code; 
1151      } 
1152    } else { 
1153      String s = displayCodeSource(system, version); 
1154      if (code != null) { 
1155        s = s + "#"+code; 
1156      } 
1157      return s; 
1158    } 
1159  } 
1160
1161  public String displayCoding(List<Coding> list) { 
1162    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1163    for (Coding c : list) { 
1164      b.append(displayCoding(wrapNC(c))); 
1165    } 
1166    return b.toString(); 
1167  } 
1168
1169  protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 
1170    if (c.isEmpty()) { 
1171      return; 
1172    } 
1173
1174    String url = getLinkForSystem(c.getSystem(), c.getVersion()); 
1175    String name = displayCodeSource(c.getSystem(), c.getVersion()); 
1176    if (!Utilities.noString(url)) { 
1177      pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
1178    } else {  
1179      pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
1180    } 
1181    pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 
1182    String s = context.getTranslated(c.getDisplayElement()); 
1183    if (Utilities.noString(s)) { 
1184      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1185    } 
1186    if (!Utilities.noString(s)) { 
1187      pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 
1188    } 
1189  } 
1190
1191  protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper c) { 
1192    if (c.isEmpty()) { 
1193      return; 
1194    } 
1195
1196    String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 
1197    String name = displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version")); 
1198    if (!Utilities.noString(url)) { 
1199      pieces.add(gen.new Piece(url, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 
1200    } else {  
1201      pieces.add(gen.new Piece(null, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 
1202    } 
1203    pieces.add(gen.new Piece(null, "#"+c.primitiveValue("code"), null)); 
1204    String s = context.getTranslated(c.child("display")); 
1205    if (Utilities.noString(s)) { 
1206      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1207    } 
1208    if (!Utilities.noString(s)) { 
1209      pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 
1210    } 
1211  } 
1212
1213  private String getLinkForSystem(String system, String version) { 
1214    if ("http://snomed.info/sct".equals(system)) {
1215      return "https://browser.ihtsdotools.org/";
1216    } else if ("http://loinc.org".equals(system)) { 
1217      return "https://loinc.org/";             
1218    } else if ("http://unitsofmeasure.org".equals(system)) { 
1219      return "http://ucum.org";    
1220    } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 
1221      return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf";    
1222    } else { 
1223      String url = system; 
1224      if (version != null) { 
1225        url = url + "|"+version; 
1226      } 
1227      CodeSystem cs = context.getWorker().fetchCodeSystem(url); 
1228      if (cs != null && cs.hasWebPath()) { 
1229        return cs.getWebPath(); 
1230      } 
1231      return null; 
1232    } 
1233  } 
1234
1235  protected String getLinkForCode(String system, String version, String code) { 
1236    if ("http://snomed.info/sct".equals(system)) { 
1237      return SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters());
1238    } else if ("http://loinc.org".equals(system)) { 
1239      if (!Utilities.noString(code)) { 
1240        return "https://loinc.org/"+code; 
1241      } else { 
1242        return "https://loinc.org/"; 
1243      } 
1244    } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 
1245      if (!Utilities.noString(code)) { 
1246        return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code;         
1247      } else { 
1248        return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 
1249      } 
1250    } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 
1251      if (!Utilities.noString(code)) { 
1252        return "https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?code="+code;         
1253      } else { 
1254        return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf"; 
1255      } 
1256    } else if ("urn:iso:std:iso:3166".equals(system)) { 
1257      if (!Utilities.noString(code)) { 
1258        return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code;         
1259      } else { 
1260        return "https://en.wikipedia.org/wiki/ISO_3166-2"; 
1261      } 
1262    } else { 
1263      CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 
1264      if (cs != null && cs.hasWebPath()) { 
1265        if (!Utilities.noString(code)) { 
1266          return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 
1267        } else { 
1268          return cs.getWebPath(); 
1269        } 
1270      } 
1271    }   
1272    return null; 
1273  } 
1274
1275  public CodeResolution resolveCode(String system, String code) { 
1276    return resolveCode(new Coding().setSystem(system).setCode(code)); 
1277  } 
1278
1279  public CodeResolution resolveCode(ResourceWrapper c) { 
1280    String systemName; 
1281    String systemLink; 
1282    String link; 
1283    String display = null; 
1284    String hint; 
1285
1286    if (c.has("display")) 
1287      display = context.getTranslated(c.child("display")); 
1288    if (Utilities.noString(display)) 
1289      display = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1290    if (Utilities.noString(display)) { 
1291      display = c.primitiveValue("code"); 
1292    } 
1293
1294    CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 
1295    systemLink = cs != null ? cs.getWebPath() : null; 
1296    systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 
1297    link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1298
1299    hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 
1300    return new CodeResolution(systemName, systemLink, link, display, hint); 
1301  } 
1302
1303  public CodeResolution resolveCode(Coding code) {
1304    return resolveCode(wrapNC(code));
1305  }
1306
1307  public CodeResolution resolveCode(CodeableConcept code) { 
1308    if (code.hasCoding()) { 
1309      return resolveCode(code.getCodingFirstRep()); 
1310    } else { 
1311      return new CodeResolution(null, null, null, code.getText(), code.getText()); 
1312    } 
1313  } 
1314  protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { 
1315    String s = ""; 
1316    if (c.has("display")) 
1317      s = context.getTranslated(c.child("display")); 
1318    if (Utilities.noString(s)) 
1319      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1320
1321    String sn = displaySystem(c.primitiveValue("system"));
1322    String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));
1323    XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x;    
1324    xi.tx(sn);
1325    xi.tx(" "); 
1326
1327    xi.tx(c.primitiveValue("code"));
1328    
1329    if (!Utilities.noString(s)) { 
1330      x.tx(": "); 
1331      x.tx(s); 
1332    } 
1333    if (c.has("version")) { 
1334      x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 
1335    } 
1336    checkRenderExtensions(status, x, c);
1337  } 
1338
1339  protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) {
1340    renderCoding(status, x, c, true);
1341  }
1342    
1343  protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { 
1344    String s = ""; 
1345    if (c.has("display")) 
1346      s = context.getTranslated(c.child("display")); 
1347    if (Utilities.noString(s)) 
1348      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1349
1350    if (Utilities.noString(s)) 
1351      s = c.primitiveValue("code"); 
1352
1353    if (context.isTechnicalMode() && details) {
1354      String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display");
1355      d = context.formatPhrase(d == null || d.equals(c.primitiveValue("code")) ? RenderingContext.DATA_REND_DETAILS_STATED_ND :  RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.primitiveValue("system")), c.primitiveValue("code"), d); 
1356      x.addText(s+" "+d);
1357    } else { 
1358      x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s);
1359    }
1360  } 
1361
1362  public String displayCodeableConcept(ResourceWrapper cc) { 
1363    String s = context.getTranslated(cc.child("Text")); 
1364    if (Utilities.noString(s)) { 
1365      for (ResourceWrapper c : cc.children("coding")) { 
1366        if (c.has("display")) { 
1367          s = context.getTranslated(c.child("display")); 
1368          break; 
1369        } 
1370      } 
1371    } 
1372    if (Utilities.noString(s)) { 
1373      // still? ok, let's try looking it up 
1374      for (ResourceWrapper c : cc.children("coding")) { 
1375        if (c.has("code") && c.has("system")) { 
1376          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1377          if (!Utilities.noString(s)) 
1378            break; 
1379        } 
1380      } 
1381    } 
1382
1383    if (Utilities.noString(s)) { 
1384      if (!cc.has("coding")) 
1385        s = ""; 
1386      else 
1387        s = cc.children("coding").get(0).primitiveValue("code"); 
1388    } 
1389    return s; 
1390  } 
1391
1392
1393  protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 
1394    if (e.has("concept")) { 
1395      renderCodeableConcept(status, x, e.child("concept")); 
1396    } 
1397    if (e.has("reference")) { 
1398      renderReference(status, x, e.child("reference")); 
1399    } 
1400  } 
1401
1402  protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 
1403    if (cc.isEmpty()) { 
1404      return; 
1405    } 
1406
1407    String s = context.getTranslated(cc.child("text")); 
1408    if (Utilities.noString(s)) { 
1409      for (ResourceWrapper c : cc.children("coding")) { 
1410        if (c.has("display")) { 
1411          s = context.getTranslated(c.child("display")); 
1412          break; 
1413        } 
1414      } 
1415    } 
1416    if (Utilities.noString(s)) { 
1417      // still? ok, let's try looking it up 
1418      for (ResourceWrapper c : cc.children("coding")) { 
1419        if (c.has("code") && c.has("system")) { 
1420          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1421          if (!Utilities.noString(s)) 
1422            break; 
1423        } 
1424      } 
1425    } 
1426
1427    if (Utilities.noString(s)) { 
1428      if (!cc.has("coding")) 
1429        s = ""; 
1430      else 
1431        s = cc.children("coding").get(0).primitiveValue("code"); 
1432    } 
1433
1434    if (status.isShowCodeDetails()) { 
1435      x.addText(s+" "); 
1436      XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 
1437      sp.tx(" ("); 
1438      boolean first = true; 
1439      for (ResourceWrapper c : cc.children("coding")) { 
1440        if (first) { 
1441          first = false; 
1442        } else { 
1443          sp.tx("; "); 
1444        } 
1445        String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 
1446        if (url != null) { 
1447          sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1448        } else { 
1449          sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1450        } 
1451        if (c.has("code")) { 
1452          sp.tx("#"+c.primitiveValue("code")); 
1453        } 
1454        if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 
1455          sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 
1456        } 
1457      } 
1458      if (hasRenderableExtensions(cc)) { 
1459        if (!first) { 
1460          sp.tx("; "); 
1461        } 
1462        renderExtensionsInText(status, sp, cc, ";"); 
1463      } 
1464      sp.tx(")"); 
1465    } else { 
1466
1467      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1468      for (ResourceWrapper c : cc.children("coding")) { 
1469        if (c.has("code") && c.has("system")) { 
1470          b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 
1471        } 
1472      } 
1473
1474      x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); 
1475    }       
1476    checkRenderExtensions(status, x, cc);
1477  } 
1478
1479  protected String displayIdentifier(ResourceWrapper ii) { 
1480    String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 
1481    if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 
1482      s = "OID:"+s.substring(8); 
1483    } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 
1484      s = "UUID:"+s.substring(9); 
1485    } else {  
1486      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1487      if (ns != null) { 
1488        s = crPresent(ns)+"#"+s; 
1489      } 
1490      if (ii.has("type")) { 
1491        ResourceWrapper type = ii.child("type");
1492        if (type.has("text")) 
1493          s = context.getTranslated(type.child("text"))+":\u00A0"+s; 
1494        else if (type.has("coding") && type.children("coding").get(0).has("display")) 
1495          s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 
1496        else if (type.has("coding") && type.children("coding").get(0).has("code")) 
1497          s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 
1498      } else if (ii.has("system")) { 
1499        s = ii.primitiveValue("system")+"#"+s; 
1500      } 
1501    } 
1502
1503    if (ii.has("use") || ii.has("period")) { 
1504      s = s + "\u00A0("; 
1505      if (ii.has("use")) { 
1506        s = s + "use:\u00A0"+ii.primitiveValue("use"); 
1507      } 
1508      if (ii.has("use") || ii.has("period")) { 
1509        s = s + ",\u00A0"; 
1510      } 
1511      if (ii.has("period")) { 
1512        s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 
1513      } 
1514      s = s + ")"; 
1515    }     
1516    return s; 
1517  } 
1518
1519  protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException {     
1520    if (ii.has("type")) { 
1521      ResourceWrapper type = ii.child("type");
1522      if (type.has("text")) { 
1523        x.tx(context.getTranslated(type.child("text"))); 
1524      } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 
1525        x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 
1526      } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 
1527        x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 
1528      } 
1529      x.tx("/"); 
1530    } else if (ii.has("system")) { 
1531      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1532      if (ns != null) { 
1533        if (ns.hasWebPath()) { 
1534          x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns));         
1535        } else { 
1536          x.tx(crPresent(ns)); 
1537        } 
1538      } else { 
1539        switch (ii.primitiveValue("system")) { 
1540        case "urn:oid:2.51.1.3": 
1541          x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 
1542          break; 
1543        default: 
1544          x.code(ii.primitiveValue("system"));       
1545        } 
1546      } 
1547      x.tx("/"); 
1548    } 
1549    x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 
1550
1551    if (ii.has("use") || ii.has("period")) { 
1552      x.nbsp(); 
1553      x.tx("("); 
1554      if (ii.has("use")) { 
1555        x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 
1556        x.nbsp(); 
1557        x.tx(ii.primitiveValue("use")); 
1558      } 
1559      if (ii.has("use") || ii.has("period")) { 
1560        x.tx(","); 
1561        x.nbsp(); 
1562      } 
1563      if (ii.has("period")) { 
1564        x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 
1565        x.nbsp(); 
1566        x.tx(displayPeriod(ii.child("period"))); 
1567      } 
1568      x.tx(")"); 
1569    }            
1570    checkRenderExtensions(status, x, ii);   
1571  } 
1572
1573  public static String displayHumanName(ResourceWrapper name) { 
1574    StringBuilder s = new StringBuilder(); 
1575    if (name.has("text")) 
1576      s.append(name.primitiveValue("text")); 
1577    else { 
1578      for (ResourceWrapper p : name.children("given")) { 
1579        s.append(p.primitiveValue()); 
1580        s.append(" "); 
1581      } 
1582      if (name.has("family")) { 
1583        s.append(name.primitiveValue("family")); 
1584        s.append(" "); 
1585      } 
1586    } 
1587    if (name.has("use") && !name.primitiveValue("use").equals("usual")) 
1588      s.append("("+name.primitiveValue("use")+")"); 
1589    return s.toString(); 
1590  } 
1591
1592
1593  protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { 
1594    StringBuilder s = new StringBuilder(); 
1595    if (name.has("text")) 
1596      s.append(context.getTranslated(name.child("text"))); 
1597    else { 
1598      for (ResourceWrapper p : name.children("given")) { 
1599        s.append(context.getTranslated(p)); 
1600        s.append(" "); 
1601      } 
1602      if (name.has("family")) { 
1603        s.append(context.getTranslated(name.child("family"))); 
1604        s.append(" "); 
1605      } 
1606    } 
1607    if (name.has("use") && !name.primitiveValue("use").equals("usual")) { 
1608      s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")");
1609    }
1610    x.addText(s.toString());       
1611    checkRenderExtensions(status, x, name);
1612  } 
1613
1614  private String displayAddress(ResourceWrapper address) { 
1615    StringBuilder s = new StringBuilder(); 
1616    if (address.has("text")) 
1617      s.append(context.getTranslated(address.child("text"))); 
1618    else { 
1619      for (ResourceWrapper p : address.children("line")) { 
1620        s.append(context.getTranslated(p)); 
1621        s.append(" "); 
1622      } 
1623      if (address.has("city")) { 
1624        s.append(context.getTranslated(address.child("city"))); 
1625        s.append(" "); 
1626      } 
1627      if (address.has("state")) { 
1628        s.append(context.getTranslated(address.child("state"))); 
1629        s.append(" "); 
1630      } 
1631
1632      if (address.has("postalCode")) { 
1633        s.append(context.getTranslated(address.child("postalCode"))); 
1634        s.append(" "); 
1635      } 
1636
1637      if (address.has("country")) { 
1638        s.append(context.getTranslated(address.child("country"))); 
1639        s.append(" "); 
1640      } 
1641    } 
1642    if (address.has("use")) {
1643      s.append("("+address.primitiveValue("use")+")");
1644    }
1645    return s.toString(); 
1646  } 
1647
1648  protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { 
1649    x.addText(displayAddress(address));       
1650    checkRenderExtensions(status, x, address);
1651  } 
1652
1653
1654  public String displayContactPoint(ResourceWrapper contact) { 
1655    StringBuilder s = new StringBuilder(); 
1656    s.append(describeSystem(contact.primitiveValue("system"))); 
1657    if (Utilities.noString(contact.primitiveValue("value"))) 
1658      s.append("-unknown-"); 
1659    else 
1660      s.append(contact.primitiveValue("value")); 
1661    if (contact.has("use")) 
1662      s.append("("+getTranslatedCode(contact.child("use"))+")"); 
1663    return s.toString(); 
1664  } 
1665
1666  public String displayContactDetail(ResourceWrapper contact) { 
1667    CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 
1668    for (ResourceWrapper cp : contact.children("telecom")) { 
1669      s.append(displayContactPoint(cp)); 
1670    } 
1671    return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 
1672  } 
1673
1674  protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 
1675    NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 
1676    numberFormat.setGroupingUsed(true); 
1677    numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 
1678    numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 
1679    return numberFormat.format(input); 
1680  } 
1681
1682  protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 
1683    if (x.getName().equals("blockquote")) { 
1684      x = x.para(); 
1685    } 
1686    Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 
1687    if (c != null) { 
1688      XhtmlNode s = x.span(null, c.getDisplayName()); 
1689      s.tx(c.getSymbol(context.getLocale())); 
1690      s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 
1691      x.tx(" ("+c.getCurrencyCode()+")"); 
1692    } else { 
1693      if (money.has("currency")) { 
1694        x.tx(money.primitiveValue("currency")); 
1695      } 
1696      x.tx(money.primitiveValue("value")); 
1697    } 
1698  } 
1699
1700  protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 
1701    // there's two parts: what the expression is, and how it's described.  
1702    // we start with what it is, and then how it's described  
1703    XhtmlNode p = x; 
1704    if (p.getName().equals("blockquote")) { 
1705      p = p.para(); 
1706    } 
1707    if (expr.has("expression")) { 
1708      if (expr.has("reference")) { 
1709        p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference")));         
1710      } 
1711      XhtmlNode c = p; 
1712      if (expr.has("language")) { 
1713        c = c.span(null, expr.primitiveValue("language")); 
1714      } 
1715      c.code().tx(expr.primitiveValue("expression")); 
1716    } else if (expr.has("reference")) { 
1717      p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 
1718    } 
1719    if (expr.has("name") || expr.has("description")) { 
1720      p.tx("("); 
1721      if (expr.has("name")) { 
1722        p.b().tx(expr.primitiveValue("name")); 
1723      } 
1724      if (expr.has("description")) { 
1725        p.tx("\""); 
1726        p.tx(context.getTranslated(expr.child("description"))); 
1727        p.tx("\""); 
1728      } 
1729      p.tx(")"); 
1730    } 
1731  } 
1732
1733
1734  protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 
1735    if (contact != null) { 
1736      if (!contact.has("system")) { 
1737        x.addText(displayContactPoint(contact));         
1738      } else { 
1739        String v = contact.primitiveValue("value");
1740        switch (contact.primitiveValue("system")) { 
1741        case "email": 
1742          x.ah("mailto:"+v).tx(v); 
1743          break; 
1744        case "fax": 
1745          x.addText(displayContactPoint(contact)); 
1746          break; 
1747        case "other": 
1748          x.addText(displayContactPoint(contact)); 
1749          break; 
1750        case "pager": 
1751          x.addText(displayContactPoint(contact)); 
1752          break; 
1753        case "phone": 
1754          if (contact.has("value") && v != null && v.startsWith("+")) { 
1755            x.ah("tel:"+v.replace(" ", "")).tx(v); 
1756          } else { 
1757            x.addText(displayContactPoint(contact)); 
1758          } 
1759          break; 
1760        case "sms": 
1761          x.addText(displayContactPoint(contact)); 
1762          break; 
1763        case "url": 
1764          x.ah(context.prefixLocalHref(v)).tx(v); 
1765          break; 
1766        default: 
1767          break;       
1768        } 
1769      } 
1770    } 
1771  } 
1772
1773  protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 
1774    if (c != null) { 
1775      if (c.getSystem() == ContactPointSystem.PHONE) { 
1776        p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 
1777      } else if (c.getSystem() == ContactPointSystem.FAX) { 
1778        p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 
1779      } else if (c.getSystem() == ContactPointSystem.EMAIL) { 
1780        p.tx(c.getValue()); 
1781      } else if (c.getSystem() == ContactPointSystem.URL) { 
1782        if (c.getValue().length() > 30) { 
1783          p.addText(c.getValue().substring(0, 30)+"..."); 
1784        } else { 
1785          p.addText(c.getValue()); 
1786        } 
1787      } 
1788    } 
1789  } 
1790
1791  protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 
1792    String sys = c.primitiveValue("system");
1793    String value = c.primitiveValue("value");
1794    if (sys.equals("phone")) { 
1795      p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 
1796    } else if (sys.equals("fax")) { 
1797      p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 
1798    } else if (sys.equals("email")) { 
1799      p.ah("mailto:"+value).addText(value); 
1800    } else if (sys.equals("url")) { 
1801      if (value.length() > 30) 
1802        p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 
1803      else 
1804        p.ah(context.prefixLocalHref(value)).addText(value); 
1805    } 
1806  } 
1807  private static String describeSystem(String system) { 
1808    if (system == null) 
1809      return ""; 
1810    switch (system) { 
1811    case "phone": return "ph: "; 
1812    case "fax": return "fax: "; 
1813    default: 
1814      return ""; 
1815    } 
1816  } 
1817
1818  protected String displayQuantity(ResourceWrapper q) { 
1819    if (q == null) {
1820      return "";
1821    }
1822    StringBuilder s = new StringBuilder(); 
1823
1824    s.append(q.has("value") ? q.primitiveValue("value") : "?"); 
1825    if (q.has("unit")) 
1826      s.append(" ").append(q.primitiveValue("unit")); 
1827    else if (q.has("code")) 
1828      s.append(" ").append(q.primitiveValue("code")); 
1829
1830    return s.toString(); 
1831  }   
1832
1833  protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { 
1834    if (q.has("comparator")) 
1835      x.addText(q.primitiveValue("comparator")); 
1836    if (q.has("value")) { 
1837      x.addText(context.getTranslated(q.child("value"))); 
1838    } 
1839    if (q.has("unit")) 
1840      x.tx(" "+context.getTranslated(q.child("unit"))); 
1841    else if (q.has("code") && q.has("system")) { 
1842      // if there's a code there *shall* be a system, so if we've got one and not the other, things are invalid and we won't bother trying to render 
1843      if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 
1844        x.tx(" "+q.primitiveValue("code")); 
1845      else 
1846        x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 
1847    } 
1848    if (context.isTechnicalMode() && q.has("code")) { 
1849      x.span("background: LightGoldenRodYellow", null).tx(" "+ (context.formatPhrase(RenderingContext.DATA_REND_DETAILS, displaySystem(q.primitiveValue("system")))) +q.primitiveValue("code")+" = '"+lookupCode(q.primitiveValue("system"), null, q.primitiveValue("code"))+"')"); 
1850    }       
1851    checkRenderExtensions(status, x, q);
1852  } 
1853
1854
1855  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 
1856    pieces.add(gen.new Piece(null, displayQuantity(q), null)); 
1857  } 
1858
1859  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 
1860    pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 
1861  } 
1862
1863  public String displayRange(ResourceWrapper q) { 
1864    if (!q.has("low") && !q.has("high")) 
1865      return "?"; 
1866
1867    StringBuilder b = new StringBuilder(); 
1868
1869    ResourceWrapper lowC = q.child("low");
1870    ResourceWrapper highC = q.child("high");
1871    boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit")))  
1872        || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 
1873    String low = "?"; 
1874    if (q.has("low") && lowC.has("value")) 
1875      low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 
1876    String high = displayQuantity(highC); 
1877    if (high.isEmpty()) 
1878      high = "?"; 
1879    b.append(low).append("\u00A0to\u00A0").append(high); 
1880    return b.toString(); 
1881  } 
1882
1883  protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 
1884    if (q.has("low")) 
1885      x.addText(q.child("low").primitiveValue("value").toString()); 
1886    else 
1887      x.tx("?"); 
1888    x.tx("-"); 
1889    if (q.has("high")) 
1890      x.addText(q.child("high").primitiveValue("value").toString()); 
1891    else 
1892      x.tx("?"); 
1893    if (q.has("low") && q.child("low").has("unit")) 
1894      x.tx(" "+q.child("low").primitiveValue("unit")); 
1895  } 
1896
1897  public String displayPeriod(ResourceWrapper p) { 
1898    String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 
1899    s = s + " --> "; 
1900    return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1901  } 
1902
1903  public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 
1904    x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 
1905    x.tx(" --> "); 
1906    x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1907  } 
1908
1909  public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 
1910    renderCoding(status, x, u.child("code"), false); 
1911    x.tx(" = "); 
1912    renderDataType(status, x, u.child("value"));     
1913  } 
1914
1915
1916  public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 
1917    if (x.isPara()) { 
1918      x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1919      x.tx(": "); 
1920      x.tx(td.child("type").primitiveValue("display")); 
1921
1922      if (td.has("name")) {     
1923        x.tx(", "); 
1924        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1925        x.tx(": "); 
1926        x.tx(context.getTranslated(td.child("name"))); 
1927      } 
1928      if (td.has("code")) {     
1929        x.tx(", "); 
1930        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1931        x.tx(": "); 
1932        renderCodeableConcept(status, x, td.child("code")); 
1933      } 
1934      if (td.has("timing")) {     
1935        x.tx(", "); 
1936        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1937        x.tx(": "); 
1938        renderDataType(status, x, td.child("timing")); 
1939      } 
1940      if (td.has("condition")) {     
1941        x.tx(", "); 
1942        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1943        x.tx(": "); 
1944        renderExpression(status, x, td.child("condition")); 
1945      }     
1946    } else { 
1947      XhtmlNode tbl = x.table("grid"); 
1948
1949      XhtmlNode tr = tbl.tr();   
1950      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1951      tr.td().tx(td.child("type").primitiveValue("display")); 
1952
1953      if (td.has("name")) {     
1954        tr = tbl.tr();   
1955        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1956        tr.td().tx(context.getTranslated(td.child("name"))); 
1957      } 
1958      if (td.has("code")) {     
1959        tr = tbl.tr();   
1960        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1961        renderCodeableConcept(status, tr.td(), td.child("code")); 
1962      } 
1963      if (td.has("timing")) {     
1964        tr = tbl.tr();   
1965        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1966        renderDataType(status, tr.td(), td.child("timing")); 
1967      } 
1968      if (td.has("condition")) {      
1969        tr = tbl.tr();   
1970        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1971        renderExpression(status, tr.td(), td.child("condition")); 
1972      }     
1973    } 
1974  } 
1975
1976  public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 
1977    XhtmlNode tbl = x.table("grid"); 
1978    XhtmlNode tr = tbl.tr();     
1979    XhtmlNode td = tr.td().colspan("2"); 
1980    td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1981    td.tx(": "); 
1982    StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 
1983    if (sd != null && sd.hasWebPath()) { 
1984      td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 
1985    } else { 
1986      td.tx(dr.primitiveValue("type")); 
1987    } 
1988    if (dr.has("profile")) { 
1989      td.tx(" ("); 
1990      boolean first = true; 
1991      for (ResourceWrapper p : dr.children("profile")) { 
1992        if (first) first = false; else td.tx(" | "); 
1993        sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 
1994        if (sd != null && sd.hasWebPath()) { 
1995          td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 
1996        } else { 
1997          td.tx(p.primitiveValue()); 
1998        } 
1999      } 
2000      td.tx(")"); 
2001    } 
2002    if (dr.has("subject")) { 
2003      tr = tbl.tr();     
2004      td = tr.td().colspan("2"); 
2005      td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 
2006      ResourceWrapper subj = dr.child("subject");
2007      if (subj.fhirType().equals("reference")) { 
2008        renderReference(status, td, subj); 
2009      } else { 
2010        renderCodeableConcept(status, td, subj); 
2011      } 
2012    } 
2013    if (dr.has("codeFilter") || dr.has("dateFilter")) { 
2014      tr = tbl.tr().backgroundColor("#efefef");     
2015      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 
2016      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 
2017    } 
2018    for (ResourceWrapper cf : dr.children("codeFilter")) { 
2019      tr = tbl.tr();     
2020      if (cf.has("path")) { 
2021        tr.td().tx(cf.primitiveValue("path")); 
2022      } else { 
2023        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
2024      } 
2025      if (cf.has("valueSet")) { 
2026        td = tr.td(); 
2027        td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 
2028        renderDataType(status, td, cf.child("valueSet")); 
2029      } else { 
2030        boolean first = true; 
2031        td = tr.td(); 
2032        td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 
2033        for (ResourceWrapper c : cf.children("code")) { 
2034          if (first) first = false; else td.tx(", "); 
2035          renderDataType(status, td, c); 
2036        } 
2037      } 
2038    } 
2039    for (ResourceWrapper cf : dr.children("dateFilter")) { 
2040      tr = tbl.tr();     
2041      if (cf.has("path")) { 
2042        tr.td().tx(cf.primitiveValue("path")); 
2043      } else { 
2044        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
2045      } 
2046      renderDataType(status, tr.td(), cf.child("value")); 
2047    } 
2048    if (dr.has("sort") || dr.has("limit")) { 
2049      tr = tbl.tr();     
2050      td = tr.td().colspan("2"); 
2051      if (dr.has("limit")) { 
2052        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 
2053        td.tx(": "); 
2054        td.tx(dr.primitiveValue("limit")); 
2055        if (dr.has("sort")) { 
2056          td.tx(", "); 
2057        } 
2058      } 
2059      if (dr.has("sort")) { 
2060        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 
2061        td.tx(": "); 
2062        boolean first = true; 
2063        for (ResourceWrapper p : dr.children("sort")) { 
2064          if (first) first = false; else td.tx(" | "); 
2065          td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 
2066          td.tx(p.primitiveValue("path")); 
2067        } 
2068      } 
2069    } 
2070  } 
2071
2072
2073  private String displayTiming(ResourceWrapper s) throws FHIRException { 
2074    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2075    if (s.has("code")) {
2076      b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 
2077    }
2078
2079    if (s.has("event")) { 
2080      CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 
2081      for (ResourceWrapper p : s.children("event")) { 
2082        if (p.hasPrimitiveValue()) { 
2083          c.append(displayDateTime(p)); 
2084        } else if (!renderExpression(c, p)) { 
2085          c.append("??"); 
2086        }         
2087      } 
2088      b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 
2089    } 
2090
2091    if (s.has("repeat")) { 
2092      ResourceWrapper rep = s.child("repeat"); 
2093      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 
2094        b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 
2095      if (rep.has("count")) 
2096        b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 
2097      if (rep.has("duration")) 
2098        b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"))) + " "); 
2099
2100      if (rep.has("when")) { 
2101        String st = ""; 
2102        if (rep.has("offset")) { 
2103          st = rep.primitiveValue("offset")+"min "; 
2104        } 
2105        b.append(st); 
2106        for (ResourceWrapper wh : rep.children("when"))  {
2107          b.append(displayEventCode(wh.primitiveValue()));
2108        }
2109      } else { 
2110        String st = ""; 
2111        if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 
2112          st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 
2113        } else { 
2114          st = rep.primitiveValue("frequency"); 
2115          if (rep.has("frequencyMax")) 
2116            st = st + "-"+rep.primitiveValue("frequencyMax"); 
2117        } 
2118        if (rep.has("period")) { 
2119          st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); 
2120          if (rep.has("periodMax")) 
2121            st = st + "-"+rep.primitiveValue("periodMax"); 
2122          st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit")); 
2123        } 
2124        b.append(st); 
2125      } 
2126      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) 
2127        b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 
2128    } 
2129    return b.toString(); 
2130  } 
2131
2132  private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 
2133    ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 
2134    if (exp == null || !exp.has("value")) { 
2135      return false; 
2136    } 
2137    c.append(exp.child("value").primitiveValue("expression")); 
2138    return true; 
2139  } 
2140
2141  private String displayEventCode(String when) { 
2142    switch (when) { 
2143    case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 
2144    case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 
2145    case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 
2146    case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 
2147    case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 
2148    case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 
2149    case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 
2150    case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 
2151    case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 
2152    case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 
2153    case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 
2154    case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 
2155    case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 
2156    case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 
2157    default: return "?ngen-6?"; 
2158    } 
2159  } 
2160
2161  private String displayTimeUnits(String units) { 
2162    if (units == null) 
2163      return "?ngen-7?"; 
2164    switch (units) { 
2165    case "a": return "years"; 
2166    case "d": return "days"; 
2167    case "h": return "hours"; 
2168    case "min": return "minutes"; 
2169    case "mo": return "months"; 
2170    case "s": return "seconds"; 
2171    case "wk": return "weeks"; 
2172    default: return "?ngen-8?"; 
2173    } 
2174  } 
2175
2176  protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 
2177    x.addText(displayTiming(s)); 
2178  } 
2179
2180
2181  private String displaySampledData(ResourceWrapper s) { 
2182    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2183    if (s.has("origin")) 
2184      b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 
2185
2186    if (s.has("interval")) { 
2187      b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 
2188
2189      if (s.has("intervalUnit")) 
2190        b.append(s.primitiveValue("intervalUnit")); 
2191    } 
2192
2193    if (s.has("factor")) 
2194      b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 
2195
2196    if (s.has("lowerLimit")) 
2197      b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 
2198
2199    if (s.has("upperLimit")) 
2200      b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 
2201
2202    if (s.has("dimensions")) 
2203      b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 
2204
2205    if (s.has("data")) 
2206      b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 
2207
2208    return b.toString(); 
2209  } 
2210
2211  protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 
2212    x.addText(displaySampledData(sampledData)); 
2213  } 
2214
2215  public RenderingContext getContext() { 
2216    return context; 
2217  } 
2218
2219
2220  public XhtmlNode makeExceptionXhtml(Exception e, String function) { 
2221    XhtmlNode xn; 
2222    xn = new XhtmlNode(NodeType.Element, "div"); 
2223    XhtmlNode p = xn.para(); 
2224    p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 
2225    p.addComment(getStackTrace(e)); 
2226    return xn; 
2227  } 
2228
2229  private String getStackTrace(Exception e) { 
2230    StringBuilder b = new StringBuilder(); 
2231    b.append("\r\n"); 
2232    for (StackTraceElement t : e.getStackTrace()) { 
2233      b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 
2234      b.append("\r\n"); 
2235    } 
2236    return b.toString(); 
2237  } 
2238
2239  protected String versionFromCanonical(String system) { 
2240    if (system == null) { 
2241      return null; 
2242    } else if (system.contains("|")) { 
2243      return system.substring(0, system.indexOf("|")); 
2244    } else { 
2245      return null; 
2246    } 
2247  } 
2248
2249  protected String systemFromCanonical(String system) { 
2250    if (system == null) { 
2251      return null; 
2252    } else if (system.contains("|")) { 
2253      return system.substring(system.indexOf("|")+1); 
2254    } else { 
2255      return system; 
2256    } 
2257  } 
2258
2259
2260}