1# encoding: utf-8 2 3""" 4Temporary stand-in for main oxml module that came across with the 5PackageReader transplant. Probably much will get replaced with objects from 6the pptx.oxml.core and then this module will either get deleted or only hold 7the package related custom element classes. 8""" 9 10from __future__ import absolute_import, print_function, unicode_literals 11 12from lxml import etree 13 14from .constants import NAMESPACE as NS, RELATIONSHIP_TARGET_MODE as RTM 15 16 17# configure XML parser 18element_class_lookup = etree.ElementNamespaceClassLookup() 19oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) 20oxml_parser.set_element_class_lookup(element_class_lookup) 21 22nsmap = { 23 'ct': NS.OPC_CONTENT_TYPES, 24 'pr': NS.OPC_RELATIONSHIPS, 25 'r': NS.OFC_RELATIONSHIPS, 26} 27 28 29# =========================================================================== 30# functions 31# =========================================================================== 32 33def parse_xml(text): 34 """ 35 ``etree.fromstring()`` replacement that uses oxml parser 36 """ 37 return etree.fromstring(text, oxml_parser) 38 39 40def qn(tag): 41 """ 42 Stands for "qualified name", a utility function to turn a namespace 43 prefixed tag name into a Clark-notation qualified tag name for lxml. For 44 example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``. 45 """ 46 prefix, tagroot = tag.split(':') 47 uri = nsmap[prefix] 48 return '{%s}%s' % (uri, tagroot) 49 50 51def serialize_part_xml(part_elm): 52 """ 53 Serialize *part_elm* etree element to XML suitable for storage as an XML 54 part. That is to say, no insignificant whitespace added for readability, 55 and an appropriate XML declaration added with UTF-8 encoding specified. 56 """ 57 return etree.tostring(part_elm, encoding='UTF-8', standalone=True) 58 59 60def serialize_for_reading(element): 61 """ 62 Serialize *element* to human-readable XML suitable for tests. No XML 63 declaration. 64 """ 65 return etree.tostring(element, encoding='unicode', pretty_print=True) 66 67 68# =========================================================================== 69# Custom element classes 70# =========================================================================== 71 72class BaseOxmlElement(etree.ElementBase): 73 """ 74 Base class for all custom element classes, to add standardized behavior 75 to all classes in one place. 76 """ 77 @property 78 def xml(self): 79 """ 80 Return XML string for this element, suitable for testing purposes. 81 Pretty printed for readability and without an XML declaration at the 82 top. 83 """ 84 return serialize_for_reading(self) 85 86 87class CT_Default(BaseOxmlElement): 88 """ 89 ``<Default>`` element, specifying the default content type to be applied 90 to a part with the specified extension. 91 """ 92 @property 93 def content_type(self): 94 """ 95 String held in the ``ContentType`` attribute of this ``<Default>`` 96 element. 97 """ 98 return self.get('ContentType') 99 100 @property 101 def extension(self): 102 """ 103 String held in the ``Extension`` attribute of this ``<Default>`` 104 element. 105 """ 106 return self.get('Extension') 107 108 @staticmethod 109 def new(ext, content_type): 110 """ 111 Return a new ``<Default>`` element with attributes set to parameter 112 values. 113 """ 114 xml = '<Default xmlns="%s"/>' % nsmap['ct'] 115 default = parse_xml(xml) 116 default.set('Extension', ext) 117 default.set('ContentType', content_type) 118 return default 119 120 121class CT_Override(BaseOxmlElement): 122 """ 123 ``<Override>`` element, specifying the content type to be applied for a 124 part with the specified partname. 125 """ 126 @property 127 def content_type(self): 128 """ 129 String held in the ``ContentType`` attribute of this ``<Override>`` 130 element. 131 """ 132 return self.get('ContentType') 133 134 @staticmethod 135 def new(partname, content_type): 136 """ 137 Return a new ``<Override>`` element with attributes set to parameter 138 values. 139 """ 140 xml = '<Override xmlns="%s"/>' % nsmap['ct'] 141 override = parse_xml(xml) 142 override.set('PartName', partname) 143 override.set('ContentType', content_type) 144 return override 145 146 @property 147 def partname(self): 148 """ 149 String held in the ``PartName`` attribute of this ``<Override>`` 150 element. 151 """ 152 return self.get('PartName') 153 154 155class CT_Relationship(BaseOxmlElement): 156 """ 157 ``<Relationship>`` element, representing a single relationship from a 158 source to a target part. 159 """ 160 @staticmethod 161 def new(rId, reltype, target, target_mode=RTM.INTERNAL): 162 """ 163 Return a new ``<Relationship>`` element. 164 """ 165 xml = '<Relationship xmlns="%s"/>' % nsmap['pr'] 166 relationship = parse_xml(xml) 167 relationship.set('Id', rId) 168 relationship.set('Type', reltype) 169 relationship.set('Target', target) 170 if target_mode == RTM.EXTERNAL: 171 relationship.set('TargetMode', RTM.EXTERNAL) 172 return relationship 173 174 @property 175 def rId(self): 176 """ 177 String held in the ``Id`` attribute of this ``<Relationship>`` 178 element. 179 """ 180 return self.get('Id') 181 182 @property 183 def reltype(self): 184 """ 185 String held in the ``Type`` attribute of this ``<Relationship>`` 186 element. 187 """ 188 return self.get('Type') 189 190 @property 191 def target_ref(self): 192 """ 193 String held in the ``Target`` attribute of this ``<Relationship>`` 194 element. 195 """ 196 return self.get('Target') 197 198 @property 199 def target_mode(self): 200 """ 201 String held in the ``TargetMode`` attribute of this 202 ``<Relationship>`` element, either ``Internal`` or ``External``. 203 Defaults to ``Internal``. 204 """ 205 return self.get('TargetMode', RTM.INTERNAL) 206 207 208class CT_Relationships(BaseOxmlElement): 209 """ 210 ``<Relationships>`` element, the root element in a .rels file. 211 """ 212 def add_rel(self, rId, reltype, target, is_external=False): 213 """ 214 Add a child ``<Relationship>`` element with attributes set according 215 to parameter values. 216 """ 217 target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL 218 relationship = CT_Relationship.new(rId, reltype, target, target_mode) 219 self.append(relationship) 220 221 @staticmethod 222 def new(): 223 """ 224 Return a new ``<Relationships>`` element. 225 """ 226 xml = '<Relationships xmlns="%s"/>' % nsmap['pr'] 227 relationships = parse_xml(xml) 228 return relationships 229 230 @property 231 def Relationship_lst(self): 232 """ 233 Return a list containing all the ``<Relationship>`` child elements. 234 """ 235 return self.findall(qn('pr:Relationship')) 236 237 @property 238 def xml(self): 239 """ 240 Return XML string for this element, suitable for saving in a .rels 241 stream, not pretty printed and with an XML declaration at the top. 242 """ 243 return serialize_part_xml(self) 244 245 246class CT_Types(BaseOxmlElement): 247 """ 248 ``<Types>`` element, the container element for Default and Override 249 elements in [Content_Types].xml. 250 """ 251 def add_default(self, ext, content_type): 252 """ 253 Add a child ``<Default>`` element with attributes set to parameter 254 values. 255 """ 256 default = CT_Default.new(ext, content_type) 257 self.append(default) 258 259 def add_override(self, partname, content_type): 260 """ 261 Add a child ``<Override>`` element with attributes set to parameter 262 values. 263 """ 264 override = CT_Override.new(partname, content_type) 265 self.append(override) 266 267 @property 268 def defaults(self): 269 return self.findall(qn('ct:Default')) 270 271 @staticmethod 272 def new(): 273 """ 274 Return a new ``<Types>`` element. 275 """ 276 xml = '<Types xmlns="%s"/>' % nsmap['ct'] 277 types = parse_xml(xml) 278 return types 279 280 @property 281 def overrides(self): 282 return self.findall(qn('ct:Override')) 283 284 285ct_namespace = element_class_lookup.get_namespace(nsmap['ct']) 286ct_namespace['Default'] = CT_Default 287ct_namespace['Override'] = CT_Override 288ct_namespace['Types'] = CT_Types 289 290pr_namespace = element_class_lookup.get_namespace(nsmap['pr']) 291pr_namespace['Relationship'] = CT_Relationship 292pr_namespace['Relationships'] = CT_Relationships 293