CAPITULO SEGUNDO AUTONOMIA DEL MUNICIPIO
3. Libertad del municipio
work of taking out the macros:
• Line 1 retrieves an SPFile object representing the payload for the current instance of the workflow.
• Lines 2 and 3 grab some values we’ll use later.
• Lines 4 through 7 check that the payload is a macro-enabled file in one of the new file formats. If it is, we continue processing. If it is not, we skip to the end and just return our status of Closed; our work here is done.
• The switch statement in lines 11 through 24 sets the file extension for the macro-free file type that corresponds with the macro-enabled file that is the payload.
• Line 27 sets a custom property so that we can get at this information later.
• Line 28 opens the payload as a stream and line 29 makes the call to the method that will remove the macros.
• Line 30 saves the new file back into SharePoint, complete with the updated file exten- sion to indicate that this is a macro-free file.
• Line 31 removes the original macro-enabled file from the document library. • Lines 32, 33, 37, 38, 43, and 44 set some of our custom properties.
Listing 5-4. The Meat of Our Execute Method—Accomplishing Some Work Before We Return a Status
1 SPFile file = PayloadItem.File;
2 string sFileExtension = Path.GetExtension(file.Name); 3 this.ParentList = PayloadItem.ParentList; 4 if ( 5 (sFileExtension.ToLower() == ".docm") 6 || (sFileExtension.ToLower() == ".xlsm") 7 || (sFileExtension.ToLower() == ".pptm") 8 ) 9 {
10 string sNewFileExtension = string.Empty; 11 switch (sFileExtension.ToLower()) 12 { 13 case ".docm": 14 sNewFileExtension = ".docx"; 15 break; 16 case ".xlsm": 17 sNewFileExtension = ".xlsx"; 18 break; 19 case ".pptm": 20 sNewFileExtension = ".pptx"; 21 break; 22 default: 23 break; 24 } 25 try 26 { 27 this.OriginalDocumentName = file.Name; 28 Stream strmFile = file.OpenBinaryStream(); 29 RemoveMacros(strmFile, sFileExtension);
30 PayloadItem.ParentList.RootFolder.Files.Add( ➥
PayloadItem.Url.Replace(sFileExtension, ➥
sNewFileExtension), strmFile, PayloadItem.Properties, ➥
true); 31 PayloadItem.Item.Delete(); 32 this.FinalDocumentName = ➥ Path.GetFileName(file.Name).Replace(➥ sFileExtension, sNewFileExtension); 33 this.IsMacroFree = true; 34 }
35 catch (Exception ex) 36 {
37 this.FinalDocumentName = Path.GetFileName(file.Name); 38 this.IsMacroFree = false;
39 } 40 }
41 else 42 { 43 this.FinalDocumentName = Path.GetFileName(file.Name); 44 this.IsMacroFree = true; 45 } 46 return ActivityExecutionStatus.Closed;
You’ll notice that in various places throughout Listing 5-4 we reference an object called
PayloadItem. This is a custom property we’ll add to our activity in just a few minutes. For now, just know that it stores the SPListItem on which the current instance of the workflow is running.
The last major piece of code we need to add to our activity is responsible for actually strip- ping the macros out of the file and converting the file to an Office 2007 macro-free document type. This is the RemoveMacros call made in line 29 of Listing 5-4. Listing 5-5 handles this chore.
■Note
The code for the RemoveMacros method was adapted from a Visual Studio 2005 Code Snippet for the Open XML file formats provided by Microsoft. I would love to claim credit for it, because it’s some pretty nifty stuff. However, anybody who knows me will tell you that I’m not that good a programmer… so thank you to the nameless Microsoft employee who put this little gem together. If you’d like to get your sweaty little hands on the code snippets (and I highly recommend that you do), I’ll post a link to it on my web site:www.kcdholdings.com.
Line numbers in Listing 5-5 are for reference only, but here’s a quick rundown of the inter- esting bits of the action:
• Lines 5 through 8 set up constants. Relationships in the Office 2007 file formats are iden- tified via a URI. These constants store those URI strings.
• The switch statement in lines 11 through 27 sets a URI indicating the macro-free content type URI for each particular type of file—Word, Excel, or PowerPoint.
• Line 28 checks whether we have a valid content type. If we don’t, we skip to the end. If we do, we continue on.
• Line 30 opens our package file from the FileStream object passed in as a parameter. • Lines 35 through 40 retrieve the root package part—we’ll use this as our launching point
to retrieve the other parts that interest us.
• Lines 42 through 47 locate and delete a part with a relationship type matching the
vbaRelationship URI we specified back in line 6, relative to the root part.
• Lines 48 through 57 do some XML manipulation to remove the node that contains the VBA relationship information.
• Lines 58 through 64 delete and re-create the document part. This is the only way to reset the package to be of a macro-free content type.
Listing 5-5. The RemoveMacros Method
1 private void RemoveMacros(Stream fs, string sFileExtension) 2 {
3 // Adapted from code in the Open XML File Formats Code Snippets 4 // provided by Microsoft
5 const string relationshipType = @"http://schemas.openxmlformats.org➥
/officeDocument/2006/relationships/officeDocument";
6 const string vbaRelationshipType = @"http://schemas.microsoft.com/➥
office/2006/relationships/vbaProject";
7 const string relationshipNamespace = @"http://schemas.openxmlformats. ➥
org/package/2006/relationships";
8 const string vbaFreeRelsContentType = @"application/vnd. ➥
openxmlformats-package.relationships+xml"; 9 string vbaFreeContentType = string.Empty; 10 Uri relsUri = null;
11 switch (sFileExtension.ToLower()) 12 {
13 case ".docm":
14 vbaFreeContentType = @"application/vnd.openxmlformats-➥
officedocument.wordprocessingml.document.main+xml"; 15 relsUri = new Uri("/word/_rels/document.xml.rels", ➥
UriKind.Relative); 16 break;
17 case ".xlsm":
18 vbaFreeContentType = @"application/vnd.openxmlformats-➥
officedocument.spreadsheetml.sheet.main+xml"; 19 relsUri = new Uri("/xl/_rels/workbook.xml.rels", ➥
UriKind.Relative); 20 break;
21 case ".pptm":
22 vbaFreeContentType = @"application/vnd.openxmlformats-➥
officedocument.presentationml.presentation.main+xml"; 23 relsUri = new Uri("/ppt/_rels/presentation.xml.rels", ➥
UriKind.Relative); 24 break;
25 default: 26 break; 27 }
28 if ((vbaFreeContentType != string.Empty) && (relsUri != null)) 29 {
30 using (Package onePackage = Package.Open(fs, FileMode.Open, ➥
FileAccess.ReadWrite)) 32 {
33 PackagePart startPart = null; 34 Uri startPartUri = null;
35 foreach (System.IO.Packaging.PackageRelationship relationship➥
in onePackage.GetRelationshipsByType(relationshipType)) 36 {
37 startPartUri = PackUriHelper.ResolvePartUri(new Uri("/",➥
UriKind.Relative), relationship.TargetUri); 38 startPart = onePackage.GetPart(startPartUri); 39 break;
40 }
41 PackagePart relsPart = onePackage.GetPart(relsUri);
42 foreach (System.IO.Packaging.PackageRelationship relationship➥
in startPart.GetRelationshipsByType(vbaRelationshipType)) 43 {
44 Uri vbaUri = PackUriHelper.ResolvePartUri(startPartUri, ➥
relationship.TargetUri); 45 onePackage.DeletePart(vbaUri); 46 break;
47 }
48 NameTable nt = new NameTable();
49 XmlNamespaceManager nsManager = new XmlNamespaceManager(nt); 50 nsManager.AddNamespace("r", relationshipNamespace);
51 XmlDocument xDocRels = new XmlDocument(nt); 52 xDocRels.Load(relsPart.GetStream());
53 XmlNode vbaNode = xDocRels.SelectSingleNode(➥
@"//r:Relationship[@Target='vbaProject.bin']", ➥ nsManager); 54 if (vbaNode != null) 55 { 56 vbaNode.ParentNode.RemoveChild(vbaNode); 57 }
58 XmlDocument xdoc = new XmlDocument(nt); 59 xdoc.Load(startPart.GetStream()); 60 onePackage.DeletePart(startPart.Uri); 61 relsPart = onePackage.CreatePart(relsUri, ➥ vbaFreeRelsContentType); 62 startPart = onePackage.CreatePart(startPartUri, ➥ vbaFreeContentType); 63 xDocRels.Save(relsPart.GetStream(FileMode.Create, ➥ FileAccess.Write)); 64 xdoc.Save(startPart.GetStream(FileMode.Create, ➥ FileAccess.Write)); 65 onePackage.Close(); 66 } 67 } 68 }
So, we’ve nearly completed our activity. The hard-core code is done; we have just a few more things to take care of. First we need to set up the PayloadItem property that we referenced in our code earlier. We’ll take care of that next.