113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
# 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
|