Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1,382 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
NoneSet,
|
||||
Integer,
|
||||
Sequence,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedText,
|
||||
NestedNoneSet,
|
||||
)
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
|
||||
from openpyxl.packaging.relationship import (
|
||||
Relationship,
|
||||
RelationshipList,
|
||||
)
|
||||
from openpyxl.utils import coordinate_to_tuple
|
||||
from openpyxl.utils.units import (
|
||||
cm_to_EMU,
|
||||
pixels_to_EMU,
|
||||
)
|
||||
from openpyxl.drawing.image import Image
|
||||
|
||||
from openpyxl.xml.constants import SHEET_DRAWING_NS
|
||||
|
||||
from openpyxl.chart._chart import ChartBase
|
||||
from .xdr import (
|
||||
XDRPoint2D,
|
||||
XDRPositiveSize2D,
|
||||
)
|
||||
from .fill import Blip
|
||||
from .connector import Shape
|
||||
from .graphic import (
|
||||
GroupShape,
|
||||
GraphicFrame,
|
||||
)
|
||||
from .geometry import PresetGeometry2D
|
||||
from .picture import PictureFrame
|
||||
from .relation import ChartRelation
|
||||
|
||||
|
||||
class AnchorClientData(Serialisable):
|
||||
|
||||
fLocksWithSheet = Bool(allow_none=True)
|
||||
fPrintsWithSheet = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
fLocksWithSheet=None,
|
||||
fPrintsWithSheet=None,
|
||||
):
|
||||
self.fLocksWithSheet = fLocksWithSheet
|
||||
self.fPrintsWithSheet = fPrintsWithSheet
|
||||
|
||||
|
||||
class AnchorMarker(Serialisable):
|
||||
|
||||
tagname = "marker"
|
||||
|
||||
col = NestedText(expected_type=int)
|
||||
colOff = NestedText(expected_type=int)
|
||||
row = NestedText(expected_type=int)
|
||||
rowOff = NestedText(expected_type=int)
|
||||
|
||||
def __init__(self,
|
||||
col=0,
|
||||
colOff=0,
|
||||
row=0,
|
||||
rowOff=0,
|
||||
):
|
||||
self.col = col
|
||||
self.colOff = colOff
|
||||
self.row = row
|
||||
self.rowOff = rowOff
|
||||
|
||||
|
||||
class _AnchorBase(Serialisable):
|
||||
|
||||
#one of
|
||||
sp = Typed(expected_type=Shape, allow_none=True)
|
||||
shape = Alias("sp")
|
||||
grpSp = Typed(expected_type=GroupShape, allow_none=True)
|
||||
groupShape = Alias("grpSp")
|
||||
graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True)
|
||||
cxnSp = Typed(expected_type=Shape, allow_none=True)
|
||||
connectionShape = Alias("cxnSp")
|
||||
pic = Typed(expected_type=PictureFrame, allow_none=True)
|
||||
contentPart = Relation()
|
||||
|
||||
clientData = Typed(expected_type=AnchorClientData)
|
||||
|
||||
__elements__ = ('sp', 'grpSp', 'graphicFrame',
|
||||
'cxnSp', 'pic', 'contentPart', 'clientData')
|
||||
|
||||
def __init__(self,
|
||||
clientData=None,
|
||||
sp=None,
|
||||
grpSp=None,
|
||||
graphicFrame=None,
|
||||
cxnSp=None,
|
||||
pic=None,
|
||||
contentPart=None
|
||||
):
|
||||
if clientData is None:
|
||||
clientData = AnchorClientData()
|
||||
self.clientData = clientData
|
||||
self.sp = sp
|
||||
self.grpSp = grpSp
|
||||
self.graphicFrame = graphicFrame
|
||||
self.cxnSp = cxnSp
|
||||
self.pic = pic
|
||||
self.contentPart = contentPart
|
||||
|
||||
|
||||
class AbsoluteAnchor(_AnchorBase):
|
||||
|
||||
tagname = "absoluteAnchor"
|
||||
|
||||
pos = Typed(expected_type=XDRPoint2D)
|
||||
ext = Typed(expected_type=XDRPositiveSize2D)
|
||||
|
||||
sp = _AnchorBase.sp
|
||||
grpSp = _AnchorBase.grpSp
|
||||
graphicFrame = _AnchorBase.graphicFrame
|
||||
cxnSp = _AnchorBase.cxnSp
|
||||
pic = _AnchorBase.pic
|
||||
contentPart = _AnchorBase.contentPart
|
||||
clientData = _AnchorBase.clientData
|
||||
|
||||
__elements__ = ('pos', 'ext') + _AnchorBase.__elements__
|
||||
|
||||
def __init__(self,
|
||||
pos=None,
|
||||
ext=None,
|
||||
**kw
|
||||
):
|
||||
if pos is None:
|
||||
pos = XDRPoint2D(0, 0)
|
||||
self.pos = pos
|
||||
if ext is None:
|
||||
ext = XDRPositiveSize2D(0, 0)
|
||||
self.ext = ext
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class OneCellAnchor(_AnchorBase):
|
||||
|
||||
tagname = "oneCellAnchor"
|
||||
|
||||
_from = Typed(expected_type=AnchorMarker)
|
||||
ext = Typed(expected_type=XDRPositiveSize2D)
|
||||
|
||||
sp = _AnchorBase.sp
|
||||
grpSp = _AnchorBase.grpSp
|
||||
graphicFrame = _AnchorBase.graphicFrame
|
||||
cxnSp = _AnchorBase.cxnSp
|
||||
pic = _AnchorBase.pic
|
||||
contentPart = _AnchorBase.contentPart
|
||||
clientData = _AnchorBase.clientData
|
||||
|
||||
__elements__ = ('_from', 'ext') + _AnchorBase.__elements__
|
||||
|
||||
|
||||
def __init__(self,
|
||||
_from=None,
|
||||
ext=None,
|
||||
**kw
|
||||
):
|
||||
if _from is None:
|
||||
_from = AnchorMarker()
|
||||
self._from = _from
|
||||
if ext is None:
|
||||
ext = XDRPositiveSize2D(0, 0)
|
||||
self.ext = ext
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class TwoCellAnchor(_AnchorBase):
|
||||
|
||||
tagname = "twoCellAnchor"
|
||||
|
||||
editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute']))
|
||||
_from = Typed(expected_type=AnchorMarker)
|
||||
to = Typed(expected_type=AnchorMarker)
|
||||
|
||||
sp = _AnchorBase.sp
|
||||
grpSp = _AnchorBase.grpSp
|
||||
graphicFrame = _AnchorBase.graphicFrame
|
||||
cxnSp = _AnchorBase.cxnSp
|
||||
pic = _AnchorBase.pic
|
||||
contentPart = _AnchorBase.contentPart
|
||||
clientData = _AnchorBase.clientData
|
||||
|
||||
__elements__ = ('_from', 'to') + _AnchorBase.__elements__
|
||||
|
||||
def __init__(self,
|
||||
editAs=None,
|
||||
_from=None,
|
||||
to=None,
|
||||
**kw
|
||||
):
|
||||
self.editAs = editAs
|
||||
if _from is None:
|
||||
_from = AnchorMarker()
|
||||
self._from = _from
|
||||
if to is None:
|
||||
to = AnchorMarker()
|
||||
self.to = to
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
def _check_anchor(obj):
|
||||
"""
|
||||
Check whether an object has an existing Anchor object
|
||||
If not create a OneCellAnchor using the provided coordinate
|
||||
"""
|
||||
anchor = obj.anchor
|
||||
if not isinstance(anchor, _AnchorBase):
|
||||
row, col = coordinate_to_tuple(anchor.upper())
|
||||
anchor = OneCellAnchor()
|
||||
anchor._from.row = row -1
|
||||
anchor._from.col = col -1
|
||||
if isinstance(obj, ChartBase):
|
||||
anchor.ext.width = cm_to_EMU(obj.width)
|
||||
anchor.ext.height = cm_to_EMU(obj.height)
|
||||
elif isinstance(obj, Image):
|
||||
anchor.ext.width = pixels_to_EMU(obj.width)
|
||||
anchor.ext.height = pixels_to_EMU(obj.height)
|
||||
return anchor
|
||||
|
||||
|
||||
class SpreadsheetDrawing(Serialisable):
|
||||
|
||||
tagname = "wsDr"
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml"
|
||||
_rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
|
||||
_path = PartName="/xl/drawings/drawing{0}.xml"
|
||||
_id = None
|
||||
|
||||
twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True)
|
||||
oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True)
|
||||
absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True)
|
||||
|
||||
__elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor")
|
||||
|
||||
def __init__(self,
|
||||
twoCellAnchor=(),
|
||||
oneCellAnchor=(),
|
||||
absoluteAnchor=(),
|
||||
):
|
||||
self.twoCellAnchor = twoCellAnchor
|
||||
self.oneCellAnchor = oneCellAnchor
|
||||
self.absoluteAnchor = absoluteAnchor
|
||||
self.charts = []
|
||||
self.images = []
|
||||
self._rels = []
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Just need to check for identity
|
||||
"""
|
||||
return id(self)
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.charts) or bool(self.images)
|
||||
|
||||
|
||||
|
||||
def _write(self):
|
||||
"""
|
||||
create required structure and the serialise
|
||||
"""
|
||||
anchors = []
|
||||
for idx, obj in enumerate(self.charts + self.images, 1):
|
||||
anchor = _check_anchor(obj)
|
||||
if isinstance(obj, ChartBase):
|
||||
rel = Relationship(type="chart", Target=obj.path)
|
||||
anchor.graphicFrame = self._chart_frame(idx)
|
||||
elif isinstance(obj, Image):
|
||||
rel = Relationship(type="image", Target=obj.path)
|
||||
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
|
||||
if not child:
|
||||
anchor.pic = self._picture_frame(idx)
|
||||
else:
|
||||
child.blipFill.blip.embed = "rId{0}".format(idx)
|
||||
|
||||
anchors.append(anchor)
|
||||
self._rels.append(rel)
|
||||
|
||||
for a in anchors:
|
||||
if isinstance(a, OneCellAnchor):
|
||||
self.oneCellAnchor.append(a)
|
||||
elif isinstance(a, TwoCellAnchor):
|
||||
self.twoCellAnchor.append(a)
|
||||
else:
|
||||
self.absoluteAnchor.append(a)
|
||||
|
||||
tree = self.to_tree()
|
||||
tree.set('xmlns', SHEET_DRAWING_NS)
|
||||
return tree
|
||||
|
||||
|
||||
def _chart_frame(self, idx):
|
||||
chart_rel = ChartRelation(f"rId{idx}")
|
||||
frame = GraphicFrame()
|
||||
nv = frame.nvGraphicFramePr.cNvPr
|
||||
nv.id = idx
|
||||
nv.name = "Chart {0}".format(idx)
|
||||
frame.graphic.graphicData.chart = chart_rel
|
||||
return frame
|
||||
|
||||
|
||||
def _picture_frame(self, idx):
|
||||
pic = PictureFrame()
|
||||
pic.nvPicPr.cNvPr.descr = "Picture"
|
||||
pic.nvPicPr.cNvPr.id = idx
|
||||
pic.nvPicPr.cNvPr.name = "Image {0}".format(idx)
|
||||
|
||||
pic.blipFill.blip = Blip()
|
||||
pic.blipFill.blip.embed = "rId{0}".format(idx)
|
||||
pic.blipFill.blip.cstate = "print"
|
||||
|
||||
pic.spPr.prstGeom = PresetGeometry2D(prst="rect")
|
||||
pic.spPr.ln = None
|
||||
return pic
|
||||
|
||||
|
||||
def _write_rels(self):
|
||||
rels = RelationshipList()
|
||||
for r in self._rels:
|
||||
rels.append(r)
|
||||
return rels.to_tree()
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path.format(self._id)
|
||||
|
||||
|
||||
@property
|
||||
def _chart_rels(self):
|
||||
"""
|
||||
Get relationship information for each chart and bind anchor to it
|
||||
"""
|
||||
rels = []
|
||||
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
|
||||
for anchor in anchors:
|
||||
if anchor.graphicFrame is not None:
|
||||
graphic = anchor.graphicFrame.graphic
|
||||
rel = graphic.graphicData.chart
|
||||
if rel is not None:
|
||||
rel.anchor = anchor
|
||||
rel.anchor.graphicFrame = None
|
||||
rels.append(rel)
|
||||
return rels
|
||||
|
||||
|
||||
@property
|
||||
def _blip_rels(self):
|
||||
"""
|
||||
Get relationship information for each blip and bind anchor to it
|
||||
|
||||
Images that are not part of the XLSX package will be ignored.
|
||||
"""
|
||||
rels = []
|
||||
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
|
||||
|
||||
for anchor in anchors:
|
||||
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
|
||||
if child and child.blipFill:
|
||||
rel = child.blipFill.blip
|
||||
if rel is not None and rel.embed:
|
||||
rel.anchor = anchor
|
||||
rels.append(rel)
|
||||
|
||||
return rels
|
Reference in New Issue
Block a user