From 4ba2d64a505ac2418d44518c4a20644f77919890 Mon Sep 17 00:00:00 2001
From: Andreas Beeker
Date: Wed, 18 Apr 2018 15:02:02 +0000
Subject: [PATCH] Bug 62092 - Text not extracted from grouped text shapes in
HSLF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1829453 13f79535-47bb-0310-9956-ffa450edef68
---
.../apache/poi/sl/draw/DrawTextParagraph.java | 4 +-
.../poi/sl/extractor/SlideShowExtractor.java | 307 +++++++++++++++
.../org/apache/poi/sl/usermodel/Comment.java | 85 +++++
.../poi/sl/usermodel/PlaceholderDetails.java | 69 ++++
.../org/apache/poi/sl/usermodel/Sheet.java | 12 +
.../apache/poi/sl/usermodel/SimpleShape.java | 18 +
.../org/apache/poi/sl/usermodel/Slide.java | 9 +-
.../apache/poi/sl/usermodel/SlideShow.java | 8 +
.../extractor/XSLFPowerPointExtractor.java | 348 +++++++++---------
.../poi/xslf/usermodel/XMLSlideShow.java | 7 +-
.../poi/xslf/usermodel/XSLFComment.java | 126 +++++++
.../poi/xslf/usermodel/XSLFComments.java | 25 +-
.../poi/xslf/usermodel/XSLFNotesMaster.java | 27 +-
.../usermodel/XSLFPlaceholderDetails.java | 203 ++++++++++
.../apache/poi/xslf/usermodel/XSLFShape.java | 57 ++-
.../apache/poi/xslf/usermodel/XSLFSheet.java | 57 ++-
.../poi/xslf/usermodel/XSLFSimpleShape.java | 11 -
.../apache/poi/xslf/usermodel/XSLFSlide.java | 49 ++-
.../poi/xslf/usermodel/XSLFSlideLayout.java | 17 +-
.../poi/xslf/usermodel/XSLFSlideMaster.java | 50 +--
.../poi/xslf/usermodel/XSLFTextParagraph.java | 262 +++++++------
.../poi/xslf/usermodel/XSLFTextRun.java | 76 +---
.../poi/xslf/usermodel/XSLFTextShape.java | 8 +-
.../poi/xslf/usermodel/TestXMLSlideShow.java | 26 +-
.../poi/xslf/usermodel/TestXSLFTextRun.java | 20 +-
.../poi/xslf/usermodel/TestXSLFTextShape.java | 22 +-
.../hslf/extractor/PowerPointExtractor.java | 271 +++-----------
.../org/apache/poi/hslf/model/Comment.java | 75 ----
.../apache/poi/hslf/model/HeadersFooters.java | 7 +
.../hslf/record/DocumentEncryptionAtom.java | 11 +-
.../record/HSLFEscherClientDataRecord.java | 14 +-
.../org/apache/poi/hslf/record/Record.java | 52 +--
.../apache/poi/hslf/record/RecordTypes.java | 217 ++++-------
.../poi/hslf/usermodel/HSLFComment.java | 104 ++++++
.../poi/hslf/usermodel/HSLFMasterSheet.java | 12 +-
.../apache/poi/hslf/usermodel/HSLFNotes.java | 27 ++
.../usermodel/HSLFPlaceholderDetails.java | 157 ++++++++
.../HSLFShapePlaceholderDetails.java | 240 ++++++++++++
.../apache/poi/hslf/usermodel/HSLFSheet.java | 18 +
.../poi/hslf/usermodel/HSLFSimpleShape.java | 153 +-------
.../apache/poi/hslf/usermodel/HSLFSlide.java | 113 +++---
.../poi/hslf/usermodel/HSLFSlideShow.java | 6 +
.../poi/hslf/usermodel/HSLFTextShape.java | 29 +-
.../poi/hslf/extractor/TestExtractor.java | 62 +++-
.../poi/hslf/record/TestRecordTypes.java | 15 +-
test-data/slideshow/bug62092.ppt | Bin 0 -> 161792 bytes
46 files changed, 2196 insertions(+), 1290 deletions(-)
create mode 100644 src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java
create mode 100644 src/java/org/apache/poi/sl/usermodel/Comment.java
create mode 100644 src/java/org/apache/poi/sl/usermodel/PlaceholderDetails.java
create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComment.java
create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
delete mode 100644 src/scratchpad/src/org/apache/poi/hslf/model/Comment.java
create mode 100644 src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFComment.java
create mode 100644 src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFPlaceholderDetails.java
create mode 100644 src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFShapePlaceholderDetails.java
create mode 100644 test-data/slideshow/bug62092.ppt
diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
index fbf6988c4..938d46230 100644
--- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
+++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
@@ -52,6 +52,7 @@ import org.apache.poi.sl.usermodel.TextRun;
import org.apache.poi.sl.usermodel.TextRun.FieldType;
import org.apache.poi.sl.usermodel.TextShape;
import org.apache.poi.sl.usermodel.TextShape.TextDirection;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -381,7 +382,8 @@ public class DrawTextParagraph implements Drawable {
return getRenderableText(tr);
}
- private String getRenderableText(final TextRun tr) {
+ @Internal
+ public String getRenderableText(final TextRun tr) {
final String txtSpace = tr.getRawText().replace("\t", tab2space(tr)).replace('\u000b', '\n');
final Locale loc = LocaleUtil.getUserLocale();
diff --git a/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java b/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java
new file mode 100644
index 000000000..44f0df19c
--- /dev/null
+++ b/src/java/org/apache/poi/sl/extractor/SlideShowExtractor.java
@@ -0,0 +1,307 @@
+package org.apache.poi.sl.extractor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.POITextExtractor;
+import org.apache.poi.sl.usermodel.Comment;
+import org.apache.poi.sl.usermodel.MasterSheet;
+import org.apache.poi.sl.usermodel.Notes;
+import org.apache.poi.sl.usermodel.ObjectShape;
+import org.apache.poi.sl.usermodel.Placeholder;
+import org.apache.poi.sl.usermodel.PlaceholderDetails;
+import org.apache.poi.sl.usermodel.Shape;
+import org.apache.poi.sl.usermodel.ShapeContainer;
+import org.apache.poi.sl.usermodel.Sheet;
+import org.apache.poi.sl.usermodel.Slide;
+import org.apache.poi.sl.usermodel.SlideShow;
+import org.apache.poi.sl.usermodel.TableCell;
+import org.apache.poi.sl.usermodel.TableShape;
+import org.apache.poi.sl.usermodel.TextParagraph;
+import org.apache.poi.sl.usermodel.TextRun;
+import org.apache.poi.sl.usermodel.TextShape;
+import org.apache.poi.util.LocaleUtil;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * Common SlideShow extractor
+ *
+ * @since POI 4.0.0
+ */
+public class SlideShowExtractor<
+ S extends Shape,
+ P extends TextParagraph
+> extends POITextExtractor {
+ private static final POILogger LOG = POILogFactory.getLogger(SlideShowExtractor.class);
+
+ private SlideShow slideshow;
+
+ private boolean slidesByDefault = true;
+ private boolean notesByDefault;
+ private boolean commentsByDefault;
+ private boolean masterByDefault;
+
+
+ public SlideShowExtractor(final SlideShow slideshow) {
+ setFilesystem(slideshow);
+ this.slideshow = slideshow;
+ }
+
+ /**
+ * Should a call to getText() return slide text? Default is yes
+ */
+ public void setSlidesByDefault(final boolean slidesByDefault) {
+ this.slidesByDefault = slidesByDefault;
+ }
+
+ /**
+ * Should a call to getText() return notes text? Default is no
+ */
+ public void setNotesByDefault(final boolean notesByDefault) {
+ this.notesByDefault = notesByDefault;
+ }
+
+ /**
+ * Should a call to getText() return comments text? Default is no
+ */
+ public void setCommentsByDefault(final boolean commentsByDefault) {
+ this.commentsByDefault = commentsByDefault;
+ }
+
+ /**
+ * Should a call to getText() return text from master? Default is no
+ */
+ public void setMasterByDefault(final boolean masterByDefault) {
+ this.masterByDefault = masterByDefault;
+ }
+
+ @Override
+ public POITextExtractor getMetadataTextExtractor() {
+ return slideshow.getMetadataTextExtractor();
+ }
+
+ /**
+ * Fetches all the slide text from the slideshow, but not the notes, unless
+ * you've called setSlidesByDefault() and setNotesByDefault() to change this
+ */
+ @Override
+ public String getText() {
+ final StringBuilder sb = new StringBuilder();
+
+ if (masterByDefault) {
+ for (final MasterSheet master : slideshow.getSlideMasters()) {
+ for (final Shape shape : master) {
+ if (shape instanceof TextShape) {
+ final TextShape ts = (TextShape)shape;
+ final String text = ts.getText();
+ if (text == null || text.isEmpty() || "*".equals(text)) {
+ continue;
+ }
+ if (ts.isPlaceholder()) {
+ // don't bother about boiler plate text on master sheets
+ LOG.log(POILogger.INFO, "Ignoring boiler plate (placeholder) text on slide master:", text);
+ continue;
+ }
+ sb.append(text);
+ if (!text.endsWith("\n")) {
+ sb.append("\n");
+ }
+
+ }
+ }
+ }
+ }
+
+ for (final Slide slide : slideshow.getSlides()) {
+ sb.append(getText(slide));
+ }
+
+ return sb.toString();
+ }
+
+ public String getText(final Slide slide) {
+ final StringBuilder sb = new StringBuilder();
+
+ if (slidesByDefault) {
+ printShapeText(slide, sb);
+ }
+
+ if (commentsByDefault) {
+ printComments(slide, sb);
+ }
+
+ if (notesByDefault) {
+ printNotes(slide, sb);
+ }
+
+ return sb.toString();
+ }
+
+ private String printHeaderReturnFooter(final Sheet sheet, final StringBuilder sb) {
+ final Sheet m = (sheet instanceof Slide) ? sheet.getMasterSheet() : sheet;
+ final StringBuilder footer = new StringBuilder("\n");
+ addSheetPlaceholderDatails(sheet, Placeholder.HEADER, sb);
+ addSheetPlaceholderDatails(sheet, Placeholder.FOOTER, footer);
+
+ if (masterByDefault) {
+ // write header texts and determine footer text
+ for (Shape s : m) {
+ if (!(s instanceof TextShape)) {
+ continue;
+ }
+ final TextShape ts = (TextShape) s;
+ final PlaceholderDetails pd = ts.getPlaceholderDetails();
+ if (pd == null || !pd.isVisible()) {
+ continue;
+ }
+ switch (pd.getPlaceholder()) {
+ case HEADER:
+ sb.append(ts.getText());
+ sb.append('\n');
+ break;
+ case SLIDE_NUMBER:
+ if (sheet instanceof Slide) {
+ footer.append(ts.getText().replace("‹#›", Integer.toString(((Slide) sheet).getSlideNumber() + 1)));
+ footer.append('\n');
+ }
+ break;
+ case FOOTER:
+ footer.append(ts.getText());
+ footer.append('\n');
+ break;
+ case DATETIME:
+ // currently not supported
+ default:
+ break;
+ }
+ }
+ }
+
+ return (footer.length() > 1) ? footer.toString() : "";
+ }
+
+ private void addSheetPlaceholderDatails(final Sheet sheet, final Placeholder placeholder, final StringBuilder sb) {
+ final PlaceholderDetails headerPD = sheet.getPlaceholderDetails(placeholder);
+ if (headerPD == null) {
+ return;
+ }
+ final String headerStr = headerPD.getText();
+ if (headerStr == null) {
+ return;
+ }
+ sb.append(headerStr);
+ }
+
+ private void printShapeText(final Sheet sheet, final StringBuilder sb) {
+ final String footer = printHeaderReturnFooter(sheet, sb);
+ printShapeText((ShapeContainer)sheet, sb);
+ sb.append(footer);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void printShapeText(final ShapeContainer container, final StringBuilder sb) {
+ for (Shape shape : container) {
+ if (shape instanceof TextShape) {
+ printShapeText((TextShape)shape, sb);
+ } else if (shape instanceof TableShape) {
+ printShapeText((TableShape)shape, sb);
+ } else if (shape instanceof ShapeContainer) {
+ printShapeText((ShapeContainer)shape, sb);
+ }
+ }
+ }
+
+ private void printShapeText(final TextShape shape, final StringBuilder sb) {
+ final List paraList = shape.getTextParagraphs();
+ if (paraList.isEmpty()) {
+ sb.append('\n');
+ 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;
+ switch (tr.getTextCap()) {
+ case ALL:
+ newStr = str.toUpperCase(LocaleUtil.getUserLocale());
+ break;
+ case SMALL:
+ newStr = str.toLowerCase(LocaleUtil.getUserLocale());
+ break;
+ default:
+ case NONE:
+ newStr = str;
+ break;
+ }
+ sb.append(newStr);
+ }
+ sb.append('\n');
+ }
+ }
+
+ @SuppressWarnings("Duplicates")
+ private void printShapeText(final TableShape shape, final StringBuilder sb) {
+ final int nrows = shape.getNumberOfRows();
+ final int ncols = shape.getNumberOfColumns();
+ for (int row = 0; row < nrows; row++){
+ for (int col = 0; col < ncols; col++){
+ TableCell cell = shape.getCell(row, col);
+ //defensive null checks; don't know if they're necessary
+ if (cell != null){
+ String txt = cell.getText();
+ txt = (txt == null) ? "" : txt;
+ sb.append(txt);
+ if (col < ncols-1){
+ sb.append('\t');
+ }
+ }
+ }
+ sb.append('\n');
+ }
+ }
+
+ private void printComments(final Slide slide, final StringBuilder sb) {
+ for (final Comment comment : slide.getComments()) {
+ sb.append(comment.getAuthor());
+ sb.append(" - ");
+ sb.append(comment.getText());
+ sb.append("\n");
+ }
+ }
+
+ private void printNotes(final Slide slide, final StringBuilder sb) {
+ final Notes notes = slide.getNotes();
+ if (notes == null) {
+ return;
+ }
+
+ final String footer = printHeaderReturnFooter(notes, sb);
+
+ printShapeText(notes, sb);
+
+ sb.append(footer);
+ }
+
+ public List extends ObjectShape> getOLEShapes() {
+ final List> oleShapes = new ArrayList<>();
+
+ for (final Slide slide : slideshow.getSlides()) {
+ addOLEShapes(oleShapes, slide);
+ }
+
+ return oleShapes;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addOLEShapes(final List> oleShapes, ShapeContainer container) {
+ for (Shape shape : container) {
+ if (shape instanceof ShapeContainer) {
+ addOLEShapes(oleShapes, (ShapeContainer)shape);
+ } else if (shape instanceof ObjectShape) {
+ oleShapes.add((ObjectShape)shape);
+ }
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/sl/usermodel/Comment.java b/src/java/org/apache/poi/sl/usermodel/Comment.java
new file mode 100644
index 000000000..43e46efec
--- /dev/null
+++ b/src/java/org/apache/poi/sl/usermodel/Comment.java
@@ -0,0 +1,85 @@
+/* ====================================================================
+ 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.usermodel;
+
+import java.awt.geom.Point2D;
+import java.util.Date;
+
+/**
+ * Common interface for comments
+ *
+ * @since POI 4.0.0
+ */
+public interface Comment {
+ /**
+ * Get the Author of this comment
+ */
+ String getAuthor();
+
+ /**
+ * Set the Author of this comment.
+ * if the author wasn't registered before, create a new entry
+ */
+ void setAuthor(String author);
+
+ /**
+ * Get the Author's Initials of this comment
+ */
+ String getAuthorInitials();
+
+ /**
+ * Set the Author's Initials of this comment.
+ * if the author wasn't registered before via {@link #setAuthor(String)}
+ * this has no effect
+ */
+ void setAuthorInitials(String initials);
+
+ /**
+ * Get the text of this comment
+ */
+ String getText();
+
+ /**
+ * Set the text of this comment
+ */
+ void setText(String text);
+
+ /**
+ * Gets the date the comment was made.
+ * @return the comment date.
+ */
+ Date getDate();
+
+ /**
+ * Sets the date the comment was made.
+ * @param date the comment date.
+ */
+ void setDate(Date date);
+
+ /**
+ * Gets the offset of the comment on the page.
+ * @return the offset.
+ */
+ Point2D getOffset();
+
+ /**
+ * Sets the offset of the comment on the page.
+ * @param offset the offset.
+ */
+ void setOffset(Point2D offset);
+}
diff --git a/src/java/org/apache/poi/sl/usermodel/PlaceholderDetails.java b/src/java/org/apache/poi/sl/usermodel/PlaceholderDetails.java
new file mode 100644
index 000000000..377b5c5b7
--- /dev/null
+++ b/src/java/org/apache/poi/sl/usermodel/PlaceholderDetails.java
@@ -0,0 +1,69 @@
+/* ====================================================================
+ 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.usermodel;
+
+
+/**
+ * Extended details about placholders
+ *
+ * @since POI 4.0.0
+ */
+public interface PlaceholderDetails {
+ enum PlaceholderSize {
+ quarter, half, full;
+ }
+
+ Placeholder getPlaceholder();
+
+ /**
+ * Specifies that the corresponding shape should be represented by the generating application
+ * as a placeholder. When a shape is considered a placeholder by the generating application
+ * it can have special properties to alert the user that they may enter content into the shape.
+ * Different types of placeholders are allowed and can be specified by using the placeholder
+ * type attribute for this element
+ *
+ * @param placeholder The shape to use as placeholder or null if no placeholder should be set.
+ */
+ void setPlaceholder(Placeholder placeholder);
+
+ boolean isVisible();
+
+ void setVisible(boolean isVisible);
+
+ PlaceholderSize getSize();
+
+ void setSize(PlaceholderSize size);
+
+ /**
+ * If the placeholder shape or object stores text, this text is returned otherwise {@code null}.
+ *
+ * @return the text of the shape / placeholder
+ *
+ * @since POI 4.0.0
+ */
+ String getText();
+
+ /**
+ * If the placeholder shape or object stores text, the given text is stored otherwise this is a no-op.
+ *
+ * @param text the placeholder text
+ *
+ * @since POI 4.0.0
+ */
+ void setText(String text);
+}
diff --git a/src/java/org/apache/poi/sl/usermodel/Sheet.java b/src/java/org/apache/poi/sl/usermodel/Sheet.java
index 923dac378..ae7cd179b 100644
--- a/src/java/org/apache/poi/sl/usermodel/Sheet.java
+++ b/src/java/org/apache/poi/sl/usermodel/Sheet.java
@@ -46,4 +46,16 @@ public interface Sheet<
* @param graphics
*/
void draw(Graphics2D graphics);
+
+ /**
+ * Get the placeholder details for the given placeholder type. Not all placeholders are also shapes -
+ * this is especially true for old HSLF slideshows, which notes have header/footers elements which
+ * aren't shapes.
+ *
+ * @param placeholder the placeholder type
+ * @return the placeholder details or {@code null}, if the placeholder isn't contained in the sheet
+ *
+ * @since POI 4.0.0
+ */
+ PlaceholderDetails getPlaceholderDetails(Placeholder placeholder);
}
diff --git a/src/java/org/apache/poi/sl/usermodel/SimpleShape.java b/src/java/org/apache/poi/sl/usermodel/SimpleShape.java
index 7bbbddfd0..ec31b5cc6 100644
--- a/src/java/org/apache/poi/sl/usermodel/SimpleShape.java
+++ b/src/java/org/apache/poi/sl/usermodel/SimpleShape.java
@@ -65,6 +65,24 @@ public interface SimpleShape<
*/
void setPlaceholder(Placeholder placeholder);
+ /**
+ * @return an accessor for placeholder details
+ *
+ * @since POI 4.0.0
+ */
+ PlaceholderDetails getPlaceholderDetails();
+
+ /**
+ * Checks if the shape is a placeholder.
+ * (placeholders aren't normal shapes, they are visible only in the Edit Master mode)
+ *
+ * @return {@code true} if the shape is a placeholder
+ *
+ * @since POI 4.0.0
+ */
+ boolean isPlaceholder();
+
+
Shadow getShadow();
/**
diff --git a/src/java/org/apache/poi/sl/usermodel/Slide.java b/src/java/org/apache/poi/sl/usermodel/Slide.java
index 74c9d6b0f..f5aed9273 100644
--- a/src/java/org/apache/poi/sl/usermodel/Slide.java
+++ b/src/java/org/apache/poi/sl/usermodel/Slide.java
@@ -17,6 +17,8 @@
package org.apache.poi.sl.usermodel;
+import java.util.List;
+
public interface Slide<
S extends Shape,
P extends TextParagraph
@@ -48,7 +50,7 @@ public interface Slide<
* whereas in HSLF they are activated via a HeadersFooter configuration.
* This method is used to generalize that handling.
*
- * @param placeholder
+ * @param placeholder the placeholder type
* @return {@code true} if the placeholder should be displayed/rendered
* @since POI 3.16-beta2
*/
@@ -69,4 +71,9 @@ public interface Slide<
* @since POI 4.0.0
*/
boolean isHidden();
+
+ /**
+ * @return the comment(s) for this slide
+ */
+ List extends Comment> getComments();
}
diff --git a/src/java/org/apache/poi/sl/usermodel/SlideShow.java b/src/java/org/apache/poi/sl/usermodel/SlideShow.java
index 90d81df3b..8ddd96217 100644
--- a/src/java/org/apache/poi/sl/usermodel/SlideShow.java
+++ b/src/java/org/apache/poi/sl/usermodel/SlideShow.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
+import org.apache.poi.POITextExtractor;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
public interface SlideShow<
@@ -118,4 +119,11 @@ public interface SlideShow<
* OutputStream
*/
void write(OutputStream out) throws IOException;
+
+ /**
+ * @return an extractor for the slideshow metadata
+ *
+ * @since POI 4.0.0
+ */
+ POITextExtractor getMetadataTextExtractor();
}
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 6e96f0e2b..ef9097f15 100644
--- a/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java
+++ b/src/ooxml/java/org/apache/poi/xslf/extractor/XSLFPowerPointExtractor.java
@@ -21,202 +21,188 @@ import java.io.IOException;
import org.apache.poi.POIXMLTextExtractor;
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.util.Removal;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
-import org.apache.poi.xslf.usermodel.XSLFCommentAuthors;
-import org.apache.poi.xslf.usermodel.XSLFComments;
-import org.apache.poi.xslf.usermodel.XSLFNotes;
import org.apache.poi.xslf.usermodel.XSLFRelation;
import org.apache.poi.xslf.usermodel.XSLFShape;
-import org.apache.poi.xslf.usermodel.XSLFShapeContainer;
import org.apache.poi.xslf.usermodel.XSLFSlide;
-import org.apache.poi.xslf.usermodel.XSLFSlideLayout;
-import org.apache.poi.xslf.usermodel.XSLFSlideMaster;
import org.apache.poi.xslf.usermodel.XSLFSlideShow;
-import org.apache.poi.xslf.usermodel.XSLFTable;
-import org.apache.poi.xslf.usermodel.XSLFTableCell;
-import org.apache.poi.xslf.usermodel.XSLFTableRow;
-import org.apache.poi.xslf.usermodel.XSLFTextShape;
+import org.apache.poi.xslf.usermodel.XSLFTextParagraph;
import org.apache.xmlbeans.XmlException;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTComment;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentAuthor;
+/**
+ * Extractor for XSLF SlideShows
+ *
+ * @deprecated use {@link SlideShowExtractor}
+ */
+@Deprecated
+@Removal(version="4.2.0")
public class XSLFPowerPointExtractor extends POIXMLTextExtractor {
- public static final XSLFRelation[] SUPPORTED_TYPES = new XSLFRelation[] {
- XSLFRelation.MAIN, XSLFRelation.MACRO, XSLFRelation.MACRO_TEMPLATE,
- XSLFRelation.PRESENTATIONML, XSLFRelation.PRESENTATIONML_TEMPLATE,
- XSLFRelation.PRESENTATION_MACRO
- };
-
- private XMLSlideShow slideshow;
- private boolean slidesByDefault = true;
- private boolean notesByDefault;
- private boolean masterByDefault;
-
- public XSLFPowerPointExtractor(XMLSlideShow slideshow) {
- super(slideshow);
- this.slideshow = slideshow;
- }
- public XSLFPowerPointExtractor(XSLFSlideShow slideshow) throws XmlException, IOException {
- this(new XMLSlideShow(slideshow.getPackage()));
- }
- public XSLFPowerPointExtractor(OPCPackage container) throws XmlException, OpenXML4JException, IOException {
- this(new XSLFSlideShow(container));
- }
+ public static final XSLFRelation[] SUPPORTED_TYPES = new XSLFRelation[]{
+ XSLFRelation.MAIN, XSLFRelation.MACRO, XSLFRelation.MACRO_TEMPLATE,
+ XSLFRelation.PRESENTATIONML, XSLFRelation.PRESENTATIONML_TEMPLATE,
+ XSLFRelation.PRESENTATION_MACRO
+ };
- public static void main(String[] args) throws Exception {
- if(args.length < 1) {
- System.err.println("Use:");
- System.err.println(" XSLFPowerPointExtractor ");
- System.exit(1);
- }
- POIXMLTextExtractor extractor =
- new XSLFPowerPointExtractor(
- new XSLFSlideShow(args[0]));
- System.out.println(extractor.getText());
- extractor.close();
- }
+ private final SlideShowExtractor delegate;
- /**
- * Should a call to getText() return slide text?
- * Default is yes
- */
- public void setSlidesByDefault(boolean slidesByDefault) {
- this.slidesByDefault = slidesByDefault;
- }
- /**
- * Should a call to getText() return notes text?
- * Default is no
- */
- public void setNotesByDefault(boolean notesByDefault) {
- this.notesByDefault = notesByDefault;
- }
-
- /**
- * Should a call to getText() return text from master? Default is no
- */
- public void setMasterByDefault(boolean masterByDefault) {
- this.masterByDefault = masterByDefault;
- }
-
- /**
- * Gets the slide text, but not the notes text
- */
- @Override
+
+ private boolean slidesByDefault = true;
+ private boolean notesByDefault;
+ private boolean commentsByDefault;
+ private boolean masterByDefault;
+
+ @SuppressWarnings("WeakerAccess")
+ public XSLFPowerPointExtractor(XMLSlideShow slideShow) {
+ super(slideShow);
+ delegate = new SlideShowExtractor<>(slideShow);
+ }
+
+ public XSLFPowerPointExtractor(XSLFSlideShow slideShow) {
+ this(new XMLSlideShow(slideShow.getPackage()));
+ }
+
+ public XSLFPowerPointExtractor(OPCPackage container) throws XmlException, OpenXML4JException, IOException {
+ this(new XSLFSlideShow(container));
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length < 1) {
+ System.err.println("Use:");
+ System.err.println(" XSLFPowerPointExtractor ");
+ System.exit(1);
+ }
+ POIXMLTextExtractor extractor =
+ new XSLFPowerPointExtractor(
+ new XSLFSlideShow(args[0]));
+ System.out.println(extractor.getText());
+ extractor.close();
+ }
+
+ /**
+ * Should a call to getText() return slide text?
+ * Default is yes
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void setSlidesByDefault(final boolean slidesByDefault) {
+ this.slidesByDefault = slidesByDefault;
+ delegate.setSlidesByDefault(slidesByDefault);
+ }
+
+ /**
+ * Should a call to getText() return notes text?
+ * Default is no
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void setNotesByDefault(final boolean notesByDefault) {
+ this.notesByDefault = notesByDefault;
+ delegate.setNotesByDefault(notesByDefault);
+ }
+
+ /**
+ * Should a call to getText() return comments text? Default is no
+ */
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ public void setCommentsByDefault(final boolean commentsByDefault) {
+ this.commentsByDefault = commentsByDefault;
+ delegate.setCommentsByDefault(commentsByDefault);
+ }
+
+ /**
+ * Should a call to getText() return text from master? Default is no
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void setMasterByDefault(final boolean masterByDefault) {
+ this.masterByDefault = masterByDefault;
+ delegate.setMasterByDefault(masterByDefault);
+ }
+
+ /**
+ * Gets the slide text, but not the notes text
+ */
+ @Override
public String getText() {
- return getText(slidesByDefault, notesByDefault);
- }
-
- /**
- * Gets the requested text from the file
- * @param slideText Should we retrieve text from slides?
- * @param notesText Should we retrieve text from notes?
- */
- public String getText(boolean slideText, boolean notesText) {
- return getText(slideText, notesText, masterByDefault);
- }
-
- /**
- * Gets the requested text from the file
- *
- * @param slideText Should we retrieve text from slides?
- * @param notesText Should we retrieve text from notes?
- * @param masterText Should we retrieve text from master slides?
- *
- * @return the extracted text
- */
- public String getText(boolean slideText, boolean notesText, boolean masterText) {
- StringBuilder text = new StringBuilder();
+ return delegate.getText();
+ }
- for (XSLFSlide slide : slideshow.getSlides()) {
- text.append(getText(slide, slideText, notesText, masterText));
- }
+ /**
+ * Gets the requested text from the file
+ *
+ * @param slideText Should we retrieve text from slides?
+ * @param notesText Should we retrieve text from notes?
+ */
+ public String getText(final boolean slideText, final boolean notesText) {
+ return getText(slideText, notesText, commentsByDefault, masterByDefault);
+ }
- return text.toString();
- }
+ /**
+ * Gets the requested text from the file
+ *
+ * @param slideText Should we retrieve text from slides?
+ * @param notesText Should we retrieve text from notes?
+ * @param masterText Should we retrieve text from master slides?
+ * @return the extracted text
+ */
+ public String getText(boolean slideText, boolean notesText, boolean masterText) {
+ return getText(slideText, notesText, commentsByDefault, masterText);
+ }
- /**
- * Gets the requested text from the slide
- *
- * @param slide the slide to retrieve the text from
- * @param slideText Should we retrieve text from slides?
- * @param notesText Should we retrieve text from notes?
- * @param masterText Should we retrieve text from master slides?
- *
- * @return the extracted text
- */
- public static String getText(XSLFSlide slide, boolean slideText, boolean notesText, boolean masterText) {
- StringBuilder text = new StringBuilder();
- XSLFCommentAuthors commentAuthors = slide.getSlideShow().getCommentAuthors();
+ /**
+ * Gets the requested text from the file
+ *
+ * @param slideText Should we retrieve text from slides?
+ * @param notesText Should we retrieve text from notes?
+ * @param commentText Should we retrieve text from comments?
+ * @param masterText Should we retrieve text from master slides?
+ * @return the extracted text
+ */
+ @SuppressWarnings("Duplicates")
+ public String getText(boolean slideText, boolean notesText, boolean commentText, boolean masterText) {
+ delegate.setSlidesByDefault(slideText);
+ delegate.setNotesByDefault(notesText);
+ delegate.setCommentsByDefault(commentText);
+ delegate.setMasterByDefault(masterText);
+ try {
+ return delegate.getText();
+ } finally {
+ delegate.setSlidesByDefault(slidesByDefault);
+ delegate.setNotesByDefault(notesByDefault);
+ delegate.setCommentsByDefault(commentsByDefault);
+ delegate.setMasterByDefault(masterByDefault);
+ }
+ }
- XSLFNotes notes = slide.getNotes();
- XSLFComments comments = slide.getComments();
- XSLFSlideLayout layout = slide.getSlideLayout();
- XSLFSlideMaster master = layout.getSlideMaster();
+ /**
+ * Gets the requested text from the slide
+ *
+ * @param slide the slide to retrieve the text from
+ * @param slideText Should we retrieve text from slides?
+ * @param notesText Should we retrieve text from notes?
+ * @param masterText Should we retrieve text from master slides?
+ * @return the extracted text
+ */
+ public static String getText(XSLFSlide slide, boolean slideText, boolean notesText, boolean masterText) {
+ return getText(slide, slideText, notesText, false, masterText);
+ }
- // TODO Do the slide's name
- // (Stored in docProps/app.xml)
-
- // Do the slide's text if requested
- if (slideText) {
- extractText(slide, false, text);
-
- // If requested, get text from the master and it's layout
- if(masterText) {
- assert (layout != null);
- extractText(layout, true, text);
- assert (master != null);
- extractText(master, true, text);
- }
-
- // If the slide has comments, do those too
- if (comments != null) {
- for (CTComment comment : comments.getCTCommentsList().getCmArray()) {
- // Do the author if we can
- if (commentAuthors != null) {
- CTCommentAuthor author = commentAuthors.getAuthorById(comment.getAuthorId());
- if(author != null) {
- text.append(author.getName() + ": ");
- }
- }
-
- // Then the comment text, with a new line afterwards
- text.append(comment.getText());
- text.append("\n");
- }
- }
- }
-
- // Do the notes if requested
- if (notesText && notes != null) {
- extractText(notes, false, text);
- }
-
- return text.toString();
- }
-
- private static void extractText(XSLFShapeContainer data, boolean skipPlaceholders, StringBuilder text) {
- for (XSLFShape s : data) {
- if (s instanceof XSLFShapeContainer) {
- extractText((XSLFShapeContainer)s, skipPlaceholders, text);
- } else if (s instanceof XSLFTextShape) {
- XSLFTextShape ts = (XSLFTextShape)s;
- // Skip non-customised placeholder text
- if (!(skipPlaceholders && ts.isPlaceholder())) {
- text.append(ts.getText());
- text.append("\n");
- }
- } else if (s instanceof XSLFTable) {
- XSLFTable ts = (XSLFTable)s;
- // Skip non-customised placeholder text
- for (XSLFTableRow r : ts) {
- for (XSLFTableCell c : r) {
- text.append(c.getText());
- text.append("\t");
- }
- text.append("\n");
- }
- }
- }
- }
+ /**
+ * Gets the requested text from the slide
+ *
+ * @param slide the slide to retrieve the text from
+ * @param slideText Should we retrieve text from slides?
+ * @param notesText Should we retrieve text from notes?
+ * @param commentText Should we retrieve text from comments?
+ * @param masterText Should we retrieve text from master slides?
+ * @return the extracted text
+ */
+ public static String getText(XSLFSlide slide, boolean slideText, boolean notesText, boolean commentText, boolean masterText) {
+ final SlideShowExtractor ex = new SlideShowExtractor<>(slide.getSlideShow());
+ ex.setSlidesByDefault(slideText);
+ ex.setNotesByDefault(notesText);
+ ex.setCommentsByDefault(commentText);
+ ex.setMasterByDefault(masterText);
+ return ex.getText(slide);
+ }
}
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 a935174f1..8734f3603 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
@@ -35,10 +35,10 @@ import java.util.regex.Pattern;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLException;
+import org.apache.poi.POIXMLPropertiesTextExtractor;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
-import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.sl.usermodel.MasterSheet;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.sl.usermodel.Resources;
@@ -626,4 +626,9 @@ public class XMLSlideShow extends POIXMLDocument
// TODO: implement!
throw new UnsupportedOperationException();
}
+
+ @Override
+ public POIXMLPropertiesTextExtractor getMetadataTextExtractor() {
+ return new POIXMLPropertiesTextExtractor(this);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComment.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComment.java
new file mode 100644
index 000000000..917df0dd9
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComment.java
@@ -0,0 +1,126 @@
+/* ====================================================================
+ 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 java.awt.geom.Point2D;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.poi.sl.usermodel.Comment;
+import org.apache.poi.util.LocaleUtil;
+import org.apache.poi.util.Units;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTComment;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentAuthor;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTCommentAuthorList;
+
+/**
+ * XSLF Comment
+ *
+ * @since POI 4.0.0
+ */
+public class XSLFComment implements Comment {
+
+ final CTComment comment;
+ final XSLFCommentAuthors authors;
+
+ XSLFComment(final CTComment comment, final XSLFCommentAuthors authors) {
+ this.comment = comment;
+ this.authors = authors;
+ }
+
+ @Override
+ public String getAuthor() {
+ return authors.getAuthorById(comment.getAuthorId()).getName();
+ }
+
+ @Override
+ public void setAuthor(final String author) {
+ if (author == null) {
+ throw new IllegalArgumentException("author must not be null");
+ }
+ final CTCommentAuthorList list = authors.getCTCommentAuthorsList();
+ long maxId = -1;
+ for (final CTCommentAuthor aut : list.getCmAuthorArray()) {
+ maxId = Math.max(aut.getId(), maxId);
+ if (author.equals(aut.getName())) {
+ comment.setAuthorId(aut.getId());
+ return;
+ }
+ }
+ // author not found -> add new author
+ final CTCommentAuthor newAuthor = list.addNewCmAuthor();
+ newAuthor.setName(author);
+ newAuthor.setId(maxId+1);
+ newAuthor.setInitials(author.replaceAll( "\\s*(\\w)\\S*", "$1").toUpperCase(LocaleUtil.getUserLocale()));
+ comment.setAuthorId(maxId+1);
+ }
+
+ @Override
+ public String getAuthorInitials() {
+ final CTCommentAuthor aut = authors.getAuthorById(comment.getAuthorId());
+ return aut == null ? null : aut.getInitials();
+ }
+
+ @Override
+ public void setAuthorInitials(final String initials) {
+ final CTCommentAuthor aut = authors.getAuthorById(comment.getAuthorId());
+ if (aut != null) {
+ aut.setInitials(initials);
+ }
+ }
+
+ @Override
+ public String getText() {
+ return comment.getText();
+ }
+
+ @Override
+ public void setText(final String text) {
+ comment.setText(text);
+ }
+
+ @Override
+ public Date getDate() {
+ final Calendar cal = comment.getDt();
+ return (cal == null) ? null : cal.getTime();
+ }
+
+ @Override
+ public void setDate(final Date date) {
+ final Calendar cal = LocaleUtil.getLocaleCalendar();
+ cal.setTime(date);
+ comment.setDt(cal);
+ }
+
+ @Override
+ public Point2D getOffset() {
+ final CTPoint2D pos = comment.getPos();
+ return new Point2D.Double(Units.toPoints(pos.getX()), Units.toPoints(pos.getY()));
+ }
+
+ @Override
+ public void setOffset(final Point2D offset) {
+ CTPoint2D pos = comment.getPos();
+ if (pos == null) {
+ pos = comment.addNewPos();
+ }
+ pos.setX(Units.toEMU(offset.getX()));
+ pos.setY(Units.toEMU(offset.getY()));
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComments.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComments.java
index 04e09c865..7a281d5bc 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComments.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFComments.java
@@ -31,42 +31,37 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CmLstDocument;
@Beta
public class XSLFComments extends POIXMLDocumentPart {
- private final CTCommentList _comments;
-
+ private final CmLstDocument doc;
+
/**
* Create a new set of slide comments
*/
XSLFComments() {
- super();
- CmLstDocument doc = CmLstDocument.Factory.newInstance();
- _comments = doc.addNewCmLst();
+ doc = CmLstDocument.Factory.newInstance();
}
/**
* Construct a SpreadsheetML slide comments from a package part
*
* @param part the package part holding the comments data,
- * the content type must be application/vnd.openxmlformats-officedocument.comments+xml
- *
+ * the content type must be application/vnd.openxmlformats-officedocument.comments+xml
* @since POI 3.14-Beta1
*/
XSLFComments(PackagePart part) throws IOException, XmlException {
super(part);
- CmLstDocument doc =
- CmLstDocument.Factory.parse(getPackagePart().getInputStream(), DEFAULT_XML_OPTIONS);
- _comments = doc.getCmLst();
+ doc = CmLstDocument.Factory.parse(getPackagePart().getInputStream(), DEFAULT_XML_OPTIONS);
}
public CTCommentList getCTCommentsList() {
- return _comments;
+ return doc.getCmLst();
}
-
+
public int getNumberOfComments() {
- return _comments.sizeOfCmArray();
+ return doc.getCmLst().sizeOfCmArray();
}
-
+
public CTComment getCommentAt(int pos) {
- return _comments.getCmArray(pos);
+ return doc.getCmLst().getCmArray(pos);
}
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFNotesMaster.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFNotesMaster.java
index 59d8d6746..b142416b3 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFNotesMaster.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFNotesMaster.java
@@ -51,7 +51,6 @@ import org.openxmlformats.schemas.presentationml.x2006.main.NotesMasterDocument;
public class XSLFNotesMaster extends XSLFSheet
implements MasterSheet {
private CTNotesMaster _slide;
- private XSLFTheme _theme;
XSLFNotesMaster() {
super();
@@ -100,21 +99,15 @@ import org.openxmlformats.schemas.presentationml.x2006.main.NotesMasterDocument;
public MasterSheet getMasterSheet() {
return null;
}
-
+
+
@Override
- public XSLFTheme getTheme() {
- if (_theme == null) {
- for (POIXMLDocumentPart p : getRelations()) {
- if (p instanceof XSLFTheme) {
- _theme = (XSLFTheme) p;
- CTColorMapping cmap = _slide.getClrMap();
- if (cmap != null) {
- _theme.initColorMap(cmap);
- }
- break;
- }
- }
- }
- return _theme;
- }
+ boolean isSupportTheme() {
+ return true;
+ }
+
+ @Override
+ CTColorMapping getColorMapping() {
+ return _slide.getClrMap();
+ }
}
\ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
new file mode 100644
index 000000000..25fbd6344
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
@@ -0,0 +1,203 @@
+package org.apache.poi.xslf.usermodel;
+
+import static org.apache.poi.xslf.usermodel.XSLFShape.PML_NS;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.poi.sl.usermodel.MasterSheet;
+import org.apache.poi.sl.usermodel.Placeholder;
+import org.apache.poi.sl.usermodel.PlaceholderDetails;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTHeaderFooter;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMaster;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMaster;
+import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderSize;
+import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType;
+
+/**
+ * XSLF Placeholder Details
+ *
+ * @since POI 4.0.0
+ */
+public class XSLFPlaceholderDetails implements PlaceholderDetails {
+
+ private final XSLFShape shape;
+ private CTPlaceholder _ph;
+
+ XSLFPlaceholderDetails(final XSLFShape shape) {
+ this.shape = shape;
+ }
+
+ @Override
+ public Placeholder getPlaceholder() {
+ final CTPlaceholder ph = getCTPlaceholder(false);
+ if (ph == null || !(ph.isSetType() || ph.isSetIdx())) {
+ return null;
+ }
+ return Placeholder.lookupOoxml(ph.getType().intValue());
+ }
+
+ @Override
+ public void setPlaceholder(final Placeholder placeholder) {
+ CTPlaceholder ph = getCTPlaceholder(placeholder != null);
+ if (ph != null) {
+ if (placeholder != null) {
+ ph.setType(STPlaceholderType.Enum.forInt(placeholder.ooxmlId));
+ } else {
+ getNvProps().unsetPh();
+ }
+ }
+ }
+
+ @Override
+ public boolean isVisible() {
+ final CTPlaceholder ph = getCTPlaceholder(false);
+ if (ph == null || !ph.isSetType()) {
+ return true;
+ }
+ final CTHeaderFooter hf = getHeaderFooter(false);
+ if (hf == null) {
+ return false;
+ }
+
+ final Placeholder pl = Placeholder.lookupOoxml(ph.getType().intValue());
+ if (pl == null) {
+ return true;
+ }
+ switch (pl) {
+ case DATETIME:
+ return !hf.isSetDt() || hf.getDt();
+ case FOOTER:
+ return !hf.isSetFtr() || hf.getFtr();
+ case HEADER:
+ return !hf.isSetHdr() || hf.getHdr();
+ case SLIDE_NUMBER:
+ return !hf.isSetSldNum() || hf.getSldNum();
+ default:
+ return true;
+ }
+ }
+
+ @Override
+ public void setVisible(final boolean isVisible) {
+ final Placeholder ph = getPlaceholder();
+ if (ph == null) {
+ return;
+ }
+ final Function> fun;
+ switch (ph) {
+ case DATETIME:
+ fun = (hf) -> hf::setDt;
+ break;
+ case FOOTER:
+ fun = (hf) -> hf::setFtr;
+ break;
+ case HEADER:
+ fun = (hf) -> hf::setHdr;
+ break;
+ case SLIDE_NUMBER:
+ fun = (hf) -> hf::setSldNum;
+ break;
+ default:
+ return;
+ }
+ // only create a header, if we need to, i.e. the placeholder type is eligible
+ final CTHeaderFooter hf = getHeaderFooter(true);
+ if (hf == null) {
+ return;
+ }
+ fun.apply(hf).accept(isVisible);
+ }
+
+ @Override
+ public PlaceholderSize getSize() {
+ final CTPlaceholder ph = getCTPlaceholder(false);
+ if (ph == null || !ph.isSetSz()) {
+ return null;
+ }
+ switch (ph.getSz().intValue()) {
+ case STPlaceholderSize.INT_FULL:
+ return PlaceholderSize.full;
+ case STPlaceholderSize.INT_HALF:
+ return PlaceholderSize.half;
+ case STPlaceholderSize.INT_QUARTER:
+ return PlaceholderSize.quarter;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void setSize(final PlaceholderSize size) {
+ final CTPlaceholder ph = getCTPlaceholder(false);
+ if (ph == null) {
+ return;
+ }
+ if (size == null) {
+ ph.unsetSz();
+ return;
+ }
+ switch (size) {
+ case full:
+ ph.setSz(STPlaceholderSize.FULL);
+ break;
+ case half:
+ ph.setSz(STPlaceholderSize.HALF);
+ break;
+ case quarter:
+ ph.setSz(STPlaceholderSize.QUARTER);
+ break;
+ }
+ }
+
+ /**
+ * Gets or creates a new placeholder element
+ *
+ * @param create if {@code true} creates the element if it hasn't existed before
+ * @return the placeholder or {@code null} if the shape doesn't support placeholders
+ */
+ CTPlaceholder getCTPlaceholder(final boolean create) {
+ if (_ph != null) {
+ return _ph;
+ }
+
+ final CTApplicationNonVisualDrawingProps nv = getNvProps();
+ if (nv == null) {
+ // shape doesn't support CTApplicationNonVisualDrawingProps
+ return null;
+ }
+
+ _ph = (nv.isSetPh() || !create) ? nv.getPh() : nv.addNewPh();
+ return _ph;
+ }
+
+ private CTApplicationNonVisualDrawingProps getNvProps() {
+ final String xquery = "declare namespace p='" + PML_NS + "' .//*/p:nvPr";
+ return shape.selectProperty(CTApplicationNonVisualDrawingProps.class, xquery);
+ }
+
+ private CTHeaderFooter getHeaderFooter(final boolean create) {
+ final XSLFSheet sheet = shape.getSheet();
+ final XSLFSheet master = (sheet instanceof MasterSheet && !(sheet instanceof XSLFSlideLayout)) ? sheet : (XSLFSheet)sheet.getMasterSheet();
+ if (master instanceof XSLFSlideMaster) {
+ final CTSlideMaster ct = ((XSLFSlideMaster) master).getXmlObject();
+ return (ct.isSetHf() || !create) ? ct.getHf() : ct.addNewHf();
+ } else if (master instanceof XSLFNotesMaster) {
+ final CTNotesMaster ct = ((XSLFNotesMaster) master).getXmlObject();
+ return (ct.isSetHf() || !create) ? ct.getHf() : ct.addNewHf();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String getText() {
+ return null;
+ }
+
+ @Override
+ public void setText(String text) {
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
index 91b5abd63..35a54e96e 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
@@ -38,7 +38,9 @@ import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
import org.apache.poi.sl.usermodel.PlaceableShape;
import org.apache.poi.sl.usermodel.Placeholder;
+import org.apache.poi.sl.usermodel.PlaceholderDetails;
import org.apache.poi.sl.usermodel.Shape;
+import org.apache.poi.sl.usermodel.SimpleShape;
import org.apache.poi.util.Beta;
import org.apache.poi.util.Internal;
import org.apache.poi.xslf.model.PropertyFetcher;
@@ -58,7 +60,6 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillPropertie
import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix;
import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference;
import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties;
import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
import org.openxmlformats.schemas.presentationml.x2006.main.CTShape;
@@ -69,7 +70,7 @@ import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType;
*/
@Beta
public abstract class XSLFShape implements Shape {
- protected static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
+ static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
private final XmlObject _shape;
private final XSLFSheet _sheet;
@@ -77,7 +78,6 @@ public abstract class XSLFShape implements Shape {
private CTShapeStyle _spStyle;
private CTNonVisualDrawingProps _nvPr;
- private CTPlaceholder _ph;
protected XSLFShape(XmlObject shape, XSLFSheet sheet) {
_shape = shape;
@@ -238,45 +238,32 @@ public abstract class XSLFShape implements Shape {
cur.dispose();
return child;
}
-
- protected CTPlaceholder getCTPlaceholder() {
- if (_ph == null) {
- String xquery = "declare namespace p='"+PML_NS+"' .//*/p:nvPr/p:ph";
- _ph = selectProperty(CTPlaceholder.class, xquery);
- }
- return _ph;
+
+ public boolean isPlaceholder() {
+ return getPlaceholderDetails().getCTPlaceholder(false) != null;
}
+ /**
+ * @see PlaceholderDetails#getPlaceholder()
+ */
public Placeholder getPlaceholder() {
- CTPlaceholder ph = getCTPlaceholder();
- if (ph == null || !(ph.isSetType() || ph.isSetIdx())) {
- return null;
- }
- return Placeholder.lookupOoxml(ph.getType().intValue());
+ return getPlaceholderDetails().getPlaceholder();
}
/**
- * Specifies that the corresponding shape should be represented by the generating application
- * as a placeholder. When a shape is considered a placeholder by the generating application
- * it can have special properties to alert the user that they may enter content into the shape.
- * Different types of placeholders are allowed and can be specified by using the placeholder
- * type attribute for this element
- *
- * @param placeholder The shape to use as placeholder or null if no placeholder should be set.
+ * @see PlaceholderDetails#setPlaceholder(Placeholder)
*/
- protected void setPlaceholder(Placeholder placeholder) {
- String xquery = "declare namespace p='"+PML_NS+"' .//*/p:nvPr";
- CTApplicationNonVisualDrawingProps nv = selectProperty(CTApplicationNonVisualDrawingProps.class, xquery);
- if (nv == null) return;
- if(placeholder == null) {
- if (nv.isSetPh()) nv.unsetPh();
- _ph = null;
- } else {
- nv.addNewPh().setType(STPlaceholderType.Enum.forInt(placeholder.ooxmlId));
- }
+ public void setPlaceholder(final Placeholder placeholder) {
+ getPlaceholderDetails().setPlaceholder(placeholder);
}
-
-
+
+ /**
+ * @see SimpleShape#getPlaceholderDetails()
+ */
+ public XSLFPlaceholderDetails getPlaceholderDetails() {
+ return new XSLFPlaceholderDetails(this);
+ }
+
/**
* As there's no xmlbeans hierarchy, but XSLF works with subclassing, not all
* child classes work with a {@link CTShape} object, but often contain the same
@@ -315,7 +302,7 @@ public abstract class XSLFShape implements Shape {
return true;
}
- CTPlaceholder ph = getCTPlaceholder();
+ final CTPlaceholder ph = getPlaceholderDetails().getCTPlaceholder(false);
if (ph == null) {
return false;
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
index 8936f209b..e8b4f2cc4 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
@@ -18,6 +18,7 @@ package org.apache.poi.xslf.usermodel;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
+import javax.xml.namespace.QName;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.io.IOException;
@@ -28,8 +29,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-
-import javax.xml.namespace.QName;
+import java.util.Optional;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLException;
@@ -56,6 +56,7 @@ import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTColorMapping;
import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector;
import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame;
import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape;
@@ -72,6 +73,7 @@ implements XSLFShapeContainer, Sheet {
private XSLFDrawing _drawing;
private List _shapes;
private CTGroupShape _spTree;
+ private XSLFTheme _theme;
private List_placeholders;
private Map _placeholderByIdMap;
@@ -456,7 +458,36 @@ implements XSLFShapeContainer, Sheet {
* Sheets that support the notion of themes (slides, masters, layouts, etc.) should override this
* method and return the corresponding package part.
*/
- XSLFTheme getTheme(){
+ public XSLFTheme getTheme() {
+ if (_theme != null || !isSupportTheme()) {
+ return _theme;
+ }
+
+ final Optional t =
+ getRelations().stream().filter((p) -> p instanceof XSLFTheme).map((p) -> (XSLFTheme) p).findAny();
+ if (t.isPresent()) {
+ _theme = t.get();
+ final CTColorMapping cmap = getColorMapping();
+ if (cmap != null) {
+ _theme.initColorMap(cmap);
+ }
+ }
+ return _theme;
+ }
+
+
+
+ /**
+ * @return {@code true} if this class supports themes
+ */
+ boolean isSupportTheme() {
+ return false;
+ }
+
+ /**
+ * @return the color mapping for this slide type
+ */
+ CTColorMapping getColorMapping() {
return null;
}
@@ -488,16 +519,16 @@ implements XSLFShapeContainer, Sheet {
return shape;
}
- void initPlaceholders() {
+ private void initPlaceholders() {
if(_placeholders == null) {
_placeholders = new ArrayList<>();
_placeholderByIdMap = new HashMap<>();
_placeholderByTypeMap = new HashMap<>();
- for(XSLFShape sh : getShapes()){
+ for(final XSLFShape sh : getShapes()){
if(sh instanceof XSLFTextShape){
- XSLFTextShape sShape = (XSLFTextShape)sh;
- CTPlaceholder ph = sShape.getCTPlaceholder();
+ final XSLFTextShape sShape = (XSLFTextShape)sh;
+ final CTPlaceholder ph = sShape.getPlaceholderDetails().getCTPlaceholder(false);
if(ph != null) {
_placeholders.add(sShape);
if(ph.isSetIdx()) {
@@ -513,7 +544,7 @@ implements XSLFShapeContainer, Sheet {
}
}
- XSLFSimpleShape getPlaceholderById(int id) {
+ private XSLFSimpleShape getPlaceholderById(int id) {
initPlaceholders();
return _placeholderByIdMap.get(id);
}
@@ -574,7 +605,7 @@ implements XSLFShapeContainer, Sheet {
/**
* Render this sheet into the supplied graphics object
*
- * @param graphics
+ * @param graphics the graphics context to draw to
*/
@Override
public void draw(Graphics2D graphics){
@@ -645,4 +676,12 @@ implements XSLFShapeContainer, Sheet {
void removePictureRelation(XSLFPictureShape pictureShape) {
removeRelation(pictureShape.getBlipId());
}
+
+
+ @Override
+ public XSLFPlaceholderDetails getPlaceholderDetails(Placeholder placeholder) {
+ final XSLFSimpleShape ph = getPlaceholder(placeholder);
+ return (ph == null) ? null : new XSLFPlaceholderDetails(ph);
+ }
+
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java
index 751bd9b92..0326250db 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java
@@ -36,7 +36,6 @@ import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;
import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
-import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.ShapeType;
import org.apache.poi.sl.usermodel.SimpleShape;
import org.apache.poi.sl.usermodel.StrokeStyle;
@@ -980,11 +979,6 @@ public abstract class XSLFSimpleShape extends XSLFShape
return ds;
}
- public boolean isPlaceholder() {
- CTPlaceholder ph = getCTPlaceholder();
- return ph != null;
- }
-
@Override
public Guide getAdjustValue(String name) {
XSLFGeometryProperties gp = XSLFPropertiesDelegate.getGeometryDelegate(getShapeProperties());
@@ -1105,11 +1099,6 @@ public abstract class XSLFSimpleShape extends XSLFShape
}
}
- @Override
- public void setPlaceholder(Placeholder placeholder) {
- super.setPlaceholder(placeholder);
- }
-
@Override
public XSLFHyperlink getHyperlink() {
CTNonVisualDrawingProps cNvPr = getCNvPr();
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 7c3d1f66c..ce650c152 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlide.java
@@ -20,6 +20,8 @@ import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import java.awt.Graphics2D;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
@@ -38,6 +40,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.presentationml.x2006.main.CTBackground;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTComment;
import org.openxmlformats.schemas.presentationml.x2006.main.CTCommonSlideData;
import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape;
import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShapeNonVisual;
@@ -52,6 +55,7 @@ implements Slide {
private final CTSlide _slide;
private XSLFSlideLayout _layout;
private XSLFComments _comments;
+ private XSLFCommentAuthors _commentAuthors;
private XSLFNotes _notes;
/**
@@ -155,22 +159,55 @@ implements Slide {
return getSlideLayout().getSlideMaster();
}
- public XSLFComments getComments() {
+ /**
+ * @return the comments part or {@code null} if there weren't any comments
+ * @since POI 4.0.0
+ */
+ public XSLFComments getCommentsPart() {
if(_comments == null) {
for (POIXMLDocumentPart p : getRelations()) {
if (p instanceof XSLFComments) {
_comments = (XSLFComments)p;
+ break;
}
}
}
- if(_comments == null) {
- // This slide lacks comments
- // Not all have them, sorry...
- return null;
- }
+
return _comments;
}
+ /**
+ * @return the comment authors part or {@code null} if there weren't any comments
+ * @since POI 4.0.0
+ */
+ public XSLFCommentAuthors getCommentAuthorsPart() {
+ if(_commentAuthors == null) {
+ for (POIXMLDocumentPart p : getRelations()) {
+ if (p instanceof XSLFCommentAuthors) {
+ _commentAuthors = (XSLFCommentAuthors)p;
+ return _commentAuthors;
+ }
+ }
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public List getComments() {
+ final List comments = new ArrayList<>();
+ final XSLFComments xComments = getCommentsPart();
+ final XSLFCommentAuthors xAuthors = getCommentAuthorsPart();
+ if (xComments != null) {
+ for (final CTComment xc : xComments.getCTCommentsList().getCmArray()) {
+ comments.add(new XSLFComment(xc, xAuthors));
+ }
+ }
+
+ return comments;
+ }
+
@Override
public XSLFNotes getNotes() {
if(_notes == null) {
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java
index 14684b4c6..bf5ef5ad2 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideLayout.java
@@ -28,21 +28,15 @@ import org.apache.poi.util.Beta;
import org.apache.poi.util.Internal;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.presentationml.x2006.main.CTBackground;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideLayout;
import org.openxmlformats.schemas.presentationml.x2006.main.SldLayoutDocument;
@Beta
public class XSLFSlideLayout extends XSLFSheet
implements MasterSheet {
- private CTSlideLayout _layout;
+ private final CTSlideLayout _layout;
private XSLFSlideMaster _master;
- XSLFSlideLayout() {
- super();
- _layout = CTSlideLayout.Factory.newInstance();
- }
-
/**
* @since POI 3.14-Beta1
*/
@@ -111,14 +105,7 @@ implements MasterSheet {
*/
@Override
protected boolean canDraw(XSLFShape shape) {
- if (shape instanceof XSLFSimpleShape) {
- XSLFSimpleShape txt = (XSLFSimpleShape) shape;
- CTPlaceholder ph = txt.getCTPlaceholder();
- if (ph != null) {
- return false;
- }
- }
- return true;
+ return !(shape instanceof XSLFSimpleShape) || !shape.isPlaceholder();
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java
index 967441a93..2f3be06f6 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSlideMaster.java
@@ -32,7 +32,6 @@ import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.drawingml.x2006.main.CTColorMapping;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextListStyle;
import org.openxmlformats.schemas.presentationml.x2006.main.CTBackground;
-import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMaster;
import org.openxmlformats.schemas.presentationml.x2006.main.CTSlideMasterTextStyles;
import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
@@ -43,7 +42,6 @@ import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
* Within a slide master slide are contained all elements
* that describe the objects and their corresponding formatting
* for within a presentation slide.
-*
*
* Within a slide master slide are two main elements.
* The cSld element specifies the common slide elements such as shapes and
@@ -52,21 +50,12 @@ import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
* within a slide master slide specify other properties for within a presentation slide
* such as color information, headers and footers, as well as timing and
* transition information for all corresponding presentation slides.
-*
- *
- * @author Yegor Kozlov
*/
@Beta
public class XSLFSlideMaster extends XSLFSheet
implements MasterSheet {
private CTSlideMaster _slide;
private Map _layouts;
- private XSLFTheme _theme;
-
- XSLFSlideMaster() {
- super();
- _slide = CTSlideMaster.Factory.newInstance();
- }
/**
* @since POI 3.14-Beta1
@@ -142,23 +131,9 @@ import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
}
- @Override
- public XSLFTheme getTheme(){
- if(_theme == null){
- for (POIXMLDocumentPart p : getRelations()) {
- if (p instanceof XSLFTheme){
- _theme = (XSLFTheme)p;
- CTColorMapping cmap = _slide.getClrMap();
- if(cmap != null){
- _theme.initColorMap(cmap);
- }
- break;
- }
- }
- }
- return _theme;
- }
+
+ @SuppressWarnings(value = "unused")
protected CTTextListStyle getTextProperties(Placeholder textType) {
CTTextListStyle props;
CTSlideMasterTextStyles txStyles = getXmlObject().getTxStyles();
@@ -183,15 +158,8 @@ import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
*
*/
@Override
- protected boolean canDraw(XSLFShape shape){
- if(shape instanceof XSLFSimpleShape){
- XSLFSimpleShape txt = (XSLFSimpleShape)shape;
- CTPlaceholder ph = txt.getCTPlaceholder();
- if(ph != null) {
- return false;
- }
- }
- return true;
+ protected boolean canDraw(XSLFShape shape) {
+ return !(shape instanceof XSLFSimpleShape) || !shape.isPlaceholder();
}
@Override
@@ -203,4 +171,14 @@ import org.openxmlformats.schemas.presentationml.x2006.main.SldMasterDocument;
return null;
}
}
+
+ @Override
+ boolean isSupportTheme() {
+ return true;
+ }
+
+ @Override
+ CTColorMapping getColorMapping() {
+ return _slide.getClrMap();
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java
index a51bc79f3..9d2f71470 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java
@@ -20,6 +20,9 @@ import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.AutoNumberingScheme;
@@ -49,6 +52,12 @@ public class XSLFTextParagraph implements TextParagraph _runs;
private final XSLFTextShape _shape;
+ @FunctionalInterface
+ private interface Procedure {
+ void accept();
+ }
+
+
XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape){
_p = p;
_runs = new ArrayList<>();
@@ -79,14 +88,6 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
public boolean fetch(CTTextParagraphProperties props){
@@ -214,6 +217,7 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
public boolean fetch(CTTextParagraphProperties props){
@@ -237,6 +242,7 @@ public class XSLFTextParagraph implements TextParagraphnull value means to use the text font color.
*/
+ @SuppressWarnings("WeakerAccess")
public PaintStyle getBulletFontColor(){
final XSLFTheme theme = getParentShape().getSheet().getTheme();
ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
@@ -265,6 +272,7 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
public boolean fetch(CTTextParagraphProperties props){
@@ -326,6 +336,7 @@ public class XSLFTextParagraph implements TextParagraph
*/
+ @SuppressWarnings("WeakerAccess")
public void setBulletFontSize(double bulletSize){
CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
@@ -343,6 +354,7 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()) {
public boolean fetch(CTTextParagraphProperties props) {
@@ -363,6 +375,7 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()) {
public boolean fetch(CTTextParagraphProperties props) {
@@ -487,10 +500,11 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
public boolean fetch(CTTextParagraphProperties props){
- if(props.isSetTabLst()){
+ if (props.isSetTabLst()) {
CTTextTabStopList tabStops = props.getTabLst();
if(idx < tabStops.sizeOfTabArray() ) {
CTTextTabStop ts = tabStops.getTabArray(idx);
@@ -506,6 +520,7 @@ public class XSLFTextParagraph implements TextParagraph= 0) {
- (spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct()).setVal((int)(lineSpacing*1000));
- if (spc.isSetSpcPts()) spc.unsetSpcPts();
- } else {
- (spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts()).setVal((int)(-lineSpacing*100));
- if (spc.isSetSpcPct()) spc.unsetSpcPct();
- }
- }
+ setSpacing(lineSpacing, props -> props::getLnSpc, props -> props::addNewLnSpc, props -> props::unsetLnSpc);
}
@Override
public Double getLineSpacing(){
- ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
- public boolean fetch(CTTextParagraphProperties props){
- if(props.isSetLnSpc()){
- CTTextSpacing spc = props.getLnSpc();
-
- if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
- else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
- return true;
- }
- return false;
- }
- };
- fetchParagraphProperty(fetcher);
-
- Double lnSpc = fetcher.getValue();
+ final Double lnSpc = getSpacing(props -> props::getLnSpc);
if (lnSpc != null && lnSpc > 0) {
// check if the percentage value is scaled
- CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit();
- if(normAutofit != null) {
- double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000;
- lnSpc *= scale;
+ final CTTextNormalAutofit normAutofit = getParentShape().getTextBodyPr().getNormAutofit();
+ if (normAutofit != null) {
+ final double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000;
+ return lnSpc * scale;
}
}
@@ -561,84 +549,82 @@ public class XSLFTextParagraph implements TextParagraph= 0) {
- spc.addNewSpcPct().setVal((int)(spaceBefore*1000));
- } else {
- spc.addNewSpcPts().setVal((int)(-spaceBefore*100));
- }
- pr.setSpcBef(spc);
+ setSpacing(spaceBefore, props -> props::getSpcBef, props -> props::addNewSpcBef, props -> props::unsetSpcBef);
}
@Override
public Double getSpaceBefore(){
- ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
- public boolean fetch(CTTextParagraphProperties props){
- if(props.isSetSpcBef()){
- CTTextSpacing spc = props.getSpcBef();
-
- if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
- else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
- return true;
- }
- return false;
- }
- };
- fetchParagraphProperty(fetcher);
-
- return fetcher.getValue();
+ return getSpacing(props -> props::getSpcBef);
}
@Override
public void setSpaceAfter(Double spaceAfter){
- if (spaceAfter == null && !_p.isSetPPr()) {
+ setSpacing(spaceAfter, props -> props::getSpcAft, props -> props::addNewSpcAft, props -> props::unsetSpcAft);
+ }
+
+ @Override
+ public Double getSpaceAfter() {
+ return getSpacing(props -> props::getSpcAft);
+ }
+
+ private void setSpacing(final Double space,
+ final Function> getSpc,
+ final Function> addSpc,
+ final Function unsetSpc
+ ) {
+ final CTTextParagraphProperties pPr = (space == null || _p.isSetPPr()) ? _p.getPPr() : _p.addNewPPr();
+ if (pPr == null) {
return;
}
- // unset the space before on null input
- if (spaceAfter == null) {
- if(_p.getPPr().isSetSpcAft()) {
- _p.getPPr().unsetSpcAft();
+ CTTextSpacing spc = getSpc.apply(pPr).get();
+
+ if (space == null) {
+ if (spc != null) {
+ // unset the space before on null input
+ unsetSpc.apply(pPr).accept();
}
return;
}
- CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
- CTTextSpacing spc = CTTextSpacing.Factory.newInstance();
-
- if(spaceAfter >= 0) {
- spc.addNewSpcPct().setVal((int)(spaceAfter*1000));
- } else {
- spc.addNewSpcPts().setVal((int)(-spaceAfter*100));
+ if (spc == null) {
+ spc = addSpc.apply(pPr).get();
+ }
+
+ if (space >= 0) {
+ if (spc.isSetSpcPts()) {
+ spc.unsetSpcPts();
+ }
+ final CTTextSpacingPercent pct = spc.isSetSpcPct() ? spc.getSpcPct() : spc.addNewSpcPct();
+ pct.setVal((int)(space*1000));
+ } else {
+ if (spc.isSetSpcPct()) {
+ spc.unsetSpcPct();
+ }
+ final CTTextSpacingPoint pts = spc.isSetSpcPts() ? spc.getSpcPts() : spc.addNewSpcPts();
+ pts.setVal((int)(-space*100));
}
- pr.setSpcAft(spc);
}
- @Override
- public Double getSpaceAfter(){
- ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
- public boolean fetch(CTTextParagraphProperties props){
- if(props.isSetSpcAft()){
- CTTextSpacing spc = props.getSpcAft();
+ private Double getSpacing(final Function> getSpc) {
+ final ParagraphPropertyFetcher fetcher = new ParagraphPropertyFetcher(getIndentLevel()){
+ public boolean fetch(final CTTextParagraphProperties props){
+ final CTTextSpacing spc = getSpc.apply(props).get();
- if(spc.isSetSpcPct()) setValue( spc.getSpcPct().getVal()*0.001 );
- else if (spc.isSetSpcPts()) setValue( -spc.getSpcPts().getVal()*0.01 );
+ if (spc == null) {
+ return false;
+ }
+
+ if (spc.isSetSpcPct()) {
+ setValue( spc.getSpcPct().getVal()*0.001 );
return true;
}
+
+ if (spc.isSetSpcPts()) {
+ setValue( -spc.getSpcPts().getVal()*0.01 );
+ return true;
+ }
+
return false;
}
};
@@ -713,6 +699,7 @@ public class XSLFTextParagraph implements TextParagraph boolean fetchParagraphProperty(ParagraphPropertyFetcher visitor){
- boolean ok = false;
+ private void fetchParagraphProperty(final ParagraphPropertyFetcher> visitor){
final XSLFTextShape shape = getParentShape();
final XSLFSheet sheet = shape.getSheet();
if (!(sheet instanceof XSLFSlideMaster)) {
- if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr());
- if (ok) return true;
-
- ok = shape.fetchShapeProperty(visitor);
- if (ok) return true;
-
-
- CTPlaceholder ph = shape.getCTPlaceholder();
- if(ph == null){
- // if it is a plain text box then take defaults from presentation.xml
- @SuppressWarnings("resource")
- XMLSlideShow ppt = sheet.getSlideShow();
- CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel());
- if (themeProps != null) ok = visitor.fetch(themeProps);
+ if (_p.isSetPPr() && visitor.fetch(_p.getPPr())) {
+ return;
+ }
+
+ if (shape.fetchShapeProperty(visitor)) {
+ return;
+ }
+
+ if (fetchThemeProperty(visitor)) {
+ return;
}
- if (ok) return true;
}
- // defaults for placeholders are defined in the slide master
- CTTextParagraphProperties defaultProps = getDefaultMasterStyle();
- // TODO: determine master shape
- if(defaultProps != null) ok = visitor.fetch(defaultProps);
- if (ok) return true;
+ fetchMasterProperty(visitor);
+ }
- return false;
+ boolean fetchMasterProperty(final ParagraphPropertyFetcher> visitor) {
+ // defaults for placeholders are defined in the slide master
+ final CTTextParagraphProperties defaultProps = getDefaultMasterStyle();
+ // TODO: determine master shape
+ return defaultProps != null && visitor.fetch(defaultProps);
+ }
+
+ boolean fetchThemeProperty(final ParagraphPropertyFetcher> visitor) {
+ final XSLFTextShape shape = getParentShape();
+
+ if (shape.isPlaceholder()) {
+ return false;
+ }
+
+ // if it is a plain text box then take defaults from presentation.xml
+ @SuppressWarnings("resource")
+ final XMLSlideShow ppt = shape.getSheet().getSlideShow();
+ final CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel());
+ return themeProps != null && visitor.fetch(themeProps);
}
void copy(XSLFTextParagraph other){
@@ -873,40 +869,40 @@ public class XSLFTextParagraph implements TextParagraph fetcher = new CharacterPropertyFetcher(_p.getIndentLevel()){
@@ -255,6 +222,7 @@ public class XSLFTextRun implements TextRun {
*
* @param spc character spacing in points.
*/
+ @SuppressWarnings("WeakerAccess")
public void setCharacterSpacing(double spc){
CTTextCharacterProperties rPr = getRPr(true);
if(spc == 0.0) {
@@ -357,9 +325,8 @@ public class XSLFTextRun implements TextRun {
* The size is specified using a percentage.
* Positive values indicate superscript, negative values indicate subscript.
*
- *
- * @param baselineOffset
*/
+ @SuppressWarnings("WeakerAccess")
public void setBaselineOffset(double baselineOffset){
getRPr(true).setBaseline((int) baselineOffset * 1000);
}
@@ -370,6 +337,7 @@ public class XSLFTextRun implements TextRun {
*
* @see #setBaselineOffset(double)
*/
+ @SuppressWarnings("WeakerAccess")
public void setSuperscript(boolean flag){
setBaselineOffset(flag ? 30. : 0.);
}
@@ -380,6 +348,7 @@ public class XSLFTextRun implements TextRun {
*
* @see #setBaselineOffset(double)
*/
+ @SuppressWarnings("WeakerAccess")
public void setSubscript(boolean flag){
setBaselineOffset(flag ? -25.0 : 0.);
}
@@ -544,38 +513,23 @@ public class XSLFTextRun implements TextRun {
return new XSLFHyperlink(hl, _p.getParentShape().getSheet());
}
- private boolean fetchCharacterProperty(CharacterPropertyFetcher> fetcher){
+ private void fetchCharacterProperty(final CharacterPropertyFetcher> visitor){
XSLFTextShape shape = _p.getParentShape();
- XSLFSheet sheet = shape.getSheet();
CTTextCharacterProperties rPr = getRPr(false);
- if (rPr != null && fetcher.fetch(rPr)) {
- return true;
+ if (rPr != null && visitor.fetch(rPr)) {
+ return;
}
- if (shape.fetchShapeProperty(fetcher)) {
- return true;
+ if (shape.fetchShapeProperty(visitor)) {
+ return;
}
- CTPlaceholder ph = shape.getCTPlaceholder();
- if (ph == null){
- // if it is a plain text box then take defaults from presentation.xml
- @SuppressWarnings("resource")
- XMLSlideShow ppt = sheet.getSlideShow();
- // TODO: determine master shape
- CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(_p.getIndentLevel());
- if (themeProps != null && fetcher.fetch(themeProps)) {
- return true;
- }
+ if (_p.fetchThemeProperty(visitor)) {
+ return;
}
- // TODO: determine master shape
- CTTextParagraphProperties defaultProps = _p.getDefaultMasterStyle();
- if(defaultProps != null && fetcher.fetch(defaultProps)) {
- return true;
- }
-
- return false;
+ _p.fetchMasterProperty(visitor);
}
void copy(XSLFTextRun r){
@@ -630,14 +584,14 @@ public class XSLFTextRun implements TextRun {
}
- private class XSLFFontInfo implements FontInfo {
+ private final class XSLFFontInfo implements FontInfo {
private final FontGroup fontGroup;
private XSLFFontInfo(FontGroup fontGroup) {
this.fontGroup = (fontGroup != null) ? fontGroup : FontGroup.getFontGroupFirst(getRawText());
}
- public void copyFrom(FontInfo fontInfo) {
+ void copyFrom(FontInfo fontInfo) {
CTTextFont tf = getXmlObject(true);
setTypeface(fontInfo.getTypeface());
setCharset(fontInfo.getCharset());
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java
index b6787a9ea..4904d0c73 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java
@@ -590,13 +590,7 @@ public abstract class XSLFTextShape extends XSLFSimpleShape
}
public Placeholder getTextType(){
- CTPlaceholder ph = getCTPlaceholder();
- if (ph == null) {
- return null;
- }
-
- int val = ph.getType().intValue();
- return Placeholder.lookupOoxml(val);
+ return getPlaceholder();
}
@Override
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java
index 2864e3903..2d70d352f 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXMLSlideShow.java
@@ -19,6 +19,7 @@ package org.apache.poi.xslf.usermodel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -51,7 +52,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
}
@After
- public void tearDown() throws IOException {
+ public void tearDown() {
pack.revert();
}
@@ -149,7 +150,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
assertEquals(null, xml.getCommentAuthors());
for (XSLFSlide slide : xml.getSlides()) {
- assertEquals(null, slide.getComments());
+ assertTrue(slide.getComments().isEmpty());
}
// Try another with comments
@@ -166,17 +167,18 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
i++;
if(i == 0) {
- assertNotNull(slide.getComments());
- assertEquals(1, slide.getComments().getNumberOfComments());
- assertEquals("testdoc", slide.getComments().getCommentAt(0).getText());
- assertEquals(0, slide.getComments().getCommentAt(0).getAuthorId());
+ assertNotNull(slide.getCommentsPart());
+ assertEquals(1, slide.getCommentsPart().getNumberOfComments());
+ assertEquals("testdoc", slide.getCommentsPart().getCommentAt(0).getText());
+ assertEquals(0, slide.getCommentsPart().getCommentAt(0).getAuthorId());
} else if (i == 1) {
assertNotNull(slide.getComments());
- assertEquals(1, slide.getComments().getNumberOfComments());
- assertEquals("test phrase", slide.getComments().getCommentAt(0).getText());
- assertEquals(0, slide.getComments().getCommentAt(0).getAuthorId());
+ assertEquals(1, slide.getCommentsPart().getNumberOfComments());
+ assertEquals("test phrase", slide.getCommentsPart().getCommentAt(0).getText());
+ assertEquals(0, slide.getCommentsPart().getCommentAt(0).getAuthorId());
} else {
- assertEquals(null, slide.getComments());
+ assertNull(slide.getCommentsPart());
+ assertTrue(slide.getComments().isEmpty());
}
}
@@ -188,7 +190,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
return reopen((XMLSlideShow)show);
}
- public static XMLSlideShow reopen(XMLSlideShow show) {
+ private static XMLSlideShow reopen(XMLSlideShow show) {
try {
BufAccessBAOS bos = new BufAccessBAOS();
show.write(bos);
@@ -200,7 +202,7 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
}
private static class BufAccessBAOS extends ByteArrayOutputStream {
- public byte[] getBuf() {
+ byte[] getBuf() {
return buf;
}
}
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java
index 9c0c1b6b5..8e77f59f2 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;
import java.awt.Color;
import java.io.IOException;
+import org.apache.poi.sl.draw.DrawTextParagraph;
import org.junit.Test;
/**
@@ -65,17 +66,17 @@ public class TestXSLFTextRun {
r.setFontSize(13.0);
assertEquals(13.0, r.getFontSize(), 0);
- assertEquals(false, r.isSuperscript());
+ assertFalse(r.isSuperscript());
r.setSuperscript(true);
- assertEquals(true, r.isSuperscript());
+ assertTrue(r.isSuperscript());
r.setSuperscript(false);
- assertEquals(false, r.isSuperscript());
+ assertFalse(r.isSuperscript());
- assertEquals(false, r.isSubscript());
+ assertFalse(r.isSubscript());
r.setSubscript(true);
- assertEquals(true, r.isSubscript());
+ assertTrue(r.isSubscript());
r.setSubscript(false);
- assertEquals(false, r.isSubscript());
+ assertFalse(r.isSubscript());
ppt.close();
}
@@ -94,8 +95,11 @@ public class TestXSLFTextRun {
try (XMLSlideShow ppt = new XMLSlideShow()) {
XSLFSlide slide = ppt.createSlide();
XSLFTextShape sh = slide.createAutoShape();
- XSLFTextRun r = sh.addNewTextParagraph().addNewTextRun();
- assertEquals(unicodeSurrogates, r.getRenderableText(unicodeSurrogates));
+ XSLFTextParagraph p = sh.addNewTextParagraph();
+ XSLFTextRun r = p.addNewTextRun();
+ r.setText(unicodeSurrogates);
+
+ assertEquals(unicodeSurrogates, new DrawTextParagraph(p).getRenderableText(r));
}
}
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java
index 8856a2a64..49dc0a3bf 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextShape.java
@@ -83,7 +83,7 @@ public class TestXSLFTextShape {
assertEquals("Title Slide",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.CTR_TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -113,7 +113,7 @@ public class TestXSLFTextShape {
assertTrue(sameColor(Color.black, r1.getFontColor()));
XSLFTextShape shape2 = (XSLFTextShape)shapes.get(1);
- CTPlaceholder ph2 = shape2.getCTPlaceholder();
+ CTPlaceholder ph2 = shape2.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.SUB_TITLE, ph2.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape2).getXfrm());
@@ -149,7 +149,7 @@ public class TestXSLFTextShape {
assertEquals("Title and Content",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -182,7 +182,7 @@ public class TestXSLFTextShape {
assertTrue(sameColor(Color.black, r1.getFontColor()));
XSLFTextShape shape2 = (XSLFTextShape)shapes.get(1);
- CTPlaceholder ph2 = shape2.getCTPlaceholder();
+ CTPlaceholder ph2 = shape2.getPlaceholderDetails().getCTPlaceholder(false);
assertFalse(ph2.isSetType()); //
assertTrue(ph2.isSetIdx());
assertEquals(1, ph2.getIdx());
@@ -262,7 +262,7 @@ public class TestXSLFTextShape {
assertEquals("Section Header",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -296,7 +296,7 @@ public class TestXSLFTextShape {
assertFalse(r1.isUnderlined());
XSLFTextShape shape2 = (XSLFTextShape)shapes.get(1);
- CTPlaceholder ph2 = shape2.getCTPlaceholder();
+ CTPlaceholder ph2 = shape2.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.BODY, ph2.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape2).getXfrm());
@@ -333,7 +333,7 @@ public class TestXSLFTextShape {
assertEquals("Two Content",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -367,7 +367,7 @@ public class TestXSLFTextShape {
assertTrue(sameColor(Color.black, r1.getFontColor()));
XSLFTextShape shape2 = (XSLFTextShape)shapes.get(1);
- CTPlaceholder ph2 = shape2.getCTPlaceholder();
+ CTPlaceholder ph2 = shape2.getPlaceholderDetails().getCTPlaceholder(false);
assertFalse(ph2.isSetType());
assertTrue(ph2.isSetIdx());
assertEquals(1, ph2.getIdx()); //
@@ -448,7 +448,7 @@ public class TestXSLFTextShape {
assertEquals("Blank",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -516,7 +516,7 @@ public class TestXSLFTextShape {
assertEquals("Content with Caption",layout.getName());
XSLFTextShape shape1 = (XSLFTextShape)shapes.get(0);
- CTPlaceholder ph1 = shape1.getCTPlaceholder();
+ CTPlaceholder ph1 = shape1.getPlaceholderDetails().getCTPlaceholder(false);
assertEquals(STPlaceholderType.TITLE, ph1.getType());
// anchor is not defined in the shape
assertNull(getSpPr(shape1).getXfrm());
@@ -549,7 +549,7 @@ public class TestXSLFTextShape {
assertTrue(r1.isBold());
XSLFTextShape shape2 = (XSLFTextShape)shapes.get(1);
- CTPlaceholder ph2 = shape2.getCTPlaceholder();
+ CTPlaceholder ph2 = shape2.getPlaceholderDetails().getCTPlaceholder(false);
assertFalse(ph2.isSetType());
assertTrue(ph2.isSetIdx());
assertEquals(1, ph2.getIdx());
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 c34080874..156747eac 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/extractor/PowerPointExtractor.java
@@ -20,47 +20,36 @@ package org.apache.poi.hslf.extractor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import org.apache.poi.POIOLE2TextExtractor;
-import org.apache.poi.hslf.model.Comment;
-import org.apache.poi.hslf.model.HSLFMetroShape;
-import org.apache.poi.hslf.model.HeadersFooters;
-import org.apache.poi.hslf.usermodel.HSLFMasterSheet;
-import org.apache.poi.hslf.usermodel.HSLFNotes;
+import org.apache.poi.hslf.usermodel.HSLFObjectShape;
import org.apache.poi.hslf.usermodel.HSLFShape;
-import org.apache.poi.hslf.usermodel.HSLFSlide;
-import org.apache.poi.hslf.usermodel.HSLFSlideMaster;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
-import org.apache.poi.hslf.usermodel.HSLFTable;
-import org.apache.poi.hslf.usermodel.HSLFTableCell;
import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
-import org.apache.poi.hslf.usermodel.HSLFTextShape;
-import org.apache.poi.hslf.usermodel.HSLFObjectShape;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
+import org.apache.poi.sl.extractor.SlideShowExtractor;
+import org.apache.poi.sl.usermodel.SlideShowFactory;
/**
* This class can be used to extract text from a PowerPoint file. Can optionally
* also get the notes from one.
+ *
+ * @deprecated in POI 4.0.0, use {@link SlideShowExtractor} instead
*/
+@SuppressWarnings("WeakerAccess")
+@Deprecated
public final class PowerPointExtractor extends POIOLE2TextExtractor {
- private static final POILogger LOG = POILogFactory.getLogger(PowerPointExtractor.class);
-
- private final HSLFSlideShow _show;
- private final List _slides;
+ private final SlideShowExtractor delegate;
- private boolean _slidesByDefault = true;
- private boolean _notesByDefault;
- private boolean _commentsByDefault;
- private boolean _masterByDefault;
+ private boolean slidesByDefault = true;
+ private boolean notesByDefault;
+ private boolean commentsByDefault;
+ private boolean masterByDefault;
/**
* Basic extractor. Returns all the text, and optionally all the notes
@@ -92,13 +81,19 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
ppe.close();
}
+ public PowerPointExtractor(final HSLFSlideShow slideShow) {
+ super(slideShow.getSlideShowImpl());
+ setFilesystem(slideShow);
+ delegate = new SlideShowExtractor<>(slideShow);
+ }
+
/**
* Creates a PowerPointExtractor, from a file
*
* @param fileName The name of the file to extract from
*/
public PowerPointExtractor(String fileName) throws IOException {
- this(new NPOIFSFileSystem(new File(fileName)));
+ this((HSLFSlideShow)SlideShowFactory.create(new File(fileName), Biff8EncryptionKey.getCurrentUserPassword(), true));
}
/**
@@ -107,7 +102,7 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param iStream The input stream containing the PowerPoint document
*/
public PowerPointExtractor(InputStream iStream) throws IOException {
- this(new POIFSFileSystem(iStream));
+ this((HSLFSlideShow)SlideShowFactory.create(iStream, Biff8EncryptionKey.getCurrentUserPassword()));
}
/**
@@ -116,7 +111,7 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param fs the POIFSFileSystem containing the PowerPoint document
*/
public PowerPointExtractor(POIFSFileSystem fs) throws IOException {
- this(fs.getRoot());
+ this((HSLFSlideShow)SlideShowFactory.create(fs, Biff8EncryptionKey.getCurrentUserPassword()));
}
/**
@@ -125,8 +120,7 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param fs the NPOIFSFileSystem containing the PowerPoint document
*/
public PowerPointExtractor(NPOIFSFileSystem fs) throws IOException {
- this(fs.getRoot());
- setFilesystem(fs);
+ this((HSLFSlideShow)SlideShowFactory.create(fs, Biff8EncryptionKey.getCurrentUserPassword()));
}
/**
@@ -136,7 +130,7 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param dir the POIFS Directory containing the PowerPoint document
*/
public PowerPointExtractor(DirectoryNode dir) throws IOException {
- this(new HSLFSlideShowImpl(dir));
+ this(new HSLFSlideShow(dir));
}
/**
@@ -145,37 +139,39 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param ss the HSLFSlideShow to extract text from
*/
public PowerPointExtractor(HSLFSlideShowImpl ss) {
- super(ss);
- _show = new HSLFSlideShow(ss);
- _slides = _show.getSlides();
+ this(new HSLFSlideShow(ss));
}
/**
* Should a call to getText() return slide text? Default is yes
*/
- public void setSlidesByDefault(boolean slidesByDefault) {
- this._slidesByDefault = slidesByDefault;
+ public void setSlidesByDefault(final boolean slidesByDefault) {
+ this.slidesByDefault = slidesByDefault;
+ delegate.setSlidesByDefault(slidesByDefault);
}
/**
* Should a call to getText() return notes text? Default is no
*/
- public void setNotesByDefault(boolean notesByDefault) {
- this._notesByDefault = notesByDefault;
+ public void setNotesByDefault(final boolean notesByDefault) {
+ this.notesByDefault = notesByDefault;
+ delegate.setNotesByDefault(notesByDefault);
}
/**
* Should a call to getText() return comments text? Default is no
*/
- public void setCommentsByDefault(boolean commentsByDefault) {
- this._commentsByDefault = commentsByDefault;
+ public void setCommentsByDefault(final boolean commentsByDefault) {
+ this.commentsByDefault = commentsByDefault;
+ delegate.setCommentsByDefault(commentsByDefault);
}
/**
* Should a call to getText() return text from master? Default is no
*/
- public void setMasterByDefault(boolean masterByDefault) {
- this._masterByDefault = masterByDefault;
+ public void setMasterByDefault(final boolean masterByDefault) {
+ this.masterByDefault = masterByDefault;
+ delegate.setMasterByDefault(masterByDefault);
}
/**
@@ -184,28 +180,7 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
*/
@Override
public String getText() {
- return getText(_slidesByDefault, _notesByDefault, _commentsByDefault, _masterByDefault);
- }
-
- /**
- * Fetches all the notes text from the slideshow, but not the slide text
- */
- public String getNotes() {
- return getText(false, true);
- }
-
- public List getOLEShapes() {
- List list = new ArrayList<>();
-
- for (HSLFSlide slide : _slides) {
- for (HSLFShape shape : slide.getShapes()) {
- if (shape instanceof HSLFObjectShape) {
- list.add((HSLFObjectShape) shape);
- }
- }
- }
-
- return list;
+ return delegate.getText();
}
/**
@@ -217,159 +192,33 @@ public final class PowerPointExtractor extends POIOLE2TextExtractor {
* @param getNoteText fetch note text
*/
public String getText(boolean getSlideText, boolean getNoteText) {
- return getText(getSlideText, getNoteText, _commentsByDefault, _masterByDefault);
+ return getText(getSlideText,getNoteText,commentsByDefault,masterByDefault);
}
public String getText(boolean getSlideText, boolean getNoteText, boolean getCommentText, boolean getMasterText) {
- StringBuffer ret = new StringBuffer();
-
- if (getSlideText) {
- if (getMasterText) {
- for (HSLFSlideMaster master : _show.getSlideMasters()) {
- for(HSLFShape sh : master.getShapes()){
- if(sh instanceof HSLFTextShape){
- HSLFTextShape hsh = (HSLFTextShape)sh;
- final String text = hsh.getText();
- if (text == null || text.isEmpty() || "*".equals(text)) {
- continue;
- }
-
- if (HSLFMasterSheet.isPlaceholder(sh)) {
- // check for metro shape of complex placeholder
- boolean isMetro = new HSLFMetroShape(sh).hasMetroBlob();
-
- if (!isMetro) {
- // don't bother about boiler plate text on master sheets
- LOG.log(POILogger.INFO, "Ignoring boiler plate (placeholder) text on slide master:", text);
- continue;
- }
- }
-
- ret.append(text);
- if (!text.endsWith("\n")) {
- ret.append("\n");
- }
- }
- }
- }
- }
-
- for (HSLFSlide slide : _slides) {
- String headerText = "";
- String footerText = "";
- HeadersFooters hf = slide.getHeadersFooters();
- if (hf != null) {
- if (hf.isHeaderVisible()) {
- headerText = safeLine(hf.getHeaderText());
- }
- if (hf.isFooterVisible()) {
- footerText = safeLine(hf.getFooterText());
- }
- }
-
- // Slide header, if set
- ret.append(headerText);
-
- // Slide text
- textRunsToText(ret, slide.getTextParagraphs());
-
- // Table text
- for (HSLFShape shape : slide.getShapes()){
- if (shape instanceof HSLFTable){
- extractTableText(ret, (HSLFTable)shape);
- }
- }
- // Slide footer, if set
- ret.append(footerText);
-
- // Comments, if requested and present
- if (getCommentText) {
- for (Comment comment : slide.getComments()) {
- ret.append(comment.getAuthor() + " - " + comment.getText() + "\n");
- }
- }
- }
- if (getNoteText) {
- ret.append('\n');
- }
+ delegate.setSlidesByDefault(getSlideText);
+ delegate.setNotesByDefault(getNoteText);
+ delegate.setCommentsByDefault(getCommentText);
+ delegate.setMasterByDefault(getMasterText);
+ try {
+ return delegate.getText();
+ } finally {
+ delegate.setSlidesByDefault(slidesByDefault);
+ delegate.setNotesByDefault(notesByDefault);
+ delegate.setCommentsByDefault(commentsByDefault);
+ delegate.setMasterByDefault(masterByDefault);
}
-
- if (getNoteText) {
- // Not currently using _notes, as that can have the notes of
- // master sheets in. Grab Slide list, then work from there,
- // but ensure no duplicates
- Set seenNotes = new HashSet<>();
- String headerText = "";
- String footerText = "";
- HeadersFooters hf = _show.getNotesHeadersFooters();
- if (hf != null) {
- if (hf.isHeaderVisible()) {
- headerText = safeLine(hf.getHeaderText());
- }
- if (hf.isFooterVisible()) {
- footerText = safeLine(hf.getFooterText());
- }
- }
-
-
- for (HSLFSlide slide : _slides) {
- HSLFNotes notes = slide.getNotes();
- if (notes == null) {
- continue;
- }
- Integer id = Integer.valueOf(notes._getSheetNumber());
- if (seenNotes.contains(id)) {
- continue;
- }
- seenNotes.add(id);
-
- // Repeat the Notes header, if set
- ret.append(headerText);
-
- // Notes text
- textRunsToText(ret, notes.getTextParagraphs());
-
- // Repeat the notes footer, if set
- ret.append(footerText);
- }
- }
-
- return ret.toString();
- }
-
- private static String safeLine(String text) {
- return (text == null) ? "" : (text+'\n');
}
- private void extractTableText(StringBuffer ret, HSLFTable table) {
- final int nrows = table.getNumberOfRows();
- final int ncols = table.getNumberOfColumns();
- for (int row = 0; row < nrows; row++){
- for (int col = 0; col < ncols; col++){
- HSLFTableCell cell = table.getCell(row, col);
- //defensive null checks; don't know if they're necessary
- if (cell != null){
- String txt = cell.getText();
- txt = (txt == null) ? "" : txt;
- ret.append(txt);
- if (col < ncols-1){
- ret.append('\t');
- }
- }
- }
- ret.append('\n');
- }
- }
- private void textRunsToText(StringBuffer ret, List> paragraphs) {
- if (paragraphs==null) {
- return;
- }
+ /**
+ * Fetches all the notes text from the slideshow, but not the slide text
+ */
+ public String getNotes() {
+ return getText(false, true, false, false);
+ }
- for (List lp : paragraphs) {
- ret.append(HSLFTextParagraph.getText(lp));
- if (ret.length() > 0 && ret.charAt(ret.length()-1) != '\n') {
- ret.append('\n');
- }
- }
- }
+ @SuppressWarnings("unchecked")
+ public List getOLEShapes() {
+ return (List)delegate.getOLEShapes();
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Comment.java b/src/scratchpad/src/org/apache/poi/hslf/model/Comment.java
deleted file mode 100644
index 27afa47c8..000000000
--- a/src/scratchpad/src/org/apache/poi/hslf/model/Comment.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/* ====================================================================
- 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.hslf.model;
-
-import org.apache.poi.hslf.record.Comment2000;
-
-/**
- *
- * @author Nick Burch
- */
-public final class Comment {
- private Comment2000 _comment2000;
-
- public Comment(Comment2000 comment2000) {
- _comment2000 = comment2000;
- }
-
- protected Comment2000 getComment2000() {
- return _comment2000;
- }
-
- /**
- * Get the Author of this comment
- */
- public String getAuthor() {
- return _comment2000.getAuthor();
- }
- /**
- * Set the Author of this comment
- */
- public void setAuthor(String author) {
- _comment2000.setAuthor(author);
- }
-
- /**
- * Get the Author's Initials of this comment
- */
- public String getAuthorInitials() {
- return _comment2000.getAuthorInitials();
- }
- /**
- * Set the Author's Initials of this comment
- */
- public void setAuthorInitials(String initials) {
- _comment2000.setAuthorInitials(initials);
- }
-
- /**
- * Get the text of this comment
- */
- public String getText() {
- return _comment2000.getText();
- }
- /**
- * Set the text of this comment
- */
- public void setText(String text) {
- _comment2000.setText(text);
- }
-}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java
index ab96c7dbe..64b6dc6a3 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/model/HeadersFooters.java
@@ -275,4 +275,11 @@ public final class HeadersFooters {
private void setFlag(int type, boolean flag) {
_container.getHeadersFootersAtom().setFlag(type, flag);
}
+
+ /**
+ * @return true, if this is a ppt 2007 document and header/footer are stored as placeholder shapes
+ */
+ public boolean isPpt2007() {
+ return _ppt2007;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
index 57f0f31ed..488893cd7 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/DocumentEncryptionAtom.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
+import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionMode;
@@ -46,15 +47,17 @@ public final class DocumentEncryptionAtom extends PositionDependentRecordAtom {
/**
* For the Document Encryption Atom
*/
- protected DocumentEncryptionAtom(byte[] source, int start, int len) throws IOException {
+ protected DocumentEncryptionAtom(byte[] source, int start, int len) {
// Get the header
_header = new byte[8];
System.arraycopy(source,start,_header,0,8);
ByteArrayInputStream bis = new ByteArrayInputStream(source, start+8, len-8);
- LittleEndianInputStream leis = new LittleEndianInputStream(bis);
- ei = new EncryptionInfo(leis, EncryptionMode.cryptoAPI);
- leis.close();
+ try (LittleEndianInputStream leis = new LittleEndianInputStream(bis)) {
+ ei = new EncryptionInfo(leis, EncryptionMode.cryptoAPI);
+ } catch (IOException e) {
+ throw new EncryptedDocumentException(e);
+ }
}
public DocumentEncryptionAtom() {
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/HSLFEscherClientDataRecord.java b/src/scratchpad/src/org/apache/poi/hslf/record/HSLFEscherClientDataRecord.java
index 6bcfd2a9a..f6094950c 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/HSLFEscherClientDataRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/HSLFEscherClientDataRecord.java
@@ -20,7 +20,6 @@ package org.apache.poi.hslf.record;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import org.apache.poi.ddf.EscherClientDataRecord;
@@ -49,12 +48,7 @@ public class HSLFEscherClientDataRecord extends EscherClientDataRecord {
}
public void removeChild(Class extends Record> childClass) {
- Iterator iter = _childRecords.iterator();
- while (iter.hasNext()) {
- if (childClass.isInstance(iter.next())) {
- iter.remove();
- }
- }
+ _childRecords.removeIf(childClass::isInstance);
}
public void addChild(Record childRecord) {
@@ -109,8 +103,10 @@ public class HSLFEscherClientDataRecord extends EscherClientDataRecord {
_childRecords.clear();
int offset = 0;
while (offset < remainingData.length) {
- Record r = Record.buildRecordAtOffset(remainingData, offset);
- _childRecords.add(r);
+ final Record r = Record.buildRecordAtOffset(remainingData, offset);
+ if (r != null) {
+ _childRecords.add(r);
+ }
long rlen = LittleEndian.getUInt(remainingData,offset+4);
offset += 8 + rlen;
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java
index 97f2eb7d8..bec722cb8 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/Record.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/Record.java
@@ -24,6 +24,7 @@ import java.util.List;
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
import org.apache.poi.hslf.exceptions.HSLFException;
+import org.apache.poi.hslf.record.RecordTypes.RecordConstructor;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -122,15 +123,13 @@ public abstract class Record
// Abort if first record is of type 0000 and length FFFF,
// as that's a sign of a screwed up record
- if(pos == 0 && type == 0l && rleni == 0xffff) {
+ if(pos == 0 && type == 0L && rleni == 0xffff) {
throw new CorruptPowerPointFileException("Corrupt document - starts with record of type 0000 and length 0xFFFF");
}
Record r = createRecordForType(type,b,pos,8+rleni);
if(r != null) {
children.add(r);
- } else {
- // Record was horribly corrupt
}
pos += 8;
pos += rleni;
@@ -150,43 +149,32 @@ public abstract class Record
* passing in corrected lengths
*/
public static Record createRecordForType(long type, byte[] b, int start, int len) {
- Record toReturn = null;
-
- // Handle case of a corrupt last record, whose claimed length
- // would take us passed the end of the file
- if(start + len > b.length) {
- logger.log(POILogger.WARN, "Warning: Skipping record of type " + type + " at position " + start + " which claims to be longer than the file! (" + len + " vs " + (b.length-start) + ")");
- return null;
- }
-
// We use the RecordTypes class to provide us with the right
// class to use for a given type
// A spot of reflection gets us the (byte[],int,int) constructor
// From there, we instanciate the class
// Any special record handling occurs once we have the class
- Class extends Record> c = null;
+ RecordConstructor c = RecordTypes.forTypeID((short)type).recordConstructor;
+ if (c == null) {
+ // How odd. RecordTypes normally substitutes in
+ // a default handler class if it has heard of the record
+ // type but there's no support for it. Explicitly request
+ // that now
+ c = RecordTypes.UnknownRecordPlaceholder.recordConstructor;
+ }
+
+ final Record toReturn;
try {
- c = RecordTypes.forTypeID((short)type).handlingClass;
- if(c == null) {
- // How odd. RecordTypes normally substitutes in
- // a default handler class if it has heard of the record
- // type but there's no support for it. Explicitly request
- // that now
- c = RecordTypes.UnknownRecordPlaceholder.handlingClass;
+ toReturn = c.apply(b, start, len);
+ } catch(RuntimeException e) {
+ // Handle case of a corrupt last record, whose claimed length
+ // would take us passed the end of the file
+ if(start + len > b.length ) {
+ logger.log(POILogger.WARN, "Warning: Skipping record of type " + type + " at position " + start + " which claims to be longer than the file! (" + len + " vs " + (b.length-start) + ")");
+ return null;
}
- // Grab the right constructor
- java.lang.reflect.Constructor extends Record> con = c.getDeclaredConstructor(new Class[] { byte[].class, Integer.TYPE, Integer.TYPE });
- // Instantiate
- toReturn = con.newInstance(new Object[] { b, start, len });
- } catch(InstantiationException ie) {
- throw new HSLFException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + ie, ie);
- } catch(java.lang.reflect.InvocationTargetException ite) {
- throw new HSLFException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + ite + "\nCause was : " + ite.getCause(), ite);
- } catch(IllegalAccessException iae) {
- throw new HSLFException("Couldn't access the constructor for type with id " + type + " on class " + c + " : " + iae, iae);
- } catch(NoSuchMethodException nsme) {
- throw new HSLFException("Couldn't access the constructor for type with id " + type + " on class " + c + " : " + nsme, nsme);
+ throw new HSLFException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + e, e);
}
// Handling for special kinds of records follow
diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
index 3fec22b1a..233d54a67 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java
@@ -29,141 +29,141 @@ import java.util.Map;
*/
public enum RecordTypes {
Unknown(0,null),
- UnknownRecordPlaceholder(-1, UnknownRecordPlaceholder.class),
- Document(1000,Document.class),
- DocumentAtom(1001,DocumentAtom.class),
+ UnknownRecordPlaceholder(-1, UnknownRecordPlaceholder::new),
+ Document(1000,Document::new),
+ DocumentAtom(1001,DocumentAtom::new),
EndDocument(1002,null),
- Slide(1006,Slide.class),
- SlideAtom(1007,SlideAtom.class),
- Notes(1008,Notes.class),
- NotesAtom(1009,NotesAtom.class),
- Environment(1010,Environment.class),
- SlidePersistAtom(1011,SlidePersistAtom.class),
+ Slide(1006,Slide::new),
+ SlideAtom(1007,SlideAtom::new),
+ Notes(1008,Notes::new),
+ NotesAtom(1009,NotesAtom::new),
+ Environment(1010,Environment::new),
+ SlidePersistAtom(1011,SlidePersistAtom::new),
SSlideLayoutAtom(1015,null),
- MainMaster(1016,MainMaster.class),
- SSSlideInfoAtom(1017,SSSlideInfoAtom.class),
+ MainMaster(1016,MainMaster::new),
+ SSSlideInfoAtom(1017,SSSlideInfoAtom::new),
SlideViewInfo(1018,null),
GuideAtom(1019,null),
ViewInfo(1020,null),
ViewInfoAtom(1021,null),
SlideViewInfoAtom(1022,null),
- VBAInfo(1023,VBAInfoContainer.class),
- VBAInfoAtom(1024,VBAInfoAtom.class),
+ VBAInfo(1023,VBAInfoContainer::new),
+ VBAInfoAtom(1024,VBAInfoAtom::new),
SSDocInfoAtom(1025,null),
Summary(1026,null),
DocRoutingSlip(1030,null),
OutlineViewInfo(1031,null),
SorterViewInfo(1032,null),
- ExObjList(1033,ExObjList.class),
- ExObjListAtom(1034,ExObjListAtom.class),
- PPDrawingGroup(1035,PPDrawingGroup.class),
- PPDrawing(1036,PPDrawing.class),
+ ExObjList(1033,ExObjList::new),
+ ExObjListAtom(1034,ExObjListAtom::new),
+ PPDrawingGroup(1035,PPDrawingGroup::new),
+ PPDrawing(1036,PPDrawing::new),
NamedShows(1040,null),
NamedShow(1041,null),
NamedShowSlides(1042,null),
SheetProperties(1044,null),
RoundTripCustomTableStyles12Atom(1064,null),
- List(2000,DocInfoListContainer.class),
- FontCollection(2005,FontCollection.class),
+ List(2000,DocInfoListContainer::new),
+ FontCollection(2005,FontCollection::new),
BookmarkCollection(2019,null),
- SoundCollection(2020,SoundCollection.class),
+ SoundCollection(2020,SoundCollection::new),
SoundCollAtom(2021,null),
- Sound(2022,Sound.class),
- SoundData(2023,SoundData.class),
+ Sound(2022,Sound::new),
+ SoundData(2023,SoundData::new),
BookmarkSeedAtom(2025,null),
- ColorSchemeAtom(2032,ColorSchemeAtom.class),
- ExObjRefAtom(3009,ExObjRefAtom.class),
- OEPlaceholderAtom(3011,OEPlaceholderAtom.class),
+ ColorSchemeAtom(2032,ColorSchemeAtom::new),
+ ExObjRefAtom(3009,ExObjRefAtom::new),
+ OEPlaceholderAtom(3011,OEPlaceholderAtom::new),
GPopublicintAtom(3024,null),
GRatioAtom(3031,null),
- OutlineTextRefAtom(3998,OutlineTextRefAtom.class),
- TextHeaderAtom(3999,TextHeaderAtom.class),
- TextCharsAtom(4000,TextCharsAtom.class),
- StyleTextPropAtom(4001, StyleTextPropAtom.class),//0x0fa1 RT_StyleTextPropAtom
- MasterTextPropAtom(4002, MasterTextPropAtom.class),
- TxMasterStyleAtom(4003,TxMasterStyleAtom.class),
+ OutlineTextRefAtom(3998,OutlineTextRefAtom::new),
+ TextHeaderAtom(3999,TextHeaderAtom::new),
+ TextCharsAtom(4000,TextCharsAtom::new),
+ StyleTextPropAtom(4001, StyleTextPropAtom::new),//0x0fa1 RT_StyleTextPropAtom
+ MasterTextPropAtom(4002, MasterTextPropAtom::new),
+ TxMasterStyleAtom(4003,TxMasterStyleAtom::new),
TxCFStyleAtom(4004,null),
TxPFStyleAtom(4005,null),
- TextRulerAtom(4006,TextRulerAtom.class),
+ TextRulerAtom(4006,TextRulerAtom::new),
TextBookmarkAtom(4007,null),
- TextBytesAtom(4008,TextBytesAtom.class),
+ TextBytesAtom(4008,TextBytesAtom::new),
TxSIStyleAtom(4009,null),
- TextSpecInfoAtom(4010, TextSpecInfoAtom.class),
+ TextSpecInfoAtom(4010, TextSpecInfoAtom::new),
DefaultRulerAtom(4011,null),
- StyleTextProp9Atom(4012, StyleTextProp9Atom.class), //0x0FAC RT_StyleTextProp9Atom
- FontEntityAtom(4023,FontEntityAtom.class),
+ StyleTextProp9Atom(4012, StyleTextProp9Atom::new), //0x0FAC RT_StyleTextProp9Atom
+ FontEntityAtom(4023,FontEntityAtom::new),
FontEmbeddedData(4024,null),
- CString(4026,CString.class),
+ CString(4026,CString::new),
MetaFile(4033,null),
- ExOleObjAtom(4035,ExOleObjAtom.class),
+ ExOleObjAtom(4035,ExOleObjAtom::new),
SrKinsoku(4040,null),
- HandOut(4041,DummyPositionSensitiveRecordWithChildren.class),
- ExEmbed(4044,ExEmbed.class),
- ExEmbedAtom(4045,ExEmbedAtom.class),
+ HandOut(4041,DummyPositionSensitiveRecordWithChildren::new),
+ ExEmbed(4044,ExEmbed::new),
+ ExEmbedAtom(4045,ExEmbedAtom::new),
ExLink(4046,null),
BookmarkEntityAtom(4048,null),
ExLinkAtom(4049,null),
SrKinsokuAtom(4050,null),
- ExHyperlinkAtom(4051,ExHyperlinkAtom.class),
- ExHyperlink(4055,ExHyperlink.class),
+ ExHyperlinkAtom(4051,ExHyperlinkAtom::new),
+ ExHyperlink(4055,ExHyperlink::new),
SlideNumberMCAtom(4056,null),
- HeadersFooters(4057,HeadersFootersContainer.class),
- HeadersFootersAtom(4058,HeadersFootersAtom.class),
- TxInteractiveInfoAtom(4063,TxInteractiveInfoAtom.class),
+ HeadersFooters(4057,HeadersFootersContainer::new),
+ HeadersFootersAtom(4058,HeadersFootersAtom::new),
+ TxInteractiveInfoAtom(4063,TxInteractiveInfoAtom::new),
CharFormatAtom(4066,null),
ParaFormatAtom(4067,null),
RecolorInfoAtom(4071,null),
ExQuickTimeMovie(4074,null),
ExQuickTimeMovieData(4075,null),
- ExControl(4078,ExControl.class),
- SlideListWithText(4080,SlideListWithText.class),
- InteractiveInfo(4082,InteractiveInfo.class),
- InteractiveInfoAtom(4083,InteractiveInfoAtom.class),
- UserEditAtom(4085,UserEditAtom.class),
+ ExControl(4078,ExControl::new),
+ SlideListWithText(4080,SlideListWithText::new),
+ InteractiveInfo(4082,InteractiveInfo::new),
+ InteractiveInfoAtom(4083,InteractiveInfoAtom::new),
+ UserEditAtom(4085,UserEditAtom::new),
CurrentUserAtom(4086,null),
DateTimeMCAtom(4087,null),
GenericDateMCAtom(4088,null),
FooterMCAtom(4090,null),
- ExControlAtom(4091,ExControlAtom.class),
- ExMediaAtom(4100,ExMediaAtom.class),
- ExVideoContainer(4101,ExVideoContainer.class),
- ExAviMovie(4102,ExAviMovie.class),
- ExMCIMovie(4103,ExMCIMovie.class),
+ ExControlAtom(4091,ExControlAtom::new),
+ ExMediaAtom(4100,ExMediaAtom::new),
+ ExVideoContainer(4101,ExVideoContainer::new),
+ ExAviMovie(4102,ExAviMovie::new),
+ ExMCIMovie(4103,ExMCIMovie::new),
ExMIDIAudio(4109,null),
ExCDAudio(4110,null),
ExWAVAudioEmbedded(4111,null),
ExWAVAudioLink(4112,null),
- ExOleObjStg(4113,ExOleObjStg.class),
+ ExOleObjStg(4113,ExOleObjStg::new),
ExCDAudioAtom(4114,null),
ExWAVAudioEmbeddedAtom(4115,null),
- AnimationInfo(4116,AnimationInfo.class),
- AnimationInfoAtom(4081,AnimationInfoAtom.class),
+ AnimationInfo(4116,AnimationInfo::new),
+ AnimationInfoAtom(4081,AnimationInfoAtom::new),
RTFDateTimeMCAtom(4117,null),
- ProgTags(5000,DummyPositionSensitiveRecordWithChildren.class),
+ ProgTags(5000,DummyPositionSensitiveRecordWithChildren::new),
ProgStringTag(5001,null),
- ProgBinaryTag(5002,DummyPositionSensitiveRecordWithChildren.class),
- BinaryTagData(5003, BinaryTagDataBlob.class),//0x138b RT_BinaryTagDataBlob
+ ProgBinaryTag(5002,DummyPositionSensitiveRecordWithChildren::new),
+ BinaryTagData(5003, BinaryTagDataBlob::new),//0x138b RT_BinaryTagDataBlob
PrpublicintOptions(6000,null),
- PersistPtrFullBlock(6001,PersistPtrHolder.class),
- PersistPtrIncrementalBlock(6002,PersistPtrHolder.class),
+ PersistPtrFullBlock(6001,PersistPtrHolder::new),
+ PersistPtrIncrementalBlock(6002,PersistPtrHolder::new),
GScalingAtom(10001,null),
GRColorAtom(10002,null),
// Records ~12000 seem to be related to the Comments used in PPT 2000/XP
// (Comments in PPT97 are normal Escher text boxes)
- Comment2000(12000,Comment2000.class),
- Comment2000Atom(12001,Comment2000Atom.class),
+ Comment2000(12000,Comment2000::new),
+ Comment2000Atom(12001,Comment2000Atom::new),
Comment2000Summary(12004,null),
Comment2000SummaryAtom(12005,null),
// Records ~12050 seem to be related to Document Encryption
- DocumentEncryptionAtom(12052,DocumentEncryptionAtom.class),
+ DocumentEncryptionAtom(12052,DocumentEncryptionAtom::new),
OriginalMainMasterId(1052,null),
CompositeMasterId(1052,null),
RoundTripContentMasterInfo12(1054,null),
RoundTripShapeId12(1055,null),
- RoundTripHFPlaceholder12(1056,RoundTripHFPlaceholder12.class),
+ RoundTripHFPlaceholder12(1056,RoundTripHFPlaceholder12::new),
RoundTripContentMasterId(1058,null),
RoundTripOArtTextStyles12(1059,null),
RoundTripShapeCheckSumForCustomLayouts12(1062,null),
@@ -207,6 +207,11 @@ public enum RecordTypes {
// same as EscherTertiaryOptRecord.RECORD_ID
EscherUserDefined(0xf122,null);
+ @FunctionalInterface
+ public interface RecordConstructor {
+ T apply(byte[] source, int start, int len);
+ }
+
private static final Map LOOKUP;
static {
@@ -217,85 +222,15 @@ public enum RecordTypes {
}
public final short typeID;
- public final Class extends Record> handlingClass;
+ public final RecordConstructor recordConstructor;
- private RecordTypes(int typeID, Class extends Record> handlingClass) {
+ RecordTypes(int typeID, RecordConstructor recordConstructor) {
this.typeID = (short)typeID;
- this.handlingClass = handlingClass;
+ this.recordConstructor = recordConstructor;
}
public static RecordTypes forTypeID(int typeID) {
RecordTypes rt = LOOKUP.get((short)typeID);
return (rt != null) ? rt : UnknownRecordPlaceholder;
}
-
-
-
- /**
- * Returns name of the record by its type
- *
- * @param type section of the record header
- * @return name of the record
- */
-// public static String recordName(int type) {
-// String name = typeToName.get(Integer.valueOf(type));
-// return (name == null) ? ("Unknown" + type) : name;
-// }
-
- /**
- * Returns the class handling a record by its type.
- * If given an un-handled PowerPoint record, will return a dummy
- * placeholder class. If given an unknown PowerPoint record, or
- * and Escher record, will return null.
- *
- * @param type section of the record header
- * @return class to handle the record, or null if an unknown (eg Escher) record
- */
-// public static Class extends Record> recordHandlingClass(int type) {
-// Class extends Record> c = typeToClass.get(Integer.valueOf(type));
-// return c;
-// }
-//
-// static {
-// typeToName = new HashMap();
-// typeToClass = new HashMap>();
-// try {
-// Field[] f = RecordTypes.class.getFields();
-// for (int i = 0; i < f.length; i++){
-// Object val = f[i].get(null);
-//
-// // Escher record, only store ID -> Name
-// if (val instanceof Integer) {
-// typeToName.put((Integer)val, f[i].getName());
-// }
-// // PowerPoint record, store ID -> Name and ID -> Class
-// if (val instanceof Type) {
-// Type t = (Type)val;
-// Class extends Record> c = t.handlingClass;
-// Integer id = Integer.valueOf(t.typeID);
-// if(c == null) { c = UnknownRecordPlaceholder.class; }
-//
-// typeToName.put(id, f[i].getName());
-// typeToClass.put(id, c);
-// }
-// }
-// } catch (IllegalAccessException e){
-// throw new HSLFException("Failed to initialize records types");
-// }
-// }
-
-
- /**
- * Wrapper for the details of a PowerPoint or Escher record type.
- * Contains both the type, and the handling class (if any), and
- * offers methods to get either back out.
- */
-// public static class Type {
-// public final int typeID;
-// public final Class extends Record> handlingClass;
-// public Type(int typeID, Class extends Record> handlingClass) {
-// this.typeID = typeID;
-// this.handlingClass = handlingClass;
-// }
-// }
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFComment.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFComment.java
new file mode 100644
index 000000000..0548fca45
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFComment.java
@@ -0,0 +1,104 @@
+/* ====================================================================
+ 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.hslf.usermodel;
+
+import org.apache.poi.hslf.record.Comment2000;
+import org.apache.poi.sl.usermodel.Comment;
+import org.apache.poi.util.Units;
+
+import java.awt.geom.Point2D;
+import java.util.Date;
+
+public final class HSLFComment implements Comment {
+ private Comment2000 _comment2000;
+
+ public HSLFComment(Comment2000 comment2000) {
+ _comment2000 = comment2000;
+ }
+
+ protected Comment2000 getComment2000() {
+ return _comment2000;
+ }
+
+ /**
+ * Get the Author of this comment
+ */
+ public String getAuthor() {
+ return _comment2000.getAuthor();
+ }
+
+ /**
+ * Set the Author of this comment
+ */
+ public void setAuthor(String author) {
+ _comment2000.setAuthor(author);
+ }
+
+ /**
+ * Get the Author's Initials of this comment
+ */
+ public String getAuthorInitials() {
+ return _comment2000.getAuthorInitials();
+ }
+
+ /**
+ * Set the Author's Initials of this comment
+ */
+ public void setAuthorInitials(String initials) {
+ _comment2000.setAuthorInitials(initials);
+ }
+
+ /**
+ * Get the text of this comment
+ */
+ public String getText() {
+ return _comment2000.getText();
+ }
+
+ /**
+ * Set the text of this comment
+ */
+ public void setText(String text) {
+ _comment2000.setText(text);
+ }
+
+ @Override
+ public Date getDate() {
+ return _comment2000.getComment2000Atom().getDate();
+ }
+
+ @Override
+ public void setDate(Date date) {
+ _comment2000.getComment2000Atom().setDate(date);
+ }
+
+ @Override
+ public Point2D getOffset() {
+ final double x = Units.masterToPoints(_comment2000.getComment2000Atom().getXOffset());
+ final double y = Units.masterToPoints(_comment2000.getComment2000Atom().getYOffset());
+ return new Point2D.Double(x, y);
+ }
+
+ @Override
+ public void setOffset(Point2D offset) {
+ final int x = Units.pointsToMaster(offset.getX());
+ final int y = Units.pointsToMaster(offset.getY());
+ _comment2000.getComment2000Atom().setXOffset(x);
+ _comment2000.getComment2000Atom().setYOffset(y);
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java
index 526a3ceed..0548bf802 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFMasterSheet.java
@@ -21,6 +21,8 @@ import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.SheetContainer;
import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.sl.usermodel.MasterSheet;
+import org.apache.poi.sl.usermodel.SimpleShape;
+import org.apache.poi.util.Removal;
/**
* The superclass of all master sheets - Slide masters, Notes masters, etc.
@@ -52,11 +54,13 @@ public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet)shape).isPlaceholder();
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java
index d922d7a89..70f15e021 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java
@@ -20,7 +20,10 @@ package org.apache.poi.hslf.usermodel;
import java.util.ArrayList;
import java.util.List;
+import org.apache.poi.hslf.model.HeadersFooters;
+import org.apache.poi.hslf.record.HeadersFootersContainer;
import org.apache.poi.sl.usermodel.Notes;
+import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -72,4 +75,28 @@ public final class HSLFNotes extends HSLFSheet implements Notes clRecords = getClientRecords();
- if (clRecords == null) {
- return null;
- }
- int phSource;
- HSLFSheet sheet = getSheet();
- if (sheet instanceof HSLFSlideMaster) {
- phSource = 1;
- } else if (sheet instanceof HSLFNotes) {
- phSource = 2;
- } else if (sheet instanceof MasterSheet) {
- // notes master aren't yet supported ...
- phSource = 3;
- } else {
- phSource = 0;
- }
-
- for (Record r : clRecords) {
- int phId;
- if (r instanceof OEPlaceholderAtom) {
- phId = ((OEPlaceholderAtom)r).getPlaceholderId();
- } else if (r instanceof RoundTripHFPlaceholder12) {
- //special case for files saved in Office 2007
- phId = ((RoundTripHFPlaceholder12)r).getPlaceholderId();
- } else {
- continue;
- }
-
- switch (phSource) {
- case 0:
- return Placeholder.lookupNativeSlide(phId);
- default:
- case 1:
- return Placeholder.lookupNativeSlideMaster(phId);
- case 2:
- return Placeholder.lookupNativeNotes(phId);
- case 3:
- return Placeholder.lookupNativeNotesMaster(phId);
- }
- }
-
- return null;
+ return getPlaceholderDetails().getPlaceholder();
}
@Override
public void setPlaceholder(Placeholder placeholder) {
- EscherSpRecord spRecord = getEscherChild(EscherSpRecord.RECORD_ID);
- int flags = spRecord.getFlags();
- if (placeholder == null) {
- flags ^= EscherSpRecord.FLAG_HAVEMASTER;
- } else {
- flags |= EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HAVEMASTER;
- }
- spRecord.setFlags(flags);
-
- // Placeholders can't be grouped
- setEscherProperty(EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, (placeholder == null ? -1 : 262144));
-
- HSLFEscherClientDataRecord clientData = getClientData(false);
- if (placeholder == null) {
- if (clientData != null) {
- clientData.removeChild(OEPlaceholderAtom.class);
- clientData.removeChild(RoundTripHFPlaceholder12.class);
- // remove client data if the placeholder was the only child to be carried
- if (clientData.getChildRecords().isEmpty()) {
- getSpContainer().removeChildRecord(clientData);
- }
- }
- return;
- }
-
- if (clientData == null) {
- clientData = getClientData(true);
- }
-
- // OEPlaceholderAtom tells powerpoint that this shape is a placeholder
- OEPlaceholderAtom oep = null;
- RoundTripHFPlaceholder12 rtp = null;
- for (Record r : clientData.getHSLFChildRecords()) {
- if (r instanceof OEPlaceholderAtom) {
- oep = (OEPlaceholderAtom)r;
- break;
- }
- if (r instanceof RoundTripHFPlaceholder12) {
- rtp = (RoundTripHFPlaceholder12)r;
- break;
- }
- }
-
- /**
- * Extract from MSDN:
- *
- * There is a special case when the placeholder does not have a position in the layout.
- * This occurs when the user has moved the placeholder from its original position.
- * In this case the placeholder ID is -1.
- */
- byte phId;
- HSLFSheet sheet = getSheet();
- // TODO: implement/switch NotesMaster
- if (sheet instanceof HSLFSlideMaster) {
- phId = (byte)placeholder.nativeSlideMasterId;
- } else if (sheet instanceof HSLFNotes) {
- phId = (byte)placeholder.nativeNotesId;
- } else {
- phId = (byte)placeholder.nativeSlideId;
- }
-
- if (phId == -2) {
- throw new HSLFException("Placeholder "+placeholder.name()+" not supported for this sheet type ("+sheet.getClass()+")");
- }
-
- switch (placeholder) {
- case HEADER:
- case FOOTER:
- if (rtp == null) {
- rtp = new RoundTripHFPlaceholder12();
- rtp.setPlaceholderId(phId);
- clientData.addChild(rtp);
- }
- if (oep != null) {
- clientData.removeChild(OEPlaceholderAtom.class);
- }
- break;
- default:
- if (rtp != null) {
- clientData.removeChild(RoundTripHFPlaceholder12.class);
- }
- if (oep == null) {
- oep = new OEPlaceholderAtom();
- oep.setPlaceholderSize((byte)OEPlaceholderAtom.PLACEHOLDER_FULLSIZE);
- // TODO: placement id only "SHOULD" be unique ... check other placeholders on sheet for unique id
- oep.setPlacementId(-1);
- oep.setPlaceholderId(phId);
- clientData.addChild(oep);
- }
- break;
- }
+ getPlaceholderDetails().setPlaceholder(placeholder);
}
@@ -727,4 +596,10 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape= path.length) {
+ return root;
+ }
+ final RecordContainer newRoot = (RecordContainer) root.findFirstOfType(path[index].typeID);
+ return selectContainer(newRoot, index+1, path);
+ }
+
/**
* Get the comment(s) for this slide.
* Note - for now, only works on PPT 2000 and
* PPT 2003 files. Doesn't work for PPT 97
* ones, as they do their comments oddly.
*/
- public Comment[] getComments() {
+ public List getComments() {
+ final List comments = new ArrayList<>();
// If there are any, they're in
// ProgTags -> ProgBinaryTag -> BinaryTagData
- RecordContainer progTags = (RecordContainer)
- getSheetContainer().findFirstOfType(
- RecordTypes.ProgTags.typeID
- );
- if(progTags != null) {
- RecordContainer progBinaryTag = (RecordContainer)
- progTags.findFirstOfType(
- RecordTypes.ProgBinaryTag.typeID
- );
- if(progBinaryTag != null) {
- RecordContainer binaryTags = (RecordContainer)
- progBinaryTag.findFirstOfType(
- RecordTypes.BinaryTagData.typeID
- );
- if(binaryTags != null) {
- // This is where they'll be
- int count = 0;
- for(int i=0; i {
}
final String text = ((tba != null) ? tba.getText() : tca.getText());
-
+
StyleTextPropAtom sta = (StyleTextPropAtom)_txtbox.findFirstOfType(StyleTextPropAtom._type);
TextPropCollection paraStyle = null, charStyle = null;
if (sta == null) {
@@ -305,7 +305,7 @@ implements TextShape {
htp.setParagraphStyle(paraStyle);
htp.setParentShape(this);
_paragraphs.add(htp);
-
+
HSLFTextRun htr = new HSLFTextRun(htp);
htr.setCharacterStyle(charStyle);
htr.setText(text);
@@ -317,7 +317,7 @@ implements TextShape {
public Rectangle2D resizeToFitText() {
return resizeToFitText(null);
}
-
+
@Override
public Rectangle2D resizeToFitText(Graphics2D graphics) {
Rectangle2D anchor = getAnchor();
@@ -649,7 +649,7 @@ implements TextShape {
} else {
_paragraphs = pList;
}
-
+
if (_paragraphs.isEmpty()) {
LOG.log(POILogger.WARN, "TextRecord didn't contained any text lines");
}
@@ -701,18 +701,13 @@ implements TextShape {
@Override
public boolean isPlaceholder() {
- OEPlaceholderAtom oep = getPlaceholderAtom();
- if (oep != null) {
- return true;
- }
-
- //special case for files saved in Office 2007
- RoundTripHFPlaceholder12 hldr = getHFPlaceholderAtom();
- if (hldr != null) {
- return true;
- }
-
- return false;
+ return
+ ((getPlaceholderAtom() != null) ||
+ //special case for files saved in Office 2007
+ (getHFPlaceholderAtom() != null)) &&
+ // check for metro shape of complex placeholder
+ (!new HSLFMetroShape(this).hasMetroBlob())
+ ;
}
@@ -738,7 +733,7 @@ implements TextShape {
public double getTextHeight() {
return getTextHeight(null);
}
-
+
@Override
public double getTextHeight(Graphics2D graphics) {
DrawFactory drawFact = DrawFactory.getInstance(graphics);
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 f2700d4fa..6a34d1af3 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/extractor/TestExtractor.java
@@ -33,9 +33,9 @@ import java.io.InputStream;
import java.util.List;
import org.apache.poi.POIDataSamples;
+import org.apache.poi.hslf.usermodel.HSLFObjectShape;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
-import org.apache.poi.hslf.usermodel.HSLFObjectShape;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -89,12 +89,12 @@ public final class TestExtractor {
public void testReadSheetText() throws IOException {
// Basic 2 page example
PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt");
- ensureTwoStringsTheSame(expectText, ppe.getText());
+ assertEquals(expectText, ppe.getText());
ppe.close();
// 1 page example with text boxes
PowerPointExtractor ppe2 = openExtractor("with_textbox.ppt");
- ensureTwoStringsTheSame(expectText2, ppe2.getText());
+ assertEquals(expectText2, ppe2.getText());
ppe2.close();
}
@@ -103,15 +103,15 @@ public final class TestExtractor {
// Basic 2 page example
PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt");
String notesText = ppe.getNotes();
- String expText = "These are the notes for page 1\nThese are the notes on page two, again lacking formatting\n";
- ensureTwoStringsTheSame(expText, notesText);
+ 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();
// Other one doesn't have notes
PowerPointExtractor ppe2 = openExtractor("with_textbox.ppt");
notesText = ppe2.getNotes();
expText = "";
- ensureTwoStringsTheSame(expText, notesText);
+ assertEquals(expText, notesText);
ppe2.close();
}
@@ -122,8 +122,8 @@ public final class TestExtractor {
"This is the title on page 2\nThis is page two\nIt has several blocks of text\nNone of them have formatting\n"
};
String[] ntText = new String[]{
- "These are the notes for page 1\n",
- "These are the notes on page two, again lacking formatting\n"
+ "\nThese are the notes for page 1\n",
+ "\nThese are the notes on page two, again lacking formatting\n"
};
PowerPointExtractor ppe = openExtractor("basic_test_ppt_file.ppt");
@@ -137,7 +137,7 @@ public final class TestExtractor {
ppe.setSlidesByDefault(true);
ppe.setNotesByDefault(true);
- assertEquals(slText[0] + slText[1] + "\n" + ntText[0] + ntText[1], ppe.getText());
+ assertEquals(slText[0] + ntText[0] + slText[1] + ntText[1], ppe.getText());
ppe.close();
}
@@ -166,16 +166,6 @@ public final class TestExtractor {
ppe.close();
}
- private void ensureTwoStringsTheSame(String exp, String act) {
- assertEquals(exp.length(), act.length());
- char[] expC = exp.toCharArray();
- char[] actC = act.toCharArray();
- for (int i = 0; i < expC.length; i++) {
- assertEquals("Char " + i, expC[i], actC[i]);
- }
- assertEquals(exp, act);
- }
-
@Test
public void testExtractFromEmbeded() throws IOException {
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("excel_with_embeded.xls");
@@ -454,4 +444,38 @@ public final class TestExtractor {
assertContains(text, "Prague");
ppe.close();
}
+
+ @Test
+ public void testExtractGroupedShapeText() throws Exception {
+ try (final PowerPointExtractor ppe = openExtractor("bug62092.ppt")) {
+ final String text = ppe.getText();
+
+ //this tests that we're ignoring text shapes at depth=0
+ //i.e. POI has already included them in the slide's getTextParagraphs()
+ assertContains(text, "Text box1");
+ assertEquals(1, countMatches(text,"Text box1"));
+
+
+ //the WordArt and text box count tests will fail
+ //if this content is available via getTextParagraphs() of the slide in POI
+ //i.e. when POI is fixed, these tests will fail, and
+ //we'll have to remove the workaround in HSLFExtractor's extractGroupText(...)
+ assertEquals(1, countMatches(text,"WordArt1"));
+ assertEquals(1, countMatches(text,"WordArt2"));
+ assertEquals(1, countMatches(text,"Ungrouped text box"));//should only be 1
+ assertContains(text, "Text box2");
+ assertContains(text, "Text box3");
+ assertContains(text, "Text box4");
+ assertContains(text, "Text box5");
+
+ //see below -- need to extract hyperlinks
+ assertContains(text, "tika");
+ assertContains(text, "MyTitle");
+
+ }
+ }
+
+ private static int countMatches(final String base, final String find) {
+ return base.split(find).length-1;
+ }
}
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestRecordTypes.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestRecordTypes.java
index 814259ca1..dbf89692b 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestRecordTypes.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestRecordTypes.java
@@ -18,10 +18,10 @@
package org.apache.poi.hslf.record;
-import static org.junit.Assert.assertEquals;
-
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
/**
* Tests that RecordTypes returns the right records and classes when asked
*/
@@ -42,20 +42,15 @@ public final class TestRecordTypes {
@Test
public void testPPTClassLookups() {
- assertEquals(Slide.class, RecordTypes.Slide.handlingClass);
- assertEquals(TextCharsAtom.class, RecordTypes.TextCharsAtom.handlingClass);
- assertEquals(TextBytesAtom.class, RecordTypes.TextBytesAtom.handlingClass);
- assertEquals(SlideListWithText.class, RecordTypes.SlideListWithText.handlingClass);
-
// If this record is ever implemented, change to one that isn't!
// This is checking the "unhandled default" stuff works
- assertEquals(UnknownRecordPlaceholder.class, RecordTypes.forTypeID(-10).handlingClass);
+ assertEquals(RecordTypes.UnknownRecordPlaceholder, RecordTypes.forTypeID(-10));
}
@Test
public void testEscherClassLookups() {
// Should all come back with null, as DDF handles them
- assertEquals(null, RecordTypes.EscherDggContainer.handlingClass);
- assertEquals(null, RecordTypes.EscherBStoreContainer.handlingClass);
+ assertEquals(null, RecordTypes.EscherDggContainer.recordConstructor);
+ assertEquals(null, RecordTypes.EscherBStoreContainer.recordConstructor);
}
}
diff --git a/test-data/slideshow/bug62092.ppt b/test-data/slideshow/bug62092.ppt
new file mode 100644
index 0000000000000000000000000000000000000000..769049581092689ba993249ed78b101e5edf9bd0
GIT binary patch
literal 161792
zcmeFa1z1&Gw=lZs5TsEWq*1y-y1PLIk=m4$d()wWluDPRNQa_?Aku=;N(fR4NGM99
zA|c$hHX-`*zR&s2ch7(S`la@|SzZ?aP&&cu6E^4tDArTxJ|F@Gp{pbdl)R#z0~6>rG8!@f
zP#Se&+Z+Q-TWhQ9D}YaVc#Eq0OKqPv!y6sOH#^P4x@{s}JI3@P68gPT25w}$4bC3A
zl|K^wU@YbyE+UX3AfDtnQerfAyuMf4<}GV$ay};Kb_K^w$q_-#;z<
zSXkXy`@XfY^%FF5vB4DRjsS|QuBIL!Jv(F#CU9JoFCqZo6cKcxbg-;R0RT0cioA@z
zr|CjEj%AS;VKCj|g@?LN5(YVUceU!1+x^4Zte5+~`c@fTBz`^_4anZz-63Z2RDWS&
z^+e$Hvw@|o2jvsd>T1@Mo5H-e=}(VbW9aTc1T8gjV9U~$=5z`QH13w0sA+r*X&TmS
zx8nSeo63%Hk)&d<@>1y-htwkG&)xhPjdWrO4L+i=W^0K1eYVGRjSZr*w@b3W$D?sn=z*2R3Rt!|U`
zb+|w>j{7WpC#OSkNacyq4r+r}!<7c0b_
z|L_)u;V1KxBg>-X`l*1luAj9ch+Bry{C8X@#GG3x>hWjPDK{NnF}$X+`eJTfcs}_g
z#|u%IQyX3T46JE0r`GAjWf{$o*-C{Z?VEX?H}g1T_0G%ck?HUnHQZhn%r~jHMq|=x
z+nwgh+)9>WUx}kA@SOCc`bcos`3}+Ow5ATtw%IgFrxURz>T{b`ozvrGq|TS9%Ozn6k@$sv&tc)Ip_go>6AISV
zo}FA^c@$4^mn8kdyS%OPE$z$N)`GivUG77CqYPeGy7@;dId53g^#z1vcG<%>3ci2R
z#uRf;SsHUqf?aRtc7RX#CZREOCo#QCQ0YN2jmKz3Nm9{sh#kGD0MjjD`+=vpEyu_W
zE2W&zg7Hw!^gS_syTv!>8fetD^ROQg=Xc%ha$&E?+$fqmX94t3iC7
z8*VVkW-0b^nv|_aYc}7BFDuIlc!)c7o-;v9@f5y{sA8-VKxCcWt%(2niLz5-7cZrT
zrW)2uB+Aae5xv`VH!_w2q+f*%$|I`KV2N4PqXB$sy<-c~$+@(@((@wv_*;@
z)TpjbdS~GgrqQjAv6a^oHC`^KSF=%lr6x#qOh2^Iv2XA#ShmrzJYBw9szpB9M1|Wi
zozKRWT-}3(lYPJ3dNlsabjJ61F5E-`;!)Zsf}MDtBq5tM9VT@7Y_E#ZaHh00pTkbh
z3cSftuW!hL#m-($u70q=m;Ej30;?GJm)r4PBbd&m_c5&5*!sKJvlA)&Si~6XY21xJ
zr@u-e{rmu@;~W;u5VWF9^1yaF-%M56ta7s07-{>~sh)_j=#Q0ATsaIkiq&cCut)C0
z!767fPa8L7p?T-}sY3wUdSmWn=NpXI^QW$0W0F*FYuK=Bx~=XG4BZiOZVK6w=y_2d
zU){CKGEt(w^@eHp8pU_n@x)=x{OQv`A+KTU`9fzM=n8n$5JIz4t<<>{
zg}l7@D|nk9Y_aA0?nxWY6V31~)R28LCZ>oieyrNR*3vVSui6)hu4#3h`B5|vf+DWz
zYPGqT^*d5^Vwc+JTj}oVRB`qCEVmOQsVls)+d8Hx1Ud`5=rjFp($U5!tuLsFxk*@X
zR7uY=5AzTdl%$%NYR--+&Sl@%pB=tl6Wd-F`H7KL&(Ja>MwE}(Z~5F8J;mxSx{>v2
zjGCb&zvVB_HzY{5)84{X6-ZcBoUWzgS;Hk*%ihGb(-d1VtZo}HvBSs;{FfTL=J|E;
za<|RD1QW{@zjIaf6Ml!9d>Kei|LZ-L{gkJ_>4Q3K&adCLW5iIC7uzcFg+2zoC+5sc;#*kIuO~1J_Vare=xnwyBQ%KS&AujnajFMz
zqs8TM7D0VnJGUnfyr#cJ>g64n{!-+l1&LOR&FcP>whJ5W7(Pos&&}plaW6y|`WWe4
zQEU?y+3nUmyZ(w!?;W~dYQp>YN{=l3hWC?9J1cj)OZbAzP-!~Vojd5%Hyl^(ljYW~
zv+Bj)*^m?DoKIS=RUZkrxOgjhbfCB;q`)R;==~d$;mYRAEkD>`CTj1AI=a
zc;OT2wr6~_rrGrdtp?(fOUk^mqv|+?G;ZvM1z$9h_^Q2-r}ufW!pCu9F@s68btPSW
zjY6TfSAN^I8_k;|j?OJBDvMR+B}MYo)#9BK@1~XHH2aFmyB_9{n(JF~^Q~l6v*$_{
zt)2C_^j6yXl(cpJ(YsZLA8+&=Z5uk^f1R;mx(*Mt`NYsi+P3-AZLV6}u#j0|$C>MD
zWaDM=x#1May6W7p!l7cucXHu*M0KZ>%_fYO$wNsnXpXQB*})tig76NMUzhAbB}&5SY4x!u`rfR{i)>Y`?;6BX
z#09Pd`tISD33MYwnhxRbhXyB=k#W%UVI!5a+wQ&{
zHK>0kROH9`2e3_J-;MYxkCvwSRnf3E1uuMbbF_z2nffzb)WX!65&|)zl>Aa_tQt3j
zStL_Q@K?^RMnuqFi$15RPpd(V@b~6gbZ~fpyT!82n9$05t}Bg2!_%7>cf3Af1|^px
z&L}waJN^lizG%w~mz%y@PS?Erl%KT{YwCB@bZ(?3X?njG;gO2yT+IG*W<_kC_H2CJ
zvuqPs*`rY6jpy~OauWTXyO-`Q1aCRKgtIu3jqIA0l&gg~@+;ju)pI+@x?dN^
z2d~}9xvT#0S`Oa?%lUjb{m7z$;jME9@nGEJ3|z(~=Z$zTIkFT|K~E`Gium*Z4?((UNkc=GmpW$%P))oEw4*~tZewjK)RO4RqSotzx-Nv|mV(&Vut_
z^{rT`?A3$5X&5
zBmXXw_+si8vo=}HZg*Q*2J0UNf7P7{Q_q`;~a6JI@^ain<^>Q
z_f_$|m03+`k*ex-E~4T4s+KuFeWa^Z7hSp&`WJ)yI7R1BC8Co~51Cd9oL%8NA73ZZ
z_(b)6&-0|11RwbX)x~bDLZ)5==7x;`HtqJGxpZ{iX~l1H@w&c>CiJySJktIC4vV8x
z%>S){=4(E~EPD$VYgO0$kCP(&ZDKpR&>9aoaB#7sq>)Dm&Fw=;yl1
z?#^cX3|D<$QOhB(Vzf5GQEeoCL+wl{FS|0UeU)EoV?9RpL)G;+`d&Pzsui^p=C*K^
z9rTxJi3-m&XfYf37QeWqY|tla*fgqpy#VLdnd}8!hM)TFMFkXKE4VM7P^oc-_KM+G
zgWNQ^kNPJUuBNPVa5gwaUCxjbi}BTxx2mm0=Z8n;Urdm)e)6XH`?wKz^~2eI-7Uoj
z#i~y2Z&xXK4cdLaJTjQ0{x7)i&YcdUEaLD}#oJ2Kxrod%z05w*UCkM_URiJ#2mqup
zyf?p|spv@x`hxpW8kT=Zqdm;7-G2E|2il$YXX*r|1IDqw>M}pPX2m#cXuj3vvL1Nh
znYct|6i5Ec?_EVr8p8Klzv|40d1agr3u*Fgjb)^?un->%D@R)sM|k3#o^Tx;S3Rg&xIHEN`~67?hAN^}M%
z3pSHU=-A;LXnjsE&tEGZt|sx|nf_YFFO}8st&bV9A6P;2U>9>z-yK(942we?gCiX+
zAssd0RX~Skfa+h&{ZHhRtq)B@$)Vkzzl4-*EwdkC9JMkwUO3_Xts%ZsF-c;h;uE=2
zjcPailXd{^pD3oAXtiIlJm(0O`{@34g!kf%6-@$$1oy`zFHutkov$x@`;7Zoxv`dr
zTIaQrX?|3DM{18n*85b4=}uw#%*oN4I20_*er@lB2{GO9Jadswiq?lBR+eV^kZ19$-;e}k7#}&
zB6vK`(Rr8OF>#$IUeqB!Jp-1;b)WZP9kp(&y6xIjaxyc`H&E7PN7V@8aDp
z$0qk)U>+zNMG#+F4{`!Z0-pV}ld2pkSxzjY3c}<&!TuF4o2uzPPhRmTX-o*tsoqvX
zDfp;;B^6KXvl;4=H(#_~|5fg4!HrkXRHoY579O=<(zIQ5OiflvZkHwXQc+6j?5_4D
z8S$aOtE%9ej+a2GxhPmR@xF6XNW#p%V|<#KhH2VlBJB+&CD(H7y52WFRf4;2b!q*i
zqE1hyaJZA3Zbj{Uuue#F#c-=sy$-9Zs9jkZSIrLKw6V*!Liwkb^5zW2qcQ1{t
za#Dpm{7Hs~fu~A2_p>Z!)7jLidM~>#k$62G;YnykE$!Tfb{*>E7OL4sUxwQ6rIJLy
ztoRP!up9j*^T9Yc$rsZsm9-i>=6+g|?}PUA=igp-4kta6t{>(4WCq=B**Ww#Dlge%d?#${>!gy4JY(Xmf~V{>
zc*{O7VJ_5sXtUy5q)qL-cx2tY#DA;F{B0v^QFGSWa_YHgnnetCz}g|S!hZ5(J2X2(
zg5htEHrQsmHC?T1b<;~;h2?c<|IG#*%36D;k1$M2A=IbkJicffiqj~-eRWll>Y{9N
zVDPP^hyJ_cNu=1z9TnQkjAQ2YF
zQEDE%`vE^7$t}iZt0Fn>Q!#P(szs2}T9be%W0tplkhkgmpN*TH7x@OZDkIVt+6~`x
zmJz*7T|Qff*F4A?yHLlJHx{OQxs20Hh>v~XG1J|-EkCBdRQl%RA5m4NeRsf2yi==lGFZePy**!At@X!4*7D|vaE;bgH;gd|F
zrHvrBsWj2c_}(|j7s+&GW+u)3nO=)yn?_wcidtz>lfUkh-u2Pi9J$vlH8Uw68r7V%
zMNQzrNsK8ETL+1`owdx#in?~+clIV4Ciar>CJKJga}iUUP|NeMqwBWmbJFY!dpl<*
zZZqk*@bacFJR?(yP*tmQ1IzTf>1(eaneAA0xZGX~3o2dFsW6HTFO}@1>7J)`8I1kU
zxg0fbqQFoF9ynQdQde
z`Q7o!y&ln%S8!ag?cGu|nY}(;@l-h*YeDrAmwP5<`susnU@VnH6;WJXEPWg=-?Wco{3U1pfY|6yuSQlBI=<7sO!kj*?!OaNo5f0`T4|13e7W1GW{)uR
zDVq_Gj73X`K^_`Y>dg-V^CtXPUOuo3P%Gm!|V`oUTlB)=-
zZBUYzN`7(sya0^lL{oR^NvdO|Q5nd=X4NjU4m!OHg9esw|
z>+8-A!N`!TiFZ4GR;=ljL~l{m8qg$(oS@bGyq*mkxdFp
zo3P;7jG|7!G+w==c1TFurJmmMihP;FP?%(O|70bG>0prk%tfqsDBSmQ-my%rcYj2c
zbQs2oxbs~QI}j)I1pn;a%G9%~sv^vyqMg!#9*ZJnCa4V+w5FArGc2RMm6DJ8g@;H4
zg1bguJ6ctAzeqEjqQH95KZLG?xMS`5yro=Ahz|qC&`eM~;PisKSc+n;NvF><&r`I$
zZmOPQxr{6OZlzFlnd-KchaeuhxaVVc4O^ZiWlHYA@@
z*tCybkC<&|fzi&O)wQwCgG8
zqG(Xksi?(Np@L;4!_H*)x>>?icrVHK21aOE6u`={gIs77D7$^ja%QB~MkW4@+2G#}
zZT|uywBb{3LxKFEV65!~Juypp|w6KF)
zAs_%I0tUBmhEt=u0*q{y?DRcpJ&${y^#=4j?Wv{ji3Jliy-_`NAmAAYss?r^QI{1w
z?ZDTaDk>ZRHdr8BfB*#*`O-cFOc8?dli>3Xcu}HY0MLXW1-z*C<5^&w4FwGe2lWJa
zL3Sp;5uV4M_uwt*K3>K+Zme>V^nAOoLc;AIzzj^teh<4}-;3b=QtSJ2X<7vPbH
z42GmRc*u>Sz#
z06-6<1n!l{6I4BYKkg64&xGx13snf~emojdJsJyuU_5+$ylByTr3(Y;(f8wNU>qf8
zj|K%QaV#!U^T!|@HFhtrrx5)gtP-l!3_$er#}zI#1!q|jvnT4m?SS3
z_;;TBH`pPOgE_)h&e_og=7cbZ@|NV`KA`;z>VQcdB$iZA!M(sF4H*9C|RgowOvV8Y
zGq>`RadC05gIP)H!`)!#xsO3W1%kk`4sI}I@SQ(c;G@Z-XgzaRH~4-P{GvJnJtT(P
z+1R=|!4QZ;alar3L~_U?Xv3YY?Hpi|3ZNmatYB7qa(~Ai%OP)P14Fn<3JM4b^2qQD
z@X89x3d+gJD2NCM@X7PY3h>K_^2y3693yX^^H8cS+}RNiz?xF4FBG+sZkZ!|hzX4r%w3hjN`AoZ*_zb_kd*%*xGj&*k^aaSZAgo0B^X?yB#s
z1UL6WSek=IRWx@%?AN-S^IxzBjPfvRIOsr!RezX1AW-mhaYnd-&6J0WXWu;zK?gL7
zb`B2eP^}-7>KEXEL~ge}2cRP)di%}e
zNDBa|4xxvXpzaQk{<<(rXV8v^wWItO#373tOvcLI4dLntb8?lr=4O6Sbg+znAdY2$
zTHLWLNW`%$NK+ikvxhmBNdp`ez|QBX03qsrFIFvT3`)T|npCuf5;c
z_EE>OfzEy`4+KA!;Udhz!P(AtQ!9PeiCf7q|#G#}A
zl8o&4k73$}|1~EKJ1bXbC)l2*g8P2;OWtqjzjEu@f-_)`J!ZbY6!R$d?|6^Vb&u^=
z=|EpNGF0uS4&;H!BjeG&{=WkcNPp4&F(mAB{Vo2V1RU)VjuY@(mv9`xZ+*hw6a147
z2W|cz%5gyQPhx)W7micqkB;FulHYrVf1vm$O~Cp)XrISPfYATM{(J9moP<9Hr9F?+
zKQg2ofgI+9^z}o3{nJaqG#p$P$%6|)I|s>w#_&7zh$x6w!O8}vVUDL~nma&);GsqTiaCbqu$MW~^pNd*Pv&pv
ze`5b-{bTY!lyZdX7-3)u!CvNgMSkJ_mhp)21C#wS!5;q+k^?bdIbm{caCaEwYI}=r
zJ=is5VlU-jC&_b;9+U_zDZvCpc8K8<6g&qmUcoN$2zD>AFJ-Ute#-^9_&bDXf`gBw
zx}6iu9R3F_5_6!ZAq^oJEDx8?E^crK-r@yN*|
z!pY04f3We$B_Mj1M?ef(!2S&rRHI0gBDAf9+~IODha+J6UM?^RR$Umv*$r+9L*{!x
zXa@%~+h1kxu_7U$qyq{!r@aOIZ$t;cUvwJIRxnAvebtbOUpR2&QSgExfk&kr0UxCW
zS2o}d*}f!Q7%~as_W{VFfu8Yi)Qto9arbe1e8|-^+!@^Tgf=|)s>cB?W@`s7x8Rnx
zV6$@2`@h6Z|5mMk!z_oL#PQZSL?5xvAxuo!4&mwy?m3+2{woZsykAgtn2oummx{K&
z-2OHeFE5X{sl={o?1}c1H^g8UF{!8FJ*2A&x_9NeIU
zMln$KUto~ckwE08I=ESE<7x{|utdQ&`WNu;v_|0K##WMFMC3p$Qo*Y)%AaPsi!^YMuZ3X1U^_WOUKJrJM^njT!8gL4^(WIy%q7G6W+pNsG4
zxZg>Fd1`2gsW>5Ap&65kyri3(ot2oJki4**yu1v*pe(Pv0xz#DpPY!Cg1jiNqMWFR
zpe*Pnf8je;6rK$FM+$pG5-e?I&tlYhy^|1sBp%=Ir>;9nyC
zk9YmYT>p{<{w3o7c-Mc-^)FfAUn2fL<6XbL1A;k$AOCoOU-|5g0&)N<_(dTK3Uo(>
z0y-LW$3RC%L&wCx#6(_LIM|q2I9QmN*e9@YaG?Ok@J`_3K?z969xEya8X5*J7A6*w
z$MYMiO;LDadnM$!Z508TAc%({Xuu;d8J4U-ZA$_c*W|~GEAgFE?0DgiC$1Y8=q5~U47uDWjNX=jHONm((n4^i+c5nVq*<^SM~;ml>P!7ZO?9
zlw@mlV!qy4DKskBIk~<{o3jhl_WQ8QKoXvR?0q9{yvsgFb_4N=H9B$&YISI
zIu^ShWAlm8a~k!}Aq~j1avGv^&?fa364$NAS(jXQ+9$VjzeeG4JCtfdItF|*jX!BI
z*?w!hlHjdF{4TKAC(arg
z!xlF3jQ361a9qCDBCcw@r#jia3y5{=jLsMI(Quq=##M
zA3RXMM-h35HC9p0zRxWwxn$d+Sk_5ggRZNBi$jm9(wZ#kja3w%Os2=2FJEwbjXY>m
zw4*R5N$^r3ePZ9l=|?xzF$O;>b-UpUk|~9RTKAUoMMd1ZP)I2>e6{E7XNf@Ido96*
z%(%sq1M;qqjh#<*4B6U!C@v`|NU#h!O_XCt6NW;HHy!5>eE)Q}khn*`tkHh!CJuU$
z#dB@y)RTx8sMX~k+@=yz-`xwTeC=<)j`6bp%eS&lLzpV?WtH%&qO6pmcZ%$fA5V3&
zXEePoS3D63mLWaM7Edr@rW#Q~C$nWjwSupNH!P+u7Jrr~kwsJ6Hz!frf@>thqm;b2
zgB7c~b$GA_1M|%k=i(O2%&q#o6W#B#TEb;0h{>h%SU=OS4+q>ayDy=R)9QS3MATfA
z<8`}Cm=uq|tj%>>+86G+@T8Ay!Bsg=8B%54d}8OvR+zp=;IpiiGcR4e!nQ~nIhB_c
z7Oqc?svoUM^;ATJQR1u4nY@&|3(4#X+a%lh*FSR-yAxI;p1wQt`az%6jRHGUl6)h7
z;j>e&U(1y58CzYCFf4d1^r?DB<4VhnaL6i2#=`_Bsnxj$sYKhd9&bwesaQ_^u!_1b
z5y*g*8$3Ui>W%TJC|BiVcMdLVjGW~lhj00vds0q!DldmnKYH+;1*Nj?r)|hI!E`~C
zJC}sZ7t6&_x6AbzMN3!YF5|6?emY|lYi)!_b`cYen1@FV)wg`~vag~^^^otJ9$5m1
zYQTBvK%AdX-cg?izuEf`dx}#uo;;SMgF!kVZ_PHUCO^c8RutjfBGL3BMLZ(wwaU}a
z4Bs}IlO6R1oIh7o2%aDlziC@AakB=mFrwDlL-F%zl@?fAzAQ5hfVw#{EwOmz*5YY?
zr7WFL4nNtq0vE?ML;|kbj=5XROa|TF7M^eu#xMS!+Ys^wF;1M4aTQ}E`05#jJdYKN
zj8aMwF~-9CenrP1ht!QMEB}Z$#3xJWh$)L7h5Y7gy02R$LP0_+lukLr?z$*nlYu6
zwM*2k=YcPcCyq-DPVp*8;wyxO79^72dxKhK_nhD-nhbF#e6_Xq`>T$^DOPmP(AqDy
zZ1eg?J_%8718)Xf)#E(tbfq8RMO?TbCku-_Wyk)$l5{!wO0yG;YjCRH9<_F}nb$?*
zM*u-4#v=R$RwX*{#A9}H7wX#=(rJVzf?nWmQQb;P{Tb<9^8|zXp%Ogy
z_S^e;dStfBSYW-0ubudF}90%`BgW;@xi6|QUc|4@CoB^zyk9YBmW
zr9aKC6YGMB(zpmW8nG;E?+$%%J$s0r#34~9oIX-|L0@TyxPd)Yc2eV6cShL>7_;E>
zvie(FXLGbLB{^-;)>U_hkb{n^T*k3xRv_YM22C1e8wiR|=`s!lHo@Q4lS>-9E1
z;U;y9W-O;*%3HBfHUW=EQ!Rtfof2RwvCcLoDM0BC3iQ0qw8W5_YdjlgW$TehdF4(i
zS_FOS&?z6RY1V|AlJ;`r^_RQA@F2qWv98f;1Is4^rOC4?r+->_nQ1&GjD&FW5lJ>#vF-r@yP?~xnU18kw-KHcc`mfPNaFB
z{oz@mR(@2JP*x+4JB3keh8C@l42Qa=58wV=Q&
z_SN(|Tk%Gxb5#r_CKa&v7rGM6h~wkS?(|IV0hlYoEbs~;uov@&Jggo694
z$0ODk&N`i2;3!vQHuG*=J3B5yLFbg6%v4!=zQ_i(hozd+CFBCz4x2=nq_=$7EnRc>
z4MU;}FBB&4m?fEyML8!HF)1_>3fxYeZi(yA!dE%Hty1}Y7f{l$8jAWrQvbyL?(lL$
zGm{_A&KY`wJnMUdUR9Nz&eBX#);dpFV7GFJbPVqBa;V;Omwu&r`}^e(+A7JFc|w9G
zlPgRYf1qt}oC#SAy*)Z=G4}oDH}dugdx|OV+(b&1LSKE`pb$2(R`p<N
zgG^M#`L1dIS1+EjOjUiM6Uj^-d2C+2_#(M^h^qYkhhE3pJVbMISf%@?k(#C3ypG0x
z=!2UP4T!2%0sdyv#}6LT2y(~>QoY1bdr7~cf-jGq`GfXh&nZzsb6fc@3H+&v7AokE
z(K%YxsD|$lGw71WR+|x0!_J{#X`D2Z`S{e&HHw>}I(E|f`OsG025iVgA%Cof`iE4acuhMLN?2oo5O5Hr5eup7jtZMGl)ga?B^6;Py#61hhLZE#Me+x9jQ8R`fTfo0;*PQ{>$c5!v1l
zOEk|B(jCqqbM^?|Ofyi?ma#C-Yy=(WRc-L(U(VYotcq9Ti(L8nm?(^qAJbCKNdo?hlQeZB-T24NK
z3BYrW-Y}9qiOa)?#fXk7`XiD;C9IP{1;r=6G5Rg#?MSB(;mAs|2qSX@ifVgO-AQRi
zpKDYnBF{c8{W!W5PxF(LiOR=Trm83_SjFd|V@CX|77PVF(lASaOH0>ZQF)Qq-z$|r
zvhOvfQtBnm?b!a>FHMNn!nrq}P2bFavx>FHd&B0m7Nha$qDCVNOeY
zO{lDRvQweX;?4yZU+-{(Yj~p_sI^xHnSo=DCEW}KhMsIP0;6_;wDxUND*Z*$qC|>#_M>h#>PgN1Vk(5w)Jp`
zCtT1exqFHi#wOzupV&1Kh*N!avD{zPa@ttjW~@3eHB>;b$0W|NQ-9T#bz%?4#=BSnaCD}5s{_ALp4=cwNBzhss!x-Z|A8~?-n6hU8yKJYHo_$vQuQ2Eu8#A~>&v-Er~
zzf-dLR;^u;wp3reQDK{9RQ#DNo}`ggNi28VtN+f^_vivkwDwLO8fjdqls?SNgV=?&
z-dmOAkPQTpAlbT?vA
z+dZl7@AeozcP=nLy>?>#YES|%4$7-*F$Hn;=QnR=HpBmB`FqnAb
zW3h`>HwGf!y@i7P)Crj*9jsLow#~4=l
zJj!`cE19*SN?xFBTBkoxJ;y(O2qk~rYl=tW!n0eSgEjA#<#J5lTL>k@10GjgfAmOV
z^m#|zT6wxD3#-yCyoB2^C?%(q12dv|=UF8xmWZtVtIpoy`-Y0C2&|_od@#Fo$|;G|
z+MpnqLi=LQg-1#xq`0b&%cj+Vo>~_>>%Q<#|1`TMr
zW6-=ad3H|AENEMLra0-y_h)Gkf)EGLB^Uv$)Qxz
z>T{t18L~1k94GUx
z51Pm?63@0@HSbVm4N0qXh|c_&%)bP@v8yPRFxe)|z
zf0gEweQRDb7?fCU#cz%*(;1-YjhL$^uY
zlw57-*wP+0zvsFl6ll4XiifuGEWG5QShvxI_2-&2q4_H8pCdoDdnwQOoi1BOvklZ#
zA!j?k3k>vAjx=xF`a=8TnvCp;a21f~xu$mY#rRoM(@GyYp9}O--Tk*d_vQ?^Mfozy
z$hyU66f8zKw>Pk_`tF#xUwqx*)W7rUUjLWYSxA|>ePztNIA(tl<
zJvE#}Gd6Vl7t@yl&nUlb2?lhCVp*LvOMVoA<-8$%{|8je`5GQTNeP!BCKg3jint01kQ@aw{GedGY~1$Vvdb
zB``snv7Z3?p*{G!a4)Qe+P72
zAgv2{S%P>Nzy&x1aKHw{|CtAL=)wPV9(ex;@__93|9&}E(H?+x#0|6&KxNXN>UuhtlzzIxY0J}fI
z^Co~Z^eBK8_yoYC7?4jW4oD-JA+FusB_sqos$q8*@_FQm2H>d(^e7-J6u`JN>K+Fa
zH$yXn&VN9{{n7owJfV6yf&K>h1eUuX0D!qn58m0p0zkx^;00v~3Ea~a#DQ^c0A=|C
z__P49mYKjOCgxxMM+X2Ce66>q2uJ|Z>+aKHVEpCW6JQ4{GDHjUK(v;?38dTr6i`j<
zAP9xsfI)?45bigtJbWj8Utf4_i
ziy%#hOe1gp(47G2!FmNU(E+6Bd$eTe5G`Z%A~w*GBK{C7z7=|cr;~s*BL5_0Gm7WlKLOuZTfRDX^f=WmLY@&f@PJm$tZLc!G=$<%8v=w+A
zJR7CRQH0_2gbVWID9WHXi2Sa6A-)0_flf_90mxAZfG`pQPzM6w7705DgPJsCiYv%J
zZ9p5)2Y3NK5OSLY2SNkP;44*-7h>Q+0*+&VFt?#@8Vva8P&t6cy$j;YfYM+_C&ByC
zjKFh6KrVmqP5?|3oCco&aI|MWO$eC+%KlG0G6&%A@q0bu-{bdsvtM{AV0gd9jUam<
ze6a4dj)4*U;USQT531|e%V;+Ai=gea)zCoCz(gK^82m`q
z75x4073f=j#APn34?RqfgEjnXc7|VqJS?J;9q=M*4WE#nR-bn`O}SC(
zRkD~vAJP+W8-+@gq20~Lt8_#iM}^caQ!%n@&h#oDqc=SnNl4k8<@ZKdSCFPzB~NC3
z;TK~^3o|^Em3fBI0mko-5$7^a&TJ{i_Qrrmgf)v6$3(}LV?K@k?Dy$QySt0_M|)YA##5^SF)UzvIRUnpD{zWq$qeh>dvcD&BI
zfJIjtYiinHFp+3`k4n^LI8FQIj3wAz{!9~|7r8owT;1gI5*u%}?=6hD_qf701!HIE
z$YNK_KfR1oIV&G=pV5#<+&^?Mb&W*ZXtIQL;;+9;eOIgSK0UusCi%?3(!K9CjJ?fNCDDeJy5
zgfU*qm6!}$^BaHX=kal6hRa~u)T&xQEa&~jDB}B;&AFz*U$@|scq)pc$0qT-AMh@TXsZG$Qsr-s75g#UyFcEQM|sDs>(!u
zLvUC&efu`8K8L0-p;w8KGQF_sYOsFW4?tyDLbnt5DSHIz`g{KBOCH#IHC5LAmw6J_
z+Adt}TQsBG)%_TIJL;Xx6QEZ%mq4r_XLW
z>$7$z6axvn&V{$N&a4e!@445vqM&ku$K@SA8~L+^9Wyu4R{LGR@7?oJ0X*QG=tyV%
zC*z;K{)?3wT#o!?CW=N)8R3Z8pDUf#1gh388pnwx@RLK0u0Di2wpN8y*
z#O$Z{N)DbixS!rz;DTo!?x**xXa-7#Y;hnDlHmfvAnyQ8vmc;(IiP@Q1!+_e2h|DE
zL6LF7!vz8==o|jWNFhBUh4_dR!Xr{n9+3hqqZrXG!Its+5+i1*dvy!M)qqNofX`!^
zv2S!cG_?%bpIWBFrLmdk0%l&q*$8;xEaAbz{||d-8I?z}Eo|J~2@*oE;I6>~1P|`+
z?iL`p6WlF$aJOK=-Q6L<-8IP9B$GRHa%5)R`{O%5rq|+qtLv?5x~i-9erngQDwl-<
zcULl#NJ@G3_l9@(*U9HgT;=T&tp186@-&c8=&pEOitj8n3tV61@olwsdKSMV>UD5(
zpEO5;6+LoHSCq=hhA!@v*z%H>z^P;E*vqg^5=m`wYYn`qDLJeP%Fw1zNVMuHacKjs
zDGA)`OCgaJRP(bVcQDXUqw?1OUt3&Sx04v7dqD1C41&;V&k0L8aS>TyR
zN4(GUunNP12ydsiGTgafk^U+gS?mN?kO{#r<*K)szT(JjaY4kM!Nl$&0=LvL!n&*A
zo&hU|bZiVBB4JacE%5?BTc3L@RQ-yNma8e8DVKVc2z-YNiKN!xQ}l5%=%Dut&MYFw
zTreB=fCT_6zAc$@zP(H6%>l52+-xf}g~E`!%PR^D{d!(Gu%PD~KWnx6t;J*0{O4k_
zC!7~E0&TGsc=MkDDfDga=6I}Vdbx73z6}qe#X#GUyLIWmDCpY3`z2ix#V$5Acns_l0?QQS7cZXp)QjX=D2D|1#yRjD
zf%$k$CMbXn>^cwSFxj|Wy=-*Hq0vvGvsM{#_wKTOKFEL#aZ$Pm+P%AoxiDIbes=mT
z97?dCpz*cJzE&Oht}Qg{jx{vvO%~VI)~Wm%OAonBdh8+S$qGa8uxH(ntdW$uj$$Ij
z$^O;r1&6zGxHO(u*DBAZg}Ln3f-CtCY9j;{m8A1QuT5MQ_XRbTQV>s80vGJO-)!bR
zEB|mtaR_PKIZO1RDgwG>IevAXgY-g^=rK{5pW74GIb@mp2cb`2%_Bra*7TO;Aoq``
zdCP@&g<`0NpsuU@UIvr3AMc-4G-CH)8ZG9SoEz-aMR9xy*LY&gLXFo=F9{9TR0@I0
zTCYK%#%tJ@(J(b}89{OX?lgDuX3OurLhY!pLgD!r=!~#}RvtPwIHabTRvrR{b9Vc4
z7gzd`ewI8um&lTIXmAI4^ZI3;Y-4AAhY7X}UP9ZdgB3@4aBYIc4P1eu)xDRnZO=r_
zkl(&EsX>n4IpjZGKE1{MpBL^Q$>>)p3?%!*@h@cZuY~lgX!<=jH6X1116uw1vj3hD
z9Z*ew`s)7?wt9iQzTY(+G6({M2FW9AfgLFT-zXq3M~{H@$YoG~=?fVI{HOR1qd%W;t`WxUI?YDR^ARbs98YCL9<7xsy58z6Npq?On
zODKo|-xcI%AQlSbGhk*z1$h8IVL^BS$p!=B+5Y%{KLcSfz<6l_jL?_^BQivQDbxJ>
z_>2Kyjkf~A`arBL5YGUl2FmgMZZ<$rz>e@f!W
zk7uCY&mU`m2?+kI0r1~yzz3)SMIgn`8X%fOLHze+Cj(OcEV}`SGf?_}U-Cz@?$46H
zgLo`C6Y{Smf4n!a?}lQ;quB51f0X>kJ?9~d0&zlrEUI@e-`6*f`X
zX?e+>m-Z_hq0z>}@$-D8c@qpKp_-)^b+l9{YgKGh=Wskz(wzh_QdlX&gj%YP)r$_KV^l9=LZL*mY#Lbej^3xQDZh%u0
zmC)-~CbL*>&ZSotw&L2szTr7Oyr`wmPpohbgPM|r56(2$T-9FLbyIftjPf4+(h-TU
zAgaCG^m$*3StnnF@rI;8HWLR&T=au#2*HbV<`jx4$zcxdd_6*Y0oPWqvs{Z>&Id!j
zTJ3@?rigG$@Etel$=d*e#zE?Z_%7R#h0CSpOw;aZ=gd#C^vLoEb=1T3;|mj;e4j5$
z9=2mq(cP8q1#Tq@&%2ATSsWkjIsaVr6L0NwNT6k>1Lb~{mfwZ5v5vI?fMDM-OmW_F
zl@-OEZp4G=N|mBswVCLISaxq8tgB7gh}SbWPnkssj%0sMWo?;@WSwMiBV%SyPzHlO
zJA-`^wlOdo=RJqL|Fe6=ho>Eak4^0`}GJB4dJfHX
zafi)#X>9e@=BjE&4Xw3*BoTpcBdLD|3wg{wNok_Mi=t;tj*L_m{un*Hq8W;7((KcP
zTbAVZZ!Ek%)5ME%s8UyI!zxAy8DPbPm_MP8wy${I)lVsUDuTy7WF*hvlpuXh8o>TQ
z8&Z-ZW;&ZdMcMF%RZ?7qeGL?+BeA=M;7TnGjKT#I+QcOpN(D2vm05C3TeY)R22HxM
zm)*VN%Pprpht`rOSOT~`xIdv37=AIy;;y*=^7wAGok$@>hz&
z#z_fGFO&Almm!oY8SF~oaJ7oMsJrdaqkwvK#Z7_gq&-=54fesDqce_n_amv*u0j7N
zJmpp$Or@>YL?U*=?g)g70E)|H;NZ=r&!O9OG;mZ{kIYF5>K0Xv<>GrYu{
zF0AOhb%Xd>$I%r8>p-f6v^HR{J^GWc-b+ND_V=sm2(vf2pN^Q%!W9S^&7Akonfk}8
zLa{Jk8$pHZCqc1^R>7QPH}-ZbL>Wb2q3$g8PLz~i_G!#BC^5h}&pxfMiS}p_0tvL?
zD{7H|&|=dZRtL>2-t6T@hQx}A~1JyiE~9qXNsHt@PZpub(Wb(6?+Al
ziUGy0Y!rGvHc_z3>)WT6&kn>Zh9jycrrxN${O5(R1p!EqSbLzw2)D~5yw#!u__Lo8
zFFtth;=nVZpC?i1VqRz|-LQUscw5c8dZ@hp<=XARC;!{E*1r@P6X@#irh#9sfxkzg
z@+dNZh4ICdO#;b5i@pe88p-b-v9cOPYuAmrQy^lg2nJ~ofb?e;>HF@R+lIfWw
z$2|^5{4_KV*42k}S(2m>*4)fg_&G)SP~sJ9)v2!TRhPLvk3d!Qj1n65l_<`H~xbDasfPr51*acyYGy
z=F>2;Fu#o3K~M8jxQ!3t+hs!z_4^xQi%X%>;Y48Xzc9YT9osLup&QO)E7pd~e9L`T
zuV^&_Z3~yF#{52xDBk0L>fj&g`d1ALB>Thh7wGy|rSp4kT0jl`2fF@MWdEM=#ZS8a
zg8=^(>VJKDzvos581a{9`IE#U09ZZ;Qffb18-e2ovt}csfQ`Te1D*l@$gGd%Cuq>e
zA%4UoM6K{?V)A5y-H=0~s(;{BI!p8_51QK-M~mdtL}osx8oj{uPiFDacrj17mO^UwM_^
z38_SpNj8|`s@6i0Ddc6YLf3^=`g1y#rH&yTFLTU`vUL%n2o{W09_}@KYi>C0{3h5w
zn)X6NJaYAl`jN%@RTnm|`ZZ7`7{>eqqpV*jhlNY5UdCH3t5-2C60=jE6%8IwB{*XF
zCt0cpo+X+mf@qK`2qI8Vy3bJNCen9!X88wXQS`O>Pk?E)Dd>K-RO%Bu8-JJcMq|S{
zG?EChjyjuJA_J2=#)6cNU+N1+V!4ls={Pk1Jz1b^S^D)jlEJ$&QFm2KtLDO(cH0Yaf(SMjA{dgAu@e=HM)Ir~MFmL}
zW#Qm&0_DQJ#jizTI!3Q<$dERoaT-4ud3vyFK`2Ehp9-OANm@m%kt5O331FWdAyatH
z_)KSQfs(e2$&RHG65l>=#?E^SOG>f^4f(2jsKJiq4VfpyZq*{Oow-+89|c8H0KUg=
zAlME)iBpfw*wgb1l!NEHbLG;80b(qr?Hjia}n&9Rj-=i6$*O_NiEq=k5L~d*Sh2rdW6_Z^;|B3PQ
znHJtEU#n`565ILJ6%VRMB2(PL|Ilp$mycKbx;q+`dO~h+gYEs7>8MSd(7<$S>97|^
zdn;>mMT3!*(@-Yk>#l@?EJSUGIz%eijX)TTqlUdb=_lBlZ4
zQF1sshPo!&u2Os1z3m8(qsz_G$uIfTT%@H&1HP{F2<@6BxIReuH$aS;1gdhr;=kI1
zu-?8mlW8kEGydSohcZtvTLcQxox0xd>?$0mTjE&5D?{q{$u{7%5lI=MpxBuydj;uN
zLc^5;bPRmwggWKgiPo7h%ze}l&@yy=X_}3qC+6=jw$dUjwzDn@yvy~;Q8b#gr_I<1
zA{?Ep9jdmckjcwEnWaLckFMC!1zDSIpmJYk-Q8+us-p^-=kL|$TCOQ2Y)3E!q}UC-
zIx^TNkESB7I#zD5iq`{`+B5sOLmy}+)y&no=bb1spC6YmlEnmE4=GtwF*RZH7
z*J#~MT6KC4Q@0&J;*zV~L6!F|H@)7fNaR*yyz0mu-+*Z6;^cRO+#mg}3bP_U{YPw)
zF>9m8@LPty53q)IP0j~afCyz|)z>4^C3g&W8mulhvlK%!ySs$vsRR69+ybm>wj`f(
z+BVk|X){akpC$MZx%IYyxTw1QzTEHvQ4m_XC?U
zke>j*{{}yRZU9CWzu&{}xby>&9%Fw?^=}X<>ko+ZXgK|eNajHBCnBZ)1$z}Mkp2fE
z$pJ=9lka9tA`mGM2M`Oux%P*N^WWhT3y}8_m)yVOlGER~^tb)$e`mjXAwt=W1~lT5
z|6sqGXGd|@(thw-*%c^12zyWASNv)bOjvtj8+rr-(@Zc@HR@#U#myl>lx>vcX_@iO
z8w_}ysWaEORKV?Xh$_AYvV;4&ZLLYv6_+nJEciCNF@QoZ>$}
zIF1fGJld~LzuT{_^)t7_9A16sO@mD^%iam}%BW913pOD`U_i-Rjb=wU6%|qSqSK)&
zUp_N4pm|%VMKv)6ImCdL?Gu5wG=;E;uo(BEYQwi8b&X{Pw}{lvk)Fz;OTNcLG)Hmm
zDf@B=^@5Z!V8!B9QfM3~DP+!Tfg7_AtSk}WtH#@~1OL=X8
zp4P=LT9P+UFxtwuBDXd-%3{t(0ecqtng8LZLt6X!z2EFvK7c(-$OKM=%`(Dp90zLW
zdf-Bf$v^&Adlo*2s~KR=avOf02iUU+D57m2?O8CT=AuI1?O9ublePy?ik`~7m~-eo
zqxi&|da63N$tGBBoZ+={RJ(heTeCbPN<2}?IOxY0&U|>gBp%C>e)AAvd_HMf%B%x*Hb|AX2mlSCv_S{7++awg9N^yICF~jq9Zv5k{5O{$;WMGNbF`iK^
zc$u}UHO(giR^)I)$Zlu+g%-rM@8&FyW9Ex+1tLZ(=Y0p}u+oy~ibgm?8!*l!8a@$h
z72H=LZcpMiRbq4c>E2vsskhQR=PTR8f>ZwV-I~?v0OD-ZQV3YHKCo(zs$XXm8};5j
zTC>udXcb(M!6??cRB#rV4Cs{T=poMR?Dz83UhJ{-&O={(#Ip=CY^;u_fxxmY&Nfde
z=904&)~3?BR-aM#tLi*NJ+ewuU<7#(XK4Dvy+&fAJMwP&Q>
z96?6{B2uB8M>DyBmG;vM(J6hD
zQ2!iSe4I}7I1c&y{G!KO^8q#}{yD$s0pLCu5D0L9?|>2MM?ic;tQSCn{PyR-=iftw
ze-9D75WalfB)H}-nhWnM9IfNk0a|bYk`@H}
z9F1HIEOjaE=sAhwp1H@E>wW#;UMjm>pPV-0psW`Wp-DAq=dkO!VxA_Pp8RA2X
z&bSA4{R|R2p#>Z{Yo-8utxS7H%#2uKH@M3{KT42U4RWtuzF7q$SziaFIObdO&oQc^
z;pl=MLUUGgA-x0Vm^n#r+b_B@i^Y-QoAnrAA58tQz1XwH>1UwymwN;Dc({|#{&go^
z+>T%3Q5`%0fB#}^Ln53X7~4Sc13dC{a9fiXDU?YDb1{SJCEdUAlIX%%bf(1&wP;@_9c*R>kZsE+1`xsByI%p
zwsdE-i=Dg%)(x-IHNeb2G~$1YI{3Az0Z-w##VgRh
z|C)#PAH_;f1gJ=M@kBIomf56dixq>sp5VOH$w5vyt8Z?kU<>dR4N8*Daow;s
zlBnPgi?Dqfkv(?94%W}&NalIxi8SaSvw*W36v@8YD>%IlUQKFTFgI4s0jbsp4DEB?E%fmL4>b>9B@c)Rn`-B551FuvJ`
zm-*cHB_Y>eVm67ek_w>OzY6m98-H=+{_Nr1>zDnJ6h_V2+CR&6zFMpC&1lxkK?b~>
z*ERI9Xvl%H(v7ku0{Wx9C}Uzm^US&=9v>rZw5FdXq~WfxN{pXl$xgFrmxSl_K2)~T
z52Q)AdX!SH3qe@2#YmW}>1O27t;#h;}#Q5W4GJ|%(OPK*p}AID!21|;FyiBR~yxMw&;%(Ce8ozw;RcM!r3=WL_U*Ei|V57!+0JesS
zKtQnm)y@C#_f^pf!Rtl!KwjfQIHlcP4!x(2LpC9C8D6S>Ps+8RS2~eoVl`Z~Wx214
z@4w>OCr)JTv_=|H`x3vKV1YlZajhN4JgPt7wZpLS1WoncVkZQ;BV@nCuF~Y^l7bRS
zhvmCH1FUOeMh{0lBbHvgd_YFN$o2_vl@LGF>L9}k|B
z-Y|m<8kUWvgcMgU26}^;d7g*_9O$^t6sb82df0{A@yb7iFXjJquZ_%0D55O{ggJN?
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