dotnet / Open-XML-SDK

Open XML SDK by Microsoft
https://www.nuget.org/packages/DocumentFormat.OpenXml/
MIT License
3.99k stars 545 forks source link

Implement missing PartTypes (such as PersonPart in threading comment feature in Excel 2019) #945

Open mcpgza opened 3 years ago

mcpgza commented 3 years ago

Description

There are a lot of internal virtual property/method in OpenXmlPart which prevents custom part implementations in custom library.

Information

Latest version, any platform.

Repro I tried to implement the missing PersonPart and ThreadedCommentsPart for Office 2019 office, but I realized it is not possible to write a custom OpenXmlPart implementation because of the lot of internal virtual modifiers.

Observed

I tried to mimic the missing part implementations with ExtendedPart, which works well on exsiting part cases (the part created by Excel), but when I want to create this part in the document, the ExtendedPart forced the uri to /udata/data.xml, and I was not able to change it in any way. The excel does not accept this uri for Persons.

Expected

Avilability of wrtiting Part implementations in custom libraries.

davidhunter22 commented 3 years ago

I am also interested in this from a user perspective. The README says "Office 2019 Support Available" and specifically mentions "Threaded comments in Excel". However when I loaded a document containing threaded comments and persons they seem to get loaded as ExtendedParts. So is there more sutff still to do? Is there any documentation or examples on how to extract threaded comments using 2.13.0? Note I am a noob in using this library so maybe missing the point!

tomjebo commented 3 years ago

@mcpgza I have a fix for this and will submit a PR shortly.

tomjebo commented 3 years ago

PR #946 is available. If you'd like, try this ahead of the merge. Here is sample code that exercises the parts:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DocumentFormat.OpenXml;
using Drawing = DocumentFormat.OpenXml.Drawing;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Office2019.Excel.ThreadedComments;

namespace tcsample
{
    class Program
    {
        static void Main(string[] args)
        {
            string sheetName = "Sheet1";
            string displayName = "Tom Jebo";

            using (SpreadsheetDocument document =
                  SpreadsheetDocument.Open(args[0], false))
            {
                // Retrieve a reference to the workbook part.
                WorkbookPart wbPart = document.WorkbookPart;

                // Get the PersonPart
                WorkbookPersonPart wbpPart = wbPart.WorkbookPersonParts.First<WorkbookPersonPart>();

                // Find the first person in the PersonList with the desired display name
                Person p = wbpPart.PersonList.Descendants<Person>().First<Person>(p => { return p.DisplayName.Value.Contains(displayName); });

                // Find the sheet with the supplied name, and then use that 
                // Sheet object to retrieve a reference to the first worksheet.
                Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
                  Where(s => s.Name == sheetName).FirstOrDefault();

                // Throw an exception if there is no sheet.
                if (theSheet == null)
                {
                    throw new ArgumentException("sheetName");
                }

                // Retrieve a reference to the worksheet part.
                WorksheetPart wsPart =
                    (WorksheetPart)(wbPart.GetPartById(theSheet.Id));

                // Find the first ThreadedComments part
                WorksheetThreadedCommentsPart wtcPart = wsPart.WorksheetThreadedCommentsParts.FirstOrDefault();

                // Look for ThreadedComment's with the a specific Person id
                IEnumerable<ThreadedComment> tomsComments = wtcPart
                    .ThreadedComments
                    .Descendants<ThreadedComment>()
                    .Where<ThreadedComment>(c => { return c.PersonId.Value == p.Id.Value; });

                // Now output the comments for this person.
                Console.WriteLine("{0}'s Comments:", displayName);
                foreach (ThreadedComment comment in tomsComments)
                    Console.WriteLine(comment.ThreadedCommentText.Text);
            }
        }
    }
}