用xlwt和xlrd在不修改Excel單元格格式的情況下修改單元格內容

  一、問題的來源以及網上的錯誤方法

  最近遇到了一個問題,給定了一個Excel模板,修改表格裏面的內容,但是不能修改Excel表格的格式。用pywin32太慢,用xlrd只能讀,用xlwt只能寫。

  很快,我查到了網上“修改Excel內容但保留格式”的方法,大概是需要用到另一個輔助的庫xlutils,併爲formatting_info參數配置爲True,代碼大概是這樣的:

  import xlrd

  import xlwt

  from xlutils.copy import copy

  rb = xlrd.open_workbook('open.xls', formatting_info=True)

  wb = copy(rb)

  sheet = wb.get_sheet(0)

  for r in range(8):

  for c in range(5):

  sheet.write(r, c, 'R%sC%s'%(r+1, c+1))

  wb.save('save.xls')

  大部分格式的確是保住了,但是“修改了”的部分的格式慘不忍睹,一看這就是默認格式嘛。

  於是繼續查找,發現xlwt.Worksheet(工作表)對象的write函數的用法參數說明爲:

  write(r, c, label='', style=)

  網上找到的教程也有說明,只要配置xlwt庫的XFStyle類,併爲write函數的第4個參數賦值,就可以爲新寫入的單元格設置單元格格式了。代碼類似於這樣,這段代碼實現了對RC位置的單元格設置了20號宋體、上下水平居中、四邊邊框:

  style = xlwt.XFStyle()

  style.font.height = 400

  style.font.name = '宋體'

  style.alignment.horz = xlwt.Alignment.HORZ_CENTER

  style.alignment.vert = xlwt.Alignment.VERT_CENTER

  style.borders.left = xlwt.Borders.THIN

  style.borders.right = xlwt.Borders.THIN

  style.borders.top = xlwt.Borders.THIN

  style.borders.bottom = xlwt.Borders.THIN

  sheet.write(r, c, 'R%sC%s'%(r+1, c+1), style)

  然而衆所周知,xlwt庫只能寫不能讀,相當於我在設置單元格的樣式的,原表格的樣式對我是“盲”的。雖然我可以“手動”地記下來所有單元格的格式,然後“抄”過去,但是這太麻煩太繁瑣了。

  於是繼續網上查找資料,全部都是答非所問的結果,要麼告訴我“怎麼複製原表格而保留樣式”,要麼告訴我“如何修改單元格同時配置格式”,但沒有一個是回答“如何修改單元格而保留格式”的。

  二、根據屬性名猜可以用的函數

  沒辦法,代碼小白的我,只好繼續猜,猜猜看那個函數能做到這樣的配置。

  首先我嘗試爲工作表對象sheet的sheet.write函數的第4個參數配置“默認”項,企圖讓它“write”單元格的時候“不修改”格式,比如False啊、0啊、None啊、-1啊,都試過了,全部報錯。

  那麼嘗試找xlrd讀取的單元格的cell對象,看看裏面有沒有存儲格式信息。但是cell對象中的屬性不多,很快就爬乾淨了,沒有存儲格式這樣如此“細緻”的內容。

  線索斷了,我只好窮舉用xlrd讀取的工作簿rb的所有屬性函數,突然發現了一個看起來很像記錄格式的東西,rb.xf_list,其中是一個大列表,裏面記錄的全是“xlrd.formatting.XF”類型的對象,而字體格式的對象名字不是叫做“XFStyle”嗎,這可是非常一致了。

  這可是一個很大的突破,我猜想Excel中的單元格格式,不是分別記錄到各個單元格的對象的屬性中,而是將工作簿中出現的樣式彙總,再通過另一個方法把各個單元格設定的格式的“索引編號”讀出來,於是就做到了記錄各個單元格的格式信息。

  得到了線索就有了進展,我找到xlrd讀取的工作表sheet對象有一個sheet.cell_xf_index(rowx, colx)函數,返回結果是一個數值,而且針對相同格式的單元格佈局,這個結果在相同格式單元格中計算返回的索引編號很一致!那麼結果肯定是這個了!

  於是我急急忙忙把rb.xf_list[sheet.cell_xf_index(rowx, colx)]賦值到了sheet.write函數的style參數上,期待奇蹟的發生。

  三、瞎貓碰不到死耗子,那就硬着頭皮爬源碼

  果不其然,事情的進展不會這麼順利,果然出現了報錯。

  我突然想起來了write函數的說明,style的默認參數設置是“style=”,而我賦值的是“xlrd.formatting.XF”對象,這都不是一個庫的東西,怎麼能直接用呢。

  但是我堅定一個信念,既然通過xlutils.copy庫的copy函數可以把格式“複製”過去,那麼肯定在某個時候發生了讀取和寫入,這個格式讀寫的“管道”肯定是通的,關鍵是我要找到它。

  於是我撐住頭皮,開始爬xlutils庫的代碼,xlutils.copy庫的內容很乾淨,代碼只有這些:

  from xlutils.filter import process,XLRDReader,XLWTWriter

  def copy(wb):

  w = XLWTWriter()

  process(

  XLRDReader(wb,'unknown.xls'),

  w

  )

  return w.output[0][1]

  於是轉頭去爬這裏引用了的xlutils.filter庫。既然知道了rb.xf_list存儲了xlrd格式的“單元格樣式”,那麼就找找看它什麼時候轉化爲了xlwt格式的“單元格樣式”。

  按照關鍵詞搜索,果然找了一系列的判斷和轉換,代碼段大概是這樣的:

  for rdxf in rdbook.xf_list:

  wtxf = xlwt.Style.XFStyle()

  ... 各種判斷和轉換

  self.style_list.append(wtxf)

  那麼很顯然,我要找的就是self.style_list了。

  那麼按理說,我只要獲取到“self.style_list”的“self”,也就是它的父對象“BaseWriter”的實例,就能獲取到這個屬性了(也只有這個方法)。但是我驚訝地發現BaseWriter裏面有一個close方法,裏面赫然寫着“del self.style_list”,這可了得,我雖然不知道它是在什麼時候調用了,但是一旦調用了,那不就功虧一簣了,這個列表刪掉了那不就全完了。

  再看看xlutils.copy庫裏的內容,找到對應的定義:

  class XLWTWriter(BaseWriter):

  def __init__(self):

  self.output = []

  def close(self):

  if self.wtbook is not None:

  self.output.append((self.wtname,self.wtbook))

  del self.wtbook

  最終copy函數中的定義中返回的是“w.output[0][1]”,那實際上就是“self.wtbook”這個東西了。

  接着找相關定義,可以看到這樣一段:

  ...

  self.wtbook = xlwt.Workbook(style_compression=2)

  self.wtbook.dates_1904 = rdbook.datemode

  self.wtname = wtbook_name

  self.style_list = []

  ...鄭州婦科醫院 http://m.zyfuke.com/

  這可麻煩了呀,wtbook和style_list是BaseWriter類下的兩個平級的屬性,並沒有相互的聯繫,並且wtbook是一個xlwt.Workbook類型的對象,自然不可能提供獲取其父對象的方法(確認了屬性也確實沒有),這可咋辦,進度又陷入了停滯。

  於是我又靈機一動,“copy”函數雖然是已經封裝好的,但是我也可以把它拆開,就比方說這樣:

  from xlutils.filter import process, XLRDReader, XLWTWriter

  rb = xlrd.open_workbook('open.xls', formatting_info=True)

  w = XLWTWriter()

  process(XLRDReader(rb, 'unknown.xls'), w)

  wb = w.output[0][1]

  w是一個XLWTWriter類的對象,而XLWTWriter繼承於BaseWriter,BaseWriter有style_list屬性。那麼我嘗試訪問其style_list屬性,也就是“w.style_list”,並按照之前猜想的方法,將sheet.cell_xf_index函數獲取到的每個單元格的對應數字,認爲是“w.style_list”列表中的查詢單元格樣式的序列號,寫入程序:

  style_list = w.style_list

  sheet2 = wb.get_sheet(0)

  style = style_list[sheet.cell_xf_index(r, c)]

  sheet2.write(r, c, sheet.cell_xf_index(r, c), style)

  再次打開生成的保存文件,發現格式完美地保留了下來,而內容卻如我設定地修改了,至此,程序調試任務完成!

  四、終於可以運行的完整代碼

  完整樣例代碼是:

  import xlrd

  from xlutils.filter import process, XLRDReader, XLWTWriter

  rb = xlrd.open_workbook('open.xls', formatting_info=True)

  # 參考xlutils.copy庫內的用法 參考xlutils.filter內的參數定義style_list

  w = XLWTWriter()

  process(XLRDReader(rb, 'unknown.xls'), w)

  wb = w.output[0][1]

  style_list = w.style_list

  for n, sheet in enumerate(rb.sheets()):

  sheet2 = wb.get_sheet(n)

  for r in range(sheet.nrows):

  for c, cell in enumerate(sheet.row_values(r)):

  style = style_list[sheet.cell_xf_index(r, c)]

  sheet2.write(r, c, sheet.cell_xf_index(r, c), style)

  wb.save('save.xls')


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章