add parameter -html-output for enhanced presentation of slicing information (issue #283)

This commit is contained in:
Grahame Grieve 2020-10-29 17:51:34 +11:00
parent bc1d67db96
commit 82afa47590
7 changed files with 270 additions and 8 deletions

View File

@ -5,6 +5,7 @@ Validator Changes:
* fix bug checking patterns (missed in some circumstances)
* fix bug checking type of resources in bundles
* improve messages around cardinality errors in profiles
* add parameter -html-output for enhanced presentation of slicing information
Other code changes:
* Render binding description in profile tables if it doesn't contain paragraphs

View File

@ -46,6 +46,7 @@ public class OperationOutcomeUtilities {
public static OperationOutcomeIssueComponent convertToIssue(ValidationMessage message, OperationOutcome op) {
OperationOutcomeIssueComponent issue = new OperationOutcome.OperationOutcomeIssueComponent();
issue.setUserData("source.vm", message);
issue.setCode(convert(message.getType()));
if (message.getLocation() != null) {

View File

@ -114,6 +114,7 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.model.ScanOutputItem;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
import org.hl7.fhir.validation.cli.utils.*;
@ -195,6 +196,50 @@ POSSIBILITY OF SUCH DAMAGE.
*/
public class ValidationEngine implements IValidatorResourceFetcher, IPackageInstaller {
public class ValidationRecord {
private String location;
private List<ValidationMessage> messages;
int err = 0;
int warn = 0;
int info = 0;
public ValidationRecord(String location, List<ValidationMessage> messages) {
this.location = location;
this.messages = messages;
for (ValidationMessage vm : messages) {
if (vm.getLevel().equals(ValidationMessage.IssueSeverity.FATAL)||vm.getLevel().equals(ValidationMessage.IssueSeverity.ERROR))
err++;
else if (vm.getLevel().equals(ValidationMessage.IssueSeverity.WARNING))
warn++;
else if (!vm.isSignpost()) {
info++;
}
}
}
public String getLocation() {
return location;
}
public List<ValidationMessage> getMessages() {
return messages;
}
public int getErr() {
return err;
}
public int getWarn() {
return warn;
}
public int getInfo() {
return info;
}
}
public class TransformSupportServices implements ITransformerServices {
private List<Base> outputs;
@ -1171,7 +1216,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
public OperationOutcome validate(String source, List<String> profiles) throws FHIRException, IOException {
List<String> l = new ArrayList<String>();
l.add(source);
return (OperationOutcome)validate(l, profiles);
return (OperationOutcome)validate(l, profiles, null);
}
public List<ScanOutputItem> validateScan(List<String> sources, Set<String> guides) throws FHIRException, IOException, EOperationOutcome {
@ -1267,7 +1312,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
}
}
public Resource validate(List<String> sources, List<String> profiles) throws FHIRException, IOException {
public Resource validate(List<String> sources, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException {
if (profiles.size() > 0) {
System.out.println(" Profiles: "+profiles);
}
@ -1281,7 +1326,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
System.out.print(" Validate " + ref);
Content cnt = loadContent(ref, "validate", false);
try {
OperationOutcome outcome = validate(ref, cnt.focus, cnt.cntType, profiles);
OperationOutcome outcome = validate(ref, cnt.focus, cnt.cntType, profiles, record);
ToolingExtensions.addStringExtension(outcome, ToolingExtensions.EXT_OO_FILE, ref);
System.out.println(" " + context.clock().milestone());
results.addEntry().setResource(outcome);
@ -1355,7 +1400,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
}
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles) throws FHIRException, IOException, EOperationOutcome, SAXException {
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException, EOperationOutcome, SAXException {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (doNative) {
SchemaValidator.validateSchema(location, cntType, messages);
@ -1365,6 +1410,9 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
if (showTimes) {
System.out.println(location+": "+validator.reportTimes());
}
if (record != null) {
record.add(new ValidationRecord(location, messages));
}
return messagesToOutcome(messages);
}

View File

@ -41,6 +41,8 @@ public class CliContext {
private String map = null;
@JsonProperty("output")
private String output = null;
@JsonProperty("htmlOutput")
private String htmlOutput = null;
@JsonProperty("txServer")
private String txServer = "http://tx.fhir.org";
@JsonProperty("sv")
@ -252,6 +254,17 @@ public class CliContext {
return this;
}
@JsonProperty("output")
public String getHtmlOutput() {
return htmlOutput;
}
@JsonProperty("output")
public CliContext setHtmlOutput(String htmlOutput) {
this.htmlOutput = htmlOutput;
return this;
}
@JsonProperty("canDoNative")
public boolean getCanDoNative() {
return canDoNative;
@ -472,6 +485,7 @@ public class CliContext {
noExtensibleBindingMessages == that.noExtensibleBindingMessages &&
Objects.equals(map, that.map) &&
Objects.equals(output, that.output) &&
Objects.equals(htmlOutput, that.htmlOutput) &&
Objects.equals(txServer, that.txServer) &&
Objects.equals(sv, that.sv) &&
Objects.equals(txLog, that.txLog) &&
@ -493,7 +507,7 @@ public class CliContext {
@Override
public int hashCode() {
return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, map, output, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes);
return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, map, output, htmlOutput, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes);
}
@Override
@ -510,6 +524,7 @@ public class CliContext {
", noExtensibleBindingMessages=" + noExtensibleBindingMessages +
", map='" + map + '\'' +
", output='" + output + '\'' +
", htmlOutput='" + htmlOutput + '\'' +
", txServer='" + txServer + '\'' +
", sv='" + sv + '\'' +
", txLog='" + txLog + '\'' +

View File

@ -0,0 +1,181 @@
package org.hl7.fhir.validation.cli.services;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.utils.VersionUtil;
public class HTMLOutputGenerator {
private List<ValidationRecord> records;
public HTMLOutputGenerator(List<ValidationRecord> records) {
super();
this.records = records;
}
public String generate(long time) {
StringBuilder b = new StringBuilder();
b.append(genHeader(time));
int i = 0;
for (ValidationRecord f : records) {
i++;
b.append(genSummaryRow(i, f));
}
b.append("</table>\r\n");
i = 0;
int id = 0;
for (ValidationRecord f : records) {
i++;
b.append(genStart(i, f));
if (f.getMessages().size() > 0) {
b.append(
" <table class=\"grid\">\r\n"+
" <tr>\r\n"+
" <td><b>Path</b></td><td><b>Severity</b></td><td><b>Message</b></td>\r\n"+
" </tr>\r\n");
for (ValidationMessage vm : f.getMessages()) {
id++;
b.append(genDetails(vm, "m"+id));
}
b.append("</table>\r\n");
} else {
b.append("<p>No Issues detected</p>\r\n");
}
}
return b.toString();
}
private String genHeader(long time) {
int err = 0;
int warn = 0;
int info = 0;
for (ValidationRecord f : records) {
err = err + f.getErr();
warn = warn + f.getWarn();
info = info + f.getInfo();
}
return
"<!DOCTYPE HTML>\r\n"+
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n"+
"<head>\r\n"+
" <title>Validation Results</title>\r\n"+
" <link href=\"http://hl7.org/fhir/fhir.css\" rel=\"stylesheet\"/>\r\n"+
" <style>\r\n"+
" span.flip { background-color: #4CAF50; color: white; border: solid 1px #a6d8a8; padding: 2px }\r\n"+
" </style>\r\n"+
" <script>\r\n"+
" function flip(id) {\r\n"+
" var span = document.getElementById('s'+id);\r\n"+
" var div = document.getElementById(id);\r\n"+
" if (document.getElementById('s'+id).innerHTML == 'Show Reasoning') {\r\n"+
" div.style.display = 'block';\r\n"+
" span.innerHTML = 'Hide Reasoning';\r\n"+
" } else {\r\n"+
" div.style.display = 'none';\r\n"+
" span.innerHTML = 'Show Reasoning';\r\n"+
" }\r\n"+
" }\r\n"+
" </script>\r\n"+
"</head>\r\n"+
"<body style=\"margin: 20px; background-color: #ffffff\">\r\n"+
" <h1>Validation Results</h1>\r\n"+
" <p>"+err+" "+Utilities.pluralize("error", err)+", "+warn+" "+Utilities.pluralize("warning", warn)+", "+info+" "+Utilities.pluralize("hint", info)+". Generated "+now()+" by Validator "+VersionUtil.getVersionString()+" ("+time+"ms)</p>\r\n"+
" <table class=\"grid\">\r\n"+
" <tr>\r\n"+
" <td><b>Filename</b></td><td><b>Errors</b></td><td><b>Warnings</b></td><td><b>Hints</b></td>\r\n"+
" </tr>\r\n";
}
private String now() {
return DateFormat.getDateTimeInstance().format(new Date());
}
private String genSummaryRow(int i, ValidationRecord rec) {
String color = colorForLevel(IssueSeverity.ERROR, false);
if (rec.getErr() == 0) {
color = "#EFFFEF";
}
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><a href=\"#;"+i+"\"><b>"+Utilities.escapeXml(rec.getLocation())+"</b></a></td><td><b>"+rec.getErr()+"</b></td><td><b>"+rec.getWarn()+"</b></td><td><b>"+rec.getInfo()+"</b></td>\r\n"+
" </tr>\r\n";
}
private String genStart(int i, ValidationRecord f) {
String xlink = Utilities.isAbsoluteUrl(f.getLocation()) ? f.getLocation() : "file:"+f.getLocation();
return
"<hr/>\r\n"+
"<a name=\"l"+i+"\"> </a>\r\n"+
"<h2><a href=\""+xlink+"\">"+Utilities.escapeXml(f.getLocation())+"</a></h2>\r\n";
}
private String genDetails(ValidationMessage vm, String id) {
String path = vm.getLocation() == null ? "" : vm.getLocation()+ lineCol(vm);
String level = vm.isSlicingHint() ? "Slicing Information" : vm.isSignpost() ? "Process Info" : vm.getLevel().toCode();
String color = colorForLevel(vm.getLevel(), vm.isSignpost());
String mid = vm.getMessageId();
String msg = vm.getHtml();
String msgdetails = vm.isSlicingHint() ? vm.getSliceHtml() : vm.getHtml();
if (vm.isSlicingHint()) {
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><b>"+path+"</b></td><td><b>"+level+"</b></td><td><b>"+msg+"</b> <span id=\"s"+id+"\" class=\"flip\" onclick=\"flip('"+id+"')\">Show Reasoning</span><div id=\""+id+"\" style=\"display: none\"><p>&nbsp;</p>"+msgdetails+"</div></td>\r\n"+
" </tr>\r\n";
} else {
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><b>"+path+"</b></td><td><b>"+level+"</b></td><td title=\""+mid+"\"><b>"+msg+"</b></td>\r\n"+
" </tr>\r\n";
}
}
private String lineCol(ValidationMessage vm) {
return vm.getLine() > 0 ? " (l"+vm.getLine()+"/c"+vm.getCol()+")" : "";
}
private String colorForLevel(IssueSeverity level, boolean signpost) {
if (signpost) {
return "#d6feff";
}
switch (level) {
case ERROR:
return "#ffcccc";
case FATAL:
return "#ff9999";
case WARNING:
return "#ffebcc";
default: // INFORMATION:
return "#ffffe6";
}
}
private String halfColorForLevel(IssueSeverity level, boolean signpost) {
if (signpost) {
return "#e3feff";
}
switch (level) {
case ERROR:
return "#ffeeee";
case FATAL:
return "#ffcccc";
case WARNING:
return "#fff4ee";
default: // INFORMATION:
return "#fffff2";
}
}
}

View File

@ -27,6 +27,7 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.model.*;
import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
@ -69,7 +70,9 @@ public class ValidationService {
}
public static void validateSources(CliContext cliContext, ValidationEngine validator) throws Exception {
Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles());
long start = System.currentTimeMillis();
List<ValidationRecord> records = new ArrayList<>();
Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), records);
int ec = 0;
System.out.println("Done. "+validator.getContext().clock().report());
System.out.println();
@ -92,6 +95,11 @@ public class ValidationService {
x.compose(s, r);
s.close();
}
if (cliContext.getHtmlOutput() != null) {
String html = new HTMLOutputGenerator(records).generate(System.currentTimeMillis()-start);
TextFile.stringToFile(html, cliContext.getHtmlOutput());
System.out.println("HTML Summary in "+cliContext.getHtmlOutput());
}
System.exit(ec > 0 ? 1 : 0);
}
@ -288,10 +296,12 @@ public class ValidationService {
System.out.println("Scanning for versions (no -version parameter):");
VersionSourceInformation versions = ValidationService.scanForVersions(cliContext);
for (String s : versions.getReport()) {
System.out.println(" " + s);
if (!s.equals("(nothing found)")) {
System.out.println(" " + s);
}
}
if (versions.isEmpty()) {
System.out.println("-> Using Default version '" + VersionUtilities.CURRENT_VERSION + "'");
System.out.println(" No Version Info found: Using Default version '" + VersionUtilities.CURRENT_VERSION + "'");
return "current";
}
if (versions.size() == 1) {

View File

@ -12,6 +12,7 @@ public class Params {
public static final String VERSION = "-version";
public static final String OUTPUT = "-output";
public static final String HTML_OUTPUT = "-html-output";
public static final String PROXY = "-proxy";
public static final String PROFILE = "-profile";
public static final String BUNDLE = "-bundle";
@ -93,6 +94,11 @@ public class Params {
throw new Error("Specified -output without indicating output file");
else
cliContext.setOutput(args[++i]);
} else if (args[i].equals(HTML_OUTPUT)) {
if (i + 1 == args.length)
throw new Error("Specified -html-output without indicating output file");
else
cliContext.setHtmlOutput(args[++i]);
} else if (args[i].equals(PROXY)) {
i++; // ignore next parameter
} else if (args[i].equals(PROFILE)) {