//
// 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;
}
}
}