#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6 on Sun May 25 23:31:23 2008

# Copyright 2008 Martin Manns
# Distributed under the terms of the GNU General Public License
# generated by wxGlade 0.6 on Mon Mar 17 23:22:49 2008

# --------------------------------------------------------------------
# pyspread is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyspread is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyspread.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

"""
_interfaces
===========

Provides:
---------
 * sniff: Sniffs CSV dialect and header info
 * fill_numpyarray: Fills the target array
 * fill_wxgrid: Fills the target grid
 
 * make_string
 * make_unicode
 * make_slice
 * make_boolean
 * make_int
 * make_float
 * make_object
 * make_repr
 
 * OdfInterfaces: ODF file operations
 * PysInterfaces: PYS file operations
 * CsvInterfaces: Connects array datastructure with csv import module
 * Clipboard: Clipboard access
 * Commandline: Gets command line options and parameters
 * Digest: Converts any object to target type as goog as possible

"""

import bz2
import cPickle as pickle
import csv
from itertools import izip, dropwhile
import optparse
import re
import types

try:
    import xml.etree.cElementTree as ElementTree
except ImportError :
    try :
        from lxml.etree import ElementTree
    except ImportError :
        try:
            import cElementTree as ElementTree
        except ImportError :
            from elementtree.ElementTree import ElementTree

import wx
import numpy

from ooopy.OOoPy import OOoPy

from odf.opendocument import OpenDocumentSpreadsheet, load
from odf.style import Style, TextProperties, ParagraphProperties
from odf.style import TableColumnProperties, TableCellProperties
from odf.style import TableRowProperties
from odf.text import P
from odf.table import Table, TableColumn, TableRow, TableCell

from pyspread.config import VERSION, SNIFF_SIZE, default_dimensions, dpi
from pyspread.config import odftags, repeated_tags, pen_styles, font_attributes
from pyspread.config import font_weight_styles, font_styles

def sniff(csvfilepath):
    """
    Sniffs CSV dialect and header info from csvfilepath
    
    Returns a tuple of dialect and has_header
    
    """
    
    csvfile = open(csvfilepath, "rb")
    sample = csvfile.read(SNIFF_SIZE)
    csvfile.close()
    
    sniffer = csv.Sniffer()
    dialect = sniffer.sniff(sample)()
    has_header = sniffer.has_header(sample)
    
    return dialect, has_header

def fill_numpyarray(target, src_it, digest_types, key=(0, 0, 0), \
                    has_header = False):
    """
    Fills the target array with data from src_it at position key
    
    Parameters
    ----------
    target: wx.grid.Grid
    \tTarget grid
    src_it: Iterable object
    \tThe source of the data that shall be pasted has to be iterable.
    \tThe iterable can yield iterables but iterables that are strings
    \tare always treated as values.
    digest_types: tuple of types
    \tTypes of data for each col
    key: 3-tuple of int
    \tInsertion point
    has_header: bool
    \t True if the first line shall be treated as a header line
    
    """
    errormessages = []
    
    for i, line in enumerate(src_it):
        row = i + key[0]
        if row > target.shape[0] - 1:
            errormessages += ["Too many lines to fit into table!"]
            break
            
        for j, value in enumerate(line):
            col = j + key[1]
            if col > target.shape[1] - 1:
                errormessages += ["Too many columns to fit into table!"]
                break
            try:
                digest_key = digest_types[j]
            except IndexError:
                digest_key = digest_types[0]
            
            if i == 0 and has_header:
                digest = lambda x: x
            else:
                digest = Digest(acceptable_types=[digest_key])
            
            try:
                digest_res = digest(value)
                
                if digest_res is not None and \
                   digest_res != "\b" and \
                   digest_key is not types.CodeType:
                    digest_res = repr(digest_res)
                elif digest_res == "\b":
                    digest_res = None
                    
            except Exception, err:
                digest_res = str(err)
            
            if digest_res is not None:
                target[row, col, key[2]] = digest_res
        
    if errormessages:
        raise ValueError, '\n'.join(set(errormessages))

def fill_wxgrid(target, src_it, digest_types, key=(0, 0)):
    """
    Fills the target array with data from src_it at position key
    
    Parameters
    ----------
    target: numpy.array
    src_it: Iterable object
    \tThe source of the data that shall be pasted has to be iterable.
    \tThe iterable can yield iterables but iterables that are strings
    \tare always treated as values.
    digest_types: tuple of types
    \tTypes of data for each col
    key: 3-tuple of int
    \tInsertion point
    has_header: bool
    \t True if the first line shall be treated as a header line
    
    """
    
    for i, line in enumerate(src_it):
        row = i + key[0]
        if row > target.shape[0] - 1:
            break
        for j, value in enumerate(line):
            col = j + key[1]
            digest_key = digest_types[j]
            
            digest = Digest(acceptable_types=[digest_key])
            try:
                digest_res = repr(digest(value))
            except Exception, err:
                digest_res = str(err)
            target.SetCellValue(row, col, digest_res)

# Type conversion functions

def make_string(obj):
    """Makes a string object from any object"""
    
    if type(obj) is types.StringType:
        return obj
    
    if obj is None:
        return ""
    try:
        return str(obj)
    except:
        return repr(obj)

def make_unicode(obj):
    """Makes a unicode object from any object"""
    
    if type(obj) is types.UnicodeType:
        return obj
    
    if obj is None:
        return u""
    
    return unicode(obj)

def make_slice(obj):
    """Makes a slice object from slice or int"""
    
    if isinstance(obj, slice):
        return obj
    
    return slice(obj, obj + 1, None)

def make_boolean(obj):
    """Makes a boolean from comparable types"""
    
    return bool(obj)

def make_int(obj):
    """Makes an integer from comparable types"""

    return int(obj)

def make_float(obj):
    """Makes a float from comparable types"""

    return float(obj)

def make_object(obj):
    """Returns the object"""
    
    return obj

def make_repr(obj):
    """Returns the string represemntation of object"""
    
    return repr(obj)

def get_default_font():
    """Returns the default system font"""
    
    global DEFAULT_FONT
    
    try:
        return DEFAULT_FONT
    except NameError:
        DEFAULT_FONT = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        return DEFAULT_FONT

def get_font_list():
    """Returns a sorted list of all system font names"""
    
    font_enum = wx.FontEnumerator()
    font_enum.EnumerateFacenames(wx.FONTENCODING_SYSTEM)
    font_list = font_enum.GetFacenames()
    font_list.sort()
    
    return font_list

def repeated(ilist):
    """Generator for simple compression of lists 
    
    Returns a list of 2-tuples, where the 1st value is the original value 
    and the 2nd value is the number of repeated occasions
    
    Parameters
    ----------
    
    ilist: List
    \tList of elements that can be compared to each other
    
    """
    
    if len(ilist) == 0:
        return
    
    counter = 1
    for i, ele in enumerate(ilist[1:]):
        try:
            if ele == ilist[i]:
                counter += 1
            else:
                yield (ilist[i], counter)
                counter = 1
        except KeyError:
            yield (ele, counter)
    try:
        yield (ele, counter)
    except UnboundLocalError:
        yield (ilist[0], 1)


class OdfInterfaces(object):
    """ODF file handling class
    
    Methods
    -------
    open: Opens ODF file
    close: Closes ODF file
    get_values: Gets the values in the grid
    
    """
    
    def __init__(self):
    
        #File handler
        self._odt_file = None
        
        # Tag prefix for accessing ElementTree
        self._tag_prefix_list = None
        
        # For avoiding dublettes on save
        self._cellstyle_content_cache = {}
        
        
    def open(self, filename):
        """Opens ODF file
        
        After opening, the file is parsed and closed again.
        
        Created class attributes
        ------------------------
        
         * self._odt_file: odf.py file object for later use
         
         * self.metadata: Metadata dict of odf file
         
         * self.cell_code: Dict of code in all cells
           {(x, y, z): u"<content>", ...}
           
         * self.xxx_styles: Dict with xxx style information
        
        """
        
        self._odt_file = OOoPy(filename)
        
        # Metadata
        
        meta_tree = self._odt_file.read("meta.xml").tree
        self.metadata = self.get_metadata(meta_tree)
        
        # Content
        tree = self._odt_file.read("content.xml").tree
        
        root = tree.getroot()
        self._tag_prefix_list = root.tag[:-len(odftags["root"])].split(":")
        
        self.cell_code, tab_attr, col_attr, row_attr, cell_attr = \
                                            self.get_cell_content(tree)
        
        autostyles = self.get_autostyles(tree)
        
        del tree
        
        self.table_styles  = self._get_style_from_attrib(tab_attr, autostyles)
        self.column_styles = self._get_style_from_attrib(col_attr, autostyles)
        self.row_styles    = self._get_style_from_attrib(row_attr, autostyles)
        self.cell_styles   = self._get_style_from_attrib(cell_attr, autostyles)
        
        no_rows = 0
        for key in row_attr:
            if key[1] == 0:
                try:
                    no_rows += int(row_attr[key][repeated_tags["rowsrepeated"]])
                except KeyError:
                    no_rows += 1
        #print "NOROWS", no_rows
        
        no_cols = 0
        for key in col_attr:
            if key[1] == 0:
                try:
                    no_cols += int(col_attr[key][repeated_tags["colsrepeated"]])
                except KeyError:
                    no_cols += 1
        #print "NOCOLS", no_cols
        no_tabs = len(tab_attr)
        
        self.shape = (no_rows, no_cols, no_tabs)
        
    def close(self):
        """Tries to close _odf_file but passes when impossible"""
        
        try:
            self._odt_file.close()
        except AttributeError:
            pass
    
    def get_tag(self, maintag, eletag=None):
        """Returns a tag string
        
        Parameters:
        -----------
        
        tree: ElementTree
        \tElement tree from OOoPy
        tagtype: String in ["body", "table", "column", "row", "cell"]
        \tType of the tag to be returned (inside braces)
        tagtype: String in ["body", "table", "column", "row", "cell"]
        \tType of the item tag tag to be returned (behind braces),
        \tdefaults to tagtype
        
        """
        
        if eletag is None:
            eletag = maintag
        
        tag_prefix_list = self._tag_prefix_list[:]
        tag_prefix_list[6] = maintag
        
        return ":".join(tag_prefix_list) + eletag
    
    def _get_cell_repetitions(self, cell_attrib):
        """Returns the number of repetions of a cell"""
        
        repkey = repeated_tags["colsrepeated"]
        
        if repkey in cell_attrib:
            col_rep = int(cell_attrib[repkey])
        else:
            col_rep = 1
        
        return col_rep
    
    def _get_cellrow_attrs(self, cells):
        """Returns dict of cell attributes for one row of cells"""
        
        cellrow_attrs = {}
        no = 0
        
        for cell in cells:
            cell_attrib = cell.attrib
            
            # Look if we have a cell repeated tag
            col_rep = self._get_cell_repetitions(cell_attrib)
            
            for _ in xrange(col_rep):
                if len(cell_attrib):
                    cellrow_attrs[no] = cell_attrib
                no += 1
        
        return cellrow_attrs
        
    def _get_cellrow_codes(self, cells):
        """Returns code for one row of cells"""
        
        repkey = repeated_tags["colsrepeated"]
        
        cell_codes = {}
        no = 0
        
        for cell in cells:
            # Look if we have a cell repeated tag
            col_rep = self._get_cell_repetitions(cell.attrib)
            
            try:
                elements = cell.iterchildren()
                code = u"".join(e.text for e in elements \
                                    if e.text is not None)
            except IndexError:
                pass
            
            if code is None or code == "":
                cell_code = None
            else:
                cell_code = code.strip()
            
            for _ in xrange(col_rep):
                if cell_code is not None:
                    cell_codes[no] = cell_code
                no += 1
            
        return cell_codes


    def get_cell_content(self, tree):
        """Returns dicts of cell code and attributes from content etree
        
        5 dicts are returned:
        cell_codes, tab_attr, col_attr, row_attr, cell_attr
        
        Parameters
        ----------
        
        tree: ElementTree
        \tElement tree from OOoPy
        
        """
        
        cell_codes = {}
        tab_attr = {}
        col_attr = {}
        row_attr = {}
        cell_attr = {}
        
        bodytag = self.get_tag(odftags["office"], odftags["body"])
        tabletag = self.get_tag(odftags["table"])
        columntag = self.get_tag(odftags["table"], odftags["column"])
        rowtag = self.get_tag(odftags["table"], odftags["row"])
        celltag = self.get_tag(odftags["table"], odftags["cell"])
        
        body = list(tree.getroot().iterchildren(tag=bodytag))[0]
        
        tables = body.getchildren()[0].iterchildren(tag=tabletag)
        
        repkey = repeated_tags["colsrepeated"]
        
        # Smallest possible spreadsheet has 1 cell
        no_rows, no_columns, no_tabs = 1, 1, 1
        
        for tab_no, table in enumerate(tables):
            table_name = table.values()
            
            if len(table.attrib):
                tab_attr[tab_no] = table.attrib
            
            # Count maximum number of columns
            columns = table.iterchildren(tag=columntag)
            no = 0
            
            for column in columns:
                col_attr[(no, tab_no)] = column.attrib
                column_values = []
                
                for val in column.values():
                    try:
                        column_values.append(int(val))
                    except ValueError:
                        pass
                no += len(column.items())
            no_columns = max(no, no_columns)
            
            # Now iterate over rows to get values
            rows = table.iterchildren(tag=rowtag)
            row_no = 0
            
            for row in rows:
                if len(row.attrib):
                    row_attr[(row_no, tab_no)] = row.attrib
                
                # Look if we have a row repeated tag
                if repeated_tags["rowsrepeated"] in row.attrib:
                    row_rep = int(row.attrib[repeated_tags["rowsrepeated"]])
                else:
                    row_rep = 1
                
                cells = row.iterchildren(tag=celltag)
                cellrow_attribs = self._get_cellrow_attrs(cells)
                
                cells = row.iterchildren(tag=celltag)
                cellrow_codes = self._get_cellrow_codes(cells)
                
                for cell_no in cellrow_attribs:
                    # Look if we have a row repeated tag
                    for r in xrange(row_no, row_no + row_rep):
                        pos = (r, cell_no, tab_no)
                        cell_attr[pos] = cellrow_attribs[cell_no]
                
                for cell_no in cellrow_codes:
                    for r in xrange(row_no, row_no + row_rep):
                        pos = (r, cell_no, tab_no)
                        cell_codes[pos] = cellrow_codes[cell_no]
                
                row_no += row_rep
            
            no_rows = max(row_no, no_rows)
            no_tabs = max(tab_no, no_tabs)
        
        lower_right = (no_rows - 1, no_columns - 1, no_tabs - 1)
        
        if lower_right not in cell_codes:
            cell_codes[lower_right] = u""
        
        return cell_codes, tab_attr, col_attr, row_attr, cell_attr
    
    def get_values(self):
        """Returns a numpy object array with unicode cell content from ODF"""
        
        cell_code = self.cell_code
        
        keys = ([x, y, z] for x, y, z in cell_code.keys())
        res = numpy.zeros(self.shape, dtype="O")
        
        for key in cell_code:
            #print key
            res[key] = cell_code[key]
        
        return res
        
    def get_metadata(self, tree):
        """Returns a dict of the ODF metadata"""
        
        metadata = {}
        
        root = tree.getroot()
        metadata[root.tag] = root.attrib
        
        
        for child in root.getchildren()[0].getchildren():
            if child.tag.split("}")[1] == "creation-date":
                metadata[child.tag] = child.text
            if child.tag.split("}")[1] == "document-statistic":
                metadata[child.tag] = child.attrib
            if child.tag.split("}")[1] == "generator":
                metadata[child.tag] = child.text
        
        return metadata
    
    
    def get_autostyles(self, tree):
        """Returns the autostyles section and returns autostyle dict
        
        Parameters
        ----------
        
        tree: ElementTree
        \tElement tree from OOoPy
        
        """
        
        autostyles = {}
        
        style_section_tag = self.get_tag(odftags["office"], odftags["autostyle"])
        style_tag = self.get_tag(odftags["style"])
        style_name_tag = self.get_tag(odftags["style"], odftags["name"])
        
        style_sections = tree.getroot().iterchildren(tag=style_section_tag)
        for style_section in style_sections:
            styles = style_section.iterchildren(tag=style_tag)
            for style in styles:
                style_name = style.attrib[style_name_tag]
                style_attrib = {}
                for style_child in style.getchildren():
                    style_attrib.update(style_child.attrib)
                autostyles[style_name] = style_attrib
        
        return autostyles
        
    def _get_style_from_attrib(self, attrib, autostyles):
        """Returns style dict from given attrib dict following self.autostyles
        
        Style dict has format {(x, y, z): {style_tag: style, ...}, ...}
        
        Parameters
        ----------
        attrib: Dict {(x, y, z): {"style-name": style_name, ...}, ...}
        \tDict of attributes
        autostyles: Dict {style-name: style, ...}
        
        """
        
        styles = {}
        
        stylenametag = self.get_tag(odftags["table"], odftags["stylename"])
        
        for key in attrib:
            if stylenametag in attrib[key]:
                attr = attrib[key]
                for style_key in autostyles[attr[stylenametag]]:
                    style = autostyles[attr[stylenametag]][style_key]
                    try:
                        styles[key][style_key] = style
                    except KeyError:
                        styles[key] = {}
                        styles[key][style_key] = style
        return styles
    
    def get_fonts(self):
        """Returns a dict of fonts in the ODF
        
        Format:{(row, col, tab): wxFont, ...}
        
        The result is put into self.MainGrid.textfont
        
        """
        
        stylenametag = self.get_tag(odftags["style"], "font-name")
        tagprefix = self.get_tag(odftags["style"], "")
        
        # NOT YET IMPLEMENTED
        table_font = {}
        column_fonts = {}
        row_fonts = {}
        
        # IMPLEMENTED
        cell_fonts = {}
        
        cell_styles = self.cell_styles
        
        font_enum = wx.FontEnumerator()
        font_enum.EnumerateFacenames(wx.FONTENCODING_SYSTEM)
        
        def getfont(key):
            # Font
            try:
                font_name = cell_styles[key][stylenametag]
            except KeyError:
                font_name = get_default_font().FaceName
            
            font = wx.Font(wx.DEFAULT, wx.DEFAULT, 
                               wx.NORMAL, wx.NORMAL, 
                               False, font_name)
            
            # Sets font attributes as defined in self.font_attr
            for attr_key in font_attributes:
                font_attr = font_attributes[attr_key]
                attr = None
                try:
                    attr = cell_styles[key][tagprefix+attr_key]
                except KeyError:
                    pass
                
                if attr is not None and font_attr["assert_func"](attr):
                    newattr = font_attr["convert_func"](attr)
                    setter = getattr(font, font_attr["setter"])
                    setter(newattr)
            
            return font
        
        style_cache = []
        
        for cell_key in cell_styles:
            if cell_styles[cell_key] in style_cache:
                cache_pos = style_cache.index(cell_styles[cell_key])
                font = getfont(cell_key)
            else:
                font = getfont(cell_key)
                style_cache.append(cell_styles[cell_key])
            cell_fonts[cell_key] = font
        
        return cell_fonts 
    
    def get_border_pens(self):
        """Returns a dict of cell border line brushes in the ODF
        
        Format
        ------
        {(row, col, tab): wxBrush, ...}
        
        The result is put into self.MainGrid.borders
        fo:border="0.0008in solid #000000"
        """
        
        # NOT YET IMPLEMENTED
        table_border_pen = {}
        column_border_pens = {}
        row_border_pens = {}
        
        # IMPLEMENTED
        cell_border_pens = {}
        
        cell_styles = self.cell_styles
        
        bordertag = self.get_tag("xsl-fo-compatible", "border")
        
        for key in cell_styles:
            next = False
            try:
                border_attributes = cell_styles[key][bordertag].split()
            except KeyError:
                next = True
            if not next:
                if type(border_attributes) is types.UnicodeType and \
                   border_attributes.lowercase() == 'none':
                    thickness = 1
                    style = 'solid'
                    color = wx.WHITE
                else:
                    thickness, style, color = border_attributes
                
                if thickness[-2:] == "in":
                    thickness = max(1, 
                                int(float(thickness[:-2]) * dpi[0]))
                elif thickness[-2:] == "mm":
                    thickness = max(1, 
                                int(float(thickness[:-2]) * dpi[0] * 2.56))
                else:
                    thickness = 1
                
                border_pen = wx.Pen(color, thickness, pen_styles[style])
                cell_border_pens[key] = border_pen
        
        return cell_border_pens
    
    
    def _get_content(self, tags, cfunc=lambda x:x):
        """Returns a dict of tag content in the ODF
        
        The content of the dict is the return value of cfunc.
        
        Parameters
        ----------
        tags: Interable of Strings
        \tODF tag names
        cfunc: Function
        \tFunction that accepts all tags as parameters.
        
        Format:{(row, col, tab): cfunc_result, ...}
        
        """
        
        # NOT YET IMPLEMENTED
        table_color = {}
        column_colors = {}
        row_colors = {}
        
        # IMPLEMENTED
        cell_content = {}
        
        cell_styles = self.cell_styles
        
        for key in cell_styles:
            content = []
            for tag in tags:
                try:
                    content.append(cell_styles[key][tag])
                except KeyError:
                    pass
            if len(content) == len(tags):
                cell_content[key] = cfunc(*content)
            
        return cell_content
    
    def get_textattributes(self):
        """Returns a dict of textcolors in the ODF
        
        Format:{(row, col, tab): wxColor, ...}
        
        The result is put into self.MainGrid.textcolor
        
        """
        
        tags = [
            self.get_tag("xsl-fo-compatible", "color"),
            self.get_tag(odftags["style"], "text-line-through-style"),
            self.get_tag("xsl-fo-compatible", "wrap-option"),
            self.get_tag("xsl-fo-compatible", "text-align"),
            self.get_tag(odftags["style"], "rotation-angle"),
            self.get_tag(odftags["style"], "vertical-align"),
        ]
        
        textattributes = {}
        
        for key, cell_style in self.cell_styles.iteritems():
            attrs = {}
            for tag in tags:
                try:
                    attr = cell_style[tag]
                    attrs.update({tag: attr})
                except KeyError:
                    pass
            
            # When some attrs are found, textattributes is updated
            if attrs != {}:
                try:
                    textattributes[key]
                except KeyError:
                    textattributes[key] = {}
                textattributes[key].update(attrs)

        return textattributes
    
    def get_bgbrushes(self):
        """Returns a dict of background colors in the ODF
        
        Format:{(row, col, tab): wxColor, ...}
        
        The result is put into self.MainGrid.bgcolor
        
        """
        
        def cfunc(color):
            if color == 'transparent':
                return wx.Brush(wx.WHITE, wx.SOLID)
            else:
                return wx.Brush(color, wx.SOLID)
        
        bgtag = self.get_tag("xsl-fo-compatible", "background-color")
        
        return self._get_content([bgtag], cfunc)
    
    def _get_text_properties(self, font, textattributes):
        """Returns a text property dict for the given font"""
        
        textproperties = {}
        
        # Text font
        # ---------
        
        if font is None:
            font = get_default_font()
            
        fontname = font.GetFaceName()
        
        fontsize = str(font.GetPointSize()) + "pt"
        textproperties["fontname"] = fontname
        textproperties["fontsize"] = fontsize
        textproperties["fontsizecomplex"] = fontsize
        
        inv_weightstyles = dict(ele[::-1] for ele in font_weight_styles)
        weightstring = inv_weightstyles[font.GetWeight()]
        
        textproperties["fontweight"] = weightstring
        textproperties["fontweightcomplex"] = weightstring
        
        inv_styles = dict(ele[::-1] for ele in font_styles)
        stylestring = inv_styles[font.GetStyle()]
        
        textproperties["fontstyle"] = stylestring
        textproperties["fontstylecomplex"] = stylestring
        
        if font.GetUnderlined():
            textproperties["textunderlinestyle"] = "solid"
        
        
        # Text attributes
        # ---------------
        
        inv_odftags = dict((odftags[key], key) for key in odftags)
        
        if textattributes is not None:
            for attrkey in textattributes:
                try:
                    attr = inv_odftags[attrkey]
                except KeyError:
                    attr = None
                if attr == "strikethrough":
                    textproperties["textlinethroughstyle"] = attr
                elif attr == "fontcolor":
                    color = textattributes[attrkey]
                    textproperties["color"] = color
                elif attr == "textalign":
                    """NOT YET IMPLEMENTED"""
                else:
                    pass

        
        return textproperties
    
    def _get_cell_properties(self, borderpen, bgbrush, textattributes):
        """Returns a cell property dict for the given font"""
        
        cellproperties = {}
        
        # Borderpen
        # ---------
        
        if borderpen is not None:
            borderwidth = borderpen.GetWidth()
            borderstyle = borderpen.GetStyle()
            bordercolor = borderpen.GetColour()
            
            thickness = str(float(borderwidth) / dpi[0]) + "in"
            if borderstyle == wx.TRANSPARENT:
                style = 'transparent'
            else:
                style = 'solid'
            color = bordercolor.GetAsString(flags=wx.C2S_HTML_SYNTAX)
            
            """0.0008in solid #000000"""
            borderstring = " ".join([thickness, style, color])
            cellproperties["border"] = borderstring
        
        # Background brush
        # ----------------
        
        if bgbrush is not None:
            bgcolor = bgbrush.GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
            cellproperties["backgroundcolor"] = bgcolor
        
        # Text attributes
        # ---------------
        
        inv_odftags = dict((odftags[key], key) for key in odftags)
        
        if textattributes is not None:
            for attrkey in textattributes:
                try:
                    attr = inv_odftags[attrkey]
                except KeyError:
                    attr = None
                if attr == "verticalalign":
                    valign = textattributes[attrkey]
                    cellproperties["verticalalign"] = valign
                elif attr == "rotationangle":
                    rotation = textattributes[attrkey]
                    cellproperties["rotationangle"] = rotation
                elif attr == "textalign":
                    """NOT YET IMPLEMENTED"""
                else:
                    pass
        
        return cellproperties
    
    def _cellstyle_from_attrdicts(self, font, borderpen, bgbrush, \
                                                    textattributes):
        """Returns odf fontstyle from wxFont
        
        Parameters
        ----------
        font: wx.Font
        \tText font for cell
        borderpen: wx.Pen
        \tPen for cell border
        bgbrush: wx.Brush
        \tBrush for cell background
        textattributes: Dict
        \tFurther attributs for text layout
        
        """
        
        if not hasattr(self, "cell_style_no"):
            self.cell_style_no = 0
        
        cellstyle = Style(name="ce" + str(self.cell_style_no), 
                          family="table-cell")
        
        self.cell_style_no += 1
        
        textproperties = self._get_text_properties(font, textattributes)
        cellproperties = self._get_cell_properties(borderpen, bgbrush, 
                                                   textattributes)
        
        if textproperties:
            cellstyle.addElement(TextProperties(**textproperties))
        if cellproperties:
            cellstyle.addElement(TableCellProperties(**cellproperties))
        
        return cellstyle
    
    def _get_attrs(self, attr_dicts, pos):
        """Retrieves the attributes for the current position pos
        
        Returned attributes: font, borderpens, bgbrushes, textattributes
        
        """
        
        try:
            font = attr_dicts["textfont"][pos]
        except KeyError:
            font = None
        try:
            borderpens = attr_dicts["borderpens"][pos]
        except KeyError:
            borderpens = None
        try:
            bgbrushes = attr_dicts["bgbrushes"][pos]
        except KeyError:
            bgbrushes = None
        try:
            textattributes = attr_dicts["textattributes"][pos]
        except KeyError:
            textattributes = None
        try:
            rowsizes = attr_dicts["rowsizes"]
        except KeyError:
            rowsizes = None
        try:
            colsizes = attr_dicts["colsizes"]
        except KeyError:
            colsizes = None
        
        return font, borderpens, bgbrushes, textattributes, rowsizes, colsizes
    
    def _build_table(self, tab, ods, attr_dicts, table_no):
        """Build table from tab array"""
        
        # Start the table, and describe the columns
        table = Table(name=str(table_no))
        
        self._row_style_cache = {}
        self._col_style_cache = {}
        
        self._old_rowsize = None # for allowing rowstyle repititions
        
        colsizes = attr_dicts["colsizes"]
        
        old_colstyle = None # Last colstyle for identifying end of repetitions
        repetitions = 0
        
        for col_no in xrange(tab.shape[1]):
            if (col_no, table_no) in colsizes:
                colwidth = repr(colsizes[(col_no, table_no)] / dpi[0]) + "in"
                if colwidth in self._col_style_cache:
                    colstyle = self._col_style_cache[colwidth]
                else:
                    colstyle = Style(name="col" + repr((col_no, table_no)), 
                                     family="table-column")
                    colstyle.addElement(\
                        TableColumnProperties(columnwidth=colwidth))
                    ods.automaticstyles.addElement(colstyle)
                    self._col_style_cache[colwidth] = colstyle
            else:
                colstyle = self.default_colstyle
            
            if colstyle == old_colstyle:
                repetitions += 1
            elif repetitions == 0:
                table.addElement(TableColumn(stylename=colstyle))
                old_colstyle = colstyle
            else:
                table.addElement(TableColumn(stylename=colstyle, 
                                             numbercolumnsrepeated=repetitions))
                repetitions = 0
                old_colstyle = colstyle
        
        if repetitions:
            table.addElement(TableColumn(stylename=colstyle, 
                                         numbercolumnsrepeated=repetitions))
            repetitions = 0
            old_colstyle = colstyle
        
        empty_rows = 0
        
        for row_no in xrange(tab.shape[0]):
            row = tab[row_no,:]
            
            has_cells = len(numpy.nonzero(row)[0]) > 0
            has_rowsize = (row_no, table_no) in attr_dicts["rowsizes"]
            
            if has_rowsize:
                rowsize = attr_dicts["rowsizes"][(row_no, table_no)]
            else:
                rowsize = None
            
            if has_cells or (has_rowsize and self._old_rowsize != rowsize):
                # There is content in the row, so we add one
                
                # Check if there are empty rows in front of our current one
                if empty_rows:
                    # Add the empty rows
                    tr = self._build_row(tab[row_no - 1,:], ods, attr_dicts, 
                                         row_no - 1, table_no, 
                                         numberrowsrepeated=empty_rows)
                    table.addElement(tr)
                    empty_rows = 0
                
                # Now add our non-empty row
                
                tr = self._build_row(row, ods, attr_dicts, row_no, table_no)
                table.addElement(tr)
                
                self._old_rowsize = rowsize
                
            else:
                # There is no content and no attribute in the row
                
                empty_rows += 1
        
        if row_no and empty_rows:
            tr = self._build_row(tab[row_no - 1,:], ods, attr_dicts, 
                                 row_no, table_no, 
                                 numberrowsrepeated=empty_rows)
            table.addElement(tr)
        
        return table
    
    def _build_row(self, row, ods, attr_dicts, row_no, table_no, 
                   numberrowsrepeated=1):
        """Build row from row list"""
        
        col_no = 0
        
        row_w_attr = []
        
        for col_no, ele in enumerate(row):
            pos = (row_no, col_no, table_no)
            
            font, borderpen, bgbrush, textattributes, rowsizes, colsizes = \
                self._get_attrs(attr_dicts, pos)
            
            cellstyle_content = \
                repr(self._get_text_properties(font, textattributes)) + \
                repr(self._get_cell_properties(borderpen, bgbrush, 
                                               textattributes))
            
            if cellstyle_content in self._cellstyle_content_cache:
                cellstyle = self._cellstyle_content_cache[cellstyle_content]
                
            else:
                if font is None and borderpen is None and \
                   bgbrush is None and textattributes is None and \
                   (col_no, table_no) not in colsizes:
                    cellstyle = None
                else:
                    cellstyle = self._cellstyle_from_attrdicts( \
                                    font, borderpen, bgbrush, 
                                    textattributes)
                
                self._cellstyle_content_cache[cellstyle_content] = cellstyle
                
                if cellstyle is not None:
                    ods.automaticstyles.addElement(cellstyle)
            
            row_w_attr.append((ele, cellstyle))
        
        
        
        # Create row
        # ----------
        
        # Row height
        
        #print "R", rowsizes, row_no
        
        # The first cell may be repeated. 
        # Therefore, the default row style is appied
            
        if row_no == 0 and (row_no, table_no) in rowsizes:
            new_style = True
        elif row_no == 0: 
            new_style = False
        elif (row_no, table_no) in rowsizes and \
             (row_no - numberrowsrepeated > 0 or numberrowsrepeated == 1):
            new_style = True
        else:
            new_style = False
        
        if new_style:
            rowheight = repr(rowsizes[(row_no, table_no)] / dpi[0]) + "in"
            if rowheight in self._row_style_cache:
                rowstyle = self._row_style_cache[rowheight]
            else:
                rowstyle = Style(name=repr((row_no, table_no)), 
                                 family="table-row")
                rowstyle.addElement(TableRowProperties(rowheight=rowheight))
                self._row_style_cache[rowheight] = rowstyle
                ods.automaticstyles.addElement(rowstyle)
        else:
            rowstyle = self.default_rowstyle
        
#        print row_no, repr((row_no, table_no)), (row_no, table_no) in \
#                                               rowsizes, rowstyle.attributes
        
        if numberrowsrepeated == 1:
            tr = TableRow(stylename=rowstyle.attributes['style:name'])
        else:
            tr = TableRow(numberrowsrepeated=numberrowsrepeated,
                          stylename=rowstyle.attributes['style:name'])
        
        
        if len(row) == 0:
            tc = TableCell()
            tr.addElement(tc)
            
        compressed_row_w_attr = repeated(row_w_attr)
        
        for (val, cellstyle), no_ele in compressed_row_w_attr:
            if no_ele > 0 or \
               (row_no, table_no) in rowsizes or \
               (col_no, table_no) in colsizes:
                if cellstyle is None or type(cellstyle) is types.StringType:
                    tc = TableCell(numbercolumnsrepeated=no_ele)
                else:
                    tc = TableCell(stylename=cellstyle, 
                                   numbercolumnsrepeated=no_ele)
                
                tr.addElement(tc)

                if type(val) is types.StringType:
                    val = unicode(val)
                if type(val) is types.UnicodeType:
                    p = P(text=val)
                    tc.addElement(p)

                col_no += no_ele
            
        return tr
    
    
    def save(self, obj, attr_dicts, filename=None):
        """Saves document
        
        Parameters
        ----------
        
        filename: String
        \tName of file to save
        obj: 3D numpy object array of strings
        \tContent of file to save
        attr_dicts: Dict
        \tAttributes of tables, cols, rows and cells
        \tContains "textfont", "borderpens", "bgbrushes" and "textattributes"
        \tdicts from MainGrid
        
        Remark: Content will change!
        
        """
        
        if filename is None:
            filename = self.filename
        
        
        # + Look if there is a loaded document
        # + If yes, adapt the header info, if no create it new
        # + Write autostyles
        # + Write each cell
        
        # Look if there is a loaded document
        # ----------------------------------
        
        if self._odt_file is None:
            # There is no loaded document -> create new header info
            pass
        else:
            # There is a loaded document ->  adapt the header info
            self.metadata
        
        # Write each cell
        # ---------------
        
        self._cellstyle_content_cache = {}
        
        ods = OpenDocumentSpreadsheet()
        
        tablecontents = Style(name="Table Contents", family="paragraph")
        tablecontents.addElement(ParagraphProperties(numberlines="false", 
                                                     linenumber="0"))
        ods.styles.addElement(tablecontents)
        
        # Default row and column styles
        
        self.default_rowstyle = Style(name="default_rowstyle", family="table-row")
        self.default_rowstyle.addElement(TableRowProperties(rowheight="0.25in"))
        self.default_colstyle = Style(name="default_colstyle", family="table-column")
        ods.automaticstyles.addElement(self.default_rowstyle)
        ods.automaticstyles.addElement(self.default_colstyle)
        
        for table_no in xrange(obj.shape[2]):
            tab = obj[:, :, table_no]
            table = self._build_table(tab, ods, attr_dicts, table_no)
            ods.spreadsheet.addElement(table)
        ods.save(filename[:-4], True)
        
        self._cellstyle_content_cache = {}

class PysInterfaces(object):
    """PYS file handling class (for the pre 0.0.12 file format)
    
    Methods
    -------
    open: Opens PYS file
    close: Closes PYS file
    get_values: Gets the values in the grid
    
    """
    def open(self, filename):
        """Opens PYS file"""
        
        self._pys_file = bz2.BZ2File(filename, "rb")
        self.filename = filename
        
        self.column_styles = {}
        self.row_styles = {}
        
    def close(self):
        """Closes PYS file"""
        
        self._pys_file.close()
    
    def save(self, obj):
        """Saves the pickled object obj to self.filename (bzipped pickle)"""
        
        outfile = bz2.BZ2File(filepath,"wb")
        pickle.dump(obj, outfile, protocol=2)
        outfile.close()
    
    def get_values(self):
        """Returns a numpy object array with unicode cell content from PYS"""
        
        return pickle.load(self._pys_file)
    
    def get_fonts(self):
        """Compatibility to odf interface"""
        
        return {}
    
    def get_bgbrushes(self):
        """Compatibility to odf interface"""
        
        return {}
        
    def get_textattributes(self):
        """Compatibility to odf interface"""
        
        return {}
        
    def get_border_pens(self):
        """Compatibility to odf interface"""
        
        return {}

class CsvInterfaces(object):
    """CSV file handling class
    
    Parameters:
    -----------
    filename: string
    \tFilename of CSV input file
    dialect: csv dialect
    \tDialect for csv import via the csv module
    
    Methods
    -------
    read: Fills target with csv values.
    write: Writes values to CSV file
    
    """
    def __init__(self, filename, dialect, digest_types, has_header = False):
        self.filename = filename
        self.dialect = dialect
        self.digest_types = digest_types
        self.has_header = has_header
        self.csvfile = None
    
    def _open_csv_reader(self):
        """Opens the csv file named self.filename in read mode"""
        self.csvfile = open(self.filename, "rb")
        return csv.reader(self.csvfile, self.dialect)
    
    def _open_csv_writer(self):
        """Opens the csv file named self.filename in write mode"""
        self.csvfile = open(self.filename, "wb")
        return csv.writer(self.csvfile, self.dialect)    
    
    def _close_csv(self):
        """Closes the csv file"""
        self.csvfile.close()
    
    def read(self, target, key=(0, 0)):
        """Fills target with csv values.
        
        The targets dimensions are assumed as default limits
        unless specified otherwise.
        
        Parameters:
        -----------
        target: Mutable array or list with 2 dimensions
        \tIn this array, the csv values are stored
        
        key: 2-tuple, defaults to (0, 0)
        \tTop-left insertion position
        
        """
        
        csv_reader = self._open_csv_reader()
        fill_numpyarray(target, csv_reader, digest_types=self.digest_types, \
                        key=key, has_header=self.has_header)
        
        self._close_csv()
    
    def write(self, it):
        """Writes values from it into CSV file"""
        
        csv_writer = self._open_csv_writer()
        
        for line in it:
            csv_writer.writerow(line)
        
        self._close_csv()

# end of class CsvInterfaces

class Clipboard(object):
    """Clipboard access

    Provides:
    ---------
    get_clipboard: Get clipboard content
    set_clipboard: Set clipboard content
    grid_paste: Inserts data into grid target

    """

    clipboard = wx.TheClipboard

    def _convert_clipboard(self, datastring=None, sep='\t'):
        """Converts data string to iterable.

        Parameters:
        -----------
        datastring: string, defaults to None
        \tThe data string to be converted.
        \tself.get_clipboard() is called if set to None
        sep: string
        \tSeparator for columns in datastring

        """

        if datastring is None:
            datastring = self.get_clipboard()

        data_it = ((ele for ele in line.split(sep)) \
                            for line in datastring.splitlines())
        return data_it

    def get_clipboard(self):
        """Returns the clipboard text content"""

        textdata = wx.TextDataObject()
        if self.clipboard.Open():
            self.clipboard.GetData(textdata)
            self.clipboard.Close()
        else:
            wx.MessageBox("Can't open the clipboard", "Error")
        return textdata.GetText()

    def set_clipboard(self, data):
        """Writes data to the clipboard"""

        error_log = []

        textdata = wx.TextDataObject()
        try:
            textdata.SetText(data)
        except UnboundLocalError, err:
            error_log.append([err, "Error converting to string"])
        if self.clipboard.Open():
            self.clipboard.SetData(textdata)
            self.clipboard.Close()
        else:
            wx.MessageBox("Can't open the clipboard", "Error")
        return error_log

    def grid_paste(self, target, key=(0, 0, 0)):
        """Inserts data into target.
        
        The data in the clipboard has to be a whitespace separated
        2D matrix.
        
        Parameters:
        -----------
        target: Mutable array or list with 2 dimensions
        \tTarget array for data
        key: 
        
        """
        
        data_it = self._convert_clipboard()
        
        fill_numpyarray(target, data_it, [types.CodeType], key)

# end of class Clipboard

def bzip_dump(obj, filepath):
    """Saves the pickled object obj to a bzipped file named filepath"""

    outfile = bz2.BZ2File(filepath,"wb")
    pickle.dump(obj, outfile, protocol=2)
    outfile.close()

def string_match(datastring, findstring, flags=None):
    """
    Returns position of findstring in datastring or None if not found.
    Flags is a list of strings. Supported strings are:
     * "MATCH_CASE": The case has to match for valid find
     * "WHOLE_WORD": The word has to be surrounded by whitespace characters
                     if in the middle of the string
     * "REG_EXP":    A regular expression is evaluated.
    
    """
    
    if type(datastring) is types.IntType: # Empty cell
        return None
    
    if flags is None:
        flags = []
    
    if "REG_EXP" in flags:
        match = re.search(findstring, datastring)
        if match is None:
            pos = -1
        else:
            pos = match.start()
    else:
        if "MATCH_CASE" not in flags:
            datastring = datastring.lower()
            findstring = findstring.lower()
        
        if "WHOLE_WORD" in flags:
            pos = -1
            for match in re.finditer(r'\b' + findstring + r'+\b', datastring):
                pos = match.start()
                break # find 1st occurrance
        else:
            pos = datastring.find(findstring)
    
    if pos == -1:
        return None
    else:
        return pos

class Commandlineparser(object):
    """
    Command line handling

    Methods:
    --------

    parse: Returns command line options and arguments as 2-tuple

    """
    def __init__(self):
        usage = "usage: %prog [options] [filename]"
        version = "%prog " + unicode(VERSION)

        self.parser = optparse.OptionParser(usage=usage, version=version)

        self.parser.add_option("-d", "--dimensions", type="int", nargs=3,
            dest="dimensions", default=default_dimensions, \
            help="Dimensions of empty grid (works only without filename) "
                 "rows, cols, tables [default: %default]")

    def parse(self):
        """
        Returns a a tuple (options, filename)

        options: The command line options
        filename: String (defaults to None)
        \tThe name of the file that is loaded on start-up

        """
        options, args = self.parser.parse_args()

        if min(options.dimensions) < 1:
            raise ValueError, "The number of cells in each dimension " + \
                              "has to be greater than 0"

        if len(args) > 1:
            raise ValueError, "Only one file may be opened at a time"
        elif len(args) == 1:
            filename = args[0]
        else:
            filename = None

        return options, filename

# end of class Commandlineparser


class Digest(object):
    """
    Maps types to types that are acceptable for target class

    The Digest class handles data of unknown type. Its objects are
    callable. They return an acceptable data type, which may be the fallback
    data type if everything else fails.

    The Digest class is intended to be subclassed by the target class.

    Parameters:
    -----------

    acceptable_types: list of types, defaults to None
    \tTypes that are acceptable for the target_class.
    \tThey are ordered highest preference first
    \tIf None, the string representation of the object is returned

    fallback_type: type, defaults to types.UnicodeType
    \t

    """

    def __init__(self, acceptable_types=None, fallback_type=None):

        if acceptable_types is None:
            acceptable_types = [None]

        self.acceptable_types = acceptable_types
        self.fallback_type = fallback_type

        self.typehandlers = { \
            None: make_repr, \
            types.StringType: make_string, \
            types.UnicodeType: make_unicode, \
            types.SliceType: make_slice, \
            types.BooleanType: make_boolean, \
            types.ObjectType: make_object, \
            types.IntType: make_int, \
            types.FloatType: make_float, \
            types.CodeType: make_object, \
            }

        if self.fallback_type is not None and \
           self.fallback_type not in self.typehandlers:

            err_msg = " ".join(["Fallback type", \
                                str(self.fallback_type), \
                                "unknown."])
            raise NotImplementedError, err_msg

    def __call__(self, orig_obj):
        """Returns acceptable object"""

        errormessage = ""
        
        type_found = False
        
        for target_type in self.acceptable_types:
            if target_type in self.typehandlers:
                type_found = True
                break
        if not type_found:
            target_type = self.fallback_type
        
        try:
            acceptable_obj = self.typehandlers[target_type](orig_obj)
            return acceptable_obj
        except TypeError, err:
            errormessage += str(err)
        
        try:
            acceptable_obj = self.typehandlers[self.fallback_type](orig_obj)
            return acceptable_obj
        except TypeError, err:
            errormessage += str(err)
        
        return errormessage

# end of class Digest
