I'm generating a Word document by replacing some placeholder text in the template document with my values. For this, I'm using GemBox.Document, more specifically, this code from Find and Replace example:
var document = DocumentModel.Load("input.docx");
var firstPlaceholder = document.Content.Find("%Text1%").First();
firstPlaceholder.LoadText("Value 1");
var secondPlaceholder = document.Content.Find("%Text2%").First();
firstPlaceholder.LoadText("Value 2");
document.Save("output.docx");
That works fine.
But now I have a scenario in which the values that will replace the placeholders depend on their location, more specifically, does the placeholder appear before or after some specific paragraph in the document.
I did try using something like this:
Paragraph separator = ...
string firstPlaceholderText = "%Text1%";
string separatorText = seperator.Content.ToString();
string wholeDocumentText = document.Content.ToString();
if (wholeDocumentText.IndexOf(firstPlaceholderText) < wholeDocumentText.IndexOf(separatorText))
{
// The placeholder is before the separator...
}
else
{
// The placeholder is after the separator...
}
However, that same separatorText
value might occur in multiple places in the document so string.IndexOf()
is not a viable solution for me.
Is there another way how I could make this comparison, or another way how I could determine the location of some placeholder compared to some other document element?
Try this:
static bool IsPositionBefore(ContentPosition position1, ContentPosition position2)
{
var parentIndexes1 = GetParentIndexes(position1.Parent);
var parentIndexes2 = GetParentIndexes(position2.Parent);
int count = Math.Min(parentIndexes1.Count, parentIndexes2.Count);
for (int i = 0; i < count; i++)
{
if (parentIndexes1[i] < parentIndexes2[i])
return true;
if (parentIndexes1[i] > parentIndexes2[i])
return false;
}
// Both positions are inside the same parent element.
var parent = position1.Parent;
var parentClone = parent.Clone(true);
string positionMarker1 = "\u0001";
string positionMarker2 = "\u0002";
position1.LoadText(positionMarker1);
position2.LoadText(positionMarker2);
string parentContent = parent.Content.ToString();
int positionOffset1 = parentContent.IndexOf(positionMarker1, StringComparison.Ordinal);
int positionOffset2 = parentContent.IndexOf(positionMarker2, StringComparison.Ordinal);
parent.Content.Set(parentClone.Content);
return positionOffset1 < positionOffset2;
}
static IList<int> GetParentIndexes(Element element)
{
var parentIndexes = new List<int>();
while (element.Parent != null)
{
parentIndexes.Add(element.ParentCollection.IndexOf(element));
element = element.Parent;
}
parentIndexes.Reverse();
return parentIndexes;
}
Also, here is how you can use this IsPositionBefore
method:
if (IsPositionBefore(firstPlaceholder.Start, separator.Content.Start))
{
// The placeholder is before the separator...
}
else
{
// The placeholder is after the separator...
}
The tricky part is how to determine which position comes first when both positions are inside the same element.
That's because the ContentPosition
currently doesn't have some kind of offset API that would tell you where exactly it's located inside the element.
So, what I'm doing is temporarily adding two random control characters, checking which one occurs before the other, and then removing them.
I think this approach is safe because Word documents cannot have control characters (Word applications will show them as corrupted) and if you try to save a DocumentModel
that has such characters you will get an exception.