# Copyright 2004,2005 Pierre Martineau <pmartino@users.sourceforge.net>
# This file is part of bibOOo, a python package to manipulate
# bibliography in an OpenOffice.org writer document.
#
# bibOOo is part of Bibus a 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.
#/home/pmartino/Desktop/Bibus/bibus-cvs/bibus/bibOOo/bibOOoBase.py
# bibOOo 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 Bibus; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
#
# this class implements basic interaction between a biliographic software and OOo

    # py2.2, to be removed in python 2.3
import sys, os
import uno
# below we commented the normal way to call the class/constants
# the form used is to be compatible with cx_freeze
#from com.sun.star.connection import NoConnectException
NoConnectException = uno.getClass("com.sun.star.connection.NoConnectException")
#from com.sun.star.uno  import RuntimeException
RuntimeException = uno.getClass("com.sun.star.uno.RuntimeException")
#from com.sun.star.lang import IllegalArgumentException, DisposedException
IllegalArgumentException = uno.getClass("com.sun.star.lang.IllegalArgumentException")
DisposedException = uno.getClass("com.sun.star.lang.DisposedException")
#from com.sun.star.io import IOException
IOException = uno.getClass("com.sun.star.io.IOException")
#from com.sun.star.container import NoSuchElementException
NoSuchElementException = uno.getClass("com.sun.star.container.NoSuchElementException")
#from com.sun.star.util import URL
URL = uno.getClass("com.sun.star.util.URL")
#from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK
PARAGRAPH_BREAK = uno.getConstantByName("com.sun.star.text.ControlCharacter.PARAGRAPH_BREAK")
#from com.sun.star.beans import PropertyValue
PropertyValue = uno.getClass("com.sun.star.beans.PropertyValue")
#from com.sun.star.beans.PropertyState import DIRECT_VALUE
# DIRECT_VALUE = uno.getConstantByName("com.sun.star.beans.PropertyState.DIRECT_VALUE")
DIRECT_VALUE = uno.Enum("com.sun.star.beans.PropertyState","DIRECT_VALUE")
#from com.sun.star.lang import Locale
Locale = uno.getClass("com.sun.star.lang.Locale")
# Styles
#from com.sun.star.awt.FontSlant import ITALIC, NONE
#FontSlantNone = NONE
#ITALIC = uno.getConstantByName("com.sun.star.awt.FontSlant.ITALIC")
ITALIC = uno.Enum("com.sun.star.awt.FontSlant","ITALIC")
#FontSlantNone = uno.getConstantByName("com.sun.star.awt.FontSlant.NONE")
FontSlantNone = uno.Enum("com.sun.star.awt.FontSlant","NONE")
#from com.sun.star.awt.FontWeight import BOLD, NORMAL
BOLD = uno.getConstantByName("com.sun.star.awt.FontWeight.BOLD")
NORMAL = uno.getConstantByName("com.sun.star.awt.FontWeight.NORMAL")
#from com.sun.star.awt.FontUnderline import SINGLE, NONE
#FontUnderlineNone = NONE
SINGLE = uno.getConstantByName("com.sun.star.awt.FontUnderline.SINGLE")
FontUnderlineNone = uno.getConstantByName("com.sun.star.awt.FontUnderline.NONE")
#from com.sun.star.style.CaseMap import UPPERCASE,SMALLCAPS, NONE
#CaseMapNone = NONE
UPPERCASE = uno.getConstantByName("com.sun.star.style.CaseMap.UPPERCASE")
SMALLCAPS = uno.getConstantByName("com.sun.star.style.CaseMap.SMALLCAPS")
CaseMapNone = uno.getConstantByName("com.sun.star.style.CaseMap.NONE")
#

from bibOOo.CONST import *

class bibOOo_Error(Exception):
    """Main error class
    bibOOo_Error--|
                  |-----bibOOo_NoConnect-----|
                  |                          |----bibOOo_NoOffice
                  |                          |----bibOOo_NoWriter
                  |
                  |-----bibOOo_ExecError-----|
                                             |----bibOOo_PositionError
                                             |----bibOOo_StyleError
                                             |----bibOOo_IOError
    """
    msg = ''
    def __init__(self,OOo=''):
        self.OOo = OOo    # we propagate the OOo error message in this attribute or an additional information

class bibOOo_NoConnect(bibOOo_Error):
    """Global class for connection error"""
    msg = _("Cannot connect to Openoffice. See Documentation.")
class bibOOo_NoOffice(bibOOo_NoConnect):
    """We were not able to connect to a OOo instance"""
    msg = _("Cannot connect to Openoffice. See Documentation.")
class bibOOo_NoWriter(bibOOo_NoConnect):
    """The top most OOo document is not a writer doc"""
    msg = _("The front document in OOo must be a Writer document.")

class bibOOo_ExecError(bibOOo_Error):
    """Parent class for all error related to bibOOo specific problems"""
    msg = _("Non allowed operation""")
class bibOOo_PositionError(bibOOo_ExecError):
    """We are trying to apply a function at a non suited position"""
    msg = _("Incorrect position")
class bibOOo_StyleError(bibOOo_ExecError):
    """Error in the style file"""
    msg = _("Style format Error")
class bibOOo_IOError(bibOOo_ExecError):
    """File error operation"""
    msg = _("File error operation")

class bibOOo_Ref(object):
    """This class is a wrapper for OOo in-text citations
    You can access the citation
    as attributes : bibRef.Author etc...
    Can be used to set or read citations in the current doc"""

    def __init__(self,bibOOo_instance,OOoRef=None,ref=(),dbdict={}):
        """
        model is the document model in which we want to create the citation.
        bibOOo_instance is needed only if OOoRef == None
        OORef = "com.sun.star.text.TextField.Bibliography"
        if OORef == None, we create a "com.sun.star.text.TextField.Bibliography"
        from
                a list/tuple = (identifier,BibliographicType,....,ISBN)
                or
                dbdict = dictionary = {'Identifier':'toto', 'BibliographicType':1, 'Address':'NY', 'Annote':'Note', ...}
                the dbdict.keys() may be incomplete => Absent fields will be set to '', BibliographicType to 1 = ARTICLE
        or an empty reference of type = 1 (ARTICLE)
        then in all cases we wrap the OOo object
        """
        if not OOoRef:
            self.OOoRef = bibOOo_instance.model.createInstance("com.sun.star.text.TextField.Bibliography")
            if ref:
                self.setRef(ref)
                #tmp = [ PropertyValue(field,0,ref[OO_BIBLIOGRAPHIC_FIELDS[field]],DIRECT_VALUE) for field in OO_BIB_FIELDS ]
            elif dbdict:
                tmp=[]
                for field in OO_BIB_FIELDS:
                    try: tmp.append( PropertyValue(field,0,dbdict[field],DIRECT_VALUE) )
                    except KeyError: tmp.append( PropertyValue(field,0,'',DIRECT_VALUE) )
                if 'BibliographicType' in list(dbdict.keys()):
                    tmp[1] = PropertyValue('BibiliographicType',0,dbdict['BibliographicType'],DIRECT_VALUE)    # to correct spelling error in OOo
                if tmp[1].Value == '':
                    tmp[1] = PropertyValue('BibiliographicType',0,1,DIRECT_VALUE)    # default type == ARTICLE
                self.OOoRef.Fields = tuple(tmp)
            else:
                #tmp = [ PropertyValue(field,0,'',DIRECT_VALUE) for field in OO_BIB_FIELDS ]
                #tmp[1] = PropertyValue('BibiliographicType',0,1,DIRECT_VALUE) # ARTICLE
                self.setRef( ('', 1, '', '', '','', '', '', '', '', '','', '', '', '', '', '', '','', '', '', '', '', '', '', '','', '', '', '', '') )
            #self.OOoRef.Fields = tuple(tmp)
        else:
            self.OOoRef = OOoRef
            
    #def __eq__(self,other):                            # suppressed because it did not work in Ubuntu 7.10
    #    return self.Identifier == other.Identifier
        
    #def __ne__(self,other):
    #    return self.Identifier != other.Identifier

    def __getattr__(self,attr):
        """ If it is a BIBLIOGRAPHIC_FIELDS, we return the value
        otherwise, we return the attribute of the OORef
        This way we can directly access the method of the OOo ref object """
        if attr in OO_BIB_FIELDS:
            return self.OOoRef.Fields[OO_BIBLIOGRAPHIC_FIELDS[attr]].Value
        elif attr == 'BibliographicType':    # spelling error in OOo
            return self.OOoRef.Fields[1].Value
        else:
            return getattr(self.OOoRef,attr)

    def __setattr__(self,attr,value):
        if attr in OO_BIB_FIELDS:
            tmp = list(self.OOoRef.Fields)
            tmp[OO_BIBLIOGRAPHIC_FIELDS[attr]] = PropertyValue(attr,0,value,DIRECT_VALUE)
            self.OOoRef.Fields = tuple(tmp)
        elif attr == 'BibliographicType':    # spelling error in OOo
            tmp = list(self.OOoRef.Fields)
            tmp[1] = PropertyValue('BibiliographicType',0,value,DIRECT_VALUE)
            self.OOoRef.Fields = tuple(tmp)
        elif attr == 'OOoRef':
            object.__setattr__(self,'OOoRef',value)
        else:
            raise AttributeError

    def setRef(self,ref):
        """
        Set the values of the reference using the data in
        ref which is a list/tuple.
        This is much faster than setting field by field
        """
        tmp = [ PropertyValue(field,0,ref[OO_BIBLIOGRAPHIC_FIELDS[field]],DIRECT_VALUE) for field in OO_BIB_FIELDS ]
        self.OOoRef.Fields = tuple(tmp)


class bibOOoBase(object):
    """This is the main class to interact with OOo"""
# ------------------------ Connection setup ------------------------------
    def __init__(self,con_type=1,host='localhost',port=8100,pipe='OOo_pipe'):
        """We connect to the running OOo instance
        con_type = connection_type = 1 if pipe ; 0 if TCP
        host = host for TCP
        port = port for TCP
        pipe = pipe name
        """
        if con_type == 0:
            OO_CON_STR = "uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext"%(host,port)
        else:
            OO_CON_STR = "uno:pipe,name=%s;urp;StarOffice.ComponentContext"%pipe
        # get the uno component context from the PyUNO runtime
        localContext = uno.getComponentContext()
        # create the UnoUrlResolver
        resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext )
        try:
            # connect to the running office
            ctx = resolver.resolve( OO_CON_STR )
            self.smgr = ctx.ServiceManager
            # get the central desktop object
            self.desktop = self.smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
            # some utilities
            self.transformer = MagicTransformer( ctx )    # this is to force PropertyValue.Value to the desired type because of a python bug
            self.oTrans = self.smgr.createInstanceWithContext("com.sun.star.util.URLTransformer", ctx ) # used in __freezeBib to format URL
            #
        except NoConnectException as e:
            raise bibOOo_NoOffice(e.Message)
        except IllegalArgumentException as e:
            #print "The url is invalid (%s)" % e.Message
            raise bibOOo_NoOffice(e.Message)
        except RuntimeException as e:
               #print "An unknown error occured (%s)" % e.Message
               raise bibOOo_NoOffice(e.Message)

    def connectToWriter(self, hilight = False, backColor = 0x00FFFF00, createBib = True, end = True):
        """connect to the top OOo document if it is a writer doc"""
        self.bib = None                    # bibliographic index
        try:
            self.model = self.desktop.getCurrentComponent()
            if self.model and self.model.getImplementationName() == 'SwXTextDocument':    # this is a text document
                self.controller = self.model.getCurrentController()
                self.cursor = self.controller.getViewCursor()    # Current ViewCursor
                # access the document's text property
                self.text = self.model.Text
                # look for the first bibliography index or create a new one at the end if no biblio index present
                bibFound = False
                for i in range(self.model.getDocumentIndexes().getCount()):
                    self.bib = self.model.getDocumentIndexes().getByIndex(i)
                    if self.bib.getServiceName() == 'com.sun.star.text.Bibliography':
                        bibFound = True
                        break
                if not bibFound and createBib:
                    # we create the bibliography index at the end of the document
                    self.createIndex(end)
                # we get the com.sun.star.text.FieldMaster.Bibliography after eventually creating it
                try:
                    self.tfm = self.model.getTextFieldMasters().getByName('com.sun.star.text.FieldMaster.Bibliography')    # the biblio TextFieldMaster
                except NoSuchElementException:
                    self.tfm = self.model.createInstance("com.sun.star.text.FieldMaster.Bibliography")
                self.stylesList = self.model.getStyleFamilies()    # styles set XIndexAccess (XStyleFamiliesSupplier interface)
                self.__createBaseStyles()                    # create the styles if needed
                self.hilightCitations(hilight, background = backColor)    # hilight citations if required. Just change the CharStyle
                #self.setIndexFormat()                        # set the biblio format REMOVED BECAUSE IT IS TOO SLOW !!!!
            else:
                #print "You must have a text document opened in OpenOffice.org in oder to be able to use this menu"
                raise bibOOo_NoWriter
        except DisposedException as e:
            raise bibOOo_NoOffice(e.Message)

# ------------------------ iteration ------------------------------
    def __iter__(self):
        for cit in self.getCitations(None):
            yield cit

# functions to get a sorted list of citations
    def __isFused(self,x,y,cursor):
        """Return True if the two citations x & y are separated only by spaces => can be fused"""
        try:
            cursor.gotoRange(x.Anchor.End,False)
            if not x.Anchor.Text.compareRegionStarts(cursor,y.Anchor):
                return True            # we should not need it but it does not work if the two ranges start at the same position
            cursor.gotoRange(y.Anchor.Start,True)
            return not bool(cursor.String.strip(' '))    # if only white spaces between citations => we fuse them
        #except IllegalArgumentException:
        except:
            return False                                # we are not in the same Text or something goes wrong

    def groupCitations(self,refs):
        """We group the citations in refs list.
        We return a list of list.
        Each list is a group of citations
        i.e. citations separated by blank spaces"""
        refs_out=[]
        if refs:
            lastref = refs[0]
            tmp_range=[lastref]
            tmpcursor = lastref.Anchor.Text.createTextCursor()
            for ref in refs[1:]:
                if self.__isFused(lastref,ref,tmpcursor):
                    tmp_range.append(ref)
                else:
                    refs_out.append(tmp_range)
                    tmp_range = [ref]
                    tmpcursor = ref.Anchor.Text.createTextCursor()
                lastref = ref
            refs_out.append(tmp_range)
        return refs_out

    def getCitations(self,order=None):
        """Return a list of the citations ordered as
        order == None : unsorted
        order == index : order of the bibliographic index
        order == document : order in the document
        order == group : list of list. Order as document. When citations are consecutives they are in a sub-list.
        Exemple: "Start doc [1] [2] continue [3] continue [4][5]" will give: [[1,2],[3],[4,5]]
        """
        # we first update the index and references.
        #updateRef()                    # we update the references to be up-to-date
        #refs = list(self.tfm.getPropertyValue('DependentTextFields'))    # we get the references
        refs = [ bibOOo_Ref(self,ref) for ref in self.tfm.getPropertyValue('DependentTextFields') ]    # we get the references and wrap them in bibOOo_Ref objects
        lbb = len(self.tfm.BracketBefore)    # used later to get the numbers without the brackets
        lba = -len(self.tfm.BracketAfter)
        if order == None:
            return refs
        elif order == 'index':
            SavedState = self.tfm.IsNumberEntries
            self.tfm.IsNumberEntries = True            # we number citations to get the order of them
            self.model.getTextFields().refresh()
            #
            tmplist = [(int(x.getPresentation(False)[lbb:len(x.getPresentation(False))+lba]), x) for x in refs]
            tmplist.sort()
            refs = [x for (key, x) in tmplist]
            #
            self.tfm.IsNumberEntries = SavedState            # we reverse to the original state
            return refs
        else:
            # to sort by document:
            # 1) We put for each ref a unique identifier (a number)
            # 2) We ask OOo to sort by doc + numbers. As identifiers are uniques => each ref has its own number = order
            # 3) We sort using the getPresentation(False)
            # 4) We put back the correct identifiers
            # 1)
            savedIdentifiers=[]
            i = 0
            for ref in refs:
                savedIdentifiers.append(ref.Identifier)
                ref.Identifier = repr(i)
                i = i+1
            # 2)
            SavedState = self.tfm.IsNumberEntries, self.tfm.IsSortByPosition
            self.tfm.IsNumberEntries = True
            self.tfm.IsSortByPosition = True
            self.model.getTextFields().refresh()
            # 3)
            tmplist = [(int(x.getPresentation(False)[lbb:len(x.getPresentation(False))+lba]), x) for x in refs]
            tmplist.sort()
            refs = [x for (key, x) in tmplist]
            # 4)
            for ref in refs:
                ref.Identifier = savedIdentifiers[int(ref.Identifier)]
            #
            self.model.getTextFields().refresh()
            self.tfm.IsNumberEntries, self.tfm.IsSortByPosition = SavedState
            if order == 'document': return refs
            if order == 'group' : return self.groupCitations(refs)                    # order = 'group'
            raise ValueError('What are we doing here? bibOOo line 357. order = %r is not a correct value.'%order)
# end of functions to get a sorted list of citations



# ------------------------ Index ------------------------------
    def createIndex(self,end = True):
        """create bibliographic index at the end if True, or at the current cursor position if False"""
        cursor = self.text.createTextCursor()
        if end: cursor.gotoEnd(False)                # we create at the end of the doc
        else: cursor.gotoRange(self.cursor,False)    # or at the view cursor
        self.text.insertControlCharacter(cursor,PARAGRAPH_BREAK,False)
        self.bib = self.model.createInstance("com.sun.star.text.Bibliography")
        self.text.insertTextContent(cursor,self.bib,False)
        if self.text.compareRegionEnds(self.cursor,cursor) == 0: # the viewcursor was at the end of the doc
            self.cursor.gotoRange(self.bib.getAnchor().getStart(),False)    # we must relocate it before the index
            self.cursor.goLeft(1,False)            # we are now just before the index
        self.bib.update()                        # update the new index (title, etc.)

    def updateIndex(self):
        """Update bibliography index"""
        self.model.getTextFields().refresh()    # refresh the fields in case citation format has changed
        if self.bib: self.bib.update()            # update de la biblio if it exists

    def setIndexFormat(self,format):
        """
        set the format of the bibliographic index
        format is a dictionary:
        format.keys() = ['SortKeys', 'Title', 'Locale', 'IsNumberEntries', 'SortAlgorithm', 'IsSortByPosition', 'Bracket']
        exemples:
        format['index']['Locale']  = 'en_US'
        format['index']['Title'] = 'Bibliography'
        format['index']['IsNumberEntries'] = True | False
        format['index']['SortAlgorithm'] = 'alphanumeric'
        format['index']['IsSortByPosition'] = True | False    True = sorted in doc order ; False =  sorted as in defined in SortKeys
        format['index']['Bracket'] = '[]'
        format['index']['SortKeys'] = ((4, True), (23, True)) = ( (field1,ascending),(field2,ascending), ... )
        field1, field2 = zero based index of the field in CONST.OO_BIB_FIELDS ( 4 = Author ; 23 = Year )
        ascending = True
        descending = False
        format['ordering']['ARTICLE'] = tuple of tuple = ('text',text,style) | ('field',field_name,style) | ('tab',value,TabStopFillCharacter) | ('tab',None,TabStopFillCharacter)
        where style = bibOOo_italic | bibOOo_bold ...
        for tabs, if value = None => right aligned
        exemple :  ( ('field', u'Author', 0), ('text', u' (', 0), ('field', u'Year', 0), ('text', u')', 0) ) = Einstein (1920)
        this must be defined for all the references types = BIB_TYPE
        you can use format['ordering']['JOURNAL'] = format['ordering']['ARTICLE'] etc.. to save space
        """
        if not self.bib: return        # if no bib, we it will crash
        # set Bibliography service
        self.bib.Locale = Locale(format['index']['Locale'][:2],format['index']['Locale'][-2:],'')
        self.bib.SortAlgorithm = format['index']['SortAlgorithm']
        self.bib.Title = format['index']['Title']
        # set Bibliography FieldMaster
        self.tfm.IsNumberEntries = bool(format['index']['IsNumberEntries'])
        self.tfm.IsSortByPosition = bool(format['index']['IsSortByPosition'])
        if format['index']['Bracket']:
            self.tfm.BracketBefore = format['index']['Bracket'][0]
            self.tfm.BracketAfter = format['index']['Bracket'][1]
        else:
            self.tfm.BracketBefore = ''
            self.tfm.BracketAfter = ''
        # set Bibliography FieldMaster SortKeys
        tmp = [ ( PropertyValue('SortKey',0,i[0],DIRECT_VALUE) , PropertyValue('IsSortAscending',0,bool(i[1]),DIRECT_VALUE) ) for i in format['index']['SortKeys'] ]
        self.tfm.SortKeys = tuple(tmp)
        # set Bibliography service LevelFormat (=fields ordering)
        for reftype in BIB_TYPE:
            tmp=[]
            for token in tuple(format['ordering'][reftype]):
                if token[0] == 'text':
                    tmp.append((PropertyValue("TokenType",0,"TokenText",DIRECT_VALUE),\
                    PropertyValue("CharacterStyleName",0,self.__setStyleName(token[2]),DIRECT_VALUE),\
                    PropertyValue("Text",0,token[1],DIRECT_VALUE)))
                elif token[0] == 'field':
                    tmp.append((PropertyValue("TokenType",0,"TokenBibliographyDataField",DIRECT_VALUE),\
                    PropertyValue("CharacterStyleName",0,self.__setStyleName(token[2]),DIRECT_VALUE),\
                    PropertyValue("BibliographyDataField",0,OO_BIBLIOGRAPHIC_FIELDS[token[1]],DIRECT_VALUE)))
                elif token[0] == 'tab':
                    if token[1] != None:
                        # PropertyValue("TabStopPosition",0,uno.Any('long',token[1]),DIRECT_VALUE), # does not work!
                        # the two following lines are a workaround for this bug.
                        special = PropertyValue("TabStopPosition",0,token[1],DIRECT_VALUE)
                        special = self.transformer.transform( special, "Value" , uno.Any( "long", token[1] ) )
                        tmp.append((PropertyValue("TokenType",0,"TokenTabStop",DIRECT_VALUE),\
                        special,\
                        PropertyValue("TabStopFillCharacter",0,token[2],DIRECT_VALUE),\
                        PropertyValue("CharacterStyleName",0,"",DIRECT_VALUE)))
                    else:
                        tmp.append((PropertyValue("TokenType",0,"TokenTabStop",DIRECT_VALUE),\
                        PropertyValue("TabStopRightAligned",0,True,DIRECT_VALUE),\
                        PropertyValue("TabStopFillCharacter",0,token[2],DIRECT_VALUE),\
                        PropertyValue("CharacterStyleName",0,"",DIRECT_VALUE)))
                else:
                    raise bibOOo_StyleError("Error in the style file: I can't understand a token like %r" %token.__repr__())
            # the following two lines are a work around for bug #12504. See python-uno FAQ.
            # since self.bib.LevelFormat.replaceByIndex(OO_BIBLIOGRAPHIC_TYPE[reftype]+1,tuple(tmp)) does not work
            unoseq = uno.Any("[][]com.sun.star.beans.PropertyValue",tuple(tmp))
            uno.invoke(self.bib.LevelFormat,"replaceByIndex",(BIBLIOGRAPHIC_TYPE[reftype]+1,unoseq))
            # remark: +1 in the previous line since Index=1=ARTICLE, etc...

# ------------------------ CharStyles ------------------------------
    def __createBaseStyles(self):
        """create base CharStyle for citations and index"""
        charStyles = self.stylesList.getByName('CharacterStyles')
        if not charStyles.hasByName(bibOOo_cit_baseCharStyleName):
            self.createCharacterStyle(bibOOo_cit_baseCharStyleName,bibOOo_regular,bibOOo_normal,'')
        if not charStyles.hasByName(bibOOo_index_baseCharStyleName):
            self.createCharacterStyle(bibOOo_index_baseCharStyleName,bibOOo_regular,bibOOo_normal,'')

    def __setStyleName(self,charstyle):
        """create a charStyle corresponding to charStyle if needed
        return the style name as:
        bibOOo_base<''|i><''|b><''|u><''|s|c> where i=italic, b=bold, u=underline, s=smallcaps, c=caps"""
        # calculating style name
        italic,bold,underline,caps = '','','',''
        if charstyle & bibOOo_italic: italic = 'i'
        if charstyle & bibOOo_bold: bold = 'b'
        if charstyle & bibOOo_underline: underline = 'u'
        if charstyle & bibOOo_caps: caps = 'c'
        elif charstyle & bibOOo_smallcaps: caps = 's'
        stylename = ''.join( (bibOOo_index_baseCharStyleName,italic,bold,underline,caps) )    # bibOOo_index_base ; bibOOo_index_basei ; etc...
        # creating style if needed based on bibus_base
        charStyles = self.stylesList.getByName('CharacterStyles')
        if not charStyles.hasByName(stylename):
            self.createCharacterStyle(stylename,charstyle,bibOOo_normal,bibOOo_index_baseCharStyleName)
        return stylename

    def createCharacterStyle(self,charstylename,charstyle,position,parentstylename=''):
        """
        create an OOo CharacterStyle "com.sun.star.style.CharacterStyle" with the name charstylename if it does not exist
        set its parent charstyle to parentstylename
        set its value to style
        where style is a combination of
        from bibOOo_CONST import bibOOo_regular,bibOOo_italic,bibOOo_bold,bibOOo_caps,bibOOo_smallcaps,bibOOo_underline
        from bibOOo_CONST import bibOOo_normal,bibOOo_exposant,bibOOo_indice
        style = bibOOo_italic | bibOOo_bold =  italic + bold
        position = bibOOo_normal (=0) OR bibOOo_exposant (=1) OR bibOOo_indice (= -1)
        """
        charStyles = self.stylesList.getByName('CharacterStyles')
        if not charStyles.hasByName(charstylename):
            tmp_style = self.model.createInstance('com.sun.star.style.CharacterStyle')    # create a char style
            charStyles.insertByName(charstylename,tmp_style)    # insert the style in the document
            if parentstylename: tmp_style.setParentStyle(parentstylename)    # set parent charstyle
            # we save the default values default from newly created style
            basePosture,baseWeight,baseUnderline,baseCaps = tmp_style.CharPosture, tmp_style.CharWeight, tmp_style.CharUnderline, tmp_style.CharCaseMap
            # setting only the specific attributes
            if charstyle & bibOOo_italic and basePosture != ITALIC: tmp_style.CharPosture = ITALIC
            elif not charstyle & bibOOo_italic and basePosture == ITALIC: tmp_style.CharPosture = FontSlantNone
            #
            if charstyle & bibOOo_bold and baseWeight != BOLD: tmp_style.CharWeight = BOLD
            elif not charstyle & bibOOo_bold and baseWeight == BOLD: tmp_style.CharWeight = NORMAL
            #
            if charstyle & bibOOo_underline and baseUnderline != SINGLE: tmp_style.CharUnderline = SINGLE
            elif not charstyle & bibOOo_underline and baseUnderline == SINGLE: tmp_style.CharUnderline = FontUnderlineNone
            #
            if charstyle & bibOOo_caps and baseCaps != UPPERCASE: tmp_style.CharCaseMap = UPPERCASE
            elif charstyle & bibOOo_smallcaps and baseCaps != SMALLCAPS: tmp_style.CharCaseMap = SMALLCAPS
            elif not charstyle & bibOOo_caps and not charstyle & bibOOo_smallcaps and baseCaps!=CaseMapNone: tmp_style.CharCaseMap = CaseMapNone
            # CharEscapement = 101 means automatique exposant ; -101 indice ; 0 normal
            tmp_style.CharEscapement = 101 * position        # automatic
            if tmp_style.CharEscapement:
                tmp_style.CharEscapementHeight = 58                # value ok in French. Other languages ?
            else:
                tmp_style.CharEscapementHeight = 100

    def updateCharacterStyle(self,charstylename,charstyle,position,parentstylename=''):
        """Reset values of style charstylename"""
        charStyles = self.stylesList.getByName('CharacterStyles')
        if charStyles.hasByName(charstylename):
            tmp_style = charStyles.getByName(charstylename)
            # reset all properties to default. Property list must be on alphabetical order!!!!
            tmp_style.setPropertiesToDefault( ('CharCaseMap','CharEscapement','CharEscapementHeight','CharPosture','CharUnderline','CharWeight') )
            if parentstylename: tmp_style.setParentStyle(parentstylename)    # set parent charstyle
            # we save the default values default from newly created style
            basePosture,baseWeight,baseUnderline,baseCaps = tmp_style.CharPosture, tmp_style.CharWeight, tmp_style.CharUnderline, tmp_style.CharCaseMap
            # setting only the specific attributes
            if charstyle & bibOOo_italic and basePosture != ITALIC: tmp_style.CharPosture = ITALIC
            elif not charstyle & bibOOo_italic and basePosture == ITALIC: tmp_style.CharPosture = FontSlantNone
            #
            if charstyle & bibOOo_bold and baseWeight != BOLD: tmp_style.CharWeight = BOLD
            elif not charstyle & bibOOo_bold and baseWeight == BOLD: tmp_style.CharWeight = NORMAL
            #
            if charstyle & bibOOo_underline and baseUnderline != SINGLE: tmp_style.CharUnderline = SINGLE
            elif not charstyle & bibOOo_underline and baseUnderline == SINGLE: tmp_style.CharUnderline = FontUnderlineNone
            #
            if charstyle & bibOOo_caps and baseCaps != UPPERCASE: tmp_style.CharCaseMap = UPPERCASE
            elif charstyle & bibOOo_smallcaps and baseCaps != SMALLCAPS: tmp_style.CharCaseMap = SMALLCAPS
            elif not charstyle & bibOOo_caps and not charstyle & bibOOo_smallcaps and baseCaps!=CaseMapNone: tmp_style.CharCaseMap = CaseMapNone
            # CharEscapement = 101 means automatique exposant ; -101 indice ; 0 normal
            tmp_style.CharEscapement = 101 * position        # automatic
            if tmp_style.CharEscapement:
                tmp_style.CharEscapementHeight = 58                # value ok in French. Other languages ?
            else:
                tmp_style.CharEscapementHeight = 100


    def styleRef(self,ref,charstylename = bibOOo_cit_baseCharStyleName):
        """Set the CharStyle of the bibOOo_Ref object to charstylename"""
        c = ref.Anchor.Text.createTextCursorByRange(ref.Anchor)
        c.CharStyleName = charstylename

    def hilightCitations(self,hilight = True, citStyleName = bibOOo_cit_baseCharStyleName, background = 0x00FFFF00):
        """
        Set the background of charStyle bibOOo_cit_baseCharStyleName
        to background (default = yellow)
        if hilight = True
        elif hilight = False => background = Default
        """
        charStyles = self.stylesList.getByName('CharacterStyles')
        tmp_style = charStyles.getByName(bibOOo_cit_baseCharStyleName)
        if hilight:
            tmp_style.CharBackColor = background            # background (default = yellow)
        else:
            tmp_style.setPropertyToDefault('CharBackColor')    # normal background

    def resetCitationStyle(self,charstylename = bibOOo_cit_baseCharStyleName):
        """
        Reset the style of all the citations to bibOOo_cit_baseCharStyleName
        This is needed if some citations have been inserted by another means than
        bibOOo, for instance when the user has used the classical OOo interface.
        """
        for ref in self:
            c = ref.Anchor.Text.createTextCursorByRange(ref.Anchor)
            c.CharStyleName = charstylename                # reset the style it case it has changed

# ------------------------ Citations ------------------------------
    def insertRef(self,ref,charstylename = bibOOo_cit_baseCharStyleName):
        """bibOOo_Ref object => insert at the current cursor position
        using CharStyle charstylename
        """
        if self.notInBibIndex(self.cursor):
            c = self.cursor.Text.createTextCursorByRange(self.cursor)    # Add at cursor location (replace selection)
            c.CharStyleName = charstylename
            c.Text.insertTextContent(c,ref.OOoRef,True)
            self.cursor.setPropertyToDefault('CharStyleName')            # reset cursor to default
        else:
            raise bibOOo_PositionError("Try to insert a reference in the Bibliographic index")

    def createRef(self,ref=(),dbdict={}):
        """
        create a bibOOo_Ref object from
        a tuple/list = (identifier,BibliographicType,....,ISBN)
        return an empty ARTICLE if ref=()
        """
        return bibOOo_Ref(self,None,ref,dbdict)

# ------------------------ Freezing ------------------------------
    def __fuse_range(self,numbers,cit_rangesep='-'):
        """numbers is a sorted list of integers.
        We return a list of strings where 1,2,3 => '1(cit_rangesep)3' """
        tmpranges,tmp = [],[]
        tmp = [numbers[0]]
        for i in range(1,len(numbers)):
            if numbers[i] == numbers[i-1] + 1:
                    tmp.append(numbers[i])
                    continue
            else:
                if len(tmp) >=3:
                    tmpranges.append("%s-%s"%(tmp[0],tmp[-1]))    # we fuse
                else:
                    tmpranges.extend([repr(x) for x in tmp])
                tmp = [numbers[i]]
        if len(tmp) >=3:
            tmpranges.append("%s%s%s"%(tmp[0],cit_rangesep,tmp[-1]))    # we fuse
        else:
            tmpranges.extend([repr(x) for x in tmp])
        return tmpranges

    def __freezeNumberedCitations(self,messages = lambda i,x:sys.stdout.write('%s\n'%x),cit_sort=True,cit_fuse=True,cit_range=True,cit_separator='; ',cit_rangesep='-'):
        """
        format the in-text citations (sort,range,style, etc..)
        for numbers:
            depending on cit_sort,cit_fuse,cit_range we replace
            [1] [3][2][5] => [1-3,5] etc...
        """
        messages(*(.7,msg7 ))
        if not cit_fuse and not cit_sort:
            for ref in self:                # we don't mind about the order
                c = ref.Anchor.Text.createTextCursorByRange(ref.Anchor.End)
                ref.Anchor.Text.insertString(c,ref.getPresentation(0),True)
        else:
            refranges = self.getCitations(order='group')
            for refs in refranges:
                c = refs[0].Anchor.Text.createTextCursorByRange(refs[0].Anchor.End)    # cursor at end
                bb = self.tfm.BracketBefore
                ba = self.tfm.BracketAfter
                lbb = len(bb)
                lba = -len(ba)
                numbers = [ int(x.getPresentation(False)[lbb:len(x.getPresentation(False))+lba]) for x in refs ]
                numbers.sort()
                if cit_range:
                    ranges = self.__fuse_range(numbers,cit_rangesep)                                # calculate ranges
                elif cit_sort:
                    ranges = [repr(x) for x in numbers]
                else:
                    print("What are we doing here. bibOOo line 492")
                # we insert in the text
                refs[0].Anchor.Text.insertString(c,"%s%s%s" %(bb,cit_separator.join(ranges),ba),True)

    def __freezeIdentifierCitations(self,messages = lambda i,x:sys.stdout.write('%s\n'%x),cit_fuse=True,cit_separator='; '):
        """
        for identifiers:
            we just copy/paste the identifiers with fusion if cit_fuse = True
        """
        messages(*(.7,msg7 ))
        if not cit_fuse:
            for ref in self:                # we don't mind about the order
                c = ref.Anchor.Text.createTextCursorByRange(ref.Anchor.End)
                ref.Anchor.Text.insertString(c,ref.getPresentation(0),True)
        else:
            refranges = self.getCitations(order='group')
            for refs in refranges:
                c = refs[0].Anchor.Text.createTextCursorByRange(refs[0].Anchor.End)    # cursor at end
                bb = self.tfm.BracketBefore
                ba = self.tfm.BracketAfter
                lbb = len(bb)
                lba = -len(ba)
                identifiers = [x.getPresentation(False)[lbb:len(x.getPresentation(False))+lba] for x in refs]
                # we insert in the text
                refs[0].Anchor.Text.insertString(c,"%s%s%s" %(bb,cit_separator.join(identifiers),ba),True)

    def freezeCitations(self,messages = lambda i,x:sys.stdout.write('%s\n'%x),cit_sort=True,cit_fuse=True,cit_range=True,cit_separator='; ',cit_rangesep='-'):
        """
        format the in-text citations (sort,range,style, etc..)
        for numbers:
            depending on cit_sort,cit_fuse,cit_range we replace
            [1] [3][2][5] => [1-3,5] etc...
        for identifiers:
            we just copy/paste the identifiers with fusion if cit_fuse = True
        """
        # we first freeze the citations
        if self.tfm.IsNumberEntries:
            self.__freezeNumberedCitations(messages,cit_sort,cit_fuse,cit_range,cit_separator,cit_rangesep)
        else:
            self.__freezeIdentifierCitations(messages,cit_fuse,cit_separator)
        self.freezeIndex()                            # we freeze the index
        self.tfm.dispose()                            # we remove all the citations

    def freezeIndex(self):
        """
        make a copy of the bibliographic index, then remove the index
        Freeze the index by copying then pasting it.
        """
        #Cut = '.uno:Cut'        # 'slot:5710'    # slot values. May change in the future ?
        Copy = '.uno:Copy'        # 'slot:5711'
        Paste = '.uno:Paste'    # 'slot:5712'
        self.cursor.gotoRange(self.bib.Anchor,False)    # select the bib
        oUrl = URL()
        oUrl.Complete = Copy    # copy the current selection
        countOfUrls,parsedUrl = self.oTrans.parseSmart( oUrl, ".uno" )
        oDisp = self.controller.queryDispatch( parsedUrl, "", 0 )
        if oDisp != None:
            oDisp.dispatch(parsedUrl,())
            # Move The cursor After the index
            self.cursor.collapseToEnd()
            self.cursor.goRight(1,False)
            # Then paste the index
            oUrl.Complete = Paste    # paste the clipboard
            countOfUrls,parsedUrl = self.oTrans.parseSmart( oUrl, ".uno" )
            oDisp = self.controller.queryDispatch( parsedUrl, "", 0 )
            if oDisp != None:
                oDisp.dispatch(parsedUrl,())
                self.text.insertControlCharacter(self.cursor,PARAGRAPH_BREAK,False)
                # Dispose the index
                self.bib.dispose()

    def __newDoc(self):
        """Save the current doc if needed, then create a new one = copy"""
        if self.model.isModified():
            raise bibOOo_IOError("You must first save the current document")
        if self.model.hasLocation():
            self.model.store()
        else:
            raise bibOOo_IOError("Impossible to save the current document")
         # we store the old name
        name = self.model.getURL()
        oURL=URL()
        oURL.Complete=name
        countOfUrls,parsedUrl = self.oTrans.parseSmart( oURL, "http" )
        if countOfUrls:
            name = os.path.splitext(parsedUrl.Name)[0]
        else:
            name = 'text'
        #try:
        #    self.model.storeAsURL(self.model.getLocation(),())
        #except IOException:
        #    raise bibOOo_IOError,"Impossible to save the current document"
        """We open a copy"""
        fa = self.smgr.createInstance('com.sun.star.ucb.SimpleFileAccess')
        inputstream = fa.openFileRead(self.model.getLocation())
        pvDescriptor=(PropertyValue('InputStream',0,inputstream,DIRECT_VALUE),
                      PropertyValue('Hidden',0,True,DIRECT_VALUE))
        self.model = self.desktop.loadComponentFromURL('private:stream', "_default",0,pvDescriptor)    # reload the document from stream
        self.controller = self.model.getCurrentController()
        self.cursor = self.controller.getViewCursor()    # Current ViewCursor
        self.text = self.model.Text
        # look for the first bibliography index or create a new one at the end if no biblio index present
        for i in range(self.model.getDocumentIndexes().getCount()):
            self.bib = self.model.getDocumentIndexes().getByIndex(i)
            if self.bib.getServiceName() == 'com.sun.star.text.Bibliography':
                break
        # we get the com.sun.star.text.FieldMaster.Bibliography after eventually creating it
        try:
            self.tfm = self.model.getTextFieldMasters().getByName('com.sun.star.text.FieldMaster.Bibliography')    # the biblio TextFieldMaster
        except NoSuchElementException:
            self.tfm = self.model.createInstance("com.sun.star.text.FieldMaster.Bibliography")
        self.stylesList = self.model.getStyleFamilies()    # styles XIndexAccess (XStyleFamiliesSupplier interface)
        # we define a name and we but it in the DocumentInfo Title => in the Windows title
        self.model.Title='%s-formatted' %name
        #
        return True

    def finalize(self,messages = lambda i,x:sys.stdout.write('%s\n'%x),**kwds):
        """
        finalize(self,cit_sort=True,cit_fuse=True,cit_range=True,cit_separator='; ')
        make a copy of the current doc
        make a copy of the bibliographic index, then remove the index
        format the in-text citations (sort,range,style, etc..)
        we print formatting messages in it
        Default = None = stdout
        """
        messages(*(.1,msg1))
        if not self.__newDoc():                                # we try to open a copy of the current doc.
            raise bibOOo_IOError("Cannot save the current document")    # error if not possible
        messages(*(.2,msg2))
        self.__createBaseStyles()                            # create the citation base style if needed
        messages(*(.3,msg3))
        self.hilightCitations(False)            # we don't want to hilight for the final format
        self.resetCitationStyle()                # reset the style for the citations
        # needed to 'freeze' the Anchors because of a bug in OOo ?
        #fixAnchors = [ref.Anchor for ref in self.getCitations(order=None)]    # bug OOo ?
        #
        # self.updateRef()                                    # make ref uptodate
        messages(*(.4,msg4))
        if not self.bib: self.createIndex()                    # we create the index if needed
        self.updateIndex()                                    # update index
        self.freezeCitations(messages = messages,**kwds)
        messages(*(.9,msg5))
        self.controller.Frame.ContainerWindow.setVisible(True)    # make the new frame visible
        self.controller.Frame.ComponentWindow.setVisible(True)    # make the new doc visible
        self.model.setModified(False)                        # disable the save button/toolbar icon
        messages(*(1.0,msg6))

# ------------------------ Divers ------------------------------
    def saveDoc(self,url=None):
        """
        Store the current doc.
        if url != None, we use it
        Otherwise we save using the current location
        """
        if not url:
            if self.model.hasLocation():
                self.model.store()
            else:
                raise bibOOo_IOError("Impossible to save the current document")
        else:
            if url.endswith(".sxw"):
                pd = (PropertyValue("FilterName",0,"StarOffice XML (Writer)",DIRECT_VALUE),)
            elif url.endswith(".odt"):
                pd = (PropertyValue("FilterName",0,"writer8",DIRECT_VALUE),)
            else:    # default format
                pd = ()
            try:
                self.model.storeAsURL(uno.systemPathToFileUrl(url),pd)
            except IOException:
                self.model.storeAsURL(uno.systemPathToFileUrl(url),()) # if it fails, we use default format

    def activate(self):
       """Give focus to the current OOo document"""
       frame = self.desktop.getCurrentFrame().getContainerWindow()
       frame.setFocus()

    def notInBibIndex(self,cursor):
        "Return True if the range does not intercept with the Bibliographic index"
        if self.bib != None and cursor.Text == self.bib.Anchor.Text:            # must be in the same Text to be compared
            curs = cursor.Text.createTextCursorByRange(cursor)
            curs.goLeft(1,True)
            b=cursor.Text.compareRegionStarts(curs.Start,self.bib.Anchor.End) != 1    # cursor after Bibliography index
            curs.collapseToEnd()
            curs.goRight(1,False)
            a=cursor.Text.compareRegionEnds(curs.End,self.bib.Anchor.Start) != -1    # cursor before Bibliography index
            return (a or b)
        else:
            return True                                            # it is not in the same Text and cannot be compared

# the following code is a workaround to a python-uno bug that makes PropertyValue().Value=uno.Any('long',100) not possible.
# many thanks to Joerg Budischewski for this code.
# this is used to set "TabStopPosition" in the Bibliographic index
class MagicTransformer:
    def __init__( self , ctx ):
        self.inv = ctx.ServiceManager.createInstanceWithContext(
            "com.sun.star.script.Invocation", ctx )
        self.insp =  ctx.ServiceManager.createInstanceWithContext(
            "com.sun.star.beans.Introspection", ctx )
    def transform( self, struct , propName, value ):
        myinv = self.inv.createInstanceWithArguments( (struct,) )
        access = self.insp.inspect( myinv )
        method = access.getMethod( "setValue" , -1 )
        uno.invoke( method, "invoke", ( myinv, ( propName , value ) ))
        method = access.getMethod( "getMaterial" , -1 )
        ret,dummy = method.invoke(myinv,() )
        return ret

