From f3fc19a90620a59f159e991fa91d4d5bef6f56d8 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 3 Jan 2023 14:54:11 +1100 Subject: [PATCH] refactor profile generation (utils -> renderer) + add new views --- .../AdditionalBindingsRenderer.java | 71 ++- .../conformance/profile/ProfileUtilities.java | 175 ++++--- .../java/org/hl7/fhir/r5/model/Coding.java | 1 + .../hl7/fhir/r5/profilemodel/PEBuilder.java | 25 +- .../fhir/r5/profilemodel/PEDefinition.java | 41 +- .../r5/profilemodel/PEDefinitionElement.java | 7 +- .../profilemodel/PEDefinitionExtension.java | 11 +- .../r5/profilemodel/PEDefinitionResource.java | 4 +- .../r5/profilemodel/PEDefinitionSlice.java | 4 +- .../PEDefinitionSubExtension.java | 11 +- .../profilemodel/PEDefinitionTypeSlice.java | 4 +- .../hl7/fhir/r5/renderers/CodeResolver.java | 47 ++ .../hl7/fhir/r5/renderers/DataRenderer.java | 48 +- .../StructureDefinitionRenderer.java | 446 ++++++++++++++++-- .../r5/renderers/utils/RenderingContext.java | 18 + .../terminologies/JurisdictionUtilities.java | 4 + .../fhir/r5/test/utils/TestPackageLoader.java | 2 +- .../r5/test/NarrativeGenerationTests.java | 50 +- .../txCache/org.hl7.fhir.r5/iso3166.cache | 11 + .../xhtml/HierarchicalTableGenerator.java | 9 +- .../hl7/fhir/utilities/xhtml/XhtmlFluent.java | 195 ++++++++ .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 224 ++------- .../fhir/utilities/xhtml/XhtmlNodeList.java | 185 ++++++++ 23 files changed, 1238 insertions(+), 355 deletions(-) create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeResolver.java create mode 100644 org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/iso3166.cache create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java create mode 100644 org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNodeList.java diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java index 9f0da47e9..b303a7f72 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java @@ -4,18 +4,29 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Set; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.conformance.profile.BindingResolution; import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.UsageContext; +import org.hl7.fhir.r5.renderers.CodeResolver; +import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; import org.hl7.fhir.r5.renderers.DataRenderer; import org.hl7.fhir.r5.renderers.IMarkdownProcessor; import org.hl7.fhir.r5.renderers.utils.RenderingContext; +import org.hl7.fhir.r5.utils.PublicationHacker; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; @@ -23,6 +34,7 @@ import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.utilities.xhtml.XhtmlNodeList; public class AdditionalBindingsRenderer { public class AdditionalBindingDetail { @@ -78,14 +90,16 @@ public class AdditionalBindingsRenderer { private String path; private RenderingContext context; private IMarkdownProcessor md; + private CodeResolver cr; - public AdditionalBindingsRenderer(ProfileKnowledgeProvider pkp, String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md) { + public AdditionalBindingsRenderer(ProfileKnowledgeProvider pkp, String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr) { this.pkp = pkp; this.corePath = corePath; this.profile = profile; this.path = path; this.context = context; this.md = md; + this.cr = cr; } public void seeMaxBinding(Extension ext) { @@ -349,5 +363,60 @@ public class AdditionalBindingsRenderer { return !bindings.isEmpty(); } + public void render(XhtmlNodeList children, List list) { + if (list.size() == 1) { + render(children, list.get(0)); + } else { + XhtmlNode ul = children.ul(); + for (ElementDefinitionBindingAdditionalComponent b : list) { + render(ul.li().getChildNodes(), b); + } + } + } + + private void render(XhtmlNodeList children, ElementDefinitionBindingAdditionalComponent b) { + if (b.getValueSet() == null) { + return; // what should happen? + } + BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath); + XhtmlNode a = children.ah(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : null); + if (b.hasDocumentation()) { + a.attribute("title", b.getDocumentation()); + } + a.tx(br.display); + + if (b.hasShortDoco()) { + children.tx(": "); + children.tx(b.getShortDoco()); + } + if (b.getAny() || b.hasUsage()) { + children.tx(" ("); + boolean ffirst = !b.getAny(); + if (b.getAny()) { + children.tx("any repeat"); + } + for (UsageContext uc : b.getUsage()) { + if (ffirst) ffirst = false; else children.tx(","); + if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) { + children.tx(displayForUsage(uc.getCode())); + children.tx("="); + } + CodeResolution ccr = cr.resolveCode(uc.getValueCodeableConcept()); + children.ah(ccr.getLink(), ccr.getHint()).tx(ccr.getDisplay()); + } + children.tx(")"); + } + } + + + private String displayForUsage(Coding c) { + if (c.hasDisplay()) { + return c.getDisplay(); + } + if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) { + return c.getCode(); + } + return c.getCode(); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index 01bb8b89d..a62f0f02e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -1604,7 +1604,7 @@ public class ProfileUtilities extends TranslatingUtilities { * @param element - the Element to update * @return - the updated Element */ - ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { + public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) { if (element != null) { ElementDefinition defn = element; if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#")) @@ -1634,7 +1634,7 @@ public class ProfileUtilities extends TranslatingUtilities { return element; } - private static String processRelativeUrls(String markdown, String webUrl, String basePath, List resourceNames, Set baseFilenames, Set localFilenames, boolean processRelatives) { + public static String processRelativeUrls(String markdown, String webUrl, String basePath, List resourceNames, Set baseFilenames, Set localFilenames, boolean processRelatives) { if (markdown == null) { return ""; } @@ -2437,95 +2437,91 @@ public class ProfileUtilities extends TranslatingUtilities { return false; } -// there was some proposal that this be surfaced in the IG publisher, but it is not currently available -// private void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { -// for (ElementDefinition edb : base.getSnapshot().getElement()) { -// if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { -// ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); -// if (edm == null) { -// ElementDefinition edd = derived.getDifferential().addElement(); -// edd.setPath(edb.getPath()); -// edd.setMax("0"); -// } else if (edb.hasSlicing()) { -// closeChildren(base, edb, derived, edm); -// } -// } -// } -// sortDifferential(base, derived, derived.getName(), new ArrayList(), false); -// } + public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException { + for (ElementDefinition edb : base.getSnapshot().getElement()) { + if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) { + ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement()); + if (edm == null) { + ElementDefinition edd = derived.getDifferential().addElement(); + edd.setPath(edb.getPath()); + edd.setMax("0"); + } else if (edb.hasSlicing()) { + closeChildren(base, edb, derived, edm); + } + } + } + sortDifferential(base, derived, derived.getName(), new ArrayList(), false); + } -// private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { -//// String path = edb.getPath()+"."; -// int baseStart = base.getSnapshot().getElement().indexOf(edb); -// int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); -// int diffStart = derived.getDifferential().getElement().indexOf(edm); -// int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); -// -// for (int cBase = baseStart; cBase < baseEnd; cBase++) { -// ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); -// if (isImmediateChild(edBase, edb)) { -// ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); -// if (edMatch == null) { -// ElementDefinition edd = derived.getDifferential().addElement(); -// edd.setPath(edBase.getPath()); -// edd.setMax("0"); -// } else { -// closeChildren(base, edBase, derived, edMatch); -// } -// } -// } -// } + private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) { +// String path = edb.getPath()+"."; + int baseStart = base.getSnapshot().getElement().indexOf(edb); + int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1); + int diffStart = derived.getDifferential().getElement().indexOf(edm); + int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1); + + for (int cBase = baseStart; cBase < baseEnd; cBase++) { + ElementDefinition edBase = base.getSnapshot().getElement().get(cBase); + if (isImmediateChild(edBase, edb)) { + ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd); + if (edMatch == null) { + ElementDefinition edd = derived.getDifferential().addElement(); + edd.setPath(edBase.getPath()); + edd.setMax("0"); + } else { + closeChildren(base, edBase, derived, edMatch); + } + } + } + } -// -// -// -// private int findEnd(List list, ElementDefinition ed, int cursor) { -// String path = ed.getPath()+"."; -// while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { -// cursor++; -// } -// return cursor; -// } -// -// -// private ElementDefinition getMatchInDerived(ElementDefinition ed, List list) { -// for (ElementDefinition t : list) { -// if (t.getPath().equals(ed.getPath())) { -// return t; -// } -// } -// return null; -// } -// -// private ElementDefinition getMatchInDerived(ElementDefinition ed, List list, int start, int end) { -// for (int i = start; i < end; i++) { -// ElementDefinition t = list.get(i); -// if (t.getPath().equals(ed.getPath())) { -// return t; -// } -// } -// return null; -// } -// -// -// private boolean isImmediateChild(ElementDefinition ed) { -// String p = ed.getPath(); -// if (!p.contains(".")) { -// return false; -// } -// p = p.substring(p.indexOf(".")+1); -// return !p.contains("."); -// } -// -// private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { -// String p = candidate.getPath(); -// if (!p.contains(".")) -// return false; -// if (!p.startsWith(base.getPath()+".")) -// return false; -// p = p.substring(base.getPath().length()+1); -// return !p.contains("."); -// } + private int findEnd(List list, ElementDefinition ed, int cursor) { + String path = ed.getPath()+"."; + while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) { + cursor++; + } + return cursor; + } + + + private ElementDefinition getMatchInDerived(ElementDefinition ed, List list) { + for (ElementDefinition t : list) { + if (t.getPath().equals(ed.getPath())) { + return t; + } + } + return null; + } + + private ElementDefinition getMatchInDerived(ElementDefinition ed, List list, int start, int end) { + for (int i = start; i < end; i++) { + ElementDefinition t = list.get(i); + if (t.getPath().equals(ed.getPath())) { + return t; + } + } + return null; + } + + + private boolean isImmediateChild(ElementDefinition ed) { + String p = ed.getPath(); + if (!p.contains(".")) { + return false; + } + p = p.substring(p.indexOf(".")+1); + return !p.contains("."); + } + + private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) { + String p = candidate.getPath(); + if (!p.contains(".")) + return false; + if (!p.startsWith(base.getPath()+".")) + return false; + p = p.substring(base.getPath().length()+1); + return !p.contains("."); + } // public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set outputTracker, RenderingContext rc) throws IOException, FHIRException { // HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); @@ -3980,4 +3976,5 @@ public class ProfileUtilities extends TranslatingUtilities { return null; } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java index 633b6b99f..faa6ddf8a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Coding.java @@ -613,5 +613,6 @@ public class Coding extends DataType implements IBaseCoding, ICompositeType, ICo } // end addition + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java index 11b814ae8..4370ce845 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java @@ -120,7 +120,7 @@ public class PEBuilder { if (!profile.hasSnapshot()) { throw new DefinitionException("Profile '"+profile.getVersionedUrl()+"' does not have a snapshot"); } - return new PEDefinitionResource(this, profile); + return new PEDefinitionResource(this, profile, profile.getName()); } /** @@ -145,7 +145,7 @@ public class PEBuilder { if (!profile.hasSnapshot()) { throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); } - return new PEDefinitionResource(this, profile); + return new PEDefinitionResource(this, profile, profile.getName()); } /** @@ -170,7 +170,7 @@ public class PEBuilder { if (!profile.hasSnapshot()) { throw new DefinitionException("Profile '"+url+"' does not have a snapshot"); } - return new PEDefinitionResource(this, profile); + return new PEDefinitionResource(this, profile, profile.getName()); } /** @@ -321,7 +321,7 @@ public class PEBuilder { ElementDefinition defn = list.get(i); if (!defn.getMax().equals("0") && (allFixed || include(defn))) { if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) { - PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn); + PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path()); pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension"))); if (cu.isPrimitiveDatatype(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) { pe.setMustHaveValue(definition.getMustHaveValue()); @@ -335,11 +335,11 @@ public class PEBuilder { while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) { StructureDefinition ext = getExtensionDefinition(list.get(i)); if (ext != null) { - res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext)); + res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path())); } else if (isTypeSlicing(defn)) { - res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn)); + res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path())); } else { - res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn)); + res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path())); } i++; } @@ -363,7 +363,6 @@ public class PEBuilder { } } - private boolean passElementPropsCheck(ElementDefinition bdefn) { switch (elementProps) { case EXTENSION: @@ -390,14 +389,14 @@ public class PEBuilder { } } - protected List listSlices(StructureDefinition profileStructure, ElementDefinition definition) { + protected List listSlices(StructureDefinition profileStructure, ElementDefinition definition, PEDefinition parent) { List list = pu.getSliceList(profileStructure, definition); List res = new ArrayList<>(); for (ElementDefinition ed : list) { if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) { - res.add(new PEDefinitionSubExtension(this, profileStructure, ed)); + res.add(new PEDefinitionSubExtension(this, profileStructure, ed, parent.path())); } else { - PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed); + PEDefinitionElement pe = new PEDefinitionElement(this, profileStructure, ed, parent.path()); pe.setRecursing(definition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension"))); res.add(pe); } @@ -562,4 +561,8 @@ public class PEBuilder { public List exec(Resource resource, Base data, String fhirpath) { return fpe.evaluate(this, resource, resource, data, fhirpath); } + + public boolean isResource(String name) { + return cu.isResource(name); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java index 786be6b9b..911c390e4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java @@ -7,14 +7,23 @@ import java.util.Map; import org.apache.xmlbeans.impl.xb.xsdschema.All; import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode; +import org.hl7.fhir.utilities.Utilities; public abstract class PEDefinition { + public enum PEDefinitionElementMode { + Resource, Element, DataType, Extension + } + protected PEBuilder builder; protected String name; + protected String path; protected StructureDefinition profile; protected ElementDefinition definition; protected List types; @@ -40,11 +49,12 @@ public abstract class PEDefinition { //// this.data = data; // } - protected PEDefinition(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition) { + protected PEDefinition(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, String ppath) { this.builder = builder; this.name = name; this.profile = profile; this.definition = definition; + this.path = path == null ? name : ppath+"."+name; } @@ -55,6 +65,13 @@ public abstract class PEDefinition { return name; } + /** + * @return The path of the element or slice in the profile (name.name.name...) + */ + public String path() { + return path; + } + /** * @return The name of the element in the resource (may be different to the slice name) */ @@ -239,6 +256,28 @@ public abstract class PEDefinition { return max() > 1; } + public PEDefinitionElementMode mode() { + if (builder.isResource(definition.getBase().getPath())) { + return PEDefinitionElementMode.Resource; + } + for (TypeRefComponent tr : definition.getType()) { + if ("Extension".equals(tr.getWorkingCode())) { + return PEDefinitionElementMode.Extension; + } + if (!Utilities.existsInList(tr.getWorkingCode(), "Element", "BackboneElement")) { + return PEDefinitionElementMode.DataType; + } + } + return PEDefinitionElementMode.Element; + } + + /** + * @return true if this element is profiled one way or another + */ + public boolean isProfiled() { + return !profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition"); + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java index c1cba339d..c4cb4475b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java @@ -10,8 +10,8 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; public class PEDefinitionElement extends PEDefinition { - public PEDefinitionElement(PEBuilder builder, StructureDefinition profile, ElementDefinition definition) { - super(builder, definition.getName(), profile, definition); + public PEDefinitionElement(PEBuilder builder, StructureDefinition profile, ElementDefinition definition, String ppath) { + super(builder, definition.getName(), profile, definition, ppath); } @Override @@ -38,7 +38,7 @@ public class PEDefinitionElement extends PEDefinition { if (definition.hasSlicing()) { // get all the slices CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or "); - List slices = builder.listSlices(profile, definition); + List slices = builder.listSlices(profile, definition, this); // list all the fhirpaths for (PEDefinition slice : slices) { b.append("("+builder.makeSliceExpression(profile, definition.getSlicing(), slice.definition())+")"); @@ -52,4 +52,5 @@ public class PEDefinitionElement extends PEDefinition { } } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java index d06c80d63..bb66d9d8f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java @@ -6,6 +6,8 @@ import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.r5.model.StructureDefinition; public class PEDefinitionExtension extends PEDefinition { @@ -15,8 +17,8 @@ public class PEDefinitionExtension extends PEDefinition { private ElementDefinition eed; private ElementDefinition ved; - public PEDefinitionExtension(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition, StructureDefinition extension) { - super(builder, name, profile, definition); + public PEDefinitionExtension(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition, StructureDefinition extension, String ppath) { + super(builder, name, profile, definition, ppath); this.sliceDefinition = sliceDefinition; this.extension= extension; eed = extension.getSnapshot().getElementByPath("Extension.extension"); @@ -48,7 +50,7 @@ public class PEDefinitionExtension extends PEDefinition { if (eed.getSlicing().getRules() != SlicingRules.CLOSED) { children.addAll(builder.listChildren(allFixed, this, extension, eed, "http://hl7.org/fhir/StructureDefinition/Extension", "value[x]", "url")); } - children.addAll(builder.listSlices(extension, eed)); + children.addAll(builder.listSlices(extension, eed, this)); } } @@ -61,4 +63,7 @@ public class PEDefinitionExtension extends PEDefinition { } } + public PEDefinitionElementMode mode() { + return PEDefinitionElementMode.Extension; + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java index d550b66d3..b7a6babe2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java @@ -6,8 +6,8 @@ import org.hl7.fhir.r5.model.StructureDefinition; public class PEDefinitionResource extends PEDefinition { - public PEDefinitionResource(PEBuilder builder, StructureDefinition profile) { - super(builder, profile.getName(), profile, profile.getSnapshot().getElementFirstRep()); + public PEDefinitionResource(PEBuilder builder, StructureDefinition profile, String ppath) { + super(builder, profile.getName(), profile, profile.getSnapshot().getElementFirstRep(), ppath); } @Override diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java index 65bcf8934..a5b6003f5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java @@ -11,8 +11,8 @@ public class PEDefinitionSlice extends PEDefinition { protected ElementDefinition sliceDefinition; - public PEDefinitionSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition profileDefinition, ElementDefinition sliceDefinition) { - super(builder, name, profile, profileDefinition); + public PEDefinitionSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition profileDefinition, ElementDefinition sliceDefinition, String ppath) { + super(builder, name, profile, profileDefinition, ppath); this.sliceDefinition = sliceDefinition; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java index c195eccb3..4b8bcb128 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java @@ -6,6 +6,7 @@ import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.profilemodel.PEDefinition.PEDefinitionElementMode; import org.hl7.fhir.r5.model.StructureDefinition; public class PEDefinitionSubExtension extends PEDefinition { @@ -14,8 +15,8 @@ public class PEDefinitionSubExtension extends PEDefinition { private ElementDefinition ved; private ElementDefinition ued; - public PEDefinitionSubExtension(PEBuilder builder, StructureDefinition profile, ElementDefinition definition) { - super(builder, definition.getSliceName(), profile, definition); + public PEDefinitionSubExtension(PEBuilder builder, StructureDefinition profile, ElementDefinition definition, String ppath) { + super(builder, definition.getSliceName(), profile, definition, ppath); List childDefs = builder.getChildren(profile, definition); eed = getElementByName(childDefs, "extension"); ved = getElementByName(childDefs, "value[x]"); @@ -56,7 +57,7 @@ public class PEDefinitionSubExtension extends PEDefinition { if (eed.getSlicing().getRules() != SlicingRules.CLOSED) { children.addAll(builder.listChildren(allFixed, this, profile, eed, "http://hl7.org/fhir/StructureDefinition/Extension", "value[x]", "url")); } - children.addAll(builder.listSlices(profile, eed)); + children.addAll(builder.listSlices(profile, eed, this)); } } @@ -69,4 +70,8 @@ public class PEDefinitionSubExtension extends PEDefinition { } } + public PEDefinitionElementMode mode() { + return PEDefinitionElementMode.Extension; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionTypeSlice.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionTypeSlice.java index 83e5ce3fa..02d44f021 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionTypeSlice.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionTypeSlice.java @@ -12,8 +12,8 @@ public class PEDefinitionTypeSlice extends PEDefinition { protected ElementDefinition sliceDefinition; - public PEDefinitionTypeSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition) { - super(builder, name, profile, definition); + public PEDefinitionTypeSlice(PEBuilder builder, String name, StructureDefinition profile, ElementDefinition definition, ElementDefinition sliceDefinition, String ppath) { + super(builder, name, profile, definition, ppath); this.sliceDefinition = sliceDefinition; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeResolver.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeResolver.java new file mode 100644 index 000000000..0e1390cc3 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeResolver.java @@ -0,0 +1,47 @@ +package org.hl7.fhir.r5.renderers; + +import org.hl7.fhir.r5.model.CodeableConcept; +import org.hl7.fhir.r5.model.Coding; + +public interface CodeResolver { + + public class CodeResolution { + private String systenName; + private String systemLink; + private String link; + private String display; + private String hint; + + + protected CodeResolution(String systenName, String systemLink, String link, String display, String hint) { + super(); + this.systenName = systenName; + this.systemLink = systemLink; + this.link = link; + this.display = display; + this.hint = hint; + } + + public String getSystenName() { + return systenName; + } + public String getSystemLink() { + return systemLink; + } + public String getLink() { + return link; + } + public String getDisplay() { + return display; + } + public String getHint() { + return hint; + } + + + } + + public CodeResolution resolveCode(String system, String code); + public CodeResolution resolveCode(Coding code); + public CodeResolution resolveCode(CodeableConcept code); +} 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 810e5f513..d84f984d5 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 @@ -70,9 +70,11 @@ import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; +import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -84,7 +86,7 @@ import org.hl7.fhir.utilities.xhtml.XhtmlParser; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -public class DataRenderer extends Renderer { +public class DataRenderer extends Renderer implements CodeResolver { // -- 1. context -------------------------------------------------------------- @@ -301,6 +303,9 @@ public class DataRenderer extends Renderer { } private String lookupCode(String system, String version, String code) { + if (JurisdictionUtilities.isJurisdiction(system)) { + return JurisdictionUtilities.displayJurisdiction(system+"#"+code); + } ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().setVersionFlexible(true), system, version, code, null); if (t != null && t.getDisplay() != null) @@ -947,6 +952,12 @@ public class DataRenderer extends Renderer { } else { return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; } + } else if ("urn:iso:std:iso:3166".equals(system)) { + if (!Utilities.noString(code)) { + return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code; + } else { + return "https://en.wikipedia.org/wiki/ISO_3166-2"; + } } else { CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); if (cs != null && cs.hasUserData("path")) { @@ -960,6 +971,41 @@ public class DataRenderer extends Renderer { return null; } + public CodeResolution resolveCode(String system, String code) { + return resolveCode(new Coding().setSystem(system).setCode(code)); + } + + public CodeResolution resolveCode(Coding c) { + String systemName; + String systemLink; + String link; + String display = null; + String hint; + + if (c.hasDisplayElement()) + display = c.getDisplay(); + if (Utilities.noString(display)) + display = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); + if (Utilities.noString(display)) { + display = c.getCode(); + } + + CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); + systemLink = cs != null ? cs.getUserString("path") : null; + systemName = cs != null ? cs.present() : describeSystem(c.getSystem()); + link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()); + + hint = systemName+": "+display+(c.hasVersion() ? " (version = "+c.getVersion()+")" : ""); + return new CodeResolution(systemName, systemLink, link, display, hint); + } + + public CodeResolution resolveCode(CodeableConcept code) { + if (code.hasCoding()) { + return resolveCode(code.getCodingFirstRep()); + } else { + return new CodeResolution(null, null, null, code.getText(), code.getText()); + } + } protected void renderCodingWithDetails(XhtmlNode x, Coding c) { String s = ""; if (c.hasDisplayElement()) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index ecc4e9612..78399e4b8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -13,12 +13,15 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.conformance.AdditionalBindingsRenderer; import org.hl7.fhir.r5.conformance.profile.BindingResolution; +import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementChoiceGroup; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ExtensionContext; +import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.model.ActorDefinition; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeType; @@ -27,12 +30,15 @@ import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.Element; import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.AdditionalBindingPurposeVS; import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode; import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionObligationComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; @@ -51,10 +57,13 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.UsageContext; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; +import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType; +import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.PublicationHacker; import org.hl7.fhir.r5.utils.ToolingExtensions; @@ -69,8 +78,10 @@ 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.HierarchicalTableGenerator.Title; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.utilities.xhtml.XhtmlNodeList; public class StructureDefinitionRenderer extends ResourceRenderer { @@ -122,8 +133,8 @@ public class StructureDefinitionRenderer extends ResourceRenderer { // private static final int AGG_IND = 1; // private static final int AGG_GR = 2; // private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false; - private static final String CONSTRAINT_CHAR = "C"; - private static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;"; + public static final String CONSTRAINT_CHAR = "C"; + public static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;"; private final boolean ADD_REFERENCE_TO_TABLE = true; private boolean useTableForFixedValues = true; @@ -266,13 +277,33 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - + private static class Column { + String id; + String title; + String hint; + private String link; + + protected Column(String id, String title, String hint) { + super(); + this.id = id; + this.title = title; + this.hint = hint; + } + protected Column(String id, String title, String hint, String link) { + super(); + this.id = id; + this.title = title; + this.hint = hint; + this.link = link; + } + + } public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean logicalModel, boolean allInvariants, Set outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix) throws IOException, FHIRException { assert(diff != snapshot);// check it's ok to get rid of one of these HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true); gen.setTranslator(getTranslator()); - TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER); + List list; if (diff) list = supplementMissingDiffElements(profile); @@ -280,11 +311,30 @@ public class StructureDefinitionRenderer extends ResourceRenderer { list = new ArrayList<>(); list.addAll(profile.getSnapshot().getElement()); } + + List columns = new ArrayList<>(); + TableModel model; + switch (context.getStructureMode()) { + case BINDINGS: + scanBindings(columns, list); + model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns); + break; + case OBLIGATIONS: + scanObligations(columns, list); + model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns); + break; + case SUMMARY: + model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER); + break; + default: + throw new Error("Unknown structure mode"); + } + List profiles = new ArrayList(); profiles.add(profile); keyRows.clear(); - genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport, rc, anchorPrefix, profile); + genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport, rc, anchorPrefix, profile, columns); try { return gen.generate(model, imagePath, 0, outputTracker); } catch (org.hl7.fhir.exceptions.FHIRException e) { @@ -292,8 +342,129 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } } + private void scanBindings(List columns, List list) { + Set cols = new HashSet<>(); + scanBindings(cols, list, list.get(0)); + if (cols.contains("required")) { + columns.add(new Column("required", "Required", "Concepts must come from this value set")); + } + if (cols.contains("extensible")) { + columns.add(new Column("extensible", "Extensible", "Concepts must come from this value set if an appropriate concept is in the value set ")); + } + if (cols.contains("maximum")) { + columns.add(new Column("maximum", "Maximum", "A required binding for additional codes, for use when the binding strength is 'extensible' or 'preferred'")); + } + if (cols.contains("minimum")) { + columns.add(new Column("minimum", "Minimum", "The minimum allowable value set - any conformant system SHALL support all these codes")); + } + if (cols.contains("candidate")) { + columns.add(new Column("candidate", "Candidate", "This value set is a candidate to substitute for the overall conformance value set in some situations; usually these are defined in the documentation")); + } + if (cols.contains("current")) { + columns.add(new Column("current", "Current", "New records are required to use this value set, but legacy records may use other codes. The definition of 'new record' is difficult, since systems often create new records based on pre-existing data. Usually 'current' bindings are mandated by an external authority that makes clear rules around this")); + } + if (cols.contains("preferred")) { + columns.add(new Column("preferred", "Preferred", "This is the value set that is preferred in a given context (documentation should explain why)")); + } + if (cols.contains("ui")) { + columns.add(new Column("ui", "UI", "This value set is provided for user look up in a given context. Typically, these valuesets only include a subset of codes relevant for input in a context")); + } + if (cols.contains("starter")) { + columns.add(new Column("starter", "Starter", "This value set is a good set of codes to start with when designing your system")); + } + if (cols.contains("component")) { + columns.add(new Column("component", "Component", "This value set is a component of the base value set. Usually this is called out so that documentation can be written about a portion of the value set")); + } + if (cols.contains("example")) { + columns.add(new Column("example", "Example", "Instances are not expected or even encouraged to draw from the specified value set. The value set merely provides examples of the types of concepts intended to be included.")); + } + } + + public void scanBindings(Set cols, List list, ElementDefinition ed) { + if (ed.hasBinding()) { + if (ed.getBinding().hasValueSet()) { + switch (ed.getBinding().getStrength()) { + case EXAMPLE: + cols.add("example"); + break; + case EXTENSIBLE: + cols.add("extensible"); + break; + case PREFERRED: + cols.add("preferred"); + break; + case REQUIRED: + cols.add("required"); + break; + default: + break; + } + } + for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { + cols.add(ab.getPurpose().toCode()); + } + for (Extension ext : ed.getBinding().getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { + cols.add(ext.getExtensionString("purpose")); + } + } + + List children = getChildren(list, ed); + for (ElementDefinition element : children) { + scanBindings(cols, list, element); + } + } + + private void scanObligations(List columns, List list) { + Set cols = new HashSet<>(); + scanObligations(cols, list, list.get(0)); + + if (cols.contains("$all")) { + columns.add(new Column("$all", "All Actors", "Obligations that apply to all actors")); + } + for (String col : cols) { + if (!"$all".equals(col)) { + ActorDefinition actor = context.getWorker().fetchResource(ActorDefinition.class, col); + if (actor == null) { + columns.add(new Column(col, tail(col), "Obligations that apply to the undefined actor "+col, col)); + } else { + columns.add(new Column(col, actor.getName(), "Obligations that apply to the actor "+actor.present(), actor.getUserString("path"))); + } + } + } + } + + private void scanObligations(Set cols, List list, ElementDefinition ed) { + + for (ElementDefinitionObligationComponent ob : ed.getObligation()) { + if (ob.hasActor()) { + for (CanonicalType a : ob.getActor()) { + cols.add(a.getValue()); + } + } else + cols.add("$all"); + } + + List children = getChildren(list, ed); + for (ElementDefinition element : children) { + scanObligations(cols, list, element); + } + } + + public TableModel initCustomTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, boolean alternating, String id, boolean isActive, List columns) { + TableModel model = gen.new TableModel(id, isActive); + + model.setAlternating(alternating); + model.setDocoImg(Utilities.pathURL(prefix, "help16.png")); + model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views")); + model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0)); + for (Column col : columns) { + model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", col.title), translate("sd.hint", col.hint), null, 0)); + } + return model; + } + private Row genElement(String defPath, HierarchicalTableGenerator gen, List rows, ElementDefinition element, List all, List profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, - boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport, RenderingContext rc, String anchorPrefix, Resource srcSD) throws IOException, FHIRException { + boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport, RenderingContext rc, String anchorPrefix, Resource srcSD, List columns) throws IOException, FHIRException { Row originalRow = slicingRow; StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1); Row typesRow = null; @@ -375,7 +546,18 @@ public class StructureDefinitionRenderer extends ResourceRenderer { if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR)) sName = "@"+sName; Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, all); - genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true, rc); + switch (context.getStructureMode()) { + case BINDINGS: + genElementBindings(gen, element, columns, row, profile, corePath); + break; + case OBLIGATIONS: + genElementObligations(gen, element, columns, row); + break; + case SUMMARY: + genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true, rc); + break; + + } if (element.hasSlicing()) { if (standardExtensionSlicing(element)) { used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile(); @@ -408,10 +590,20 @@ public class StructureDefinitionRenderer extends ResourceRenderer { hrow.setLineColor(1); hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null)); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); + switch (context.getStructureMode()) { + case BINDINGS: + case OBLIGATIONS: + for (Column col : columns) { + hrow.getCells().add(gen.new Cell()); + } + break; + case SUMMARY: + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null)); + break; + } row.getSubRows().add(hrow); row = hrow; } @@ -423,10 +615,19 @@ public class StructureDefinitionRenderer extends ResourceRenderer { hrow.setLineColor(1); hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT); hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null)); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell()); - hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); + switch (context.getStructureMode()) { + case BINDINGS: + case OBLIGATIONS: + for (Column col : columns) { + hrow.getCells().add(gen.new Cell()); + } + break; + case SUMMARY: + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell()); + hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null)); + } row.getSubRows().add(hrow); row = hrow; } @@ -442,7 +643,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode); if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) { - currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport, rc, anchorPrefix, srcSD); + currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport, rc, anchorPrefix, srcSD, columns); } } } @@ -451,13 +652,169 @@ public class StructureDefinitionRenderer extends ResourceRenderer { // if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension")) // genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants); } - if (typesRow != null && !element.prohibited()) { + if (typesRow != null && !element.prohibited() && context.getStructureMode() == StructureDefinitionRendererMode.SUMMARY) { makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport, srcSD); } } return slicingRow; } + private void genElementObligations(HierarchicalTableGenerator gen, ElementDefinition element, List columns, Row row) throws IOException { + for (Column col : columns) { + Cell gc = gen.new Cell(); + row.getCells().add(gc); + List obligations = collectObligations(element, col.id); + if (obligations.size() > 0) { + Piece p = gen.new Piece(null); + gc.addPiece(p); + if (obligations.size() == 1) { + renderObligation(p.getChildren(), obligations.get(0)); + } else { + XhtmlNode ul = p.getChildren().ul(); + for (ElementDefinitionObligationComponent ob : obligations) { + renderObligation(ul.li().getChildNodes(), ob); + } + } + } + + } + } + + private List collectObligations(ElementDefinition element, String id) { + List res = new ArrayList<>(); + for (ElementDefinitionObligationComponent ob : element.getObligation()) { + if (("$all".equals(id) && !ob.hasActor()) || (ob.hasActor(id))) { + res.add(ob); + } + } + return res; + } + + private void renderObligation(XhtmlNodeList children, ElementDefinitionObligationComponent ob) throws IOException { + if ("http://hl7.org/fhir/tools/CodeSystem/obligation".equals(ob.getCode().getSystem())) { + boolean first = true; + String[] codes = ob.getCode().getCode().split("\\+"); + for (String code : codes) { + if (first) first = false; else children.tx(" & "); + int i = code.indexOf(":"); + if (i > -1) { + String c = code.substring(0, i); + code = code.substring(i+1); + children.b().tx(c.toUpperCase()); + children.tx(":"); + } + CodeResolution cr = resolveCode("http://hl7.org/fhir/tools/CodeSystem/obligation", code); + code = code.replace("will-", "").replace("can-", ""); + if (cr.getLink() != null) { + children.ah(cr.getLink(), cr.getHint()).tx(code); + } else { + children.span(null, cr.getHint()).tx(code); + } + } + + } else { + CodeResolution cr = resolveCode(ob.getCode()); + if (cr.getLink() != null) { + children.ah(cr.getLink(), cr.getHint()).tx(cr.getDisplay()); + } else { + children.span(null, cr.getHint()).tx(cr.getDisplay()); + } + } + if (ob.hasFilter() || ob.hasUsage()) { + children.tx(" ("); + boolean ffirst = !ob.hasFilter(); + if (ob.hasFilter()) { + children.span(null, ob.getFilterDocumentation()).code().tx(ob.getFilter()); + } + for (UsageContext uc : ob.getUsage()) { + if (ffirst) ffirst = false; else children.tx(","); + if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) { + children.tx(displayForUsage(uc.getCode())); + children.tx("="); + } + CodeResolution ccr = resolveCode(uc.getValueCodeableConcept()); + children.ah(ccr.getLink(), ccr.getHint()).tx(ccr.getDisplay()); + } + children.tx(")"); + } + // usage + // filter + // process + } + + + private String displayForUsage(Coding c) { + if (c.hasDisplay()) { + return c.getDisplay(); + } + if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) { + return c.getCode(); + } + return c.getCode(); + } + + private void genElementBindings(HierarchicalTableGenerator gen, ElementDefinition element, List columns, Row row, StructureDefinition profile, String corepath) { + for (Column col : columns) { + Cell gc = gen.new Cell(); + row.getCells().add(gc); + List bindings = collectBindings(element, col.id); + if (bindings.size() > 0) { + Piece p = gen.new Piece(null); + gc.addPiece(p); + new AdditionalBindingsRenderer(context.getPkp(), corepath, profile, element.getPath(), context, null, this).render(p.getChildren(), bindings); + } + } + } + + private List collectBindings(ElementDefinition element, String type) { + List res = new ArrayList<>(); + if (element.hasBinding()) { + ElementDefinitionBindingComponent b = element.getBinding(); + if (type.equals(b.getStrength().toCode())) { + ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); + res.add(ab.setAny(false).setDocumentation(b.getDescription()).setValueSet(b.getValueSet())); + } + if ("maximum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { + ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); + res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MAX_VALUESET))); + } + if ("minimum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) { + ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); + res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MIN_VALUESET))); + } + for (ElementDefinitionBindingAdditionalComponent t : b.getAdditional()) { + if (type.equals(t.getPurpose().toCode())) { + res.add(t); + } + } + for (Extension ext : b.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { + if (type.equals(ext.getExtensionString("purpose"))) { + ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent(); + if (ext.hasExtension("any")) { + ab.setAny(ToolingExtensions.readBooleanExtension(ext, "any")); + } + if (ext.hasExtension("purpose")) { + ab.setPurpose(AdditionalBindingPurposeVS.fromCode(ToolingExtensions.readStringExtension(ext, "purpose"))); + } + if (ext.hasExtension("documentation")) { + ab.setDocumentation(ToolingExtensions.readStringExtension(ext, "documentation")); + } + if (ext.hasExtension("shortDoco")) { + ab.setShortDoco(ToolingExtensions.readStringExtension(ext, "shortDoco")); + } + if (ToolingExtensions.hasExtension(ext, "usage")) { + ab.addUsage(ext.getExtensionByUrl("usage").getValueUsageContext()); + } + if (ext.hasExtension("valueSet")) { + ab.setValueSet(ToolingExtensions.readStringExtension(ext, "valueSet")); + } + res.add(ab); + } + } + } + return res; + } + public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef, boolean ext, UnusedTracker used, String ref, String sName, List elements) throws IOException { @@ -592,7 +949,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return cell; } - private List supplementMissingDiffElements(StructureDefinition profile) { + public List supplementMissingDiffElements(StructureDefinition profile) { List list = new ArrayList<>(); list.addAll(profile.getDifferential().getElement()); if (list.isEmpty()) { @@ -694,11 +1051,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer { && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE); } - private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { + public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly, allowSubRows, rc); } - private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { + public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException { Cell c = gen.new Cell(); row.getCells().add(c); @@ -928,7 +1285,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()))); } - AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, profile, definition.getPath(), rc, null); + AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, profile, definition.getPath(), rc, null, this); if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) { abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)); } @@ -1223,7 +1580,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - private static String codeForAggregation(AggregationMode a) { + public String codeForAggregation(AggregationMode a) { switch (a) { case BUNDLED : return "b"; case CONTAINED : return "c"; @@ -1232,7 +1589,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } } - private static String hintForAggregation(AggregationMode a) { + public String hintForAggregation(AggregationMode a) { if (a != null) return a.getDefinition(); else @@ -1833,7 +2190,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return app == null ? src : src + app; } - private boolean hasNonBaseConditions(List conditions) { + public boolean hasNonBaseConditions(List conditions) { for (IdType c : conditions) { if (!isBaseCondition(c)) { return true; @@ -1843,7 +2200,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - private boolean hasNonBaseConstraints(List constraints) { + public boolean hasNonBaseConstraints(List constraints) { for (ElementDefinitionConstraintComponent c : constraints) { if (!isBaseConstraint(c)) { return true; @@ -1852,7 +2209,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return false; } - private String listConstraintsAndConditions(ElementDefinition element) { + public String listConstraintsAndConditions(ElementDefinition element) { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); for (ElementDefinitionConstraintComponent con : element.getConstraint()) { if (!isBaseConstraint(con)) { @@ -2036,7 +2393,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } } - private static boolean allTypesMustSupport(ElementDefinition e) { + private boolean allTypesMustSupport(ElementDefinition e) { boolean all = true; boolean any = false; for (TypeRefComponent tr : e.getType()) { @@ -2046,7 +2403,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return !all && !any; } - private static boolean allProfilesMustSupport(List profiles) { + private boolean allProfilesMustSupport(List profiles) { boolean all = true; boolean any = false; for (CanonicalType u : profiles) { @@ -2055,11 +2412,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } return !all && !any; } - private static boolean isMustSupportDirect(TypeRefComponent tr) { + public boolean isMustSupportDirect(TypeRefComponent tr) { return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))); } - private static boolean isMustSupport(TypeRefComponent tr) { + public boolean isMustSupport(TypeRefComponent tr) { if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) { return true; } @@ -2069,7 +2426,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return isMustSupport(tr.getTargetProfile()); } - private static boolean isMustSupport(List profiles) { + public boolean isMustSupport(List profiles) { for (CanonicalType ct : profiles) { if (isMustSupport(ct)) { return true; @@ -2079,7 +2436,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } - private static boolean isMustSupport(CanonicalType profile) { + public boolean isMustSupport(CanonicalType profile) { return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT)); } @@ -2278,5 +2635,30 @@ public class StructureDefinitionRenderer extends ResourceRenderer { return ed.getPath().substring(ed.getPath().indexOf(".")+1); } + public static String formatTypeSpecifiers(IWorkerContext context, ElementDefinition d) { + StringBuilder b = new StringBuilder(); + boolean first = true; + for (Extension e : d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) { + if (first) first = false; else b.append("
"); + String cond = ToolingExtensions.readStringExtension(e, "condition"); + String type = ToolingExtensions.readStringExtension(e, "type"); + b.append("If "); + b.append(Utilities.escapeXml(cond)); + b.append(" then the type is "); + StructureDefinition sd = context.fetchTypeDefinition(type); + if (sd == null) { + b.append(""); + b.append(Utilities.escapeXml(type)); + b.append(""); + } else { + b.append(""); + b.append(Utilities.escapeXml(sd.getTypeName())); + b.append(""); + } + } + return b.toString(); + } } 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 6fcb3e81f..46804dc84 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 @@ -74,6 +74,12 @@ public class RenderingContext { IG_PUBLISHER } + public enum StructureDefinitionRendererMode { + SUMMARY, // 5 cells: tree/name | flags | cardinality | type | details + BINDINGS, // tree/name + column for each kind of binding found, cells are lists of bindings + OBLIGATIONS, // tree/name + column for each actor that has obligations + } + public enum QuestionnaireRendererMode { /** * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off. @@ -139,6 +145,8 @@ public class RenderingContext { private boolean inlineGraphics; private QuestionnaireRendererMode questionnaireMode = QuestionnaireRendererMode.FORM; + private StructureDefinitionRendererMode structureMode = StructureDefinitionRendererMode.SUMMARY; + private boolean addGeneratedNarrativeHeader = true; private boolean showComments = false; @@ -201,6 +209,7 @@ public class RenderingContext { res.destDir = destDir; res.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader; res.questionnaireMode = questionnaireMode; + res.structureMode = structureMode; res.header = header; res.links.putAll(links); res.inlineGraphics = inlineGraphics; @@ -435,6 +444,15 @@ public class RenderingContext { this.questionnaireMode = questionnaireMode; return this; } + + public StructureDefinitionRendererMode getStructureMode() { + return structureMode; + } + + public RenderingContext setStructureMode(StructureDefinitionRendererMode structureMode) { + this.structureMode = structureMode; + return this; + } public String fixReference(String ref) { if (!Utilities.isAbsoluteUrl(ref)) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java index 862312517..38dd154d3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java @@ -5996,5 +5996,9 @@ public class JurisdictionUtilities { } return "Unknown region code '"+c.getCode()+"'"; } + + public static boolean isJurisdiction(String system) { + return Utilities.existsInList(system, "http://unstats.un.org/unsd/methods/m49/m49.htm", "urn:iso:std:iso:3166", "urn:iso:std:iso:3166:-2"); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java index 448e4bb70..3ee6e683f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java @@ -39,7 +39,7 @@ public class TestPackageLoader implements IContextResourceLoader { @Override public String getResourcePath(Resource resource) { - return null; + return resource.fhirType().toLowerCase()+"-"+resource.getId()+".html"; } @Override 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 a0cf7cd49..4e0594758 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 @@ -23,17 +23,22 @@ import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.utils.ElementWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode; import org.hl7.fhir.r5.test.utils.CompareUtilities; +import org.hl7.fhir.r5.test.utils.TestPackageLoader; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; +import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.utilities.xml.XMLUtil; @@ -87,6 +92,14 @@ public class NarrativeGenerationTests { @Override public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { + ValueSet vs = context.fetchResource(ValueSet.class, url); + if (vs != null) { + if (vs.hasUserData("path")) { + return new BindingResolution(vs.present(), vs.getUserString("path")); + } else { + return new BindingResolution(vs.present(), "valueset-"+vs.getIdBase()+".html"); + } + } throw new NotImplementedException(); } @@ -100,7 +113,7 @@ public class NarrativeGenerationTests { @Override public boolean prependLinks() { - throw new NotImplementedException(); + return false; } @Override @@ -138,13 +151,23 @@ public class NarrativeGenerationTests { public static class TestDetails { private String id; + private String sdmode; private boolean header; private boolean meta; private boolean technical; + private String register; public TestDetails(Element test) { super(); id = test.getAttribute("id"); + sdmode = test.getAttribute("sdmode"); + if ("".equals(sdmode)) { + sdmode = null; + } + register = test.getAttribute("register"); + if ("".equals(register)) { + register = null; + } header = "true".equals(test.getAttribute("header")); meta = "true".equals(test.getAttribute("meta")); technical = "technical".equals(test.getAttribute("mode")); @@ -154,12 +177,20 @@ public class NarrativeGenerationTests { return id; } + public String getSDMode() { + return sdmode; + } + public boolean isHeader() { return header; } public boolean isMeta() { return meta; + } + + public String getRegister() { + return register; } } @@ -177,13 +208,23 @@ public class NarrativeGenerationTests { } @BeforeAll - public static void setUp() { + public static void setUp() throws IOException { context = TestingUtilities.getSharedWorkerContext("5.0.0"); + FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true); + NpmPackage ips = pcm.loadPackage("hl7.fhir.uv.ips#1.1.0"); + context.loadFromPackage(ips, new TestPackageLoader(new String[] { "StructureDefinition", "ValueSet" })); } @ParameterizedTest(name = "{index}: file {0}") @MethodSource("data") public void test(String id, TestDetails test) throws Exception { + if (test.getRegister() != null) { + if (test.getRegister().endsWith(".json")) { + context.cacheResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getRegister()))); + } else { + context.cacheResource(new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getRegister()))); + } + } RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE); rc.setDestDir(Utilities.path("[tmp]", "narrative")); rc.setHeader(test.isHeader()); @@ -199,6 +240,9 @@ public class NarrativeGenerationTests { rc.setMode(test.technical ? ResourceRendererMode.TECHNICAL : ResourceRendererMode.END_USER); rc.setProfileUtilities(new ProfileUtilities(rc.getContext(), null, new TestProfileKnowledgeProvider(rc.getContext()))); + if (test.getSDMode() != null) { + rc.setStructureMode(StructureDefinitionRendererMode.valueOf(test.getSDMode().toUpperCase())); + } Resource source; if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".json")) { @@ -209,7 +253,7 @@ public class NarrativeGenerationTests { XhtmlNode x = RendererFactory.factory(source, rc).build(source); String expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html")); - String actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; + String actual = HEADER+new XhtmlComposer(true, false).compose(x)+FOOTER; String expectedFileName = CompareUtilities.tempFile("narrative", test.getId() + ".expected.html"); String actualFileName = CompareUtilities.tempFile("narrative", test.getId() + ".actual.html"); TextFile.stringToFile(expected, expectedFileName); diff --git a/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/iso3166.cache b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/iso3166.cache new file mode 100644 index 000000000..17ea50a4b --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/txCache/org.hl7.fhir.r5/iso3166.cache @@ -0,0 +1,11 @@ +------------------------------------------------------------------------------------- +{"code" : { + "system" : "urn:iso:std:iso:3166", + "code" : "BE" +}, "valueSet" :null, "lang":"en-US", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"true"}#### +v: { + "severity" : "error", + "error" : "Local Error: Resolved system urn:iso:std:iso:3166 (v1.0.0), but the definition is not complete. Server Error: Attempt to use Terminology server when no Terminology server is available", + "class" : "SERVER_ERROR" +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java index 334464d7f..42a7e44f9 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/HierarchicalTableGenerator.java @@ -134,7 +134,7 @@ public class HierarchicalTableGenerator extends TranslatingUtilities { private String hint; private String style; private Map attributes; - private List children; + private XhtmlNodeList children; public Piece(String tag) { super(); @@ -205,9 +205,9 @@ public class HierarchicalTableGenerator extends TranslatingUtilities { return children != null && !children.isEmpty(); } - public List getChildren() { + public XhtmlNodeList getChildren() { if (children == null) - children = new ArrayList(); + children = new XhtmlNodeList(); return children; } @@ -853,8 +853,9 @@ public class HierarchicalTableGenerator extends TranslatingUtilities { } else if (p.getStyle() != null) { XhtmlNode s = addStyle(tc.addTag("span"), p); s.addText(p.getText()); - } else + } else { tc.addText(p.getText()); + } if (p.hasChildren()) { tc.getChildNodes().addAll(p.getChildren()); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java new file mode 100644 index 000000000..93f1158d0 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java @@ -0,0 +1,195 @@ +package org.hl7.fhir.utilities.xhtml; + +import java.io.IOException; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.utilities.MarkDownProcessor; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; + +public abstract class XhtmlFluent { + + protected abstract XhtmlNode addTag(String string); + protected abstract XhtmlNode addText(String cnt); + protected abstract void addChildren(XhtmlNodeList childNodes); + + public XhtmlNode h1() { + return addTag("h1"); + } + + + public XhtmlNode h2() { + return addTag("h2"); + } + + public XhtmlNode h(int level) { + if (level < 1 || level > 6) { + throw new FHIRException("Illegal Header level "+level); + } + return addTag("h"+Integer.toString(level)); + } + + public XhtmlNode h3() { + return addTag("h3"); + } + + public XhtmlNode h4() { + return addTag("h4"); + } + + public XhtmlNode table(String clss) { + XhtmlNode res = addTag("table"); + if (!Utilities.noString(clss)) + res.setAttribute("class", clss); + return res; + } + + public XhtmlNode tr() { + return addTag("tr"); + } + + public XhtmlNode th() { + return addTag("th"); + } + + public XhtmlNode td() { + return addTag("td"); + } + + public XhtmlNode td(String clss) { + return addTag("td").attribute("class", clss); + } + + public XhtmlNode div() { + return addTag("div"); + } + + public XhtmlNode para() { + return addTag("p"); + } + + public XhtmlNode pre() { + return addTag("pre"); + } + + public XhtmlNode pre(String clss) { + return addTag("pre").setAttribute("class", clss); + } + + public void br() { + addTag("br"); + } + + public void hr() { + addTag("hr"); + } + + public XhtmlNode ul() { + return addTag("ul"); + } + + public XhtmlNode li() { + return addTag("li"); + } + + public XhtmlNode b() { + return addTag("b"); + } + + public XhtmlNode i() { + return addTag("i"); + } + + public XhtmlNode tx(String cnt) { + return addText(cnt); + } + + public XhtmlNode tx(int cnt) { + return addText(Integer.toString(cnt)); + } + + public XhtmlNode ah(String href) { + return addTag("a").attribute("href", href); + } + + public XhtmlNode ah(String href, String title) { + XhtmlNode x = addTag("a").attribute("href", href); + if (title != null) { + x.attribute("title", title); + } + return x; + } + + public XhtmlNode img(String src, String alt) { + return addTag("img").attribute("src", src).attribute("alt", alt); + } + + public XhtmlNode img(String src, String alt, String title) { + return addTag("img").attribute("src", src).attribute("alt", alt).attribute("title", title); + } + + public XhtmlNode an(String href) { + return an(href, " "); + } + + public XhtmlNode an(String href, String tx) { + XhtmlNode a = addTag("a").attribute("name", href); + a.tx(tx); + return a; + } + + public XhtmlNode span(String style, String title) { + XhtmlNode res = addTag("span"); + if (!Utilities.noString(style)) + res.attribute("style", style); + if (!Utilities.noString(title)) + res.attribute("title", title); + return res; + } + + + public XhtmlNode code(String text) { + return addTag("code").tx(text); + } + + public XhtmlNode code() { + return addTag("code"); + } + + + public XhtmlNode blockquote() { + return addTag("blockquote"); + } + + + 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); + } + addChildren(m.getChildNodes()); + } + } + + public void innerHTML(String html) throws IOException { + if (html != null) { + XhtmlParser p = new XhtmlParser(); + XhtmlNode m; + try { + m = p.parse("
"+html+"
", "div"); + } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { + throw new FHIRFormatError(e.getMessage(), e); + } + addChildren(m.getChildNodes()); + } + } + + + +} 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 0869f1fb3..2bccd0e3a 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 @@ -35,7 +35,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.Serializable; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,7 +49,7 @@ import org.hl7.fhir.utilities.Utilities; import ca.uhn.fhir.model.primitive.XhtmlDt; @ca.uhn.fhir.model.api.annotation.DatatypeDef(name="xhtml") -public class XhtmlNode implements IBaseXhtml { +public class XhtmlNode extends XhtmlFluent implements IBaseXhtml { private static final long serialVersionUID = -4362547161441436492L; @@ -83,11 +82,9 @@ public class XhtmlNode implements IBaseXhtml { private NodeType nodeType; private String name; private Map attributes = new HashMap(); - private List childNodes = new ArrayList(); + private XhtmlNodeList childNodes = new XhtmlNodeList(); private String content; private boolean notPretty; - private boolean inPara; - private boolean inLink; private boolean seperated; private Boolean emptyExpanded; @@ -129,7 +126,7 @@ public class XhtmlNode implements IBaseXhtml { return attributes; } - public List getChildNodes() { + public XhtmlNodeList getChildNodes() { return childNodes; } @@ -205,11 +202,11 @@ public class XhtmlNode implements IBaseXhtml { // } XhtmlNode node = new XhtmlNode(NodeType.Element); node.setName(name); - if (inPara || name.equals("p")) { - node.inPara = true; + if (childNodes.isInPara() || name.equals("p")) { + node.getChildNodes().setInPara(true); } - if (inLink || name.equals("a")) { - node.inLink = true; + if (childNodes.isInLink() || name.equals("a")) { + node.getChildNodes().setInLink(true); } childNodes.add(node); return node; @@ -221,11 +218,11 @@ 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 (childNodes.isInPara() || name.equals("p")) { + node.getChildNodes().setInPara(true); } - if (inLink || name.equals("a")) { - node.inLink = true; + if (childNodes.isInLink() || name.equals("a")) { + node.getChildNodes().setInLink(true); } node.setName(name); childNodes.add(index, node); @@ -554,159 +551,7 @@ public class XhtmlNode implements IBaseXhtml { } // xhtml easy adders ----------------------------------------------- - public XhtmlNode h1() { - return addTag("h1"); - } - public XhtmlNode h2() { - return addTag("h2"); - } - - public XhtmlNode h(int level) { - if (level < 1 || level > 6) { - throw new FHIRException("Illegal Header level "+level); - } - return addTag("h"+Integer.toString(level)); - } - - public XhtmlNode h3() { - return addTag("h3"); - } - - public XhtmlNode h4() { - return addTag("h4"); - } - - public XhtmlNode table(String clss) { - XhtmlNode res = addTag("table"); - if (!Utilities.noString(clss)) - res.setAttribute("class", clss); - return res; - } - - public XhtmlNode tr() { - return addTag("tr"); - } - - public XhtmlNode th() { - return addTag("th"); - } - - public XhtmlNode td() { - return addTag("td"); - } - - public XhtmlNode td(String clss) { - return addTag("td").attribute("class", clss); - } - - public XhtmlNode colspan(String n) { - return setAttribute("colspan", n); - } - - public XhtmlNode div() { - return addTag("div"); - } - - public XhtmlNode para() { - return addTag("p"); - } - - public XhtmlNode pre() { - return addTag("pre"); - } - - public XhtmlNode pre(String clss) { - return addTag("pre").setAttribute("class", clss); - } - - public void br() { - addTag("br"); - } - - public void hr() { - addTag("hr"); - } - - public XhtmlNode ul() { - return addTag("ul"); - } - - public XhtmlNode li() { - return addTag("li"); - } - - public XhtmlNode b() { - return addTag("b"); - } - - public XhtmlNode i() { - return addTag("i"); - } - - public XhtmlNode tx(String cnt) { - return addText(cnt); - } - - // differs from tx because it returns the owner node, not the created text - public XhtmlNode txN(String cnt) { - addText(cnt); - return this; - } - - public XhtmlNode tx(int cnt) { - return addText(Integer.toString(cnt)); - } - - public XhtmlNode ah(String href) { - return addTag("a").attribute("href", href); - } - - public XhtmlNode ah(String href, String title) { - return addTag("a").attribute("href", href).attribute("title", title); - } - - public XhtmlNode img(String src, String alt) { - return addTag("img").attribute("src", src).attribute("alt", alt); - } - - public XhtmlNode img(String src, String alt, String title) { - return addTag("img").attribute("src", src).attribute("alt", alt).attribute("title", title); - } - - public XhtmlNode an(String href) { - return an(href, " "); - } - - public XhtmlNode an(String href, String tx) { - XhtmlNode a = addTag("a").attribute("name", href); - a.tx(tx); - return a; - } - - public XhtmlNode span(String style, String title) { - XhtmlNode res = addTag("span"); - if (!Utilities.noString(style)) - res.attribute("style", style); - if (!Utilities.noString(title)) - res.attribute("title", title); - return res; - } - - - public XhtmlNode code(String text) { - return addTag("code").tx(text); - } - - public XhtmlNode code() { - return addTag("code"); - } - - - public XhtmlNode blockquote() { - return addTag("blockquote"); - } - @Override public String toString() { @@ -847,36 +692,6 @@ public class XhtmlNode implements IBaseXhtml { return "p".equals(name); } - - 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()); - } - } - - public void innerHTML(String html) throws IOException { - if (html != null) { - XhtmlParser p = new XhtmlParser(); - XhtmlNode m; - try { - m = p.parse("
"+html+"
", "div"); - } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { - throw new FHIRFormatError(e.getMessage(), e); - } - getChildNodes().addAll(m.getChildNodes()); - } - } - - - public XhtmlNode sep(String separator) { // if there's already text, add the separator. otherwise, we'll add it next time if (!seperated) { @@ -887,8 +702,23 @@ public class XhtmlNode implements IBaseXhtml { } - + // more fluent + public XhtmlNode colspan(String n) { + return setAttribute("colspan", n); + } + + // differs from tx because it returns the owner node, not the created text + public XhtmlNode txN(String cnt) { + addText(cnt); + return this; + } + + + @Override + protected void addChildren(XhtmlNodeList childNodes) { + this.childNodes.addAll(childNodes); + } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNodeList.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNodeList.java new file mode 100644 index 000000000..4cc1d8736 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNodeList.java @@ -0,0 +1,185 @@ +package org.hl7.fhir.utilities.xhtml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public class XhtmlNodeList extends XhtmlFluent implements List { + + private List list = new ArrayList<>(); + private boolean inPara; + private boolean inLink; + + + public boolean isInPara() { + return inPara; + } + + public void setInPara(boolean inPara) { + this.inPara = inPara; + } + + public boolean isInLink() { + return inLink; + } + + public void setInLink(boolean inLink) { + this.inLink = inLink; + } + + + public XhtmlNode addTag(String name) + { + +// 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 (isInPara() || name.equals("p")) { + node.getChildNodes().setInPara(true); + } + if (isInLink() || name.equals("a")) { + node.getChildNodes().setInLink(true); + } + add(node); + return node; + } + + public XhtmlNode addText(String content) { + if (content != null) { + XhtmlNode node = new XhtmlNode(NodeType.Text); + node.setContent(content); + add(node); + return node; + } else { + return null; + } + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return list.toArray(a); + } + + @Override + public boolean add(XhtmlNode e) { + return list.add(e); + } + + @Override + public boolean remove(Object o) { + return list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public XhtmlNode get(int index) { + return list.get(index); + } + + @Override + public XhtmlNode set(int index, XhtmlNode element) { + return list.set(index, element); + } + + @Override + public void add(int index, XhtmlNode element) { + list.add(index, element); + } + + @Override + public XhtmlNode remove(int index) { + return list.remove(index); + } + + @Override + public int indexOf(Object o) { + return list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return list.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + + @Override + protected void addChildren(XhtmlNodeList childNodes) { + this.addAll(childNodes); + } +} \ No newline at end of file