| /* ==================================================================== |
| 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.ooxml; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertNotSame; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.time.ZoneId; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.Optional; |
| |
| import org.apache.poi.POIDataSamples; |
| import org.apache.poi.ooxml.POIXMLProperties.CoreProperties; |
| import org.apache.poi.openxml4j.opc.OPCPackage; |
| import org.apache.poi.ss.usermodel.Cell; |
| import org.apache.poi.ss.usermodel.Row; |
| import org.apache.poi.ss.usermodel.Sheet; |
| import org.apache.poi.util.IOUtils; |
| import org.apache.poi.util.LocaleUtil; |
| import org.apache.poi.xssf.XSSFTestDataSamples; |
| import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| import org.apache.poi.xwpf.XWPFTestDataSamples; |
| import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| import org.junit.jupiter.api.AfterEach; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| |
| /** |
| * Test setting extended and custom OOXML properties |
| */ |
| public final class TestPOIXMLProperties { |
| private XWPFDocument sampleDoc; |
| private XWPFDocument sampleNoThumb; |
| private POIXMLProperties _props; |
| private CoreProperties _coreProperties; |
| |
| @BeforeEach |
| void setUp() throws IOException { |
| sampleDoc = XWPFTestDataSamples.openSampleDocument("documentProperties.docx"); |
| sampleNoThumb = XWPFTestDataSamples.openSampleDocument("SampleDoc.docx"); |
| assertNotNull(sampleDoc); |
| assertNotNull(sampleNoThumb); |
| _props = sampleDoc.getProperties(); |
| _coreProperties = _props.getCoreProperties(); |
| assertNotNull(_props); |
| } |
| |
| @AfterEach |
| void closeResources() throws Exception { |
| sampleDoc.close(); |
| sampleNoThumb.close(); |
| } |
| |
| @Test |
| void testWorkbookExtendedProperties() throws Exception { |
| XSSFWorkbook workbook = new XSSFWorkbook(); |
| POIXMLProperties props = workbook.getProperties(); |
| assertNotNull(props); |
| |
| POIXMLProperties.ExtendedProperties properties = |
| props.getExtendedProperties(); |
| |
| org.openxmlformats.schemas.officeDocument.x2006.extendedProperties.CTProperties |
| ctProps = properties.getUnderlyingProperties(); |
| |
| |
| String appVersion = "3.5 beta"; |
| String application = "POI"; |
| |
| ctProps.setApplication(application); |
| ctProps.setAppVersion(appVersion); |
| |
| XSSFWorkbook newWorkbook = |
| XSSFTestDataSamples.writeOutAndReadBack(workbook); |
| workbook.close(); |
| assertNotSame(workbook, newWorkbook); |
| |
| |
| POIXMLProperties newProps = newWorkbook.getProperties(); |
| assertNotNull(newProps); |
| POIXMLProperties.ExtendedProperties newProperties = |
| newProps.getExtendedProperties(); |
| |
| assertEquals(application, newProperties.getApplication()); |
| assertEquals(appVersion, newProperties.getAppVersion()); |
| |
| org.openxmlformats.schemas.officeDocument.x2006.extendedProperties.CTProperties |
| newCtProps = newProperties.getUnderlyingProperties(); |
| |
| assertEquals(application, newCtProps.getApplication()); |
| assertEquals(appVersion, newCtProps.getAppVersion()); |
| |
| newWorkbook.close(); |
| } |
| |
| @Test |
| void testWorkbookExtendedPropertiesGettersSetters() throws Exception { |
| XSSFWorkbook workbook = new XSSFWorkbook(); |
| POIXMLProperties props = workbook.getProperties(); |
| assertNotNull(props); |
| |
| POIXMLProperties.ExtendedProperties properties = |
| props.getExtendedProperties(); |
| |
| String appVersion = "3.5 beta"; |
| String application = "POI Modified"; |
| |
| assertEquals("Apache POI", properties.getApplication()); |
| properties.setApplication(application); |
| assertEquals(properties.getApplication(), application); |
| |
| assertNull(properties.getAppVersion()); |
| properties.setAppVersion(appVersion); |
| assertEquals(properties.getAppVersion(), appVersion); |
| |
| XSSFWorkbook newWorkbook = |
| XSSFTestDataSamples.writeOutAndReadBack(workbook); |
| workbook.close(); |
| assertNotSame(workbook, newWorkbook); |
| |
| POIXMLProperties newProps = newWorkbook.getProperties(); |
| assertNotNull(newProps); |
| POIXMLProperties.ExtendedProperties newProperties = |
| newProps.getExtendedProperties(); |
| |
| assertEquals(application, newProperties.getApplication()); |
| assertEquals(appVersion, newProperties.getAppVersion()); |
| |
| newWorkbook.close(); |
| } |
| |
| |
| /** |
| * Test usermodel API for setting custom properties |
| */ |
| @Test |
| void testCustomProperties() throws Exception { |
| try (XSSFWorkbook wb1 = new XSSFWorkbook()) { |
| |
| POIXMLProperties.CustomProperties customProps = wb1.getProperties().getCustomProperties(); |
| customProps.addProperty("test-1", "string val"); |
| customProps.addProperty("test-2", 1974); |
| customProps.addProperty("test-3", 36.6); |
| //adding a duplicate |
| IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> customProps.addProperty("test-3", 36.6)); |
| assertEquals("A property with this name already exists in the custom properties", e.getMessage()); |
| customProps.addProperty("test-4", true); |
| |
| try (XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1)) { |
| org.openxmlformats.schemas.officeDocument.x2006.customProperties.CTProperties ctProps = |
| wb2.getProperties().getCustomProperties().getUnderlyingProperties(); |
| assertEquals(4, ctProps.sizeOfPropertyArray()); |
| org.openxmlformats.schemas.officeDocument.x2006.customProperties.CTProperty p; |
| |
| p = ctProps.getPropertyArray(0); |
| assertEquals("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", p.getFmtid()); |
| assertEquals("test-1", p.getName()); |
| assertEquals("string val", p.getLpwstr()); |
| assertEquals(2, p.getPid()); |
| |
| p = ctProps.getPropertyArray(1); |
| assertEquals("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", p.getFmtid()); |
| assertEquals("test-2", p.getName()); |
| assertEquals(1974, p.getI4()); |
| assertEquals(3, p.getPid()); |
| |
| p = ctProps.getPropertyArray(2); |
| assertEquals("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", p.getFmtid()); |
| assertEquals("test-3", p.getName()); |
| assertEquals(36.6, p.getR8(), 0); |
| assertEquals(4, p.getPid()); |
| |
| p = ctProps.getPropertyArray(3); |
| assertEquals("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}", p.getFmtid()); |
| assertEquals("test-4", p.getName()); |
| assertTrue(p.getBool()); |
| assertEquals(5, p.getPid()); |
| } |
| } |
| } |
| |
| @Test |
| void testDocumentProperties() { |
| String category = _coreProperties.getCategory(); |
| assertEquals("test", category); |
| String contentStatus = "Draft"; |
| _coreProperties.setContentStatus(contentStatus); |
| assertEquals("Draft", contentStatus); |
| Date created = _coreProperties.getCreated(); |
| // the original file contains a following value: 2009-07-20T13:12:00Z |
| assertTrue(dateTimeEqualToUTCString(created, "2009-07-20T13:12:00Z")); |
| String creator = _coreProperties.getCreator(); |
| assertEquals("Paolo Mottadelli", creator); |
| String subject = _coreProperties.getSubject(); |
| assertEquals("Greetings", subject); |
| String title = _coreProperties.getTitle(); |
| assertEquals("Hello World", title); |
| } |
| |
| @Test |
| void testTransitiveSetters() throws IOException { |
| XWPFDocument doc = new XWPFDocument(); |
| CoreProperties cp = doc.getProperties().getCoreProperties(); |
| |
| |
| Date dateCreated = LocaleUtil.getLocaleCalendar(2010, 6, 15, 10, 0, 0).getTime(); |
| cp.setCreated(Optional.of(dateCreated)); |
| assertEquals(dateCreated, cp.getCreated()); |
| |
| XWPFDocument doc2 = XWPFTestDataSamples.writeOutAndReadBack(doc); |
| doc.close(); |
| cp = doc2.getProperties().getCoreProperties(); |
| Date dt3 = cp.getCreated(); |
| assertEquals(dateCreated, dt3); |
| doc2.close(); |
| } |
| |
| @Test |
| void testGetSetRevision() { |
| String revision = _coreProperties.getRevision(); |
| assertTrue(Integer.parseInt(revision) > 1, "Revision number is 1"); |
| _coreProperties.setRevision("20"); |
| assertEquals("20", _coreProperties.getRevision()); |
| _coreProperties.setRevision("20xx"); |
| assertEquals("20", _coreProperties.getRevision()); |
| } |
| |
| @Test |
| void testLastModifiedByUserProperty() { |
| String lastModifiedByUser = _coreProperties.getLastModifiedByUser(); |
| assertEquals("Paolo Mottadelli", lastModifiedByUser); |
| _coreProperties.setLastModifiedByUser("Test User"); |
| assertEquals("Test User", _coreProperties.getLastModifiedByUser()); |
| } |
| |
| public static boolean dateTimeEqualToUTCString(Date dateTime, String utcString) { |
| Calendar utcCalendar = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); |
| utcCalendar.setTimeInMillis(dateTime.getTime()); |
| String dateTimeUtcString = utcCalendar.get(Calendar.YEAR) + "-" + |
| zeroPad((utcCalendar.get(Calendar.MONTH)+1)) + "-" + |
| zeroPad(utcCalendar.get(Calendar.DAY_OF_MONTH)) + "T" + |
| zeroPad(utcCalendar.get(Calendar.HOUR_OF_DAY)) + ":" + |
| zeroPad(utcCalendar.get(Calendar.MINUTE)) + ":" + |
| zeroPad(utcCalendar.get(Calendar.SECOND)) + "Z"; |
| |
| return utcString.equals(dateTimeUtcString); |
| } |
| |
| @Disabled("Fails to add some of the thumbnails, needs more investigation") |
| @Test |
| void testThumbnails() throws Exception { |
| POIXMLProperties noThumbProps = sampleNoThumb.getProperties(); |
| |
| assertNotNull(_props.getThumbnailPart()); |
| assertNull(noThumbProps.getThumbnailPart()); |
| |
| assertNotNull(_props.getThumbnailFilename()); |
| assertNull(noThumbProps.getThumbnailFilename()); |
| |
| assertNotNull(_props.getThumbnailImage()); |
| assertNull(noThumbProps.getThumbnailImage()); |
| |
| assertEquals("/thumbnail.jpeg", _props.getThumbnailFilename()); |
| |
| |
| // Adding / changing |
| ByteArrayInputStream imageData = new ByteArrayInputStream(new byte[1]); |
| noThumbProps.setThumbnail("Testing.png", imageData); |
| assertNotNull(noThumbProps.getThumbnailPart()); |
| assertEquals("/Testing.png", noThumbProps.getThumbnailFilename()); |
| assertNotNull(noThumbProps.getThumbnailImage()); |
| assertEquals(1, IOUtils.toByteArray(noThumbProps.getThumbnailImage()).length); |
| |
| imageData = new ByteArrayInputStream(new byte[2]); |
| noThumbProps.setThumbnail("Testing2.png", imageData); |
| assertNotNull(noThumbProps.getThumbnailPart()); |
| assertEquals("/Testing.png", noThumbProps.getThumbnailFilename()); |
| assertNotNull(noThumbProps.getThumbnailImage()); |
| assertEquals(2, IOUtils.toByteArray(noThumbProps.getThumbnailImage()).length); |
| } |
| |
| private static String zeroPad(long i) { |
| if (i >= 0 && i <= 9) { |
| return "0" + i; |
| } else { |
| return String.valueOf(i); |
| } |
| } |
| |
| @Test |
| void testAddProperty() throws IOException { |
| try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("documentProperties.docx")) { |
| POIXMLProperties.CustomProperties cps = doc.getProperties().getCustomProperties(); |
| assertEquals(1, cps.getLastPid()); |
| cps.addProperty("prop1", "abc"); |
| assertEquals(2, cps.getLastPid()); |
| assertEquals(2, cps.getProperty("prop1").getPid()); |
| assertEquals("abc", cps.getProperty("prop1").getLpwstr()); |
| } |
| } |
| |
| @Test |
| void testOoxmlStrict() throws Exception { |
| POIDataSamples _ssTests = POIDataSamples.getSpreadSheetInstance(); |
| try (OPCPackage pkg = OPCPackage.open(_ssTests.openResourceAsStream("sample.strict.xlsx"))) { |
| POIXMLProperties props = new POIXMLProperties(pkg); |
| assertNotNull(props.getCoreProperties().getCreated()); |
| assertEquals(2007, props.getCoreProperties().getCreated().toInstant().atZone(ZoneId.of("UTC")).getYear()); |
| } |
| } |
| |
| @Test |
| void testBug60977() throws IOException { |
| |
| try (final XSSFWorkbook workbook = new XSSFWorkbook()) { |
| final Sheet sheet = workbook.createSheet("sheet"); |
| final Row row = sheet.createRow(0); |
| final Cell cell = row.createCell(0); |
| cell.setCellValue("cell"); |
| |
| final POIXMLProperties properties = workbook.getProperties(); |
| final POIXMLProperties.CustomProperties customProperties = properties.getCustomProperties(); |
| final String propName = "Project"; |
| final String propValue = "Some name"; |
| customProperties.addProperty(propName, propValue); |
| |
| // in the unit-test just try to write out the file more than once and see if we can still parse it |
| XSSFWorkbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook); |
| assertNotNull(wbBack); |
| // properties documents are read lazily, so we have to access them to verify they parse properly |
| assertNotNull(wbBack.getProperties(), "First writeOutAndReadBack"); |
| assertEquals(propValue, wbBack.getProperties().getCustomProperties().getProperty(propName).getLpwstr(), "First prop check"); |
| |
| customProperties.addProperty(propName + "1", propValue); |
| wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook); |
| assertNotNull(wbBack); |
| // properties documents are read lazily, so we have to access them to verify they parse properly |
| assertNotNull(wbBack.getProperties(), "Second writeOutAndReadBack"); |
| assertEquals(propValue, wbBack.getProperties().getCustomProperties().getProperty(propName).getLpwstr(), "Second prop check"); |
| assertEquals(propValue, wbBack.getProperties().getCustomProperties().getProperty(propName + "1").getLpwstr(), "Second prop check1"); |
| |
| wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook); |
| assertNotNull(wbBack); |
| // properties documents are read lazily, so we have to access them to verify they parse properly |
| assertNotNull(wbBack.getProperties(), "Third writeOutAndReadBack"); |
| assertEquals(propValue, wbBack.getProperties().getCustomProperties().getProperty(propName).getLpwstr(), "Third prop check"); |
| assertEquals(propValue, wbBack.getProperties().getCustomProperties().getProperty(propName + "1").getLpwstr(), "Third prop check1"); |
| |
| /* Manual test to write out the file more than once: |
| File test1 = File.createTempFile("test1", ".xlsx", new File("C:\\temp")); |
| File test2 = File.createTempFile("test2", ".xlsx", new File("C:\\temp")); |
| try (final java.io.FileOutputStream fs = new java.io.FileOutputStream(test1)) { |
| workbook.write(fs); |
| } |
| try (final XSSFWorkbook wb = new XSSFWorkbook(test1)) { |
| assertNotNull(wb.getProperties()); |
| } catch (InvalidFormatException e) { |
| fail("Test1 copy failed: " + e.getMessage()); |
| } |
| |
| try (final java.io.FileOutputStream fs = new java.io.FileOutputStream(test2)) { |
| workbook.write(fs); |
| } |
| |
| try (final XSSFWorkbook wb = new XSSFWorkbook(test2)) { |
| assertNotNull(wb.getProperties()); |
| } catch (InvalidFormatException e) { |
| fail("Test2 copy failed: " + e.getMessage()); |
| } |
| */ |
| } |
| } |
| } |