# Top class:
# read xml file
# generate html headers
# get root node
# pass node to container element

# Container class:
# loop over elements
# pass each element to appropriate subclass

# Table class:
# produce table

# Graph class:
# produce graph

# Text class:
# produce text

#Questions: does reading cause formatting straight away?
#Or do we store the content for later? Yes.

from lxml import etree
from StringIO import StringIO
import os

from CCP4ErrorHandling import *

XRTNS = "{http://www.ccp4.ac.uk/xrt}"
CCP4NS = "http://www.ccp4.ac.uk/ccp4ns"
# From http://www.w3.org/QA/2002/04/valid-dtd-list.html
DOCTYPE = '''<?xml version="1.0"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns:xsi="http://www.w3.org/1999/xhtml"></html>
    '''
#DOCTYPE = '''<?xml version="1.0" encoding="utf-8"?>\n<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >'''

XHTMLNS = "{http://www.w3.org/1999/xhtml}"
CURRENT_CSS_VERSION = '0.1.0'

USEQTICONS = True


def htmlBase():
  # This is a bad i2 dependence which we are trying to avoid in the Reportparser
  # Needed to support use of images for Reference icon etc
  import CCP4Modules
  port = CCP4Modules.HTTPSERVER().port
  if port is None: port = 43434
  htmlBase = 'http://127.0.0.1:'+str(port)+'/report_files/'+CURRENT_CSS_VERSION
  return htmlBase

def htmlDoc(htmlBase=None,cssVersion=None,cssFile=None,jsFile=None,title=None, additionalJsFiles=None, additionalCssFiles=None, requireDataMain=None, additionalScript=None):
  import CCP4Utils
  import sys
  if cssFile is None:cssFile = 'xreport.css'
  if jsFile is None: jsFile = 'xreport.js'
  doc = etree.parse(StringIO(DOCTYPE))
  html = doc.getroot()
  if htmlBase is None: htmlBase = '../../report_files'
  
  # Default ot latest htmlBaseVersion or get the latest minor version
  # for the version proposed 
  if cssVersion is not None: cssVersion = getLastestMinorVersion(cssVersion)
  if htmlBase.endswith('report_files'):
    if cssVersion is None: cssVersion = CURRENT_CSS_VERSION
    #print 'htmlDoc cssVersion',cssVersion
    htmlBase = htmlBase + '/'+cssVersion
  head = etree.Element('head')
  if title is not None:
    titleEle =  etree.Element('title')
    titleEle.text=title
    head.append(titleEle)
  
  allJsFiles = [jsFile]
  if additionalJsFiles is not None: allJsFiles += additionalJsFiles
  for aJsFile in allJsFiles:
    script = etree.Element('script')
    script.set('src',htmlBase+'/'+aJsFile)
    head.append(script)

  allCssFiles= [cssFile,'jspimple.css']
  if additionalCssFiles is not None: allCssFiles += additionalCssFiles
  for aCssFile in allCssFiles:
    link = etree.Element('link')
    link.set('rel','stylesheet')
    link.set('type','text/css')
    link.set('href',htmlBase+'/'+aCssFile)
    head.append(link)

  if additionalScript is not None:
    script = etree.Element('script')
    script.text = additionalScript
    head.append(script)

  if requireDataMain is not None:
    script = etree.Element('script')
    script.set('src',htmlBase+'/require.min.js')
    script.set('data-main',htmlBase+'/'+requireDataMain)
    head.append(script)
  html.append(head)

  body = etree.Element('body')
  html.append(body)
  return doc

def testPathExists(self,relPath):
  return True

def getLastestMinorVersion(baseIn):
  import glob
  import CCP4Utils
  testSplit = baseIn.split('.')
  globList = glob.glob(os.path.join(CCP4Utils.getCCP4I2Dir(),'docs','report_files','*'))
  lastMinor = -1
  for path in globList:
    v = os.path.split(path)[1].split('.')
    if v[0] == testSplit[0] and  v[1] == testSplit[1]:
      minor = int(v[2])
      lastMinor = max(lastMinor,minor)
  if lastMinor>=0:
    return testSplit[0]+'.'+testSplit[1]+'.'+str(lastMinor)
  else:
    return None

def toBoolean(text):
  try:
    i = int(text)
    return bool(i)
  except:
    return bool(text)

def getChildObject(child,xmlnode,jobInfo={},report=None):
        if   child.tag == XRTNS+"table":
          obj = Table( child, xmlnode ) 
        elif child.tag == XRTNS+"graphgroup":
          obj = GraphGroup( child, xmlnode, jobInfo  ) 
        elif child.tag == XRTNS+"graph":
          obj = Graph( child, xmlnode, jobInfo  ) 
        elif child.tag == XRTNS+"objectgallery":
           obj = ObjectGallery( child, xmlnode, jobInfo  )
        elif child.tag == XRTNS+"results":
          obj = Results(  child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"fold":
          obj = Fold(  child, xmlnode ) 
        elif child.tag == XRTNS+"text":
          obj = Text( child, xmlnode ) 
        elif child.tag == XRTNS+"status":
          obj = Status( child, xmlnode ) 
        elif child.tag == XRTNS+"if":
          obj = IfContainer(  child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"ifnot":
          obj = IfContainer(  child, xmlnode,jobInfo, False ) 
        elif child.tag == XRTNS+"loop":
          obj = Loop(  child, xmlnode,jobInfo  ) 
        elif child.tag == XRTNS+"copy":
          obj = Copy( child, xmlnode ) 
        elif child.tag == XRTNS+"inputdata":
          obj = InputData( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"outputdata":
          obj = OutputData( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"importedfiles":
          obj = ImportedFiles( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"title":
          obj = Title( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"jobdetails":
          obj = JobDetails( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"picture":
          obj = Picture(  child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"launch":
          obj = Launch( child, xmlnode, jobInfo ) 
        elif child.tag == XRTNS+"drilldown":
          obj = DrillDown( jobInfo ) 
        elif child.tag == XRTNS+"comment":
          obj = None
        else:
          obj = Generic( child, xmlnode )
        return obj
      
def findChildren(obj,cls,id=None):
    retList = []
    if not hasattr(obj,'children'): return retList
    for child in obj.children:
      #print 'findChildren',child, isinstance(child,cls),isinstance(child,Container)
      if isinstance(child,cls):
        retList.append(child)
      elif isinstance(child,Container):
        retList.extend(findChildren(child,cls,id))
    return retList

def applySelect(xrtnode,xmlnode,jobInfo={}):
  # Test all nodes in xrtnode for a 'select' attribute
  # Find the value in xmlnode and copy into xrtnode node.text and remove the select attribute
  # Currently this used to process the plotting input
  if xrtnode.get('select') is not None:
    if xmlnode is not None:
      values = xmlnode.xpath(xrtnode.get('select'))
      if len(values)>0:
        xrtnode.text = str(values[0].text)
      else:
        xrtnode.text = 'NO DATA AVAILABLE'
    del xrtnode.attrib['select']
  elif xrtnode.get('database') is not None:
    pathList = xrtnode.get('database').split('/')
    while pathList.count('')>0: pathList.remove('')
    data = jobInfo.get(pathList[0],{})
    for path in pathList[1:]: data = data.get(path,{})
    if not isinstance(data,dict):
      if isinstance(data,list):
        xrtnode.text = str(data[0])
      else:
        xrtnode.text = str(data)
    del xrtnode.attrib['database']
  for child in xrtnode:
    applySelect(child,xmlnode,jobInfo)
  return xrtnode

def saveToFile(tree,fileName):
    text = etree.tostring(tree, pretty_print=True, xml_declaration=True)
    f = open(fileName,'w')
    f.write(text)
    f.close()

def xpathEval( srcexpr, xmlnode ):
  """Evaluate a python expression containing xpath elements deliminated by {}. The xpath expressions are evaluated and return strings which are substituted into the expression. The expression is evaluated and the result returned."""
  tgtexpr = ""
  for i in range(srcexpr.count('{')):
    i0 = srcexpr.find('{')
    if i0 < 0: break
    i1 = srcexpr.find('}',i0)
    if i1 < 0: break
    tgtexpr += srcexpr[:i0]
    xp = srcexpr[i0+1:i1]
    nodes = xmlnode.xpath( xp )
    tgtexpr += '"""'
    for node in nodes:
      tgtexpr += node.text
    tgtexpr += '"""'
    srcexpr = srcexpr[i1+1:]
  tgtexpr += srcexpr
  return eval( tgtexpr )


class SearchableElement(etree.ElementBase):
   def select(self,path):
     l = self.xpath(path)
     if len(l)>0:
       if '@' in path:
         return l[0]
       else:
         return l[0].text
     else:
       return ''
   def ifselect(self,path,default=False):
     l = self.xpath(path)
     if len(l)>0:
       return toBoolean(l[0].text)
     else:
       return default
   def haspath(self,path):
     l = self.xpath(path)
     if len(l)>0:
       return True
     else:
       return False
   def xpath0(self,path):
     l = self.xpath(path)
     if len(l)>0:
       return l[0]
     else:
       None
   def xpath1(self,path):
     l = self.xpath(path)
     if len(l)>0:
       return l[0]
     else:
       return PARSER().makeelement("dummy")

     

def PARSER():
  if I2XmlParser.insts is None:
    lookup = etree.ElementDefaultClassLookup(element=SearchableElement)
    I2XmlParser.insts = etree.XMLParser(remove_blank_text=True)
    I2XmlParser.insts.set_element_class_lookup(lookup)
  return I2XmlParser.insts

class I2XmlParser:
  insts = None
  
class ReportClass(object):
    def __init__(self, *arg, **kw):
        super(ReportClass,self).__init__()
        self.id = kw.get('id',None)
        self.label = kw.get('label',None)
        self.title = kw.get('title',None)
        self.class_ =  kw.get('class_',None)
        self.style =  kw.get('style',None)
        self.parent = kw.get('parent',None)
        self.outputXml = kw.get('outputXml',False)
        self.internalId = kw.get('internalId',None)
        if self.internalId is None:
            report = self.getReport()
            if report is not None:
                className = type(self).__name__
                instanceCount = len(findChildren(report,type(self)))
                self.internalId = className+'_'+str(instanceCount)
            else:
                className = type(self).__name__
                self.internalId = className + '_' + str(Report.elementCount)
                Report.elementCount += 1
    def getReport(self):
        if hasattr(self,'parent') and self.parent is not None: return self.parent.getReport()
        return None
    def data_id(self):
        return 'data_'+self.internalId
    def data_url(self):
        return './tables_as_xml_files/'+self.data_id()+'.xml'


# Container class - base class for reports and folds
class Container(ReportClass):
  tag = 'container'
  ERROR_CODES = { 1 : { 'severity': SEVERITY_WARNING, 'description' : 'Failed to interpret xrt node tag' },
                  2 : { 'description' : 'Can not create IfContainer instance without xrtnode argument (it should not be used in Python mode)' },
                  3 : { 'description' : 'Can not create Loop instance without xrtnode argument (it should not be used in Python mode)' },
                  4 : { 'description' : 'Unable to create RTF file - unable to import PyQt4' }
                  }
  def __init__( self,  xrtnode=None, xmlnode=None, jobInfo = {} , **kw ):
    super(Container,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo = jobInfo, **kw)
    if getattr(self,'errReport',None) is None: self.errReport = CException()
    self.children = []
    self.jobInfo = {}
    self.jobInfo.update(jobInfo)
    self.xrtnode = xrtnode
    self.xmlnode= xmlnode
    self.text = ''
    self.tail = ''
    self._scriptErrorReport = None
    if xrtnode is not None: self.interpretXrt(xrtnode=xrtnode)

  def interpretXrt(self,xrtnode=None):
    if xrtnode is not None:
      if xrtnode.tag in [ XRTNS+'remove', 'remove']:
        self.tag = XRTNS+'remove'
      else:
        self.tag = XHTMLNS+'root'
        # Initialising 'ifContainer' may skip initialising children if the tag is changed to dummy      
        for child in xrtnode:
          obj = getChildObject(child,self.xmlnode,self.jobInfo)
          if obj is None:
            self.errReport.append(self.__class__,1,str(child.tag))
          else:
            self.children.append( obj )       
      self.text = xrtnode.text
      self.tail = xrtnode.tail
    else:
      self.text = kw.get('text',None)
      self.tail = kw.get('tail',None)
    if self.text is None: self.text = ''
    if self.tail is None: self.tail = ''

  def append(self,child):
    if isinstance(child,str):
      self.children.append(Generic(xmlnode=self.xmlnode,text=child))
    else:
      self.children.append(child)
      
  def insert(self,indx,child):
    if isinstance(child,str):
      self.children.insert(indx,Generic(xmlnode=self.xmlnode,text=child))
    else:
      self.children.insert(indx,child)
  

  def __len__(self):
    return len(self.children)

  def addObjectOfClass(self, classOfObject, xrtnode=None,xmlnode=None,jobInfo=None,**kw):
    if xrtnode is None: xrtnode=self.xrtnode
    if xmlnode is None: xmlnode = self.xmlnode
    if jobInfo is None: jobInfo = self.jobInfo
    obj = classOfObject(xrtnode=xrtnode, xmlnode = xmlnode, jobInfo = jobInfo, parent=self, **kw)
    self.children.append(obj)
    return obj

  def addText(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Text, xrtnode, xmlnode, jobInfo, **kw)

  def addPre(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Pre, xrtnode, xmlnode, jobInfo, **kw)

  def addGraph(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Graph, xrtnode, xmlnode, jobInfo, **kw)

  def addProgress(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Progress, xrtnode, xmlnode, jobInfo, **kw)

  def addFlotGraph(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(FlotGraph, xrtnode, xmlnode, jobInfo, **kw)

  def addGraphGroup(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(GraphGroup, xrtnode, xmlnode, jobInfo, **kw)

  def addFlotGraphGroup(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(FlotGraphGroup, xrtnode, xmlnode, jobInfo, **kw)

  def addDrawnDiv(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(DrawnDiv, xrtnode, xmlnode, jobInfo, **kw)

  def addObjectGallery(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(ObjectGallery, xrtnode, xmlnode, jobInfo, **kw)

  def addGraphLineChooser(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(GraphLineChooser, xrtnode, xmlnode, jobInfo, **kw)

  def addPictureGroup(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(PictureGroup, xrtnode, xmlnode, jobInfo, **kw)

  def addDiv(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Div, xrtnode, xmlnode, jobInfo, **kw)

  def addTable(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Table, xrtnode, xmlnode, jobInfo, **kw)

  def addPicture(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Picture, xrtnode, xmlnode, jobInfo, **kw)

  def addTitle(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Title, xrtnode, xmlnode, jobInfo, **kw)

  def addLaunch(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Launch, xrtnode, xmlnode, jobInfo, **kw)

  def addDownload(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Download, xrtnode, xmlnode, jobInfo, **kw)

  def addResults(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Results, xrtnode, xmlnode, jobInfo, **kw)

  def addFold(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Fold, xrtnode, xmlnode, jobInfo, **kw)

  def addCopy(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Copy, xrtnode, xmlnode, jobInfo, **kw)
  
  def addHelp(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
      return self.addObjectOfClass(Help, xrtnode, xmlnode, jobInfo, **kw)

  def getPictures(self):
    rv = []
    for child in self.children:
      if isinstance(child,Picture):
        rv.append(child.picDefFile.__str__())
      elif isinstance(child,(Container,Loop)):
        rv.extend(child.getPictures())
    #print 'Container.getPictures',rv
    return rv
  
  def as_html( self ):
    s = ""
    if self.text:
      s += self.text
    for child in self.children:
      s += child.as_html() + "\n"
    if self.tail:
      s += self.tail
    return s

  def as_etree(self):
    # Test if marked for removal or tag is not str (probably <!-- --> comment)
    if self.tag == XRTNS+'remove' or not isinstance(self.tag,str): return None
    root = etree.Element(self.tag)
    root.text = self.text
    root.tail = self.tail
    if self.id is not None: root.set('id',self.id)
    if self.class_ is not None: root.set('class',self.class_)
    if self.style is not None: root.set('style',self.style)
    for child in self.children:
      #print 'Container.as_etree',child
      tree = child.as_etree()
      #print 'Container.as_etree',child,tree,tree.tag,len(tree)
      if tree is not None:
        if str(tree.tag) in ['root',XHTMLNS+'root']:
          #print 'Container.as_etree add root',len(root),root.
          root.extend(tree)
          
        elif tree.tag == XRTNS+'dummy':
          # Dont output tags around this (always Text?) element
          # Need to put the text content in either self.text or preceeding siblings tail
          if len(root)>=1:
            root[-1].tail += child.getText()
          else:
            root.text+= child.getText()
        else:
          root.append(tree)
    return root

  def graph_data_as_rtf(self,fileName=None):
    try:
      from PyQt4 import QtGui,QtCore
    except:
      raise CException(self.__class__,4,fileName)

    document = QtGui.QTextDocument()
    graphList = findChildren(self,Graph)
    for graph in graphList:
      table = graph.data_as_rtf(document=document)

    writer = QtGui.QTextDocumentWriter()
    writer.setFormat(QtCore.QByteArray('ODF'))
    writer.setFileName(fileName)
    writer.write(document)

  def errorReport(self):
    err = CException()
    err.extend(self.errReport)
    for child in self.children:
      if getattr(child,'errReport',None) is not None:
        err.extend(child.errorReport())
    return err

  def loadXmlFile(self,fileName):
    import CCP4Utils
    text = CCP4Utils.readFile(fileName)
    ele = etree.fromstring(text,PARSER())
    return ele

  def scriptErrorReport(self):
    import CCP4ErrorHandling,CCP4File
    if self._scriptErrorReport is None:
      self._scriptErrorReport = CCP4ErrorHandling.CErrorReport()
      root = self.jobInfo.get('fileroot',None)
      print 'scriptErrorReport root',root
      if root is not None:
        filePath = os.path.join(root,'diagnostic.xml')
        if os.path.exists(filePath):
          i2xml = CCP4File.CI2XmlDataFile(filePath)
          self._scriptErrorReport.setEtree(i2xml.getBodyEtree())
    return self._scriptErrorReport

  
# If class - container for conditional content
class IfContainer( Container ):
  def __init__( self,  xrtnode=None, xmlnode=None , jobInfo={}, target = True, **kw ):
    if xrtnode is None:
      raise CException(self.__class__,2)
    #print 'ReportParser IfContainer',xmlnode.xpath( xrtnode.get( "select" ) ),type(xmlnode.xpath( xrtnode.get( "select" ) ))
    if xmlnode is not None and len(xmlnode.xpath( xrtnode.get( "select" ) )) != 1:
      # Failed to find the 'if' parameter in the output so do not put anything in report
      Container.__init__ ( self, etree.Element(XRTNS+"remove"), xmlnode, jobInfo, **kw )
      return
    try:
      value = toBoolean(xmlnode.xpath( xrtnode.get( "select" ) )[0].text)
    except:
      Container.__init__ ( self,  etree.Element(XRTNS+"remove"), xmlnode, jobInfo , **kw )
      return
    #print 'ReportParser IfContainer from program output:',value,'target:',target
    if value == target:
      Container.__init__ ( self, xrtnode, xmlnode, jobInfo )
    else:
      Container.__init__ ( self, etree.Element(XRTNS+"remove"), xmlnode, jobInfo, **kw  )

class Loop(ReportClass):

  def __init__( self,  xrtnode=None, xmlnode=None, jobInfo={}, **kw):
    super(Loop,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
    if xrtnode is None:
      raise CException(self.__class__,3)    
    import copy
    self.children = []
    self.text = xrtnode.text
    self.tail = xrtnode.tail
    selectStr = xrtnode.get('select')
    #print 'Loop.init',selectStr
    if selectStr is not None and xmlnode is not None:
      selectedNodes = xmlnode.xpath( selectStr )
      #print 'Loop.init selectedNodes',selectedNodes
      for node in selectedNodes:
        for child in xrtnode:
          childCopy = copy.deepcopy(child)
          obj = getChildObject(childCopy,node,jobInfo)
          if obj is not None: self.children.append( obj )
          #self.children[-1].text = xrtnode.text
          #self.children[-1].tail = xrtnode.tail

      
  def as_etree(self):
    root = etree.Element('root')
    root.text = self.text
    root.tail = self.tail
    if self.id is not None: root.set('id',self.id)
    if self.class_ is not None: root.set('class',self.class_)
    for child in self.children:
      tree = child.as_etree()
      if str(tree.tag) in ['root',XHTMLNS+'root']:
        root.extend(tree)
      elif tree.tag == XRTNS+'dummy':
        # Dont output tags around this (always Text?) element
        # Need to put the text content in either self.text or preceeding siblings tail
        if len(root)>=1:
          root[-1].tail += child.getText()
        else:
          root.text+= child.getText()
      else:
        root.append(tree)
    return root

  def getPictures(self):
    rv = []
    for child in self.children:
      if isinstance(child,Picture):
        rv.append(child.picDefFile.__str__())
      elif isinstance(child,(Container,Loop)):
        rv.extend(child.getPictures())
    #print 'Loop.getPictures',rv
    return rv

# Report class - container for ccp4 html report
class Report( Container ):
  TASKNAME = ''
  TASKVERSION = '0.0.0'
  RUNNING = False
  WATCHED_FILE = None
  FAILED = False
  USEPROGRAMXML = True
  SEPARATEDATA=False
  elementCount = 1
  ERROR_CODES = { 0 : { 'severity' : SEVERITY_OK,
                             'description' : 'OK' },
                   101 : {'description' : 'Import xrt file does not exist'},
                   102 : {'description' : 'Failed to read import xrt file'},
                   103 : {'description' : 'Failed to find insert xrt element in program file'},
                   104 : {'description' : 'Failed to find insert element in imported xrt file'},
                   105 : {'description' : 'Failed loading job info for jobId'},
                   106 : {'description' : 'Failed loading XML file'},
                   107 : {'description' : 'Failed creating csv file'},
                   108 : {'description' : 'Failed creating xml data file'},
                  }
   
  def __init__(self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw ):
    Container.__init__(self, xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
    self.tag = 'report'
    self.pictureQueue = None
    self.standardise = kw.get('standardise',False)
    self.jobNumber = kw.get('jobNumber',None)
    self.jobStatus = kw.get('jobStatus',None)
    self.cssFile = kw.get('cssFile',None)
    self.jsFile = kw.get('jsFile',None)
    self.additionalCssFiles = kw.get('additionalCssFiles',None)
    self.additionalJsFiles = kw.get('additionalJsFiles',None)
    self.additionalScript = kw.get('additionalScript',None)
    self.requireDataMain = kw.get('requireDataMain','mosflmApp')
    self.referenceList = []

    self.htmlBase =  kw.get('htmlBase',None)
    self.cssVersion =  kw.get('cssVersion',None)
    
    if kw.has_key('jobId'):
      try:
        import CCP4ReportGenerator
        jobInfo = CCP4ReportGenerator.getReportJobInfo(kw['jobId'])
        self.jobInfo.update(jobInfo)
      except:
        self.errReport.append(self.__class__,105,kw['jobId'])
        return
      
    if xmlnode is None and kw.has_key('xmlFile'):
      try:
        text = open( kw['xmlFile'] ).read()
        self.xmlnode = etree.fromstring( text, PARSER() )
      except Exception as e:
        self.errReport.append(self.__class__,106, 'Reading file: '+str(kw['xmlFile'])+'\n'+str(e))
        return
    
    if xrtnode is not None:
      xrtnode = self.removeComments(xrtnode)
      if xmlnode is not None: xrtnode = self.makeInserts(xrtnode, xmlnode)
      if self.standardise and str(xrtnode.tag) == 'report':
        xrtnode = self.standardiseXrtReport(xrtnode)
      Container.interpretXrt(self,xrtnode=xrtnode)
    #self._makeMgPicture = None

  def removeComments(self,xrtnode):
    for ele in xrtnode.iterfind('.//'+XRTNS+'comment'):
      #print 'Removing comment',ele.text
      ele.getparent().remove(ele)
    return xrtnode
      
  def makeInserts(self,xrtnode, xmlnode):
    for insertEle in xrtnode.iterfind('.//'+XRTNS+'insertXrt'):
      ele = None
      if insertEle.get('filename') is not None:
        import CCP4Utils
        fileName = insertEle.get('filename')
        if fileName[0:8] == '$CCP4I2/':
          fileName = os.path.join(CCP4Utils.getCCP4I2Dir(),fileName[8:])
        #print 'Report.makeInserts fileName',fileName
        if not os.path.exists(fileName):
          self.errReport.append(self.__class__,101,fileName)
        else:
          try:
            ele = etree.fromstring( open(fileName ).read(), PARSER() )
          except:
            self.errReport.append(self.__class__,102,fileName)
          else:
            # Look for sub-element of the inserted file
            if insertEle.get('select') is not None:
              path = insertEle.get('select')
              ele0 = ele.find(path)
              if ele0 is None:
                self.errReport.append(self.__class__,104,'file: '+str(fileName)+' xpath: '+str(path))
              else:
                ele = ele0
      # This is looking in the program output for xrt - dubious.
      elif insertEle.get('select') is not None:
        path = insertEle.get('select')
        ele = xmlnode.find(path)
        if ele is None:
          self.errReport.append(self.__class__,103,path)
          
      if ele is not None:      
        for child in ele.getchildren(): insertEle.addnext(child)
        insertEle.getparent().remove(insertEle)
         
      #print etree.tostring(xrtnode,pretty_print=True)
    return xrtnode

  def containsPictures(self):
    #print 'Report.containsPictures',self._makeMgPicture
    #if self._makeMgPicture is None or len(self._makeMgPicture.pictureQueue)==0:
    if self.pictureQueue is None:
      self.pictureQueue = self.getPictures()
      
    if len(self.pictureQueue) == 0:
      return False
    else:
      return True


  def makeMgPicture(self):
    if self._makeMgPicture is None:
      
        import CCP4MakeMgPicture
        self._makeMgPicture = CCP4MakeMgPicture.CMakeMgPicture(jobInfo=self.jobInfo)
      
    return self._makeMgPicture

  def standardisePythonReport(self):
    self.children.insert(0,Title(jobInfo = self.jobInfo))
    for cls in (InputData,OutputData,JobDetails):
      add = True
      for child in self.children:
        if isinstance(child,cls):
          add = False
          break
      if add: self.children.append(cls(jobInfo=self.jobInfo))
      
    # If there is not list of references try adding the default for the task
    if len(self.referenceList)==0: self.addTaskReferences()
    if len(self.referenceList)>0:
      fold = Fold(label='Bibliographic references',brief='Biblio',jobInfo=self.jobInfo)
      fold.children.extend(self.referenceList)
  
      if isinstance(self.children[-1],JobDetails):
        self.children.insert(-1,fold)
      else:
        self.children.append(fold)
    
               
  def standardiseXrtReport(self,report):

    #if report.find(XRTNS+'title') is None:
    #  titleEle = etree.Element(XRTNS+'title')
    #  report.insert(0,titleEle)      
    if report.find(XRTNS+'inputdata') is None:
      inEle = etree.Element(XRTNS+'inputdata')
      report.append(inEle)
    if report.find(XRTNS+'outputdata') is None:
      outEle = etree.Element(XRTNS+'outputdata')
      report.append(outEle)
    #if report.find(XRTNS+'drilldown') is None:
    #  jobEle = etree.Element(XRTNS+'drilldown')
    #  report.append(jobEle)
    if report.find(XRTNS+'jobdetails') is None:
      jobEle = etree.Element(XRTNS+'jobdetails')
      report.append(jobEle)

    return report


  def as_html_file(self,fileName,pretty_print=True,htmlBase=None,cssVersion=None):
    import CCP4Utils
    text = self.as_html(htmlBase=htmlBase,cssVersion=cssVersion)
    if pretty_print:
      try:
        from bs4 import BeautifulSoup
        soup=BeautifulSoup(text) 
        prettyHTML=soup.prettify()
        #print 'PRINTING BeautifulSoup'
        CCP4Utils.saveFile(fileName=fileName,text=prettyHTML)
      except:
        CCP4Utils.saveFile(fileName=fileName,text=text)
    else:
      CCP4Utils.saveFile(fileName=fileName,text=text)

  def as_html(self, htmlBase=None,cssVersion=None):
    tree = self.as_etree(htmlBase,cssVersion=cssVersion)
    text = etree.tostring(tree, pretty_print=True)
    return text

  def as_etree(self, htmlBase=None,cssVersion=None):
    if self.standardise: self.standardisePythonReport()
    if cssVersion is None: cssVersion = self.cssVersion
    if htmlBase is not None:
      doc = htmlDoc(htmlBase=htmlBase,cssFile=self.cssFile,jsFile=self.jsFile,cssVersion=cssVersion,title=self.getTitle(),additionalJsFiles=self.additionalJsFiles, additionalCssFiles=self.additionalCssFiles, requireDataMain=self.requireDataMain,additionalScript=self.additionalScript)
    elif self.jobNumber is not None:
      nSub = self.jobNumber.count('.')
      htmlBase = '../..'
      for n in range(nSub): htmlBase = htmlBase + '/..'
      htmlBase = htmlBase + '/report_files'
      doc = htmlDoc(htmlBase=htmlBase,cssFile=self.cssFile,jsFile=self.jsFile,cssVersion=cssVersion,title=self.getTitle(),additionalJsFiles=self.additionalJsFiles, additionalCssFiles=self.additionalCssFiles, requireDataMain=self.requireDataMain,additionalScript=self.additionalScript)
    else:
      doc = htmlDoc(cssFile=self.cssFile,jsFile=self.jsFile,cssVersion=cssVersion,title=self.getTitle(), additionalJsFiles=self.additionalJsFiles, additionalCssFiles=self.additionalCssFiles, requireDataMain=self.requireDataMain, additionalScript=self.additionalScript)
    body = doc.getroot().xpath('./body')[0]
    #print 'Report.as_etree',self,etree.tostring(Container.as_etree(self),pretty_print=True)
    tree = Container.as_etree(self)
    body.text = tree.text
    body.tail = tree.tail
    body.extend(tree)
    if self.style is not None: body.set('style',self.style)
    if self.standardise: self.addLinks(body)

    return doc

  
  def addLinks(self,body):
    #links = etree.Element('p')
    #links.set('class','links')
    eleTree = etree.parse(StringIO('<ccp4:ccp4_data xmlns:ccp4="'+CCP4NS+'"></ccp4:ccp4_data>'))
    links = eleTree.getroot()
    links.set('id','links')
    links.set('type','CLinksInReport')
    links.set('style','display:none;')
    linkList =  [ ['inputData','Input data'],['outputData','Output data'] ]
    folds = findChildren(self,Fold)
    for item in folds: linkList.append([item.label,item.label])
    drillDownList = findChildren(self,DrillDown)
    if len(drillDownList)>0: linkList.append(['Show Sub-Jobs','Show Sub-Jobs'])
    linkList.append(['jobDetails','Job details'])
    #print 'addLinks linkList',linkList
    for href,label in linkList:
      anchor = etree.Element('a')
      anchor.set('href','#'+href)
      anchor.text = label
      links.append(anchor)   
    title = body.find("h4")
    #print 'addLinks title',title
    if title is None:
      body.insert(0,links)
    else:
      body.insert(2,links)


  def getTitle(self):
    if self.jobInfo.has_key('jobnumber') and self.jobInfo.has_key('tasktitle'):
      title = self.jobInfo['jobnumber']+' '+self.jobInfo['tasktitle']
    else:
      title =  'CCP4 Report'

  def makeCSVFiles(self,directory=None):
    import CCP4Utils
    if directory is None: directory = os.getcwd()
    graphList = findChildren(self,Graph)
    flotGraphList = findChildren(self,FlotGraph)
    tableList = findChildren(self,Table)
    for name,objList in [ ['graph',graphList],['table',tableList],['flotgraph',flotGraphList]]:
      for obj in objList:
        if getattr(obj,'outputCsv',True):
          title = str(obj.id)
          if obj.title is not None: title = title +'_'+CCP4Utils.safeOneWord(obj.title)
          fileName = os.path.normpath(os.path.join(directory,title+'.csv'))
          #print 'Report.makeCSVFiles fileName',fileName
          self.errReport.extend(obj.data_as_csv(fileName=fileName))
    
  def makeXMLFiles(self,directory=None):
    import CCP4Utils
    if directory is None: directory = os.getcwd()
    for ofClass in [Table, FlotGraph, FlotGraphGroup, Progress, Text, Pre]:
        objList = findChildren(self, ofClass)
        for obj in objList:
            if getattr(obj,'outputXml',False):
                if ofClass == FlotGraphGroup:
                    fileRoot = os.path.normpath(os.path.join(directory,obj.data_url()))
                    self.errReport.extend(obj.data_as_xmls(fileRoot=fileRoot))
                else:
                    fileName = os.path.normpath(os.path.join(directory,obj.data_url()))
                    #print 'Report.makeXMLFiles fileName',fileName
                    self.errReport.extend(obj.data_as_xml(fileName=fileName))

  def clearXMLFiles(self,directory=None):
    import CCP4Utils
    if directory is None: directory = os.getcwd()
    for ofClass in [Table, FlotGraph, Progress, Pre]:
        objList = findChildren(self, ofClass)
        for obj in objList:
            if getattr(obj,'outputXml',False):
                fileName = os.path.normpath(os.path.join(directory,obj.data_url()))
                os.remove(fileName)
                #print 'Report.makeXMLFiles fileName',fileName

  def addReference(self,xrtnode=None,xmlnode=None,jobInfo=None,**kw):
    if xmlnode is None: xmlnode = self.xmlnode
    if jobInfo is None: jobInfo = self.jobInfo
    if not hasattr(self,'referenceList'): self.referenceList = [ ReferenceGroup() ]
    obj = Reference(xrtnode=xrtnode, xmlnode = xmlnode, jobInfo = jobInfo, **kw)
    self.referenceList[-1].append(obj)
    return obj

  def addTaskReferences(self,taskName=None,drillDown=True):
    if taskName is None: taskName = self.TASKNAME
    if not hasattr(self,'referenceList'): self.referenceList = [ ]
    import CCP4TaskManager
    helpFileList =  CCP4TaskManager.TASKMANAGER().searchReferenceFile(name=taskName,drillDown=drillDown)
    #print 'Report.addTaskReferences',helpFileList
    for helpFile in helpFileList:
      self.referenceList.append(ReferenceGroup())
      self.referenceList[-1].loadFromMedLine(fileName=helpFile)

  def getReport(self):
    return self

  def newPageOrNewData(self):
    # MN This function returns the string "NEWPAGE" if it has generated new
    # HTML code, or "NEWDATA" if it has generated only new data to be supplied to existing
    # widgets on the GUI.  Code that uses this call will decide whether to force an HTML reload
    # or simply cause a loaded report  to reload its data.
    if not self.SEPARATEDATA:
        #print 'Newpage because not SEPARATEDATA'
        return "NEWPAGE"
    if not hasattr(self, 'jobStatus'):
        #print 'Newpage because not has jobStatus'
        return "NEWPAGE"
    if not hasattr(self.jobStatus, 'lower'):
        #print 'Newpage because not lowerable jobStatus'
        return "NEWPAGE"
    if not self.jobStatus.lower()=='running':
        #print 'Newpage because not running'
        return "NEWPAGE"
        
    #Here we look to see if data files for *ALL* report element for which data are separated
    #already have data files...this would imply that we should return newdata
    try:
        flotGraphs = findChildren(self, FlotGraph)
        #flotGraphGroups = findChildren(self, FlotGraphGroup)
        tables = findChildren(self, Table)
        progresses = findChildren(self, Progress)
        pres = findChildren(self, Pre)
        #texts = findChildren(self, Text)
        xmlSeparableItems = progresses+flotGraphs+tables+pres#+texts
        result="NEWDATA"
        import os
        for xmlSeparableItem in xmlSeparableItems:
            if not xmlSeparableItem.outputXml:
                #print 'Newpage because ',xmlSeparableItem,'is not outputXML'
                result="NEWPAGE"
                continue
            xmlPath = os.path.normpath(os.path.join(self.jobInfo['fileroot'],xmlSeparableItem.data_url()))
            if not os.path.isfile(xmlPath):
                #print 'Newpage because ',xmlPath,'does not exist'
                result="NEWPAGE"
                continue
        return result
    except:
        return "NEWPAGE"


class Results(Container):
  def as_etree(self, htmlBase=None):
    root = etree.Element('root')
    anchor = etree.Element('a')
    anchor.set('name','results')
    root.append(anchor)
    head = etree.Element('h5')
    if self.id is not None: head.set('id',self.id)
    if self.class_ is not None:
      head.set('class',self.class_)
    else:
      head.set('class','section')
      #Substituted .style for .class_ below....looked like a typo MN
      #if self.style is not None: head.set('style',self.class_)
    if self.style is not None: head.set('style',self.style)
    
    head.text = 'Results'
    root.append(head)
    root.extend(Container.as_etree(self))
    return root

class DrillDown(ReportClass):
  
  def __init__(self,jobInfo,**kw):
    super(DrillDown,self).__init__(**kw)
    self.jobInfo = jobInfo
    #print 'DrillDown jobInfo',jobInfo
  
  def as_etree(self):
    # Check that ther are some sub-directories
    # Beware fileroot has fragment of file name on end of directory path
    jobDir = self.jobInfo.get('fileroot',None)
    if jobDir is not None: jobDir = os.path.split(jobDir)[0]  
    subJobs = self.getSubJobs(jobDir)
    #print 'DrillDown.as_etree subJobs',subJobs
    if len(subJobs)==0: return  etree.Element('root')
    
    # make a folder
    '''
    fold = foldTitleLine('Show Sub-Jobs')
    if fold.tag == 'root':
      root.extend(fold)
    else:
      root.append(fold)
    div = root.find('div')
    '''
    div = etree.Element('div')
    div.set('class','sub-job-list')
    if self.id is not None: div.set('id',self.id)
    if self.class_ is not None: div.set('class',self.class_)
    h4 = etree.Element('h4')
    h4.text = 'Sub-Jobs'
    div.append(h4)
    
    try:
      import CCP4TaskManager
      ifi2 = True
    except:
      ifi2 = False

    
    for job in subJobs:
       # jobs is list of subJobNumber,jobId,pluginName,reportFile,subJobs
      text = str(job[0])+': '
      if job[1] is not None:
        if ifi2:
          text = text + CCP4TaskManager.TASKMANAGER().getTitle(job[2])
          
        else:
          text = text + job[2]         
      fold = foldLinkLine(text,job[3],'jobId'+str(job[1]))
      div.extend(fold)

      '''
      # Simple link mode
      div = root.find('div')
      a =  etree.Element('a')
      a.set('href',job[3])
      a.set('id','jobId'+str(job[1]))
      a.text= text
      div.append(a)
      div.append(etree.Element('br'))
      '''   
      
      '''
      # Alternative 'button' mode
      obj = etree.Element('object')
      obj.set('class','qt_object_subjob')
      obj.set('type','x-ccp4-widget/CSubJobButton')
      #obj.set('id',self.id+'_'+str(job[0]))
      div.append(obj)

      for key,value in [['label',text],['jobId',str(self.jobInfo.get('jobid',None))],['subJobNumber',job[0]],['link',job[2]]:
        p = etree.Element('param')
        p.set('name',key)
        p.set('value',value)
        obj.append(p)
      '''
    return div

  def getSubJobs(self,jobDir):
    import glob,CCP4File
    if jobDir is None: return []
    globDirs = glob.glob(os.path.join(jobDir,'job_*'))
    def sortSubJobsKey(inp):
      return int(inp.split('_')[-1])
    sortedDirs = sorted(globDirs,key=sortSubJobsKey)
    retSubJobs = []
    for path in sortedDirs:
      subJobs = self.getSubJobs(path)
      parsFile = glob.glob(os.path.join(path,'*.params.xml'))
      if len(parsFile)>0:
        # Get taskName from file 
        f = CCP4File.CI2XmlDataFile(fullPath=parsFile[0])
        pluginName = str(f.header.get('pluginName'))
        jobId = int(f.header.get('jobId'))
      else:
        pluginName = None
        jobId = None
      '''
      reportFile =  glob.glob(os.path.join(path,'*.report.html'))
      if len(reportFile) == 0:
        reportFile = None
      else:
        reportFile = reportFile[0]
      '''
      reportFile = parsFile[0][0:-10]+'report.html'
      
      retSubJobs.append([os.path.split(path)[-1].split('_')[-1],jobId,pluginName,reportFile,subJobs])
      
    return retSubJobs


def foldTitleLine(label, initiallyOpen, brief = None):
  root = etree.Element('root')
  anchor = etree.Element('a')
  anchor.set('name',label)
  root.append(anchor)
  span = etree.Element('span')
  span.set('class','folder')
  if brief is not None: span.set('title',brief)
  span.set('onclick',"toggleview(this)")
  if initiallyOpen:
      span.text = u"\u25BC"+ " "+label
  else:
      span.text = u"\u25B6"+ " "+label
  root.append(span)
  div = etree.Element('div')
  if initiallyOpen:
      div.set('class','hidesection displayed')
  else:
      div.set('class','hidesection hidden')
  root.append(div)
  return root

def foldLinkLine(label,href,id):
  '''
  root = etree.Element('root')
  anchor = etree.Element('a')
  anchor.set('name',label)
  root.append(anchor)
  span = etree.Element('span')
  span.set('class','folder_link')
  a =  etree.Element('a')
  a.set('href',href)
  a.set('id',id)
  a.text = 'Show '+label
  span.append(a)
  root.append(span)
  return root
  '''

  '''
  Aiming for..
  <span class="folder" onclick="togglesubjob(this)">Show 1: Model building - Buccaneer</span>
  <div id="jobId613" class="subjob"></div>
  '''
  root = etree.Element('root')
  span = etree.Element('span')
  span.set('class','folder')
  span.set('onclick',"togglesubjob(this)")
  span.text = 'Show '+label
  root.append(span)
  div = etree.Element('div')
  div.set('id',id)
  div.set('class','subjob')
  root.append(div)
  return root
    
# Fold class - container for a hidden report within a report
class Fold( Container ):
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
    super(Fold,self).__init__(xrtnode=xrtnode,xmlnode=xmlnode,jobInfo=jobInfo, **kw )
    if xrtnode is not None:
      self.label = xrtnode.get('label','Show details')
      self.brief = xrtnode.get('brief',None)
    else:
      self.label = kw.get('label','Fold')
      self.brief = kw.get('brief',None)
    self.initiallyOpen = kw.get('initiallyOpen',False)

  def as_html( self ):
    return """<span class='folder' onclick='toggleview(this)'>Show details</span><div class='hidesection'>\n{0}</div>\n""".format(Container.as_html(self))

  def as_etree(self):
    root = foldTitleLine(self.label, self.initiallyOpen, self.brief)
    root.find('div').extend(Container.as_etree(self))
    return root

class Text(ReportClass):
  tag='span'
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {},  **kw ):
    super(Text,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo,  **kw)
    self.text = ""
    self.tail = ""
    #print 'Text.init',xrtnode.get( "if" ),xrtnode.get( "select" ),xrtnode.text,xrtnode.tail
    if xrtnode is not None and len(xrtnode)>0:
      if xrtnode.get( "if" ) is not None and (xmlnode is None or not bool(xmlnode.xpath( xrtnode.get("if")))):
          return
      if xrtnode.text: self.text += xrtnode.text
      #print 'Text.init self.text',self.text
      if xrtnode.tail: self.tail += xrtnode.tail 
      if xrtnode.get( "select" ) and xmlnode is not None:
        nodes = xmlnode.xpath( xrtnode.get( "select" ) )
        for node in nodes: 
          self.text += node.text
    elif kw.get('text',None) is not None:
      self.text += kw['text']
      #print 'Text.__init__',text
    elif kw.get('select',None) is not None and xmlnode is not None:
      nodes = xmlnode.xpath( kw['select'] )
      for node in nodes: 
          self.text += node.text
  
  def as_html( self ):
    return self.text

  def as_etree(self):
    # Tag as dummy so parent container will strip the tags
    if self.outputXml:
        return  etree.fromstring('<span data-url="'+self.data_url()+'"></span>')
    else:
        return self.data_as_xml()
      
  def data_as_xml(self, fileName=None):
    # Tag as dummy so parent container will strip the tags
    root = etree.Element(self.tag)
    if self.style is not None: root.set('style',self.style)
    if self.class_ is not None: root.set('class',self.class_)
    try:
      if self.text is not None: root.text = self.text
    except:
      print 'Failed creating XML for:',self.text
    try:
      if self.tail is not None: root.tail = self.tail
    except:
      print 'Failed creating XML for:',self.tail
    #print 'Text.as_etree',root.text,root.tail
    
    if fileName is not None:
        with open(fileName,'w') as outputFile:
            outputFile.write(etree.tostring(root, pretty_print=True))
    
    return root

  def getText(self):
    if len(self.text)==0 or len(self.tail)==0 or self.text[-1]==' ' or self.tail[0]==' ':
      return self.text + self.tail
    else:
      return self.text + ' ' + self.tail

class Pre(Text):
    tag='pre'

# Status class
class Status(Container):

  def as_html( self ):
    return "<div width='100%' style='background-color:#80FF80;'><h1>"+self.text+"</h1></div>"

  def as_etree(self):
    div = etree.Element('div')
    if self.id is not None: div.set('id',self.id)
    if self.class_ is not None: div.set('class',self.class_)
    #div.set('width','100%')
    if self.style is not None:
      div.set('style',self.style)
    else:
      div.set('style','background-color:#80FF80;')
    div.extend(self.text)
    return div
    

# Copy class
class Copy(ReportClass):
  def __init__( self, xrtnode=None, xmlnode=None, **kw ):
    super(Copy,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, **kw)
    self.text = ""
    self.root = etree.Element('root')
    if xmlnode is not None:
       if xrtnode is not None:
          self.root.extend(xmlnode.xpath( xrtnode.get( "select" ) ))
       elif kw.get('select',False):
          self.root.extend(xmlnode.xpath(kw.get('select',False)))

     
    #print 'Copy __init__ root',self.root.tag

  def as_html( self ):
    text = ''
    for node in self.root.iterChildren(): 
      text += etree.tostring( node )
    return text

  def as_etree(self):
    return self.root



class Generic(ReportClass):

  ERROR_CODES = { 1 : { 'description' : 'Can not interpret text' },
                  2 : { 'description' : 'Error substituting values into generic item' }  }
  def __init__(self,xrtnode=None,xmlnode=None,jobInfo={},text=None,defaultTag='p',**kw):
    self.id = kw.get('id',None)
    self.class_ = kw.get('class_',None)
    self.xmltree = None

    if xrtnode is None and text is not None:
      try:
        xrtnode = etree.fromstring(text,PARSER())
      except:
        try:
          xrtnode = etree.fromstring('<'+defaultTag+'>'+text+'</'+defaultTag+'>',PARSER())
        except:
          raise CException(self.__class__,1,str(text))

    if xrtnode is not None:
      try:
        self.xmltree = applySelect(xrtnode,xmlnode,jobInfo)
      except:
        if text is not None:
          self.errReport.append(self.__class__,2,str(text))

  def as_etree(self):
    if self.id is not None: self.xmltree.set('id',self.id)
    if self.class_ is not None: self.xmltree.set('class',self.class_)
    return self.xmltree

# Table class
class BaseTable(ReportClass):
  tableCount = 0
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo={}, **kw ):
    super(BaseTable,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, **kw)
    BaseTable.tableCount += 1
    self.id = kw.get('id','table_'+str( BaseTable.tableCount))
    self.coldata = []
    self.coltitle = []
    self.colsubtitle = []
    self.colTips = []
    self.xmldata = []
    self.help = None
    self.download  = None
    self.outputCsv = kw.get('outputCsv',True)
    downloadable = kw.get('downloadable',False)
    
    if downloadable:
      if self.title is not None:
        self.download = Download(jobInfo=jobInfo,dataName=self.id+'_'+self.title)
      else:
        self.download = Download(jobInfo=jobInfo,dataName=self.id)
    if xrtnode is not None:
      self.excludeIfDataMissing = xrtnode.get('excludeIfDataMissing',False)
      if xmlnode is not None:
        self.xmldata = xmlnode.xpath( xrtnode.get( "select" ) )
      self.transpose = ( xrtnode.get( "transpose" ) != None )
      if xrtnode.get( "help" ) is not None:
        self.help =   Help(ref = xrtnode.get( "help" ))
    else:
      self.excludeIfDataMissing = kw.get('excludeIfDataMissing',False)
      if xmlnode is not None:
        if kw.get('select',None) is not None:
          self.xmldata = xmlnode.xpath(kw.get('select'))
        else:
          self.xmldata = [ xmlnode ]
      self.transpose = kw.get('transpose',False)
      if kw.get('help',None) is not None:
        self.help = Help(ref = kw['help'])
    # assemble column data

    if xrtnode is not None:
      for col in xrtnode:
        if col.tag == XRTNS+"data":
          colttl1 = col.get( "title" )
          colttl2 = col.get( "subtitle" )
          colexpr = col.get( "expr" )
          colsel  = col.get( "select" )
          self.addData(xmldata=self.xmldata,title=colttl1,subtitle=colttl2,expr=colexpr,select=colsel)
    
  def addData(self,xmldata=None,title=None,subtitle=None,expr=None,function=None,select=None,data=[], tip=None):
    if tip is not None:
        self.colTips.append(tip)
    else:
        self.colTips += [None]
    colvals = []
    if xmldata is not None:
      if not isinstance(xmldata,list): xmldata = [xmldata ]
    else:
      xmldata = self.xmldata
    if select != None:         # select the values from the xml
      for x in xmldata:
        for selitem in x.xpath( select ):
          if selitem.text is None:
            val = '-'
          else:
            val = selitem.text.strip()
            if expr != None:  # allow an expression to be applied to the data
              try:
                val = eval( expr, {"x":float(val)} )
              except:
                val = '-'
            if function is not None:
              val = function(val)
          colvals.append(val)
    else:                      # allow a list of values to be given
        colvals.extend(data)
    if self.excludeIfDataMissing and len(colvals)==0:
      pass
    else:
      self.coldata.append(colvals)
      maxlen = max( [ len(col) for col in self.coldata ] )
      for i in range(len(self.coldata)):
        while len(self.coldata[i]) < maxlen:
          self.coldata[i].append(None)
      self.coltitle.append(title)
      self.colsubtitle.append(subtitle)

  def as_html( self ):
    # check for subheads
    hassubhead = sum( [ x != None for x in self.colsubtitle ] ) > 0
    # deal with normal and transpose tables in turn
    if not self.transpose:
      # Normal tables
      s = "<table><thead>\n"
      # column headers
      s += "<tr>"
      for i in range(len(self.coltitle)):
        if self.coltitle[i] != None:
          span = 1
          while i+span < len(self.coltitle):
            if self.coltitle[i+span] != None:
              break
            span += 1
          text = self.coltitle[i] if self.coltitle[i] else ""
          colTip = ""
          if i>=0 and i<len(self.colTips) and self.colTips[i] is not None:
              colTip = "title='{0}'".format(self.colTips[i])
          if span > 1:
            s += "<th colspan='{0}' {1}>{2}</th>".format(span, colTip, text)
          else:
            s += "<th {0}>{1}</th>".format(colTip, text)
      # column subheaders
      if hassubhead:
        s += "</tr>\n<tr>"
        for i in range(len(self.colsubtitle)):
          if self.coltitle[i] != None or self.colsubtitle[i] != None:
            span = 1
            while i+span < len(self.colsubtitle):
              if self.coltitle[i+span] != None or self.colsubtitle[i+span] != None:
                break
              span += 1
            text = self.colsubtitle[i] if self.colsubtitle[i] else ""
            colTip = ""
            if i>=0 and i<len(self.colTips) and self.colTips[i] is not None:
                colTip = "title='{0}'".format(self.colTips[i])
            if span > 1:
              s += "<th colspan='{0}' {1}>{2}</th>".format(span, colTip, text)
            else:
              s += "<th>{0}</th>".format(text)
      s += "</tr>\n</thead>\n"
      # column data
      s += "<tbody>\n"
      for i in range(len(self.coldata[0])):
        s += "<tr>"
        for col in self.coldata:
          colTip = ""
          if col>=0 and col<len(self.colTips) and self.colTips[col] is not None:
            colTip = "title='{0}'".format(self.colTips[col])
          s += "<td {0}>{1}</td>".format(colTip, col[i])
        s += "</tr>\n"
      s += "</tbody></table>\n"
    else:
      # Transpose tables
      s = "<table><tbody>\n"
      # column headers
      for i in range(len(self.coldata)):
        s += "<tr>"
        colTip = ""
        if col>=0 and col<len(self.colTips) and self.colTips[col] is not None:
          colTip = "title='{0}'".format(self.colTips[col])
        if self.coltitle[i] != None:
          s += "<th {0}>{1}</th>".format(colTip, self.coltitle[i])
        else:
          s += "<th {0}></th>".format(colTip)
        if hassubhead:
          if self.colsubtitle[i] != None:
            s += "<th>{0}</th>".format(self.colsubtitle[i])
          else:
            s += "<th></th>"
        for j in range(len(self.coldata[i])):
          s += "<td {0}>{0}</td>".format(colTip, self.coldata[i][j])
        s += "</tr>\n"
      s += "</tbody></table>\n"
    return s

  def as_etree(self):
    # check for subheads
    hassubhead = sum( [ x != None for x in self.colsubtitle ] ) > 0
    table = etree.Element('table')
    if self.id is not None: table.set('id',self.id)
    if self.class_ is not None: table.set('class',self.class_)
    if self.style is not None: table.set('style',self.style)
    
    # deal with normal and transpose tables in turn
    if not self.transpose:
      head = etree.Element('thead')
      table.append(head)
      headtr = etree.Element('tr')
      for i in range(len(self.coltitle)):
        if self.coltitle[i] != None:
          span = 1
          while i+span < len(self.coltitle):
            if self.coltitle[i+span] != None:
              break
            span += 1
          th =  etree.Element('th')
          th.text = self.coltitle[i] if self.coltitle[i] else ""
          if span > 1: th.set('colspan',str(span))
          headtr.append(th)
      head.append(headtr)
      tbody = etree.Element('tbody')
      tbody.set('class','fancy')
      for i in range(len(self.coldata[0])):
        tr= etree.Element('tr')
        for col in self.coldata:
          td = etree.Element('td')
          td.text = unicode(col[i])
          tr.append(td)
        tbody.append(tr)
      table.append(tbody)
    else:
      tbody = etree.Element('tbody')
      tbody.set('class','fancy')
      for i in range(len(self.coldata)):
        tr = etree.Element('tr')
        tbody.append(tr)
        th = etree.Element('th')
        if self.coltitle[i] != None: th.text = str(self.coltitle[i])
        tr.append(th)
        if hassubhead:
          th1 = etree.Element('th')
          if self.colsubtitle[i] != None: th1.text = self.colsubtitle[i]
          tr.append(th1)
        for j in range(len(self.coldata[i])):
          td = etree.Element('td')
          td.text = str(self.coldata[i][j])
          tr.append(td)
      table.append(tbody)

    root = etree.Element('root')
    if self.help is None and self.download is None:
      root.append(table)
    else:
      
      #This is not working well
      div = etree.Element('div')
      div.set('id',self.id)
      root.append(div)
      div.append(table)
      if self.help is not None:
        div.append(self.help.as_etree())
      if self.download is not None:
        div.append(self.download.as_etree())
      '''
      tab = etree.Element('table')
      tab.append(etree.Element('tr'))
      tab.append(etree.Element('tr'))
      tab[0].append(etree.Element('td'))
      tab[0][0].set('colspan','2')
      tab[1].append(etree.Element('td'))
      tab[1].append(etree.Element('td'))
      tab[0][0].append(table)
      if self.help is not None:
        tab[1][0].append(self.help.as_etree())
      if self.download is not None:
        tab[1][1].append(self.download.as_etree())
      root.append(tab)
      '''
    return root


  def data_as_csv(self,fileName=None):
    import csv
    if len(self.coldata)==0: return CErrorReport()
    try:
      f = open(fileName,'wb')
    except:
      return CErrorReport(Report,107,str(fileName))
    try:
      writer = csv.writer(f)
      if self.transpose:
        iRow=0
        for row in self.coldata:
            if len(self.coltitle)>0:
              if iRow < len(self.coltitle): outputRow = [self.coltitle[iRow]] + row
              else: outputRow = [" "] + row
            else: outputRow = row
            writer.writerow(outputRow)
            iRow += 1
      else:
        if len(self.coltitle)>0:
          writer.writerow(self.coltitle)
        nc = len(self.coldata)
        nr = len(self.coldata[0])
        for n in range(nr):
          row = []
          for col in self.coldata:
            row.append(col[n])
          writer.writerow(row)
    except:
      return CErrorReport(Report,107,str(fileName))
    try:
      f.close()
    except:
      return CErrorReport(Report,107,str(fileName))
    return CErrorReport()

  def data_as_xml(self,fileName=None):
    import csv
    if len(self.coldata)==0: return CErrorReport()
    try:
      f = open(fileName,'wb')
    except:
      return CErrorReport(Report,107,str(fileName))
    try:
      dataAsEtree = self.data_as_etree()
      #As it comes out here, the data tag is in ccp4 namespace (i.e. ccp4_data:data)
      from lxml import etree
      rootNode = etree.Element('CCP4ApplicationOutput')
      for childTable in dataAsEtree:
          childTable.tag = 'CCP4Table'
          rootNode.append(childTable)
      from lxml import etree
      dataAsText = etree.tostring(rootNode,pretty_print=True)
      f.write(dataAsText)
    except:
      print 'Failed to select data'
      return CErrorReport(Report,107,str(fileName))
    try:
      f.close()
    except:
      return CErrorReport(Report,107,str(fileName))
    return CErrorReport()

# JavaScript-Enhances Table class
class Table(BaseTable):

  def __init__( self, xrtnode=None, xmlnode=None, jobInfo={}, **kw ):
    super(Table,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )
    #self.internalId = kw.get("internalId", "tabletable_{0}_{1}".format(jobInfo.get('jobid','xxx'),BaseTable.tableCount))

  def as_etree(self):
    # check for subheads
    hassubhead = sum( [ x != None for x in self.colsubtitle ] ) > 0
    
    root = etree.Element('root')

    # deal with normal and transpose tables in turn
    if True:
      styleDict = {'height':'100%', 'width':'100%','margin':'0px','padding':'0px','display':'inline-block','margin-top':'1px'}
      styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
      
      
      if self.outputXml:
          drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_url(), data_is_urls=True,renderer='CCP4i2Widgets.CCP4TableRenderer', require='CCP4i2Widgets', initiallyDrawn='True')
      else:
          drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = 'data_'+self.internalId, data_is_urls=False, renderer='CCP4i2Widgets.CCP4TableRenderer', require='CCP4i2Widgets', initiallyDrawn='True')
      root.append(self.data_as_etree())
      surroundingDiv = etree.SubElement(root,'div')
      surroundingDiv.append(drawnDiv.as_etree())
      surroundingDiv.set('id',self.id)

    if self.help is None and self.download is None:
      pass
    else:
      #This is not working well (<- *not* written by me SJM 05/12/2014)
      if self.help is not None:
        surroundingDiv.append(self.help.as_etree())
      if self.download is not None:
        surroundingDiv.append(self.download.as_etree())
    return root

  def data_as_etree(self,fileName=None):

    hassubhead = sum( [ x != None for x in self.colsubtitle ] ) > 0
    eleTree = etree.parse(StringIO('<script></script>'))
    ccp4_data = eleTree.getroot()
    ccp4_data.set('id','data_'+self.internalId)
    ccp4_data.set('type','application/xml')

    table = etree.Element('table')
    if not self.transpose:
      head = etree.Element('thead')
      table.append(head)
      headtr = etree.Element('tr')
      for i in range(len(self.coltitle)):
        if self.coltitle[i] != None:
          span = 1
          while i+span < len(self.coltitle):
            if self.coltitle[i+span] != None:
              break
            span += 1
          th =  etree.Element('th')
          if i >=0 and i<len(self.colTips) and self.colTips[i] is not None: th.set('title', self.colTips[i])
          th.text = self.coltitle[i] if self.coltitle[i] else ""
          if span > 1: th.set('colspan',str(span))
          headtr.append(th)
      head.append(headtr)
      tbody = etree.Element('tbody')
      for i in range(len(self.coldata[0])):
        tr= etree.Element('tr')
        for col in self.coldata:
          td = etree.Element('td')
          if i >=0 and i<len(self.colTips) and self.colTips[i] is not None: td.set('title', self.colTips[i])
          td.text = unicode(col[i])
          tr.append(td)
        tbody.append(tr)
      table.append(tbody)
    else:
      tbody = etree.Element('tbody')
      for i in range(len(self.coldata)):
        tr = etree.Element('tr')
        tbody.append(tr)
        th = etree.Element('th')
        if self.coltitle[i] != None: th.text = str(self.coltitle[i])
        if i >=0 and i<len(self.colTips) and self.colTips[i] is not None: th.set('title', self.colTips[i])
        tr.append(th)
        if hassubhead:
          th1 = etree.Element('th')
          if self.colsubtitle[i] != None: th1.text = self.colsubtitle[i]
          tr.append(th1)
        for j in range(len(self.coldata[i])):
          td = etree.Element('td')
          if i >=0 and i<len(self.colTips) and self.colTips[i] is not None: td.set('title', self.colTips[i])
          td.text = str(self.coldata[i][j])
          tr.append(td)
      table.append(tbody)
      table.set('class','transpose')

    ccp4_data.append(table)
      
    #print etree.tostring(ccp4_data,encoding='utf-8', pretty_print=True)
    return ccp4_data

class Div(Container):
  tag='div'
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
    super(Div,self).__init__( xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )

class Progress(ReportClass):
    tag='progress'
    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
        super(Progress,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )
        self.value = 0
        self.label = kw.get('label','')
        self.outputXml = kw.get('outputXml', False)
        self.initiallyDrawn = kw.get('initiallyDrawn',True)
        if 'value' in kw: self.value = kw['value']
        self.max = 100
        if 'max' in kw: self.max = kw['max']
    
    def as_etree(self):
        #print '\n\n** In Progress.as_etree'
        # Tag as dummy so parent container will strip the tags
        styleDict = {'height':'50px', 'width':'250px','margin':'0px','padding':'0px','display':'inline-block','border':'1px solid black','margin-top':'0px'}
        if self.style is not None:
            styleBits = self.style.split(';')
            extraStyleDict = {}
            for styleBit in styleBits:
                stylePair = styleBit.split(':')
                if len(stylePair) == 2: extraStyleDict[stylePair[0].strip()] = stylePair[1].strip()
            styleDict.update(extraStyleDict)
        styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
        
        root = etree.Element('div',style=styleStr)
        progressElement = etree.Element('progress', value=str(self.value), max=str(self.max), style=styleStr)
        if self.outputXml:
            drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_url(), data_is_urls=True, renderer='CCP4i2Widgets.CCP4i2HTMLChunk', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn))
        else:
            drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = etree.tostring(progressElement,pretty_print=True), data_is_urls=False, renderer='CCP4i2Widgets.CCP4i2HTMLChunk', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn))

            #print etree.tostring(drawnDiv.as_etree(), pretty_print=True)
        root.append(drawnDiv.as_etree())

        if self.style is not None: root.set("style",styleStr)
        #print 'Text.as_etree',root.text,root.tail
        return root
            
    def data_as_xml(self,fileName=None):
      dataAsEtree = etree.Element('span')
      dataAsEtree.text = self.label
      dataAsEtree.append(etree.Element('progress', value=str(self.value), max=str(self.max), style=self.style))
      rootNode = etree.Element('CCP4ApplicationOutput')
      rootNode.append(dataAsEtree)
      dataAsText = etree.tostring(rootNode,pretty_print=True)
      with open(fileName,'wb') as f:
        f.write(dataAsText)
      return CErrorReport()

class PictureGroup(Container):
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
    super(PictureGroup,self).__int__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
    self.help = None
    self.launch = None
    Container.__init__( self, xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )

    
class GraphGroup(Container):
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
    super(GraphGroup,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )
    self.help = None
    #print 'GraphGroup.__init__',self.children

    if kw.get('launcher',None) is not None:
      ele = etree.Element('launch')
      ele.set('label',kw['launcher'])
      ele.set('exe','loggraph')
      self.launch = Launch(ele,jobInfo=jobInfo)
    else:
      self.launch = None

    if xrtnode is not None:
      help = xrtnode.find(XRTNS+'help')
      if help is not None:
        self.help = Help(help,mode='graph')
        if xrtnode is not None: xrtnode.remove(help)
    elif kw.has_key('help'):
      self.help = Help(ref=kw['help'])

  def as_etree(self):
    root = etree.Element('root')
    
    # Beware children could include Loop or other non-Graph elements
    graphObjList = []
    for child in self.children:
      if isinstance(child,Graph):
        #child.launch = None
        child.help = None
        graphObjList.append(child)
      
      elif isinstance(child,(Container,Loop)):
        #print 'GraphGroup.as_etree grandChildren',child,child.children
        for grandChild in child.children:
          if isinstance(grandChild,Graph):
            graphObjList.append(grandChild)
    #print 'GraphGroup.as_etree graphObjList',graphObjList
    if len(graphObjList)==0: return root

    if self.launch is None:
      loggraphObj = etree.Element('object')
      loggraphObj.set('type','x-ccp4-widget/LogGraph')
      loggraphObj.set('class','loggraph')
      if self.style is not None:
        loggraphObj.set('style',self.style)
      if graphObjList[0].id is not None:
        loggraphObj.set('id',graphObjList[0].id)
      root.append(loggraphObj)
      
    for graphObj in graphObjList:
      #print '    ',graphObj.title     
      param = etree.Element('param')
      param.set('name','ccp4_data_id')
      param.set('value','data_'+graphObj.internalId)
      if self.launch is None:
        loggraphObj.append(param)
      else:
        self.launch.appendDataId('data_'+graphObj.id)

      root.append(graphObj.data_as_etree())
    
    #if self.help is not None or self.launch is not None:
    if self.help is not None:
      h = self.help.as_etree()
      if h.tag == 'root':
        root.extend(h)
      else:
        root.append(h)    
    
  
    return root

class FlotGraphGroup(Container):
    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
        super(FlotGraphGroup,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
        self.help = None
        self.launch = None
        self.launchOnly = False
        #print 'GraphGroup.__init__',self.children
        
        self.launch = None
        ele = etree.Element('launch')
        if kw.get('launcher',None) is not None:
            self.launchOnly = True
            ele.set('label',kw['launcher'])
            ele.set('exe','loggraph')
            if kw.get('withLaunch',True):
              self.launch = Launch(ele,jobInfo=jobInfo)
 
        if xrtnode is not None:
            help = xrtnode.find(XRTNS+'help')
            if help is not None:
                self.help = Help(help,mode='graph')
                if xrtnode is not None: xrtnode.remove(help)
        elif kw.has_key('help'):
            self.help = Help(ref=kw['help'])
    
    
    def as_etree(self):
        root = etree.Element('root')
        
        # Beware children could include Loop or other non-Graph elements
        graphObjList = []
        for child in self.children:
            if isinstance(child,FlotGraph):
                #child.launch = None
                child.help = None
                graphObjList.append(child)
            
            elif isinstance(child,(Container,Loop)):
                #print 'GraphGroup.as_etree grandChildren',child,child.children
                for grandChild in child.children:
                    if isinstance(grandChild,FlotGraph):
                        graphObjList.append(grandChild)
        #print 'GraphGroup.as_etree graphObjList',graphObjList
        if len(graphObjList)==0: return root
            
        dataList = ",".join(['data_'+graphObj.internalId for graphObj in graphObjList])
        for graphObj in graphObjList:
            #print '    ',graphObj.title
            if self.launch is not None:
                param = etree.Element('param')
                param.set('name','ccp4_data_id')
                param.set('value','data_'+graphObj.internalId)
                self.launch.appendDataId('data_'+graphObj.id)
            root.append(graphObj.data_as_etree())
            
        
        styleDict = {'height':'250px', 'width':'250px','margin':'0px','padding':'0px','display':'inline-block','border':'1px solid black','margin-top':'1px'}
        if self.style is not None:
            styleBits = self.style.split(';')
            extraStyleDict = {}
            for styleBit in styleBits:
                stylePair = styleBit.split(':')
                if len(stylePair) == 2: extraStyleDict[stylePair[0].strip()] = stylePair[1].strip()
            styleDict.update(extraStyleDict)
        styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
        
        launchButtonLocation = root
        if not self.launchOnly:
            surroundingDiv = etree.SubElement(root,'div',style=styleStr)
            launchButtonLocation = surroundingDiv
            #Here I shring the Flot Div if I am going to append a launch button
            if self.launch is not None:
                heightPx = int(styleDict['height'][:-2])
                heightPx -= 30;
                styleDict['height'] = str(heightPx) + 'px'
            if 'border-width' in styleDict: del styleDict['border-width']
            if 'border-style' in styleDict: del styleDict['border-style']
            if 'border-color' in styleDict: del styleDict['border-color']
            styleDict['margin-top'] = '0px'
            styleDict['margin'] = '0px'
            styleDict['padding'] = '0px'
            styleDict['border'] = '0px solid white'
            
            styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
            drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = dataList, renderer='CCP4i2Widgets.CCP4FlotRenderer', require='CCP4i2Widgets', initiallyDrawn='True')
            #print etree.tostring(drawnDiv.as_etree(), pretty_print=True)
            surroundingDiv.append(drawnDiv.as_etree())

        if self.launch:
            l = self.launch.as_etree()
            if l.tag == 'root':
                launchButtonLocation.extend(l)
            else:
                launchButtonLocation.append(l)

        #if self.help is not None or self.launch is not None:
        if self.help is not None:
            h = self.help.as_etree()
            if h.tag == 'root':
                root.extend(h)
            else:
                root.append(h)    
        
        
        return root
    
    def data_as_xmls(self,fileRoot=None):
        # Beware children could include Loop or other non-Graph elements
        graphObjList = []
        for child in self.children:
            if isinstance(child,FlotGraph):
                graphObjList.append(child)

class DrawnDiv(Container):
    drawnDivCount=0
    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
        super(DrawnDiv,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
        self.help = None
        DrawnDiv.drawnDivCount += 1
        self.id = kw.get('id','drawnDiv_'+str(DrawnDiv.drawnDivCount))
        self.data_is_urls = kw.get('data_is_urls',False)
        
        if xrtnode is not None:
            help = xrtnode.find(XRTNS+'help')
            if help is not None:
                self.help = Help(help,mode='gallery')
                if xrtnode is not None: xrtnode.remove(help)
        elif kw.has_key('help'):
            self.help = Help(ref=kw['help'])
        
        self.height = kw.get('height', '250px')
        self.width = kw.get('width', '250px')
        self.style = kw.get('style', 'margin:0px;padding:0px;display:inline-block;') + 'height:'+self.height+';width:'+self.width+';'
        self.data_data = kw.get('data', 'None')
        self.data_renderer = kw.get('renderer', 'None')
        self.data_require = kw.get('require', 'None')
        self.data_initially_drawn = kw.get('initiallyDrawn', False)
        
    def as_etree(self):
        rootDiv = etree.Element('div')
        rootDiv.set('style',self.style)
        try:
          import CCP4Modules
          preferences = CCP4Modules.PREFERENCES()
          rootDiv.set('data-app-font-size',str(preferences.GUI_FONT_SIZE)+'px')
        except:
          pass
        rootDiv.set('data-data',self.data_data)
        rootDiv.set('data-renderer',self.data_renderer)
        rootDiv.set('data-height',self.height)
        rootDiv.set('data-width',self.width)
        rootDiv.set('data-widget-type','CCP4i2DrawnDiv')
        rootDiv.set('data-is-urls',str(self.data_is_urls))
        rootDiv.set('id', self.id)

        if self.data_require is not None: rootDiv.set('data-require',self.data_require)
        rootDiv.set('data-initially-drawn',str(self.data_initially_drawn))
        rootDiv.text=''
        return rootDiv

class ObjectGallery(Container):
    galleryCount = 0
    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
        super(ObjectGallery,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
        self.help = None
        ObjectGallery.galleryCount += 1
        self.id = kw.get('id','gallery_'+str(ObjectGallery.galleryCount))
       
        if xrtnode is not None:
            help = xrtnode.find(XRTNS+'help')
            if help is not None:
                self.help = Help(help,mode='gallery')
                if xrtnode is not None: xrtnode.remove(help)
        elif kw.has_key('help'):
            self.help = Help(ref=kw['help'])

        self.tableWidth = kw.get('tableWidth', '7em')
        self.contentWidth = kw.get('contentWidth', '250px')
        self.height = kw.get('height', '250px')
        self.style = kw.get('style', 'padding:0px;overflow:auto;display:inline-block;margin:1px;')
        
    def as_etree(self):
        rootElement = etree.Element('root')
        rootDiv = etree.SubElement(rootElement, 'div', style=self.style)
        rootDiv.set('class','ObjectGallery')
        listDiv = etree.SubElement(rootDiv, 'div', style='width:'+self.tableWidth+';height:'+self.height+';overflow:auto;float:left;margin:0px;')
        contentDiv = etree.SubElement(rootDiv, 'div', style = 'width:'+self.contentWidth+';height:'+self.height+';overflow:auto;padding:0px;border-width:0px;margin:0px;float:left;' )

        tableElement = etree.SubElement(listDiv,'table',style='width:'+self.tableWidth+';')
        bodyElement = etree.SubElement(tableElement,'tbody')
        bodyElement.set('class','fancy')
        for iChild, child in enumerate(self.children):
            #Make an entry for each child in the list
            rowElement = etree.SubElement(bodyElement,'tr')

            cellElement = etree.SubElement(rowElement,'td',onclick='galleryListObjClicked(this)')
            cellElement.set('value',self.id+'_item_'+str(iChild))
            
            if iChild == 0: cellElement.set('class','galleryListObj Selected')
            else: cellElement.set('class','galleryListObj NotSelected')
            
            cellElement.text = 'Item '+str(iChild)
            if hasattr(child,'title'): cellElement.text = child.title
            if hasattr(child,'label'): cellElement.text = child.label
            
            #and make a Div for each child of the contentDiv
            correspondingDiv = etree.SubElement(contentDiv,'div',style = 'width:'+self.contentWidth+';height:'+self.height+';padding:0px;margin:0px;overflow:auto;',id=self.id+'_item_'+str(iChild)+'_div')

            if iChild == 0: correspondingDiv.set('class','galleryDivObj displayed')
            
            else: correspondingDiv.set('class','galleryDivObj hidden')

            correspondingDiv.append(child.as_etree())

        return rootElement

class GraphLineChooser(Container):
    graphLineChooserCount = 0
    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
        super(GraphLineChooser,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
        self.help = None
        GraphLineChooser.graphLineChooserCount += 1
        self.id = kw.get('id','graphLineChooser_'+str(GraphLineChooser.graphLineChooserCount))
        
        if xrtnode is not None:
            help = xrtnode.find(XRTNS+'help')
            if help is not None:
                self.help = Help(help,mode='gallery')
                if xrtnode is not None: xrtnode.remove(help)
        elif kw.has_key('help'):
            self.help = Help(ref=kw['help'])
        
        self.height = kw.get('height', '250px')
        self.tableWidth = kw.get('tableWidth', '200px')
        self.contentWidth = kw.get('contentWidth', '200px')
        if 'px' in self.tableWidth and 'px' in self.contentWidth:
            totalWidth = str(int(self.tableWidth[:-2]) + int(self.contentWidth[:-2]))+'px'
        self.style = kw.get('style', 'margin:0px;padding:0px;display:inline-block;') + 'height:'+self.height+'; width:'+totalWidth+';'

    def as_etree(self):
        rootDiv = etree.Element('div', style=self.style, id=self.id)
        rootDiv.set('data-widget-type','CCP4i2LineChooser')
        rootDiv.set('data-table-width', self.tableWidth)
        rootDiv.set('data-content-width', self.contentWidth)
        rootDiv.set('data-height', self.height)
        self.children[0].makeTableText()
        rootDiv.set('data-data',etree.tostring(self.children[0].data_as_etree(), pretty_print=True))
        return rootDiv

# Graph class
class Graph(ReportClass):

  tableCount = 0
  ERROR_CODES = { 1 : { 'description' : 'Plot definition text unreadable. Maybe invalid XML?' },
                  2 : { 'description' : 'Plot definition file unreadable. Maybe invalid XML?' },
                  3 : { 'description' : 'Unable to create RTF file - unable to import PyQt4 ' } }

  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw ):
    super(Graph,self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
    self.id = kw.get('id','graph_'+str(BaseTable.tableCount))
    #self.internalId = kw.get("internalId","table_{0}_{1}".format(jobInfo.get('jobid','xxx'),BaseTable.tableCount))
    BaseTable.tableCount += 1
    self.coldata = []
    self.coltitle = []
    self.plots = []
    self.title = None
    self.tableText = ''
    self.headerText = ''
    self.launch = None
    self.headerSeparator = None
    self.pimpleData = None
    self.outputCsv =  kw.get('outputCsv',True)
    
    # Find title
    if xrtnode is not None:
      if xrtnode.find(XRTNS+'title') is not None:
        #print 'Graph.__init__',xrtnode.find(XRTNS+'title'),xrtnode.find(XRTNS+'title').get('select')
        if xrtnode.find(XRTNS+'title').get('select') is not None and xmlnode is not None:
          xmlEleList = xmlnode.xpath(xrtnode.find(XRTNS+'title').get('select'))
          #print 'Graph.__init__ xmlEleList',xmlEleList
          if len(xmlEleList)>0:
            if isinstance(xmlEleList[0],str):
              self.title = xmlEleList[0]
            else:
              self.title = xmlEleList[0].text
          else:
            self.title = None
        else:
          self.title = xrtnode.find('title').text
      else:
        self.title = xrtnode.get('title',None)
    elif kw.has_key('title'):
      self.title = kw['title']
      
    if kw.get('launcher',None) is not None:
      ele = etree.Element('launch')
      ele.set('label',kw.get('launcher','More graphs'))
      ele.set('exe','loggraph')
      self.launch = Launch(ele,jobInfo=jobInfo,ccp4_data_id='data_'+self.internalId)
    

    if xrtnode is not None:
      help = xrtnode.find(XRTNS+'help')
    else:
      help = kw.get('help',None)
    if help is not None:
      self.help = Help(help,mode='graph')
    else:
      self.help = None

    if xrtnode is not None:
      if  xrtnode.get( "select" ) is not None and xmlnode is not None:
        # Make list of selectd xml nodes
        self.xmldata = xmlnode.xpath( xrtnode.get( "select" ) )
    elif kw.has_key('select') and xmlnode is not None:
       self.xmldata = xmlnode.xpath( kw['select'] )
    else:
      # or put current xml node in a list
      self.xmldata = []
      if xmlnode is not None: self.xmldata.append( xmlnode )
      
   # assemble column data
    if xrtnode is not None:
      for node in xrtnode:
        if node.tag == XRTNS+"data":       
          self.addData(xmldata=self.xmldata,title=node.get('title'),select=node.get('select'),expr=node.get('expr'))
       # Handle table as a block in the xml file
        elif node.tag == XRTNS+"table":
          self.addTable(xmldata=self.xmldata,select=node.get('select'),headers=node.find( XRTNS+'headers'))


    # get plot definitions
    if xrtnode is not None:
      for node in xrtnode:
        if node.tag == XRTNS+"plot":
          self.addPlot(xrtnode=node,xmlnode=xmlnode,select=node.get('select'))


  def addPimpleData(self,xmlnode=None,select=None,usePlotly=False):
    if xmlnode is None: xmlnode = self.xmlnode
    if select is not None: xmlnode=xmlnode.xpath0(select)
    self.pimpleData = etree.Element('pimple_data')
    if usePlotly: self.pimpleData.set('usePlotly','True')
    from copy import deepcopy
    for key in ['headers','data','plot']:
      eleList = xmlnode.xpath(key)
      for ele in eleList:
        #print 'addPimpleData',key,ele.get('id')
        self.pimpleData.append(deepcopy(ele))
    for attr in xmlnode.attrib.keys():
      #print 'addPimpleData',attr,xmlnode.get(attr)
      self.pimpleData.set(attr,xmlnode.get(attr))
    
  def addData(self,xmldata=None,title=None,select=None,expr=None,data=[]):  
    colvals = []
    if len(data)>0:
      colvals.extend(data)
    else:
      if xmldata is None: xmldata = self.xmldata
      for x in xmldata:
        selectedList = x.xpath(select)
        for selitem in selectedList:
          if selitem.text is None:
            val = '-'
          else:
            val = selitem.text.strip()
            if expr is not None:  # allow an expression to be applied to the data
              try:
                val = eval( expr, {"x":float(val)} )
              except:
                val = '-'
          colvals.append(val)
    self.coldata.append(colvals)
    self.coltitle.append(title)
        
  def addTable(self,xmlnode=None,**kw):
    if xmlnode is None:
      if len(self.xmldata)>0:
        xmlnode = self.xmldata[0]
      else:
        return
    if kw.get('select',None) is not None and xmlnode is not None:
      tableEleList = xmlnode.xpath(kw['select'])
      if len(tableEleList)>0: self.tableText = tableEleList[0].text
    
    if kw.get('headers',None) is not None and xmlnode is not None:
      headersEleList = xmlnode.xpath(kw['headers'])
      #print 'Graph.addTable headersEleList',headersEleList
      if len(headersEleList)>0:
         self.headerText = headersEleList[0].text
         self.headerSeparator = headersEleList[0].get('separator',None)
         
  def addPlot(self,xrtnode=None,xmlnode=None,**kw):
    if xmlnode is None:
      if len(self.xmldata)>0: xmlnode = self.xmldata[0]   
    if xrtnode is None:
      #try:
      if kw.get('plotFile',None):
        import CCP4Utils
        try:
          text = CCP4Utils.readFile(kw['plotFile'])
          import re
          #text = re.sub('<xrt:','<'+XRTNS,text)
          #text = re.sub('</xrt:','</'+XRTNS,text)
          text = re.sub('<xrt:','<',text)
          text = re.sub('</xrt:','</',text)
          #print 'addPlot plot',text
          xrtnode = etree.fromstring(text)
        except:
          raise CException(self.__class__,1,str(kw['plotFile']))
      if kw.get('plot',None) is not None:
        import re
        #text = re.sub('<xrt:','<'+XRTNS,kw['plot'])
        #text = re.sub('</xrt:','</'+XRTNS,text)
        text = re.sub('<xrt:','<',kw['plot'])
        text = re.sub('</xrt:','</',text)
        xrtnode = etree.fromstring(text,PARSER())
      #except:
      #  raise CException(self.__class__,1,str(text))

    if xrtnode is not None and kw.get('select',None) is None:
      # Just copy plot directives from xrt - substitute infor any 'select' attribute
      xrtnode.tag = 'plot'
      xrtnode = applySelect(xrtnode,xmlnode)
      self.plots.append( xrtnode )
    elif xmlnode is not None and kw.get('select',None) is not None:
      # Copy plot directives from xml
      plotEleList = xmlnode.xpath(kw['select'])
      for plotEle in plotEleList:
        plotEle.tag = 'plot'
        self.plots.append( plotEle )

  def addPlotObject(self):
    plot = Plot()
    self.plots.append(plot)
    return plot


  def makeTableText(self):
    # pad data arrays to a uniform length   
    if len(self.coldata)>0:
      maxlen = max( [ len(data) for data in self.coldata ] )
      for i in range(len(self.coldata)):
        while len(self.coldata[i]) < maxlen:
          self.coldata[i].append(None)     
      # Convert to single text block
      text = ''
      for i in range(len(self.coldata[0])):
        for col in self.coldata:
          if col[i] is None:
            text += '- '
          else:
           text += str(col[i]) + ' '
        text += "\n"
      self.tableText = text
      # Headers to single text block
      for head in self.coltitle: self.headerText += head + ' '
    self.coldata = []
    self.coltitle = []
 
  def as_html( self ):
    s = "<object type='x-ccp4-widget/LogGraph' id='{0}'><param name='ccp4_data_id' value = 'data_{0}'/></object>\n<ccp4_data id='data_{0}' title='{1}' style='display:none;'>\n".format(self.id,self.title)
    s += "<headers>\n"
    for head in self.coltitle:
      s += head + " "
    s += "\n</headers>\n<data>\n"
    for i in range(len(self.coldata[0])):
      for col in self.coldata:
        s += "{0} ".format(col[i])
      s += "\n"
    s += "</data>\n"
    for plot in self.plots:
      s += "<plot "
      for attr in plot:
        s += "{0}='{1}' ".format(attr[0],attr[1])
      s += "/>\n"
    s += "</ccp4_data>\n"
    return s

  def as_etree(self):

    self.makeTableText()
    root = etree.Element('root')

    if not self.launch:
      obj = etree.Element('object')
      obj.set('type','x-ccp4-widget/LogGraph')
      if self.class_ is not None:
        obj.set('class',self.class_)
      else:
        obj.set('class','loggraph')
      if self.id is not None: obj.set('id',self.id)
      if self.style is not None: obj.set('style',self.style)
      root.append(obj)
      param = etree.Element('param')
      param.set('name','ccp4_data_id')
      param.set('value','data_'+self.internalId)
      obj.append(param)
    else:
      l = self.launch.as_etree()
      if l.tag == 'root':
        root.extend(l)
      else:
        root.append(l)     
    

    root.append(self.data_as_etree())
    
    #if self.help is not None or self.launch is not None:
    if self.help is not None:
      h = self.help.as_etree()
      if h.tag == 'root':
        root.extend(h)
      else:
        root.append(h)

    return root

  def data_as_etree(self):
    eleTree = etree.parse(StringIO('<ccp4:ccp4_data xmlns:ccp4="'+CCP4NS+'"></ccp4:ccp4_data>'))
    ccp4_data = eleTree.getroot()
    if self.title is not None: ccp4_data.set('title',self.title)
    #ccp4_data = etree.Element('ccp4_data')
    ccp4_data.set('id','data_'+self.internalId)
    ccp4_data.set('style','display:none;')

    if self.pimpleData is not None:
      import copy
      #print 'Graph.data_as_etree',self.pimpleData,len(self.pimpleData),self.pimpleData.get('title')
      for item in self.pimpleData:
        ccp4_data.append(copy.deepcopy(item))
      if self.pimpleData.get('title') is not None: ccp4_data.set('title',self.pimpleData.get('title'))
      if self.pimpleData.get('usePlotly') is not None: ccp4_data.set('usePlotly',self.pimpleData.get('usePlotly'))
    else:
      header = etree.Element('headers')
      header.text = self.headerText
      if self.headerSeparator is not None: header.set('separator',self.headerSeparator)
      ccp4_data.append(header)

      data = etree.Element('data')
      data.text = self.tableText
      ccp4_data.append(data)

      #print 'data_as_etree',self.plots
      for plot in self.plots:
        if isinstance(plot,Plot):
          ccp4_data.append(plot.as_etree())
        else:
         ccp4_data.append(plot)

    return ccp4_data

  def getListOfRows(self):
    rowdatalist = []
    if len(self.coldata)>0:
      rowdatalist = self.coldata
    elif len(self.tableText)>0:
      for row in self.tableText.split('\n'):
        rowdata = []
        for item in row.split(): rowdata.append(item)
        if len(rowdata)>0: rowdatalist.append(rowdata)
    elif self.pimpleData is not None:    
      dataEleList = self.pimpleData.xpath('./data')
      #print 'Graph.getListOfRows dataEleList',dataEleList
      if len(dataEleList)>0:
        for row in dataEleList[0].text.split('\n'):
          rowdata = []
          for item in row.split(): rowdata.append(item)
          if len(rowdata)>0: rowdatalist.append(rowdata)
      # Do we have some column headers - should be space separated words
      headerEleList = self.pimpleData.xpath('./headers')
      if len(headerEleList)>0:
        separator = headerEleList[0].get('separator',' ')
        headerList = headerEleList[0].text.split(separator)
        if len(rowdatalist)>0 and len(rowdatalist[0])==len(headerList):
          rowdatalist.insert(0,headerList)      
    return rowdatalist
      

  def data_as_rtf(self,document=None):
    try:
      from PyQt4 import QtGui
    except:
      raise CException(self.__class__,3)

    # Is there some table data to output
    #print 'data_as_rtf coldata',len(self.coldata),'tableText',self.tableText    
    if document is None:
      document = QtGui.QTextDocument()
    cursor = QtGui.QTextCursor(document)
    table = cursor.insertTable(len(coldata),len(coldata[0]))
    #print 'data_as_rtf columns,rows',table.rows(),table.columns(),'should be',len(coldata),len(coldata[0])
    coldata = self.getListOfRows()
    ir = -1
    for col in coldata:
      ir += 1
      ic = -1
      for item in col:
        ic += 1
        #print 'data_as_rtf ir,ic,item',ir,ic,item
        cell = table.cellAt(ir,ic)
        cell.firstCursorPosition().insertText(str(item))
    return table

  def data_as_csv(self,fileName=None):
    rowList = self.getListOfRows()
    if len(rowList)==0: return CErrorReport(Report,107,fileName)
    import csv
    try:
      f = open(fileName,'wb')
    except:
      return CErrorReport(Report,107,fileName)
    try:
      writer = csv.writer(f)
      if len(self.coltitle)>0:
        writer.writerow(self.coltitle)
      for row in rowList: writer.writerow(row)
    except Exception as e:
      return CErrorReport(Report,107,fileName)
    try:
      f.close()
    except:
      return CErrorReport(Report,107,fileName)
    
    return CErrorReport()

  def data_as_xml(self,fileName=None):
    rowList = self.getListOfRows()
    if len(rowList)==0: return CErrorReport(Report,108,fileName)
    try:
      f = open(fileName,'wb')
    except:
      return CErrorReport(Report,108,fileName)
    try:
      dataAsEtree = self.data_as_etree()
      #As it comes out here, the data tag is in ccp4 namespace (i.e. ccp4_data:data)
      dataAsEtree.tag = 'CCP4Table'
      from lxml import etree
      rootNode = etree.Element('CCP4ApplicationOutput')
      rootNode.append(dataAsEtree)
      from lxml import etree
      dataAsText = etree.tostring(rootNode,pretty_print=True)
      f.write(dataAsText)
    except Exception as e:
      return CErrorReport(Report,108,fileName)
    try:
      f.close()
    except:
      return CErrorReport(Report,108,fileName)
    
    return CErrorReport()

# Graph class
class FlotGraph(Graph):

    def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw ):
        super(FlotGraph, self).__init__(xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw)
        self.initiallyDrawn = kw.get('initiallyDrawn',True)
        
        #if kw.get('launcher',None) is not None:
        self.launch = None
        self.launchOnly = False
        self.flot_id = kw.get ( 'internalId', None )
        from lxml import etree
        ele = etree.Element('launch')
        if  kw.get('launcher',None) is not None:
          self.launchOnly = True
          ele.set('label',kw.get('launcher','More graphs'))
          ele.set('exe','loggraph')
          if kw.get('withLaunch',True):
            self.launch = Launch(ele,jobInfo=jobInfo,ccp4_data_id='data_'+self.internalId)

    def as_etree(self):
        self.makeTableText()
        root = etree.Element('root')
        
        styleDict = {'height':'250px', 'width':'250px','margin':'0px','padding':'0px','display':'inline-block','border':'1px solid black','margin-top':'1px'}
        if self.style is not None:
            styleBits = self.style.split(';')
            extraStyleDict = {}
            for styleBit in styleBits:
                stylePair = styleBit.split(':')
                if len(stylePair) == 2: extraStyleDict[stylePair[0].strip()] = stylePair[1].strip()
            styleDict.update(extraStyleDict)
        styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
        launchButtonLocation = root
        if not self.launchOnly:
            surroundingDiv = etree.SubElement(root,'div',style=styleStr)
            launchButtonLocation = surroundingDiv
            #Here I shring the Flot Div if I am going to append a launch button
            if self.launch is not None:
                heightPx = int(styleDict['height'][:-2])
                heightPx -= 30;
                styleDict['height'] = str(heightPx) + 'px'
            if 'border-width' in styleDict: del styleDict['border-width']
            if 'border-style' in styleDict: del styleDict['border-style']
            if 'border-color' in styleDict: del styleDict['border-color']
            styleDict['margin-top'] = '0px'
            styleDict['margin'] = '0px'
            styleDict['padding'] = '0px'
            styleDict['border'] = '0px solid white'
            
            styleStr = ';'.join([key + ':'+styleDict[key] for key in styleDict]) + ';'
            
            if self.flot_id :
            
                if self.outputXml:
                    drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_url(), data_is_urls=True, renderer='CCP4i2Widgets.CCP4FlotRenderer', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn), id=self.flot_id )
                else:
                    drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_id(), data_is_urls=False, renderer='CCP4i2Widgets.CCP4FlotRenderer', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn), id=self.flot_id )
            else :

                if self.outputXml:
                    drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_url(), data_is_urls=True, renderer='CCP4i2Widgets.CCP4FlotRenderer', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn))
                else:
                    drawnDiv = DrawnDiv(style = styleStr, height=styleDict['height'], width=styleDict['width'], data = self.data_id(), data_is_urls=False, renderer='CCP4i2Widgets.CCP4FlotRenderer', require='CCP4i2Widgets', initiallyDrawn=str(self.initiallyDrawn))
            

            #print etree.tostring(drawnDiv.as_etree(), pretty_print=True)
            surroundingDiv.append(drawnDiv.as_etree())
        if self.launch is not None:
            l = self.launch.as_etree()
            if l.tag == 'root':
                launchButtonLocation.extend(l)
            else:
                launchButtonLocation.append(l)
        
        if not self.outputXml: root.append(self.data_as_etree())

        #if self.help is not None or self.launch is not None:
        if self.help is not None:
            h = self.help.as_etree()
            if h.tag == 'root':
                root.extend(h)
            else:
                root.append(h)
        
        
        return root
    
    
class GenericElement:
  def __init__(self,tag=None,text=None,**kw):
    self.tag = tag
    self.id = kw.get('id',None)
    self.class_ = kw.get('class_',None)
    self.text = text
    self.attributes = kw
    self.children = []
    #print 'GenericElement.init', self.tag , self.text

  def append(self,name,text=None,**kw):
    if isinstance(name,GenericElement):
      self.children.append(name)
    else:
      self.children.append(GenericElement(name,text,**kw))
    return self.children[-1]

  def set(self,key,value):
    self.attributes[key] = value

  def as_etree(self):
    #print 'GenericElement.as_etree',self.tag,self.text,self.attributes
    ele = etree.Element(self.tag)
    if self.id is not None: ele.set('id',self.id)
    if self.class_ is not None: ele.set('class',self.class_)
    if self.text is not None: ele.text = self.text
    for key,value in self.attributes.items():
      ele.set(key,str(value).strip())
    for obj in self.children:
      ele.append(obj.as_etree())
    return ele
  
class Plot(GenericElement):

  ERROR_CODES = { 101 : { 'severity': SEVERITY_WARNING , 'description' : 'Failed loading Plot schema' },
                  102 : {  'description' : 'Failed validation' },
                  }

  def __init__(self,text=None,**kw):
    GenericElement.__init__(self,tag='plot',text=text,**kw)

  def validate(self):
    import os,CCP4Utils
    try:
      schemafile = os.path.join(CCP4Utils.getCCP4I2Dir(),'pimple','CCP4ApplicationOutput.xsd')
      schematree = etree.fromstring(open(schemafile).read())
      schema = etree.XMLSchema(schematree)
    except:
      return CErrorReport(self.__class__,101,schemafile)

    tree = self.as_etree()
    
    table = etree.Element('CCP4Table')
    table.append(tree)
    output = etree.Element('CCP4ApplicationOutput')
    output.append(table)
    valid = schema.validate(output)
    if not valid:
      log = str(schema.error_log)
      #print 'validate log',log
      err = CErrorReport()
      err.append(self.__class__,102,log)
      return err
    else:
      return CErrorReport()
    
  def as_etree(self):
    tree = GenericElement.as_etree(self)
    return tree
    
    
class IODataList:
  OBJ_WIDTH = '600'
  OBJ_HEIGHT = '30'

  def __init__(self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw):
    #print 'IODataList.__init__',xrtnode, xmlnode
    self.jobInfo = {}
    self.jobInfo.update(jobInfo)
  

  def as_etree0(self,role=None):

    import CCP4DbApi
    root = etree.Element('div')
    root.set('class','datalist')

    '''
    Aiming for something like
    <object type="x-ccp4-widget/CPdbDataFile" id="result_file_pdb" width="600" height="30">
    <param name="mimeTypeName" value="chemical/x-pdb" />
    <param name="fullPath" value="/Users/lizp/Desktop/dev/ccp4i2/test/data/1df7.pdb" />
    </object>
    '''

    if not self.jobInfo.has_key(role): return root
 
    for fileInfo in self.jobInfo[role]:
      
      obj = etree.Element('object')
      obj.set('class','qt_object')
      obj.set('type','x-ccp4-widget/C'+fileInfo['filetypeclass'])
      obj.set('id','input_file_'+str(fileInfo['fileId']))
      obj.set('width',IODataList.OBJ_WIDTH)
      obj.set('height',IODataList.OBJ_HEIGHT)
      if role == 'importedfiles':
         obj.set('role','input')
      elif role == 'inputfiles':
         obj.set('role','input')
      else:
        obj.set('role','output')
      root.append(obj)
      mimetype = etree.Element('param')
      mimetype.set('name','mimeTypeName')
      mimetype.set('value',fileInfo['filetype'])
      obj.append(mimetype)
      fileid = etree.Element('param')
      fileid.set('name','dbFileId')
      fileid.set('value',str(fileInfo['fileId']))
      obj.append(fileid)

      
      for key1,key2 in [['filename','baseName'],['relpath','relPath'],['projectid','project'],['annotation','annotation']]:
        if fileInfo.get(key1,None) is not None:
          param =  etree.Element('param')
          param.set('name',key2)
          param.set('value',str(fileInfo[key1]))
          obj.append(param)
    
    return root

class InputData(IODataList):

  def as_etree(self):
    import CCP4DbApi
    root = etree.Element('root')
    anchor = etree.Element('a')
    anchor.set('name','inputData')
    root.append(anchor)
    head = etree.Element('h5')
    head.set('class','section')
    head.text = 'Input Data'
    root.append(head)
    root.append(self.as_etree0(role='inputfiles'))
    return root
  
class OutputData(IODataList):

  def as_etree(self):
    import CCP4DbApi
    root = etree.Element('root')
    anchor = etree.Element('a')
    anchor.set('name','outputData')
    root.append(anchor)
    head = etree.Element('h5')
    head.set('class','section')
    head.text = 'Output Data'
    root.append(head)
    root.append(self.as_etree0(role='outputfiles'))
    return root
    
class ImportedFiles(IODataList):

  def as_etree(self):
    import CCP4DbApi
    root = etree.Element('root')
    anchor = etree.Element('a')
    anchor.set('name','importedFiles')
    root.append(anchor)
    head = etree.Element('h5')
    head.set('class','section')
    head.text = 'Imported Files'
    root.append(head)
    root.append(self.as_etree0(role='importedfiles'))
    return root

class Title:
  def __init__(self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw):
    import time
    #print 'Title.__init__ jobInfo',jobInfo
    self.title0 = 'Job '+str(jobInfo['jobnumber'])+': '+ jobInfo['tasktitle']
    if jobInfo.get('jobtitle',None) is not None and len(jobInfo['jobtitle'])>0:
      self.title1 = jobInfo['jobtitle']
    else:
      self.title1 = None

    self.title2 =  time.strftime('%H:%M %d-%b-%Y',time.localtime(jobInfo['creationtime']))

  def as_etree(self):
    root =  etree.Element('div')
    root.set('class','title')
    #for item in ['title0','title1','title2']:
    for item in ['title1','title2']:
      if getattr(self,item,None) is not None:
        h1 = etree.Element('p')
        h1.set('class',item)
        h1.text = getattr(self,item)
        root.append(h1)

    '''
    intLinks = etree.Element('p')
    intLinks.set('class','links')
    root.append(intLinks)
    '''
    
    return root
  
class JobDetails:
  def __init__(self, xrtnode=None, xmlnode=None, jobInfo = {},**kw):
    self.id = kw.get('id',None)
    self.class_ = kw.get('class_',None)
    self.jobInfo = {}
    self.jobInfo.update(jobInfo)

  def getI2Version(self):
    # try getting i2 version from the diagnostic.xml file
    diagfile = os.path.normpath(os.path.join(self.jobInfo['fileroot'],'diagnostic.xml'))
    #print 'JobDetails.getI2Version diagfile',diagfile
    if os.path.exists(diagfile):
      import CCP4File
      x = CCP4File.CI2XmlHeader()
      x.loadFromXml(diagfile)
      return str(x.ccp4iVersion)
    else:
      return 'Unknown'

  def as_etree(self):
    import time
    fold = Fold(label='Job run details',brief='Run')
    tab = fold.addTable()
    tab.internalId='data_JobDetails'
    self.jobInfo['runtime'] = round(float(self.jobInfo['runtime']),1)
    self.jobInfo['finishtime'] = time.strftime('%H:%M %d-%b-%Y',time.localtime(self.jobInfo['finishtime']))
    for key1,key2 in [['status','Job status'],['finishtime','Finish time'],['runtime','Running time']]:
      if self.jobInfo.has_key(key1):
        tab.addData(title=key2,data=[str(self.jobInfo[key1])])
    tab.transpose=True
    return fold.as_etree()


  def makeRow(self,key,value):
    tr = etree.Element('tr')
    th = etree.Element('th')
    th.text = str(key)
    tr.append(th)
    td = etree.Element('td')
    td.text = str(value)
    tr.append(td)
    return tr

class Help:
  def __init__(self,xrtnode=None,xmlnode=None,jobInfo={},**kw):
    self.id = kw.get('id',None)
    if xrtnode is not None:
      self.ref = xrtnode.get('ref',None)
    else:
      self.ref = kw.get('ref',None)
    if self.ref is not None and self.ref[0]=='$':
      import sys
      import re,CCP4Utils
      if sys.platform == "win32":
        tweak = CCP4Utils.getCCP4I2Dir().replace('\\','/') # This had better be sane.
        tweakref = self.ref.replace('\\','/')              # Ditto
        self.ref = re.sub('\$CCP4I2',tweak,tweakref)
        self.ref = os.path.normpath(self.ref)
      else:
        self.ref = re.sub('\$CCP4I2',CCP4Utils.getCCP4I2Dir(),self.ref)
    if xrtnode is not None:
      self.label =  xrtnode.get('label','About this '+kw.get('mode',''))
    else:
      self.label = kw.get('label','About this '+kw.get('mode',''))

  def as_etree(self):
    root = etree.Element('root')
    span = etree.Element('span')
    if self.id is not None: span.set('id',self.id)
    root.append(span)
    span.set('class','help')
    if self.ref is not None:
      a =  etree.Element('a')
      a.set('href',self.ref)
      a.text= self.label
      span.append(a)
    return root

class Launch:
  
  counter = 0
  def __init__(self,xrtnode=None,xmlnode=None,jobInfo={},**kw):
    Launch.counter += 1
    self.id = kw.get('id',None)
    #self.internalId = 'launcher_'+str(jobInfo.get('jobid','xxx'))+'_'+str(Launch.counter)
    self.jobId = jobInfo.get('jobid',None)
    self.exe=None
    self.label=None
    # This is a list - could be more than one graph 
    self.ccp4_data_id=[]
    self.sceneFile =None

    if xrtnode is not None:
      self.exe = xrtnode.get('exe',None)
      self.label = xrtnode.get('label',None)
      if xrtnode.get('ccp4_data_id',None) is not None: self.ccp4_data_id.append(xrtnode.get('ccp4_data_id'))
      self.sceneFile = xrtnode.get('sceneFile',None)
    self.exe = kw.get('exe',self.exe)
    self.label = kw.get('label',self.label)
    if kw.get('ccp4_data_id',None) is not None: self.ccp4_data_id.append(kw.get('ccp4_data_id'))
    # Use relative path in case project moved - Launcher widget will know jobId and be able to find file
    self.sceneFile = kw.get('sceneFile',self.sceneFile)
    if self.sceneFile is not None: self.sceneFile = './'+os.path.split(self.sceneFile)[-1]

  def appendDataId(self,ccp4_data_id=None):
    if self.ccp4_data_id.count(ccp4_data_id)==0:
      self.ccp4_data_id.append(ccp4_data_id)

  def as_etree(self):

    root =etree.Element('root')
    obj = etree.Element('object')
    obj.set('class','qt_launch')
    obj.set('type','x-ccp4-widget/CLauncherButton')
    if self.id is not None: obj.set('id',self.id)
    root.append(obj)

    for key in ['label','exe','sceneFile']:
      if getattr(self,key,None ) is not None:
        p = etree.Element('param')
        p.set('name',key)
        p.set('value',getattr(self,key))
        obj.append(p)
    for data_id in self.ccp4_data_id:
      p = etree.Element('param')
      p.set('name','ccp4_data_id')
      p.set('value',data_id)
      obj.append(p)
    if self.jobId is not None:
      p = etree.Element('param')
      p.set('name','jobId')
      p.set('value',str(self.jobId))
      obj.append(p)
  
    return root

class Download:
  
  counter = 0
  def __init__(self,xrtnode=None,xmlnode=None,jobInfo={},**kw):
    Launch.counter += 1
    self.id = kw.get('id',None)
    #self.internalId = 'download_'+str(jobInfo.get('jobid','xxx'))+'_'+str(Download.counter)
    self.jobId = jobInfo.get('jobid',None)
    self.dataName=None
    self.label=None

    if xrtnode is not None:
      self.dataName = xrtnode.get('dataName',None)
      self.label = xrtnode.get('label',None)
    self.dataName = kw.get('dataName',self.dataName)
    self.label = kw.get('label',self.label)


  def as_etree(self):

    #root =etree.Element('root')
    obj = etree.Element('object')
    obj.set('class','qt_launch')
    obj.set('type','x-ccp4-widget/CDownloadButton')
    if self.id is not None: obj.set('id',self.id)
    #root.append(obj)

    for key in ['label','dataName']:
      if getattr(self,key,None ) is not None:
        p = etree.Element('param')
        p.set('name',key)
        p.set('value',getattr(self,key))
        obj.append(p)
    if self.jobId is not None:
      p = etree.Element('param')
      p.set('name','jobId')
      p.set('value',str(self.jobId))
      obj.append(p)
  
    return obj
  
class LaunchTask:
  
  counter = 0
  def __init__(self,xrtnode=None,xmlnode=None,jobInfo={},**kw):
    Launch.counter += 1
    self.id = kw.get('id',None)
    #self.internalId = 'launcher_'+str(jobInfo.get('jobid','xxx'))+'_'+str(Launch.counter)
    self.jobId = jobInfo.get('jobid',None)
    if xrtnode is not None:
      self.taskName = xrtnode.get('taskName',None)
      self.label = xrtnode.get('label',None)
      self.ccp4_data_id = xrtnode.get('ccp4_data_id',ccp4_data_id)
    
    self.taskName = kw.get('taskName',self.taskName)
    self.label = kw.get('label',self.label)
    self.ccp4_data_id = kw.get('ccp4_data_id',self.ccp4_data_id)

  def as_etree(self):

    #root =etree.Element('root')
    obj = etree.Element('object')
    obj.set('class','qt_launch_task')
    obj.set('type','x-ccp4-widget/CLaunchTaskButton')
    if self.id is not None: obj.set('id',self.id)
    #root.append(obj)

    for key in ['label','taskName','ccp4_data_id']:
      if getattr(self,key,None ) is not None:
        p = etree.Element('param')
        p.set('name',key)
        p.set('value',getattr(self,key))
        obj.append(p)
    
    if self.jobId is not None:
      p = etree.Element('param')
      p.set('name','jobId')
      p.set('value',str(self.jobId))
      obj.append(p)
  
    return obj

class Picture:

  ERROR_CODES = { 101 : { 'description' : 'Error reading picture definition' },
                  102 : { 'description' : 'Error parsing xml from scene file' },
                  103 : { 'description' : 'Error parsing xml from scene description' },
                  104 : { 'description' : 'No scene description provided' } }

  def __init__(self, xrtnode=None,xmlnode=None,jobInfo={},**kw):
    import copy
    self.id = kw.get('id',None)
    self.class_ = kw.get('class_',None)
    self.launchList = []

    bodyEle = etree.Element('ccp4i2_body')
    sceneEle = etree.Element('scene')
    bodyEle.append(sceneEle)

    sceneRoot = None
    if xrtnode is not None:
      sceneRoot = xrtnode
    elif kw.get('scene',None) is not None:
      try:
        sceneRoot = etree.fromstring( kw['scene'], PARSER() )
      except:
        raise CException(self.__class__,103, kw['scene'])
    elif kw.get('sceneFile',None) is not None:
      import CCP4Utils,os
      fileName = kw.get('sceneFile')
      if fileName[0:8] == '$CCP4I2/':
        fileName = os.path.join(CCP4Utils.getCCP4I2Dir(),fileName[8:])
      #print 'Report.Pictures fileName',fileName
      if not os.path.exists(fileName):
        raise CException(self.__class__,101,fileName)
      else:
        try:
          sceneRoot = etree.fromstring( open(fileName ).read(), PARSER() )
        except:
          raise CException(self.__class__,102,fileName)
    
    if sceneRoot is None:
      raise CException(self.__class__,104,fileName)
      
    for child in sceneRoot:
      sceneEle.append(copy.deepcopy(child))
      bodyEle = applySelect(bodyEle,xmlnode,jobInfo)
  
    #print etree.tostring(bodyEle,pretty_print=True)
    
    import CCP4File,glob
    sceneFileList = glob.glob(jobInfo.get('fileroot','')+'scene_*.scene.xml')
    indx = 0
    for sceneFile in sceneFileList:
      indx = max ( indx, int(sceneFile[0:-10].split('_')[-1]))
    #print 'Picture.__init__',sceneFileList,indx

    if jobInfo.has_key('fileroot') and jobInfo['fileroot'] is not None:
      self.picDefFile = CCP4File.CI2XmlDataFile(fullPath=jobInfo['fileroot']+'scene_'+str(indx+1)+'.scene.xml')
    else:
      self.picDefFile = CCP4File.CI2XmlDataFile(os.path.join(os.getcwd(),'scene_'+str(indx+1)+'.scene.xml'))
    self.picDefFile.header.setCurrent()
    self.picDefFile.header.function = 'MGSCENE'
    self.picDefFile.header.projectId = jobInfo.get('projectid',None)
    self.picDefFile.header.projectName = jobInfo.get('projectname',None)
    self.picDefFile.header.jobId = jobInfo.get('jobid',None)
    self.picDefFile.header.jobNumber = jobInfo.get('jobnumber',None)
    self.picDefFile.saveFile(bodyEtree=bodyEle)

    #report.pictureQueue.append(self.picDefFile.__str__())
    if xrtnode is not None:
      self.label = xrtnode.get('label',None)
    else:
      self.label= kw.get('label',None)
    
    launchNode = etree.Element('launch')
    launchNode.set('exe','CCP4mg')
    launchNode.set('label','View in CCP4mg')
    if self.picDefFile is not None:
      launchNode.set('sceneFile',str(self.picDefFile))
    self.launchList.append(Launch(launchNode,jobInfo=jobInfo))
    launchNode = etree.Element('launch')
    launchNode.set('exe','Coot')
    launchNode.set('label','View in Coot')
    #launchNode.set('cootScript',self.cootScript)
    self.launchList.append(Launch(xrtnode=launchNode,jobInfo=jobInfo))

    
  def as_etree(self):
    
    root = etree.Element('root')
    if self.picDefFile is None: return root

    # Make the path relative or will be broken on exporting project
    fileRoot = os.path.split(os.path.splitext(os.path.splitext(str(self.picDefFile))[0])[0])[-1]
    picFile = './'+fileRoot+'.png'
    #print 'Picture.as_etree picFile',picFile
    
    t = etree.Element('table')
    if self.class_ is None:
      t.set('class','picture')
    else:
      t.set('class',self.class_)
    if self.id is not None: t.set('id',self.id)
    root.append(t)
    r = etree.Element('tr')
    t.append(r)
    
    d =  etree.Element('td')
    d.set('colspan',str(len(self.launchList)))
    r.append(d)
    image = etree.Element('img')
    image.set('class','mg')
    image.set('src',picFile)
    image.set('alt','CCP4mg is preparing picture: '+fileRoot+'.png')
    d.append(image)

    d =  etree.Element('td')
    r.append(d)
    if self.label is not None:
      anno = etree.Element('p')
      anno.set('class','annotation')
      anno.text = self.label
    d.append(anno)

    buttonRow = etree.Element('tr')
    t.append(buttonRow)
    for launch in self.launchList:
      d =  etree.Element('td')
      buttonRow.append(d)
      l = launch.as_etree()
      if l.tag == 'root':
        d.extend(l)
      else:
        d.append(l)
    
    return root

# Main program, if run from command line.
# Usage: python report.py my.xrt my.xml
if __name__ == "__main__":
  import sys
  xrt = etree.fromstring( open( sys.argv[1] ).read(), PARSER() )
  xml = etree.fromstring( open( sys.argv[2] ).read(), PARSER() )
  xreport = xrt.xpath( "/report" )[0]
  report = Report( xreport, xml )
  #print report.as_html()

  tree= report.as_etree()
  print etree.tostring(tree,pretty_print=True)


class GenericReport(Report):
  def __init__(self,xmlnode=None,jobInfo={},**kw):
    Report. __init__(self,xmlnode=xmlnode,jobInfo=jobInfo,**kw)
    title = jobInfo.get('tasktitle','')
    self.addText(text=title)


class Reference:
  ERROR_CODES = {  }

  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {}, **kw ):
    self.id = kw.get('id',None)
    if xrtnode is not None:
      data = xrtnode
    else:
      data = kw
    self.href = data.get('href',None)
    self.authorList = data.get('authorList',[])
    if data.get('author',None) is not None: self.authorList.append(data.get('author',None))
    self.source = data.get('source',None)
    self.articleTitle = data.get('articleTitle',None)
    self.articleLink = data.get('articleLink',None)

  def as_etree(self):
    root = etree.Element('div')
    root.set('class','bibreference')
    '''
    div = etree.SubElement(root,'div')
    div.set('style','float:left;')
    if USEQTICONS:
      icon = etree.SubElement(div,'object')
      icon.set('class','qticon')
      icon.set('type','x-ccp4-widget/CReferenceIcon')
    else: 
      icon = etree.SubElement(div,'image')
      icon.set('height','24')
      icon.set('width','24')
      icon.set('src',htmlBase()+"/book.svg")
      icon.set('alt','Bibliographic reference icon')
    div = etree.SubElement(root,'div')
    div.set('style','float:left;')
    '''
    div = root
    p = etree.SubElement(div,'p')    
    p.set('class','articletitle')
    if self.articleLink is not None:
      a = etree.SubElement(p,'a')
      a.set('href',self.articleLink)
      a.text = self.articleTitle
    else:
      p.text = self.articleTitle
    #div = etree.SubElement(root,'div')
    #div.set('style','clear:both;') 
    if len(self.authorList)>0:
      ele = etree.SubElement(div,'p')
      ele.set('class','authors')
      t = ''
      for item in self.authorList: t += item + ', '
      ele.text = t[0:-2]
    ele = etree.SubElement(div,'p')
    ele.set('class','source')
    ele.text = self.source

    return root

    
class ReferenceGroup(Container):
  ERROR_CODES = { 100 : { 'description' : 'Failed attempting to load MedLine file - file not found'  }
                  }
  def __init__( self, xrtnode=None, xmlnode=None, jobInfo = {} , **kw):
    Container.__init__( self, xrtnode=xrtnode, xmlnode=xmlnode, jobInfo=jobInfo, **kw )
    self.label='References'
    self.tag='div'
    self._class = 'bibreference_group'
    self.taskName = kw.get('taskName',None)

  def as_etree(self):

    if self.title is not None:
      title = self.title
    elif self.taskName is not None:
      import CCP4TaskManager
      title = CCP4TaskManager.TASKMANAGER().getTitle(taskName=self.taskName)+ ' references'
    else:
      title = 'References'
      
    root = etree.Element('div')
    root.set('class','bibreference_group')

    div = etree.SubElement(root,'div')
    div.set('style','float:left;')
    if USEQTICONS:
      icon = etree.SubElement(div,'object')
      icon.set('class','qticon')
      icon.set('type','x-ccp4-widget/CReferenceIcon')
      param = etree.SubElement(icon,'param')
      param.set('name','taskName')
      param.set('value',str(self.taskName))
    else: 
      icon = etree.SubElement(div,'image')
      icon.set('height','24')
      icon.set('width','24')
      icon.set('src',htmlBase()+"/book.svg")
      icon.set('alt','Bibliographic reference icon')
    div = etree.SubElement(root,'div')
    div.set('style','float:left;') 
    #p = etree.SubElement(div,'p')    
    div.set('class','reference_group_title')
    div.text=title
    div = etree.SubElement(root,'div')
    div.set('style','clear:both;') 
    for child in self.children:
      tree = child.as_etree()
      if tree is not None: div.append(tree)

    return root
    
  def loadFromMedLine(self,taskName=None,fileName=None):
    if fileName is None and taskName is not None:
      import CCP4TaskManager
      fileNameList = CCP4TaskManager.TASKMANAGER().searchReferenceFile(taskName)
      if len(fileNameList)>0:fileName=fileNameList[0]
    if fileName is None or not os.path.exists(fileName):
      self.errReport.append(self.__class__,100,'Taskname: '+str(taskName)+' Filename: '+str(fileName))
      return
    self.taskName = taskName
    import re
    import CCP4Utils
    try:
      text = CCP4Utils.readFile(fileName=fileName)
    except CException as e:
      self.errReport.extend(e)
      return
    textList = text.split('\nPMID- ')
    #print 'ReferenceGroup.loadFromMedLine textList',textList
    for text in textList:
      ref = Reference()
      m = re.search(r'TI  -(.*)',text)
      if m is not None: ref.articleTitle = m.groups()[0].strip()
      m = re.search(r'SO  -(.*)',text)
      if m is not None: ref.source = m.groups()[0].strip()
      m = re.findall(r'AU  -(.*)',text)
      for item in m: ref.authorList.append(item.strip())
      m = re.search(r'URL -(.*)',text)
      if m is not None: ref.articleLink = m.groups()[0].strip()
      if ref.source is not None: self.append(ref)
    #print 'ReferenceGroup.loadFromMedLine',ref



class BaublesHtml:

  ERROR_CODES = { 1 : { 'description' : 'Failed to find Baubles html file' }
                }

  def __init__(self,fileName):
    if not os.path.exists(fileName):
      raise CErrorReport(self.__class__,1,fileName)

    import lxml.html
    self.fileNode = lxml.html.parse(fileName)

  def convert(self,fileName):
    #self.extendHead()
    self.replaceApplets()
    self.saveFile(fileName)


  def extendHead(self,base=None,jsFileList=['xreport.js']):
    if base is None: base = htmlBase()
    head = self.fileNode.xpath("/html/head[1]")

    for jsFile in jsFileList:
      script = etree.Element('script')
      script.set('src',base+'/'+jsFile)
      head.insert(0,script)

  def replaceApplets(self):
    appletDivList = self.fileNode.xpath("//div[@class='applet']")
    #print 'appletDivList',appletDivList

    for appletDiv in appletDivList:
      parent = appletDiv.getparent()
      indx = parent.index(appletDiv)
      tableName,graphTitleList,graphTypeList,graphColumnsList,columnNameList,columnDataList = self.parseLoggraph(appletDiv)
      graphObj = FlotGraph(title=tableName)
      for n in range(0,min(len(columnNameList),len(columnDataList))):
        graphObj.addData(title=columnNameList[n],data=columnDataList[n])
      for n in range(0,len(graphTitleList)):
        plotObj = Plot(title=graphTitleList[n],description=graphTitleList[n])
        graphObj.plots.append(plotObj)
        for i in range(1,len(graphColumnsList[n])):
          plotObj.append('plotline',text=columnNameList[graphColumnsList[n][i]], xcol=str(graphColumnsList[n][0]), ycol=str(graphColumnsList[n][i]))
      
      parent.insert(indx,graphObj.as_etree())
      parent.remove(appletDiv)

  def parseLoggraph(self,appletDiv):
    import os
    graphTitleList = []
    graphTypeList = []
    graphColumnsList = []
    tableList = appletDiv.xpath(".//param[@name='table']")
    #print 'parseLoggraph',appletDiv,tableList
    if len(tableList)>0:
      splitList = tableList[0].get('value').split('$$')
      dum,tabText,graphText = splitList.pop(0).split('$')
      tableName = tabText.split(':')[1].strip()
      graphSplit = graphText.split(':')
      #print 'parseLoggraph',graphSplit
      for nn in range(0,len(graphSplit)/4):
        graphTitleList.append(graphSplit[(nn*4)+1])
        graphTypeList.append(graphSplit[(nn*4)+2])
        columnText = graphSplit[(nn*4)+3].split(',')
        graphColumnsList.append([])
        for item in columnText:
          try:
            graphColumnsList[-1].append(int(item))
          except:
            print 'Error interpreting columns for graph',graphTitleList[-1]
      #print 'parseLoggraph',graphTitleList,graphTypeList,graphColumnsList
      columnNameList = splitList.pop(0).split()
      #print 'parseLoggraph columnNameList',columnNameList
      columnDataList = []
      nColumns = len(splitList[1].split(os.linesep)[1].split())
      for n in range(0,nColumns): columnDataList.append([])
      for row in splitList[1].split(os.linesep):
        dataTextList = row.split()
        if len(dataTextList)>0:
          for ii in range(0,min(len(dataTextList),nColumns)):
            try:
              columnDataList[ii].append(float(dataTextList[ii]))
            except:
              columnDataList[ii].append(None)
          
      #print 'parseLoggraph',columnDataList
      return tableName,graphTitleList,graphTypeList,graphColumnsList,columnNameList,columnDataList

  def saveFile(self,fileName):
    import lxml.html
    import CCP4Utils
    text = lxml.html.tostring(self.fileNode,method='html')
    CCP4Utils.saveFile(fileName,text)

    #Beware testing may need
    #import sys; sys.path.append('/Users/lizp/Desktop/dev/ccp4i2-devel/utils'); startHTTPServer()

"""
$TABLE :table name:
$GRAPHS :graph1 name:graphtype:column_list: :graph2 name:graphtype:column_list:
        :graph 3 ...: ... $$
column1_name column2_name ... $$ any_characters $$ numbers $$

where:

table name, graphN name
    are arbitrary strings (without newline) 
columnN_name
    are arbitrary strings without tab, space, newline 
graphtype
    is 
A[UTO]
    for fully automatic scaling (e.g. ... :A:1,2,4,5:) 
N[OUGHT]
    for automatic y coordinate scaling, where y lowest limit is 0 (e.g. ... :N:1,2,4,5:) 
XMIN|XMAXxYMIN|YMAX
    for user defined scaling where XMIN ... are axis limits (e.g. ... :0|100x-1|1:1,2,4,5:) 
any_characters
    are treated as a comment. They can be eventually used as a human oriented table header 
numbers
    represents the table itself. (See parsing algorithm below) 
"""


#========================================================================================
def test(arg1,arg2,arg3=None):
  xrt = etree.fromstring( open( arg1 ).read(), PARSER() )
  xml = etree.fromstring( open( arg2 ).read(), PARSER() )
  #print 'text',xrt.xpath( "/report" )
  standardise = False
  xreport = xrt.xpath( "/report" )[0]
  #Get the jobInfo
  import CCP4ReportGenerator
  if arg3 is not None:
    g = CCP4ReportGenerator.CReportGenerator(jobId=arg3)
    jobInfo = g.getJobInfo(arg3)
    # Need to refer to job that is already in db
    report = Report( xreport, xml, jobInfo=jobInfo,standardise=standardise)
  else:
    report = Report( xreport, xml,jobInfo={},standardise=standardise)

  tree= report.as_etree()
  if len(report.errReport)>0:
    print 'ERROR REPORT'
    print report.errReport.report()
  text = etree.tostring(tree,pretty_print=True)
  #print text
  import CCP4Utils
  CCP4Utils.saveFile('report.html',text=text)
  if report.containsPictures():
    import functools
    g.runMg(report.pictureQueue,functools.partial(printMgFinished,arg3))
    print 'Returned from runMg'



def printMgFinished(jobId,exit,status):
  print 'MG finished',jobId,exit,status,jobId
