Tuesday, May 4, 2010

Creating New Presentations from Slide Masters Using OpenXML (Revisited)

Here’s an update to a prior post, Creating New Presentations from Slide Masters Using OpenXML, about creating new presentation slides from the slide master.  A reader commented about an error with layouts that had images in them and it came to my attention that my code completely ignored the images.  Here’s an update:


private static void InsertSlide(PresentationPart pPart, string layoutName, UInt32 slideId)


        {


            Slide slide = new Slide(new CommonSlideData(new ShapeTree()));


            SlidePart sPart = pPart.AddNewPart<SlidePart>();


            slide.Save(sPart);


            SlideMasterPart smPart = pPart.SlideMasterParts.First();


            SlideLayoutPart slPart = smPart.SlideLayoutParts.Single(kaark => kaark.SlideLayout.CommonSlideData.Name == layoutName);




            //Add the layout part to the new slide from the slide master


            sPart.AddPart<SlideLayoutPart>(slPart);


            sPart.Slide.CommonSlideData = (CommonSlideData)smPart.SlideLayoutParts.Single(kaark => kaark.SlideLayout.CommonSlideData.Name == layoutName).SlideLayout.CommonSlideData.Clone();


 


            using (Stream stream = slPart.GetStream())


            {


                sPart.SlideLayoutPart.FeedData(stream);


            }


 


            //UPDATED: Copy the images from the slide master layout to the new slide


            foreach (ImagePart iPart in slPart.ImageParts)


            {


                ImagePart newImagePart = sPart.AddImagePart(iPart.ContentType, slPart.GetIdOfPart(iPart));


                newImagePart.FeedData(iPart.GetStream());


            }


 


            SlideId newSlideId = pPart.Presentation.SlideIdList.AppendChild<SlideId>(new SlideId());


            newSlideId.Id = slideId;


            newSlideId.RelationshipId = pPart.GetIdOfPart(sPart);


        }





After feeding the slide layout part data into the new slide’s layout part, the code then adds the new image parts to the slide from the slide master layout.

Wednesday, March 24, 2010

Creating New Presentations Using the Slide Master

Note: Cross posted from Coding The Document.

Permalink

The slide master in Powerpoint provides a powerful way for end-users to easily control the appearance and layouts of a presentation. A slide master contains a set of layouts that are subsequently used by the slides in the presentation.

A common approach to constructing a new presentation is to have a template with slides that are then copied/merged into the new presentation. The approach I will be demonstrating creates slides in the new presentation based off the slide master layouts in the template. This approach still requires a template, but does not require slides to already exist.

The Template

The template I used contained a few layouts in the slide master, each arranged with some placeholder objects. A great benefit of layouts in the slide master is that they can be renamed through the UI. The layout name is what will be used in the code to construct the slide deck in the new presentation.

The Code

Now the fun part. The InsertSlide method takes a PresentationPart, layout name from the slide master, and ID for the new slide. It creates the new Slide and adds the associated parts to the PresentationPart, copying all the required layout and common slide data from the slide master layout.




private static void InsertSlide(PresentationPart pPart, string layoutName, UInt32 slideId)
{
Slide slide = new Slide(new CommonSlideData(new ShapeTree()));
SlidePart sPart = pPart.AddNewPart();
slide.Save(sPart);
SlideMasterPart smPart = pPart.SlideMasterParts.First();
SlideLayoutPart slPart = smPart.SlideLayoutParts.Single(kaark => kaark.SlideLayout.CommonSlideData.Name == layoutName);
sPart.AddPart(slPart);
sPart.Slide.CommonSlideData = (CommonSlideData)smPart.SlideLayoutParts.Single(kaark => kaark.SlideLayout.CommonSlideData.Name == layoutName).SlideLayout.CommonSlideData.Clone();
using (Stream stream = slPart.GetStream())
{
sPart.SlideLayoutPart.FeedData(stream);
}
SlideId newSlideId = pPart.Presentation.SlideIdList.AppendChild(new SlideId());
newSlideId.Id = slideId;
newSlideId.RelationshipId = pPart.GetIdOfPart(sPart);
}


Since the presentation started with only a slide master in the template and no slides, a SlideIdList must be added to the PresentationPart. Then start adding slides, using the layout names from the slide master. Notice that the slide IDs were started at 256, that’s not a typo. Slide IDs must be >= 256.




using (PresentationDocument pDoc = PresentationDocument.Open(newFileCopiedFromTemplate, true))
{
PresentationPart pPart = pDoc.PresentationPart;
pPart.Presentation.SlideIdList = new SlideIdList();
InsertSlide(pPart, "Layout1", 256);
InsertSlide(pPart, "Layout3", 257);
InsertSlide(pPart, "Layout3", 258);
InsertSlide(pPart, "Layout2", 259);
pPart.Presentation.Save();
pDoc.Close();
}

Wednesday, November 4, 2009

Open XML and Office Services

Brian Jones has a new post on his blog, http://blogs.msdn.com/brian_jones/archive/2009/11/03/open-xml-and-office-services.aspx ,showing a great scenario for using Open XML and the new Office Services. A big highlight for me with these new tools is the ability to create PDFs at the end of the document generation process without including the Word client and performing Office automation. The scenario highlighted is just the tip of the iceberg of the power being brought to the enterprise.

Sunday, April 12, 2009

OpenXML SDK Version 2 April 2009 CTP

Some exciting news, the second CTP version of the OpenXML SDK v2 has been released. Read the details over at http://blogs.msdn.com/brian_jones/archive/2009/04/08/announcing-the-release-of-the-open-xml-sdk-version-2-april-2009-ctp.aspx.  Most exciting is the addition of schema level validation, this should drastically reduce invalid and corrupt files, something that has dogged me many times.

Tuesday, December 16, 2008

SpreadsheetML - Convert column integer index to column alpha value(i.e. A, AA, BH)

Here's a quick snippet I found in a forum post at http://stackoverflow.com/questions/181596/how-to-convert-a-column-number-eg-127-into-an-excel-column-eg-aa. Many times in Excel I want to use a column index value instead of the letter representation(i.e A, AA, BH). I don't have control of the Excel so I can't change the representation. This method returns the letter representation from an integer column value.



protected string GetExcelColumnName(int columnNumber)
{
int dividend = columnNumber;
string columnName = String.Empty;
int modulo;

while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}

return columnName;
}

SpreadsheetML - Retrieving SharedString values

Retrieving cell values in Excel spreadsheets is very straight forward, especially when string values are stored inline. Other times the string values are stored in the SharedStringTable collection. If the CellType is a SharedString. then the cell value references the index in the SharedStringTable. Below is a quick snippet that will return the SharedString value if that is the case, otherwise the inline string value.

protected string GetCellValue(WorkbookPart wbPart, string targetCell)
{
string result = null;
WorksheetPart wsPart = wbPart.WorksheetParts.First();
SheetData sheetData = wsPart.Worksheet.GetFirstChild();
SharedStringTablePart sstPart = wbPart.GetPartsOfType().First();
SharedStringTable ssTable = sstPart.SharedStringTable;

try
{
Cell cell = wsPart.Worksheet.Descendants()
.Where(c => targetCell.Equals(c.CellReference))
.First();

if (cell.DataType != null && cell.DataType == CellValues.SharedString)
{
result = ssTable.ChildElements[Convert.ToInt32(cell.CellValue.Text)].InnerText;
}
else
{
if (cell.CellValue != null)
{
result = cell.CellValue.Text;
}
}
}
catch (Exception)
{

}

return result;
}

Monday, November 10, 2008

Powerpoint spell check and text substitution problem

A situtation can occur when identifying substitution text in a presentation. In my example the text token is surrounded with a start/end token(i.e. [@token]). If the token is a word in the Office dictionary then there is no problem. On the other hand if the token word isn't in the dictionary and gets the red underline from spellcheck then Powerpoint breaks the string into multiple text runs. It's put into multiple runs because the spellchecked text has a RunProperty that identifies it as being a spelling error and must be repressented by the schema as such. The following will show the issue.


Slide to be parsed


Here's the breakdown of the text runs
Text Before: Token fun
Text Before: Sometimes a [@Token] string will get broken up.
Text Before: When spell check flags a misspelled word the
Text Before: [@
Text Before: MisspelledToken
Text Before: ] gets split between multiple text blocks


As you can see there is a token that gets spanned across multiple text runs and thus doesn't get replaced. This can be a major hang up. A quick solution for this one example is that I know when there is a start-token at the end of a text run then the next couple text runs are the token itself and then the end-token. So I can append those three together and then replace the token as necessary.


void SubstituteText(SlidePart slidePart, string tokenStart, string tokenEnd, string tokenValue, string subValue)
{
string token = tokenStart + tokenValue + tokenEnd;
int tokenStartId = 0;

//Collect all the Paragraph sections containing the token
List paragraphList = slidePart.Slide
.Descendants()
.Where(t => t.InnerText.Contains(token)).ToList();

foreach (Drawing.Paragraph p in paragraphList)
{
//Collect all the Text
List textList = p
.Descendants()
.ToList();

//Iterate and find tokenStart Text block or replace text if whole token found
foreach (Drawing.Text t in new List(p.Descendants().ToList()))
{
if (t.Text.EndsWith(tokenStart))
{
tokenStartId = textList.IndexOf(t);

//append next two text segments and remove them
Drawing.Text appendText = textList[tokenStartId + 1];
t.Text = t.Text + appendText.Text;
//must remove at the Drawing.Run level
appendText.Parent.Remove();
appendText = textList[tokenStartId + 2];
t.Text = t.Text + appendText.Text;
//must remove at the Drawing.Run level
appendText.Parent.Remove();

textList.RemoveAt(tokenStartId + 1);
textList.RemoveAt(tokenStartId + 2);
}
//substitute text
t.Text = t.Text.Replace(token, subValue);
}

}
}


After running this I get the following output as desired.

.pptx after substitution


Breakdown of the text runs after substitution
Text After: Token fun
Text After: Sometimes a TOKEN string will get broken up.
Text After: When spell check flags a misspelled word the
Text After: BROKEN TOKEN gets split between multiple text blocks

Now this doesn't cure all ails. I have also seen it where after going back and making changes to existing text that sometimes the start-token and token are together but the end-token is seperated to another text run. We also can't just take all the runs of a paragraph and smash them together into a single run and then substitute. This would ignore any text styling that was done within the paragraph. I have tried parsing a paragraph such that it looks at the RunProperties between consequetive runs to see if it's valid to append them together. I am still working on this and will present the final solution at a later time.

Hopefully this post has revealed some nuances of doing text substitution and that it isn't always straight forward.