DataTplFixPbbAutoRange.cs
//
// This code is part of Document Solutions for Word demos.
// Copyright (c) MESCIUS inc. All rights reserved.
//
using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using GrapeCity.Documents.Word;

namespace DsWordWeb.Demos
{
    // 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:
        GcWordDocument Problem()
        {
            using var oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"));
            var doc = new GcWordDocument();
            doc.DataTemplate.DataSources.Add("ds", oceans);
            // Limit the number of processed records so that everything fits on a single page:
            var take3 = new int[] { 1, 2, 3 };
            doc.DataTemplate.DataSources.Add("take3", take3);

            // Define a 1x1 table:
            var table = doc.Body.Tables.Add(1, 1);
            var 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:
            var 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;
        }

        // Code demonstrating the fix:
        GcWordDocument Fix()
        {
            using var oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"));
            var doc = new GcWordDocument();
            doc.DataTemplate.DataSources.Add("ds", oceans);
            // Limit the number of processed records so that everything fits on a single page:
            var take3 = new int[] { 1, 2, 3 };
            doc.DataTemplate.DataSources.Add("take3", take3);

            // Define a 1x1 table:
            var table = doc.Body.Tables.Add(1, 1, doc.Styles[BuiltInStyleId.ListTable4Accent4]);
            var 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:
            var 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;
        }

        public GcWordDocument CreateDocx()
        {
            GcWordDocument doc;
            try
            {
                // This fails:
                doc = Problem();
            }
            catch (Exception ex)
            {
                // 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);
            }
            return doc;
        }
    }
}