Apply comments to specific words/tags within a large HTML text

Hi,
I see that I can add comments by utilizing the following code:

builder.Write("Some text is added.");
Comment comment = new Comment(doc, "Amy Lee", "AL", DateTime.Today);
builder.CurrentParagraph.AppendChild(comment);
comment.Paragraphs.Add(new Paragraph(doc));
comment.FirstParagraph.Runs.Add(new Run(doc, "Comment text."));

I am inserting a large amount of HTML text via the method -

builder.InsertHtml(html);

Can I apply comments to specific words/tags within the HTML text after the InsertHTML method is called? Or, do I have to break up the HTML by comments and then call the InsertHTML method and apply the comment to the specific HTML tag?

Hello

Thanks for your inquiry. There is no way to insert HTML comments in the document. If you need to have comments in the output HTML, you can insert them after converting document to HTML.
Best regards,

Hi Todd,
Thanks for your inquiry.
I think what you meant is can you insert MS Word type comments within content even when a large amount is inserted using the InsertHTML method.
This is possible, please see the code below:

// This will hold the start and end nodes of the inserted HTML
NodeRange nodeRange = new NodeRange();
// Set the node changing handler to catch inserted nodes and pass the node range object used to the store
// the nodes are looking for.
doc.NodeChangingCallback = new FindNodeRangeHtml(nodeRange);
// Insert HTML
builder.InsertHtml("Text to comment");
// Remove the node changing callback
doc.NodeChangingCallback = null;
// Insert a comment around certain text between these two nodes using replace.
doc.Range.Replace(new Regex("Text to comment"), new InsertCommentsReplaceHandler(nodeRange), false);
///
/// Finds the first and last node inserted during the duration of the callback and passes this to the NodeRange object.
///
public class FindNodeRangeHtml : INodeChangingCallback
{
    NodeRange mNodeRange;
    bool isFirstNode = true;
    public FindNodeRangeHtml(NodeRange nodeRange)
    {
        mNodeRange = nodeRange;
    }
    void INodeChangingCallback.NodeInserted(NodeChangingArgs args)
    {
        Document doc = (Document)args.Node.Document;
        if (isFirstNode)
        {
            mNodeRange.StartNode = args.Node;
            isFirstNode = false;
        }
        mNodeRange.EndNode = args.Node;
    }
    void INodeChangingCallback.NodeInserting(NodeChangingArgs args)
    { }
    void INodeChangingCallback.NodeRemoved(NodeChangingArgs args)
    { }
    void INodeChangingCallback.NodeRemoving(NodeChangingArgs args)
    { }
}
public class NodeRange
{
    private Node mStartNode;
    private Node mEndNode;

    public Node StartNode
    {
        get
        {
            return mStartNode;
        }
        set
        {
            mStartNode = value;
        }
    }

    public Node EndNode
    {
        get
        {
            return mEndNode;
        }
        set
        {
            mEndNode = value;
        }
    }
}

As you can see it will gather the start and end nodes of the content insterted using the InsertHTML method and stores them in an NodeRange object.
You can then apply comments to specific text using an ReplaceEvaluator. I suggest using the code from Find and Highlight Text sample here as the basis for your comment insertion logic. For instance you split the runs around the text that matches in the same way but instead of highlighting the text you use your code above to insert a comment at the matching text instead. The NodeRange object passed to the handler is used to verify that the match node is occurs within the start and end node.
Thanks,

Thank you for the suggestions. I have implemented most of your code. I have one question, if I have a run object, how do I attach a comment to that object?
Thanks again for your help.

Hi Todd,
Thanks for your inquiry.
You can find an example of how to insert a comment range around nodes from this post here.
In your case if you are using the Replacement Evaluator then you will want to insert the range around the first matching run and the last matching run. You can achieve this using the code below.

Run startRun = (Run) runs[0];
Run endRun = (Run) runs[runs.Count - 1];
startRun.ParentNode.InsertBefore(comment);
startRun.ParentNode.InsertBefore(start, startRun);
endRun.ParentNode.InsertAfter(end, endRun);

Thanks,

Thanks again for your help. I assume when you reference “start” and “end” in your code above, you are referrring to the NodeRange passed in to ReplaceEvaluatorFindAndComment. When implementing the code below, I am getting the error: “The reference node is not a child of this node.” when executing:

startRun.ParentNode.InsertBefore(NR.StartNode, comment);

Any Ideas?

public class ReplaceEvaluatorFindAndComment : IReplacingCallback
{
    private NodeRange NR = null;
    private List<Common.DataModel.ReviewersGuide.Comment> CommentList = new List<Common.DataModel.ReviewersGuide.Comment>();
    private Document Doc;

    public ReplaceEvaluatorFindAndComment(NodeRange nodeRange, List<Common.DataModel.ReviewersGuide.Comment> commentList, Document doc)
    {
        NR = nodeRange;
        CommentList = commentList;
        Doc = doc;
    }
    ReplaceAction IReplacingCallback.Replacing(ReplacingArgs e)
    {
        Node currentNode = e.MatchNode;
        if (e.MatchOffset > 0)
            currentNode = SplitRun((Run)currentNode, e.MatchOffset);
        foreach (Common.DataModel.ReviewersGuide.Comment c in CommentList)
        {
            Aspose.Words.Comment comment = new Aspose.Words.Comment(Doc, c.Author, string.Empty, c.Date);
            comment.SetText(c.Text);
            Run startRun = (Run)currentNode;
            Run endRun = (Run)currentNode;
            startRun.ParentNode.InsertBefore(NR.StartNode, comment);
            endRun.ParentNode.InsertAfter(NR.EndNode, endRun);
        }
        return ReplaceAction.Skip;
    }
    private static Run SplitRun(Run run, int position)
    {
        Run afterRun = (Run)run.Clone(true);
        afterRun.Text = run.Text.Substring(position);
        run.Text = run.Text.Substring(0, position);
        run.ParentNode.InsertAfter(afterRun, run);
        return afterRun;
    }
}

Hi Todd,
Thank you for the additional information.
My apologises for the confusion, my last post was a bit ambiguous. I was actually referring to the start and end CommentRange nodes found in the code that I linked here. These are what are inserted to define where a comment is located in the doucment.
The nodes in the NodeRange are simply there for your to check if your match node was actually inserted during the InsertHtml call. i.e to avoid inserting comments in places other then the inserted content. If you need to use this then you will need to implement a bit of code that check if the current node is inbetween these two nodes (most likely by iterating backward and checking for the start node, and iterating forward and checking for the end node).
Please note if the text your are searching for spans over multiple runs then you will need to include the other code from the sample page in the documentation or else the output may not look correct.
Thanks,

Hi Adam,
Since I am applying a comment after I have executed:

builder.InsertHtml(html);

That example is not very helpful.
The code below is close to what I need - although, it associates a comment to the end of my document (not the text):

int commentId = 0;
CommentRangeStart start = new CommentRangeStart(Doc, commentId);
CommentRangeEnd end = new CommentRangeEnd(Doc, commentId);
SplitRun((Run)currentNode, start, end, comment);
private static void SplitRun(Run run, CommentRangeStart start, CommentRangeEnd end, Aspose.Words.Comment comment)
{
    run.ParentNode.AppendChild(comment);
    run.ParentNode.AppendChild(start);
    run.ParentNode.AppendChild(end);
    run.ParentNode.InsertBefore(start, comment);
    run.ParentNode.InsertAfter(end, comment);
}

I get an error if I don’t append the “start”, “end”, and “comment” before executing InsertBefore and InsertAfter.
Any ideas?

Hi Todd,
Thanks for this additional information.
This error is occuring because you are trying to insert the wrong nodes into your document. Please see the screenshot below of sample document which contains a Comment viewed in DocumentExplorer.

The CommentRange nodes need to encapsulate the Run or Runs you want to comment and the Comment node can appear before or after these.
You need to use my original code:

startRun.ParentNode.InsertBefore(comment, startRun);
startRun.ParentNode.InsertBefore(start, startRun);
endRun.ParentNode.InsertAfter(end, endRun);

In this case the Comment node and CommentRangeStart node are inserted before the startRun and the CommentRangeEnd is inserted after the endRun. The startRun and endRun can be the same run as in your case.
If you have any further troubles could you please attach your template and some sample HTML code here that you are trying to comment.
Thanks,

Hi - the method:

startRun.ParentNode.InsertBefore(comment);

Does not have an overload for one paramenter.
Thanks.

Todd

Hi

Thanks for your request. This is just a typo. You should use code like the following:

startRun.ParentNode.InsertBefore(comment, startRun);
startRun.ParentNode.InsertBefore(start, startRun);
endRun.ParentNode.InsertAfter(end, endRun);

Best regards,

That was it! Thanks for your help from both of you.