Skip to content

Commit

Permalink
Merge pull request #995 from HL7/2024-11-gg-custom-resources-2
Browse files Browse the repository at this point in the history
2024 11 gg custom resources 2
  • Loading branch information
grahamegrieve authored Nov 21, 2024
2 parents 2fcdd2e + 57fe2a9 commit a13e6e6
Show file tree
Hide file tree
Showing 12 changed files with 368 additions and 20 deletions.
15 changes: 15 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
* Security: Enforce that XML can't load XML entity declarations
* Loader: Fix missing isModifierReason on modifier extensions
* Version Convertor: fix bug converting NamingSystem.url between versions
* Version Convertor: Fix IG dependsOn.reason conversion
* Validator: fix value set validation on import validation to find external value sets
* Validator: Fix terminology tester for change to language header
* Validator: Adjust wording of R5 slicing check
* Validator: Sort entries in error message about profiles to make the order reproducible
* Validator: support resolving /_history/ URLs in IG publisher
* Renderer: make HTA messages translatable
* Renderer: new release - pubpack
* Renderer: suppress Json resourceType property in some logical models
* Renderer: more support for canonical logical models
* SQL: Fix NPE building package.db
* Go-Publish: Fix bug in publication checker - space in sequence blows logic up
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
import java.util.HashSet;
import java.util.List;

import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideDefinitionResourceComponent;
import org.hl7.fhir.r5.utils.UserDataNames;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.ValidationMessage;

Expand Down Expand Up @@ -370,6 +372,17 @@ public boolean isCustomResource() {
return getElement().getProperty().getStructure().hasUserData(UserDataNames.loader_custom_resource);
}
}

public boolean isCanonical(IWorkerContext context) {
StructureDefinition sd = getElement().getProperty().getStructure();
while (sd != null) {
if ("CanonicalResource".equals(sd.getType())) {
return true;
}
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
}
return false;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,18 @@ public String getSourceFor(String ref) {
public void findConfiguration(FetchedFile f, FetchedResource r) {
if (template != null) {
JsonObject cfg = null;
if (r.isExample()) {
if (r.isCanonical(context)) {
if (r.isExample()) {
cfg = defaultConfig.getJsonObject("example:canonical");
}
if (cfg == null) {
cfg = defaultConfig.getJsonObject(r.fhirType()+":canonical");
}
if (cfg == null) {
cfg = defaultConfig.getJsonObject("Any:canonical");
}
}
if (cfg == null && r.isExample()) {
cfg = defaultConfig.getJsonObject("example");
}
if (cfg == null && r.fhirType().equals("StructureDefinition")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9212,6 +9212,8 @@ private void generateSummaryOutputs(DBBuilder db) throws Exception {
}
if (r.getResource() != null) {
populateResourceEntry(r, item, null);
} else if (r.isCustomResource()) {
populateCustomResourceEntry(r, item, null);
}
JsonArray contained = null;
// contained resources get added twice - once as sub-entries under the resource that contains them, and once as an entry in their own right, for their own rendering
Expand Down Expand Up @@ -9272,7 +9274,233 @@ private void generateSummaryOutputs(DBBuilder db) throws Exception {

json = org.hl7.fhir.utilities.json.parser.JsonParser.compose(data, true);
TextFile.stringToFile(json, Utilities.path(tempDir, "_data", "languages.json"));

}

private void populateCustomResourceEntry(FetchedResource r, JsonObject item, Object object) {
Element e = r.getElement();
// item.add("layout-type", "canonical");
if (e.getChildren("url").size() == 1) {
item.add("url", e.getNamedChildValue("url"));
}
if (e.hasChildren("identifier")) {
List<String> ids = new ArrayList<String>();
for (Element id : e.getChildren("identifier")) {
if (id.hasChild("value")) {
ids.add(dr.displayDataType(ResourceWrapper.forType(cu, id)));
}
}
if (!ids.isEmpty()) {
item.add("identifiers", String.join(", ", ids));
}
}
if (e.getChildren("version").size() == 1) {
item.add("version", e.getNamedChildValue("version"));
}
if (e.getChildren("name").size() == 1) {
item.add("name", e.getNamedChildValue("name"));
}
if (e.getChildren("title").size() == 1) {
item.add("title", e.getNamedChildValue("title"));
// addTranslationsToJson(item, "title", e.getNamedChild("title"), false);
}
if (e.getChildren("experimental").size() == 1) {
item.add("experimental", e.getNamedChildValue("experimental"));
}
if (e.getChildren("date").size() == 1) {
item.add("date", e.getNamedChildValue("date"));
}
if (e.getChildren("description").size() == 1) {
item.add("description", ProfileUtilities.processRelativeUrls(e.getNamedChildValue("description"), "", igpkp.specPath(), context.getResourceNames(), specMaps.get(0).listTargets(), pageTargets(), false));
// addTranslationsToJson(item, "description", e.getNamedChild("description"), false);
}

// if (cr.hasUseContext() && !containedCr) {
// List<String> contexts = new ArrayList<String>();
// for (UsageContext uc : cr.getUseContext()) {
// String label = dr.displayDataType(uc.getCode());
// if (uc.hasValueCodeableConcept()) {
// String value = dr.displayDataType(uc.getValueCodeableConcept());
// if (value!=null) {
// contexts.add(label + ":\u00A0" + value);
// }
// } else if (uc.hasValueQuantity()) {
// String value = dr.displayDataType(uc.getValueQuantity());
// if (value!=null)
// contexts.add(label + ":\u00A0" + value);
// } else if (uc.hasValueRange()) {
// String value = dr.displayDataType(uc.getValueRange());
// if (!value.isEmpty())
// contexts.add(label + ":\u00A0" + value);
//
// } else if (uc.hasValueReference()) {
// String value = null;
// String reference = null;
// if (uc.getValueReference().hasReference()) {
// reference = uc.getValueReference().getReference().contains(":") ? "" : igpkp.getCanonical() + "/";
// reference += uc.getValueReference().getReference();
// }
// if (uc.getValueReference().hasDisplay()) {
// if (reference != null)
// value = "[" + uc.getValueReference().getDisplay() + "](" + reference + ")";
// else
// value = uc.getValueReference().getDisplay();
// } else if (reference!=null)
// value = "[" + uc.getValueReference().getReference() + "](" + reference + ")";
// else if (uc.getValueReference().hasIdentifier()) {
// String idLabel = dr.displayDataType(uc.getValueReference().getIdentifier().getType());
// value = idLabel!=null ? label + ":\u00A0" + uc.getValueReference().getIdentifier().getValue() : uc.getValueReference().getIdentifier().getValue();
// }
// if (value != null)
// contexts.add(value);
// } else if (uc.hasValue()) {
// throw new FHIRException("Unsupported type for UsageContext.value - " + uc.getValue().fhirType());
// }
// }
// if (!contexts.isEmpty())
// item.add("contexts", String.join(", ", contexts));
// }
// if (cr.hasJurisdiction() && !containedCr) {
// File flagDir = new File(tempDir + "/assets/images");
// if (!flagDir.exists())
// flagDir.mkdirs();
// JsonArray jNodes = new JsonArray();
// item.add("jurisdictions", jNodes);
// ValueSet jvs = context.fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/jurisdiction");
// for (CodeableConcept cc : cr.getJurisdiction()) {
// JsonObject jNode = new JsonObject();
// jNodes.add(jNode);
// ValidationResult vr = jvs==null ? null : context.validateCode(new ValidationOptions(FhirPublication.R5, "en-US"), cc, jvs);
// if (vr != null && vr.asCoding()!=null) {
// Coding cd = vr.asCoding();
// jNode.add("code", cd.getCode());
// if (cd.getSystem().equals("http://unstats.un.org/unsd/methods/m49/m49.htm") && cd.getCode().equals("001")) {
// jNode.add("name", "International");
// jNode.add("flag", "001");
// } else if (cd.getSystem().equals("urn:iso:std:iso:3166")) {
// String code = translateCountryCode(cd.getCode()).toLowerCase();
// jNode.add("name", displayForCountryCode(cd.getCode()));
// File flagFile = new File(vsCache + "/" + code + ".svg");
// if (!flagFile.exists() && !ignoreFlags.contains(code)) {
// URL url2 = new URL("https://flagcdn.com/" + shortCountryCode.get(code.toUpperCase()).toLowerCase() + ".svg");
// try {
// InputStream in = url2.openStream();
// Files.copy(in, Paths.get(flagFile.getAbsolutePath()));
// } catch (Exception e2) {
// ignoreFlags.add(code);
// System.out.println("Unable to access " + url2 + " or " + url2+" ("+e2.getMessage()+")");
// }
// }
// if (flagFile.exists()) {
// FileUtils.copyFileToDirectory(flagFile, flagDir);
// jNode.add("flag", code);
// }
// } else if (cd.getSystem().equals("urn:iso:std:iso:3166:-2")) {
// String code = cd.getCode();
// String[] codeParts = cd.getCode().split("-");
// jNode.add("name", displayForStateCode(cd.getCode()) + " (" + displayForCountryCode(codeParts[0]) + ")");
// File flagFile = new File(vsCache + "/" + code + ".svg");
// if (!flagFile.exists()) {
// URL url = new URL("http://flags.ox3.in/svg/" + codeParts[0].toLowerCase() + "/" + codeParts[1].toLowerCase() + ".svg");
// try (InputStream in = url.openStream()) {
// Files.copy(in, Paths.get(flagFile.getAbsolutePath()));
// } catch (Exception e) {
// // If we can't find the file, that's ok.
// }
// }
// if (flagFile.exists()) {
// FileUtils.copyFileToDirectory(flagFile, flagDir);
// jNode.add("flag", code);
// }
// }
// } else {
// jNode.add("name", dr.displayDataType(cc));
// }
// }
// }

if (e.getChildren("purpose").size() == 1) {
item.add("purpose", ProfileUtilities.processRelativeUrls(e.getNamedChildValue("purpose"), "", igpkp.specPath(), context.getResourceNames(), specMaps.get(0).listTargets(), pageTargets(), false));
// addTranslationsToJson(item, "purpose", e.getNamedChild("purpose"), false);
}
if (e.getChildren("status").size() == 1) {
item.add("status", e.getNamedChildValue("status"));
}
if (e.getChildren("copyright").size() == 1) {
item.add("copyright", ProfileUtilities.processRelativeUrls(e.getNamedChildValue("copyright"), "", igpkp.specPath(), context.getResourceNames(), specMaps.get(0).listTargets(), pageTargets(), false));
// addTranslationsToJson(item, "description", e.getNamedChild("description"), false);
}

// if (pcr!=null && pcr.hasExtension(ToolingExtensions.EXT_FMM_LEVEL)) {
// IntegerType fmm = pcr.getExtensionByUrl(ToolingExtensions.EXT_FMM_LEVEL).getValueIntegerType();
// item.add("fmm", fmm.asStringValue());
// if (fmm.hasExtension(ToolingExtensions.EXT_FMM_DERIVED)) {
// String derivedFrom = "FMM derived from: ";
// for (Extension ext: fmm.getExtensionsByUrl(ToolingExtensions.EXT_FMM_DERIVED)) {
// derivedFrom += "\r\n" + ext.getValueCanonicalType().asStringValue();
// }
// item.add("fmmSource", derivedFrom);
// }
// }
// List<String> keywords = new ArrayList<String>();
// if (r.getResource() instanceof StructureDefinition) {
// StructureDefinition sd = (StructureDefinition)r.getResource();
// if (sd.hasKeyword()) {
// for (Coding coding : sd.getKeyword()) {
// String value = dr.displayDataType(coding);
// if (value != null)
// keywords.add(value);
// }
// }
// } else if (r.getResource() instanceof CodeSystem) {
// CodeSystem cs = (CodeSystem)r.getResource();
// for (Extension e : cs.getExtensionsByUrl(ToolingExtensions.EXT_CS_KEYWORD)) {
// keywords.add(e.getValueStringType().asStringValue());
// }
// } else if (r.getResource() instanceof ValueSet) {
// ValueSet vs = (ValueSet)r.getResource();
// for (Extension e : vs.getExtensionsByUrl(ToolingExtensions.EXT_VS_KEYWORD)) {
// keywords.add(e.getValueStringType().asStringValue());
// }
// }
// if (!keywords.isEmpty())
// item.add("keywords", String.join(", ", keywords));
//
//
org.hl7.fhir.igtools.renderers.StatusRenderer.ResourceStatusInformation info = StatusRenderer.analyse(e);
JsonObject jo = new JsonObject();
if (info.getColorClass() != null) {
jo.add("class", info.getColorClass());
}
if (info.getOwner() != null) {
jo.add("owner", info.getOwner());
}
if (info.getOwnerLink() != null) {
jo.add("link", info.getOwnerLink());
}
if (info.getSstatus() != null) {
jo.add("standards-status", info.getSstatus());
} else if (sourceIg.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
jo.add("standards-status","informative");
}
if (info.getSstatusSupport() != null) {
jo.add("standards-status-support", info.getSstatusSupport());
}
if (info.getNormVersion() != null) {
item.add("normativeVersion", info.getNormVersion());
}
if (info.getFmm() != null) {
jo.add("fmm", info.getFmm());
}
if (info.getSstatusSupport() != null) {
jo.add("fmm-support", info.getFmmSupport());
}
if (info.getStatus() != null && !jo.has("status")) {
jo.add("status", info.getStatus());
}
if (!jo.getProperties().isEmpty()) {
item.set("status", jo);
}

}

private void genBasePages() throws IOException, Exception {
Expand Down Expand Up @@ -9556,6 +9784,7 @@ private List<DependencyAnalyser.ArtifactDependency> makeDependencies() {

public void populateResourceEntry(FetchedResource r, JsonObject item, ContainedResourceDetails crd) throws Exception {
if (r.getResource() instanceof CanonicalResource || (crd!= null && crd.getCanonical() != null)) {
// item.add("layout-type", "canonical");
boolean containedCr = crd != null && crd.getCanonical() != null;
CanonicalResource cr = containedCr ? crd.getCanonical() : (CanonicalResource) r.getResource();
CanonicalResource pcr = r.getResource() instanceof CanonicalResource ? (CanonicalResource) r.getResource() : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public ValidationServices(IWorkerContext context, IGKnowledgeProvider ipg, Imple
public Element fetch(IResourceValidator validator, Object appContext, String url) throws FHIRException, IOException {
if (url == null)
return null;
if (url.contains("/_history/")) {
url = url.substring(0, url.indexOf("/_history"));
}
String turl = (!Utilities.isAbsoluteUrl(url)) ? Utilities.pathURL(ipg.getCanonical(), url) : url;
Resource res = context.fetchResource(getResourceType(turl), turl);
if (res != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,12 +328,12 @@ public void saveResource(FetchedFile f, FetchedResource r, byte[] json) {
psql.setInt(3, r.getElement().getProperty().getStructure().hasUserData(UserDataNames.loader_custom_resource) ? 1 : 0);
bindString(psql, 4, r.getId());
bindString(psql, 5, r.getElement().getWebPath());
bindString(psql, 6, r.getElement().getNamedChildValue("url"));
bindString(psql, 7, r.getElement().getNamedChildValue("version"));
bindString(psql, 8, r.getElement().getNamedChildValue("status"));
bindString(psql, 9, r.getElement().getNamedChildValue("date"));
bindString(psql, 10, r.getElement().getChildren("name").size() == 1 && r.getElement().getNamedChild("name").isPrimitive() ? r.getElement().getNamedChildValue("name") : r.getResourceName());
bindString(psql, 11, r.getElement().getNamedChildValue("title"));
bindString(psql, 6, getSingleChildValue(r, "url", null));
bindString(psql, 7, getSingleChildValue(r, "version", null));
bindString(psql, 8, getSingleChildValue(r, "status", null));
bindString(psql, 9, getSingleChildValue(r, "date", null));
bindString(psql, 10, getSingleChildValue(r, "name", r.getResourceName()));
bindString(psql, 11, getSingleChildValue(r, "title", null));
bindString(psql, 12, r.getResourceDescription());
psql.setBytes(13, json);
psql.executeUpdate();
Expand Down Expand Up @@ -380,6 +380,10 @@ public void saveResource(FetchedFile f, FetchedResource r, byte[] json) {
time(start);
}

private String getSingleChildValue(FetchedResource r, String name, String defaultValue) {
return r.getElement().getChildren(name).size() == 1 && r.getElement().getNamedChild(name).isPrimitive() ? r.getElement().getNamedChildValue(name) : defaultValue;
}

public void finishResources() {
long start = System.currentTimeMillis();
if (con == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hl7.fhir.r5.renderers.utils.RenderingContext.RenderingContextLangs;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
Expand Down Expand Up @@ -216,14 +217,12 @@ private boolean isHL7Ig() {

private String getCopyRightStatement(SystemUsage system) throws IOException {
if ("http://snomed.info/sct".equals(system.system)) {
system.desc = "SNOMED Clinical Terms® (SNOMED CT®)";
return "This material contains content that is copyright of SNOMED International. Implementers of these specifications must have the appropriate SNOMED CT Affiliate license - "+
"for more information contact <a href=\"https://www.snomed.org/get-snomed\">https://www.snomed.org/get-snomed</a> or <a href=\"mailto:info@snomed.org\">info@snomed.org</a>.";
system.desc = ctxt.formatMessage(I18nConstants.HTA_SCT_DESC);
return ctxt.formatMessage(I18nConstants.HTA_SCT_MESSAGE);
}
if ("http://loinc.org".equals(system.system)) {
system.desc = "LOINC";
return "This material contains content from <a href=\"http://loinc.org\">LOINC</a>. LOINC is copyright © 1995-2020, Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) "+
"Committee and is available at no cost under the <a href=\"http://loinc.org/license\">license</a>. LOINC® is a registered United States trademark of Regenstrief Institute, Inc.";
system.desc = ctxt.formatMessage(I18nConstants.HTA_LOINC_DESC);
return ctxt.formatMessage(I18nConstants.HTA_LOINC_MESSAGE);
}
if (system.cs != null) {
system.desc = system.cs.present();
Expand Down
Loading

0 comments on commit a13e6e6

Please sign in to comment.