📜 ⬆️ ⬇️

Creation and processing of print form templates

As practice has shown working with ERP systems - corporate applications for 30% consist of reports. A typical situation for such applications is to build a report on some data.

To build reports, you can use ReportBuilder or any other report building system . In this article I want to consider building reports in MS Word 2003 (and later versions) using Aspose.Words, since it is easy to edit, convenient to develop, does not require special skills for working with giants of reporting systems, etc.

According to the same type of data set, you need to create report templates that are a regular Word 2003 document with a set of key fields .
Then you can customize the template as you like, that is, bring it to the form in which it should look like in the final version. That is, create a print form template for the selected data set.

The advantage of this solution is that the person developing the printed form itself may not have any knowledge about SQL or data sources. He should only be able to work with MS Office Word. It would seem that everything is simple , but in our case it is assumed that:
')

Formulation of the problem


So, the task is clear, proceed.

The main elements of printed forms are the usual data fields and data tables. For convenience, let's divide the creation of a set of fields in a template and the creation of a table into separate methods.

/// <summary>      </summary> /// <param name="additionalFields"> ///  -   ,  -    ///    (  ) /// </param> /// <returns>  </returns> public static MemoryStream CreateNewTemplate(Dictionary<string, string> additionalFields) { var doc = new Document(); var docBuilder = new DocumentBuilder(doc); docBuilder.MoveToDocumentEnd();//    ,       foreach (var field in additionalFields) { docBuilder.Font.Bold = true; docBuilder.InsertHtml(string.Format("{0}: ", field.Value)); docBuilder.Font.Bold = false; //  ,   -  ,  - ,   ,   Word docBuilder.InsertField(string.Format(@"MERGEFIELD {0} \* MERGEFORMAT", field.Key), field.Key); docBuilder.InsertHtml("<br>"); } var stream = new MemoryStream(); doc.Save(stream, SaveFormat.Doc); //      stream.Position = 0; return stream; } /// <summary>     </summary> /// <param name="stream">   </param> /// <param name="tableSysName">  (       )</param> /// <param name="additionalFields">   ( -   ,  -    ) </param> /// <param name="tableRusName">  </param> /// <returns>  </returns> public static MemoryStream AddTable(Stream stream, string tableSysName, Dictionary<string, string> additionalFields, string tableRusName) { var doc = new Document(stream); var docBuilder = new DocumentBuilder(doc); docBuilder.MoveToDocumentEnd(); docBuilder.Font.Bold = true; docBuilder.Font.Size = 14; docBuilder.Writeln(tableRusName); docBuilder.Font.Size = 12; docBuilder.StartTable(); docBuilder.InsertCell(); foreach (var field in additionalFields) { docBuilder.Writeln(field.Value); if (field.Key != additionalFields.Last().Key) { docBuilder.InsertCell(); } } docBuilder.EndRow(); docBuilder.Font.Bold = false; docBuilder.InsertCell(); docBuilder.InsertField(string.Format("MERGEFIELD TableStart:{0}", tableSysName), "{"); foreach (var field in additionalFields) { docBuilder.InsertField(string.Format(@"MERGEFIELD {0} \* MERGEFORMAT", field.Key), field.Key); if (field.Key != additionalFields.Last().Key) { docBuilder.InsertCell(); } } docBuilder.InsertField(string.Format("MERGEFIELD TableEnd:{0}", tableSysName), "}"); docBuilder.Writeln(); docBuilder.EndTable(); docBuilder.Writeln(); stream.Close(); var newStream = new MemoryStream(); doc.Save(newStream, SaveFormat.Doc); newStream.Position = 0; return newStream; } 

Pay attention to the line
 docBuilder.InsertField(string.Format("MERGEFIELD TableStart:{0}", tableSysName), "{"); 

Here the key field is added to the table TableStart with the external symbol “{”, this symbol is not fundamental, it just externally takes up little space on the form. Also at the end of the table is added with the symbol "}". These fields must be in the same row of the table. The fields enclosed between them refer to the current table and will be repeated for the data set. That is, if you need to arrange the data on several lines, you will get an error.

But there is a trick: you can add a table of three columns opening the character in the first column, closing the character in the last column, and in the middle cell insert another table, the data in which can be decorated as you like. The inserted table and will be repeated for each data set. By default, this is not added, as it is rarely required (at least for those purposes where it is now used).

These two methods are enough to create a template. Further, this template is configured (fonts, markup, etc.) - we are not very interested. It remains only to fill in the data ready template, while maintaining the markup. To fill in fields that are not in the table, everything is simple:
 /// <summary>    </summary> /// <param name="stream">   () </param> /// <param name="fieldsToFill">    ( -   ,  -   ) </param> /// <returns>  </returns> public static MemoryStream FillTemplate(Stream stream, Dictionary<string, string> fieldsToFill) { var doc = new Document(stream); var firstArray = fieldsToFill.Select(keys => keys.Key).ToArray(); var secondArray = fieldsToFill.Select(a => (object)a.Value).ToArray(); /*    object.   ,           .        ,   ,  IFieldMergingCallback,     . */ doc.MailMerge.Execute(firstArray, secondArray); var newStream = new MemoryStream(); doc.Save(newStream, SaveFormat.Doc); newStream.Position = 0; stream.Close(); return newStream; } 

Now we come to filling out the tables. In Aspose, this issue has been approached very thoroughly, as you will soon see.
 /// <summary> ///   /// </summary> /// <param name="stream"> </param> /// <param name="tableSysName"> </param> /// <param name="tableValues">  </param> /// <returns> </returns> public static MemoryStream FillTable(Stream stream, string tableSysName, List<Dictionary<string, string>> tableValues) { var doc = new Document(stream); // ,  var customersDataSource = new CustomerMailMergeDataSource(tableValues.ToArray(), tableSysName); //    ,  IMailMergeDataSource,     doc.MailMerge.ExecuteWithRegions(customersDataSource); var newStream = new MemoryStream(); doc.Save(newStream, SaveFormat.Doc); newStream.Position = 0; stream.Close(); return newStream; } 

It's all pretty simple, but let's look:
  /// <summary>     </summary> internal class CustomerMailMergeDataSource : IMailMergeDataSource { /// <summary>   </summary> private readonly Dictionary<string, string>[] tableValue; /// <summary>   </summary> private int recordIndex; /// <summary>  </summary> /// <param name="data">  </param> /// <param name="tableName">  </param> public CustomerMailMergeDataSource(Dictionary<string, string>[] data, string tableName) { tableValue = data; recordIndex = -1; TableNamePrivate = tableName; } /// <summary>   </summary> public string TableName { get { return TableNamePrivate; } } /// <summary>    </summary> private static string TableNamePrivate { get; set; } /// <summary>   </summary> private bool IsEof { get { return recordIndex >= tableValue.Count(); } } /// <summary> Aspose.Words   ,      </summary> /// <param name="fieldName">   </param> /// <param name="fieldValue">     </param> /// <returns>       </returns> public bool GetValue(string fieldName, out object fieldValue) { var containsKey = tableValue[recordIndex].ContainsKey(fieldName); fieldValue = containsKey ? tableValue[recordIndex][fieldName] : null; return containsKey; } /// <summary>        </summary> /// <returns>    ,   false </returns> public bool MoveNext() { if (!IsEof) { recordIndex++; } return !IsEof; } /// <summary>    </summary> /// <param name="tableName"> </param> /// <returns>  </returns> public IMailMergeDataSource GetChildDataSource(string tableName) { return null; } } 

That's all, the necessary minimum is. Changes in the markup do not occur, empty fields (for which there is no data) simply disappear in the printed form. When fields are filled with values, object must be passed, but string is passed everywhere here. In principle, you can transfer any data types, and then, implementing IFieldMergingCallback , process and format the data.

You can attach your handler by assigning a doc.MailMerge.FieldMergingCallback handler instance to doc . That is, it is potentially possible to make how many handlers are required for
all cases of life and all data sets. Alternating the assignment of a handler with a call to doc.MailMerge.ExecuteWithRegions () , we obtain more flexible processing.

Possible questions

  1. Is it an advertisement? No, this is not an advertisement, just decided to share the results.
  2. Why choose Aspose.Words? It happened. Under the requirements approached the most optimal.
  3. Is this a working version? No, this is a simplified version. We can say a special edition. In fact, everything is a little more complicated.
  4. Where are the comments on the code? I considered the comments in the code itself quite exhaustive.

Here are the sources

Source: https://habr.com/ru/post/135170/


All Articles