From ab390ce1705e578f63626a0061e97fc7848314ea Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Fri, 20 Apr 2018 12:52:59 +0000 Subject: [PATCH] #62319 - Decommission XSLF-/PowerPointExtractor git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1829653 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/TestAllFiles.java | 2 - .../apache/poi/stress/XSLFFileHandler.java | 16 +- .../org/apache/poi/POIOLE2TextExtractor.java | 1 + src/java/org/apache/poi/POITextExtractor.java | 5 + .../poi/extractor/OLE2ExtractorFactory.java | 19 +- .../filesystem/DocumentFactoryHelper.java | 42 +- .../poi/sl/extractor/SlideShowExtractor.java | 28 +- .../apache/poi/sl/usermodel/SlideShow.java | 9 + .../poi/sl/usermodel/SlideShowFactory.java | 33 +- .../org/apache/poi/POIXMLTextExtractor.java | 1 + .../poi/extractor/ExtractorFactory.java | 38 +- .../extractor/XSLFPowerPointExtractor.java | 2 +- .../poi/xslf/usermodel/XMLSlideShow.java | 5 + .../usermodel/XSLFPlaceholderDetails.java | 17 + .../apache/poi/xslf/usermodel/XSLFSlide.java | 8 + .../poi/extractor/TestExtractorFactory.java | 916 ++++-------------- .../poi/poifs/crypt/TestHxxFEncryption.java | 20 +- .../org/apache/poi/xslf/TestXSLFBugs.java | 47 +- .../TestXSLFPowerPointExtractor.java | 438 ++++----- .../OLE2ScratchpadExtractorFactory.java | 4 +- .../hslf/extractor/PowerPointExtractor.java | 2 + .../poi/hslf/usermodel/HSLFSlideShow.java | 5 + .../hslf/usermodel/HSLFSlideShowFactory.java | 18 +- .../poi/hslf/usermodel/HSLFSlideShowImpl.java | 10 +- .../poi/hslf/extractor/TestExtractor.java | 447 +++++---- .../apache/poi/hslf/usermodel/TestBugs.java | 13 +- test-data/slideshow/SampleShow.pptx | Bin 48008 -> 42414 bytes 27 files changed, 861 insertions(+), 1285 deletions(-) diff --git a/src/integrationtest/org/apache/poi/TestAllFiles.java b/src/integrationtest/org/apache/poi/TestAllFiles.java index 333a8ebcdc..8721c50948 100644 --- a/src/integrationtest/org/apache/poi/TestAllFiles.java +++ b/src/integrationtest/org/apache/poi/TestAllFiles.java @@ -330,8 +330,6 @@ public class TestAllFiles { ); private static final Set IGNORED = unmodifiableHashSet( - // need JDK8+ - https://bugs.openjdk.java.net/browse/JDK-8038081 - "slideshow/42474-2.ppt", // OPC handler works / XSSF handler fails "spreadsheet/57181.xlsm", "spreadsheet/61300.xls"//intentionally fuzzed -- used to cause infinite loop diff --git a/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java b/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java index 7aab657650..80fe858249 100644 --- a/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java +++ b/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java @@ -24,6 +24,7 @@ import java.io.FileInputStream; import java.io.InputStream; import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; import org.apache.poi.xslf.usermodel.XMLSlideShow; import org.apache.poi.xslf.usermodel.XSLFSlideShow; @@ -53,12 +54,19 @@ public class XSLFFileHandler extends SlideShowHandler { // additionally try the other getText() methods - try (XSLFPowerPointExtractor extractor = (XSLFPowerPointExtractor) ExtractorFactory.createExtractor(file)) { + try (SlideShowExtractor extractor = ExtractorFactory.createExtractor(file)) { assertNotNull(extractor); + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(true); + extractor.setMasterByDefault(true); - assertNotNull(extractor.getText(true, true, true)); - assertEquals("With all options disabled we should not get text", - "", extractor.getText(false, false, false)); + assertNotNull(extractor.getText()); + + extractor.setSlidesByDefault(false); + extractor.setNotesByDefault(false); + extractor.setMasterByDefault(false); + + assertEquals("With all options disabled we should not get text", "", extractor.getText()); } } diff --git a/src/java/org/apache/poi/POIOLE2TextExtractor.java b/src/java/org/apache/poi/POIOLE2TextExtractor.java index 05c7781057..0fccf71c4a 100644 --- a/src/java/org/apache/poi/POIOLE2TextExtractor.java +++ b/src/java/org/apache/poi/POIOLE2TextExtractor.java @@ -105,6 +105,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor { * * @return the underlying POIDocument */ + @Override public POIDocument getDocument() { return document; } diff --git a/src/java/org/apache/poi/POITextExtractor.java b/src/java/org/apache/poi/POITextExtractor.java index 58cf5e6ddb..55d0832f16 100644 --- a/src/java/org/apache/poi/POITextExtractor.java +++ b/src/java/org/apache/poi/POITextExtractor.java @@ -74,4 +74,9 @@ public abstract class POITextExtractor implements Closeable { fsToClose.close(); } } + + /** + * @return the processed document + */ + public abstract Object getDocument(); } diff --git a/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java b/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java index 5f895dc375..5e52f9d6c2 100644 --- a/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java +++ b/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java @@ -115,26 +115,23 @@ public class OLE2ExtractorFactory { return threadPreferEventExtractors.get(); } - public static POIOLE2TextExtractor createExtractor(POIFSFileSystem fs) throws IOException { - // Only ever an OLE2 one from the root of the FS - return (POIOLE2TextExtractor)createExtractor(fs.getRoot()); + public static T createExtractor(POIFSFileSystem fs) throws IOException { + return (T)createExtractor(fs.getRoot()); } - public static POIOLE2TextExtractor createExtractor(NPOIFSFileSystem fs) throws IOException { - // Only ever an OLE2 one from the root of the FS - return (POIOLE2TextExtractor)createExtractor(fs.getRoot()); + public static T createExtractor(NPOIFSFileSystem fs) throws IOException { + return (T)createExtractor(fs.getRoot()); } - public static POIOLE2TextExtractor createExtractor(OPOIFSFileSystem fs) throws IOException { - // Only ever an OLE2 one from the root of the FS - return (POIOLE2TextExtractor)createExtractor(fs.getRoot()); + public static T createExtractor(OPOIFSFileSystem fs) throws IOException { + return (T)createExtractor(fs.getRoot()); } - public static POITextExtractor createExtractor(InputStream input) throws IOException { + public static T createExtractor(InputStream input) throws IOException { Class cls = getOOXMLClass(); if (cls != null) { // Use Reflection to get us the full OOXML-enabled version try { Method m = cls.getDeclaredMethod("createExtractor", InputStream.class); - return (POITextExtractor)m.invoke(null, input); + return (T)m.invoke(null, input); } catch (IllegalArgumentException iae) { throw iae; } catch (Exception e) { diff --git a/src/java/org/apache/poi/poifs/filesystem/DocumentFactoryHelper.java b/src/java/org/apache/poi/poifs/filesystem/DocumentFactoryHelper.java index 0657b9bb63..57b57a1757 100644 --- a/src/java/org/apache/poi/poifs/filesystem/DocumentFactoryHelper.java +++ b/src/java/org/apache/poi/poifs/filesystem/DocumentFactoryHelper.java @@ -44,8 +44,30 @@ public class DocumentFactoryHelper { * @throws IOException If an error occurs while decrypting or if the password does not match */ public static InputStream getDecryptedStream(final NPOIFSFileSystem fs, String password) + throws IOException { + // wrap the stream in a FilterInputStream to close the NPOIFSFileSystem + // as well when the resulting OPCPackage is closed + return new FilterInputStream(getDecryptedStream(fs.getRoot(), password)) { + @Override + public void close() throws IOException { + fs.close(); + super.close(); + } + }; + } + + /** + * Wrap the OLE2 data of the DirectoryNode into a decrypted stream by using + * the given password. + * + * @param root The OLE2 directory node for the document + * @param password The password, null if the default password should be used + * @return A stream for reading the decrypted data + * @throws IOException If an error occurs while decrypting or if the password does not match + */ + public static InputStream getDecryptedStream(final DirectoryNode root, String password) throws IOException { - EncryptionInfo info = new EncryptionInfo(fs); + EncryptionInfo info = new EncryptionInfo(root); Decryptor d = Decryptor.getInstance(info); try { @@ -58,21 +80,11 @@ public class DocumentFactoryHelper { } if (passwordCorrect) { - // wrap the stream in a FilterInputStream to close the NPOIFSFileSystem - // as well when the resulting OPCPackage is closed - return new FilterInputStream(d.getDataStream(fs.getRoot())) { - @Override - public void close() throws IOException { - fs.close(); - - super.close(); - } - }; + return d.getDataStream(root); + } else if (password != null) { + throw new EncryptedDocumentException("Password incorrect"); } else { - if (password != null) - throw new EncryptedDocumentException("Password incorrect"); - else - throw new EncryptedDocumentException("The supplied spreadsheet is protected, but no password was supplied"); + throw new EncryptedDocumentException("The supplied spreadsheet is protected, but no password was supplied"); } } catch (GeneralSecurityException e) { throw new IOException(e); diff --git a/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java b/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java index bf234f9d63..a7283acbee 100644 --- a/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java +++ b/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java @@ -1,3 +1,20 @@ +/* ==================================================================== + 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.sl.extractor; import java.util.ArrayList; @@ -48,6 +65,16 @@ public class SlideShowExtractor< this.slideshow = slideshow; } + /** + * Returns opened document + * + * @return the opened document + */ + @Override + public final Object getDocument() { + return slideshow.getPersistDocument(); + } + /** * Should a call to getText() return slide text? Default is yes */ @@ -219,7 +246,6 @@ public class SlideShowExtractor< return; } for (final P para : paraList) { - final int oldLen = sb.length(); for (final TextRun tr : para) { final String str = tr.getRawText().replace("\r", ""); final String newStr; diff --git a/src/java/org/apache/poi/sl/usermodel/SlideShow.java b/src/java/org/apache/poi/sl/usermodel/SlideShow.java index 8ddd96217c..9894e687d8 100644 --- a/src/java/org/apache/poi/sl/usermodel/SlideShow.java +++ b/src/java/org/apache/poi/sl/usermodel/SlideShow.java @@ -126,4 +126,13 @@ public interface SlideShow< * @since POI 4.0.0 */ POITextExtractor getMetadataTextExtractor(); + + /** + * @return the instance which handles the persisting of the slideshow, + * which is either a subclass of {@link org.apache.poi.POIDocument} + * or {@link org.apache.poi.POIXMLDocument} + * + * @since POI 4.0.0 + */ + Object getPersistDocument(); } diff --git a/src/java/org/apache/poi/sl/usermodel/SlideShowFactory.java b/src/java/org/apache/poi/sl/usermodel/SlideShowFactory.java index cdee782c32..02e9fcb146 100644 --- a/src/java/org/apache/poi/sl/usermodel/SlideShowFactory.java +++ b/src/java/org/apache/poi/sl/usermodel/SlideShowFactory.java @@ -60,13 +60,40 @@ public class SlideShowFactory { * @throws IOException if an error occurs while reading the data */ public static SlideShow create(final NPOIFSFileSystem fs, String password) throws IOException { - DirectoryNode root = fs.getRoot(); + return create(fs.getRoot(), password); + } + + /** + * Creates a SlideShow from the given NPOIFSFileSystem. + * + * @param root The {@link DirectoryNode} to start reading the document from + * + * @return The created SlideShow + * + * @throws IOException if an error occurs while reading the data + */ + public static SlideShow create(final DirectoryNode root) throws IOException { + return create(root, null); + } + + /** + * Creates a SlideShow from the given NPOIFSFileSystem, which may + * be password protected + * + * @param root The {@link DirectoryNode} to start reading the document from + * @param password The password that should be used or null if no password is necessary. + * + * @return The created SlideShow + * + * @throws IOException if an error occurs while reading the data + */ + public static SlideShow create(final DirectoryNode root, String password) throws IOException { // Encrypted OOXML files go inside OLE2 containers, is this one? if (root.hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) { InputStream stream = null; try { - stream = DocumentFactoryHelper.getDecryptedStream(fs, password); + stream = DocumentFactoryHelper.getDecryptedStream(root, password); return createXSLFSlideShow(stream); } finally { @@ -82,7 +109,7 @@ public class SlideShowFactory { passwordSet = true; } try { - return createHSLFSlideShow(fs); + return createHSLFSlideShow(root); } finally { if (passwordSet) { Biff8EncryptionKey.setCurrentUserPassword(null); diff --git a/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java b/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java index 26b2cd84c2..003fe353f1 100644 --- a/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java +++ b/src/ooxml/java/org/apache/poi/POIXMLTextExtractor.java @@ -68,6 +68,7 @@ public abstract class POIXMLTextExtractor extends POITextExtractor { * * @return the opened document */ + @Override public final POIXMLDocument getDocument() { return _document; } diff --git a/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java b/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java index 910baf6a64..9a7765af0d 100644 --- a/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java +++ b/src/ooxml/java/org/apache/poi/extractor/ExtractorFactory.java @@ -51,6 +51,7 @@ import org.apache.poi.poifs.filesystem.NotOLE2FileException; import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; import org.apache.poi.poifs.filesystem.OfficeXmlFileException; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.util.IOUtils; import org.apache.poi.util.NotImplemented; import org.apache.poi.util.POILogFactory; @@ -58,6 +59,7 @@ import org.apache.poi.util.POILogger; import org.apache.poi.util.Removal; import org.apache.poi.xdgf.extractor.XDGFVisioExtractor; import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; +import org.apache.poi.xslf.usermodel.XMLSlideShow; import org.apache.poi.xslf.usermodel.XSLFRelation; import org.apache.poi.xslf.usermodel.XSLFSlideShow; import org.apache.poi.xssf.extractor.XSSFBEventBasedExcelExtractor; @@ -127,20 +129,20 @@ public class ExtractorFactory { return OLE2ExtractorFactory.getPreferEventExtractor(); } - public static POITextExtractor createExtractor(File f) throws IOException, OpenXML4JException, XmlException { + public static T createExtractor(File f) throws IOException, OpenXML4JException, XmlException { NPOIFSFileSystem fs = null; try { fs = new NPOIFSFileSystem(f); if (fs.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) { - return createEncryptedOOXMLExtractor(fs); + return (T)createEncryptedOOXMLExtractor(fs); } - POIOLE2TextExtractor extractor = createExtractor(fs); + POITextExtractor extractor = createExtractor(fs); extractor.setFilesystem(fs); - return extractor; + return (T)extractor; } catch (OfficeXmlFileException e) { // ensure file-handle release IOUtils.closeQuietly(fs); - return createExtractor(OPCPackage.open(f.toString(), PackageAccess.READ)); + return (T)createExtractor(OPCPackage.open(f.toString(), PackageAccess.READ)); } catch (NotOLE2FileException ne) { // ensure file-handle release IOUtils.closeQuietly(fs); @@ -179,7 +181,7 @@ public class ExtractorFactory { * @throws XmlException If an XML parsing error occurs. * @throws IllegalArgumentException If no matching file type could be found. */ - public static POIXMLTextExtractor createExtractor(OPCPackage pkg) throws IOException, OpenXML4JException, XmlException { + public static POITextExtractor createExtractor(OPCPackage pkg) throws IOException, OpenXML4JException, XmlException { try { // Check for the normal Office core document PackageRelationshipCollection core; @@ -226,13 +228,13 @@ public class ExtractorFactory { // Is it XSLF? for (XSLFRelation rel : XSLFPowerPointExtractor.SUPPORTED_TYPES) { if ( rel.getContentType().equals( contentType ) ) { - return new XSLFPowerPointExtractor(pkg); + return new SlideShowExtractor(new XMLSlideShow(pkg)); } } // special handling for SlideShow-Theme-files, if (XSLFRelation.THEME_MANAGER.getContentType().equals(contentType)) { - return new XSLFPowerPointExtractor(new XSLFSlideShow(pkg)); + return new SlideShowExtractor(new XMLSlideShow(pkg)); } // How about xlsb? @@ -252,28 +254,28 @@ public class ExtractorFactory { } } - public static POIOLE2TextExtractor createExtractor(POIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { - return OLE2ExtractorFactory.createExtractor(fs); + public static T createExtractor(POIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { + return createExtractor(fs.getRoot()); } - public static POIOLE2TextExtractor createExtractor(NPOIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { - return OLE2ExtractorFactory.createExtractor(fs); + public static T createExtractor(NPOIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { + return createExtractor(fs.getRoot()); } - public static POIOLE2TextExtractor createExtractor(OPOIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { - return OLE2ExtractorFactory.createExtractor(fs); + public static T createExtractor(OPOIFSFileSystem fs) throws IOException, OpenXML4JException, XmlException { + return createExtractor(fs.getRoot()); } - public static POITextExtractor createExtractor(DirectoryNode poifsDir) throws IOException, OpenXML4JException, XmlException + public static T createExtractor(DirectoryNode poifsDir) throws IOException, OpenXML4JException, XmlException { // First, check for OOXML for (String entryName : poifsDir.getEntryNames()) { if (entryName.equals("Package")) { OPCPackage pkg = OPCPackage.open(poifsDir.createDocumentInputStream("Package")); - return createExtractor(pkg); + return (T)createExtractor(pkg); } } // If not, ask the OLE2 code to check, with Scratchpad if possible - return OLE2ExtractorFactory.createExtractor(poifsDir); + return (T)OLE2ExtractorFactory.createExtractor(poifsDir); } /** @@ -403,7 +405,7 @@ public class ExtractorFactory { throw new IllegalStateException("Not yet supported"); } - private static POIXMLTextExtractor createEncryptedOOXMLExtractor(NPOIFSFileSystem fs) + private static POITextExtractor createEncryptedOOXMLExtractor(NPOIFSFileSystem fs) throws IOException { String pass = Biff8EncryptionKey.getCurrentUserPassword(); if (pass == null) { diff --git a/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java b/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java index ef9097f15e..b3cc7c5879 100644 --- a/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java +++ b/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java @@ -37,7 +37,7 @@ import org.apache.xmlbeans.XmlException; * @deprecated use {@link SlideShowExtractor} */ @Deprecated -@Removal(version="4.2.0") +@Removal(version="5.0.0") public class XSLFPowerPointExtractor extends POIXMLTextExtractor { public static final XSLFRelation[] SUPPORTED_TYPES = new XSLFRelation[]{ XSLFRelation.MAIN, XSLFRelation.MACRO, XSLFRelation.MACRO_TEMPLATE, diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java index 8734f3603e..ffc6f0d86b 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java @@ -631,4 +631,9 @@ public class XMLSlideShow extends POIXMLDocument public POIXMLPropertiesTextExtractor getMetadataTextExtractor() { return new POIXMLPropertiesTextExtractor(this); } + + @Override + public Object getPersistDocument() { + return this; + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java index 25fbd63443..e5d321bc0d 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java @@ -1,3 +1,20 @@ +/* ==================================================================== + 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.xslf.usermodel; import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS; diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java index ce650c152d..7ba272ed63 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java @@ -182,12 +182,20 @@ implements Slide { */ public XSLFCommentAuthors getCommentAuthorsPart() { if(_commentAuthors == null) { + // first scan the slide relations for (POIXMLDocumentPart p : getRelations()) { if (p instanceof XSLFCommentAuthors) { _commentAuthors = (XSLFCommentAuthors)p; return _commentAuthors; } } + // then scan the presentation relations + for (POIXMLDocumentPart p : getSlideShow().getRelations()) { + if (p instanceof XSLFCommentAuthors) { + _commentAuthors = (XSLFCommentAuthors)p; + return _commentAuthors; + } + } } return null; diff --git a/src/ooxml/testcases/org/apache/poi/extractor/TestExtractorFactory.java b/src/ooxml/testcases/org/apache/poi/extractor/TestExtractorFactory.java index 3670f6fc7a..0a885377a6 100644 --- a/src/ooxml/testcases/org/apache/poi/extractor/TestExtractorFactory.java +++ b/src/ooxml/testcases/org/apache/poi/extractor/TestExtractorFactory.java @@ -27,16 +27,15 @@ import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Locale; import org.apache.poi.POIDataSamples; import org.apache.poi.POIOLE2TextExtractor; import org.apache.poi.POITextExtractor; -import org.apache.poi.POIXMLException; import org.apache.poi.POIXMLTextExtractor; import org.apache.poi.UnsupportedFileFormatException; import org.apache.poi.hdgf.extractor.VisioTextExtractor; import org.apache.poi.hpbf.extractor.PublisherTextExtractor; -import org.apache.poi.hslf.extractor.PowerPointExtractor; import org.apache.poi.hsmf.extractor.OutlookTextExtactor; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.OldExcelFormatException; @@ -44,18 +43,20 @@ import org.apache.poi.hssf.extractor.EventBasedExcelExtractor; import org.apache.poi.hssf.extractor.ExcelExtractor; import org.apache.poi.hwpf.extractor.Word6Extractor; import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.xdgf.extractor.XDGFVisioExtractor; -import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; +import org.apache.poi.xssf.extractor.XSSFBEventBasedExcelExtractor; import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor; import org.apache.poi.xssf.extractor.XSSFExcelExtractor; import org.apache.poi.xwpf.extractor.XWPFWordExtractor; -import org.junit.BeforeClass; +import org.apache.xmlbeans.XmlException; import org.junit.Test; /** @@ -65,34 +66,39 @@ public class TestExtractorFactory { private static final POILogger LOG = POILogFactory.getLogger(TestExtractorFactory.class); - private static File txt; - - private static File xls; - private static File xlsx; - private static File xlsxStrict; - private static File xltx; - private static File xlsEmb; - private static File xlsb; - - private static File doc; - private static File doc6; - private static File doc95; - private static File docx; - private static File dotx; - private static File docEmb; - private static File docEmbOOXML; - - private static File ppt; - private static File pptx; - - private static File msg; - private static File msgEmb; - private static File msgEmbMsg; - - private static File vsd; - private static File vsdx; - - private static File pub; + private static final POIDataSamples ssTests = POIDataSamples.getSpreadSheetInstance(); + private static final File xls = getFileAndCheck(ssTests, "SampleSS.xls"); + private static final File xlsx = getFileAndCheck(ssTests, "SampleSS.xlsx"); + private static final File xlsxStrict = getFileAndCheck(ssTests, "SampleSS.strict.xlsx"); + private static final File xltx = getFileAndCheck(ssTests, "test.xltx"); + private static final File xlsEmb = getFileAndCheck(ssTests, "excel_with_embeded.xls"); + private static final File xlsb = getFileAndCheck(ssTests, "testVarious.xlsb"); + + private static final POIDataSamples wpTests = POIDataSamples.getDocumentInstance(); + private static final File doc = getFileAndCheck(wpTests, "SampleDoc.doc"); + private static final File doc6 = getFileAndCheck(wpTests, "Word6.doc"); + private static final File doc95 = getFileAndCheck(wpTests, "Word95.doc"); + private static final File docx = getFileAndCheck(wpTests, "SampleDoc.docx"); + private static final File dotx = getFileAndCheck(wpTests, "test.dotx"); + private static final File docEmb = getFileAndCheck(wpTests, "word_with_embeded.doc"); + private static final File docEmbOOXML = getFileAndCheck(wpTests, "word_with_embeded_ooxml.doc"); + + private static final POIDataSamples slTests = POIDataSamples.getSlideShowInstance(); + private static final File ppt = getFileAndCheck(slTests, "SampleShow.ppt"); + private static final File pptx = getFileAndCheck(slTests, "SampleShow.pptx"); + private static final File txt = getFileAndCheck(slTests, "SampleShow.txt"); + + private static final POIDataSamples olTests = POIDataSamples.getHSMFInstance(); + private static final File msg = getFileAndCheck(olTests, "quick.msg"); + private static final File msgEmb = getFileAndCheck(olTests, "attachment_test_msg.msg"); + private static final File msgEmbMsg = getFileAndCheck(olTests, "attachment_msg_pdf.msg"); + + private static final POIDataSamples dgTests = POIDataSamples.getDiagramInstance(); + private static final File vsd = getFileAndCheck(dgTests, "Test_Visio-Some_Random_Text.vsd"); + private static final File vsdx = getFileAndCheck(dgTests, "test.vsdx"); + + private static POIDataSamples pubTests = POIDataSamples.getPublisherInstance(); + private static File pub = getFileAndCheck(pubTests, "Simple.pub"); private static File getFileAndCheck(POIDataSamples samples, String name) { File file = samples.getFile(name); @@ -104,595 +110,133 @@ public class TestExtractorFactory { return file; } - @BeforeClass - public static void setUp() throws Exception { - - POIDataSamples ssTests = POIDataSamples.getSpreadSheetInstance(); - xls = getFileAndCheck(ssTests, "SampleSS.xls"); - xlsx = getFileAndCheck(ssTests, "SampleSS.xlsx"); - xlsxStrict = getFileAndCheck(ssTests, "SampleSS.strict.xlsx"); - xltx = getFileAndCheck(ssTests, "test.xltx"); - xlsEmb = getFileAndCheck(ssTests, "excel_with_embeded.xls"); - xlsb = getFileAndCheck(ssTests, "testVarious.xlsb"); - - POIDataSamples wpTests = POIDataSamples.getDocumentInstance(); - doc = getFileAndCheck(wpTests, "SampleDoc.doc"); - doc6 = getFileAndCheck(wpTests, "Word6.doc"); - doc95 = getFileAndCheck(wpTests, "Word95.doc"); - docx = getFileAndCheck(wpTests, "SampleDoc.docx"); - dotx = getFileAndCheck(wpTests, "test.dotx"); - docEmb = getFileAndCheck(wpTests, "word_with_embeded.doc"); - docEmbOOXML = getFileAndCheck(wpTests, "word_with_embeded_ooxml.doc"); - - POIDataSamples slTests = POIDataSamples.getSlideShowInstance(); - ppt = getFileAndCheck(slTests, "SampleShow.ppt"); - pptx = getFileAndCheck(slTests, "SampleShow.pptx"); - txt = getFileAndCheck(slTests, "SampleShow.txt"); - - POIDataSamples dgTests = POIDataSamples.getDiagramInstance(); - vsd = getFileAndCheck(dgTests, "Test_Visio-Some_Random_Text.vsd"); - vsdx = getFileAndCheck(dgTests, "test.vsdx"); - - POIDataSamples pubTests = POIDataSamples.getPublisherInstance(); - pub = getFileAndCheck(pubTests, "Simple.pub"); - - POIDataSamples olTests = POIDataSamples.getHSMFInstance(); - msg = getFileAndCheck(olTests, "quick.msg"); - msgEmb = getFileAndCheck(olTests, "attachment_test_msg.msg"); - msgEmbMsg = getFileAndCheck(olTests, "attachment_msg_pdf.msg"); - } - - @Test - public void testFile() throws Exception { - // Excel - POITextExtractor xlsExtractor = ExtractorFactory.createExtractor(xls); - assertNotNull("Had empty extractor for " + xls, xlsExtractor); - assertTrue("Expected instanceof ExcelExtractor, but had: " + xlsExtractor.getClass(), - xlsExtractor - instanceof ExcelExtractor - ); - assertTrue( - xlsExtractor.getText().length() > 200 - ); - xlsExtractor.close(); - - POITextExtractor extractor = ExtractorFactory.createExtractor(xlsx); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof XSSFExcelExtractor - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(xlsx); - assertTrue( - extractor.getText().length() > 200 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(xltx); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof XSSFExcelExtractor - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(xlsb); - assertContains(extractor.getText(), "test"); - extractor.close(); - - - extractor = ExtractorFactory.createExtractor(xltx); - assertContains(extractor.getText(), "test"); - extractor.close(); + private static final Object[] TEST_SET = { + "Excel", xls, ExcelExtractor.class, 200, + "Excel - xlsx", xlsx, XSSFExcelExtractor.class, 200, + "Excel - xltx", xltx, XSSFExcelExtractor.class, -1, + "Excel - xlsb", xlsb, XSSFBEventBasedExcelExtractor.class, -1, + "Word", doc, WordExtractor.class, 120, + "Word - docx", docx, XWPFWordExtractor.class, 120, + "Word - dotx", dotx, XWPFWordExtractor.class, -1, + "Word 6", doc6, Word6Extractor.class, 20, + "Word 95", doc95, Word6Extractor.class, 120, + "PowerPoint", ppt, SlideShowExtractor.class, 120, + "PowerPoint - pptx", pptx, SlideShowExtractor.class, 120, + "Visio", vsd, VisioTextExtractor.class, 50, + "Visio - vsdx", vsdx, XDGFVisioExtractor.class, 20, + "Publisher", pub, PublisherTextExtractor.class, 50, + "Outlook msg", msg, OutlookTextExtactor.class, 50, // TODO Support OOXML-Strict, see bug #57699 - try { - /*extractor =*/ ExtractorFactory.createExtractor(xlsxStrict); - fail("OOXML-Strict isn't yet supported"); - } catch (POIXMLException e) { - // Expected, for now - } -// extractor = ExtractorFactory.createExtractor(xlsxStrict); -// assertTrue( -// extractor -// instanceof XSSFExcelExtractor -// ); -// extractor.close(); -// -// extractor = ExtractorFactory.createExtractor(xlsxStrict); -// assertTrue( -// extractor.getText().contains("test") -// ); -// extractor.close(); - - - // Word - extractor = ExtractorFactory.createExtractor(doc); - assertTrue( - extractor - instanceof WordExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(doc6); - assertTrue( - extractor - instanceof Word6Extractor - ); - assertTrue( - extractor.getText().length() > 20 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(doc95); - assertTrue( - extractor - instanceof Word6Extractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(docx); - assertTrue( - extractor instanceof XWPFWordExtractor - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(docx); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(dotx); - assertTrue( - extractor instanceof XWPFWordExtractor - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(dotx); - assertContains(extractor.getText(), "Test"); - extractor.close(); - - // PowerPoint (PPT) - extractor = ExtractorFactory.createExtractor(ppt); - assertTrue( - extractor - instanceof PowerPointExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - // PowerPoint (PPTX) - extractor = ExtractorFactory.createExtractor(pptx); - assertTrue( - extractor - instanceof XSLFPowerPointExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); + // xlsxStrict + }; - // Visio - binary - extractor = ExtractorFactory.createExtractor(vsd); - assertTrue( - extractor - instanceof VisioTextExtractor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); + @FunctionalInterface + interface FunctionEx { + R apply(T t) throws IOException, OpenXML4JException, XmlException; + } - // Visio - vsdx - extractor = ExtractorFactory.createExtractor(vsdx); - assertTrue( - extractor - instanceof XDGFVisioExtractor - ); - assertTrue( - extractor.getText().length() > 20 - ); - extractor.close(); - // Publisher - extractor = ExtractorFactory.createExtractor(pub); - assertTrue( - extractor - instanceof PublisherTextExtractor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); - - // Outlook msg - extractor = ExtractorFactory.createExtractor(msg); - assertTrue( - extractor - instanceof OutlookTextExtactor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); + @Test + public void testFile() throws Exception { + for (int i = 0; i < TEST_SET.length; i += 4) { + try (POITextExtractor ext = ExtractorFactory.createExtractor((File) TEST_SET[i + 1])) { + testExtractor(ext, (String) TEST_SET[i], (Class) TEST_SET[i + 2], (Integer) TEST_SET[i + 3]); + } + } + } + @Test(expected = IllegalArgumentException.class) + public void testFileInvalid() throws Exception { // Text - try { - ExtractorFactory.createExtractor(txt); - fail("expected IllegalArgumentException"); - } catch(IllegalArgumentException e) { - // Good - } + try (POITextExtractor te = ExtractorFactory.createExtractor(txt)) {} } @Test public void testInputStream() throws Exception { - // Excel - POITextExtractor extractor = ExtractorFactory.createExtractor(new FileInputStream(xls)); - assertTrue( - extractor - instanceof ExcelExtractor - ); - assertTrue( - extractor.getText().length() > 200 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(new FileInputStream(xlsx)); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof XSSFExcelExtractor - ); - assertTrue( - extractor.getText().length() > 200 - ); - // TODO Support OOXML-Strict, see bug #57699 -// assertTrue( -// ExtractorFactory.createExtractor(new FileInputStream(xlsxStrict)) -// instanceof XSSFExcelExtractor -// ); -// assertTrue( -// ExtractorFactory.createExtractor(new FileInputStream(xlsxStrict)).getText().length() > 200 -// ); - extractor.close(); - - // Word - extractor = ExtractorFactory.createExtractor(new FileInputStream(doc)); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof WordExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(new FileInputStream(doc6)); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof Word6Extractor - ); - assertTrue( - extractor.getText().length() > 20 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(new FileInputStream(doc95)); - assertTrue( - extractor.getClass().getName(), - extractor - instanceof Word6Extractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(new FileInputStream(docx)); - assertTrue( - extractor - instanceof XWPFWordExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - // PowerPoint - extractor = ExtractorFactory.createExtractor(new FileInputStream(ppt)); - assertTrue( - extractor - instanceof PowerPointExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(new FileInputStream(pptx)); - assertTrue( - extractor - instanceof XSLFPowerPointExtractor - ); - assertTrue( - extractor.getText().length() > 120 - ); - extractor.close(); - - // Visio - extractor = ExtractorFactory.createExtractor(new FileInputStream(vsd)); - assertTrue( - extractor - instanceof VisioTextExtractor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); - - // Visio - vsdx - extractor = ExtractorFactory.createExtractor(new FileInputStream(vsdx)); - assertTrue( - extractor - instanceof XDGFVisioExtractor - ); - assertTrue( - extractor.getText().length() > 20 - ); - extractor.close(); - - // Publisher - extractor = ExtractorFactory.createExtractor(new FileInputStream(pub)); - assertTrue( - extractor - instanceof PublisherTextExtractor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); - - // Outlook msg - extractor = ExtractorFactory.createExtractor(new FileInputStream(msg)); - assertTrue( - extractor - instanceof OutlookTextExtactor - ); - assertTrue( - extractor.getText().length() > 50 - ); - extractor.close(); + testStream((f) -> ExtractorFactory.createExtractor(f), true); + } - // Text - try (FileInputStream stream = new FileInputStream(txt)) { - ExtractorFactory.createExtractor(stream); - fail("expected IllegalArgumentException"); - } catch(IllegalArgumentException e) { - // Good - } + @Test(expected = IllegalArgumentException.class) + public void testInputStreamInvalid() throws Exception { + testInvalid((f) -> ExtractorFactory.createExtractor(f)); } @Test public void testPOIFS() throws Exception { - // Excel - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(xls))) - instanceof ExcelExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(xls))).getText().length() > 200 - ); - - // Word - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc))) - instanceof WordExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc))).getText().length() > 120 - ); - - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc6))) - instanceof Word6Extractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc6))).getText().length() > 20 - ); - - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc95))) - instanceof Word6Extractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(doc95))).getText().length() > 120 - ); - - // PowerPoint - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(ppt))) - instanceof PowerPointExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(ppt))).getText().length() > 120 - ); - - // Visio - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(vsd))) - instanceof VisioTextExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(vsd))).getText().length() > 50 - ); - - // Publisher - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(pub))) - instanceof PublisherTextExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(pub))).getText().length() > 50 - ); - - // Outlook msg - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(msg))) - instanceof OutlookTextExtactor - ); - assertTrue( - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(msg))).getText().length() > 50 - ); - - // Text - try { - ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(txt))); - fail("expected IllegalArgumentException"); - } catch(IOException e) { - // Good - } + testStream((f) -> ExtractorFactory.createExtractor(new POIFSFileSystem(f)), false); } + @Test(expected = IOException.class) + public void testPOIFSInvalid() throws Exception { + testInvalid((f) -> ExtractorFactory.createExtractor(new POIFSFileSystem(f))); + } @Test public void testOPOIFS() throws Exception { - // Excel - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(xls))) - instanceof ExcelExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(xls))).getText().length() > 200 - ); - - // Word - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc))) - instanceof WordExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc))).getText().length() > 120 - ); - - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc6))) - instanceof Word6Extractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc6))).getText().length() > 20 - ); - - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc95))) - instanceof Word6Extractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(doc95))).getText().length() > 120 - ); + testStream((f) -> ExtractorFactory.createExtractor(new OPOIFSFileSystem(f)), false); + } - // PowerPoint - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(ppt))) - instanceof PowerPointExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(ppt))).getText().length() > 120 - ); + @Test(expected = IOException.class) + public void testOPOIFSInvalid() throws Exception { + testInvalid((f) -> ExtractorFactory.createExtractor(new OPOIFSFileSystem(f))); + } - // Visio - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(vsd))) - instanceof VisioTextExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(vsd))).getText().length() > 50 - ); - // Publisher - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(pub))) - instanceof PublisherTextExtractor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(pub))).getText().length() > 50 - ); + private void testStream(final FunctionEx poifsIS, final boolean loadOOXML) + throws IOException, OpenXML4JException, XmlException { + for (int i = 0; i < TEST_SET.length; i += 4) { + File testFile = (File) TEST_SET[i + 1]; + if (!loadOOXML && (testFile.getName().endsWith("x") || testFile.getName().endsWith("xlsb"))) { + continue; + } + try (FileInputStream fis = new FileInputStream(testFile); + POITextExtractor ext = poifsIS.apply(fis)) { + testExtractor(ext, (String) TEST_SET[i], (Class) TEST_SET[i + 2], (Integer) TEST_SET[i + 3]); + } catch (IllegalArgumentException e) { + fail("failed to process "+testFile); + } + } + } - // Outlook msg - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(msg))) - instanceof OutlookTextExtactor - ); - assertTrue( - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(msg))).getText().length() > 50 - ); + private void testExtractor(final POITextExtractor ext, final String testcase, final Class extrClass, final Integer minLength) { + assertTrue("invalid extractor for " + testcase, extrClass.isInstance(ext)); + final String actual = ext.getText(); + if (minLength == -1) { + assertContains(actual.toLowerCase(Locale.ROOT), "test"); + } else { + assertTrue("extracted content too short for " + testcase, actual.length() > minLength); + } + } + private void testInvalid(FunctionEx poifs) throws IOException, OpenXML4JException, XmlException { // Text - try { - ExtractorFactory.createExtractor(new OPOIFSFileSystem(new FileInputStream(txt))); - fail("expected IllegalArgumentException"); - } catch(IOException e) { - // Good + try (FileInputStream fis = new FileInputStream(txt); + POITextExtractor te = poifs.apply(fis)) { } } @Test public void testPackage() throws Exception { - // Excel - POIXMLTextExtractor extractor = ExtractorFactory.createExtractor(OPCPackage.open(xlsx.toString(), PackageAccess.READ)); - assertTrue(extractor instanceof XSSFExcelExtractor); - extractor.close(); - extractor = ExtractorFactory.createExtractor(OPCPackage.open(xlsx.toString())); - assertTrue(extractor.getText().length() > 200); - extractor.close(); - - // Word - extractor = ExtractorFactory.createExtractor(OPCPackage.open(docx.toString())); - assertTrue(extractor instanceof XWPFWordExtractor); - extractor.close(); - - extractor = ExtractorFactory.createExtractor(OPCPackage.open(docx.toString())); - assertTrue(extractor.getText().length() > 120); - extractor.close(); - - // PowerPoint - extractor = ExtractorFactory.createExtractor(OPCPackage.open(pptx.toString())); - assertTrue(extractor instanceof XSLFPowerPointExtractor); - extractor.close(); + for (int i = 0; i < TEST_SET.length; i += 4) { + final File testFile = (File) TEST_SET[i + 1]; + if (!testFile.getName().endsWith("x")) { + continue; + } - extractor = ExtractorFactory.createExtractor(OPCPackage.open(pptx.toString())); - assertTrue(extractor.getText().length() > 120); - extractor.close(); - - // Visio - extractor = ExtractorFactory.createExtractor(OPCPackage.open(vsdx.toString())); - assertTrue(extractor instanceof XDGFVisioExtractor); - assertTrue(extractor.getText().length() > 20); - extractor.close(); + try (final OPCPackage pkg = OPCPackage.open(testFile, PackageAccess.READ); + final POITextExtractor ext = ExtractorFactory.createExtractor(pkg)) { + testExtractor(ext, (String) TEST_SET[i], (Class) TEST_SET[i + 2], (Integer) TEST_SET[i + 3]); + pkg.revert(); + } + } + } + @Test(expected = UnsupportedFileFormatException.class) + public void testPackageInvalid() throws Exception { // Text - try { - ExtractorFactory.createExtractor(OPCPackage.open(txt.toString())); - fail("TestExtractorFactory.testPackage() failed on " + txt); - } catch(UnsupportedFileFormatException e) { - // Good - } catch (Exception e) { - LOG.log(POILogger.WARN, "TestExtractorFactory.testPackage() failed on " + txt); - throw e; - } + try (final OPCPackage pkg = OPCPackage.open(txt, PackageAccess.READ); + final POITextExtractor te = ExtractorFactory.createExtractor(pkg)) {} } @Test @@ -781,142 +325,49 @@ public class TestExtractorFactory { * does poifs embedded, but will do ooxml ones * at some point. */ - @SuppressWarnings("deprecation") @Test public void testEmbedded() throws Exception { - POIOLE2TextExtractor ext; - POITextExtractor[] embeds; - - // No embeddings - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(xls); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - assertEquals(0, embeds.length); - ext.close(); - - // No embeddings - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(xls); - embeds = ExtractorFactory.getEmbeddedDocsTextExtractors(ext); - assertEquals(0, embeds.length); - ext.close(); - - // Excel - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(xlsEmb); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - assertNotNull(embeds); - ext.close(); - - // Excel - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(xlsEmb); - embeds = ExtractorFactory.getEmbeddedDocsTextExtractors(ext); - - assertEquals(6, embeds.length); - int numWord = 0, numXls = 0, numPpt = 0, numMsg = 0, numWordX; - for (POITextExtractor embed : embeds) { - assertTrue(embed.getText().length() > 20); - - if (embed instanceof PowerPointExtractor) numPpt++; - else if (embed instanceof ExcelExtractor) numXls++; - else if (embed instanceof WordExtractor) numWord++; - else if (embed instanceof OutlookTextExtactor) numMsg++; - } - assertEquals(2, numPpt); - assertEquals(2, numXls); - assertEquals(2, numWord); - assertEquals(0, numMsg); - ext.close(); - - // Word - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(docEmb); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - - numWord = 0; numXls = 0; numPpt = 0; numMsg = 0; - assertEquals(4, embeds.length); - for (POITextExtractor embed : embeds) { - assertTrue(embed.getText().length() > 20); - if (embed instanceof PowerPointExtractor) numPpt++; - else if (embed instanceof ExcelExtractor) numXls++; - else if (embed instanceof WordExtractor) numWord++; - else if (embed instanceof OutlookTextExtactor) numMsg++; - } - assertEquals(1, numPpt); - assertEquals(2, numXls); - assertEquals(1, numWord); - assertEquals(0, numMsg); - ext.close(); - - // Word which contains an OOXML file - ext = (POIOLE2TextExtractor) - ExtractorFactory.createExtractor(docEmbOOXML); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - - numWord = 0; numXls = 0; numPpt = 0; numMsg = 0; numWordX = 0; - assertEquals(3, embeds.length); - for (POITextExtractor embed : embeds) { - assertTrue(embed.getText().length() > 20); - if (embed instanceof PowerPointExtractor) numPpt++; - else if (embed instanceof ExcelExtractor) numXls++; - else if (embed instanceof WordExtractor) numWord++; - else if (embed instanceof OutlookTextExtactor) numMsg++; - else if (embed instanceof XWPFWordExtractor) numWordX++; - } - assertEquals(1, numPpt); - assertEquals(1, numXls); - assertEquals(0, numWord); - assertEquals(1, numWordX); - assertEquals(0, numMsg); - ext.close(); - - // Outlook - ext = (OutlookTextExtactor) - ExtractorFactory.createExtractor(msgEmb); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - - numWord = 0; numXls = 0; numPpt = 0; numMsg = 0; - assertEquals(1, embeds.length); - for (POITextExtractor embed : embeds) { - assertTrue(embed.getText().length() > 20); - if (embed instanceof PowerPointExtractor) numPpt++; - else if (embed instanceof ExcelExtractor) numXls++; - else if (embed instanceof WordExtractor) numWord++; - else if (embed instanceof OutlookTextExtactor) numMsg++; - } - assertEquals(0, numPpt); - assertEquals(0, numXls); - assertEquals(1, numWord); - assertEquals(0, numMsg); - ext.close(); - - // Outlook with another outlook file in it - ext = (OutlookTextExtactor) - ExtractorFactory.createExtractor(msgEmbMsg); - embeds = ExtractorFactory.getEmbededDocsTextExtractors(ext); - - numWord = 0; numXls = 0; numPpt = 0; numMsg = 0; - assertEquals(1, embeds.length); - for (POITextExtractor embed : embeds) { - assertTrue(embed.getText().length() > 20); - if (embed instanceof PowerPointExtractor) numPpt++; - else if (embed instanceof ExcelExtractor) numXls++; - else if (embed instanceof WordExtractor) numWord++; - else if (embed instanceof OutlookTextExtactor) numMsg++; + final Object[] testObj = { + "No embeddings", xls, "0-0-0-0-0-0", + "Excel", xlsEmb, "6-2-2-2-0-0", + "Word", docEmb, "4-1-2-1-0-0", + "Word which contains an OOXML file", docEmbOOXML, "3-0-1-1-0-1", + "Outlook", msgEmb, "1-1-0-0-0-0", + "Outlook with another outlook file in it", msgEmbMsg, "1-0-0-0-1-0", + }; + + for (int i=0; i 20); + if (embed instanceof SlideShowExtractor) { + numPpt++; + } else if (embed instanceof ExcelExtractor) { + numXls++; + } else if (embed instanceof WordExtractor) { + numWord++; + } else if (embed instanceof OutlookTextExtactor) { + numMsg++; + } else if (embed instanceof XWPFWordExtractor) { + numWordX++; + } + } + + final String actual = embeds.length+"-"+numWord+"-"+numXls+"-"+numPpt+"-"+numMsg+"-"+numWordX; + final String expected = (String)testObj[i+2]; + assertEquals("invalid number of embeddings - "+testObj[i], expected, actual); + } } - assertEquals(0, numPpt); - assertEquals(0, numXls); - assertEquals(0, numWord); - assertEquals(1, numMsg); - ext.close(); // TODO - PowerPoint // TODO - Publisher // TODO - Visio } - private static final String[] EXPECTED_FAILURES = new String[] { + private static final String[] EXPECTED_FAILURES = { // password protected files "spreadsheet/password.xls", "spreadsheet/protected_passtika.xlsx", @@ -1018,37 +469,26 @@ public class TestExtractorFactory { * #59074 - Excel 95 files should give a helpful message, not just * "No supported documents found in the OLE2 stream" */ - @Test + @Test(expected = OldExcelFormatException.class) public void bug59074() throws Exception { - try { - ExtractorFactory.createExtractor( - POIDataSamples.getSpreadSheetInstance().getFile("59074.xls")); - fail("Old excel formats not supported via ExtractorFactory"); - } catch (OldExcelFormatException e) { - // expected here - } + ExtractorFactory.createExtractor( + POIDataSamples.getSpreadSheetInstance().getFile("59074.xls")); } @SuppressWarnings("deprecation") - @Test - public void testGetEmbeddedFromXMLExtractor() { - try { - // currently not implemented - ExtractorFactory.getEmbededDocsTextExtractors((POIXMLTextExtractor)null); - fail("Unsupported currently"); - } catch (IllegalStateException e) { - // expected here - } + @Test(expected = IllegalStateException.class) + public void testGetEmbedFromXMLExtractor() { + // currently not implemented + ExtractorFactory.getEmbededDocsTextExtractors((POIXMLTextExtractor) null); + } - try { - // currently not implemented - ExtractorFactory.getEmbeddedDocsTextExtractors((POIXMLTextExtractor)null); - fail("Unsupported currently"); - } catch (IllegalStateException e) { - // expected here - } + @SuppressWarnings("deprecation") + @Test(expected = IllegalStateException.class) + public void testGetEmbeddedFromXMLExtractor() { + // currently not implemented + ExtractorFactory.getEmbeddedDocsTextExtractors((POIXMLTextExtractor)null); } - + // This bug is currently open. This test will fail with "expected error not thrown" when the bug has been fixed. // When this happens, change this from @Test(expected=...) to @Test // bug 45565: text within TextBoxes is extracted by ExcelExtractor and WordExtractor diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java index 68c8bfa054..7403ecc3d7 100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java @@ -120,10 +120,10 @@ public class TestHxxFEncryption { public void newPassword(String newPass) throws IOException, OpenXML4JException, XmlException { Biff8EncryptionKey.setCurrentUserPassword(password); File f = sampleDir.getFile(file); - POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f); + POITextExtractor te1 = ExtractorFactory.createExtractor(f); Biff8EncryptionKey.setCurrentUserPassword(newPass); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - POIDocument doc = te1.getDocument(); + POIDocument doc = (POIDocument)te1.getDocument(); doc.write(bos); doc.close(); te1.close(); @@ -140,25 +140,25 @@ public class TestHxxFEncryption { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Biff8EncryptionKey.setCurrentUserPassword(password); File f = sampleDir.getFile(file); - POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f); + POITextExtractor te1 = ExtractorFactory.createExtractor(f); // first remove encryption Biff8EncryptionKey.setCurrentUserPassword(null); - POIDocument doc = te1.getDocument(); + POIDocument doc = (POIDocument)te1.getDocument(); doc.write(bos); doc.close(); te1.close(); // then use default setting, which is cryptoapi String newPass = "newPass"; - POIOLE2TextExtractor te2 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + POITextExtractor te2 = ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); Biff8EncryptionKey.setCurrentUserPassword(newPass); - doc = te2.getDocument(); + doc = (POIDocument)te2.getDocument(); bos.reset(); doc.write(bos); doc.close(); te2.close(); // and finally update cryptoapi setting - POIOLE2TextExtractor te3 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); - doc = te3.getDocument(); + POITextExtractor te3 = ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + doc = (POIDocument)te3.getDocument(); // need to cache data (i.e. read all data) before changing the key size if (doc instanceof HSLFSlideShowImpl) { HSLFSlideShowImpl hss = (HSLFSlideShowImpl)doc; @@ -175,8 +175,8 @@ public class TestHxxFEncryption { doc.close(); te3.close(); // check the setting - POIOLE2TextExtractor te4 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); - doc = te4.getDocument(); + POITextExtractor te4 = ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + doc = (POIDocument)te4.getDocument(); ei = doc.getEncryptionInfo(); assertNotNull(ei); assertTrue(ei.getHeader() instanceof CryptoAPIEncryptionHeader); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java b/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java index 94e5a5077f..a6a6467f9e 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java @@ -50,6 +50,7 @@ import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePartName; import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.sl.draw.DrawPaint; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; @@ -221,28 +222,27 @@ public class TestXSLFBugs { * rID2 -> slide3.xml */ @Test - public void bug54916() throws Exception { - XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("OverlappingRelations.pptx"); - XSLFSlide slide; + public void bug54916() throws IOException { + try (XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("OverlappingRelations.pptx")) { + XSLFSlide slide; - // Should find 4 slides - assertEquals(4, ss.getSlides().size()); + // Should find 4 slides + assertEquals(4, ss.getSlides().size()); - // Check the text, to see we got them in order - slide = ss.getSlides().get(0); - assertContains(getSlideText(slide), "POI cannot read this"); + // Check the text, to see we got them in order + slide = ss.getSlides().get(0); + assertContains(getSlideText(ss, slide), "POI cannot read this"); - slide = ss.getSlides().get(1); - assertContains(getSlideText(slide), "POI can read this"); - assertContains(getSlideText(slide), "Has a relationship to another slide"); + slide = ss.getSlides().get(1); + assertContains(getSlideText(ss, slide), "POI can read this"); + assertContains(getSlideText(ss, slide), "Has a relationship to another slide"); - slide = ss.getSlides().get(2); - assertContains(getSlideText(slide), "POI can read this"); + slide = ss.getSlides().get(2); + assertContains(getSlideText(ss, slide), "POI can read this"); - slide = ss.getSlides().get(3); - assertContains(getSlideText(slide), "POI can read this"); - - ss.close(); + slide = ss.getSlides().get(3); + assertContains(getSlideText(ss, slide), "POI can read this"); + } } /** @@ -311,8 +311,15 @@ public class TestXSLFBugs { ss.close(); } - protected String getSlideText(XSLFSlide slide) { - return XSLFPowerPointExtractor.getText(slide, true, false, false); + protected String getSlideText(XMLSlideShow ppt, XSLFSlide slide) throws IOException { + try (SlideShowExtractor extr = new SlideShowExtractor(ppt)) { + // do not auto-close the slideshow + extr.setFilesystem(null); + extr.setSlidesByDefault(true); + extr.setNotesByDefault(false); + extr.setMasterByDefault(false); + return extr.getText(slide); + } } @Test @@ -458,7 +465,7 @@ public class TestXSLFBugs { for (int i = 0; i < slideTexts.length; i++) { XSLFSlide slide = ss.getSlides().get(i); - assertContains(getSlideText(slide), slideTexts[i]); + assertContains(getSlideText(ss, slide), slideTexts[i]); } } diff --git a/src/ooxml/testcases/org/apache/poi/xslf/extractor/TestXSLFPowerPointExtractor.java b/src/ooxml/testcases/org/apache/poi/xslf/extractor/TestXSLFPowerPointExtractor.java index 2b6a96ad8d..353ce7cbd4 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/extractor/TestXSLFPowerPointExtractor.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/extractor/TestXSLFPowerPointExtractor.java @@ -24,16 +24,17 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.IOException; import java.io.InputStream; import org.apache.poi.POIDataSamples; -import org.apache.poi.POITextExtractor; import org.apache.poi.extractor.ExtractorFactory; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; -import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.xslf.usermodel.XMLSlideShow; import org.apache.xmlbeans.XmlException; +import org.junit.Ignore; import org.junit.Test; /** @@ -44,188 +45,189 @@ public class TestXSLFPowerPointExtractor { /** * Get text out of the simple file - * @throws XmlException - * @throws OpenXML4JException */ @Test - public void testGetSimpleText() - throws IOException, XmlException, OpenXML4JException { - XMLSlideShow xmlA = openPPTX("sample.pptx"); - @SuppressWarnings("resource") - OPCPackage pkg = xmlA.getPackage(); - - new XSLFPowerPointExtractor(xmlA).close(); - new XSLFPowerPointExtractor(pkg).close(); - - XSLFPowerPointExtractor extractor = - new XSLFPowerPointExtractor(xmlA); - extractor.getText(); - - String text = extractor.getText(); - assertTrue(text.length() > 0); - - // Check Basics - assertStartsWith(text, "Lorem ipsum dolor sit amet\n"); - assertContains(text, "amet\n\n"); - - // Our placeholder master text - // This shouldn't show up in the output - // String masterText = - // "Click to edit Master title style\n" + - // "Click to edit Master subtitle style\n" + - // "\n\n\n\n\n\n" + - // "Click to edit Master title style\n" + - // "Click to edit Master text styles\n" + - // "Second level\n" + - // "Third level\n" + - // "Fourth level\n" + - // "Fifth level\n"; - - // Just slides, no notes - text = extractor.getText(true, false, false); - String slideText = - "Lorem ipsum dolor sit amet\n" + - "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + - "\n" + - "Lorem ipsum dolor sit amet\n" + - "Lorem\n" + - "ipsum\n" + - "dolor\n" + - "sit\n" + - "amet\n" + - "\n"; - assertEquals(slideText, text); - - // Just notes, no slides - text = extractor.getText(false, true); - assertEquals("\n\n1\n\n\n2\n", text); - - // Both - text = extractor.getText(true, true, false); - String bothText = - "Lorem ipsum dolor sit amet\n" + - "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + - "\n\n\n1\n" + - "Lorem ipsum dolor sit amet\n" + - "Lorem\n" + - "ipsum\n" + - "dolor\n" + - "sit\n" + - "amet\n" + - "\n\n\n2\n"; - assertEquals(bothText, text); - - // With Slides and Master Text - text = extractor.getText(true, false, true); - String smText = - "Lorem ipsum dolor sit amet\n" + - "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + - "\n" + - "Lorem ipsum dolor sit amet\n" + - "Lorem\n" + - "ipsum\n" + - "dolor\n" + - "sit\n" + - "amet\n" + - "\n"; - assertEquals(smText, text); - - // With Slides, Notes and Master Text - text = extractor.getText(true, true, true); - String snmText = - "Lorem ipsum dolor sit amet\n" + - "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + - "\n\n\n1\n" + - "Lorem ipsum dolor sit amet\n" + - "Lorem\n" + - "ipsum\n" + - "dolor\n" + - "sit\n" + - "amet\n" + - "\n\n\n2\n"; - assertEquals(snmText, text); - - // Via set defaults - extractor.setSlidesByDefault(false); - extractor.setNotesByDefault(true); - text = extractor.getText(); - assertEquals("\n\n1\n\n\n2\n", text); - - extractor.close(); - xmlA.close(); + public void testGetSimpleText() throws IOException { + try (XMLSlideShow xmlA = openPPTX("sample.pptx"); + SlideShowExtractor extractor = new SlideShowExtractor(xmlA)) { + + extractor.getText(); + + String text = extractor.getText(); + assertTrue(text.length() > 0); + + // Check Basics + assertStartsWith(text, "Lorem ipsum dolor sit amet\n"); + assertContains(text, "amet\n\n"); + + // Our placeholder master text + // This shouldn't show up in the output + // String masterText = + // "Click to edit Master title style\n" + + // "Click to edit Master subtitle style\n" + + // "\n\n\n\n\n\n" + + // "Click to edit Master title style\n" + + // "Click to edit Master text styles\n" + + // "Second level\n" + + // "Third level\n" + + // "Fourth level\n" + + // "Fifth level\n"; + + // Just slides, no notes + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(false); + extractor.setMasterByDefault(false); + text = extractor.getText(); + String slideText = + "Lorem ipsum dolor sit amet\n" + + "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + + "\n" + + "Lorem ipsum dolor sit amet\n" + + "Lorem\n" + + "ipsum\n" + + "dolor\n" + + "sit\n" + + "amet\n" + + "\n"; + assertEquals(slideText, text); + + // Just notes, no slides + extractor.setSlidesByDefault(false); + extractor.setNotesByDefault(true); + text = extractor.getText(); + assertEquals("\n\n1\n\n\n2\n", text); + + // Both + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(true); + text = extractor.getText(); + String bothText = + "Lorem ipsum dolor sit amet\n" + + "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + + "\n\n\n1\n" + + "Lorem ipsum dolor sit amet\n" + + "Lorem\n" + + "ipsum\n" + + "dolor\n" + + "sit\n" + + "amet\n" + + "\n\n\n2\n"; + assertEquals(bothText, text); + + // With Slides and Master Text + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(false); + extractor.setMasterByDefault(true); + text = extractor.getText(); + String smText = + "Lorem ipsum dolor sit amet\n" + + "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + + "\n" + + "Lorem ipsum dolor sit amet\n" + + "Lorem\n" + + "ipsum\n" + + "dolor\n" + + "sit\n" + + "amet\n" + + "\n"; + assertEquals(smText, text); + + // With Slides, Notes and Master Text + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(true); + extractor.setMasterByDefault(true); + text = extractor.getText(); + String snmText = + "Lorem ipsum dolor sit amet\n" + + "Nunc at risus vel erat tempus posuere. Aenean non ante.\n" + + "\n\n\n1\n" + + "Lorem ipsum dolor sit amet\n" + + "Lorem\n" + + "ipsum\n" + + "dolor\n" + + "sit\n" + + "amet\n" + + "\n\n\n2\n"; + assertEquals(snmText, text); + + // Via set defaults + extractor.setSlidesByDefault(false); + extractor.setNotesByDefault(true); + text = extractor.getText(); + assertEquals("\n\n1\n\n\n2\n", text); + } } + @Test public void testGetComments() throws IOException { - XMLSlideShow xml = openPPTX("45545_Comment.pptx"); - XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(xml); - - String text = extractor.getText(); - assertTrue(text.length() > 0); + try (XMLSlideShow xml = openPPTX("45545_Comment.pptx"); + SlideShowExtractor extractor = new SlideShowExtractor(xml)) { + extractor.setCommentsByDefault(true); - // Check comments are there - assertContains(text, "testdoc"); - assertContains(text, "test phrase"); + String text = extractor.getText(); + assertTrue(text.length() > 0); - // Check the authors came through too - assertContains(text, "XPVMWARE01"); + // Check comments are there + assertContains(text, "testdoc"); + assertContains(text, "test phrase"); - extractor.close(); - xml.close(); + // Check the authors came through too + assertContains(text, "XPVMWARE01"); + } } + @Test + @Ignore("currently slidelayouts aren't yet supported") public void testGetMasterText() throws Exception { - XMLSlideShow xml = openPPTX("WithMaster.pptx"); - XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(xml); - extractor.setSlidesByDefault(true); - extractor.setNotesByDefault(false); - extractor.setMasterByDefault(true); - - String text = extractor.getText(); - assertTrue(text.length() > 0); - - // Check master text is there - assertContains(text, "Footer from the master slide"); - - // Theme text shouldn't show up - // String themeText = - // "Theme Master Title\n" + - // "Theme Master first level\n" + - // "And the 2nd level\n" + - // "Our 3rd level goes here\n" + - // "And onto the 4th, such fun....\n" + - // "Finally is the Fifth level\n"; - - // Check the whole text - String wholeText = - "First page title\n" + - "First page subtitle\n" + - "This is the Master Title\n" + - "This text comes from the Master Slide\n" + - "\n" + - // TODO Detect we didn't have a title, and include the master one - "2nd page subtitle\n" + - "Footer from the master slide\n" + - "This is the Master Title\n" + - "This text comes from the Master Slide\n"; - assertEquals(wholeText, text); - - extractor.close(); - xml.close(); + try (XMLSlideShow xml = openPPTX("WithMaster.pptx"); + SlideShowExtractor extractor = new SlideShowExtractor(xml)) { + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(false); + extractor.setMasterByDefault(true); + + + String text = extractor.getText(); + assertTrue(text.length() > 0); + + // Check master text is there + assertContains(text, "Footer from the master slide"); + + // Theme text shouldn't show up + // String themeText = + // "Theme Master Title\n" + + // "Theme Master first level\n" + + // "And the 2nd level\n" + + // "Our 3rd level goes here\n" + + // "And onto the 4th, such fun....\n" + + // "Finally is the Fifth level\n"; + + // Check the whole text + String wholeText = + "First page title\n" + + "First page subtitle\n" + + "This is the Master Title\n" + + "This text comes from the Master Slide\n" + + "\n" + + // TODO Detect we didn't have a title, and include the master one + "2nd page subtitle\n" + + "Footer from the master slide\n" + + "This is the Master Title\n" + + "This text comes from the Master Slide\n"; + assertEquals(wholeText, text); + } } @Test public void testTable() throws Exception { - XMLSlideShow xml = openPPTX("present1.pptx"); - XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(xml); - - String text = extractor.getText(); - assertTrue(text.length() > 0); + try (XMLSlideShow xml = openPPTX("present1.pptx"); + SlideShowExtractor extractor = new SlideShowExtractor(xml)) { - // Check comments are there - assertContains(text, "TEST"); + String text = extractor.getText(); + assertTrue(text.length() > 0); - extractor.close(); - xml.close(); + // Check comments are there + assertContains(text, "TEST"); + } } /** @@ -241,74 +243,76 @@ public class TestXSLFPowerPointExtractor { }; for(String extension : extensions) { String filename = "testPPT." + extension; - XMLSlideShow xml = openPPTX(filename); - XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(xml); - String text = extractor.getText(); - if (extension.equals("thmx")) { - // Theme file doesn't have any textual content - assertEquals(filename, 0, text.length()); - continue; + try (XMLSlideShow xml = openPPTX(filename); + SlideShowExtractor extractor = new SlideShowExtractor(xml)) { + + String text = extractor.getText(); + if (extension.equals("thmx")) { + // Theme file doesn't have any textual content + assertEquals(filename, 0, text.length()); + continue; + } + + assertTrue(filename, text.length() > 0); + assertContains(filename, text, "Attachment Test"); + assertContains(filename, text, "This is a test file data with the same content"); + assertContains(filename, text, "content parsing"); + assertContains(filename, text, "Different words to test against"); + assertContains(filename, text, "Mystery"); } - - assertTrue(filename, text.length() > 0); - assertContains(filename, text, "Attachment Test"); - assertContains(filename, text, "This is a test file data with the same content"); - assertContains(filename, text, "content parsing"); - assertContains(filename, text, "Different words to test against"); - assertContains(filename, text, "Mystery"); - - extractor.close(); - xml.close(); } } @Test - public void test45541() throws Exception { + public void test45541() throws IOException, OpenXML4JException, XmlException { // extract text from a powerpoint that has a header in the notes-element - POITextExtractor extr = ExtractorFactory.createExtractor( - slTests.getFile("45541_Header.pptx")); - String text = extr.getText(); - assertNotNull(text); - assertFalse("Had: " + text, text.contains("testdoc")); - - text = ((XSLFPowerPointExtractor)extr).getText(false, true); - assertContains(text, "testdoc"); - extr.close(); - assertNotNull(text); + final File headerFile = slTests.getFile("45541_Header.pptx"); + try (final SlideShowExtractor extr = ExtractorFactory.createExtractor(headerFile)) { + String text = extr.getText(); + assertNotNull(text); + assertFalse("Had: " + text, text.contains("testdoc")); + + extr.setSlidesByDefault(false); + extr.setNotesByDefault(true); + + text = extr.getText(); + assertContains(text, "testdoc"); + assertNotNull(text); + } // extract text from a powerpoint that has a footer in the master-slide - extr = ExtractorFactory.createExtractor( - slTests.getFile("45541_Footer.pptx")); - text = extr.getText(); - assertNotContained(text, "testdoc"); - - text = ((XSLFPowerPointExtractor)extr).getText(false, true); - assertNotContained(text, "testdoc"); - - text = ((XSLFPowerPointExtractor)extr).getText(false, false, true); - assertNotContained(text, "testdoc"); - - extr.close(); + final File footerFile = slTests.getFile("45541_Footer.pptx"); + try (SlideShowExtractor extr = ExtractorFactory.createExtractor(footerFile)) { + String text = extr.getText(); + assertNotContained(text, "testdoc"); + + extr.setSlidesByDefault(false); + extr.setNotesByDefault(true); + text = extr.getText(); + assertNotContained(text, "testdoc"); + + extr.setSlidesByDefault(false); + extr.setNotesByDefault(false); + extr.setMasterByDefault(true); + text = extr.getText(); + assertNotContained(text, "testdoc"); + } } @Test public void bug54570() throws IOException { - XMLSlideShow xml = openPPTX("bug54570.pptx"); - XSLFPowerPointExtractor extractor = new XSLFPowerPointExtractor(xml); - String text = extractor.getText(); - assertNotNull(text); - extractor.close(); - xml.close(); + try (XMLSlideShow xml = openPPTX("bug54570.pptx"); + SlideShowExtractor extractor = new SlideShowExtractor(xml)) { + String text = extractor.getText(); + assertNotNull(text); + } } private XMLSlideShow openPPTX(String file) throws IOException { - InputStream is = slTests.openResourceAsStream(file); - try { + try (InputStream is = slTests.openResourceAsStream(file)) { return new XMLSlideShow(is); - } finally { - is.close(); } } } diff --git a/src/scratchpad/src/org/apache/poi/extractor/OLE2ScratchpadExtractorFactory.java b/src/scratchpad/src/org/apache/poi/extractor/OLE2ScratchpadExtractorFactory.java index bf7cc57d4f..f77d0834f9 100644 --- a/src/scratchpad/src/org/apache/poi/extractor/OLE2ScratchpadExtractorFactory.java +++ b/src/scratchpad/src/org/apache/poi/extractor/OLE2ScratchpadExtractorFactory.java @@ -38,6 +38,8 @@ import org.apache.poi.hwpf.extractor.WordExtractor; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.Entry; +import org.apache.poi.sl.extractor.SlideShowExtractor; +import org.apache.poi.sl.usermodel.SlideShowFactory; /** * Scratchpad-specific logic for {@link OLE2ExtractorFactory} and @@ -65,7 +67,7 @@ public class OLE2ScratchpadExtractorFactory { } if (poifsDir.hasEntry(HSLFSlideShow.POWERPOINT_DOCUMENT)) { - return new PowerPointExtractor(poifsDir); + return new SlideShowExtractor(SlideShowFactory.create(poifsDir)); } if (poifsDir.hasEntry("VisioDocument")) { diff --git a/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java b/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java index 156747eace..efbefd6ceb 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java +++ b/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java @@ -34,6 +34,7 @@ import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.sl.usermodel.SlideShowFactory; +import org.apache.poi.util.Removal; /** * This class can be used to extract text from a PowerPoint file. Can optionally @@ -43,6 +44,7 @@ import org.apache.poi.sl.usermodel.SlideShowFactory; */ @SuppressWarnings("WeakerAccess") @Deprecated +@Removal(version="5.0.0") public final class PowerPointExtractor extends POIOLE2TextExtractor { private final SlideShowExtractor delegate; diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java index ab6cc818d0..16df6f384f 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java @@ -1139,4 +1139,9 @@ public final class HSLFSlideShow implements SlideShowNote that in order to properly release resources the - * SlideShow should be closed after use. + * Creates a HSLFSlideShow from the given NPOIFSFileSystem

+ * Note that in order to properly release resources the + * SlideShow should be closed after use. */ - public static SlideShow createSlideShow(NPOIFSFileSystem fs) throws IOException { + public static HSLFSlideShow createSlideShow(final NPOIFSFileSystem fs) throws IOException { return new HSLFSlideShow(fs); } + /** + * Creates a HSLFSlideShow from the given DirectoryNode

+ * Note that in order to properly release resources the + * SlideShow should be closed after use. + */ + public static HSLFSlideShow createSlideShow(final DirectoryNode root) throws IOException { + return new HSLFSlideShow(root); + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java index 248fb3b2c5..c0676f2264 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java @@ -846,9 +846,13 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { @Override public void close() throws IOException { - NPOIFSFileSystem fs = getDirectory().getFileSystem(); - if (fs != null) { - fs.close(); + // only close the filesystem, if we are based on the root node. + // embedded documents/slideshows shouldn't close the parent container + if (getDirectory().getParent() == null) { + NPOIFSFileSystem fs = getDirectory().getFileSystem(); + if (fs != null) { + fs.close(); + } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java b/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java index 6a34d1af3f..9a420a6e26 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java @@ -42,6 +42,10 @@ import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.OPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.sl.extractor.SlideShowExtractor; +import org.apache.poi.sl.usermodel.ObjectShape; +import org.apache.poi.sl.usermodel.SlideShow; +import org.apache.poi.sl.usermodel.SlideShowFactory; import org.apache.poi.util.IOUtils; import org.junit.Test; @@ -76,43 +80,46 @@ public final class TestExtractor { // ppe.close(); // } - private PowerPointExtractor openExtractor(String fileName) throws IOException { - InputStream is = slTests.openResourceAsStream(fileName); - try { - return new PowerPointExtractor(is); - } finally { - is.close(); + private SlideShowExtractor openExtractor(String fileName) throws IOException { + try (InputStream is = slTests.openResourceAsStream(fileName)) { + return new SlideShowExtractor(SlideShowFactory.create(is)); } } @Test public void testReadSheetText() throws IOException { // Basic 2 page example - PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt"); - assertEquals(expectText, ppe.getText()); - ppe.close(); + try (SlideShowExtractor ppe = openExtractor("basic_test_ppt_file.ppt")) { + assertEquals(expectText, ppe.getText()); + } // 1 page example with text boxes - PowerPointExtractor ppe2 = openExtractor("with_textbox.ppt"); - assertEquals(expectText2, ppe2.getText()); - ppe2.close(); + try (SlideShowExtractor ppe = openExtractor("with_textbox.ppt")) { + assertEquals(expectText2, ppe.getText()); + } } @Test public void testReadNoteText() throws IOException { // Basic 2 page example - PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt"); - String notesText = ppe.getNotes(); - String expText = "\nThese are the notes for page 1\n\nThese are the notes on page two, again lacking formatting\n"; - assertEquals(expText, notesText); - ppe.close(); + try (SlideShowExtractor ppe = openExtractor("basic_test_ppt_file.ppt")) { + ppe.setNotesByDefault(true); + ppe.setSlidesByDefault(false); + ppe.setMasterByDefault(false); + String notesText = ppe.getText(); + String expText = "\nThese are the notes for page 1\n\nThese are the notes on page two, again lacking formatting\n"; + assertEquals(expText, notesText); + } // Other one doesn't have notes - PowerPointExtractor ppe2 = openExtractor("with_textbox.ppt"); - notesText = ppe2.getNotes(); - expText = ""; - assertEquals(expText, notesText); - ppe2.close(); + try (SlideShowExtractor ppe = openExtractor("with_textbox.ppt")) { + ppe.setNotesByDefault(true); + ppe.setSlidesByDefault(false); + ppe.setMasterByDefault(false); + String notesText = ppe.getText(); + String expText = ""; + assertEquals(expText, notesText); + } } @Test @@ -126,19 +133,19 @@ public final class TestExtractor { "\nThese are the notes on page two, again lacking formatting\n" }; - PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt"); - ppe.setSlidesByDefault(true); - ppe.setNotesByDefault(false); - assertEquals(slText[0] + slText[1], ppe.getText()); + try (SlideShowExtractor ppe = openExtractor("basic_test_ppt_file.ppt")) { + ppe.setSlidesByDefault(true); + ppe.setNotesByDefault(false); + assertEquals(slText[0] + slText[1], ppe.getText()); - ppe.setSlidesByDefault(false); - ppe.setNotesByDefault(true); - assertEquals(ntText[0] + ntText[1], ppe.getText()); + ppe.setSlidesByDefault(false); + ppe.setNotesByDefault(true); + assertEquals(ntText[0] + ntText[1], ppe.getText()); - ppe.setSlidesByDefault(true); - ppe.setNotesByDefault(true); - assertEquals(slText[0] + ntText[0] + slText[1] + ntText[1], ppe.getText()); - ppe.close(); + ppe.setSlidesByDefault(true); + ppe.setNotesByDefault(true); + assertEquals(slText[0] + ntText[0] + slText[1] + ntText[1], ppe.getText()); + } } /** @@ -149,45 +156,46 @@ public final class TestExtractor { */ @Test public void testMissingCoreRecords() throws IOException { - PowerPointExtractor ppe = openExtractor("missing_core_records.ppt"); - - String text = ppe.getText(true, false); - String nText = ppe.getNotes(); - - assertNotNull(text); - assertNotNull(nText); - - // Notes record were corrupt, so don't expect any - assertEquals(nText.length(), 0); - - // Slide records were fine - assertContains(text, "Using Disease Surveillance and Response"); - - ppe.close(); + try (SlideShowExtractor ppe = openExtractor("missing_core_records.ppt")) { + ppe.setSlidesByDefault(true); + ppe.setNotesByDefault(false); + String text = ppe.getText(); + ppe.setSlidesByDefault(false); + ppe.setNotesByDefault(true); + String nText = ppe.getText(); + + assertNotNull(text); + assertNotNull(nText); + + // Notes record were corrupt, so don't expect any + assertEquals(nText.length(), 0); + + // Slide records were fine + assertContains(text, "Using Disease Surveillance and Response"); + } } @Test public void testExtractFromEmbeded() throws IOException { - InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("excel_with_embeded.xls"); - POIFSFileSystem fs = new POIFSFileSystem(is); - DirectoryNode root = fs.getRoot(); - PowerPointExtractor ppe1 = assertExtractFromEmbedded(root, "MBD0000A3B6", "Sample PowerPoint file\nThis is the 1st file\nNot much too it\n"); - PowerPointExtractor ppe2 = assertExtractFromEmbedded(root, "MBD0000A3B3", "Sample PowerPoint file\nThis is the 2nd file\nNot much too it either\n"); - ppe2.close(); - ppe1.close(); - fs.close(); - } - - private PowerPointExtractor assertExtractFromEmbedded(DirectoryNode root, String entryName, String expected) - throws IOException { - DirectoryNode dir = (DirectoryNode)root.getEntry(entryName); - assertTrue(dir.hasEntry(HSLFSlideShow.POWERPOINT_DOCUMENT)); - - // Check the first file - HSLFSlideShowImpl ppt = new HSLFSlideShowImpl(dir); - PowerPointExtractor ppe = new PowerPointExtractor(ppt); - assertEquals(expected, ppe.getText(true, false)); - return ppe; + try (final InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("excel_with_embeded.xls"); + final POIFSFileSystem fs = new POIFSFileSystem(is)) { + final DirectoryNode root = fs.getRoot(); + + final String[] TEST_SET = { + "MBD0000A3B6", "Sample PowerPoint file\nThis is the 1st file\nNot much too it\n", + "MBD0000A3B3", "Sample PowerPoint file\nThis is the 2nd file\nNot much too it either\n" + }; + + for (int i=0; i ppt = SlideShowFactory.create(dir); + final SlideShowExtractor ppe = new SlideShowExtractor(ppt)) { + assertEquals(TEST_SET[i+1], ppe.getText()); + } + } + } } /** @@ -195,32 +203,32 @@ public final class TestExtractor { */ @Test public void testExtractFromOwnEmbeded() throws IOException { - PowerPointExtractor ppe = openExtractor("ppt_with_embeded.ppt"); - List shapes = ppe.getOLEShapes(); - assertEquals("Expected 6 ole shapes", 6, shapes.size()); - int num_ppt = 0, num_doc = 0, num_xls = 0; - for (HSLFObjectShape ole : shapes) { - String name = ole.getInstanceName(); - InputStream data = ole.getObjectData().getInputStream(); - if ("Worksheet".equals(name)) { - HSSFWorkbook wb = new HSSFWorkbook(data); - num_xls++; - wb.close(); - } else if ("Document".equals(name)) { - HWPFDocument doc = new HWPFDocument(data); - num_doc++; - doc.close(); - } else if ("Presentation".equals(name)) { - num_ppt++; - HSLFSlideShow ppt = new HSLFSlideShow(data); - ppt.close(); + try (SlideShowExtractor ppe = openExtractor("ppt_with_embeded.ppt")) { + List shapes = ppe.getOLEShapes(); + assertEquals("Expected 6 ole shapes", 6, shapes.size()); + int num_ppt = 0, num_doc = 0, num_xls = 0; + for (ObjectShape ole : shapes) { + String name = ((HSLFObjectShape)ole).getInstanceName(); + InputStream data = ole.getObjectData().getInputStream(); + if ("Worksheet".equals(name)) { + HSSFWorkbook wb = new HSSFWorkbook(data); + num_xls++; + wb.close(); + } else if ("Document".equals(name)) { + HWPFDocument doc = new HWPFDocument(data); + num_doc++; + doc.close(); + } else if ("Presentation".equals(name)) { + num_ppt++; + HSLFSlideShow ppt = new HSLFSlideShow(data); + ppt.close(); + } + data.close(); } - data.close(); + assertEquals("Expected 2 embedded Word Documents", 2, num_doc); + assertEquals("Expected 2 embedded Excel Spreadsheets", 2, num_xls); + assertEquals("Expected 2 embedded PowerPoint Presentations", 2, num_ppt); } - assertEquals("Expected 2 embedded Word Documents", 2, num_doc); - assertEquals("Expected 2 embedded Excel Spreadsheets", 2, num_xls); - assertEquals("Expected 2 embedded PowerPoint Presentations", 2, num_ppt); - ppe.close(); } /** @@ -228,11 +236,11 @@ public final class TestExtractor { */ @Test public void test52991() throws IOException { - PowerPointExtractor ppe = openExtractor("badzip.ppt"); - for (HSLFObjectShape shape : ppe.getOLEShapes()) { - IOUtils.copy(shape.getObjectData().getInputStream(), new ByteArrayOutputStream()); + try (SlideShowExtractor ppe = openExtractor("badzip.ppt")) { + for (ObjectShape shape : ppe.getOLEShapes()) { + IOUtils.copy(shape.getObjectData().getInputStream(), new ByteArrayOutputStream()); + } } - ppe.close(); } /** @@ -240,27 +248,27 @@ public final class TestExtractor { */ @Test public void testWithComments() throws IOException { - PowerPointExtractor ppe1 = openExtractor("WithComments.ppt"); - String text = ppe1.getText(); - assertFalse("Comments not in by default", text.contains("This is a test comment")); + try (final SlideShowExtractor ppe = openExtractor("WithComments.ppt")) { + String text = ppe.getText(); + assertFalse("Comments not in by default", text.contains("This is a test comment")); - ppe1.setCommentsByDefault(true); + ppe.setCommentsByDefault(true); - text = ppe1.getText(); - assertContains(text, "This is a test comment"); - ppe1.close(); + text = ppe.getText(); + assertContains(text, "This is a test comment"); + } // And another file - PowerPointExtractor ppe2 = openExtractor("45543.ppt"); - text = ppe2.getText(); - assertFalse("Comments not in by default", text.contains("testdoc")); + try (SlideShowExtractor ppe = openExtractor("45543.ppt")) { + String text = ppe.getText(); + assertFalse("Comments not in by default", text.contains("testdoc")); - ppe2.setCommentsByDefault(true); + ppe.setCommentsByDefault(true); - text = ppe2.getText(); - assertContains(text, "testdoc"); - ppe2.close(); + text = ppe.getText(); + assertContains(text, "testdoc"); + } } /** @@ -268,48 +276,37 @@ public final class TestExtractor { */ @Test public void testHeaderFooter() throws IOException { - String text; - // With a header on the notes - InputStream is1 = slTests.openResourceAsStream("45537_Header.ppt"); - HSLFSlideShow ppt1 = new HSLFSlideShow(is1); - is1.close(); - assertNotNull(ppt1.getNotesHeadersFooters()); - assertEquals("testdoc test phrase", ppt1.getNotesHeadersFooters().getHeaderText()); - - PowerPointExtractor ppe1 = new PowerPointExtractor(ppt1.getSlideShowImpl()); + try (InputStream is = slTests.openResourceAsStream("45537_Header.ppt"); + HSLFSlideShow ppt = new HSLFSlideShow(is)) { - text = ppe1.getText(); - assertFalse("Header shouldn't be there by default\n" + text, text.contains("testdoc")); - assertFalse("Header shouldn't be there by default\n" + text, text.contains("test phrase")); + assertNotNull(ppt.getNotesHeadersFooters()); + assertEquals("testdoc test phrase", ppt.getNotesHeadersFooters().getHeaderText()); - ppe1.setNotesByDefault(true); - text = ppe1.getText(); - assertContains(text, "testdoc"); - assertContains(text, "test phrase"); - ppe1.close(); - ppt1.close(); + testHeaderFooterInner(ppt); + } // And with a footer, also on notes - InputStream is2 = slTests.openResourceAsStream("45537_Footer.ppt"); - HSLFSlideShow ppt2 = new HSLFSlideShow(is2); - is2.close(); - - assertNotNull(ppt2.getNotesHeadersFooters()); - assertEquals("testdoc test phrase", ppt2.getNotesHeadersFooters().getFooterText()); - ppt2.close(); - - PowerPointExtractor ppe2 = openExtractor("45537_Footer.ppt"); - - text = ppe2.getText(); - assertFalse("Header shouldn't be there by default\n" + text, text.contains("testdoc")); - assertFalse("Header shouldn't be there by default\n" + text, text.contains("test phrase")); - - ppe2.setNotesByDefault(true); - text = ppe2.getText(); - assertContains(text, "testdoc"); - assertContains(text, "test phrase"); - ppe2.close(); + try (final InputStream is = slTests.openResourceAsStream("45537_Footer.ppt"); + final HSLFSlideShow ppt = new HSLFSlideShow(is)) { + assertNotNull(ppt.getNotesHeadersFooters()); + assertEquals("testdoc test phrase", ppt.getNotesHeadersFooters().getFooterText()); + + testHeaderFooterInner(ppt); + } + } + + private void testHeaderFooterInner(final HSLFSlideShow ppt) throws IOException { + try (final SlideShowExtractor ppe = new SlideShowExtractor(ppt)) { + String text = ppe.getText(); + assertFalse("Header shouldn't be there by default\n" + text, text.contains("testdoc")); + assertFalse("Header shouldn't be there by default\n" + text, text.contains("test phrase")); + + ppe.setNotesByDefault(true); + text = ppe.getText(); + assertContains(text, "testdoc"); + assertContains(text, "test phrase"); + } } @SuppressWarnings("unused") @@ -318,41 +315,40 @@ public final class TestExtractor { String masterTitleText = "This is the Master Title"; String masterRandomText = "This text comes from the Master Slide"; String masterFooterText = "Footer from the master slide"; - PowerPointExtractor ppe = openExtractor("WithMaster.ppt"); - ppe.setMasterByDefault(true); + try (final SlideShowExtractor ppe = openExtractor("WithMaster.ppt")) { + ppe.setMasterByDefault(true); - String text = ppe.getText(); - assertContains(text, masterRandomText); - assertContains(text, masterFooterText); - ppe.close(); + String text = ppe.getText(); + assertContains(text, masterRandomText); + assertContains(text, masterFooterText); + } } @Test public void testMasterText() throws IOException { - PowerPointExtractor ppe1 = openExtractor("master_text.ppt"); - - // Initially not there - String text = ppe1.getText(); - assertFalse(text.contains("Text that I added to the master slide")); - - // Enable, shows up - ppe1.setMasterByDefault(true); - text = ppe1.getText(); - assertContains(text, "Text that I added to the master slide"); - - // Make sure placeholder text does not come out - assertNotContained(text, "Click to edit Master"); - ppe1.close(); + try (final SlideShowExtractor ppe = openExtractor("master_text.ppt")) { + // Initially not there + String text = ppe.getText(); + assertFalse(text.contains("Text that I added to the master slide")); + + // Enable, shows up + ppe.setMasterByDefault(true); + text = ppe.getText(); + assertContains(text, "Text that I added to the master slide"); + + // Make sure placeholder text does not come out + assertNotContained(text, "Click to edit Master"); + } // Now with another file only containing master text // Will always show up - PowerPointExtractor ppe2 = openExtractor("WithMaster.ppt"); - String masterText = "Footer from the master slide"; + try (final SlideShowExtractor ppe = openExtractor("WithMaster.ppt")) { + String masterText = "Footer from the master slide"; - text = ppe2.getText(); - assertContainsIgnoreCase(text, "master"); - assertContains(text, masterText); - ppe2.close(); + String text = ppe.getText(); + assertContainsIgnoreCase(text, "master"); + assertContains(text, masterText); + } } /** @@ -360,22 +356,21 @@ public final class TestExtractor { */ @Test public void testChineseText() throws IOException { - PowerPointExtractor ppe = openExtractor("54880_chinese.ppt"); - - String text = ppe.getText(); + try (final SlideShowExtractor ppe = openExtractor("54880_chinese.ppt")) { + String text = ppe.getText(); - // Check for the english text line - assertContains(text, "Single byte"); + // Check for the english text line + assertContains(text, "Single byte"); - // Check for the english text in the mixed line - assertContains(text, "Mix"); + // Check for the english text in the mixed line + assertContains(text, "Mix"); - // Check for the chinese text in the mixed line - assertContains(text, "\u8868"); + // Check for the chinese text in the mixed line + assertContains(text, "\u8868"); - // Check for the chinese only text line - assertContains(text, "\uff8a\uff9d\uff76\uff78"); - ppe.close(); + // Check for the chinese only text line + assertContains(text, "\uff8a\uff9d\uff76\uff78"); + } } /** @@ -387,67 +382,59 @@ public final class TestExtractor { public void testDifferentPOIFS() throws IOException { // Open the two filesystems File pptFile = slTests.getFile("basic_test_ppt_file.ppt"); - InputStream is1 = new FileInputStream(pptFile); - OPOIFSFileSystem opoifs = new OPOIFSFileSystem(is1); - is1.close(); - NPOIFSFileSystem npoifs = new NPOIFSFileSystem(pptFile); - - DirectoryNode[] files = { opoifs.getRoot(), npoifs.getRoot() }; - - // Open directly - for (DirectoryNode dir : files) { - PowerPointExtractor extractor = new PowerPointExtractor(dir); - assertEquals(expectText, extractor.getText()); - } + try (final InputStream is1 = new FileInputStream(pptFile); + final NPOIFSFileSystem npoifs = new NPOIFSFileSystem(pptFile)) { - // Open via a HSLFSlideShow - for (DirectoryNode dir : files) { - HSLFSlideShowImpl slideshow = new HSLFSlideShowImpl(dir); - PowerPointExtractor extractor = new PowerPointExtractor(slideshow); - assertEquals(expectText, extractor.getText()); - extractor.close(); - slideshow.close(); - } + final OPOIFSFileSystem opoifs = new OPOIFSFileSystem(is1); - npoifs.close(); + DirectoryNode[] files = {opoifs.getRoot(), npoifs.getRoot()}; + + // Open directly + for (DirectoryNode dir : files) { + try (SlideShow ppt = SlideShowFactory.create(dir); + SlideShowExtractor extractor = new SlideShowExtractor(ppt)) { + assertEquals(expectText, extractor.getText()); + } + } + } } @Test public void testTable() throws Exception { - PowerPointExtractor ppe1 = openExtractor("54111.ppt"); - String text1 = ppe1.getText(); - String target1 = "TH Cell 1\tTH Cell 2\tTH Cell 3\tTH Cell 4\n"+ - "Row 1, Cell 1\tRow 1, Cell 2\tRow 1, Cell 3\tRow 1, Cell 4\n"+ - "Row 2, Cell 1\tRow 2, Cell 2\tRow 2, Cell 3\tRow 2, Cell 4\n"+ - "Row 3, Cell 1\tRow 3, Cell 2\tRow 3, Cell 3\tRow 3, Cell 4\n"+ - "Row 4, Cell 1\tRow 4, Cell 2\tRow 4, Cell 3\tRow 4, Cell 4\n"+ - "Row 5, Cell 1\tRow 5, Cell 2\tRow 5, Cell 3\tRow 5, Cell 4\n"; - assertContains(text1, target1); - ppe1.close(); - - PowerPointExtractor ppe2 = openExtractor("54722.ppt"); - String text2 = ppe2.getText(); - - String target2 = "this\tText\tis\twithin\ta\n" + - "table\t1\t2\t3\t4"; - assertContains(text2, target2); - ppe2.close(); + try (SlideShowExtractor ppe = openExtractor("54111.ppt")) { + String text = ppe.getText(); + String target = "TH Cell 1\tTH Cell 2\tTH Cell 3\tTH Cell 4\n" + + "Row 1, Cell 1\tRow 1, Cell 2\tRow 1, Cell 3\tRow 1, Cell 4\n" + + "Row 2, Cell 1\tRow 2, Cell 2\tRow 2, Cell 3\tRow 2, Cell 4\n" + + "Row 3, Cell 1\tRow 3, Cell 2\tRow 3, Cell 3\tRow 3, Cell 4\n" + + "Row 4, Cell 1\tRow 4, Cell 2\tRow 4, Cell 3\tRow 4, Cell 4\n" + + "Row 5, Cell 1\tRow 5, Cell 2\tRow 5, Cell 3\tRow 5, Cell 4\n"; + assertContains(text, target); + } + + try (SlideShowExtractor ppe = openExtractor("54722.ppt")) { + String text = ppe.getText(); + + String target = "this\tText\tis\twithin\ta\n" + + "table\t1\t2\t3\t4"; + assertContains(text, target); + } } // bug 60003 @Test public void testExtractMasterSlideFooterText() throws Exception { - PowerPointExtractor ppe = openExtractor("60003.ppt"); - ppe.setMasterByDefault(true); + try (SlideShowExtractor ppe = openExtractor("60003.ppt")) { + ppe.setMasterByDefault(true); - String text = ppe.getText(); - assertContains(text, "Prague"); - ppe.close(); + String text = ppe.getText(); + assertContains(text, "Prague"); + } } @Test public void testExtractGroupedShapeText() throws Exception { - try (final PowerPointExtractor ppe = openExtractor("bug62092.ppt")) { + try (final SlideShowExtractor ppe = openExtractor("bug62092.ppt")) { final String text = ppe.getText(); //this tests that we're ignoring text shapes at depth=0 diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java index df44216093..aee64bd6f9 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java @@ -73,6 +73,7 @@ import org.apache.poi.poifs.macros.VBAMacroReader; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawPaint; import org.apache.poi.sl.draw.DrawTextParagraph; +import org.apache.poi.sl.extractor.SlideShowExtractor; import org.apache.poi.sl.usermodel.ColorStyle; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; @@ -800,18 +801,18 @@ public final class TestBugs { String files[] = { "bug58718_008524.ppt","bug58718_008558.ppt","bug58718_349008.ppt","bug58718_008495.ppt", }; for (String f : files) { File sample = HSLFTestDataSamples.getSampleFile(f); - PowerPointExtractor ex = new PowerPointExtractor(sample.getAbsolutePath()); - assertNotNull(ex.getText()); - ex.close(); + try (SlideShowExtractor ex = new SlideShowExtractor(SlideShowFactory.create(sample))) { + assertNotNull(ex.getText()); + } } } @Test public void bug58733() throws IOException { File sample = HSLFTestDataSamples.getSampleFile("bug58733_671884.ppt"); - PowerPointExtractor ex = new PowerPointExtractor(sample.getAbsolutePath()); - assertNotNull(ex.getText()); - ex.close(); + try (SlideShowExtractor ex = new SlideShowExtractor(SlideShowFactory.create(sample))) { + assertNotNull(ex.getText()); + } } @Test diff --git a/test-data/slideshow/SampleShow.pptx b/test-data/slideshow/SampleShow.pptx index e5237f4c15dd7adf33bd79a7416f54c9ca6751ef..df3a26debdc27989bddd8257a278727974a75424 100644 GIT binary patch literal 42414 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�|HS84#irxjs3 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�K*)_mBB+7}Fn-Dj~1W_uTj@rv@sk>mBzWdkAE)<$6= z1|zCZoYK%u&S^UnOnW59;7pc;* z9f+c-ENi(K8|FCgGi&O%JONe$R=hc^VxAO3*1mI>6)b~M{bQP}MPhy#%Dd8;$}O&_ zGVfK=b6^AZtL8$ZlQLJY@$mpnmb^5UnUhz1uk1h@)N9uqOUz13USC402Qm|- zVN{GVvkLhTfT$5~~>K1dxE4*T4ZOdd@u(ki|r3Z~FZyL_ohePSi=A}dDLU`RI9$w+KG>w71u4GSwp ziIiG4lxrh+m4GhoH5-Y#YA9H_J_i4VXG|2`Zjl20Glz!|YlP%DS(Hz*l~VxnN}c(2-+koKfL(j0h_uEzjdb&IboY#I3M1y0e4>$(Hz<#DC40Ral)&I`4- zn^*zFnYoT~CGh=YmBpev`z#x*Le{aE(Uh;6J~>roUI!U<9a$_aj5;R|e~Dk5Z>%l6 zHfG=(3(*!s{6p7!p2%^^5PD%|Z11mfc5H|kNHIibNyWDL zs%|Fxiq0gHML79$RNyEL-3)R>H^nY*p*fgs*}ISTc)Cx)yHh;rIS%W**P^bzS~VPx z+%HTd1LXvm3apyG-CFNx^jhT-T2V~pz!DG>2;LaATw-0>OZGoim2b0`cf9QhqIinbirl8Gh~Fl}TAArv*;GT@6vx zf*`k+o)vV-;c?0Cwp`jqsrD0oVAB=4U1KZPj$^r*!%M4udr9n!sGfkA3qVwR_GAg& z9ypg&(;6*z=N;2DACJK4 zvW$c$d6#K!2r(9!9a>0S66>hEY7|gP+#Cz04Dy978>0cNz4wz*u+5DO@klYb0{*=C zu^kthh0OA4Vpe@CyZSc2`Ldm-Hz(POh0IphHj3Jk`A!B^y|~EqLPn5s{UWMbZT6nU z=(%!z8>(7MHkSD)jB@=b>Oxue{6Yp+b$MCsn^K@$k?&&5oiW0r($yPw525!%9vm&L znWw{c#Ra=jbkvgRz(kxYBGY=!D2(3ln50qbNG=u*IuI4Ly`60C`AS< z92h>PRqn>l7U)k-#Q9X>kSA5E*J}EXNy+JUm8F~y;u>%}2Dl|0(@T=y*osptOf2j@sw+M?^dE| z)8yAlVfdf3+cOvC*&O%ep>$3z8F%0zzx=KcVbcF(bpi$bx&;98qya)zp#D|0V@@Ddcu-i=hI})}2=QUBpJKB(nfaJ<@l3PVPy- zt_#Y&qlU9?A9tRlJSQSCU4LXvd-Ri>>dk{o97 zX-*qg%~OHF$ z$GvT!(vv%UgF!j9U7(0c$YP@%b(P^x=~4AG<)%j}lAIigp^Bha9zxe7(lq5=4Sp{Z zgf5DZJaM?0P=y(Yl33&E-a-<|}u&C*`gdE6?MXfQHAf|5En@<+%&^AMM~z zD~3*hP!$qDsOrNiE@extxc57FJDGg41E>-IEZk*BgGyBO`3h34)61DfJ0_R_e|SV1 z_U3_P)~z1)S_iYt9@OMG0l>~J&U_GtTEh9$FWi*4)ZMba5>68<2)yTUSl(GUJ4jMt zKQ16jSH6XyF$^&z4R(@x-*I1E{<%Xy(ZX&55 z66AI848!MgP^-@@FE^WGrwkZeAQJ7CK}8GP`He861ANJK&g^%9R)sCnlGSFuh)gfl z*nH5%xXa)>C7Iz>`Z$_fUh;-&M(Uc4<~plb6-j4AV9x91sl##qDn^wcpTiqJ87c0x z9+dMfq*+R4O>nWyg3xbA)?G?N(itGul?pM}&bOWZxT%InXMEQwjcx+DtO6=X-)4b` zM~E2`Q~WRY%UXurwOlOv)b=YO>2OX0PU>1yysFBNqvGq{DIH%4@@Q}_$Vdx+5@I=q z8A7&fasbawc`bubV{%rb`^*gU1-r6N9XaL*m{1{T}=H)vM@mI$#@w1RrzZ zsumtaGwZctdvwz$Vcrl8<4aqt+}>T+026mgSrQ^CW!J(TqN3}Dfv)ooLJp!^O7=Q7oKwcy&+GE+F`lk$XpLP=<9`) zQvOY`!o8GwCy6UGsKfg0WAA%AUUC}UO;g&^L3t?pRj-qC(cYp5hf<9*nSRRVGMA@1 zb`8l^+CIQl_y5a|<3A0)r*_Crxq0z#fDk7@B`*Hv%km3y+2R>~Uyy={SUHDlpfyo{ zUmcZ;cg$|P7H5|?#5n9&_C`DzX!+8}OZr0ttf``~uyMt{D#5*u%n0oBguvTXrNIq} z{z08PQzpOx(S_0Hu%sCsM6kKTX*fPs6)0u3B07u+FjD9u3AWzF!t-38*DB|_Gs zVu~_u2J(ty8Zt+lO=GOIH*nKdkM`yaUH8t#fQRMr|VN+{BH_$;DyB1bk`6@@d35;d3f|9ocR$jltfgB02JK7|z+?pQF zJjYvRMRBm8rH$GFmpY=6`yBP7-OW2-m4##uO{z~o)`W)olcbjvDGQ!M?cw}0UvwDP zliFQMOOaTHUEkiPE~fAgvxcPu?}EIS@oPXmmD}5(}4&o~PC4TxviI=XJuM#dfIf|t1 zq$vNn5z?)8v*!W@CZs363Tw}GU7m|~UiTtG9QHub*o>o!md`><)QoI@b?>9pB@Ry} zqUxez5Lg@52Z%<{_m^ieg|9xk2h6xV-cS)tFdq`<`G>|u(^RwHEeP&F71nTLa-vrH zcNDk9V3u6hldOs{^tB@_EmV7G3Nq&wFvg}_svk2?=f#Il=^@_EXYB|<=FLvpw>kzA zPBC_=f-_EoD%ymUdBOy08njFwcOp@o0kb`Nw6V>t+0jgMN|(&%)Es+5Zq*VS9k%!! z*O|UJd@Qt9PwF`#O?Y^ESa(rgmm32Hap3>QLHx6W_m@FD`2S`QYh`0e3Z}l#T5P|( zJ~aas4$NE@AgOu>eYtw2D^CL?EGav-(MT`D!r76I{@WGAhy%Z60J069@ zE--mC{?0qYOvJSXz3Y=mnn8fjQk>$xE(7S}GB9F2<3bkr%P<#jop)1K3sy!|nq`ns zPBlx}999xeHigIvdm}l9NtQ1&ZESe`%|4{pamFicADrt;eKY$4ouzW+G9E+ZxrGz3 z8ufA_AhnITzzZ*r%il=qh%za2$HsBg+0H6OH zD*jIaEL%f8Gd-H8uYUy=l*Gcu1<(Qe2Yy7qh7Cn>=`HEW5M%NLfht^h*J-6Kv5&74 z#z;R)TniZ*LoSysM-$_`mu?&cCQWlTZ7MP;9ndl|jVsjEtJ~Wy&+mneIDF;Q!hIKN zbKU11pH4_Xv?0`QO+7`)hnNP=0HHFK;(o+C&MUn_)EZ2hr6=ZJRbu+_Zd9)^n*b3B z>@470I~9dA`9g)+P6tygnv*bM9rHRt7i26!6{u&Jr^Ud&b8(GUcRUHIuqCvq1>C0z z(!8|ts+!z^2Iyu);!2pbfcM;Sc{1xWn(MmU)&%ULp9u$)QF|b+tkO6nQMDDx>qaY# zue&R1Y3At(a^y#hZJX|7#$v$Q@>0hGENKaCoewv&7cd^3+dg7UAK0_28FbNrYc7F{ zq!1U)o3I^_Nx;aeQlB19G$yKan%rTDTn*Yc9GJ;}4`E$YYiBmWIuvj6a@caTPS-Spit#-7Tpgi=zjzO9S;^iCt*~Ox$k<9>IWu<8uXcW3njm{6_ zg-^1pSxW)cSeXe z04aGCeMAjKZlsnZUJJBDtl)Jc#b>B^4~6ba3P@Z5?V>zjN5VjgR?Z=%B&bX==C?}q zXk9{vsMK9-hd^tDQTs!@t_^F*V+o0h_5{*WHfcOPR)UBblJ;R?ujs-TGOJdnId`F` zuO-u6kgMq!>$4=l`$TO+y5Qpign0ZNI+|{7Zq=-1`FZ6u^|gf*J3|zX6<22R z=t2`h8w2EYrB5sg?gBfqy#zn`SWUqAN0TJZYfDRAzO;q2nYo-HDk1RAAVfML*;M^- zcp^pGZe=`?KoSvp&lifvPdB&Kmj_lVFIeNpvaG9DcO;!LBtP$9h@OB%)Ig7_w^zcj z5>JB5b~1jvMdi$ud$8f~Hny@;a7YUjX* z^@XLFQK&)eW(xV>R3pTshvfxi>uerc2EGn0AtH{Y5XOZm z*QRL(qok19s2cHbTdPAzt3_76{2F9pmoL~#(rW2CBYlM~s3%r)6@Ij0ZFjo&G*ccOmlLegLWYidgmePGY+4{_03lyVkV>Nllg(2hQLjGfCs%Y; zkdX)1H}dIEotV(pXg%FafV~!$8CFlf#Y!Aw`5@DHIUP-Dqh)FacAXB{1>`9en@JS} zg<`eMYs7b->d}TD_re+fO^_(@*9zw0gpzf`#ahVM{83PZcJJO3y|HCOf#AK6GukqV zXSKV(X48|sNJyxEOXUu}?qB9E--MNLj~bY>kY&pkssf#8R#3t-F)v7wDloOI^N!*v7%Xm*eXm4>YYz`R~_sy+WmzeMH=BZg4?z^*5(+s|Ff3!rs z>p>>)&o(mj)H>%&p;D*t3<_UwIX+1Y&W4~2ajAVvVxOnKnqyy>ys!?Ko8uwTpt=X` z1i$3DpvI2w675|L83Sc8w$FLQ0m5|=ufKnawqFlJu+{*SomKy3PtG6g{?l}S@Nc{S zZ@d3*yZ>*y|8Kkh|4X|MC|i5lPsQFy&*7&YrcAGR2Q7RP;W1q7(S#9XI-l4VJR%~Z zaOFCcA%^Ppnb>Z%mQk zBN3xyUoQ)w>%FT7^Yzngzexv8FipC+7irzwHu#c*>9I35uPG<>sVCk_qmxU${6eri z=g%PYR!YR|+MMgoU~y1ixh+X12-9@`9SAyx5XVq;H)rA_Tq2wY*tmF_5M~tXJVWBr z5W&#Gt_ea&&G&Twf@Pt3KA_PTZ@9HkG0crVN+=~-S}7;7Eyb$8t}LPou=$>Y7DBy( zUJyVgk&)t`4UI$$v%u3r+)aWWrWW><|Y3oHrcaqs=Hin{!C$ogc*{B2-*{_$aNgE^IsOeRvPPm}i{$GAVj z<_9HC?H+)rk_I4AE-3;Gf((QV^y<|spu?mn(W$m9RPOm`QeuSdZ@g&Ft-JW z7i-8mS?bxS{cuu$2hcvBwYdhIwFHO;0Fv;}4luw^{)*c@h1q{U(G@cv5g70*(SVXi zfN;&Tg9*S>*Fxv%8KTj$wEWYbUAI*diwE$24fx$358lti4zNJ^ud{V5to8nM`tu!v z(0-l^cqIymegD_#wuXS7WOFSeQ)**NJ%it`;Q2-u_`iVI12!J|KW;7bJKXaPBYxyC zejaCxe}Mbz*8Xv?jNg%;%liM&1^#*5GCf0<`Q!Ezzk@v&!T+%nz|W(U_3vQMSU$o3 zjNbl^`&@MVSCT_)f5&~g?kB{jwdL<%&n3ElC0WDycd*|{{)|rjj{97D`Nul<=P}9k z7w+#$%fBN$*B1WuGG%%GLimf#?>K)PL(f%we;oxiyg%vumofgG%X7`oUmxIv_kW%K zR21@0!oR=6J=e_q^#LyU@CUfRuJhSn_~&Qixp?5OM863B0r?r`AD@nYCJHI~2e`ix z{paW7xfIv0L@~tvfc%W;lknDm|BmWO{Q>T8M1QJz{f_)xHRo5Nw$gtH_sW1I^xPK0c*Sn{2jpi&>7FXOf5&^y^hag*&!f=h z4|soL`WNaSr?%&noxf6Tu>Aw{bE;4C$bX-zrsE&*{!aCuXQ$_7DZl>rTRJ_1{?AgB zC#uhiP=06nyb9yjX;j?f4{*;2^jzefB*mh1b`n|Z9C+^0Kg?20DuI50M-z+v34}F zcGOXHvo&(iqH(pd#LxQ#OqK)qxaog?|C13IRT+t2rbB2erg-0+W)1?)Fw_ZJ62vX$ zYBXoX%UJ{=iz7)Q9=v|QSb~NaUaV%Zqw%d~o;g0csvmIq+7goJp*WL5b7^ zF2z`cS<mgK} z0{p%^!RYAz;k$*<%A_>0)ddWY)-CvVvP%_X9Ex^D`If`Xe0(`1aqWNy5wjI$>WnUF z2;ilQ+l3#MpFzX4!;fBlLXT=irQq-y_#!6my_5pkOBp6O*uQRM`scjnmKpOQ`TO48 zBV$jv_rY*YJdOxSQ++yXyIFO8hJV^3l3EgIKAb^P51np!nWkHD0O5Kt&TsauYtxSb z(tEgPVhc3^N7@4t9^2-eeH4a+W@pInA_d;rU5`r7U%GcL{@f#)%*3 z&q5Wt7I+lQsv#%10=l7igQ#JfpPK!JJ>qfYlebxgwY$F241WoOEl2R(&eBEYA(nbu zmCW0Ri+Fzr0m%M?hcW%-mk0Qdmweaa zM5{}vWF=VtHn{#L?QVzX>SoY5jP`DNS9#kUn2h~v{Z}C(dyL_LC^cl1Y!sN$E1BVP zaQoCUgrO7(w0J=py<;Px*I@1;C9RFYTLqBxu%bmwP&124zWNYVmUmGN4t=IUGAFV6 z(2`h^#y#r1zF8znI)2_{M;>RzAk1B^$>TOYuG7^BQO@xk&$-eCu~!?ukxr1H%DVLP zfOYn3E(8BeXHUkyK?c%cqf^u861-NPF9>I8Z3GU>Op#X!Vlp&!j~_Vv^Qs}bu`O8m zm>K_AIrtx=t*s-igQc0Fkpu1D7SQh#hCkx-@1;~8*KOWQ2NQe|&>k@5rJOE^kuO7Q zAiHRGhTF9$p`X0iZ!dXs??Bt<*F*E;C}px3zJ5jLKwOio23osYgav;vLpKW#*)y9* zTbti@e*Ku-RlXXbx5LAG*=wr6L2Y|V7&T~Htvl7hQ_zrcx?C+-tV8N4);LR23Pi-p z`<8E!Weine%-6aP8N3W1#}azG{-^z;jq{<0$su>@jT8?rn;(zi_RcO9N~sKp5vaVf zRpqfVs!P{^ddBNK=bVK%GC8wVN06YK$&>xoaHxse7H*)x(a(a$tHrD?alMkN@^ugvn9{8XA zD^Ki}`H7D#{6P8!qGw}P7Tqt!iDke5jg)@}l6v1j(#Yq^w%nQGC}l*7v|8=AD(QOB zMN-bXvBP(-D@&#}NhMAWq*f*}SebJ%H#5ggV%lQ)D~A}_5x5}Z`or%)fizho-3t)e zDG{UAH!1Vd1v|kL4G5XVS{EuA@6Lgj1Ut+2BVK!TM zd|xiL5{8W_eZ%HoW~5Ht`vJ6m%G9h78=e7Y$8WkAc&hlVEdUrl#1ggqbT$~aYDdj#wh=%8fE&Ld*BZ3$mw+rt#Vnu zCVGN;ATB@Ea)h7KQV=Cnn`V69!bXuK`A!dGD11K5dEN_-Us5D$)Dvq~_EQ6?M5PN1Qp6xvltsvGI z!2@vlHSzfJI1qAmdZg%Z5h||~HK=w;XN()3M^0HYvNU2$FOgQv6pH9dL}y~6cxv`` zH&Q;P9tITzCS8a-1d+r)ey`q?c2TAu!~8v@NSNPXMPxpwgxtlOd<%YPVHJL0XR#_5 zadd8EVwQEqN@;VVh;6q1MOcWiiD$OMd`_w2HB#LNttlB9+s?~X={s7~WcXnzr_oN7s{MLlFqZ1**FZPV9Er*=$ zG_=}9b`wIT#}y$lTLr9KM_l)X^B3B7!%GGT!aMY!1#AaYGQ}lUhYpf8vOz+~Q$rN> zX?F+1JjUs@Wx)+{+o*7wHkSnpPos7`0g+G2lt^;zzCXQpci7wU@+BUI zsW<{W5Q9!q?lwT$!*`mlVW8I*7=r>%b`=P!HxZZ#n?Dg>un$PW-8bHpA#>_}!00<0 zpBebzWbX5Ttr%X*$8+5GfW8?+wgM}E7!;3{w})_XmCLszpEtC>vdUu=+?wbQs|*JN z08swJD*q!`{yj|oR9dl~r9+=1NOtnRA)xAIdZ8m2)8SQR&n%eovE z(v38!`)jaL{AkZ^(oF`Ys}i!fKLpA@^0k~F+}M;Q0efYpYH50m`*L=1pmwd;f2RP zb(fq#%tKBHbru)Ps1T`cXKs4IuDZ(wBh4Z#*f_gKUdb_?{oKCuqg)YskzZdUL?ykB z*AUFYmuEo*rh$*!(6uL9QuzXok;q|N{P|}vJZ!G3TWs9HK|?e?ZzUulxrt<`ND32* zmujU6fJB3aZnOA#5pBd{F^m#bJ_rH5EJ=^{U?=Z9IuDm)M4w$1T~U!!LEbm3GeI>) zKlTdsEvEGX!#vW~%>ahpwU}De7L|#2vPq>f;R2pEZuvr|6V(5>zvB~(q0#V>{L1SF zH@WxcxqH&<7AqE+YgKcq#v5Cb1?G`;uqZIN-SbMu%XB5L$S9WW*0lwU@0fzbxSArAllQPaqSMlINp7`*UVy` zJL-Hkh>X}8rHy(-Z%xW%fF4g{89LP zw1{{=^0StvWN28Uj4N*W)f6rxZa$k#LugRcQT&?OzCWcj0qRc7sfYvJ81AAi#^aUJ z#C04ZTM5L5(k5!<8u9;b1hTctVRl$q+W}Nip<`4SvJtp^Xvu}?HH0Y}{P6ge=&7M3 zF^EMuAH}rj=UM_(axF`V2FBz9m43s3AT{i*Je`;t){eGR9qV>SVu@{8+3x7VwkzQN5z1GYB&&*qB0kI0K-v zi@K^Xu5!qW>F$< zZ#I5j$wrwpK#T+pt8D>kc%Uu*!Qw6Zs*}s*dGMjtlk5c$37o!zClzJ-g$xLu7C!gdIAvGJgAh1%UU)jIHlpz)A^(L=_HKjh~#0ig-)7c;*xtADe`Tf(wB&h^bj-30nZ!~t) zE*V46xga5!bQkYm8sFSh{8h)t4C@Ccr24b>l+tsvadP|z!ST0CtpAry2F7$l_0S;* zUHHEv+*?n(Bg7SH3tess--09?u7Ir&8|EKwWEg;U@0`-vIH$i_VD=>-{@7%mtp(;^ z|8(M6wdKrR*@(}U-^2#?6?6S(wM|OIz5vla;&%6Mz0n)%uu9%%oCEoX-eA)bL)s@) zs3J_~yf2tnYLGJ<_q~rGmr)kvSi<~yha-98<9x_ra)>Z$K$z{LNQRqL$Y@`rSe@!V zj9HMm;YMSwwhlhWa%^G!{PCv$%xxs9oY-eS-oNu>mf}Bfu(okDa!~lQ?)v+cKk1Cv ze-K~Oz36|4uSr4LLJxo*Wtf>h)(F1abtR$SzwY#&Cm2&Yg%!uDDM=WDB#3zn8?sL6s})Pt$OJ2*QZgrhu5)opeZj*!U34ZK4c6YsT$^_FrrD1;%l()+6#|8cs#dJhWpYKE?#HMgY3H7o= z^f?=s9`Y*0D7Uso?G8gF_m#PKY~>m6FTpv@lYAQb@ZQWH&i#)&{vgGFHxq|H9sh=< zTuIAjnGV@2yZ&AGF)O%=9+_FOuo|u;|9d@e7aJ>ax`9Wd-lW;P*8-fGnWQ9T6u7PB zhQkH>!>X-&BRQyAKuu6Qq*WFZIna=j`k;}(g>@(MQ=;mC;c#Gl>?F0SJIk+^BgFxE z_UcbNg;8n(4hN3d!pR4+3?6w*Vq7DMMD-bB{qdCql95faK`tr4sjVJd3M zw&>&?6#T7FKYexKESlVMt)hxH0*YW&10korN?_u%@}QNxH$2S)ekEz@U~(@oB1IRU zq!9>*hszPFh4oCDx>Wx_#U;`16~3kNR3oZ!O-Qr19oF9!VSyBWY}e&ne}9@W(9`6i*+8{T^q(^F!6x0xVGof z^I5c~wPgjW%L~<@R~4HO2J>r95hUnRGq!v@YCot8f<`Me5X^zA2|jD{*wk(y20jhA zWn8vuQcy_a*8_oRD7FIE=nh>}p{*~SGe0Nd92s8m59#^z+u^f)_2>AbSc%m>-Eb#I ztHGJwcrkoYNjOT~ytsTaza@r^&XzJ6t;t2?5)Li@0t zTIX5|9dBxfHu(EaHtMkB=SHSCX*Z^}C)d7+eQ?uD_`h-y|NSt+{%;vZ%<^;ya6dxG zz=t>Zhj1#b=iq2${}18xZc=-8fit&b1oTvjJOB{C z@~GzJIaAs2lEWa9MGJIg_=X?6{G!}J-?h8UIzlVO43p|zpq#Gkh# zcda63K%eR<@$}~K)}6@OFkF$**e(+r$#IgwBWqBK0BH)FCVQ2r812f!OAj{J_PPQy z`&I6Yx^48f{dQ#b=qND|$crF8N{w%Y}i8nX*Si8z}h)z7@ zxGn?!}dVa2dut>us}ZW1UX zrT%cZuCbjUbWvki+A?Wsy3ffDcZC{eZH2+~Ji!uAk>&fB{JIUB$G=Pl{`c+e?>PTE z(U$){4lMtc>+Zjg1M~ml@SEW?{Vxu`abWyk9Dd{Qck=!}+fTCnTiSy_+Z%fLhgzZj z$Tj`{tCipJvRrY+8j~Kut7_J#`b8#LgIv8vNIXZbAUP-2(|0G!Nae1gF?aI&`+7|Z z2@^qzgqNS%#9b!i{h=e#hg5M&QpPyTtS1NRbJ8v`68VrS^}&g36N=-3%CS?_Z0;Oy zzw`{{3%S|+ABPL6@(UkGr4a`tV@I=0L7=>X8sptw>PLv1PQT125gs+J1PIkiOlfs^ zeeIwL5APMaN&2*RLWuh5hV2I>NnKTNE9U59Kc+;N|!e4oMt#>k&Ea_t~3IKIO24Mj7*Wtpw~^A zAmNz5XE8^nbl+mq)isxd(6!q1x_V4|WZ95g14$s8xjAVkJdLrFe$o@v@9MjRQ*&#) zZvF0g=yHfSPWz$^_*RDuYG5{6-Rug?K1TL^evh5R&B$vNZo8lpq3aU&0(CH^3L@cW zitlmYNNL0`ImLOmZU3LS#W7h1PO^`gYR(ReyViD-1>X<{_^V;@nKt6{HpJvE<}67r z?9$*+j$Yw~ZC$FFI#fFuo(%fR!B-%V$sa|72=6AG$b@h&-UOd5r@uM7ug%xnZCFW2CWx6J|BdsB~&FmlX7IdTwe20Ro1Qzh=spklt~nr4;mE!`eUuG{B-UD_^Rt zdIWX(06R;!Pd*@3^`kg&_)HCC+ps9fRJwIkKq^p&;v-Xr>Le=>QLE2@S9}%%j=b{5 zj^|*CrAEk~Ps%gw55HUI@7F%nC9y5y8@uF^Oh9n}FdzTD1g-q0Yr8ARu8YRdv_xdlh|RN)Q@~3LNxkX?JrU`=Z^>zwP;pc1g*Z89lIThoFs5jqbP(g2vkqPr|riEWu6~E zT;ALZ{O;^uuGU9>3S+n8Dh-vs9V+(bnBms)IBn2fK9P!2xVtk`=iB{mYT;aJ!tS-H$BCkvciJU&WpYs z=Bg6_pLwdufhi|OI&j<#Z4+UBEG;KDnNwL1O?`$GMs@aqqjk>P@f@VpEV)dk( zM}HdR_4Kiz|2g@XLMK7y^U>XqkMKvw>YsXH{$i>B=5V8`E7m(K2wt?4KKQdHRZew= z&~~Ve1`Y5_#HuE|zHx`Ds>kx-A4?tdzG-=4iS`KNqhewi-nxW87(t!TyOqXhG8i|p zZsRb-A~2_>7D;`OWkoMXY?O}jU~n>HwTh))KHnzRLIq9V=$zNMp8fu-R}E#3p?&v< zeFcUmveVCjd5IH>`;jm~40TRUWr6rV^l$1)QRkuRbUp+-DGO zC?QQXZ8dALHKir{vp;{wRERE2F}iLs77k-0e<5w#HJ@S6PaX1V(1*QC*KBYeE7;1f ztAZ`YndC;wJ#mjVIlT1ZL|=*#gY2EWqaO^#9Dvs3#7>pw__|nNrzPypNj-xj=N@8X z7>zbm8YsbBu^Cr>CvZW09eEJt=o@P17Q+xP3lt+?^Aei-^iUH-U2~8n?%C4~NU2?G z)CzOty@jZC`y_D*^d(+_J3Q~f%+G!FP6{eIYWeW?QObD8q)3uF0qrRQ%0EoDc+X32l`UxKR4KE8}#Qj5EkfnPrj+Icjm+ z1-YWbM{7?|110?S#7Nj-V~$$GR8frNL-UzM`lKi;wvcyLM-T!WbpotJ1iDxx_--Bs z3ByFZsTR%*NFH+-N3FzbU?sc|e!z~|fCY>WLd_mAJ&+a`Uru}3{o)>2?awrNmRnEY z&CzHaf!j>(_h9wL+hsz>mKt~UW?%kw-GgV&)M}EdqFK}V!ONjSj>z&bFC<8>(riY`Wy*;{ zE8Ez5?wp2&7p6+m@pRVXoj!d6cmuk9ov9=B_NCi!IfJdvY$8MI;A-_son>>o*1~=hEAYCoOh|<00d=3{b1><`&^?fL4XRr@IjyUa!~d2WZo|+mW#oTHGV< zr8Xe3J3o~LEQi#sDxgCMSn6Pk7Ysm2>``~1r=t%|=11%6UwE(f!s{*3kC{!Vf9<{g zRv%E-u>YtJyk1B>}%t-hgi(W zviy2t@UG{k+7EE?N?aN!$kY)D9LArNpUaI1di1~TTCO+Y8po|Q#44teq}q!-&8Lf< zm|wnWRlyKTTVJNrA_K#af~+<%6b^UU8D_=-k1%Q@f^EptK$UMQjb;gLR{3hl4p=pC z$oM%J&)xL#+_5;y;rq*$VV8H4vm$o~#mVXJLx%$Hr$YlO3EX znL2=BwHorWmNLq#R`g1u!qn8dqEe84s(bIwmoW4Y4yQ*Yx2v^Upc!V1UkrjMHxi5* z14?+XcpnQu23F0Sd3#75?G3_(F%*PiPO*ZVK9Fyj4Pe_0cty6*Lo()-;$IAcBJak3 zN#|mQFvH~~==&^gA7uRp%SeEE0Rt?7I3qI^2NP4J>H6_n5u7~P0w@KirGqb3<$l=^ zwg(TYOPdPVNioPEiS=+t&vOCEF^y*X%9#T4BwKUO#ey3wVY{zkdr=~SC7PKe(;47Q zj}w1lzsX4?2c84jr1Ai~llSy}9KSwX0d7NS1BdTr)_IY6zPjWEBxEYCc;}y4ln-L* zVF`38W3m3C?G!_P8Q zzNhP#o8FO|DkRgn)3~`UNZdRAk<6Iy&3${X)0XtMm7j2ejK2pa6s(&Q+%gLpFnyWn zw)soBfVhj4%kaS=zWx_D#Ba6mj~t@Hsg4-B2CdQHjwIug(>(}!SYuEj4T;%;wVbX` z*=y!&{CI5qUIC3cvd9)%NM3M$uY>Vm+N#%TO6F;5tgu!T^+SNcXJsQNZN-zCyGN1+ zg9tImc0@bytA-nn`@=0 zBubZL^8uQZI$bV9;NNxv1L9GW<|@%r%Bxh$2n`;b8mZL^BaynNaUINv^;xvn|Gd_K2R=}+vL4&itv6j$oyv3oZ5 zSlX#{2=)+p7z;+9XC$d_nxAc3NOY7eC~D|+CnkyvsWCvm(%gV-+tSd^BfAD>;&Wcb zx5qZHYqi*E6`%E1J^(koLwPe-GVLi;F9k74x7rLO^~Lt)(~zMzP&M+=7U)kjAIAua zG*6OCV5hC66`Bj&3nI+^t|*02m@k?)!U@Rbdz>dJyEdUX{CdGfk#A=Chv%?n1xK*J~Fjz=37cMLaUjYkA z`uTQW`o+;RQwhdsoAX>?Gd4aPJd0g8N$~!JV9!Dpnh?Gg9orIbtA2*!z!H>jv!I+{ zWX9j&)mIcd!-3y9v6sJQAOQ%Oio(b^FV`WkbV876{y6Iw5H;sJo#j<79=bdCtS&kM z<>;UH?&#tu%4pG@6KZv?(Z8-L6LdTG{o^hviwpv4; zuism*0?zw8i^JngWEdMu+~Al)e4;XVRb+Qxph#HuD4SDm&unOn%Qcxj*i_94Yj~zW z6?N(|+w-4G%|Y*&QAawXP;clii8j6!J}7Xa%b48HW=&Z7aA*`jFW?nAyt|ZDU^y(7 zQb;7Do>b_5)iBDnPC+&~$L~@mJddE`^Npy|2!`d9n{MsDzEDiw#H=mP@t$gZX)6To zm=wREO~OF%sbN|$B0fc*!a;UhO|H6dog(3?*VO8~%;tbHRqDP|Gj}8?&xkk?KI6Kc z#?NwdM&N+v6vG+#R#al+VYW-H&-yJ|-~zn?(PIE=QVX+uqvP$Wx2eiUn?Tv=%JNAG zU0^5bSq!~M~`UAI3R)hNCYWX64p z9yVP>EwiA`@9~Pk60}IpKE6NDh#V(%p`J-6%kzD0q)n&x7d1IMHOyvQvKIs0LFU1$ z^N+}VNB&_nH5mof4C6k3TURRMvu6BGpbJoom8|Fu?O0;wxi|gn*Coevp<5?ZYBZ+3 zcyWG=E!#KyZ@Y#2$rruUw5E`jKhOK)WH!C0KqIc+4M}-D@q71vo! zh*r}phrmirNHc2us@-S@jZpU4n&xK2Umm-#EIa&#keBw7z`cos*7r*_Q&b`qU&kOH z6&b@-DsYLSlfrkD>mHL5EiLJZKCPsmkQNGx$YS`I|<2~)-_>75$_SYuzCWSj1w zH_hiHuavJvnyL&=PBcj~<#saTLdO|V11cdthhYs{;rG2zMN~=osw z45;im1n3;#lSEshWN8K_6ry;M@0P)9iTB;L<~Ry4IWOF0<&bB0HG1$=<|k5(IB{J1 znk>-`N5sDi)dl$l_^C~XLJQ-U)x}H7OA}6}fDb);au07?d@=xj2Xd%pTjZSx|60_& zUa;47s#XbovA@lqIm;COG+a>Av&06H6o5k<_QH-!2nn#Lllo>V7OWlbDjo;6YA%ps0ku}KbjQ)|2rpoG$wkGqMqjx=NElL=NVJZf?hV+PM(TkQ;NTsP;iVYe;PaIT2=80e4P zGi25GqzF}KfB9OCu!ezNlDHzUE9*gTkWl#y>&G#_u5;o%SI#)8=U?7c@45HNS(rwN zQk$@Gz<#AJMvKnk#s3~&KmaDaGvpsUWT`g!Ac@iX0O_r~(kI2Z$Og>@^liVIWy>rB zq7G)}*gXfy*;A)1RUjPS_QgpY$~B-C1|x>fZ+U?Md&Jp^qG656|Jy^kw4jMb%mf=; zSwhGB5Hy{ud7B0eDJzr0ueN7%(%JYf_Y8ZezRrY|d+v$HR(l?oPHJesf#UR}4P2MA zNmx!awWwDWV@+oUj*&q2v9WmBDDAMg5q8 zemsxGNLag^yy65h;Nl@0bYCMV*A}L91lL&V!fHyuy*ydkWBG-8uynhOp(CpvKRaBx zulr+c30HP2`WR=z=Q~N#_Sg89QLG-Y>U{-;DcxaU!&o1kE$Z8CE`yXc`xX#e4pq=u zeP_6n(#eo7J*hc>Nhtit4*H1H(}%khDMGKt0WtRCq-e1a;ntwrV@62+@pX=hzWYFx zi;wT^(w{6Tq(br?*N zu7_~c#hO)CB2d!7)^APW0pYv0P_HF^k2~8bs5=nAhR(bFz$Eqr6gE^sBs8bP+8Ode zDR-z0~tg7YJ%;r$F+p$F<4KnCjXl(2)? z7gaID%UgX>o_;`4a$e>P1ErKh6&FX%Wj9sV287hMfanJS$QSl3tEVegF;$)M!xIjG z=B;{b@kaS^5vYX3{PyO%fOUngJ8H^JK<80M1d5;Wr%ru!2?U_1Y(SFNu381=zLBWD zzj~}d^MIBk;0Gn?^bXhMESd+Fn;5BTv~4N?pg+s&I$7x6wI)L}PfS-g?)j&B@VL{4 zLXcPc!>Te!^QtSi*O#`wTtfq1By_8QPkjmgc;wDp7u4@P^CsfUG1zAUvOKIiyY-1* znW#z)FiATtfr@jncN~U4ndvr?b?gNF3Y8ba_oNRN2qmH1kqAyl!(EsCjtqHl({8{240T4 zd(wV5ED%QO9A-KhA|d{kLECdFsq;E>pYZ;dLH*A?2GoC*iTu;vyuT$U|5keVy+!Ze zwV3)}1cL70vYI+B=|&9mp(|@Y|0kOF7rE)*eHN#xYxc`*$Q>JLUf@v=%Y8oRm!CNJ zF4ybDizdNkEkAouYG{ii9FC9QHaO!If1M^;A`Ik!wrn1i*5Cb_?bvaXZ=lt_fs9-M ziN;PDlKebggFudyyua5w`P3GA))5r=(kmL205wO z*RPLAP(fKDXR1o5VKb0Sw&g8L<>om01cUX2Gx}PQo{6zS<|fbYD4V2fGuMIcwVQ6s zv~IWW5K1)X(=rUT?V|E)kT@G5R#?0{og1cf?Fy=8{FwSAP95M>w~6dmYbo{9681!D z-5?3c;F_9}AidOUS3E6n63nUM{=z;9tA${ZrSh#(Fy<19Qb3uGIX8^9Z;?7xOIo#K z8BAx$yE}rY;$X|Fd>*Ku)egkq7X0AXns9UZa7uhCy_zY^humP$RTuT zJ*4#$LlhkXEIKW91V04aesEgTvR#hQ+MJaD&$--_)@ zqOZ=PnY0>9TgWY@TF`H0m1feLT+AWeX#;)yS~G7mqCurhPkQg8rezj-lRlJ|wuEgc zjfWN0XPCK7JaI8Tm%o@W-hLh8^RJM30< zTVjHFPc(hGx?6;6-3jA%E=F3x`)YhOdIx?r`@Uk%af2+C4d0%gD2->f9&cJ`oo zzc&0flF%W&_|sZ>_%n!BkK`e66A<4T#aNudBet2d9Os#v9l}>GPKc~DoS)GKF*}P* z?ac>cPu4zM5bavCP)2T$KRB6HJAIxHnl3ip+WE?lIpjAWy7F6DC-GhreSXfJsk2(x zDgfIt%?YBcM)~4GJ$;hH-mf&e{mVo#N%cg|)JJ0m$j6si{#BXlcMD&+(%ByyEnj)f zE_B^G$+z;&!s8VoVV~<*rlxs(p-FQvGnQnMEz@TgmT`-})>?pAEjY;+T!BY=U0iV= z;O<=Yb?BRl7E*v_>s}c8e%>n?l;}v?T*>%}p;y4Pg`wv^1IZ&BL|b&R5p^O~bBe?s zS|Q@wtt3R+JlPtpsZt195z6tId9OMdTJ1Azw*8KAyeH^sc*~nEog<~Z{p$XR!L{(W z6!LcDqsBZBiqvL$xxwHx6r~L@iLuZNo?&;bssice^$KDNMvzHT{U$Bb-qSB3bm+}h)2uj% zVfQWNMFxm|sHVK#leGbE0*GOU+WGZqQrRXm`i~-(>Qw8Y&Ocyt{EMYw%Yvsi9ABbk z@&g`zmO_%mK8Y7RK-mFt!M+A-ySJZo!I*&PpdAKXT4~mq-W_hEn40%ZKer6R1^>cn zJ0lKDowo4OorMB6N`UCv0=j0@_mpmN_yMM)!ttTLE;O!gnT(CxW9s>sD4AOcB^1KS zp*N8MyXdo1lbNXT*OtP>k1tS5mqedWVT4^!daC0tHw3`4VpD_^>h*~WgLE+zHy?7% ziu|rw*-!)b&?6yeuBhxNE6lol%rbl3=+57~J3>SBY*E>6cJ22Ai|bk#Z?;>IM)xBO zw?GfXv0u8Z@)>hVN5V<>j46-o%dToFu2(0q-=`cTbAS_8Z4`Pc$-@x(VQ5znZ>N$q zM~Y~58ijh`v@BW;rUPD|m3HeD49A8e^G>Xs9wbt#;k7p1feu*iQ&1Fu?s7jx+Dnp_pQ zhts41o$8*0<_SMjgiWz}{^12jkG{6Y{ra(+_@6T<-%Y@%v_Dn~+JB|P<2Sn~ zS04MQoFH_#6kX)lO+yBiM57M$;s2` zA<+zW*S`MPIQN#n(9cq+C)6}7Kh!u&NDdPFT*F3*>UCm`yQ~Eb85V)Apd8!LK+w~Q z?)frF3Btca*`_BM_EDAwI@c&kG}>e%ln{^ZgQOG(+Cc1gpxUCJm18iObp2(GPWT2j{!EnBf z(37B(=8%4c>t~98jtk%Lg~uRL|O zgEt&>7;YWrZNjaXaAF^t<*t}%vAw0&I4h1b3CxH{pK2kY9LVCkfWi*&{$||9-l{^s zoLUu2-6Dkret`#-P2w@d?-I=c*^w3~Ni{SMFcshnk8*Ak*V2dVeFuq#l0uq!QU|Ud zBqMiESthoXBFlPrOzlQrKv9<& zxTHTcbS95OIwn+h_Ul;el*ffqC?_*UBT+#iF_xYr;{7<@y5J$1B(AM=q={s|I)Hpk z!|(`&fP;nzr*|;Yn4X_?IDqS+qkR@=P(3A@E&z{L76glOTOvm)#)q#~Ic zMsUKpm(gX~$|d9g$7Q8Kz(||VQ99%9=<1=kcz{>*P{}J#S%unFDTUE1Bku(>rHPU{ z%@!w!2lJ#5ktXL@7i`7^DM*i|KA`h23o4ne6NaXs0DvFXzm8VF8?8^(K0Y5Mg1q+e z`KTQEdGuR8U~_adIx;K|V~S09(*k4Ja+QK$jChG8AIe8;AMx|{3b3nMW*BIdAXo^y zr$e5YN_Ex?ooz9Vt2T(4f-0;F_11nQUf7=9k}98c_o(Pf6n_$+i0QzGJ7(J3hbG>4 z$Ar6ZvR_^ENAjj!amd{B3upp{?Z$PNXk5m9KbUqPUs%81q(c5m<7E!W zG3%$2y0!PxHu2)f4&mvPd^H+HA`rL7r3bo4i=d-K)=&|+n zKn)X{qxqe6gjWLnRSEzGKGweXH9YK2d*98|qt_h)_N=fC$#;dlhRH=)w&LHgD#}p9F7Bu=Td^7?Zy^eNpfM5y~SQ9f>WF>V~E0VU|x_VR+7n&qqMfe?Ebb2Mm-A=6tHw?mFlk znpBnvXi{Qh8*?i(x{#_4g9f);}gU6;ni3%&~%lNUh3dte2?sLAL#wk5#o62r>v@;zL(c zNOB4D7|LsY>J;RlGK#B>X{T9I8$W^{N6?u*t&l6C}=i=xVH5i>?vZ%?1%pGFa5-JUu+o-{gFk^pO$RpXchKMaI zuu?JQG!C(3OF?asP&bjZNXMbuf}JN+5kFBj)mq8p;95}Mr7+{vP~R=jt|h7wu8eQg z$aPvi&yWw+EFhWthA-S3!8d1B5K-yf(UZFw+l9gjHOd?d?Vjq@N?5DsV5M8?irGFw>~m0*urJ?p4D*lxDnPAPH1+DK0enx5AIx1 z!zn%y1%ORKUqXS=I0;q;@Kr*T>yD4NtcC)p&OInmDewKhIOd7?#~hCP@4?FD_t5V6 z$B+b@M+y{98@AiugqI&+itYXX{ig~S$8^r~?JQbVPBu+X8;^(E zK~)8tcI#+gUgK5c>EP4t)(aWuC@EUj__D-8(en(3nrFxNp+{926Ea&LDm6F*lM*{* zE6gMdi%f$i4boKTgvAoIFSrJ788ZTfsr{JRq3u zhP?6AOSn z0Gf_-XfUimOYeChxpjpyEBl&cff0?aEl=AW`o_|gr?fc@R$OKHATbn3#Ne6o`hR}4s^UMb+fOq zo7#mukWUvrEM<~tiWmr3vHaeK1kZp@{COPMWO|gE8ZExiMB35$OtD6*hmRQBQe9qT zon!gY1sJBDS!|Hq6W2UX&YeVP%$uxOVOKv> zEs{{7pon#|q1e7Lgx|>L?ltA?fl>mAR9fSTV>?{3#01>&6HS*d_QGmq_#SkJhY%M+ zuB!I?EuutvMhgq=GYvS1WiaTVMdmuSzPAsmJl4ia;ysM`|0EEYLs0L0%V$a8RiF!q zc#GJZfC_UDv5J#A{|^3{AfyH?jC1=7RUR%U1fn8qz|9)Id#gT{Pz2??R|8PyW+)=B-uVZX>fXd07X%sa6ePdw@M2!fjuVRtB_$}Pz^qwDY+`Y;bxBYmSHH( zE-qqx;Z{5Rl#Xxqd;mKtBS1eK|;LVW97P zu*0CuD;E!DPV{t{Tw#Ek#*qAVo@gFu0&c%hJ284)s02QaO2(Yy0CcFz;6SY@l88C^|FQSpQB8GS zzbHr(l-@&AkY1Hu1O@5Rdl!)sdMDHAi-cAT@NP1f;h_T7(cz9-s4l z-*fMH@BQw$XPhz4AMeUo8Na>u+&h_Tm$}wlYyQ?_Jod#ybznb*o^#J6!RN=m=3E4N z)h_zYi?+Onrgo35Nn+ye{x*X@=_KsMhbQr@&H~su)z2w*IV9%NeNA|~oN-M)eVd3h z);o*$NpOpem3=*{?^f^@Q~FU&yk++3O40qGLX}#)8DFRJ`$0jR1o+)bxi zHs}RtyzUb&JF!bFNVIA3g+w|2`vQ7`UU1l(uY&H&k zz!Ix#I*r1JXHlmsg?IAR0%0w!a(bphK*MX6=?}jnq)!CPcO}@9fRwb-DR|IT;vhrOc8Xwv=fAbjwbj%~3AwrMI)lzA33=L#mn zdfe9EmL=ybME91cUqcRkE;-x%)!J*dE5xjOT3atrxjxl7lfRp`UP05Uix^;QMXJQq zAHVb00$@v@rkV5$NMo5>Xk4EMJcpfpTaql~nw9I0%&}w&XHXmxDJg!ab#NuaZ$^6g zOyc=kyJ7|4u6#v$9zQC4Ui<=T?XfneOWamtQd6F(tY?3q=&+wf3=8KO?0s?5Rs~qj z7q5BhLIbNHnfF{L0mY&>`(R7>) zSQL*k)H14XFo)2#TQe~e#nJ*2?Oqc_6CeA>h0Zw)GOoQ@T(aYOv)T-mVa^{T3=UwC z+J4rBLy$)%QuTzwuJUVLVdThcGWZjfUrCWZpkY10qR(z#pJC?mS0E`#z)sb!fLkb9 z*kxXib~hkiATV9jyRrJ1%r`DM|BiL^?dt+D?SRs+$P#~lK*_ZjaVO@pr*SAj`lY8IK<|dwovRCW@;V zojUUqOK}Qa<(l|K*e2AHil6UOmfPMdHyrig%oDXq4YqK$hTiQvXJ-;ffT>;t9w|#6 zOP1eru{KuEB_+MAcBIIS3QT)7uC%csw}T=qe=x$%^(&D)Nw6mIPiM>EE+6G`y<1l* zzEquaPqd1F{lgvqN|7zz%9Txm@wIk)>yqbv-J3p=d<%|i+RAeWXFuGBQ@=ZnWx9=R zkJwZFYL~MtmU}O*{*B=M3otFWa`8gEgsbv5{?B@)DO#yZw>n1YyT>I7^}cwMN|t%u z$&C@Ee3w}4X_x9q#qf3e+2E@_k_IK)ikH^j$vdvsE@t23OZ8boO%>7~#J#P^7ik-% zCw+eX3HPp08~0G2I5m@NhJ8>ll#qh5juG&-MKG-IzQh`ndf%l(OE=SCisKhsxyKPl zdql4Z+G7;Q5*7Wj)?pW>1lLx43%Y9@*pSj|p#nB%U}W zP+Ilas4Wp z$B4IfE%Iw`Zr9`Xw4sg8>KnvLLj_Y<%3fpAgn|!J1{R~1j+oIM*`Ye~2r_mBqQ8tx*JU=8@ieS;EIUK<{zw)TX!O?Xq+~ZDP#7*d={mZ-;OuJUu^Lw` z8KO2Tb6*vc>(ssF!*Xo@8wLJOwBy>TRYrg}_v853DA~9`mh1OBD&E5o2$k|LCYFM; zrq|sPVaVTI`YrVJ|cw zVIs8{i7YFHTPF6YzwQNEr=z_%)B76C-T>zYSKna5+`|cYq&;L=EpyWDjeC8QXQm)a zZkQ{v=N*v|23MI9>o}?4cYeXQd7sss6kE#ih+4h>8AP zH)U+c?sA zER+JUsh7-uwSE3Ak1@fy-%MpsZn1;W@`L^GBd4w6Aho0_zIcv1t9L1wxPZZA`G=bK zme+lLft#2J3lZznJ}RHWo_$FRwj~_?{Lt&OuivcMzz{VH+N%wKO zUzf4qh+sFc8B3Um^UA|c*W1I>N7&cN&+VnVEzniS+0)+fX8vXkheAU|T?Gdh7Z>LR z_Jeb?groG=*8fua>m>h6C;02&n;sm>+c-VAIC!}1IJYQq@hEX``u~bp#wGas+<%nC ze&OE2!zUmlx=l=S2P@D>fpZHN5APN}9svP9)?VU~(K9dtIJvlac=^P{B_yS!Wt5&NtEj4}YZw?B8Jn1z zncKaxcW`t9I{Wzg`3HakgCZiMqGMv?;#1$JrDtS*_?T5tSX5k6T2@|BSKrXs)ZEhA z)(7n$7##ZgZFmYcJu^Euzpx0$k|1wxZSU;vA%C5mo}FKyeqa8j*I#=6NBUc{|C?Tv zSiNrH^BWPcGPm<6* z5QB3f|5ELbX8+$53;+M6*?%hbUwSR#+{MGiUOYTX90eQL8``H^x`; z)vBAXR(GrOHQi?#ldV_ahB-71na850?scBs^;@uAp0GB;Arq-#!=Qks!P_uzM~ly( z1FArLvwI3;HE3%4ISh5072UpJ3*wW@$Y&094?DKxEmF<{EBbx~6faxP>cU13WTR$r zso&;Kw`04Y$$16R1m&?~o8;!1`}tUf=m%^1vS#r4t)LqQ($EB&`#o zz>YFT<=Y?Hq&?~?0e+lTCO;F);SUW8_6JSvll|VukL0rLXICz^dG-H9{lKKFsbtF5 zs9tg4#ts=lVvK;cyW>Q6pY0lb_r3(xOak~f$s=!Y8j@oA$z$0X`ss7;p@MI4NMhCw z6iEDzZ-*#PiJO9J#cl9GCu^8@NLhWPf8+YD-_q(`8>fe=Wt!j;mrL{AAVrX`vY(gu zdh|5gwEUg0i9DPe9H8(W8_@Q-9}13$*w_wl81>$U0x}cj?}TU~90wHc05P0(b+9wp zadI0_M9<=Ed(omxMwWX`u`|8Q^9!%lUily2;mLvGBa& zx(tnnSUpd^elKT>7(e$%GNHw9aH1p;=b#RcE2R~ewtyIUR_CnMK{jg8i7IMUA_{(k z6C=?jieyXdqE=ojMiF~SBUK}Wgny!1ga-@PMirjRCiB(Q<;(andwz_T@lG=LkEZ7@ zkA`kImz`W`tU#HPdR|l-KovVJA6weXpelStXdbqF=c|#j&1am;b}C;N0hv1&>iRuo zK+Tj#7PGo=3Meznvp2E^(3*>JlL!?DP8OS-#>b-@k>qGvrE?$Tcm&u91$H1$WFwU4 z3bDNY)Wr+phR-F)(a7He(GLbl(gEgDFtjMmepbm^#42<(HXw{EvzN|0M5zM-$&B;6 zpReSBB!?~z#su5(10O9}Uk@N|aL(Ue=U@a-0YlazC==xVT6cdpafr~YcsCjPIZ_pR zgA;E}gv^WyV*y1#jd{yaDN4xMX834;K1`0I3Ix_leXL{wlY8n{y0UBhh;tcDcYaXE zYM%u4F`J2e`KyD=kEAZmPNl>ssq|0;g);Qs%wQZCS~O~lVjL6OYngWEg@w)f0bw0` zxz1@XVg!&e))dlVKm`{q`p zuXDnrLhhk{uR%mDTM>iB8`!;6Xhx(?-y%59wW{G-`vf|-at|TVSiLKewOR71a@Ra+ z?ET{(0SGhapwJ&py99>~shHr9=QlXv(K47h1s1U5ad;j;!ET&#zq@Yx;3NQPF{n`Q z7>^EskC3O_;IM>o`~cak55-!*o1?<+?XfX+u)q}-hSX6W9&4*Hi_QHtOrR6E@Sw#V zQu1}J&h1!1c^2TJUrB3~uSqIYx=MMc!c;)p(I!U$s43KEE8l7Y_%gXQ_>w1B8or)O zP9B#9>UeC{CuL$lsi`4iw5^fNr<+*I&>>_K<5x=~GF`Dz+tTvl%e+n1y0d=YWVwFF zqIdsXp++`vHpl#J5^v+>q%ugN@=7y=0-^mL@e7>LP2WX|et;zJkL_YLLxG{zk5HX! zED~-O12)tH%gqsTyl^We-C5s#Fyo#)@2qd9hYG?D6vqF4L$i}!?*=E*{|?km&dW<) z`np}=CHBdXIUYzM0=1u4UuTP@#46y0u$7}u)>omjtI2sJuJRlqj}g`Bz#^+3F7l!X z_tNz%7LUH5;L_L-X&RZ7IEZhObSaYcbxI(^tN9$yrkkpRX~QPEi+$qYEO^;O37HZh zr`C06`oW}t_@QGjMz#wP6WgKw8RRqAMcy?1q6vH3Z_RG!K`*`uNa}x>;Q}*E-{82{ zM08WQ$`gYU&UI09SMr$2Tngmep#q%zwe@49YTtMqD;=sc9|ROXCInRuWJTxEH6xY= zF50YBx`^opV$BGU?QWMK+VZ+UTqjDQKIJur3T=%_ zN9>y-QxY(bL&Ro6h=K6D+aXUfd*#WHHHmV8booUnJNPVQW8Pun><)I2n6Ik|sSqxn z6a?$NJkhM?S_p|*>pq-8Y!5AF9_C>A6Vb*f(!#LsdG+1N7@9>8^~Ppia)Y2Oyr~v3 zw9a-gj!@dr1a0@<;Lw~Y(659jOpCSWQJ{6EAq@)DP8UJkF(rt-9nbX6F6zCuXnxul z-`!cG7iBX&li!Vi8V^hFas@OQM*bq4E$G>n5+|h0)?jk!kW6`TJ;L?ClT&Uh|+JS?6jv$$HTMN2 zLq0nfYR&o{DydDAM(2B-Ki2L$@7o?}Z_MSny4)|<_AQk$vX$}U9o8+^F0$k%9%$sz zrqa+$Gk4ZY)e?i5NEzsxBvqX32Fh@i+Xi3fb7f^2=7JXr zx>ApLKc}pNTln~5rXtD;YyOx*L?_TjNFq2{ z(v=oUawsTgE>gUk2{Zz=Av@?WkbEO>78?8vagoyDw&vvFA@*}p+{)rfM`!rc%iqeL z_N}1;HH_(Yh(>-n6&(Zd%1A#e>E7wlmJ>lFRma|deu`{iu06S&kc#&`agK+Jq^*!NDJ-yF89Tyww-Q- zmP^ybd)-N)5U)z*^&F4&u{;mRs_C9JEllnqIJDI1GN~!^GEmuX^K{nYt{bzg2YFHk zdqKWQMv+LmmuakVzfs@VaEGIQ4uHY9(zr%jDoDMLvfrrxCBNaOehF(bYq6@h>cWYU z;f_H~?74TLo`9ByVfmG5urmb&pv5OvG3=eMohhpdPwu`_2%EeBlXXdA`s*>nxg5we z1v&+naVB)YB-mXrc!;h3z=oiKlzI@`_++U$q=wtXnRmU;qoY1A)Je9fKZ+IWj$tDe zbE974983w`sENuWlS_%0r$Vr7jwhjcFJ-SDg@_`%p{opGbBi^JVmb#2jxLnDmla^g>+%ER;l1Il?R@}$ zNtDftVjfsA-ROSp~1&XT(Ax5l_q!UE+*?u`i%+yNx#hmN* z3=s?@^-K)e&DtB}GNc^FIleb6bvAl9!|SCbGnLK9mk||+40DUE%IDDr#lju-cwi6` z>{D+;1$JCbVUzfUosPis@xe$foCyQ)}=%>Lo7EbIoOFriU zsNcxBb)7>aA4JwXND`Vyaf9PPK5y_8(H+;|rV3Arx=6&fM<0gGf|cLCe&a(?>4aF`iGvfMUz#|&NgOYu`n#2wZQ7=R#K+n4brtV#TE@)ZidsiL-QKC

pxmSUp}CYP+P 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 -- 2.39.5