about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py270
1 files changed, 270 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py
new file mode 100644
index 00000000..598aa23d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/header_footer.py
@@ -0,0 +1,270 @@
+# Copyright (c) 2010-2024 openpyxl
+
+# Simplified implementation of headers and footers: let worksheets have separate items
+
+import re
+from warnings import warn
+
+from openpyxl.descriptors import (
+    Alias,
+    Bool,
+    Strict,
+    String,
+    Integer,
+    MatchPattern,
+    Typed,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+
+
+from openpyxl.xml.functions import Element
+from openpyxl.utils.escape import escape, unescape
+
+
+FONT_PATTERN = '&"(?P<font>.+)"'
+COLOR_PATTERN  = "&K(?P<color>[A-F0-9]{6})"
+SIZE_REGEX = r"&(?P<size>\d+\s?)"
+FORMAT_REGEX = re.compile("{0}|{1}|{2}".format(FONT_PATTERN, COLOR_PATTERN,
+                                               SIZE_REGEX)
+                          )
+
+def _split_string(text):
+    """
+    Split the combined (decoded) string into left, center and right parts
+
+    # See http://stackoverflow.com/questions/27711175/regex-with-multiple-optional-groups for discussion
+    """
+
+    ITEM_REGEX = re.compile("""
+    (&L(?P<left>.+?))?
+    (&C(?P<center>.+?))?
+    (&R(?P<right>.+?))?
+    $""", re.VERBOSE | re.DOTALL)
+
+    m = ITEM_REGEX.match(text)
+    try:
+        parts = m.groupdict()
+    except AttributeError:
+        warn("""Cannot parse header or footer so it will be ignored""")
+        parts = {'left':'', 'right':'', 'center':''}
+    return parts
+
+
+class _HeaderFooterPart(Strict):
+
+    """
+    Individual left/center/right header/footer part
+
+    Do not use directly.
+
+    Header & Footer ampersand codes:
+
+    * &A   Inserts the worksheet name
+    * &B   Toggles bold
+    * &D or &[Date]   Inserts the current date
+    * &E   Toggles double-underline
+    * &F or &[File]   Inserts the workbook name
+    * &I   Toggles italic
+    * &N or &[Pages]   Inserts the total page count
+    * &S   Toggles strikethrough
+    * &T   Inserts the current time
+    * &[Tab]   Inserts the worksheet name
+    * &U   Toggles underline
+    * &X   Toggles superscript
+    * &Y   Toggles subscript
+    * &P or &[Page]   Inserts the current page number
+    * &P+n   Inserts the page number incremented by n
+    * &P-n   Inserts the page number decremented by n
+    * &[Path]   Inserts the workbook path
+    * &&   Escapes the ampersand character
+    * &"fontname"   Selects the named font
+    * &nn   Selects the specified 2-digit font point size
+
+    Colours are in RGB Hex
+    """
+
+    text = String(allow_none=True)
+    font = String(allow_none=True)
+    size = Integer(allow_none=True)
+    RGB = ("^[A-Fa-f0-9]{6}$")
+    color = MatchPattern(allow_none=True, pattern=RGB)
+
+
+    def __init__(self, text=None, font=None, size=None, color=None):
+        self.text = text
+        self.font = font
+        self.size = size
+        self.color = color
+
+
+    def __str__(self):
+        """
+        Convert to Excel HeaderFooter miniformat minus position
+        """
+        fmt = []
+        if self.font:
+            fmt.append(u'&"{0}"'.format(self.font))
+        if self.size:
+            fmt.append("&{0} ".format(self.size))
+        if self.color:
+            fmt.append("&K{0}".format(self.color))
+        return u"".join(fmt + [self.text])
+
+    def __bool__(self):
+        return bool(self.text)
+
+
+
+    @classmethod
+    def from_str(cls, text):
+        """
+        Convert from miniformat to object
+        """
+        keys = ('font', 'color', 'size')
+        kw = dict((k, v) for match in FORMAT_REGEX.findall(text)
+                  for k, v in zip(keys, match) if v)
+
+        kw['text'] = FORMAT_REGEX.sub('', text)
+
+        return cls(**kw)
+
+
+class HeaderFooterItem(Strict):
+    """
+    Header or footer item
+
+    """
+
+    left = Typed(expected_type=_HeaderFooterPart)
+    center = Typed(expected_type=_HeaderFooterPart)
+    centre = Alias("center")
+    right = Typed(expected_type=_HeaderFooterPart)
+
+    __keys = ('L', 'C', 'R')
+
+
+    def __init__(self, left=None, right=None, center=None):
+        if left is None:
+            left = _HeaderFooterPart()
+        self.left = left
+        if center is None:
+            center = _HeaderFooterPart()
+        self.center = center
+        if right is None:
+            right = _HeaderFooterPart()
+        self.right = right
+
+
+    def __str__(self):
+        """
+        Pack parts into a single string
+        """
+        TRANSFORM = {'&[Tab]': '&A', '&[Pages]': '&N', '&[Date]': '&D',
+                     '&[Path]': '&Z', '&[Page]': '&P', '&[Time]': '&T', '&[File]': '&F',
+                     '&[Picture]': '&G'}
+
+        # escape keys and create regex
+        SUBS_REGEX = re.compile("|".join(["({0})".format(re.escape(k))
+                                          for k in TRANSFORM]))
+
+        def replace(match):
+            """
+            Callback for re.sub
+            Replace expanded control with mini-format equivalent
+            """
+            sub = match.group(0)
+            return TRANSFORM[sub]
+
+        txt = []
+        for key, part in zip(
+            self.__keys, [self.left, self.center, self.right]):
+            if part.text is not None:
+                txt.append(u"&{0}{1}".format(key, str(part)))
+        txt = "".join(txt)
+        txt = SUBS_REGEX.sub(replace, txt)
+        return escape(txt)
+
+
+    def __bool__(self):
+        return any([self.left, self.center, self.right])
+
+
+
+    def to_tree(self, tagname):
+        """
+        Return as XML node
+        """
+        el = Element(tagname)
+        el.text = str(self)
+        return el
+
+
+    @classmethod
+    def from_tree(cls, node):
+        if node.text:
+            text = unescape(node.text)
+            parts = _split_string(text)
+            for k, v in parts.items():
+                if v is not None:
+                    parts[k] = _HeaderFooterPart.from_str(v)
+            self = cls(**parts)
+            return self
+
+
+class HeaderFooter(Serialisable):
+
+    tagname = "headerFooter"
+
+    differentOddEven = Bool(allow_none=True)
+    differentFirst = Bool(allow_none=True)
+    scaleWithDoc = Bool(allow_none=True)
+    alignWithMargins = Bool(allow_none=True)
+    oddHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
+    oddFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
+    evenHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
+    evenFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
+    firstHeader = Typed(expected_type=HeaderFooterItem, allow_none=True)
+    firstFooter = Typed(expected_type=HeaderFooterItem, allow_none=True)
+
+    __elements__ = ("oddHeader", "oddFooter", "evenHeader", "evenFooter", "firstHeader", "firstFooter")
+
+    def __init__(self,
+                 differentOddEven=None,
+                 differentFirst=None,
+                 scaleWithDoc=None,
+                 alignWithMargins=None,
+                 oddHeader=None,
+                 oddFooter=None,
+                 evenHeader=None,
+                 evenFooter=None,
+                 firstHeader=None,
+                 firstFooter=None,
+                ):
+        self.differentOddEven = differentOddEven
+        self.differentFirst = differentFirst
+        self.scaleWithDoc = scaleWithDoc
+        self.alignWithMargins = alignWithMargins
+        if oddHeader is None:
+            oddHeader = HeaderFooterItem()
+        self.oddHeader = oddHeader
+        if oddFooter is None:
+            oddFooter = HeaderFooterItem()
+        self.oddFooter = oddFooter
+        if evenHeader is None:
+            evenHeader = HeaderFooterItem()
+        self.evenHeader = evenHeader
+        if evenFooter is None:
+            evenFooter = HeaderFooterItem()
+        self.evenFooter = evenFooter
+        if firstHeader is None:
+            firstHeader = HeaderFooterItem()
+        self.firstHeader = firstHeader
+        if firstFooter is None:
+            firstFooter = HeaderFooterItem()
+        self.firstFooter = firstFooter
+
+
+    def __bool__(self):
+        parts = [getattr(self, attr) for attr in self.__attrs__ + self.__elements__]
+        return any(parts)
+