DataTplFixPbbAutoRange.cs
  1. //
  2. // This code is part of Document Solutions for Word demos.
  3. // Copyright (c) MESCIUS inc. All rights reserved.
  4. //
  5. using System;
  6. using System.IO;
  7. using System.Drawing;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Globalization;
  11. using GrapeCity.Documents.Word;
  12.  
  13. namespace DsWordWeb.Demos
  14. {
  15. // This example shows how to avoid errors related to pbb (paragraph-block-behavior) formatter
  16. // which are caused by the template engine automatically adding implicit ranges that form
  17. // an invalid template structure.
  18. public class DataTplFixPbbAutoRange
  19. {
  20. // Code demonstrating the problem:
  21. GcWordDocument Problem()
  22. {
  23. using var oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"));
  24. var doc = new GcWordDocument();
  25. doc.DataTemplate.DataSources.Add("ds", oceans);
  26. // Limit the number of processed records so that everything fits on a single page:
  27. var take3 = new int[] { 1, 2, 3 };
  28. doc.DataTemplate.DataSources.Add("take3", take3);
  29.  
  30. // Define a 1x1 table:
  31. var table = doc.Body.Tables.Add(1, 1);
  32. var cell_00 = table[0, 0];
  33. // Add a pbb (paragraph-block-behavior) range to a cell that will display ocean name
  34. // (#take3 simply limits the number of oceans and does not affect the problem or fix):
  35. cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#take3}:seq(take3)}{{#ds}:follow(take3):pbb()}{{ds.name}}: ");
  36. // Add a value tag which for every ocean will display the names of its seas:
  37. cell_00.GetRange().Paragraphs.Add("{{ds.seas.name}}");
  38. // Add an inner 1x1 table to the cell and get its only cell:
  39. var inner_cell = cell_00.GetRange().Tables.Add(1, 1)[0, 0];
  40. // We want the sea names to be duplicated in the inner cell
  41. // but we do now explicitly specify the #ds.seas range, relying
  42. // on automatic range expansion.
  43. // Incorrect: this will not work, see explanation below:
  44. inner_cell.GetRange().Paragraphs.First.GetRange().Runs.Add("{{ds.seas.name}}");
  45. // Close #ds range in the cell_00:
  46. cell_00.GetRange().Paragraphs.Last.GetRange().Runs.Add("{{/ds}}{{/take3}}");
  47.  
  48. /* Problem explanation:
  49. *
  50. * The defined layout that uses concise template syntax:
  51. *
  52. * +--------------------------------+ (1)
  53. * | {{#ds}:pbb()}{{ds.name}} |
  54. * | {{ds.seas.name}} |
  55. * | +--------------------+ (2) |
  56. * | | {{ds.seas.name}} | |
  57. * | +--------------------+ |
  58. * | {{/ds}} |
  59. * +--------------------------------+
  60. *
  61. * (1) is cell_11
  62. * (2) is inner_cell
  63. *
  64. * The above concise template uses implicit ranges, and is automatically expanded
  65. * to the following form by the template engine before processing:
  66. *
  67. * +--------------------------------------------+
  68. * | {{#ds}:pbb()}{{ds.name}} |
  69. * | {{#ds.seas}:pbb()}{{ds.seas.name}} |
  70. * | +--------------------------------+ |
  71. * | | {{ds.seas.name}}{{/ds.seas}} | |
  72. * | +--------------------------------+ |
  73. * | {{/ds}} |
  74. * +--------------------------------------------+
  75. *
  76. * Note that a new auto-generated inner range '{{#ds.seas}}' was created by the template engine.
  77. * When the engine adds an automatic range, it places the start tag ('{{#ds.seas}}' here) immediately
  78. * in front of the first used tag, and places the end tag ('{{/ds.seas}}' here) immediately after the
  79. * last used tag. But in this case this results in an invalid template, as the start and end auto generated
  80. * tags are placed in different cells which is not allowed.
  81. */
  82.  
  83. doc.DataTemplate.Process(CultureInfo.GetCultureInfo("en-US"));
  84. return doc;
  85. }
  86.  
  87. // Code demonstrating the fix:
  88. GcWordDocument Fix()
  89. {
  90. using var oceans = File.OpenRead(Path.Combine("Resources", "data", "oceans.json"));
  91. var doc = new GcWordDocument();
  92. doc.DataTemplate.DataSources.Add("ds", oceans);
  93. // Limit the number of processed records so that everything fits on a single page:
  94. var take3 = new int[] { 1, 2, 3 };
  95. doc.DataTemplate.DataSources.Add("take3", take3);
  96.  
  97. // Define a 1x1 table:
  98. var table = doc.Body.Tables.Add(1, 1, doc.Styles[BuiltInStyleId.ListTable4Accent4]);
  99. var cell_00 = table[0, 0];
  100. // Add a pbb (paragraph-block-behavior) range to a cell that will display ocean name
  101. // (#take3 simply limits the number of oceans and does not affect the problem or fix):
  102. cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#take3}:seq(take3)}{{#ds}:follow(take3):pbb()}{{ds.name}}: ");
  103. // Correct: here we explicitly define the start of '#ds.seas' collection,
  104. // and add a value tag which for every ocean will display the names of its seas:
  105. cell_00.GetRange().Paragraphs.First.GetRange().Runs.Add("{{#ds.seas}:pbb()}{{ds.seas.name}}");
  106. // Add an inner 1x1 table to the cell and get its only cell:
  107. var inner_cell = cell_00.GetRange().Tables.Add(1, 1, doc.Styles[BuiltInStyleId.ListTable5DarkAccent5])[0, 0];
  108. // We want the sea names to be duplicated in the inner cell:
  109. inner_cell.GetRange().Paragraphs.First.GetRange().Runs.Add("{{ds.seas.name}}");
  110. // Explicitly end the '#ds.seas' range, ensuring that start and end tags remain in the same cell:
  111. cell_00.GetRange().Paragraphs.Last.GetRange().Runs.Add("{{/ds.seas}}{{/ds}}{{/take3}}");
  112.  
  113. /*
  114. * New and valid layout:
  115. * +--------------------------------------------+(1)
  116. * | {{#ds}:pbb()}{{ds.name}} |
  117. * | {{#ds.seas}:pbb()}{{ds.seas.name}} |
  118. * | +--------------------------------+(2) |
  119. * | | {{ds.seas.name}} | |
  120. * | +--------------------------------+ |
  121. * | {{/ds.seas}}{{/ds}} |
  122. * +--------------------------------------------+
  123. *
  124. * (1) is cell_11
  125. * (2) is inner_cell
  126. */
  127. doc.DataTemplate.Process(CultureInfo.GetCultureInfo("en-US"));
  128. return doc;
  129. }
  130.  
  131. public GcWordDocument CreateDocx()
  132. {
  133. GcWordDocument doc;
  134. try
  135. {
  136. // This fails:
  137. doc = Problem();
  138. }
  139. catch (Exception ex)
  140. {
  141. // This works:
  142. doc = Fix();
  143. // Insert a brief explanation of the problem and the fix into the generated document:
  144. doc.Body.Paragraphs.Insert(
  145. $"The error \"{ex.Message}\" occurred because a pbb (paragraph-block-behavior) formatter on an auto-generated range " +
  146. $"started in one table cell and ended in another. A pbb formatter must start and end in the same table cell.",
  147. doc.Styles[BuiltInStyleId.BlockText],
  148. InsertLocation.Start);
  149. }
  150. return doc;
  151. }
  152. }
  153. }
  154.