001package org.hl7.fhir.validation.cli.services;
002
003import java.text.DateFormat;
004import java.util.Date;
005import java.util.List;
006
007import org.hl7.fhir.utilities.Utilities;
008import org.hl7.fhir.utilities.validation.ValidationMessage;
009import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
010import org.hl7.fhir.validation.ValidationRecord;
011import org.hl7.fhir.validation.cli.utils.VersionUtil;
012
013public class HTMLOutputGenerator {
014
015  private List<ValidationRecord> records;
016
017  public HTMLOutputGenerator(List<ValidationRecord> records) {
018    super();
019    this.records = records;
020  }
021  
022  public String generate(long time) {
023    
024    StringBuilder b = new StringBuilder();
025    b.append(genHeader(time));
026    int i = 0;
027    for (ValidationRecord f : records) {
028      i++;
029      b.append(genSummaryRow(i, f));
030    }
031    b.append("</table>\r\n");
032
033    i = 0;
034    int id = 0;
035    for (ValidationRecord f : records) {
036      i++;
037      b.append(genStart(i, f));
038      if (f.getMessages().size() > 0) {
039        b.append(
040            " <table class=\"grid\">\r\n"+
041                "   <tr>\r\n"+
042                "     <td><b>Path</b></td><td><b>Severity</b></td><td><b>Message</b></td>\r\n"+
043            "   </tr>\r\n");
044        for (ValidationMessage vm : f.getMessages()) {
045          id++;
046          b.append(genDetails(vm, "m"+id));
047        }
048        b.append("</table>\r\n");
049      } else {
050        b.append("<p>No Issues detected</p>\r\n");
051      }
052    }
053    return b.toString();
054  }    
055   
056  private String genHeader(long time) {
057    int err = 0;
058    int warn = 0;
059    int info = 0;
060    for (ValidationRecord f : records) {
061      err = err + f.getErr();
062      warn = warn + f.getWarn();
063      info = info + f.getInfo();
064    }
065
066    return 
067        "<!DOCTYPE HTML>\r\n"+
068        "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n"+
069        "<head>\r\n"+
070        "  <title>Validation Results</title>\r\n"+
071        "  <link href=\"http://hl7.org/fhir/fhir.css\" rel=\"stylesheet\"/>\r\n"+
072        "  <style>\r\n"+
073        "    span.flip  { background-color: #4CAF50; color: white; border: solid 1px #a6d8a8; padding: 2px }\r\n"+
074        "  </style>\r\n"+
075        "  <script>\r\n"+
076        "    function flip(id) {\r\n"+
077        "      var span = document.getElementById('s'+id);\r\n"+
078        "      var div = document.getElementById(id);\r\n"+
079        "      if (document.getElementById('s'+id).innerHTML == 'Show Reasoning') {\r\n"+
080        "        div.style.display = 'block';\r\n"+
081        "        span.innerHTML = 'Hide Reasoning';\r\n"+
082        "      } else {\r\n"+
083        "        div.style.display = 'none';\r\n"+
084        "        span.innerHTML = 'Show Reasoning';\r\n"+
085        "      }\r\n"+
086        "    }\r\n"+
087        "  </script>\r\n"+
088        "</head>\r\n"+
089        "<body style=\"margin: 20px; background-color: #ffffff\">\r\n"+
090        " <h1>Validation Results</h1>\r\n"+
091        " <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"+
092        " <table class=\"grid\">\r\n"+
093        "   <tr>\r\n"+
094        "     <td><b>Filename</b></td><td><b>Errors</b></td><td><b>Warnings</b></td><td><b>Hints</b></td>\r\n"+
095        "   </tr>\r\n";
096  }
097
098  private String now() {
099    return DateFormat.getDateTimeInstance().format(new Date());
100  }
101
102  private String genSummaryRow(int i, ValidationRecord rec) {
103    String color = colorForLevel(IssueSeverity.ERROR, false);
104    if (rec.getErr() == 0) {
105      color = "#EFFFEF";
106    }       
107    return       
108        "   <tr style=\"background-color: "+color+"\">\r\n"+
109        "     <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"+
110        "   </tr>\r\n";
111  }
112
113  private String genStart(int i, ValidationRecord f) {
114    String xlink = Utilities.isAbsoluteUrl(f.getLocation()) ? f.getLocation() : "file:"+f.getLocation();
115    return 
116        "<hr/>\r\n"+
117        "<a name=\"l"+i+"\"> </a>\r\n"+
118        "<h2><a href=\""+xlink+"\">"+Utilities.escapeXml(f.getLocation())+"</a></h2>\r\n";
119  }
120  
121  
122  private String genDetails(ValidationMessage vm, String id) {
123    String path = vm.getLocation() == null ? "" : vm.getLocation()+ lineCol(vm); 
124    String level = vm.isSlicingHint() ? "Slicing Information" : vm.isSignpost() ? "Process Info" : vm.getLevel().toCode();
125    String color = colorForLevel(vm.getLevel(), vm.isSignpost());
126    String mid = vm.getMessageId();
127    String msg = vm.getHtml();
128    String msgdetails = vm.isSlicingHint() ? vm.getSliceHtml() : vm.getHtml();
129    if (vm.isSlicingHint()) {
130      return 
131          "   <tr style=\"background-color: "+color+"\">\r\n"+
132          "     <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"+
133          "   </tr>\r\n";
134          
135    } else {
136      return 
137          "   <tr style=\"background-color: "+color+"\">\r\n"+
138          "     <td><b>"+path+"</b></td><td><b>"+level+"</b></td><td title=\""+mid+"\"><b>"+msg+"</b></td>\r\n"+
139          "   </tr>\r\n";
140    }
141  }
142  
143  private String lineCol(ValidationMessage vm) {
144    return vm.getLine() > 0 ? " (l"+vm.getLine()+"/c"+vm.getCol()+")" : "";
145  }
146  
147
148  private String colorForLevel(IssueSeverity level, boolean signpost) {
149    if (signpost) {
150      return "#d6feff";
151    }
152    switch (level) {
153    case ERROR:
154      return "#ffcccc";
155    case FATAL:
156      return "#ff9999";
157    case WARNING:
158      return "#ffebcc";
159    default: // INFORMATION:
160      return "#ffffe6";
161    }
162  }
163
164  private String halfColorForLevel(IssueSeverity level, boolean signpost) {
165    if (signpost) {
166      return "#e3feff";
167    }
168    switch (level) {
169    case ERROR:
170      return "#ffeeee";
171    case FATAL:
172      return "#ffcccc";
173    case WARNING:
174      return "#fff4ee";
175    default: // INFORMATION:
176      return "#fffff2";
177    }
178  }
179
180
181}