
001package org.hl7.fhir.r5.renderers; 002 003import java.util.Date; 004 005import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 006import org.hl7.fhir.r5.context.IWorkerContext; 007import org.hl7.fhir.r5.model.Base; 008import org.hl7.fhir.r5.model.DataType; 009import org.hl7.fhir.r5.model.Enumeration; 010import org.hl7.fhir.r5.model.Resource; 011import org.hl7.fhir.r5.renderers.utils.RenderingContext; 012import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 013import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 014import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 015import org.hl7.fhir.r5.utils.UserDataNames; 016import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 017import org.hl7.fhir.utilities.*; 018import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 019import org.hl7.fhir.utilities.validation.ValidationOptions; 020import org.hl7.fhir.utilities.xhtml.NodeType; 021import org.hl7.fhir.utilities.xhtml.XhtmlNode; 022 023/** 024 * Rendering framework: 025 * 026 * * boolean render(DomainResource) : produce an HTML representation suitable for runtime / documentation, and insert it into the resource. Return true of any extensions encountered 027 * * boolean render(XhtmlNode, Resource: produce an HTML representation, and fill out the provided node with it. Return true of any extensions encountered 028 * * XhtmlNode build(DomainResource): same as render(DomainResource) but also return the XHtmlNode 029 * 030 * * String display(Base) : produce a plan text concise representation that serves to describe the resource 031 * * void display(XhtmlNode, Base) : produce a plan text concise representation that serves to describe the resource 032 * 033 * * void describe(XhtmlNode, Resource) : produce a short summary of the resource with key details presented (potentially more verbose than display, but still suitable for a single line) 034 * 035 * if not specific code for rendering a resource has been provided, and there's no liquid script to guide it, a generic rendering based onthe profile will be performed 036 * 037 * @author graha 038 * 039 */ 040@MarkedToMoveToAdjunctPackage 041public class Renderer { 042 043 public static class RenderingStatus { 044 private boolean extensions; 045 046 public void setExtensions(boolean b) { 047 extensions = b; 048 } 049 050 public boolean getExtensions() { 051 return extensions; 052 } 053 054 public boolean isShowCodeDetails() { 055 // TODO Auto-generated method stub 056 return false; 057 } 058 059 } 060 protected RenderingContext context; 061 062 public Renderer(RenderingContext context) { 063 this.context = context; 064 } 065 066 public Renderer(IWorkerContext worker) { 067 this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", worker.getLocale(), ResourceRendererMode.END_USER, GenerationRules.IG_PUBLISHER); 068 } 069 070 071 protected String formatPhrase(String theMessage, Object... theMessageArguments) { 072 return context.formatPhrase(theMessage, theMessageArguments); 073 } 074 075 public void genStandardsStatus(XhtmlNode td, StandardsStatus ss) { 076 if (ss != null) { 077 td.tx(" "); 078 XhtmlNode a = td.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC, true), "versions.html#std-process"), (context.formatPhrase(RenderingContext.REND_STANDARDS, ss.toDisplay()))); 079 a.style("padding-left: 3px; padding-right: 3px; border: 1px grey solid; font-weight: bold; color: black; background-color: "+ss.getColor()); 080 a.tx(ss.getAbbrev()); 081 } 082 } 083 084 protected XhtmlNode renderStatus(Base b, XhtmlNode x) { 085 if (b == null || context.getChangeVersion() == null) { 086 return x; 087 } 088 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION); 089 if (vca == null) { 090 return x; 091 } 092 switch (vca.getType()) { 093 case Added: 094 XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 095 XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_SINCE_ADDED, context.getChangeVersion()))); 096 spanInner.img("icon-change-add.png", "icon"); 097 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED)); 098 return spanOuter; 099 case Changed: 100 spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 101 String s = context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, context.getChangeVersion()); 102 spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", vca.getOriginal() == null ? s : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, context.getChangeVersion(), vca.getOriginal())); 103 spanInner.img("icon-change-edit.png", "icon"); 104 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED)); 105 return spanOuter; 106 case Deleted: 107 spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 108 spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion()))); 109 spanInner.img("icon-change-remove.png", "icon"); 110 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED)); 111 return spanOuter.strikethrough(); 112 default: 113 return x; 114 } 115 } 116 117 protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) { 118 if (b == null || context.getChangeVersion() == null) { 119 return x; 120 } 121 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION); 122 if (vca == null) { 123 return x; 124 } 125 switch (vca.getType()) { 126 case Added: 127 XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 128 XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_SINCE_ADDED, context.getChangeVersion()))); 129 spanInner.img("icon-change-add.png", "icon"); 130 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED)); 131 return divOuter; 132 case Changed: 133 divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 134 String s = context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, context.getChangeVersion()); 135 spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", vca.getOriginal() == null ? s : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, context.getChangeVersion(), vca.getOriginal())); 136 spanInner.img("icon-change-edit.png", "icon"); 137 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED)); 138 return divOuter; 139 case Deleted: 140 divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 141 spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion()))); 142 spanInner.img("icon-change-remove.png", "icon"); 143 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED)); 144 return divOuter.strikethrough(); 145 default: 146 return x; 147 } 148 } 149 150 151 protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) { 152 if (b == null || context.getChangeVersion() == null) { 153 return tr.td(); 154 } 155 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION); 156 if (vca == null) { 157 return tr.td(); 158 } 159 switch (vca.getType()) { 160 case Added: 161 if (tbl.isClass("grid")) { 162 tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 163 } 164 XhtmlNode td = tr.td(); 165 XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion()))); 166 span.img("icon-change-add.png", "icon"); 167 span.tx(" "+ context.formatPhrase(RenderingContext.REND_ADDED)); 168 XhtmlNode x = new XhtmlNode(NodeType.Element, "holder"); 169 x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion())).tx(" "); 170 tr.styleCells(x); 171 return td; 172 case Changed: 173 td = tr.td(); 174 span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_ROW_CHANGED_SINCE_WAS, context.getChangeVersion(), vca.getOriginal())); 175 span.img("icon-change-edit.png", "icon"); 176 span.tx(" "+ context.formatPhrase(RenderingContext.REND_CHANGED)); 177 return td; 178 case Deleted: 179 tr.style("text-decoration: line-through"); 180 td = tr.td(); 181 span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion())); 182 span.img("icon-change-remove.png", "icon"); 183 span.tx(" "+ context.formatPhrase(RenderingContext.REND_REMOVED)); 184 x = new XhtmlNode(NodeType.Element, "holder"); 185 x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion())).tx(" "); 186 tr.styleCells(x); 187 return td; 188 default: 189 return tr.td(); 190 } 191 } 192 193 /** 194 * return true if there's any actual changes 195 * 196 * @param context 197 * @param base 198 * @param x 199 * @param version 200 * @param metadataFields 201 * @return 202 */ 203 public static boolean renderStatusSummary(RenderingContext context, Base base, XhtmlNode x, String version, String... metadataFields) { 204 if (base.hasUserData(UserDataNames.COMP_VERSION_ANNOTATION)) { 205 VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(UserDataNames.COMP_VERSION_ANNOTATION); 206 switch (self.getType()) { 207 case Added: 208 XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_SINCE_ADDED, version)); 209 spanInner.img("icon-change-add.png", "icon"); 210 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED)); 211 return true; 212 case Changed: 213 if (self.getComp().noChangeOtherThanMetadata(metadataFields)) { 214 x.span("color: #eeeeee").tx("n/c"); 215 return false; 216 } else { 217 spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", 218 self.getOriginal() != null ? context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, version, self.getOriginal()) : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, version)); 219 spanInner.img("icon-change-edit.png", "icon"); 220 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED)); 221 } 222 return true; 223 case Deleted: 224 spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, version)); 225 spanInner.img("icon-change-remove.png", "icon"); 226 spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED)); 227 return true; 228 default: 229 x.span("color: #eeeeee").tx("n/c"); 230 return false; 231 } 232 } else { 233 x.span("color: #eeeeee").tx("--"); 234 } 235 return false; 236 } 237 238 239 public String egt(@SuppressWarnings("rawtypes") Enumeration<? extends Enum> value) { 240 if (value == null || !value.hasPrimitiveValue()) { 241 return null; 242 } else { 243 return (value == null || !value.hasPrimitiveValue()) ? null : value.asStringValue(); 244 } 245 } 246 247 public String toStr(int value) { 248 return Integer.toString(value); 249 } 250 251 public String toStr(Date value) { 252 return value.toString(); 253 } 254 255 protected ResourceWrapper wrapNC(DataType type) { 256 return ResourceWrapper.forType(context.getContextUtilities(), type); 257 } 258 259 protected ResourceWrapper wrap(Resource resource) { 260 return ResourceWrapper.forResource(context.getContextUtilities(), resource); 261 } 262 protected ResourceWrapper wrapWC(ResourceWrapper resource, DataType type) { 263 return ResourceWrapper.forType(context.getContextUtilities(), resource, type); 264 } 265 266 protected String getTranslatedCode(ResourceWrapper child) { 267 return context.getTranslatedCode(child.primitiveValue(), child.getCodeSystemUri()); 268 } 269 270 271 public XhtmlNode xlinkNarrative(XhtmlNode x, ResourceWrapper type) { 272 if (context.isTrackNarrativeSource() && !x.hasUserData("narrative.linked") && type != null) { 273 if (type.hasId()) { 274 x.id(type.getId()); 275 } else { 276 String id = context.nextXNKey(); 277 x.id(id); 278 type.setId(id); 279 } 280 x.clss("generated"); 281 x.setUserData("narrative.linked", true); 282 } 283 return x; 284 } 285 286 public XhtmlNode markBoilerplate(XhtmlNode x) { 287 if (context.isTrackNarrativeSource()) { 288 x.clss("boilerplate"); 289 } 290 return x; 291 } 292 293 public XhtmlNode markGenerated(XhtmlNode x) { 294 if (context.isTrackNarrativeSource()) { 295 x.clss("generated"); 296 } 297 return x; 298 } 299 300 public XhtmlNode spanIfTracking(XhtmlNode x, ResourceWrapper v) { 301 if (context.isTrackNarrativeSource()) { 302 XhtmlNode span = x.span(); 303 xlinkNarrative(span, v); 304 return span; 305 } else { 306 return x; 307 } 308 } 309 310 311 312}