Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1125607
  • 博文数量: 170
  • 博客积分: 1603
  • 博客等级: 上尉
  • 技术积分: 1897
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-09 15:54
文章分类

全部博文(170)

文章存档

2016年(27)

2015年(21)

2014年(27)

2013年(21)

2012年(7)

2011年(67)

我的朋友

分类: Python/Ruby

2014-09-11 20:26:40

openpyxl单元格格式在style中

点击(此处)折叠或打开

  1. def __init__(self, static=False):
  2.         self.static = static
  3.         self.font = Font()
  4.         self.fill = Fill()
  5.         self.borders = Borders()
  6.         self.alignment = Alignment()
  7.         self.number_format = NumberFormat()
  8.         self.protection = Protection()
NumberFormat对应单元格格式中的 "数字"
Fill对应填充
Font对应字体
Alignment对应对齐
Borders对应边框

填充方式设置代码(先设置填充方式,然后设置填充颜色)

点击(此处)折叠或打开

  1. excel_sheet.cell(row=row_num,column=1).style.fill.fill_type = Fill.FILL_SOLID
  2. excel_sheet.cell(row=row_num,column=1).style.fill.start_color.index = 'FFD306'

单元格格式 设置单元格格式为纯文本

点击(此处)折叠或打开

  1. excel_sheet.cell(row=row_num,column=1).style.number_format.format_code = NumberFormat.FORMAT_TEXT


合并单元格

点击(此处)折叠或打开

  1. excel_sheet.merge_cells(start_row=row_num, end_row=row_num, start_column=1, end_column=7)


具体可以见去各自的类文件中查看定义,比如numbers.py里有

点击(此处)折叠或打开

  1. FORMAT_GENERAL = 'General'
  2.     FORMAT_TEXT = '@'
  3.     FORMAT_NUMBER = '0'
  4.     FORMAT_NUMBER_00 = '0.00'
  5.     FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00'
  6.     FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'
  7.     FORMAT_PERCENTAGE = '0%'
  8.     FORMAT_PERCENTAGE_00 = '0.00%'
  9.     FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'
  10.     FORMAT_DATE_YYYYMMDD = 'yy-mm-dd'
  11.     FORMAT_DATE_DDMMYYYY = 'dd/mm/yy'
  12.     FORMAT_DATE_DMYSLASH = 'd/m/y'
  13.     FORMAT_DATE_DMYMINUS = 'd-m-y'
  14.     FORMAT_DATE_DMMINUS = 'd-m'
  15.     FORMAT_DATE_MYMINUS = 'm-y'
  16.     FORMAT_DATE_XLSX14 = 'mm-dd-yy'
  17.     FORMAT_DATE_XLSX15 = 'd-mmm-yy'
  18.     FORMAT_DATE_XLSX16 = 'd-mmm'
  19.     FORMAT_DATE_XLSX17 = 'mmm-yy'
  20.     FORMAT_DATE_XLSX22 = 'm/d/yy h:mm'
  21.     FORMAT_DATE_DATETIME = 'd/m/y h:mm'
  22.     FORMAT_DATE_TIME1 = 'h:mm AM/PM'
  23.     FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM'

NumberFormat缺陷
过长大的int即使设置了纯文本格式写入依然胡会被截断......估计修正这个需要
修改openpyxl底层save文件的代码
~~~~听说你精通excel,来来来,先把7000页的office文件格式看一遍.......



补充.......
昨天为了解决长ID写入excel文件被截断的问题,直接在长ID前加了个字母,这样终于让文本正确保存.......
导完数据以后.....硬着头皮看了openpyxl   保存文件的代码.......
麻痹也没别人说的7000页office文件格式那么复杂嘛.........


先看workbook的save方法,调用了下面

点击(此处)折叠或打开

  1. if self.__optimized_write:
  2.             save_dump(self, filename)
  3.         else:
  4.             save_workbook(self, filename)
dump不知道什么东西,先看save_workbook方法
方法在“from openpyxl.writer.excel import save_workbook”

点击(此处)折叠或打开

  1. def save_workbook(workbook, filename):
  2.     """Save the given workbook on the filesystem under the name filename.

  3.     :param workbook: the workbook to save
  4.     :type workbook: :class:`openpyxl.workbook.Workbook`

  5.     :param filename: the path to which save the workbook
  6.     :type filename: string

  7.     :rtype: bool

  8.     """
  9.     writer = ExcelWriter(workbook)
  10.     writer.save(filename)
  11.     return True

先不细看ExcelWriter类
直接看ExcelWriter类的save方法

点击(此处)折叠或打开

  1. def save(self, filename):
  2.         """Write data into the archive."""
  3.         archive = ZipFile(filename, 'w', ZIP_DEFLATED)
  4.         self.write_data(archive)
  5.         archive.close()

WTF?zip? excel是个zip文件?直接用7z解压一个excel文件,我操还真是......
分析下文件结构
我们的各个sheet的都是以xml形式保存在'xl/worksheets/sheet%d' % sheet_id 中
随便打开个sheet1文件,可以发现,所有只包含数字的的value都保存在这个文件中,但是这里没有非数值的值。
找了下发现,所有非数值的字符都在xl/sharedStrings.xml

仔细分析发现,'xl/worksheets/sheet%d' % sheet_id 中的数值,其实保存的内容是正确的没有被截断,但是excel本身显示的时候,只要数值超过15位数(好像是15),就是会被截断,即使单元格格式设置正确(这里可以看出excel的单元格格式只不能修改数据存储,只负责显示)。
所以可以猜测,只要我们把纯数值的value存到
xl/sharedStrings.xml中而不是'xl/worksheets/sheet%d' % sheet_id里就不会被截断了

从上面分析我们可以猜测下excle的文件设计思路
sharedStrings.xml,顾名思义,共享的字符串,为了减少excel的文件大小,所有非数值的字符串都放在这里,这就有一个最大的好处
当你大部分单元格都是一个字符串比如“aaa”的时候,excel不用每个单元格都保存一个“aaa”,只要在shareString.xml里放一个“aaa”,然后所有值为“aaa”的单元格都引用这个数值就好


缺点当然也明显,当大部分字符串都不一样的时候,会造成单个文件过大,这也是excel为什么会把纯数值文件保存在'xl/worksheets/sheet%d' % sheet_id里的原因
所以,在一般情况下....纯数值文件还是放在'xl/worksheets/sheet%d' % sheet_id里比较好,但是在数值太长,无发正常显示的情况下,我们还是得想办法把数值弄到sharedStrings.xml里

我们在代码里找到
sharedStrings.xml在哪里调用,看看代码在哪里过滤了纯数值让他不写入sharedStrings.xml,我们先跟着上面的self.write_data(archive)去看看

点击(此处)折叠或打开

  1. class ExcelWriter(object):
  2.     """Write a workbook object to an Excel file."""

  3.     def __init__(self, workbook):
  4.         self.workbook = workbook
  5.         self.style_writer = StyleWriter(self.workbook)

  6.     def write_data(self, archive):
  7.         """Write the various xml files into the zip archive."""
  8.         # cleanup all worksheets
  9.         shared_string_table = self._write_string_table(archive)

  10.         archive.writestr(ARC_CONTENT_TYPES, write_content_types(self.workbook))
  11.         archive.writestr(ARC_ROOT_RELS, write_root_rels(self.workbook))
  12.         archive.writestr(ARC_WORKBOOK_RELS, write_workbook_rels(self.workbook))
  13.         archive.writestr(ARC_APP, write_properties_app(self.workbook))
  14.         archive.writestr(ARC_CORE, write_properties_core(self.workbook.properties))
  15.         if self.workbook.loaded_theme:
  16.             archive.writestr(ARC_THEME, self.workbook.loaded_theme)
  17.         else:
  18.             archive.writestr(ARC_THEME, write_theme())
  19.         archive.writestr(ARC_STYLE, self.style_writer.write_table())
  20.         archive.writestr(ARC_WORKBOOK, write_workbook(self.workbook))

  21.         if self.workbook.vba_archive:
  22.             vba_archive = self.workbook.vba_archive
  23.             for name in vba_archive.namelist():
  24.                 for s in ARC_VBA:
  25.                     if name.startswith(s):
  26.                         archive.writestr(name, vba_archive.read(name))
  27.                         break

  28.         self._write_worksheets(archive, shared_string_table, self.style_writer)

  29.     def _write_string_table(self, archive):

  30.         for ws in self.workbook.worksheets:
  31.             ws.garbage_collect()
  32.         shared_string_table = create_string_table(self.workbook)
  33.         archive.writestr(ARC_SHARED_STRINGS,
  34.                 write_string_table(shared_string_table))

  35.         return shared_string_table

write_data的第一步是执行
shared_string_table = self._write_string_table(archive),一看名字就知道啦 ,这个是和share  string 有关的
我们去看shared_string_table = self._write_string_table(archive),第一个循环是和worksheet有关的,我们先不看

看看他下面的 archive.writestr(ARC_SHARED_STRINGS,write_string_table(shared_string_table))
ARC_SHARED_STRINGS是啥?代码追踪过去一看
ARC_SHARED_STRINGS = PACKAGE_XL + '/sharedStrings.xml'
那么可以看到这里果然是写
sharedStrings.xml的,那么继续看write_string_table


点击(此处)折叠或打开

  1. def write_string_table(string_table):
  2.     """Write the string table xml."""
  3.     temp_buffer = StringIO()
  4.     doc = XMLGenerator(out=temp_buffer, encoding='utf-8')
  5.     start_tag(doc, 'sst', {'xmlns':
  6.             '',
  7.             'uniqueCount': '%d' % len(string_table)})
  8.     strings_to_write = sorted(string_table.items(),
  9.             key=lambda pair: pair[1])
  10.     for key in [pair[0] for pair in strings_to_write]:
  11.         start_tag(doc, 'si')
  12.         if key.strip() != key:
  13.             attr = {'xml:space': 'preserve'}
  14.         else:
  15.             attr = {}
  16.         tag(doc, 't', attr, key)
  17.         end_tag(doc, 'si')
  18.     end_tag(doc, 'sst')
  19.     string_table_xml = temp_buffer.getvalue()
  20.     temp_buffer.close()
  21.     return string_table_xml

这看上去是个写xml文件头的,archive.writestr这个就没什么看头了....回头看create_string_table

点击(此处)折叠或打开

  1. def create_string_table(workbook):
  2.     """Compile the string table for a workbook."""
  3.     strings = set()
  4.     for sheet in workbook.worksheets:
  5.         for cell in sheet.get_cell_collection():
  6.             if cell.data_type == cell.TYPE_STRING and cell._value is not None:
  7.                 strings.add(cell.value)
  8.     return dict((key, i) for i, key in enumerate(sorted(strings)))

oh yeah,看见if cell.data_type == cell.TYPE_STRING and cell._value is not None:就能大概知道了,应该就是这个函数判断了单元格里的value类型
看看get_cell_collection

点击(此处)折叠或打开

  1. def get_cell_collection(self):
  2.         """Return an unordered list of the cells in this worksheet."""
  3.         return self._cells.values()
看前面能知道self._cells = {},那么_cells是个字典,那么字典的values()就是一个列表,这个列表里是啥呢 ~从名字猜,列表里有个事存着Cell类的实例
去看看cell文件,里面果然存在TYPE_STRING!

OK,那么我我们把赋值的代码这样修改
excel_sheet.cell(row=x,y).value = str(a)
excel_sheet.cell(row=x,y).data_type = cell.Cell.TYPE_STRING 
excel_sheet.cell(row=x,column=y).style.number_format.format_code = NumberFormat.FORMAT_TEXT

运行发现....报错!不能赋值...........
难道我需要继承重写cell!!这就大条了!仔细看看Cell的类

发现有个

点击(此处)折叠或打开

  1. def set_explicit_value(self, value=None, data_type=TYPE_STRING):
  2.         """Coerce values according to their explicit type"""
  3.         type_coercion_map = {
  4.             self.TYPE_INLINE: self.check_string,
  5.             self.TYPE_STRING: self.check_string,
  6.             self.TYPE_FORMULA: self.check_string,
  7.             self.TYPE_NUMERIC: self.check_numeric,
  8.             self.TYPE_BOOL: bool,
  9.             self.TYPE_ERROR: self.check_error}
  10.         try:
  11.             self._value = type_coercion_map[data_type](value)
  12.         except KeyError:
  13.             if data_type not in self.VALID_TYPES:
  14.                 msg = 'Invalid data type: %s' % data_type
  15.                 raise DataTypeException(msg)
  16.         self._data_type = data_type

oh yeah,代码改成
 cur_cell = excel_sheet.cell(row=x,column=y)
cur_cell.set_explicit_value(str(a), cell.Cell.TYPE_STRING)


OK 大功告成并熟悉了下excel文件格式~~

补充说明下,我另外一台电脑上openpyx是1.6.2版的
set_explicit_value函数名有变化
set_value_explicit,真是蛋痛








阅读(21220) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~