How to Obtain a Link to a Comment Programmatically in Microsoft Word

Mohamad Al Sayed 0 Reputation points
2023-11-15T08:58:23.04+00:00

Hello,

I am seeking assistance in obtaining the link to a comment in Word programmatically. I am using OpenXml and C#, and I have successfully retrieved the comments along with their statuses and replies.

I've observed that when creating the comment link manually, the link includes a "nav" parameter, for example: &nav=eyjjijoymdu4mtqxmjgwfq.

Additionally, I've noticed that "eyJjIjo" is a common prefix for all values passed to "nav."

I'm uncertain about the significance of the value passed to "nav" and how to generate it. Any ideas or insights would be greatly appreciated.

Regards

Microsoft 365 and Office | Development | Other
Microsoft 365 and Office | Word | For business | Windows
Developer technologies | C#
{count} votes

1 answer

Sort by: Most helpful
  1. gekka 12,206 Reputation points MVP Volunteer Moderator
    2023-11-17T09:32:21.3233333+00:00

    It seems to be a Base64-encoded string of durableId in CommentID.

    namespace WordOpenXMLSDK
    {
        using System;
        using System.Linq;
        using System.Collections.Generic;
        using DocumentFormat.OpenXml;
        using DocumentFormat.OpenXml.Packaging;
        using DocumentFormat.OpenXml.Wordprocessing;
    
        internal class Program
        {
            static void Main(string[] args)
            {
                var docxPath = @"Test.docx";
    
                var doc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open(docxPath, false);
    
                var comments = Comment.GetList(doc);
                var commentIds = CommentId.GetList(doc);
    
                foreach (var commentId in commentIds)
                {
                    Comment? comment = comments.FirstOrDefault(_ => _.Paragraphs.Contains(commentId.paraId));
                    Console.WriteLine($"{commentId.ToNavString()}\tTime=>{comment?.Time}\tText={comment?.Text}");
                }
            }
    
    
            class Comment
            {
                public DateTime? Time;
                public string Text;
                public readonly List<UInt64> Paragraphs = new List<UInt64>();
    
                public static List<Comment> GetList(DocumentFormat.OpenXml.Packaging.WordprocessingDocument doc)
                {
                    List<Comment> list = new List<Comment>();
    
                    var comments = doc?.MainDocumentPart?.WordprocessingCommentsPart.Comments;
                    if (comments == null)
                    {
                        return list;
                    }
    
                    foreach (var c in comments.OfType<DocumentFormat.OpenXml.Wordprocessing.Comment>())
                    {
                        Comment com = new Comment();
                        com.Time = c.DateUtc?.Value ?? c.Date?.Value;
                        com.Text = c.InnerText;
    
                        foreach (var p in c.ChildElements.OfType<DocumentFormat.OpenXml.Wordprocessing.Paragraph>())
                        {
                            if (p?.ParagraphId != null && TryGetUint64(p.ParagraphId, out var paraID))
                            {
                                com.Paragraphs.Add(paraID);
                            }
                        }
    
                        list.Add(com);
                    }
    
                    return list;
                }
            }
    
            class CommentId
            {
                public UInt64 paraId;
                public UInt64 durableId;
    
                public string ToNavString()
                {
                    var stext = "{\"c\":" + durableId.ToString() + "}";
                    var base64 = System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(stext));
                    return "nav=" + base64;
                }
    
                public static List<CommentId> GetList(DocumentFormat.OpenXml.Packaging.WordprocessingDocument doc)
                {
                    List<CommentId> list = new List<CommentId>();
    
                    var commentsIds = doc.MainDocumentPart?.WordprocessingCommentsIdsPart?.CommentsIds;
                    if (commentsIds == null)
                    {
                        return list;
                    }
    
                    foreach (var commentId in commentsIds.ChildElements.OfType<DocumentFormat.OpenXml.Office2019.Word.Cid.CommentId>())
                    {
                        var comment = new CommentId();
                        if (commentId.ParaId == null || !TryGetUint64(commentId.ParaId, out comment.paraId))
                        {
                            continue;
                        }
                        if (commentId.DurableId == null || !TryGetUint64(commentId.DurableId, out comment.durableId))
                        {
                            continue;
                        }
    
                        list.Add(comment);
                    }
    
                    return list;
                }
            }
    
            class CommentExtensible
            {
                public UInt64 durableId;
                public DateTime? dateUtc;
    
                public static List<CommentExtensible> GetList(DocumentFormat.OpenXml.Packaging.WordprocessingDocument doc)
                {
                    List<CommentExtensible> list = new List<CommentExtensible>();
    
                    var extensibles = doc.MainDocumentPart?.WordCommentsExtensiblePart?.CommentsExtensible;
                    if (extensibles == null)
                    {
                        return list;
                    }
    
                    foreach (var commentExt in extensibles.OfType<DocumentFormat.OpenXml.Office2021.Word.CommentsExt.CommentExtensible>())
                    {
                        var cex = new CommentExtensible();
    
                        if (commentExt.DurableId == null || !TryGetUint64(commentExt.DurableId, out cex.durableId))
                        {
                            continue;
                        }
                        cex.dateUtc = commentExt.DateUtc?.Value;
    
                        list.Add(cex);
    
                    }
    
                    return list;
                }
            }
    
            private static bool TryGetUint64(HexBinaryValue hex, out UInt64 u64)
            {
                u64 = 0;
    
                if (hex == null || !hex.TryGetBytes(out byte[]? bytes) || bytes == null || bytes.Length == 0)
                {
                    return false;
                }
    
                if (bytes.Length > 8)
                {
                    throw new NotSupportedException();
                }
    
    
                if (BitConverter.IsLittleEndian)
                {
                    Array.Reverse(bytes);
                }
    
                byte[] bs = new byte[8];
                Array.Copy(bytes, bs, bytes.Length);
    
                u64 = System.BitConverter.ToUInt64(bs, 0);
    
                return true;
            }
        }
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.