I have problem with slide master layout which contains page number and static text. I have them on my layouts but they are not appearing once I'm generating presentation. When I go INSTER --> Page Number I can select and they appear. When I'm adding new slide to my generated presentation via PowerPoint (add slide with selected layout), then it's showing page number for that page.
Here is the code:
String fileName = "slidemaster-010";
String templateLocation = "/Users/akonopko/Export/Templates/Presentation10.pptx";
XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(templateLocation));
XSLFSlideMaster defaultMaster = ppt.getSlideMasters().get(0);
XSLFSlideLayout masterLayout = defaultMaster.getLayout("Master");
XSLFSlide slide = ppt.createSlide(masterLayout);
XSLFSlideLayout masterLayout1 = defaultMaster.getLayout("1_Custom Layout");
XSLFSlide slide2 = ppt.createSlide(masterLayout1);
List<XSLFShape> slideShapes = slide2.getShapes();
for (XSLFShape shape : slideShapes) {
if (shape.getPlaceholder() == Placeholder.TITLE) {
((XSLFTextShape) shape).setText("Test Text");
}
}
Apache POI just does not copy the placeholders from the XSLFSlideLayout while XMLSlideShow.createSlide(XSLFSlideLayout). It uses XSLFSlideLayout.copyLayout(XSLFSlide) and there DATETIME, SLIDE_NUMBER and FOOTER is explicitly excluded.
One could write a method which copies those placeholders from the layout. This could look like so:
public void copyPlaceholdersFromLayout(XSLFSlideLayout layout, XSLFSlide slide) {
for (XSLFShape sh : layout.getShapes()) {
if (sh instanceof XSLFTextShape) {
XSLFTextShape tsh = (XSLFTextShape) sh;
Placeholder ph = tsh.getTextType();
if (ph == null) continue;
switch (ph) {
// these are special and not copied by default
case DATETIME:
case SLIDE_NUMBER:
case FOOTER:
System.out.println(ph);
slide.getXmlObject().getCSld().getSpTree().addNewSp().set(tsh.getXmlObject().copy());
break;
default:
//slide.getSpTree().addNewSp().set(tsh.getXmlObject().copy());
//already done
}
}
}
}
Get called like so:
copyPlaceholdersFromLayout(masterLayout1, slide2);
But there might be a reason why apache poi explicitly excludes this.
Related
I'm working with PowerPoint template. I'm trying to fill it with data in specific placeholders. I don't have any problem with placing text:
for (XSLFShape sh : slide.getShapes()) {
String name = sh.getShapeName();
String placeholderName = export.getPlaceholderName();
if (sh instanceof XSLFTextShape && name.equalsIgnoreCase(placeholderName)) {
XSLFTextShape txShape = (XSLFTextShape) sh;
txShape.clearText();
txShape.setText(parsedText);
}
}
I'm not able to add image to existing anchor. What I found is that first I have to collect shape data with anchor
for (XSLFShape sh : slide.getShapes()) {
String name = sh.getShapeName();
String placeholderName = export.getPlaceholderName();
if (sh instanceof XSLFPictureShape) {
XSLFPictureShape shape = (XSLFPictureShape) sh;
String shapeName = shape.getShapeName();
removeShape.setShapeName(shapeName);
removeShape.setAnchor(shape.getAnchor());
removeShape.setShape(shape);
}
}
and then add picture with anchor collected data and remove original.
XSLFPictureData pd = ppt.addPicture(removeShape.getPictureData(), PictureData.PictureType.PNG);
XSLFPictureShape pic = slide.createPicture(pd);
pic.setAnchor(removeShape.getAnchor());
slide.removeShape(removeShape.getShape());
Is there any easier way to add image to placeholder image on template?
I have tried creating the new presentation using the existing list of oldPresentations. Now If I am trying to copy the slidemASTER and layout from the oldPresenation file's slide into my new Slide it is breaking the existing layout and masterSlide.
for (SlideInfo slideInfo : req) {
XSLFSlide srcSlide = slidesMap.get(slideInfo.getDocIdentity() + "|" + slideInfo.getId());
XSLFSlide newSlide = newPpt.createSlide();
XSLFTheme srcSlideTheme = srcSlide.getTheme();
XSLFTheme newSlideTheme = newSlide.getTheme();
XSLFBackground srcBackground = srcSlide.getBackground();
XSLFBackground newBackground = newSlide.getBackground();
XSLFSlideMaster srcSlideMaster = srcSlide.getSlideMaster();
XSLFSlideMaster newSlideMaster = newSlide.getSlideMaster();
XSLFSlideLayout srcSlideLayout = srcSlide.getSlideLayout();
XSLFSlideLayout newSlideLayout = newSlide.getSlideLayout();
// Check if the importContent is working fine else catch the ClassCastException for slides with different picture data
try {
newSlideLayout.importContent(srcSlideLayout);
newSlideMaster.importContent(srcSlideMaster);
newBackground.setFillColor(srcBackground.getFillColor());
newSlideTheme.importTheme(srcSlideTheme);
newSlide.importContent(srcSlide);
Does anyone have an idea how we can add outlines to text (text outline) within powerpoint templates (ppxt) using Apache POI? What I have gathered so far is that the XSLFTextRun class does not have a method to get/ set the text outline for a given run element.
And as such, I could only persist the following font/ text styles:
def fontStyles(textBox: XSLFTextBox, textRun: XSLFTextRun): Unit = {
val fontFamily = textRun.getFontFamily
val fontColor = textRun.getFontColor
val fontSize = textRun.getFontSize
val fontBold = textRun.isBold
val fontItalic = textRun.isItalic
val textAlign = textRun.getParagraph.getTextAlign
textBox.getTextParagraphs.foreach { p =>
p.getTextRuns.foreach { tr =>
tr.setFontFamily(fontFamily)
tr.setFontColor(fontColor)
tr.setFontSize(fontSize)
tr.setBold(fontBold)
tr.setItalic(fontItalic)
tr.getParagraph.setTextAlign(textAlign)
}
}
}
Is it possible to add text outline?
Any assistance/ suggestions would be highly appreciated.
Apache poi uses underlying ooxml-schemas classes. Those are auto generated from Office Open XML standard. So they are more complete than the high level XSLF classes. Of course they are much less convenient.
So if somewhat is not implemented in high level XSLF classes, we can get the underlying CT classes and do it using those. In case of XSLFTextRun we can get the CTRegularTextRun object. Then we can look whether there are run properties already. If not, we add one. Then we look whether there is outline set already. If so, we unset it, because we want set it new. Then we set a new outline. This simply is a line having a special color. That line is represented by CTLineProperties object. So we need to have methods to create that CTLineProperties, to set CTLineProperties to the XSLFTextRun and get CTLineProperties from XSLFTextRun.
Complete example using Java code:
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import java.awt.Rectangle;
public class PPTXTextRunOutline {
static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties createSolidFillLineProperties(java.awt.Color color) {
// create new CTLineProperties
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties
= org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties.Factory.newInstance();
// set line solid fill color
lineProperties.addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()});
return lineProperties;
}
static void setOutline(XSLFTextRun run, org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties) {
// get underlying CTRegularTextRun object
org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun
= (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
// Are there run properties already? If not, add one.
if (ctRegularTextRun.getRPr() == null) ctRegularTextRun.addNewRPr();
// Is there outline set already? If so, unset it, because we are creating it new.
if (ctRegularTextRun.getRPr().isSetLn()) ctRegularTextRun.getRPr().unsetLn();
// set a new outline
ctRegularTextRun.getRPr().setLn(lineProperties);
}
static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties getOutline(XSLFTextRun run) {
// get underlying CTRegularTextRun object
org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun
= (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
// Are there run properties already? If not, return null.
if (ctRegularTextRun.getRPr() == null) return null;
// get outline, may be null
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = ctRegularTextRun.getRPr().getLn();
// make a copy to avoid orphaned exceptions or value disconnected exception when set to its own XML parent
if (lineProperties != null) lineProperties = (org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties)lineProperties.copy();
return lineProperties;
}
// your method fontStyles taken to Java code
static void fontStyles(XSLFTextRun templateRun, XSLFTextShape textShape) {
String fontFamily = templateRun.getFontFamily();
PaintStyle fontColor = templateRun.getFontColor();
Double fontSize = templateRun.getFontSize();
boolean fontBold = templateRun.isBold();
boolean fontItalic = templateRun.isItalic();
TextParagraph.TextAlign textAlign = templateRun.getParagraph().getTextAlign();
org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = getOutline(templateRun);
for (XSLFTextParagraph paragraph : textShape.getTextParagraphs()) {
for (XSLFTextRun run : paragraph.getTextRuns()) {
run.setFontFamily(fontFamily);
if(run != templateRun) run.setFontColor(fontColor); // set PaintStyle has the issue which I am avoiding by using a copy of the underlying XML
run.setFontSize(fontSize);
run.setBold(fontBold);
run.setItalic(fontItalic);
run.getParagraph().setTextAlign(textAlign);
setOutline(run, lineProperties);
}
}
}
public static void main(String[] args) throws Exception {
XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTXIn.pptx"));
XSLFSlide slide = slideShow.getSlides().get(0);
//as in your code, get a template text run and set its font style to all other runs in text shape
if (slide.getShapes().size() > 0) {
XSLFShape shape = slide.getShapes().get(0);
if (shape instanceof XSLFTextShape) {
XSLFTextShape textShape = (XSLFTextShape) shape;
XSLFTextParagraph paragraph = null;
if(textShape.getTextParagraphs().size() > 0) paragraph = textShape.getTextParagraphs().get(0);
if (paragraph != null) {
XSLFTextRun run = null;
if(paragraph.getTextRuns().size() > 0) run = paragraph.getTextRuns().get(0);
if (run != null) {
fontStyles(run, textShape);
}
}
}
}
//new text box having outlined text from scratch
XSLFTextBox textbox = slide.createTextBox();
textbox.setAnchor(new Rectangle(100, 300, 570, 80));
XSLFTextParagraph paragraph = null;
if(textbox.getTextParagraphs().size() > 0) paragraph = textbox.getTextParagraphs().get(0);
if(paragraph == null) paragraph = textbox.addNewTextParagraph();
XSLFTextRun run = paragraph.addNewTextRun();
run.setText("Test text outline");
run.setFontSize(60d);
run.setFontColor(java.awt.Color.YELLOW);
setOutline(run, createSolidFillLineProperties(java.awt.Color.BLUE));
FileOutputStream out = new FileOutputStream("./PPTXOit.pptx");
slideShow.write(out);
out.close();
}
}
Tested and works using current apache poi 5.0.0.
So I've been browsing around the source code / documentation for POI (specifically XWPF) and I can't seem to find anything that relates to editing a hyperlink in a .docx. I only see functionality to get the information for the currently set hyperlink. My goal is to change the hyperlink in a .docx to link to "http://yahoo.com" from "http://google.com" as an example. Any help would be greatly appreciated. Thanks!
I found a way to edit the url of the link in a "indirect way" (copy the previous hyperlink, modify the url, delete the previous hyperlink and add the new one in the paragraph).
Code is shown below:
private void editLinksOfParagraph(XWPFParagraph paragraph, XWPFDocument document) {
for (int rIndex = 0; rIndex < paragraph.getRuns().size(); rIndex++) {
XWPFRun run = paragraph.getRuns().get(rIndex);
if (run instanceof XWPFHyperlinkRun) {
// get the url of the link to edit it
XWPFHyperlink link = ((XWPFHyperlinkRun) run).getHyperlink(document);
String linkURL = link.getURL();
//get the xml representation of the hyperlink that includes all the information
XmlObject xmlObject = run.getCTR().copy();
linkURL += "-edited-link"; //edited url of the link, f.e add a '-edited-link' suffix
//remove the previous link from the paragraph
paragraph.removeRun(rIndex);
//add the new hyperlinked with updated url in the paragraph, in place of the previous deleted
XWPFHyperlinkRun hyperlinkRun = paragraph.insertNewHyperlinkRun(rIndex, linkURL);
hyperlinkRun.getCTR().set(xmlObject);
}
}
}
This requirement needs knowledge about how hyperlinks referring to an external reference get stored in Microsoft Word documents and how this gets represented in XWPF of Apache POI.
The XWPFHyperlinkRun is the representation of a linked text run in a IRunBody. This text run, or even multiple text runs, is/are wrapped with a XML object of type CTHyperlink. This contains a relation ID which points to a relation in the package relations part. This package relation contains the URI which is the hyperlink's target.
Currently (apache poi 5.2.2) XWPFHyperlinkRun provides access to a XWPFHyperlink. But this is very rudimentary. It only has getters for the Id and the URI. It neither provides access to it's XWPFHyperlinkRun and it's IRunBody nor it provides a setter for the target URI in the package relations part. It not even has internally access to it's the package relations part.
So only using Apache POI classes the only possibility currently is to delete the old XWPFHyperlinkRun and create a new one pointing to the new URI. But as the text runs also contain the text formatting, deleting them will also delete the text formatting. It would must be copied from the old XWPFHyperlinkRun to the new before deleting the old one. That's uncomfortable.
So the rudimentary XWPFHyperlink should be extended to provide a setter for the target URI in the package relations part. A new class XWPFHyperlinkExtended could look like so:
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
/**
* Extended XWPF hyperlink class
* Provides access to it's Id, URI, XWPFHyperlinkRun, IRunBody.
* Provides setting target URI in PackageRelationship.
*/
public class XWPFHyperlinkExtended {
private String id;
private String uri;
private XWPFHyperlinkRun hyperlinkRun;
private IRunBody runBody;
private PackageRelationship rel;
public XWPFHyperlinkExtended(XWPFHyperlinkRun hyperlinkRun, PackageRelationship rel) {
this.id = rel.getId();
this.uri = rel.getTargetURI().toString();
this.hyperlinkRun = hyperlinkRun;
this.runBody = hyperlinkRun.getParent();
this.rel = rel;
}
public String getId() {
return this.id;
}
public String getURI() {
return this.uri;
}
public IRunBody getIRunBody() {
return this.runBody;
}
public XWPFHyperlinkRun getHyperlinkRun() {
return this.hyperlinkRun;
}
/**
* Provides setting target URI in PackageRelationship.
* The old PackageRelationship gets removed.
* A new PackageRelationship gets added using the same Id.
*/
public void setTargetURI(String uri) {
this.runBody.getPart().getPackagePart().removeRelationship(this.getId());
this.uri = uri;
PackageRelationship rel = this.runBody.getPart().getPackagePart().addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation(), this.getId());
this.rel = rel;
}
}
It does not extend XWPFHyperlink as this is so rudimentary it's not worth it. Furthermore after setTargetURI the String uri needs to be updated. But there is no setter in XWPFHyperlink and the field is only accessible from inside the package.
The new XWPFHyperlinkExtended can be got from XWPFHyperlinkRun like so:
/**
* If this HyperlinkRun refers to an external reference hyperlink,
* return the XWPFHyperlinkExtended object for it.
* May return null if no PackageRelationship found.
*/
/*modifiers*/ XWPFHyperlinkExtended getHyperlink(XWPFHyperlinkRun hyperlinkRun) {
try {
for (org.apache.poi.openxml4j.opc.PackageRelationship rel : hyperlinkRun.getParent().getPart().getPackagePart().getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation())) {
if (rel.getId().equals(hyperlinkRun.getHyperlinkId())) {
return new XWPFHyperlinkExtended(hyperlinkRun, rel);
}
}
} catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException ifex) {
// do nothing, simply do not return something
}
return null;
}
Once we have that XWPFHyperlinkExtended we can set an new target URI using it's method setTargetURI.
A further problem results from the fact, that the XML object of type CTHyperlink can wrap around multiple text runs, not only one. Then multiple XWPFHyperlinkRun are in one CTHyperlink and point to one target URI. For example this could look like:
... [this is a link to example.com]->https://example.com ...
This results in 6 XWPFHyperlinkRuns in one CTHyperlink linking to https://example.com.
This leads to problems when link text needs to be changed when the link target changes. The text of all the 6 text runs is the link text. So which text run shall be changed?
The best I have found is a method which sets the text of the first text run in the CTHyperlink.
/**
* Sets the text of the first text run in the CTHyperlink of this XWPFHyperlinkRun.
* Tries solving the problem when a CTHyperlink contains multiple text runs.
* Then the String value is set in first text run only. All other text runs are set empty.
*/
/*modifiers*/ void setTextInFirstRun(XWPFHyperlinkRun hyperlinkRun, String value) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink ctHyperlink = hyperlinkRun.getCTHyperlink();
for (int r = 0; r < ctHyperlink.getRList().size(); r++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR ctR = ctHyperlink.getRList().get(r);
for (int t = 0; t < ctR.getTList().size(); t++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText ctText = ctR.getTList().get(t);
if (r == 0 && t == 0) {
ctText.setStringValue(value);
} else {
ctText.setStringValue("");
}
}
}
}
There the String value is set in first text run only. All other text runs are set empty. The text formatting of the first text run remains.
This works, but need more some steps to get text formatting correctly:
try (var fis = new FileInputStream(fileName);
var doc = new XWPFDocument(fis)) {
var pList = doc.getParagraphs();
for (var p : pList) {
var runs = p.getRuns();
for (int i = 0; i < runs.size(); i++) {
var r = runs.get(i);
if (r instanceof XWPFHyperlinkRun) {
var run = (XWPFHyperlinkRun) r;
var link = run.getHyperlink(doc);
// To get text: link for checking
System.out.println(run.getText(0) + ": " + link.getURL());
// how i replace it
var run1 = p.insertNewHyperlinkRun(i, "http://google.com");
run1.setText(run.getText(0));
// remove the old link
p.removeRun(i + 1);
}
}
}
try (var fos = new FileOutputStream(outFileName)) {
doc.write(fos);
}
}
I'm using these libraries:
implementation 'org.apache.poi:poi:5.2.2'
implementation 'org.apache.poi:poi-ooxml:5.2.2'
Following the post made # POI Excel Merging Causing "Repaired Records: Format from /xl/styles.xml part (Styles)"
I have two excel files which are opening fine with styles and colors (In microsoft office 2010).
Iam merging both of those excel files using the code posted in the thread above.
The problem is with the styles (I created styles as below):
newCellStyle = newCell.getSheet().getWorkbook().createCellStyle();
newCellStyle.cloneStyleFrom(oldCellStyle);
styleMap.put(stHashCode, newCellStyle);
which has caused the styles problem "Repaired Records: Format from /xl/styles.xml part (Styles)"
after a fair amount of research what I learnt is the border and fill is causing the problem. Un setting these parameters has solved the problem. But as it states border and fill went missing.
Can some one throws idea around how to get the border and fill style from a cell and apply to a new cell ?
This appears to be a bug in Apache POI, #55800. The border ID and fill ID used in the "CoreXf" object aren't copied, causing the issue.
According to Comment 5 on that bug, it is possible to work around it by copying the fill and border attributes manually yourself.
The reason is it won't copy the XSSFCellFill and XSSFCellBorder. This also gives problem with borders. I have added a method in org.apache.poi.xssf.model.StylesTable which will help in creating a copy of workbook.
public void copyTo(StylesTable stylesTable){
stylesTable.numberFormats.clear();
stylesTable.fonts.clear();
stylesTable.fills.clear();
stylesTable.borders.clear();
stylesTable.styleXfs.clear();
stylesTable.xfs.clear();
stylesTable.dxfs.clear();
for(String str : numberFormats.values())
stylesTable.putNumberFormat(str);
for(XSSFFont font : fonts){
XSSFFont fontNew = new XSSFFont(font.getCTFont());
fontNew.registerTo(stylesTable);
}
for(XSSFCellFill fill : fills){
XSSFCellFill fillNew = new XSSFCellFill(fill.getCTFill());
stylesTable.putFill(fillNew);
}
for(XSSFCellBorder border : borders){
XSSFCellBorder borderNew = new XSSFCellBorder(border.getCTBorder());
stylesTable.putBorder(borderNew);
}
for(CTXf ctxf : styleXfs){
CTXf ctxfNew = (CTXf)ctxf.copy();
stylesTable.putCellStyleXf(ctxfNew);
}
for(CTXf ctxf : xfs){
CTXf ctxfNew = (CTXf)ctxf.copy();
stylesTable.putCellXf(ctxfNew);
}
for(CTDxf dxf : dxfs){
CTDxf dxfNew = (CTDxf)dxf.copy();
stylesTable.putDxf(dxfNew);
}
}
Following the post at https://issues.apache.org/bugzilla/show_bug.cgi?id=55800
As we are having trouble with border and fill
adding below piece of code worked like Charm
newCellStyle = newCell.getSheet().getWorkbook().createCellStyle();
newCellStyle.cloneStyleFrom(oldCellStyle);
// newCellStyle.getCoreXf().unsetBorderId();
// newCellStyle.getCoreXf().unsetFillId();
StylesTable newStylesSource = newCell.getSheet().getWorkbook().getStylesSource();
StylesTable oldStylesSource = oldCell.getSheet().getWorkbook().getStylesSource();
for (XSSFCellFill fill : oldStylesSource.getFills())
{
XSSFCellFill fillNew = new XSSFCellFill(fill.getCTFill());
newStylesSource.putFill(fillNew);
}
for (XSSFCellBorder border : oldStylesSource.getBorders())
{
XSSFCellBorder borderNew = new XSSFCellBorder(border.getCTBorder());
newStylesSource.putBorder(borderNew);
}
You could use the following code. I verified with .xlsx and I believe it would work with .xls also.
int stHashCode = oldCell.getCellStyle().hashCode();
CellStyle newCellStyle = newCell.getSheet().getWorkbook().createCellStyle();
newCellStyle.cloneStyleFrom(oldCell.getCellStyle());
newCell.setCellStyle(newCellStyle);
styleMap.put(stHashCode, newCellStyle);
if ( (newCell.getSheet().getWorkbook() instanceof XSSFWorkbook) && (oldCell.getSheet().getWorkbook() instanceof XSSFWorkbook) ){
StylesTable newStylesSource = ((XSSFWorkbook) newCell.getSheet().getWorkbook()).getStylesSource();
StylesTable oldStylesSource = ((XSSFWorkbook) oldCell.getSheet().getWorkbook()).getStylesSource();
for (XSSFCellFill fill : oldStylesSource.getFills()) {
XSSFCellFill fillNew = new XSSFCellFill(fill.getCTFill());
newStylesSource.putFill(fillNew);
}
for (XSSFCellBorder border : oldStylesSource.getBorders()) {
XSSFCellBorder borderNew = new XSSFCellBorder(border.getCTBorder());
newStylesSource.putBorder(borderNew);
}
}