print_settings.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # Copyright (c) 2010-2024 openpyxl
  2. import re
  3. from openpyxl.descriptors import (
  4. Strict,
  5. Integer,
  6. String,
  7. Typed,
  8. )
  9. from openpyxl.utils import quote_sheetname, absolute_coordinate
  10. from openpyxl.utils.cell import SHEET_TITLE, SHEETRANGE_RE, RANGE_EXPR
  11. from .cell_range import MultiCellRange
  12. COL_RANGE = r"""(?P<cols>[$]?(?P<min_col>[a-zA-Z]{1,3}):[$]?(?P<max_col>[a-zA-Z]{1,3}))"""
  13. COL_RANGE_RE = re.compile(COL_RANGE)
  14. ROW_RANGE = r"""(?P<rows>[$]?(?P<min_row>\d+):[$]?(?P<max_row>\d+))"""
  15. ROW_RANGE_RE = re.compile(ROW_RANGE)
  16. TITLES_REGEX = re.compile("""{0}{1}?,?{2}?,?""".format(SHEET_TITLE, ROW_RANGE, COL_RANGE),
  17. re.VERBOSE)
  18. PRINT_AREA_RE = re.compile(f"({SHEET_TITLE})?(?P<cells>{RANGE_EXPR})", re.VERBOSE)
  19. class ColRange(Strict):
  20. """
  21. Represent a range of at least one column
  22. """
  23. min_col = String()
  24. max_col = String()
  25. def __init__(self, range_string=None, min_col=None, max_col=None):
  26. if range_string is not None:
  27. match = COL_RANGE_RE.match(range_string)
  28. if not match:
  29. raise ValueError(f"{range_string} is not a valid column range")
  30. min_col, max_col = match.groups()[1:]
  31. self.min_col = min_col
  32. self.max_col = max_col
  33. def __eq__(self, other):
  34. if isinstance(other, self.__class__):
  35. return (self.min_col == other.min_col
  36. and
  37. self.max_col == other.max_col)
  38. elif isinstance(other, str):
  39. return (str(self) == other
  40. or
  41. f"{self.min_col}:{self.max_col}")
  42. return False
  43. def __repr__(self):
  44. return f"Range of columns from '{self.min_col}' to '{self.max_col}'"
  45. def __str__(self):
  46. return f"${self.min_col}:${self.max_col}"
  47. class RowRange(Strict):
  48. """
  49. Represent a range of at least one row
  50. """
  51. min_row = Integer()
  52. max_row = Integer()
  53. def __init__(self, range_string=None, min_row=None, max_row=None):
  54. if range_string is not None:
  55. match = ROW_RANGE_RE.match(range_string)
  56. if not match:
  57. raise ValueError(f"{range_string} is not a valid row range")
  58. min_row, max_row = match.groups()[1:]
  59. self.min_row = min_row
  60. self.max_row = max_row
  61. def __eq__(self, other):
  62. if isinstance(other, self.__class__):
  63. return (self.min_row == other.min_row
  64. and
  65. self.max_row == other.max_row)
  66. elif isinstance(other, str):
  67. return (str(self) == other
  68. or
  69. f"{self.min_row}:{self.max_row}")
  70. return False
  71. def __repr__(self):
  72. return f"Range of rows from '{self.min_row}' to '{self.max_row}'"
  73. def __str__(self):
  74. return f"${self.min_row}:${self.max_row}"
  75. class PrintTitles(Strict):
  76. """
  77. Contains at least either a range of rows or columns
  78. """
  79. cols = Typed(expected_type=ColRange, allow_none=True)
  80. rows = Typed(expected_type=RowRange, allow_none=True)
  81. title = String()
  82. def __init__(self, cols=None, rows=None, title=""):
  83. self.cols = cols
  84. self.rows = rows
  85. self.title = title
  86. @classmethod
  87. def from_string(cls, value):
  88. kw = dict((k, v) for match in TITLES_REGEX.finditer(value)
  89. for k, v in match.groupdict().items() if v)
  90. if not kw:
  91. raise ValueError(f"{value} is not a valid print titles definition")
  92. cols = rows = None
  93. if "cols" in kw:
  94. cols = ColRange(kw["cols"])
  95. if "rows" in kw:
  96. rows = RowRange(kw["rows"])
  97. title = kw.get("quoted") or kw.get("notquoted")
  98. return cls(cols=cols, rows=rows, title=title)
  99. def __eq__(self, other):
  100. if isinstance(other, self.__class__):
  101. return (self.cols == other.cols
  102. and
  103. self.rows == other.rows
  104. and
  105. self.title == other.title)
  106. elif isinstance(other, str):
  107. return str(self) == other
  108. return False
  109. def __repr__(self):
  110. return f"Print titles for sheet {self.title} cols {self.rows}, rows {self.cols}"
  111. def __str__(self):
  112. title = quote_sheetname(self.title)
  113. titles = ",".join([f"{title}!{value}" for value in (self.rows, self.cols) if value])
  114. return titles or ""
  115. class PrintArea(MultiCellRange):
  116. @classmethod
  117. def from_string(cls, value):
  118. new = []
  119. for m in PRINT_AREA_RE.finditer(value): # can be multiple
  120. coord = m.group("cells")
  121. if coord:
  122. new.append(coord)
  123. return cls(new)
  124. def __init__(self, ranges=(), title=""):
  125. self.title = ""
  126. super().__init__(ranges)
  127. def __str__(self):
  128. if self.ranges:
  129. return ",".join([f"{quote_sheetname(self.title)}!{absolute_coordinate(str(range))}"
  130. for range in self.sorted()])
  131. return ""
  132. def __eq__(self, other):
  133. super().__eq__(other)
  134. if isinstance(other, str):
  135. return str(self) == other