#! /usr/bin/env /usr/bin/python
"""
returns HTML table representation of a tab-delimited text file
"""
# Copyright © 2004 Saugus.net, Inc.
# All Rights Reserved.

# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.

__version__='$Revision: 1.0 $'[11:-2]

from Products.PythonScripts.standard import structured_text

def tableInHTML(self,data,num_of_header_cols=1,num_of_header_rows=1,
                border=1,balanced=1,width='',filler='&nbsp;',summary='',
                use_structured_text=0):
  """Returns an HTML string representing a table."""
  class tableCell:
    """Instances of this class represent cells within a table."""
    def __init__(self,isHeader=0,data='',colSpan=1,rowSpan=1,centered=0):
      'Initializes a cell, the type defaults to "d" and the data defaults to "".'
      self.data=data
      self.colSpan=1
      self.rowSpan=1
      self.isHeader=isHeader
      self.centered=centered
    def __str__(self):
      'Returns the cell as a string.'
      spanSection=''
      if self.colSpan>1:
        spanSection+=' colspan="'+str(self.colSpan)+'"'
      if self.rowSpan>1:
        spanSection+=' rowspan="'+str(self.rowSpan)+'"'
      if self.centered:
        spanSection+=' align="center"'
      datum=self.getNum()
      if type(datum)==float:
        datum=str(round(datum,2))
      else:
        datum=self.getData()
      return '<t'+self.getType()+spanSection+'>'+datum+'</t'+self.getType()+'>'
    def getData(self):
      'Returns the data as a string.'
      return str(self.data)
    def getNum(self):
      'Returns the data as a numerical quantity.'
      num=0
      try:
        num=float(self.data)
        if num==int(num):
          num=int(num)
      except ValueError:
        pass
      return num
    def getType(self):
      'Returns the type as a string; either "d" for <td> or "h" for <th>.'
      if self.isHeader:
        return 'h'
      else:
        return 'd'
    def getColSpan(self):
      'Returns the column span as an int.'
      return self.colSpan
    def getRowSpan(self):
      'Returns the row span as an int.'
      return self.rowSpan
    def setType(self,newType):
      'Sets the type to either "d" for <td> or "h" for <th>.'
      if newType and newType!='d':
        self.isHeader=newType
      else:
        self.isHeader=0
    def setData(self,newData):
      'Sets the data to whatever is passed in.'
      self.data=str(newData)
    def incrementColSpan(self,increment=1):
      'Increments the column span.'
      self.colSpan=self.colSpan+increment
    def incrementRowSpan(self,increment=1):
      'Increments the row span.'
      self.rowSpan=self.rowSpan+increment

  class lineSummary:
    """Instances of this class track line statistical data."""
    def __init__(self):
      'Initializes a line.'
      self.numOfActualCells=0
      self.sum=0
      self.count=0
      self.max=None
      self.min=None
    def getAverage(self):
      'Returns the mean of all numerical cells within the line.'
      return float(self.sum)/self.count
    def getMin(self):
      'Returns the minima of all numerical cells within the line.'
      return self.min
    def getMax(self):
      'Returns the maxima of all numerical cells within the line.'
      return self.max
    def getSum(self):
      'Returns the sum of all numerical cells within the line.'
      return self.sum
    def getCount(self):
      'Returns the count of all numerical cells within the line.'
      return self.count
    def getNumOfCells(self):
      'Returns the count of all cells within the line.'
      return self.count
    def getAny(self,kind):
      if kind=='Total' or kind=='Sum':
        return self.getSum()
      elif kind=='Average':
        return self.getAverage()
      elif kind=='Min':
        return self.getMin()
      elif kind=='Max':
        return self.getMax()
      elif kind=='Count':
        return self.getCount()
      else:
        return self.getNumOfCells()
    def addCell(self,cell,dontTouchStats=0):
      'Updates the summary based upon the addition of a new cell.'
      self.numOfActualCells=self.numOfActualCells+1
      if not dontTouchStats:
        try:
          num=float(cell.data)
          if num==int(num):
            num=int(num)
          self.sum=self.sum+num
          self.count=self.count+1
          if self.max==None or self.max<num:
            self.max=num
          if self.min==None or self.min>num:
            self.min=num
        except ValueError:
          pass

  tableList=[]
  rowCount=0
  rowSummaries=[]
  colSummaries=[]
  specialColInstructionsList=[]
  specialRowInstructionsList=[]
  # looping through the rows; rowCount tracks them
  for row in data.splitlines():
    rowList=[]
    colCount=0
    rowSummaries.append(lineSummary())
    specialRowInstructionsList.append('')
    # checking if we're in the header
    if rowCount<num_of_header_rows:
      headerFromRow=1
    else:
      headerFromRow=0

    #looping through the columns; colCount tracks them
    columns=row.split('\t')
    for col in columns:
      # checking if we're in the header
      if colCount<num_of_header_cols:
        headerFromCol=1
      else:
        headerFromCol=0
      # if in the first row, build the row summaries list
      if rowCount==0:
        colSummaries.append(lineSummary())
        specialColInstructionsList.append('')

      currentCell=tableCell(data=col,isHeader=headerFromRow or headerFromCol,centered=balanced)
      if col=='' and (headerFromCol or headerFromRow):
        # this cell to be combined with another cell
        if colCount==0 and rowCount>0:
          #Must combine with the cell above
          tableList[-1][0].rowSpan+=1
        elif colCount==len(columns)-1 and rowCount>0:
          #Must combine with the cell above
          tableList[-1][-1].rowSpan+=1
        else:
          #Must combine with the cell to the left
          rowList[-1].colSpan+=1
        colSummaries[colCount].addCell(currentCell,dontTouchStats=1)
        rowSummaries[rowCount].addCell(currentCell,dontTouchStats=1)
      elif col=='':
        # contents of this cell to be filled
        currentCell.setData(filler)
        rowList.append(currentCell)
        colSummaries[colCount].addCell(currentCell)
        rowSummaries[rowCount].addCell(currentCell)        
      elif col=='##':
        # contents of this cell to be calculated
        if specialRowInstructionsList[rowCount]:
          datum=colSummaries[colCount].getAny(specialRowInstructionsList[rowCount])
          currentCell.setData(datum)
          colSummaries[colCount].addCell(currentCell,dontTouchStats=1)
          rowSummaries[rowCount].addCell(currentCell)
          currentCell.setData('<strong>'+str(datum)+'</strong>')
        elif specialColInstructionsList[colCount]:
          datum=rowSummaries[rowCount].getAny(specialColInstructionsList[colCount])
          currentCell.setData(datum)
          colSummaries[colCount].addCell(currentCell)
          rowSummaries[rowCount].addCell(currentCell,dontTouchStats=1)
          currentCell.setData('<strong>'+str(datum)+'</strong>')          
        rowList.append(currentCell)
      else:
        rowList.append(currentCell)
        if headerFromCol or headerFromRow:
          colSummaries[colCount].addCell(currentCell,dontTouchStats=1)
          rowSummaries[rowCount].addCell(currentCell,dontTouchStats=1)
        else:
          colSummaries[colCount].addCell(currentCell)
          rowSummaries[rowCount].addCell(currentCell)
        # this cell is ordinary data
        if use_structured_text:
          currentCell.setData(structured_text(currentCell.getData()))
      for specialCmd in ['Total','Average','Min','Max','Count']:
        if col.find(specialCmd)!=-1:
          if colCount==0:
            specialRowInstructionsList[rowCount]=specialCmd
          elif rowCount==0:
            specialColInstructionsList[colCount]=specialCmd
      colCount+=1

    tableList.append(rowList)
    rowCount+=1
  
  attrs=''
  if summary:
    attrs+=' summary="%s"'%summary
  if border:
    attrs+=' border="%d"'%border
  if width:
    attrs+=' width="%s"'%width
  tableHTML='<table%s>\n<thead>\n'%attrs
  rowNum=0
  for row in tableList:
    if rowNum==num_of_header_rows:
      tableHTML+='</thead>\n<tbody>\n'
    rowNum+=1
    tableHTML+='<tr>'
    for cell in row:
      tableHTML+=str(cell)
    tableHTML+='</tr>\n'
  tableHTML+='</tbody>\n</table>\n'
  return tableHTML


# tests
# for testing purposes
if __name__=='__main__':
  data="""rows	big				Totals
	col 1	col 2	col 3	col 4	
row 1	1	2	3	4	##
row 2	5	6	7	8	##
row 3	9	10	11		##
Totals	##	##	##	##	##
Min	##	##	##	##	##
Max	##	##	##	##	##
Average	##	##	##	##	##
Count	##	##	##	##	##"""
  print tableInHTML('',data,num_of_header_cols=1,num_of_header_rows=2,border=1,balanced=1,width='75%')

