DataTplFixPbbAutoRange.cs
C# VB
//// This code is part of Document Solutions for Word demos.// Copyright (c) MESCIUS inc. All rights reserved.//using System;using System.IO;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; } }}