shape_writer.py 3.8 KB

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