diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e69de29bb..5c49d3361 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -0,0 +1,11 @@ +Validator: +* Fix handling resources in bundles when type is profiled +* Prevent NPE resolving resource in batch +* fix value set validation for primitive types when an expansion is provided, and the code system is not known + +Other Changes: +* Package Subsystem - Support wildcars for patch version +* Renderer: Don't make a column for definitions in a code system if there are none +* Renderer: special case support for fr-CA language +* Renderer: Prevent NPE when auto-generating narrative and an illegal resource type is encountered +* FHIRPath Engine: correction for allowing boolean conversion of primitive types diff --git a/org.hl7.fhir.core.generator/configuration/ContactDetail.java b/org.hl7.fhir.core.generator/configuration/ContactDetail.java new file mode 100644 index 000000000..f307e2aa0 --- /dev/null +++ b/org.hl7.fhir.core.generator/configuration/ContactDetail.java @@ -0,0 +1,26 @@ + public ContactPoint getEmail() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.EMAIL) { + return cp; + } + } + return null; + } + + public ContactPoint getPhone() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.PHONE) { + return cp; + } + } + return null; + } + + public ContactPoint getUrl() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.URL) { + return cp; + } + } + return null; + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ContactDetail.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ContactDetail.java index 59e3195d8..2dc6dfa16 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ContactDetail.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ContactDetail.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.r5.model.Enumerations.*; import org.hl7.fhir.instance.model.api.IBaseDatatypeElement; import org.hl7.fhir.exceptions.FHIRException; @@ -308,6 +309,32 @@ public class ContactDetail extends DataType implements ICompositeType { return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(name, telecom); } + public ContactPoint getEmail() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.EMAIL) { + return cp; + } + } + return null; + } + + public ContactPoint getPhone() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.PHONE) { + return cp; + } + } + return null; + } + + public ContactPoint getUrl() { + for (ContactPoint cp : getTelecom()) { + if (cp.getSystem() == ContactPointSystem.URL) { + return cp; + } + } + return null; + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index a3531ce7c..ac04d007d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -184,8 +184,18 @@ public class DataRenderer extends Renderer { lang = lang.substring(0, lang.indexOf("-")); } for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { - if (cc.getCode().equals(lang) || cc.getCode().startsWith(lang+"-")) + if (cc.getCode().equals(lang)) { l = cc; + break; + } + } + if (l == null) { + for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { + if (cc.getCode().startsWith(lang+"-")) { + l = cc; + break; + } + } } } if (l != null) { @@ -689,9 +699,63 @@ public class DataRenderer extends Renderer { } protected void renderContactPoint(XhtmlNode x, ContactPoint contact) { - x.addText(displayContactPoint(contact)); + if (contact != null) { + if (!contact.hasSystem()) { + x.addText(displayContactPoint(contact)); + } else { + switch (contact.getSystem()) { + case EMAIL: + x.ah("mailto:"+contact.getValue()).tx(contact.getValue()); + break; + case FAX: + x.addText(displayContactPoint(contact)); + break; + case NULL: + x.addText(displayContactPoint(contact)); + break; + case OTHER: + x.addText(displayContactPoint(contact)); + break; + case PAGER: + x.addText(displayContactPoint(contact)); + break; + case PHONE: + if (contact.hasValue() && contact.getValue().startsWith("+")) { + x.ah("tel:"+contact.getValue().replace(" ", "")).tx(contact.getValue()); + } else { + x.addText(displayContactPoint(contact)); + } + break; + case SMS: + x.addText(displayContactPoint(contact)); + break; + case URL: + x.ah(contact.getValue()).tx(contact.getValue()); + break; + default: + break; + } + } + } } + protected void displayContactPoint(XhtmlNode p, ContactPoint c) { + if (c != null) { + if (c.getSystem() == ContactPointSystem.PHONE) { + p.tx("Phone: "+c.getValue()); + } else if (c.getSystem() == ContactPointSystem.FAX) { + p.tx("Fax: "+c.getValue()); + } else if (c.getSystem() == ContactPointSystem.EMAIL) { + p.tx(c.getValue()); + } else if (c.getSystem() == ContactPointSystem.URL) { + if (c.getValue().length() > 30) { + p.addText(c.getValue().substring(0, 30)+"..."); + } else { + p.addText(c.getValue()); + } + } + } + } protected void addTelecom(XhtmlNode p, ContactPoint c) { if (c.getSystem() == ContactPointSystem.PHONE) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LibraryRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LibraryRenderer.java new file mode 100644 index 000000000..7173349a3 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/LibraryRenderer.java @@ -0,0 +1,533 @@ +package org.hl7.fhir.r5.renderers; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.model.Annotation; +import org.hl7.fhir.r5.model.Attachment; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ContactDetail; +import org.hl7.fhir.r5.model.ContactPoint; +import org.hl7.fhir.r5.model.DataRequirement; +import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Library; +import org.hl7.fhir.r5.model.ListResource; +import org.hl7.fhir.r5.model.ListResource.ListResourceEntryComponent; +import org.hl7.fhir.r5.model.ParameterDefinition; +import org.hl7.fhir.r5.model.Reference; +import org.hl7.fhir.r5.model.RelatedArtifact; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; +import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +public class LibraryRenderer extends ResourceRenderer { + + private static final int DATA_IMG_SIZE_CUTOFF = 4000; + + public LibraryRenderer(RenderingContext context) { + super(context); + } + + public LibraryRenderer(RenderingContext context, ResourceContext rcontext) { + super(context, rcontext); + } + + public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException { + return render(x, (Library) dr); + } + + public boolean render(XhtmlNode x, ResourceWrapper lib) throws FHIRFormatError, DefinitionException, IOException { + PropertyWrapper authors = lib.getChildByName("author"); + PropertyWrapper editors = lib.getChildByName("editor"); + PropertyWrapper reviewers = lib.getChildByName("reviewer"); + PropertyWrapper endorsers = lib.getChildByName("endorser"); + if ((authors != null && authors.hasValues()) || (editors != null && editors.hasValues()) || (reviewers != null && reviewers.hasValues()) || (endorsers != null && endorsers.hasValues())) { + boolean email = hasCT(authors, "email") || hasCT(editors, "email") || hasCT(reviewers, "email") || hasCT(endorsers, "email"); + boolean phone = hasCT(authors, "phone") || hasCT(editors, "phone") || hasCT(reviewers, "phone") || hasCT(endorsers, "phone"); + boolean url = hasCT(authors, "url") || hasCT(editors, "url") || hasCT(reviewers, "url") || hasCT(endorsers, "url"); + x.h2().tx("Participants"); + XhtmlNode t = x.table("grid"); + if (authors != null) { + for (BaseWrapper cd : authors.getValues()) { + participantRow(t, "Author", cd, email, phone, url); + } + } + if (authors != null) { + for (BaseWrapper cd : editors.getValues()) { + participantRow(t, "Editor", cd, email, phone, url); + } + } + if (authors != null) { + for (BaseWrapper cd : reviewers.getValues()) { + participantRow(t, "Reviewer", cd, email, phone, url); + } + } + if (authors != null) { + for (BaseWrapper cd : endorsers.getValues()) { + participantRow(t, "Endorser", cd, email, phone, url); + } + } + } + PropertyWrapper artifacts = lib.getChildByName("relatedArtifact"); + if (artifacts != null && artifacts.hasValues()) { + x.h2().tx("Related Artifacts"); + XhtmlNode t = x.table("grid"); + boolean label = false; + boolean display = false; + boolean citation = false; + for (BaseWrapper ra : artifacts.getValues()) { + label = label || ra.has("label"); + display = display || ra.has("display"); + citation = citation || ra.has("citation"); + } + for (BaseWrapper ra : artifacts.getValues()) { + renderArtifact(t, ra, lib, label, display, citation); + } + } + PropertyWrapper parameters = lib.getChildByName("parameter"); + if (parameters != null && parameters.hasValues()) { + x.h2().tx("Parameters"); + XhtmlNode t = x.table("grid"); + boolean doco = false; + for (BaseWrapper p : parameters.getValues()) { + doco = doco || p.has("documentation"); + } + for (BaseWrapper p : parameters.getValues()) { + renderParameter(t, p, doco); + } + } + PropertyWrapper dataRequirements = lib.getChildByName("dataRequirement"); + if (dataRequirements != null && dataRequirements.hasValues()) { + x.h2().tx("Data Requirements"); + for (BaseWrapper p : dataRequirements.getValues()) { + renderDataRequirement(x, (DataRequirement) p.getBase()); + } + } + PropertyWrapper contents = lib.getChildByName("content"); + if (contents != null) { + x.h2().tx("Contents"); + boolean isCql = false; + int counter = 0; + for (BaseWrapper p : contents.getValues()) { + Attachment att = (Attachment) p.getBase(); + renderAttachment(x, att, isCql, counter, lib.getId()); + isCql = isCql || (att.hasContentType() && att.getContentType().startsWith("text/cql")); + counter++; + } + } + return false; + } + + private boolean hasCT(PropertyWrapper prop, String type) throws UnsupportedEncodingException, FHIRException, IOException { + if (prop != null) { + for (BaseWrapper cd : prop.getValues()) { + PropertyWrapper telecoms = cd.getChildByName("telecom"); + if (getContactPoint(telecoms, type) != null) { + return true; + } + } + } + return false; + } + + private boolean hasCT(List list, String type) { + for (ContactDetail cd : list) { + for (ContactPoint t : cd.getTelecom()) { + if (type.equals(t.getSystem().toCode())) { + return true; + } + } + } + return false; + } + + + public boolean render(XhtmlNode x, Library lib) throws FHIRFormatError, DefinitionException, IOException { + if (lib.hasAuthor() || lib.hasEditor() || lib.hasReviewer() || lib.hasEndorser()) { + boolean email = hasCT(lib.getAuthor(), "email") || hasCT(lib.getEditor(), "email") || hasCT(lib.getReviewer(), "email") || hasCT(lib.getEndorser(), "email"); + boolean phone = hasCT(lib.getAuthor(), "phone") || hasCT(lib.getEditor(), "phone") || hasCT(lib.getReviewer(), "phone") || hasCT(lib.getEndorser(), "phone"); + boolean url = hasCT(lib.getAuthor(), "url") || hasCT(lib.getEditor(), "url") || hasCT(lib.getReviewer(), "url") || hasCT(lib.getEndorser(), "url"); + x.h2().tx("Participants"); + XhtmlNode t = x.table("grid"); + for (ContactDetail cd : lib.getAuthor()) { + participantRow(t, "Author", cd, email, phone, url); + } + for (ContactDetail cd : lib.getEditor()) { + participantRow(t, "Editor", cd, email, phone, url); + } + for (ContactDetail cd : lib.getReviewer()) { + participantRow(t, "Reviewer", cd, email, phone, url); + } + for (ContactDetail cd : lib.getEndorser()) { + participantRow(t, "Endorser", cd, email, phone, url); + } + } + if (lib.hasRelatedArtifact()) { + x.h2().tx("Related Artifacts"); + XhtmlNode t = x.table("grid"); + boolean label = false; + boolean display = false; + boolean citation = false; + for (RelatedArtifact ra : lib.getRelatedArtifact()) { + label = label || ra.hasLabel(); + display = display || ra.hasDisplay(); + citation = citation || ra.hasCitation(); + } + for (RelatedArtifact ra : lib.getRelatedArtifact()) { + renderArtifact(t, ra, lib, label, display, citation); + } + } + if (lib.hasParameter()) { + x.h2().tx("Parameters"); + XhtmlNode t = x.table("grid"); + boolean doco = false; + for (ParameterDefinition p : lib.getParameter()) { + doco = doco || p.hasDocumentation(); + } + for (ParameterDefinition p : lib.getParameter()) { + renderParameter(t, p, doco); + } + } + if (lib.hasDataRequirement()) { + x.h2().tx("Data Requirements"); + for (DataRequirement p : lib.getDataRequirement()) { + renderDataRequirement(x, p); + } + } + if (lib.hasContent()) { + x.h2().tx("Contents"); + boolean isCql = false; + int counter = 0; + for (Attachment att : lib.getContent()) { + renderAttachment(x, att, isCql, counter, lib.getId()); + isCql = isCql || (att.hasContentType() && att.getContentType().startsWith("text/cql")); + counter++; + } + } + return false; + } + + private void renderParameter(XhtmlNode t, BaseWrapper p, boolean doco) throws UnsupportedEncodingException, FHIRException, IOException { + XhtmlNode tr = t.tr(); + tr.td().tx(p.has("name") ? p.get("name").primitiveValue() : null); + tr.td().tx(p.has("use") ? p.get("use").primitiveValue() : null); + tr.td().tx(p.has("min") ? p.get("min").primitiveValue() : null); + tr.td().tx(p.has("max") ? p.get("max").primitiveValue() : null); + tr.td().tx(p.has("type") ? p.get("type").primitiveValue() : null); + if (doco) { + tr.td().tx(p.has("documentation") ? p.get("documentation").primitiveValue() : null); + } + } + + private void renderParameter(XhtmlNode t, ParameterDefinition p, boolean doco) { + XhtmlNode tr = t.tr(); + tr.td().tx(p.getName()); + tr.td().tx(p.getUse().getDisplay()); + tr.td().tx(p.getMin()); + tr.td().tx(p.getMax()); + tr.td().tx(p.getType().getDisplay()); + if (doco) { + tr.td().tx(p.getDocumentation()); + } + } + + private void renderArtifact(XhtmlNode t, BaseWrapper ra, ResourceWrapper lib, boolean label, boolean display, boolean citation) throws UnsupportedEncodingException, FHIRException, IOException { + XhtmlNode tr = t.tr(); + tr.td().tx(ra.has("type") ? ra.get("type").primitiveValue() : null); + if (label) { + tr.td().tx(ra.has("label") ? ra.get("label").primitiveValue() : null); + } + if (display) { + tr.td().tx(ra.has("display") ? ra.get("display").primitiveValue() : null); + } + if (citation) { + tr.td().markdown(ra.has("citation") ? ra.get("citation").primitiveValue() : null, "Citation"); + } + if (ra.has("resource")) { + renderCanonical(lib, tr.td(), ra.get("resource").primitiveValue()); + } else { + tr.td().tx(ra.has("url") ? ra.get("url").primitiveValue() : null); + } + } + + private void renderArtifact(XhtmlNode t, RelatedArtifact ra, Resource lib, boolean label, boolean display, boolean citation) throws IOException { + XhtmlNode tr = t.tr(); + tr.td().tx(ra.getType().getDisplay()); + if (label) { + tr.td().tx(ra.getLabel()); + } + if (display) { + tr.td().tx(ra.getDisplay()); + } + if (citation) { + tr.td().markdown(ra.getCitation(), "Citation"); + } + if (ra.hasResource()) { + renderCanonical(lib, tr.td(), ra.getResource()); + } else { + tr.td().tx(ra.getUrl()); + } + } + + private void participantRow(XhtmlNode t, String label, BaseWrapper cd, boolean email, boolean phone, boolean url) throws UnsupportedEncodingException, FHIRException, IOException { + XhtmlNode tr = t.tr(); + tr.td().tx(label); + tr.td().tx(cd.get("name") != null ? cd.get("name").primitiveValue() : null); + PropertyWrapper telecoms = cd.getChildByName("telecom"); + if (email) { + renderContactPoint(tr.td(), getContactPoint(telecoms, "email")); + } + if (phone) { + renderContactPoint(tr.td(), getContactPoint(telecoms, "phone")); + } + if (url) { + renderContactPoint(tr.td(), getContactPoint(telecoms, "url")); + } + } + + private ContactPoint getContactPoint(PropertyWrapper telecoms, String value) throws UnsupportedEncodingException, FHIRException, IOException { + for (BaseWrapper t : telecoms.getValues()) { + if (t.has("system")) { + String system = t.get("system").primitiveValue(); + if (value.equals(system)) { + return (ContactPoint) t.getBase(); + } + } + } + return null; + } + + private void participantRow(XhtmlNode t, String label, ContactDetail cd, boolean email, boolean phone, boolean url) { + XhtmlNode tr = t.tr(); + tr.td().tx(label); + tr.td().tx(cd.getName()); + if (email) { + renderContactPoint(tr.td(), cd.getEmail()); + } + if (phone) { + renderContactPoint(tr.td(), cd.getPhone()); + } + if (url) { + renderContactPoint(tr.td(), cd.getUrl()); + } + } + + public void describe(XhtmlNode x, Library lib) { + x.tx(display(lib)); + } + + public String display(Library lib) { + return lib.present(); + } + + @Override + public String display(Resource r) throws UnsupportedEncodingException, IOException { + return ((Library) r).present(); + } + + @Override + public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { + if (r.has("title")) { + return r.children("title").get(0).getBase().primitiveValue(); + } + return "??"; + } + + private void renderAttachment(XhtmlNode x, Attachment att, boolean noShowData, int counter, String baseId) { + boolean ref = !att.hasData() && att.hasUrl(); + if (ref) { + XhtmlNode p = x.para(); + if (att.hasTitle()) { + p.tx(att.getTitle()); + p.tx(": "); + } + p.code().ah(att.getUrl()).tx(att.getUrl()); + p.tx(" ("); + p.code().tx(att.getContentType()); + p.tx(lang(att)); + p.tx(")"); + } else if (!att.hasData()) { + XhtmlNode p = x.para(); + if (att.hasTitle()) { + p.tx(att.getTitle()); + p.tx(": "); + } + p.code().tx("No Content"); + p.tx(" ("); + p.code().tx(att.getContentType()); + p.tx(lang(att)); + p.tx(")"); + } else { + String txt = getText(att); + if (isImage(att.getContentType())) { + XhtmlNode p = x.para(); + if (att.hasTitle()) { + p.tx(att.getTitle()); + p.tx(": ("); + p.code().tx(att.getContentType()); + p.tx(lang(att)); + p.tx(")"); + } + else { + p.code().tx(att.getContentType()+lang(att)); + } + if (att.getData().length < LibraryRenderer.DATA_IMG_SIZE_CUTOFF) { + x.img("data: "+att.getContentType()+">;base64,"+b64(att.getData())); + } else { + String filename = "Library-"+baseId+(counter == 0 ? "" : "-"+Integer.toString(counter))+"."+imgExtension(att.getContentType()); + x.img(filename); + } + } else if (txt != null && !noShowData) { + XhtmlNode p = x.para(); + if (att.hasTitle()) { + p.tx(att.getTitle()); + p.tx(": ("); + p.code().tx(att.getContentType()); + p.tx(lang(att)); + p.tx(")"); + } + else { + p.code().tx(att.getContentType()+lang(att)); + } + String prismCode = determinePrismCode(att); + if (prismCode != null) { + x.pre().code().setAttribute("class", "language-"+prismCode).tx(txt); + } else { + x.pre().code().tx(txt); + } + } else { + XhtmlNode p = x.para(); + if (att.hasTitle()) { + p.tx(att.getTitle()); + p.tx(": "); + } + p.code().tx("Content not shown - ("); + p.code().tx(att.getContentType()); + p.tx(lang(att)); + p.tx(", size = "+Utilities.describeSize(att.getData().length)+")"); + } + } + } + + private String imgExtension(String contentType) { + if (contentType != null && contentType.startsWith("image/")) { + if (contentType.startsWith("image/png")) { + return "png"; + } + if (contentType.startsWith("image/jpeg")) { + return "jpg"; + } + } + return null; + } + + private String b64(byte[] data) { + byte[] encodeBase64 = Base64.encodeBase64(data); + return new String(encodeBase64); + } + + private boolean isImage(String contentType) { + return imgExtension(contentType) != null; + } + + private String lang(Attachment att) { + if (att.hasLanguage()) { + return ", language = "+describeLang(att.getLanguage()); + } + return ""; + } + + private String getText(Attachment att) { + try { + try { + String src = new String(att.getData(), "UTF-8"); + if (checkString(src)) { + return src; + } + } catch (Exception e) { + // ignore + } + try { + String src = new String(att.getData(), "UTF-16"); + if (checkString(src)) { + return src; + } + } catch (Exception e) { + // ignore + } + try { + String src = new String(att.getData(), "ASCII"); + if (checkString(src)) { + return src; + } + } catch (Exception e) { + // ignore + } + return null; + } catch (Exception e) { + return null; + } + } + + public boolean checkString(String src) { + for (char ch : src.toCharArray()) { + if (ch < ' ' && ch != '\r' && ch != '\n' && ch != '\t') { + return false; + } + } + return true; + } + + private String determinePrismCode(Attachment att) { + if (att.hasContentType()) { + String ct = att.getContentType(); + if (ct.contains(";")) { + ct = ct.substring(0, ct.indexOf(";")); + } + switch (ct) { + case "text/html" : return "html"; + case "text/xml" : return "xml"; + case "application/xml" : return "xml"; + case "text/markdown" : return "markdown"; + case "application/js" : return "JavaScript"; + case "application/css" : return "css"; + case "text/x-csrc" : return "c"; + case "text/x-csharp" : return "csharp"; + case "text/x-c++src" : return "cpp"; + case "application/graphql" : return "graphql"; + case "application/x-java" : return "java"; + case "application/json" : return "json"; + case "text/json" : return "json"; + case "application/liquid" : return "liquid"; + case "text/x-pascal" : return "pascal"; + case "text/x-python" : return "python"; + case "text/x-rsrc" : return "r"; + case "text/x-ruby" : return "ruby"; + case "text/x-sas" : return "sas"; + case "text/x-sql" : return "sql"; + case "application/typescript" : return "typescript"; + case "text/cql" : return "sql"; // not that bad... + } + if (att.getContentType().contains("json+") || att.getContentType().contains("+json")) { + return "json"; + } + if (att.getContentType().contains("xml+") || att.getContentType().contains("+xml")) { + return "xml"; + } + } + return null; + } + + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 4ab610b78..155e8bbc0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -392,6 +392,8 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } else if (e instanceof DataRequirement) { DataRequirement p = (DataRequirement) e; renderDataRequirement(x, p); + } else if (e instanceof PrimitiveType) { + x.tx(((PrimitiveType) e).primitiveValue()); } else if (e instanceof ElementDefinition) { x.tx("todo-bundle"); } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) { @@ -486,7 +488,11 @@ public class ProfileDrivenRenderer extends ResourceRenderer { renderAddress(x, (Address) e); return true; } else if (e instanceof ContactPoint) { - renderContactPoint(x, (ContactPoint) e); + if (allowLinks) { + renderContactPoint(x, (ContactPoint) e); + } else { + displayContactPoint(x, (ContactPoint) e); + } return true; } else if (e instanceof Timing) { renderTiming(x, (Timing) e); @@ -528,7 +534,11 @@ public class ProfileDrivenRenderer extends ResourceRenderer { boolean first = true; for (ContactPoint c : cd.getTelecom()) { if (first) first = false; else x.tx(","); - renderContactPoint(x, c); + if (allowLinks) { + renderContactPoint(x, c); + } else { + displayContactPoint(x, c); + } } return true; } else if (e instanceof Range) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java index 5935729b4..f435b97a1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/RendererFactory.java @@ -59,6 +59,9 @@ public class RendererFactory { if ("Encounter".equals(resourceName)) { return new EncounterRenderer(context); } + if ("Library".equals(resourceName)) { + return new LibraryRenderer(context); + } if ("List".equals(resourceName)) { return new ListRenderer(context); } @@ -102,6 +105,9 @@ public class RendererFactory { if ("List".equals(resource.getName())) { return new ListRenderer(context); } + if ("Library".equals(resource.getName())) { + return new LibraryRenderer(context); + } if ("DiagnosticReport".equals(resource.getName())) { return new DiagnosticReportRenderer(context); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index 53db3f5ac..deb2b4a38 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -125,6 +125,44 @@ public abstract class ResourceRenderer extends DataRenderer { } } + public void renderCanonical(Resource res, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { + ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); + renderCanonical(rw, x, url); + } + + public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { + renderCanonical(rw, x, url, true); + } + + public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url, boolean allowLinks) throws UnsupportedEncodingException, IOException { + if (url == null) { + return; + } + Resource target = context.getWorker().fetchResource(Resource.class, url); + if (target == null || !(target instanceof CanonicalResource)) { + x.code().tx(url); + } else { + CanonicalResource cr = (CanonicalResource) target; + if (url.contains("|")) { + if (target.hasUserData("path")) { + x.ah(target.getUserString("path")).tx(cr.present()+" (version "+cr.getVersion()+")"); + } else { + url = url.substring(0, url.indexOf("|")); + x.code().tx(url); + x.tx(": "+cr.present()+" (version "+cr.getVersion()+")"); + } + } else { + if (target.hasUserData("path")) { + x.ah(target.getUserString("path")).tx(cr.present()); + } else { + url = url.substring(0, url.indexOf("|")); + x.code().tx(url); + x.tx(": "+cr.present()); + } + } + } + } + public void renderReference(Resource res, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException { ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); renderReference(rw, x, r); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index fef5851a2..83101e229 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -52,6 +52,7 @@ public class BaseWrappers { public void describe(XhtmlNode x) throws UnsupportedEncodingException, IOException; public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException; public BaseWrapper root(); + public PropertyWrapper getChildByName(String tail); public StructureDefinition getDefinition(); public boolean hasNarrative(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java index 3fd8d6a7c..6fbb5f457 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java @@ -362,6 +362,16 @@ public class DOMWrappers { public String fhirType() { return wrapped.getNodeName(); } + + @Override + public PropertyWrapper getChildByName(String name) { + for (PropertyWrapper p : children()) + if (p.getName().equals(name)) + return p; + return null; + } + + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index 7ec2aeb5b..5fe4dc859 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -267,6 +267,16 @@ public class DirectWrappers { public String fhirType() { return wrapped.fhirType(); } + + @Override + public PropertyWrapper getChildByName(String name) { + Property p = wrapped.getChildByName(name); + if (p == null) + return null; + else + return new PropertyWrapperDirect(context, p); + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index 2ca58caf5..c0ab9ff0a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -246,7 +246,16 @@ public class ElementWrappers { public String fhirType() { return wrapped.fhirType(); } - } + + @Override + public PropertyWrapper getChildByName(String name) { + for (PropertyWrapper p : children()) + if (p.getName().equals(name)) + return p; + return null; + } + +} public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MimeType.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MimeType.java index e3309e12c..d3a3f0b8d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MimeType.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MimeType.java @@ -90,4 +90,49 @@ public class MimeType { return source; } + public static String getExtension(String mimeType) { + MimeType mt = new MimeType(mimeType); + return mt.getExtension(); + } + + public String getExtension() { + switch (base) { + case "text/html" : return "html"; + case "text/xml" : return "xml"; + case "application/xml" : return "xml"; + case "text/markdown" : return "md"; + case "application/js" : return "js"; + case "application/css" : return "css"; + case "text/x-csrc" : return "c"; + case "text/x-csharp" : return "cs"; + case "text/x-c++src" : return "c"; + case "application/graphql" : return "graphql"; + case "application/x-java" : return "java"; + case "application/json" : return "json"; + case "text/json" : return "json"; + case "application/liquid" : return "liquid"; + case "text/x-pascal" : return "pas"; + case "text/x-python" : return "py"; + case "text/x-rsrc" : return "r"; + case "text/x-ruby" : return "ruby"; + case "text/x-sas" : return "sas"; + case "text/x-sql" : return "sql"; + case "application/typescript" : return "ts"; + case "text/cql": return "cql"; + case "image/png": return "png"; + case "image/gif": return "gif"; + case "image/jpeg": return "jpg"; + } + if (base.contains("xml+") || base.contains("+xml")) { + return "xml"; + } + if (base.contains("json+") || base.contains("+json")) { + return "json"; + } + if (base.contains("turtle+") || base.contains("+turtle")) { + return "ttl"; + } + return null; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 2015c3e22..82d06017a 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -47,6 +47,8 @@ import java.math.RoundingMode; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.util.ArrayList; @@ -320,13 +322,13 @@ public class Utilities { destFile.createNewFile(); } - FileChannel source = null; - FileChannel destination = null; + FileInputStream source = null; + FileOutputStream destination = null; try { - source = new FileInputStream(sourceFile).getChannel(); - destination = new FileOutputStream(destFile).getChannel(); - destination.transferFrom(source, 0, source.size()); + source = new FileInputStream(sourceFile); + destination = new FileOutputStream(destFile); + destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size()); } finally { if (source != null) { source.close(); @@ -398,8 +400,8 @@ public class Utilities { clearDirectory(fh.getAbsolutePath()); fh.delete(); } + } } - } } } } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageHacker.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageHacker.java index ba3319bbe..c9c2c7ebe 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageHacker.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageHacker.java @@ -25,7 +25,7 @@ import com.google.gson.JsonObject; public class PackageHacker { public static void main(String[] args) throws FileNotFoundException, IOException { - new PackageHacker().edit("M:\\web\\hl7.org\\fhir\\uv\\cdisc-lab\\package.tgz"); + new PackageHacker().edit("M:\\web\\hl7.org\\fhir\\us\\carin-rtpbc\\package.tgz"); } private void edit(String name) throws FileNotFoundException, IOException { @@ -58,9 +58,9 @@ public class PackageHacker { private void change(JsonObject npm, Map content) throws FileNotFoundException, IOException { fixVersions(npm); npm.remove("url"); - npm.addProperty("url", "http://hl7.org/fhir/uv/cdisc-lab/STU1"); - npm.remove("name"); - npm.addProperty("name", "hl7.fhir.uv.cdisc-lab"); + npm.addProperty("url", "http://hl7.org/fhir/us/carin-rtpbc/STU1"); +// npm.remove("name"); +// npm.addProperty("name", "hl7.fhir.uv.smart-app-launch"); // npm.remove("canonical"); // npm.addProperty("canonical", "http://hl7.org/fhir/us/davinci-drug-formulary"); //// npm.remove("description"); @@ -70,7 +70,7 @@ public class PackageHacker { // npm.remove("dependencies"); // JsonObject dep = new JsonObject(); // npm.add("dependencies", dep); -// dep.addProperty("hl7.fhir.r4.core", "4.0.1"); +// dep.addProperty("hl7.fhir.r3.core", "3.0.1"); // dep.addProperty("hl7.fhir.r4.examples", "4.0.1"); // dep.addProperty("hl7.fhir.r4.expansions", "4.0.1"); // dep.addProperty("hl7.fhir.r4.elements", "4.0.1"); @@ -80,7 +80,7 @@ public class PackageHacker { npm.remove("fhirVersions"); JsonArray a = new JsonArray(); npm.add("fhirVersions", a); - a.add("4.5.0"); + a.add("3.0.1"); } private void setProperty(JsonObject npm, String name, String value) { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index d501ff789..e561009f2 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -38,9 +38,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.instance.model.api.IBaseXhtml; +import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; @@ -86,6 +90,9 @@ public class XhtmlNode implements IBaseXhtml { private List childNodes = new ArrayList(); private String content; private boolean notPretty; + private boolean inPara; + private boolean inLink; + public XhtmlNode() { super(); @@ -140,7 +147,7 @@ public class XhtmlNode implements IBaseXhtml { return this; } - public void validate(List errors, String path, boolean inResource, boolean inPara) { + public void validate(List errors, String path, boolean inResource, boolean inPara, boolean inLink) { if (nodeType == NodeType.Element || nodeType == NodeType.Document) { path = Utilities.noString(path) ? name : path+"/"+name; if (inResource) { @@ -168,13 +175,19 @@ public class XhtmlNode implements IBaseXhtml { if (inPara && Utilities.existsInList(name, "div", "blockquote", "table", "ol", "ul", "p")) { errors.add("Error at "+path+": Found "+name+" inside an html paragraph"); } + if (inLink && Utilities.existsInList(name, "a")) { + errors.add("Error at "+path+": Found an inside an paragraph"); + } if (childNodes != null) { if ("p".equals(name)) { inPara = true; } + if ("a".equals(name)) { + inLink = true; + } for (XhtmlNode child : childNodes) { - child.validate(errors, path, inResource, inPara); + child.validate(errors, path, inResource, inPara, inLink); } } } @@ -187,8 +200,20 @@ public class XhtmlNode implements IBaseXhtml { throw new Error("Wrong node type - node is "+nodeType.toString()+" ('"+getName()+"/"+getContent()+"')"); } +// if (inPara && name.equals("p")) { +// throw new FHIRException("nested Para"); +// } +// if (inLink && name.equals("a")) { +// throw new FHIRException("Nested Link"); +// } XhtmlNode node = new XhtmlNode(NodeType.Element); node.setName(name); + if (inPara || name.equals("p")) { + node.inPara = true; + } + if (inLink || name.equals("a")) { + node.inLink = true; + } childNodes.add(node); return node; } @@ -199,6 +224,12 @@ public class XhtmlNode implements IBaseXhtml { if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) throw new Error("Wrong node type. is "+nodeType.toString()); XhtmlNode node = new XhtmlNode(NodeType.Element); + if (inPara || name.equals("p")) { + node.inPara = true; + } + if (inLink || name.equals("a")) { + node.inLink = true; + } node.setName(name); childNodes.add(index, node); return node; @@ -796,6 +827,21 @@ public class XhtmlNode implements IBaseXhtml { } + public void markdown(String md, String source) throws IOException { + if (md != null) { + String s = new MarkDownProcessor(Dialect.COMMON_MARK).process(md, source); + XhtmlParser p = new XhtmlParser(); + XhtmlNode m; + try { + m = p.parse("
"+s+"
", "div"); + } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { + throw new FHIRFormatError(e.getMessage(), e); + } + getChildNodes().addAll(m.getChildNodes()); + } + } + + diff --git a/pom.xml b/pom.xml index d83343043..41bf97232 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 5.1.0 - 1.1.42 + 1.1.43-SNAPSHOT 5.6.2 3.0.0-M4 0.8.5