Source code for fastpyxl.comments.comment_sheet
# Copyright (c) 2010-2024 fastpyxl
## Incomplete!
import re
from fastpyxl.descriptors.excel import ExtensionList
from fastpyxl.utils.indexed_list import IndexedList
from fastpyxl.xml.constants import SHEET_MAIN_NS
from fastpyxl.cell.text import Text
from fastpyxl.typed_serialisable.base import Serialisable
from fastpyxl.typed_serialisable.errors import FieldValidationError
from fastpyxl.typed_serialisable.fields import Field
from .author import AuthorList
from .comments import Comment
from .shape_writer import ShapeWriter
_GUID_RE = re.compile(
r"^\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}$"
)
def _guid_converter(value):
if value is None:
return None
if not isinstance(value, str):
raise FieldValidationError(f"guid rejected value {value!r}")
v = value.upper()
if _GUID_RE.match(v) is None:
raise FieldValidationError(f"guid rejected value {value!r}")
return v
_H_ALIGN = frozenset({"left", "center", "right", "justify", "distributed"})
_V_ALIGN = frozenset({"top", "center", "bottom", "justify", "distributed"})
def _h_align_converter(value):
if value is None:
return None
if value not in _H_ALIGN:
raise FieldValidationError(f"textHAlign rejected value {value!r}")
return value
def _v_align_converter(value):
if value is None:
return None
if value not in _V_ALIGN:
raise FieldValidationError(f"textVAlign rejected value {value!r}")
return value
[docs]
class Properties(Serialisable):
tagname = "commentPr"
locked: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
defaultSize: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
_print: bool | None = Field.attribute(
expected_type=bool, allow_none=True, xml_name="print", default=None
)
disabled: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
uiObject: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
autoFill: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
autoLine: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
altText: str | None = Field.attribute(expected_type=str, allow_none=True, default=None)
textHAlign: str | None = Field.attribute(
expected_type=str,
allow_none=True,
converter=_h_align_converter, default=None,
)
textVAlign: str | None = Field.attribute(
expected_type=str,
allow_none=True,
converter=_v_align_converter, default=None,
)
lockText: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
justLastX: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
autoScale: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
rowHidden: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
colHidden: bool | None = Field.attribute(expected_type=bool, allow_none=True, default=None)
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
[docs]
class CommentRecord(Serialisable):
tagname = "comment"
ref: str = Field.attribute(expected_type=str, default=None)
authorId: int = Field.attribute(expected_type=int, default=None)
guid: str | None = Field.attribute(
expected_type=str, allow_none=True, converter=_guid_converter, default=None
)
shapeId: int | None = Field.attribute(expected_type=int, allow_none=True, default=None)
text: Text = Field.element(expected_type=Text, default=None)
commentPr: Properties | None = Field.element(expected_type=Properties, allow_none=True, default=None)
xml_order = ("text", "commentPr")
def __init__(
self,
ref="",
authorId=0,
guid=None,
shapeId=0,
text=None,
commentPr=None,
author=None,
height=79,
width=144,
):
if text is None:
text = Text()
self.ref = ref
self.authorId = authorId
self.guid = guid
self.shapeId = shapeId
self.text = text
self.commentPr = commentPr
self.author = author
self.height = height
self.width = width
[docs]
@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
[docs]
class CommentSheet(Serialisable):
tagname = "comments"
authors: AuthorList = Field.element(expected_type=AuthorList, default=None)
commentList: list[CommentRecord] = Field.nested_sequence(
expected_type=CommentRecord, count=False, default=list
)
extLst: ExtensionList | None = Field.element(
expected_type=ExtensionList, allow_none=True, serialize=False, default=None
)
_id = None
_path = "/xl/comments/comment{0}.xml"
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
_rel_type = "comments"
_rel_id = None
xml_order = ("authors", "commentList")
def __init__(
self,
authors=None,
commentList=None,
extLst=None,
):
self.authors = authors or AuthorList()
self.commentList = commentList or []
self.extLst = extLst
[docs]
def to_tree(self, tagname=None, idx=None, namespace=None):
tree = super().to_tree(tagname, idx, namespace)
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)
[docs]
@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)
[docs]
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)