From ebf3fe136fdab3eee55c89fed030ee6812e0cf3d Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Tue, 17 Oct 2023 14:49:14 -0700 Subject: [PATCH 1/2] Add samples projects to a solution This starts the process of refactoring the samples to not be inline. Instead, this change creates a solution that the samples are added to in order to validate they are correct (and enable refactoring much easier in the future). This adds a script in the samples directory that helps scaffold out a new sample project. --- .github/workflows/samples.yml | 21 + ...revisions-in-a-word-processing-document.md | 361 +---------- ...-a-comment-to-a-slide-in-a-presentation.md | 601 +----------------- samples/Directory.Build.props | 8 + samples/Directory.Build.targets | 5 + samples/README.md | 18 + samples/add-sample.ps1 | 21 + .../presentation/add_comment/cs/Program.cs | 140 ++++ .../add_comment/cs/add_comment_cs.csproj | 1 + .../presentation/add_comment/vb/Program.vb | 148 +++++ .../add_comment/vb/add_comment_vb.vbproj | 1 + samples/samples.sln | 53 ++ .../word/accept_all_revisions/cs/Program.cs | 71 +++ .../cs/accept_all_revisions_cs.csproj | 2 + .../word/accept_all_revisions/vb/Program.vb | 66 ++ .../vb/accept_all_revisions_vb.vbproj | 2 + 16 files changed, 596 insertions(+), 923 deletions(-) create mode 100644 .github/workflows/samples.yml create mode 100644 samples/Directory.Build.props create mode 100644 samples/Directory.Build.targets create mode 100644 samples/README.md create mode 100644 samples/add-sample.ps1 create mode 100644 samples/presentation/add_comment/cs/Program.cs create mode 100644 samples/presentation/add_comment/cs/add_comment_cs.csproj create mode 100644 samples/presentation/add_comment/vb/Program.vb create mode 100644 samples/presentation/add_comment/vb/add_comment_vb.vbproj create mode 100644 samples/samples.sln create mode 100644 samples/word/accept_all_revisions/cs/Program.cs create mode 100644 samples/word/accept_all_revisions/cs/accept_all_revisions_cs.csproj create mode 100644 samples/word/accept_all_revisions/vb/Program.vb create mode 100644 samples/word/accept_all_revisions/vb/accept_all_revisions_vb.vbproj diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml new file mode 100644 index 00000000..62a02fb7 --- /dev/null +++ b/.github/workflows/samples.yml @@ -0,0 +1,21 @@ +name: Samples compilation + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.x + - name: Build + run: dotnet build samples/samples.sln /p:TreatWarningsAsErrors=true diff --git a/docs/how-to-accept-all-revisions-in-a-word-processing-document.md b/docs/how-to-accept-all-revisions-in-a-word-processing-document.md index 666b0932..9c5e7e2f 100644 --- a/docs/how-to-accept-all-revisions-in-a-word-processing-document.md +++ b/docs/how-to-accept-all-revisions-in-a-word-processing-document.md @@ -18,44 +18,31 @@ ms.localizationpriority: high This topic shows how to use the Open XML SDK for Office to accept all revisions in a word processing document programmatically. -The following assembly directives are required to compile the code in this topic. - -```csharp - using DocumentFormat.OpenXml; - using DocumentFormat.OpenXml.Packaging; - using DocumentFormat.OpenXml.Wordprocessing; - using System.Linq; - using System.Collections.Generic; -``` - -```vb - Imports DocumentFormat.OpenXml - Imports DocumentFormat.OpenXml.Packaging - Imports DocumentFormat.OpenXml.Wordprocessing - Imports System.Linq - Imports System.Collections.Generic -``` - -## Open the Existing Document for Editing - -To open an existing document, you can instantiate the [WordprocessingDocument](https://msdn.microsoft.com/library/office/documentformat.openxml.packaging.wordprocessingdocument.aspx) class as shown in the following **using** statement. To do so, you open the word processing file with the specified *fileName* by using the [Open(String, Boolean)](https://msdn.microsoft.com/library/office/cc562234.aspx) method, with the Boolean parameter set to **true** in order to enable editing the document. - -```csharp - using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(fileName, true)) - { - // Insert other code here. - } -``` - -```vb - Using wdDoc As WordprocessingDocument = WordprocessingDocument.Open(fileName, True) - ' Insert other code here. - End Using -``` +[!include[Structure](./includes/word/structure.md)] -The **using** statement provides a recommended alternative to the typical .Open, .Save, .Close sequence. It ensures that the **Dispose** method (internal method used by the Open XML SDK to clean up resources) is automatically called when the closing brace is reached. The block that follows the **using** statement establishes a scope for the object that is created or named in the **using** statement, in this case *wdDoc*. Because the **WordprocessingDocument** class in the Open XML SDK automatically saves and closes the object as part of its **System.IDisposable** implementation, and because **Dispose** is automatically called when you exit the block, you do not have to explicitly call **Save** and **Close** as long as you use **using**. +The basic document structure of a **WordProcessingML** document consists of the **document** and **body** elements, followed by one or more block level elements such as **p**, which represents a paragraph. A paragraph contains one or more **r** elements. The **r** stands for run, which is a region of text with a common set of properties, such as formatting. A run contains one or more **t** elements. The **t** element contains a range of text. The following code example shows the **WordprocessingML** markup for a document that contains the text "Example text." -[!include[Structure](./includes/word/structure.md)] +```xml + + + + + Example text. + + + + +``` + +Using the Open XML SDK, you can create document structure and content using strongly-typed classes that correspond to **WordprocessingML** elements. You will find these classes in the [DocumentFormat.OpenXml.Wordprocessing](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.aspx) namespace. The following table lists the class names of the classes that correspond to the **document**, **body**, **p**, **r**, and **t** elements. + +| WordprocessingML Element | Open XML SDK Class | Description | +|---|---|---| +| document | [Document](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.document.aspx) | The root element for the main document part. | +| body | [Body](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.body.aspx) | The container for the block level structures such as paragraphs, tables, annotations and others specified in the [ISO/IEC 29500](https://www.iso.org/standard/71691.html) specification. | +| p | [Paragraph](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.paragraph.aspx) | A paragraph. | +| r | [Run](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.run.aspx) | A run. | +| t | [Text](https://msdn.microsoft.com/library/office/documentformat.openxml.wordprocessing.text.aspx) | A range of text. | ## ParagraphPropertiesChange Element @@ -175,310 +162,24 @@ a revision. © ISO/IEC29500: 2008. -## How the Sample Code Works - -After you have opened the document in the using statement, you -instantiate the **Body** class, and then handle -the formatting changes by creating the *changes* **List**, and removing each change (the **w:pPrChange** element) from the **List**, which is the same as accepting changes. - -```csharp - Body body = wdDoc.MainDocumentPart.Document.Body; - - // Handle the formatting changes. - List changes = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - foreach (OpenXmlElement change in changes) - { - change.Remove(); - } -``` - -```vb - Dim body As Body = wdDoc.MainDocumentPart.Document.Body - - ' Handle the formatting changes. - Dim changes As List(Of OpenXmlElement) = _ - body.Descendants(Of ParagraphPropertiesChange)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList() - - For Each change In changes - change.Remove() - Next -``` - -You then handle the deletions by constructing the *deletions* **List**, and removing each deletion element (**w:del**) from the **List**, which is similar to the process of -accepting deletion changes. - -```csharp - // Handle the deletions. - List deletions = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - deletions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - deletions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - foreach (OpenXmlElement deletion in deletions) - { - deletion.Remove(); - } -``` - -```vb - ' Handle the deletions. - Dim deletions As List(Of OpenXmlElement) = _ - body.Descendants(Of Deleted)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList() - - deletions.AddRange(body.Descendants(Of DeletedRun)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList()) - - deletions.AddRange(body.Descendants(Of DeletedMathControl)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList()) - - For Each deletion In deletions - deletion.Remove() - Next -``` - -Finally, you handle the insertions by constructing the *insertions* **List** and inserting the new text by removing the -insertion element (**w:ins**), which is the -same as accepting the inserted text. - -```csharp - // Handle the insertions. - List insertions = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - insertions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - insertions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - Run lastInsertedRun = null; - foreach (OpenXmlElement insertion in insertions) - { - // Found new content. - // Promote them to the same level as node, and then delete the node. - foreach (var run in insertion.Elements()) - { - if (run == insertion.FirstChild) - { - lastInsertedRun = insertion.InsertAfterSelf(new Run(run.OuterXml)); - } - else - { - lastInsertedRun = lastInsertedRun.Insertion.InsertAfterSelf(new Run(run.OuterXml)); - } - } - insertion.RemoveAttribute("rsidR", - "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); - insertion.RemoveAttribute("rsidRPr", - "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); - insertion.Remove(); - } -``` - -```vb - ' Handle the insertions. - Dim insertions As List(Of OpenXmlElement) = _ - body.Descendants(Of Inserted)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList() - - insertions.AddRange(body.Descendants(Of InsertedRun)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList()) - - insertions.AddRange(body.Descendants(Of InsertedMathControl)() _ - .Where(Function(c) c.Author.Value = authorName).Cast _ - (Of OpenXmlElement)().ToList()) - - Dim lastInsertedRun As Run = Nothing - For Each insertion In insertions - ' Found new content. Promote them to the same level as node, and then - ' delete the node. - For Each run In insertion.Elements(Of Run)() - If run Is insertion.FirstChild Then - lastInsertedRun = insertion.InsertAfterSelf(New Run(run.OuterXml)) - Else - lastInsertedRun = lastInsertedRun.Insertion.InsertAfterSelf(New Run(run.OuterXml)) - End If - Next - insertion.RemoveAttribute("rsidR", _ - "https://schemas.openxmlformats.org/wordprocessingml/2006/main") - insertion.RemoveAttribute("rsidRPr", _ - "https://schemas.openxmlformats.org/wordprocessingml/2006/main") - insertion.Remove() - Next -``` - ## Sample Code The following code example shows how to accept the entire revisions in a -word processing document. To run the program, you can call the method -**AcceptRevisions** to accept revisions in the -file "word1.docx" as in the following example. - -```csharp - string docName = @"C:\Users\Public\Documents\word1.docx"; - string authorName = "Katie Jordan"; - AcceptRevisions(docName, authorName); -``` +word processing document. To run the program, you can pass in the file path +and the author name: -```vb - Dim docName As String = "C:\Users\Public\Documents\word1.docx" - Dim authorName As String = "Katie Jordan" - AcceptRevisions(docName, authorName) +```dotnetcli +dotnet run -- [filePath] [authorName] ``` After you have run the program, open the word processing file to make sure that all revision marks have been accepted. -The following is the complete sample code in both C\# and Visual Basic. - -```csharp - public static void AcceptRevisions(string fileName, string authorName) - { - // Given a document name and an author name, accept revisions. - using (WordprocessingDocument wdDoc = - WordprocessingDocument.Open(fileName, true)) - { - Body body = wdDoc.MainDocumentPart.Document.Body; - - // Handle the formatting changes. - List changes = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - foreach (OpenXmlElement change in changes) - { - change.Remove(); - } - - // Handle the deletions. - List deletions = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - deletions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - deletions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - foreach (OpenXmlElement deletion in deletions) - { - deletion.Remove(); - } - - // Handle the insertions. - List insertions = - body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList(); - - insertions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - insertions.AddRange(body.Descendants() - .Where(c => c.Author.Value == authorName).Cast().ToList()); - - foreach (OpenXmlElement insertion in insertions) - { - // Found new content. - // Promote them to the same level as node, and then delete the node. - foreach (var run in insertion.Elements()) - { - if (run == insertion.FirstChild) - { - insertion.InsertAfterSelf(new Run(run.OuterXml)); - } - else - { - insertion.NextSibling().InsertAfterSelf(new Run(run.OuterXml)); - } - } - insertion.RemoveAttribute("rsidR", - "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); - insertion.RemoveAttribute("rsidRPr", - "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); - insertion.Remove(); - } - } - } -``` +### [CSharp](#tab/cs) +[!code-csharp[](../samples/word/accept_all_revisions/cs/Program.cs)] -```vb - Public Sub AcceptRevisions(ByVal fileName As String, ByVal authorName As String) - ' Given a document name and an author name, accept revisions. - Using wdDoc As WordprocessingDocument = WordprocessingDocument.Open(fileName, True) - Dim body As Body = wdDoc.MainDocumentPart.Document.Body - - ' Handle the formatting changes. - Dim changes As List(Of OpenXmlElement) = _ - body.Descendants(Of ParagraphPropertiesChange)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() - - For Each change In changes - change.Remove() - Next - - ' Handle the deletions. - Dim deletions As List(Of OpenXmlElement) = _ - body.Descendants(Of Deleted)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() - - deletions.AddRange(body.Descendants(Of DeletedRun)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) - - deletions.AddRange(body.Descendants(Of DeletedMathControl)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) - - For Each deletion In deletions - deletion.Remove() - Next - - ' Handle the insertions. - Dim insertions As List(Of OpenXmlElement) = _ - body.Descendants(Of Inserted)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() - - insertions.AddRange(body.Descendants(Of InsertedRun)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) - - insertions.AddRange(body.Descendants(Of InsertedMathControl)() _ - .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) - - For Each insertion In insertions - ' Found new content. Promote them to the same level as node, and then - ' delete the node. - For Each run In insertion.Elements(Of Run)() - If run Is insertion.FirstChild Then - insertion.InsertAfterSelf(New Run(run.OuterXml)) - Else - insertion.NextSibling().InsertAfterSelf(New Run(run.OuterXml)) - End If - Next - insertion.RemoveAttribute("rsidR", _ - "https://schemas.openxmlformats.org/wordprocessingml/2006/main") - insertion.RemoveAttribute("rsidRPr", _ - "https://schemas.openxmlformats.org/wordprocessingml/2006/main") - insertion.Remove() - Next - End Using - End Sub -``` +### [Visual Basic](#tab/vb) +[!code-vb[](../samples/word/accept_all_revisions/vb/Program.vb)] ## See also diff --git a/docs/how-to-add-a-comment-to-a-slide-in-a-presentation.md b/docs/how-to-add-a-comment-to-a-slide-in-a-presentation.md index 70471fe5..84b6f8ad 100644 --- a/docs/how-to-add-a-comment-to-a-slide-in-a-presentation.md +++ b/docs/how-to-add-a-comment-to-a-slide-in-a-presentation.md @@ -21,56 +21,6 @@ This topic shows how to use the classes in the Open XML SDK for Office to add a comment to the first slide in a presentation programmatically. -The following assembly directives are required to compile the code in -this topic. - -```csharp - using System; - using System.Linq; - using DocumentFormat.OpenXml.Presentation; - using DocumentFormat.OpenXml.Packaging; -``` - -```vb - Imports System - Imports System.Linq - Imports DocumentFormat.OpenXml.Presentation - Imports DocumentFormat.OpenXml.Packaging -``` - -## Getting a PresentationDocument Object - -In the Open XML SDK, the [PresentationDocument](https://msdn.microsoft.com/library/office/documentformat.openxml.packaging.presentationdocument.aspx) class represents a -presentation document package. To work with a presentation document, -first create an instance of the **PresentationDocument** class, and then work with -that instance. To create the class instance from the document call the -[Open(String, Boolean)](https://msdn.microsoft.com/library/office/cc562287.aspx) method that uses a -file path, and a Boolean value as the second parameter to specify -whether a document is editable. To open a document for read/write, -specify the value **true** for this parameter -as shown in the following **using** statement. -In this code, the *file* parameter is a string that represents the path -for the file from which you want to open the document. - -```csharp - using (PresentationDocument doc = PresentationDocument.Open(file, true)) - { - // Insert other code here. - } -``` - -```vb - Using doc As PresentationDocument = PresentationDocument.Open(file, True) - ' Insert other code here. - End Using -``` - -The **using** statement provides a recommended -alternative to the typical .Open, .Save, .Close sequence. It ensures -that the **Dispose** method (internal method -used by the Open XML SDK to clean up resources) is automatically called -when the closing brace is reached. The block that follows the **using** statement establishes a scope for the -object that is created or named in the **using** statement, in this case *doc*. [!include[Structure](./includes/presentation/structure.md)] @@ -125,558 +75,23 @@ optional attributes. ``` -## How the Sample Code Works - -The sample code opens the presentation document in the **using** statement. Then it instantiates the **CommentAuthorsPart**, and verifies that there is an -existing comment authors part. If there is not, it adds one. - -```csharp - // Declare a CommentAuthorsPart object. - CommentAuthorsPart authorsPart; - - // Verify that there is an existing comment authors part. - if (doc.PresentationPart.CommentAuthorsPart == null) - { - // If not, add a new one. - authorsPart = doc.PresentationPart.AddNewPart(); - } - else - { - authorsPart = doc.PresentationPart.CommentAuthorsPart; - } -``` - -```vb - ' Declare a CommentAuthorsPart object. - Dim authorsPart As CommentAuthorsPart - - ' Verify that there is an existing comment authors part. - If doc.PresentationPart.CommentAuthorsPart Is Nothing Then - ' If not, add a new one. - authorsPart = doc.PresentationPart.AddNewPart(Of CommentAuthorsPart)() - Else - authorsPart = doc.PresentationPart.CommentAuthorsPart - End If -``` - -The code determines whether there is an existing comment author list in -the comment-authors part; if not, it adds one. It also verifies that the -author that is passed in is on the list of existing comment authors; if -so, it assigns the existing author ID. If not, it adds a new author to -the list of comment authors and assigns an author ID and the parameter -values. - -```csharp - // Verify that there is a comment author list in the comment authors part. - if (authorsPart.CommentAuthorList == null) - { - // If not, add a new one. - authorsPart.CommentAuthorList = new CommentAuthorList(); - } - - // Declare a new author ID. - uint authorId = 0; - CommentAuthor author = null; - - // If there are existing child elements in the comment authors list... - if (authorsPart.CommentAuthorList.HasChildren) - { - // Verify that the author passed in is on the list. - var authors = authorsPart.CommentAuthorList.Elements().Where(a => a.Name == name && a.Initials == initials); - - // If so... - if (authors.Any()) - { - // Assign the new comment author the existing author ID. - author = authors.First(); - authorId = author.Id; - } - - // If not... - if (author == null) - { - // Assign the author passed in a new ID - authorId = authorsPart.CommentAuthorList.Elements().Select(a => a.Id.Value).Max(); - } - } - - // If there are no existing child elements in the comment authors list. - if (author == null) - { - authorId++; - - // Add a new child element(comment author) to the comment author list. - author = authorsPart.CommentAuthorList.AppendChild - (new CommentAuthor() - { - Id = authorId, - Name = name, - Initials = initials, - ColorIndex = 0 - }); - } -``` - -```vb - ' Verify that there is a comment author list in the comment authors part. - If authorsPart.CommentAuthorList Is Nothing Then - ' If not, add a new one. - authorsPart.CommentAuthorList = New CommentAuthorList() - End If - - ' Declare a new author ID. - Dim authorId As UInteger = 0 - Dim author As CommentAuthor = Nothing - - ' If there are existing child elements in the comment authors list... - If authorsPart.CommentAuthorList.HasChildren Then - ' Verify that the author passed in is on the list. - Dim authors = authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Where(Function(a) a.Name = name AndAlso a.Initials = initials) - - ' If so... - If authors.Any() Then - ' Assign the new comment author the existing author ID. - author = authors.First() - authorId = author.Id - End If - - ' If not... - If author Is Nothing Then - ' Assign the author passed in a new ID - authorId = authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Select(Function(a) a.Id.Value).Max() - End If - End If - - ' If there are no existing child elements in the comment authors list. - If author Is Nothing Then - authorId += 1 - - ' Add a new child element(comment author) to the comment author list. - author = authorsPart.CommentAuthorList.AppendChild(Of CommentAuthor) (New CommentAuthor() With {.Id = authorId, .Name = name, .Initials = initials, .ColorIndex = 0}) - End If -``` - -In the following code segment, the code gets the first slide in the -presentation by calling the *GetFirstSlide* method. Then it verifies -that there is a comments part in the slide; if not, it adds one. It also -verifies that a comments list exists in the comments part; if not, it -creates one. - -```csharp - // Get the first slide, using the GetFirstSlide method. - SlidePart slidePart1 = GetFirstSlide(doc); - - // Declare a comments part. - SlideCommentsPart commentsPart; - - // Verify that there is a comments part in the first slide part. - if (slidePart1.GetPartsOfType().Count() == 0) - { - // If not, add a new comments part. - commentsPart = slidePart1.AddNewPart(); - } - else - { - // Else, use the first comments part in the slide part. - commentsPart = slidePart1.SlideCommentsPart; - } - - // If the comment list does not exist. - if (commentsPart.CommentList == null) - { - // Add a new comments list. - commentsPart.CommentList = new CommentList(); - } -``` - -```vb - ' Get the first slide, using the GetFirstSlide method. - Dim slidePart1 As SlidePart = GetFirstSlide(doc) - - ' Declare a comments part. - Dim commentsPart As SlideCommentsPart - - ' Verify that there is a comments part in the first slide part. - If slidePart1.GetPartsOfType(Of SlideCommentsPart)().Count() = 0 Then - ' If not, add a new comments part. - commentsPart = slidePart1.AddNewPart(Of SlideCommentsPart)() - Else - ' Else, use the first comments part in the slide part. - commentsPart = slidePart1.SlideCommentsPart - End If - - ' If the comment list does not exist. - If commentsPart.CommentList Is Nothing Then - ' Add a new comments list. - commentsPart.CommentList = New CommentList() - End If -``` - -The code then gets the ID of the new comment, and adds the specified -comment, containing the specified text, at the specified position. Then -it saves the comment authors part and the comments part. - -```csharp - // Get the new comment ID. - uint commentIdx = author.LastIndex == null ? 1 : author.LastIndex + 1; - author.LastIndex = commentIdx; - - // Add a new comment. - Comment comment = commentsPart.CommentList.AppendChild( - new Comment() - { - AuthorId = authorId, - Index = commentIdx, - DateTime = DateTime.Now - }); - - // Add the position child node to the comment element. - comment.Append( - new Position() { X = 100, Y = 200 }, - new Text() { Text = text }); - - // Save the comment authors part. - authorsPart.CommentAuthorList.Save(); - - // Save the comments part. - commentsPart.CommentList.Save(); -``` - -```vb - ' Get the new comment ID. - Dim commentIdx As UInteger = If(author.LastIndex Is Nothing, 1, author.LastIndex + 1) - author.LastIndex = commentIdx - - ' Add a new comment. - Dim comment As Comment = commentsPart.CommentList.AppendChild(Of Comment)(New Comment() With {.AuthorId = authorId, .Index = commentIdx, .DateTime = Date.Now}) - - ' Add the position child node to the comment element. - comment.Append(New Position() With {.X = 100, .Y = 200}, New Text() With {.Text = text}) - - ' Save the comment authors part. - authorsPart.CommentAuthorList.Save() - - ' Save the comments part. - commentsPart.CommentList.Save() -``` - ## Sample Code -The **AddCommentToPresentation** method can be -used to add a comment to a slide. The method takes as parameters the -source presentation file name and path, the initials and name of the -comment author, and the text of the comment to be added. It adds an -author to the list of comment authors and then adds the specified -comment text at the specified coordinates in the first slide in the -presentation. +The following code example shows how to add comments to a +presentation document. To run the program, you can pass in the arguments: -The second method, **GetFirstSlide**, is used -to get the first slide in the presentation. It takes the **PresentationDocument** object passed in, gets its -presentation part, and then gets the ID of the first slide in its slide -list. It then gets the relationship ID of the slide, gets the slide part -from the relationship ID, and returns the slide part to the calling -method. - -The following code example shows a call to the **AddCommentToPresentation** which adds the specified -comment string to the first slide in the presentation file Myppt1.pptx. - -```csharp - AddCommentToPresentation(@"C:\Users\Public\Documents\Myppt1.pptx", - "Katie Jordan", "KJ", - "This is my programmatically added comment."); -``` - -```vb - AddCommentToPresentation("C:\Users\Public\Documents\Myppt1.pptx", _ - "Katie Jordan", "KJ", _ - "This is my programmatically added comment.") +```dotnetcli +dotnet run -- [filePath] [initials] [name] [test ...] ``` > [!NOTE] > To get the exact author name and initials, open the presentation file and click the **File** menu item, and then click **Options**. The **PowerPointOptions** window opens and the content of the **General** tab is displayed. The author name and initials must match the **User name** and **Initials** in this tab. +### [CSharp](#tab/cs) +[!code-csharp[](../samples/presentation/add_comment/cs/Program.cs)] -```csharp - // Adds a comment to the first slide of the presentation document. - // The presentation document must contain at least one slide. - public static void AddCommentToPresentation(string file, string initials, string name, string text) - { - using (PresentationDocument doc = PresentationDocument.Open(file, true)) - { - - // Declare a CommentAuthorsPart object. - CommentAuthorsPart authorsPart; - - // Verify that there is an existing comment authors part. - if (doc.PresentationPart.CommentAuthorsPart == null) - { - // If not, add a new one. - authorsPart = doc.PresentationPart.AddNewPart(); - } - else - { - authorsPart = doc.PresentationPart.CommentAuthorsPart; - } - - // Verify that there is a comment author list in the comment authors part. - if (authorsPart.CommentAuthorList == null) - { - // If not, add a new one. - authorsPart.CommentAuthorList = new CommentAuthorList(); - } - - // Declare a new author ID. - uint authorId = 0; - CommentAuthor author = null; - - // If there are existing child elements in the comment authors list... - if (authorsPart.CommentAuthorList.HasChildren) - { - // Verify that the author passed in is on the list. - var authors = authorsPart.CommentAuthorList.Elements().Where(a => a.Name == name && a.Initials == initials); - - // If so... - if (authors.Any()) - { - // Assign the new comment author the existing author ID. - author = authors.First(); - authorId = author.Id; - } - - // If not... - if (author == null) - { - // Assign the author passed in a new ID - authorId = authorsPart.CommentAuthorList.Elements().Select(a => a.Id.Value).Max(); - } - } - - // If there are no existing child elements in the comment authors list. - if (author == null) - { - - authorId++; - - // Add a new child element(comment author) to the comment author list. - author = authorsPart.CommentAuthorList.AppendChild - (new CommentAuthor() - { - Id = authorId, - Name = name, - Initials = initials, - ColorIndex = 0 - }); - } - - // Get the first slide, using the GetFirstSlide method. - SlidePart slidePart1 = GetFirstSlide(doc); - - // Declare a comments part. - SlideCommentsPart commentsPart; - - // Verify that there is a comments part in the first slide part. - if (slidePart1.GetPartsOfType().Count() == 0) - { - // If not, add a new comments part. - commentsPart = slidePart1.AddNewPart(); - } - else - { - // Else, use the first comments part in the slide part. - commentsPart = slidePart1.GetPartsOfType().First(); - } - - // If the comment list does not exist. - if (commentsPart.CommentList == null) - { - // Add a new comments list. - commentsPart.CommentList = new CommentList(); - } - - // Get the new comment ID. - uint commentIdx = author.LastIndex == null ? 1 : author.LastIndex + 1; - author.LastIndex = commentIdx; - - // Add a new comment. - Comment comment = commentsPart.CommentList.AppendChild( - new Comment() - { - AuthorId = authorId, - Index = commentIdx, - DateTime = DateTime.Now - }); - - // Add the position child node to the comment element. - comment.Append( - new Position() { X = 100, Y = 200 }, - new Text() { Text = text }); - - // Save the comment authors part. - authorsPart.CommentAuthorList.Save(); - - // Save the comments part. - commentsPart.CommentList.Save(); - } - } - // Get the slide part of the first slide in the presentation document. - public static SlidePart GetFirstSlide(PresentationDocument presentationDocument) - { - // Get relationship ID of the first slide - PresentationPart part = presentationDocument.PresentationPart; - SlideId slideId = part.Presentation.SlideIdList.GetFirstChild(); - string relId = slideId.RelationshipId; - - // Get the slide part by the relationship ID. - SlidePart slidePart = (SlidePart)part.GetPartById(relId); - - return slidePart; - } -``` - -```vb - ' Adds a comment to the first slide of the presentation document. - ' The presentation document must contain at least one slide. - Public Sub AddCommentToPresentation(ByVal file As String, _ - ByVal initials As String, _ - ByVal name As String, _ - ByVal text As String) - - Dim doc As PresentationDocument = _ - PresentationDocument.Open(file, True) - - Using (doc) - - ' Declare a CommentAuthorsPart object. - Dim authorsPart As CommentAuthorsPart - - ' Verify that there is an existing comment authors part. - If (doc.PresentationPart.CommentAuthorsPart Is Nothing) Then - - ' If not, add a new one. - authorsPart = doc.PresentationPart.AddNewPart(Of CommentAuthorsPart)() - Else - authorsPart = doc.PresentationPart.CommentAuthorsPart - End If - - ' Verify that there is a comment author list in the comment authors part. - If (authorsPart.CommentAuthorList Is Nothing) Then - - ' If not, add a new one. - authorsPart.CommentAuthorList = New CommentAuthorList() - End If - - ' Declare a new author ID. - Dim authorId As UInteger = 0 - Dim author As CommentAuthor = Nothing - - ' If there are existing child elements in the comment authors list. - If authorsPart.CommentAuthorList.HasChildren = True Then - - ' Verify that the author passed in is on the list. - Dim authors = authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Where _ - (Function(a) a.Name = name AndAlso a.Initials = initials) - - ' If so... - If (authors.Any()) Then - - ' Assign the new comment author the existing ID. - author = authors.First() - authorId = author.Id - End If - - ' If not... - If (author Is Nothing) Then - - ' Assign the author passed in a new ID. - authorId = _ - authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Select(Function(a) a.Id.Value).Max() - End If - - End If - - ' If there are no existing child elements in the comment authors list. - If (author Is Nothing) Then - - authorId = authorId + 1 - - ' Add a new child element (comment author) to the comment author list. - author = (authorsPart.CommentAuthorList.AppendChild(Of CommentAuthor) _ - (New CommentAuthor() With {.Id = authorId, _ - .Name = name, _ - .Initials = initials, _ - .ColorIndex = 0})) - End If - - ' Get the first slide, using the GetFirstSlide() method. - Dim slidePart1 As SlidePart - slidePart1 = GetFirstSlide(doc) - - ' Declare a comments part. - Dim commentsPart As SlideCommentsPart - - ' Verify that there is a comments part in the first slide part. - If slidePart1.GetPartsOfType(Of SlideCommentsPart)().Count() = 0 Then - - ' If not, add a new comments part. - commentsPart = slidePart1.AddNewPart(Of SlideCommentsPart)() - Else - - ' Else, use the first comments part in the slide part. - commentsPart = _ - slidePart1.GetPartsOfType(Of SlideCommentsPart)().First() - End If - - ' If the comment list does not exist. - If (commentsPart.CommentList Is Nothing) Then - - ' Add a new comments list. - commentsPart.CommentList = New CommentList() - End If - - ' Get the new comment ID. - Dim commentIdx As UInteger - If author.LastIndex Is Nothing Then - commentIdx = 1 - Else - commentIdx = CType(author.LastIndex, UInteger) + 1 - End If - - author.LastIndex = commentIdx - - ' Add a new comment. - Dim comment As Comment = _ - (commentsPart.CommentList.AppendChild(Of Comment)(New Comment() _ - With {.AuthorId = authorId, .Index = commentIdx, .DateTime = DateTime.Now})) - - ' Add the position child node to the comment element. - comment.Append(New Position() With _ - {.X = 100, .Y = 200}, New Text() With {.Text = text}) - - - ' Save comment authors part. - authorsPart.CommentAuthorList.Save() - - ' Save comments part. - commentsPart.CommentList.Save() - - End Using - - End Sub - - ' Get the slide part of the first slide in the presentation document. - Public Function GetFirstSlide(ByVal presentationDocument As PresentationDocument) As SlidePart - ' Get relationship ID of the first slide - Dim part As PresentationPart = presentationDocument.PresentationPart - Dim slideId As SlideId = part.Presentation.SlideIdList.GetFirstChild(Of SlideId)() - Dim relId As String = slideId.RelationshipId - - ' Get the slide part by the relationship ID. - Dim slidePart As SlidePart = DirectCast(part.GetPartById(relId), SlidePart) - - Return slidePart - End Function - End Module -``` +### [Visual Basic](#tab/vb) +[!code-vb[](../samples/presentation/add_comment/vb/Program.vb)] ## See also diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props new file mode 100644 index 00000000..6aee36e9 --- /dev/null +++ b/samples/Directory.Build.props @@ -0,0 +1,8 @@ + + + net8.0 + disable + disable + Exe + + \ No newline at end of file diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 00000000..ea8a1477 --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 00000000..9f15cb49 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,18 @@ +# Adding samples + +To add a sample, run the the following: + +```powershell +./add-sample.ps1 [word|presentation|spreadsheet] sample-name +``` + +This will scaffold out the projects in a common layout. Samples should be a single file, with the entrypoint being the command line so that `dotnet run -- [args]` can be used in the docs. + +In the future, we expect to set up .editorconfig/stylecop to enforce a shared style across the samples, but for now, the goal is to move the inline samples to this compilable solution. + +General changes to move a sample: + +- Many examples give details on how to open a project; this can be removed +- Sections about what `Dispose/Close/etc` is can be removed - this is an artifact from before `using` was common +- Samples currently have a "How the Sample Works" section followed by the actual sample. Going forward, this will be collapsed to just the sample - any comments required will be in the cs/vb +- Many users use the VB examples, so we will maintain them. Using docfx tabs allows us to hide the languages not needed by a viewer \ No newline at end of file diff --git a/samples/add-sample.ps1 b/samples/add-sample.ps1 new file mode 100644 index 00000000..f2aa05e6 --- /dev/null +++ b/samples/add-sample.ps1 @@ -0,0 +1,21 @@ +param($area, $name) + +# Basic normalization +$name = $name.Replace("-", "_") + +$dir = "$PSScriptRoot\$area\$name\cs" +$proj = "$dir\${name}_cs.csproj" +mkdir $dir -ErrorAction Ignore +echo "" > $proj +echo "" > "$dir\Program.cs" +dotnet sln add $proj --solution-folder $area + +$dir = "$PSScriptRoot\$area\$name\vb" +$proj = "$dir\${name}_vb.vbproj" +mkdir $dir -ErrorAction Ignore +echo "" > $proj +echo "Module Program ` + Sub Main(args As String())` + End Sub` +End Module" > "$dir\Program.vb" +dotnet sln add $proj --solution-folder $area diff --git a/samples/presentation/add_comment/cs/Program.cs b/samples/presentation/add_comment/cs/Program.cs new file mode 100644 index 00000000..cbe7f9c2 --- /dev/null +++ b/samples/presentation/add_comment/cs/Program.cs @@ -0,0 +1,140 @@ +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Presentation; +using System; +using System.Linq; + +AddCommentToPresentation(args[0], args[1], args[2], string.Join(' ', args[3..])); + +static void AddCommentToPresentation(string file, string initials, string name, string text) +{ + using (PresentationDocument doc = PresentationDocument.Open(file, true)) + { + + // Declare a CommentAuthorsPart object. + CommentAuthorsPart authorsPart; + + // Verify that there is an existing comment authors part. + if (doc.PresentationPart.CommentAuthorsPart == null) + { + // If not, add a new one. + authorsPart = doc.PresentationPart.AddNewPart(); + } + else + { + authorsPart = doc.PresentationPart.CommentAuthorsPart; + } + + // Verify that there is a comment author list in the comment authors part. + if (authorsPart.CommentAuthorList == null) + { + // If not, add a new one. + authorsPart.CommentAuthorList = new CommentAuthorList(); + } + + // Declare a new author ID. + uint authorId = 0; + CommentAuthor author = null; + + // If there are existing child elements in the comment authors list... + if (authorsPart.CommentAuthorList.HasChildren) + { + // Verify that the author passed in is on the list. + var authors = authorsPart.CommentAuthorList.Elements().Where(a => a.Name == name && a.Initials == initials); + + // If so... + if (authors.Any()) + { + // Assign the new comment author the existing author ID. + author = authors.First(); + authorId = author.Id; + } + + // If not... + if (author == null) + { + // Assign the author passed in a new ID + authorId = authorsPart.CommentAuthorList.Elements().Select(a => a.Id.Value).Max(); + } + } + + // If there are no existing child elements in the comment authors list. + if (author == null) + { + + authorId++; + + // Add a new child element(comment author) to the comment author list. + author = authorsPart.CommentAuthorList.AppendChild + (new CommentAuthor() + { + Id = authorId, + Name = name, + Initials = initials, + ColorIndex = 0 + }); + } + + // Get the first slide, using the GetFirstSlide method. + SlidePart slidePart1 = GetFirstSlide(doc); + + // Declare a comments part. + SlideCommentsPart commentsPart; + + // Verify that there is a comments part in the first slide part. + if (slidePart1.GetPartsOfType().Count() == 0) + { + // If not, add a new comments part. + commentsPart = slidePart1.AddNewPart(); + } + else + { + // Else, use the first comments part in the slide part. + commentsPart = slidePart1.GetPartsOfType().First(); + } + + // If the comment list does not exist. + if (commentsPart.CommentList == null) + { + // Add a new comments list. + commentsPart.CommentList = new CommentList(); + } + + // Get the new comment ID. + uint commentIdx = author.LastIndex == null ? 1 : author.LastIndex + 1; + author.LastIndex = commentIdx; + + // Add a new comment. + Comment comment = commentsPart.CommentList.AppendChild( + new Comment() + { + AuthorId = authorId, + Index = commentIdx, + DateTime = DateTime.Now + }); + + // Add the position child node to the comment element. + comment.Append( + new Position() { X = 100, Y = 200 }, + new Text() { Text = text }); + + // Save the comment authors part. + authorsPart.CommentAuthorList.Save(); + + // Save the comments part. + commentsPart.CommentList.Save(); + } +} + +// Get the slide part of the first slide in the presentation document. +static SlidePart GetFirstSlide(PresentationDocument presentationDocument) +{ + // Get relationship ID of the first slide + PresentationPart part = presentationDocument.PresentationPart; + SlideId slideId = part.Presentation.SlideIdList.GetFirstChild(); + string relId = slideId.RelationshipId; + + // Get the slide part by the relationship ID. + SlidePart slidePart = (SlidePart)part.GetPartById(relId); + + return slidePart; +} diff --git a/samples/presentation/add_comment/cs/add_comment_cs.csproj b/samples/presentation/add_comment/cs/add_comment_cs.csproj new file mode 100644 index 00000000..d6ca42c7 --- /dev/null +++ b/samples/presentation/add_comment/cs/add_comment_cs.csproj @@ -0,0 +1 @@ + diff --git a/samples/presentation/add_comment/vb/Program.vb b/samples/presentation/add_comment/vb/Program.vb new file mode 100644 index 00000000..39f99e74 --- /dev/null +++ b/samples/presentation/add_comment/vb/Program.vb @@ -0,0 +1,148 @@ +Imports DocumentFormat.OpenXml.Packaging +Imports DocumentFormat.OpenXml.Presentation + +Module Program + Sub Main(args As String()) + AddCommentToPresentation(args(0), args(1), args(2), String.Join(" ", args.Skip(3))) + End Sub + + Public Sub AddCommentToPresentation(ByVal file As String, + ByVal initials As String, + ByVal name As String, + ByVal text As String) + + Dim doc As PresentationDocument = + PresentationDocument.Open(file, True) + + Using (doc) + + ' Declare a CommentAuthorsPart object. + Dim authorsPart As CommentAuthorsPart + + ' Verify that there is an existing comment authors part. + If (doc.PresentationPart.CommentAuthorsPart Is Nothing) Then + + ' If not, add a new one. + authorsPart = doc.PresentationPart.AddNewPart(Of CommentAuthorsPart)() + Else + authorsPart = doc.PresentationPart.CommentAuthorsPart + End If + + ' Verify that there is a comment author list in the comment authors part. + If (authorsPart.CommentAuthorList Is Nothing) Then + + ' If not, add a new one. + authorsPart.CommentAuthorList = New CommentAuthorList() + End If + + ' Declare a new author ID. + Dim authorId As UInteger = 0 + Dim author As CommentAuthor = Nothing + + ' If there are existing child elements in the comment authors list. + If authorsPart.CommentAuthorList.HasChildren = True Then + + ' Verify that the author passed in is on the list. + Dim authors = authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Where _ + (Function(a) a.Name = name AndAlso a.Initials = initials) + + ' If so... + If (authors.Any()) Then + + ' Assign the new comment author the existing ID. + author = authors.First() + authorId = author.Id + End If + + ' If not... + If (author Is Nothing) Then + + ' Assign the author passed in a new ID. + authorId = + authorsPart.CommentAuthorList.Elements(Of CommentAuthor)().Select(Function(a) a.Id.Value).Max() + End If + + End If + + ' If there are no existing child elements in the comment authors list. + If (author Is Nothing) Then + + authorId = authorId + 1 + + ' Add a new child element (comment author) to the comment author list. + author = (authorsPart.CommentAuthorList.AppendChild(Of CommentAuthor) _ + (New CommentAuthor() With {.Id = authorId, + .Name = name, + .Initials = initials, + .ColorIndex = 0})) + End If + + ' Get the first slide, using the GetFirstSlide() method. + Dim slidePart1 As SlidePart + slidePart1 = GetFirstSlide(doc) + + ' Declare a comments part. + Dim commentsPart As SlideCommentsPart + + ' Verify that there is a comments part in the first slide part. + If slidePart1.GetPartsOfType(Of SlideCommentsPart)().Count() = 0 Then + + ' If not, add a new comments part. + commentsPart = slidePart1.AddNewPart(Of SlideCommentsPart)() + Else + + ' Else, use the first comments part in the slide part. + commentsPart = + slidePart1.GetPartsOfType(Of SlideCommentsPart)().First() + End If + + ' If the comment list does not exist. + If (commentsPart.CommentList Is Nothing) Then + + ' Add a new comments list. + commentsPart.CommentList = New CommentList() + End If + + ' Get the new comment ID. + Dim commentIdx As UInteger + If author.LastIndex Is Nothing Then + commentIdx = 1 + Else + commentIdx = CType(author.LastIndex, UInteger) + 1 + End If + + author.LastIndex = commentIdx + + ' Add a new comment. + Dim comment As Comment = +(commentsPart.CommentList.AppendChild(Of Comment)(New Comment() _ + With {.AuthorId = authorId, .Index = commentIdx, .DateTime = DateTime.Now})) + + ' Add the position child node to the comment element. + comment.Append(New Position() With + {.X = 100, .Y = 200}, New Text() With {.Text = text}) + + + ' Save comment authors part. + authorsPart.CommentAuthorList.Save() + + ' Save comments part. + commentsPart.CommentList.Save() + + End Using + + End Sub + + ' Get the slide part of the first slide in the presentation document. + Public Function GetFirstSlide(ByVal presentationDocument As PresentationDocument) As SlidePart + ' Get relationship ID of the first slide + Dim part As PresentationPart = presentationDocument.PresentationPart + Dim slideId As SlideId = part.Presentation.SlideIdList.GetFirstChild(Of SlideId)() + Dim relId As String = slideId.RelationshipId + + ' Get the slide part by the relationship ID. + Dim slidePart As SlidePart = DirectCast(part.GetPartById(relId), SlidePart) + + Return slidePart + End Function +End Module diff --git a/samples/presentation/add_comment/vb/add_comment_vb.vbproj b/samples/presentation/add_comment/vb/add_comment_vb.vbproj new file mode 100644 index 00000000..d6ca42c7 --- /dev/null +++ b/samples/presentation/add_comment/vb/add_comment_vb.vbproj @@ -0,0 +1 @@ + diff --git a/samples/samples.sln b/samples/samples.sln new file mode 100644 index 00000000..b159a822 --- /dev/null +++ b/samples/samples.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "word", "word", "{D207D3D7-FD4D-4FD4-A7D0-79A82086FB6F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "accept_all_revisions_cs", "word\accept_all_revisions\cs\accept_all_revisions_cs.csproj", "{93E0FE7B-9437-449F-852D-25C5F183BBCF}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "accept_all_revisions_vb", "word\accept_all_revisions\vb\accept_all_revisions_vb.vbproj", "{E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presentation", "presentation", "{CDB9D4A6-7A7A-4CDF-A7A3-4F17F5F1602D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "add_comment_cs", "presentation\add_comment\cs\add_comment_cs.csproj", "{B87B46CC-6642-4754-AA32-2AD057D877DC}" +EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "add_comment_vb", "presentation\add_comment\vb\add_comment_vb.vbproj", "{F5204024-B0D0-4106-AE8B-8CB1552C1F87}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93E0FE7B-9437-449F-852D-25C5F183BBCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93E0FE7B-9437-449F-852D-25C5F183BBCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93E0FE7B-9437-449F-852D-25C5F183BBCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93E0FE7B-9437-449F-852D-25C5F183BBCF}.Release|Any CPU.Build.0 = Release|Any CPU + {E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0}.Release|Any CPU.Build.0 = Release|Any CPU + {B87B46CC-6642-4754-AA32-2AD057D877DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B87B46CC-6642-4754-AA32-2AD057D877DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B87B46CC-6642-4754-AA32-2AD057D877DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B87B46CC-6642-4754-AA32-2AD057D877DC}.Release|Any CPU.Build.0 = Release|Any CPU + {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {93E0FE7B-9437-449F-852D-25C5F183BBCF} = {D207D3D7-FD4D-4FD4-A7D0-79A82086FB6F} + {E079BD07-B6BB-441C-8FB6-3FFC8B30E6B0} = {D207D3D7-FD4D-4FD4-A7D0-79A82086FB6F} + {B87B46CC-6642-4754-AA32-2AD057D877DC} = {CDB9D4A6-7A7A-4CDF-A7A3-4F17F5F1602D} + {F5204024-B0D0-4106-AE8B-8CB1552C1F87} = {CDB9D4A6-7A7A-4CDF-A7A3-4F17F5F1602D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {721B3030-08D7-4412-9087-D1CFBB3F5046} + EndGlobalSection +EndGlobal diff --git a/samples/word/accept_all_revisions/cs/Program.cs b/samples/word/accept_all_revisions/cs/Program.cs new file mode 100644 index 00000000..79f58e33 --- /dev/null +++ b/samples/word/accept_all_revisions/cs/Program.cs @@ -0,0 +1,71 @@ +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using System.Collections.Generic; +using System.Linq; + +string fileName = args[0]; +string authorName = args[1]; + +using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(fileName, true)) +{ + Body body = wdDoc.MainDocumentPart.Document.Body; + + // Handle the formatting changes. + List changes = body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList(); + + foreach (OpenXmlElement change in changes) + { + change.Remove(); + } + + // Handle the deletions. + List deletions = body + .Descendants() + .Where(c => c.Author.Value == authorName) + .Cast().ToList(); + + deletions.AddRange(body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList()); + + deletions.AddRange(body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList()); + + foreach (OpenXmlElement deletion in deletions) + { + deletion.Remove(); + } + + // Handle the insertions. + List insertions = + body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList(); + + insertions.AddRange(body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList()); + + insertions.AddRange(body.Descendants() + .Where(c => c.Author.Value == authorName).Cast().ToList()); + + foreach (OpenXmlElement insertion in insertions) + { + // Found new content. + // Promote them to the same level as node, and then delete the node. + foreach (var run in insertion.Elements()) + { + if (run == insertion.FirstChild) + { + insertion.InsertAfterSelf(new Run(run.OuterXml)); + } + else + { + insertion.NextSibling().InsertAfterSelf(new Run(run.OuterXml)); + } + } + + insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); + insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main"); + insertion.Remove(); + } +} \ No newline at end of file diff --git a/samples/word/accept_all_revisions/cs/accept_all_revisions_cs.csproj b/samples/word/accept_all_revisions/cs/accept_all_revisions_cs.csproj new file mode 100644 index 00000000..bef03830 --- /dev/null +++ b/samples/word/accept_all_revisions/cs/accept_all_revisions_cs.csproj @@ -0,0 +1,2 @@ + + diff --git a/samples/word/accept_all_revisions/vb/Program.vb b/samples/word/accept_all_revisions/vb/Program.vb new file mode 100644 index 00000000..9056b765 --- /dev/null +++ b/samples/word/accept_all_revisions/vb/Program.vb @@ -0,0 +1,66 @@ +Imports DocumentFormat.OpenXml +Imports DocumentFormat.OpenXml.Packaging +Imports DocumentFormat.OpenXml.Wordprocessing + +Module Program + Sub Main(args As String()) + Dim fileName = args(0) + Dim authorName = args(1) + + 'Public Sub AcceptRevisions(ByVal fileName As String, ByVal authorName As String) + ' Given a document name and an author name, accept revisions. + Using wdDoc As WordprocessingDocument = WordprocessingDocument.Open(fileName, True) + Dim body As Body = wdDoc.MainDocumentPart.Document.Body + + ' Handle the formatting changes. + Dim changes As List(Of OpenXmlElement) = + body.Descendants(Of ParagraphPropertiesChange)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() + + For Each change In changes + change.Remove() + Next + + ' Handle the deletions. + Dim deletions As List(Of OpenXmlElement) = + body.Descendants(Of Deleted)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() + + deletions.AddRange(body.Descendants(Of DeletedRun)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) + + deletions.AddRange(body.Descendants(Of DeletedMathControl)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) + + For Each deletion In deletions + deletion.Remove() + Next + + ' Handle the insertions. + Dim insertions As List(Of OpenXmlElement) = + body.Descendants(Of Inserted)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList() + + insertions.AddRange(body.Descendants(Of InsertedRun)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) + + insertions.AddRange(body.Descendants(Of InsertedMathControl)() _ + .Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()) + + For Each insertion In insertions + ' Found new content. Promote them to the same level as node, and then + ' delete the node. + For Each run In insertion.Elements(Of Run)() + If run Is insertion.FirstChild Then + insertion.InsertAfterSelf(New Run(run.OuterXml)) + Else + insertion.NextSibling().InsertAfterSelf(New Run(run.OuterXml)) + End If + Next + insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main") + insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main") + insertion.Remove() + Next + End Using + End Sub +End Module diff --git a/samples/word/accept_all_revisions/vb/accept_all_revisions_vb.vbproj b/samples/word/accept_all_revisions/vb/accept_all_revisions_vb.vbproj new file mode 100644 index 00000000..bef03830 --- /dev/null +++ b/samples/word/accept_all_revisions/vb/accept_all_revisions_vb.vbproj @@ -0,0 +1,2 @@ + + From d781d7dc76a9a2f0acadee6e61ca89f1505937f5 Mon Sep 17 00:00:00 2001 From: Taylor Southwick Date: Tue, 17 Oct 2023 20:13:52 -0700 Subject: [PATCH 2/2] add better migrator tool --- samples/README.md | 20 ++++-- samples/migrate-sample.ps1 | 3 + samples/samples.sln | 6 ++ samples/tools/migrator/Program.cs | 86 ++++++++++++++++++++++++++ samples/tools/migrator/migrator.csproj | 10 +++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 samples/migrate-sample.ps1 create mode 100644 samples/tools/migrator/Program.cs create mode 100644 samples/tools/migrator/migrator.csproj diff --git a/samples/README.md b/samples/README.md index 9f15cb49..814cd586 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,18 +1,30 @@ # Adding samples +## New Samples + To add a sample, run the the following: ```powershell -./add-sample.ps1 [word|presentation|spreadsheet] sample-name +./add-sample.ps1 area name ``` -This will scaffold out the projects in a common layout. Samples should be a single file, with the entrypoint being the command line so that `dotnet run -- [args]` can be used in the docs. +This will create an initial scaffold for a sample and add it to the solution file. -In the future, we expect to set up .editorconfig/stylecop to enforce a shared style across the samples, but for now, the goal is to move the inline samples to this compilable solution. +## Migrate old samples + +```powershell +./migrate-sample.ps1 path-to-md-file +``` + +This will do an inital extraction and clean up of the file, as well as add the code to the solution. Additional clean up will be necessary, but should be minimal. General changes to move a sample: - Many examples give details on how to open a project; this can be removed - Sections about what `Dispose/Close/etc` is can be removed - this is an artifact from before `using` was common - Samples currently have a "How the Sample Works" section followed by the actual sample. Going forward, this will be collapsed to just the sample - any comments required will be in the cs/vb -- Many users use the VB examples, so we will maintain them. Using docfx tabs allows us to hide the languages not needed by a viewer \ No newline at end of file +- Many users use the VB examples, so we will maintain them. Using docfx tabs allows us to hide the languages not needed by a viewer + +## Code set up + +In the future, we expect to set up .editorconfig/stylecop to enforce a shared style across the samples, but for now, the goal is to move the inline samples to this compilable solution. diff --git a/samples/migrate-sample.ps1 b/samples/migrate-sample.ps1 new file mode 100644 index 00000000..b07ea5f7 --- /dev/null +++ b/samples/migrate-sample.ps1 @@ -0,0 +1,3 @@ +param($path) + +dotnet run "$(PSScriptRoot)\tools\migrator.csproj" -- $path \ No newline at end of file diff --git a/samples/samples.sln b/samples/samples.sln index b159a822..e7daa0f9 100644 --- a/samples/samples.sln +++ b/samples/samples.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "add_comment_cs", "presentat EndProject Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "add_comment_vb", "presentation\add_comment\vb\add_comment_vb.vbproj", "{F5204024-B0D0-4106-AE8B-8CB1552C1F87}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "migrator", "tools\migrator\migrator.csproj", "{0470C4B3-18CE-4621-A41C-A1C70DDF8EAD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,6 +39,10 @@ Global {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5204024-B0D0-4106-AE8B-8CB1552C1F87}.Release|Any CPU.Build.0 = Release|Any CPU + {0470C4B3-18CE-4621-A41C-A1C70DDF8EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0470C4B3-18CE-4621-A41C-A1C70DDF8EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0470C4B3-18CE-4621-A41C-A1C70DDF8EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0470C4B3-18CE-4621-A41C-A1C70DDF8EAD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/tools/migrator/Program.cs b/samples/tools/migrator/Program.cs new file mode 100644 index 00000000..bd10fa5b --- /dev/null +++ b/samples/tools/migrator/Program.cs @@ -0,0 +1,86 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; + +if (args is not [string path]) +{ + Console.WriteLine("Must supply a path"); + return; +} + +var samplesDir = Path.GetFullPath(Path.Combine(path, "..", "..", "samples"))!; + +if (!Directory.Exists(samplesDir)) +{ + Console.WriteLine("Not a valid document"); + return; +} + +var text = File.ReadAllText(path); + +text = Matchers.GetAssemblyDirective().Replace(text, string.Empty); +text = Matchers.HowWorks().Replace(text, "##"); + +var csMatch = Matchers.Csharp().Match(text); +var cs = csMatch.Groups[2].Value; +var vbMatch = Matchers.Vb().Match(text); +var vb = vbMatch.Groups[2].Value; + +var area = Matchers.Area().Match(text).Groups[1].Value; +Console.WriteLine($"Enter name for {Path.GetFileName(path)}"); +string name = Console.ReadLine() ?? throw new InvalidOperationException(); + +name = name.Replace("-", "_"); + +text = text.Replace(csMatch.Groups[1].Value, $""" + ### [CSharp](#tab/cs) + [!code-csharp[](../samples/{area}/{name}/cs/Program.cs)] + """); + +text = text.Replace(vbMatch.Groups[1].Value, $""" + ### [CSharp](#tab/cs) + [!code-vb[](../samples/{area}/{name}/vb/Program.vb)] + """); + +var thisSampleDir = Path.Combine(samplesDir, area, name); + +var csDir = Path.Combine(thisSampleDir, "cs"); +Directory.CreateDirectory(csDir); +var csProj = Path.Combine(csDir, $"{name}_cs.csproj"); +File.WriteAllText(csProj, """"""); +File.WriteAllText(Path.Combine(csDir, "Program.cs"), cs); + +var vbDir = Path.Combine(thisSampleDir, "vb"); +Directory.CreateDirectory(vbDir); +var vbProj = Path.Combine(vbDir, $"{name}_vb.vbproj"); +File.WriteAllText(vbProj, """"""); +File.WriteAllText(Path.Combine(vbDir, "Program.vb"), $""" + Module Program ` + Sub Main(args As String())` + End Sub` + + {vb} + End Module + """); + +File.WriteAllText(path, text); + +Process.Start(new ProcessStartInfo("dotnet", $"sln add {csProj} --solution-folder {area}") { WorkingDirectory = samplesDir })!.WaitForExit(); +Process.Start(new ProcessStartInfo("dotnet", $"sln add {vbProj} --solution-folder {area}") { WorkingDirectory = samplesDir })!.WaitForExit(); + +partial class Matchers +{ + [GeneratedRegex("""The following assembly directives.*?```vb.*?```""", RegexOptions.Singleline)] + public static partial Regex GetAssemblyDirective(); + + [GeneratedRegex("## How the Sample Code Works .*?##", RegexOptions.Singleline)] + public static partial Regex HowWorks(); + + [GeneratedRegex(".*(```csharp(.*?)```)", RegexOptions.Singleline)] + public static partial Regex Csharp(); + + [GeneratedRegex(".*(```vb(.*?)```)", RegexOptions.Singleline)] + public static partial Regex Vb(); + + [GeneratedRegex("./includes/(.*?)/structure\\.md")] + public static partial Regex Area(); +} \ No newline at end of file diff --git a/samples/tools/migrator/migrator.csproj b/samples/tools/migrator/migrator.csproj new file mode 100644 index 00000000..2150e379 --- /dev/null +++ b/samples/tools/migrator/migrator.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + +