rule.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. from openpyxl.compat import basestring, unicode
  4. from openpyxl.descriptors.serialisable import Serialisable
  5. from openpyxl.descriptors import (
  6. Typed,
  7. String,
  8. Sequence,
  9. Bool,
  10. NoneSet,
  11. Set,
  12. Integer,
  13. Float,
  14. )
  15. from openpyxl.descriptors.excel import HexBinary, ExtensionList
  16. from openpyxl.styles.colors import Color, ColorDescriptor
  17. from openpyxl.styles.differential import DifferentialStyle
  18. from openpyxl.utils.cell import COORD_RE
  19. class ValueDescriptor(Float):
  20. """
  21. Expected type depends upon type attribue of parent :-(
  22. Most values should be numeric BUT they can also be cell references
  23. """
  24. def __set__(self, instance, value):
  25. ref = None
  26. if value is not None and isinstance(value, basestring):
  27. ref = COORD_RE.match(value)
  28. if instance.type == "formula" or ref:
  29. self.expected_type = basestring
  30. else:
  31. self.expected_type = float
  32. super(ValueDescriptor, self).__set__(instance, value)
  33. class FormatObject(Serialisable):
  34. tagname = "cfvo"
  35. type = Set(values=(['num', 'percent', 'max', 'min', 'formula', 'percentile']))
  36. val = ValueDescriptor(allow_none=True)
  37. gte = Bool(allow_none=True)
  38. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  39. __elements__ = ()
  40. def __init__(self,
  41. type,
  42. val=None,
  43. gte=None,
  44. extLst=None,
  45. ):
  46. self.type = type
  47. self.val = val
  48. self.gte = gte
  49. class RuleType(Serialisable):
  50. cfvo = Sequence(expected_type=FormatObject)
  51. class IconSet(RuleType):
  52. tagname = "iconSet"
  53. iconSet = NoneSet(values=(['3Arrows', '3ArrowsGray', '3Flags',
  54. '3TrafficLights1', '3TrafficLights2', '3Signs', '3Symbols', '3Symbols2',
  55. '4Arrows', '4ArrowsGray', '4RedToBlack', '4Rating', '4TrafficLights',
  56. '5Arrows', '5ArrowsGray', '5Rating', '5Quarters']))
  57. showValue = Bool(allow_none=True)
  58. percent = Bool(allow_none=True)
  59. reverse = Bool(allow_none=True)
  60. __elements__ = ("cfvo",)
  61. def __init__(self,
  62. iconSet=None,
  63. showValue=None,
  64. percent=None,
  65. reverse=None,
  66. cfvo=None,
  67. ):
  68. self.iconSet = iconSet
  69. self.showValue = showValue
  70. self.percent = percent
  71. self.reverse = reverse
  72. self.cfvo = cfvo
  73. class DataBar(RuleType):
  74. tagname = "dataBar"
  75. minLength = Integer(allow_none=True)
  76. maxLength = Integer(allow_none=True)
  77. showValue = Bool(allow_none=True)
  78. color = ColorDescriptor()
  79. __elements__ = ('cfvo', 'color')
  80. def __init__(self,
  81. minLength=None,
  82. maxLength=None,
  83. showValue=None,
  84. cfvo=None,
  85. color=None,
  86. ):
  87. self.minLength = minLength
  88. self.maxLength = maxLength
  89. self.showValue = showValue
  90. self.cfvo = cfvo
  91. self.color = color
  92. class ColorScale(RuleType):
  93. tagname = "colorScale"
  94. color = Sequence(expected_type=Color)
  95. __elements__ = ('cfvo', 'color')
  96. def __init__(self,
  97. cfvo=None,
  98. color=None,
  99. ):
  100. self.cfvo = cfvo
  101. self.color = color
  102. class Rule(Serialisable):
  103. tagname = "cfRule"
  104. type = Set(values=(['expression', 'cellIs', 'colorScale', 'dataBar',
  105. 'iconSet', 'top10', 'uniqueValues', 'duplicateValues', 'containsText',
  106. 'notContainsText', 'beginsWith', 'endsWith', 'containsBlanks',
  107. 'notContainsBlanks', 'containsErrors', 'notContainsErrors', 'timePeriod',
  108. 'aboveAverage']))
  109. dxfId = Integer(allow_none=True)
  110. priority = Integer()
  111. stopIfTrue = Bool(allow_none=True)
  112. aboveAverage = Bool(allow_none=True)
  113. percent = Bool(allow_none=True)
  114. bottom = Bool(allow_none=True)
  115. operator = NoneSet(values=(['lessThan', 'lessThanOrEqual', 'equal',
  116. 'notEqual', 'greaterThanOrEqual', 'greaterThan', 'between', 'notBetween',
  117. 'containsText', 'notContains', 'beginsWith', 'endsWith']))
  118. text = String(allow_none=True)
  119. timePeriod = NoneSet(values=(['today', 'yesterday', 'tomorrow', 'last7Days',
  120. 'thisMonth', 'lastMonth', 'nextMonth', 'thisWeek', 'lastWeek',
  121. 'nextWeek']))
  122. rank = Integer(allow_none=True)
  123. stdDev = Integer(allow_none=True)
  124. equalAverage = Bool(allow_none=True)
  125. formula = Sequence(expected_type=unicode)
  126. colorScale = Typed(expected_type=ColorScale, allow_none=True)
  127. dataBar = Typed(expected_type=DataBar, allow_none=True)
  128. iconSet = Typed(expected_type=IconSet, allow_none=True)
  129. extLst = Typed(expected_type=ExtensionList, allow_none=True)
  130. dxf = Typed(expected_type=DifferentialStyle, allow_none=True)
  131. __elements__ = ('colorScale', 'dataBar', 'iconSet', 'formula')
  132. __attrs__ = ('type', 'rank', 'priority', 'equalAverage', 'operator',
  133. 'aboveAverage', 'dxfId', 'stdDev', 'stopIfTrue', 'timePeriod', 'text',
  134. 'percent', 'bottom')
  135. def __init__(self,
  136. type,
  137. dxfId=None,
  138. priority=0,
  139. stopIfTrue=None,
  140. aboveAverage=None,
  141. percent=None,
  142. bottom=None,
  143. operator=None,
  144. text=None,
  145. timePeriod=None,
  146. rank=None,
  147. stdDev=None,
  148. equalAverage=None,
  149. formula=(),
  150. colorScale=None,
  151. dataBar=None,
  152. iconSet=None,
  153. extLst=None,
  154. dxf=None,
  155. ):
  156. self.type = type
  157. self.dxfId = dxfId
  158. self.priority = priority
  159. self.stopIfTrue = stopIfTrue
  160. self.aboveAverage = aboveAverage
  161. self.percent = percent
  162. self.bottom = bottom
  163. self.operator = operator
  164. self.text = text
  165. self.timePeriod = timePeriod
  166. self.rank = rank
  167. self.stdDev = stdDev
  168. self.equalAverage = equalAverage
  169. self.formula = formula
  170. self.colorScale = colorScale
  171. self.dataBar = dataBar
  172. self.iconSet = iconSet
  173. self.dxf = dxf
  174. def ColorScaleRule(start_type=None,
  175. start_value=None,
  176. start_color=None,
  177. mid_type=None,
  178. mid_value=None,
  179. mid_color=None,
  180. end_type=None,
  181. end_value=None,
  182. end_color=None):
  183. """Backwards compatibility"""
  184. formats = []
  185. if start_type is not None:
  186. formats.append(FormatObject(type=start_type, val=start_value))
  187. if mid_type is not None:
  188. formats.append(FormatObject(type=mid_type, val=mid_value))
  189. if end_type is not None:
  190. formats.append(FormatObject(type=end_type, val=end_value))
  191. colors = []
  192. for v in (start_color, mid_color, end_color):
  193. if v is not None:
  194. if not isinstance(v, Color):
  195. v = Color(v)
  196. colors.append(v)
  197. cs = ColorScale(cfvo=formats, color=colors)
  198. rule = Rule(type="colorScale", colorScale=cs)
  199. return rule
  200. def FormulaRule(formula=None, stopIfTrue=None, font=None, border=None,
  201. fill=None):
  202. """
  203. Conditional formatting with custom differential style
  204. """
  205. rule = Rule(type="expression", formula=formula, stopIfTrue=stopIfTrue)
  206. rule.dxf = DifferentialStyle(font=font, border=border, fill=fill)
  207. return rule
  208. def CellIsRule(operator=None, formula=None, stopIfTrue=None, font=None, border=None, fill=None):
  209. """
  210. Conditional formatting rule based on cell contents.
  211. """
  212. # Excel doesn't use >, >=, etc, but allow for ease of python development
  213. expand = {">": "greaterThan", ">=": "greaterThanOrEqual", "<": "lessThan", "<=": "lessThanOrEqual",
  214. "=": "equal", "==": "equal", "!=": "notEqual"}
  215. operator = expand.get(operator, operator)
  216. rule = Rule(type='cellIs', operator=operator, formula=formula, stopIfTrue=stopIfTrue)
  217. rule.dxf = DifferentialStyle(font=font, border=border, fill=fill)
  218. return rule
  219. def IconSetRule(icon_style=None, type=None, values=None, showValue=None, percent=None, reverse=None):
  220. """
  221. Convenience function for creating icon set rules
  222. """
  223. cfvo = []
  224. for val in values:
  225. cfvo.append(FormatObject(type, val))
  226. icon_set = IconSet(iconSet=icon_style, cfvo=cfvo, showValue=showValue,
  227. percent=percent, reverse=reverse)
  228. rule = Rule(type='iconSet', iconSet=icon_set)
  229. return rule
  230. def DataBarRule(start_type=None, start_value=None, end_type=None,
  231. end_value=None, color=None, showValue=None, minLength=None, maxLength=None):
  232. start = FormatObject(start_type, start_value)
  233. end = FormatObject(end_type, end_value)
  234. data_bar = DataBar(cfvo=[start, end], color=color, showValue=showValue,
  235. minLength=minLength, maxLength=maxLength)
  236. rule = Rule(type='dataBar', dataBar=data_bar)
  237. return rule