| | 1 | | using ValidateLib.Metadata.Descriptors; |
| | 2 | | using ValidateLib.Metadata.Properties; |
| | 3 | | using ValidateLib.TabularData.AnnotatedTabularDataModel; |
| | 4 | | using ValidateLib.TabularData.Parsing; |
| | 5 | |
|
| | 6 | | namespace ValidateLib.TabularData.Validation |
| | 7 | | { |
| | 8 | | /// <summary> |
| | 9 | | /// Creates annotated tabular data model needed for parsing and validation |
| | 10 | | /// of tabular data files. |
| | 11 | | /// </summary> |
| | 12 | | public class TabularDataAnnotator |
| | 13 | | { |
| 1 | 14 | | protected Flags flags { get; set; } |
| | 15 | |
|
| 1 | 16 | | public TabularDataAnnotator(Flags flags) |
| | 17 | | { |
| 1 | 18 | | this.flags = flags; |
| 1 | 19 | | } |
| | 20 | |
|
| | 21 | | public void AnnotateTable(Table table, TableDescriptor tableDescriptor) |
| | 22 | | { |
| 1 | 23 | | tableDescriptor.table = table; |
| 1 | 24 | | table.tableDirection = tableDescriptor.tableDirection._value!; |
| 1 | 25 | | table.notes = tableDescriptor.notes is null ? new List<NoteDescriptor>() : tableDescriptor.notes._value!; |
| 1 | 26 | | table.suppressOutput = tableDescriptor.suppressOutput is null ? false : tableDescriptor.suppressOutput._valu |
| 1 | 27 | | table.transformations = tableDescriptor.transformations is null ? null : tableDescriptor.transformations._va |
| 1 | 28 | | table.url = tableDescriptor.url is null ? null : tableDescriptor.url._value; |
| | 29 | |
|
| 1 | 30 | | if (tableDescriptor.tableSchema is null || tableDescriptor.tableSchema._value is null) |
| 1 | 31 | | return; |
| 1 | 32 | | SchemaDescriptor tableSchema = tableDescriptor.tableSchema._value; |
| 1 | 33 | | table.foreignKeys = tableSchema.foreignKeys is null ? new List<ForeignKeyDescriptor>() : tableSchema.foreign |
| 1 | 34 | | table.id = tableSchema.id is null ? null : tableSchema.id._value; |
| 1 | 35 | | table.schema = tableSchema; |
| 1 | 36 | | } |
| | 37 | |
|
| | 38 | | /// <summary> |
| | 39 | | /// Creates annotated columns before actually parsing the tabular file only from metadata file. |
| | 40 | | /// We need to do this beforehand because we need to know which cells to save to columns for later |
| | 41 | | /// primary and foreign keys checks. |
| | 42 | | /// </summary> |
| | 43 | | /// <param name="table"></param> |
| | 44 | | /// <param name="tableDescriptor"></param> |
| | 45 | | /// <exception cref="ArgumentException"> Occurs when this is called twice on the same table </exception> |
| | 46 | | public void CreateAnnotatedColumns(Table table, TableDescriptor tableDescriptor) |
| | 47 | | { |
| 1 | 48 | | if (tableDescriptor.tableSchema is null || |
| 1 | 49 | | tableDescriptor.tableSchema._value is null || |
| 1 | 50 | | tableDescriptor.tableSchema._value.columns is null || |
| 1 | 51 | | tableDescriptor.tableSchema._value.columns._value is null) return; |
| | 52 | |
|
| 1 | 53 | | var columns = tableDescriptor.tableSchema._value.columns._value; |
| | 54 | |
|
| 1 | 55 | | if (table.columns.Count != 0) |
| 0 | 56 | | throw new ArgumentException("CreateAnnotatedColumns called twice"); |
| | 57 | |
|
| 1 | 58 | | int sourceColumnNumber = 1 + flags.skipColumns; |
| 1 | 59 | | List<Column> annotatedColumns = new List<Column>(); |
| 1 | 60 | | for (int i = flags.skipColumns; i < columns.Count; i++) |
| | 61 | | { |
| 1 | 62 | | int iFromAlgorithm = i - flags.skipColumns + 1; |
| 1 | 63 | | ColumnDescriptor columnDescriptor = columns[i]; |
| | 64 | |
|
| | 65 | |
|
| 1 | 66 | | Column column = new Column(table, iFromAlgorithm, sourceColumnNumber) |
| 1 | 67 | | { |
| 1 | 68 | | name = columnDescriptor.name is null ? null : columnDescriptor!.name!._value!, |
| 1 | 69 | | titles = columnDescriptor.titles is null ? new Dictionary<string, string[]>() : columnDescriptor.tit |
| 1 | 70 | | _virtual = columnDescriptor._virtual is null ? false : columnDescriptor._virtual._value, |
| 1 | 71 | | suppresOutput = columnDescriptor.suppresOutput is null ? false : columnDescriptor.suppresOutput._val |
| 1 | 72 | | datatype = columnDescriptor.datatype is null ? new DatatypeDescriptor() : columnDescriptor.datatype. |
| 1 | 73 | | _default = columnDescriptor._default is null ? "" : columnDescriptor._default._value, |
| 1 | 74 | | lang = columnDescriptor.lang is null ? "und" : columnDescriptor.lang._value, |
| 1 | 75 | | _null = columnDescriptor._null is null ? new List<string> { "" } : columnDescriptor._null._value!, |
| 1 | 76 | | ordered = columnDescriptor.ordered is null ? false : columnDescriptor.ordered._value, |
| 1 | 77 | | required = columnDescriptor.required is null ? false : columnDescriptor.required._value, |
| 1 | 78 | | separator = columnDescriptor.separator is null ? null : columnDescriptor.separator._value, |
| 1 | 79 | | textDirection = columnDescriptor.textDirection is null ? "auto" : columnDescriptor.textDirection._va |
| 1 | 80 | | aboutURL = columnDescriptor.aboutURL is null ? null : columnDescriptor.aboutURL, |
| 1 | 81 | | propertyURL = columnDescriptor.propertyUrl is null ? null : columnDescriptor.propertyUrl, |
| 1 | 82 | | valueURL = columnDescriptor.valueUrl is null ? null : columnDescriptor.valueUrl, |
| 1 | 83 | | }; |
| | 84 | |
|
| 1 | 85 | | annotatedColumns.Add(column); |
| 1 | 86 | | sourceColumnNumber++; |
| | 87 | | } |
| | 88 | |
|
| 1 | 89 | | var tableSchemaDescriptor = tableDescriptor.tableSchema._value; |
| 1 | 90 | | if (tableSchemaDescriptor.primaryKey is not null) |
| 1 | 91 | | SetPrimaryKeysForColumns(tableSchemaDescriptor.primaryKey, annotatedColumns); |
| | 92 | |
|
| 1 | 93 | | if (tableSchemaDescriptor.foreignKeys is not null && tableSchemaDescriptor.foreignKeys._value is not null) |
| 1 | 94 | | SetForeignKeysForColumns(tableSchemaDescriptor.foreignKeys._value, annotatedColumns); |
| | 95 | |
|
| 1 | 96 | | table.columns = annotatedColumns; |
| 1 | 97 | | } |
| | 98 | | /// <summary> |
| | 99 | | /// Annotates one row in tabular data model. |
| | 100 | | /// </summary> |
| | 101 | | /// <param name="row"></param> |
| | 102 | | /// <param name="tableDescriptor"></param> |
| | 103 | | public void AnnotateRow(Row row, TableDescriptor tableDescriptor) |
| | 104 | | { |
| 1 | 105 | | if (tableDescriptor.tableSchema is null || |
| 1 | 106 | | tableDescriptor.tableSchema._value is null || |
| 1 | 107 | | tableDescriptor.tableSchema._value.columns is null || |
| 1 | 108 | | tableDescriptor.tableSchema._value.columns._value is null) return; |
| | 109 | |
|
| | 110 | | // the row can have less columns which would lead to an error during the validation |
| 1 | 111 | | int columnsCount = Math.Min(row.table.columns.Count, row.cells.Count); |
| | 112 | |
|
| 1 | 113 | | row.table = tableDescriptor.table!; |
| 1 | 114 | | row.primaryKeyDescriptor = tableDescriptor?.tableSchema?._value?.primaryKey; |
| | 115 | | // we expect that the columns are created based on the table descriptor so the number |
| | 116 | | // of columns should be the same |
| 1 | 117 | | if (tableDescriptor.tableSchema._value.columns._value.Count != row.table.columns.Count) |
| 0 | 118 | | throw new ArgumentException("Wrong usage of annotate row"); |
| | 119 | |
|
| 1 | 120 | | for (int i = 0; i < columnsCount; i++) |
| | 121 | | { |
| 1 | 122 | | Cell cell = row.cells[i]; |
| 1 | 123 | | AnnotateCell(cell, tableDescriptor.tableSchema._value.columns._value[i]); |
| | 124 | | } |
| 1 | 125 | | } |
| | 126 | |
|
| | 127 | | protected void AnnotateCell(Cell cell, ColumnDescriptor columnDescriptor) |
| | 128 | | { |
| | 129 | | // add cell for later primary and foreign key checks |
| 1 | 130 | | if (cell.column!.isPartOfKey()) |
| 1 | 131 | | cell.column!.cells.Add(cell); |
| | 132 | |
|
| 1 | 133 | | cell.aboutURL = columnDescriptor.aboutURL is null ? null : columnDescriptor.aboutURL; |
| 1 | 134 | | cell.ordered = columnDescriptor.ordered is null ? false : columnDescriptor.ordered._value; |
| 1 | 135 | | cell.propertyURL = columnDescriptor.aboutURL is null ? null : columnDescriptor.aboutURL; |
| 1 | 136 | | cell.textDirection = columnDescriptor.textDirection is null ? "auto" : columnDescriptor.textDirection._value |
| 1 | 137 | | cell.valueURL = columnDescriptor.valueUrl is null ? null : columnDescriptor.valueUrl; |
| 1 | 138 | | } |
| | 139 | |
|
| | 140 | |
|
| | 141 | | protected void SetPrimaryKeysForColumns(ColumnReferenceProperty primaryKey, List<Column> columns) |
| | 142 | | { |
| 1 | 143 | | if (primaryKey._value is null) |
| 0 | 144 | | return; |
| 1 | 145 | | List<string> primaryKeyVal = primaryKey._value; |
| | 146 | |
|
| 1 | 147 | | if (primaryKeyVal.Count == 0) |
| 0 | 148 | | return; |
| | 149 | |
|
| 1 | 150 | | foreach (Column column in columns) |
| | 151 | | { |
| 1 | 152 | | if (column.name is not null && primaryKeyVal.Contains(column.name)) |
| | 153 | | { |
| 1 | 154 | | column.isPartOfPrimaryKey = true; |
| | 155 | | } |
| | 156 | | } |
| 1 | 157 | | } |
| | 158 | |
|
| | 159 | | protected void SetForeignKeysForColumns(List<ForeignKeyDescriptor> foreignKeys, List<Column> columns) |
| | 160 | | { |
| 1 | 161 | | if (foreignKeys.Count == 0) return; |
| | 162 | |
|
| 1 | 163 | | foreach (var column in columns) |
| | 164 | | { |
| 1 | 165 | | if (IsPartOfForeingKey(column.name, foreignKeys)) |
| 1 | 166 | | column.isPartOfForeignKey = true; |
| | 167 | |
|
| | 168 | | } |
| 1 | 169 | | } |
| | 170 | |
|
| | 171 | | protected bool IsPartOfForeingKey(string? columnName, List<ForeignKeyDescriptor> foreignKeys) |
| | 172 | | { |
| 1 | 173 | | if (columnName is null) return false; |
| | 174 | |
|
| 1 | 175 | | foreach (ForeignKeyDescriptor foreignKeyDescriptor in foreignKeys) |
| | 176 | | { |
| 1 | 177 | | if ( |
| 1 | 178 | | foreignKeyDescriptor.columnReference is not null |
| 1 | 179 | | && foreignKeyDescriptor.columnReference._value is not null |
| 1 | 180 | | ) |
| | 181 | | { |
| 1 | 182 | | if (foreignKeyDescriptor.columnReference._value.Contains(columnName)) |
| 1 | 183 | | return true; |
| | 184 | | } |
| | 185 | | } |
| | 186 | |
|
| 1 | 187 | | return false; |
| 1 | 188 | | } |
| | 189 | | } |
| | 190 | | } |