Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/odf/opendocument.py: 11%

528 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1# -*- coding: utf-8 -*- 

2# Copyright (C) 2006-2010 Søren Roug, European Environment Agency 

3# 

4# This library is free software; you can redistribute it and/or 

5# modify it under the terms of the GNU Lesser General Public 

6# License as published by the Free Software Foundation; either 

7# version 2.1 of the License, or (at your option) any later version. 

8# 

9# This library is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

12# Lesser General Public License for more details. 

13# 

14# You should have received a copy of the GNU Lesser General Public 

15# License along with this library; if not, write to the Free Software 

16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

17# 

18# Contributor(s): 

19# 

20# Copyright (C) 2014 Georges Khaznadar <georgesk@debian.org> 

21# migration to Python3, JavaDOC comments and automatic 

22# build of documentation 

23# 

24 

25__doc__="""Use OpenDocument to generate your documents.""" 

26 

27import zipfile, time, uuid, sys, mimetypes, copy, os.path 

28 

29# to allow Python3 to access modules in the same path 

30sys.path.append(os.path.dirname(__file__)) 

31 

32# using BytesIO provides a cleaner interface than StringIO 

33# with both Python2 and Python3: the programmer must care to 

34# convert strings or unicode to bytes, which is valid for Python 2 and 3. 

35from io import StringIO, BytesIO 

36 

37from odf.namespaces import * 

38import odf.manifest as manifest 

39import odf.meta as meta 

40from odf.office import * 

41import odf.element as element 

42from odf.attrconverters import make_NCName 

43from xml.sax.xmlreader import InputSource 

44from odf.odfmanifest import manifestlist 

45import codecs 

46 

47if sys.version_info[0] == 3: 47 ↛ 50line 47 didn't jump to line 50, because the condition on line 47 was never false

48 unicode=str # unicode function does not exist 

49 

50__version__= TOOLSVERSION 

51 

52_XMLPROLOGUE = u"<?xml version='1.0' encoding='UTF-8'?>\n" 

53 

54##### 

55# file permission as an integer value. 

56# The following syntax would be invalid for Python3: 

57# UNIXPERMS = 0100644 << 16L # -rw-r--r-- 

58# 

59# So it has been precomputed: 

60# 2175008768 is the same value as 0100644 << 16L == -rw-r--r-- 

61#### 

62UNIXPERMS = 2175008768 

63 

64IS_FILENAME = 0 

65IS_IMAGE = 1 

66# We need at least Python 2.2 

67assert sys.version_info[0]>=2 and sys.version_info[1] >= 2 

68 

69#sys.setrecursionlimit(100) 

70#The recursion limit is set conservative so mistakes like 

71# s=content() s.addElement(s) won't eat up too much processor time. 

72 

73############### 

74# mime-types => file extensions 

75############### 

76odmimetypes = { 

77 u'application/vnd.oasis.opendocument.text': u'.odt', 

78 u'application/vnd.oasis.opendocument.text-template': u'.ott', 

79 u'application/vnd.oasis.opendocument.graphics': u'.odg', 

80 u'application/vnd.oasis.opendocument.graphics-template': u'.otg', 

81 u'application/vnd.oasis.opendocument.presentation': u'.odp', 

82 u'application/vnd.oasis.opendocument.presentation-template': u'.otp', 

83 u'application/vnd.oasis.opendocument.spreadsheet': u'.ods', 

84 u'application/vnd.oasis.opendocument.spreadsheet-template': u'.ots', 

85 u'application/vnd.oasis.opendocument.chart': u'.odc', 

86 u'application/vnd.oasis.opendocument.chart-template': u'.otc', 

87 u'application/vnd.oasis.opendocument.image': u'.odi', 

88 u'application/vnd.oasis.opendocument.image-template': u'.oti', 

89 u'application/vnd.oasis.opendocument.formula': u'.odf', 

90 u'application/vnd.oasis.opendocument.formula-template': u'.otf', 

91 u'application/vnd.oasis.opendocument.text-master': u'.odm', 

92 u'application/vnd.oasis.opendocument.text-web': u'.oth', 

93} 

94 

95class OpaqueObject: 

96 """ 

97 just a record to bear a filename, a mediatype and a bytes content 

98 """ 

99 def __init__(self, filename, mediatype, content=None): 

100 """ 

101 the constructor 

102 @param filename a unicode string 

103 @param mediatype a unicode string 

104 @param content a byte string or None 

105 """ 

106 assert(type(filename)==type(u"")) 

107 assert(type(mediatype)==type(u"")) 

108 assert(type(content)==type(b"") or content == None) 

109 

110 self.mediatype = mediatype 

111 self.filename = filename 

112 self.content = content 

113 

114class OpenDocument: 

115 """ 

116 A class to hold the content of an OpenDocument document 

117 Use the xml method to write the XML 

118 source to the screen or to a file. 

119 Example of use: d = OpenDocument(mimetype); fd.write(d.xml()) 

120 """ 

121 thumbnail = None 

122 

123 def __init__(self, mimetype, add_generator=True): 

124 """ 

125 the constructor 

126 @param mimetype a unicode string 

127 @param add_generator a boolean 

128 """ 

129 assert(type(mimetype)==type(u"")) 

130 assert(isinstance(add_generator,True.__class__)) 

131 

132 self.mimetype = mimetype 

133 self.childobjects = [] 

134 self._extra = [] 

135 self.folder = u"" # Always empty for toplevel documents 

136 self.topnode = Document(mimetype=self.mimetype) 

137 self.topnode.ownerDocument = self 

138 

139 self.clear_caches() 

140 

141 self.Pictures = {} 

142 self.meta = Meta() 

143 self.topnode.addElement(self.meta) 

144 if add_generator: 

145 self.meta.addElement(meta.Generator(text=TOOLSVERSION)) 

146 self.scripts = Scripts() 

147 self.topnode.addElement(self.scripts) 

148 self.fontfacedecls = FontFaceDecls() 

149 self.topnode.addElement(self.fontfacedecls) 

150 self.settings = Settings() 

151 self.topnode.addElement(self.settings) 

152 self.styles = Styles() 

153 self.topnode.addElement(self.styles) 

154 self.automaticstyles = AutomaticStyles() 

155 self.topnode.addElement(self.automaticstyles) 

156 self.masterstyles = MasterStyles() 

157 self.topnode.addElement(self.masterstyles) 

158 self.body = Body() 

159 self.topnode.addElement(self.body) 

160 

161 def rebuild_caches(self, node=None): 

162 if node is None: node = self.topnode 

163 self.build_caches(node) 

164 for e in node.childNodes: 

165 if e.nodeType == element.Node.ELEMENT_NODE: 

166 self.rebuild_caches(e) 

167 

168 def clear_caches(self): 

169 """ 

170 Clears internal caches 

171 """ 

172 self.element_dict = {} 

173 self._styles_dict = {} 

174 self._styles_ooo_fix = {} 

175 

176 def build_caches(self, elt): 

177 """ 

178 Builds internal caches; called from element.py 

179 @param elt an element.Element instance 

180 """ 

181 # assert(isinstance(elt, element.Element)) 

182 # why do I need this more intricated assertion? 

183 # with Python3, the type of elt pops out as odf.element.Element 

184 # in one test ??? 

185 import odf.element 

186 assert(isinstance(elt, element.Element) or isinstance(elt, odf.element.Element) ) 

187 

188 if elt.qname not in self.element_dict: 

189 self.element_dict[elt.qname] = [] 

190 self.element_dict[elt.qname].append(elt) 

191 if elt.qname == (STYLENS, u'style'): 

192 self.__register_stylename(elt) # Add to style dictionary 

193 styleref = elt.getAttrNS(TEXTNS,u'style-name') 

194 if styleref is not None and styleref in self._styles_ooo_fix: 

195 elt.setAttrNS(TEXTNS,u'style-name', self._styles_ooo_fix[styleref]) 

196 

197 def remove_from_caches(self, elt): 

198 """ 

199 Updates internal caches when an element has been removed 

200 @param elt an element.Element instance 

201 """ 

202 # See remark in build_caches about the following assertion 

203 import odf.element 

204 assert(isinstance(elt, element.Element) or isinstance(elt, odf.element.Element)) 

205 

206 self.element_dict[elt.qname].remove(elt) 

207 for e in elt.childNodes: 

208 if e.nodeType == element.Node.ELEMENT_NODE: 

209 self.remove_from_caches(e) 

210 

211 if elt.qname == (STYLENS, u'style'): 

212 del self._styles_dict[elt.getAttrNS(STYLENS, u'name')] 

213 

214 def __register_stylename(self, elt): 

215 ''' 

216 Register a style. But there are three style dictionaries: 

217 office:styles, office:automatic-styles and office:master-styles 

218 Chapter 14. 

219 @param elt an element.Element instance 

220 ''' 

221 assert(isinstance(elt, element.Element)) 

222 

223 name = elt.getAttrNS(STYLENS, u'name') 

224 if name is None: 

225 return 

226 if elt.parentNode.qname in ((OFFICENS,u'styles'), (OFFICENS,u'automatic-styles')): 

227 if name in self._styles_dict: 

228 newname = u'M'+name # Rename style 

229 self._styles_ooo_fix[name] = newname 

230 # From here on all references to the old name will refer to the new one 

231 name = newname 

232 elt.setAttrNS(STYLENS, u'name', name) 

233 self._styles_dict[name] = elt 

234 

235 def toXml(self, filename=u''): 

236 """ 

237 converts the document to a valid Xml format. 

238 @param filename unicode string: the name of a file, defaults to 

239 an empty string. 

240 @return if filename is not empty, the XML code will be written into it 

241 and the method returns None; otherwise the method returns a StringIO 

242 containing valid XML. 

243 Then a ".getvalue()" should return a unicode string. 

244 """ 

245 assert(type(filename)==type(u"")) 

246 

247 result=None 

248 xml=StringIO() 

249 if sys.version_info[0]==2: 

250 xml.write(_XMLPROLOGUE) 

251 else: 

252 xml.write(_XMLPROLOGUE) 

253 self.body.toXml(0, xml) 

254 if not filename: 

255 result=xml.getvalue() 

256 else: 

257 f=codecs.open(filename,'w', encoding='utf-8') 

258 f.write(xml.getvalue()) 

259 f.close() 

260 return result 

261 

262 def xml(self): 

263 """ 

264 Generates the full document as an XML "file" 

265 @return a bytestream in UTF-8 encoding 

266 """ 

267 self.__replaceGenerator() 

268 xml=StringIO() 

269 if sys.version_info[0]==2: 

270 xml.write(_XMLPROLOGUE) 

271 else: 

272 xml.write(_XMLPROLOGUE) 

273 self.topnode.toXml(0, xml) 

274 return xml.getvalue().encode("utf-8") 

275 

276 

277 def contentxml(self): 

278 """ 

279 Generates the content.xml file 

280 @return a bytestream in UTF-8 encoding 

281 """ 

282 xml=StringIO() 

283 xml.write(_XMLPROLOGUE) 

284 x = DocumentContent() 

285 x.write_open_tag(0, xml) 

286 if self.scripts.hasChildNodes(): 

287 self.scripts.toXml(1, xml) 

288 if self.fontfacedecls.hasChildNodes(): 

289 self.fontfacedecls.toXml(1, xml) 

290 a = AutomaticStyles() 

291 stylelist = self._used_auto_styles([self.styles, self.automaticstyles, self.body]) 

292 if len(stylelist) > 0: 

293 a.write_open_tag(1, xml) 

294 for s in stylelist: 

295 s.toXml(2, xml) 

296 a.write_close_tag(1, xml) 

297 else: 

298 a.toXml(1, xml) 

299 self.body.toXml(1, xml) 

300 x.write_close_tag(0, xml) 

301 return xml.getvalue().encode("utf-8") 

302 

303 def __manifestxml(self): 

304 """ 

305 Generates the manifest.xml file; 

306 The self.manifest isn't avaible unless the document is being saved 

307 @return a unicode string 

308 """ 

309 xml=StringIO() 

310 xml.write(_XMLPROLOGUE) 

311 self.manifest.toXml(0,xml) 

312 result=xml.getvalue() 

313 assert(type(result)==type(u"")) 

314 return result 

315 

316 def metaxml(self): 

317 """ 

318 Generates the meta.xml file 

319 @return a unicode string 

320 """ 

321 self.__replaceGenerator() 

322 x = DocumentMeta() 

323 x.addElement(self.meta) 

324 xml=StringIO() 

325 xml.write(_XMLPROLOGUE) 

326 x.toXml(0,xml) 

327 result=xml.getvalue() 

328 assert(type(result)==type(u"")) 

329 return result 

330 

331 def settingsxml(self): 

332 """ 

333 Generates the settings.xml file 

334 @return a unicode string 

335 """ 

336 x = DocumentSettings() 

337 x.addElement(self.settings) 

338 xml=StringIO() 

339 if sys.version_info[0]==2: 

340 xml.write(_XMLPROLOGUE) 

341 else: 

342 xml.write(_XMLPROLOGUE) 

343 x.toXml(0,xml) 

344 result=xml.getvalue() 

345 assert(type(result)==type(u"")) 

346 return result 

347 

348 def _parseoneelement(self, top, stylenamelist): 

349 """ 

350 Finds references to style objects in master-styles 

351 and add the style name to the style list if not already there. 

352 Recursive 

353 @return the list of style names as unicode strings 

354 """ 

355 for e in top.childNodes: 

356 if e.nodeType == element.Node.ELEMENT_NODE: 

357 for styleref in ( 

358 (CHARTNS,u'style-name'), 

359 (DRAWNS,u'style-name'), 

360 (DRAWNS,u'text-style-name'), 

361 (PRESENTATIONNS,u'style-name'), 

362 (STYLENS,u'data-style-name'), 

363 (STYLENS,u'list-style-name'), 

364 (STYLENS,u'page-layout-name'), 

365 (STYLENS,u'style-name'), 

366 (TABLENS,u'default-cell-style-name'), 

367 (TABLENS,u'style-name'), 

368 (TEXTNS,u'style-name') ): 

369 if e.getAttrNS(styleref[0],styleref[1]): 

370 stylename = e.getAttrNS(styleref[0],styleref[1]) 

371 if stylename not in stylenamelist: 

372 # due to the polymorphism of e.getAttrNS(), 

373 # a unicode type is enforced for elements 

374 stylenamelist.append(unicode(stylename)) 

375 stylenamelist = self._parseoneelement(e, stylenamelist) 

376 return stylenamelist 

377 

378 def _used_auto_styles(self, segments): 

379 """ 

380 Loop through the masterstyles elements, and find the automatic 

381 styles that are used. These will be added to the automatic-styles 

382 element in styles.xml 

383 @return a list of element.Element instances 

384 """ 

385 stylenamelist = [] 

386 for top in segments: 

387 stylenamelist = self._parseoneelement(top, stylenamelist) 

388 stylelist = [] 

389 for e in self.automaticstyles.childNodes: 

390 if isinstance(e, element.Element) and e.getAttrNS(STYLENS,u'name') in stylenamelist: 

391 stylelist.append(e) 

392 

393 # check the type of the returned data 

394 ok=True 

395 for e in stylelist: ok = ok and isinstance(e, element.Element) 

396 assert(ok) 

397 

398 return stylelist 

399 

400 def stylesxml(self): 

401 """ 

402 Generates the styles.xml file 

403 @return valid XML code as a unicode string 

404 """ 

405 xml=StringIO() 

406 xml.write(_XMLPROLOGUE) 

407 x = DocumentStyles() 

408 x.write_open_tag(0, xml) 

409 if self.fontfacedecls.hasChildNodes(): 

410 self.fontfacedecls.toXml(1, xml) 

411 self.styles.toXml(1, xml) 

412 a = AutomaticStyles() 

413 a.write_open_tag(1, xml) 

414 for s in self._used_auto_styles([self.masterstyles]): 

415 s.toXml(2, xml) 

416 a.write_close_tag(1, xml) 

417 if self.masterstyles.hasChildNodes(): 

418 self.masterstyles.toXml(1, xml) 

419 x.write_close_tag(0, xml) 

420 result = xml.getvalue() 

421 

422 assert(type(result)==type(u"")) 

423 

424 return result 

425 

426 def addPicture(self, filename, mediatype=None, content=None): 

427 """ 

428 Add a picture 

429 It uses the same convention as OOo, in that it saves the picture in 

430 the zipfile in the subdirectory 'Pictures' 

431 If passed a file ptr, mediatype must be set 

432 @param filename unicode string: name of a file for Pictures 

433 @param mediatype unicode string: name of a media, None by default 

434 @param content bytes: content of media, None by default 

435 @return a unicode string: the file name of the media, eventually 

436 created on the fly 

437 """ 

438 if content is None: 

439 if mediatype is None: 

440 mediatype, encoding = mimetypes.guess_type(filename) 

441 if mediatype is None: 

442 mediatype = u'' 

443 try: ext = filename[filename.rindex(u'.'):] 

444 except: ext=u'' 

445 else: 

446 ext = mimetypes.guess_extension(mediatype) 

447 manifestfn = u"Pictures/%s%s" % (uuid.uuid4().hex.upper(), ext) 

448 self.Pictures[manifestfn] = (IS_FILENAME, filename, mediatype) 

449 content=b"" # this value is only use by the assert further 

450 filename=u"" # this value is only use by the assert further 

451 else: 

452 manifestfn = filename 

453 self.Pictures[manifestfn] = (IS_IMAGE, content, mediatype) 

454 

455 assert(type(filename)==type(u"")) 

456 assert(type(content) == type(b"")) 

457 

458 return manifestfn 

459 

460 def addPictureFromFile(self, filename, mediatype=None): 

461 """ 

462 Add a picture 

463 It uses the same convention as OOo, in that it saves the picture in 

464 the zipfile in the subdirectory 'Pictures'. 

465 If mediatype is not given, it will be guessed from the filename 

466 extension. 

467 @param filesname unicode string: name of an image file 

468 @param mediatype unicode string: type of media, dfaults to None 

469 @return a unicode string, the name of the created file 

470 """ 

471 if mediatype is None: 

472 mediatype, encoding = mimetypes.guess_type(filename) 

473 if mediatype is None: 

474 mediatype = u'' 

475 try: ext = filename[filename.rindex(u'.'):] 

476 except ValueError: ext=u'' 

477 else: 

478 ext = mimetypes.guess_extension(mediatype) 

479 manifestfn = u"Pictures/%s%s" % (uuid.uuid4().hex.upper(), ext) 

480 self.Pictures[manifestfn] = (IS_FILENAME, filename, mediatype) 

481 

482 assert(type(filename)==type(u"")) 

483 assert(type(mediatype)==type(u"")) 

484 

485 return manifestfn 

486 

487 def addPictureFromString(self, content, mediatype): 

488 """ 

489 Add a picture from contents given as a Byte string. 

490 It uses the same convention as OOo, in that it saves the picture in 

491 the zipfile in the subdirectory 'Pictures'. The content variable 

492 is a string that contains the binary image data. The mediatype 

493 indicates the image format. 

494 @param content bytes: content of media 

495 @param mediatype unicode string: name of a media 

496 @return a unicode string, the name of the created file 

497 """ 

498 assert(type(content)==type(b"")) 

499 assert(type(mediatype)==type(u"")) 

500 

501 ext = mimetypes.guess_extension(mediatype) 

502 manifestfn = u"Pictures/%s%s" % (uuid.uuid4().hex.upper(), ext) 

503 self.Pictures[manifestfn] = (IS_IMAGE, content, mediatype) 

504 return manifestfn 

505 

506 def addThumbnail(self, filecontent=None): 

507 """ 

508 Add a fixed thumbnail 

509 The thumbnail in the library is big, so this is pretty useless. 

510 @param filecontent bytes: the content of a file; defaults to None 

511 """ 

512 assert(type(filecontent)==type(b"")) 

513 

514 if filecontent is None: 

515 import thumbnail 

516 self.thumbnail = thumbnail.thumbnail() 

517 else: 

518 self.thumbnail = filecontent 

519 

520 def addObject(self, document, objectname=None): 

521 """ 

522 Adds an object (subdocument). The object must be an OpenDocument class 

523 @param document OpenDocument instance 

524 @param objectname unicode string: the name of an object to add 

525 @return a unicode string: the folder name in the zipfile the object is 

526 stored in. 

527 """ 

528 assert(isinstance(document, OpenDocument)) 

529 assert(type(objectname)==type(u"") or objectname == None) 

530 

531 self.childobjects.append(document) 

532 if objectname is None: 

533 document.folder = u"%s/Object %d" % (self.folder, len(self.childobjects)) 

534 else: 

535 document.folder = objectname 

536 return u".%s" % document.folder 

537 

538 def _savePictures(self, anObject, folder): 

539 """ 

540 saves pictures contained in an object 

541 @param anObject instance of OpenDocument containing pictures 

542 @param folder unicode string: place to save pictures 

543 """ 

544 assert(isinstance(anObject, OpenDocument)) 

545 assert(type(folder)==type(u"")) 

546 

547 hasPictures = False 

548 for arcname, picturerec in anObject.Pictures.items(): 

549 what_it_is, fileobj, mediatype = picturerec 

550 self.manifest.addElement(manifest.FileEntry(fullpath=u"%s%s" % ( folder ,arcname), mediatype=mediatype)) 

551 hasPictures = True 

552 if what_it_is == IS_FILENAME: 

553 self._z.write(fileobj, folder + arcname, zipfile.ZIP_STORED) 

554 else: 

555 zi = zipfile.ZipInfo(str(arcname), self._now) 

556 zi.compress_type = zipfile.ZIP_STORED 

557 zi.external_attr = UNIXPERMS 

558 self._z.writestr(zi, fileobj) 

559 # According to section 17.7.3 in ODF 1.1, the pictures folder should not have a manifest entry 

560# if hasPictures: 

561# self.manifest.addElement(manifest.FileEntry(fullpath="%sPictures/" % folder, mediatype="")) 

562 # Look in subobjects 

563 subobjectnum = 1 

564 for subobject in anObject.childobjects: 

565 self._savePictures(subobject, u'%sObject %d/' % (folder, subobjectnum)) 

566 subobjectnum += 1 

567 

568 def __replaceGenerator(self): 

569 """ 

570 Removes a previous 'generator' stance and declares TOOLSVERSION 

571 as the new generator. 

572 Section 3.1.1: The application MUST NOT export the original identifier 

573 belonging to the application that created the document. 

574 """ 

575 for m in self.meta.childNodes[:]: 

576 if m.qname == (METANS, u'generator'): 

577 self.meta.removeChild(m) 

578 self.meta.addElement(meta.Generator(text=TOOLSVERSION)) 

579 

580 def save(self, outputfile, addsuffix=False): 

581 """ 

582 Save the document under the filename. 

583 If the filename is '-' then save to stdout 

584 @param outputfile unicode string: the special name '-' is for stdout; 

585 as an alternative, it can be an io.ByteIO instance which contains 

586 the ZIP content. 

587 @param addsuffix boolean: whether to add a suffix or not; defaults to False 

588 """ 

589 

590 if outputfile == u'-': 

591 outputfp = zipfile.ZipFile(sys.stdout,"w") 

592 else: 

593 if addsuffix: 

594 outputfile = outputfile + odmimetypes.get(self.mimetype,u'.xxx') 

595 outputfp = zipfile.ZipFile(outputfile, "w") 

596 self.__zipwrite(outputfp) 

597 outputfp.close() 

598 

599 def write(self, outputfp): 

600 """ 

601 User API to write the ODF file to an open file descriptor 

602 Writes the ZIP format 

603 @param outputfp open file descriptor 

604 """ 

605 zipoutputfp = zipfile.ZipFile(outputfp,"w") 

606 self.__zipwrite(zipoutputfp) 

607 

608 def __zipwrite(self, outputfp): 

609 """ 

610 Write the document to an open file pointer 

611 This is where the real work is done 

612 @param outputfp instance of zipfile.ZipFile 

613 """ 

614 assert(isinstance(outputfp, zipfile.ZipFile)) 

615 

616 self._z = outputfp 

617 self._now = time.localtime()[:6] 

618 self.manifest = manifest.Manifest() 

619 

620 # Write mimetype 

621 zi = zipfile.ZipInfo('mimetype', self._now) 

622 zi.compress_type = zipfile.ZIP_STORED 

623 zi.external_attr = UNIXPERMS 

624 self._z.writestr(zi, self.mimetype.encode("utf-8")) 

625 

626 self._saveXmlObjects(self,u"") 

627 

628 # Write pictures 

629 self._savePictures(self,u"") 

630 

631 # Write the thumbnail 

632 if self.thumbnail is not None: 

633 self.manifest.addElement(manifest.FileEntry(fullpath=u"Thumbnails/", mediatype=u'')) 

634 self.manifest.addElement(manifest.FileEntry(fullpath=u"Thumbnails/thumbnail.png", mediatype=u'')) 

635 zi = zipfile.ZipInfo(u"Thumbnails/thumbnail.png", self._now) 

636 zi.compress_type = zipfile.ZIP_DEFLATED 

637 zi.external_attr = UNIXPERMS 

638 self._z.writestr(zi, self.thumbnail) 

639 

640 # Write any extra files 

641 for op in self._extra: 

642 if op.filename == u"META-INF/documentsignatures.xml": continue # Don't save signatures 

643 self.manifest.addElement(manifest.FileEntry(fullpath=op.filename, mediatype=op.mediatype)) 

644 if sys.version_info[0]==3: 

645 zi = zipfile.ZipInfo(op.filename, self._now) 

646 else: 

647 zi = zipfile.ZipInfo(op.filename.encode('utf-8'), self._now) 

648 zi.compress_type = zipfile.ZIP_DEFLATED 

649 zi.external_attr = UNIXPERMS 

650 if op.content is not None: 

651 self._z.writestr(zi, op.content) 

652 # Write manifest 

653 zi = zipfile.ZipInfo(u"META-INF/manifest.xml", self._now) 

654 zi.compress_type = zipfile.ZIP_DEFLATED 

655 zi.external_attr = UNIXPERMS 

656 self._z.writestr(zi, self.__manifestxml() ) 

657 del self._z 

658 del self._now 

659 del self.manifest 

660 

661 

662 def _saveXmlObjects(self, anObject, folder): 

663 """ 

664 save xml objects of an opendocument to some folder 

665 @param anObject instance of OpenDocument 

666 @param folder unicode string place to save xml objects 

667 """ 

668 assert(isinstance(anObject, OpenDocument)) 

669 assert(type(folder)==type(u"")) 

670 

671 if self == anObject: 

672 self.manifest.addElement(manifest.FileEntry(fullpath=u"/", mediatype=anObject.mimetype)) 

673 else: 

674 self.manifest.addElement(manifest.FileEntry(fullpath=folder, mediatype=anObject.mimetype)) 

675 # Write styles 

676 self.manifest.addElement(manifest.FileEntry(fullpath=u"%sstyles.xml" % folder, mediatype=u"text/xml")) 

677 zi = zipfile.ZipInfo(u"%sstyles.xml" % folder, self._now) 

678 zi.compress_type = zipfile.ZIP_DEFLATED 

679 zi.external_attr = UNIXPERMS 

680 self._z.writestr(zi, anObject.stylesxml().encode("utf-8") ) 

681 

682 # Write content 

683 self.manifest.addElement(manifest.FileEntry(fullpath=u"%scontent.xml" % folder, mediatype=u"text/xml")) 

684 zi = zipfile.ZipInfo(u"%scontent.xml" % folder, self._now) 

685 zi.compress_type = zipfile.ZIP_DEFLATED 

686 zi.external_attr = UNIXPERMS 

687 self._z.writestr(zi, anObject.contentxml() ) 

688 

689 # Write settings 

690 if anObject.settings.hasChildNodes(): 

691 self.manifest.addElement(manifest.FileEntry(fullpath=u"%ssettings.xml" % folder, mediatype=u"text/xml")) 

692 zi = zipfile.ZipInfo(u"%ssettings.xml" % folder, self._now) 

693 zi.compress_type = zipfile.ZIP_DEFLATED 

694 zi.external_attr = UNIXPERMS 

695 self._z.writestr(zi, anObject.settingsxml().encode("utf-8") ) 

696 

697 # Write meta 

698 if self == anObject: 

699 self.manifest.addElement(manifest.FileEntry(fullpath=u"meta.xml", mediatype=u"text/xml")) 

700 zi = zipfile.ZipInfo(u"meta.xml", self._now) 

701 zi.compress_type = zipfile.ZIP_DEFLATED 

702 zi.external_attr = UNIXPERMS 

703 self._z.writestr(zi, anObject.metaxml().encode("utf-8") ) 

704 

705 # Write subobjects 

706 subobjectnum = 1 

707 for subobject in anObject.childobjects: 

708 self._saveXmlObjects(subobject, u'%sObject %d/' % (folder, subobjectnum)) 

709 subobjectnum += 1 

710 

711# Document's DOM methods 

712 def createElement(self, elt): 

713 """ 

714 Inconvenient interface to create an element, but follows XML-DOM. 

715 Does not allow attributes as argument, therefore can't check grammar. 

716 @param elt element.Element instance 

717 @return an element.Element instance whose grammar is not checked 

718 """ 

719 assert(isinstance(elt, element.Element)) 

720 

721 # this old code is ambiguous: is 'element' the module or is it the 

722 # local variable? To disambiguate this, the local variable has been 

723 # renamed to 'elt' 

724 #return element(check_grammar=False) 

725 return elt(check_grammar=False) 

726 

727 def createTextNode(self, data): 

728 """ 

729 Method to create a text node 

730 @param data unicode string to include in the Text element 

731 @return an instance of element.Text 

732 """ 

733 assert(type(data)==type(u"")) 

734 

735 return element.Text(data) 

736 

737 def createCDATASection(self, data): 

738 """ 

739 Method to create a CDATA section 

740 @param data unicode string to include in the CDATA element 

741 @return an instance of element.CDATASection 

742 """ 

743 assert(type(data)==type(u"")) 

744 

745 return element.CDATASection(cdata) 

746 

747 def getMediaType(self): 

748 """ 

749 Returns the media type 

750 @result a unicode string 

751 """ 

752 assert (type(self.mimetype)==type(u"")) 

753 

754 return self.mimetype 

755 

756 def getStyleByName(self, name): 

757 """ 

758 Finds a style object based on the name 

759 @param name unicode string the name of style to search 

760 @return a syle as an element.Element instance 

761 """ 

762 assert(type(name)==type(u"")) 

763 

764 ncname = make_NCName(name) 

765 if self._styles_dict == {}: 

766 self.rebuild_caches() 

767 result=self._styles_dict.get(ncname, None) 

768 

769 assert(isinstance(result, element.Element)) 

770 return result 

771 

772 def getElementsByType(self, elt): 

773 """ 

774 Gets elements based on the type, which is function from 

775 text.py, draw.py etc. 

776 @param elt instance of a function which returns an element.Element 

777 @return a list of istances of element.Element 

778 """ 

779 import types 

780 assert(isinstance (elt, types.FunctionType)) 

781 

782 obj = elt(check_grammar=False) 

783 assert (isinstance(obj, element.Element)) 

784 

785 if self.element_dict == {}: 

786 self.rebuild_caches() 

787 

788 # This previous code was ambiguous 

789 # was "element" the module name or the local variable? 

790 # the local variable is renamed to "elt" to disambiguate the code 

791 #return self.element_dict.get(obj.qname, []) 

792 

793 result=self.element_dict.get(obj.qname, []) 

794 

795 ok=True 

796 for e in result: ok = ok and isinstance(e, element.Element) 

797 assert(ok) 

798 

799 return result 

800 

801# Convenience functions 

802def OpenDocumentChart(): 

803 """ 

804 Creates a chart document 

805 @return an OpenDocument instance with chart mimetype 

806 """ 

807 doc = OpenDocument(u'application/vnd.oasis.opendocument.chart') 

808 doc.chart = Chart() 

809 doc.body.addElement(doc.chart) 

810 return doc 

811 

812def OpenDocumentDrawing(): 

813 """ 

814 Creates a drawing document 

815 @return an OpenDocument instance with drawing mimetype 

816 """ 

817 doc = OpenDocument(u'application/vnd.oasis.opendocument.graphics') 

818 doc.drawing = Drawing() 

819 doc.body.addElement(doc.drawing) 

820 return doc 

821 

822def OpenDocumentImage(): 

823 """ 

824 Creates an image document 

825 @return an OpenDocument instance with image mimetype 

826 """ 

827 doc = OpenDocument(u'application/vnd.oasis.opendocument.image') 

828 doc.image = Image() 

829 doc.body.addElement(doc.image) 

830 return doc 

831 

832def OpenDocumentPresentation(): 

833 """ 

834 Creates a presentation document 

835 @return an OpenDocument instance with presentation mimetype 

836 """ 

837 doc = OpenDocument(u'application/vnd.oasis.opendocument.presentation') 

838 doc.presentation = Presentation() 

839 doc.body.addElement(doc.presentation) 

840 return doc 

841 

842def OpenDocumentSpreadsheet(): 

843 """ 

844 Creates a spreadsheet document 

845 @return an OpenDocument instance with spreadsheet mimetype 

846 """ 

847 doc = OpenDocument(u'application/vnd.oasis.opendocument.spreadsheet') 

848 doc.spreadsheet = Spreadsheet() 

849 doc.body.addElement(doc.spreadsheet) 

850 return doc 

851 

852def OpenDocumentText(): 

853 """ 

854 Creates a text document 

855 @return an OpenDocument instance with text mimetype 

856 """ 

857 doc = OpenDocument(u'application/vnd.oasis.opendocument.text') 

858 doc.text = Text() 

859 doc.body.addElement(doc.text) 

860 return doc 

861 

862def OpenDocumentTextMaster(): 

863 """ 

864 Creates a text master document 

865 @return an OpenDocument instance with master mimetype 

866 """ 

867 doc = OpenDocument(u'application/vnd.oasis.opendocument.text-master') 

868 doc.text = Text() 

869 doc.body.addElement(doc.text) 

870 return doc 

871 

872def __loadxmlparts(z, manifest, doc, objectpath): 

873 """ 

874 Parses a document from its zipfile 

875 @param z an instance of zipfile.ZipFile 

876 @param manifest Manifest data structured in a dictionary 

877 @param doc instance of OpenDocument to feed in 

878 @param objectpath unicode string: path to an object 

879 """ 

880 assert(isinstance(z, zipfile.ZipFile)) 

881 assert(type(manifest)==type(dict())) 

882 assert(isinstance(doc, OpenDocument)) 

883 assert(type(objectpath)==type(u"")) 

884 

885 from odf.load import LoadParser 

886 from defusedxml.sax import make_parser 

887 from xml.sax import handler 

888 

889 for xmlfile in (objectpath+u'settings.xml', objectpath+u'meta.xml', objectpath+u'content.xml', objectpath+u'styles.xml'): 

890 if xmlfile not in manifest: 

891 continue 

892 ########################################################## 

893 # this one is added to debug the bad behavior with Python2 

894 # which raises exceptions of type SAXParseException 

895 from xml.sax._exceptions import SAXParseException 

896 ########################################################## 

897 try: 

898 xmlpart = z.read(xmlfile).decode("utf-8") 

899 doc._parsing = xmlfile 

900 

901 parser = make_parser() 

902 parser.setFeature(handler.feature_namespaces, 1) 

903 parser.setFeature(handler.feature_external_ges, 0) 

904 parser.setContentHandler(LoadParser(doc)) 

905 parser.setErrorHandler(handler.ErrorHandler()) 

906 

907 inpsrc = InputSource() 

908 ################# 

909 # There may be a SAXParseException triggered because of 

910 # a missing xmlns prefix like meta, config, etc. 

911 # So i add such declarations when needed (GK, 2014/10/21). 

912 # Is there any option to prevent xmlns checks by SAX? 

913 xmlpart=__fixXmlPart(xmlpart) 

914 

915 inpsrc.setByteStream(BytesIO(xmlpart.encode("utf-8"))) 

916 parser.parse(inpsrc) 

917 del doc._parsing 

918 except KeyError as v: pass 

919 except SAXParseException: 

920 print (u"====== SAX FAILED TO PARSE ==========\n", xmlpart) 

921 

922def __fixXmlPart(xmlpart): 

923 """ 

924 fixes an xml code when it does not contain a set of requested 

925 "xmlns:whatever" declarations. 

926 added by G.K. on 2014/10/21 

927 @param xmlpart unicode string: some XML code 

928 @return fixed XML code 

929 """ 

930 result=xmlpart 

931 requestedPrefixes = (u'meta', u'config', u'dc', u'style', 

932 u'svg', u'fo',u'draw', u'table',u'form') 

933 for prefix in requestedPrefixes: 

934 if u' xmlns:{prefix}'.format(prefix=prefix) not in xmlpart: 

935 ########################################### 

936 # fixed a bug triggered by math elements 

937 # Notice: math elements are creectly exported to XHTML 

938 # and best viewed with MathJax javascript. 

939 # 2016-02-19 G.K. 

940 ########################################### 

941 try: 

942 pos=result.index(u" xmlns:") 

943 toInsert=u' xmlns:{prefix}="urn:oasis:names:tc:opendocument:xmlns:{prefix}:1.0"'.format(prefix=prefix) 

944 result=result[:pos]+toInsert+result[pos:] 

945 except: 

946 pass 

947 return result 

948 

949 

950def __detectmimetype(zipfd, odffile): 

951 """ 

952 detects the mime-type of an ODF file 

953 @param zipfd an open zipfile.ZipFile instance 

954 @param odffile this parameter is not used 

955 @return a mime-type as a unicode string 

956 """ 

957 assert(isinstance(zipfd, zipfile.ZipFile)) 

958 

959 try: 

960 mimetype = zipfd.read('mimetype').decode("utf-8") 

961 return mimetype 

962 except: 

963 pass 

964 # Fall-through to next mechanism 

965 manifestpart = zipfd.read('META-INF/manifest.xml') 

966 manifest = manifestlist(manifestpart) 

967 for mentry,mvalue in manifest.items(): 

968 if mentry == "/": 

969 assert(type(mvalue['media-type'])==type(u"")) 

970 return mvalue['media-type'] 

971 

972 # Fall-through to last mechanism 

973 return u'application/vnd.oasis.opendocument.text' 

974 

975def load(odffile): 

976 """ 

977 Load an ODF file into memory 

978 @param odffile unicode string: name of a file, or as an alternative, 

979 an open readable stream 

980 @return a reference to the structure (an OpenDocument instance) 

981 """ 

982 z = zipfile.ZipFile(odffile) 

983 mimetype = __detectmimetype(z, odffile) 

984 doc = OpenDocument(mimetype, add_generator=False) 

985 

986 # Look in the manifest file to see if which of the four files there are 

987 manifestpart = z.read('META-INF/manifest.xml') 

988 manifest = manifestlist(manifestpart) 

989 __loadxmlparts(z, manifest, doc, u'') 

990 for mentry,mvalue in manifest.items(): 

991 if mentry[:9] == u"Pictures/" and len(mentry) > 9: 

992 doc.addPicture(mvalue['full-path'], mvalue['media-type'], z.read(mentry)) 

993 elif mentry == u"Thumbnails/thumbnail.png": 

994 doc.addThumbnail(z.read(mentry)) 

995 elif mentry in (u'settings.xml', u'meta.xml', u'content.xml', u'styles.xml'): 

996 pass 

997 # Load subobjects into structure 

998 elif mentry[:7] == u"Object " and len(mentry) < 11 and mentry[-1] == u"/": 

999 subdoc = OpenDocument(mvalue['media-type'], add_generator=False) 

1000 doc.addObject(subdoc, u"/" + mentry[:-1]) 

1001 __loadxmlparts(z, manifest, subdoc, mentry) 

1002 elif mentry[:7] == u"Object ": 

1003 pass # Don't load subobjects as opaque objects 

1004 else: 

1005 if mvalue['full-path'][-1] == u'/': 

1006 doc._extra.append(OpaqueObject(mvalue['full-path'], mvalue['media-type'], None)) 

1007 else: 

1008 doc._extra.append(OpaqueObject(mvalue['full-path'], mvalue['media-type'], z.read(mentry))) 

1009 # Add the SUN junk here to the struct somewhere 

1010 # It is cached data, so it can be out-of-date 

1011 z.close() 

1012 b = doc.getElementsByType(Body) 

1013 if mimetype[:39] == u'application/vnd.oasis.opendocument.text': 

1014 doc.text = b[0].firstChild 

1015 elif mimetype[:43] == u'application/vnd.oasis.opendocument.graphics': 

1016 doc.graphics = b[0].firstChild 

1017 elif mimetype[:47] == u'application/vnd.oasis.opendocument.presentation': 

1018 doc.presentation = b[0].firstChild 

1019 elif mimetype[:46] == u'application/vnd.oasis.opendocument.spreadsheet': 

1020 doc.spreadsheet = b[0].firstChild 

1021 elif mimetype[:40] == u'application/vnd.oasis.opendocument.chart': 

1022 doc.chart = b[0].firstChild 

1023 elif mimetype[:40] == u'application/vnd.oasis.opendocument.image': 

1024 doc.image = b[0].firstChild 

1025 elif mimetype[:42] == u'application/vnd.oasis.opendocument.formula': 

1026 doc.formula = b[0].firstChild 

1027 

1028 return doc 

1029 

1030# vim: set expandtab sw=4 :