reorganize generation code for terminology resources
This commit is contained in:
parent
8d3a0cf18e
commit
4316c81f7e
|
@ -87,6 +87,7 @@ import org.hl7.fhir.r5.model.UriType;
|
|||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyRenderer;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.utils.NarrativeGenerator;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
|
@ -5467,7 +5468,7 @@ public class ProfileUtilities extends TranslatingUtilities {
|
|||
|
||||
private String summarizeCoding(Coding value) {
|
||||
String uri = value.getSystem();
|
||||
String system = NarrativeGenerator.describeSystem(uri);
|
||||
String system = TerminologyRenderer.describeSystem(uri);
|
||||
if (Utilities.isURL(system)) {
|
||||
if (system.equals("http://cap.org/protocols"))
|
||||
system = "CAP Code";
|
||||
|
|
|
@ -0,0 +1,489 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.Enumeration;
|
||||
import org.hl7.fhir.r5.model.Extension;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.CodeSystemNavigator;
|
||||
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.XhtmlNode;
|
||||
|
||||
public class CodeSystemRenderer extends TerminologyRenderer {
|
||||
|
||||
|
||||
private List<String> codeSystemPropList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode - whether we are rendering for a resource directly, or for an IG
|
||||
* @param context - common services (terminology server, code system cache etc)
|
||||
* @param markdown - markdown processing engine of the correct sort for the version applicable
|
||||
* @param prefix - the path to the base FHIR specification
|
||||
* @param lang - the language to use (null for the default)
|
||||
*/
|
||||
public CodeSystemRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
|
||||
super(mode, context, markdown, prefix, lang);
|
||||
}
|
||||
|
||||
public List<String> getCodeSystemPropList() {
|
||||
return codeSystemPropList;
|
||||
}
|
||||
|
||||
public boolean generate(XhtmlNode x, CodeSystem cs, boolean header) throws FHIRFormatError, DefinitionException, IOException {
|
||||
boolean hasExtensions = false;
|
||||
|
||||
if (header) {
|
||||
XhtmlNode h = x.h2();
|
||||
h.addText(cs.hasTitle() ? cs.getTitle() : cs.getName());
|
||||
addMarkdown(x, cs.getDescription());
|
||||
if (cs.hasCopyright())
|
||||
generateCopyright(x, cs);
|
||||
}
|
||||
|
||||
generateProperties(x, cs);
|
||||
generateFilters(x, cs);
|
||||
List<UsedConceptMap> maps = new ArrayList<UsedConceptMap>();
|
||||
hasExtensions = generateCodeSystemContent(x, cs, hasExtensions, maps);
|
||||
|
||||
return hasExtensions;
|
||||
}
|
||||
|
||||
private void generateFilters(XhtmlNode x, CodeSystem cs) {
|
||||
if (cs.hasFilter()) {
|
||||
x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Filters", lang));
|
||||
XhtmlNode tbl = x.table("grid");
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "operator", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Value", lang));
|
||||
for (CodeSystemFilterComponent f : cs.getFilter()) {
|
||||
tr = tbl.tr();
|
||||
tr.td().tx(f.getCode());
|
||||
tr.td().tx(f.getDescription());
|
||||
XhtmlNode td = tr.td();
|
||||
for (Enumeration<org.hl7.fhir.r5.model.Enumerations.FilterOperator> t : f.getOperator())
|
||||
td.tx(t.asStringValue()+" ");
|
||||
tr.td().tx(f.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateProperties(XhtmlNode x, CodeSystem cs) {
|
||||
if (cs.hasProperty()) {
|
||||
boolean hasRendered = false;
|
||||
for (PropertyComponent p : cs.getProperty()) {
|
||||
hasRendered = hasRendered || !p.getCode().equals(ToolingExtensions.getPresentation(p, p.getCodeElement()));
|
||||
}
|
||||
|
||||
x.para().b().tx(context.translator().translate("xhtml-gen-cs", "Properties", lang));
|
||||
XhtmlNode tbl = x.table("grid");
|
||||
XhtmlNode tr = tbl.tr();
|
||||
if (hasRendered) {
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Name", lang));
|
||||
}
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "URL", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Description", lang));
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Type", lang));
|
||||
for (PropertyComponent p : cs.getProperty()) {
|
||||
tr = tbl.tr();
|
||||
if (hasRendered) {
|
||||
tr.td().tx(ToolingExtensions.getPresentation(p, p.getCodeElement()));
|
||||
}
|
||||
tr.td().tx(p.getCode());
|
||||
tr.td().tx(p.getUri());
|
||||
tr.td().tx(p.getDescription());
|
||||
tr.td().tx(p.hasType() ? p.getType().toCode() : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean generateCodeSystemContent(XhtmlNode x, CodeSystem cs, boolean hasExtensions, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
|
||||
XhtmlNode p = x.para();
|
||||
if (cs.getContent() == CodeSystemContentMode.COMPLETE)
|
||||
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines the following codes", cs.getUrl())+":");
|
||||
else if (cs.getContent() == CodeSystemContentMode.EXAMPLE)
|
||||
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are some examples", cs.getUrl())+":");
|
||||
else if (cs.getContent() == CodeSystemContentMode.FRAGMENT )
|
||||
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, of which the following are a subset", cs.getUrl())+":");
|
||||
else if (cs.getContent() == CodeSystemContentMode.NOTPRESENT ) {
|
||||
p.tx(context.translator().translateAndFormat("xhtml-gen-cs", lang, "This code system %s defines many codes, but they are not represented here", cs.getUrl()));
|
||||
return false;
|
||||
}
|
||||
XhtmlNode t = x.table( "codes");
|
||||
boolean commentS = false;
|
||||
boolean deprecated = false;
|
||||
boolean display = false;
|
||||
boolean hierarchy = false;
|
||||
boolean version = false;
|
||||
List<PropertyComponent> properties = new ArrayList<>();
|
||||
for (PropertyComponent cp : cs.getProperty()) {
|
||||
if (showPropertyInTable(cp)) {
|
||||
boolean exists = false;
|
||||
for (ConceptDefinitionComponent c : cs.getConcept()) {
|
||||
exists = exists || conceptsHaveProperty(c, cp);
|
||||
}
|
||||
if (exists) {
|
||||
properties.add(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionComponent c : cs.getConcept()) {
|
||||
commentS = commentS || conceptsHaveComments(c);
|
||||
deprecated = deprecated || conceptsHaveDeprecated(cs, c);
|
||||
display = display || conceptsHaveDisplay(c);
|
||||
version = version || conceptsHaveVersion(c);
|
||||
hierarchy = hierarchy || c.hasConcept();
|
||||
}
|
||||
CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
|
||||
hierarchy = hierarchy || csNav.isRestructure();
|
||||
|
||||
Set<String> langs = new HashSet<>();
|
||||
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, version, deprecated, properties), maps);
|
||||
for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
|
||||
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, version, deprecated, maps, cs.getUrl(), cs, properties, csNav, langs) || hasExtensions;
|
||||
}
|
||||
// if (langs.size() > 0) {
|
||||
// Collections.sort(langs);
|
||||
// x.para().b().tx("Additional Language Displays");
|
||||
// t = x.table( "codes");
|
||||
// XhtmlNode tr = t.tr();
|
||||
// tr.td().b().tx("Code");
|
||||
// for (String lang : langs)
|
||||
// tr.td().b().addText(describeLang(lang));
|
||||
// for (ConceptDefinitionComponent c : cs.getConcept()) {
|
||||
// addLanguageRow(c, t, langs);
|
||||
// }
|
||||
// }
|
||||
return hasExtensions;
|
||||
}
|
||||
|
||||
private boolean conceptsHaveProperty(ConceptDefinitionComponent c, PropertyComponent cp) {
|
||||
if (CodeSystemUtilities.hasProperty(c, cp.getCode()))
|
||||
return true;
|
||||
for (ConceptDefinitionComponent g : c.getConcept())
|
||||
if (conceptsHaveProperty(g, cp))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private boolean showPropertyInTable(PropertyComponent cp) {
|
||||
if (cp.hasCode()) {
|
||||
if (cp.hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
|
||||
return true;
|
||||
}
|
||||
if (cp.getCodeElement().hasExtension(ToolingExtensions.EXT_RENDERED_VALUE)) {
|
||||
return true;
|
||||
}
|
||||
String uri = cp.getUri();
|
||||
String code = null;
|
||||
if (Utilities.noString(uri)){
|
||||
return false;
|
||||
}
|
||||
if (uri.contains("#")) {
|
||||
code = uri.substring(uri.indexOf("#")+1);
|
||||
uri = uri.substring(0, uri.indexOf("#"));
|
||||
}
|
||||
if (Utilities.existsInList(uri, "http://hl7.org/fhir/concept-properties") || codeSystemPropList.contains(uri)) {
|
||||
return true;
|
||||
};
|
||||
CodeSystem cs = context.fetchCodeSystem(uri);
|
||||
if (cs == null) {
|
||||
return false;
|
||||
}
|
||||
return CodeSystemUtilities.hasCode(cs, code);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private int countConcepts(List<ConceptDefinitionComponent> list) {
|
||||
int count = list.size();
|
||||
for (ConceptDefinitionComponent c : list)
|
||||
if (c.hasConcept())
|
||||
count = count + countConcepts(c.getConcept());
|
||||
return count;
|
||||
}
|
||||
|
||||
private boolean conceptsHaveComments(ConceptDefinitionComponent c) {
|
||||
if (ToolingExtensions.hasCSComment(c))
|
||||
return true;
|
||||
for (ConceptDefinitionComponent g : c.getConcept())
|
||||
if (conceptsHaveComments(g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean conceptsHaveDisplay(ConceptDefinitionComponent c) {
|
||||
if (c.hasDisplay())
|
||||
return true;
|
||||
for (ConceptDefinitionComponent g : c.getConcept())
|
||||
if (conceptsHaveDisplay(g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean conceptsHaveVersion(ConceptDefinitionComponent c) {
|
||||
if (c.hasUserData("cs.version.notes"))
|
||||
return true;
|
||||
for (ConceptDefinitionComponent g : c.getConcept())
|
||||
if (conceptsHaveVersion(g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean conceptsHaveDeprecated(CodeSystem cs, ConceptDefinitionComponent c) {
|
||||
if (CodeSystemUtilities.isDeprecated(cs, c))
|
||||
return true;
|
||||
for (ConceptDefinitionComponent g : c.getConcept())
|
||||
if (conceptsHaveDeprecated(cs, g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, List<PropertyComponent> properties, CodeSystemNavigator csNav, Set<String> langs) throws FHIRFormatError, DefinitionException, IOException {
|
||||
boolean hasExtensions = false;
|
||||
XhtmlNode tr = t.tr();
|
||||
XhtmlNode td = tr.td();
|
||||
if (hasHierarchy) {
|
||||
td.addText(Integer.toString(level+1));
|
||||
td = tr.td();
|
||||
String s = Utilities.padLeft("", '\u00A0', level*2);
|
||||
td.addText(s);
|
||||
}
|
||||
td.attribute("style", "white-space:nowrap").addText(c.getCode());
|
||||
XhtmlNode a;
|
||||
if (c.hasCodeElement()) {
|
||||
td.an(cs.getId()+"-" + Utilities.nmtokenize(c.getCode()));
|
||||
}
|
||||
|
||||
if (hasDisplay) {
|
||||
td = tr.td();
|
||||
renderDisplayName(c, cs, td);
|
||||
}
|
||||
td = tr.td();
|
||||
if (c != null &&
|
||||
c.hasDefinitionElement()) {
|
||||
if (lang == null) {
|
||||
if (hasMarkdownInDefinitions(cs))
|
||||
addMarkdown(td, c.getDefinition());
|
||||
else
|
||||
td.addText(c.getDefinition());
|
||||
} else if (lang.equals("*")) {
|
||||
boolean sl = false;
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue()))
|
||||
sl = true;
|
||||
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDefinition());
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && !c.getDefinition().equalsIgnoreCase(cd.getValue())) {
|
||||
td.br();
|
||||
td.addText(cd.getLanguage()+": "+cd.getValue());
|
||||
}
|
||||
}
|
||||
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
|
||||
td.addText(c.getDefinition());
|
||||
} else {
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "definition") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
|
||||
td.addText(cd.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deprecated) {
|
||||
td = tr.td();
|
||||
Boolean b = CodeSystemUtilities.isDeprecated(cs, c);
|
||||
if (b != null && b) {
|
||||
smartAddText(td, context.translator().translate("xhtml-gen-cs", "Deprecated", lang));
|
||||
hasExtensions = true;
|
||||
if (ToolingExtensions.hasExtension(c, ToolingExtensions.EXT_REPLACED_BY)) {
|
||||
Coding cc = (Coding) ToolingExtensions.getExtension(c, ToolingExtensions.EXT_REPLACED_BY).getValue();
|
||||
td.tx(" (replaced by ");
|
||||
String url = getCodingReference(cc, system);
|
||||
if (url != null) {
|
||||
td.ah(url).addText(cc.getCode());
|
||||
td.tx(": "+cc.getDisplay()+")");
|
||||
} else
|
||||
td.addText(cc.getCode()+" '"+cc.getDisplay()+"' in "+cc.getSystem()+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (comment) {
|
||||
td = tr.td();
|
||||
Extension ext = c.getExtensionByUrl(ToolingExtensions.EXT_CS_COMMENT);
|
||||
if (ext != null) {
|
||||
hasExtensions = true;
|
||||
String bc = ext.hasValue() ? ext.getValue().primitiveValue() : null;
|
||||
Map<String, String> translations = ToolingExtensions.getLanguageTranslations(ext.getValue());
|
||||
|
||||
if (lang == null) {
|
||||
if (bc != null)
|
||||
td.addText(bc);
|
||||
} else if (lang.equals("*")) {
|
||||
boolean sl = false;
|
||||
for (String l : translations.keySet())
|
||||
if (bc == null || !bc.equalsIgnoreCase(translations.get(l)))
|
||||
sl = true;
|
||||
if (bc != null) {
|
||||
td.addText((sl ? cs.getLanguage("en")+": " : "")+bc);
|
||||
}
|
||||
for (String l : translations.keySet()) {
|
||||
if (bc == null || !bc.equalsIgnoreCase(translations.get(l))) {
|
||||
if (!td.getChildNodes().isEmpty())
|
||||
td.br();
|
||||
td.addText(l+": "+translations.get(l));
|
||||
}
|
||||
}
|
||||
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
|
||||
if (bc != null)
|
||||
td.addText(bc);
|
||||
} else {
|
||||
if (bc != null)
|
||||
translations.put(cs.getLanguage("en"), bc);
|
||||
for (String l : translations.keySet()) {
|
||||
if (l.equals(lang)) {
|
||||
td.addText(translations.get(l));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (version) {
|
||||
td = tr.td();
|
||||
if (c.hasUserData("cs.version.notes"))
|
||||
td.addText(c.getUserString("cs.version.notes"));
|
||||
}
|
||||
if (properties != null) {
|
||||
for (PropertyComponent pc : properties) {
|
||||
td = tr.td();
|
||||
boolean first = true;
|
||||
ConceptPropertyComponent pcv = CodeSystemUtilities.getProperty(c, pc.getCode());
|
||||
if (pcv != null && pcv.hasValue()) {
|
||||
if (first) first = false; else td.addText(", ");
|
||||
if (pcv.hasValueCoding()) {
|
||||
td.addText(pcv.getValueCoding().getCode());
|
||||
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) {
|
||||
td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
|
||||
} else {
|
||||
td.addText(pcv.getValue().primitiveValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (UsedConceptMap m : maps) {
|
||||
td = tr.td();
|
||||
List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
|
||||
boolean first = true;
|
||||
for (TargetElementComponentWrapper mapping : mappings) {
|
||||
if (!first)
|
||||
td.br();
|
||||
first = false;
|
||||
XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ? mapping.comp.getRelationship().toCode() : "");
|
||||
span.addText(getCharForRelationship(mapping.comp));
|
||||
a = td.ah(prefix+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode()));
|
||||
a.addText(mapping.comp.getCode());
|
||||
if (!Utilities.noString(mapping.comp.getComment()))
|
||||
td.i().tx("("+mapping.comp.getComment()+")");
|
||||
}
|
||||
}
|
||||
List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
|
||||
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
|
||||
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, properties, csNav, langs) || hasExtensions;
|
||||
}
|
||||
for (ConceptDefinitionComponent cc : ocl) {
|
||||
tr = t.tr();
|
||||
td = tr.td();
|
||||
td.addText(Integer.toString(level+2));
|
||||
td = tr.td();
|
||||
String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
|
||||
td.addText(s);
|
||||
td.attribute("style", "white-space:nowrap");
|
||||
a = td.ah("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()));
|
||||
a.addText(cc.getCode());
|
||||
if (hasDisplay) {
|
||||
td = tr.td();
|
||||
renderDisplayName(cc, cs, td);
|
||||
}
|
||||
int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size();
|
||||
if (properties != null) {
|
||||
w = w + properties.size();
|
||||
}
|
||||
td = tr.td().colspan(Integer.toString(w));
|
||||
}
|
||||
return hasExtensions;
|
||||
}
|
||||
|
||||
private boolean hasMarkdownInDefinitions(CodeSystem cs) {
|
||||
return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
|
||||
}
|
||||
|
||||
|
||||
public void renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, XhtmlNode td) {
|
||||
if (c.hasDisplayElement()) {
|
||||
if (lang == null) {
|
||||
td.addText(c.getDisplay());
|
||||
} else if (lang.equals("*")) {
|
||||
boolean sl = false;
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue()))
|
||||
sl = true;
|
||||
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDisplay());
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) {
|
||||
td.br();
|
||||
td.addText(cd.getLanguage()+": "+cd.getValue());
|
||||
}
|
||||
}
|
||||
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
|
||||
td.addText(c.getDisplay());
|
||||
} else {
|
||||
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
|
||||
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
|
||||
td.addText(cd.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getCodingReference(Coding cc, String system) {
|
||||
if (cc.getSystem().equals(system))
|
||||
return "#"+cc.getCode();
|
||||
if (cc.getSystem().equals("http://snomed.info/sct"))
|
||||
return "http://snomed.info/sct/"+cc.getCode();
|
||||
if (cc.getSystem().equals("http://loinc.org"))
|
||||
return "http://s.details.loinc.org/LOINC/"+cc.getCode()+".html";
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.ContactDetail;
|
||||
import org.hl7.fhir.r5.model.ContactPoint;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
||||
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.r5.utils.NarrativeGenerator.ResourceContext;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
|
||||
public class ConceptMapRenderer extends TerminologyRenderer {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode - whether we are rendering for a resource directly, or for an IG
|
||||
* @param context - common services (terminology server, code system cache etc)
|
||||
* @param markdown - markdown processing engine of the correct sort for the version applicable
|
||||
* @param prefix - the path to the base FHIR specification
|
||||
* @param lang - the language to use (null for the default)
|
||||
*/
|
||||
public ConceptMapRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
|
||||
super(mode, context, markdown, prefix, lang);
|
||||
}
|
||||
|
||||
public boolean generate(ResourceContext rcontext, ConceptMap cm, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException {
|
||||
x.h2().addText(cm.getName()+" ("+cm.getUrl()+")");
|
||||
|
||||
XhtmlNode p = x.para();
|
||||
p.tx("Mapping from ");
|
||||
if (cm.hasSource())
|
||||
AddVsRef(rcontext, cm.getSource().primitiveValue(), p);
|
||||
else
|
||||
p.tx("(not specified)");
|
||||
p.tx(" to ");
|
||||
if (cm.hasTarget())
|
||||
AddVsRef(rcontext, cm.getTarget().primitiveValue(), p);
|
||||
else
|
||||
p.tx("(not specified)");
|
||||
|
||||
p = x.para();
|
||||
if (cm.getExperimental())
|
||||
p.addText(Utilities.capitalize(cm.getStatus().toString())+" (not intended for production usage). ");
|
||||
else
|
||||
p.addText(Utilities.capitalize(cm.getStatus().toString())+". ");
|
||||
p.tx("Published on "+(cm.hasDate() ? cm.getDateElement().toHumanDisplay() : "?ngen-10?")+" by "+cm.getPublisher());
|
||||
if (!cm.getContact().isEmpty()) {
|
||||
p.tx(" (");
|
||||
boolean firsti = true;
|
||||
for (ContactDetail ci : cm.getContact()) {
|
||||
if (firsti)
|
||||
firsti = false;
|
||||
else
|
||||
p.tx(", ");
|
||||
if (ci.hasName())
|
||||
p.addText(ci.getName()+": ");
|
||||
boolean first = true;
|
||||
for (ContactPoint c : ci.getTelecom()) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
p.tx(", ");
|
||||
addTelecom(p, c);
|
||||
}
|
||||
}
|
||||
p.tx(")");
|
||||
}
|
||||
p.tx(". ");
|
||||
p.addText(cm.getCopyright());
|
||||
if (!Utilities.noString(cm.getDescription()))
|
||||
addMarkdown(x, cm.getDescription());
|
||||
|
||||
x.br();
|
||||
|
||||
CodeSystem cs = context.fetchCodeSystem("http://hl7.org/fhir/concept-map-relationship");
|
||||
if (cs == null)
|
||||
cs = context.fetchCodeSystem("http://hl7.org/fhir/concept-map-equivalence");
|
||||
String eqpath = cs == null ? null : cs.getUserString("path");
|
||||
|
||||
for (ConceptMapGroupComponent grp : cm.getGroup()) {
|
||||
String src = grp.getSource();
|
||||
boolean comment = false;
|
||||
boolean ok = true;
|
||||
Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>();
|
||||
Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>();
|
||||
sources.put("code", new HashSet<String>());
|
||||
targets.put("code", new HashSet<String>());
|
||||
SourceElementComponent cc = grp.getElement().get(0);
|
||||
String dst = grp.getTarget();
|
||||
sources.get("code").add(grp.getSource());
|
||||
targets.get("code").add(grp.getTarget());
|
||||
for (SourceElementComponent ccl : grp.getElement()) {
|
||||
ok = ok && ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty();
|
||||
for (TargetElementComponent ccm : ccl.getTarget()) {
|
||||
comment = comment || !Utilities.noString(ccm.getComment());
|
||||
for (OtherElementComponent d : ccm.getDependsOn()) {
|
||||
if (!sources.containsKey(d.getProperty()))
|
||||
sources.put(d.getProperty(), new HashSet<String>());
|
||||
sources.get(d.getProperty()).add(d.getSystem());
|
||||
}
|
||||
for (OtherElementComponent d : ccm.getProduct()) {
|
||||
if (!targets.containsKey(d.getProperty()))
|
||||
targets.put(d.getProperty(), new HashSet<String>());
|
||||
targets.get(d.getProperty()).add(d.getSystem());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String display;
|
||||
if (ok) {
|
||||
// simple
|
||||
XhtmlNode tbl = x.table( "grid");
|
||||
XhtmlNode tr = tbl.tr();
|
||||
tr.td().b().tx("Source Code");
|
||||
tr.td().b().tx("Relationship");
|
||||
tr.td().b().tx("Destination Code");
|
||||
if (comment)
|
||||
tr.td().b().tx("Comment");
|
||||
for (SourceElementComponent ccl : grp.getElement()) {
|
||||
tr = tbl.tr();
|
||||
XhtmlNode td = tr.td();
|
||||
td.addText(ccl.getCode());
|
||||
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
|
||||
if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display))
|
||||
td.tx(" ("+display+")");
|
||||
TargetElementComponent ccm = ccl.getTarget().get(0);
|
||||
if (!ccm.hasRelationship())
|
||||
tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")");
|
||||
else {
|
||||
if (ccm.getRelationshipElement().hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
|
||||
String code = ToolingExtensions.readStringExtension(ccm.getRelationshipElement(), ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
|
||||
tr.td().ah(eqpath+"#"+code).tx(presentEquivalenceCode(code));
|
||||
} else {
|
||||
tr.td().ah(eqpath+"#"+ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
|
||||
}
|
||||
}
|
||||
td = tr.td();
|
||||
td.addText(ccm.getCode());
|
||||
display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
|
||||
if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display))
|
||||
td.tx(" ("+display+")");
|
||||
if (comment)
|
||||
tr.td().addText(ccm.getComment());
|
||||
addUnmapped(tbl, grp);
|
||||
}
|
||||
} else {
|
||||
XhtmlNode tbl = x.table( "grid");
|
||||
XhtmlNode tr = tbl.tr();
|
||||
XhtmlNode td;
|
||||
tr.td().colspan(Integer.toString(1+sources.size())).b().tx("Source Concept Details");
|
||||
tr.td().b().tx("Relationship");
|
||||
tr.td().colspan(Integer.toString(1+targets.size())).b().tx("Destination Concept Details");
|
||||
if (comment) {
|
||||
tr.td().b().tx("Comment");
|
||||
}
|
||||
tr = tbl.tr();
|
||||
if (sources.get("code").size() == 1) {
|
||||
String url = sources.get("code").iterator().next();
|
||||
renderCSDetailsLink(tr, url, true);
|
||||
} else
|
||||
tr.td().b().tx("Code");
|
||||
for (String s : sources.keySet()) {
|
||||
if (!s.equals("code")) {
|
||||
if (sources.get(s).size() == 1) {
|
||||
String url = sources.get(s).iterator().next();
|
||||
renderCSDetailsLink(tr, url, false);
|
||||
} else
|
||||
tr.td().b().addText(getDescForConcept(s));
|
||||
}
|
||||
}
|
||||
tr.td();
|
||||
if (targets.get("code").size() == 1) {
|
||||
String url = targets.get("code").iterator().next();
|
||||
renderCSDetailsLink(tr, url, true);
|
||||
} else
|
||||
tr.td().b().tx("Code");
|
||||
for (String s : targets.keySet()) {
|
||||
if (!s.equals("code")) {
|
||||
if (targets.get(s).size() == 1) {
|
||||
String url = targets.get(s).iterator().next();
|
||||
renderCSDetailsLink(tr, url, false);
|
||||
} else
|
||||
tr.td().b().addText(getDescForConcept(s));
|
||||
}
|
||||
}
|
||||
if (comment)
|
||||
tr.td();
|
||||
|
||||
for (int si = 0; si < grp.getElement().size(); si++) {
|
||||
SourceElementComponent ccl = grp.getElement().get(si);
|
||||
boolean slast = si == grp.getElement().size()-1;
|
||||
boolean first = true;
|
||||
if (ccl.hasNoMap() && ccl.getNoMap()) {
|
||||
tr = tbl.tr();
|
||||
td = tr.td().style("border-right-width: 0px");
|
||||
if (!first)
|
||||
td.style("border-top-style: none");
|
||||
else
|
||||
td.style("border-bottom-style: none");
|
||||
if (sources.get("code").size() == 1)
|
||||
td.addText(ccl.getCode());
|
||||
else
|
||||
td.addText(grp.getSource()+" / "+ccl.getCode());
|
||||
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
|
||||
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
|
||||
tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)");
|
||||
|
||||
} else {
|
||||
for (int ti = 0; ti < ccl.getTarget().size(); ti++) {
|
||||
TargetElementComponent ccm = ccl.getTarget().get(ti);
|
||||
boolean last = ti == ccl.getTarget().size()-1;
|
||||
tr = tbl.tr();
|
||||
td = tr.td().style("border-right-width: 0px");
|
||||
if (!first && !last)
|
||||
td.style("border-top-style: none; border-bottom-style: none");
|
||||
else if (!first)
|
||||
td.style("border-top-style: none");
|
||||
else if (!last)
|
||||
td.style("border-bottom-style: none");
|
||||
if (first) {
|
||||
if (sources.get("code").size() == 1)
|
||||
td.addText(ccl.getCode());
|
||||
else
|
||||
td.addText(grp.getSource()+" / "+ccl.getCode());
|
||||
display = getDisplayForConcept(grp.getSource(), ccl.getCode());
|
||||
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
|
||||
}
|
||||
for (String s : sources.keySet()) {
|
||||
if (!s.equals("code")) {
|
||||
td = tr.td();
|
||||
if (first) {
|
||||
td.addText(getValue(ccm.getDependsOn(), s, sources.get(s).size() != 1));
|
||||
display = getDisplay(ccm.getDependsOn(), s);
|
||||
if (display != null)
|
||||
td.tx(" ("+display+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
if (!ccm.hasRelationship())
|
||||
tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")");
|
||||
else {
|
||||
if (ccm.getRelationshipElement().hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
|
||||
String code = ToolingExtensions.readStringExtension(ccm.getRelationshipElement(), ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
|
||||
tr.td().ah(eqpath+"#"+code).tx(presentEquivalenceCode(code));
|
||||
} else {
|
||||
tr.td().ah(eqpath+"#"+ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
|
||||
}
|
||||
}
|
||||
td = tr.td().style("border-right-width: 0px");
|
||||
if (targets.get("code").size() == 1)
|
||||
td.addText(ccm.getCode());
|
||||
else
|
||||
td.addText(grp.getTarget()+" / "+ccm.getCode());
|
||||
display = getDisplayForConcept(grp.getTarget(), ccm.getCode());
|
||||
tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
|
||||
|
||||
for (String s : targets.keySet()) {
|
||||
if (!s.equals("code")) {
|
||||
td = tr.td();
|
||||
td.addText(getValue(ccm.getProduct(), s, targets.get(s).size() != 1));
|
||||
display = getDisplay(ccm.getProduct(), s);
|
||||
if (display != null)
|
||||
td.tx(" ("+display+")");
|
||||
}
|
||||
}
|
||||
if (comment)
|
||||
tr.td().addText(ccm.getComment());
|
||||
}
|
||||
}
|
||||
addUnmapped(tbl, grp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSameCodeAndDisplay(String code, String display) {
|
||||
String c = code.replace(" ", "").replace("-", "").toLowerCase();
|
||||
String d = display.replace(" ", "").replace("-", "").toLowerCase();
|
||||
return c.equals(d);
|
||||
}
|
||||
|
||||
|
||||
private String presentRelationshipCode(String code) {
|
||||
if ("related-to".equals(code)) {
|
||||
return "is related to";
|
||||
} else if ("equivalent".equals(code)) {
|
||||
return "is equivalent to";
|
||||
} else if ("broader".equals(code)) {
|
||||
return "maps to wider concept";
|
||||
} else if ("narrower".equals(code)) {
|
||||
return "maps to narrower concept";
|
||||
} else if ("not-related-to".equals(code)) {
|
||||
return "is not related to";
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
private String presentEquivalenceCode(String code) {
|
||||
if ("relatedto".equals(code)) {
|
||||
return "is related to";
|
||||
} else if ("equivalent".equals(code)) {
|
||||
return "is equivalent to";
|
||||
} else if ("equal".equals(code)) {
|
||||
return "is equal to";
|
||||
} else if ("wider".equals(code)) {
|
||||
return "maps to wider concept";
|
||||
} else if ("subsumes".equals(code)) {
|
||||
return "is subsumed by";
|
||||
} else if ("narrower".equals(code)) {
|
||||
return "maps to narrower concept";
|
||||
} else if ("specializes".equals(code)) {
|
||||
return "has specialization";
|
||||
} else if ("inexact".equals(code)) {
|
||||
return "maps loosely to";
|
||||
} else if ("unmatched".equals(code)) {
|
||||
return "has no match";
|
||||
} else if ("disjoint".equals(code)) {
|
||||
return "is not related to";
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
public void renderCSDetailsLink(XhtmlNode tr, String url, boolean span2) {
|
||||
CodeSystem cs;
|
||||
XhtmlNode td;
|
||||
cs = context.fetchCodeSystem(url);
|
||||
td = tr.td();
|
||||
if (span2) {
|
||||
td.colspan("2");
|
||||
}
|
||||
td.b().tx("Code");
|
||||
td.tx(" from ");
|
||||
if (cs == null)
|
||||
td.tx(url);
|
||||
else
|
||||
td.ah(cs.getUserString("path")).attribute("title", url).tx(cs.present());
|
||||
}
|
||||
|
||||
private void addUnmapped(XhtmlNode tbl, ConceptMapGroupComponent grp) {
|
||||
if (grp.hasUnmapped()) {
|
||||
// throw new Error("not done yet");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getDescForConcept(String s) {
|
||||
if (s.startsWith("http://hl7.org/fhir/v2/element/"))
|
||||
return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length());
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String getValue(List<OtherElementComponent> list, String s, boolean withSystem) {
|
||||
for (OtherElementComponent c : list) {
|
||||
if (s.equals(c.getProperty()))
|
||||
if (withSystem)
|
||||
return c.getSystem()+" / "+c.getValue();
|
||||
else
|
||||
return c.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getDisplay(List<OtherElementComponent> list, String s) {
|
||||
for (OtherElementComponent c : list) {
|
||||
if (s.equals(c.getProperty()))
|
||||
return getDisplayForConcept(c.getSystem(), c.getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,460 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.exceptions.TerminologyServiceException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.ContactPoint;
|
||||
import org.hl7.fhir.r5.model.Questionnaire;
|
||||
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.model.Bundle.BundleEntryComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.r5.utils.NarrativeGenerator.ResourceContext;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
|
||||
|
||||
public class TerminologyRenderer {
|
||||
|
||||
public enum TerminologyRendererMode {
|
||||
RESOURCE, IG
|
||||
}
|
||||
|
||||
protected class TargetElementComponentWrapper {
|
||||
protected ConceptMapGroupComponent group;
|
||||
protected TargetElementComponent comp;
|
||||
protected TargetElementComponentWrapper(ConceptMapGroupComponent group, TargetElementComponent comp) {
|
||||
super();
|
||||
this.group = group;
|
||||
this.comp = comp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UsedConceptMap {
|
||||
|
||||
private ConceptMapRenderInstructions details;
|
||||
private String link;
|
||||
private ConceptMap map;
|
||||
public UsedConceptMap(ConceptMapRenderInstructions details, String link, ConceptMap map) {
|
||||
super();
|
||||
this.details = details;
|
||||
this.link = link;
|
||||
this.map = map;
|
||||
}
|
||||
public ConceptMapRenderInstructions getDetails() {
|
||||
return details;
|
||||
}
|
||||
public ConceptMap getMap() {
|
||||
return map;
|
||||
}
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConceptMapRenderInstructions {
|
||||
private String name;
|
||||
private String url;
|
||||
private boolean doDescription;
|
||||
public ConceptMapRenderInstructions(String name, String url, boolean doDescription) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.doDescription = doDescription;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
public boolean isDoDescription() {
|
||||
return doDescription;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected TerminologyRendererMode mode;
|
||||
protected IWorkerContext context;
|
||||
protected MarkDownProcessor markdown;
|
||||
protected String lang;
|
||||
protected String prefix;
|
||||
protected int headerLevelContext;
|
||||
protected ValidationOptions terminologyServiceOptions = new ValidationOptions();
|
||||
protected boolean noSlowLookup;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mode - whether we are rendering for a resource directly, or for an IG
|
||||
* @param context - common services (terminology server, code system cache etc)
|
||||
* @param markdown - markdown processing engine of the correct sort for the version applicable
|
||||
* @param prefix - the path to the base FHIR specification
|
||||
* @param lang - the language to use (null for the default)
|
||||
*/
|
||||
public TerminologyRenderer(TerminologyRendererMode mode, IWorkerContext context, MarkDownProcessor markdown, String prefix, String lang) {
|
||||
super();
|
||||
this.mode = mode;
|
||||
this.context = context;
|
||||
this.markdown = markdown;
|
||||
this.lang = lang;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public TerminologyRendererMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public int getHeaderLevelContext() {
|
||||
return headerLevelContext;
|
||||
}
|
||||
|
||||
public void setHeaderLevelContext(int headerLevelContext) {
|
||||
this.headerLevelContext = headerLevelContext;
|
||||
}
|
||||
|
||||
public ValidationOptions getTerminologyServiceOptions() {
|
||||
return terminologyServiceOptions;
|
||||
}
|
||||
|
||||
public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
|
||||
this.terminologyServiceOptions = terminologyServiceOptions;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean isNoSlowLookup() {
|
||||
return noSlowLookup;
|
||||
}
|
||||
|
||||
public void setNoSlowLookup(boolean noSlowLookup) {
|
||||
this.noSlowLookup = noSlowLookup;
|
||||
}
|
||||
|
||||
protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException {
|
||||
if (text != null) {
|
||||
// 1. custom FHIR extensions
|
||||
while (text.contains("[[[")) {
|
||||
String left = text.substring(0, text.indexOf("[[["));
|
||||
String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]"));
|
||||
String right = text.substring(text.indexOf("]]]")+3);
|
||||
String url = link;
|
||||
String[] parts = link.split("\\#");
|
||||
StructureDefinition p = context.fetchResource(StructureDefinition.class, parts[0]);
|
||||
if (p == null)
|
||||
p = context.fetchTypeDefinition(parts[0]);
|
||||
if (p == null)
|
||||
p = context.fetchResource(StructureDefinition.class, link);
|
||||
if (p != null) {
|
||||
url = p.getUserString("path");
|
||||
if (url == null)
|
||||
url = p.getUserString("filename");
|
||||
} else
|
||||
throw new DefinitionException("Unable to resolve markdown link "+link);
|
||||
|
||||
text = left+"["+link+"]("+url+")"+right;
|
||||
}
|
||||
|
||||
// 2. markdown
|
||||
String s = markdown.process(Utilities.escapeXml(text), "narrative generator");
|
||||
XhtmlParser p = new XhtmlParser();
|
||||
XhtmlNode m;
|
||||
try {
|
||||
m = p.parse("<div>"+s+"</div>", "div");
|
||||
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
|
||||
throw new FHIRFormatError(e.getMessage(), e);
|
||||
}
|
||||
x.getChildNodes().addAll(m.getChildNodes());
|
||||
}
|
||||
}
|
||||
|
||||
protected void generateCopyright(XhtmlNode x, CanonicalResource cs) {
|
||||
XhtmlNode p = x.para();
|
||||
p.b().tx(context.translator().translate("xhtml-gen-cs", "Copyright Statement:", lang));
|
||||
smartAddText(p, " " + cs.getCopyright());
|
||||
}
|
||||
|
||||
protected void smartAddText(XhtmlNode p, String text) {
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
String[] lines = text.split("\\r\\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (i > 0)
|
||||
p.br();
|
||||
p.addText(lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void addMapHeaders(XhtmlNode tr, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
|
||||
for (UsedConceptMap m : maps) {
|
||||
XhtmlNode td = tr.td();
|
||||
XhtmlNode b = td.b();
|
||||
XhtmlNode a = b.ah(prefix+m.getLink());
|
||||
a.addText(m.getDetails().getName());
|
||||
if (m.getDetails().isDoDescription() && m.getMap().hasDescription())
|
||||
addMarkdown(td, m.getMap().getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
protected String getHeader() {
|
||||
int i = 3;
|
||||
while (i <= headerLevelContext)
|
||||
i++;
|
||||
if (i > 6)
|
||||
i = 6;
|
||||
return "h"+Integer.toString(i);
|
||||
}
|
||||
|
||||
public static String describeSystem(String system) {
|
||||
if (system == null)
|
||||
return "[not stated]";
|
||||
if (system.equals("http://loinc.org"))
|
||||
return "LOINC";
|
||||
if (system.startsWith("http://snomed.info"))
|
||||
return "SNOMED CT";
|
||||
if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
|
||||
return "RxNorm";
|
||||
if (system.equals("http://hl7.org/fhir/sid/icd-9"))
|
||||
return "ICD-9";
|
||||
if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
|
||||
return "DICOM";
|
||||
if (system.equals("http://unitsofmeasure.org"))
|
||||
return "UCUM";
|
||||
|
||||
return system;
|
||||
}
|
||||
|
||||
|
||||
protected String makeAnchor(String codeSystem, String code) {
|
||||
String s = codeSystem+'-'+code;
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (char c : s.toCharArray()) {
|
||||
if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.')
|
||||
b.append(c);
|
||||
else
|
||||
b.append('-');
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
protected List<TargetElementComponentWrapper> findMappingsForCode(String code, ConceptMap map) {
|
||||
List<TargetElementComponentWrapper> mappings = new ArrayList<TargetElementComponentWrapper>();
|
||||
|
||||
for (ConceptMapGroupComponent g : map.getGroup()) {
|
||||
for (SourceElementComponent c : g.getElement()) {
|
||||
if (c.getCode().equals(code))
|
||||
for (TargetElementComponent cc : c.getTarget())
|
||||
mappings.add(new TargetElementComponentWrapper(g, cc));
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected String getCharForRelationship(TargetElementComponent mapping) {
|
||||
if (!mapping.hasRelationship())
|
||||
return "";
|
||||
switch (mapping.getRelationship()) {
|
||||
case EQUIVALENT : return "~";
|
||||
case BROADER : return "<";
|
||||
case NARROWER : return ">";
|
||||
case NOTRELATEDTO : return "!=";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends Resource> void addCsRef(ConceptSetComponent inc, XhtmlNode li, T cs) {
|
||||
String ref = null;
|
||||
boolean addHtml = true;
|
||||
if (cs != null) {
|
||||
ref = (String) cs.getUserData("external.url");
|
||||
if (Utilities.noString(ref))
|
||||
ref = (String) cs.getUserData("filename");
|
||||
else
|
||||
addHtml = false;
|
||||
if (Utilities.noString(ref))
|
||||
ref = (String) cs.getUserData("path");
|
||||
}
|
||||
String spec = getSpecialReference(inc.getSystem());
|
||||
if (spec != null) {
|
||||
XhtmlNode a = li.ah(spec);
|
||||
a.code(inc.getSystem());
|
||||
} else if (cs != null && ref != null) {
|
||||
if (!Utilities.noString(prefix) && ref.startsWith("http://hl7.org/fhir/"))
|
||||
ref = ref.substring(20)+"/index.html";
|
||||
else if (addHtml && !ref.contains(".html"))
|
||||
ref = ref + ".html";
|
||||
XhtmlNode a = li.ah(prefix+ref.replace("\\", "/"));
|
||||
a.code(inc.getSystem());
|
||||
} else {
|
||||
li.code(inc.getSystem());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getSpecialReference(String system) {
|
||||
if ("http://snomed.info/sct".equals(system))
|
||||
return "http://www.snomed.org/";
|
||||
if (Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "http://ncimeta.nci.nih.gov", "http://fdasis.nlm.nih.gov",
|
||||
"http://www.radlex.org", "http://www.whocc.no/atc", "http://dicom.nema.org/resources/ontology/DCM", "http://www.genenames.org", "http://www.ensembl.org", "http://www.ncbi.nlm.nih.gov/nuccore",
|
||||
"http://www.ncbi.nlm.nih.gov/clinvar", "http://sequenceontology.org", "http://www.hgvs.org/mutnomen", "http://www.ncbi.nlm.nih.gov/projects/SNP", "http://cancer.sanger.ac.uk/cancergenome/projects/cosmic",
|
||||
"http://www.lrg-sequence.org", "http://www.omim.org", "http://www.ncbi.nlm.nih.gov/pubmed", "http://www.pharmgkb.org", "http://clinicaltrials.gov", "http://www.ebi.ac.uk/ipd/imgt/hla/"))
|
||||
return system;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected XhtmlNode addTableHeaderRowStandard(XhtmlNode t, boolean hasHierarchy, boolean hasDisplay, boolean definitions, boolean comments, boolean version, boolean deprecated, List<PropertyComponent> properties) {
|
||||
XhtmlNode tr = t.tr();
|
||||
if (hasHierarchy)
|
||||
tr.td().b().tx("Lvl");
|
||||
tr.td().attribute("style", "white-space:nowrap").b().tx(context.translator().translate("xhtml-gen-cs", "Code", lang));
|
||||
if (hasDisplay)
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Display", lang));
|
||||
if (definitions)
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Definition", lang));
|
||||
if (deprecated)
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Deprecated", lang));
|
||||
if (comments)
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Comments", lang));
|
||||
if (version)
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", "Version", lang));
|
||||
if (properties != null) {
|
||||
for (PropertyComponent pc : properties) {
|
||||
String display = ToolingExtensions.getPresentation(pc, pc.getCodeElement());
|
||||
if (display == null || display.equals(pc.getCode()) && pc.hasUri()) {
|
||||
display = getDisplayForProperty(pc.getUri());
|
||||
if (display == null) {
|
||||
display = pc.getCode();
|
||||
}
|
||||
}
|
||||
tr.td().b().tx(context.translator().translate("xhtml-gen-cs", display, lang));
|
||||
}
|
||||
}
|
||||
return tr;
|
||||
}
|
||||
|
||||
|
||||
protected String getDisplayForProperty(String uri) {
|
||||
if (Utilities.noString(uri)){
|
||||
return null;
|
||||
}
|
||||
String code = null;
|
||||
if (uri.contains("#")) {
|
||||
code = uri.substring(uri.indexOf("#")+1);
|
||||
uri = uri.substring(0, uri.indexOf("#"));
|
||||
}
|
||||
CodeSystem cs = context.fetchCodeSystem(uri);
|
||||
if (cs == null) {
|
||||
return null;
|
||||
}
|
||||
ConceptDefinitionComponent cc = CodeSystemUtilities.getCode(cs, code);
|
||||
return cc == null ? null : cc.getDisplay();
|
||||
}
|
||||
|
||||
|
||||
protected void AddVsRef(ResourceContext rcontext, String value, XhtmlNode li) {
|
||||
Resource res = null;
|
||||
if (rcontext != null) {
|
||||
BundleEntryComponent be = rcontext.resolve(value);
|
||||
if (be != null) {
|
||||
res = be.getResource();
|
||||
}
|
||||
}
|
||||
if (res != null && !(res instanceof CanonicalResource)) {
|
||||
li.addText(value);
|
||||
return;
|
||||
}
|
||||
CanonicalResource vs = (CanonicalResource) res;
|
||||
if (vs == null)
|
||||
vs = context.fetchResource(ValueSet.class, value);
|
||||
if (vs == null)
|
||||
vs = context.fetchResource(StructureDefinition.class, value);
|
||||
// if (vs == null)
|
||||
// vs = context.fetchResource(DataElement.class, value);
|
||||
if (vs == null)
|
||||
vs = context.fetchResource(Questionnaire.class, value);
|
||||
if (vs != null) {
|
||||
String ref = (String) vs.getUserData("path");
|
||||
|
||||
ref = adjustForPath(ref);
|
||||
XhtmlNode a = li.ah(ref == null ? "?ngen-11?" : ref.replace("\\", "/"));
|
||||
a.addText(value);
|
||||
} else {
|
||||
CodeSystem cs = context.fetchCodeSystem(value);
|
||||
if (cs != null) {
|
||||
String ref = (String) cs.getUserData("path");
|
||||
ref = adjustForPath(ref);
|
||||
XhtmlNode a = li.ah(ref == null ? "?ngen-12?" : ref.replace("\\", "/"));
|
||||
a.addText(value);
|
||||
} else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) {
|
||||
XhtmlNode a = li.ah(value);
|
||||
a.tx("SNOMED-CT");
|
||||
}
|
||||
else {
|
||||
if (value.startsWith("http://hl7.org") && !Utilities.existsInList(value, "http://hl7.org/fhir/sid/icd-10-us"))
|
||||
System.out.println("Unable to resolve value set "+value);
|
||||
li.addText(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String adjustForPath(String ref) {
|
||||
if (prefix == null)
|
||||
return ref;
|
||||
else
|
||||
return prefix+ref;
|
||||
}
|
||||
|
||||
|
||||
protected void addTelecom(XhtmlNode p, ContactPoint c) {
|
||||
if (c.getSystem() == ContactPointSystem.PHONE) {
|
||||
p.tx("Phone: "+c.getValue());
|
||||
} else if (c.getSystem() == ContactPointSystem.FAX) {
|
||||
p.tx("Fax: "+c.getValue());
|
||||
} else if (c.getSystem() == ContactPointSystem.EMAIL) {
|
||||
p.ah( "mailto:"+c.getValue()).addText(c.getValue());
|
||||
} else if (c.getSystem() == ContactPointSystem.URL) {
|
||||
if (c.getValue().length() > 30)
|
||||
p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"...");
|
||||
else
|
||||
p.ah(c.getValue()).addText(c.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String getDisplayForConcept(String system, String value) {
|
||||
if (value == null || system == null)
|
||||
return null;
|
||||
ValidationResult cl = context.validateCode(terminologyServiceOptions, system, value, null);
|
||||
return cl == null ? null : cl.getDisplay();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -102,6 +102,7 @@ import org.hl7.fhir.r5.model.TypeDetails.ProfiledType;
|
|||
import org.hl7.fhir.r5.model.UriType;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.TerminologyRenderer;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
|
||||
|
@ -2612,7 +2613,7 @@ public class StructureMapUtilities {
|
|||
if (Utilities.noString(code))
|
||||
throw new FHIRException("Describe Transform, but the code is blank");
|
||||
Coding c = buildCoding(uri, code);
|
||||
return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
|
||||
return TerminologyRenderer.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -505,4 +505,6 @@ public class I18nConstants {
|
|||
//public static final String
|
||||
public static final String XHTML_URL_EMPTY = "XHTML_URL_EMPTY";
|
||||
public static final String XHTML_URL_INVALID_CHARS = "XHTML_URL_INVALID_CHARS";
|
||||
public static final String TERMINOLOGY_TX_SYSTEM_HTTPS = "TERMINOLOGY_TX_SYSTEM_HTTPS";
|
||||
public static final String CODESYSTEM_CS_NO_VS_NOTCOMPLETE = "CODESYSTEM_CS_NO_VS_NOTCOMPLETE";
|
||||
}
|
|
@ -482,4 +482,7 @@ MEASURE_M_CRITERIA_CQL_NO_ELM = Error in {0}: No compiled version of CQL found
|
|||
MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID = = Error in {0}: Compiled version of CQL is not valid
|
||||
MEASURE_M_CRITERIA_CQL_NOT_FOUND = The function {1} does not exist in the library {0}
|
||||
XHTML_URL_EMPTY = URL is empty
|
||||
XHTML_URL_INVALID_CHARS = URL contains Invalid Characters ({0})
|
||||
XHTML_URL_INVALID_CHARS = URL contains Invalid Characters ({0})
|
||||
TERMINOLOGY_TX_SYSTEM_HTTPS = The system URL ''{0}'' wrongly starts with https: not http:
|
||||
CODESYSTEM_CS_NO_VS_NOTCOMPLETE = Review the All Codes Value Set - incomplete CodeSystems generally should not have an all codes value set specified
|
||||
|
Loading…
Reference in New Issue