diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py new file mode 100644 index 00000000..4318282d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py @@ -0,0 +1,158 @@ +# Copyright (c) 2010-2024 openpyxl + +import posixpath +from warnings import warn + +from openpyxl.descriptors import ( + String, + Alias, + Sequence, +) +from openpyxl.descriptors.serialisable import Serialisable +from openpyxl.descriptors.container import ElementList + +from openpyxl.xml.constants import REL_NS, PKG_REL_NS +from openpyxl.xml.functions import ( + Element, + fromstring, +) + + +class Relationship(Serialisable): + """Represents many kinds of relationships.""" + + tagname = "Relationship" + + Type = String() + Target = String() + target = Alias("Target") + TargetMode = String(allow_none=True) + Id = String(allow_none=True) + id = Alias("Id") + + + def __init__(self, + Id=None, + Type=None, + type=None, + Target=None, + TargetMode=None + ): + """ + `type` can be used as a shorthand with the default relationships namespace + otherwise the `Type` must be a fully qualified URL + """ + if type is not None: + Type = "{0}/{1}".format(REL_NS, type) + self.Type = Type + self.Target = Target + self.TargetMode = TargetMode + self.Id = Id + + +class RelationshipList(ElementList): + + tagname = "Relationships" + expected_type = Relationship + + + def append(self, value): + super().append(value) + if not value.Id: + value.Id = f"rId{len(self)}" + + + def find(self, content_type): + """ + Find relationships by content-type + NB. these content-types namespaced objects and different to the MIME-types + in the package manifest :-( + """ + for r in self: + if r.Type == content_type: + yield r + + + def get(self, key): + for r in self: + if r.Id == key: + return r + raise KeyError("Unknown relationship: {0}".format(key)) + + + def to_dict(self): + """Return a dictionary of relations keyed by id""" + return {r.id:r for r in self} + + + def to_tree(self): + tree = super().to_tree() + tree.set("xmlns", PKG_REL_NS) + return tree + + +def get_rels_path(path): + """ + Convert relative path to absolutes that can be loaded from a zip + archive. + The path to be passed in is that of containing object (workbook, + worksheet, etc.) + """ + folder, obj = posixpath.split(path) + filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj)) + return filename + + +def get_dependents(archive, filename): + """ + Normalise dependency file paths to absolute ones + + Relative paths are relative to parent object + """ + src = archive.read(filename) + node = fromstring(src) + try: + rels = RelationshipList.from_tree(node) + except TypeError: + msg = "{0} contains invalid dependency definitions".format(filename) + warn(msg) + rels = RelationshipList() + folder = posixpath.dirname(filename) + parent = posixpath.split(folder)[0] + for r in rels: + if r.TargetMode == "External": + continue + elif r.target.startswith("/"): + r.target = r.target[1:] + else: + pth = posixpath.join(parent, r.target) + r.target = posixpath.normpath(pth) + return rels + + +def get_rel(archive, deps, id=None, cls=None): + """ + Get related object based on id or rel_type + """ + if not any([id, cls]): + raise ValueError("Either the id or the content type are required") + if id is not None: + rel = deps.get(id) + else: + try: + rel = next(deps.find(cls.rel_type)) + except StopIteration: # no known dependency + return + + path = rel.target + src = archive.read(path) + tree = fromstring(src) + obj = cls.from_tree(tree) + + rels_path = get_rels_path(path) + try: + obj.deps = get_dependents(archive, rels_path) + except KeyError: + obj.deps = [] + + return obj |