You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
5.3 KiB
Python

# encoding: utf-8
"""
Relationship-related objects.
"""
from __future__ import (
absolute_import, division, print_function, unicode_literals
)
from .oxml import CT_Relationships
class Relationships(dict):
"""
Collection object for |_Relationship| instances, having list semantics.
"""
def __init__(self, baseURI):
super(Relationships, self).__init__()
self._baseURI = baseURI
self._target_parts_by_rId = {}
def add_relationship(self, reltype, target, rId, is_external=False):
"""
Return a newly added |_Relationship| instance.
"""
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
self[rId] = rel
if not is_external:
self._target_parts_by_rId[rId] = target
return rel
def get_or_add(self, reltype, target_part):
"""
Return relationship of *reltype* to *target_part*, newly added if not
already present in collection.
"""
rel = self._get_matching(reltype, target_part)
if rel is None:
rId = self._next_rId
rel = self.add_relationship(reltype, target_part, rId)
return rel
def get_or_add_ext_rel(self, reltype, target_ref):
"""
Return rId of external relationship of *reltype* to *target_ref*,
newly added if not already present in collection.
"""
rel = self._get_matching(reltype, target_ref, is_external=True)
if rel is None:
rId = self._next_rId
rel = self.add_relationship(
reltype, target_ref, rId, is_external=True
)
return rel.rId
def part_with_reltype(self, reltype):
"""
Return target part of rel with matching *reltype*, raising |KeyError|
if not found and |ValueError| if more than one matching relationship
is found.
"""
rel = self._get_rel_of_type(reltype)
return rel.target_part
@property
def related_parts(self):
"""
dict mapping rIds to target parts for all the internal relationships
in the collection.
"""
return self._target_parts_by_rId
@property
def xml(self):
"""
Serialize this relationship collection into XML suitable for storage
as a .rels file in an OPC package.
"""
rels_elm = CT_Relationships.new()
for rel in self.values():
rels_elm.add_rel(
rel.rId, rel.reltype, rel.target_ref, rel.is_external
)
return rels_elm.xml
def _get_matching(self, reltype, target, is_external=False):
"""
Return relationship of matching *reltype*, *target*, and
*is_external* from collection, or None if not found.
"""
def matches(rel, reltype, target, is_external):
if rel.reltype != reltype:
return False
if rel.is_external != is_external:
return False
rel_target = rel.target_ref if rel.is_external else rel.target_part
if rel_target != target:
return False
return True
for rel in self.values():
if matches(rel, reltype, target, is_external):
return rel
return None
def _get_rel_of_type(self, reltype):
"""
Return single relationship of type *reltype* from the collection.
Raises |KeyError| if no matching relationship is found. Raises
|ValueError| if more than one matching relationship is found.
"""
matching = [rel for rel in self.values() if rel.reltype == reltype]
if len(matching) == 0:
tmpl = "no relationship of type '%s' in collection"
raise KeyError(tmpl % reltype)
if len(matching) > 1:
tmpl = "multiple relationships of type '%s' in collection"
raise ValueError(tmpl % reltype)
return matching[0]
@property
def _next_rId(self):
"""
Next available rId in collection, starting from 'rId1' and making use
of any gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
"""
for n in range(1, len(self)+2):
rId_candidate = 'rId%d' % n # like 'rId19'
if rId_candidate not in self:
return rId_candidate
class _Relationship(object):
"""
Value object for relationship to part.
"""
def __init__(self, rId, reltype, target, baseURI, external=False):
super(_Relationship, self).__init__()
self._rId = rId
self._reltype = reltype
self._target = target
self._baseURI = baseURI
self._is_external = bool(external)
@property
def is_external(self):
return self._is_external
@property
def reltype(self):
return self._reltype
@property
def rId(self):
return self._rId
@property
def target_part(self):
if self._is_external:
raise ValueError("target_part property on _Relationship is undef"
"ined when target mode is External")
return self._target
@property
def target_ref(self):
if self._is_external:
return self._target
else:
return self._target.partname.relative_ref(self._baseURI)