DataTplFixPbbAutoRange.vb
''
'' This code is part of Document Solutions for Word demos.
'' Copyright (c) MESCIUS inc. All rights reserved.
''
Imports System.IO
Imports System.Globalization
Imports GrapeCity.Documents.Word

'' This example shows how to avoid errors related to pbb (paragraph-block-behavior) formatter
'' which are caused by the template engine automatically adding implicit ranges that form
'' an invalid template structure.
Public Class DataTplFixPbbAutoRange
    '' Code demonstrating the problem:
    Private Function Problem() As GcWordDocument
        Using oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"))
            Dim doc = New GcWordDocument()
            doc.DataTemplate.DataSources.Add("ds", oceans)
            '' Limit the number of processed records so that everything fits on a single page:
            Dim take3 = {1, 2, 3}
            doc.DataTemplate.DataSources.Add("take3", take3)

            '' Define a 1x1 table:
            Dim table = doc.Body.Tables.Add(1, 1)
            Dim cell_00 = table(0, 0)
            '' Add a pbb (paragraph-block-behavior) range to a cell that will display ocean name
            '' (#take3 simply limits the number of oceans and does not affect the problem or fix):
            cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#take3}:seq(take3)}{{#ds}:follow(take3):pbb()}{{ds.name}}: ")
            '' Add a value tag which for every ocean will display the names of its seas:
            cell_00.GetRange().Paragraphs.Add("{{ds.seas.name}}")
            '' Add an inner 1x1 table to the cell and get its only cell:
            Dim inner_cell = cell_00.GetRange().Tables.Add(1, 1)(0, 0)
            '' We want the sea names to be duplicated in the inner cell
            '' but we do now explicitly specify the #ds.seas range, relying
            '' on automatic range expansion.
            '' Incorrect: this will not work, see explanation below:
            inner_cell.GetRange().Paragraphs.First.GetRange().Runs.Add("{{ds.seas.name}}")
            '' Close #ds range in the cell_00:
            cell_00.GetRange().Paragraphs.Last.GetRange().Runs.Add("{{/ds}}{{/take3}}")

            '' Problem explanation:
            ''
            '' The defined layout that uses concise template syntax:
            ''
            '' +--------------------------------+ (1)
            '' | {{#ds}:pbb()}{{ds.name}}       |
            '' | {{ds.seas.name}}               |
            '' | +--------------------+ (2)     |
            '' | | {{ds.seas.name}}   |         |
            '' | +--------------------+         |
            '' | {{/ds}}                        |
            '' +--------------------------------+
            ''
            '' (1) is cell_11
            '' (2) is inner_cell
            ''
            '' The above concise template uses implicit ranges, and is automatically expanded
            '' to the following form by the template engine before processing:
            ''
            '' +--------------------------------------------+
            '' | {{#ds}:pbb()}{{ds.name}}                   |
            '' | {{#ds.seas}:pbb()}{{ds.seas.name}}         |
            '' | +--------------------------------+         |
            '' | | {{ds.seas.name}}{{/ds.seas}}   |         |
            '' | +--------------------------------+         |
            '' | {{/ds}}                                    |
            '' +--------------------------------------------+
            ''
            '' Note that a new auto-generated inner range '{{#ds.seas}}' was created by the template engine.
            '' When the engine adds an automatic range, it places the start tag ('{{#ds.seas}}' here) immediately
            '' in front of the first used tag, and places the end tag ('{{/ds.seas}}' here) immediately after the
            '' last used tag. But in this case this results in an invalid template, as the start and end auto generated
            '' tags are placed in different cells which is not allowed.

            doc.DataTemplate.Process(CultureInfo.GetCultureInfo("en-US"))
            Return doc
        End Using
    End Function

    '' Code demonstrating the fix:
    Private Function Fix() As GcWordDocument
        Using oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"))
            Dim doc = New GcWordDocument()
            doc.DataTemplate.DataSources.Add("ds", oceans)
            '' Limit the number of processed records so that everything fits on a single page:
            Dim take3 = {1, 2, 3}
            doc.DataTemplate.DataSources.Add("take3", take3)

            '' Define a 1x1 table:
            Dim table = doc.Body.Tables.Add(1, 1, doc.Styles(BuiltInStyleId.ListTable4Accent4))
            Dim cell_00 = table(0, 0)
            '' Add a pbb (paragraph-block-behavior) range to a cell that will display ocean name
            '' (#take3 simply limits the number of oceans and does not affect the problem or fix):
            cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#take3}:seq(take3)}{{#ds}:follow(take3):pbb()}{{ds.name}}: ")
            '' Correct: here we explicitly define the start of '#ds.seas' collection,
            '' and add a value tag which for every ocean will display the names of its seas:
            cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#ds.seas}:pbb()}{{ds.seas.name}}")
            '' Add an inner 1x1 table to the cell and get its only cell:
            Dim inner_cell = cell_00.GetRange().Tables.Add(1, 1, doc.Styles(BuiltInStyleId.ListTable5DarkAccent5))(0, 0)
            '' We want the sea names to be duplicated in the inner cell:
            inner_cell.GetRange().Paragraphs.First.GetRange().Runs.Add("{{ds.seas.name}}")
            '' Explicitly end the '#ds.seas' range, ensuring that start and end tags remain in the same cell:
            cell_00.GetRange().Paragraphs.Last.GetRange().Runs.Add("{{/ds.seas}}{{/ds}}{{/take3}}")

            '' New and valid layout:
            '' +--------------------------------------------+(1)
            '' | {{#ds}:pbb()}{{ds.name}}                   |
            '' | {{#ds.seas}:pbb()}{{ds.seas.name}}         |
            '' | +--------------------------------+(2)      |
            '' | | {{ds.seas.name}}               |         |
            '' | +--------------------------------+         |
            '' | {{/ds.seas}}{{/ds}}                        |
            '' +--------------------------------------------+
            ''
            '' (1) is cell_11
            '' (2) is inner_cell

            doc.DataTemplate.Process(CultureInfo.GetCultureInfo("en-US"))
            Return doc
        End Using
    End Function

    Function CreateDocx() As GcWordDocument
        Dim doc As GcWordDocument
        Try
            '' This fails:
            doc = Problem()
        Catch ex As Exception
            '' This works:
            doc = Fix()
            '' Insert a brief explanation of the problem and the fix into the generated document:
            doc.Body.Paragraphs.Insert(
                $"The error ""{ex.Message}"" occurred because a pbb (paragraph-block-behavior) formatter on an auto-generated range " +
                $"started in one table cell and ended in another. A pbb formatter must start and end in the same table cell.",
                doc.Styles(BuiltInStyleId.BlockText),
                InsertLocation.Start)
        End Try
        Return doc
    End Function
End Class