001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hpsf.examples;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028: import java.util.HashMap;
029: import java.util.Map;
030:
031: import org.apache.poi.hpsf.HPSFRuntimeException;
032: import org.apache.poi.hpsf.MarkUnsupportedException;
033: import org.apache.poi.hpsf.MutablePropertySet;
034: import org.apache.poi.hpsf.MutableSection;
035: import org.apache.poi.hpsf.NoPropertySetStreamException;
036: import org.apache.poi.hpsf.PropertySet;
037: import org.apache.poi.hpsf.PropertySetFactory;
038: import org.apache.poi.hpsf.SummaryInformation;
039: import org.apache.poi.hpsf.Util;
040: import org.apache.poi.hpsf.Variant;
041: import org.apache.poi.hpsf.WritingNotSupportedException;
042: import org.apache.poi.hpsf.wellknown.PropertyIDMap;
043: import org.apache.poi.poifs.eventfilesystem.POIFSReader;
044: import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
045: import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
046: import org.apache.poi.poifs.filesystem.DirectoryEntry;
047: import org.apache.poi.poifs.filesystem.DocumentInputStream;
048: import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
049: import org.apache.poi.poifs.filesystem.POIFSFileSystem;
050:
051: /**
052: * <p>This class is a sample application which shows how to write or modify the
053: * author and title property of an OLE 2 document. This could be done in two
054: * different ways:</p>
055: *
056: * <ul>
057: *
058: * <li><p>The first approach is to open the OLE 2 file as a POI filesystem
059: * (see class {@link POIFSFileSystem}), read the summary information property
060: * set (see classes {@link SummaryInformation} and {@link PropertySet}), write
061: * the author and title properties into it and write the property set back into
062: * the POI filesystem.</p></li>
063: *
064: * <li><p>The second approach does not modify the original POI filesystem, but
065: * instead creates a new one. All documents from the original POIFS are copied
066: * to the destination POIFS, except for the summary information stream. The
067: * latter is modified by setting the author and title property before writing
068: * it to the destination POIFS. It there are several summary information streams
069: * in the original POIFS - e.g. in subordinate directories - they are modified
070: * just the same.</p></li>
071: *
072: * </ul>
073: *
074: * <p>This sample application takes the second approach. It expects the name of
075: * the existing POI filesystem's name as its first command-line parameter and
076: * the name of the output POIFS as the second command-line argument. The
077: * program then works as described above: It copies nearly all documents
078: * unmodified from the input POI filesystem to the output POI filesystem. If it
079: * encounters a summary information stream it reads its properties. Then it sets
080: * the "author" and "title" properties to new values and writes the modified
081: * summary information stream into the output file.</p>
082: *
083: * <p>Further explanations can be found in the HPSF HOW-TO.</p>
084: *
085: * @author Rainer Klute <a
086: * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
087: * @version $Id: WriteAuthorAndTitle.java 489730 2006-12-22 19:18:16Z bayard $
088: * @since 2003-09-01
089: */
090: public class WriteAuthorAndTitle {
091: /**
092: * <p>Runs the example program.</p>
093: *
094: * @param args Command-line arguments. The first command-line argument must
095: * be the name of a POI filesystem to read.
096: * @throws IOException if any I/O exception occurs.
097: */
098: public static void main(final String[] args) throws IOException {
099: /* Check whether we have exactly two command-line arguments. */
100: if (args.length != 2) {
101: System.err.println("Usage: "
102: + WriteAuthorAndTitle.class.getName()
103: + " originPOIFS destinationPOIFS");
104: System.exit(1);
105: }
106:
107: /* Read the names of the origin and destination POI filesystems. */
108: final String srcName = args[0];
109: final String dstName = args[1];
110:
111: /* Read the origin POIFS using the eventing API. The real work is done
112: * in the class ModifySICopyTheRest which is registered here as a
113: * POIFSReader. */
114: final POIFSReader r = new POIFSReader();
115: final ModifySICopyTheRest msrl = new ModifySICopyTheRest(
116: dstName);
117: r.registerListener(msrl);
118: r.read(new FileInputStream(srcName));
119:
120: /* Write the new POIFS to disk. */
121: msrl.close();
122: }
123:
124: /**
125: * <p>This class does all the work. As its name implies it modifies a
126: * summary information property set and copies everything else unmodified
127: * to the destination POI filesystem. Since an instance of it is registered
128: * as a {@link POIFSReader} its method {@link
129: * #processPOIFSReaderEvent(POIFSReaderEvent)} is called for each document
130: * in the origin POIFS.</p>
131: */
132: static class ModifySICopyTheRest implements POIFSReaderListener {
133: String dstName;
134: OutputStream out;
135: POIFSFileSystem poiFs;
136:
137: /**
138: * <p>The constructor of a {@link ModifySICopyTheRest} instance creates
139: * the target POIFS. It also stores the name of the file the POIFS will
140: * be written to once it is complete.</p>
141: *
142: * @param dstName The name of the disk file the destination POIFS is to
143: * be written to.
144: */
145: public ModifySICopyTheRest(final String dstName) {
146: this .dstName = dstName;
147: poiFs = new POIFSFileSystem();
148: }
149:
150: /**
151: * <p>The method is called by POI's eventing API for each file in the
152: * origin POIFS.</p>
153: */
154: public void processPOIFSReaderEvent(final POIFSReaderEvent event) {
155: /* The following declarations are shortcuts for accessing the
156: * "event" object. */
157: final POIFSDocumentPath path = event.getPath();
158: final String name = event.getName();
159: final DocumentInputStream stream = event.getStream();
160:
161: Throwable t = null;
162:
163: try {
164: /* Find out whether the current document is a property set
165: * stream or not. */
166: if (PropertySet.isPropertySetStream(stream)) {
167: /* Yes, the current document is a property set stream.
168: * Let's create a PropertySet instance from it. */
169: PropertySet ps = null;
170: try {
171: ps = PropertySetFactory.create(stream);
172: } catch (NoPropertySetStreamException ex) {
173: /* This exception will not be thrown because we already
174: * checked above. */
175: }
176:
177: /* Now we know that we really have a property set. The next
178: * step is to find out whether it is a summary information
179: * or not. */
180: if (ps.isSummaryInformation())
181: /* Yes, it is a summary information. We will modify it
182: * and write the result to the destination POIFS. */
183: editSI(poiFs, path, name, ps);
184: else
185: /* No, it is not a summary information. We don't care
186: * about its internals and copy it unmodified to the
187: * destination POIFS. */
188: copy(poiFs, path, name, ps);
189: } else
190: /* No, the current document is not a property set stream. We
191: * copy it unmodified to the destination POIFS. */
192: copy(poiFs, event.getPath(), event.getName(),
193: stream);
194: } catch (MarkUnsupportedException ex) {
195: t = ex;
196: } catch (IOException ex) {
197: t = ex;
198: } catch (WritingNotSupportedException ex) {
199: t = ex;
200: }
201:
202: /* According to the definition of the processPOIFSReaderEvent method
203: * we cannot pass checked exceptions to the caller. The following
204: * lines check whether a checked exception occured and throws an
205: * unchecked exception. The message of that exception is that of
206: * the underlying checked exception. */
207: if (t != null) {
208: throw new HPSFRuntimeException("Could not read file \""
209: + path + "/" + name + "\". Reason: "
210: + Util.toString(t));
211: }
212: }
213:
214: /**
215: * <p>Receives a summary information property set modifies (or creates)
216: * its "author" and "title" properties and writes the result under the
217: * same path and name as the origin to a destination POI filesystem.</p>
218: *
219: * @param poiFs The POI filesystem to write to.
220: * @param path The original (and destination) stream's path.
221: * @param name The original (and destination) stream's name.
222: * @param si The property set. It should be a summary information
223: * property set.
224: * @throws IOException
225: * @throws WritingNotSupportedException
226: */
227: public void editSI(final POIFSFileSystem poiFs,
228: final POIFSDocumentPath path, final String name,
229: final PropertySet si)
230: throws WritingNotSupportedException, IOException
231:
232: {
233: /* Get the directory entry for the target stream. */
234: final DirectoryEntry de = getPath(poiFs, path);
235:
236: /* Create a mutable property set as a copy of the original read-only
237: * property set. */
238: final MutablePropertySet mps = new MutablePropertySet(si);
239:
240: /* Retrieve the section containing the properties to modify. A
241: * summary information property set contains exactly one section. */
242: final MutableSection s = (MutableSection) mps.getSections()
243: .get(0);
244:
245: /* Set the properties. */
246: s.setProperty(PropertyIDMap.PID_AUTHOR, Variant.VT_LPSTR,
247: "Rainer Klute");
248: s.setProperty(PropertyIDMap.PID_TITLE, Variant.VT_LPWSTR,
249: "Test");
250:
251: /* Create an input stream containing the bytes the property set
252: * stream consists of. */
253: final InputStream pss = mps.toInputStream();
254:
255: /* Write the property set stream to the POIFS. */
256: de.createDocument(name, pss);
257: }
258:
259: /**
260: * <p>Writes a {@link PropertySet} to a POI filesystem. This method is
261: * simpler than {@link #editSI} because the origin property set has just
262: * to be copied.</p>
263: *
264: * @param poiFs The POI filesystem to write to.
265: * @param path The file's path in the POI filesystem.
266: * @param name The file's name in the POI filesystem.
267: * @param ps The property set to write.
268: * @throws WritingNotSupportedException
269: * @throws IOException
270: */
271: public void copy(final POIFSFileSystem poiFs,
272: final POIFSDocumentPath path, final String name,
273: final PropertySet ps)
274: throws WritingNotSupportedException, IOException {
275: final DirectoryEntry de = getPath(poiFs, path);
276: final MutablePropertySet mps = new MutablePropertySet(ps);
277: de.createDocument(name, mps.toInputStream());
278: }
279:
280: /**
281: * <p>Copies the bytes from a {@link DocumentInputStream} to a new
282: * stream in a POI filesystem.</p>
283: *
284: * @param poiFs The POI filesystem to write to.
285: * @param path The source document's path.
286: * @param name The source document's name.
287: * @param stream The stream containing the source document.
288: * @throws IOException
289: */
290: public void copy(final POIFSFileSystem poiFs,
291: final POIFSDocumentPath path, final String name,
292: final DocumentInputStream stream) throws IOException {
293: final DirectoryEntry de = getPath(poiFs, path);
294: final ByteArrayOutputStream out = new ByteArrayOutputStream();
295: int c;
296: while ((c = stream.read()) != -1)
297: out.write(c);
298: stream.close();
299: out.close();
300: final InputStream in = new ByteArrayInputStream(out
301: .toByteArray());
302: de.createDocument(name, in);
303: }
304:
305: /**
306: * <p>Writes the POI file system to a disk file.</p>
307: *
308: * @throws FileNotFoundException
309: * @throws IOException
310: */
311: public void close() throws FileNotFoundException, IOException {
312: out = new FileOutputStream(dstName);
313: poiFs.writeFilesystem(out);
314: out.close();
315: }
316:
317: /** Contains the directory paths that have already been created in the
318: * output POI filesystem and maps them to their corresponding
319: * {@link org.apache.poi.poifs.filesystem.DirectoryNode}s. */
320: private final Map paths = new HashMap();
321:
322: /**
323: * <p>Ensures that the directory hierarchy for a document in a POI
324: * fileystem is in place. When a document is to be created somewhere in
325: * a POI filesystem its directory must be created first. This method
326: * creates all directories between the POI filesystem root and the
327: * directory the document should belong to which do not yet exist.</p>
328: *
329: * <p>Unfortunately POI does not offer a simple method to interrogate
330: * the POIFS whether a certain child node (file or directory) exists in
331: * a directory. However, since we always start with an empty POIFS which
332: * contains the root directory only and since each directory in the
333: * POIFS is created by this method we can maintain the POIFS's directory
334: * hierarchy ourselves: The {@link DirectoryEntry} of each directory
335: * created is stored in a {@link Map}. The directories' path names map
336: * to the corresponding {@link DirectoryEntry} instances.</p>
337: *
338: * @param poiFs The POI filesystem the directory hierarchy is created
339: * in, if needed.
340: * @param path The document's path. This method creates those directory
341: * components of this hierarchy which do not yet exist.
342: * @return The directory entry of the document path's parent. The caller
343: * should use this {@link DirectoryEntry} to create documents in it.
344: */
345: public DirectoryEntry getPath(final POIFSFileSystem poiFs,
346: final POIFSDocumentPath path) {
347: try {
348: /* Check whether this directory has already been created. */
349: final String s = path.toString();
350: DirectoryEntry de = (DirectoryEntry) paths.get(s);
351: if (de != null)
352: /* Yes: return the corresponding DirectoryEntry. */
353: return de;
354:
355: /* No: We have to create the directory - or return the root's
356: * DirectoryEntry. */
357: int l = path.length();
358: if (l == 0)
359: /* Get the root directory. It does not have to be created
360: * since it always exists in a POIFS. */
361: de = poiFs.getRoot();
362: else {
363: /* Create a subordinate directory. The first step is to
364: * ensure that the parent directory exists: */
365: de = getPath(poiFs, path.getParent());
366: /* Now create the target directory: */
367: de = de.createDirectory(path.getComponent(path
368: .length() - 1));
369: }
370: paths.put(s, de);
371: return de;
372: } catch (IOException ex) {
373: /* This exception will be thrown if the directory already
374: * exists. However, since we have full control about directory
375: * creation we can ensure that this will never happen. */
376: ex.printStackTrace(System.err);
377: throw new RuntimeException(ex.toString());
378: /* FIXME (2): Replace the previous line by the following once we
379: * no longer need JDK 1.3 compatibility. */
380: // throw new RuntimeException(ex);
381: }
382: }
383: }
384:
385: }
|