sequencer.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #Copyright ReportLab Europe Ltd. 2000-2017
  2. #see license.txt for license details
  3. __version__='3.3.0'
  4. __doc__="""A Sequencer class counts things. It aids numbering and formatting lists."""
  5. __all__='''Sequencer getSequencer setSequencer'''.split()
  6. #
  7. # roman numbers conversion thanks to
  8. #
  9. # fredrik lundh, november 1996 (based on a C hack from 1984)
  10. #
  11. # fredrik@pythonware.com
  12. # http://www.pythonware.com
  13. _RN_TEMPLATES = [ 0, 0o1, 0o11, 0o111, 0o12, 0o2, 0o21, 0o211, 0o2111, 0o13 ]
  14. _RN_LETTERS = "IVXLCDM"
  15. def _format_I(value):
  16. if value < 0 or value > 3999:
  17. raise ValueError("illegal value")
  18. str = ""
  19. base = -1
  20. while value:
  21. value, index = divmod(value, 10)
  22. tmp = _RN_TEMPLATES[index]
  23. while tmp:
  24. tmp, index = divmod(tmp, 8)
  25. str = _RN_LETTERS[index+base] + str
  26. base += 2
  27. return str
  28. def _format_i(num):
  29. return _format_I(num).lower()
  30. def _format_123(num):
  31. """The simplest formatter"""
  32. return str(num)
  33. def _format_ABC(num):
  34. """Uppercase. Wraps around at 26."""
  35. n = (num -1) % 26
  36. return chr(n+65)
  37. def _format_abc(num):
  38. """Lowercase. Wraps around at 26."""
  39. n = (num -1) % 26
  40. return chr(n+97)
  41. _type2formatter = {
  42. 'I':_format_I,
  43. 'i':_format_i,
  44. '1':_format_123,
  45. 'A':_format_ABC,
  46. 'a':_format_abc,
  47. }
  48. class _Counter:
  49. """Private class used by Sequencer. Each counter
  50. knows its format, and the IDs of anything it
  51. resets, as well as its value. Starts at zero
  52. and increments just before you get the new value,
  53. so that it is still 'Chapter 5' and not 'Chapter 6'
  54. when you print 'Figure 5.1'"""
  55. def __init__(self):
  56. self._base = 0
  57. self._value = self._base
  58. self._formatter = _format_123
  59. self._resets = []
  60. def setFormatter(self, formatFunc):
  61. self._formatter = formatFunc
  62. def reset(self, value=None):
  63. if value:
  64. self._value = value
  65. else:
  66. self._value = self._base
  67. def next(self):
  68. self._value += 1
  69. v = self._value
  70. for counter in self._resets:
  71. counter.reset()
  72. return v
  73. __next__ = next
  74. def _this(self):
  75. return self._value
  76. def nextf(self):
  77. """Returns next value formatted"""
  78. return self._formatter(next(self))
  79. def thisf(self):
  80. return self._formatter(self._this())
  81. def chain(self, otherCounter):
  82. if not otherCounter in self._resets:
  83. self._resets.append(otherCounter)
  84. class Sequencer:
  85. """Something to make it easy to number paragraphs, sections,
  86. images and anything else. The features include registering
  87. new string formats for sequences, and 'chains' whereby
  88. some counters are reset when their parents.
  89. It keeps track of a number of
  90. 'counters', which are created on request:
  91. Usage::
  92. >>> seq = layout.Sequencer()
  93. >>> seq.next('Bullets')
  94. 1
  95. >>> seq.next('Bullets')
  96. 2
  97. >>> seq.next('Bullets')
  98. 3
  99. >>> seq.reset('Bullets')
  100. >>> seq.next('Bullets')
  101. 1
  102. >>> seq.next('Figures')
  103. 1
  104. >>>
  105. """
  106. def __init__(self):
  107. self._counters = {} #map key to current number
  108. self._formatters = {}
  109. self._reset()
  110. def _reset(self):
  111. self._counters.clear()
  112. self._formatters.clear()
  113. self._formatters.update({
  114. # the formats it knows initially
  115. '1':_format_123,
  116. 'A':_format_ABC,
  117. 'a':_format_abc,
  118. 'I':_format_I,
  119. 'i':_format_i,
  120. })
  121. d = dict(_counters=self._counters,_formatters=self._formatters)
  122. self.__dict__.clear()
  123. self.__dict__.update(d)
  124. self._defaultCounter = None
  125. def _getCounter(self, counter=None):
  126. """Creates one if not present"""
  127. try:
  128. return self._counters[counter]
  129. except KeyError:
  130. cnt = _Counter()
  131. self._counters[counter] = cnt
  132. return cnt
  133. def _this(self, counter=None):
  134. """Retrieves counter value but does not increment. For
  135. new counters, sets base value to 1."""
  136. if not counter:
  137. counter = self._defaultCounter
  138. return self._getCounter(counter)._this()
  139. def __next__(self):
  140. """Retrieves the numeric value for the given counter, then
  141. increments it by one. New counters start at one."""
  142. return next(self._getCounter(self._defaultCounter))
  143. def next(self,counter=None):
  144. if not counter:
  145. return next(self)
  146. else:
  147. dc = self._defaultCounter
  148. try:
  149. self._defaultCounter = counter
  150. return next(self)
  151. finally:
  152. self._defaultCounter = dc
  153. def thisf(self, counter=None):
  154. if not counter:
  155. counter = self._defaultCounter
  156. return self._getCounter(counter).thisf()
  157. def nextf(self, counter=None):
  158. """Retrieves the numeric value for the given counter, then
  159. increments it by one. New counters start at one."""
  160. if not counter:
  161. counter = self._defaultCounter
  162. return self._getCounter(counter).nextf()
  163. def setDefaultCounter(self, default=None):
  164. """Changes the key used for the default"""
  165. self._defaultCounter = default
  166. def registerFormat(self, format, func):
  167. """Registers a new formatting function. The funtion
  168. must take a number as argument and return a string;
  169. fmt is a short menmonic string used to access it."""
  170. self._formatters[format] = func
  171. def setFormat(self, counter, format):
  172. """Specifies that the given counter should use
  173. the given format henceforth."""
  174. func = self._formatters[format]
  175. self._getCounter(counter).setFormatter(func)
  176. def reset(self, counter=None, base=0):
  177. if not counter:
  178. counter = self._defaultCounter
  179. self._getCounter(counter)._value = base
  180. def chain(self, parent, child):
  181. p = self._getCounter(parent)
  182. c = self._getCounter(child)
  183. p.chain(c)
  184. def __getitem__(self, key):
  185. """Allows compact notation to support the format function.
  186. s['key'] gets current value, s['key+'] increments."""
  187. if key[-1:] == '+':
  188. counter = key[:-1]
  189. return self.nextf(counter)
  190. else:
  191. return self.thisf(key)
  192. def format(self, template):
  193. """The crowning jewels - formats multi-level lists."""
  194. return template % self
  195. def dump(self):
  196. """Write current state to stdout for diagnostics"""
  197. counters = list(self._counters.items())
  198. counters.sort()
  199. print('Sequencer dump:')
  200. for (key, counter) in counters:
  201. print(' %s: value = %d, base = %d, format example = %s' % (
  202. key, counter._this(), counter._base, counter.thisf()))
  203. """Your story builder needs to set this to"""
  204. _sequencer = None
  205. def getSequencer():
  206. global _sequencer
  207. if _sequencer is None:
  208. _sequencer = Sequencer()
  209. return _sequencer
  210. def setSequencer(seq):
  211. global _sequencer
  212. s = _sequencer
  213. _sequencer = seq
  214. return s
  215. def _reset():
  216. global _sequencer
  217. if _sequencer:
  218. _sequencer._reset()
  219. from reportlab.rl_config import register_reset
  220. register_reset(_reset)
  221. del register_reset
  222. def test():
  223. s = Sequencer()
  224. print('Counting using default sequence: %d %d %d' % (next(s),next(s), next(s)))
  225. print('Counting Figures: Figure %d, Figure %d, Figure %d' % (
  226. s.next('figure'), s.next('figure'), s.next('figure')))
  227. print('Back to default again: %d' % next(s))
  228. s.setDefaultCounter('list1')
  229. print('Set default to list1: %d %d %d' % (next(s),next(s), next(s)))
  230. s.setDefaultCounter()
  231. print('Set default to None again: %d %d %d' % (next(s),next(s), next(s)))
  232. print()
  233. print('Creating Appendix counter with format A, B, C...')
  234. s.setFormat('Appendix', 'A')
  235. print(' Appendix %s, Appendix %s, Appendix %s' % (
  236. s.nextf('Appendix'), s.nextf('Appendix'),s.nextf('Appendix')))
  237. def format_french(num):
  238. return ('un','deux','trois','quatre','cinq')[(num-1)%5]
  239. print()
  240. print('Defining a custom format with french words:')
  241. s.registerFormat('french', format_french)
  242. s.setFormat('FrenchList', 'french')
  243. print(' ' +(' '.join(str(s.nextf('FrenchList')) for i in range(1,6))))
  244. print()
  245. print('Chaining H1 and H2 - H2 goes back to one when H1 increases')
  246. s.chain('H1','H2')
  247. print(' H1 = %d' % s.next('H1'))
  248. print(' H2 = %d' % s.next('H2'))
  249. print(' H2 = %d' % s.next('H2'))
  250. print(' H2 = %d' % s.next('H2'))
  251. print(' H1 = %d' % s.next('H1'))
  252. print(' H2 = %d' % s.next('H2'))
  253. print(' H2 = %d' % s.next('H2'))
  254. print(' H2 = %d' % s.next('H2'))
  255. print()
  256. print('GetItem notation - append a plus to increment')
  257. print(' seq["Appendix"] = %s' % s["Appendix"])
  258. print(' seq["Appendix+"] = %s' % s["Appendix+"])
  259. print(' seq["Appendix+"] = %s' % s["Appendix+"])
  260. print(' seq["Appendix"] = %s' % s["Appendix"])
  261. print()
  262. print('Finally, string format notation for nested lists. Cool!')
  263. print('The expression ("Figure %(Chapter)s.%(Figure+)s" % seq) gives:')
  264. print(' Figure %(Chapter)s.%(Figure+)s' % s)
  265. print(' Figure %(Chapter)s.%(Figure+)s' % s)
  266. print(' Figure %(Chapter)s.%(Figure+)s' % s)
  267. if __name__=='__main__':
  268. test()