From 9882382c8031493e34c5c71dc619f6c8f10efba4 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Wed, 17 Aug 2011 10:18:31 +0000 Subject: [PATCH] support for tables and hyperlinks in XSLF git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1158611 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/index.xml | 4 +- src/documentation/content/xdocs/status.xml | 1 + .../poi/xslf/usermodel/XSLFAutoShape.java | 308 +-------------- .../poi/xslf/usermodel/XSLFDrawing.java | 9 + .../poi/xslf/usermodel/XSLFGraphicFrame.java | 22 +- .../poi/xslf/usermodel/XSLFHyperlink.java | 69 ++++ .../poi/xslf/usermodel/XSLFRelation.java | 7 + .../apache/poi/xslf/usermodel/XSLFShape.java | 4 - .../apache/poi/xslf/usermodel/XSLFSheet.java | 10 +- .../apache/poi/xslf/usermodel/XSLFTable.java | 157 ++++++++ .../poi/xslf/usermodel/XSLFTableCell.java | 290 ++++++++++++++ .../poi/xslf/usermodel/XSLFTableRow.java | 86 +++++ .../poi/xslf/usermodel/XSLFTextParagraph.java | 13 +- .../poi/xslf/usermodel/XSLFTextRun.java | 23 +- .../poi/xslf/usermodel/XSLFTextShape.java | 365 ++++++++++++++++++ .../poi/xslf/usermodel/TestXSLFAutoShape.java | 2 +- .../poi/xslf/usermodel/TestXSLFHyperlink.java | 102 +++++ .../poi/xslf/usermodel/TestXSLFShape.java | 1 - .../poi/xslf/usermodel/TestXSLFSlide.java | 9 +- .../poi/xslf/usermodel/TestXSLFTable.java | 150 +++++++ test-data/slideshow/shapes.pptx | Bin 64058 -> 67435 bytes 21 files changed, 1306 insertions(+), 326 deletions(-) create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTable.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableCell.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableRow.java create mode 100644 src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java create mode 100644 src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTable.java diff --git a/src/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml index 56e89aa2b..61fc1de6f 100644 --- a/src/documentation/content/xdocs/index.xml +++ b/src/documentation/content/xdocs/index.xml @@ -32,8 +32,8 @@ -
6 June 2011 - POI 3.8 beta 3 available -

The Apache POI team is pleased to announce the release of 3.8 beta 3. +

22 August 2011 - POI 3.8 beta 4 available +

The Apache POI team is pleased to announce the release of 3.8 beta 4. This includes a large number of bug fixes and enhancements.

A full list of changes is available in the change log. diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 96acff77e..4c9f245e0 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + support for tables and hyperlinks 51535 - correct signed vs unsigned short reading in NDocumentInputStream 51634 - support SXSSF streaming from templates initial support for XSLF usermodel API diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java index 186974997..feb77a404 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFAutoShape.java @@ -48,19 +48,10 @@ import java.util.List; * @author Yegor Kozlov */ @Beta -public class XSLFAutoShape extends XSLFSimpleShape { - private final List _paragraphs; +public class XSLFAutoShape extends XSLFTextShape { /*package*/ XSLFAutoShape(CTShape shape, XSLFSheet sheet) { super(shape, sheet); - - _paragraphs = new ArrayList(); - if (shape.isSetTxBody()) { - CTTextBody txBody = shape.getTxBody(); - for (CTTextParagraph p : txBody.getPList()) { - _paragraphs.add(new XSLFTextParagraph(p)); - } - } } /*package*/ @@ -74,36 +65,6 @@ public class XSLFAutoShape extends XSLFSimpleShape { } } - // textual properties - public String getText() { - StringBuilder out = new StringBuilder(); - for (XSLFTextParagraph p : _paragraphs) { - if (out.length() > 0) out.append('\n'); - out.append(p.getText()); - } - return out.toString(); - } - - public List getTextParagraphs() { - return _paragraphs; - } - - public XSLFTextParagraph addNewTextParagraph() { - CTShape shape = (CTShape) getXmlObject(); - CTTextBody txBody; - if (!shape.isSetTxBody()) { - txBody = shape.addNewTxBody(); - txBody.addNewBodyPr(); - txBody.addNewLstStyle(); - } else { - txBody = shape.getTxBody(); - } - CTTextParagraph p = txBody.addNewP(); - XSLFTextParagraph paragraph = new XSLFTextParagraph(p); - _paragraphs.add(paragraph); - return paragraph; - } - /** * @param shapeId 1-based shapeId */ @@ -160,268 +121,15 @@ public class XSLFAutoShape extends XSLFSimpleShape { return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); } - /** - * Sets the type of vertical alignment for the text. - * One of the Anchor* constants defined in this class. - * - * @param anchor - the type of alignment. Default is {@link VerticalAlignment#TOP} - */ - public void setVerticalAlignment(VerticalAlignment anchor){ + protected CTTextBody getTextBody(boolean create){ CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(anchor == null) { - if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor(); - } else { - bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1)); - } - } - } - - /** - * Returns the type of vertical alignment for the text. - * - * @return the type of alignment - */ - public VerticalAlignment getVerticalAlignment(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - STTextAnchoringType.Enum val = shape.getTxBody().getBodyPr().getAnchor(); - if(val != null){ - return VerticalAlignment.values()[val.intValue() - 1]; - } - } - return VerticalAlignment.TOP; - } - - /** - * - * @param orientation vertical orientation of the text - */ - public void setTextDirection(TextDirection orientation){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(orientation == null) { - if(bodyPr.isSetVert()) bodyPr.unsetVert(); - } else { - bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1)); - } - } - } - - /** - * @return vertical orientation of the text - */ - public TextDirection getTextDirection(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - STTextVerticalType.Enum val = shape.getTxBody().getBodyPr().getVert(); - if(val != null){ - return TextDirection.values()[val.intValue() - 1]; - } - } - return TextDirection.HORIZONTAL; - } - /** - * Returns the distance (in points) between the bottom of the text frame - * and the bottom of the inscribed rectangle of the shape that contains the text. - * - * @return the bottom margin or -1 if not set - */ - public double getMarginBottom(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - return bodyPr.isSetBIns() ? Units.toPoints(bodyPr.getBIns()) : -1; - } - return -1; - } - - /** - * Returns the distance (in points) between the left edge of the text frame - * and the left edge of the inscribed rectangle of the shape that contains - * the text. - * - * @return the left margin - */ - public double getMarginLeft(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - return bodyPr.isSetLIns() ? Units.toPoints(bodyPr.getLIns()) : -1; - } - return -1; - } - - /** - * Returns the distance (in points) between the right edge of the - * text frame and the right edge of the inscribed rectangle of the shape - * that contains the text. - * - * @return the right margin - */ - public double getMarginRight(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - return bodyPr.isSetRIns() ? Units.toPoints(bodyPr.getRIns()) : -1; - } - return -1; - } - - /** - * Returns the distance (in points) between the top of the text frame - * and the top of the inscribed rectangle of the shape that contains the text. - * - * @return the top margin - */ - public double getMarginTop(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - return bodyPr.isSetTIns() ? Units.toPoints(bodyPr.getTIns()) : -1; - } - return -1; - } - - /** - * Sets the botom margin. - * @see #getMarginBottom() - * - * @param margin the bottom margin - */ - public void setMarginBottom(double margin){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(margin == -1) bodyPr.unsetBIns(); - else bodyPr.setBIns(Units.toEMU(margin)); - } - } - - /** - * Sets the left margin. - * @see #getMarginLeft() - * - * @param margin the left margin - */ - public void setMarginLeft(double margin){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(margin == -1) bodyPr.unsetLIns(); - else bodyPr.setLIns(Units.toEMU(margin)); - } - } - - /** - * Sets the right margin. - * @see #getMarginRight() - * - * @param margin the right margin - */ - public void setMarginRight(double margin){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(margin == -1) bodyPr.unsetRIns(); - else bodyPr.setRIns(Units.toEMU(margin)); - } - } - - /** - * Sets the top margin. - * @see #getMarginTop() - * - * @param margin the top margin - */ - public void setMarginTop(double margin){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(margin == -1) bodyPr.unsetTIns(); - else bodyPr.setTIns(Units.toEMU(margin)); - } - } - - - /** - * Returns the value indicating word wrap. - * One of the Wrap* constants defined in this class. - * - * @return the value indicating word wrap - */ - public boolean getWordWrap(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - return shape.getTxBody().getBodyPr().getWrap() == STTextWrappingType.SQUARE; - } - return false; - } - - /** - * Specifies how the text should be wrapped - * - * @param wrap the value indicating how the text should be wrapped - */ - public void setWordWrap(boolean wrap){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - shape.getTxBody().getBodyPr().setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE); - } - } - - /** - * - * Specifies that a shape should be auto-fit to fully contain the text described within it. - * Auto-fitting is when text within a shape is scaled in order to contain all the text inside - * - * @param value type of autofit - */ - public void setTextAutofit(TextAutofit value){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit(); - if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit(); - if(bodyPr.isSetNormAutofit()) bodyPr.unsetNormAutofit(); - - switch(value){ - case NONE: bodyPr.addNewNoAutofit(); break; - case NORMAL: bodyPr.addNewNormAutofit(); break; - case SHAPE: bodyPr.addNewSpAutoFit(); break; - } - } - } - - /** - * - * @return type of autofit - */ - public TextAutofit getTextAutofit(){ - CTShape shape = (CTShape) getXmlObject(); - if (shape.isSetTxBody()) { - CTTextBodyProperties bodyPr = shape.getTxBody().getBodyPr(); - if(bodyPr.isSetNoAutofit()) return TextAutofit.NONE; - else if (bodyPr.isSetNormAutofit()) return TextAutofit.NORMAL; - else if (bodyPr.isSetSpAutoFit()) return TextAutofit.SHAPE; - } - return TextAutofit.NORMAL; - } - - - @Override - void onCopy(XSLFSheet srcSheet){ - CTShape shape = (CTShape) getXmlObject(); - if (!shape.isSetTxBody()) return; - - CTPlaceholder ph = shape.getNvSpPr().getNvPr().getPh(); - if(ph == null || !ph.isSetType()) return; - - if(ph.getType() == STPlaceholderType.TITLE){ - + CTTextBody txBody = shape.getTxBody(); + if (txBody == null && create) { + txBody = shape.addNewTxBody(); + txBody.addNewBodyPr(); + txBody.addNewLstStyle(); } + return txBody; } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java index ab8c1658f..e731a632d 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java @@ -24,6 +24,7 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector; import org.openxmlformats.schemas.presentationml.x2006.main.CTGroupShape; import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture; import org.openxmlformats.schemas.presentationml.x2006.main.CTShape; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; import java.awt.*; @@ -97,4 +98,12 @@ public class XSLFDrawing { shape.setAnchor(new Rectangle()); return shape; } + + public XSLFTable createTable(){ + CTGraphicalObjectFrame obj = _spTree.addNewGraphicFrame(); + obj.set(XSLFTable.prototype(_shapeId++)); + XSLFTable shape = new XSLFTable(obj, _sheet); + shape.setAnchor(new Rectangle()); + return shape; + } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java index 0abad44f3..7e0990331 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFGraphicFrame.java @@ -44,6 +44,10 @@ public class XSLFGraphicFrame extends XSLFShape { return _shape; } + public XSLFSheet getSheet(){ + return _sheet; + } + public int getShapeType(){ throw new RuntimeException("NotImplemented"); } @@ -64,16 +68,14 @@ public class XSLFGraphicFrame extends XSLFShape { throw new RuntimeException("NotImplemented"); } - public ShapeGroup getParent(){ - throw new RuntimeException("NotImplemented"); + + static XSLFGraphicFrame create(CTGraphicalObjectFrame shape, XSLFSheet sheet){ + String uri = shape.getGraphic().getGraphicData().getUri(); + if(XSLFTable.TABLE_URI.equals(uri)){ + return new XSLFTable(shape, sheet); + } else { + return new XSLFGraphicFrame(shape, sheet); + } } - public Shape[] getShapes(){ - throw new RuntimeException("NotImplemented"); - } - - - public boolean removeShape(Shape shape){ - throw new RuntimeException("NotImplemented"); - } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java new file mode 100644 index 000000000..5a25851b5 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.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.xslf.usermodel; + +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.util.Internal; +import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink; + +import java.net.URI; + +/** + * @author Yegor Kozlov + */ +public class XSLFHyperlink { + final XSLFTextRun _r; + final CTHyperlink _link; + + XSLFHyperlink(CTHyperlink link, XSLFTextRun r){ + _r = r; + _link = link; + } + + @Internal + public CTHyperlink getXmlObject(){ + return _link; + } + + public void setAddress(String address){ + XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); + PackageRelationship rel = + sheet.getPackagePart(). + addExternalRelationship(address, XSLFRelation.HYPERLINK.getRelation()); + _link.setId(rel.getId()); + + } + + public void setAddress(XSLFSlide slide){ + XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); + PackageRelationship rel = + sheet.getPackagePart(). + addRelationship(slide.getPackagePart().getPartName(), + TargetMode.INTERNAL, + XSLFRelation.SLIDE.getRelation()); + _link.setId(rel.getId()); + _link.setAction("ppaction://hlinksldjump"); + } + + @Internal + public URI getTargetURI(){ + XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); + String id = _link.getId(); + return sheet.getPackagePart().getRelationship(id).getTargetURI(); + } +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java index 10a35dfee..15f91c6ec 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFRelation.java @@ -103,6 +103,13 @@ public class XSLFRelation extends POIXMLRelation { null, null ); + public static final XSLFRelation HYPERLINK = new XSLFRelation( + null, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", + null, + null + ); + public static final XSLFRelation THEME = new XSLFRelation( "application/vnd.openxmlformats-officedocument.theme+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme", 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 b1b65eac4..dfb659557 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java @@ -41,8 +41,4 @@ public abstract class XSLFShape { public abstract String getShapeName(); public abstract int getShapeId(); - - void onCopy(XSLFSheet srcSheet){ - - } } \ No newline at end of file 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 02ce9fdb7..d97600d39 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java @@ -70,7 +70,8 @@ public abstract class XSLFSheet extends POIXMLDocumentPart { } else if (ch instanceof CTPicture){ shapes.add(new XSLFPictureShape((CTPicture)ch, this)); } else if (ch instanceof CTGraphicalObjectFrame){ - shapes.add(new XSLFGraphicFrame((CTGraphicalObjectFrame)ch, this)); + XSLFGraphicFrame shape = XSLFGraphicFrame.create((CTGraphicalObjectFrame)ch, this); + shapes.add(shape); } } return shapes; @@ -147,6 +148,13 @@ public abstract class XSLFSheet extends POIXMLDocumentPart { return sh; } + public XSLFTable createTable(){ + List shapes = getShapeList(); + XSLFTable sh = getDrawing().createTable(); + shapes.add(sh); + return sh; + } + public XSLFShape[] getShapes(){ return getShapeList().toArray(new XSLFShape[_shapes.size()]); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTable.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTable.java new file mode 100644 index 000000000..dce9cc238 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTable.java @@ -0,0 +1,157 @@ +/* + * ==================================================================== + * 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 org.apache.poi.util.Internal; +import org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlCursor; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; +import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTable; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; +import org.openxmlformats.schemas.drawingml.x2006.main.CTGroupTransform2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPoint2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTransform2D; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrameNonVisual; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.awt.geom.Rectangle2D; + +/** + * Represents a table in a .pptx presentation + * + * @author Yegor Kozlov + */ +public class XSLFTable extends XSLFGraphicFrame implements Iterable { + static String TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table"; + + private CTTable _table; + private List _rows; + + /*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){ + super(shape, sheet); + + for(XmlObject obj : shape.getGraphic().getGraphicData().selectPath("*")){ + if(obj instanceof CTTable){ + _table = (CTTable)obj; + } + } + if(_table == null) throw new IllegalStateException("CTTable element was not found"); + + _rows = new ArrayList(_table.sizeOfTrArray()); + for(CTTableRow row : _table.getTrList()) _rows.add(new XSLFTableRow(row, this)); + } + + @Internal + public CTTable getCTTable(){ + return _table; + } + + public int getNumberOfColumns() { + return _table.getTblGrid().sizeOfGridColArray(); + } + + public int getNumberOfRows() { + return _table.sizeOfTrArray(); + } + + public double getColumnWidth(int idx){ + return Units.toPoints( + _table.getTblGrid().getGridColArray(idx).getW()); + } + + public void setColumnWidth(int idx, double width){ + _table.getTblGrid().getGridColArray(idx).setW(Units.toEMU(width)); + } + + public Iterator iterator(){ + return _rows.iterator(); + } + + public List getRows(){ + return Collections.unmodifiableList(_rows); + } + + public XSLFTableRow addRow(){ + CTTableRow tr = _table.addNewTr(); + XSLFTableRow row = new XSLFTableRow(tr, this); + row.setHeight(20.0); // default height is 20 points + _rows.add(row); + return row; + } + + static CTGraphicalObjectFrame prototype(int shapeId){ + CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance(); + CTGraphicalObjectFrameNonVisual nvGr = frame.addNewNvGraphicFramePr(); + + CTNonVisualDrawingProps cnv = nvGr.addNewCNvPr(); + cnv.setName("Table " + shapeId); + cnv.setId(shapeId + 1); + nvGr.addNewCNvGraphicFramePr().addNewGraphicFrameLocks().setNoGrp(true); + nvGr.addNewNvPr(); + + frame.addNewXfrm(); + CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData(); + XmlCursor cursor = gr.newCursor(); + cursor.toNextToken(); + cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tbl")); + cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblPr")); + cursor.toNextToken(); + cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblGrid")); + cursor.dispose(); + gr.setUri(TABLE_URI); + return frame; + } + + public Rectangle2D getAnchor(){ + CTTransform2D xfrm = getXmlObject().getXfrm(); + CTPoint2D off = xfrm.getOff(); + long x = off.getX(); + long y = off.getY(); + CTPositiveSize2D ext = xfrm.getExt(); + long cx = ext.getCx(); + long cy = ext.getCy(); + return new Rectangle2D.Double( + Units.toPoints(x), Units.toPoints(y), + Units.toPoints(cx), Units.toPoints(cy)); + } + + public void setAnchor(Rectangle2D anchor){ + CTTransform2D xfrm = getXmlObject().getXfrm(); + CTPoint2D off = xfrm.isSetOff() ? xfrm.getOff() : xfrm.addNewOff(); + long x = Units.toEMU(anchor.getX()); + long y = Units.toEMU(anchor.getY()); + off.setX(x); + off.setY(y); + CTPositiveSize2D ext = xfrm.isSetExt() ? xfrm.getExt() : xfrm.addNewExt(); + long cx = Units.toEMU(anchor.getWidth()); + long cy = Units.toEMU(anchor.getHeight()); + ext.setCx(cx); + ext.setCy(cy); + } + +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableCell.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableCell.java new file mode 100644 index 000000000..cc04a10ae --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableCell.java @@ -0,0 +1,290 @@ +/* + * ==================================================================== + * 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 org.openxmlformats.schemas.drawingml.x2006.main.CTTableCell; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTableCellProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STPenAlignment; +import org.openxmlformats.schemas.drawingml.x2006.main.STCompoundLine; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineCap; +import org.openxmlformats.schemas.drawingml.x2006.main.STPresetLineDashVal; +import org.openxmlformats.schemas.drawingml.x2006.main.CTLineEndProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndLength; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndType; +import org.openxmlformats.schemas.drawingml.x2006.main.STLineEndWidth; +import org.apache.poi.util.Internal; +import org.apache.poi.util.Units; + +import java.awt.*; + +/** + * Represents a cell of a table in a .pptx presentation + * + * @author Yegor Kozlov + */ +public class XSLFTableCell extends XSLFTextShape { + static double defaultBorderWidth = 1.0; + + /*package*/ XSLFTableCell(CTTableCell cell, XSLFSheet sheet){ + super(cell, sheet); + } + + @Override + public CTTableCell getXmlObject(){ + return (CTTableCell)super.getXmlObject(); + } + + @Override + protected CTTextBody getTextBody(boolean create){ + CTTableCell cell = getXmlObject(); + CTTextBody txBody = cell.getTxBody(); + if (txBody == null && create) { + txBody = cell.addNewTxBody(); + txBody.addNewBodyPr(); + txBody.addNewLstStyle(); + } + return txBody; + } + + static CTTableCell prototype() { + CTTableCell cell = CTTableCell.Factory.newInstance(); + CTTableCellProperties pr = cell.addNewTcPr(); + pr.addNewLnL().addNewNoFill(); + pr.addNewLnR().addNewNoFill(); + pr.addNewLnT().addNewNoFill(); + pr.addNewLnB().addNewNoFill(); + return cell; + } + + @Override + public void setMarginLeft(double margin){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + if(pr == null) pr = getXmlObject().addNewTcPr(); + + pr.setMarL(Units.toEMU(margin)); + } + + @Override + public void setMarginRight(double margin){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + if(pr == null) pr = getXmlObject().addNewTcPr(); + + pr.setMarR(Units.toEMU(margin)); + } + + @Override + public void setMarginTop(double margin){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + if(pr == null) pr = getXmlObject().addNewTcPr(); + + pr.setMarT(Units.toEMU(margin)); + } + + @Override + public void setMarginBottom(double margin){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + if(pr == null) pr = getXmlObject().addNewTcPr(); + + pr.setMarB(Units.toEMU(margin)); + } + + public void setBorderLeft(double width){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.isSetLnL() ? pr.getLnL() : pr.addNewLnL(); + ln.setW(Units.toEMU(width)); + } + + public double getBorderLeft(){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.getLnL(); + return ln == null || !ln.isSetW() ? defaultBorderWidth : Units.toPoints(ln.getW()); + } + + public void setBorderLeftColor(Color color){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + CTLineProperties ln = pr.isSetLnL() ? pr.getLnL() : pr.addNewLnL(); + setLineColor(ln, color); + } + + public Color getBorderLeftColor(){ + return getLineColor(getXmlObject().getTcPr().getLnL()); + } + + public void setBorderRight(double width){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.isSetLnR() ? pr.getLnR() : pr.addNewLnR(); + ln.setW(Units.toEMU(width)); + } + + public double getBorderRight(){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.getLnR(); + return ln == null || !ln.isSetW() ? defaultBorderWidth : Units.toPoints(ln.getW()); + } + + public void setBorderRightColor(Color color){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + CTLineProperties ln = pr.isSetLnR() ? pr.getLnR() : pr.addNewLnR(); + setLineColor(ln, color); + } + + public Color getBorderRightColor(){ + return getLineColor(getXmlObject().getTcPr().getLnR()); + } + + public void setBorderTop(double width){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.isSetLnT() ? pr.getLnT() : pr.addNewLnT(); + ln.setW(Units.toEMU(width)); + } + + public double getBorderTop(){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.getLnT(); + return ln == null || !ln.isSetW() ? defaultBorderWidth : Units.toPoints(ln.getW()); + } + + public void setBorderTopColor(Color color){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + CTLineProperties ln = pr.isSetLnT() ? pr.getLnT() : pr.addNewLnT(); + setLineColor(ln, color); + } + + public Color getBorderTopColor(){ + return getLineColor(getXmlObject().getTcPr().getLnT()); + } + + public void setBorderBottom(double width){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.isSetLnB() ? pr.getLnB() : pr.addNewLnB(); + ln.setW(Units.toEMU(width)); + } + + public double getBorderBottom(){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + + CTLineProperties ln = pr.getLnB(); + return ln == null || !ln.isSetW() ? defaultBorderWidth : Units.toPoints(ln.getW()); + } + + public void setBorderBottomColor(Color color){ + CTTableCellProperties pr = getXmlObject().getTcPr(); + CTLineProperties ln = pr.isSetLnB() ? pr.getLnB() : pr.addNewLnB(); + setLineColor(ln, color); + } + + public Color getBorderBottomColor(){ + return getLineColor(getXmlObject().getTcPr().getLnB()); + } + + private void setLineColor(CTLineProperties ln, Color color){ + if(color == null){ + ln.addNewNoFill(); + if(ln.isSetSolidFill()) ln.unsetSolidFill(); + } else { + if(ln.isSetNoFill()) ln.unsetNoFill(); + + if(!ln.isSetPrstDash()) ln.addNewPrstDash().setVal(STPresetLineDashVal.SOLID); + ln.setCmpd(STCompoundLine.SNG); + ln.setAlgn(STPenAlignment.CTR); + ln.setCap(STLineCap.FLAT); + ln.addNewRound(); + + CTLineEndProperties hd = ln.addNewHeadEnd(); + hd.setType(STLineEndType.NONE); + hd.setW(STLineEndWidth.MED); + hd.setLen(STLineEndLength.MED); + + CTLineEndProperties tl = ln.addNewTailEnd(); + tl.setType(STLineEndType.NONE); + tl.setW(STLineEndWidth.MED); + tl.setLen(STLineEndLength.MED); + + CTSRgbColor rgb = CTSRgbColor.Factory.newInstance(); + rgb.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + ln.addNewSolidFill().setSrgbClr(rgb); + } + } + + private Color getLineColor(CTLineProperties ln){ + if(ln == null || ln.isSetNoFill() || !ln.isSetSolidFill()) return null; + + CTSolidColorFillProperties fill = ln.getSolidFill(); + if(!fill.isSetSrgbClr()) { + // TODO for now return null for all colors except explicit RGB + return null; + } + byte[] val = fill.getSrgbClr().getVal(); + return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); + } + /** + * Specifies a solid color fill. The shape is filled entirely with the specified color. + * + * @param color the solid color fill. + * The value of null unsets the solidFIll attribute from the underlying xml + */ + @Override + public void setFillColor(Color color) { + CTTableCellProperties spPr = getXmlObject().getTcPr(); + if (color == null) { + if(spPr.isSetSolidFill()) spPr.unsetSolidFill(); + } + else { + CTSolidColorFillProperties fill = spPr.isSetSolidFill() ? spPr.getSolidFill() : spPr.addNewSolidFill(); + + CTSRgbColor rgb = CTSRgbColor.Factory.newInstance(); + rgb.setVal(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}); + + fill.setSrgbClr(rgb); + } + } + + /** + * + * @return solid fill color of null if not set + */ + @Override + public Color getFillColor(){ + CTTableCellProperties spPr = getXmlObject().getTcPr(); + if(!spPr.isSetSolidFill() ) return null; + + CTSolidColorFillProperties fill = spPr.getSolidFill(); + if(!fill.isSetSrgbClr()) { + // TODO for now return null for all colors except explicit RGB + return null; + } + byte[] val = fill.getSrgbClr().getVal(); + return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); + } + +} diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableRow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableRow.java new file mode 100644 index 000000000..328c76ae7 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTableRow.java @@ -0,0 +1,86 @@ +/* + * ==================================================================== + * 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 org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTable; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTableCell; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTableRow; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Iterator; + +/** + * Represents a table in a .pptx presentation + * + * @author Yegor Kozlov + */ +public class XSLFTableRow implements Iterable { + private CTTableRow _row; + private List _cells; + private XSLFTable _table; + + /*package*/ XSLFTableRow(CTTableRow row, XSLFTable table){ + _row = row; + _table = table; + _cells = new ArrayList(_row.sizeOfTcArray()); + for(CTTableCell cell : _row.getTcList()) { + _cells.add(new XSLFTableCell(cell, table.getSheet())); + } + } + + public CTTableRow getXmlObject(){ + return _row; + } + + public Iterator iterator(){ + return _cells.iterator(); + } + + public List getCells(){ + return Collections.unmodifiableList(_cells); + } + + public double getHeight(){ + return Units.toPoints(_row.getH()); + } + + public void setHeight(double height){ + _row.setH(Units.toEMU(height)); + } + + public XSLFTableCell addCell(){ + CTTableCell c = _row.addNewTc(); + c.set(XSLFTableCell.prototype()); + XSLFTableCell cell = new XSLFTableCell(c, _table.getSheet()); + _cells.add(cell); + + if(_table.getNumberOfColumns() < _row.sizeOfTcArray()) { + _table.getCTTable().getTblGrid().addNewGridCol().setW(Units.toEMU(100.0)); + } + return cell; + } + + +} 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 9317298ee..e20be9b5c 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -40,12 +40,14 @@ import java.util.List; public class XSLFTextParagraph implements Iterable{ private final CTTextParagraph _p; private final List _runs; + private final XSLFTextShape _shape; - XSLFTextParagraph(CTTextParagraph p){ + XSLFTextParagraph(CTTextParagraph p, XSLFTextShape shape){ _p = p; _runs = new ArrayList(); + _shape = shape; for (CTRegularTextRun r : _p.getRList()) { - _runs.add(new XSLFTextRun(r)); + _runs.add(new XSLFTextRun(r, this)); } } @@ -62,6 +64,10 @@ public class XSLFTextParagraph implements Iterable{ return _p; } + XSLFTextShape getParentShape() { + return _shape; + + } public List getTextRuns(){ return _runs; } @@ -72,7 +78,8 @@ public class XSLFTextParagraph implements Iterable{ public XSLFTextRun addNewTextRun(){ CTRegularTextRun r = _p.addNewR(); - XSLFTextRun run = new XSLFTextRun(r); + r.addNewRPr(); + XSLFTextRun run = new XSLFTextRun(r, this); _runs.add(run); return run; } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index 859649197..c62775e08 100755 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -36,9 +36,15 @@ import java.awt.*; @Beta public class XSLFTextRun { private final CTRegularTextRun _r; + private final XSLFTextParagraph _p; - XSLFTextRun(CTRegularTextRun r){ + XSLFTextRun(CTRegularTextRun r, XSLFTextParagraph p){ _r = r; + _p = p; + } + + XSLFTextParagraph getParentParagraph(){ + return _p; } public String getText(){ @@ -78,7 +84,7 @@ public class XSLFTextRun { * @return font size in points or -1 if font size is not set. */ public double getFontSize(){ - if(!_r.isSetRPr()) return -1; + if(!_r.isSetRPr() || !_r.getRPr().isSetSz()) return -1; return _r.getRPr().getSz()*0.01; } @@ -198,5 +204,16 @@ public class XSLFTextRun { public String toString(){ return "[" + getClass() + "]" + getText(); } - + + public XSLFHyperlink createHyperlink(){ + XSLFHyperlink link = new XSLFHyperlink(_r.getRPr().addNewHlinkClick(), this); + return link; + } + + public XSLFHyperlink getHyperlink(){ + if(!_r.getRPr().isSetHlinkClick()) return null; + + + return new XSLFHyperlink(_r.getRPr().getHlinkClick(), this); + } } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java new file mode 100644 index 000000000..5bb48b600 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextShape.java @@ -0,0 +1,365 @@ +/* + * ==================================================================== + * 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 org.apache.poi.util.Beta; +import org.apache.poi.util.Units; +import org.apache.xmlbeans.XmlObject; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor; +import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBodyProperties; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextAnchoringType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextVerticalType; +import org.openxmlformats.schemas.drawingml.x2006.main.STTextWrappingType; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a shape that can hold text. + * + * @author Yegor Kozlov + */ +@Beta +public abstract class XSLFTextShape extends XSLFSimpleShape { + private final List _paragraphs; + + /*package*/ XSLFTextShape(XmlObject shape, XSLFSheet sheet) { + super(shape, sheet); + + _paragraphs = new ArrayList(); + CTTextBody txBody = getTextBody(false); + if (txBody != null) { + for (CTTextParagraph p : txBody.getPList()) { + _paragraphs.add(new XSLFTextParagraph(p, this)); + } + } + } + + // textual properties + public String getText() { + StringBuilder out = new StringBuilder(); + for (XSLFTextParagraph p : _paragraphs) { + if (out.length() > 0) out.append('\n'); + out.append(p.getText()); + } + return out.toString(); + } + + public List getTextParagraphs() { + return _paragraphs; + } + + public XSLFTextParagraph addNewTextParagraph() { + CTTextBody txBody = getTextBody(true); + CTTextParagraph p = txBody.addNewP(); + XSLFTextParagraph paragraph = new XSLFTextParagraph(p, this); + _paragraphs.add(paragraph); + return paragraph; + } + + /** + * Specifies a solid color fill. The shape is filled entirely with the specified color. + * + * @param color the solid color fill. + * The value of null unsets the solidFIll attribute from the underlying xml + */ + public void setFillColor(Color color) { + CTShapeProperties spPr = getSpPr(); + if (color == null) { + if(spPr.isSetSolidFill()) spPr.unsetSolidFill(); + } + else { + CTSolidColorFillProperties fill = spPr.isSetSolidFill() ? spPr.getSolidFill() : spPr.addNewSolidFill(); + + CTSRgbColor rgb = CTSRgbColor.Factory.newInstance(); + rgb.setVal(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}); + + fill.setSrgbClr(rgb); + } + } + + /** + * + * @return solid fill color of null if not set + */ + public Color getFillColor(){ + CTShapeProperties spPr = getSpPr(); + if(!spPr.isSetSolidFill() ) return null; + + CTSolidColorFillProperties fill = spPr.getSolidFill(); + if(!fill.isSetSrgbClr()) { + // TODO for now return null for all colors except explicit RGB + return null; + } + byte[] val = fill.getSrgbClr().getVal(); + return new Color(0xFF & val[0], 0xFF & val[1], 0xFF & val[2]); + } + + /** + * Sets the type of vertical alignment for the text. + * One of the Anchor* constants defined in this class. + * + * @param anchor - the type of alignment. Default is {@link org.apache.poi.xslf.usermodel.VerticalAlignment#TOP} + */ + public void setVerticalAlignment(VerticalAlignment anchor){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(anchor == null) { + if(bodyPr.isSetAnchor()) bodyPr.unsetAnchor(); + } else { + bodyPr.setAnchor(STTextAnchoringType.Enum.forInt(anchor.ordinal() + 1)); + } + } + } + + /** + * Returns the type of vertical alignment for the text. + * + * @return the type of alignment + */ + public VerticalAlignment getVerticalAlignment(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + STTextAnchoringType.Enum val = bodyPr.getAnchor(); + if(val != null){ + return VerticalAlignment.values()[val.intValue() - 1]; + } + } + return VerticalAlignment.TOP; + } + + /** + * + * @param orientation vertical orientation of the text + */ + public void setTextDirection(TextDirection orientation){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(orientation == null) { + if(bodyPr.isSetVert()) bodyPr.unsetVert(); + } else { + bodyPr.setVert(STTextVerticalType.Enum.forInt(orientation.ordinal() + 1)); + } + } + } + + /** + * @return vertical orientation of the text + */ + public TextDirection getTextDirection(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + STTextVerticalType.Enum val = bodyPr.getVert(); + if(val != null){ + return TextDirection.values()[val.intValue() - 1]; + } + } + return TextDirection.HORIZONTAL; + } + /** + * Returns the distance (in points) between the bottom of the text frame + * and the bottom of the inscribed rectangle of the shape that contains the text. + * + * @return the bottom margin or -1 if not set + */ + public double getMarginBottom(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + return bodyPr.isSetBIns() ? Units.toPoints(bodyPr.getBIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the left edge of the text frame + * and the left edge of the inscribed rectangle of the shape that contains + * the text. + * + * @return the left margin + */ + public double getMarginLeft(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + return bodyPr.isSetLIns() ? Units.toPoints(bodyPr.getLIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the right edge of the + * text frame and the right edge of the inscribed rectangle of the shape + * that contains the text. + * + * @return the right margin + */ + public double getMarginRight(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + return bodyPr.isSetRIns() ? Units.toPoints(bodyPr.getRIns()) : -1; + } + return -1; + } + + /** + * Returns the distance (in points) between the top of the text frame + * and the top of the inscribed rectangle of the shape that contains the text. + * + * @return the top margin + */ + public double getMarginTop(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + return bodyPr.isSetTIns() ? Units.toPoints(bodyPr.getTIns()) : -1; + } + return -1; + } + + /** + * Sets the botom margin. + * @see #getMarginBottom() + * + * @param margin the bottom margin + */ + public void setMarginBottom(double margin){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetBIns(); + else bodyPr.setBIns(Units.toEMU(margin)); + } + } + + /** + * Sets the left margin. + * @see #getMarginLeft() + * + * @param margin the left margin + */ + public void setMarginLeft(double margin){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetLIns(); + else bodyPr.setLIns(Units.toEMU(margin)); + } + } + + /** + * Sets the right margin. + * @see #getMarginRight() + * + * @param margin the right margin + */ + public void setMarginRight(double margin){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetRIns(); + else bodyPr.setRIns(Units.toEMU(margin)); + } + } + + /** + * Sets the top margin. + * @see #getMarginTop() + * + * @param margin the top margin + */ + public void setMarginTop(double margin){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(margin == -1) bodyPr.unsetTIns(); + else bodyPr.setTIns(Units.toEMU(margin)); + } + } + + + /** + * Returns the value indicating word wrap. + * One of the Wrap* constants defined in this class. + * + * @return the value indicating word wrap + */ + public boolean getWordWrap(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + return bodyPr.getWrap() == STTextWrappingType.SQUARE; + } + return false; + } + + /** + * Specifies how the text should be wrapped + * + * @param wrap the value indicating how the text should be wrapped + */ + public void setWordWrap(boolean wrap){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + bodyPr.setWrap(wrap ? STTextWrappingType.SQUARE : STTextWrappingType.NONE); + } + } + + /** + * + * Specifies that a shape should be auto-fit to fully contain the text described within it. + * Auto-fitting is when text within a shape is scaled in order to contain all the text inside + * + * @param value type of autofit + */ + public void setTextAutofit(TextAutofit value){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetSpAutoFit()) bodyPr.unsetSpAutoFit(); + if(bodyPr.isSetNoAutofit()) bodyPr.unsetNoAutofit(); + if(bodyPr.isSetNormAutofit()) bodyPr.unsetNormAutofit(); + + switch(value){ + case NONE: bodyPr.addNewNoAutofit(); break; + case NORMAL: bodyPr.addNewNormAutofit(); break; + case SHAPE: bodyPr.addNewSpAutoFit(); break; + } + } + } + + /** + * + * @return type of autofit + */ + public TextAutofit getTextAutofit(){ + CTTextBodyProperties bodyPr = getTextBodyPr(); + if (bodyPr != null) { + if(bodyPr.isSetNoAutofit()) return TextAutofit.NONE; + else if (bodyPr.isSetNormAutofit()) return TextAutofit.NORMAL; + else if (bodyPr.isSetSpAutoFit()) return TextAutofit.SHAPE; + } + return TextAutofit.NORMAL; + } + + protected CTTextBodyProperties getTextBodyPr(){ + CTTextBody textBody = getTextBody(false); + return textBody == null ? null : textBody.getBodyPr(); + } + + + protected abstract CTTextBody getTextBody(boolean create); +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java index 69be4b7a6..efed3ca03 100755 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFAutoShape.java @@ -210,7 +210,7 @@ public class TestXSLFAutoShape extends TestCase { assertSame(r, p.getTextRuns().get(0)); assertEquals(-1.0, r.getFontSize()); - assertFalse(r.getXmlObject().isSetRPr()); + assertFalse(r.getXmlObject().getRPr().isSetSz()); r.setFontSize(10.0); assertTrue(r.getXmlObject().isSetRPr()); assertEquals(1000, r.getXmlObject().getRPr().getSz()); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java new file mode 100644 index 000000000..fe968b0b7 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFHyperlink.java @@ -0,0 +1,102 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.util.*; +import java.util.List; +import java.net.URI; + +import org.apache.poi.xslf.XSLFTestDataSamples; +import org.apache.poi.xssf.usermodel.XSSFTable; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFHyperlink extends TestCase { + + public void testRead(){ + XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); + + XSLFSlide slide = ppt.getSlides()[4]; + XSLFShape[] shapes = slide.getShapes(); + XSLFTable tbl = (XSLFTable)shapes[0]; + XSLFTableCell cell1 = tbl.getRows().get(1).getCells().get(0); + assertEquals("Web Page", cell1.getText()); + XSLFHyperlink link1 = cell1.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); + assertNotNull(link1); + assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI()); + + XSLFTableCell cell2 = tbl.getRows().get(2).getCells().get(0); + assertEquals("Place in this document", cell2.getText()); + XSLFHyperlink link2 = cell2.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); + assertNotNull(link2); + assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI()); + + XSLFTableCell cell3 = tbl.getRows().get(3).getCells().get(0); + assertEquals("Email", cell3.getText()); + XSLFHyperlink link3 = cell3.getTextParagraphs().get(0).getTextRuns().get(0).getHyperlink(); + assertNotNull(link3); + assertEquals(URI.create("mailto:dev@poi.apache.org?subject=Hi%20There"), link3.getTargetURI()); + } + + public void testCreate() throws Exception { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide1 = ppt.createSlide(); + XSLFSlide slide2 = ppt.createSlide(); + + int numRel = slide1.getPackagePart().getRelationships().size(); + assertEquals(1, numRel); + XSLFTextBox sh1 = slide1.createTextBox(); + XSLFTextRun r1 = sh1.addNewTextParagraph().addNewTextRun(); + r1.setText("Web Page"); + XSLFHyperlink link1 = r1.createHyperlink(); + link1.setAddress("http://poi.apache.org/"); + assertEquals(URI.create("http://poi.apache.org/"), link1.getTargetURI()); + assertEquals(numRel + 1, slide1.getPackagePart().getRelationships().size()); + + String id1 = link1.getXmlObject().getId(); + assertNotNull(id1); + PackageRelationship rel1 = slide1.getPackagePart().getRelationship(id1); + assertNotNull(rel1); + assertEquals(id1, rel1.getId()); + assertEquals(TargetMode.EXTERNAL, rel1.getTargetMode()); + assertEquals(XSLFRelation.HYPERLINK.getRelation(), rel1.getRelationshipType()); + + XSLFTextBox sh2 = slide1.createTextBox(); + XSLFTextRun r2 = sh2.addNewTextParagraph().addNewTextRun(); + r2.setText("Place in this document"); + XSLFHyperlink link2 = r2.createHyperlink(); + link2.setAddress(slide2); + assertEquals(URI.create("/ppt/slides/slide2.xml"), link2.getTargetURI()); + assertEquals(numRel + 2, slide1.getPackagePart().getRelationships().size()); + + String id2 = link2.getXmlObject().getId(); + assertNotNull(id2); + PackageRelationship rel2 = slide1.getPackagePart().getRelationship(id2); + assertNotNull(rel2); + assertEquals(id2, rel2.getId()); + assertEquals(TargetMode.INTERNAL, rel2.getTargetMode()); + assertEquals(XSLFRelation.SLIDE.getRelation(), rel2.getRelationshipType()); + } +} \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java index ca3d82f97..ecdcdd032 100755 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFShape.java @@ -31,7 +31,6 @@ public class TestXSLFShape extends TestCase { public void testReadTextShapes() { XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); XSLFSlide[] slides = ppt.getSlides(); - assertEquals(3, slides.length); XSLFSlide slide1 = slides[0]; XSLFShape[] shapes1 = slide1.getShapes(); diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java index 9c7d131d8..9ffe69fe9 100755 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSlide.java @@ -30,7 +30,6 @@ public class TestXSLFSlide extends TestCase { public void testReadShapes(){ XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); XSLFSlide[] slides = ppt.getSlides(); - assertEquals(3, slides.length); XSLFSlide slide1 = slides[0]; XSLFShape[] shapes1 = slide1.getShapes(); @@ -85,6 +84,14 @@ public class TestXSLFSlide extends TestCase { assertTrue(groupShapes[2] instanceof XSLFAutoShape); assertEquals("Right Arrow 3", groupShapes[2].getShapeName()); + + XSLFSlide slide4 = slides[3]; + XSLFShape[] shapes4 = slide4.getShapes(); + assertEquals(1, shapes4.length); + assertTrue(shapes4[0] instanceof XSLFTable); + XSLFTable tbl = (XSLFTable)shapes4[0]; + assertEquals(3, tbl.getNumberOfColumns()); + assertEquals(6, tbl.getNumberOfRows()); } public void testCreateSlide(){ diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTable.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTable.java new file mode 100644 index 000000000..a5cdb0d6c --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTable.java @@ -0,0 +1,150 @@ +/* ==================================================================== + 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 junit.framework.TestCase; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.util.*; +import java.util.List; + +import org.apache.poi.xslf.XSLFTestDataSamples; +import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFrame; + +/** + * @author Yegor Kozlov + */ +public class TestXSLFTable extends TestCase { + + public void testRead(){ + XMLSlideShow ppt = XSLFTestDataSamples.openSampleDocument("shapes.pptx"); + + XSLFSlide slide = ppt.getSlides()[3]; + XSLFShape[] shapes = slide.getShapes(); + assertEquals(1, shapes.length); + assertTrue(shapes[0] instanceof XSLFTable); + XSLFTable tbl = (XSLFTable)shapes[0]; + assertEquals(3, tbl.getNumberOfColumns()); + assertEquals(6, tbl.getNumberOfRows()); + assertNotNull(tbl.getCTTable()); + + List rows = tbl.getRows(); + assertEquals(6, rows.size()); + + assertEquals(90.0, tbl.getColumnWidth(0)); + assertEquals(240.0, tbl.getColumnWidth(1)); + assertEquals(150.0, tbl.getColumnWidth(2)); + + for(XSLFTableRow row : tbl){ + // all rows have the same height + assertEquals(29.2, row.getHeight()); + } + + XSLFTableRow row0 = rows.get(0); + List cells0 = row0.getCells(); + assertEquals(3, cells0.size()); + assertEquals("header1", cells0.get(0).getText()); + assertEquals("header2", cells0.get(1).getText()); + assertEquals("header3", cells0.get(2).getText()); + + XSLFTableRow row1 = rows.get(1); + List cells1 = row1.getCells(); + assertEquals(3, cells1.size()); + assertEquals("A1", cells1.get(0).getText()); + assertEquals("B1", cells1.get(1).getText()); + assertEquals("C1", cells1.get(2).getText()); + } + + public void testCreate() { + XMLSlideShow ppt = new XMLSlideShow(); + XSLFSlide slide = ppt.createSlide(); + + XSLFTable tbl = slide.createTable(); + assertNotNull(tbl.getCTTable()); + assertNotNull(tbl.getCTTable().getTblGrid()); + assertNotNull(tbl.getCTTable().getTblPr()); + assertTrue(tbl.getXmlObject() instanceof CTGraphicalObjectFrame); + assertEquals("Table 1", tbl.getShapeName()); + assertEquals(2, tbl.getShapeId()); + assertEquals(0, tbl.getRows().size()); + assertEquals(0, tbl.getCTTable().sizeOfTrArray()); + assertEquals(0, tbl.getCTTable().getTblGrid().sizeOfGridColArray()); + + assertEquals(0, tbl.getNumberOfColumns()); + assertEquals(0, tbl.getNumberOfRows()); + + XSLFTableRow row0 = tbl.addRow(); + assertNotNull(row0.getXmlObject()); + assertEquals(1, tbl.getNumberOfRows()); + assertSame(row0, tbl.getRows().get(0)); + assertEquals(20.0, row0.getHeight()); + row0.setHeight(30.0); + assertEquals(30.0, row0.getHeight()); + + assertEquals(0, row0.getCells().size()); + XSLFTableCell cell0 = row0.addCell(); + assertNotNull(cell0.getXmlObject()); + // by default table cell has no borders + assertTrue(cell0.getXmlObject().getTcPr().getLnB().isSetNoFill()); + assertTrue(cell0.getXmlObject().getTcPr().getLnT().isSetNoFill()); + assertTrue(cell0.getXmlObject().getTcPr().getLnL().isSetNoFill()); + assertTrue(cell0.getXmlObject().getTcPr().getLnR().isSetNoFill()); + + assertSame(cell0, row0.getCells().get(0)); + assertEquals(1, tbl.getNumberOfColumns()); + assertEquals(100.0, tbl.getColumnWidth(0)); + cell0.addNewTextParagraph().addNewTextRun().setText("POI"); + assertEquals("POI", cell0.getText()); + + XSLFTableCell cell1 = row0.addCell(); + assertSame(cell1, row0.getCells().get(1)); + assertEquals(2, tbl.getNumberOfColumns()); + assertEquals(100.0, tbl.getColumnWidth(1)); + cell1.addNewTextParagraph().addNewTextRun().setText("Apache"); + assertEquals("Apache", cell1.getText()); + + assertEquals(1.0, cell1.getBorderBottom()); + cell1.setBorderBottom(2.0); + assertEquals(2.0, cell1.getBorderBottom()); + assertNull(cell1.getBorderBottomColor()); + cell1.setBorderBottomColor(Color.yellow); + assertEquals(Color.yellow, cell1.getBorderBottomColor()); + + assertEquals(1.0, cell1.getBorderTop()); + cell1.setBorderTop(2.0); + assertEquals(2.0, cell1.getBorderTop()); + assertNull(cell1.getBorderTopColor()); + cell1.setBorderTopColor(Color.yellow); + assertEquals(Color.yellow, cell1.getBorderTopColor()); + + assertEquals(1.0, cell1.getBorderLeft()); + cell1.setBorderLeft(2.0); + assertEquals(2.0, cell1.getBorderLeft()); + assertNull(cell1.getBorderLeftColor()); + cell1.setBorderLeftColor(Color.yellow); + assertEquals(Color.yellow, cell1.getBorderLeftColor()); + + assertEquals(1.0, cell1.getBorderRight()); + cell1.setBorderRight(2.0); + assertEquals(2.0, cell1.getBorderRight()); + assertNull(cell1.getBorderRightColor()); + cell1.setBorderRightColor(Color.yellow); + assertEquals(Color.yellow, cell1.getBorderRightColor()); + } +} \ No newline at end of file diff --git a/test-data/slideshow/shapes.pptx b/test-data/slideshow/shapes.pptx index 0b3861228d1dce1969bb7252c03c8fcaf4fdded4..cf017a873aa3707fbfabda5603c63a27cd655fe2 100755 GIT binary patch delta 14658 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 SM`i#p0H%=u0RP87|NTGAQ4hBO delta 11650 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