# Copyright (c) 2010-2024 openpyxl from openpyxl.descriptors.serialisable import Serialisable from openpyxl.descriptors import ( Typed, Bool, Float, Set, NoneSet, String, Integer, DateTime, Sequence, ) from openpyxl.descriptors.excel import ( HexBinary, ExtensionList, Relation, ) from openpyxl.descriptors.nested import NestedInteger from openpyxl.descriptors.sequence import ( NestedSequence, MultiSequence, MultiSequencePart, ) from openpyxl.xml.constants import SHEET_MAIN_NS from openpyxl.xml.functions import tostring from openpyxl.packaging.relationship import ( RelationshipList, Relationship, get_rels_path ) from .table import ( PivotArea, Reference, ) from .fields import ( Boolean, Error, Missing, Number, Text, TupleList, DateTimeField, ) class MeasureDimensionMap(Serialisable): tagname = "map" measureGroup = Integer(allow_none=True) dimension = Integer(allow_none=True) def __init__(self, measureGroup=None, dimension=None, ): self.measureGroup = measureGroup self.dimension = dimension class MeasureGroup(Serialisable): tagname = "measureGroup" name = String() caption = String() def __init__(self, name=None, caption=None, ): self.name = name self.caption = caption class PivotDimension(Serialisable): tagname = "dimension" measure = Bool() name = String() uniqueName = String() caption = String() def __init__(self, measure=None, name=None, uniqueName=None, caption=None, ): self.measure = measure self.name = name self.uniqueName = uniqueName self.caption = caption class CalculatedMember(Serialisable): tagname = "calculatedMember" name = String() mdx = String() memberName = String(allow_none=True) hierarchy = String(allow_none=True) parent = String(allow_none=True) solveOrder = Integer(allow_none=True) set = Bool() extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = () def __init__(self, name=None, mdx=None, memberName=None, hierarchy=None, parent=None, solveOrder=None, set=None, extLst=None, ): self.name = name self.mdx = mdx self.memberName = memberName self.hierarchy = hierarchy self.parent = parent self.solveOrder = solveOrder self.set = set #self.extLst = extLst class CalculatedItem(Serialisable): tagname = "calculatedItem" field = Integer(allow_none=True) formula = String() pivotArea = Typed(expected_type=PivotArea, ) extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = ('pivotArea', 'extLst') def __init__(self, field=None, formula=None, pivotArea=None, extLst=None, ): self.field = field self.formula = formula self.pivotArea = pivotArea self.extLst = extLst class ServerFormat(Serialisable): tagname = "serverFormat" culture = String(allow_none=True) format = String(allow_none=True) def __init__(self, culture=None, format=None, ): self.culture = culture self.format = format class Query(Serialisable): tagname = "query" mdx = String() tpls = Typed(expected_type=TupleList, allow_none=True) __elements__ = ('tpls',) def __init__(self, mdx=None, tpls=None, ): self.mdx = mdx self.tpls = tpls class OLAPSet(Serialisable): tagname = "set" count = Integer() maxRank = Integer() setDefinition = String() sortType = NoneSet(values=(['ascending', 'descending', 'ascendingAlpha', 'descendingAlpha', 'ascendingNatural', 'descendingNatural'])) queryFailed = Bool() tpls = Typed(expected_type=TupleList, allow_none=True) sortByTuple = Typed(expected_type=TupleList, allow_none=True) __elements__ = ('tpls', 'sortByTuple') def __init__(self, count=None, maxRank=None, setDefinition=None, sortType=None, queryFailed=None, tpls=None, sortByTuple=None, ): self.count = count self.maxRank = maxRank self.setDefinition = setDefinition self.sortType = sortType self.queryFailed = queryFailed self.tpls = tpls self.sortByTuple = sortByTuple class PCDSDTCEntries(Serialisable): # Implements CT_PCDSDTCEntries tagname = "entries" count = Integer(allow_none=True) # elements are choice m = Typed(expected_type=Missing, allow_none=True) n = Typed(expected_type=Number, allow_none=True) e = Typed(expected_type=Error, allow_none=True) s = Typed(expected_type=Text, allow_none=True) __elements__ = ('m', 'n', 'e', 's') def __init__(self, count=None, m=None, n=None, e=None, s=None, ): self.count = count self.m = m self.n = n self.e = e self.s = s class TupleCache(Serialisable): tagname = "tupleCache" entries = Typed(expected_type=PCDSDTCEntries, allow_none=True) sets = NestedSequence(expected_type=OLAPSet, count=True) queryCache = NestedSequence(expected_type=Query, count=True) serverFormats = NestedSequence(expected_type=ServerFormat, count=True) extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = ('entries', 'sets', 'queryCache', 'serverFormats', 'extLst') def __init__(self, entries=None, sets=(), queryCache=(), serverFormats=(), extLst=None, ): self.entries = entries self.sets = sets self.queryCache = queryCache self.serverFormats = serverFormats self.extLst = extLst class OLAPKPI(Serialisable): tagname = "kpi" uniqueName = String() caption = String(allow_none=True) displayFolder = String(allow_none=True) measureGroup = String(allow_none=True) parent = String(allow_none=True) value = String() goal = String(allow_none=True) status = String(allow_none=True) trend = String(allow_none=True) weight = String(allow_none=True) time = String(allow_none=True) def __init__(self, uniqueName=None, caption=None, displayFolder=None, measureGroup=None, parent=None, value=None, goal=None, status=None, trend=None, weight=None, time=None, ): self.uniqueName = uniqueName self.caption = caption self.displayFolder = displayFolder self.measureGroup = measureGroup self.parent = parent self.value = value self.goal = goal self.status = status self.trend = trend self.weight = weight self.time = time class GroupMember(Serialisable): tagname = "groupMember" uniqueName = String() group = Bool() def __init__(self, uniqueName=None, group=None, ): self.uniqueName = uniqueName self.group = group class LevelGroup(Serialisable): tagname = "group" name = String() uniqueName = String() caption = String() uniqueParent = String() id = Integer() groupMembers = NestedSequence(expected_type=GroupMember, count=True) __elements__ = ('groupMembers',) def __init__(self, name=None, uniqueName=None, caption=None, uniqueParent=None, id=None, groupMembers=(), ): self.name = name self.uniqueName = uniqueName self.caption = caption self.uniqueParent = uniqueParent self.id = id self.groupMembers = groupMembers class GroupLevel(Serialisable): tagname = "groupLevel" uniqueName = String() caption = String() user = Bool() customRollUp = Bool() groups = NestedSequence(expected_type=LevelGroup, count=True) extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = ('groups', 'extLst') def __init__(self, uniqueName=None, caption=None, user=None, customRollUp=None, groups=(), extLst=None, ): self.uniqueName = uniqueName self.caption = caption self.user = user self.customRollUp = customRollUp self.groups = groups self.extLst = extLst class FieldUsage(Serialisable): tagname = "fieldUsage" x = Integer() def __init__(self, x=None, ): self.x = x class CacheHierarchy(Serialisable): tagname = "cacheHierarchy" uniqueName = String() caption = String(allow_none=True) measure = Bool() set = Bool() parentSet = Integer(allow_none=True) iconSet = Integer() attribute = Bool() time = Bool() keyAttribute = Bool() defaultMemberUniqueName = String(allow_none=True) allUniqueName = String(allow_none=True) allCaption = String(allow_none=True) dimensionUniqueName = String(allow_none=True) displayFolder = String(allow_none=True) measureGroup = String(allow_none=True) measures = Bool() count = Integer() oneField = Bool() memberValueDatatype = Integer(allow_none=True) unbalanced = Bool(allow_none=True) unbalancedGroup = Bool(allow_none=True) hidden = Bool() fieldsUsage = NestedSequence(expected_type=FieldUsage, count=True) groupLevels = NestedSequence(expected_type=GroupLevel, count=True) extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = ('fieldsUsage', 'groupLevels') def __init__(self, uniqueName="", caption=None, measure=None, set=None, parentSet=None, iconSet=0, attribute=None, time=None, keyAttribute=None, defaultMemberUniqueName=None, allUniqueName=None, allCaption=None, dimensionUniqueName=None, displayFolder=None, measureGroup=None, measures=None, count=None, oneField=None, memberValueDatatype=None, unbalanced=None, unbalancedGroup=None, hidden=None, fieldsUsage=(), groupLevels=(), extLst=None, ): self.uniqueName = uniqueName self.caption = caption self.measure = measure self.set = set self.parentSet = parentSet self.iconSet = iconSet self.attribute = attribute self.time = time self.keyAttribute = keyAttribute self.defaultMemberUniqueName = defaultMemberUniqueName self.allUniqueName = allUniqueName self.allCaption = allCaption self.dimensionUniqueName = dimensionUniqueName self.displayFolder = displayFolder self.measureGroup = measureGroup self.measures = measures self.count = count self.oneField = oneField self.memberValueDatatype = memberValueDatatype self.unbalanced = unbalanced self.unbalancedGroup = unbalancedGroup self.hidden = hidden self.fieldsUsage = fieldsUsage self.groupLevels = groupLevels self.extLst = extLst class GroupItems(Serialisable): tagname = "groupItems" m = Sequence(expected_type=Missing) n = Sequence(expected_type=Number) b = Sequence(expected_type=Boolean) e = Sequence(expected_type=Error) s = Sequence(expected_type=Text) d = Sequence(expected_type=DateTimeField,) __elements__ = ('m', 'n', 'b', 'e', 's', 'd') __attrs__ = ("count", ) def __init__(self, count=None, m=(), n=(), b=(), e=(), s=(), d=(), ): self.m = m self.n = n self.b = b self.e = e self.s = s self.d = d @property def count(self): return len(self.m + self.n + self.b + self.e + self.s + self.d) class RangePr(Serialisable): tagname = "rangePr" autoStart = Bool(allow_none=True) autoEnd = Bool(allow_none=True) groupBy = NoneSet(values=(['range', 'seconds', 'minutes', 'hours', 'days', 'months', 'quarters', 'years'])) startNum = Float(allow_none=True) endNum = Float(allow_none=True) startDate = DateTime(allow_none=True) endDate = DateTime(allow_none=True) groupInterval = Float(allow_none=True) def __init__(self, autoStart=True, autoEnd=True, groupBy="range", startNum=None, endNum=None, startDate=None, endDate=None, groupInterval=1, ): self.autoStart = autoStart self.autoEnd = autoEnd self.groupBy = groupBy self.startNum = startNum self.endNum = endNum self.startDate = startDate self.endDate = endDate self.groupInterval = groupInterval class FieldGroup(Serialisable): tagname = "fieldGroup" par = Integer(allow_none=True) base = Integer(allow_none=True) rangePr = Typed(expected_type=RangePr, allow_none=True) discretePr = NestedSequence(expected_type=NestedInteger, count=True) groupItems = Typed(expected_type=GroupItems, allow_none=True) __elements__ = ('rangePr', 'discretePr', 'groupItems') def __init__(self, par=None, base=None, rangePr=None, discretePr=(), groupItems=None, ): self.par = par self.base = base self.rangePr = rangePr self.discretePr = discretePr self.groupItems = groupItems class SharedItems(Serialisable): tagname = "sharedItems" _fields = MultiSequence() m = MultiSequencePart(expected_type=Missing, store="_fields") n = MultiSequencePart(expected_type=Number, store="_fields") b = MultiSequencePart(expected_type=Boolean, store="_fields") e = MultiSequencePart(expected_type=Error, store="_fields") s = MultiSequencePart(expected_type=Text, store="_fields") d = MultiSequencePart(expected_type=DateTimeField, store="_fields") # attributes are optional and must be derived from associated cache records containsSemiMixedTypes = Bool(allow_none=True) containsNonDate = Bool(allow_none=True) containsDate = Bool(allow_none=True) containsString = Bool(allow_none=True) containsBlank = Bool(allow_none=True) containsMixedTypes = Bool(allow_none=True) containsNumber = Bool(allow_none=True) containsInteger = Bool(allow_none=True) minValue = Float(allow_none=True) maxValue = Float(allow_none=True) minDate = DateTime(allow_none=True) maxDate = DateTime(allow_none=True) longText = Bool(allow_none=True) __attrs__ = ('count', 'containsBlank', 'containsDate', 'containsInteger', 'containsMixedTypes', 'containsNonDate', 'containsNumber', 'containsSemiMixedTypes', 'containsString', 'minValue', 'maxValue', 'minDate', 'maxDate', 'longText') def __init__(self, _fields=(), containsSemiMixedTypes=None, containsNonDate=None, containsDate=None, containsString=None, containsBlank=None, containsMixedTypes=None, containsNumber=None, containsInteger=None, minValue=None, maxValue=None, minDate=None, maxDate=None, count=None, longText=None, ): self._fields = _fields self.containsBlank = containsBlank self.containsDate = containsDate self.containsNonDate = containsNonDate self.containsString = containsString self.containsMixedTypes = containsMixedTypes self.containsSemiMixedTypes = containsSemiMixedTypes self.containsNumber = containsNumber self.containsInteger = containsInteger self.minValue = minValue self.maxValue = maxValue self.minDate = minDate self.maxDate = maxDate self.longText = longText @property def count(self): return len(self._fields) class CacheField(Serialisable): tagname = "cacheField" sharedItems = Typed(expected_type=SharedItems, allow_none=True) fieldGroup = Typed(expected_type=FieldGroup, allow_none=True) mpMap = NestedInteger(allow_none=True, attribute="v") extLst = Typed(expected_type=ExtensionList, allow_none=True) name = String() caption = String(allow_none=True) propertyName = String(allow_none=True) serverField = Bool(allow_none=True) uniqueList = Bool(allow_none=True) numFmtId = Integer(allow_none=True) formula = String(allow_none=True) sqlType = Integer(allow_none=True) hierarchy = Integer(allow_none=True) level = Integer(allow_none=True) databaseField = Bool(allow_none=True) mappingCount = Integer(allow_none=True) memberPropertyField = Bool(allow_none=True) __elements__ = ('sharedItems', 'fieldGroup', 'mpMap') def __init__(self, sharedItems=None, fieldGroup=None, mpMap=None, extLst=None, name=None, caption=None, propertyName=None, serverField=None, uniqueList=True, numFmtId=None, formula=None, sqlType=0, hierarchy=0, level=0, databaseField=True, mappingCount=None, memberPropertyField=None, ): self.sharedItems = sharedItems self.fieldGroup = fieldGroup self.mpMap = mpMap self.extLst = extLst self.name = name self.caption = caption self.propertyName = propertyName self.serverField = serverField self.uniqueList = uniqueList self.numFmtId = numFmtId self.formula = formula self.sqlType = sqlType self.hierarchy = hierarchy self.level = level self.databaseField = databaseField self.mappingCount = mappingCount self.memberPropertyField = memberPropertyField class RangeSet(Serialisable): tagname = "rangeSet" i1 = Integer(allow_none=True) i2 = Integer(allow_none=True) i3 = Integer(allow_none=True) i4 = Integer(allow_none=True) ref = String() name = String(allow_none=True) sheet = String(allow_none=True) def __init__(self, i1=None, i2=None, i3=None, i4=None, ref=None, name=None, sheet=None, ): self.i1 = i1 self.i2 = i2 self.i3 = i3 self.i4 = i4 self.ref = ref self.name = name self.sheet = sheet class PageItem(Serialisable): tagname = "pageItem" name = String() def __init__(self, name=None, ): self.name = name class Consolidation(Serialisable): tagname = "consolidation" autoPage = Bool(allow_none=True) pages = NestedSequence(expected_type=PageItem, count=True) rangeSets = NestedSequence(expected_type=RangeSet, count=True) __elements__ = ('pages', 'rangeSets') def __init__(self, autoPage=None, pages=(), rangeSets=(), ): self.autoPage = autoPage self.pages = pages self.rangeSets = rangeSets class WorksheetSource(Serialisable): tagname = "worksheetSource" ref = String(allow_none=True) name = String(allow_none=True) sheet = String(allow_none=True) def __init__(self, ref=None, name=None, sheet=None, ): self.ref = ref self.name = name self.sheet = sheet class CacheSource(Serialisable): tagname = "cacheSource" type = Set(values=(['worksheet', 'external', 'consolidation', 'scenario'])) connectionId = Integer(allow_none=True) # some elements are choice worksheetSource = Typed(expected_type=WorksheetSource, allow_none=True) consolidation = Typed(expected_type=Consolidation, allow_none=True) extLst = Typed(expected_type=ExtensionList, allow_none=True) __elements__ = ('worksheetSource', 'consolidation',) def __init__(self, type=None, connectionId=None, worksheetSource=None, consolidation=None, extLst=None, ): self.type = type self.connectionId = connectionId self.worksheetSource = worksheetSource self.consolidation = consolidation class CacheDefinition(Serialisable): mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" _id = 1 _path = "/xl/pivotCache/pivotCacheDefinition{0}.xml" records = None tagname = "pivotCacheDefinition" invalid = Bool(allow_none=True) saveData = Bool(allow_none=True) refreshOnLoad = Bool(allow_none=True) optimizeMemory = Bool(allow_none=True) enableRefresh = Bool(allow_none=True) refreshedBy = String(allow_none=True) refreshedDate = Float(allow_none=True) refreshedDateIso = DateTime(allow_none=True) backgroundQuery = Bool(allow_none=True) missingItemsLimit = Integer(allow_none=True) createdVersion = Integer(allow_none=True) refreshedVersion = Integer(allow_none=True) minRefreshableVersion = Integer(allow_none=True) recordCount = Integer(allow_none=True) upgradeOnRefresh = Bool(allow_none=True) supportSubquery = Bool(allow_none=True) supportAdvancedDrill = Bool(allow_none=True) cacheSource = Typed(expected_type=CacheSource) cacheFields = NestedSequence(expected_type=CacheField, count=True) cacheHierarchies = NestedSequence(expected_type=CacheHierarchy, allow_none=True) kpis = NestedSequence(expected_type=OLAPKPI, count=True) tupleCache = Typed(expected_type=TupleCache, allow_none=True) calculatedItems = NestedSequence(expected_type=CalculatedItem, count=True) calculatedMembers = NestedSequence(expected_type=CalculatedMember, count=True) dimensions = NestedSequence(expected_type=PivotDimension, allow_none=True) measureGroups = NestedSequence(expected_type=MeasureGroup, count=True) maps = NestedSequence(expected_type=MeasureDimensionMap, count=True) extLst = Typed(expected_type=ExtensionList, allow_none=True) id = Relation() __elements__ = ('cacheSource', 'cacheFields', 'cacheHierarchies', 'kpis', 'tupleCache', 'calculatedItems', 'calculatedMembers', 'dimensions', 'measureGroups', 'maps',) def __init__(self, invalid=None, saveData=None, refreshOnLoad=None, optimizeMemory=None, enableRefresh=None, refreshedBy=None, refreshedDate=None, refreshedDateIso=None, backgroundQuery=None, missingItemsLimit=None, createdVersion=None, refreshedVersion=None, minRefreshableVersion=None, recordCount=None, upgradeOnRefresh=None, tupleCache=None, supportSubquery=None, supportAdvancedDrill=None, cacheSource=None, cacheFields=(), cacheHierarchies=(), kpis=(), calculatedItems=(), calculatedMembers=(), dimensions=(), measureGroups=(), maps=(), extLst=None, id = None, ): self.invalid = invalid self.saveData = saveData self.refreshOnLoad = refreshOnLoad self.optimizeMemory = optimizeMemory self.enableRefresh = enableRefresh self.refreshedBy = refreshedBy self.refreshedDate = refreshedDate self.refreshedDateIso = refreshedDateIso self.backgroundQuery = backgroundQuery self.missingItemsLimit = missingItemsLimit self.createdVersion = createdVersion self.refreshedVersion = refreshedVersion self.minRefreshableVersion = minRefreshableVersion self.recordCount = recordCount self.upgradeOnRefresh = upgradeOnRefresh self.supportSubquery = supportSubquery self.supportAdvancedDrill = supportAdvancedDrill self.cacheSource = cacheSource self.cacheFields = cacheFields self.cacheHierarchies = cacheHierarchies self.kpis = kpis self.tupleCache = tupleCache self.calculatedItems = calculatedItems self.calculatedMembers = calculatedMembers self.dimensions = dimensions self.measureGroups = measureGroups self.maps = maps self.id = id def to_tree(self): node = super().to_tree() node.set("xmlns", SHEET_MAIN_NS) return node @property def path(self): return self._path.format(self._id) def _write(self, archive, manifest): """ Add to zipfile and update manifest """ self._write_rels(archive, manifest) xml = tostring(self.to_tree()) archive.writestr(self.path[1:], xml) manifest.append(self) def _write_rels(self, archive, manifest): """ Write the relevant child objects and add links """ if self.records is None: return rels = RelationshipList() r = Relationship(Type=self.records.rel_type, Target=self.records.path) rels.append(r) self.id = r.id self.records._id = self._id self.records._write(archive, manifest) path = get_rels_path(self.path) xml = tostring(rels.to_tree()) archive.writestr(path[1:], xml)