1# encoding: utf-8 2 3""" 4Provides a general interface to a *physical* OPC package, such as a zip file. 5""" 6 7from __future__ import absolute_import 8 9import os 10 11from zipfile import ZipFile, is_zipfile, ZIP_DEFLATED 12 13from .compat import is_string 14from .exceptions import PackageNotFoundError 15from .packuri import CONTENT_TYPES_URI 16 17 18class PhysPkgReader(object): 19 """ 20 Factory for physical package reader objects. 21 """ 22 def __new__(cls, pkg_file): 23 # if *pkg_file* is a string, treat it as a path 24 if is_string(pkg_file): 25 if os.path.isdir(pkg_file): 26 reader_cls = _DirPkgReader 27 elif is_zipfile(pkg_file): 28 reader_cls = _ZipPkgReader 29 else: 30 raise PackageNotFoundError( 31 "Package not found at '%s'" % pkg_file 32 ) 33 else: # assume it's a stream and pass it to Zip reader to sort out 34 reader_cls = _ZipPkgReader 35 36 return super(PhysPkgReader, cls).__new__(reader_cls) 37 38 39class PhysPkgWriter(object): 40 """ 41 Factory for physical package writer objects. 42 """ 43 def __new__(cls, pkg_file): 44 return super(PhysPkgWriter, cls).__new__(_ZipPkgWriter) 45 46 47class _DirPkgReader(PhysPkgReader): 48 """ 49 Implements |PhysPkgReader| interface for an OPC package extracted into a 50 directory. 51 """ 52 def __init__(self, path): 53 """ 54 *path* is the path to a directory containing an expanded package. 55 """ 56 super(_DirPkgReader, self).__init__() 57 self._path = os.path.abspath(path) 58 59 def blob_for(self, pack_uri): 60 """ 61 Return contents of file corresponding to *pack_uri* in package 62 directory. 63 """ 64 path = os.path.join(self._path, pack_uri.membername) 65 with open(path, 'rb') as f: 66 blob = f.read() 67 return blob 68 69 def close(self): 70 """ 71 Provides interface consistency with |ZipFileSystem|, but does 72 nothing, a directory file system doesn't need closing. 73 """ 74 pass 75 76 @property 77 def content_types_xml(self): 78 """ 79 Return the `[Content_Types].xml` blob from the package. 80 """ 81 return self.blob_for(CONTENT_TYPES_URI) 82 83 def rels_xml_for(self, source_uri): 84 """ 85 Return rels item XML for source with *source_uri*, or None if the 86 item has no rels item. 87 """ 88 try: 89 rels_xml = self.blob_for(source_uri.rels_uri) 90 except IOError: 91 rels_xml = None 92 return rels_xml 93 94 95class _ZipPkgReader(PhysPkgReader): 96 """ 97 Implements |PhysPkgReader| interface for a zip file OPC package. 98 """ 99 def __init__(self, pkg_file): 100 super(_ZipPkgReader, self).__init__() 101 self._zipf = ZipFile(pkg_file, 'r') 102 103 def blob_for(self, pack_uri): 104 """ 105 Return blob corresponding to *pack_uri*. Raises |ValueError| if no 106 matching member is present in zip archive. 107 """ 108 return self._zipf.read(pack_uri.membername) 109 110 def close(self): 111 """ 112 Close the zip archive, releasing any resources it is using. 113 """ 114 self._zipf.close() 115 116 @property 117 def content_types_xml(self): 118 """ 119 Return the `[Content_Types].xml` blob from the zip package. 120 """ 121 return self.blob_for(CONTENT_TYPES_URI) 122 123 def rels_xml_for(self, source_uri): 124 """ 125 Return rels item XML for source with *source_uri* or None if no rels 126 item is present. 127 """ 128 try: 129 rels_xml = self.blob_for(source_uri.rels_uri) 130 except KeyError: 131 rels_xml = None 132 return rels_xml 133 134 135class _ZipPkgWriter(PhysPkgWriter): 136 """ 137 Implements |PhysPkgWriter| interface for a zip file OPC package. 138 """ 139 def __init__(self, pkg_file): 140 super(_ZipPkgWriter, self).__init__() 141 self._zipf = ZipFile(pkg_file, 'w', compression=ZIP_DEFLATED) 142 143 def close(self): 144 """ 145 Close the zip archive, flushing any pending physical writes and 146 releasing any resources it's using. 147 """ 148 self._zipf.close() 149 150 def write(self, pack_uri, blob): 151 """ 152 Write *blob* to this zip package with the membername corresponding to 153 *pack_uri*. 154 """ 155 self._zipf.writestr(pack_uri.membername, blob) 156