Updated script that can be controled by Nodejs web app

This commit is contained in:
mac OS
2024-11-25 12:24:18 +07:00
parent c440eda1f4
commit 8b0ab2bd3a
8662 changed files with 1803808 additions and 34 deletions

View File

@ -0,0 +1,4 @@
# Copyright (c) 2010-2024 openpyxl
from .workbook import Workbook

View File

@ -0,0 +1,197 @@
# Copyright (c) 2010-2024 openpyxl
"""Write the workbook global settings to the archive."""
from openpyxl.utils import quote_sheetname
from openpyxl.xml.constants import (
ARC_APP,
ARC_CORE,
ARC_CUSTOM,
ARC_WORKBOOK,
PKG_REL_NS,
CUSTOMUI_NS,
ARC_ROOT_RELS,
)
from openpyxl.xml.functions import tostring, fromstring
from openpyxl.packaging.relationship import Relationship, RelationshipList
from openpyxl.workbook.defined_name import (
DefinedName,
DefinedNameList,
)
from openpyxl.workbook.external_reference import ExternalReference
from openpyxl.packaging.workbook import ChildSheet, WorkbookPackage, PivotCache
from openpyxl.workbook.properties import WorkbookProperties
from openpyxl.utils.datetime import CALENDAR_MAC_1904
def get_active_sheet(wb):
"""
Return the index of the active sheet.
If the sheet set to active is hidden return the next visible sheet or None
"""
visible_sheets = [idx for idx, sheet in enumerate(wb._sheets) if sheet.sheet_state == "visible"]
if not visible_sheets:
raise IndexError("At least one sheet must be visible")
idx = wb._active_sheet_index
sheet = wb.active
if sheet and sheet.sheet_state == "visible":
return idx
for idx in visible_sheets[idx:]:
wb.active = idx
return idx
return None
class WorkbookWriter:
def __init__(self, wb):
self.wb = wb
self.rels = RelationshipList()
self.package = WorkbookPackage()
self.package.workbookProtection = wb.security
self.package.calcPr = wb.calculation
def write_properties(self):
props = WorkbookProperties() # needs a mapping to the workbook for preservation
if self.wb.code_name is not None:
props.codeName = self.wb.code_name
if self.wb.excel_base_date == CALENDAR_MAC_1904:
props.date1904 = True
self.package.workbookPr = props
def write_worksheets(self):
for idx, sheet in enumerate(self.wb._sheets, 1):
sheet_node = ChildSheet(name=sheet.title, sheetId=idx, id="rId{0}".format(idx))
rel = Relationship(type=sheet._rel_type, Target=sheet.path)
self.rels.append(rel)
if not sheet.sheet_state == 'visible':
if len(self.wb._sheets) == 1:
raise ValueError("The only worksheet of a workbook cannot be hidden")
sheet_node.state = sheet.sheet_state
self.package.sheets.append(sheet_node)
def write_refs(self):
for link in self.wb._external_links:
# need to match a counter with a workbook's relations
rId = len(self.wb.rels) + 1
rel = Relationship(type=link._rel_type, Target=link.path)
self.rels.append(rel)
ext = ExternalReference(id=rel.id)
self.package.externalReferences.append(ext)
def write_names(self):
defined_names = list(self.wb.defined_names.values())
for idx, sheet in enumerate(self.wb.worksheets):
quoted = quote_sheetname(sheet.title)
# local names
if sheet.defined_names:
names = sheet.defined_names.values()
for n in names:
n.localSheetId = idx
defined_names.extend(names)
if sheet.auto_filter:
name = DefinedName(name='_FilterDatabase', localSheetId=idx, hidden=True)
name.value = f"{quoted}!{sheet.auto_filter}"
defined_names.append(name)
if sheet.print_titles:
name = DefinedName(name="Print_Titles", localSheetId=idx)
name.value = sheet.print_titles
defined_names.append(name)
if sheet.print_area:
name = DefinedName(name="Print_Area", localSheetId=idx)
name.value = sheet.print_area
defined_names.append(name)
self.package.definedNames = DefinedNameList(definedName=defined_names)
def write_pivots(self):
pivot_caches = set()
for pivot in self.wb._pivots:
if pivot.cache not in pivot_caches:
pivot_caches.add(pivot.cache)
c = PivotCache(cacheId=pivot.cacheId)
self.package.pivotCaches.append(c)
rel = Relationship(Type=pivot.cache.rel_type, Target=pivot.cache.path)
self.rels.append(rel)
c.id = rel.id
#self.wb._pivots = [] # reset
def write_views(self):
active = get_active_sheet(self.wb)
if self.wb.views:
self.wb.views[0].activeTab = active
self.package.bookViews = self.wb.views
def write(self):
"""Write the core workbook xml."""
self.write_properties()
self.write_worksheets()
self.write_names()
self.write_pivots()
self.write_views()
self.write_refs()
return tostring(self.package.to_tree())
def write_rels(self):
"""Write the workbook relationships xml."""
styles = Relationship(type='styles', Target='styles.xml')
self.rels.append(styles)
theme = Relationship(type='theme', Target='theme/theme1.xml')
self.rels.append(theme)
if self.wb.vba_archive:
vba = Relationship(type='', Target='vbaProject.bin')
vba.Type ='http://schemas.microsoft.com/office/2006/relationships/vbaProject'
self.rels.append(vba)
return tostring(self.rels.to_tree())
def write_root_rels(self):
"""Write the package relationships"""
rels = RelationshipList()
rel = Relationship(type="officeDocument", Target=ARC_WORKBOOK)
rels.append(rel)
rel = Relationship(Type=f"{PKG_REL_NS}/metadata/core-properties", Target=ARC_CORE)
rels.append(rel)
rel = Relationship(type="extended-properties", Target=ARC_APP)
rels.append(rel)
if len(self.wb.custom_doc_props) >= 1:
rel = Relationship(type="custom-properties", Target=ARC_CUSTOM)
rels.append(rel)
if self.wb.vba_archive is not None:
# See if there was a customUI relation and reuse it
xml = fromstring(self.wb.vba_archive.read(ARC_ROOT_RELS))
root_rels = RelationshipList.from_tree(xml)
for rel in root_rels.find(CUSTOMUI_NS):
rels.append(rel)
return tostring(rels.to_tree())

View File

@ -0,0 +1,166 @@
# Copyright (c) 2010-2024 openpyxl
import re
import warnings
from openpyxl.worksheet.header_footer import HeaderFooter
"""
Base class for worksheets, chartsheets, etc. that can be added to workbooks
"""
INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]')
def avoid_duplicate_name(names, value):
"""
Naive check to see whether name already exists.
If name does exist suggest a name using an incrementer
Duplicates are case insensitive
"""
# Check for an absolute match in which case we need to find an alternative
match = [n for n in names if n.lower() == value.lower()]
if match:
names = u",".join(names)
sheet_title_regex = re.compile(f'(?P<title>{re.escape(value)})(?P<count>\\d*),?', re.I)
matches = sheet_title_regex.findall(names)
if matches:
# use name, but append with the next highest integer
counts = [int(idx) for (t, idx) in matches if idx.isdigit()]
highest = 0
if counts:
highest = max(counts)
value = u"{0}{1}".format(value, highest + 1)
return value
class _WorkbookChild:
__title = ""
_id = None
_path = "{0}"
_parent = None
_default_title = "Sheet"
def __init__(self, parent=None, title=None):
self._parent = parent
self.title = title or self._default_title
self.HeaderFooter = HeaderFooter()
def __repr__(self):
return '<{0} "{1}">'.format(self.__class__.__name__, self.title)
@property
def parent(self):
return self._parent
@property
def encoding(self):
return self._parent.encoding
@property
def title(self):
return self.__title
@title.setter
def title(self, value):
"""
Set a sheet title, ensuring it is valid.
Limited to 31 characters, no special characters.
Duplicate titles will be incremented numerically
"""
if not self._parent:
return
if not value:
raise ValueError("Title must have at least one character")
if hasattr(value, "decode"):
if not isinstance(value, str):
try:
value = value.decode("ascii")
except UnicodeDecodeError:
raise ValueError("Worksheet titles must be str")
m = INVALID_TITLE_REGEX.search(value)
if m:
msg = "Invalid character {0} found in sheet title".format(m.group(0))
raise ValueError(msg)
if self.title is not None and self.title != value:
value = avoid_duplicate_name(self.parent.sheetnames, value)
if len(value) > 31:
warnings.warn("Title is more than 31 characters. Some applications may not be able to read the file")
self.__title = value
@property
def oddHeader(self):
return self.HeaderFooter.oddHeader
@oddHeader.setter
def oddHeader(self, value):
self.HeaderFooter.oddHeader = value
@property
def oddFooter(self):
return self.HeaderFooter.oddFooter
@oddFooter.setter
def oddFooter(self, value):
self.HeaderFooter.oddFooter = value
@property
def evenHeader(self):
return self.HeaderFooter.evenHeader
@evenHeader.setter
def evenHeader(self, value):
self.HeaderFooter.evenHeader = value
@property
def evenFooter(self):
return self.HeaderFooter.evenFooter
@evenFooter.setter
def evenFooter(self, value):
self.HeaderFooter.evenFooter = value
@property
def firstHeader(self):
return self.HeaderFooter.firstHeader
@firstHeader.setter
def firstHeader(self, value):
self.HeaderFooter.firstHeader = value
@property
def firstFooter(self):
return self.HeaderFooter.firstFooter
@firstFooter.setter
def firstFooter(self, value):
self.HeaderFooter.firstFooter = value
@property
def path(self):
return self._path.format(self._id)

View File

@ -0,0 +1,189 @@
# Copyright (c) 2010-2024 openpyxl
from collections import defaultdict
import re
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
String,
Integer,
Bool,
Sequence,
Descriptor,
)
from openpyxl.compat import safe_string
from openpyxl.formula import Tokenizer
from openpyxl.utils.cell import SHEETRANGE_RE
RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria",
"_FilterDatabase", "Extract", "Consolidate_Area",
"Sheet_Title"])
_names = "|".join(RESERVED)
RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names))
class DefinedName(Serialisable):
tagname = "definedName"
name = String() # unique per workbook/worksheet
comment = String(allow_none=True)
customMenu = String(allow_none=True)
description = String(allow_none=True)
help = String(allow_none=True)
statusBar = String(allow_none=True)
localSheetId = Integer(allow_none=True)
hidden = Bool(allow_none=True)
function = Bool(allow_none=True)
vbProcedure = Bool(allow_none=True)
xlm = Bool(allow_none=True)
functionGroupId = Integer(allow_none=True)
shortcutKey = String(allow_none=True)
publishToServer = Bool(allow_none=True)
workbookParameter = Bool(allow_none=True)
attr_text = Descriptor()
value = Alias("attr_text")
def __init__(self,
name=None,
comment=None,
customMenu=None,
description=None,
help=None,
statusBar=None,
localSheetId=None,
hidden=None,
function=None,
vbProcedure=None,
xlm=None,
functionGroupId=None,
shortcutKey=None,
publishToServer=None,
workbookParameter=None,
attr_text=None
):
self.name = name
self.comment = comment
self.customMenu = customMenu
self.description = description
self.help = help
self.statusBar = statusBar
self.localSheetId = localSheetId
self.hidden = hidden
self.function = function
self.vbProcedure = vbProcedure
self.xlm = xlm
self.functionGroupId = functionGroupId
self.shortcutKey = shortcutKey
self.publishToServer = publishToServer
self.workbookParameter = workbookParameter
self.attr_text = attr_text
@property
def type(self):
tok = Tokenizer("=" + self.value)
parsed = tok.items[0]
if parsed.type == "OPERAND":
return parsed.subtype
return parsed.type
@property
def destinations(self):
if self.type == "RANGE":
tok = Tokenizer("=" + self.value)
for part in tok.items:
if part.subtype == "RANGE":
m = SHEETRANGE_RE.match(part.value)
sheetname = m.group('notquoted') or m.group('quoted')
yield sheetname, m.group('cells')
@property
def is_reserved(self):
m = RESERVED_REGEX.match(self.name)
if m:
return m.group("name")
@property
def is_external(self):
return re.compile(r"^\[\d+\].*").match(self.value) is not None
def __iter__(self):
for key in self.__attrs__:
if key == "attr_text":
continue
v = getattr(self, key)
if v is not None:
if v in RESERVED:
v = "_xlnm." + v
yield key, safe_string(v)
class DefinedNameDict(dict):
"""
Utility class for storing defined names.
Allows access by name and separation of global and scoped names
"""
def __setitem__(self, key, value):
if not isinstance(value, DefinedName):
raise TypeError("Value must be a an instance of DefinedName")
elif value.name != key:
raise ValueError("Key must be the same as the name")
super().__setitem__(key, value)
def add(self, value):
"""
Add names without worrying about key and name matching.
"""
self[value.name] = value
class DefinedNameList(Serialisable):
tagname = "definedNames"
definedName = Sequence(expected_type=DefinedName)
def __init__(self, definedName=()):
self.definedName = definedName
def by_sheet(self):
"""
Break names down into sheet locals and globals
"""
names = defaultdict(DefinedNameDict)
for defn in self.definedName:
if defn.localSheetId is None:
if defn.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area", "_xlnm._FilterDatabase"):
continue
names["global"][defn.name] = defn
else:
sheet = int(defn.localSheetId)
names[sheet][defn.name] = defn
return names
def _duplicate(self, defn):
"""
Check for whether DefinedName with the same name and scope already
exists
"""
for d in self.definedName:
if d.name == defn.name and d.localSheetId == defn.localSheetId:
return True
def __len__(self):
return len(self.definedName)

View File

@ -0,0 +1,3 @@
# Copyright (c) 2010-2024 openpyxl
from .external import ExternalLink

View File

@ -0,0 +1,190 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
String,
Bool,
Integer,
NoneSet,
Sequence,
)
from openpyxl.descriptors.excel import Relation
from openpyxl.descriptors.nested import NestedText
from openpyxl.descriptors.sequence import NestedSequence, ValueSequence
from openpyxl.packaging.relationship import (
Relationship,
get_rels_path,
get_dependents
)
from openpyxl.xml.constants import SHEET_MAIN_NS
from openpyxl.xml.functions import fromstring
"""Manage links to external Workbooks"""
class ExternalCell(Serialisable):
r = String()
t = NoneSet(values=(['b', 'd', 'n', 'e', 's', 'str', 'inlineStr']))
vm = Integer(allow_none=True)
v = NestedText(allow_none=True, expected_type=str)
def __init__(self,
r=None,
t=None,
vm=None,
v=None,
):
self.r = r
self.t = t
self.vm = vm
self.v = v
class ExternalRow(Serialisable):
r = Integer()
cell = Sequence(expected_type=ExternalCell)
__elements__ = ('cell',)
def __init__(self,
r=(),
cell=None,
):
self.r = r
self.cell = cell
class ExternalSheetData(Serialisable):
sheetId = Integer()
refreshError = Bool(allow_none=True)
row = Sequence(expected_type=ExternalRow)
__elements__ = ('row',)
def __init__(self,
sheetId=None,
refreshError=None,
row=(),
):
self.sheetId = sheetId
self.refreshError = refreshError
self.row = row
class ExternalSheetDataSet(Serialisable):
sheetData = Sequence(expected_type=ExternalSheetData, )
__elements__ = ('sheetData',)
def __init__(self,
sheetData=None,
):
self.sheetData = sheetData
class ExternalSheetNames(Serialisable):
sheetName = ValueSequence(expected_type=str)
__elements__ = ('sheetName',)
def __init__(self,
sheetName=(),
):
self.sheetName = sheetName
class ExternalDefinedName(Serialisable):
tagname = "definedName"
name = String()
refersTo = String(allow_none=True)
sheetId = Integer(allow_none=True)
def __init__(self,
name=None,
refersTo=None,
sheetId=None,
):
self.name = name
self.refersTo = refersTo
self.sheetId = sheetId
class ExternalBook(Serialisable):
tagname = "externalBook"
sheetNames = Typed(expected_type=ExternalSheetNames, allow_none=True)
definedNames = NestedSequence(expected_type=ExternalDefinedName)
sheetDataSet = Typed(expected_type=ExternalSheetDataSet, allow_none=True)
id = Relation()
__elements__ = ('sheetNames', 'definedNames', 'sheetDataSet')
def __init__(self,
sheetNames=None,
definedNames=(),
sheetDataSet=None,
id=None,
):
self.sheetNames = sheetNames
self.definedNames = definedNames
self.sheetDataSet = sheetDataSet
self.id = id
class ExternalLink(Serialisable):
tagname = "externalLink"
_id = None
_path = "/xl/externalLinks/externalLink{0}.xml"
_rel_type = "externalLink"
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"
externalBook = Typed(expected_type=ExternalBook, allow_none=True)
file_link = Typed(expected_type=Relationship, allow_none=True) # link to external file
__elements__ = ('externalBook', )
def __init__(self,
externalBook=None,
ddeLink=None,
oleLink=None,
extLst=None,
):
self.externalBook = externalBook
# ignore other items for the moment.
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 read_external_link(archive, book_path):
src = archive.read(book_path)
node = fromstring(src)
book = ExternalLink.from_tree(node)
link_path = get_rels_path(book_path)
deps = get_dependents(archive, link_path)
book.file_link = deps[0]
return book

View File

@ -0,0 +1,18 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence
)
from openpyxl.descriptors.excel import (
Relation,
)
class ExternalReference(Serialisable):
tagname = "externalReference"
id = Relation()
def __init__(self, id):
self.id = id

View File

@ -0,0 +1,36 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence,
String,
Integer,
)
class FunctionGroup(Serialisable):
tagname = "functionGroup"
name = String()
def __init__(self,
name=None,
):
self.name = name
class FunctionGroupList(Serialisable):
tagname = "functionGroups"
builtInGroupCount = Integer(allow_none=True)
functionGroup = Sequence(expected_type=FunctionGroup, allow_none=True)
__elements__ = ('functionGroup',)
def __init__(self,
builtInGroupCount=16,
functionGroup=(),
):
self.builtInGroupCount = builtInGroupCount
self.functionGroup = functionGroup

View File

@ -0,0 +1,151 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
String,
Float,
Integer,
Bool,
NoneSet,
Set,
)
from openpyxl.descriptors.excel import Guid
class WorkbookProperties(Serialisable):
tagname = "workbookPr"
date1904 = Bool(allow_none=True)
dateCompatibility = Bool(allow_none=True)
showObjects = NoneSet(values=(['all', 'placeholders']))
showBorderUnselectedTables = Bool(allow_none=True)
filterPrivacy = Bool(allow_none=True)
promptedSolutions = Bool(allow_none=True)
showInkAnnotation = Bool(allow_none=True)
backupFile = Bool(allow_none=True)
saveExternalLinkValues = Bool(allow_none=True)
updateLinks = NoneSet(values=(['userSet', 'never', 'always']))
codeName = String(allow_none=True)
hidePivotFieldList = Bool(allow_none=True)
showPivotChartFilter = Bool(allow_none=True)
allowRefreshQuery = Bool(allow_none=True)
publishItems = Bool(allow_none=True)
checkCompatibility = Bool(allow_none=True)
autoCompressPictures = Bool(allow_none=True)
refreshAllConnections = Bool(allow_none=True)
defaultThemeVersion = Integer(allow_none=True)
def __init__(self,
date1904=None,
dateCompatibility=None,
showObjects=None,
showBorderUnselectedTables=None,
filterPrivacy=None,
promptedSolutions=None,
showInkAnnotation=None,
backupFile=None,
saveExternalLinkValues=None,
updateLinks=None,
codeName=None,
hidePivotFieldList=None,
showPivotChartFilter=None,
allowRefreshQuery=None,
publishItems=None,
checkCompatibility=None,
autoCompressPictures=None,
refreshAllConnections=None,
defaultThemeVersion=None,
):
self.date1904 = date1904
self.dateCompatibility = dateCompatibility
self.showObjects = showObjects
self.showBorderUnselectedTables = showBorderUnselectedTables
self.filterPrivacy = filterPrivacy
self.promptedSolutions = promptedSolutions
self.showInkAnnotation = showInkAnnotation
self.backupFile = backupFile
self.saveExternalLinkValues = saveExternalLinkValues
self.updateLinks = updateLinks
self.codeName = codeName
self.hidePivotFieldList = hidePivotFieldList
self.showPivotChartFilter = showPivotChartFilter
self.allowRefreshQuery = allowRefreshQuery
self.publishItems = publishItems
self.checkCompatibility = checkCompatibility
self.autoCompressPictures = autoCompressPictures
self.refreshAllConnections = refreshAllConnections
self.defaultThemeVersion = defaultThemeVersion
class CalcProperties(Serialisable):
tagname = "calcPr"
calcId = Integer()
calcMode = NoneSet(values=(['manual', 'auto', 'autoNoTable']))
fullCalcOnLoad = Bool(allow_none=True)
refMode = NoneSet(values=(['A1', 'R1C1']))
iterate = Bool(allow_none=True)
iterateCount = Integer(allow_none=True)
iterateDelta = Float(allow_none=True)
fullPrecision = Bool(allow_none=True)
calcCompleted = Bool(allow_none=True)
calcOnSave = Bool(allow_none=True)
concurrentCalc = Bool(allow_none=True)
concurrentManualCount = Integer(allow_none=True)
forceFullCalc = Bool(allow_none=True)
def __init__(self,
calcId=124519,
calcMode=None,
fullCalcOnLoad=True,
refMode=None,
iterate=None,
iterateCount=None,
iterateDelta=None,
fullPrecision=None,
calcCompleted=None,
calcOnSave=None,
concurrentCalc=None,
concurrentManualCount=None,
forceFullCalc=None,
):
self.calcId = calcId
self.calcMode = calcMode
self.fullCalcOnLoad = fullCalcOnLoad
self.refMode = refMode
self.iterate = iterate
self.iterateCount = iterateCount
self.iterateDelta = iterateDelta
self.fullPrecision = fullPrecision
self.calcCompleted = calcCompleted
self.calcOnSave = calcOnSave
self.concurrentCalc = concurrentCalc
self.concurrentManualCount = concurrentManualCount
self.forceFullCalc = forceFullCalc
class FileVersion(Serialisable):
tagname = "fileVersion"
appName = String(allow_none=True)
lastEdited = String(allow_none=True)
lowestEdited = String(allow_none=True)
rupBuild = String(allow_none=True)
codeName = Guid(allow_none=True)
def __init__(self,
appName=None,
lastEdited=None,
lowestEdited=None,
rupBuild=None,
codeName=None,
):
self.appName = appName
self.lastEdited = lastEdited
self.lowestEdited = lowestEdited
self.rupBuild = rupBuild
self.codeName = codeName

View File

@ -0,0 +1,163 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Alias,
Typed,
String,
Float,
Integer,
Bool,
NoneSet,
Set,
)
from openpyxl.descriptors.excel import (
ExtensionList,
HexBinary,
Guid,
Relation,
Base64Binary,
)
from openpyxl.utils.protection import hash_password
class WorkbookProtection(Serialisable):
_workbook_password, _revisions_password = None, None
tagname = "workbookPr"
workbook_password = Alias("workbookPassword")
workbookPasswordCharacterSet = String(allow_none=True)
revision_password = Alias("revisionsPassword")
revisionsPasswordCharacterSet = String(allow_none=True)
lockStructure = Bool(allow_none=True)
lock_structure = Alias("lockStructure")
lockWindows = Bool(allow_none=True)
lock_windows = Alias("lockWindows")
lockRevision = Bool(allow_none=True)
lock_revision = Alias("lockRevision")
revisionsAlgorithmName = String(allow_none=True)
revisionsHashValue = Base64Binary(allow_none=True)
revisionsSaltValue = Base64Binary(allow_none=True)
revisionsSpinCount = Integer(allow_none=True)
workbookAlgorithmName = String(allow_none=True)
workbookHashValue = Base64Binary(allow_none=True)
workbookSaltValue = Base64Binary(allow_none=True)
workbookSpinCount = Integer(allow_none=True)
__attrs__ = ('workbookPassword', 'workbookPasswordCharacterSet', 'revisionsPassword',
'revisionsPasswordCharacterSet', 'lockStructure', 'lockWindows', 'lockRevision',
'revisionsAlgorithmName', 'revisionsHashValue', 'revisionsSaltValue',
'revisionsSpinCount', 'workbookAlgorithmName', 'workbookHashValue',
'workbookSaltValue', 'workbookSpinCount')
def __init__(self,
workbookPassword=None,
workbookPasswordCharacterSet=None,
revisionsPassword=None,
revisionsPasswordCharacterSet=None,
lockStructure=None,
lockWindows=None,
lockRevision=None,
revisionsAlgorithmName=None,
revisionsHashValue=None,
revisionsSaltValue=None,
revisionsSpinCount=None,
workbookAlgorithmName=None,
workbookHashValue=None,
workbookSaltValue=None,
workbookSpinCount=None,
):
if workbookPassword is not None:
self.workbookPassword = workbookPassword
self.workbookPasswordCharacterSet = workbookPasswordCharacterSet
if revisionsPassword is not None:
self.revisionsPassword = revisionsPassword
self.revisionsPasswordCharacterSet = revisionsPasswordCharacterSet
self.lockStructure = lockStructure
self.lockWindows = lockWindows
self.lockRevision = lockRevision
self.revisionsAlgorithmName = revisionsAlgorithmName
self.revisionsHashValue = revisionsHashValue
self.revisionsSaltValue = revisionsSaltValue
self.revisionsSpinCount = revisionsSpinCount
self.workbookAlgorithmName = workbookAlgorithmName
self.workbookHashValue = workbookHashValue
self.workbookSaltValue = workbookSaltValue
self.workbookSpinCount = workbookSpinCount
def set_workbook_password(self, value='', already_hashed=False):
"""Set a password on this workbook."""
if not already_hashed:
value = hash_password(value)
self._workbook_password = value
@property
def workbookPassword(self):
"""Return the workbook password value, regardless of hash."""
return self._workbook_password
@workbookPassword.setter
def workbookPassword(self, value):
"""Set a workbook password directly, forcing a hash step."""
self.set_workbook_password(value)
def set_revisions_password(self, value='', already_hashed=False):
"""Set a revision password on this workbook."""
if not already_hashed:
value = hash_password(value)
self._revisions_password = value
@property
def revisionsPassword(self):
"""Return the revisions password value, regardless of hash."""
return self._revisions_password
@revisionsPassword.setter
def revisionsPassword(self, value):
"""Set a revisions password directly, forcing a hash step."""
self.set_revisions_password(value)
@classmethod
def from_tree(cls, node):
"""Don't hash passwords when deserialising from XML"""
self = super().from_tree(node)
if self.workbookPassword:
self.set_workbook_password(node.get('workbookPassword'), already_hashed=True)
if self.revisionsPassword:
self.set_revisions_password(node.get('revisionsPassword'), already_hashed=True)
return self
# Backwards compatibility
DocumentSecurity = WorkbookProtection
class FileSharing(Serialisable):
tagname = "fileSharing"
readOnlyRecommended = Bool(allow_none=True)
userName = String(allow_none=True)
reservationPassword = HexBinary(allow_none=True)
algorithmName = String(allow_none=True)
hashValue = Base64Binary(allow_none=True)
saltValue = Base64Binary(allow_none=True)
spinCount = Integer(allow_none=True)
def __init__(self,
readOnlyRecommended=None,
userName=None,
reservationPassword=None,
algorithmName=None,
hashValue=None,
saltValue=None,
spinCount=None,
):
self.readOnlyRecommended = readOnlyRecommended
self.userName = userName
self.reservationPassword = reservationPassword
self.algorithmName = algorithmName
self.hashValue = hashValue
self.saltValue = saltValue
self.spinCount = spinCount

View File

@ -0,0 +1,56 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Sequence,
String,
Bool,
NoneSet,
)
class SmartTag(Serialisable):
tagname = "smartTagType"
namespaceUri = String(allow_none=True)
name = String(allow_none=True)
url = String(allow_none=True)
def __init__(self,
namespaceUri=None,
name=None,
url=None,
):
self.namespaceUri = namespaceUri
self.name = name
self.url = url
class SmartTagList(Serialisable):
tagname = "smartTagTypes"
smartTagType = Sequence(expected_type=SmartTag, allow_none=True)
__elements__ = ('smartTagType',)
def __init__(self,
smartTagType=(),
):
self.smartTagType = smartTagType
class SmartTagProperties(Serialisable):
tagname = "smartTagPr"
embed = Bool(allow_none=True)
show = NoneSet(values=(['all', 'noIndicator']))
def __init__(self,
embed=None,
show=None,
):
self.embed = embed
self.show = show

View File

@ -0,0 +1,155 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Sequence,
String,
Float,
Integer,
Bool,
NoneSet,
Set,
)
from openpyxl.descriptors.excel import (
ExtensionList,
Guid,
)
class BookView(Serialisable):
tagname = "workbookView"
visibility = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
minimized = Bool(allow_none=True)
showHorizontalScroll = Bool(allow_none=True)
showVerticalScroll = Bool(allow_none=True)
showSheetTabs = Bool(allow_none=True)
xWindow = Integer(allow_none=True)
yWindow = Integer(allow_none=True)
windowWidth = Integer(allow_none=True)
windowHeight = Integer(allow_none=True)
tabRatio = Integer(allow_none=True)
firstSheet = Integer(allow_none=True)
activeTab = Integer(allow_none=True)
autoFilterDateGrouping = Bool(allow_none=True)
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
visibility="visible",
minimized=False,
showHorizontalScroll=True,
showVerticalScroll=True,
showSheetTabs=True,
xWindow=None,
yWindow=None,
windowWidth=None,
windowHeight=None,
tabRatio=600,
firstSheet=0,
activeTab=0,
autoFilterDateGrouping=True,
extLst=None,
):
self.visibility = visibility
self.minimized = minimized
self.showHorizontalScroll = showHorizontalScroll
self.showVerticalScroll = showVerticalScroll
self.showSheetTabs = showSheetTabs
self.xWindow = xWindow
self.yWindow = yWindow
self.windowWidth = windowWidth
self.windowHeight = windowHeight
self.tabRatio = tabRatio
self.firstSheet = firstSheet
self.activeTab = activeTab
self.autoFilterDateGrouping = autoFilterDateGrouping
class CustomWorkbookView(Serialisable):
tagname = "customWorkbookView"
name = String()
guid = Guid()
autoUpdate = Bool(allow_none=True)
mergeInterval = Integer(allow_none=True)
changesSavedWin = Bool(allow_none=True)
onlySync = Bool(allow_none=True)
personalView = Bool(allow_none=True)
includePrintSettings = Bool(allow_none=True)
includeHiddenRowCol = Bool(allow_none=True)
maximized = Bool(allow_none=True)
minimized = Bool(allow_none=True)
showHorizontalScroll = Bool(allow_none=True)
showVerticalScroll = Bool(allow_none=True)
showSheetTabs = Bool(allow_none=True)
xWindow = Integer(allow_none=True)
yWindow = Integer(allow_none=True)
windowWidth = Integer()
windowHeight = Integer()
tabRatio = Integer(allow_none=True)
activeSheetId = Integer()
showFormulaBar = Bool(allow_none=True)
showStatusbar = Bool(allow_none=True)
showComments = NoneSet(values=(['commNone', 'commIndicator',
'commIndAndComment']))
showObjects = NoneSet(values=(['all', 'placeholders']))
extLst = Typed(expected_type=ExtensionList, allow_none=True)
__elements__ = ()
def __init__(self,
name=None,
guid=None,
autoUpdate=None,
mergeInterval=None,
changesSavedWin=None,
onlySync=None,
personalView=None,
includePrintSettings=None,
includeHiddenRowCol=None,
maximized=None,
minimized=None,
showHorizontalScroll=None,
showVerticalScroll=None,
showSheetTabs=None,
xWindow=None,
yWindow=None,
windowWidth=None,
windowHeight=None,
tabRatio=None,
activeSheetId=None,
showFormulaBar=None,
showStatusbar=None,
showComments="commIndicator",
showObjects="all",
extLst=None,
):
self.name = name
self.guid = guid
self.autoUpdate = autoUpdate
self.mergeInterval = mergeInterval
self.changesSavedWin = changesSavedWin
self.onlySync = onlySync
self.personalView = personalView
self.includePrintSettings = includePrintSettings
self.includeHiddenRowCol = includeHiddenRowCol
self.maximized = maximized
self.minimized = minimized
self.showHorizontalScroll = showHorizontalScroll
self.showVerticalScroll = showVerticalScroll
self.showSheetTabs = showSheetTabs
self.xWindow = xWindow
self.yWindow = yWindow
self.windowWidth = windowWidth
self.windowHeight = windowHeight
self.tabRatio = tabRatio
self.activeSheetId = activeSheetId
self.showFormulaBar = showFormulaBar
self.showStatusbar = showStatusbar
self.showComments = showComments
self.showObjects = showObjects

View File

@ -0,0 +1,98 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Sequence,
String,
Float,
Integer,
Bool,
NoneSet,
)
class WebPublishObject(Serialisable):
tagname = "webPublishingObject"
id = Integer()
divId = String()
sourceObject = String(allow_none=True)
destinationFile = String()
title = String(allow_none=True)
autoRepublish = Bool(allow_none=True)
def __init__(self,
id=None,
divId=None,
sourceObject=None,
destinationFile=None,
title=None,
autoRepublish=None,
):
self.id = id
self.divId = divId
self.sourceObject = sourceObject
self.destinationFile = destinationFile
self.title = title
self.autoRepublish = autoRepublish
class WebPublishObjectList(Serialisable):
tagname ="webPublishingObjects"
count = Integer(allow_none=True)
webPublishObject = Sequence(expected_type=WebPublishObject)
__elements__ = ('webPublishObject',)
def __init__(self,
count=None,
webPublishObject=(),
):
self.webPublishObject = webPublishObject
@property
def count(self):
return len(self.webPublishObject)
class WebPublishing(Serialisable):
tagname = "webPublishing"
css = Bool(allow_none=True)
thicket = Bool(allow_none=True)
longFileNames = Bool(allow_none=True)
vml = Bool(allow_none=True)
allowPng = Bool(allow_none=True)
targetScreenSize = NoneSet(values=(['544x376', '640x480', '720x512', '800x600',
'1024x768', '1152x882', '1152x900', '1280x1024', '1600x1200',
'1800x1440', '1920x1200']))
dpi = Integer(allow_none=True)
codePage = Integer(allow_none=True)
characterSet = String(allow_none=True)
def __init__(self,
css=None,
thicket=None,
longFileNames=None,
vml=None,
allowPng=None,
targetScreenSize='800x600',
dpi=None,
codePage=None,
characterSet=None,
):
self.css = css
self.thicket = thicket
self.longFileNames = longFileNames
self.vml = vml
self.allowPng = allowPng
self.targetScreenSize = targetScreenSize
self.dpi = dpi
self.codePage = codePage
self.characterSet = characterSet

View File

@ -0,0 +1,438 @@
# Copyright (c) 2010-2024 openpyxl
"""Workbook is the top-level container for all document information."""
from copy import copy
from openpyxl.compat import deprecated
from openpyxl.worksheet.worksheet import Worksheet
from openpyxl.worksheet._read_only import ReadOnlyWorksheet
from openpyxl.worksheet._write_only import WriteOnlyWorksheet
from openpyxl.worksheet.copier import WorksheetCopy
from openpyxl.utils import quote_sheetname
from openpyxl.utils.indexed_list import IndexedList
from openpyxl.utils.datetime import WINDOWS_EPOCH, MAC_EPOCH
from openpyxl.utils.exceptions import ReadOnlyWorkbookException
from openpyxl.writer.excel import save_workbook
from openpyxl.styles.cell_style import StyleArray
from openpyxl.styles.named_styles import NamedStyle
from openpyxl.styles.differential import DifferentialStyleList
from openpyxl.styles.alignment import Alignment
from openpyxl.styles.borders import DEFAULT_BORDER
from openpyxl.styles.fills import DEFAULT_EMPTY_FILL, DEFAULT_GRAY_FILL
from openpyxl.styles.fonts import DEFAULT_FONT
from openpyxl.styles.protection import Protection
from openpyxl.styles.colors import COLOR_INDEX
from openpyxl.styles.named_styles import NamedStyleList
from openpyxl.styles.table import TableStyleList
from openpyxl.chartsheet import Chartsheet
from .defined_name import DefinedName, DefinedNameDict
from openpyxl.packaging.core import DocumentProperties
from openpyxl.packaging.custom import CustomPropertyList
from openpyxl.packaging.relationship import RelationshipList
from .child import _WorkbookChild
from .protection import DocumentSecurity
from .properties import CalcProperties
from .views import BookView
from openpyxl.xml.constants import (
XLSM,
XLSX,
XLTM,
XLTX
)
INTEGER_TYPES = (int,)
class Workbook:
"""Workbook is the container for all other parts of the document."""
_read_only = False
_data_only = False
template = False
path = "/xl/workbook.xml"
def __init__(self,
write_only=False,
iso_dates=False,
):
self._sheets = []
self._pivots = []
self._active_sheet_index = 0
self.defined_names = DefinedNameDict()
self._external_links = []
self.properties = DocumentProperties()
self.custom_doc_props = CustomPropertyList()
self.security = DocumentSecurity()
self.__write_only = write_only
self.shared_strings = IndexedList()
self._setup_styles()
self.loaded_theme = None
self.vba_archive = None
self.is_template = False
self.code_name = None
self.epoch = WINDOWS_EPOCH
self.encoding = "utf-8"
self.iso_dates = iso_dates
if not self.write_only:
self._sheets.append(Worksheet(self))
self.rels = RelationshipList()
self.calculation = CalcProperties()
self.views = [BookView()]
def _setup_styles(self):
"""Bootstrap styles"""
self._fonts = IndexedList()
self._fonts.add(DEFAULT_FONT)
self._alignments = IndexedList([Alignment()])
self._borders = IndexedList()
self._borders.add(DEFAULT_BORDER)
self._fills = IndexedList()
self._fills.add(DEFAULT_EMPTY_FILL)
self._fills.add(DEFAULT_GRAY_FILL)
self._number_formats = IndexedList()
self._date_formats = {}
self._timedelta_formats = {}
self._protections = IndexedList([Protection()])
self._colors = COLOR_INDEX
self._cell_styles = IndexedList([StyleArray()])
self._named_styles = NamedStyleList()
self.add_named_style(NamedStyle(font=copy(DEFAULT_FONT), border=copy(DEFAULT_BORDER), builtinId=0))
self._table_styles = TableStyleList()
self._differential_styles = DifferentialStyleList()
@property
def epoch(self):
if self._epoch == WINDOWS_EPOCH:
return WINDOWS_EPOCH
return MAC_EPOCH
@epoch.setter
def epoch(self, value):
if value not in (WINDOWS_EPOCH, MAC_EPOCH):
raise ValueError("The epoch must be either 1900 or 1904")
self._epoch = value
@property
def read_only(self):
return self._read_only
@property
def data_only(self):
return self._data_only
@property
def write_only(self):
return self.__write_only
@property
def excel_base_date(self):
return self.epoch
@property
def active(self):
"""Get the currently active sheet or None
:type: :class:`openpyxl.worksheet.worksheet.Worksheet`
"""
try:
return self._sheets[self._active_sheet_index]
except IndexError:
pass
@active.setter
def active(self, value):
"""Set the active sheet"""
if not isinstance(value, (_WorkbookChild, INTEGER_TYPES)):
raise TypeError("Value must be either a worksheet, chartsheet or numerical index")
if isinstance(value, INTEGER_TYPES):
self._active_sheet_index = value
return
#if self._sheets and 0 <= value < len(self._sheets):
#value = self._sheets[value]
#else:
#raise ValueError("Sheet index is outside the range of possible values", value)
if value not in self._sheets:
raise ValueError("Worksheet is not in the workbook")
if value.sheet_state != "visible":
raise ValueError("Only visible sheets can be made active")
idx = self._sheets.index(value)
self._active_sheet_index = idx
def create_sheet(self, title=None, index=None):
"""Create a worksheet (at an optional index).
:param title: optional title of the sheet
:type title: str
:param index: optional position at which the sheet will be inserted
:type index: int
"""
if self.read_only:
raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook')
if self.write_only :
new_ws = WriteOnlyWorksheet(parent=self, title=title)
else:
new_ws = Worksheet(parent=self, title=title)
self._add_sheet(sheet=new_ws, index=index)
return new_ws
def _add_sheet(self, sheet, index=None):
"""Add an worksheet (at an optional index)."""
if not isinstance(sheet, (Worksheet, WriteOnlyWorksheet, Chartsheet)):
raise TypeError("Cannot be added to a workbook")
if sheet.parent != self:
raise ValueError("You cannot add worksheets from another workbook.")
if index is None:
self._sheets.append(sheet)
else:
self._sheets.insert(index, sheet)
def move_sheet(self, sheet, offset=0):
"""
Move a sheet or sheetname
"""
if not isinstance(sheet, Worksheet):
sheet = self[sheet]
idx = self._sheets.index(sheet)
del self._sheets[idx]
new_pos = idx + offset
self._sheets.insert(new_pos, sheet)
def remove(self, worksheet):
"""Remove `worksheet` from this workbook."""
idx = self._sheets.index(worksheet)
self._sheets.remove(worksheet)
@deprecated("Use wb.remove(worksheet) or del wb[sheetname]")
def remove_sheet(self, worksheet):
"""Remove `worksheet` from this workbook."""
self.remove(worksheet)
def create_chartsheet(self, title=None, index=None):
if self.read_only:
raise ReadOnlyWorkbookException("Cannot create new sheet in a read-only workbook")
cs = Chartsheet(parent=self, title=title)
self._add_sheet(cs, index)
return cs
@deprecated("Use wb[sheetname]")
def get_sheet_by_name(self, name):
"""Returns a worksheet by its name.
:param name: the name of the worksheet to look for
:type name: string
"""
return self[name]
def __contains__(self, key):
return key in self.sheetnames
def index(self, worksheet):
"""Return the index of a worksheet."""
return self.worksheets.index(worksheet)
@deprecated("Use wb.index(worksheet)")
def get_index(self, worksheet):
"""Return the index of the worksheet."""
return self.index(worksheet)
def __getitem__(self, key):
"""Returns a worksheet by its name.
:param name: the name of the worksheet to look for
:type name: string
"""
for sheet in self.worksheets + self.chartsheets:
if sheet.title == key:
return sheet
raise KeyError("Worksheet {0} does not exist.".format(key))
def __delitem__(self, key):
sheet = self[key]
self.remove(sheet)
def __iter__(self):
return iter(self.worksheets)
@deprecated("Use wb.sheetnames")
def get_sheet_names(self):
return self.sheetnames
@property
def worksheets(self):
"""A list of sheets in this workbook
:type: list of :class:`openpyxl.worksheet.worksheet.Worksheet`
"""
return [s for s in self._sheets if isinstance(s, (Worksheet, ReadOnlyWorksheet, WriteOnlyWorksheet))]
@property
def chartsheets(self):
"""A list of Chartsheets in this workbook
:type: list of :class:`openpyxl.chartsheet.chartsheet.Chartsheet`
"""
return [s for s in self._sheets if isinstance(s, Chartsheet)]
@property
def sheetnames(self):
"""Returns the list of the names of worksheets in this workbook.
Names are returned in the worksheets order.
:type: list of strings
"""
return [s.title for s in self._sheets]
@deprecated("Assign scoped named ranges directly to worksheets or global ones to the workbook. Deprecated in 3.1")
def create_named_range(self, name, worksheet=None, value=None, scope=None):
"""Create a new named_range on a worksheet
"""
defn = DefinedName(name=name)
if worksheet is not None:
defn.value = "{0}!{1}".format(quote_sheetname(worksheet.title), value)
else:
defn.value = value
self.defined_names[name] = defn
def add_named_style(self, style):
"""
Add a named style
"""
self._named_styles.append(style)
style.bind(self)
@property
def named_styles(self):
"""
List available named styles
"""
return self._named_styles.names
@property
def mime_type(self):
"""
The mime type is determined by whether a workbook is a template or
not and whether it contains macros or not. Excel requires the file
extension to match but openpyxl does not enforce this.
"""
ct = self.template and XLTX or XLSX
if self.vba_archive:
ct = self.template and XLTM or XLSM
return ct
def save(self, filename):
"""Save the current workbook under the given `filename`.
Use this function instead of using an `ExcelWriter`.
.. warning::
When creating your workbook using `write_only` set to True,
you will only be able to call this function once. Subsequent attempts to
modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception.
"""
if self.read_only:
raise TypeError("""Workbook is read-only""")
if self.write_only and not self.worksheets:
self.create_sheet()
save_workbook(self, filename)
@property
def style_names(self):
"""
List of named styles
"""
return [s.name for s in self._named_styles]
def copy_worksheet(self, from_worksheet):
"""Copy an existing worksheet in the current workbook
.. warning::
This function cannot copy worksheets between workbooks.
worksheets can only be copied within the workbook that they belong
:param from_worksheet: the worksheet to be copied from
:return: copy of the initial worksheet
"""
if self.__write_only or self._read_only:
raise ValueError("Cannot copy worksheets in read-only or write-only mode")
new_title = u"{0} Copy".format(from_worksheet.title)
to_worksheet = self.create_sheet(title=new_title)
cp = WorksheetCopy(source_worksheet=from_worksheet, target_worksheet=to_worksheet)
cp.copy_worksheet()
return to_worksheet
def close(self):
"""
Close workbook file if open. Only affects read-only and write-only modes.
"""
if hasattr(self, '_archive'):
self._archive.close()
def _duplicate_name(self, name):
"""
Check for duplicate name in defined name list and table list of each worksheet.
Names are not case sensitive.
"""
name = name.lower()
for sheet in self.worksheets:
for t in sheet.tables:
if name == t.lower():
return True
if name in self.defined_names:
return True