Beruflich Dokumente
Kultur Dokumente
Summary: Combining data and images from various Microsoft Office programs to create a single document is a frequently requested scenario. Doing this from documents stored in SharePoint Foundation 2010 has several advantages. Learn to create Word documents in a document library by merging components from Excel, PowerPoint, and other Word documents. (25 printed pages) Applies to: Microsoft Excel 2010 | Microsoft Office 2010 | Microsoft PowerPoint 2010 | Microsoft Word 2010 | Microsoft SharePoint Server 2010 Published: March 2010 Provided by: Zeyad Rajabi, Microsoft Corporation | Frank Rice, Microsoft Corporation Contents Document Assembly with Office 2010 and SharePoint 2010 Scenario Overview Solution Overview o o o o o o o Results Conclusion Additional Resources Step 1 Create the Template Step 2 Set Up a Document Set in SharePoint 2010 Step 3 Create a Web Part with an Assemble Documents Command Step 4 Find the Content Controls Step 5 Merge the Content Together Step 6 Offer the Assembled Document to the User Step 7 - Sign the Project
Download code
Document assembly seems to be a hot topic these days especially when you combine it with the power of Microsoft SharePoint Server 2010. Prior to the 2007 Microsoft Office system, accessing text, tables, images, and other information to create documents in Microsoft Word, for example, relied on COM Automation to access each program contributing to the merge. Automation has its share of issues such as interruptions by pop-up dialog boxes, timing constraints, inability to scale out, and other limitations. Additionally, running Microsoft Office solutions on the server is not recommended or supported by Microsoft. With the release of 2007 Microsoft Office system, the Open XML file formats present documents as a composite of parts and relationships. For example, there are image parts, document parts, and charts parts. This enables you to access and create documents and their component parts without using Automation. In addition, the Open XML SDK 2.0 for Microsoft Office enables you to create and manipulate documents, and expose data and other components programmatically, without using Microsoft Office Automation. And because Automation is no longer required to work with Microsoft Office documents, you can now perform merges and other actions directly on the server. Likewise, SharePoint Server 2010 is a great a tool for storing, sharing, and controlling documents and providing functionality to work with documents. For example, the Document Center in SharePoint Server 2010 is an area where you can collaborate and store documents in document libraries. Document libraries are containers that are already configured to use workflow, version history, and other features that are important for working with documents at the enterprise level. This article describes a rich document assembly solution which takes Microsoft Word 2010 documents, Microsoft Excel 2010 documents, and Microsoft PowerPoint 2010 documents and merges them together to form a final report in Word. It does all of this from a SharePoint 2010 Web Part. You can download the solution project from the Code Gallery.
Scenario Overview
Imagine a scenario where you work for a company that analyzes stocks and generates reports for every company and stock analyzed. These reports are typically very rich and usually involve more than one person contributing to the content. Content is separated into multiple Word documents, Excel documents, and PowerPoint documents where each document is assigned to a person. After all the content is written, the content is assembled into a final report as a Word document. The company asks you to write a solution that merges all these documents programmatically.
Solution Overview
This solution uses document sets, a feature in SharePoint 2010. Document sets enables you to manage collection of documents as single objects. Think of this feature as a binder of related content. In this solution, a custom document set includes a set of files (six in this example) that correspond to the various components of the final analysis report. Figure 1 shows a document set for a company called Contoso.
Based on this scenarios, by using document sets, you follow these steps: 1. Create a set of documents that represent the document set for this solution. One of the documents is a Word template that represents the appearance of the final report. Create and add a custom document set to a document library. Create a Web Part for the document library with a button labeled Assemble Documents. The button merges all the content together into a final document. Add this Web Part to the document set library. Using the Open XML SDK 2.0, open the template document from the document set and search for all content controls. For every content control you find, find the corresponding document content in the library and merge that content into the final document. After the document assembly is complete, offer the user the ability to open or save the report.
2. 3.
4.
5.
6.
To make the process easier, the titles of the content controls represent the types of content to be merged. For example, a Word document, a chart from a spreadsheet, a table from a spreadsheet, and a SmartArt graphic from a presentation. The content of a content control represents the name of the file that contains the content to be merged. For example, Figure 2 shows the template document highlighting one of the content controls labeled "Word:Document" with the content set to "Introduction".
This content control represents the region where you merge the Contoso - Introduction document (from Word) into the template file. For the sake of completeness, the other content controls are labeled Spreadsheet:Chart, Spreadsheet:Table, and Presentation:SmartArt.
Next, you must create document sets for the six documents on the SharePoint 2010 site.
At this point, you should have a library set up with a document set content type displaying the six documents that use the document set name prefix (see Figure 1).
3. 4. 5. 6.
Under Installed Templates, click SharePoint, 2010, and then Visual Web Part. In the Name box, type DocumentAssembly, and then click OK. In the SharePoint Configuration Wizard page, select your Web application, and then click Finish. After you create the Web Part, open the VisualWebPart1.cs file and add the following references to the existing references at the top of the file. VB C# C++ F# JScript Copy using using using using using using using using using using using using System.Linq; System.IO; DocumentFormat.OpenXml.Packaging; Word = DocumentFormat.OpenXml.Wordprocessing; Draw = DocumentFormat.OpenXml.Drawing; DocumentFormat.OpenXml.Drawing.Charts; WP = DocumentFormat.OpenXml.Drawing.Wordprocessing; DocumentFormat.OpenXml.Drawing.Spreadsheet; Excel = DocumentFormat.OpenXml.Spreadsheet; DocumentFormat.OpenXml; PPT = DocumentFormat.OpenXml.Presentation; Dgm = DocumentFormat.OpenXml.Drawing.Diagrams;
7.
Next, modify the CreateChildControls method as follows. VB C# C++ F# JScript Copy protected override void CreateChildControls() {
Control control = this.Page.LoadControl(_ascxPath); Controls.Add(control); base.CreateChildControls(); Button btnSubmit = new Button(); btnSubmit.Text = "Assemble Documents"; btnSubmit.Click += new EventHandler(btnSubmit_Click); Controls.Add(btnSubmit); } This method adds a new button control where you add logic to merge the documents that are contained in a given document set (the merge code is called from the btnSubmit_Click event). When you create the Web Part, add the button to the document set. The easiest way to do this task is to use Microsoft SharePoint Designer 2010.
6.
At this point, you should see an Assemble Documents command displayed for any document set as shown in Figure 4.
The following code performs these steps. VB C# C++ F# JScript Copy void btnSubmit_Click(object sender, EventArgs e)
{ SPFolder folder = SPContext.Current.ListItem.Folder; char[] splitter = { '/' }; string[] folderName = folder.Name.Split(splitter); string filePrefix = @"Stock Analysis Demo/" + folderName[0] + "/" + folderName[0]; SPFile template = folder.Files[filePrefix + " - Template.docx"]; SPFile file; byte[] byteArray = template.OpenBinary(); using (MemoryStream mem = new MemoryStream()) { mem.Write(byteArray, 0, (int)byteArray.Length); using (WordprocessingDocument myDoc = WordprocessingDocument.Open(mem, true)) { MainDocumentPart mainPart = myDoc.MainDocumentPart; foreach (Word.SdtElement sdt in mainPart.Document .Descendants<Word.SdtElement>().ToList()) { Word.Alias alias = sdt.Descendants<Word.Alias>().FirstOrDefault(); if (alias != null) { string sdtTitle = alias.Val.Value; if (sdtTitle == "Spreadsheet:Table") { file = folder.Files[filePrefix + " - " + sdt.InnerText ImportTableFromSpreadsheet(mainPart, sdt, file); } else if (sdtTitle == "Spreadsheet:Chart") { file = folder.Files[filePrefix + " - " + sdt.InnerText ImportChartFromSpreadsheet(mainPart, sdt, file); } else if (sdtTitle == "Presentation:SmartArt") { file = folder.Files[filePrefix + " - " + sdt.InnerText ImportSmartArtFromPowerPoint(mainPart, sdt, file); } else if (sdtTitle == "Word:Document") { file = folder.Files[filePrefix + " - " + sdt.InnerText AddAltChunk(mainPart, sdt, file); } } } } // For the remainder of the code in this procedure, see step 6. } }
+ ".xlsx"];
+ ".xlsx"];
+ ".pptx"];
+ ".docx"];
DiagramColorsPart docColorsPart = mainPart.AddPart<DiagramColorsPart>(colorsPart); DiagramStylePart docStylePart = mainPart.AddPart<DiagramStylePart>(stylePart); // Get all the relationship ids of the added parts. docLayoutPartId = mainPart.GetIdOfPart(docLayoutPart); docDataPartId = mainPart.GetIdOfPart(docDataPart); docColorsPartId = mainPart.GetIdOfPart(docColorsPart); docStylePartId = mainPart.GetIdOfPart(docStylePart); // Use the document reflector to figure out how to add a SmartArt // graphic to Word. // Change attribute values based on specifics related to the SmartArt. Word.Paragraph p = new Word.Paragraph( new Word.Run( new Word.Drawing( new WP.Inline( new WP.Extent() { Cx = extents.Cx, Cy = extents.Cy }, new WP.EffectExtent() { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L }, new WP.DocProperties() {Id = drawingPr.Id, Name = drawingPr.Name }, new WP.NonVisualGraphicFrameDrawingProperties(), new Draw.Graphic( new Draw.GraphicData( new Dgm.RelationshipIds() { DataPart = docDataPartId, LayoutPart = docLayoutPartId, StylePart = docStylePartId, ColorPart = docColorsPartId }) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/diagram" })) { DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, DistanceFromLeft = (UInt32Value)0U, DistanceFromRight = (UInt32Value)0U }))); // Swap out the content control for the SmartArt. OpenXmlElement parent = sdt.Parent; parent.InsertAfter(p, sdt); sdt.Remove(); } } }
C++ F# JScript Copy void ImportChartFromSpreadsheet(MainDocumentPart mainPart, Word.SdtElement sdt, SPFile spreadsheetFileName) { // Create a paragraph that has an inline drawing object. Word.Paragraph p = new Word.Paragraph(); Word.Run r = new Word.Run(); p.Append(r); Word.Drawing drawing = new Word.Drawing(); r.Append(drawing); // These dimensions work perfectly for the template document. WP.Inline inline = new WP.Inline( new WP.Extent() { Cx = 5486400, Cy = 3200400 }); byte[] byteArray = spreadsheetFileName.OpenBinary(); using (MemoryStream mem = new MemoryStream()) { mem.Write(byteArray, 0, (int)byteArray.Length); // Open the Excel spreadsheet. using (SpreadsheetDocument mySpreadsheet = SpreadsheetDocument.Open(mem, true)) { // Get all of the appropriate parts. WorkbookPart workbookPart = mySpreadsheet.WorkbookPart; WorksheetPart worksheetPart = XLGetWorksheetPartByName(mySpreadsheet, "Sheet2"); DrawingsPart drawingPart = worksheetPart.DrawingsPart; ChartPart chartPart = (ChartPart)drawingPart.GetPartById("rId1"); // Clone the chart part and add it to the Word document. ChartPart importedChartPart = mainPart.AddPart<ChartPart>(chartPart); string relId = mainPart.GetIdOfPart(importedChartPart); // The frame element contains information for the chart. GraphicFrame frame = drawingPart.WorksheetDrawing.Descendants<GraphicFrame>().First(); string chartName = frame.NonVisualGraphicFrameProperties.NonVisualDrawingProperties.Name; // Clone this node so that you can add it to the Word document. Draw.Graphic clonedGraphic = (Draw.Graphic)frame.Graphic.CloneNode(true); ChartReference c = clonedGraphic.GraphicData.GetFirstChild<ChartReference>(); c.Id = relId; // Give the chart a unique ID and name. WP.DocProperties docPr = new WP.DocProperties(); docPr.Name = chartName; docPr.Id = GetMaxDocPrId(mainPart) + 1; // Add the chart data to the inline drawing object. inline.Append(docPr, clonedGraphic); drawing.Append(inline); }
} OpenXmlElement parent = sdt.Parent; parent.InsertAfter(p, sdt); sdt.Remove(); } WorksheetPart XLGetWorksheetPartByName(SpreadsheetDocument document, string sheetName) { WorkbookPart wbPart = document.WorkbookPart; // Find the sheet with the supplied name, and then use that Sheet object // to retrieve a reference to the appropriate worksheet. Excel.Sheet theSheet = wbPart.Workbook.Descendants<Excel.Sheet>() .Where(s => s.Name == sheetName).FirstOrDefault(); if (theSheet == null) { throw new ArgumentException("sheetName"); } return (WorksheetPart)(wbPart.GetPartById(theSheet.Id)); } uint GetMaxDocPrId(MainDocumentPart mainPart) { uint max = 1; // Get the maximum ID value of the docPr elements. foreach (WP.DocProperties docPr in mainPart.Document.Descendants<WP.DocProperties>()) { uint id = docPr.Id; if (id > max) max = id; } return max; }
6. 7.
After all data in the row is retrieved, create a Word row with the same data. Append the created Word row to the Word table.
To help with these tasks, you can take advantage of some Open XML SDK 2.0 code examples. For more information, see the 2007 Office System Sample: Open XML Format SDK 2.0 Code Snippets for Visual Studio 2008. Here is the code that you need to do these tasks. Copy void ImportTableFromSpreadsheet(MainDocumentPart mainPart, Word.SdtElement sdt, SPFile spreadsheetFileName) { ArrayList cellText = new ArrayList(); // Create a Word table. Word.Table tbl = new Word.Table(); Word.TableProperties tblPr = new Word.TableProperties(); Word.TableStyle tblStyle = new Word.TableStyle(); tblStyle.Val = "LightShading-Accent1"; tblPr.AppendChild(tblStyle); Word.TableWidth tblW = new Word.TableWidth(); tblW.Width = 5000; tblW.Type = Word.TableWidthUnitValues.Pct; tblPr.Append(tblW); tbl.AppendChild(tblPr); byte[] byteArray = spreadsheetFileName.OpenBinary(); using (MemoryStream mem = new MemoryStream()) { mem.Write(byteArray, 0, (int)byteArray.Length); using (SpreadsheetDocument mySpreadsheet = SpreadsheetDocument.Open(mem, true)) { WorkbookPart workbookPart = mySpreadsheet.WorkbookPart; WorksheetPart worksheetPart = XLGetWorksheetPartByName(mySpreadsheet, "Sheet1"); Excel.SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<Excel.SheetData>(); foreach (Excel.Row r in sheetData) { foreach (Excel.Cell c in r) { cellText.Add(XLGetCellValue(c, workbookPart)); } Word.TableRow tr = CreateRow(cellText); tbl.Append(tr); cellText = new ArrayList(); }
} } // Swap out the content control for the SmartArt. OpenXmlElement parent = sdt.Parent; parent.InsertAfter(tbl, sdt); sdt.Remove(); } Word.TableRow CreateRow(ArrayList cellText) { Word.TableRow tr = new Word.TableRow(); // Create cells with simple text. foreach (string s in cellText) { Word.TableCell tc = new Word.TableCell(); Word.Paragraph p = new Word.Paragraph(); Word.Run r = new Word.Run(); Word.Text t = new Word.Text(s); r.AppendChild(t); p.AppendChild(r); tc.AppendChild(p); tr.AppendChild(tc); } return tr; } // Get the value of a cell, given a file name, sheet name, and address name. string XLGetCellValue(Excel.Cell c, WorkbookPart wbPart) { string value = null; // If the cell does not exist, return an empty string. if (c != null) { value = c.InnerText; // // // // // // if { If the cell represents an integer number, you are finished. For dates, this code returns the serialized value that represents the date. The code handles strings and Boolean values individually. For shared strings, the code looks up the corresponding value in the shared string table. For Boolean values, the code converts the value into the words TRUE or FALSE. (c.DataType != null)
switch (c.DataType.Value) { case Excel.CellValues.SharedString: // For shared strings, look up the value in the shared strings table. var stringTable = wbPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault(); // If the shared string table is missing, something is wrong. // Just return the index that you found in the cell. // Otherwise, look up the correct text in the table. if (stringTable != null) {
value = stringTable.SharedStringTable.ElementAt(int.Parse(value)).InnerText; } break; case Excel.CellValues.Boolean: switch (value) { case "0": value = "FALSE"; break; default: value = "TRUE"; break; } break; } } } return value; }
Here is the code example to create this dialog based on a document that is in memory.
VB C# C++ F# JScript Copy void btnSubmit_Click(object sender, EventArgs e) { // For the code that goes here, see step 4. HttpResponse resp = HttpContext.Current.Response; resp.ClearContent(); resp.ClearHeaders(); resp.AddHeader("Content-Disposition", "attachment; filename=Assembled Document.docx"); resp.ContentEncoding = System.Text.Encoding.UTF8; resp.OutputStream.Write(mem.ToArray(), 0, (int)mem.Length); resp.Flush(); resp.Close(); resp.End(); }
Results
After putting everything together and running the code, you have a document that contains all the content that is contained within the library merged into a final report as shown in Figure 6.
Conclusion
This article describes how you can combine the power of SharePoint 2010 Web Parts and the Open XML SDK 2.0 to simplify the assembly of documents from different parts. By using content controls and altchunks, it is easy to insert whole sections from other documents. I encourage you to experiment more with the techniques described here to create your own custom solutions.
Additional Resources
For more information, see the following: Downloadable version: Open XML SDK 2.0 for Microsoft Office Walkthrough: Creating a Basic Web Part Creating Documents by Using the Open XML Format SDK 2.0 (Part 1 of 3) Creating Documents by Using the Open XML Format SDK 2.0 (Part 2 of 3) Creating Documents by Using the Open XML Format SDK 2.0 (Part 3 of 3)