# 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

from __future__ import generators	# 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")
#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")
FontSlantNone = uno.getConstantByName("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 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, e:
			raise bibOOo_NoOffice, e.Message
		except IllegalArgumentException, e:
			#print "The url is invalid (%s)" % e.Message
			raise bibOOo_NoOffice, e.Message
		except RuntimeException, 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,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 xrange(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(map(lambda x:repr(x),tmp))
				tmp = [numbers[i]]
		if len(tmp) >=3:
			tmpranges.append("%s%s%s"%(tmp[0],cit_rangesep,tmp[-1]))	# we fuse
		else:
			tmpranges.extend(map(lambda x:repr(x),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...
		"""
		apply(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
		"""
		apply(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.DocumentInfo.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
		"""
		apply(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
		apply(messages, (.2,msg2) )
		self.__createBaseStyles()							# create the citation base style if needed
		apply(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
		apply(messages, (.4,msg4) )
		if not self.bib: self.createIndex()					# we create the index if needed
		self.updateIndex()									# update index
		self.freezeCitations(messages = messages,**kwds)
		apply(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
		apply(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

