001package org.hl7.fhir.r5.renderers; 002 003import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation; 004import org.hl7.fhir.r5.context.IWorkerContext; 005import org.hl7.fhir.r5.model.Base; 006import org.hl7.fhir.r5.renderers.utils.RenderingContext; 007import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 008import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; 009import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 010import org.hl7.fhir.r5.utils.TranslatingUtilities; 011import org.hl7.fhir.utilities.MarkDownProcessor; 012import org.hl7.fhir.utilities.StandardsStatus; 013import org.hl7.fhir.utilities.Utilities; 014import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; 015import org.hl7.fhir.utilities.validation.ValidationOptions; 016import org.hl7.fhir.utilities.xhtml.NodeType; 017import org.hl7.fhir.utilities.xhtml.XhtmlNode; 018 019/** 020 * Rendering framework: 021 * 022 * * boolean render(DomainResource) : produce an HTML representation suitable for runtime / documentation, and insert it into the resource. Return true of any extensions encountered 023 * * boolean render(XhtmlNode, Resource: produce an HTML representation, and fill out the provided node with it. Return true of any extensions encountered 024 * * XhtmlNode build(DomainResource): same as render(DomainResource) but also return the XHtmlNode 025 * 026 * * String display(Base) : produce a plan text concise representation that serves to describe the resource 027 * * void display(XhtmlNode, Base) : produce a plan text concise representation that serves to describe the resource 028 * 029 * * 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) 030 * 031 * 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 032 * 033 * @author graha 034 * 035 */ 036public class Renderer extends TranslatingUtilities { 037 038 protected RenderingContext context; 039 040 public Renderer(RenderingContext context) { 041 this.context = context; 042 } 043 044 public Renderer(IWorkerContext worker) { 045 this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", null, ResourceRendererMode.END_USER, GenerationRules.IG_PUBLISHER); 046 } 047 048 049 protected static final String RENDER_BUNDLE_HEADER_ROOT = "RENDER_BUNDLE_HEADER_ROOT"; 050 protected static final String RENDER_BUNDLE_HEADER_ENTRY = "RENDER_BUNDLE_HEADER_ENTRY"; 051 protected static final String RENDER_BUNDLE_HEADER_ENTRY_URL = "RENDER_BUNDLE_HEADER_ENTRY_URL"; 052 protected static final String RENDER_BUNDLE_RESOURCE = "RENDER_BUNDLE_RESOURCE"; 053 protected static final String RENDER_BUNDLE_SEARCH = "RENDER_BUNDLE_SEARCH"; 054 protected static final String RENDER_BUNDLE_SEARCH_MODE = "RENDER_BUNDLE_SEARCH_MODE"; 055 protected static final String RENDER_BUNDLE_SEARCH_SCORE = "RENDER_BUNDLE_SEARCH_SCORE"; 056 protected static final String RENDER_BUNDLE_RESPONSE = "RENDER_BUNDLE_RESPONSE"; 057 protected static final String RENDER_BUNDLE_LOCATION = "RENDER_BUNDLE_LOCATION"; 058 protected static final String RENDER_BUNDLE_ETAG = "RENDER_BUNDLE_ETAG"; 059 protected static final String RENDER_BUNDLE_LAST_MOD = "RENDER_BUNDLE_LAST_MOD"; 060 protected static final String RENDER_BUNDLE_REQUEST = "RENDER_BUNDLE_REQUEST"; 061 protected static final String RENDER_BUNDLE_IF_NON_MATCH = "RENDER_BUNDLE_IF_NON_MATCH"; 062 protected static final String RENDER_BUNDLE_IF_MOD = "RENDER_BUNDLE_IF_MOD"; 063 protected static final String RENDER_BUNDLE_IF_MATCH = "RENDER_BUNDLE_IF_MATCH"; 064 protected static final String RENDER_BUNDLE_IF_NONE = "RENDER_BUNDLE_IF_NONE"; 065 protected static final String RENDER_BUNDLE_DOCUMENT_CONTENT = "RENDER_BUNDLE_DOCUMENT_CONTENT"; 066 protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_URD = "RENDER_BUNDLE_HEADER_DOC_ENTRY_URD"; 067 protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_U = "RENDER_BUNDLE_HEADER_DOC_ENTRY_U"; 068 protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_RD = "RENDER_BUNDLE_HEADER_DOC_ENTRY_RD"; 069 070 /** the plan here is to make this have it's own implementation of messages, rather than using the 071 * validator messages, for better alignment with publisher I18n strategy 072 * 073 * @param theMessage 074 * @param theMessageArguments 075 * @return 076 */ 077 protected String formatMessage(String theMessage, Object... theMessageArguments) { 078 return context.getWorker().formatMessage(theMessage, theMessageArguments); 079 } 080 081 public void genStandardsStatus(XhtmlNode td, StandardsStatus ss) { 082 if (ss != null) { 083 td.tx(" "); 084 XhtmlNode a = td.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "versions.html#std-process"), "Standards Status = "+ss.toDisplay()); 085 a.style("padding-left: 3px; padding-right: 3px; border: 1px grey solid; font-weight: bold; color: black; background-color: "+ss.getColor()); 086 a.tx(ss.getAbbrev()); 087 } 088 } 089 090 protected XhtmlNode renderStatus(Base b, XhtmlNode x) { 091 if (b == null || context.getChangeVersion() == null) { 092 return x; 093 } 094 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); 095 if (vca == null) { 096 return x; 097 } 098 switch (vca.getType()) { 099 case Added: 100 XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 101 XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion()); 102 spanInner.img("icon-change-add.png", "icon"); 103 spanInner.tx(" Added:"); 104 return spanOuter; 105 case Changed: 106 spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 107 spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : "")); 108 spanInner.img("icon-change-edit.png", "icon"); 109 spanInner.tx(" Changed:"); 110 return spanOuter; 111 case Deleted: 112 spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null); 113 spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); 114 spanInner.img("icon-change-remove.png", "icon"); 115 spanInner.tx(" Removed:"); 116 return spanOuter.strikethrough(); 117 default: 118 return x; 119 } 120 } 121 122 protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) { 123 if (b == null || context.getChangeVersion() == null) { 124 return x; 125 } 126 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); 127 if (vca == null) { 128 return x; 129 } 130 switch (vca.getType()) { 131 case Added: 132 XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 133 XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion()); 134 spanInner.img("icon-change-add.png", "icon"); 135 spanInner.tx(" Added:"); 136 return divOuter; 137 case Changed: 138 divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 139 spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+(vca.getOriginal())+"')" : "")); 140 spanInner.img("icon-change-edit.png", "icon"); 141 spanInner.tx(" Changed:"); 142 return divOuter; 143 case Deleted: 144 divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 145 spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); 146 spanInner.img("icon-change-remove.png", "icon"); 147 spanInner.tx(" Removed:"); 148 return divOuter.strikethrough(); 149 default: 150 return x; 151 } 152 } 153 154 155 protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) { 156 if (b == null || context.getChangeVersion() == null) { 157 return tr.td(); 158 } 159 VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); 160 if (vca == null) { 161 return tr.td(); 162 } 163 switch (vca.getType()) { 164 case Added: 165 if (tbl.isClass("grid")) { 166 tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px"); 167 } 168 XhtmlNode td = tr.td(); 169 XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()); 170 span.img("icon-change-add.png", "icon"); 171 span.tx(" Added:"); 172 XhtmlNode x = new XhtmlNode(NodeType.Element, "holder"); 173 x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()).tx(" "); 174 tr.styleCells(x); 175 return td; 176 case Changed: 177 td = tr.td(); 178 span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since"+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : "")); 179 span.img("icon-change-edit.png", "icon"); 180 span.tx(" Changed:"); 181 return td; 182 case Deleted: 183 tr.style("text-decoration: line-through"); 184 td = tr.td(); 185 span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion()); 186 span.img("icon-change-remove.png", "icon"); 187 span.tx(" Removed:"); 188 x = new XhtmlNode(NodeType.Element, "holder"); 189 x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+context.getChangeVersion()).tx(" "); 190 tr.styleCells(x); 191 return td; 192 default: 193 return tr.td(); 194 } 195 } 196 197 public static void renderStatusSummary(Base base, XhtmlNode x, String version, String... metadataFields) { 198 if (base.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) { 199 VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(VersionComparisonAnnotation.USER_DATA_NAME); 200 switch (self.getType()) { 201 case Added: 202 XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); 203 spanInner.img("icon-change-add.png", "icon"); 204 spanInner.tx(" Added"); 205 return; 206 case Changed: 207 if (self.getComp().noChangeOtherThanMetadata(metadataFields)) { 208 x.span("color: #eeeeee").tx("n/c"); 209 return; 210 } else { 211 spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.getOriginal() != null ? " (was '"+(self.getOriginal())+"')" : "")); 212 spanInner.img("icon-change-edit.png", "icon"); 213 spanInner.tx(" Changed"); 214 } 215 return; 216 case Deleted: 217 spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version); 218 spanInner.img("icon-change-remove.png", "icon"); 219 spanInner.tx(" Removed"); 220 return; 221 default: 222 x.span("color: #eeeeee").tx("n/c"); 223 return; 224 } 225 } else { 226 x.span("color: #eeeeee").tx("--"); 227 } 228 } 229 230}