|
@@ -0,0 +1,466 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Drawing;
|
|
|
+using System.Linq;
|
|
|
+using System.Text.RegularExpressions;
|
|
|
+using FastReport;
|
|
|
+using FastReport.Table;
|
|
|
+using FastReport.Utils;
|
|
|
+using InABox.Core;
|
|
|
+using InABox.Scripting;
|
|
|
+using InABox.Wpf.Reports;
|
|
|
+using UnderlineType = InABox.Core.UnderlineType;
|
|
|
+
|
|
|
+namespace InABox.DynamicGrid
|
|
|
+{
|
|
|
+
|
|
|
+ public static class DigitalFormUtils
|
|
|
+ {
|
|
|
+
|
|
|
+ #region Layout Importer
|
|
|
+
|
|
|
+ private class Cell
|
|
|
+ {
|
|
|
+ public string Content { get; set; }
|
|
|
+
|
|
|
+ public int Row { get; set; }
|
|
|
+
|
|
|
+ public int Column { get; set; }
|
|
|
+
|
|
|
+ public int RowSpan { get; set; } = 1;
|
|
|
+
|
|
|
+ public int ColumnSpan { get; set; } = 1;
|
|
|
+
|
|
|
+ public ICell InnerCell { get; set; }
|
|
|
+
|
|
|
+ public Cell(int row, int column, string content, ICell cell)
|
|
|
+ {
|
|
|
+ Row = row;
|
|
|
+ Column = column;
|
|
|
+ Content = content;
|
|
|
+ InnerCell = cell;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void DeleteColumn(List<Cell> cells, int column)
|
|
|
+ {
|
|
|
+ foreach(var cell in cells)
|
|
|
+ {
|
|
|
+ if(cell.Column <= column && cell.Column + cell.ColumnSpan - 1 >= column)
|
|
|
+ {
|
|
|
+ --cell.ColumnSpan;
|
|
|
+ }
|
|
|
+ else if(cell.Column > column)
|
|
|
+ {
|
|
|
+ --cell.Column;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cells.RemoveAll(x => x.ColumnSpan < 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<Cell> GetCells(ISheet sheet)
|
|
|
+ {
|
|
|
+ var grid = new Dictionary<int, Dictionary<int, Cell>>();
|
|
|
+
|
|
|
+ for (int rowIdx = sheet.FirstRow; rowIdx <= sheet.LastRow; ++rowIdx)
|
|
|
+ {
|
|
|
+ var row = sheet.GetRow(rowIdx);
|
|
|
+ if (row is not null && row.FirstColumn >= 0)
|
|
|
+ {
|
|
|
+ var rowCells = new Dictionary<int, Cell>();
|
|
|
+ for (int colIdx = row.FirstColumn; colIdx <= row.LastColumn; ++colIdx)
|
|
|
+ {
|
|
|
+ var cell = row.GetCell(colIdx);
|
|
|
+ if (cell is not null)
|
|
|
+ {
|
|
|
+ rowCells.Add(colIdx, new Cell(rowIdx, colIdx, cell.GetValue(), cell));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ grid.Add(rowIdx, rowCells);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var region in sheet.GetMergedCells())
|
|
|
+ {
|
|
|
+ for (int r = region.FirstRow; r <= region.LastRow; ++r)
|
|
|
+ {
|
|
|
+ if (!grid.TryGetValue(r, out var row)) continue;
|
|
|
+
|
|
|
+ for (int c = region.FirstColumn; c <= region.LastColumn; ++c)
|
|
|
+ {
|
|
|
+ if ((r - region.FirstRow) + (c - region.FirstColumn) != 0)
|
|
|
+ {
|
|
|
+ row.Remove(c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (row.Count == 0)
|
|
|
+ {
|
|
|
+ grid.Remove(r);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (grid.TryGetValue(region.FirstRow, out var cRow) && cRow.TryGetValue(region.FirstColumn, out var cCell))
|
|
|
+ {
|
|
|
+ cCell.RowSpan = region.LastRow - region.FirstRow + 1;
|
|
|
+ cCell.ColumnSpan = region.LastColumn - region.FirstColumn + 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var cells = new List<Cell>();
|
|
|
+ foreach (var row in grid.Values)
|
|
|
+ {
|
|
|
+ foreach (var cell in row.Values)
|
|
|
+ {
|
|
|
+ cells.Add(cell);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cells;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Regex VariableRegex = new(@"^\[(?<VAR>[^:\]]+)(?::(?<TYPE>[^:\]]*))?(?::(?<PROPERTIES>[^\]]*))?\]$");
|
|
|
+ private static Regex HeaderRegex = new(@"^{(?<HEADER>[^:}]+)(?::(?<COLLAPSED>[^}]*))?}$");
|
|
|
+
|
|
|
+ public static DFLayout LoadLayout(ISpreadsheet spreadsheet)
|
|
|
+ {
|
|
|
+ var sheet = spreadsheet.GetSheet(0);
|
|
|
+
|
|
|
+ var cells = GetCells(sheet);
|
|
|
+
|
|
|
+ int firstRow = int.MaxValue;
|
|
|
+ int lastRow = 0;
|
|
|
+ int firstCol = int.MaxValue;
|
|
|
+ int lastCol = 0;
|
|
|
+
|
|
|
+ foreach (var cell in cells)
|
|
|
+ {
|
|
|
+ firstCol = Math.Min(cell.Column, firstCol);
|
|
|
+ lastCol = Math.Max(cell.Column + cell.ColumnSpan - 1, lastCol);
|
|
|
+
|
|
|
+ firstRow = Math.Min(cell.Row, firstRow);
|
|
|
+ lastRow = Math.Max(cell.Row + cell.RowSpan - 1, lastRow);
|
|
|
+ }
|
|
|
+
|
|
|
+ var layout = new DFLayout();
|
|
|
+
|
|
|
+ var columnWidths = new Dictionary<int, float>();
|
|
|
+ var colOffset = 0;
|
|
|
+ for (int col = firstCol; col <= lastCol; ++col)
|
|
|
+ {
|
|
|
+ var width = sheet.GetColumnWidth(col);
|
|
|
+ if(width == float.MinValue)
|
|
|
+ {
|
|
|
+ layout.ColumnWidths.Add("10*");
|
|
|
+ }
|
|
|
+ else if(width <= 0f)
|
|
|
+ {
|
|
|
+ DeleteColumn(cells, col);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ layout.ColumnWidths.Add($"{width}*");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int row = firstRow; row <= lastRow; ++row)
|
|
|
+ layout.RowHeights.Add("Auto");
|
|
|
+
|
|
|
+ foreach(var cell in cells)
|
|
|
+ {
|
|
|
+ var style = cell.InnerCell.GetStyle();
|
|
|
+
|
|
|
+ if (string.IsNullOrWhiteSpace(cell.Content) && style.Foreground == Color.Empty) continue;
|
|
|
+
|
|
|
+ DFLayoutControl? control;
|
|
|
+ String content = cell.Content?.Trim() ?? "";
|
|
|
+
|
|
|
+ var headermatch = HeaderRegex.Match(content);
|
|
|
+ var variablematch = VariableRegex.Match(content);
|
|
|
+
|
|
|
+ if (headermatch.Success)
|
|
|
+ {
|
|
|
+ var text = headermatch.Groups["HEADER"];
|
|
|
+ var collapsed = headermatch.Groups["COLLAPSED"];
|
|
|
+ var header = new DFLayoutHeader()
|
|
|
+ {
|
|
|
+ Header = text.Value,
|
|
|
+ Collapsed = collapsed.Success ? String.Equals(collapsed.Value.ToUpper(),"COLLAPSED") : false,
|
|
|
+ Style = CreateStyle(style)
|
|
|
+ };
|
|
|
+
|
|
|
+ control = header;
|
|
|
+ }
|
|
|
+ else if (variablematch.Success)
|
|
|
+ {
|
|
|
+ var variableName = variablematch.Groups["VAR"];
|
|
|
+ var variableType = variablematch.Groups["TYPE"];
|
|
|
+ var variableProps = variablematch.Groups["PROPERTIES"];
|
|
|
+
|
|
|
+ Type? fieldType = null;
|
|
|
+ if (variableType.Success)
|
|
|
+ fieldType = DFUtils.GetFieldType(variableType.Value);
|
|
|
+ fieldType ??= typeof(DFLayoutStringField);
|
|
|
+
|
|
|
+ var field = (Activator.CreateInstance(fieldType) as DFLayoutField)!;
|
|
|
+ field.Name = variableName.Value;
|
|
|
+
|
|
|
+ if (variableProps.Success)
|
|
|
+ {
|
|
|
+ if (field is DFLayoutOptionField option)
|
|
|
+ option.Properties.Options = variableProps.Value;
|
|
|
+ if (field is DFLayoutStringField text)
|
|
|
+ text.Properties.TextWrapping = style.WrapText;
|
|
|
+ // need to populate other variable types here
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ control = field;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ control = new DFLayoutLabel
|
|
|
+ {
|
|
|
+ Caption = cell.Content,
|
|
|
+ Style = CreateStyle(style)
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if(control is not null)
|
|
|
+ {
|
|
|
+ control.Row = cell.Row - firstRow + 1;
|
|
|
+ control.Column = cell.Column - firstCol + 1 - colOffset;
|
|
|
+ control.RowSpan = cell.RowSpan;
|
|
|
+ control.ColumnSpan = cell.ColumnSpan;
|
|
|
+ layout.Elements.Add(control);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return layout;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static DFLayoutTextStyle CreateStyle(ICellStyle style)
|
|
|
+ {
|
|
|
+ if (style == null)
|
|
|
+ return new DFLayoutTextStyle();
|
|
|
+
|
|
|
+ var result = new DFLayoutTextStyle
|
|
|
+ {
|
|
|
+ FontSize = style.Font.FontSize,
|
|
|
+
|
|
|
+ IsItalic = style.Font.Italic,
|
|
|
+ IsBold = style.Font.Bold,
|
|
|
+ Underline = style.Font.Underline switch
|
|
|
+ {
|
|
|
+ Scripting.UnderlineType.None => UnderlineType.None,
|
|
|
+ Scripting.UnderlineType.Single or Scripting.UnderlineType.SingleAccounting => UnderlineType.Single,
|
|
|
+ Scripting.UnderlineType.Double or Scripting.UnderlineType.DoubleAccounting => UnderlineType.Double,
|
|
|
+ _ => UnderlineType.None
|
|
|
+ },
|
|
|
+ BackgroundColour = style.Background,
|
|
|
+ ForegroundColour = style.Font.Colour,
|
|
|
+
|
|
|
+ HorizontalTextAlignment = style.HorizontalAlignment switch
|
|
|
+ {
|
|
|
+ CellAlignment.Middle => DFLayoutAlignment.Middle,
|
|
|
+ CellAlignment.End => DFLayoutAlignment.End,
|
|
|
+ CellAlignment.Justify => DFLayoutAlignment.Stretch,
|
|
|
+ _ => DFLayoutAlignment.Start
|
|
|
+ },
|
|
|
+
|
|
|
+ VerticalTextAlignment = style.VerticalAlignment switch
|
|
|
+ {
|
|
|
+ CellAlignment.Start => DFLayoutAlignment.Start,
|
|
|
+ CellAlignment.End => DFLayoutAlignment.End,
|
|
|
+ CellAlignment.Justify => DFLayoutAlignment.Stretch,
|
|
|
+ _ => DFLayoutAlignment.Middle
|
|
|
+ },
|
|
|
+
|
|
|
+ TextWrapping = style.WrapText
|
|
|
+
|
|
|
+ };
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+
|
|
|
+ #region Report Generator
|
|
|
+
|
|
|
+ public static Report? GenerateReport(DigitalFormLayout layout, DataModel model)
|
|
|
+ {
|
|
|
+
|
|
|
+ var report = ReportUtils.SetupReport(null, model, true);
|
|
|
+
|
|
|
+ var dfLayout = new DFLayout();
|
|
|
+ dfLayout.LoadLayout(layout.Layout);
|
|
|
+
|
|
|
+ var page = new ReportPage();
|
|
|
+ page.Name = "Page1";
|
|
|
+ page.PaperWidth = 210;
|
|
|
+ page.PaperHeight = 297;
|
|
|
+ page.Landscape = false;
|
|
|
+ page.LeftMargin = 10;
|
|
|
+ page.TopMargin = 10;
|
|
|
+ page.RightMargin = 10;
|
|
|
+ page.BottomMargin = 10;
|
|
|
+ report.Pages.Add(page);
|
|
|
+
|
|
|
+ DataBand band = new DataBand();
|
|
|
+ band.Name = "Data1";
|
|
|
+ band.Height = Units.Millimeters * (page.PaperHeight - (page.TopMargin + page.BottomMargin));
|
|
|
+ band.Width = Units.Millimeters * (page.PaperWidth - (page.LeftMargin + page.RightMargin));
|
|
|
+ band.PrintIfDatasourceEmpty = true;
|
|
|
+ band.DataSource = report.GetDataSource("Form_Data");
|
|
|
+ page.AddChild(band);
|
|
|
+
|
|
|
+ TableObject table = new TableObject();
|
|
|
+ table.ColumnCount = dfLayout.ColumnWidths.Count;
|
|
|
+ table.RowCount = dfLayout.RowHeights.Count;
|
|
|
+ ProcessColumnWidths(band.Width, dfLayout.ColumnWidths, table.Columns);
|
|
|
+ ProcessRowHeights(band.Height, dfLayout.RowHeights, table.Rows);
|
|
|
+ band.AddChild(table);
|
|
|
+ foreach(var element in dfLayout.Elements)
|
|
|
+ {
|
|
|
+ var row = table.Rows[element.Row-1];
|
|
|
+ var cell = row.ChildObjects[element.Column-1] as TableCell;
|
|
|
+ cell.Border.Lines = BorderLines.All;
|
|
|
+ cell.ColSpan = element.ColumnSpan;
|
|
|
+ cell.RowSpan = element.RowSpan;
|
|
|
+ if (element is DFLayoutField field)
|
|
|
+ {
|
|
|
+ cell.Text = $"[Form_Data.{field.Name}]";
|
|
|
+ cell.Font = new System.Drawing.Font(cell.Font.FontFamily, 8F);
|
|
|
+ }
|
|
|
+ else if (element is DFLayoutLabel label)
|
|
|
+ {
|
|
|
+ cell.Text = label.Description;
|
|
|
+ ApplyStyle(cell, label.Style);
|
|
|
+ }
|
|
|
+ else if (element is DFLayoutHeader header)
|
|
|
+ {
|
|
|
+ cell.Text = header.Header;
|
|
|
+ ApplyStyle(cell, header.Style);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return report;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ApplyStyle(TableCell cell, DFLayoutTextStyle style)
|
|
|
+ {
|
|
|
+ cell.FillColor = style.BackgroundColour;
|
|
|
+ FontStyle fontstyle = System.Drawing.FontStyle.Regular;
|
|
|
+ if (style.IsBold)
|
|
|
+ fontstyle = fontstyle | System.Drawing.FontStyle.Bold;
|
|
|
+ if (style.IsItalic)
|
|
|
+ fontstyle = fontstyle | System.Drawing.FontStyle.Italic;
|
|
|
+ float fontsize = (float)style.FontSize * 8F / 12F;
|
|
|
+ fontsize = fontsize == 0F ? 8F : fontsize;
|
|
|
+ cell.Font = new System.Drawing.Font(cell.Font.FontFamily, fontsize, fontstyle);
|
|
|
+ cell.HorzAlign = style.HorizontalTextAlignment switch
|
|
|
+ {
|
|
|
+ DFLayoutAlignment.Start => HorzAlign.Left,
|
|
|
+ DFLayoutAlignment.Middle => HorzAlign.Center,
|
|
|
+ DFLayoutAlignment.End => HorzAlign.Right,
|
|
|
+ DFLayoutAlignment.Stretch => HorzAlign.Justify
|
|
|
+ };
|
|
|
+ cell.VertAlign = style.VerticalTextAlignment switch
|
|
|
+ {
|
|
|
+ DFLayoutAlignment.Start => VertAlign.Top,
|
|
|
+ DFLayoutAlignment.Middle => VertAlign.Center,
|
|
|
+ DFLayoutAlignment.End => VertAlign.Bottom,
|
|
|
+ DFLayoutAlignment.Stretch => VertAlign.Center
|
|
|
+ };
|
|
|
+ cell.WordWrap = style.TextWrapping;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ProcessRowHeights(float bandheight, List<string> values, TableRowCollection rows)
|
|
|
+ {
|
|
|
+ float fixedtotal = 0F;
|
|
|
+ for (int iFixed = 0; iFixed < values.Count; iFixed++)
|
|
|
+ {
|
|
|
+ if (!values[iFixed].Contains("*"))
|
|
|
+ {
|
|
|
+ if (!float.TryParse(values[iFixed], out float value))
|
|
|
+ value = 4F;
|
|
|
+ rows[iFixed].Height = Units.Millimeters * value;
|
|
|
+ fixedtotal += Units.Millimeters * value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Dictionary<int, float> starvalues = new Dictionary<int, float>();
|
|
|
+ float startotal = 0F;
|
|
|
+ for (int iStar = 0; iStar < values.Count; iStar++)
|
|
|
+ {
|
|
|
+ float fstartotal = 0F;
|
|
|
+ if (values[iStar].Contains("*"))
|
|
|
+ {
|
|
|
+ if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
|
|
|
+ value += 1;
|
|
|
+ starvalues[iStar] = value;
|
|
|
+ startotal += value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach (var key in starvalues.Keys)
|
|
|
+ rows[key].Height = (starvalues[key] / startotal) * (bandheight - fixedtotal);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ProcessColumnWidths(float bandwidth, List<string> values, TableColumnCollection columns)
|
|
|
+ {
|
|
|
+ float fixedtotal = 0F;
|
|
|
+ for (int iFixed = 0; iFixed < values.Count; iFixed++)
|
|
|
+ {
|
|
|
+ if (!values[iFixed].Contains("*"))
|
|
|
+ {
|
|
|
+ if (!float.TryParse(values[iFixed], out float value))
|
|
|
+ value = 20F;
|
|
|
+ columns[iFixed].Width = Units.Millimeters * value;
|
|
|
+ fixedtotal += Units.Millimeters * value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Dictionary<int, float> starvalues = new Dictionary<int, float>();
|
|
|
+ float startotal = 0F;
|
|
|
+ for (int iStar = 0; iStar < values.Count; iStar++)
|
|
|
+ {
|
|
|
+ float fstartotal = 0F;
|
|
|
+ if (values[iStar].Contains("*"))
|
|
|
+ {
|
|
|
+ if (!float.TryParse(values[iStar].Replace("*", ""), out float value))
|
|
|
+ value += 1;
|
|
|
+ starvalues[iStar] = value;
|
|
|
+ startotal += value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var key in starvalues.Keys)
|
|
|
+ columns[key].Width = (starvalues[key] / startotal) * (bandwidth - fixedtotal);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Data Model
|
|
|
+
|
|
|
+ private static List<Type>? _entityForms;
|
|
|
+ public static DataModel? GetDataModel(String appliesto, IEnumerable<DigitalFormVariable> variables)
|
|
|
+ {
|
|
|
+ _entityForms ??= CoreUtils.Entities
|
|
|
+ .Where(x => x.IsSubclassOfRawGeneric(typeof(EntityForm<,>)))
|
|
|
+ .ToList();
|
|
|
+ var entityForm = _entityForms
|
|
|
+ .Where(x => x.GetSuperclassDefinition(typeof(EntityForm<,>))?.GenericTypeArguments[0].Name == appliesto)
|
|
|
+ .FirstOrDefault();
|
|
|
+ if(entityForm is not null)
|
|
|
+ {
|
|
|
+ var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(entityForm), Filter.Create(entityForm).None(), null) as DataModel)!;
|
|
|
+ (model as IDigitalFormReportDataModel)!.Variables = variables.ToArray();
|
|
|
+ return model;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|