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

OPCPackage.java 52KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.openxml4j.opc;
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.Closeable;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileOutputStream;
  21. import java.io.IOException;
  22. import java.io.InputStream;
  23. import java.io.OutputStream;
  24. import java.net.URI;
  25. import java.net.URISyntaxException;
  26. import java.util.ArrayList;
  27. import java.util.Collections;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.concurrent.locks.ReentrantReadWriteLock;
  33. import java.util.regex.Matcher;
  34. import java.util.regex.Pattern;
  35. import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
  36. import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
  37. import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
  38. import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
  39. import org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException;
  40. import org.apache.poi.openxml4j.opc.internal.ContentType;
  41. import org.apache.poi.openxml4j.opc.internal.ContentTypeManager;
  42. import org.apache.poi.openxml4j.opc.internal.PackagePropertiesPart;
  43. import org.apache.poi.openxml4j.opc.internal.PartMarshaller;
  44. import org.apache.poi.openxml4j.opc.internal.PartUnmarshaller;
  45. import org.apache.poi.openxml4j.opc.internal.ZipContentTypeManager;
  46. import org.apache.poi.openxml4j.opc.internal.marshallers.DefaultMarshaller;
  47. import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPackagePropertiesMarshaller;
  48. import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
  49. import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
  50. import org.apache.poi.openxml4j.util.Nullable;
  51. import org.apache.poi.openxml4j.util.ZipEntrySource;
  52. import org.apache.poi.util.NotImplemented;
  53. import org.apache.poi.util.POILogFactory;
  54. import org.apache.poi.util.POILogger;
  55. /**
  56. * Represents a container that can store multiple data objects.
  57. */
  58. public abstract class OPCPackage implements RelationshipSource, Closeable {
  59. /**
  60. * Logger.
  61. */
  62. private static final POILogger logger = POILogFactory.getLogger(OPCPackage.class);
  63. /**
  64. * Default package access.
  65. */
  66. protected static final PackageAccess defaultPackageAccess = PackageAccess.READ_WRITE;
  67. /**
  68. * Package access.
  69. */
  70. private PackageAccess packageAccess;
  71. /**
  72. * Package parts collection.
  73. */
  74. protected PackagePartCollection partList;
  75. /**
  76. * Package relationships.
  77. */
  78. protected PackageRelationshipCollection relationships;
  79. /**
  80. * Part marshallers by content type.
  81. */
  82. protected Map<ContentType, PartMarshaller> partMarshallers;
  83. /**
  84. * Default part marshaller.
  85. */
  86. protected PartMarshaller defaultPartMarshaller;
  87. /**
  88. * Part unmarshallers by content type.
  89. */
  90. protected Map<ContentType, PartUnmarshaller> partUnmarshallers;
  91. /**
  92. * Core package properties.
  93. */
  94. protected PackagePropertiesPart packageProperties;
  95. /**
  96. * Manage parts content types of this package.
  97. */
  98. protected ContentTypeManager contentTypeManager;
  99. /**
  100. * Flag if a modification is done to the document.
  101. */
  102. protected boolean isDirty = false;
  103. /**
  104. * File path of this package.
  105. */
  106. protected String originalPackagePath;
  107. /**
  108. * Output stream for writing this package.
  109. */
  110. protected OutputStream output;
  111. /**
  112. * Constructor.
  113. *
  114. * @param access
  115. * Package access.
  116. */
  117. OPCPackage(PackageAccess access) {
  118. if (getClass() != ZipPackage.class) {
  119. throw new IllegalArgumentException("PackageBase may not be subclassed");
  120. }
  121. init();
  122. this.packageAccess = access;
  123. }
  124. /**
  125. * Initialize the package instance.
  126. */
  127. private void init() {
  128. this.partMarshallers = new HashMap<ContentType, PartMarshaller>(5);
  129. this.partUnmarshallers = new HashMap<ContentType, PartUnmarshaller>(2);
  130. try {
  131. // Add 'default' unmarshaller
  132. this.partUnmarshallers.put(new ContentType(
  133. ContentTypes.CORE_PROPERTIES_PART),
  134. new PackagePropertiesUnmarshaller());
  135. // Add default marshaller
  136. this.defaultPartMarshaller = new DefaultMarshaller();
  137. // TODO Delocalize specialized marshallers
  138. this.partMarshallers.put(new ContentType(
  139. ContentTypes.CORE_PROPERTIES_PART),
  140. new ZipPackagePropertiesMarshaller());
  141. } catch (InvalidFormatException e) {
  142. // Should never happen
  143. throw new OpenXML4JRuntimeException(
  144. "Package.init() : this exception should never happen, " +
  145. "if you read this message please send a mail to the developers team. : " +
  146. e.getMessage(),
  147. e
  148. );
  149. }
  150. }
  151. /**
  152. * Open a package with read/write permission.
  153. *
  154. * @param path
  155. * The document path.
  156. * @return A Package object, else <b>null</b>.
  157. * @throws InvalidFormatException
  158. * If the specified file doesn't exist, and a parsing error
  159. * occur.
  160. */
  161. public static OPCPackage open(String path) throws InvalidFormatException {
  162. return open(path, defaultPackageAccess);
  163. }
  164. /**
  165. * Open a package with read/write permission.
  166. *
  167. * @param file
  168. * The file to open.
  169. * @return A Package object, else <b>null</b>.
  170. * @throws InvalidFormatException
  171. * If the specified file doesn't exist, and a parsing error
  172. * occur.
  173. */
  174. public static OPCPackage open(File file) throws InvalidFormatException {
  175. return open(file, defaultPackageAccess);
  176. }
  177. /**
  178. * Open an user provided {@link ZipEntrySource} with read-only permission.
  179. * This method can be used to stream data into POI.
  180. * Opposed to other open variants, the data is read as-is, e.g. there aren't
  181. * any zip-bomb protection put in place.
  182. *
  183. * @param zipEntry the custom source
  184. * @return A Package object
  185. * @throws InvalidFormatException if a parsing error occur.
  186. */
  187. public static OPCPackage open(ZipEntrySource zipEntry)
  188. throws InvalidFormatException {
  189. OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ);
  190. try {
  191. if (pack.partList == null) {
  192. pack.getParts();
  193. }
  194. // pack.originalPackagePath = file.getAbsolutePath();
  195. return pack;
  196. } catch (InvalidFormatException e) {
  197. try {
  198. pack.close();
  199. } catch (IOException e1) {
  200. throw new IllegalStateException(e);
  201. }
  202. throw e;
  203. } catch (RuntimeException e) {
  204. try {
  205. pack.close();
  206. } catch (IOException e1) {
  207. throw new IllegalStateException(e);
  208. }
  209. throw e;
  210. }
  211. }
  212. /**
  213. * Open a package.
  214. *
  215. * @param path
  216. * The document path.
  217. * @param access
  218. * PackageBase access.
  219. * @return A PackageBase object, else <b>null</b>.
  220. * @throws InvalidFormatException
  221. * If the specified file doesn't exist, and a parsing error
  222. * occur.
  223. * @throws InvalidOperationException
  224. */
  225. public static OPCPackage open(String path, PackageAccess access)
  226. throws InvalidFormatException, InvalidOperationException {
  227. if (path == null || "".equals(path.trim())) {
  228. throw new IllegalArgumentException("'path' must be given");
  229. }
  230. File file = new File(path);
  231. if (file.exists() && file.isDirectory()) {
  232. throw new IllegalArgumentException("path must not be a directory");
  233. }
  234. OPCPackage pack = new ZipPackage(path, access);
  235. boolean success = false;
  236. if (pack.partList == null && access != PackageAccess.WRITE) {
  237. try {
  238. pack.getParts();
  239. success = true;
  240. } finally {
  241. if (! success) {
  242. try {
  243. pack.close();
  244. } catch (final IOException e) {
  245. throw new InvalidOperationException("Could not close OPCPackage while cleaning up", e);
  246. }
  247. }
  248. }
  249. }
  250. pack.originalPackagePath = new File(path).getAbsolutePath();
  251. return pack;
  252. }
  253. /**
  254. * Open a package.
  255. *
  256. * @param file
  257. * The file to open.
  258. * @param access
  259. * PackageBase access.
  260. * @return A PackageBase object, else <b>null</b>.
  261. * @throws InvalidFormatException
  262. * If the specified file doesn't exist, and a parsing error
  263. * occur.
  264. */
  265. public static OPCPackage open(File file, PackageAccess access)
  266. throws InvalidFormatException {
  267. if (file == null) {
  268. throw new IllegalArgumentException("'file' must be given");
  269. }
  270. if (file.exists() && file.isDirectory()) {
  271. throw new IllegalArgumentException("file must not be a directory");
  272. }
  273. OPCPackage pack = new ZipPackage(file, access);
  274. try {
  275. if (pack.partList == null && access != PackageAccess.WRITE) {
  276. pack.getParts();
  277. }
  278. pack.originalPackagePath = file.getAbsolutePath();
  279. return pack;
  280. } catch (InvalidFormatException e) {
  281. try {
  282. pack.close();
  283. } catch (IOException e1) {
  284. throw new IllegalStateException(e);
  285. }
  286. throw e;
  287. } catch (RuntimeException e) {
  288. try {
  289. pack.close();
  290. } catch (IOException e1) {
  291. throw new IllegalStateException(e);
  292. }
  293. throw e;
  294. }
  295. }
  296. /**
  297. * Open a package.
  298. *
  299. * Note - uses quite a bit more memory than {@link #open(String)}, which
  300. * doesn't need to hold the whole zip file in memory, and can take advantage
  301. * of native methods
  302. *
  303. * @param in
  304. * The InputStream to read the package from
  305. * @return A PackageBase object
  306. */
  307. public static OPCPackage open(InputStream in) throws InvalidFormatException,
  308. IOException {
  309. OPCPackage pack = new ZipPackage(in, PackageAccess.READ_WRITE);
  310. if (pack.partList == null) {
  311. pack.getParts();
  312. }
  313. return pack;
  314. }
  315. /**
  316. * Opens a package if it exists, else it creates one.
  317. *
  318. * @param file
  319. * The file to open or to create.
  320. * @return A newly created package if the specified file does not exist,
  321. * else the package extract from the file.
  322. * @throws InvalidFormatException
  323. * Throws if the specified file exist and is not valid.
  324. */
  325. public static OPCPackage openOrCreate(File file) throws InvalidFormatException {
  326. OPCPackage retPackage = null;
  327. if (file.exists()) {
  328. retPackage = open(file.getAbsolutePath());
  329. } else {
  330. retPackage = create(file);
  331. }
  332. return retPackage;
  333. }
  334. /**
  335. * Creates a new package.
  336. *
  337. * @param path
  338. * Path of the document.
  339. * @return A newly created PackageBase ready to use.
  340. */
  341. public static OPCPackage create(String path) {
  342. return create(new File(path));
  343. }
  344. /**
  345. * Creates a new package.
  346. *
  347. * @param file
  348. * Path of the document.
  349. * @return A newly created PackageBase ready to use.
  350. */
  351. public static OPCPackage create(File file) {
  352. if (file == null || (file.exists() && file.isDirectory())) {
  353. throw new IllegalArgumentException("file");
  354. }
  355. if (file.exists()) {
  356. throw new InvalidOperationException(
  357. "This package (or file) already exists : use the open() method or delete the file.");
  358. }
  359. // Creates a new package
  360. OPCPackage pkg = new ZipPackage();
  361. pkg.originalPackagePath = file.getAbsolutePath();
  362. configurePackage(pkg);
  363. return pkg;
  364. }
  365. public static OPCPackage create(OutputStream output) {
  366. OPCPackage pkg = new ZipPackage();
  367. pkg.originalPackagePath = null;
  368. pkg.output = output;
  369. configurePackage(pkg);
  370. return pkg;
  371. }
  372. /**
  373. * Configure the package.
  374. *
  375. * @param pkg
  376. */
  377. private static void configurePackage(OPCPackage pkg) {
  378. try {
  379. // Content type manager
  380. pkg.contentTypeManager = new ZipContentTypeManager(null, pkg);
  381. // Add default content types for .xml and .rels
  382. pkg.contentTypeManager.addContentType(
  383. PackagingURIHelper.createPartName(
  384. PackagingURIHelper.PACKAGE_RELATIONSHIPS_ROOT_URI),
  385. ContentTypes.RELATIONSHIPS_PART);
  386. pkg.contentTypeManager.addContentType(
  387. PackagingURIHelper.createPartName("/default.xml"),
  388. ContentTypes.PLAIN_OLD_XML);
  389. // Initialise some PackageBase properties
  390. pkg.packageProperties = new PackagePropertiesPart(pkg,
  391. PackagingURIHelper.CORE_PROPERTIES_PART_NAME);
  392. pkg.packageProperties.setCreatorProperty("Generated by Apache POI OpenXML4J");
  393. pkg.packageProperties.setCreatedProperty(new Nullable<Date>(new Date()));
  394. } catch (InvalidFormatException e) {
  395. // Should never happen
  396. throw new IllegalStateException(e);
  397. }
  398. }
  399. /**
  400. * Flush the package : save all.
  401. *
  402. * @see #close()
  403. */
  404. public void flush() {
  405. throwExceptionIfReadOnly();
  406. if (this.packageProperties != null) {
  407. this.packageProperties.flush();
  408. }
  409. this.flushImpl();
  410. }
  411. /**
  412. * Close the open, writable package and save its content.
  413. *
  414. * If your package is open read only, then you should call {@link #revert()}
  415. * when finished with the package.
  416. *
  417. * @throws IOException
  418. * If an IO exception occur during the saving process.
  419. */
  420. @Override
  421. public void close() throws IOException {
  422. if (this.packageAccess == PackageAccess.READ) {
  423. logger.log(POILogger.WARN,
  424. "The close() method is intended to SAVE a package. This package is open in READ ONLY mode, use the revert() method instead !");
  425. revert();
  426. return;
  427. }
  428. if (this.contentTypeManager == null) {
  429. logger.log(POILogger.WARN,
  430. "Unable to call close() on a package that hasn't been fully opened yet");
  431. revert();
  432. return;
  433. }
  434. // Save the content
  435. ReentrantReadWriteLock l = new ReentrantReadWriteLock();
  436. try {
  437. l.writeLock().lock();
  438. if (this.originalPackagePath != null
  439. && !"".equals(this.originalPackagePath.trim())) {
  440. File targetFile = new File(this.originalPackagePath);
  441. if (!targetFile.exists()
  442. || !(this.originalPackagePath
  443. .equalsIgnoreCase(targetFile.getAbsolutePath()))) {
  444. // Case of a package created from scratch
  445. save(targetFile);
  446. } else {
  447. closeImpl();
  448. }
  449. } else if (this.output != null) {
  450. save(this.output);
  451. output.close();
  452. }
  453. } finally {
  454. l.writeLock().unlock();
  455. }
  456. // Clear
  457. this.contentTypeManager.clearAll();
  458. }
  459. /**
  460. * Close the package WITHOUT saving its content. Reinitialize this package
  461. * and cancel all changes done to it.
  462. */
  463. public void revert() {
  464. revertImpl();
  465. }
  466. /**
  467. * Add a thumbnail to the package. This method is provided to make easier
  468. * the addition of a thumbnail in a package. You can do the same work by
  469. * using the traditionnal relationship and part mechanism.
  470. *
  471. * @param path The full path to the image file.
  472. */
  473. public void addThumbnail(String path) throws IOException {
  474. // Check parameter
  475. if (path == null || path.isEmpty()) {
  476. throw new IllegalArgumentException("path");
  477. }
  478. String name = path.substring(path.lastIndexOf(File.separatorChar) + 1);
  479. FileInputStream is = new FileInputStream(path);
  480. try {
  481. addThumbnail(name, is);
  482. } finally {
  483. is.close();
  484. }
  485. }
  486. /**
  487. * Add a thumbnail to the package. This method is provided to make easier
  488. * the addition of a thumbnail in a package. You can do the same work by
  489. * using the traditionnal relationship and part mechanism.
  490. *
  491. * @param filename The full path to the image file.
  492. * @param data the image data
  493. */
  494. public void addThumbnail(String filename, InputStream data) throws IOException {
  495. // Check parameter
  496. if (filename == null || filename.isEmpty()) {
  497. throw new IllegalArgumentException("filename");
  498. }
  499. // Create the thumbnail part name
  500. String contentType = ContentTypes
  501. .getContentTypeFromFileExtension(filename);
  502. PackagePartName thumbnailPartName;
  503. try {
  504. thumbnailPartName = PackagingURIHelper.createPartName("/docProps/"
  505. + filename);
  506. } catch (InvalidFormatException e) {
  507. String partName = "/docProps/thumbnail" +
  508. filename.substring(filename.lastIndexOf(".") + 1);
  509. try {
  510. thumbnailPartName = PackagingURIHelper.createPartName(partName);
  511. } catch (InvalidFormatException e2) {
  512. throw new InvalidOperationException(
  513. "Can't add a thumbnail file named '" + filename + "'", e2);
  514. }
  515. }
  516. // Check if part already exist
  517. if (this.getPart(thumbnailPartName) != null)
  518. throw new InvalidOperationException(
  519. "You already add a thumbnail named '" + filename + "'");
  520. // Add the thumbnail part to this package.
  521. PackagePart thumbnailPart = this.createPart(thumbnailPartName,
  522. contentType, false);
  523. // Add the relationship between the package and the thumbnail part
  524. this.addRelationship(thumbnailPartName, TargetMode.INTERNAL,
  525. PackageRelationshipTypes.THUMBNAIL);
  526. // Copy file data to the newly created part
  527. StreamHelper.copyStream(data, thumbnailPart.getOutputStream());
  528. }
  529. /**
  530. * Throws an exception if the package access mode is in read only mode
  531. * (PackageAccess.Read).
  532. *
  533. * @throws InvalidOperationException
  534. * Throws if a writing operation is done on a read only package.
  535. * @see org.apache.poi.openxml4j.opc.PackageAccess
  536. */
  537. void throwExceptionIfReadOnly() throws InvalidOperationException {
  538. if (packageAccess == PackageAccess.READ) {
  539. throw new InvalidOperationException(
  540. "Operation not allowed, document open in read only mode!");
  541. }
  542. }
  543. /**
  544. * Throws an exception if the package access mode is in write only mode
  545. * (PackageAccess.Write). This method is call when other methods need write
  546. * right.
  547. *
  548. * @throws InvalidOperationException
  549. * Throws if a read operation is done on a write only package.
  550. * @see org.apache.poi.openxml4j.opc.PackageAccess
  551. */
  552. void throwExceptionIfWriteOnly() throws InvalidOperationException {
  553. if (packageAccess == PackageAccess.WRITE) {
  554. throw new InvalidOperationException(
  555. "Operation not allowed, document open in write only mode!");
  556. }
  557. }
  558. /**
  559. * Retrieves or creates if none exists, core package property part.
  560. *
  561. * @return The PackageProperties part of this package.
  562. */
  563. public PackageProperties getPackageProperties()
  564. throws InvalidFormatException {
  565. this.throwExceptionIfWriteOnly();
  566. // If no properties part has been found then we create one
  567. if (this.packageProperties == null) {
  568. this.packageProperties = new PackagePropertiesPart(this,
  569. PackagingURIHelper.CORE_PROPERTIES_PART_NAME);
  570. }
  571. return this.packageProperties;
  572. }
  573. /**
  574. * Retrieve a part identified by its name.
  575. *
  576. * @param partName
  577. * Part name of the part to retrieve.
  578. * @return The part with the specified name, else <code>null</code>.
  579. */
  580. public PackagePart getPart(PackagePartName partName) {
  581. throwExceptionIfWriteOnly();
  582. if (partName == null) {
  583. throw new IllegalArgumentException("partName");
  584. }
  585. // If the partlist is null, then we parse the package.
  586. if (partList == null) {
  587. try {
  588. getParts();
  589. } catch (InvalidFormatException e) {
  590. return null;
  591. }
  592. }
  593. return getPartImpl(partName);
  594. }
  595. /**
  596. * Retrieve parts by content type.
  597. *
  598. * @param contentType
  599. * The content type criteria.
  600. * @return All part associated to the specified content type.
  601. */
  602. public ArrayList<PackagePart> getPartsByContentType(String contentType) {
  603. ArrayList<PackagePart> retArr = new ArrayList<PackagePart>();
  604. for (PackagePart part : partList.values()) {
  605. if (part.getContentType().equals(contentType)) {
  606. retArr.add(part);
  607. }
  608. }
  609. Collections.sort(retArr);
  610. return retArr;
  611. }
  612. /**
  613. * Retrieve parts by relationship type.
  614. *
  615. * @param relationshipType
  616. * Relationship type.
  617. * @return All parts which are the target of a relationship with the
  618. * specified type, if the method can't retrieve relationships from
  619. * the package, then return <code>null</code>.
  620. */
  621. public ArrayList<PackagePart> getPartsByRelationshipType(
  622. String relationshipType) {
  623. if (relationshipType == null) {
  624. throw new IllegalArgumentException("relationshipType");
  625. }
  626. ArrayList<PackagePart> retArr = new ArrayList<PackagePart>();
  627. for (PackageRelationship rel : getRelationshipsByType(relationshipType)) {
  628. PackagePart part = getPart(rel);
  629. if (part != null) {
  630. retArr.add(part);
  631. }
  632. }
  633. Collections.sort(retArr);
  634. return retArr;
  635. }
  636. /**
  637. * Retrieve parts by name
  638. *
  639. * @param namePattern
  640. * The pattern for matching the names
  641. * @return All parts associated to the specified content type, sorted
  642. * in alphanumerically by the part-name
  643. */
  644. public List<PackagePart> getPartsByName(final Pattern namePattern) {
  645. if (namePattern == null) {
  646. throw new IllegalArgumentException("name pattern must not be null");
  647. }
  648. Matcher matcher = namePattern.matcher("");
  649. ArrayList<PackagePart> result = new ArrayList<PackagePart>();
  650. for (PackagePart part : partList.values()) {
  651. PackagePartName partName = part.getPartName();
  652. if (matcher.reset(partName.getName()).matches()) {
  653. result.add(part);
  654. }
  655. }
  656. Collections.sort(result);
  657. return result;
  658. }
  659. /**
  660. * Get the target part from the specified relationship.
  661. *
  662. * @param partRel
  663. * The part relationship uses to retrieve the part.
  664. */
  665. public PackagePart getPart(PackageRelationship partRel) {
  666. PackagePart retPart = null;
  667. ensureRelationships();
  668. for (PackageRelationship rel : relationships) {
  669. if (rel.getRelationshipType().equals(partRel.getRelationshipType())) {
  670. try {
  671. retPart = getPart(PackagingURIHelper.createPartName(rel
  672. .getTargetURI()));
  673. } catch (InvalidFormatException e) {
  674. continue;
  675. }
  676. break;
  677. }
  678. }
  679. return retPart;
  680. }
  681. /**
  682. * Load the parts of the archive if it has not been done yet. The
  683. * relationships of each part are not loaded.
  684. *
  685. * Note - Rule M4.1 states that there may only ever be one Core
  686. * Properties Part, but Office produced files will sometimes
  687. * have multiple! As Office ignores all but the first, we relax
  688. * Compliance with Rule M4.1, and ignore all others silently too.
  689. *
  690. * @return All this package's parts.
  691. */
  692. public ArrayList<PackagePart> getParts() throws InvalidFormatException {
  693. throwExceptionIfWriteOnly();
  694. // If the part list is null, we parse the package to retrieve all parts.
  695. if (partList == null) {
  696. /* Variables use to validate OPC Compliance */
  697. // Check rule M4.1 -> A format consumer shall consider more than
  698. // one core properties relationship for a package to be an error
  699. // (We just log it and move on, as real files break this!)
  700. boolean hasCorePropertiesPart = false;
  701. boolean needCorePropertiesPart = true;
  702. PackagePart[] parts = this.getPartsImpl();
  703. this.partList = new PackagePartCollection();
  704. for (PackagePart part : parts) {
  705. if (partList.containsKey(part._partName)) {
  706. throw new InvalidFormatException(
  707. "A part with the name '" +
  708. part._partName +
  709. "' already exist : Packages shall not contain equivalent " +
  710. "part names and package implementers shall neither create " +
  711. "nor recognize packages with equivalent part names. [M1.12]");
  712. }
  713. // Check OPC compliance rule M4.1
  714. if (part.getContentType().equals(
  715. ContentTypes.CORE_PROPERTIES_PART)) {
  716. if (!hasCorePropertiesPart) {
  717. hasCorePropertiesPart = true;
  718. } else {
  719. logger.log(POILogger.WARN, "OPC Compliance error [M4.1]: " +
  720. "there is more than one core properties relationship in the package! " +
  721. "POI will use only the first, but other software may reject this file.");
  722. }
  723. }
  724. PartUnmarshaller partUnmarshaller = partUnmarshallers.get(part._contentType);
  725. if (partUnmarshaller != null) {
  726. UnmarshallContext context = new UnmarshallContext(this,
  727. part._partName);
  728. try {
  729. PackagePart unmarshallPart = partUnmarshaller
  730. .unmarshall(context, part.getInputStream());
  731. partList.put(unmarshallPart._partName, unmarshallPart);
  732. // Core properties case-- use first CoreProperties part we come across
  733. // and ignore any subsequent ones
  734. if (unmarshallPart instanceof PackagePropertiesPart &&
  735. hasCorePropertiesPart &&
  736. needCorePropertiesPart) {
  737. this.packageProperties = (PackagePropertiesPart) unmarshallPart;
  738. needCorePropertiesPart = false;
  739. }
  740. } catch (IOException ioe) {
  741. logger.log(POILogger.WARN, "Unmarshall operation : IOException for "
  742. + part._partName);
  743. continue;
  744. } catch (InvalidOperationException invoe) {
  745. throw new InvalidFormatException(invoe.getMessage(), invoe);
  746. }
  747. } else {
  748. try {
  749. partList.put(part._partName, part);
  750. } catch (InvalidOperationException e) {
  751. throw new InvalidFormatException(e.getMessage(), e);
  752. }
  753. }
  754. }
  755. }
  756. ArrayList<PackagePart> result = new ArrayList<PackagePart>(partList.values());
  757. java.util.Collections.sort(result);
  758. return result;
  759. }
  760. /**
  761. * Create and add a part, with the specified name and content type, to the
  762. * package.
  763. *
  764. * @param partName
  765. * Part name.
  766. * @param contentType
  767. * Part content type.
  768. * @return The newly created part.
  769. * @throws PartAlreadyExistsException
  770. * If rule M1.12 is not verified : Packages shall not contain
  771. * equivalent part names and package implementers shall neither
  772. * create nor recognize packages with equivalent part names.
  773. * @see #createPartImpl(PackagePartName, String, boolean)
  774. */
  775. public PackagePart createPart(PackagePartName partName, String contentType) {
  776. return this.createPart(partName, contentType, true);
  777. }
  778. /**
  779. * Create and add a part, with the specified name and content type, to the
  780. * package. For general purpose, prefer the overload version of this method
  781. * without the 'loadRelationships' parameter.
  782. *
  783. * @param partName
  784. * Part name.
  785. * @param contentType
  786. * Part content type.
  787. * @param loadRelationships
  788. * Specify if the existing relationship part, if any, logically
  789. * associated to the newly created part will be loaded.
  790. * @return The newly created part.
  791. * @throws PartAlreadyExistsException
  792. * If rule M1.12 is not verified : Packages shall not contain
  793. * equivalent part names and package implementers shall neither
  794. * create nor recognize packages with equivalent part names.
  795. * @see #createPartImpl(PackagePartName, String, boolean)
  796. */
  797. PackagePart createPart(PackagePartName partName, String contentType,
  798. boolean loadRelationships) {
  799. throwExceptionIfReadOnly();
  800. if (partName == null) {
  801. throw new IllegalArgumentException("partName");
  802. }
  803. if (contentType == null || contentType.equals("")) {
  804. throw new IllegalArgumentException("contentType");
  805. }
  806. // Check if the specified part name already exists
  807. if (partList.containsKey(partName)
  808. && !partList.get(partName).isDeleted()) {
  809. throw new PartAlreadyExistsException(
  810. "A part with the name '" + partName.getName() + "'" +
  811. " already exists : Packages shall not contain equivalent part names and package" +
  812. " implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
  813. }
  814. /* Check OPC compliance */
  815. // Rule [M4.1]: The format designer shall specify and the format producer
  816. // shall create at most one core properties relationship for a package.
  817. // A format consumer shall consider more than one core properties
  818. // relationship for a package to be an error. If present, the
  819. // relationship shall target the Core Properties part.
  820. // Note - POI will read files with more than one Core Properties, which
  821. // Office sometimes produces, but is strict on generation
  822. if (contentType.equals(ContentTypes.CORE_PROPERTIES_PART)) {
  823. if (this.packageProperties != null) {
  824. throw new InvalidOperationException(
  825. "OPC Compliance error [M4.1]: you try to add more than one core properties relationship in the package !");
  826. }
  827. }
  828. /* End check OPC compliance */
  829. PackagePart part = this.createPartImpl(partName, contentType,
  830. loadRelationships);
  831. this.contentTypeManager.addContentType(partName, contentType);
  832. this.partList.put(partName, part);
  833. this.isDirty = true;
  834. return part;
  835. }
  836. /**
  837. * Add a part to the package.
  838. *
  839. * @param partName
  840. * Part name of the part to create.
  841. * @param contentType
  842. * type associated with the file
  843. * @param content
  844. * the contents to add. In order to have faster operation in
  845. * document merge, the data are stored in memory not on a hard
  846. * disk
  847. *
  848. * @return The new part.
  849. * @see #createPart(PackagePartName, String)
  850. */
  851. public PackagePart createPart(PackagePartName partName, String contentType,
  852. ByteArrayOutputStream content) {
  853. PackagePart addedPart = this.createPart(partName, contentType);
  854. if (addedPart == null) {
  855. return null;
  856. }
  857. // Extract the zip entry content to put it in the part content
  858. if (content != null) {
  859. try {
  860. OutputStream partOutput = addedPart.getOutputStream();
  861. if (partOutput == null) {
  862. return null;
  863. }
  864. partOutput.write(content.toByteArray(), 0, content.size());
  865. partOutput.close();
  866. } catch (IOException ioe) {
  867. return null;
  868. }
  869. } else {
  870. return null;
  871. }
  872. return addedPart;
  873. }
  874. /**
  875. * Add the specified part to the package. If a part already exists in the
  876. * package with the same name as the one specified, then we replace the old
  877. * part by the specified part.
  878. *
  879. * @param part
  880. * The part to add (or replace).
  881. * @return The part added to the package, the same as the one specified.
  882. * @throws InvalidFormatException
  883. * If rule M1.12 is not verified : Packages shall not contain
  884. * equivalent part names and package implementers shall neither
  885. * create nor recognize packages with equivalent part names.
  886. */
  887. protected PackagePart addPackagePart(PackagePart part) {
  888. throwExceptionIfReadOnly();
  889. if (part == null) {
  890. throw new IllegalArgumentException("part");
  891. }
  892. if (partList.containsKey(part._partName)) {
  893. if (!partList.get(part._partName).isDeleted()) {
  894. throw new InvalidOperationException(
  895. "A part with the name '"
  896. + part._partName.getName()
  897. + "' already exists : Packages shall not contain equivalent part names and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");
  898. }
  899. // If the specified partis flagged as deleted, we make it
  900. // available
  901. part.setDeleted(false);
  902. // and delete the old part to replace it thereafeter
  903. this.partList.remove(part._partName);
  904. }
  905. this.partList.put(part._partName, part);
  906. this.isDirty = true;
  907. return part;
  908. }
  909. /**
  910. * Remove the specified part in this package. If this part is relationship
  911. * part, then delete all relationships in the source part.
  912. *
  913. * @param part
  914. * The part to remove. If <code>null</code>, skip the action.
  915. * @see #removePart(PackagePartName)
  916. */
  917. public void removePart(PackagePart part) {
  918. if (part != null) {
  919. removePart(part.getPartName());
  920. }
  921. }
  922. /**
  923. * Remove a part in this package. If this part is relationship part, then
  924. * delete all relationships in the source part.
  925. *
  926. * @param partName
  927. * The part name of the part to remove.
  928. */
  929. public void removePart(PackagePartName partName) {
  930. throwExceptionIfReadOnly();
  931. if (partName == null || !this.containPart(partName)) {
  932. throw new IllegalArgumentException("partName");
  933. }
  934. // Delete the specified part from the package.
  935. if (this.partList.containsKey(partName)) {
  936. this.partList.get(partName).setDeleted(true);
  937. this.removePartImpl(partName);
  938. this.partList.remove(partName);
  939. } else {
  940. this.removePartImpl(partName);
  941. }
  942. // Delete content type
  943. this.contentTypeManager.removeContentType(partName);
  944. // If this part is a relationship part, then delete all relationships of
  945. // the source part.
  946. if (partName.isRelationshipPartURI()) {
  947. URI sourceURI = PackagingURIHelper
  948. .getSourcePartUriFromRelationshipPartUri(partName.getURI());
  949. PackagePartName sourcePartName;
  950. try {
  951. sourcePartName = PackagingURIHelper.createPartName(sourceURI);
  952. } catch (InvalidFormatException e) {
  953. logger
  954. .log(POILogger.ERROR, "Part name URI '"
  955. + sourceURI
  956. + "' is not valid ! This message is not intended to be displayed !");
  957. return;
  958. }
  959. if (sourcePartName.getURI().equals(
  960. PackagingURIHelper.PACKAGE_ROOT_URI)) {
  961. clearRelationships();
  962. } else if (containPart(sourcePartName)) {
  963. PackagePart part = getPart(sourcePartName);
  964. if (part != null) {
  965. part.clearRelationships();
  966. }
  967. }
  968. }
  969. this.isDirty = true;
  970. }
  971. /**
  972. * Remove a part from this package as well as its relationship part, if one
  973. * exists, and all parts listed in the relationship part. Be aware that this
  974. * do not delete relationships which target the specified part.
  975. *
  976. * @param partName
  977. * The name of the part to delete.
  978. * @throws InvalidFormatException
  979. * Throws if the associated relationship part of the specified
  980. * part is not valid.
  981. */
  982. public void removePartRecursive(PackagePartName partName)
  983. throws InvalidFormatException {
  984. // Retrieves relationship part, if one exists
  985. PackagePart relPart = this.partList.get(PackagingURIHelper
  986. .getRelationshipPartName(partName));
  987. // Retrieves PackagePart object from the package
  988. PackagePart partToRemove = this.partList.get(partName);
  989. if (relPart != null) {
  990. PackageRelationshipCollection partRels = new PackageRelationshipCollection(
  991. partToRemove);
  992. for (PackageRelationship rel : partRels) {
  993. PackagePartName partNameToRemove = PackagingURIHelper
  994. .createPartName(PackagingURIHelper.resolvePartUri(rel
  995. .getSourceURI(), rel.getTargetURI()));
  996. removePart(partNameToRemove);
  997. }
  998. // Finally delete its relationship part if one exists
  999. this.removePart(relPart._partName);
  1000. }
  1001. // Delete the specified part
  1002. this.removePart(partToRemove._partName);
  1003. }
  1004. /**
  1005. * Delete the part with the specified name and its associated relationships
  1006. * part if one exists. Prefer the use of this method to delete a part in the
  1007. * package, compare to the remove() methods that don't remove associated
  1008. * relationships part.
  1009. *
  1010. * @param partName
  1011. * Name of the part to delete
  1012. */
  1013. public void deletePart(PackagePartName partName) {
  1014. if (partName == null) {
  1015. throw new IllegalArgumentException("partName");
  1016. }
  1017. // Remove the part
  1018. this.removePart(partName);
  1019. // Remove the relationships part
  1020. this.removePart(PackagingURIHelper.getRelationshipPartName(partName));
  1021. }
  1022. /**
  1023. * Delete the part with the specified name and all part listed in its
  1024. * associated relationships part if one exists. This process is recursively
  1025. * apply to all parts in the relationships part of the specified part.
  1026. * Prefer the use of this method to delete a part in the package, compare to
  1027. * the remove() methods that don't remove associated relationships part.
  1028. *
  1029. * @param partName
  1030. * Name of the part to delete
  1031. */
  1032. public void deletePartRecursive(PackagePartName partName) {
  1033. if (partName == null || !this.containPart(partName)) {
  1034. throw new IllegalArgumentException("partName");
  1035. }
  1036. PackagePart partToDelete = this.getPart(partName);
  1037. // Remove the part
  1038. this.removePart(partName);
  1039. // Remove all relationship parts associated
  1040. try {
  1041. for (PackageRelationship relationship : partToDelete
  1042. .getRelationships()) {
  1043. PackagePartName targetPartName = PackagingURIHelper
  1044. .createPartName(PackagingURIHelper.resolvePartUri(
  1045. partName.getURI(), relationship.getTargetURI()));
  1046. this.deletePartRecursive(targetPartName);
  1047. }
  1048. } catch (InvalidFormatException e) {
  1049. logger.log(POILogger.WARN, "An exception occurs while deleting part '"
  1050. + partName.getName()
  1051. + "'. Some parts may remain in the package. - "
  1052. + e.getMessage());
  1053. return;
  1054. }
  1055. // Remove the relationships part
  1056. PackagePartName relationshipPartName = PackagingURIHelper
  1057. .getRelationshipPartName(partName);
  1058. if (relationshipPartName != null && containPart(relationshipPartName)) {
  1059. this.removePart(relationshipPartName);
  1060. }
  1061. }
  1062. /**
  1063. * Check if a part already exists in this package from its name.
  1064. *
  1065. * @param partName
  1066. * Part name to check.
  1067. * @return <i>true</i> if the part is logically added to this package, else
  1068. * <i>false</i>.
  1069. */
  1070. public boolean containPart(PackagePartName partName) {
  1071. return (this.getPart(partName) != null);
  1072. }
  1073. /**
  1074. * Add a relationship to the package (except relationships part).
  1075. *
  1076. * Check rule M4.1 : The format designer shall specify and the format
  1077. * producer shall create at most one core properties relationship for a
  1078. * package. A format consumer shall consider more than one core properties
  1079. * relationship for a package to be an error. If present, the relationship
  1080. * shall target the Core Properties part.
  1081. *
  1082. * Check rule M1.25: The Relationships part shall not have relationships to
  1083. * any other part. Package implementers shall enforce this requirement upon
  1084. * the attempt to create such a relationship and shall treat any such
  1085. * relationship as invalid.
  1086. *
  1087. * @param targetPartName
  1088. * Target part name.
  1089. * @param targetMode
  1090. * Target mode, either Internal or External.
  1091. * @param relationshipType
  1092. * Relationship type.
  1093. * @param relID
  1094. * ID of the relationship.
  1095. * @see PackageRelationshipTypes
  1096. */
  1097. @Override
  1098. public PackageRelationship addRelationship(PackagePartName targetPartName,
  1099. TargetMode targetMode, String relationshipType, String relID) {
  1100. /* Check OPC compliance */
  1101. // Check rule M4.1 : The format designer shall specify and the format
  1102. // producer
  1103. // shall create at most one core properties relationship for a package.
  1104. // A format consumer shall consider more than one core properties
  1105. // relationship for a package to be an error. If present, the
  1106. // relationship shall target the Core Properties part.
  1107. if (relationshipType.equals(PackageRelationshipTypes.CORE_PROPERTIES)
  1108. && this.packageProperties != null) {
  1109. throw new InvalidOperationException(
  1110. "OPC Compliance error [M4.1]: can't add another core properties part ! Use the built-in package method instead.");
  1111. }
  1112. /*
  1113. * Check rule M1.25: The Relationships part shall not have relationships
  1114. * to any other part. Package implementers shall enforce this
  1115. * requirement upon the attempt to create such a relationship and shall
  1116. * treat any such relationship as invalid.
  1117. */
  1118. if (targetPartName.isRelationshipPartURI()) {
  1119. throw new InvalidOperationException(
  1120. "Rule M1.25: The Relationships part shall not have relationships to any other part.");
  1121. }
  1122. /* End OPC compliance */
  1123. ensureRelationships();
  1124. PackageRelationship retRel = relationships.addRelationship(
  1125. targetPartName.getURI(), targetMode, relationshipType, relID);
  1126. this.isDirty = true;
  1127. return retRel;
  1128. }
  1129. /**
  1130. * Add a package relationship.
  1131. *
  1132. * @param targetPartName
  1133. * Target part name.
  1134. * @param targetMode
  1135. * Target mode, either Internal or External.
  1136. * @param relationshipType
  1137. * Relationship type.
  1138. * @see PackageRelationshipTypes
  1139. */
  1140. @Override
  1141. public PackageRelationship addRelationship(PackagePartName targetPartName,
  1142. TargetMode targetMode, String relationshipType) {
  1143. return this.addRelationship(targetPartName, targetMode,
  1144. relationshipType, null);
  1145. }
  1146. /**
  1147. * Adds an external relationship to a part (except relationships part).
  1148. *
  1149. * The targets of external relationships are not subject to the same
  1150. * validity checks that internal ones are, as the contents is potentially
  1151. * any file, URL or similar.
  1152. *
  1153. * @param target
  1154. * External target of the relationship
  1155. * @param relationshipType
  1156. * Type of relationship.
  1157. * @return The newly created and added relationship
  1158. * @see org.apache.poi.openxml4j.opc.RelationshipSource#addExternalRelationship(java.lang.String,
  1159. * java.lang.String)
  1160. */
  1161. @Override
  1162. public PackageRelationship addExternalRelationship(String target,
  1163. String relationshipType) {
  1164. return addExternalRelationship(target, relationshipType, null);
  1165. }
  1166. /**
  1167. * Adds an external relationship to a part (except relationships part).
  1168. *
  1169. * The targets of external relationships are not subject to the same
  1170. * validity checks that internal ones are, as the contents is potentially
  1171. * any file, URL or similar.
  1172. *
  1173. * @param target
  1174. * External target of the relationship
  1175. * @param relationshipType
  1176. * Type of relationship.
  1177. * @param id
  1178. * Relationship unique id.
  1179. * @return The newly created and added relationship
  1180. * @see org.apache.poi.openxml4j.opc.RelationshipSource#addExternalRelationship(java.lang.String,
  1181. * java.lang.String)
  1182. */
  1183. @Override
  1184. public PackageRelationship addExternalRelationship(String target,
  1185. String relationshipType, String id) {
  1186. if (target == null) {
  1187. throw new IllegalArgumentException("target");
  1188. }
  1189. if (relationshipType == null) {
  1190. throw new IllegalArgumentException("relationshipType");
  1191. }
  1192. URI targetURI;
  1193. try {
  1194. targetURI = new URI(target);
  1195. } catch (URISyntaxException e) {
  1196. throw new IllegalArgumentException("Invalid target - " + e);
  1197. }
  1198. ensureRelationships();
  1199. PackageRelationship retRel = relationships.addRelationship(targetURI,
  1200. TargetMode.EXTERNAL, relationshipType, id);
  1201. this.isDirty = true;
  1202. return retRel;
  1203. }
  1204. /**
  1205. * Delete a relationship from this package.
  1206. *
  1207. * @param id
  1208. * Id of the relationship to delete.
  1209. */
  1210. @Override
  1211. public void removeRelationship(String id) {
  1212. if (relationships != null) {
  1213. relationships.removeRelationship(id);
  1214. this.isDirty = true;
  1215. }
  1216. }
  1217. /**
  1218. * Retrieves all package relationships.
  1219. *
  1220. * @return All package relationships of this package.
  1221. * @throws OpenXML4JException
  1222. * @see #getRelationshipsHelper(String)
  1223. */
  1224. @Override
  1225. public PackageRelationshipCollection getRelationships() {
  1226. return getRelationshipsHelper(null);
  1227. }
  1228. /**
  1229. * Retrieves all relationships with the specified type.
  1230. *
  1231. * @param relationshipType
  1232. * The filter specifying the relationship type.
  1233. * @return All relationships with the specified relationship type.
  1234. */
  1235. @Override
  1236. public PackageRelationshipCollection getRelationshipsByType(
  1237. String relationshipType) {
  1238. throwExceptionIfWriteOnly();
  1239. if (relationshipType == null) {
  1240. throw new IllegalArgumentException("relationshipType");
  1241. }
  1242. return getRelationshipsHelper(relationshipType);
  1243. }
  1244. /**
  1245. * Retrieves all relationships with specified id (normally just ine because
  1246. * a relationship id is supposed to be unique).
  1247. *
  1248. * @param id
  1249. * Id of the wanted relationship.
  1250. */
  1251. private PackageRelationshipCollection getRelationshipsHelper(String id) {
  1252. throwExceptionIfWriteOnly();
  1253. ensureRelationships();
  1254. return this.relationships.getRelationships(id);
  1255. }
  1256. /**
  1257. * Clear package relationships.
  1258. */
  1259. @Override
  1260. public void clearRelationships() {
  1261. if (relationships != null) {
  1262. relationships.clear();
  1263. this.isDirty = true;
  1264. }
  1265. }
  1266. /**
  1267. * Ensure that the relationships collection is not null.
  1268. */
  1269. public void ensureRelationships() {
  1270. if (this.relationships == null) {
  1271. try {
  1272. this.relationships = new PackageRelationshipCollection(this);
  1273. } catch (InvalidFormatException e) {
  1274. this.relationships = new PackageRelationshipCollection();
  1275. }
  1276. }
  1277. }
  1278. /**
  1279. * @see org.apache.poi.openxml4j.opc.RelationshipSource#getRelationship(java.lang.String)
  1280. */
  1281. @Override
  1282. public PackageRelationship getRelationship(String id) {
  1283. return this.relationships.getRelationshipByID(id);
  1284. }
  1285. /**
  1286. * @see org.apache.poi.openxml4j.opc.RelationshipSource#hasRelationships()
  1287. */
  1288. @Override
  1289. public boolean hasRelationships() {
  1290. return (relationships.size() > 0);
  1291. }
  1292. /**
  1293. * @see org.apache.poi.openxml4j.opc.RelationshipSource#isRelationshipExists(org.apache.poi.openxml4j.opc.PackageRelationship)
  1294. */
  1295. @Override
  1296. public boolean isRelationshipExists(PackageRelationship rel) {
  1297. for (PackageRelationship r : this.getRelationships()) {
  1298. if (r == rel) {
  1299. return true;
  1300. }
  1301. }
  1302. return false;
  1303. }
  1304. /**
  1305. * Add a marshaller.
  1306. *
  1307. * @param contentType
  1308. * The content type to bind to the specified marshaller.
  1309. * @param marshaller
  1310. * The marshaller to register with the specified content type.
  1311. */
  1312. public void addMarshaller(String contentType, PartMarshaller marshaller) {
  1313. try {
  1314. partMarshallers.put(new ContentType(contentType), marshaller);
  1315. } catch (InvalidFormatException e) {
  1316. logger.log(POILogger.WARN, "The specified content type is not valid: '"
  1317. + e.getMessage() + "'. The marshaller will not be added !");
  1318. }
  1319. }
  1320. /**
  1321. * Add an unmarshaller.
  1322. *
  1323. * @param contentType
  1324. * The content type to bind to the specified unmarshaller.
  1325. * @param unmarshaller
  1326. * The unmarshaller to register with the specified content type.
  1327. */
  1328. public void addUnmarshaller(String contentType,
  1329. PartUnmarshaller unmarshaller) {
  1330. try {
  1331. partUnmarshallers.put(new ContentType(contentType), unmarshaller);
  1332. } catch (InvalidFormatException e) {
  1333. logger.log(POILogger.WARN, "The specified content type is not valid: '"
  1334. + e.getMessage()
  1335. + "'. The unmarshaller will not be added !");
  1336. }
  1337. }
  1338. /**
  1339. * Remove a marshaller by its content type.
  1340. *
  1341. * @param contentType
  1342. * The content type associated with the marshaller to remove.
  1343. */
  1344. public void removeMarshaller(String contentType) {
  1345. try {
  1346. partMarshallers.remove(new ContentType(contentType));
  1347. } catch (InvalidFormatException e) {
  1348. throw new RuntimeException(e);
  1349. }
  1350. }
  1351. /**
  1352. * Remove an unmarshaller by its content type.
  1353. *
  1354. * @param contentType
  1355. * The content type associated with the unmarshaller to remove.
  1356. */
  1357. public void removeUnmarshaller(String contentType) {
  1358. try {
  1359. partUnmarshallers.remove(new ContentType(contentType));
  1360. } catch (InvalidFormatException e) {
  1361. throw new RuntimeException(e);
  1362. }
  1363. }
  1364. /* Accesseurs */
  1365. /**
  1366. * Get the package access mode.
  1367. *
  1368. * @return the packageAccess The current package access.
  1369. */
  1370. public PackageAccess getPackageAccess() {
  1371. return packageAccess;
  1372. }
  1373. /**
  1374. * Validates the package compliance with the OPC specifications.
  1375. *
  1376. * @return <b>true</b> if the package is valid else <b>false</b>
  1377. */
  1378. @NotImplemented
  1379. public boolean validatePackage(OPCPackage pkg) throws InvalidFormatException {
  1380. throw new InvalidOperationException("Not implemented yet !!!");
  1381. }
  1382. /**
  1383. * Save the document in the specified file.
  1384. *
  1385. * @param targetFile
  1386. * Destination file.
  1387. * @throws IOException
  1388. * Throws if an IO exception occur.
  1389. * @see #save(OutputStream)
  1390. */
  1391. public void save(File targetFile) throws IOException {
  1392. if (targetFile == null) {
  1393. throw new IllegalArgumentException("targetFile");
  1394. }
  1395. this.throwExceptionIfReadOnly();
  1396. // You shouldn't save the the same file, do a close instead
  1397. if(targetFile.exists() &&
  1398. targetFile.getAbsolutePath().equals(this.originalPackagePath)) {
  1399. throw new InvalidOperationException(
  1400. "You can't call save(File) to save to the currently open " +
  1401. "file. To save to the current file, please just call close()"
  1402. );
  1403. }
  1404. // Do the save
  1405. FileOutputStream fos = null;
  1406. try {
  1407. fos = new FileOutputStream(targetFile);
  1408. this.save(fos);
  1409. } finally {
  1410. if (fos != null) fos.close();
  1411. }
  1412. }
  1413. /**
  1414. * Save the document in the specified output stream.
  1415. *
  1416. * @param outputStream
  1417. * The stream to save the package.
  1418. * @see #saveImpl(OutputStream)
  1419. */
  1420. public void save(OutputStream outputStream) throws IOException {
  1421. throwExceptionIfReadOnly();
  1422. this.saveImpl(outputStream);
  1423. }
  1424. /**
  1425. * Core method to create a package part. This method must be implemented by
  1426. * the subclass.
  1427. *
  1428. * @param partName
  1429. * URI of the part to create.
  1430. * @param contentType
  1431. * Content type of the part to create.
  1432. * @return The newly created package part.
  1433. */
  1434. protected abstract PackagePart createPartImpl(PackagePartName partName,
  1435. String contentType, boolean loadRelationships);
  1436. /**
  1437. * Core method to delete a package part. This method must be implemented by
  1438. * the subclass.
  1439. *
  1440. * @param partName
  1441. * The URI of the part to delete.
  1442. */
  1443. protected abstract void removePartImpl(PackagePartName partName);
  1444. /**
  1445. * Flush the package but not save.
  1446. */
  1447. protected abstract void flushImpl();
  1448. /**
  1449. * Close the package and cause a save of the package.
  1450. *
  1451. */
  1452. protected abstract void closeImpl() throws IOException;
  1453. /**
  1454. * Close the package without saving the document. Discard all changes made
  1455. * to this package.
  1456. */
  1457. protected abstract void revertImpl();
  1458. /**
  1459. * Save the package into the specified output stream.
  1460. *
  1461. * @param outputStream
  1462. * The output stream use to save this package.
  1463. */
  1464. protected abstract void saveImpl(OutputStream outputStream)
  1465. throws IOException;
  1466. /**
  1467. * Get the package part mapped to the specified URI.
  1468. *
  1469. * @param partName
  1470. * The URI of the part to retrieve.
  1471. * @return The package part located by the specified URI, else <b>null</b>.
  1472. */
  1473. protected abstract PackagePart getPartImpl(PackagePartName partName);
  1474. /**
  1475. * Get all parts link to the package.
  1476. *
  1477. * @return A list of the part owned by the package.
  1478. */
  1479. protected abstract PackagePart[] getPartsImpl()
  1480. throws InvalidFormatException;
  1481. /**
  1482. * Replace a content type in this package.
  1483. *
  1484. * <p>
  1485. * A typical scneario to call this method is to rename a template file to the main format, e.g.
  1486. * ".dotx" to ".docx"
  1487. * ".dotm" to ".docm"
  1488. * ".xltx" to ".xlsx"
  1489. * ".xltm" to ".xlsm"
  1490. * ".potx" to ".pptx"
  1491. * ".potm" to ".pptm"
  1492. * </p>
  1493. * For example, a code converting a .xlsm macro workbook to .xlsx would look as follows:
  1494. * <p>
  1495. * <pre><code>
  1496. *
  1497. * OPCPackage pkg = OPCPackage.open(new FileInputStream("macro-workbook.xlsm"));
  1498. * pkg.replaceContentType(
  1499. * "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
  1500. * "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml");
  1501. *
  1502. * FileOutputStream out = new FileOutputStream("workbook.xlsx");
  1503. * pkg.save(out);
  1504. * out.close();
  1505. *
  1506. * </code></pre>
  1507. * </p>
  1508. *
  1509. * @param oldContentType the content type to be replaced
  1510. * @param newContentType the replacement
  1511. * @return whether replacement was succesfull
  1512. * @since POI-3.8
  1513. */
  1514. public boolean replaceContentType(String oldContentType, String newContentType){
  1515. boolean success = false;
  1516. ArrayList<PackagePart> list = getPartsByContentType(oldContentType);
  1517. for (PackagePart packagePart : list) {
  1518. if (packagePart.getContentType().equals(oldContentType)) {
  1519. PackagePartName partName = packagePart.getPartName();
  1520. contentTypeManager.addContentType(partName, newContentType);
  1521. success = true;
  1522. }
  1523. }
  1524. return success;
  1525. }
  1526. /**
  1527. * Add the specified part, and register its content type with the content
  1528. * type manager.
  1529. *
  1530. * @param part
  1531. * The part to add.
  1532. */
  1533. public void registerPartAndContentType(PackagePart part) {
  1534. addPackagePart(part);
  1535. this.contentTypeManager.addContentType(part.getPartName(), part.getContentType());
  1536. this.isDirty = true;
  1537. }
  1538. /**
  1539. * Remove the specified part, and clear its content type from the content
  1540. * type manager.
  1541. *
  1542. * @param partName
  1543. * The part name of the part to remove.
  1544. */
  1545. public void unregisterPartAndContentType(PackagePartName partName) {
  1546. removePart(partName);
  1547. this.contentTypeManager.removeContentType(partName);
  1548. this.isDirty = true;
  1549. }
  1550. }