Inserting tables in a document using find & replace

Hi,
I’m currently evaluating the Aspose WORD control to be used in our web application to generate documents from templates. I’m using the Range.Replace method to find tags in the form of and replace them with the actual data. This works fine. However, we also have the need to replace some tags with an Aspose.Word.Table object. I have not been able to find a straight forward way to do this, since for inserting a table at a certain place it is necessary to have a reference node. The only way I’ve been able to come up with is using the Range.Replace overload that accepts a delegate function, since via the MatchNode in de event args in that delegate I can get a reference to the necessary nodes to insert the table.

  1. Am I doing things the hard way while there’s an easier solution to this?
  2. It works, however, an exception is generated (Object reference not set to an instance of an object) if one table needs to be inserted twice or more. I’ve been debugging a while and found that the delegates is called 4 times if the template contains two instances of the same tag, 6 for three etc. While looking at the plain Document.Range.Text it is clearly visible that a second table is being inserted in one of the cells of an inserted table (MatchNode.Type=“Run” and MatchNode.Range.Text= the text that’s in the cell where a second instance of the table is being inserted. I suspect this problem occurs due to the fact that the document object layout changes because of the insert of a number of nodes that make up the table. I’ve also found a work around for the problem; just replacing one instance at a time, however I’m afraid this has some performance issues. Below is the code I’m using to do the replace (bold is the workaround). Am I doing something wrong here that triggers the exception?
Private Function WORDTableReplace(ByRef rastuVars() As structWORDVar, ByVal vobjDoc As Aspose.Word.Document) As Boolean
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'//Method: Globals.clsDocument.WORDTableReplace
'//
'//Date: 06/12/2005 / 8:59
'//Author: Rick Langevoort
'//Purpose: 
'//
'//Rev: 
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
**Dim intTable, intTables As Integer**
Dim stuVar As structWORDVar, objRange As Aspose.Word.Range = vobjDoc.Range
For Each stuVar In rastuVars
With stuVar
If StrComp(.Table, "tabel", CompareMethod.Text) = 0 Then
m_stuCurrentWORDVar = stuVar
**intTables = objRange.Replace(.VarCode, .VarCode, False, False)
m_blnMultipleTables = intTables > 1**
**For intTable = 0 To intTables - 1
m_blnReplaced = False**
objRange.Replace(New System.Text.RegularExpressions.Regex(Replace(.VarCode, ".", "\.") & "", RegularExpressions.RegexOptions.IgnoreCase), New Aspose.Word.ReplaceEvaluator(AddressOf WORDTableReplaceHandler), True)
**Next intTable**
End If
End With
Next stuVar
Return True
End Function
Public Function WORDTableReplaceHandler(ByVal sender As Object, ByVal e As Aspose.Word.ReplaceEvaluatorArgs) As Aspose.Word.ReplaceAction
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'//Method: Globals.clsDocument.WORDTableReplace
'//
'//Date: 06/12/2005 / 8:59
'//Author: Rick Langevoort
'//Purpose: Deze functie handeld de events af die worden gegenereerd door de replace functie voor de tabellen.
'//
'//Rev: 
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
**If m_blnReplaced And m_blnMultipleTables Then Return Aspose.Word.ReplaceAction.Stop**
With m_stuCurrentWORDVar
**m_blnReplaced = True**
If StrComp(.Waarde, "-") <> 0 Then
Dim intRow, intCol As Integer
Dim objTable As New Aspose.Word.Table(e.MatchNode.Document), objRow As Aspose.Word.Row, objCell As Aspose.Word.Cell, objParent As Aspose.Word.Node = e.MatchNode, objSibbling As Aspose.Word.Node = e.MatchNode
Dim objDocBuilder As New Aspose.Word.DocumentBuilder(e.MatchNode.Document), dblPageWidth As Double = objDocBuilder.PageSetup.PageWidth - objDocBuilder.PageSetup.LeftMargin - objDocBuilder.PageSetup.RightMargin
Dim astrRows() As String = Split(.Waarde, vbCrLf), astrCols() As String
While objParent.NodeType <> Aspose.Word.NodeType.Body And objParent.NodeType <> Aspose.Word.NodeType.Cell
objSibbling = objParent
objParent = objParent.ParentNode
End While
With objTable
For intRow = 0 To UBound(astrRows)
astrCols = Split(astrRows(intRow), "|")
objRow = New Aspose.Word.Row(e.MatchNode.Document)
.Rows.Add(objRow)
With objRow
With .RowFormat
.HeadingFormat = intRow = 0 '//De eerste rij is de titelrij......
With .Borders
.LineStyle = Aspose.Word.LineStyle.Single
.Color = System.Drawing.Color.FromArgb(0, 0, 128)
End With
End With
For intCol = 0 To UBound(astrCols)
objCell = New Aspose.Word.Cell(e.MatchNode.Document)
.Cells.Add(objCell)
With objCell
.EnsureMinimum()
If astrCols(intCol).Length > 0 Then .FirstParagraph.Runs.Add(New Aspose.Word.Run(e.MatchNode.Document, astrCols(intCol)))
With .CellFormat
.LeftPadding = 5
.RightPadding = 5
.VerticalAlignment = Aspose.Word.CellVerticalAlignment.Center
.Width = dblPageWidth / (UBound(astrCols) + 1)
End With
If intRow = 0 Then
.CellFormat.Shading.BackgroundPatternColor = System.Drawing.Color.FromArgb(0, 0, 128)
With .FirstParagraph.Runs(0).Font
.Bold = True
.Color = System.Drawing.Color.White
End With
End If
End With
Next intCol
End With
Next intRow
End With
CType(objParent, Aspose.Word.CompositeNode).InsertBefore(objTable, objSibbling)
e.MatchNode.Range.Delete()
Return Aspose.Word.ReplaceAction.Skip
Else
e.Replacement = "-"
Return Aspose.Word.ReplaceAction.Replace
End If
End With
End Function

Hi Rick,
Thanks for taking Aspose.Word into consideration. Give me one day to work out an expedient solution to your task.

Generally it’s not recommended to remove nodes, while iterating with foreach. Try to collect the nodes to be removed in an ArrayList and remove them afterwards in a separate cycle.

Hi,
Thanks for your reply. Since I’m not using a for each loop in the callback, I presume that the control is using a for-each loop while doing the find&replace with a callback?
Anyway, it doesn’t make a difference; I started out the callback with replacing the Run that contains the tag to be searched for with an empty string instead of removing the node and this generated the same error.
Rick

It would be very helpful if you could provide code sufficient to reproduce the error. The code above has some structures definitions missing, and it does not provide the exact call that causes your application to fail with exception. You can send it zipped in attachment. All attachments on Aspose forum are private.

Replacing a text string with a table is possible. I fully understand that what I’m about to show is not as easy as just a single call to the Replace method. The reason for this is that existing replace functionality in Aspose.Word is still in its early stages. At the moment it is best for replacing text with other “flat text”. If you want to find or replace paragraph marks, tables, sections etc - there is no built in functionality for this yet, hence more code to workaround.
This code example takes a document:

Tag
Other text
Tag
----
and replaces the words “Tag” with a table consisting of one cell that contains text “Hello”.
The code works, see comments below. Your code was almost correct. You just need to pass isForward = false into the Replace method. When isForward = true, then the replace function has to do more work to keep everything in sync since every replace changes positions, nodes etc. It can handle only replace of flat text with another flat text automatically at the moment. Since you insert complex text (a table consisting of multiple nodes), it upsets the automatic syncing process so you have to use isForward = false.

/// 
/// Replacing a particular string with a table.
/// 
[Test]
public void TestReplaceWithTable()
{
    Document doc = TestUtil.Open(@"Model\Replace\TestReplaceWithTable.doc");
    // Note I have to pass "isForward = false", otherwise such a complex replace will not work.
    doc.Range.Replace(new Regex("Tag"), new ReplaceEvaluator(HandleReplaceWithTable), false);
    TestUtil.Save(doc, @"Model\Replace\TestReplaceWithTable Out.doc");
    Assert.AreEqual(
    "Hello\x0007\x0007" +
    "Other text\r" +
    "Hello\x0007\x0007" +
    "\x000c",
    doc.Range.Text);
}
private ReplaceAction HandleReplaceWithTable(object sender, ReplaceEvaluatorArgs e)
{
    // The node that contains text should be a Run.
    Run run = (Run)e.MatchNode;
    // We can only insert a table at the "block" level, that is among paragraphs and tables.
    // Step up to the paragraph node that contains the run with the match text.
    Paragraph para = run.ParentParagraph;
    // This is the table we are going to insert.
    // Note you can use DocumentBuilder instead of building node by node, 
    // it could be easier to build a table with DocumentBuilder.
    Table newTable = CreateTestTable(para.Document);
    // Ask the parent of the paragraph to insert the new table after this paragraph.
    para.ParentNode.InsertAfter(newTable, para);
    // In fact I want the old paragraph to be removed.
    para.Remove();
    // Let the replace continue.
    e.Replacement = "";
    return ReplaceAction.Replace;
}
private Table CreateTestTable(Document doc)
{
    // Create a table with one cell.
    Table table = new Table(doc);
    table.EnsureMinimum();
    Cell cell = table.FirstRow.FirstCell;
    cell.CellFormat.Width = 144;
    // Add some text.
    Run run = new Run(doc, "Hello");
    cell.FirstParagraph.AppendChild(run);
    return table;
}

Thank you both for your answers. I’ll modify the code according the last comments and if I still encounter problems I’ll attach all relevant code and a test document…

Yes, thank you it works; we’re now the proud owner of an Aspose.Word license…