/* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ package org.apache.poi.hpsf.basic; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.poi.POIDataSamples; import org.apache.poi.hpsf.ClassID; import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.HPSFException; import org.apache.poi.hpsf.IllegalPropertySetDataException; import org.apache.poi.hpsf.MutableProperty; import org.apache.poi.hpsf.MutablePropertySet; import org.apache.poi.hpsf.MutableSection; import org.apache.poi.hpsf.NoFormatIDException; import org.apache.poi.hpsf.NoPropertySetStreamException; import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.ReadingNotSupportedException; import org.apache.poi.hpsf.Section; import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.UnsupportedVariantTypeException; import org.apache.poi.hpsf.Variant; import org.apache.poi.hpsf.VariantSupport; import org.apache.poi.hpsf.WritingNotSupportedException; import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.SectionIDMap; import org.apache.poi.poifs.eventfilesystem.POIFSReader; import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DocumentNode; import org.apache.poi.poifs.filesystem.NDocumentInputStream; import org.apache.poi.poifs.filesystem.NDocumentOutputStream; import org.apache.poi.poifs.filesystem.NPOIFSDocument; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.TempFile; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; /** * Tests HPSF's writing functionality */ public class TestWrite { private static final POIDataSamples _samples = POIDataSamples.getHPSFInstance(); private static final int CODEPAGE_DEFAULT = -1; private static final String POI_FS = "TestHPSFWritingFunctionality.doc"; private static final int BYTE_ORDER = 0xfffe; private static final int FORMAT = 0x0000; private static final int OS_VERSION = 0x00020A04; private static final int[] SECTION_COUNT = {1, 2}; private static final boolean[] IS_SUMMARY_INFORMATION = {true, false}; private static final boolean[] IS_DOCUMENT_SUMMARY_INFORMATION = {false, true}; private static final String IMPROPER_DEFAULT_CHARSET_MESSAGE = "Your default character set is " + getDefaultCharsetName() + ". However, this testcase must be run in an environment " + "with a default character set supporting at least " + "8-bit-characters. You can achieve this by setting the " + "LANG environment variable to a proper value, e.g. " + "\"de_DE\"."; POIFile[] poiFiles; @BeforeClass public static void setUp() { VariantSupport.setLogUnsupportedTypes(false); } /** *
Writes an empty property set to a POIFS and reads it back * in.
* * @exception IOException if an I/O exception occurs */ @Test(expected=NoFormatIDException.class) public void withoutAFormatID() throws Exception { final File filename = TempFile.createTempFile(POI_FS, ".doc"); /* Create a mutable property set with a section that does not have the * formatID set: */ final OutputStream out = new FileOutputStream(filename); final POIFSFileSystem poiFs = new POIFSFileSystem(); final MutablePropertySet ps = new MutablePropertySet(); ps.clearSections(); ps.addSection(new MutableSection()); /* Write it to a POIFS and the latter to disk: */ try { final ByteArrayOutputStream psStream = new ByteArrayOutputStream(); ps.write(psStream); psStream.close(); final byte[] streamData = psStream.toByteArray(); poiFs.createDocument(new ByteArrayInputStream(streamData), SummaryInformation.DEFAULT_STREAM_NAME); poiFs.writeFilesystem(out); } finally { poiFs.close(); out.close(); } } /** *Writes an empty property set to a POIFS and reads it back * in.
* * @exception IOException if an I/O exception occurs * @exception UnsupportedVariantTypeException if HPSF does not yet support * a variant type to be written */ @Test public void writeEmptyPropertySet() throws IOException, UnsupportedVariantTypeException { final File dataDir = _samples.getFile(""); final File filename = new File(dataDir, POI_FS); filename.deleteOnExit(); /* Create a mutable property set and write it to a POIFS: */ final OutputStream out = new FileOutputStream(filename); final POIFSFileSystem poiFs = new POIFSFileSystem(); final MutablePropertySet ps = new MutablePropertySet(); final MutableSection s = (MutableSection) ps.getSections().get(0); s.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID); final ByteArrayOutputStream psStream = new ByteArrayOutputStream(); ps.write(psStream); psStream.close(); final byte[] streamData = psStream.toByteArray(); poiFs.createDocument(new ByteArrayInputStream(streamData), SummaryInformation.DEFAULT_STREAM_NAME); poiFs.writeFilesystem(out); poiFs.close(); out.close(); /* Read the POIFS: */ final POIFSReader r = new POIFSReader(); r.registerListener(new MyPOIFSReaderListener(), SummaryInformation.DEFAULT_STREAM_NAME); FileInputStream stream = new FileInputStream(filename); try { r.read(stream); } finally { stream.close(); } } /** *Writes a simple property set with a SummaryInformation section to a * POIFS and reads it back in.
* * @exception IOException if an I/O exception occurs * @exception UnsupportedVariantTypeException if HPSF does not yet support * a variant type to be written */ @Test public void writeSimplePropertySet() throws IOException, UnsupportedVariantTypeException { final String AUTHOR = "Rainer Klute"; final String TITLE = "Test Document"; final File dataDir = _samples.getFile(""); final File filename = new File(dataDir, POI_FS); filename.deleteOnExit(); final OutputStream out = new FileOutputStream(filename); final POIFSFileSystem poiFs = new POIFSFileSystem(); final MutablePropertySet ps = new MutablePropertySet(); final MutableSection si = new MutableSection(); si.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID); ps.clearSections(); ps.addSection(si); final MutableProperty p = new MutableProperty(); p.setID(PropertyIDMap.PID_AUTHOR); p.setType(Variant.VT_LPWSTR); p.setValue(AUTHOR); si.setProperty(p); si.setProperty(PropertyIDMap.PID_TITLE, Variant.VT_LPSTR, TITLE); poiFs.createDocument(ps.toInputStream(), SummaryInformation.DEFAULT_STREAM_NAME); poiFs.writeFilesystem(out); poiFs.close(); out.close(); /* Read the POIFS: */ final PropertySet[] psa = new PropertySet[1]; final POIFSReader r = new POIFSReader(); r.registerListener(new POIFSReaderListener() { @Override public void processPOIFSReaderEvent(final POIFSReaderEvent event) { try { psa[0] = PropertySetFactory.create(event.getStream()); } catch (Exception ex) { fail(ex.getMessage()); } }}, SummaryInformation.DEFAULT_STREAM_NAME ); InputStream stream = new FileInputStream(filename); try { r.read(stream); } finally { stream.close(); } assertNotNull(psa[0]); assertTrue(psa[0].isSummaryInformation()); final Section s = (psa[0].getSections().get(0)); Object p1 = s.getProperty(PropertyIDMap.PID_AUTHOR); Object p2 = s.getProperty(PropertyIDMap.PID_TITLE); assertEquals(AUTHOR, p1); assertEquals(TITLE, p2); } /** *Writes a simple property set with two sections to a POIFS and reads it * back in.
* * @exception IOException if an I/O exception occurs * @exception WritingNotSupportedException if HPSF does not yet support * a variant type to be written */ @Test public void writeTwoSections() throws WritingNotSupportedException, IOException { final String STREAM_NAME = "PropertySetStream"; final String SECTION1 = "Section 1"; final String SECTION2 = "Section 2"; final File dataDir = _samples.getFile(""); final File filename = new File(dataDir, POI_FS); filename.deleteOnExit(); final OutputStream out = new FileOutputStream(filename); final POIFSFileSystem poiFs = new POIFSFileSystem(); final MutablePropertySet ps = new MutablePropertySet(); ps.clearSections(); final ClassID formatID = new ClassID(); formatID.setBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); final MutableSection s1 = new MutableSection(); s1.setFormatID(formatID); s1.setProperty(2, SECTION1); ps.addSection(s1); final MutableSection s2 = new MutableSection(); s2.setFormatID(formatID); s2.setProperty(2, SECTION2); ps.addSection(s2); poiFs.createDocument(ps.toInputStream(), STREAM_NAME); poiFs.writeFilesystem(out); poiFs.close(); out.close(); /* Read the POIFS: */ final PropertySet[] psa = new PropertySet[1]; final POIFSReader r = new POIFSReader(); r.registerListener(new POIFSReaderListener() { @Override public void processPOIFSReaderEvent(final POIFSReaderEvent event) { try { psa[0] = PropertySetFactory.create(event.getStream()); } catch (Exception ex) { throw new RuntimeException(ex); } } }, STREAM_NAME); FileInputStream stream = new FileInputStream(filename); try { r.read(stream); } finally { stream.close(); } assertNotNull(psa[0]); Section s = (psa[0].getSections().get(0)); assertEquals(s.getFormatID(), formatID); Object p = s.getProperty(2); assertEquals(SECTION1, p); s = (psa[0].getSections().get(1)); p = s.getProperty(2); assertEquals(SECTION2, p); } static class MyPOIFSReaderListener implements POIFSReaderListener { @Override public void processPOIFSReaderEvent(final POIFSReaderEvent event) { try { PropertySetFactory.create(event.getStream()); } catch (Exception ex) { fail(ex.getMessage()); } } } /** *Writes and reads back various variant types and checks whether the * stuff that has been read back equals the stuff that was written.
* @throws IOException * @throws UnsupportedEncodingException * @throws UnsupportedVariantTypeException * @throws ReadingNotSupportedException */ @Test public void variantTypes() throws Exception { final int codepage = CODEPAGE_DEFAULT; Assume.assumeTrue(IMPROPER_DEFAULT_CHARSET_MESSAGE, hasProperDefaultCharset()); check(Variant.VT_EMPTY, null, codepage); check(Variant.VT_BOOL, Boolean.TRUE, codepage); check(Variant.VT_BOOL, Boolean.FALSE, codepage); check( Variant.VT_CF, new byte[] { 8, 0, 0, 0, 1, 0, 0, 0, 1, 2, 3, 4 }, codepage ); check(Variant.VT_I4, 27, codepage); check(Variant.VT_I8, 28L, codepage); check(Variant.VT_R8, 29.0d, codepage); check(Variant.VT_I4, -27, codepage); check(Variant.VT_I8, -28L, codepage); check(Variant.VT_R8, -29.0d, codepage); check(Variant.VT_FILETIME, new Date(), codepage); check(Variant.VT_I4, Integer.MAX_VALUE, codepage); check(Variant.VT_I4, Integer.MIN_VALUE, codepage); check(Variant.VT_I8, Long.MAX_VALUE, codepage); check(Variant.VT_I8, Long.MIN_VALUE, codepage); check(Variant.VT_R8, Double.MAX_VALUE, codepage); check(Variant.VT_R8, Double.MIN_VALUE, codepage); checkString(Variant.VT_LPSTR, "\u00e4\u00f6\u00fc\u00df\u00c4\u00d6\u00dc", codepage); checkString(Variant.VT_LPWSTR, "\u00e4\u00f6\u00fc\u00df\u00c4\u00d6\u00dc", codepage); } /** * Writes and reads back strings using several different codepages and * checks whether the stuff that has been read back equals the stuff that * was written. */ @Test public void codepages() throws ReadingNotSupportedException, UnsupportedVariantTypeException, IOException { Throwable thr = null; final int[] validCodepages = {CODEPAGE_DEFAULT, CodePageUtil.CP_UTF8, CodePageUtil.CP_UNICODE, CodePageUtil.CP_WINDOWS_1252}; for (final int cp : validCodepages) { if (cp == -1 && !hasProperDefaultCharset()) { System.err.println(IMPROPER_DEFAULT_CHARSET_MESSAGE + " This testcase is skipped for the default codepage."); continue; } final long t = (cp == CodePageUtil.CP_UNICODE) ? Variant.VT_LPWSTR : Variant.VT_LPSTR; checkString(t, "\u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df", cp); if (cp == CodePageUtil.CP_UTF16 || cp == CodePageUtil.CP_UTF8) { check(t, "\u79D1\u5B78", cp); } } final int[] invalidCodepages = new int[] {0, 1, 2, 4711, 815}; for (int cp : invalidCodepages) { final long type = (cp == CodePageUtil.CP_UNICODE) ? Variant.VT_LPWSTR : Variant.VT_LPSTR; try { checkString(type, "\u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df", cp); fail("UnsupportedEncodingException for codepage " + cp + " expected."); } catch (UnsupportedEncodingException ex) { /* This is the expected behaviour. */ } } } /** * Tests whether writing 8-bit characters to a Unicode property succeeds. */ @Test public void unicodeWrite8Bit() throws WritingNotSupportedException, IOException, NoPropertySetStreamException { final String TITLE = "This is a sample title"; final MutablePropertySet mps = new MutablePropertySet(); final MutableSection ms = (MutableSection) mps.getSections().get(0); ms.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID); final MutableProperty p = new MutableProperty(); p.setID(PropertyIDMap.PID_TITLE); p.setType(Variant.VT_LPSTR); p.setValue(TITLE); ms.setProperty(p); ByteArrayOutputStream out = new ByteArrayOutputStream(); mps.write(out); out.close(); byte[] bytes = out.toByteArray(); PropertySet psr = new PropertySet(bytes); assertTrue(psr.isSummaryInformation()); Section sr = psr.getSections().get(0); String title = (String) sr.getProperty(PropertyIDMap.PID_TITLE); assertEquals(TITLE, title); } private void checkString(final long variantType, final String value, final int codepage) throws UnsupportedVariantTypeException, IOException, ReadingNotSupportedException, UnsupportedEncodingException { for (int i=0; iTests writing and reading back a proper dictionary with an invalid * codepage. (HPSF writes Unicode dictionaries only.)
* @throws IOException * @throws HPSFException */ @Test(expected=IllegalPropertySetDataException.class) public void dictionaryWithInvalidCodepage() throws IOException, HPSFException { final File copy = TempFile.createTempFile("Test-HPSF", "ole2"); copy.deleteOnExit(); /* Write: */ final OutputStream out = new FileOutputStream(copy); final POIFSFileSystem poiFs = new POIFSFileSystem(); final MutablePropertySet ps1 = new MutablePropertySet(); final MutableSection s = (MutableSection) ps1.getSections().get(0); final MapReturns the display name of the default character set.
* * @return the display name of the default character set. */ private static String getDefaultCharsetName() { final String charSetName = System.getProperty("file.encoding"); final Charset charSet = Charset.forName(charSetName); return charSet.displayName(Locale.ROOT); } /** *In order to execute tests with characters beyond US-ASCII, this * method checks whether the application is runing in an environment * where the default character set is 16-bit-capable.
* * @returntrue if the default character set is 16-bit-capable,
* else false.
*/
private boolean hasProperDefaultCharset() {
final String charSetName = System.getProperty("file.encoding");
final Charset charSet = Charset.forName(charSetName);
return charSet.newEncoder().canEncode('\u00e4');
}
}