Style.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. from __future__ import print_function
  2. # -*- coding: windows-1252 -*-
  3. from . import Formatting
  4. from .BIFFRecords import NumberFormatRecord, XFRecord, StyleRecord
  5. from .compat import basestring, xrange
  6. FIRST_USER_DEFINED_NUM_FORMAT_IDX = 164
  7. class XFStyle(object):
  8. def __init__(self):
  9. self.num_format_str = 'General'
  10. self.font = Formatting.Font()
  11. self.alignment = Formatting.Alignment()
  12. self.borders = Formatting.Borders()
  13. self.pattern = Formatting.Pattern()
  14. self.protection = Formatting.Protection()
  15. default_style = XFStyle()
  16. class StyleCollection(object):
  17. _std_num_fmt_list = [
  18. 'general',
  19. '0',
  20. '0.00',
  21. '#,##0',
  22. '#,##0.00',
  23. '"$"#,##0_);("$"#,##0)',
  24. '"$"#,##0_);[Red]("$"#,##0)',
  25. '"$"#,##0.00_);("$"#,##0.00)',
  26. '"$"#,##0.00_);[Red]("$"#,##0.00)',
  27. '0%',
  28. '0.00%',
  29. '0.00E+00',
  30. '# ?/?',
  31. '# ??/??',
  32. 'M/D/YY',
  33. 'D-MMM-YY',
  34. 'D-MMM',
  35. 'MMM-YY',
  36. 'h:mm AM/PM',
  37. 'h:mm:ss AM/PM',
  38. 'h:mm',
  39. 'h:mm:ss',
  40. 'M/D/YY h:mm',
  41. '_(#,##0_);(#,##0)',
  42. '_(#,##0_);[Red](#,##0)',
  43. '_(#,##0.00_);(#,##0.00)',
  44. '_(#,##0.00_);[Red](#,##0.00)',
  45. '_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
  46. '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
  47. '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
  48. '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
  49. 'mm:ss',
  50. '[h]:mm:ss',
  51. 'mm:ss.0',
  52. '##0.0E+0',
  53. '@'
  54. ]
  55. def __init__(self, style_compression=0):
  56. self.style_compression = style_compression
  57. self.stats = [0, 0, 0, 0, 0, 0]
  58. self._font_id2x = {}
  59. self._font_x2id = {}
  60. self._font_val2x = {}
  61. for x in (0, 1, 2, 3, 5): # The font with index 4 is omitted in all BIFF versions
  62. font = Formatting.Font()
  63. search_key = font._search_key()
  64. self._font_id2x[font] = x
  65. self._font_x2id[x] = font
  66. self._font_val2x[search_key] = x
  67. self._xf_id2x = {}
  68. self._xf_x2id = {}
  69. self._xf_val2x = {}
  70. self._num_formats = {}
  71. for fmtidx, fmtstr in zip(range(0, 23), StyleCollection._std_num_fmt_list[0:23]):
  72. self._num_formats[fmtstr] = fmtidx
  73. for fmtidx, fmtstr in zip(range(37, 50), StyleCollection._std_num_fmt_list[23:]):
  74. self._num_formats[fmtstr] = fmtidx
  75. self.default_style = XFStyle()
  76. self._default_xf = self._add_style(self.default_style)[0]
  77. def add(self, style):
  78. if style == None:
  79. return 0x10
  80. return self._add_style(style)[1]
  81. def _add_style(self, style):
  82. num_format_str = style.num_format_str
  83. if num_format_str in self._num_formats:
  84. num_format_idx = self._num_formats[num_format_str]
  85. else:
  86. num_format_idx = (
  87. FIRST_USER_DEFINED_NUM_FORMAT_IDX
  88. + len(self._num_formats)
  89. - len(StyleCollection._std_num_fmt_list)
  90. )
  91. self._num_formats[num_format_str] = num_format_idx
  92. font = style.font
  93. if font in self._font_id2x:
  94. font_idx = self._font_id2x[font]
  95. self.stats[0] += 1
  96. elif self.style_compression:
  97. search_key = font._search_key()
  98. font_idx = self._font_val2x.get(search_key)
  99. if font_idx is not None:
  100. self._font_id2x[font] = font_idx
  101. self.stats[1] += 1
  102. else:
  103. font_idx = len(self._font_x2id) + 1 # Why plus 1? Font 4 is missing
  104. self._font_id2x[font] = font_idx
  105. self._font_val2x[search_key] = font_idx
  106. self._font_x2id[font_idx] = font
  107. self.stats[2] += 1
  108. else:
  109. font_idx = len(self._font_id2x) + 1
  110. self._font_id2x[font] = font_idx
  111. self.stats[2] += 1
  112. gof = (style.alignment, style.borders, style.pattern, style.protection)
  113. xf = (font_idx, num_format_idx) + gof
  114. if xf in self._xf_id2x:
  115. xf_index = self._xf_id2x[xf]
  116. self.stats[3] += 1
  117. elif self.style_compression == 2:
  118. xf_key = (font_idx, num_format_idx) + tuple(obj._search_key() for obj in gof)
  119. xf_index = self._xf_val2x.get(xf_key)
  120. if xf_index is not None:
  121. self._xf_id2x[xf] = xf_index
  122. self.stats[4] += 1
  123. else:
  124. xf_index = 0x10 + len(self._xf_x2id)
  125. self._xf_id2x[xf] = xf_index
  126. self._xf_val2x[xf_key] = xf_index
  127. self._xf_x2id[xf_index] = xf
  128. self.stats[5] += 1
  129. else:
  130. xf_index = 0x10 + len(self._xf_id2x)
  131. self._xf_id2x[xf] = xf_index
  132. self.stats[5] += 1
  133. if xf_index >= 0xFFF:
  134. # 12 bits allowed, 0xFFF is a sentinel value
  135. raise ValueError("More than 4094 XFs (styles)")
  136. return xf, xf_index
  137. def add_font(self, font):
  138. return self._add_font(font)
  139. def _add_font(self, font):
  140. if font in self._font_id2x:
  141. font_idx = self._font_id2x[font]
  142. self.stats[0] += 1
  143. elif self.style_compression:
  144. search_key = font._search_key()
  145. font_idx = self._font_val2x.get(search_key)
  146. if font_idx is not None:
  147. self._font_id2x[font] = font_idx
  148. self.stats[1] += 1
  149. else:
  150. font_idx = len(self._font_x2id) + 1 # Why plus 1? Font 4 is missing
  151. self._font_id2x[font] = font_idx
  152. self._font_val2x[search_key] = font_idx
  153. self._font_x2id[font_idx] = font
  154. self.stats[2] += 1
  155. else:
  156. font_idx = len(self._font_id2x) + 1
  157. self._font_id2x[font] = font_idx
  158. self.stats[2] += 1
  159. return font_idx
  160. def get_biff_data(self):
  161. result = b''
  162. result += self._all_fonts()
  163. result += self._all_num_formats()
  164. result += self._all_cell_styles()
  165. result += self._all_styles()
  166. return result
  167. def _all_fonts(self):
  168. result = b''
  169. if self.style_compression:
  170. fonts = self._font_x2id.items()
  171. else:
  172. fonts = [(x, o) for o, x in self._font_id2x.items()]
  173. for font_idx, font in sorted(fonts):
  174. result += font.get_biff_record().get()
  175. return result
  176. def _all_num_formats(self):
  177. result = b''
  178. alist = [
  179. (v, k)
  180. for k, v in self._num_formats.items()
  181. if v >= FIRST_USER_DEFINED_NUM_FORMAT_IDX
  182. ]
  183. alist.sort()
  184. for fmtidx, fmtstr in alist:
  185. result += NumberFormatRecord(fmtidx, fmtstr).get()
  186. return result
  187. def _all_cell_styles(self):
  188. result = b''
  189. for i in range(0, 16):
  190. result += XFRecord(self._default_xf, 'style').get()
  191. if self.style_compression == 2:
  192. styles = self._xf_x2id.items()
  193. else:
  194. styles = [(x, o) for o, x in self._xf_id2x.items()]
  195. for xf_idx, xf in sorted(styles):
  196. result += XFRecord(xf).get()
  197. return result
  198. def _all_styles(self):
  199. return StyleRecord().get()
  200. # easyxf and its supporting objects ###################################
  201. class EasyXFException(Exception):
  202. pass
  203. class EasyXFCallerError(EasyXFException):
  204. pass
  205. class EasyXFAuthorError(EasyXFException):
  206. pass
  207. class IntULim(object):
  208. # If astring represents a valid unsigned integer ('123', '0xabcd', etc)
  209. # and it is <= limit, return the int value; otherwise return None.
  210. def __init__(self, limit):
  211. self.limit = limit
  212. def __call__(self, astring):
  213. try:
  214. value = int(astring, 0)
  215. except ValueError:
  216. return None
  217. if not 0 <= value <= self.limit:
  218. return None
  219. return value
  220. bool_map = {
  221. # Text values for all Boolean attributes
  222. '1': 1, 'yes': 1, 'true': 1, 'on': 1,
  223. '0': 0, 'no': 0, 'false': 0, 'off': 0,
  224. }
  225. border_line_map = {
  226. # Text values for these borders attributes:
  227. # left, right, top, bottom and diag
  228. 'no_line': 0x00,
  229. 'thin': 0x01,
  230. 'medium': 0x02,
  231. 'dashed': 0x03,
  232. 'dotted': 0x04,
  233. 'thick': 0x05,
  234. 'double': 0x06,
  235. 'hair': 0x07,
  236. 'medium_dashed': 0x08,
  237. 'thin_dash_dotted': 0x09,
  238. 'medium_dash_dotted': 0x0a,
  239. 'thin_dash_dot_dotted': 0x0b,
  240. 'medium_dash_dot_dotted': 0x0c,
  241. 'slanted_medium_dash_dotted': 0x0d,
  242. }
  243. charset_map = {
  244. # Text values for font.charset
  245. 'ansi_latin': 0x00,
  246. 'sys_default': 0x01,
  247. 'symbol': 0x02,
  248. 'apple_roman': 0x4d,
  249. 'ansi_jap_shift_jis': 0x80,
  250. 'ansi_kor_hangul': 0x81,
  251. 'ansi_kor_johab': 0x82,
  252. 'ansi_chinese_gbk': 0x86,
  253. 'ansi_chinese_big5': 0x88,
  254. 'ansi_greek': 0xa1,
  255. 'ansi_turkish': 0xa2,
  256. 'ansi_vietnamese': 0xa3,
  257. 'ansi_hebrew': 0xb1,
  258. 'ansi_arabic': 0xb2,
  259. 'ansi_baltic': 0xba,
  260. 'ansi_cyrillic': 0xcc,
  261. 'ansi_thai': 0xde,
  262. 'ansi_latin_ii': 0xee,
  263. 'oem_latin_i': 0xff,
  264. }
  265. # Text values for colour indices. "grey" is a synonym of "gray".
  266. # The names are those given by Microsoft Excel 2003 to the colours
  267. # in the default palette. There is no great correspondence with
  268. # any W3C name-to-RGB mapping.
  269. _colour_map_text = """\
  270. aqua 0x31
  271. black 0x08
  272. blue 0x0C
  273. blue_gray 0x36
  274. bright_green 0x0B
  275. brown 0x3C
  276. coral 0x1D
  277. cyan_ega 0x0F
  278. dark_blue 0x12
  279. dark_blue_ega 0x12
  280. dark_green 0x3A
  281. dark_green_ega 0x11
  282. dark_purple 0x1C
  283. dark_red 0x10
  284. dark_red_ega 0x10
  285. dark_teal 0x38
  286. dark_yellow 0x13
  287. gold 0x33
  288. gray_ega 0x17
  289. gray25 0x16
  290. gray40 0x37
  291. gray50 0x17
  292. gray80 0x3F
  293. green 0x11
  294. ice_blue 0x1F
  295. indigo 0x3E
  296. ivory 0x1A
  297. lavender 0x2E
  298. light_blue 0x30
  299. light_green 0x2A
  300. light_orange 0x34
  301. light_turquoise 0x29
  302. light_yellow 0x2B
  303. lime 0x32
  304. magenta_ega 0x0E
  305. ocean_blue 0x1E
  306. olive_ega 0x13
  307. olive_green 0x3B
  308. orange 0x35
  309. pale_blue 0x2C
  310. periwinkle 0x18
  311. pink 0x0E
  312. plum 0x3D
  313. purple_ega 0x14
  314. red 0x0A
  315. rose 0x2D
  316. sea_green 0x39
  317. silver_ega 0x16
  318. sky_blue 0x28
  319. tan 0x2F
  320. teal 0x15
  321. teal_ega 0x15
  322. turquoise 0x0F
  323. violet 0x14
  324. white 0x09
  325. yellow 0x0D"""
  326. colour_map = {}
  327. for _line in _colour_map_text.splitlines():
  328. _name, _num = _line.split()
  329. _num = int(_num, 0)
  330. colour_map[_name] = _num
  331. if 'gray' in _name:
  332. colour_map[_name.replace('gray', 'grey')] = _num
  333. del _colour_map_text, _line, _name, _num
  334. def add_palette_colour(colour_str, colour_index):
  335. if not (8 <= colour_index <= 63):
  336. raise Exception("add_palette_colour: colour_index (%d) not in range(8, 64)" %
  337. (colour_index))
  338. colour_map[colour_str] = colour_index
  339. # user-defined palette defines 56 RGB colors from entry 8 - 64
  340. #excel_default_palette_b8 = [ # (red, green, blue)
  341. # ( 0, 0, 0), (255,255,255), (255, 0, 0), ( 0,255, 0),
  342. # ( 0, 0,255), (255,255, 0), (255, 0,255), ( 0,255,255),
  343. # (128, 0, 0), ( 0,128, 0), ( 0, 0,128), (128,128, 0),
  344. # (128, 0,128), ( 0,128,128), (192,192,192), (128,128,128),
  345. # (153,153,255), (153, 51,102), (255,255,204), (204,255,255),
  346. # (102, 0,102), (255,128,128), ( 0,102,204), (204,204,255),
  347. # ( 0, 0,128), (255, 0,255), (255,255, 0), ( 0,255,255),
  348. # (128, 0,128), (128, 0, 0), ( 0,128,128), ( 0, 0,255),
  349. # ( 0,204,255), (204,255,255), (204,255,204), (255,255,153),
  350. # (153,204,255), (255,153,204), (204,153,255), (255,204,153),
  351. # ( 51,102,255), ( 51,204,204), (153,204, 0), (255,204, 0),
  352. # (255,153, 0), (255,102, 0), (102,102,153), (150,150,150),
  353. # ( 0, 51,102), ( 51,153,102), ( 0, 51, 0), ( 51, 51, 0),
  354. # (153, 51, 0), (153, 51,102), ( 51, 51,153), ( 51, 51, 51),
  355. # ]
  356. # Default colour table for BIFF8 copied from
  357. # OpenOffice.org's Documentation of the Microsoft Excel File Format, Excel Version 2003
  358. # Note palette has LSB padded with 2 bytes 0x00
  359. excel_default_palette_b8 = (
  360. 0x00000000,
  361. 0xFFFFFF00,
  362. 0xFF000000,
  363. 0x00FF0000,
  364. 0x0000FF00,
  365. 0xFFFF0000,
  366. 0xFF00FF00,
  367. 0x00FFFF00,
  368. 0x80000000,
  369. 0x00800000,
  370. 0x00008000,
  371. 0x80800000,
  372. 0x80008000,
  373. 0x00808000,
  374. 0xC0C0C000,
  375. 0x80808000,
  376. 0x9999FF00,
  377. 0x99336600,
  378. 0xFFFFCC00,
  379. 0xCCFFFF00,
  380. 0x66006600,
  381. 0xFF808000,
  382. 0x0066CC00,
  383. 0xCCCCFF00,
  384. 0x00008000,
  385. 0xFF00FF00,
  386. 0xFFFF0000,
  387. 0x00FFFF00,
  388. 0x80008000,
  389. 0x80000000,
  390. 0x00808000,
  391. 0x0000FF00,
  392. 0x00CCFF00,
  393. 0xCCFFFF00,
  394. 0xCCFFCC00,
  395. 0xFFFF9900,
  396. 0x99CCFF00,
  397. 0xFF99CC00,
  398. 0xCC99FF00,
  399. 0xFFCC9900,
  400. 0x3366FF00,
  401. 0x33CCCC00,
  402. 0x99CC0000,
  403. 0xFFCC0000,
  404. 0xFF990000,
  405. 0xFF660000,
  406. 0x66669900,
  407. 0x96969600,
  408. 0x00336600,
  409. 0x33996600,
  410. 0x00330000,
  411. 0x33330000,
  412. 0x99330000,
  413. 0x99336600,
  414. 0x33339900,
  415. 0x33333300)
  416. assert len(excel_default_palette_b8) == 56
  417. pattern_map = {
  418. # Text values for pattern.pattern
  419. # xlwt/doc/pattern_examples.xls showcases all of these patterns.
  420. 'no_fill': 0,
  421. 'none': 0,
  422. 'solid': 1,
  423. 'solid_fill': 1,
  424. 'solid_pattern': 1,
  425. 'fine_dots': 2,
  426. 'alt_bars': 3,
  427. 'sparse_dots': 4,
  428. 'thick_horz_bands': 5,
  429. 'thick_vert_bands': 6,
  430. 'thick_backward_diag': 7,
  431. 'thick_forward_diag': 8,
  432. 'big_spots': 9,
  433. 'bricks': 10,
  434. 'thin_horz_bands': 11,
  435. 'thin_vert_bands': 12,
  436. 'thin_backward_diag': 13,
  437. 'thin_forward_diag': 14,
  438. 'squares': 15,
  439. 'diamonds': 16,
  440. }
  441. def any_str_func(s):
  442. return s.strip()
  443. def colour_index_func(s, maxval=0x7F):
  444. try:
  445. value = int(s, 0)
  446. except ValueError:
  447. return None
  448. if not (0 <= value <= maxval):
  449. return None
  450. return value
  451. colour_index_func_7 = colour_index_func
  452. def colour_index_func_15(s):
  453. return colour_index_func(s, maxval=0x7FFF)
  454. def rotation_func(s):
  455. try:
  456. value = int(s, 0)
  457. except ValueError:
  458. return None
  459. if not (-90 <= value <= 90):
  460. raise EasyXFCallerError("rotation %d: should be -90 to +90 degrees" % value)
  461. if value < 0:
  462. value = 90 - value # encode as 91 to 180 (clockwise)
  463. return value
  464. xf_dict = {
  465. 'align': 'alignment', # synonym
  466. 'alignment': {
  467. 'dire': {
  468. 'general': 0,
  469. 'lr': 1,
  470. 'rl': 2,
  471. },
  472. 'direction': 'dire',
  473. 'horiz': 'horz',
  474. 'horizontal': 'horz',
  475. 'horz': {
  476. 'general': 0,
  477. 'left': 1,
  478. 'center': 2,
  479. 'centre': 2, # "align: horiz centre" means xf.alignment.horz is set to 2
  480. 'right': 3,
  481. 'filled': 4,
  482. 'justified': 5,
  483. 'center_across_selection': 6,
  484. 'centre_across_selection': 6,
  485. 'distributed': 7,
  486. },
  487. 'inde': IntULim(15), # restriction: 0 <= value <= 15
  488. 'indent': 'inde',
  489. 'rota': [{'stacked': 255, 'none': 0, }, rotation_func],
  490. 'rotation': 'rota',
  491. 'shri': bool_map,
  492. 'shrink': 'shri',
  493. 'shrink_to_fit': 'shri',
  494. 'vert': {
  495. 'top': 0,
  496. 'center': 1,
  497. 'centre': 1,
  498. 'bottom': 2,
  499. 'justified': 3,
  500. 'distributed': 4,
  501. },
  502. 'vertical': 'vert',
  503. 'wrap': bool_map,
  504. },
  505. 'border': 'borders',
  506. 'borders': {
  507. 'left': [border_line_map, IntULim(0x0d)],
  508. 'right': [border_line_map, IntULim(0x0d)],
  509. 'top': [border_line_map, IntULim(0x0d)],
  510. 'bottom': [border_line_map, IntULim(0x0d)],
  511. 'diag': [border_line_map, IntULim(0x0d)],
  512. 'top_colour': [colour_map, colour_index_func_7],
  513. 'bottom_colour': [colour_map, colour_index_func_7],
  514. 'left_colour': [colour_map, colour_index_func_7],
  515. 'right_colour': [colour_map, colour_index_func_7],
  516. 'diag_colour': [colour_map, colour_index_func_7],
  517. 'top_color': 'top_colour',
  518. 'bottom_color': 'bottom_colour',
  519. 'left_color': 'left_colour',
  520. 'right_color': 'right_colour',
  521. 'diag_color': 'diag_colour',
  522. 'need_diag1': bool_map,
  523. 'need_diag2': bool_map,
  524. },
  525. 'font': {
  526. 'bold': bool_map,
  527. 'charset': charset_map,
  528. 'color': 'colour_index',
  529. 'color_index': 'colour_index',
  530. 'colour': 'colour_index',
  531. 'colour_index': [colour_map, colour_index_func_15],
  532. 'escapement': {'none': 0, 'superscript': 1, 'subscript': 2},
  533. 'family': {'none': 0, 'roman': 1, 'swiss': 2, 'modern': 3, 'script': 4, 'decorative': 5, },
  534. 'height': IntULim(0xFFFF), # practical limits are much narrower e.g. 160 to 1440 (8pt to 72pt)
  535. 'italic': bool_map,
  536. 'name': any_str_func,
  537. 'outline': bool_map,
  538. 'shadow': bool_map,
  539. 'struck_out': bool_map,
  540. 'underline': [bool_map, {'none': 0, 'single': 1, 'single_acc': 0x21, 'double': 2, 'double_acc': 0x22, }],
  541. },
  542. 'pattern': {
  543. 'back_color': 'pattern_back_colour',
  544. 'back_colour': 'pattern_back_colour',
  545. 'fore_color': 'pattern_fore_colour',
  546. 'fore_colour': 'pattern_fore_colour',
  547. 'pattern': [pattern_map, IntULim(16)],
  548. 'pattern_back_color': 'pattern_back_colour',
  549. 'pattern_back_colour': [colour_map, colour_index_func_7],
  550. 'pattern_fore_color': 'pattern_fore_colour',
  551. 'pattern_fore_colour': [colour_map, colour_index_func_7],
  552. },
  553. 'protection': {
  554. 'cell_locked' : bool_map,
  555. 'formula_hidden': bool_map,
  556. },
  557. }
  558. def _esplit(s, split_char, esc_char="\\"):
  559. escaped = False
  560. olist = ['']
  561. for c in s:
  562. if escaped:
  563. olist[-1] += c
  564. escaped = False
  565. elif c == esc_char:
  566. escaped = True
  567. elif c == split_char:
  568. olist.append('')
  569. else:
  570. olist[-1] += c
  571. return olist
  572. def _parse_strg_to_obj(strg, obj, parse_dict,
  573. field_sep=",", line_sep=";", intro_sep=":", esc_char="\\", debug=False):
  574. for line in _esplit(strg, line_sep, esc_char):
  575. line = line.strip()
  576. if not line:
  577. break
  578. split_line = _esplit(line, intro_sep, esc_char)
  579. if len(split_line) != 2:
  580. raise EasyXFCallerError('line %r should have exactly 1 "%c"' % (line, intro_sep))
  581. section, item_str = split_line
  582. section = section.strip().lower()
  583. for counter in range(2):
  584. result = parse_dict.get(section)
  585. if result is None:
  586. raise EasyXFCallerError('section %r is unknown' % section)
  587. if isinstance(result, dict):
  588. break
  589. if not isinstance(result, str):
  590. raise EasyXFAuthorError(
  591. 'section %r should map to dict or str object; found %r' % (section, type(result)))
  592. # synonym
  593. old_section = section
  594. section = result
  595. else:
  596. raise EasyXFAuthorError('Attempt to define synonym of synonym (%r: %r)' % (old_section, result))
  597. section_dict = result
  598. section_obj = getattr(obj, section, None)
  599. if section_obj is None:
  600. raise EasyXFAuthorError('instance of %s class has no attribute named %s' % (obj.__class__.__name__, section))
  601. for kv_str in _esplit(item_str, field_sep, esc_char):
  602. guff = kv_str.split()
  603. if not guff:
  604. continue
  605. k = guff[0].lower().replace('-', '_')
  606. v = ' '.join(guff[1:])
  607. if not v:
  608. raise EasyXFCallerError("no value supplied for %s.%s" % (section, k))
  609. for counter in xrange(2):
  610. result = section_dict.get(k)
  611. if result is None:
  612. raise EasyXFCallerError('%s.%s is not a known attribute' % (section, k))
  613. if not isinstance(result, basestring):
  614. break
  615. # synonym
  616. old_k = k
  617. k = result
  618. else:
  619. raise EasyXFAuthorError('Attempt to define synonym of synonym (%r: %r)' % (old_k, result))
  620. value_info = result
  621. if not isinstance(value_info, list):
  622. value_info = [value_info]
  623. for value_rule in value_info:
  624. if isinstance(value_rule, dict):
  625. # dict maps strings to integer field values
  626. vl = v.lower().replace('-', '_')
  627. if vl in value_rule:
  628. value = value_rule[vl]
  629. break
  630. elif callable(value_rule):
  631. value = value_rule(v)
  632. if value is not None:
  633. break
  634. else:
  635. raise EasyXFAuthorError("unknown value rule for attribute %r: %r" % (k, value_rule))
  636. else:
  637. raise EasyXFCallerError("unexpected value %r for %s.%s" % (v, section, k))
  638. try:
  639. orig = getattr(section_obj, k)
  640. except AttributeError:
  641. raise EasyXFAuthorError('%s.%s in dictionary but not in supplied object' % (section, k))
  642. if debug: print("+++ %s.%s = %r # %s; was %r" % (section, k, value, v, orig))
  643. setattr(section_obj, k, value)
  644. def easyxf(strg_to_parse="", num_format_str=None,
  645. field_sep=",", line_sep=";", intro_sep=":", esc_char="\\", debug=False):
  646. """
  647. This function is used to create and configure
  648. :class:`XFStyle` objects for use with (for example) the
  649. :meth:`Worksheet.write` method.
  650. It takes a string to be parsed to obtain attribute values for
  651. :class:`Alignment`, :class:`Borders`, :class:`Font`, :class:`Pattern` and
  652. :class:`Protection` objects.
  653. Refer to the examples in the file `examples/xlwt_easyxf_simple_demo.py`
  654. and to the `xf_dict` dictionary in :mod:`xlwt.Style`.
  655. Various synonyms including color/colour, center/centre and gray/grey are
  656. allowed. Case is irrelevant (except maybe in font names). ``-`` may be used
  657. instead of ``_``.
  658. Example: ``font: bold on; align: wrap on, vert centre, horiz center``
  659. :param num_format_str:
  660. To get the "number format string" of an existing
  661. cell whose format you want to reproduce, select the cell and click on
  662. Format/Cells/Number/Custom. Otherwise, refer to Excel help.
  663. Examples: ``"#,##0.00"``, ``"dd/mm/yyyy"``
  664. :return: An :class:`XFstyle` object.
  665. """
  666. xfobj = XFStyle()
  667. if num_format_str is not None:
  668. xfobj.num_format_str = num_format_str
  669. if strg_to_parse:
  670. _parse_strg_to_obj(strg_to_parse, xfobj, xf_dict,
  671. field_sep=field_sep, line_sep=line_sep, intro_sep=intro_sep, esc_char=esc_char, debug=debug)
  672. return xfobj
  673. def easyfont(strg_to_parse="", field_sep=",", esc_char="\\", debug=False):
  674. xfobj = XFStyle()
  675. if strg_to_parse:
  676. _parse_strg_to_obj("font: " + strg_to_parse, xfobj, xf_dict,
  677. field_sep=field_sep, line_sep=";", intro_sep=":", esc_char=esc_char, debug=debug)
  678. return xfobj.font