@alexey.noskov I attached the code module for your view.
using System.Text;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Reflection.Metadata.Ecma335;
using System.Diagnostics;
using LexisNexis.Services.Schemas.RecentActivity;
using Amazon.Runtime.Internal.Util;
using System.Reflection;
using System.Text.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Aspose.Words.Saving;
using DocumentFormat.OpenXml.Office2010.Word;
using DocParserService.Services.Utilities;
using Microsoft.AspNetCore.Identity;
using RestSharp;
using Body = Aspose.Words.Body;
using Microsoft.Extensions.Configuration;
using System.Xml.Xsl;
using System.Xml;
using Formatting = Newtonsoft.Json.Formatting;
using LexisNexis.Services;
using System.Linq;
using Tuple = System.Tuple;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using System.Xml.Linq;
using LexisNexis.Services.Telemetry;
using DocParserService.Abstractions.Models;
using DocParserService.Abstractions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using DocParserService.Services.Models;
using Aspose.Words;
using DocumentFormat.OpenXml.Packaging;
using Aspose.Words.Fields;
using Microsoft.Extensions.FileSystemGlobbing.Internal;
using Microsoft.Extensions.FileSystemGlobbing;
using Nest;
using DocParserService.Peer.Helpers;
using FieldType = Aspose.Words.Fields.FieldType;
using Run = Aspose.Words.Run;
using DocumentFormat.OpenXml.ExtendedProperties;
using System.Text.RegularExpressions;
using Aspose.Words.Replacing;
using static MassTransit.ValidationResultExtensions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using System.Collections;
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
using DocumentFormat.OpenXml.Wordprocessing;
using Field = Aspose.Words.Fields.Field;
using Document = Aspose.Words.Document;
using Paragraph = Aspose.Words.Paragraph;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Runtime.ExceptionServices;
using System;
using BookmarkStart = Aspose.Words.BookmarkStart;
using FieldChar = Aspose.Words.Fields.FieldChar;
using DocumentFormat.OpenXml;
using Style = Aspose.Words.Style;
using DocumentFormat.OpenXml.Bibliography;
using Comment = Aspose.Words.Comment;
namespace DocParserService.Peer.Controllers
{
[ApiController]
[Route("[controller]/[action]")]
[RequestKeyEvent]
public class CrossReferenceController
{
public readonly IConfiguration _config;
public readonly ILogger<CrossReferenceController> _logger;
public static string insertReferenceType { get; set; } = "";
//Assume some starting bookmark number.
public string bookmarkStartNumber { get; set; } = "100000000";
public static string CorrectedbookmarkStartNumber { get; set; } = "100000000";
public static bool ISReferencedParaListItem { get; set; } = false;
public static string preText { get; set; } = "";
/// <summary>
/// Constructor used to read the appsettings json file.
/// </summary>
/// <param name="config"></param>
public CrossReferenceController(ILogger<CrossReferenceController> logger,
IConfiguration config)
{
this._logger = logger;
_config = config;
}
[HttpPost]
[AllowAnonymous]
[Route("InsertCrossReference")]
public string InsertCrossReference([FromBody] CrossReferenceRequestModel crossReferenceRequestModel)
{
//Get the Open xml content
string ooxmlContent = crossReferenceRequestModel.ooxml;
//Get the List of Paragraph Id's
JArray jArray = crossReferenceRequestModel.paragraphIds;
int crossReferenceParaId = Convert.ToInt32(crossReferenceRequestModel.referenceId);
List<int> paraIds = jArray.Select(x => Convert.ToInt32(x)).ToList();
//Get the search text
string findText = crossReferenceRequestModel.text;
//Get the pre text
if (!String.IsNullOrWhiteSpace(crossReferenceRequestModel.preText))
{
preText = crossReferenceRequestModel.preText;
}
insertReferenceType = crossReferenceRequestModel.InsertReferenceType;
//Assume the bookmark number as
using (System.IO.MemoryStream mStream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(ooxmlContent)))
{
//Applying the Aspose License
AsposeHelper.ApplyingAsposeLicense();
Aspose.Words.Document doc = new Aspose.Words.Document(mStream);
while (doc.Range.Bookmarks["_ref" + bookmarkStartNumber] != null)
{
bookmarkStartNumber = (Convert.ToInt64(bookmarkStartNumber) + 1).ToString();
CorrectedbookmarkStartNumber = bookmarkStartNumber;
}
//Creating the builder from the Aspose Document
DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveToDocumentStart();
bool hasListFormatinginReferencePara = false;
if (doc.Sections.Count == 1)
{
ProcessSingleSection(doc, crossReferenceParaId, ref hasListFormatinginReferencePara, bookmarkStartNumber, builder);
}
else
{
ProcessMultipleSections(doc, crossReferenceParaId, ref hasListFormatinginReferencePara, bookmarkStartNumber, builder);
}
builder.MoveToDocumentStart();
AddCrossReferenceToParagraphs(paraIds, findText, doc, hasListFormatinginReferencePara);
using (MemoryStream dstStream = new MemoryStream())
{
doc.Save(dstStream, SaveFormat.Docx);
using (WordprocessingDocument wordprocessingDocument =
WordprocessingDocument.Open(dstStream, true))
{
string? jsonstring = wordprocessingDocument.ToFlatOpcString();
preText = "";
bookmarkStartNumber = "";
insertReferenceType = "";
CorrectedbookmarkStartNumber = "";
return jsonstring;
}
}
}
}
void RemoveFootnoteParagraphs(NodeCollection paragraphs)
{
foreach (Paragraph item in paragraphs.OfType<Paragraph>().Where(x => x.ParentNode.NodeType == NodeType.Footnote))
{
item.Remove();
}
}
int CountImageParagraphs(NodeCollection paragraphs)
{
return paragraphs.OfType<Paragraph>().Count(x => x.ParentNode.NodeType == NodeType.Shape);
}
int CountCommentsBeforeParagraph(Document doc, int targetParagraphIndex, int CurrentSection)
{
int commentsParaCount = 0;
for (int i = 0; i < targetParagraphIndex; i++)
{
Paragraph paragraph = doc.Sections[CurrentSection].Body.Paragraphs[i];
foreach (Comment comment in doc.GetChildNodes(NodeType.Comment, true).Cast<Comment>())
{
if (comment.GetAncestor(NodeType.Paragraph) == paragraph)
{
commentsParaCount++;
}
}
}
return commentsParaCount;
}
void ProcessSingleSection(Document doc, int crossReferenceParaId, ref bool hasListFormatinginReferencePara, string bookmarkStartNumber, DocumentBuilder builder)
{
NodeCollection paragraphs = doc.Sections[0].Body.GetChildNodes(NodeType.Paragraph, true);
RemoveFootnoteParagraphs(paragraphs);
int imageParaCount = CountImageParagraphs(paragraphs);
crossReferenceParaId -= imageParaCount;
int commentsParaCount = CountCommentsBeforeParagraph(doc, crossReferenceParaId - 1, 0);
Paragraph targetParagraph = (Paragraph)paragraphs[crossReferenceParaId - 1 + commentsParaCount];
hasListFormatinginReferencePara = HasNoListFormatting(targetParagraph);
builder.MoveTo(targetParagraph.GetChildNodes(NodeType.Run, true).Count > 0
? targetParagraph.GetChild(NodeType.Run, 0, true)
: targetParagraph);
builder.StartBookmark("_Ref" + bookmarkStartNumber);
builder.MoveTo(targetParagraph);
builder.EndBookmark("_Ref" + bookmarkStartNumber);
}
void ProcessMultipleSections(Document doc, int crossReferenceParaId, ref bool hasListFormatinginReferencePara, string bookmarkStartNumber, DocumentBuilder builder)
{
int currentSection = 0;
int commentsParaCount = 0;
for (int i = 0; i < doc.Sections.Count; i++)
{
int sectionParaCount = 0;
NodeCollection paragraphs = doc.Sections[i].Body.GetChildNodes(NodeType.Paragraph, true);
RemoveFootnoteParagraphs(paragraphs);
int imageParaCount = CountImageParagraphs(paragraphs);
sectionParaCount = paragraphs.Count - imageParaCount;
if (crossReferenceParaId > sectionParaCount)
{
crossReferenceParaId -= sectionParaCount;
}
else
{
currentSection = i;
break;
}
}
commentsParaCount = CountCommentsBeforeParagraph(doc, crossReferenceParaId - 1, currentSection);
Paragraph targetParagraph = (Paragraph)doc.Sections[currentSection].Body.GetChildNodes(NodeType.Paragraph, true)[crossReferenceParaId - 1 + commentsParaCount];
hasListFormatinginReferencePara = HasNoListFormatting(targetParagraph);
if (targetParagraph.ChildNodes.Count > 0 && targetParagraph.GetChildNodes(NodeType.Run, true).Count > 0)
{
builder.MoveTo(targetParagraph.GetChild(NodeType.Run, 0, true));
}
else
{
builder.MoveTo(targetParagraph);
}
builder.StartBookmark("_Ref" + bookmarkStartNumber);
builder.MoveTo(targetParagraph);
builder.EndBookmark("_Ref" + bookmarkStartNumber);
}
private static void AddCrossReferenceToParagraphs(List<int> paraIds, string findText, Document doc, bool HasListFormatinginReferencePara)
{
bool bracketInFindText = Regex.IsMatch(findText, @"[\{\}\(\)\[\]]");
string changedfindText = bracketInFindText ? Regex.Escape(findText) : findText;
FindReplaceOptions options = new(FindReplaceDirection.Backward, new CrossReferenceAddingEvaluator());
foreach (int paraId in paraIds)
{
int currentSection = 0;
int currentPara = paraId;
for (int sectionIndex = 0; sectionIndex < doc.Sections.Count; sectionIndex++)
{
NodeCollection paragraphs = doc.Sections[sectionIndex].Body.GetChildNodes(NodeType.Paragraph, true);
foreach (Paragraph item in paragraphs.OfType<Paragraph>().Where(x => x.ParentNode.NodeType == NodeType.Footnote))
{
item.Remove();
}
int imageParaCount = paragraphs.OfType<Paragraph>().Count(x => x.ParentNode.NodeType == NodeType.Shape);
int sectionParaCount = paragraphs.Count - imageParaCount;
if (currentPara > sectionParaCount)
{
currentPara -= sectionParaCount;
}
else
{
currentSection = sectionIndex;
break;
}
}
Paragraph targetParagraph = doc.Sections[currentSection].Body.GetChildNodes(NodeType.Paragraph, true).OfType<Paragraph>().ToList()[currentPara - 1];
if (targetParagraph != null)
{
if (!HasListFormatinginReferencePara)
{
targetParagraph.Range.Replace(new Regex("\\b(?<!\\.)" + changedfindText + "(?<!\\.)"), String.Empty, options);
}
else
{
ISReferencedParaListItem = true;
targetParagraph.Range.Replace(new Regex("\\b(?<!\\.)" + changedfindText + "(?<!\\.)"), String.Empty, options);
}
}
}
}
static bool HasNoListFormatting(Paragraph para)
{
// Check if the paragraph is part of a multilevel list
if (para.IsListItem && para.ListLabel?.LabelString == "" && (para.ListFormat?.ListLevel?.NumberFormat == "" || para.ListFormat?.ListLevel?.NumberStyle == NumberStyle.Bullet))
{
return true;
}
return false;
}
}
public class CrossReferenceAddingEvaluator : IReplacingCallback
{
private readonly string mStartBookmarkNumber;
private readonly string insertReferenceType;
private readonly string preText;
internal CrossReferenceAddingEvaluator(FindReplaceOptions options)
{
mStartBookmarkNumber = CrossReferenceController.CorrectedbookmarkStartNumber;
insertReferenceType = CrossReferenceController.insertReferenceType;
preText = CrossReferenceController.preText;
}
internal CrossReferenceAddingEvaluator()
{
mStartBookmarkNumber = CrossReferenceController.CorrectedbookmarkStartNumber;
insertReferenceType = CrossReferenceController.insertReferenceType;
preText = CrossReferenceController.preText;
}
private static Aspose.Words.Run SplitRun(Aspose.Words.Run run, int position)
{
Aspose.Words.Run afterRun = (Aspose.Words.Run)run.Clone(true);
afterRun.Text = run.Text.Substring(position);
run.Text = run.Text.Substring(0, position);
run.ParentNode.InsertAfter(afterRun, run);
return afterRun;
}
ReplaceAction IReplacingCallback.Replacing(ReplacingArgs args)
{
bool needtoCheckPreText = !String.IsNullOrEmpty(preText);
// This is a Run node that contains either the beginning or the complete match.
Node currentNode = args.MatchNode;
string FindText = currentNode.Range.Text;
if (currentNode.Range.Text.Length < args.MatchOffset) return ReplaceAction.Skip;
Node prevNodeVal = currentNode;
// The first (and may be the only) run can contain text before the match,
// In this case it is necessary to split the run.
if (args.MatchOffset > 0)
currentNode = SplitRun((Aspose.Words.Run)currentNode, args.MatchOffset);
// This array is used to store all nodes of the match for further highlighting.
List<Run> runs = new List<Run>();
// Find all runs that contain parts of the match string.
int remainingLength = args.Match.Value.Length;
while (
(remainingLength > 0) &&
(currentNode != null) &&
(currentNode.GetText().Length <= remainingLength))
{
runs.Add((Run)currentNode);
remainingLength = remainingLength - currentNode.GetText().Length;
prevNodeVal = currentNode;
// Select the next Run node.
// Have to loop because there could be other nodes such as BookmarkStart etc.
do
{
currentNode = currentNode.NextSibling;
}
while ((currentNode != null) && (currentNode.NodeType != NodeType.Run));
}
// Split the last run that contains the match if there is any text left.
if ((currentNode != null) && (remainingLength > 0))
{
SplitRun((Aspose.Words.Run)currentNode, remainingLength);
runs.Add((Run)currentNode);
}
DocumentBuilder oDocBuild = new DocumentBuilder(args.MatchNode.Document as Document);
// Check if the replaced text contains a hyperlink
if (currentNode != null)
{
bool FieldCountPresentAlready = false;
if (currentNode.ParentNode.Range.Fields.Count > 0)
{
FieldCountPresentAlready = true;
}
foreach (Field field in currentNode.ParentNode.Range.Fields)
{
if (field.Type == FieldType.FieldRef && field.Result.Contains(FindText))
{
// Get the text from the field result
string resultText = field.Result;
// Remove the field from the document
field.Remove();
// Find the first paragraph after the parent node
Paragraph? para = currentNode.ParentNode as Paragraph;
// If no paragraph found, create a new paragraph
if (para == null)
{
para = new Paragraph(currentNode.Document);
currentNode.ParentNode.ParentNode.InsertBefore(para, currentNode.ParentNode);
}
// Create a new Run node with the resultText
Run newRun = new Run(currentNode.Document, resultText);
para.InsertBefore(newRun, currentNode);
}
}
if (FieldCountPresentAlready)
{
currentNode = currentNode.PreviousSibling;
if (currentNode.NodeType == NodeType.BookmarkEnd || currentNode.NodeType == NodeType.BookmarkStart)
{
currentNode = currentNode.PreviousSibling;
}
if (args.MatchOffset > 0 && currentNode.NextSibling.NodeType != NodeType.BookmarkStart && currentNode.NextSibling.NodeType != NodeType.BookmarkEnd)
{
currentNode = SplitRun((Aspose.Words.Run)currentNode, args.MatchOffset);
runs.Clear();
runs.Add((Aspose.Words.Run)currentNode);
}
else
{
runs.Clear();
runs.Add((Aspose.Words.Run)currentNode);
}
}
}
// Now Add field codes in the sequence
if (currentNode != null)
{
oDocBuild.MoveTo(currentNode);
SelectInsertReferenceType(oDocBuild, runs, currentNode);
foreach (Run run in runs)
{
if (run.ParentNode != null)
{
run.Remove();
}
}
}
else
{
oDocBuild.MoveTo(prevNodeVal);
}
return ReplaceAction.Skip;
}
private void SelectInsertReferenceType(DocumentBuilder oDocBuild, List<Run> runs, Node currentNode)
{
if (CrossReferenceController.ISReferencedParaListItem)
{
// Insert field after text
oDocBuild.MoveTo(runs[runs.Count - 1]);
oDocBuild.InsertField("REF _Ref" + mStartBookmarkNumber + " \\h", currentNode?.GetText());
// Remove text
runs.ForEach(r => r.Remove());
}
else
{
switch (insertReferenceType)
{
// Clone the runs with their formatting
case "page_number":
FieldPageRef ReffieldPage = (FieldPageRef)oDocBuild.InsertField(FieldType.FieldPageRef, false);
ReffieldPage.BookmarkName = "_Ref" + mStartBookmarkNumber;
ReffieldPage.InsertHyperlink = true;
ReffieldPage.Update();
break;
case "paragraph_number":
case "paragraph_number_no_context":
case "paragraph_number_full_context":
case "above_or_below":
case "paragraph_text":
FieldRef Reffield = (FieldRef)oDocBuild.InsertField(FieldType.FieldRef, false);
Reffield.InsertParagraphNumber = insertReferenceType != "paragraph_text" && insertReferenceType != "above_or_below";
if (insertReferenceType == "paragraph_number_full_context")
{
Reffield.InsertParagraphNumberInFullContext = true;
}
else if (insertReferenceType == "paragraph_number_no_context")
{
Reffield.InsertParagraphNumberInRelativeContext = true;
}
if (insertReferenceType == "above_or_below")
{
Reffield.InsertRelativePosition = true;
}
Reffield.BookmarkName = "_Ref" + mStartBookmarkNumber;
Reffield.InsertHyperlink = true;
Reffield.Update();
break;
}
}
}
}
}