about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/comments
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/comments')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py4
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/comments/author.py21
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py211
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py62
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py112
5 files changed, 410 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py
new file mode 100644
index 00000000..288bdf1d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from .comments import Comment
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py
new file mode 100644
index 00000000..9155fa5a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/author.py
@@ -0,0 +1,21 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Sequence,
+    Alias
+)
+
+
+class AuthorList(Serialisable):
+
+    tagname = "authors"
+
+    author = Sequence(expected_type=str)
+    authors = Alias("author")
+
+    def __init__(self,
+                 author=(),
+                ):
+        self.author = author
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py
new file mode 100644
index 00000000..67dccc55
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/comment_sheet.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2010-2024 openpyxl
+
+## Incomplete!
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Integer,
+    Set,
+    String,
+    Bool,
+)
+from openpyxl.descriptors.excel import Guid, ExtensionList
+from openpyxl.descriptors.sequence import NestedSequence
+
+from openpyxl.utils.indexed_list import IndexedList
+from openpyxl.xml.constants import SHEET_MAIN_NS
+
+from openpyxl.cell.text import Text
+from .author import AuthorList
+from .comments import Comment
+from .shape_writer import ShapeWriter
+
+
+class Properties(Serialisable):
+
+    locked = Bool(allow_none=True)
+    defaultSize = Bool(allow_none=True)
+    _print = Bool(allow_none=True)
+    disabled = Bool(allow_none=True)
+    uiObject = Bool(allow_none=True)
+    autoFill = Bool(allow_none=True)
+    autoLine = Bool(allow_none=True)
+    altText = String(allow_none=True)
+    textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed']))
+    textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed']))
+    lockText = Bool(allow_none=True)
+    justLastX = Bool(allow_none=True)
+    autoScale = Bool(allow_none=True)
+    rowHidden = Bool(allow_none=True)
+    colHidden = Bool(allow_none=True)
+    # anchor = Typed(expected_type=ObjectAnchor, )
+
+    __elements__ = ('anchor',)
+
+    def __init__(self,
+                 locked=None,
+                 defaultSize=None,
+                 _print=None,
+                 disabled=None,
+                 uiObject=None,
+                 autoFill=None,
+                 autoLine=None,
+                 altText=None,
+                 textHAlign=None,
+                 textVAlign=None,
+                 lockText=None,
+                 justLastX=None,
+                 autoScale=None,
+                 rowHidden=None,
+                 colHidden=None,
+                 anchor=None,
+                ):
+        self.locked = locked
+        self.defaultSize = defaultSize
+        self._print = _print
+        self.disabled = disabled
+        self.uiObject = uiObject
+        self.autoFill = autoFill
+        self.autoLine = autoLine
+        self.altText = altText
+        self.textHAlign = textHAlign
+        self.textVAlign = textVAlign
+        self.lockText = lockText
+        self.justLastX = justLastX
+        self.autoScale = autoScale
+        self.rowHidden = rowHidden
+        self.colHidden = colHidden
+        self.anchor = anchor
+
+
+class CommentRecord(Serialisable):
+
+    tagname = "comment"
+
+    ref = String()
+    authorId = Integer()
+    guid = Guid(allow_none=True)
+    shapeId = Integer(allow_none=True)
+    text = Typed(expected_type=Text)
+    commentPr = Typed(expected_type=Properties, allow_none=True)
+    author = String(allow_none=True)
+
+    __elements__ = ('text', 'commentPr')
+    __attrs__ = ('ref', 'authorId', 'guid', 'shapeId')
+
+    def __init__(self,
+                 ref="",
+                 authorId=0,
+                 guid=None,
+                 shapeId=0,
+                 text=None,
+                 commentPr=None,
+                 author=None,
+                 height=79,
+                 width=144
+                ):
+        self.ref = ref
+        self.authorId = authorId
+        self.guid = guid
+        self.shapeId = shapeId
+        if text is None:
+            text = Text()
+        self.text = text
+        self.commentPr = commentPr
+        self.author = author
+        self.height = height
+        self.width = width
+
+
+    @classmethod
+    def from_cell(cls, cell):
+        """
+        Class method to convert cell comment
+        """
+        comment = cell._comment
+        ref = cell.coordinate
+        self = cls(ref=ref, author=comment.author)
+        self.text.t = comment.content
+        self.height = comment.height
+        self.width = comment.width
+        return self
+
+
+    @property
+    def content(self):
+        """
+        Remove all inline formatting and stuff
+        """
+        return self.text.content
+
+
+class CommentSheet(Serialisable):
+
+    tagname = "comments"
+
+    authors = Typed(expected_type=AuthorList)
+    commentList = NestedSequence(expected_type=CommentRecord, count=0)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    _id = None
+    _path = "/xl/comments/comment{0}.xml"
+    mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
+    _rel_type = "comments"
+    _rel_id = None
+
+    __elements__ = ('authors', 'commentList')
+
+    def __init__(self,
+                 authors=None,
+                 commentList=None,
+                 extLst=None,
+                ):
+        self.authors = authors
+        self.commentList = commentList
+
+
+    def to_tree(self):
+        tree = super().to_tree()
+        tree.set("xmlns", SHEET_MAIN_NS)
+        return tree
+
+
+    @property
+    def comments(self):
+        """
+        Return a dictionary of comments keyed by coord
+        """
+        authors = self.authors.author
+
+        for c in self.commentList:
+            yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width)
+
+
+    @classmethod
+    def from_comments(cls, comments):
+        """
+        Create a comment sheet from a list of comments for a particular worksheet
+        """
+        authors = IndexedList()
+
+        # dedupe authors and get indexes
+        for comment in comments:
+            comment.authorId = authors.add(comment.author)
+
+        return cls(authors=AuthorList(authors), commentList=comments)
+
+
+    def write_shapes(self, vml=None):
+        """
+        Create the VML for comments
+        """
+        sw = ShapeWriter(self.comments)
+        return sw.write(vml)
+
+
+    @property
+    def path(self):
+        """
+        Return path within the archive
+        """
+        return self._path.format(self._id)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py
new file mode 100644
index 00000000..192bbc46
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/comments.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+class Comment:
+
+    _parent = None
+
+    def __init__(self, text, author, height=79, width=144):
+        self.content = text
+        self.author = author
+        self.height = height
+        self.width = width
+
+
+    @property
+    def parent(self):
+        return self._parent
+
+
+    def __eq__(self, other):
+        return (
+            self.content == other.content
+            and self.author == other.author
+        )
+
+    def __repr__(self):
+        return "Comment: {0} by {1}".format(self.content, self.author)
+
+
+    def __copy__(self):
+        """Create a detached copy of this comment."""
+        clone = self.__class__(self.content, self.author, self.height, self.width)
+        return clone
+
+
+    def bind(self, cell):
+        """
+        Bind comment to a particular cell
+        """
+        if cell is not None and self._parent is not None and self._parent != cell:
+            fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell"
+            raise AttributeError(fmt.format(cell.coordinate, cell.parent.title))
+        self._parent = cell
+
+
+    def unbind(self):
+        """
+        Unbind a comment from a cell
+        """
+        self._parent = None
+
+
+    @property
+    def text(self):
+        """
+        Any comment text stripped of all formatting.
+        """
+        return self.content
+
+    @text.setter
+    def text(self, value):
+        self.content = value
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py
new file mode 100644
index 00000000..cebfbc3d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/comments/shape_writer.py
@@ -0,0 +1,112 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.xml.functions import (
+    Element,
+    SubElement,
+    tostring,
+)
+
+from openpyxl.utils import coordinate_to_tuple
+
+vmlns = "urn:schemas-microsoft-com:vml"
+officens = "urn:schemas-microsoft-com:office:office"
+excelns = "urn:schemas-microsoft-com:office:excel"
+
+
+class ShapeWriter:
+    """
+    Create VML for comments
+    """
+
+    vml = None
+    vml_path = None
+
+
+    def __init__(self, comments):
+        self.comments = comments
+
+
+    def add_comment_shapetype(self, root):
+        shape_layout = SubElement(root, "{%s}shapelayout" % officens,
+                                  {"{%s}ext" % vmlns: "edit"})
+        SubElement(shape_layout,
+                   "{%s}idmap" % officens,
+                   {"{%s}ext" % vmlns: "edit", "data": "1"})
+        shape_type = SubElement(root,
+                                "{%s}shapetype" % vmlns,
+                                {"id": "_x0000_t202",
+                                 "coordsize": "21600,21600",
+                                 "{%s}spt" % officens: "202",
+                                 "path": "m,l,21600r21600,l21600,xe"})
+        SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
+        SubElement(shape_type,
+                   "{%s}path" % vmlns,
+                   {"gradientshapeok": "t",
+                    "{%s}connecttype" % officens: "rect"})
+
+
+    def add_comment_shape(self, root, idx, coord, height, width):
+        row, col = coordinate_to_tuple(coord)
+        row -= 1
+        col -= 1
+        shape = _shape_factory(row, col, height, width)
+
+        shape.set('id', "_x0000_s%04d" % idx)
+        root.append(shape)
+
+
+    def write(self, root):
+
+        if not hasattr(root, "findall"):
+            root = Element("xml")
+
+        # Remove any existing comment shapes
+        comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns)
+        for c in comments:
+            root.remove(c)
+
+        # check whether comments shape type already exists
+        shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns)
+        if shape_types is None:
+            self.add_comment_shapetype(root)
+
+        for idx, (coord, comment) in enumerate(self.comments, 1026):
+            self.add_comment_shape(root, idx, coord, comment.height, comment.width)
+
+        return tostring(root)
+
+
+def _shape_factory(row, column, height, width):
+    style = ("position:absolute; "
+             "margin-left:59.25pt;"
+             "margin-top:1.5pt;"
+             "width:{width}px;"
+             "height:{height}px;"
+             "z-index:1;"
+             "visibility:hidden").format(height=height,
+                                         width=width)
+    attrs = {
+        "type": "#_x0000_t202",
+        "style": style,
+        "fillcolor": "#ffffe1",
+        "{%s}insetmode" % officens: "auto"
+    }
+    shape = Element("{%s}shape" % vmlns, attrs)
+
+    SubElement(shape, "{%s}fill" % vmlns,
+               {"color2": "#ffffe1"})
+    SubElement(shape, "{%s}shadow" % vmlns,
+               {"color": "black", "obscured": "t"})
+    SubElement(shape, "{%s}path" % vmlns,
+               {"{%s}connecttype" % officens: "none"})
+    textbox = SubElement(shape, "{%s}textbox" % vmlns,
+                         {"style": "mso-direction-alt:auto"})
+    SubElement(textbox, "div", {"style": "text-align:left"})
+    client_data = SubElement(shape, "{%s}ClientData" % excelns,
+                             {"ObjectType": "Note"})
+    SubElement(client_data, "{%s}MoveWithCells" % excelns)
+    SubElement(client_data, "{%s}SizeWithCells" % excelns)
+    SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
+    SubElement(client_data, "{%s}Row" % excelns).text = str(row)
+    SubElement(client_data, "{%s}Column" % excelns).text = str(column)
+    return shape