tag r1873550 as 4.1.2
git-svn-id: https://svn.apache.org/repos/asf/poi/tags/REL_4_1_2@1873551 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/build.xml b/build.xml
index 774a40a..e858ad7 100644
--- a/build.xml
+++ b/build.xml
@@ -1648,7 +1648,7 @@
unless="integration.test.notRequired">
<propertyreset name="org.apache.poi.util.POILogger" value="org.apache.poi.util.CommonsLogger"/>
<delete dir="build" includes="test-integration.log*"/>
- <poiunit failureproperty="integration.test.failed" heap="2048" showoutput="true" jacocodest="build/jacoco-integration.exec">
+ <poiunit failureproperty="integration.test.failed" heap="1512" showoutput="true" jacocodest="build/jacoco-integration.exec">
<classpath refid="test.integration.classpath"/>
<batchtest todir="${integration.reports.test}">
<fileset dir="${integration.src.test}">
diff --git a/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java b/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java
index 2b1114a..64179ff 100644
--- a/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java
+++ b/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java
@@ -31,9 +31,9 @@
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.extractor.POIOLE2TextExtractor;
import org.apache.poi.extractor.POITextExtractor;
+import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
import org.apache.poi.hssf.extractor.EventBasedExcelExtractor;
import org.apache.poi.ooxml.extractor.ExtractorFactory;
-import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.ss.extractor.ExcelExtractor;
import org.apache.poi.util.IOUtils;
@@ -48,12 +48,13 @@
static {
// password protected files without password
// ... currently none ...
-
+
// unsupported file-types, no supported OLE2 parts
EXPECTED_EXTRACTOR_FAILURES.add("hmef/quick-winmail.dat");
EXPECTED_EXTRACTOR_FAILURES.add("hmef/winmail-sample1.dat");
EXPECTED_EXTRACTOR_FAILURES.add("hmef/bug52400-winmail-simple.dat");
EXPECTED_EXTRACTOR_FAILURES.add("hmef/bug52400-winmail-with-attachments.dat");
+ EXPECTED_EXTRACTOR_FAILURES.add("hmef/bug63955-winmail.dat");
EXPECTED_EXTRACTOR_FAILURES.add("hpsf/Test0313rur.adm");
EXPECTED_EXTRACTOR_FAILURES.add("poifs/Notes.ole2");
}
@@ -70,10 +71,10 @@
} finally {
ExtractorFactory.setThreadPrefersEventExtractors(before);
}
-
+
/* Did fail for some documents with special XML contents...
try {
- OOXMLPrettyPrint.main(new String[] { file.getAbsolutePath(),
+ OOXMLPrettyPrint.main(new String[] { file.getAbsolutePath(),
"/tmp/pretty-" + file.getName() });
} catch (ZipException e) {
// ignore, not a Zip/OOXML file
@@ -83,7 +84,7 @@
private void handleExtractingInternal(File file) throws Exception {
long length = file.length();
long modified = file.lastModified();
-
+
POITextExtractor extractor = null;
String fileAndParentName = file.getParentFile().getName() + "/" + file.getName();
try {
diff --git a/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java b/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java
index cd6b601..ba2e1bc 100644
--- a/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java
+++ b/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java
@@ -59,7 +59,8 @@
);
static final Set<String> EXCLUDES_HANDLE_FILE = unmodifiableHashSet(
- "hpsf/Test_Humor-Generation.ppt"
+ "hpsf/Test_Humor-Generation.ppt",
+ "slideshow/missing-moveto.ppt" // POIFS properties corrupted
);
diff --git a/src/java/org/apache/poi/sl/draw/DrawPaint.java b/src/java/org/apache/poi/sl/draw/DrawPaint.java
index d8a11a9..eb18e10 100644
--- a/src/java/org/apache/poi/sl/draw/DrawPaint.java
+++ b/src/java/org/apache/poi/sl/draw/DrawPaint.java
@@ -25,7 +25,6 @@
import java.awt.MultipleGradientPaint.ColorSpaceType;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.Paint;
-import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
@@ -33,9 +32,8 @@
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
-import java.awt.image.DataBufferInt;
-import java.awt.image.DirectColorModel;
-import java.awt.image.Raster;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.io.InputStream;
@@ -350,25 +348,42 @@
* @return the original image if no duotone was found, otherwise the colorized pattern
*/
private static BufferedImage colorizePattern(TexturePaint fill, BufferedImage pattern) {
- List<ColorStyle> duoTone = fill.getDuoTone();
+ final List<ColorStyle> duoTone = fill.getDuoTone();
if (duoTone == null || duoTone.size() != 2) {
return pattern;
}
// the pattern image is actually a gray scale image, so we simply take the first color component
// as an index into our gradient samples
- int blendShades = 1 << pattern.getSampleModel().getSampleSize(0);
- int[] gradSample = linearBlendedColors(duoTone, blendShades);
- int[] redSample = pattern.getRaster().getSamples(0, 0, pattern.getWidth(), pattern.getHeight(), 0, (int[])null);
+ final int redBits = pattern.getSampleModel().getSampleSize(0);
+ final int blendBits = Math.max(Math.min(redBits, 8), 1);
+ final int blendShades = 1 << blendBits;
+ // Currently ImageIO converts 16-bit images to 8-bit internally, so it's unlikely to get a blendRatio != 1
+ final double blendRatio = blendShades / (double)(1 << Math.max(redBits,1));
+ final int[] gradSample = linearBlendedColors(duoTone, blendShades);
- for (int i=0; i<redSample.length; i++) {
- redSample[i] = gradSample[redSample[i] & 0xFF];
+ final IndexColorModel icm = new IndexColorModel(blendBits, blendShades, gradSample, 0, true, -1, DataBuffer.TYPE_BYTE);
+ final BufferedImage patIdx = new BufferedImage(pattern.getWidth(), pattern.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, icm);
+
+ final WritableRaster rasterRGBA = pattern.getRaster();
+ final WritableRaster rasterIdx = patIdx.getRaster();
+
+ final int[] redSample = new int[pattern.getWidth()];
+ for (int y=0; y<pattern.getHeight(); y++) {
+ rasterRGBA.getSamples(0, y, redSample.length, 1, 0, redSample);
+ scaleShades(redSample, blendRatio);
+ rasterIdx.setSamples(0, y, redSample.length, 1, 0, redSample);
}
- DirectColorModel dcm = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
- DataBufferInt dbi = new DataBufferInt(redSample, redSample.length);
- WritableRaster raster = Raster.createPackedRaster(dbi, pattern.getWidth(), pattern.getHeight(), pattern.getWidth(), new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000}, new Point());
- return new BufferedImage(dcm, raster, true, null);
+ return patIdx;
+ }
+
+ private static void scaleShades(int[] samples, double ratio) {
+ if (ratio != 1) {
+ for (int x=0; x<samples.length; x++) {
+ samples[x] = (int)Math.rint(samples[x] * ratio);
+ }
+ }
}
private static int[] linearBlendedColors(List<ColorStyle> duoTone, final int blendShades) {
diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
index 1ab4af3..c1de9c9 100644
--- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
+++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
@@ -31,6 +31,7 @@
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -60,7 +61,7 @@
public class DrawTextParagraph implements Drawable {
private static final POILogger LOG = POILogFactory.getLogger(DrawTextParagraph.class);
-
+
/** Keys for passing hyperlinks to the graphics context */
public static final XlinkAttribute HYPERLINK_HREF = new XlinkAttribute("href");
public static final XlinkAttribute HYPERLINK_LABEL = new XlinkAttribute("label");
@@ -206,7 +207,7 @@
line.setPosition(penX, penY);
line.draw(graphics);
-
+
if(spacing > 0) {
// If linespacing >= 0, then linespacing is a percentage of normal line height.
penY += spacing*0.01* line.getHeight();
@@ -383,7 +384,14 @@
@Internal
public String getRenderableText(final TextRun tr) {
- final String txtSpace = tr.getRawText().replace("\t", tab2space(tr)).replace('\u000b', '\n');
+ String txtSpace = tr.getRawText();
+ if (txtSpace == null) {
+ return null;
+ }
+ if (txtSpace.contains("\t")) {
+ txtSpace = txtSpace.replace("\t", tab2space(tr));
+ }
+ txtSpace = txtSpace.replace('\u000b', '\n');
final Locale loc = LocaleUtil.getUserLocale();
switch (tr.getTextCap()) {
@@ -414,19 +422,23 @@
string.addAttribute(TextAttribute.SIZE, fs.floatValue());
TextLayout l = new TextLayout(string.getIterator(), new FontRenderContext(null, true, true));
+ // some JDK versions return 0 here
double wspace = l.getAdvance();
+ final int numSpaces;
Double tabSz = paragraph.getDefaultTabSize();
- if (tabSz == null) {
- tabSz = wspace*4;
+ if (wspace <= 0) {
+ numSpaces = 4;
+ } else {
+ if (tabSz == null) {
+ tabSz = wspace*4;
+ }
+ numSpaces = (int)Math.min(Math.ceil(tabSz / wspace), 20);
}
- int numSpaces = (int)Math.ceil(tabSz / wspace);
- StringBuilder buf = new StringBuilder();
- for(int i = 0; i < numSpaces; i++) {
- buf.append(' ');
- }
- return buf.toString();
+ char[] buf = new char[numSpaces];
+ Arrays.fill(buf, ' ');
+ return new String(buf);
}
@@ -683,7 +695,7 @@
// fallback for unsupported glyphs
partBegin = partEnd;
partEnd = nextPart(fontMapped, runText, partBegin, rangeBegin+rangeLen, false);
-
+
if (partBegin < partEnd) {
// handle (a) and (b)
attList.add(new AttributedStringData(TextAttribute.FAMILY, fontFallback.getFontName(Locale.ROOT), beginIndex+partBegin, beginIndex+partEnd));
@@ -692,11 +704,11 @@
}
}
}
-
+
rangeBegin += rangeLen;
}
}
-
+
private static int nextPart(Font fontMapped, String runText, int beginPart, int endPart, boolean isDisplayed) {
int rIdx = beginPart;
while (rIdx < endPart) {
diff --git a/src/java/org/apache/poi/sl/draw/geom/PresetGeometries.java b/src/java/org/apache/poi/sl/draw/geom/PresetGeometries.java
index 98b2d58..6b191bd 100644
--- a/src/java/org/apache/poi/sl/draw/geom/PresetGeometries.java
+++ b/src/java/org/apache/poi/sl/draw/geom/PresetGeometries.java
@@ -38,12 +38,24 @@
import org.apache.poi.util.XMLHelper;
/**
- *
+ *
*/
public class PresetGeometries extends LinkedHashMap<String, CustomGeometry> {
private final static POILogger LOG = POILogFactory.getLogger(PresetGeometries.class);
- protected final static String BINDING_PACKAGE = "org.apache.poi.sl.draw.binding";
-
+ private final static String BINDING_PACKAGE = "org.apache.poi.sl.draw.binding";
+
+ private static class SingletonHelper {
+ private static JAXBContext JAXB_CONTEXT;
+ static {
+ try {
+ JAXB_CONTEXT = JAXBContext.newInstance(BINDING_PACKAGE);
+ } catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
protected static PresetGeometries _inst;
protected PresetGeometries(){}
@@ -57,7 +69,7 @@
streamReader.nextTag();
// JAXB:
- JAXBContext jaxbContext = JAXBContext.newInstance(BINDING_PACKAGE);
+ JAXBContext jaxbContext = SingletonHelper.JAXB_CONTEXT;
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
long cntElem = 0;
@@ -76,13 +88,13 @@
streamReader.close();
}
}
-
+
/**
* Convert a single CustomGeometry object, i.e. from xmlbeans
*/
public static CustomGeometry convertCustomGeometry(XMLStreamReader staxReader) {
try {
- JAXBContext jaxbContext = JAXBContext.newInstance(BINDING_PACKAGE);
+ JAXBContext jaxbContext = SingletonHelper.JAXB_CONTEXT;
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
JAXBElement<CTCustomGeometry2D> el = unmarshaller.unmarshal(staxReader, CTCustomGeometry2D.class);
return new CustomGeometry(el.getValue());
diff --git a/src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java b/src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java
index 6085b9d..1d2c342 100644
--- a/src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java
+++ b/src/ooxml/java/org/apache/poi/xslf/model/ParagraphPropertyFetcher.java
@@ -19,15 +19,21 @@
package org.apache.poi.xslf.model;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
+
import org.apache.poi.xslf.usermodel.XSLFShape;
-import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties;
-/**
- *
- * @author Yegor Kozlov
- */
public abstract class ParagraphPropertyFetcher<T> extends PropertyFetcher<T> {
+ static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
+ static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
+
+ private static final QName[] TX_BODY = { new QName(PML_NS, "txBody") };
+ private static final QName[] LST_STYLE = { new QName(DML_NS, "lstStyle") };
+
int _level;
public ParagraphPropertyFetcher(int level) {
@@ -35,17 +41,20 @@
}
public boolean fetch(XSLFShape shape) {
-
- XmlObject[] o = shape.getXmlObject().selectPath(
- "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " +
- "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " +
- ".//p:txBody/a:lstStyle/a:lvl" + (_level + 1) + "pPr"
- );
- if (o.length == 1) {
- CTTextParagraphProperties props = (CTTextParagraphProperties) o[0];
- return fetch(props);
+ QName[] lvlProp = { new QName(DML_NS, "lvl" + (_level + 1) + "pPr") };
+ CTTextParagraphProperties props = null;
+ try {
+ props = shape.selectProperty(
+ CTTextParagraphProperties.class, ParagraphPropertyFetcher::parse, TX_BODY, LST_STYLE, lvlProp);
+ return (props != null) && fetch(props);
+ } catch (XmlException e) {
+ return false;
}
- return false;
+ }
+
+ private static CTTextParagraphProperties parse(XMLStreamReader reader) throws XmlException {
+ CTTextParagraph para = CTTextParagraph.Factory.parse(reader);
+ return (para != null && para.isSetPPr()) ? para.getPPr() : null;
}
public abstract boolean fetch(CTTextParagraphProperties props);
diff --git a/src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java b/src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java
index 4b1d546..9bb0250 100644
--- a/src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java
+++ b/src/ooxml/java/org/apache/poi/xslf/model/TextBodyPropertyFetcher.java
@@ -19,32 +19,35 @@
package org.apache.poi.xslf.model;
+import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.DML_NS;
+import static org.apache.poi.xslf.model.ParagraphPropertyFetcher.PML_NS;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
+
import org.apache.poi.xslf.usermodel.XSLFShape;
-import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties;
-/**
- * Created by IntelliJ IDEA.
- * User: yegor
- * Date: Oct 21, 2011
- * Time: 1:18:52 PM
- * To change this template use File | Settings | File Templates.
- */
public abstract class TextBodyPropertyFetcher<T> extends PropertyFetcher<T> {
+ private static final QName[] TX_BODY = { new QName(PML_NS, "txBody") };
+ private static final QName[] BODY_PR = { new QName(DML_NS, "bodyPr") };
public boolean fetch(XSLFShape shape) {
-
- XmlObject[] o = shape.getXmlObject().selectPath(
- "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' " +
- "declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' " +
- ".//p:txBody/a:bodyPr"
- );
- if (o.length == 1) {
- CTTextBodyProperties props = (CTTextBodyProperties) o[0];
- return fetch(props);
+ CTTextBodyProperties props = null;
+ try {
+ props = shape.selectProperty(
+ CTTextBodyProperties.class, TextBodyPropertyFetcher::parse, TX_BODY, BODY_PR);
+ return (props != null) && fetch(props);
+ } catch (XmlException e) {
+ return false;
}
+ }
- return false;
+ private static CTTextBodyProperties parse(XMLStreamReader reader) throws XmlException {
+ CTTextBody body = CTTextBody.Factory.parse(reader);
+ return (body != null) ? body.getBodyPr() : null;
}
public abstract boolean fetch(CTTextBodyProperties props);
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java
index 3404227..eb71e7e 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFObjectShape.java
@@ -25,10 +25,11 @@
import java.io.OutputStream;
import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
+import org.apache.poi.hpsf.ClassID;
import org.apache.poi.ooxml.POIXMLDocumentPart.RelationPart;
import org.apache.poi.ooxml.POIXMLException;
-import org.apache.poi.hpsf.ClassID;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
@@ -42,8 +43,6 @@
import org.apache.poi.util.Internal;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
-import org.apache.xmlbeans.XmlObject;
-import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlip;
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
@@ -63,6 +62,10 @@
public class XSLFObjectShape extends XSLFGraphicFrame implements ObjectShape<XSLFShape,XSLFTextParagraph> {
/* package */ static final String OLE_URI = "http://schemas.openxmlformats.org/presentationml/2006/ole";
+ private static final QName[] GRAPHIC = { new QName(DML_NS, "graphic") };
+ private static final QName[] GRAPHIC_DATA = { new QName(DML_NS, "graphicData") };
+ private static final QName[] OLE_OBJ = { new QName(PML_NS, "oleObj") };
+ private static final QName[] CT_PICTURE = { new QName(PML_NS, "pic") };
private CTOleObject _oleObject;
private XSLFPictureData _data;
@@ -70,31 +73,13 @@
/*package*/ XSLFObjectShape(CTGraphicalObjectFrame shape, XSLFSheet sheet){
super(shape, sheet);
- CTGraphicalObjectData god = shape.getGraphic().getGraphicData();
- XmlCursor xc = god.newCursor();
// select oleObj potentially under AlternateContent
// usually the mc:Choice element will be selected first
- xc.selectPath("declare namespace p='"+PML_NS+"' .//p:oleObj");
try {
- if (!xc.toNextSelection()) {
- throw new IllegalStateException("p:oleObj element was not found in\n " + god);
- }
-
- XmlObject xo = xc.getObject();
- // Pesky XmlBeans bug - see Bugzilla #49934
- // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
- if (xo instanceof XmlAnyTypeImpl){
- String errStr =
- "Schemas (*.xsb) for CTOleObject can't be loaded - usually this happens when OSGI " +
- "loading is used and the thread context classloader has no reference to " +
- "the xmlbeans classes - use POIXMLTypeLoader.setClassLoader() to set the loader, " +
- "e.g. with CTOleObject.class.getClassLoader()"
- ;
- throw new IllegalStateException(errStr);
- }
- _oleObject = (CTOleObject)xo;
- } finally {
- xc.dispose();
+ _oleObject = selectProperty(CTOleObject.class, null, GRAPHIC, GRAPHIC_DATA, OLE_OBJ);
+ } catch (XmlException e) {
+ // ole objects should be also inside AlternateContent tags, even with ECMA 376 edition 1
+ throw new IllegalStateException(e);
}
}
@@ -113,13 +98,13 @@
public String getProgId() {
return (_oleObject == null) ? null : _oleObject.getProgId();
}
-
+
@Override
public String getFullName() {
return (_oleObject == null) ? null : _oleObject.getName();
}
-
-
+
+
/**
* Return the data on the (internal) picture.
* For an external linked picture, will return null
@@ -160,19 +145,19 @@
}
protected CTBlipFillProperties getBlipFill() {
- String xquery =
- "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' "
- + ".//p:blipFill"
- ;
- XmlObject xo = selectProperty(XmlObject.class, xquery);
try {
- xo = CTPicture.Factory.parse(xo.getDomNode());
- } catch (XmlException xe) {
+ CTPicture pic = selectProperty
+ (CTPicture.class, XSLFObjectShape::parse, GRAPHIC, GRAPHIC_DATA, OLE_OBJ, CT_PICTURE);
+ return (pic != null) ? pic.getBlipFill() : null;
+ } catch (XmlException e) {
return null;
}
- return ((CTPicture)xo).getBlipFill();
}
+ private static CTPicture parse(XMLStreamReader reader) throws XmlException {
+ CTGroupShape gs = CTGroupShape.Factory.parse(reader);
+ return (gs.sizeOfPicArray() > 0) ? gs.getPicArray(0) : null;
+ }
@Override
public OutputStream updateObjectData(final Application application, final ObjectMetaData metaData) throws IOException {
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java
index 1a0c96f..1408bed 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPictureShape.java
@@ -33,6 +33,7 @@
import javax.imageio.ImageIO;
import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
@@ -69,11 +70,15 @@
implements PictureShape<XSLFShape,XSLFTextParagraph> {
private static final POILogger LOG = POILogFactory.getLogger(XSLFPictureShape.class);
- private static final String DML_NS = "http://schemas.microsoft.com/office/drawing/2010/main";
- private static final String SVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
+ private static final String MS_DML_NS = "http://schemas.microsoft.com/office/drawing/2010/main";
+ private static final String MS_SVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
private static final String BITMAP_URI = "{28A0092B-C50C-407E-A947-70E740481C1C}";
private static final String SVG_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
+ private static final QName EMBED_TAG = new QName(CORE_PROPERTIES_ECMA376_NS, "embed", "rel");
+ private static final QName[] BLIP_FILL = { new QName(PML_NS, "blipFill") };
+
+
private XSLFPictureData _data;
/*package*/ XSLFPictureShape(CTPicture shape, XSLFSheet sheet) {
@@ -135,8 +140,8 @@
public void setPlaceholder(Placeholder placeholder) {
super.setPlaceholder(placeholder);
}
-
-
+
+
/**
* For an external linked picture, return the last-seen
* path to the picture.
@@ -147,13 +152,13 @@
// Internal picture, nothing to return
return null;
}
-
+
String rId = getBlipLink();
if (rId == null) {
// No link recorded, nothing we can do
return null;
}
-
+
PackagePart p = getSheet().getPackagePart();
PackageRelationship rel = p.getRelationship(rId);
if (rel != null) {
@@ -168,25 +173,23 @@
if (bfp != null) {
return bfp;
}
-
- String xquery =
- "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main'; "
- + "declare namespace mc='http://schemas.openxmlformats.org/markup-compatibility/2006' "
- + ".//mc:Fallback/p:blipFill"
- ;
- XmlObject xo = selectProperty(XmlObject.class, xquery);
+
try {
- xo = CTPicture.Factory.parse(xo.getDomNode());
+ return selectProperty(CTBlipFillProperties.class, XSLFPictureShape::parse, BLIP_FILL);
} catch (XmlException xe) {
return null;
}
- return ((CTPicture)xo).getBlipFill();
}
-
+
+ private static CTBlipFillProperties parse(XMLStreamReader reader) throws XmlException {
+ CTPicture pic = CTPicture.Factory.parse(reader);
+ return (pic != null) ? pic.getBlipFill() : null;
+ }
+
protected CTBlip getBlip(){
return getBlipFill().getBlip();
}
-
+
@SuppressWarnings("WeakerAccess")
protected String getBlipLink(){
CTBlip blip = getBlip();
@@ -232,8 +235,8 @@
extBitmap.setUri(BITMAP_URI);
XmlCursor cur = extBitmap.newCursor();
cur.toEndToken();
- cur.beginElement(new QName(DML_NS, "useLocalDpi", "a14"));
- cur.insertNamespace("a14", DML_NS);
+ cur.beginElement(new QName(MS_DML_NS, "useLocalDpi", "a14"));
+ cur.insertNamespace("a14", MS_DML_NS);
cur.insertAttributeWithValue("val", "0");
cur.dispose();
}
@@ -252,9 +255,9 @@
svgBitmap.setUri(SVG_URI);
XmlCursor cur = svgBitmap.newCursor();
cur.toEndToken();
- cur.beginElement(new QName(SVG_NS, "svgBlip", "asvg"));
- cur.insertNamespace("asvg", SVG_NS);
- cur.insertAttributeWithValue(new QName(CORE_PROPERTIES_ECMA376_NS, "embed", "rel"), svgRelId);
+ cur.beginElement(new QName(MS_SVG_NS, "svgBlip", "asvg"));
+ cur.insertNamespace("asvg", MS_SVG_NS);
+ cur.insertAttributeWithValue(EMBED_TAG, svgRelId);
cur.dispose();
}
@@ -277,8 +280,8 @@
for (int i = 0; i < size; i++) {
XmlCursor cur = extLst.getExtArray(i).newCursor();
try {
- if (cur.toChild(SVG_NS, "svgBlip")) {
- String svgRelId = cur.getAttributeText(new QName(CORE_PROPERTIES_ECMA376_NS, "embed"));
+ if (cur.toChild(MS_SVG_NS, "svgBlip")) {
+ String svgRelId = cur.getAttributeText(EMBED_TAG);
return (svgRelId != null) ? (XSLFPictureData) getSheet().getRelationById(svgRelId) : null;
}
} finally {
@@ -367,13 +370,13 @@
CTOfficeArtExtensionList extLst = blip.getExtLst();
//noinspection deprecation
for(CTOfficeArtExtension ext : extLst.getExtArray()){
- String xpath = "declare namespace a14='"+ DML_NS +"' $this//a14:imgProps/a14:imgLayer";
+ String xpath = "declare namespace a14='"+ MS_DML_NS +"' $this//a14:imgProps/a14:imgLayer";
XmlObject[] obj = ext.selectPath(xpath);
if(obj != null && obj.length == 1){
XmlCursor c = obj[0].newCursor();
- String id = c.getAttributeText(new QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed"));//selectPath("declare namespace r='http://schemas.openxmlformats.org/officeDocument/2006/relationships' $this//[@embed]");
+ String id = c.getAttributeText(EMBED_TAG);
String newId = getSheet().importBlip(id, p.getSheet());
- c.setAttributeText(new QName("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed"), newId);
+ c.setAttributeText(EMBED_TAG, newId);
c.dispose();
}
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
index e5d321b..6cb179d 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFPlaceholderDetails.java
@@ -22,9 +22,12 @@
import java.util.function.Consumer;
import java.util.function.Function;
+import javax.xml.namespace.QName;
+
import org.apache.poi.sl.usermodel.MasterSheet;
import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.PlaceholderDetails;
+import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
import org.openxmlformats.schemas.presentationml.x2006.main.CTHeaderFooter;
import org.openxmlformats.schemas.presentationml.x2006.main.CTNotesMaster;
@@ -190,9 +193,24 @@
return _ph;
}
+ private static final QName[] NV_CONTAINER = {
+ new QName(PML_NS, "nvSpPr"),
+ new QName(PML_NS, "nvCxnSpPr"),
+ new QName(PML_NS, "nvGrpSpPr"),
+ new QName(PML_NS, "nvPicPr"),
+ new QName(PML_NS, "nvGraphicFramePr")
+ };
+
+ private static final QName[] NV_PROPS = {
+ new QName(PML_NS, "nvPr")
+ };
+
private CTApplicationNonVisualDrawingProps getNvProps() {
- final String xquery = "declare namespace p='" + PML_NS + "' .//*/p:nvPr";
- return shape.selectProperty(CTApplicationNonVisualDrawingProps.class, xquery);
+ try {
+ return shape.selectProperty(CTApplicationNonVisualDrawingProps.class, null, NV_CONTAINER, NV_PROPS);
+ } catch (XmlException e) {
+ return null;
+ }
}
private CTHeaderFooter getHeaderFooter(final boolean create) {
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 7d80fbe..efdbd7b 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
@@ -21,7 +21,13 @@
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
+import java.util.Locale;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamReader;
+
+import com.microsoft.schemas.compatibility.AlternateContentDocument;
+import com.microsoft.schemas.compatibility.AlternateContentDocument.AlternateContent;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawPaint;
@@ -37,7 +43,9 @@
import org.apache.poi.xslf.model.PropertyFetcher;
import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties;
import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
import org.openxmlformats.schemas.drawingml.x2006.main.CTBlipFillProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGradientFillProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupShapeProperties;
@@ -59,7 +67,36 @@
*/
@Beta
public abstract class XSLFShape implements Shape<XSLFShape,XSLFTextParagraph> {
+
+ @Internal
+ public interface ReparseFactory<T extends XmlObject> {
+ T parse(XMLStreamReader reader) throws XmlException;
+ }
+
+ static final String DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
static final String PML_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
+ private static final String MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
+ private static final String MAC_DML_NS = "http://schemas.microsoft.com/office/mac/drawingml/2008/main";
+
+ private static final QName ALTERNATE_CONTENT_TAG = new QName(MC_NS, "AlternateContent");
+
+ private static final QName[] NV_CONTAINER = {
+ new QName(PML_NS, "nvSpPr"),
+ new QName(PML_NS, "nvCxnSpPr"),
+ new QName(PML_NS, "nvGrpSpPr"),
+ new QName(PML_NS, "nvPicPr"),
+ new QName(PML_NS, "nvGraphicFramePr")
+ };
+
+ private static final QName[] CNV_PROPS = {
+ new QName(PML_NS, "cNvPr")
+ };
+
+ private static final String OSGI_ERROR =
+ "Schemas (*.xsb) for <CLASS> can't be loaded - usually this happens when OSGI " +
+ "loading is used and the thread context classloader has no reference to " +
+ "the xmlbeans classes - please either verify if the <XSB>.xsb is on the " +
+ "classpath or alternatively try to use the full ooxml-schemas-x.x.jar";
private final XmlObject _shape;
private final XSLFSheet _sheet;
@@ -199,11 +236,14 @@
}
protected CTNonVisualDrawingProps getCNvPr() {
- if (_nvPr == null) {
- String xquery = "declare namespace p='http://schemas.openxmlformats.org/presentationml/2006/main' .//*/p:cNvPr";
- _nvPr = selectProperty(CTNonVisualDrawingProps.class, xquery);
+ try {
+ if (_nvPr == null) {
+ _nvPr = selectProperty(CTNonVisualDrawingProps.class, null, NV_CONTAINER, CNV_PROPS);
+ }
+ return _nvPr;
+ } catch (XmlException e) {
+ return null;
}
- return _nvPr;
}
@SuppressWarnings("WeakerAccess")
@@ -282,6 +322,160 @@
}
/**
+ * Internal code - API may change any time!
+ * <p>
+ * The {@link #selectProperty(Class, String)} xquery method has some performance penalties,
+ * which can be workaround by using {@link XmlCursor}. This method also takes into account
+ * that {@code AlternateContent} tags can occur anywhere on the given path.
+ * <p>
+ * It returns the first element found - the search order is:
+ * <ul>
+ * <li>searching for a direct child</li>
+ * <li>searching for a AlternateContent.Choice child</li>
+ * <li>searching for a AlternateContent.Fallback child</li>
+ * </ul>
+ * Currently POI OOXML is based on the first edition of the ECMA 376 schema, which doesn't
+ * allow AlternateContent tags to show up everywhere. The factory flag is
+ * a workaround to process files based on a later edition. But it comes with the drawback:
+ * any change on the returned XmlObject aren't saved back to the underlying document -
+ * so it's a non updatable clone. If factory is null, a XmlException is
+ * thrown if the AlternateContent is not allowed by the surrounding element or if the
+ * extracted object is of the generic type XmlAnyTypeImpl.
+ *
+ * @param resultClass the requested result class
+ * @param factory a factory parse method reference to allow reparsing of elements
+ * extracted from AlternateContent elements. Usually the enclosing XmlBeans type needs to be used
+ * to parse the stream
+ * @param path the elements path, each array must contain at least 1 QName,
+ * but can contain additional alternative tags
+ * @return the xml object at the path location, or null if not found
+ *
+ * @throws XmlException If factory is null, a XmlException is
+ * thrown if the AlternateContent is not allowed by the surrounding element or if the
+ * extracted object is of the generic type XmlAnyTypeImpl.
+ *
+ * @since POI 4.1.2
+ */
+ @SuppressWarnings("unchecked")
+ @Internal
+ public <T extends XmlObject> T selectProperty(Class<T> resultClass, ReparseFactory<T> factory, QName[]... path)
+ throws XmlException {
+ XmlObject xo = getXmlObject();
+ XmlCursor cur = xo.newCursor();
+ XmlCursor innerCur = null;
+ try {
+ innerCur = selectProperty(cur, path, 0, factory != null, false);
+ if (innerCur == null) {
+ return null;
+ }
+
+ // Pesky XmlBeans bug - see Bugzilla #49934
+ // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
+ xo = innerCur.getObject();
+ if (xo instanceof XmlAnyTypeImpl) {
+ String errorTxt = OSGI_ERROR
+ .replace("<CLASS>", resultClass.getSimpleName())
+ .replace("<XSB>", resultClass.getSimpleName().toLowerCase(Locale.ROOT)+"*");
+ if (factory == null) {
+ throw new XmlException(errorTxt);
+ } else {
+ xo = factory.parse(innerCur.newXMLStreamReader());
+ }
+ }
+
+ return (T)xo;
+ } finally {
+ cur.dispose();
+ if (innerCur != null) {
+ innerCur.dispose();
+ }
+ }
+ }
+
+ private XmlCursor selectProperty(final XmlCursor cur, final QName[][] path, final int offset, final boolean reparseAlternate, final boolean isAlternate)
+ throws XmlException {
+ // first try the direct children
+ for (QName qn : path[offset]) {
+ if (cur.toChild(qn)) {
+ if (offset == path.length-1) {
+ return cur;
+ }
+ cur.push();
+ XmlCursor innerCur = selectProperty(cur, path, offset+1, reparseAlternate, false);
+ if (innerCur != null) {
+ return innerCur;
+ }
+ cur.pop();
+ }
+ }
+ // if we were called inside an alternate content handling don't look for alternates again
+ if (isAlternate || !cur.toChild(ALTERNATE_CONTENT_TAG)) {
+ return null;
+ }
+
+ // otherwise check first the choice then the fallback content
+ XmlObject xo = cur.getObject();
+ AlternateContent alterCont;
+ if (xo instanceof AlternateContent) {
+ alterCont = (AlternateContent)xo;
+ } else {
+ // Pesky XmlBeans bug - see Bugzilla #49934
+ // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
+ if (!reparseAlternate) {
+ throw new XmlException(OSGI_ERROR
+ .replace("<CLASS>", "AlternateContent")
+ .replace("<XSB>", "alternatecontentelement")
+ );
+ }
+ try {
+ AlternateContentDocument acd = AlternateContentDocument.Factory.parse(cur.newXMLStreamReader());
+ alterCont = acd.getAlternateContent();
+ } catch (XmlException e) {
+ throw new XmlException("unable to parse AlternateContent element", e);
+ }
+ }
+
+ final int choices = alterCont.sizeOfChoiceArray();
+ for (int i=0; i<choices; i++) {
+ // TODO: check [Requires] attribute of [Choice] element, if we can handle the content
+ AlternateContent.Choice choice = alterCont.getChoiceArray(i);
+ XmlCursor cCur = choice.newCursor();
+ XmlCursor innerCur = null;
+ try {
+ String requiresNS = cCur.namespaceForPrefix(choice.getRequires());
+ if (MAC_DML_NS.equalsIgnoreCase(requiresNS)) {
+ // Mac DML usually contains PDFs ...
+ continue;
+ }
+ innerCur = selectProperty(cCur, path, offset, reparseAlternate, true);
+ if (innerCur != null) {
+ return innerCur;
+ }
+ } finally {
+ if (innerCur != cCur) {
+ cCur.dispose();
+ }
+ }
+ }
+
+ if (!alterCont.isSetFallback()) {
+ return null;
+ }
+
+ XmlCursor fCur = alterCont.getFallback().newCursor();
+ XmlCursor innerCur = null;
+ try {
+ innerCur = selectProperty(fCur, path, offset, reparseAlternate, true);
+ return innerCur;
+ } finally {
+ if (innerCur != fCur) {
+ fCur.dispose();
+ }
+ }
+ }
+
+
+ /**
* Walk up the inheritance tree and fetch shape properties.<p>
*
* The following order of inheritance is assumed:<p>
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java b/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java
index a9a9e16..d83de63 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/TestXSLFBugs.java
@@ -17,7 +17,14 @@
package org.apache.poi.xslf;
import static org.apache.poi.POITestCase.assertContains;
-import static org.junit.Assert.*;
+import static org.apache.poi.xslf.XSLFTestDataSamples.openSampleDocument;
+import static org.junit.Assert.assertArrayEquals;
+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;
import java.awt.Color;
import java.awt.Dimension;
@@ -66,6 +73,7 @@
import org.apache.poi.xslf.usermodel.XSLFGroupShape;
import org.apache.poi.xslf.usermodel.XSLFHyperlink;
import org.apache.poi.xslf.usermodel.XSLFNotes;
+import org.apache.poi.xslf.usermodel.XSLFObjectShape;
import org.apache.poi.xslf.usermodel.XSLFPictureData;
import org.apache.poi.xslf.usermodel.XSLFPictureShape;
import org.apache.poi.xslf.usermodel.XSLFRelation;
@@ -89,7 +97,7 @@
@Test
public void bug62929() throws Exception {
- try(XMLSlideShow ss1 = XSLFTestDataSamples.openSampleDocument("missing-blip-fill.pptx")) {
+ try(XMLSlideShow ss1 = openSampleDocument("missing-blip-fill.pptx")) {
assertEquals(1, ss1.getSlides().size());
XSLFSlide slide = ss1.getSlides().get(0);
@@ -108,7 +116,7 @@
@Test
public void bug62736() throws Exception {
- XMLSlideShow ss1 = XSLFTestDataSamples.openSampleDocument("bug62736.pptx");
+ XMLSlideShow ss1 = openSampleDocument("bug62736.pptx");
assertEquals(1, ss1.getSlides().size());
@@ -332,7 +340,7 @@
@Test
public void bug51187() throws Exception {
- XMLSlideShow ss1 = XSLFTestDataSamples.openSampleDocument("51187.pptx");
+ XMLSlideShow ss1 = openSampleDocument("51187.pptx");
assertEquals(1, ss1.getSlides().size());
@@ -373,7 +381,7 @@
*/
@Test
public void tika705() throws Exception {
- XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("with_japanese.pptx");
+ XMLSlideShow ss = openSampleDocument("with_japanese.pptx");
// Should have one slide
assertEquals(1, ss.getSlides().size());
@@ -423,7 +431,7 @@
*/
@Test
public void bug54916() throws IOException {
- try (XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("OverlappingRelations.pptx")) {
+ try (XMLSlideShow ss = openSampleDocument("OverlappingRelations.pptx")) {
XSLFSlide slide;
// Should find 4 slides
@@ -452,7 +460,7 @@
*/
@Test
public void bug56812() throws Exception {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("56812.pptx");
+ XMLSlideShow ppt = openSampleDocument("56812.pptx");
int internalPictures = 0;
int externalPictures = 0;
@@ -485,7 +493,7 @@
@Test
@Ignore("Similar to TestFontRendering it doesn't make sense to compare images because of tiny rendering differences in windows/unix")
public void bug54542() throws Exception {
- XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("54542_cropped_bitmap.pptx");
+ XMLSlideShow ss = openSampleDocument("54542_cropped_bitmap.pptx");
Dimension pgsize = ss.getPageSize();
@@ -676,7 +684,7 @@
@Test
public void bug58205() throws IOException {
- XMLSlideShow ss = XSLFTestDataSamples.openSampleDocument("themes.pptx");
+ XMLSlideShow ss = openSampleDocument("themes.pptx");
int i = 1;
for (XSLFSlideMaster sm : ss.getSlideMasters()) {
@@ -688,14 +696,14 @@
@Test
public void bug55791a() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("45541_Footer.pptx");
+ XMLSlideShow ppt = openSampleDocument("45541_Footer.pptx");
removeAndCreateSlide(ppt);
ppt.close();
}
@Test
public void bug55791b() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("SampleShow.pptx");
+ XMLSlideShow ppt = openSampleDocument("SampleShow.pptx");
removeAndCreateSlide(ppt);
ppt.close();
}
@@ -708,7 +716,7 @@
@Test
public void blibFillAlternateContent() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("2411-Performance_Up.pptx");
+ XMLSlideShow ppt = openSampleDocument("2411-Performance_Up.pptx");
XSLFPictureShape ps = (XSLFPictureShape)ppt.getSlides().get(4).getShapes().get(0);
assertNotNull(ps.getPictureData());
ppt.close();
@@ -781,7 +789,7 @@
@Test
public void bug55714() throws IOException {
- XMLSlideShow srcPptx = XSLFTestDataSamples.openSampleDocument("pptx2svg.pptx");
+ XMLSlideShow srcPptx = openSampleDocument("pptx2svg.pptx");
XMLSlideShow newPptx = new XMLSlideShow();
XSLFSlide srcSlide = srcPptx.getSlides().get(0);
XSLFSlide newSlide = newPptx.createSlide();
@@ -807,7 +815,7 @@
@Test
public void bug59273() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("bug59273.potx");
+ XMLSlideShow ppt = openSampleDocument("bug59273.potx");
ppt.getPackage().replaceContentType(
XSLFRelation.PRESENTATIONML_TEMPLATE.getContentType(),
XSLFRelation.MAIN.getContentType()
@@ -851,7 +859,7 @@
@Test
public void bug60715() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("bug60715.pptx");
+ XMLSlideShow ppt = openSampleDocument("bug60715.pptx");
ppt.createSlide();
ppt.close();
}
@@ -887,7 +895,7 @@
@Test
public void test60810() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("60810.pptx");
+ XMLSlideShow ppt = openSampleDocument("60810.pptx");
for(XSLFSlide slide : ppt.getSlides()) {
XSLFNotes notesSlide = ppt.getNotesSlide(slide);
assertNotNull(notesSlide);
@@ -898,7 +906,7 @@
@Test
public void test60042() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("60042.pptx");
+ XMLSlideShow ppt = openSampleDocument("60042.pptx");
ppt.removeSlide(0);
ppt.createSlide();
ppt.close();
@@ -906,7 +914,7 @@
@Test
public void test61515() throws IOException {
- XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("61515.pptx");
+ XMLSlideShow ppt = openSampleDocument("61515.pptx");
ppt.removeSlide(0);
assertEquals(1, ppt.createSlide().getRelations().size());
try {
@@ -923,7 +931,7 @@
@Test
public void testAptia() throws IOException {
- try (XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("aptia.pptx");
+ try (XMLSlideShow ppt = openSampleDocument("aptia.pptx");
XMLSlideShow saved = XSLFTestDataSamples.writeOutAndReadBack(ppt)) {
assertEquals(ppt.getSlides().size(), saved.getSlides().size());
}
@@ -932,7 +940,7 @@
@Ignore
@Test
public void testDivinoRevelado() throws IOException {
- try (XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("Divino_Revelado.pptx");
+ try (XMLSlideShow ppt = openSampleDocument("Divino_Revelado.pptx");
XMLSlideShow saved = XSLFTestDataSamples.writeOutAndReadBack(ppt)){
assertEquals(ppt.getSlides().size(), saved.getSlides().size());
}
@@ -968,7 +976,7 @@
@Test
public void bug63200() throws Exception {
- try (XMLSlideShow ss1 = XSLFTestDataSamples.openSampleDocument("63200.pptx")) {
+ try (XMLSlideShow ss1 = openSampleDocument("63200.pptx")) {
assertEquals(1, ss1.getSlides().size());
XSLFSlide slide = ss1.getSlides().get(0);
@@ -982,4 +990,25 @@
assertNull(arrow.getFillColor());
}
}
+
+ @Test
+ public void alternateContent() throws Exception {
+ try (XMLSlideShow ppt = openSampleDocument("alterman_security.pptx")) {
+ XSLFSlideMaster slide = ppt.getSlideMasters().get(0);
+ XSLFObjectShape os = (XSLFObjectShape)slide.getShapes().get(0);
+ // ctOleObject is nested in AlternateContent in this file
+ // if there are casting errors, we would fail early and wouldn't reach this point anyway
+ assertNotNull(os.getCTOleObject());
+ // accessing the picture data of the AlternateContent fallback part
+ XSLFPictureData picData = os.getPictureData();
+ assertNotNull(picData);
+ }
+
+ try (XMLSlideShow ppt = openSampleDocument("2411-Performance_Up.pptx")) {
+ XSLFSlide slide = ppt.getSlides().get(4);
+ XSLFPictureShape ps = (XSLFPictureShape)slide.getShapes().get(0);
+ assertEquals("image4.png", ps.getPictureData().getFileName());
+ assertEquals("Picture 5", ps.getShapeName());
+ }
+ }
}
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
index 10757fb..bf6f565 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
@@ -47,7 +47,7 @@
private static final String files =
"53446.ppt, alterman_security.ppt, alterman_security.pptx, KEY02.pptx, themes.pptx, " +
"backgrounds.pptx, layouts.pptx, sample.pptx, shapes.pptx, 54880_chinese.ppt, keyframes.pptx," +
- "customGeo.pptx, customGeo.ppt, wrench.emf, santa.wmf";
+ "customGeo.pptx, customGeo.ppt, wrench.emf, santa.wmf, missing-moveto.ppt";
@BeforeClass
public static void checkHslf() {
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFAutoShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFAutoShape.java
index 3b57ccb..206bb16 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFAutoShape.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFAutoShape.java
@@ -17,6 +17,9 @@
package org.apache.poi.hslf.usermodel;
+import static org.apache.poi.hslf.usermodel.HSLFFreeformShape.ShapePath.CURVES_CLOSED;
+import static org.apache.poi.hslf.usermodel.HSLFFreeformShape.ShapePath.LINES_CLOSED;
+
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
@@ -69,22 +72,10 @@
static final byte[] SEGMENTINFO_CLOSE = new byte[]{0x01, (byte)0x60};
static final byte[] SEGMENTINFO_END = new byte[]{0x00, (byte)0x80};
+ private static final ObjectFactory OF = new ObjectFactory();
private static final BitField PATH_INFO = BitFieldFactory.getInstance(0xE000);
private static final BitField ESCAPE_INFO = BitFieldFactory.getInstance(0x1F00);
- private static final EscherPropertyTypes[] ADJUST_VALUES = {
- EscherPropertyTypes.GEOMETRY__ADJUSTVALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST2VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST3VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST4VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST5VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST6VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST7VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST8VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST9VALUE,
- EscherPropertyTypes.GEOMETRY__ADJUST10VALUE
- };
-
enum PathInfo {
lineTo(0),curveTo(1),moveTo(2),close(3),end(4),escape(5),clientEscape(6);
private final int flag;
@@ -220,12 +211,11 @@
}
CustomGeometry getGeometry(Path2D path2D) {
- final ObjectFactory of = new ObjectFactory();
- final CTCustomGeometry2D cusGeo = of.createCTCustomGeometry2D();
- cusGeo.setAvLst(of.createCTGeomGuideList());
- cusGeo.setGdLst(of.createCTGeomGuideList());
- cusGeo.setAhLst(of.createCTAdjustHandleList());
- cusGeo.setCxnLst(of.createCTConnectionSiteList());
+ final CTCustomGeometry2D cusGeo = OF.createCTCustomGeometry2D();
+ cusGeo.setAvLst(OF.createCTGeomGuideList());
+ cusGeo.setGdLst(OF.createCTGeomGuideList());
+ cusGeo.setAhLst(OF.createCTAdjustHandleList());
+ cusGeo.setCxnLst(OF.createCTConnectionSiteList());
final AbstractEscherOptRecord opt = getEscherOptRecord();
@@ -249,8 +239,8 @@
final int[] xyPoints = new int[2];
boolean isClosed = false;
- final CTPath2DList pathLst = of.createCTPath2DList();
- final CTPath2D pathCT = of.createCTPath2D();
+ final CTPath2DList pathLst = OF.createCTPath2DList();
+ final CTPath2D pathCT = OF.createCTPath2D();
final List<Object> moveLst = pathCT.getCloseOrMoveToOrLnTo();
pathLst.getPath().add(pathCT);
cusGeo.setPathLst(pathLst);
@@ -262,46 +252,23 @@
continue;
}
switch (pi) {
- case escape: {
+ case escape:
handleEscapeInfo(pathCT, path2D, segElem, vertIter);
break;
- }
case moveTo:
- if (vertIter.hasNext()) {
- final CTPath2DMoveTo m = of.createCTPath2DMoveTo();
- m.setPt(fillPoint(vertIter.next(), xyPoints));
- moveLst.add(m);
- path2D.moveTo(xyPoints[0], xyPoints[1]);
- }
+ handleMoveTo(vertIter, xyPoints, moveLst, path2D);
break;
case lineTo:
- if (vertIter.hasNext()) {
- final CTPath2DLineTo m = of.createCTPath2DLineTo();
- m.setPt(fillPoint(vertIter.next(), xyPoints));
- moveLst.add(m);
- path2D.lineTo(xyPoints[0], xyPoints[1]);
- }
+ handleLineTo(vertIter, xyPoints, moveLst, path2D);
break;
- case curveTo: {
- final CTPath2DCubicBezierTo m = of.createCTPath2DCubicBezierTo();
- List<CTAdjPoint2D> mLst = m.getPt();
-
- int[] pts = new int[6];
-
- for (int i=0; vertIter.hasNext() && i<3; i++) {
- mLst.add(fillPoint(vertIter.next(), xyPoints));
- pts[i*2] = xyPoints[0];
- pts[i*2+1] = xyPoints[1];
- if (i == 2) {
- moveLst.add(m);
- path2D.curveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]);
- }
- }
+ case curveTo:
+ handleCurveTo(vertIter, xyPoints, moveLst, path2D);
break;
- }
case close:
- moveLst.add(of.createCTPath2DClose());
- path2D.closePath();
+ if (path2D.getCurrentPoint() != null) {
+ moveLst.add(OF.createCTPath2DClose());
+ path2D.closePath();
+ }
isClosed = true;
break;
default:
@@ -309,28 +276,11 @@
}
}
- EscherSimpleProperty shapePath = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__SHAPEPATH);
- HSLFFreeformShape.ShapePath sp = HSLFFreeformShape.ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue());
- if ((sp == HSLFFreeformShape.ShapePath.LINES_CLOSED || sp == HSLFFreeformShape.ShapePath.CURVES_CLOSED) && !isClosed) {
- moveLst.add(of.createCTPath2DClose());
- path2D.closePath();
+ if (!isClosed) {
+ handleClosedShape(opt, moveLst, path2D);
}
- EscherSimpleProperty geoLeft = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__LEFT);
- EscherSimpleProperty geoRight = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__RIGHT);
- EscherSimpleProperty geoTop = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__TOP);
- EscherSimpleProperty geoBottom = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__BOTTOM);
-
- final Rectangle2D bounds;
- if (geoLeft != null && geoRight != null && geoTop != null && geoBottom != null) {
- bounds = new Rectangle2D.Double();
- bounds.setFrameFromDiagonal(
- new Point2D.Double(geoLeft.getPropertyValue(), geoTop.getPropertyValue()),
- new Point2D.Double(geoRight.getPropertyValue(), geoBottom.getPropertyValue())
- );
- } else {
- bounds = path2D.getBounds2D();
- }
+ final Rectangle2D bounds = getBounds(opt, path2D);
pathCT.setW((int)Math.rint(bounds.getWidth()));
pathCT.setH((int)Math.rint(bounds.getHeight()));
@@ -338,8 +288,94 @@
return new CustomGeometry(cusGeo);
}
- private void handleEscapeInfo(CTPath2D pathCT, Path2D path2D, byte[] segElem, Iterator<byte[]> vertIter) {
- final ObjectFactory of = new ObjectFactory();
+ private static Rectangle2D getBounds(AbstractEscherOptRecord opt, Path2D path2D) {
+ EscherSimpleProperty geoLeft = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__LEFT);
+ EscherSimpleProperty geoRight = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__RIGHT);
+ EscherSimpleProperty geoTop = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__TOP);
+ EscherSimpleProperty geoBottom = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__BOTTOM);
+
+ if (geoLeft != null && geoRight != null && geoTop != null && geoBottom != null) {
+ final Rectangle2D bounds = new Rectangle2D.Double();
+ bounds.setFrameFromDiagonal(
+ new Point2D.Double(geoLeft.getPropertyValue(), geoTop.getPropertyValue()),
+ new Point2D.Double(geoRight.getPropertyValue(), geoBottom.getPropertyValue())
+ );
+ return bounds;
+ } else {
+ return path2D.getBounds2D();
+ }
+ }
+
+ private static void handleClosedShape(AbstractEscherOptRecord opt, List<Object> moveLst, Path2D path2D) {
+ EscherSimpleProperty shapePath = getEscherProperty(opt, EscherPropertyTypes.GEOMETRY__SHAPEPATH);
+ HSLFFreeformShape.ShapePath sp = HSLFFreeformShape.ShapePath.valueOf(shapePath == null ? 1 : shapePath.getPropertyValue());
+ if (sp == LINES_CLOSED || sp == CURVES_CLOSED) {
+ moveLst.add(OF.createCTPath2DClose());
+ path2D.closePath();
+ }
+ }
+
+ private static void handleMoveTo(Iterator<byte[]> vertIter, int[] xyPoints, List<Object> moveLst, Path2D path2D) {
+ if (!vertIter.hasNext()) {
+ return;
+ }
+ final CTPath2DMoveTo m = OF.createCTPath2DMoveTo();
+ m.setPt(fillPoint(vertIter.next(), xyPoints));
+ moveLst.add(m);
+ path2D.moveTo(xyPoints[0], xyPoints[1]);
+ }
+
+ private static void handleLineTo(Iterator<byte[]> vertIter, int[] xyPoints, List<Object> moveLst, Path2D path2D) {
+ if (!vertIter.hasNext()) {
+ return;
+ }
+ handleMoveTo0(moveLst, path2D);
+
+ final CTPath2DLineTo m = OF.createCTPath2DLineTo();
+ m.setPt(fillPoint(vertIter.next(), xyPoints));
+ moveLst.add(m);
+ path2D.lineTo(xyPoints[0], xyPoints[1]);
+ }
+
+ private static void handleCurveTo(Iterator<byte[]> vertIter, int[] xyPoints, List<Object> moveLst, Path2D path2D) {
+ if (!vertIter.hasNext()) {
+ return;
+ }
+ handleMoveTo0(moveLst, path2D);
+
+ final CTPath2DCubicBezierTo m = OF.createCTPath2DCubicBezierTo();
+ List<CTAdjPoint2D> mLst = m.getPt();
+
+ int[] pts = new int[6];
+
+ for (int i=0; vertIter.hasNext() && i<3; i++) {
+ mLst.add(fillPoint(vertIter.next(), xyPoints));
+ pts[i*2] = xyPoints[0];
+ pts[i*2+1] = xyPoints[1];
+ if (i == 2) {
+ moveLst.add(m);
+ path2D.curveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]);
+ }
+ }
+ }
+
+ /**
+ * Sometimes the path2D is not initialized - this initializes it with the 0,0 position
+ */
+ private static void handleMoveTo0(List<Object> moveLst, Path2D path2D) {
+ if (path2D.getCurrentPoint() == null) {
+ final CTPath2DMoveTo m = OF.createCTPath2DMoveTo();
+
+ CTAdjPoint2D pt = OF.createCTAdjPoint2D();
+ pt.setX("0");
+ pt.setY("0");
+ m.setPt(pt);
+ moveLst.add(m);
+ path2D.moveTo(0, 0);
+ }
+ }
+
+ private static void handleEscapeInfo(CTPath2D pathCT, Path2D path2D, byte[] segElem, Iterator<byte[]> vertIter) {
HSLFFreeformShape.EscapeInfo ei = getEscapeInfo(segElem);
if (ei == null) {
return;
@@ -376,7 +412,7 @@
path2D.append(arc2D, true);
- CTPath2DArcTo arcTo = of.createCTPath2DArcTo();
+ CTPath2DArcTo arcTo = OF.createCTPath2DArcTo();
arcTo.setHR(d2s(bounds.getHeight()/2.0));
arcTo.setWR(d2s(bounds.getWidth()/2.0));
@@ -451,7 +487,7 @@
}
- private CTAdjPoint2D fillPoint(byte[] xyMaster, int[] xyPoints) {
+ private static CTAdjPoint2D fillPoint(byte[] xyMaster, int[] xyPoints) {
if (xyMaster == null || xyPoints == null) {
LOG.log(POILogger.WARN, "Master bytes or points not set - ignore point");
return null;
@@ -477,7 +513,7 @@
}
private static CTAdjPoint2D toPoint(int[] xyPoints) {
- CTAdjPoint2D pt = new CTAdjPoint2D();
+ CTAdjPoint2D pt = OF.createCTAdjPoint2D();
pt.setX(Integer.toString(xyPoints[0]));
pt.setY(Integer.toString(xyPoints[1]));
return pt;
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java
index dd7afa4..097342f 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java
@@ -62,7 +62,7 @@
public final static double DEFAULT_LINE_WIDTH = 0.75;
- private static final EscherPropertyTypes[] ADJUST_VALUES = {
+ protected static final EscherPropertyTypes[] ADJUST_VALUES = {
EscherPropertyTypes.GEOMETRY__ADJUSTVALUE,
EscherPropertyTypes.GEOMETRY__ADJUST2VALUE,
EscherPropertyTypes.GEOMETRY__ADJUST3VALUE,
@@ -79,7 +79,7 @@
* Hyperlink
*/
protected HSLFHyperlink _hyperlink;
-
+
/**
* Create a SimpleShape object and initialize it from the supplied Record container.
*
@@ -563,8 +563,8 @@
public HSLFShapePlaceholderDetails getPlaceholderDetails() {
return new HSLFShapePlaceholderDetails(this);
}
-
-
+
+
@Override
public Placeholder getPlaceholder() {
return getPlaceholderDetails().getPlaceholder();
@@ -604,7 +604,7 @@
public HSLFHyperlink getHyperlink(){
return _hyperlink;
}
-
+
@Override
public HSLFHyperlink createHyperlink() {
if (_hyperlink == null) {
@@ -612,7 +612,7 @@
}
return _hyperlink;
}
-
+
/**
* Sets the hyperlink - used when the document is parsed
*
@@ -621,7 +621,7 @@
protected void setHyperlink(HSLFHyperlink link) {
_hyperlink = link;
}
-
+
@Override
public boolean isPlaceholder() {
// currently we only identify TextShapes as placeholders
diff --git a/test-data/slideshow/missing-moveto.ppt b/test-data/slideshow/missing-moveto.ppt
new file mode 100644
index 0000000..1d8c241
--- /dev/null
+++ b/test-data/slideshow/missing-moveto.ppt
Binary files differ