# -*- coding: utf-8 -*-

"""
    MoinMoin - OpenDocument Formatter - utility class for context.xml part

    Dummy MimeType is "application/vnd.oasis.opendocument.text.util"

    (c) 2007-2008 Hans-Peter Schaal <hp.news@gmx.de>

    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""

import sys, re

from MoinMoin.formatter import FormatterBase
from MoinMoin.action import AttachFile
from MoinMoin.Page import Page
from MoinMoin import wikiutil

xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"

# Element for formatting (e.g. "bold", "italic", ...)
E_FORMATTING = '__FORMAT'

# Element for text
E_TEXT = '__TEXT'

# Element for raw XML, e.g. content of included pages
E_RAW = '__RAW'

# Lists of direct subelements
validChildren = {'office:document-content': ['office:body', 'office:automatic-styles'], \
                            'office:automatic-styles': ['style:style', E_RAW], \
                            'office:body': ['office:text'], \
                            'office:text': ['text:h', 'text:p', 'table:table', 'text:list', E_RAW], \
                            'style:style': ['style:paragraph-properties', 'style:text-properties'], \
                            'text:h': [ E_TEXT ], \
                            'text:list' : [ 'text:list-item' ], \
                            'text:list-item': [ 'text:p', 'text:list' ], \
                            'text:p': [E_TEXT, 'text:s', 'text:line-break', E_FORMATTING, 'text:span', 'text:a', 'draw:frame' ], \
                            'text:a': [ E_TEXT ], \
                            'text:span': [E_TEXT], \
                            'draw:frame': [ 'draw:image' ], \
                            E_FORMATTING: [E_TEXT, E_FORMATTING], \
                            'table:table': ['table:table-row', 'table:table-column'], \
                            'table:table-row': ['table:table-cell', 'table:covered-table-cell'], \
                            'table:table-cell': ['text:p'] }

# These subelements are ok as well, but we need to wrap them into a container. Format: { sub1: container1, ...}
validGrandChildren = { 'table:table-cell': { E_TEXT: 'text:p' }, \
                                         'text:list-item': { E_TEXT: 'text:p' }}

# find two or more space characters or a single space character if at the beginning (useful for preformatted 
# paragraphs where a first single space character is always ignored)
whitespaceSearch = re.compile('(  +|^ )')  

# escape HTML / XML text
def _escape(text):
        text = text.replace('&','&amp;')
        text = text.replace("'", '&apos;')
        text = text.replace('"', '&quot;')
        text = text.replace('<','&lt;')
        text = text.replace('>','&gt;')
        return text


# ---------------------------------------------- Node Class  ---------------------------------------------------


class Node:

    def __init__(self, element=""):
        self.parent = None 
        self.isContainer = False   # True if this is a forced container which has no counterpart in MoinMoin doc structure
        self.mark = None              # The other way around: A MoinMoin tag which creates the same ODF-Tag as 
                                                    # other MoinMoin tags has the possibility to mark this node as its own.
        self.element = element
        self.attributes = {}
        self.content = ""
        self.children = []

    def setAttribute(self, attribute, value):
        self.attributes[attribute] = value

    def getAttribute(self, attribute, fallback):
        return self.attributes.get( attribute, fallback )

    def addChild(self, candidate):
        return self.insertChild( len(self.children), candidate)

    def insertChild(self, pos, candidate):
        # Test if candidate is a valid child of this element
        myValidChildren = validChildren.get( self.element, [] )
        if candidate.element in myValidChildren:
            candidate.parent = self
            self.children.insert(pos, candidate)
            return True
        # Test if candidate is a valid grandchild (subelement which needs a container element)
        else:
            myValidGrandChildren = validGrandChildren.get( self.element, { } )
            if candidate.element in myValidGrandChildren:
                containerNode = Node( myValidGrandChildren[ candidate.element ] )
                containerNode.isContainer = True
                containerNode.parent = self
                self.children.insert(pos, containerNode)
                candidate.parent = containerNode
                containerNode.children = [ candidate ]
                return True
            # If neither child nor grandchild: Don't append to tree, report failure...
            else:
                return False


    def addTextChild(self, text):
        # Remove line breaks
        text = text.replace('\n','')
        # Don't add just nothing
        if len(text) == 0: return
        # Excape XML
        text = _escape(text)
        # Add Text Node
        newNode = Node(E_TEXT)
        newNode.content = text
        return self.addChild(newNode)


    # Convert <__FORMAT><__FORMAT>... hierarchies into one level with combined <__FORMAT> tags
    # This method returns a node list which is meant to replace 'self'. If no change was made it returns just [ self ].
    #
    # Example 1 (Fx=__FORMAT with attributes x, T=Text):
    #      <Fa><Fb>T</Fb></Fa> becomes <Fab>T</Fab>
    # Example 2 (Fx=__FORMAT with attributes x, Ti=Text with index i):
    #     <Fa>T1<Fb>T2</Fb>T3</Fa> becomes <Fa>T1</Fa><Fab>T2</Fab><Fa>T3</Fa>
    #
    def flattenFormattings(self):
        # we're not interested in leafes, neither for processing nor for further tree parsing
        if not len( self.children ):
            return [ self ]

        # bottom up tree parsing
        newchildren = []
        for child in self.children:  
            newchildren.extend( child.flattenFormattings() )
        self.children = newchildren

        # we're only interested in __FORMAT tags
        if self.element <> E_FORMATTING:
            return [ self ]

        # we're only interested in __FORMAT tags which have __FORMAT children
        containsFormat = False
        for child in newchildren:
            if child.element == E_FORMATTING:
                containsFormat = True
                break
        if not containsFormat:
            return [ self ]

        conversion = []                  # convert this __FORMAT element into a list of __FORMAT elements
        openContainer = None    # __FORMAT element as container for non-format children
        
        # convert each child as self will be deleted
        for child in newchildren:
                if child.element == E_FORMATTING:
                    child.attributes.update( self.attributes ) # make sub __FORMAT self contained, i.e. add the attributes of self
                    child.parent = self.parent
                    conversion.append( child )
                    openContainer = None                            # use a new container for next non-format children
                else:
                    if openContainer  == None:
                        openContainer = Node(E_FORMATTING)
                        openContainer.attributes.update( self.attributes )  # copy attributes of self
                        openContainer.parent = self.parent
                        conversion.append( openContainer )
                    child.parent = openContainer
                    openContainer.children.append( child )

        # replace self by its converted children
        return conversion


    def to_XML_list(self, liste ):

        if self.element == E_TEXT or self.element == E_RAW:
            liste.append( self.content )
            return
            
        append = liste.append  # tiny Speedup
        
        if self.parent == None: 
            append( xmlHeader )   # Root element: Show XML Header

        append( '<%s' % (self.element) )

        liste.extend([ ' %s=\"%s\"' % (aName, aValue) for aName, aValue in self.attributes.iteritems() ])
        
        if len( self.children ) == 0:
            append( '/>' )
        else:
            append( '>' )
            for child in self.children:
                child.to_XML_list( liste)
            append( '</%s>\n' % (self.element) )



# ---------------------------------------------- Formatter Class ---------------------------------------------------


class Formatter(FormatterBase):

    def __init__(self, request, **kw):
        apply(FormatterBase.__init__, (self, request), kw)
        self.request = request
        self.Tree = None                     # Root
        self.Node = None                    # Current Node
        self.stylesNode = None          # Automatic Styles Node
        self.styleNames = []                # Names of children of self.stylesNode
        self.attachmentCounter = 0   # Replace Attachment File names with Counter + File Extension
        self.attachments = {}             # Real file name, internal file name (s.a.)
        self.include_headers = ""        # Attachments of included pages
        self.include_counter = 0         # Count Included Pages
        self.isIncluded = kw.get('is_included', False)


    # Helper Method: get next attachment counter
    def getNextAttachmentCounter( self ):
        self.attachmentCounter += 1
        return self.attachmentCounter
        

    # Helper Method: create node and try to attach it. Always return new node.
    def open(self, element, mark=None):
        newNode = Node( element )
        newNode.mark = mark
        if self.Node.addChild( newNode ):
            # If attached: Set pointer to new node
            self.Node = newNode
        return newNode


    # Helper Method: Close node and return element of closed node or "None" if no element was closed
    def close(self, element, mark=None):
        
        # does element name (and optional mark attribute) match and is element closeable?
        if self.Node.element == element and self.Node.mark == mark and self.Node.parent <> None:
            
            result = self.Node
            
            # test if parent is a container (forced ODT element which has no counterpart in MoinMoin doc structure)
            if self.Node.parent.isContainer and self.Node.parent.parent <> None:
                # 2 steps up.
                self.Node = self.Node.parent.parent
            else:
                # 1 step up
                self.Node = self.Node.parent
            return result
        
        else:
            return None


    # Helper Method: create AutomaticStyle Node and try to append it. Return node if style was new, otherwise None.
    def addAutomaticStyle(self, styleName, styleFamily):
        if styleName in self.styleNames:
            return None

        newNode = Node('style:style')
        newNode.setAttribute('style:name', styleName)
        newNode.setAttribute('style:family', styleFamily)
        self.stylesNode.addChild( newNode )
        self.styleNames.append( styleName )
        
        return newNode


    # Helper Method: convert __FORMAT pseudo tags into text:span tags
    def createSpans(self, currentNode):
        if currentNode.element == E_FORMATTING:
            # Create Style Name from attributes. For Example: 'AS_bold_italic_underline'
            attribList = [ attrib for attrib in currentNode.attributes ]
            attribList.sort()
            styleName = 'AS_' + '_'.join( attribList )
            if self.isIncluded:
                styleName = '<<INCLUDE_ID>>' + styleName  # Prefix will be replaced by calling formatter
            # Convert node into text:span
            currentNode.element = 'text:span'
            currentNode.attributes = {}
            # Set Automatic Style Name
            currentNode.setAttribute( 'text:style-name',  styleName )
            # Try to create Automatic Style (fails if it was created before)
            styleNode = self.addAutomaticStyle( styleName, 'text' )
            # If style wasn't created before: Create its text properties
            if styleNode <> None:
                textprop = Node( 'style:text-properties' )
                styleNode.addChild( textprop )

                if 'bold' in attribList:
                    textprop.setAttribute("fo:font-weight","bold")
                    textprop.setAttribute("style:font-weight-asian","bold")
                    textprop.setAttribute("style:font-weight-complex","bold")
                if 'italic' in attribList:
                    textprop.setAttribute("fo:font-style","italic")
                    textprop.setAttribute("style:font-style-asian","italic")
                    textprop.setAttribute("style:font-style-complex","italic")
                if 'underline' in attribList:
                    textprop.setAttribute("style:text-underline-style","solid")
                    textprop.setAttribute("style:text-underline-width","auto")
                    textprop.setAttribute("style:text-underline-color","font-color")
                if 'strike' in attribList:
                    textprop.setAttribute("style:text-line-through-style","solid")
                if 'super' in attribList:
                    textprop.setAttribute("style:text-position","super 58%")
                if 'sub' in attribList:
                    textprop.setAttribute("style:text-position","sub 58%")
                if 'bigger' in attribList:
                    textprop.setAttribute("fo:font-size","115%")
                    textprop.setAttribute("style:font-size-asian","115%")
                    textprop.setAttribute("style:font-size-complex","115%")
                if 'smaller' in attribList:
                    textprop.setAttribute("fo:font-size","90%")
                    textprop.setAttribute("style:font-size-asian","90%")
                    textprop.setAttribute("style:font-size-complex","90%")
                    
        for child in currentNode.children:
                self.createSpans( child )


    # Helper method: Open or close format tag
    def formatTag(self, on, attrib):
        if on:
            newNode = self.open( E_FORMATTING )
            newNode.setAttribute( attrib, attrib )
        else:
            self.close( E_FORMATTING )


    # Helper method: Guess mimetype and normalize file extension
    def getMimetype( self, filename ):
        low = filename.lower()

        if low.endswith(".jpg") or low.endswith(".jpeg"): 
            extension = "jpg"
            mimetype = "image/jpeg"
            return (mimetype, extension)
        
        if low.endswith(".png"):
            extension = "png"
            mimetype = "image/png"
            return (mimetype, extension)

        if low.endswith(".gif"):
            extension = "gif"
            mimetype = "image/gif"
            return (mimetype, extension)

        return ("", "")


    # Helper method: render internal header (only for action class RenderAsOpenDocument)
    def getInternalHeader(self):
        result = "\n<pseudoformatter_header>\n"
        
        for at in self.attachments:
            result += "ATTACHMENT:" + at + "\n" 
            result += "INTERNAL:" + self.attachments[ at ] + "\n"
            result += "MIMETYPE:" + self.getMimetype( at )[0] + "\n"

        # Add Headers of included pages
        result += '\n' + self.include_headers

        result += "</pseudoformatter_header>\n"
        return result


    def startDocument(self, pagename):
        self.pagename = pagename
        
       # Create root
        newNode = Node("office:document-content")
        newNode.setAttribute("xmlns:draw","urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")
        newNode.setAttribute("xmlns:fo","urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0")
        newNode.setAttribute("xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0")
        newNode.setAttribute("xmlns:style","urn:oasis:names:tc:opendocument:xmlns:style:1.0")
        newNode.setAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0")
        newNode.setAttribute("xmlns:table", "urn:oasis:names:tc:opendocument:xmlns:table:1.0")
        newNode.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink")

        # Initialize tree with root
        self.Tree = newNode

        # Manually set current selected node
        self.Node = newNode
        
        # Add Section "Automatic-Styles"
        newNode = Node("office:automatic-styles")
        self.Node.addChild( newNode )
        self.stylesNode = newNode
        
        # From now on we can use the automated way of node insertion
        self.open( "office:body" )
        self.open( "office:text" )
        
        return ''


    def endDocument(self):
        [ self.Tree ] = self.Tree.flattenFormattings()   # convert __FORMAT hierarchies into single __FORMAT tags
        self.createSpans(self.Tree)                               # convert __FORMAT tags into text:span tags
        liste = []
        self.Tree.to_XML_list( liste )
        return self.getInternalHeader() + ''.join( liste )


    # Embedded Mode (e.g. Include Macro)
    def startContent(self, content_id="content", **kw):
        if self.isIncluded:
            return self.startDocument(self.page.page_name)
        # Without this line, TableOfContents-Macro fails
        return FormatterBase.startContent(self, content_id, **kw)


    # Embedded Mode (e.g. Include Macro)
    def endContent(self):
        if self.isIncluded:
            return self.endDocument()
        return FormatterBase.endContent(self)


    # The MoinMoin drawing extension creates a PNG file besides the vector file
    def attachment_drawing(self, url, *args, **kargs): 
        self.attachment_image( url + '.png', *args, **kargs)
        return ""


    def attachment_image(self, url, *args, **kargs): 
        # prevent hacking (filenames like "../../../../etc/passwd")
        attachbasename = wikiutil.taintfilename( url )

        # accept only the most popular web image types
        (mimetype, extension) = self.getMimetype( attachbasename )
        if not mimetype.startswith( "image/" ): return ""
        
        # get real path in filesystem
        attachfilename = AttachFile.getFilename(self.request, self.pagename, attachbasename)

        # create internal name and store filename->internal name mapping, but only once
        if attachfilename in self.attachments:
            internalName = self.attachments[ attachfilename ]
        else:
            if self.isIncluded:
                # Will be replaced by calling formatter
                includeId = '<<INCLUDE_ID>>'
            else:
                includeId = ''
                
            internalName = str( self.getNextAttachmentCounter() )
            internalName = "Pictures/" + includeId + (12 - len(internalName) ) * "0" + internalName + "." + extension
            self.attachments[ attachfilename ] = internalName

        # embed image
        self.open( 'draw:frame' )
        imageNode = self.open( 'draw:image' )
        imageNode.setAttribute( 'xlink:href', self.attachments[attachfilename] )
        imageNode.setAttribute( 'xlink:type', 'simple' )
        imageNode.setAttribute( 'xlink:show', 'embed' )
        imageNode.setAttribute( 'xlink:actuate', 'onLoad' )
        self.close( 'draw:image' )
        self.close( 'draw:frame' )
        return ""
    
    
    def attachment_link(self, *args, **kargs): return ""
    
    
    def big(self, on, **kargs): 
        self.formatTag( on, "bigger" )
        return ""
    
    
    def bullet_list(self, on, *args, **kargs): 
        if on:
            newNode = self.open( 'text:list' )
            newNode.setAttribute( 'text:style-name', 'MoinBulletList' )
        else:
            self.close( 'text:list' )
        return ""
        
    def code(self, *args, **kargs): return ""
    
    
    def code_area(self, on, *args, **kargs): 
        self.preformatted( on )
        return ""
    
    
    def code_line(self, on, *args, **kargs): 
        self.linebreak()
        return ""
    
    
    # spaces between code words
    def code_token(self, on, *args, **kargs): 
        if on:
            self.text(' ')
        return ""
    
    
    # Definition: Description. 
    def definition_list(self, *args, **kargs): 
        return ''


    def definition_term(self, on, *args, **kargs): 
        if on:
            newNode = self.open('text:p', 'definition_list') # Mark text:p as other might use it too
            newNode.setAttribute("text:style-name","MoinDefinitionTerm")
        else:
            self.close('text:p', 'definition_list') # Mark text:p as other might use it too
        return ""


    # Left indent for definition_desc. Use list because this way the correct indent is used for this and for 
    # child lists (yes, a definition_desc may contain another list!)
    def definition_desc(self, on, *args, **kargs): 
        if on:
            newNode = self.open('text:list', 'definition_desc')
            newNode.setAttribute('text:style-name', 'MoinHiddenList')
            self.open('text:list-item', 'definition_desc')
            self.open('text:p', 'definition_desc')
        else:
            self.close('text:p', 'definition_desc')
            self.close( 'text:list-item', 'definition_desc' )
            self.close( 'text:list', 'definition_desc' )
        return ""


    def emphasis(self, on, **kargs): 
        self.formatTag( on, "italic" )
        return ""
    
    
    def heading(self, on, depth, **kargs): 
        if on:
            newNode = self.open( "text:h" )
            newNode.setAttribute("text:style-name", "Heading_20_%s" % (min(depth, 4)))  # We have styles from H1 to H4
            newNode.setAttribute("text:outline-level", "%s" % (depth))
        else:
            self.close("text:h")
        return ""
    

    # I have no idea how to create a highlight tag in MoinMoin
    def highlight(self, *args, **kargs): return ""
        
        
    def linebreak(self, preformatted = 1, **kargs): 
        # Suppress starting line break
        if self.Node.children:
            self.Node.addChild( Node("text:line-break") )
        return ""


    def listitem(self, on, *args, **kargs): 
        if on:
            self.open( 'text:list-item' )
        else:
            self.close( 'text:list-item' )
        return ""


    def macro(self, macro_obj, name, args, markup=None):
        if name == "Include":
            # Include-Macro
            
            rawXML = FormatterBase.macro(self, macro_obj, name, args)
            
            # Include-ID for embedded page
            self.include_counter += 1
            
            if self.isIncluded:
                # If we are included as well: Don't remove placeholder <<INCLUDE_ID>>,
                # but extend it with IncludeId of embedded page
                replacement = '<<INCLUDE_ID>>INC' + str(self.include_counter) + '_'
            else:
                # If we are not included: Replace <<INCLUDE_ID>> with IncludeId of embedded page
                replacement = 'INC' + str(self.include_counter) + '_'
            
            rawXML = rawXML.replace('<<INCLUDE_ID>>', replacement)
            
            # Get Header. Since this is not really build from XML tree, it's never an empty tag
            searchL = '<pseudoformatter_header>\n'
            searchR = '</pseudoformatter_header>'
            self.include_headers += rawXML [ rawXML.find( searchL ) + len(searchL) :  rawXML.find( searchR ) ]

            # Search for Automatic Styles
            searchL = '<office:automatic-styles>'
            posL = rawXML.find( searchL )
            # Avoid empty <office:automatic-styles/>
            if posL != -1:
                searchR = '</office:automatic-styles>'
                rawAutomaticStyles = rawXML [ posL + len(searchL) : rawXML.find( searchR ) ].strip()
                if len(rawAutomaticStyles):
                    rawNode = Node( E_RAW )
                    rawNode.content = rawAutomaticStyles
                    self.stylesNode.addChild( rawNode )
                    
            # Get office:text content
            searchL = '<office:text>'
            posL = rawXML.find( searchL )
            # Avoid empty <office:text/>
            if posL == -1:   
                return ''
            searchR = '</office:text>'
            rawOfficeText = rawXML [ posL + len(searchL) : rawXML.find( searchR ) ]
            # Return from Lists, paragraphs, etc.
            while self.Node.element != 'office:text' and self.Node.parent:
                self.Node = self.Node.parent
            # Add Raw Node
            rawNode = Node( E_RAW )
            rawNode.content = rawOfficeText
            self.Node.addChild( rawNode )
            return ""
            
        else:
            
            # Other Macro
            return FormatterBase.macro(self, macro_obj, name, args)

    
    def number_list(self, on, *args, **kargs): 
        if on:
            newNode = self.open( 'text:list' )
            newNode.setAttribute( 'text:style-name', 'MoinNumberedList' )
        else:
            self.close( 'text:list' )
        return ""
    
    
    def pagelink(self, on, pagename, text=None, **kw): return ""


    def paragraph(self, on, **kargs): 
        
        # without this no close-paragraph would be called
        FormatterBase.paragraph(self, on)
        
        if on:
            newNode = self.open("text:p", "paragraph_function")  # Mark text:p as others might use it too
            newNode.setAttribute("text:style-name","Standard")
        else:
            self.close("text:p", "paragraph_function") # Mark text:p as others might use it too
        return ""


    def preformatted(self, on, **kargs):
        
        FormatterBase.preformatted(self, on)

        if on:
            newNode = self.open("text:p", "preformatted_function") # Mark text:p as other might use it too
            newNode.setAttribute("text:style-name","Preformatted_20_Text")
        else:
            pNode = self.close("text:p", "preformatted_function") # Mark text:p as other might use it too
        return ""


    # Horizontal Line
    def rule(self, size=0, **kargs): 
        newNode = Node( 'text:p')
        self.Node.addChild( newNode )
        
        styleName = 'HL%s' % (size+1)
        newNode.setAttribute( 'text:style-name', styleName)
        
        style = self.addAutomaticStyle( styleName, 'paragraph' )
        
        # If Automatic Style was never used before: Define Style
        if style <> None:
            style.setAttribute( 'style:parent-style-name', 'Horizontal_20_Line')
            para = Node( 'style:paragraph-properties' )
            style.addChild( para )
            para.setAttribute( 'fo:border-bottom', "%.2fcm solid #606060" % ((size+1)*0.02) )
        
        return ""
        
        
    def small(self, on, **kargs): 
        self.formatTag( on, "smaller" )
        return ""
        
        
    def smiley(self, text, **kargs): 
        self.text( text )
        return ""
        
        
    def strike(self, on, **kargs): 
        self.formatTag( on, "strike" )
        return ""
    
    
    def strong(self, on, **kargs): 
        self.formatTag( on, "bold" )
        return ""
    
    
    def sub(self, on, **kargs): 
        self.formatTag( on, "sub" )
        return ""


    def sup(self, on, **kargs): 
        self.formatTag( on, "super" )
        return ""


    def table(self, on, attrs={}, **kargs): 
        if on:
            self.open( "table:table" )
        else:
            tableNode = self.close( "table:table" )
            if tableNode <> None:
                # Create list rowinfos with pairs [row, cellcount]
                rowinfos = [ [row,0] for row in tableNode.children if row.element == 'table:table-row' ]
                maxCellCount = 0
                
                for rowinfo in rowinfos:
                    row = rowinfo[0]
                    cellCount = len( \
                        [cell for cell in row.children if cell.element=='table:table-cell' or cell.element=='table:covered-table-cell'] )
                    rowinfo[1] = cellCount
                    maxCellCount = max( maxCellCount, cellCount)
                
                # Append missing cells to short rows
                for (row, cellcount) in rowinfos:
                    for i in xrange(0, maxCellCount - cellcount):
                        row.addChild( Node('table:table-cell') )
                
                # Insert as first table element info about table dimension
                # One element is enough if columns have the same style (e.g. width)
                newNode = Node ( 'table:table-column' )
                newNode.setAttribute( 'table:number-columns-repeated', str(maxCellCount) )
                tableNode.insertChild(0, newNode)
                
        return ""


    def table_row(self, on, attrs={}, **kargs): 
        if on:
            self.open( "table:table-row" )
        else:
            self.close( "table:table-row" )
        return ""
        
        
    def table_cell(self, on, attrs={}, **kargs): 
        if on:
            newNode = self.open( "table:table-cell")
            newNode.setAttribute("office:value-type","string")
            if 'colspan' in attrs:
                newNode.setAttribute( "table:number-columns-spanned", "2" )
        else:
            cellNode = self.close( "table:table-cell" )
            if cellNode <> None:
                for i in xrange(1, int(cellNode.attributes.get("table:number-columns-spanned", "1"))):
                    self.Node.addChild( Node('table:covered-table-cell') )
        return ""


    # Create Text, but replace long whitespace by whitespace tag
    def text(self, text, **kargs):

        # split text by two or more space characters (or if a single space is at the beginning of a text element)
        textlist = whitespaceSearch.split(text)   
        whitespace = False   # split never returns the delimiter as first token
        
        for token in textlist:
            if whitespace:
                newNode = Node("text:s")
                if len(token) > 1:
                    newNode.setAttribute("text:c",str( len(token) ))
                self.Node.addChild( newNode ) 
            else:
                # Add Text. Split preformatted text into its lines
                if self.Node.mark == 'preformatted_function':
                    lines = token.split('\n')
                    for i in range(0, len(lines)):
                        line = lines[i]
                        if len(line) > 0:
                            self.Node.addTextChild( line )
                        elif i == 0:
                            continue                     # don't start with a linebreak
                        if (i < len(lines) - 1):       # don't end with a linebreak
                            self.linebreak();
                else:
                    self.Node.addTextChild( token )
            whitespace = whitespace != True    # toggle whitespace 
            
        return ""


    def transclusion(self, on, **kargs):
        # TODO: Embed another object
        return ''


    def transclusion_param(self, **kargs):
        # TODO: Give parameters to embedded object
        return ''


    def underline(self, on, **kargs): 
        self.formatTag( on, "underline" )
        return ""


    def url(self, on, url=None, *args, **kargs): 
        if on:
            newNode = self.open("text:a")
            newNode.setAttribute("xlink:type", "simple")
            newNode.setAttribute("xlink:href", _escape(url) )
        else:
            self.close("text:a")
        return ""
