Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/packaging/relationship.py: 53%

94 statements  

« prev     ^ index     » next       coverage.py v7.3.3, created at 2023-12-20 06:34 +0000

1# Copyright (c) 2010-2023 openpyxl 

2 

3import posixpath 

4from warnings import warn 

5 

6from openpyxl.descriptors import ( 

7 String, 

8 Alias, 

9 Sequence, 

10) 

11from openpyxl.descriptors.serialisable import Serialisable 

12 

13from openpyxl.xml.constants import REL_NS, PKG_REL_NS 

14from openpyxl.xml.functions import ( 

15 Element, 

16 fromstring, 

17) 

18 

19 

20class Relationship(Serialisable): 

21 """Represents many kinds of relationships.""" 

22 

23 tagname = "Relationship" 

24 

25 Type = String() 

26 Target = String() 

27 target = Alias("Target") 

28 TargetMode = String(allow_none=True) 

29 Id = String(allow_none=True) 

30 id = Alias("Id") 

31 

32 

33 def __init__(self, 

34 Id=None, 

35 Type=None, 

36 type=None, 

37 Target=None, 

38 TargetMode=None 

39 ): 

40 """ 

41 `type` can be used as a shorthand with the default relationships namespace 

42 otherwise the `Type` must be a fully qualified URL 

43 """ 

44 if type is not None: 

45 Type = "{0}/{1}".format(REL_NS, type) 

46 self.Type = Type 

47 self.Target = Target 

48 self.TargetMode = TargetMode 

49 self.Id = Id 

50 

51 

52class RelationshipList(Serialisable): 

53 

54 tagname = "Relationships" 

55 

56 Relationship = Sequence(expected_type=Relationship) 

57 

58 

59 def __init__(self, Relationship=()): 

60 self.Relationship = Relationship 

61 

62 

63 def append(self, value): 

64 values = self.Relationship[:] 

65 values.append(value) 

66 if not value.Id: 

67 value.Id = "rId{0}".format((len(values))) 

68 self.Relationship = values 

69 

70 

71 def __len__(self): 

72 return len(self.Relationship) 

73 

74 

75 def __bool__(self): 

76 return bool(self.Relationship) 

77 

78 

79 def find(self, content_type): 

80 """ 

81 Find relationships by content-type 

82 NB. these content-types namespaced objects and different to the MIME-types 

83 in the package manifest :-( 

84 """ 

85 for r in self.Relationship: 

86 if r.Type == content_type: 

87 yield r 

88 

89 

90 def __getitem__(self, key): 

91 for r in self.Relationship: 

92 if r.Id == key: 

93 return r 

94 raise KeyError("Unknown relationship: {0}".format(key)) 

95 

96 

97 def to_tree(self): 

98 tree = Element("Relationships", xmlns=PKG_REL_NS) 

99 for idx, rel in enumerate(self.Relationship, 1): 

100 if not rel.Id: 

101 rel.Id = "rId{0}".format(idx) 

102 tree.append(rel.to_tree()) 

103 

104 return tree 

105 

106 

107def get_rels_path(path): 

108 """ 

109 Convert relative path to absolutes that can be loaded from a zip 

110 archive. 

111 The path to be passed in is that of containing object (workbook, 

112 worksheet, etc.) 

113 """ 

114 folder, obj = posixpath.split(path) 

115 filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj)) 

116 return filename 

117 

118 

119def get_dependents(archive, filename): 

120 """ 

121 Normalise dependency file paths to absolute ones 

122 

123 Relative paths are relative to parent object 

124 """ 

125 src = archive.read(filename) 

126 node = fromstring(src) 

127 try: 

128 rels = RelationshipList.from_tree(node) 

129 except TypeError: 

130 msg = "{0} contains invalid dependency definitions".format(filename) 

131 warn(msg) 

132 rels = RelationshipList() 

133 folder = posixpath.dirname(filename) 

134 parent = posixpath.split(folder)[0] 

135 for r in rels.Relationship: 

136 if r.TargetMode == "External": 

137 continue 

138 elif r.target.startswith("/"): 

139 r.target = r.target[1:] 

140 else: 

141 pth = posixpath.join(parent, r.target) 

142 r.target = posixpath.normpath(pth) 

143 return rels 

144 

145 

146def get_rel(archive, deps, id=None, cls=None): 

147 """ 

148 Get related object based on id or rel_type 

149 """ 

150 if not any([id, cls]): 

151 raise ValueError("Either the id or the content type are required") 

152 if id is not None: 

153 rel = deps[id] 

154 else: 

155 try: 

156 rel = next(deps.find(cls.rel_type)) 

157 except StopIteration: # no known dependency 

158 return 

159 

160 path = rel.target 

161 src = archive.read(path) 

162 tree = fromstring(src) 

163 obj = cls.from_tree(tree) 

164 

165 rels_path = get_rels_path(path) 

166 try: 

167 obj.deps = get_dependents(archive, rels_path) 

168 except KeyError: 

169 obj.deps = [] 

170 

171 return obj