From f02c9f0e09b13d84b2f6517b74c0ff9826fe7c6c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 20 May 2020 18:50:08 +1000 Subject: [PATCH] Add questionnaire + NamingSystem rendering and fix various bugs in renderers from further testing --- .../hl7/fhir/r5/renderers/BundleRenderer.java | 8 +- .../fhir/r5/renderers/CodeSystemRenderer.java | 8 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 10 +- .../r5/renderers/NamingSystemRenderer.java | 127 +++++++++++++++ .../r5/renderers/ProfileDrivenRenderer.java | 3 + .../r5/renderers/QuestionnaireRenderer.java | 147 ++++++++++++++++-- .../fhir/r5/renderers/RendererFactory.java | 6 + .../fhir/r5/renderers/ResourceRenderer.java | 30 ++++ .../r5/renderers/utils/RenderingContext.java | 39 ++--- .../r5/terminologies/CodeSystemUtilities.java | 5 +- .../r5/test/NarrativeGenerationTests.java | 27 +++- 11 files changed, 363 insertions(+), 47 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java index b060ad7de..9570d5205 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java @@ -47,7 +47,9 @@ public class BundleRenderer extends ResourceRenderer { throw new FHIRException("Invalid document - first entry is not a Composition"); Composition dr = (Composition) b.getEntryFirstRep().getResource(); return dr.getText().getDiv(); - } else { + } else if ((b.getType() == BundleType.DOCUMENT && allEntresAreHistoryProvenance(b))) { + return null; + } else { XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); root.para().addText("Bundle "+b.getId()+" of type "+b.getType().toCode()); int i = 0; @@ -79,6 +81,10 @@ public class BundleRenderer extends ResourceRenderer { } + private boolean allEntresAreHistoryProvenance(Bundle b) { + return false; + } + private List checkInternalLinks(Bundle b, List childNodes) { scanNodesForInternalLinks(b, childNodes); return childNodes; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java index 0f829536b..799e415bb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java @@ -39,13 +39,13 @@ public class CodeSystemRenderer extends TerminologyRenderer { public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { - return render(x, (CodeSystem) dr, false); + return render(x, (CodeSystem) dr); } - public boolean render(XhtmlNode x, CodeSystem cs, boolean header) throws FHIRFormatError, DefinitionException, IOException { + public boolean render(XhtmlNode x, CodeSystem cs) throws FHIRFormatError, DefinitionException, IOException { boolean hasExtensions = false; - if (header) { + if (context.isHeader()) { XhtmlNode h = x.h2(); h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName()); addMarkdown(x, cs.getDescription()); @@ -283,7 +283,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { } for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) { - if (cd.hasLanguage() && !langs.contains(cd.getLanguage())) { + if (cd.hasLanguage() && !langs.contains(cd.getLanguage()) && (!cs.hasLanguage() || !cs.getLanguage().equals(cd.getLanguage()))) { langs.add(cd.getLanguage()); } 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 325b37303..f1a101da2 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 @@ -23,6 +23,7 @@ import org.hl7.fhir.r5.model.HumanName; import org.hl7.fhir.r5.model.HumanName.NameUse; import org.hl7.fhir.r5.model.Identifier; import org.hl7.fhir.r5.model.Period; +import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Quantity; import org.hl7.fhir.r5.model.Range; import org.hl7.fhir.r5.model.SampledData; @@ -203,11 +204,18 @@ public class DataRenderer { return sd.getBaseDefinitionElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-codegen-super"); } + // -- 4. Language support ------------------------------------------------------ + protected String translate(String source, String content) { return content; } - // -- 4. Data type Rendering ---------------------------------------------- + public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { + return value.primitiveValue(); + } + + + // -- 5. Data type Rendering ---------------------------------------------- public static String display(IWorkerContext context, DataType type) { return new DataRenderer(new RenderingContext(context, null, null, "", null, ResourceRendererMode.RESOURCE)).display(type); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java new file mode 100644 index 000000000..2f9a8db9a --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/NamingSystemRenderer.java @@ -0,0 +1,127 @@ +package org.hl7.fhir.r5.renderers; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.NamingSystem; +import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent; +import org.hl7.fhir.r5.model.PrimitiveType; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; +import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +public class NamingSystemRenderer extends ResourceRenderer { + + public NamingSystemRenderer(RenderingContext context) { + super(context); + } + + public NamingSystemRenderer(RenderingContext context, ResourceContext rcontext) { + super(context, rcontext); + } + + public boolean render(XhtmlNode x, DomainResource dr) throws FHIRFormatError, DefinitionException, IOException { + return render(x, (NamingSystem) dr); + } + + public boolean render(XhtmlNode x, NamingSystem ns) throws FHIRFormatError, DefinitionException, IOException { + x.h3().tx("Summary"); + XhtmlNode tbl = x.table("grid"); + row(tbl, "Defining URL", ns.getUrl()); + if (ns.hasVersion()) { + row(tbl, "Version", ns.getVersion()); + } + if (ns.hasName()) { + row(tbl, "Name", gt(ns.getNameElement())); + } + if (ns.hasTitle()) { + row(tbl, "Title", gt(ns.getTitleElement())); + } + row(tbl, "Status", ns.getStatus().toCode()); + if (ns.hasDescription()) { + addMarkdown(row(tbl, "Definition"), ns.getDescription()); + } + if (ns.hasPublisher()) { + row(tbl, "Publisher", gt(ns.getPublisherElement())); + } + if (ns.hasExtension(ToolingExtensions.EXT_WORKGROUP)) { + renderCommitteeLink(row(tbl, "Committee"), ns); + } + if (CodeSystemUtilities.hasOID(ns)) { + row(tbl, "OID", CodeSystemUtilities.getOID(ns)).tx("("+translate("ns.summary", "for OID based terminology systems")+")"); + } + if (ns.hasCopyright()) { + addMarkdown(row(tbl, "Copyright"), ns.getCopyright()); + } + boolean hasPreferred = false; + boolean hasPeriod = false; + boolean hasComment = false; + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + hasPreferred = hasPreferred || id.hasPreferred(); + hasPeriod = hasPeriod || id.hasPeriod(); + hasComment = hasComment || id.hasComment(); + } + x.h3().tx("Identifiers"); + tbl = x.table("grid"); + XhtmlNode tr = tbl.tr(); + tr.td().b().tx(translate("ns.summary", "Type")); + tr.td().b().tx(translate("ns.summary", "Value")); + if (hasPreferred) { + tr.td().b().tx(translate("ns.summary", "Preferred")); + } + if (hasPeriod) { + tr.td().b().tx(translate("ns.summary", "Period")); + } + if (hasComment) { + tr.td().b().tx(translate("ns.summary", "Comment")); + } + for (NamingSystemUniqueIdComponent id : ns.getUniqueId()) { + tr = tbl.tr(); + tr.td().tx(id.getType().getDisplay()); + tr.td().tx(id.getValue()); + if (hasPreferred) { + tr.td().tx(id.getPreferredElement().primitiveValue()); + } + if (hasPeriod) { + tr.td().tx(display(id.getPeriod())); + } + if (hasComment) { + tr.td().tx(id.getComment()); + } + } + return false; + } + + private XhtmlNode row(XhtmlNode tbl, String name) { + XhtmlNode tr = tbl.tr(); + XhtmlNode td = tr.td(); + td.tx(translate("ns.summary", name)); + return tr.td(); + } + private XhtmlNode row(XhtmlNode tbl, String name, String value) { + XhtmlNode td = row(tbl, name); + td.tx(value); + return td; + } + + public void describe(XhtmlNode x, NamingSystem ns) { + x.tx(display(ns)); + } + + public String display(NamingSystem ns) { + return ns.present(); + } + + @Override + public String display(DomainResource r) throws UnsupportedEncodingException, IOException { + return ((NamingSystem) r).present(); + } + +} 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 f2cb684b0..dbb6fc971 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 @@ -97,6 +97,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer { try { StructureDefinition sd = r.getDefinition(); ElementDefinition ed = sd.getSnapshot().getElement().get(0); + if (sd.getType().equals("NamingSystem") && "icd10".equals(r.getId())) { + System.out.println("hah!"); + } generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.getName(), false, 0); } catch (Exception e) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index d3f9a33ed..79ad3373b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -4,20 +4,28 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; +import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Expression; +import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent; import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemInitialComponent; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; +import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; +import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; import org.hl7.fhir.utilities.xhtml.XhtmlNode; -public class QuestionnaireRenderer extends ResourceRenderer { +public class QuestionnaireRenderer extends TerminologyRenderer { - private boolean tree; + private boolean tree = false; public QuestionnaireRenderer(RenderingContext context) { super(context); @@ -53,26 +61,59 @@ public class QuestionnaireRenderer extends ResourceRenderer { model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Text"), translate("sd.hint", "Text for the item"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Cardinality"), translate("sd.hint", "Minimum and Maximum # of times the the itemcan appear in the instance"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Type"), translate("sd.hint", "The type of the item"), null, 0)); + model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Flags"), translate("sd.hint", "Other attributes of the item"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the item"), null, 0)); boolean hasExt = false; for (QuestionnaireItemComponent i : q.getItem()) { hasExt = renderTreeItem(gen, model.getRows(), q, i) || hasExt; } + XhtmlNode xn = gen.generate(model, context.getDestDir(), 1, null); + x.getChildNodes().add(xn); return hasExt; } - private boolean renderTreeItem(HierarchicalTableGenerator gen, List rows, Questionnaire q, QuestionnaireItemComponent i) { + private boolean renderTreeItem(HierarchicalTableGenerator gen, List rows, Questionnaire q, QuestionnaireItemComponent i) throws IOException { Row r = gen.new Row(); rows.add(r); boolean hasExt = false; + r.setIcon("icon-q-"+i.getType().toCode()+".png", i.getType().getDisplay()); r.getCells().add(gen.new Cell(null, context.getDefinitionsTarget() == null ? "" : context.getDefinitionsTarget()+"-definitions.html#extension."+i.getLinkId(), i.getLinkId(), null, null)); String txt = (i.hasPrefix() ? i.getPrefix() + ". " : "") + i.getText(); r.getCells().add(gen.new Cell(null, null, txt, null, null)); r.getCells().add(gen.new Cell(null, null, (i.getRequired() ? "1" : "0")+".."+(i.getRepeats() ? "*" : "1"), null, null)); r.getCells().add(gen.new Cell(null, context.getPrefix()+"codesystem-item-type.html#"+i.getType().toCode(), i.getType().toCode(), null, null)); - Cell defn = gen.new Cell(null, null, null, null, null); + + // flags: + Cell flags = gen.new Cell(); + r.getCells().add(flags); + if (i.getReadOnly()) { + flags.addPiece(gen.new Piece(Utilities.pathURL(context.getPrefix(), "questionnaire-definitions.html#Questionnaire.item.readOnly"), null, "Is Readonly").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-readonly.png")))); + } + if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { + flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject", null, "Can change the subject of the questionnaire").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-subject.png")))); + } + if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { + flags.addPiece(gen.new Piece(Utilities.pathURL(context.getPrefix(), "extension-questionnaire-hidden.html"), null, "Is a hidden item").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-hidden.png")))); + } + if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay")) { + flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay", null, "Is optional to display").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-optional.png")))); + } + if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod")) { + flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod", null, "Is linked to an observation").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-observation.png")))); + } + if (i.hasExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation")) { + String code = ToolingExtensions.readStringExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation"); + flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod", null, "Orientation: "+code).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-"+code+".png")))); + } + if (i.hasExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory")) { + CodeableConcept cc = i.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory").getValueCodeableConcept(); + String code = cc.getCode("http://hl7.org/fhir/questionnaire-display-category"); + flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod", null, "Category: "+code).addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-"+code+".png")))); + } + + Cell defn = gen.new Cell(); r.getCells().add(defn); if (i.hasMaxLength()) { @@ -93,12 +134,26 @@ public class QuestionnaireRenderer extends ResourceRenderer { if (i.hasAnswerValueSet()) { if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); defn.getPieces().add(gen.new Piece(null, "Value Set: ", null)); - defn.getPieces().add(gen.new Piece(null, "todo", null)); + if (i.getAnswerValueSet().startsWith("#")) { + ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); + if (vs == null) { + defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); + } else { + defn.getPieces().add(gen.new Piece("todo", vs.present(), null)); + } + } else { + ValueSet vs = context.getWorker().fetchResource(ValueSet.class, i.getAnswerValueSet()); + if (vs == null || !vs.hasUserData("path")) { + defn.getPieces().add(gen.new Piece(null, i.getAnswerValueSet(), null)); + } else { + defn.getPieces().add(gen.new Piece(vs.getUserString("path"), vs.present(), null)); + } + } } if (i.hasAnswerOption()) { if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); defn.getPieces().add(gen.new Piece(null, "Options: ", null)); - defn.getPieces().add(gen.new Piece(null, "todo", null)); + defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#"+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); } if (i.hasInitial()) { for (QuestionnaireItemInitialComponent v : i.getInitial()) { @@ -113,31 +168,101 @@ public class QuestionnaireRenderer extends ResourceRenderer { } } } + // still todo + +// +//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-choiceColumn +// +//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-width +//http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-observationLinkPeriod +//http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl +//http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue + if (i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression") || i.hasExtension("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { + if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); + defn.getPieces().add(gen.new Piece(null, "Expressions: ", null)); + Piece p = gen.new Piece("ul"); + defn.getPieces().add(p); + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression")) { + addExpression(p, e.getValueExpression(), "Initial Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-initialExpression"); + } + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression")) { + addExpression(p, e.getValueExpression(), "Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-contextExpression"); + } + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext")) { + addExpression(p, e.getValueExpression(), "Item Context", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext"); + } + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression")) { + addExpression(p, e.getValueExpression(), "Enable When", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression"); + } + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression")) { + addExpression(p, e.getValueExpression(), "Calculated Value", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-calculatedExpression"); + } + for (Extension e : i.getExtensionsByUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression")) { + addExpression(p, e.getValueExpression(), "Candidates", "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-candidateExpression"); + } + } + for (QuestionnaireItemComponent c : i.getItem()) { - hasExt = renderTreeItem(gen, rows, q, c) || hasExt; + hasExt = renderTreeItem(gen, r.getSubRows(), q, c) || hasExt; } return hasExt; } + private void addExpression(Piece p, Expression exp, String label, String url) { + XhtmlNode x = new XhtmlNode(NodeType.Element, "li").style("font-size: 11px"); + p.addHtml(x); + x.ah(url).tx(label); + x.tx(": "); + x.code(exp.getExpression()); + } + public boolean renderForm(XhtmlNode x, Questionnaire q) throws UnsupportedEncodingException, IOException { boolean hasExt = false; XhtmlNode d = x.div(); + boolean hasPrefix = false; for (QuestionnaireItemComponent c : q.getItem()) { - hasExt = renderFormItem(d, q, c) || hasExt; + hasPrefix = hasPrefix || doesItemHavePrefix(c); + } + int i = 1; + for (QuestionnaireItemComponent c : q.getItem()) { + hasExt = renderFormItem(d, q, c, hasPrefix ? null : Integer.toString(i), true) || hasExt; + i++; } return hasExt; } - private boolean renderFormItem(XhtmlNode x, Questionnaire q, QuestionnaireItemComponent i) { + private boolean doesItemHavePrefix(QuestionnaireItemComponent i) { + if (i.hasPrefix()) { + return true; + } + for (QuestionnaireItemComponent c : i.getItem()) { + if (doesItemHavePrefix(c)) { + return true; + } + } + return false; + } + + private boolean renderFormItem(XhtmlNode x, Questionnaire q, QuestionnaireItemComponent i, String pfx, boolean root) { boolean hasExt = false; XhtmlNode d = x.div(); - d.para().tx(i.getText()); + if (!root) { + d.style("margin-left: 10px"); + } + XhtmlNode p = d.para(); + if (i.hasPrefix()) { + p.tx(i.getPrefix()); + p.tx(": "); + } + p.span(null, "linkId: "+i.getLinkId()).tx(i.getText()); + int t = 1; for (QuestionnaireItemComponent c : i.getItem()) { - hasExt = renderFormItem(d, q, c) || hasExt; + hasExt = renderFormItem(d, q, c, pfx == null ? null : pfx+"."+Integer.toString(t), false) || hasExt; + t++; } return hasExt; } 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 0965a2f71..1c8030668 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 @@ -41,6 +41,12 @@ public class RendererFactory { if ("ImplementationGuide".equals(resourceName)) { return new ImplementationGuideRenderer(context); } + if ("NamingSystem".equals(resourceName)) { + return new NamingSystemRenderer(context); + } + if ("Questionnaire".equals(resourceName)) { + return new QuestionnaireRenderer(context); + } if ("Patient".equals(resourceName)) { return new PatientRenderer(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 118019eaf..72555c999 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 @@ -9,7 +9,10 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Narrative; import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; @@ -23,7 +26,9 @@ import org.hl7.fhir.r5.renderers.utils.ElementWrappers.ResourceWrapperMetaElemen 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.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; +import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -242,4 +247,29 @@ public abstract class ResourceRenderer extends DataRenderer { } + protected String describeStatus(PublicationStatus status, boolean experimental) { + switch (status) { + case ACTIVE: return experimental ? "Experimental" : "Active"; + case DRAFT: return "draft"; + case RETIRED: return "retired"; + default: return "Unknown"; + } + } + + protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) { + String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP); + CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group"); + if (cs == null || !cs.hasUserData("path")) + x.tx(code); + else { + ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code); + if (cd == null) { + x.tx(code); + } else { + x.ah(cs.getUserString("path")+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay()); + } + } + } + + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java index a3293c71b..4c3873f94 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java @@ -15,36 +15,11 @@ import org.hl7.fhir.r5.renderers.utils.Resolver.IReferenceResolver; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.utilities.MarkDownProcessor; +import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; import org.hl7.fhir.utilities.validation.ValidationOptions; public class RenderingContext { -// from NarrativeGenerator -// -// private String prefix; -// private IWorkerContext context; -// private String basePath; -// private String tooCostlyNoteEmpty; -// private String tooCostlyNoteNotEmpty; -// private String tooCostlyNoteEmptyDependent; -// private String tooCostlyNoteNotEmptyDependent; -// private IReferenceResolver resolver; -// private int headerLevelContext; -// private boolean canonicalUrlsAsLinks; -// private ValidationOptions terminologyServiceOptions = new ValidationOptions(); -// private boolean noSlowLookup; -// private List codeSystemPropList = new ArrayList<>(); -// private ProfileUtilities profileUtilities; -// private XVerExtensionManager xverManager; -// -// -// public Base parseType(String xml, String type) throws IOException, FHIRException { -// if (parser != null) -// return parser.parseType(xml, type); -// else -// return new XmlParser().parseAnyType(xml, type); -// } - public interface ILiquidTemplateProvider { String findTemplate(RenderingContext rcontext, DomainResource r); String findTemplate(RenderingContext rcontext, String resourceName); @@ -71,6 +46,7 @@ public class RenderingContext { private int headerLevelContext; private boolean canonicalUrlsAsLinks; private boolean pretty; + private boolean header; protected ValidationOptions terminologyServiceOptions; private boolean noSlowLookup; @@ -129,6 +105,9 @@ public class RenderingContext { } public MarkDownProcessor getMarkdown() { + if (markdown == null) { + markdown = new MarkDownProcessor(Dialect.COMMON_MARK); + } return markdown; } @@ -315,6 +294,14 @@ public class RenderingContext { public void setInlineGraphics(boolean inlineGraphics) { this.inlineGraphics = inlineGraphics; } + + public boolean isHeader() { + return header; + } + + public void setHeader(boolean header) { + this.header = header; + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index f5ee88089..b7083dd89 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -40,6 +40,7 @@ import java.util.Set; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.model.BooleanType; +import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; @@ -291,11 +292,11 @@ public class CodeSystemUtilities { } - public static boolean hasOID(CodeSystem cs) { + public static boolean hasOID(CanonicalResource cs) { return getOID(cs) != null; } - public static String getOID(CodeSystem cs) { + public static String getOID(CanonicalResource cs) { if (cs.hasIdentifier() && "urn:ietf:rfc:3986".equals(cs.getIdentifierFirstRep().getSystem()) && cs.getIdentifierFirstRep().hasValue() && cs.getIdentifierFirstRep().getValue().startsWith("urn:oid:")) return cs.getIdentifierFirstRep().getValue().substring(8); return null; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index ede9c0df8..1dcd0e1ba 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -20,6 +20,8 @@ import org.hl7.fhir.r5.renderers.ResourceRenderer; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xml.XMLUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -31,22 +33,38 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; -@Disabled //Test case 1 doesn't pass public class NarrativeGenerationTests { + private static final String HEADER = ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "\r\n
\r\n

Narrative

"; + private static final String FOOTER = "\r\n
"; + private static IWorkerContext context; public static class TestDetails { private String id; + private boolean header; public TestDetails(Element test) { super(); id = test.getAttribute("id"); + header = "true".equals(test.getAttribute("header")); } public String getId() { return id; } + + public boolean isHeader() { + return header; + } + } public static Stream data() throws ParserConfigurationException, IOException, FHIRFormatError, SAXException { @@ -69,13 +87,18 @@ public class NarrativeGenerationTests { @ParameterizedTest(name = "{index}: file {0}") @MethodSource("data") public void test(String id, TestDetails test) throws Exception { - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", null, ResourceRendererMode.RESOURCE); + rc.setDestDir("C:\\work\\org.hl7.fhir\\packages\\packages\\hl7.fhir.pubpack\\package\\other\\"); + rc.setHeader(test.isHeader()); + rc.setDefinitionsTarget("test.html"); IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml"), new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-expected.xml"))); DomainResource source = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-input.xml")); DomainResource target = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-expected.xml")); RendererFactory.factory(source, rc).render(source); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml")), source); source = (DomainResource) new XmlParser().parse(new FileInputStream(TestingUtilities.tempFile("narrative", test.getId() + "-actual.xml"))); + String html = HEADER+new XhtmlComposer(true).compose(source.getText().getDiv())+FOOTER; + TextFile.stringToFile(html, TestingUtilities.tempFile("narrative", test.getId() + ".html")); Assertions.assertTrue(source.equalsDeep(target), "Output does not match expected"); } } \ No newline at end of file