CMSIS-DSP: Improved formating script for test reports.
diff --git a/CMSIS/DSP/Testing/extractDb.py b/CMSIS/DSP/Testing/extractDb.py
index 9395f15..cb51716 100755
--- a/CMSIS/DSP/Testing/extractDb.py
+++ b/CMSIS/DSP/Testing/extractDb.py
@@ -4,6 +4,361 @@
 import pandas as pd
 import numpy as np
 
+class Document:
+    def __init__(self,runid,date):
+        self._runid = runid 
+        self._date = date 
+        self._sections = []
+
+    @property
+    def runid(self):
+        return(self._runid)
+
+    @property
+    def date(self):
+        return(self._date)
+
+    @property
+    def sections(self):
+        return(self._sections)
+
+    def addSection(self,section):
+        self._sections.append(section)
+
+    def accept(self, visitor):
+      visitor.visitDocument(self)
+      for element in self._sections:
+          element.accept(visitor)   
+      visitor.leaveDocument(self)
+
+class Section:
+  def __init__(self,name):
+      self._name=name 
+      self._subsections = []
+      self._tables = []
+
+  def addSection(self,section):
+      self._subsections.append(section)
+
+  def addTable(self,table):
+      self._tables.append(table)
+
+  @property
+  def hasChildren(self):
+      return(len(self._subsections)>0)
+
+  @property
+  def name(self):
+     return(self._name)
+
+  def accept(self, visitor):
+      visitor.visitSection(self)
+      for element in self._subsections:
+          element.accept(visitor) 
+      for element in self._tables:
+          element.accept(visitor)    
+      visitor.leaveSection(self)   
+
+class Table:
+    def __init__(self,columns):
+       self._columns=columns
+       self._rows=[] 
+
+    def addRow(self,row):
+       self._rows.append(row)
+
+    @property
+    def columns(self):
+       return(self._columns)
+
+    @property
+    def rows(self):
+       return(self._rows)
+
+    def accept(self, visitor):
+      visitor.visitTable(self)
+
+
+
+class Markdown:
+  def __init__(self,output):
+    self._id=0
+    self._output = output
+
+    # Write columns in markdown format
+  def writeColumns(self,cols):
+        colStr = "".join(joinit(cols,"|"))
+        self._output.write("|")
+        self._output.write(colStr)
+        self._output.write("|\n")
+        sepStr="".join(joinit([":-:" for x in cols],"|"))
+        self._output.write("|")
+        self._output.write(sepStr)
+        self._output.write("|\n")
+    
+    # Write row in markdown format
+  def writeRow(self,row):
+        row=[str(x) for x in row]
+        rowStr = "".join(joinit(row,"|"))
+        self._output.write("|")
+        self._output.write(rowStr)
+        self._output.write("|\n")
+
+  def visitTable(self,table):
+      self.writeColumns(table.columns)
+      for row in table.rows:
+        self.writeRow(row)
+
+  def visitSection(self,section):
+     self._id = self._id + 1 
+     header = "".join(["#" for i in range(self._id)])
+     output.write("%s %s\n" % (header,section.name))
+
+  def leaveSection(self,section):
+     self._id = self._id - 1 
+
+  def visitDocument(self,document):
+      self._output.write("Run number %d on %s\n" % (document.runid, str(document.date)))
+
+  def leaveDocument(self,document):
+      pass
+
+styleSheet="""
+<style type='text/css'>
+
+#TOC {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 250px;
+  height: 100%;
+  overflow:auto;
+}
+
+html {
+  font-size: 16px;
+}
+
+html, body {
+  background-color: #f3f2ee;
+  font-family: "PT Serif", 'Times New Roman', Times, serif;
+  color: #1f0909;
+  line-height: 1.5em;
+
+  margin: auto;
+  margin-left:220px;
+}
+
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+  width: 100%; 
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-weight: bold;
+}
+h1 {
+  font-size: 1.875em;
+  line-height: 1.6em;
+  margin-top: 1em;
+}
+h2,
+h3 {
+  font-size: 1.3125em;
+  line-height: 1.15;
+  margin-top: 2.285714em;
+  margin-bottom: 1.15em;
+}
+h4 {
+  font-size: 1.125em;
+  margin-top: 2.67em;
+}
+h5,
+h6 {
+  font-size: 1em;
+}
+
+
+table {
+  margin-bottom: 1.5em;
+  /*24 / 16*/
+  font-size: 1em;
+  /* width: 100%; */
+}
+thead th,
+tfoot th {
+  padding: .25em .25em .25em .4em;
+  text-transform: uppercase;
+}
+th {
+  text-align: left;
+}
+td {
+  vertical-align: top;
+  padding: .25em .25em .25em .4em;
+}
+
+.ty-table-edit {
+  background-color: transparent;
+}
+thead {
+  background-color: #dadada;
+}
+tr:nth-child(even) {
+  background: #e8e7e7;
+}
+
+ul, #myUL {
+  list-style-type: none;
+  padding-inline-start:10px;
+}
+
+
+
+/* Remove margins and padding from the parent ul */
+#myUL {
+  margin: 0;
+  padding: 0;
+}
+
+/* Style the caret/arrow */
+.caret {
+  cursor: pointer;
+  user-select: none; /* Prevent text selection */
+}
+
+/* Create the caret/arrow with a unicode, and style it */
+.caret::before {
+  content: "\\25B6";
+  color: black;
+  display: inline-block;
+  margin-right: 6px;
+}
+
+/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
+.caret-down::before {
+  transform: rotate(90deg);
+}
+
+/* Hide the nested list */
+.nested {
+  display: none;
+}
+
+/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */
+.active {
+  display: block;
+}
+
+</style>
+"""
+
+script="""<script type="text/javascript">
+var toggler = document.getElementsByClassName("caret");
+var i;
+for (i = 0; i < toggler.length; i++) {
+  toggler[i].addEventListener("click", function() {
+    this.parentElement.querySelector(".nested").classList.toggle("active");
+    this.classList.toggle("caret-down");
+  });
+}</script>"""
+
+
+class HTMLToc:
+  def __init__(self,output):
+    self._id=0
+    self._sectionID = 0
+    self._output = output
+
+  
+
+  def visitTable(self,table):
+      pass
+
+
+  def visitSection(self,section):
+     self._id = self._id + 1 
+     self._sectionID = self._sectionID + 1
+     if section.hasChildren:
+        self._output.write("<li><span class=\"caret\"><a href=\"#section%d\">%s</a></span>\n" % (self._sectionID,section.name))
+        self._output.write("<ul class=\"nested\">\n")
+     else:
+        self._output.write("<li><span><a href=\"#section%d\">%s</a></span>\n" % (self._sectionID,section.name))
+
+  def leaveSection(self,section):
+    if section.hasChildren:
+       self._output.write("</ul></li>\n")
+
+    self._id = self._id - 1 
+
+  def visitDocument(self,document):
+      self._output.write("<div id=\"TOC\"><h1>Table of content</h1><ul id=\"myUL\">\n")
+
+
+  def leaveDocument(self,document):
+      self._output.write("</ul></div>%s\n" % script)
+
+
+class HTML:
+  def __init__(self,output):
+    self._id=0
+    self._sectionID = 0
+    self._output = output
+
+  
+
+  def visitTable(self,table):
+      output.write("<table>\n")
+      output.write("<thead>\n")
+      output.write("<tr>\n")
+      for col in table.columns:
+        output.write("<th>")
+        output.write(str(col))
+        output.write("</th>\n")
+      output.write("</tr>\n")
+      output.write("</thead>\n")
+      for row in table.rows:
+        output.write("<tr>\n")
+        for elem in row:
+            output.write("<td>")
+            output.write(str(elem))
+            output.write("</td>\n")
+        output.write("</tr>\n")
+      output.write("</table>\n")
+
+
+  def visitSection(self,section):
+     self._id = self._id + 1 
+     self._sectionID = self._sectionID + 1
+     output.write("<h%d id=\"section%d\">%s</h%d>\n" % (self._id,self._sectionID,section.name,self._id))
+
+  def leaveSection(self,section):
+     self._id = self._id - 1 
+
+  def visitDocument(self,document):
+      self._output.write("""<!doctype html>
+<html>
+<head>
+<meta charset='UTF-8'><meta name='viewport' content='width=device-width initial-scale=1'>
+<title>Benchmarks</title>%s</head><body>\n""" % styleSheet)
+      self._output.write("<p>Run number %d on %s</p>\n" % (document.runid, str(document.date)))
+
+  def leaveDocument(self,document):
+    document.accept(HTMLToc(self._output))
+
+    self._output.write("</body></html>\n")
+
+
+
+
+
 # Command to get last runid 
 lastID="""SELECT runid FROM RUN ORDER BY runid DESC LIMIT 1
 """
@@ -27,6 +382,7 @@
 parser.add_argument('-b', nargs='?',type = str, default="bench.db", help="Benchmark database")
 parser.add_argument('-o', nargs='?',type = str, default="full.md", help="Full summary")
 parser.add_argument('-r', action='store_true', help="Regression database")
+parser.add_argument('-t', nargs='?',type = str, default="md", help="md,html")
 
 # For runid or runid range
 parser.add_argument('others', nargs=argparse.REMAINDER)
@@ -203,28 +559,11 @@
     vals =np.array([list(x) for x in list(result)])
     return(keepCols,vals)
 
-# Write columns in markdown format
-def writeColumns(f,cols):
-    colStr = "".join(joinit(cols,"|"))
-    f.write("|")
-    f.write(colStr)
-    f.write("|\n")
-    sepStr="".join(joinit([":-:" for x in cols],"|"))
-    f.write("|")
-    f.write(sepStr)
-    f.write("|\n")
 
-# Write row in markdown format
-def writeRow(f,row):
-    row=[str(x) for x in row]
-    rowStr = "".join(joinit(row,"|"))
-    f.write("|")
-    f.write(rowStr)
-    f.write("|\n")
 
 PARAMS=["NB","NumTaps", "NBA", "NBB", "Factor", "NumStages","VECDIM","NBR","NBC","NBI","IFFT", "BITREV"]
 
-def regressionTableFor(name,output,ref,toSort,indexCols,field):
+def regressionTableFor(name,section,ref,toSort,indexCols,field):
     data=ref.pivot_table(index=indexCols, columns='core', 
     values=[field], aggfunc='first')
        
@@ -233,7 +572,9 @@
     cores = [c[1] for c in list(data.columns)]
     columns = diff(indexCols,['NAME']) + cores
 
-    writeColumns(output,columns)
+    dataTable=Table(columns)
+    section.addTable(dataTable)
+
     dataForFunc=data.loc[name]
     if type(dataForFunc) is pd.DataFrame:
        for row in dataForFunc.itertuples():
@@ -242,11 +583,11 @@
               row=[row[0]] + row[1:]
            else: 
               row=list(row[0]) + row[1:]
-           writeRow(output,row)
+           dataTable.addRow(row)
     else:
-       writeRow(output,dataForFunc)
+       dataTable.addRow(dataForFunc)
 
-def formatTableByCore(output,testNames,cols,vals):
+def formatTableByCore(typeSection,testNames,cols,vals):
     if vals.size != 0:
        ref=pd.DataFrame(vals,columns=cols)
        toSort=["NAME"]
@@ -272,16 +613,20 @@
 
        for name in testNames:
            if args.r:
-              output.write("#### %s\n" % name)
+              testSection = Section(name)
+              typeSection.addSection(testSection)
 
-              output.write("##### Regression\n" )
-              regressionTableFor(name,output,ref,toSort,indexCols,'Regression')
+              regressionSection = Section("Regression")
+              testSection.addSection(regressionSection)
+              regressionTableFor(name,regressionSection,ref,toSort,indexCols,'Regression')
               
-              output.write("##### Max cycles\n" )
-              regressionTableFor(name,output,ref,toSort,indexCols,'MAX')
+              maxCyclesSection = Section("Max cycles")
+              testSection.addSection(maxCyclesSection)
+              regressionTableFor(name,maxCyclesSection,ref,toSort,indexCols,'MAX')
               
-              output.write("##### Max Reg Coef\n" )
-              regressionTableFor(name,output,ref,toSort,indexCols,'MAXREGCOEF')
+              maxRegCoefSection = Section("Max Reg Coef")
+              testSection.addSection(maxRegCoefSection)
+              regressionTableFor(name,maxRegCoefSection,ref,toSort,indexCols,'MAXREGCOEF')
 
            else:
               data=ref.pivot_table(index=indexCols, columns='core', 
@@ -292,8 +637,12 @@
               cores = [c[1] for c in list(data.columns)]
               columns = diff(indexCols,['NAME']) + cores
 
-              output.write("#### %s\n" % name)
-              writeColumns(output,columns)
+              testSection = Section(name)
+              typeSection.addSection(testSection)
+
+              dataTable=Table(columns)
+              testSection.addTable(dataTable)
+
               dataForFunc=data.loc[name]
               if type(dataForFunc) is pd.DataFrame:
                  for row in dataForFunc.itertuples():
@@ -302,23 +651,25 @@
                         row=[row[0]] + row[1:]
                      else: 
                         row=list(row[0]) + row[1:]
-                     writeRow(output,row)
+                     dataTable.addRow(row)
               else:
-                 writeRow(output,dataForFunc)
+                 dataTable.addRow(dataForFunc)
 
 # Add a report for each table
-def addReportFor(output,benchName):
+def addReportFor(document,benchName):
     nbElems = getNbElemsInBenchCmd(benchName)
     if nbElems > 0:
+       benchSection = Section(benchName)
+       document.addSection(benchSection)
        print("Process %s\n" % benchName)
-       output.write("# %s\n" % benchName)
        allTypes = getExistingTypes(benchName)
        # Add report for each type
        for aTypeID in allTypes:
            nbElems = getNbElemsInBenchAndTypeCmd(benchName,aTypeID)
            if nbElems > 0:
               typeName = getTypeName(aTypeID)
-              output.write("## %s\n" % typeName)
+              typeSection = Section(typeName)
+              benchSection.addSection(typeSection)
               ## Add report for each compiler
               allCompilers = getExistingCompiler(benchName,aTypeID)
               for compiler in allCompilers:
@@ -326,23 +677,31 @@
                   nbElems = getNbElemsInBenchAndTypeAndCompilerCmd(benchName,compiler,aTypeID)
                   # Print test results for table, type, compiler
                   if nbElems > 0:
-                     output.write("### %s (%s)\n" % compiler)
+                     compilerSection = Section("%s (%s)" % compiler)
+                     typeSection.addSection(compilerSection)
                      cols,vals=getColNamesAndData(benchName,compiler,aTypeID)
                      names=getTestNames(benchName,compiler,aTypeID)
-                     formatTableByCore(output,names,cols,vals)
+                     formatTableByCore(compilerSection,names,cols,vals)
            
 
 
 
 
 try:
-  with open(args.o,"w") as output:
       benchtables=getBenchTables()
       theDate = getrunIDDate(runid)
-      output.write("Run number %d on %s\n" % (runid, str(theDate)))
+      document = Document(runid,theDate)
       for bench in benchtables:
-          addReportFor(output,bench)
+          addReportFor(document,bench)
+      with open(args.o,"w") as output:
+          if args.t=="md":
+             document.accept(Markdown(output))
+          if args.t=="html":
+             document.accept(HTML(output))
+
 finally:
      c.close()
 
+    
+