shape_writer.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. # Copyright (c) 2010-2024 openpyxl
  2. from openpyxl.xml.functions import (
  3. Element,
  4. SubElement,
  5. tostring,
  6. )
  7. from openpyxl.utils import coordinate_to_tuple
  8. vmlns = "urn:schemas-microsoft-com:vml"
  9. officens = "urn:schemas-microsoft-com:office:office"
  10. excelns = "urn:schemas-microsoft-com:office:excel"
  11. class ShapeWriter:
  12. """
  13. Create VML for comments
  14. """
  15. vml = None
  16. vml_path = None
  17. def __init__(self, comments):
  18. self.comments = comments
  19. def add_comment_shapetype(self, root):
  20. shape_layout = SubElement(root, "{%s}shapelayout" % officens,
  21. {"{%s}ext" % vmlns: "edit"})
  22. SubElement(shape_layout,
  23. "{%s}idmap" % officens,
  24. {"{%s}ext" % vmlns: "edit", "data": "1"})
  25. shape_type = SubElement(root,
  26. "{%s}shapetype" % vmlns,
  27. {"id": "_x0000_t202",
  28. "coordsize": "21600,21600",
  29. "{%s}spt" % officens: "202",
  30. "path": "m,l,21600r21600,l21600,xe"})
  31. SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
  32. SubElement(shape_type,
  33. "{%s}path" % vmlns,
  34. {"gradientshapeok": "t",
  35. "{%s}connecttype" % officens: "rect"})
  36. def add_comment_shape(self, root, idx, coord, height, width):
  37. row, col = coordinate_to_tuple(coord)
  38. row -= 1
  39. col -= 1
  40. shape = _shape_factory(row, col, height, width)
  41. shape.set('id', "_x0000_s%04d" % idx)
  42. root.append(shape)
  43. def write(self, root):
  44. if not hasattr(root, "findall"):
  45. root = Element("xml")
  46. # Remove any existing comment shapes
  47. comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns)
  48. for c in comments:
  49. root.remove(c)
  50. # check whether comments shape type already exists
  51. shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns)
  52. if shape_types is None:
  53. self.add_comment_shapetype(root)
  54. for idx, (coord, comment) in enumerate(self.comments, 1026):
  55. self.add_comment_shape(root, idx, coord, comment.height, comment.width)
  56. return tostring(root)
  57. def _shape_factory(row, column, height, width):
  58. style = ("position:absolute; "
  59. "margin-left:59.25pt;"
  60. "margin-top:1.5pt;"
  61. "width:{width}px;"
  62. "height:{height}px;"
  63. "z-index:1;"
  64. "visibility:hidden").format(height=height,
  65. width=width)
  66. attrs = {
  67. "type": "#_x0000_t202",
  68. "style": style,
  69. "fillcolor": "#ffffe1",
  70. "{%s}insetmode" % officens: "auto"
  71. }
  72. shape = Element("{%s}shape" % vmlns, attrs)
  73. SubElement(shape, "{%s}fill" % vmlns,
  74. {"color2": "#ffffe1"})
  75. SubElement(shape, "{%s}shadow" % vmlns,
  76. {"color": "black", "obscured": "t"})
  77. SubElement(shape, "{%s}path" % vmlns,
  78. {"{%s}connecttype" % officens: "none"})
  79. textbox = SubElement(shape, "{%s}textbox" % vmlns,
  80. {"style": "mso-direction-alt:auto"})
  81. SubElement(textbox, "div", {"style": "text-align:left"})
  82. client_data = SubElement(shape, "{%s}ClientData" % excelns,
  83. {"ObjectType": "Note"})
  84. SubElement(client_data, "{%s}MoveWithCells" % excelns)
  85. SubElement(client_data, "{%s}SizeWithCells" % excelns)
  86. SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
  87. SubElement(client_data, "{%s}Row" % excelns).text = str(row)
  88. SubElement(client_data, "{%s}Column" % excelns).text = str(column)
  89. return shape