@joebigelow / wix-1 / commits / 9317f7c8

Redesign CustomTable tuples to support resolving binary columns

Rob Mensching committed Jun 3, 2020 at 02:19 UTC 9317f7c8ea709da55e4602eaaba06952bbf315b7
15 files changed +731 -515
src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+14 -13
@@ -281,20 +281,20 @@ namespace WixToolset.Core.Burn.Bundles
281 }
282
283 var dataTablesById = this.Section.Tuples.OfType<WixCustomTableTuple>()
284 - .Where(t => t.Unreal && t.Id != null)
285 - .ToDictionary(t => t.Id.Id);
286 - var dataRowsByTable = this.Section.Tuples.OfType<WixCustomRowTuple>()
287 - .GroupBy(t => t.Table);
288 - foreach (var tableDataRows in dataRowsByTable)
284 + .Where(t => t.Unreal && t.Id != null)
285 + .ToDictionary(t => t.Id.Id);
286 + var cellsByTable = this.Section.Tuples.OfType<WixCustomTableCellTuple>()
287 + .GroupBy(t => t.TableRef);
288 + foreach (var tableCells in cellsByTable)
289 {
290 - var tableName = tableDataRows.Key;
290 + var tableName = tableCells.Key;
291 if (!dataTablesById.TryGetValue(tableName, out var tableTuple))
292 {
293 // This should have been a linker error.
294 continue;
295 }
296
297 - var columnNames = tableTuple.ColumnNames.Split('\t');
297 + var columnNames = tableTuple.ColumnNamesSeparated;
298
299 // We simply assert that the table (and field) name is valid, because
300 // this is up to the extension developer to get right. An author will
@@ -307,17 +307,18 @@ namespace WixToolset.Core.Burn.Bundles
307 }
308 #endif // DEBUG
309
310 - foreach (var rowTuple in tableDataRows)
310 + foreach (var rowCells in tableCells.GroupBy(t => t.RowId))
311 {
312 + var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data);
313 +
314 writer.WriteStartElement(tableName);
315
314 - //var rowFields = rowTuple.FieldDataSeparated;
315 - foreach (var field in rowTuple.FieldDataSeparated)
316 + // Write all row data as attributes in table column order.
317 + foreach (var column in columnNames)
318 {
317 - var splitField = field.Split(ColonCharacter, 2);
318 - if (splitField.Length == 2)
319 + if (rowDataByColumn.TryGetValue(column, out var data))
320 {
320 - writer.WriteAttributeString(splitField[0], splitField[1]);
321 + writer.WriteAttributeString(column, data);
322 }
323 }
324
src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+228 -219
@@ -18,8 +18,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
18 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
19 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
20
21 - private static readonly char[] ColonCharacter = new[] { ':' };
22 -
21 public CreateOutputFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IWindowsInstallerBackendHelper backendHelper)
22 {
23 this.Messaging = messaging;
@@ -54,171 +52,174 @@ namespace WixToolset.Core.WindowsInstaller.Bind
52
53 private void AddSectionToOutput()
54 {
55 + var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellTuple>>();
56 +
57 foreach (var tuple in this.Section.Tuples)
58 {
59 var unknownTuple = false;
60 switch (tuple.Definition.Type)
61 {
62 - case TupleDefinitionType.AppSearch:
63 - this.AddTupleDefaultly(tuple);
64 - this.Output.EnsureTable(this.TableDefinitions["Signature"]);
65 - break;
62 + case TupleDefinitionType.AppSearch:
63 + this.AddTupleDefaultly(tuple);
64 + this.Output.EnsureTable(this.TableDefinitions["Signature"]);
65 + break;
66
67 - case TupleDefinitionType.Assembly:
68 - this.AddAssemblyTuple((AssemblyTuple)tuple);
69 - break;
67 + case TupleDefinitionType.Assembly:
68 + this.AddAssemblyTuple((AssemblyTuple)tuple);
69 + break;
70
71 - case TupleDefinitionType.BBControl:
72 - this.AddBBControlTuple((BBControlTuple)tuple);
73 - break;
71 + case TupleDefinitionType.BBControl:
72 + this.AddBBControlTuple((BBControlTuple)tuple);
73 + break;
74
75 - case TupleDefinitionType.Class:
76 - this.AddClassTuple((ClassTuple)tuple);
77 - break;
75 + case TupleDefinitionType.Class:
76 + this.AddClassTuple((ClassTuple)tuple);
77 + break;
78
79 - case TupleDefinitionType.Control:
80 - this.AddControlTuple((ControlTuple)tuple);
81 - break;
79 + case TupleDefinitionType.Control:
80 + this.AddControlTuple((ControlTuple)tuple);
81 + break;
82
83 - case TupleDefinitionType.Component:
84 - this.AddComponentTuple((ComponentTuple)tuple);
85 - break;
83 + case TupleDefinitionType.Component:
84 + this.AddComponentTuple((ComponentTuple)tuple);
85 + break;
86
87 - case TupleDefinitionType.CustomAction:
88 - this.AddCustomActionTuple((CustomActionTuple)tuple);
89 - break;
87 + case TupleDefinitionType.CustomAction:
88 + this.AddCustomActionTuple((CustomActionTuple)tuple);
89 + break;
90
91 - case TupleDefinitionType.Dialog:
92 - this.AddDialogTuple((DialogTuple)tuple);
93 - break;
91 + case TupleDefinitionType.Dialog:
92 + this.AddDialogTuple((DialogTuple)tuple);
93 + break;
94
95 - case TupleDefinitionType.Directory:
96 - this.AddDirectoryTuple((DirectoryTuple)tuple);
97 - break;
95 + case TupleDefinitionType.Directory:
96 + this.AddDirectoryTuple((DirectoryTuple)tuple);
97 + break;
98
99 - case TupleDefinitionType.Environment:
100 - this.AddEnvironmentTuple((EnvironmentTuple)tuple);
101 - break;
99 + case TupleDefinitionType.Environment:
100 + this.AddEnvironmentTuple((EnvironmentTuple)tuple);
101 + break;
102
103 - case TupleDefinitionType.Error:
104 - this.AddErrorTuple((ErrorTuple)tuple);
105 - break;
103 + case TupleDefinitionType.Error:
104 + this.AddErrorTuple((ErrorTuple)tuple);
105 + break;
106
107 - case TupleDefinitionType.Feature:
108 - this.AddFeatureTuple((FeatureTuple)tuple);
109 - break;
107 + case TupleDefinitionType.Feature:
108 + this.AddFeatureTuple((FeatureTuple)tuple);
109 + break;
110
111 - case TupleDefinitionType.File:
112 - this.AddFileTuple((FileTuple)tuple);
113 - break;
111 + case TupleDefinitionType.File:
112 + this.AddFileTuple((FileTuple)tuple);
113 + break;
114
115 - case TupleDefinitionType.IniFile:
116 - this.AddIniFileTuple((IniFileTuple)tuple);
117 - break;
115 + case TupleDefinitionType.IniFile:
116 + this.AddIniFileTuple((IniFileTuple)tuple);
117 + break;
118
119 - case TupleDefinitionType.Media:
120 - this.AddMediaTuple((MediaTuple)tuple);
121 - break;
119 + case TupleDefinitionType.Media:
120 + this.AddMediaTuple((MediaTuple)tuple);
121 + break;
122
123 - case TupleDefinitionType.ModuleConfiguration:
124 - this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple);
125 - break;
123 + case TupleDefinitionType.ModuleConfiguration:
124 + this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple);
125 + break;
126
127 - case TupleDefinitionType.MsiEmbeddedUI:
128 - this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple);
129 - break;
127 + case TupleDefinitionType.MsiEmbeddedUI:
128 + this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple);
129 + break;
130
131 - case TupleDefinitionType.MsiServiceConfig:
132 - this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple);
133 - break;
131 + case TupleDefinitionType.MsiServiceConfig:
132 + this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple);
133 + break;
134
135 - case TupleDefinitionType.MsiServiceConfigFailureActions:
136 - this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple);
137 - break;
135 + case TupleDefinitionType.MsiServiceConfigFailureActions:
136 + this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple);
137 + break;
138
139 - case TupleDefinitionType.MoveFile:
140 - this.AddMoveFileTuple((MoveFileTuple)tuple);
141 - break;
139 + case TupleDefinitionType.MoveFile:
140 + this.AddMoveFileTuple((MoveFileTuple)tuple);
141 + break;
142
143 - case TupleDefinitionType.ProgId:
144 - this.AddTupleDefaultly(tuple);
145 - this.Output.EnsureTable(this.TableDefinitions["Extension"]);
146 - break;
143 + case TupleDefinitionType.ProgId:
144 + this.AddTupleDefaultly(tuple);
145 + this.Output.EnsureTable(this.TableDefinitions["Extension"]);
146 + break;
147
148 - case TupleDefinitionType.Property:
149 - this.AddPropertyTuple((PropertyTuple)tuple);
150 - break;
148 + case TupleDefinitionType.Property:
149 + this.AddPropertyTuple((PropertyTuple)tuple);
150 + break;
151
152 - case TupleDefinitionType.RemoveFile:
153 - this.AddRemoveFileTuple((RemoveFileTuple)tuple);
154 - break;
152 + case TupleDefinitionType.RemoveFile:
153 + this.AddRemoveFileTuple((RemoveFileTuple)tuple);
154 + break;
155
156 - case TupleDefinitionType.Registry:
157 - this.AddRegistryTuple((RegistryTuple)tuple);
158 - break;
156 + case TupleDefinitionType.Registry:
157 + this.AddRegistryTuple((RegistryTuple)tuple);
158 + break;
159
160 - case TupleDefinitionType.RegLocator:
161 - this.AddRegLocatorTuple((RegLocatorTuple)tuple);
162 - break;
160 + case TupleDefinitionType.RegLocator:
161 + this.AddRegLocatorTuple((RegLocatorTuple)tuple);
162 + break;
163
164 - case TupleDefinitionType.RemoveRegistry:
165 - this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple);
166 - break;
164 + case TupleDefinitionType.RemoveRegistry:
165 + this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple);
166 + break;
167
168 - case TupleDefinitionType.ServiceControl:
169 - this.AddServiceControlTuple((ServiceControlTuple)tuple);
170 - break;
168 + case TupleDefinitionType.ServiceControl:
169 + this.AddServiceControlTuple((ServiceControlTuple)tuple);
170 + break;
171
172 - case TupleDefinitionType.ServiceInstall:
173 - this.AddServiceInstallTuple((ServiceInstallTuple)tuple);
174 - break;
172 + case TupleDefinitionType.ServiceInstall:
173 + this.AddServiceInstallTuple((ServiceInstallTuple)tuple);
174 + break;
175
176 - case TupleDefinitionType.Shortcut:
177 - this.AddShortcutTuple((ShortcutTuple)tuple);
178 - break;
176 + case TupleDefinitionType.Shortcut:
177 + this.AddShortcutTuple((ShortcutTuple)tuple);
178 + break;
179
180 - case TupleDefinitionType.TextStyle:
181 - this.AddTextStyleTuple((TextStyleTuple)tuple);
182 - break;
180 + case TupleDefinitionType.TextStyle:
181 + this.AddTextStyleTuple((TextStyleTuple)tuple);
182 + break;
183
184 - case TupleDefinitionType.Upgrade:
185 - this.AddUpgradeTuple((UpgradeTuple)tuple);
186 - break;
184 + case TupleDefinitionType.Upgrade:
185 + this.AddUpgradeTuple((UpgradeTuple)tuple);
186 + break;
187
188 - case TupleDefinitionType.WixAction:
189 - this.AddWixActionTuple((WixActionTuple)tuple);
190 - break;
188 + case TupleDefinitionType.WixAction:
189 + this.AddWixActionTuple((WixActionTuple)tuple);
190 + break;
191
192 - case TupleDefinitionType.WixMediaTemplate:
193 - this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple);
194 - break;
192 + case TupleDefinitionType.WixMediaTemplate:
193 + this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple);
194 + break;
195
196 - case TupleDefinitionType.WixCustomRow:
197 - this.AddWixCustomRowTuple((WixCustomRowTuple)tuple);
198 - break;
196 + case TupleDefinitionType.WixCustomTableCell:
197 + this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId);
198 + break;
199
200 - case TupleDefinitionType.WixEnsureTable:
201 - this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple);
202 - break;
200 + case TupleDefinitionType.WixEnsureTable:
201 + this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple);
202 + break;
203
204 - // ignored.
205 - case TupleDefinitionType.WixComponentGroup:
206 - case TupleDefinitionType.WixDeltaPatchFile:
207 - case TupleDefinitionType.WixFeatureGroup:
208 - case TupleDefinitionType.WixPatchBaseline:
204 + // ignored.
205 + case TupleDefinitionType.WixComponentGroup:
206 + case TupleDefinitionType.WixDeltaPatchFile:
207 + case TupleDefinitionType.WixFeatureGroup:
208 + case TupleDefinitionType.WixPatchBaseline:
209 break;
210
211 - // Already processed.
212 - case TupleDefinitionType.WixCustomTable:
213 - break;
211 + // Already processed by LoadTableDefinitions.
212 + case TupleDefinitionType.WixCustomTable:
213 + case TupleDefinitionType.WixCustomTableColumn:
214 + break;
215
215 - case TupleDefinitionType.MustBeFromAnExtension:
216 - unknownTuple = !this.AddTupleFromExtension(tuple);
217 - break;
216 + case TupleDefinitionType.MustBeFromAnExtension:
217 + unknownTuple = !this.AddTupleFromExtension(tuple);
218 + break;
219
219 - default:
220 - unknownTuple = !this.AddTupleDefaultly(tuple);
221 - break;
220 + default:
221 + unknownTuple = !this.AddTupleDefaultly(tuple);
222 + break;
223 }
224
225 if (unknownTuple)
@@ -226,6 +227,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
227 this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple));
228 }
229 }
230 +
231 + this.AddIndexedCellTuples(cellsByTableAndRowId);
232 }
233
234 private void AddAssemblyTuple(AssemblyTuple tuple)
@@ -383,16 +386,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
386 private void AddDialogTuple(DialogTuple tuple)
387 {
388 var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0;
386 - attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0;
387 - attributes|= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0;
388 - attributes|= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette: 0;
389 - attributes|= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0;
390 - attributes|= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0;
391 - attributes|= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0;
392 - attributes|= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0;
393 - attributes|= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0;
394 - attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0;
395 - attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0;
389 + attributes |= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0;
390 + attributes |= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0;
391 + attributes |= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0;
392 + attributes |= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0;
393 + attributes |= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0;
394 + attributes |= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0;
395 + attributes |= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0;
396 + attributes |= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0;
397 + attributes |= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0;
398 + attributes |= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0;
399
400 var row = this.CreateRow(tuple, "Dialog");
401 row[0] = tuple.Id.Id;
@@ -419,7 +422,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
422 targetName = ".";
423 }
424
422 - var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ;
425 + var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName;
426
427 var row = this.CreateRow(tuple, "Directory");
428 row[0] = tuple.Id.Id;
@@ -436,25 +439,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
439
440 switch (tuple.Action)
441 {
439 - case EnvironmentActionType.Create:
440 - action = "+";
441 - break;
442 - case EnvironmentActionType.Set:
443 - action = "=";
444 - break;
445 - case EnvironmentActionType.Remove:
446 - action = "!";
447 - break;
442 + case EnvironmentActionType.Create:
443 + action = "+";
444 + break;
445 + case EnvironmentActionType.Set:
446 + action = "=";
447 + break;
448 + case EnvironmentActionType.Remove:
449 + action = "!";
450 + break;
451 }
452
453 switch (tuple.Part)
454 {
452 - case EnvironmentPartType.First:
453 - value = String.Concat(value, tuple.Separator, "[~]");
454 - break;
455 - case EnvironmentPartType.Last:
456 - value = String.Concat("[~]", tuple.Separator, value);
457 - break;
455 + case EnvironmentPartType.First:
456 + value = String.Concat(value, tuple.Separator, "[~]");
457 + break;
458 + case EnvironmentPartType.Last:
459 + value = String.Concat("[~]", tuple.Separator, value);
460 + break;
461 }
462
463 var row = this.CreateRow(tuple, "Environment");
@@ -661,40 +664,40 @@ namespace WixToolset.Core.WindowsInstaller.Bind
664
665 switch (tuple.ValueType)
666 {
664 - case RegistryValueType.Binary:
665 - value = String.Concat("#x", value);
666 - break;
667 - case RegistryValueType.Expandable:
668 - value = String.Concat("#%", value);
669 - break;
670 - case RegistryValueType.Integer:
671 - value = String.Concat("#", value);
672 - break;
673 - case RegistryValueType.MultiString:
674 - switch (tuple.ValueAction)
675 - {
676 - case RegistryValueActionType.Append:
677 - value = String.Concat("[~]", value);
667 + case RegistryValueType.Binary:
668 + value = String.Concat("#x", value);
669 break;
679 - case RegistryValueActionType.Prepend:
680 - value = String.Concat(value, "[~]");
670 + case RegistryValueType.Expandable:
671 + value = String.Concat("#%", value);
672 break;
682 - case RegistryValueActionType.Write:
683 - default:
684 - if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
673 + case RegistryValueType.Integer:
674 + value = String.Concat("#", value);
675 + break;
676 + case RegistryValueType.MultiString:
677 + switch (tuple.ValueAction)
678 {
686 - value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value);
679 + case RegistryValueActionType.Append:
680 + value = String.Concat("[~]", value);
681 + break;
682 + case RegistryValueActionType.Prepend:
683 + value = String.Concat(value, "[~]");
684 + break;
685 + case RegistryValueActionType.Write:
686 + default:
687 + if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
688 + {
689 + value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value);
690 + }
691 + break;
692 + }
693 + break;
694 + case RegistryValueType.String:
695 + // escape the leading '#' character for string registry keys
696 + if (null != value && value.StartsWith("#", StringComparison.Ordinal))
697 + {
698 + value = String.Concat("#", value);
699 }
700 break;
689 - }
690 - break;
691 - case RegistryValueType.String:
692 - // escape the leading '#' character for string registry keys
693 - if (null != value && value.StartsWith("#", StringComparison.Ordinal))
694 - {
695 - value = String.Concat("#", value);
696 - }
697 - break;
701 }
702
703 var row = this.CreateRow(tuple, "Registry");
@@ -757,7 +760,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
760 row[3] = tuple.Arguments;
761 if (tuple.Wait.HasValue)
762 {
760 - row[4] = tuple.Wait.Value ? 1 : 0;
763 + row[4] = tuple.Wait.Value ? 1 : 0;
764 }
765 row[5] = tuple.ComponentRef;
766 }
@@ -938,83 +941,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind
941 row[2] = tuple.Sequence;
942 }
943 }
941 -
942 - private void AddWixCustomRowTuple(WixCustomRowTuple tuple)
943 - {
944 - var customTableDefinition = this.TableDefinitions[tuple.Table];
944
946 - if (customTableDefinition.Unreal)
945 + private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary<string, List<WixCustomTableCellTuple>> cellsByTableAndRowId)
946 + {
947 + var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId;
948 + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells))
949 {
948 -
949 - return;
950 + cells = new List<WixCustomTableCellTuple>();
951 + cellsByTableAndRowId.Add(tableAndRowId, cells);
952 }
953
952 - var customRow = this.CreateRow(tuple, customTableDefinition);
954 + cells.Add(wixCustomTableCellTuple);
955 + }
956
954 -#if TODO // SectionId seems like a good thing to preserve.
955 - customRow.SectionId = tuple.SectionId;
956 -#endif
957 + private void AddIndexedCellTuples(Dictionary<string, List<WixCustomTableCellTuple>> cellsByTableAndRowId)
958 + {
959 + foreach (var rowOfCells in cellsByTableAndRowId.Values)
960 + {
961 + var firstCellTuple = rowOfCells[0];
962 + var customTableDefinition = this.TableDefinitions[firstCellTuple.TableRef];
963
958 - var data = tuple.FieldDataSeparated;
964 + if (customTableDefinition.Unreal)
965 + {
966 + return;
967 + }
968
960 - for (var i = 0; i < data.Length; ++i)
961 - {
962 - var foundColumn = false;
963 - var item = data[i].Split(ColonCharacter, 2);
969 + var customRow = this.CreateRow(firstCellTuple, customTableDefinition);
970 + var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name);
971
965 - for (var j = 0; j < customRow.Fields.Length; ++j)
972 +#if TODO // SectionId seems like a good thing to preserve.
973 + customRow.SectionId = tuple.SectionId;
974 +#endif
975 + foreach (var cell in rowOfCells)
976 {
967 - if (customRow.Fields[j].Column.Name == item[0])
977 + var data = cell.Data;
978 +
979 + if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField))
980 {
969 - if (0 < item[1].Length)
981 + if (!String.IsNullOrEmpty(data))
982 {
971 - if (ColumnType.Number == customRow.Fields[j].Column.Type)
983 + if (rowField.Column.Type == ColumnType.Number)
984 {
985 try
986 {
975 - customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture);
987 + rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture);
988 }
989 catch (FormatException)
990 {
979 - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1]));
991 + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data));
992 }
993 catch (OverflowException)
994 {
983 - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1]));
995 + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data));
996 }
997 }
986 - else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category)
998 + else if (rowField.Column.Category == ColumnCategory.Identifier)
999 {
988 - if (Common.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category)
1000 + if (Common.IsIdentifier(data) || Common.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category)
1001 {
990 - customRow.Fields[j].Data = item[1];
1002 + rowField.Data = data;
1003 }
1004 else
1005 {
994 - this.Messaging.Write(ErrorMessages.IllegalIdentifier(tuple.SourceLineNumbers, "Data", item[1]));
1006 + this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data));
1007 }
1008 }
1009 else
1010 {
999 - customRow.Fields[j].Data = item[1];
1011 + rowField.Data = data;
1012 }
1013 }
1002 - foundColumn = true;
1003 - break;
1014 + }
1015 + else
1016 + {
1017 + this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef));
1018 }
1019 }
1020
1007 - if (!foundColumn)
1008 - {
1009 - this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(tuple.SourceLineNumbers, item[0]));
1010 - }
1011 - }
1012 -
1013 - for (var i = 0; i < customTableDefinition.Columns.Length; ++i)
1014 - {
1015 - if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length))
1021 + for (var i = 0; i < customTableDefinition.Columns.Length; ++i)
1022 {
1017 - this.Messaging.Write(ErrorMessages.NoDataForColumn(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name));
1023 + if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length))
1024 + {
1025 + this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellTuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name));
1026 + }
1027 }
1028 }
1029 }
src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+4 -3
@@ -314,18 +314,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
314 break;
315
316 case ColumnType.Object:
317 - if (null != row[i])
317 + var path = row.FieldAsString(i);
318 + if (null != path)
319 {
320 needStream = true;
321 try
322 {
322 - record.SetStream(i + 1, row.FieldAsString(i));
323 + record.SetStream(i + 1, path);
324 }
325 catch (Win32Exception e)
326 {
327 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
328 {
328 - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(i)));
329 + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path));
330 }
331 else
332 {
src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs
+117 -140
@@ -32,11 +32,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
32 public TableDefinitionCollection Execute()
33 {
34 var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
35 + var customColumnsById = this.Section.Tuples.OfType<WixCustomTableColumnTuple>().ToDictionary(t => t.Id.Id);
36
36 - foreach (var tuple in this.Section.Tuples.OfType<WixCustomTableTuple>())
37 + if (customColumnsById.Any())
38 {
38 - var customTableDefinition = this.CreateCustomTable(tuple);
39 - tableDefinitions.Add(customTableDefinition);
39 + foreach (var tuple in this.Section.Tuples.OfType<WixCustomTableTuple>())
40 + {
41 + var customTableDefinition = this.CreateCustomTable(tuple, customColumnsById);
42 + tableDefinitions.Add(customTableDefinition);
43 + }
44 }
45
46 foreach (var backendExtension in this.BackendExtensions)
@@ -56,177 +60,150 @@ namespace WixToolset.Core.WindowsInstaller.Bind
60 return this.TableDefinitions;
61 }
62
59 - private TableDefinition CreateCustomTable(WixCustomTableTuple tuple)
63 + private TableDefinition CreateCustomTable(WixCustomTableTuple tuple, Dictionary<string, WixCustomTableColumnTuple> customColumnsById)
64 {
61 - var columnNames = tuple.ColumnNames.Split('\t');
62 - var columnTypes = tuple.ColumnTypes.Split('\t');
63 - var primaryKeys = tuple.PrimaryKeys.Split('\t');
64 - var minValues = tuple.MinValues?.Split('\t');
65 - var maxValues = tuple.MaxValues?.Split('\t');
66 - var keyTables = tuple.KeyTables?.Split('\t');
67 - var keyColumns = tuple.KeyColumns?.Split('\t');
68 - var categories = tuple.Categories?.Split('\t');
69 - var sets = tuple.Sets?.Split('\t');
70 - var descriptions = tuple.Descriptions?.Split('\t');
71 - var modularizations = tuple.Modularizations?.Split('\t');
72 -
73 - var currentPrimaryKey = 0;
74 -
65 + var columnNames = tuple.ColumnNamesSeparated;
66 var columns = new List<ColumnDefinition>(columnNames.Length);
76 - for (var i = 0; i < columnNames.Length; ++i)
67 +
68 + foreach (var name in columnNames)
69 {
78 - var name = columnNames[i];
70 + var column = customColumnsById[tuple.Id.Id + "/" + name];
71 +
72 var type = ColumnType.Unknown;
73
81 - if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase))
82 - {
83 - type = ColumnType.String;
84 - }
85 - else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase))
74 + if (column.Type == IntermediateFieldType.String)
75 {
87 - type = ColumnType.Localized;
76 + type = column.Localizable ? ColumnType.Localized : ColumnType.String;
77 }
89 - else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase))
78 + else if (column.Type == IntermediateFieldType.Number)
79 {
80 type = ColumnType.Number;
81 }
93 - else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase))
82 + else if (column.Type == IntermediateFieldType.Path)
83 {
84 type = ColumnType.Object;
85 }
86
98 - var nullable = columnTypes[i].Substring(0, 1) == columnTypes[i].Substring(0, 1).ToUpperInvariant();
99 - var length = Convert.ToInt32(columnTypes[i].Substring(1), CultureInfo.InvariantCulture);
100 -
101 - var primaryKey = false;
102 - if (currentPrimaryKey < primaryKeys.Length && primaryKeys[currentPrimaryKey] == columnNames[i])
103 - {
104 - primaryKey = true;
105 - currentPrimaryKey++;
106 - }
107 -
108 - var minValue = String.IsNullOrEmpty(minValues?[i]) ? (int?)null : Convert.ToInt32(minValues[i], CultureInfo.InvariantCulture);
109 - var maxValue = String.IsNullOrEmpty(maxValues?[i]) ? (int?)null : Convert.ToInt32(maxValues[i], CultureInfo.InvariantCulture);
110 - var keyColumn = String.IsNullOrEmpty(keyColumns?[i]) ? (int?)null : Convert.ToInt32(keyColumns[i], CultureInfo.InvariantCulture);
111 -
87 var category = ColumnCategory.Unknown;
113 - if (null != categories && null != categories[i] && 0 < categories[i].Length)
88 + switch (column.Category)
89 {
115 - switch (categories[i])
116 - {
117 - case "Text":
118 - category = ColumnCategory.Text;
119 - break;
120 - case "UpperCase":
121 - category = ColumnCategory.UpperCase;
122 - break;
123 - case "LowerCase":
124 - category = ColumnCategory.LowerCase;
125 - break;
126 - case "Integer":
127 - category = ColumnCategory.Integer;
128 - break;
129 - case "DoubleInteger":
130 - category = ColumnCategory.DoubleInteger;
131 - break;
132 - case "TimeDate":
133 - category = ColumnCategory.TimeDate;
134 - break;
135 - case "Identifier":
136 - category = ColumnCategory.Identifier;
137 - break;
138 - case "Property":
139 - category = ColumnCategory.Property;
140 - break;
141 - case "Filename":
142 - category = ColumnCategory.Filename;
143 - break;
144 - case "WildCardFilename":
145 - category = ColumnCategory.WildCardFilename;
146 - break;
147 - case "Path":
148 - category = ColumnCategory.Path;
149 - break;
150 - case "Paths":
151 - category = ColumnCategory.Paths;
152 - break;
153 - case "AnyPath":
154 - category = ColumnCategory.AnyPath;
155 - break;
156 - case "DefaultDir":
157 - category = ColumnCategory.DefaultDir;
158 - break;
159 - case "RegPath":
160 - category = ColumnCategory.RegPath;
161 - break;
162 - case "Formatted":
163 - category = ColumnCategory.Formatted;
164 - break;
165 - case "FormattedSddl":
166 - category = ColumnCategory.FormattedSDDLText;
167 - break;
168 - case "Template":
169 - category = ColumnCategory.Template;
170 - break;
171 - case "Condition":
172 - category = ColumnCategory.Condition;
173 - break;
174 - case "Guid":
175 - category = ColumnCategory.Guid;
176 - break;
177 - case "Version":
178 - category = ColumnCategory.Version;
179 - break;
180 - case "Language":
181 - category = ColumnCategory.Language;
182 - break;
183 - case "Binary":
184 - category = ColumnCategory.Binary;
185 - break;
186 - case "CustomSource":
187 - category = ColumnCategory.CustomSource;
188 - break;
189 - case "Cabinet":
190 - category = ColumnCategory.Cabinet;
191 - break;
192 - case "Shortcut":
193 - category = ColumnCategory.Shortcut;
194 - break;
195 - default:
196 - break;
197 - }
90 + case "Text":
91 + category = ColumnCategory.Text;
92 + break;
93 + case "UpperCase":
94 + category = ColumnCategory.UpperCase;
95 + break;
96 + case "LowerCase":
97 + category = ColumnCategory.LowerCase;
98 + break;
99 + case "Integer":
100 + category = ColumnCategory.Integer;
101 + break;
102 + case "DoubleInteger":
103 + category = ColumnCategory.DoubleInteger;
104 + break;
105 + case "TimeDate":
106 + category = ColumnCategory.TimeDate;
107 + break;
108 + case "Identifier":
109 + category = ColumnCategory.Identifier;
110 + break;
111 + case "Property":
112 + category = ColumnCategory.Property;
113 + break;
114 + case "Filename":
115 + category = ColumnCategory.Filename;
116 + break;
117 + case "WildCardFilename":
118 + category = ColumnCategory.WildCardFilename;
119 + break;
120 + case "Path":
121 + category = ColumnCategory.Path;
122 + break;
123 + case "Paths":
124 + category = ColumnCategory.Paths;
125 + break;
126 + case "AnyPath":
127 + category = ColumnCategory.AnyPath;
128 + break;
129 + case "DefaultDir":
130 + category = ColumnCategory.DefaultDir;
131 + break;
132 + case "RegPath":
133 + category = ColumnCategory.RegPath;
134 + break;
135 + case "Formatted":
136 + category = ColumnCategory.Formatted;
137 + break;
138 + case "FormattedSddl":
139 + category = ColumnCategory.FormattedSDDLText;
140 + break;
141 + case "Template":
142 + category = ColumnCategory.Template;
143 + break;
144 + case "Condition":
145 + category = ColumnCategory.Condition;
146 + break;
147 + case "Guid":
148 + category = ColumnCategory.Guid;
149 + break;
150 + case "Version":
151 + category = ColumnCategory.Version;
152 + break;
153 + case "Language":
154 + category = ColumnCategory.Language;
155 + break;
156 + case "Binary":
157 + category = ColumnCategory.Binary;
158 + break;
159 + case "CustomSource":
160 + category = ColumnCategory.CustomSource;
161 + break;
162 + case "Cabinet":
163 + category = ColumnCategory.Cabinet;
164 + break;
165 + case "Shortcut":
166 + category = ColumnCategory.Shortcut;
167 + break;
168 + default:
169 + break;
170 }
171
200 - var keyTable = keyTables?[i];
201 - var setValue = sets?[i];
202 - var description = descriptions?[i];
203 - var modString = modularizations?[i];
172 var modularization = ColumnModularizeType.None;
173
206 - switch (modString)
174 + switch (column.Modularize)
175 {
176 case null:
209 - case "None":
177 + case WixCustomTableColumnModularizeType.None:
178 modularization = ColumnModularizeType.None;
179 break;
212 - case "Column":
180 + case WixCustomTableColumnModularizeType.Column:
181 modularization = ColumnModularizeType.Column;
182 break;
215 - case "Property":
216 - modularization = ColumnModularizeType.Property;
183 + case WixCustomTableColumnModularizeType.CompanionFile:
184 + modularization = ColumnModularizeType.CompanionFile;
185 break;
218 - case "Condition":
186 + case WixCustomTableColumnModularizeType.Condition:
187 modularization = ColumnModularizeType.Condition;
188 break;
221 - case "CompanionFile":
222 - modularization = ColumnModularizeType.CompanionFile;
189 + case WixCustomTableColumnModularizeType.ControlEventArgument:
190 + modularization = ColumnModularizeType.ControlEventArgument;
191 + break;
192 + case WixCustomTableColumnModularizeType.ControlText:
193 + modularization = ColumnModularizeType.ControlText;
194 + break;
195 + case WixCustomTableColumnModularizeType.Icon:
196 + modularization = ColumnModularizeType.Icon;
197 + break;
198 + case WixCustomTableColumnModularizeType.Property:
199 + modularization = ColumnModularizeType.Property;
200 break;
224 - case "SemicolonDelimited":
201 + case WixCustomTableColumnModularizeType.SemicolonDelimited:
202 modularization = ColumnModularizeType.SemicolonDelimited;
203 break;
204 }
205
229 - var columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, setValue, description, modularization, ColumnType.Localized == type, true);
206 + var columnDefinition = new ColumnDefinition(name, type, column.Width, column.PrimaryKey, column.Nullable, category, column.MinValue, column.MaxValue, column.KeyTable, column.KeyColumn, column.Set, column.Description, modularization, ColumnType.Localized == type, useCData: true, column.Unreal);
207 columns.Add(columnDefinition);
208 }
209
src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+31 -26
@@ -4,7 +4,9 @@ namespace WixToolset.Core.Bind
4 {
5 using System;
6 using System.Collections.Generic;
7 + using System.Linq;
8 using WixToolset.Data;
9 + using WixToolset.Data.Tuples;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
@@ -42,6 +44,9 @@ namespace WixToolset.Core.Bind
44
45 var fileResolver = new FileResolver(this.BindPaths, this.Extensions);
46
47 + // Build the column lookup only when needed.
48 + Dictionary<string, WixCustomTableColumnTuple> customColumnsById = null;
49 +
50 foreach (var sections in this.Intermediate.Sections)
51 {
52 foreach (var tuple in sections.Tuples)
@@ -53,13 +58,37 @@ namespace WixToolset.Core.Bind
58 continue;
59 }
60
61 + var fieldType = field.Type;
62 +
63 + // Custom table cells require an extra look up to the column definition as the
64 + // cell's data type is always a string (because strings can store anything) but
65 + // the column definition may be more specific.
66 + if (tuple.Definition.Type == TupleDefinitionType.WixCustomTableCell)
67 + {
68 + // We only care about the Data in a CustomTable cell.
69 + if (field.Name != nameof(WixCustomTableCellTupleFields.Data))
70 + {
71 + continue;
72 + }
73 +
74 + if (customColumnsById == null)
75 + {
76 + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Tuples.OfType<WixCustomTableColumnTuple>()).ToDictionary(t => t.Id.Id);
77 + }
78 +
79 + if (customColumnsById.TryGetValue(tuple.Fields[(int)WixCustomTableCellTupleFields.TableRef].AsString() + "/" + tuple.Fields[(int)WixCustomTableCellTupleFields.ColumnRef].AsString(), out var customColumn))
80 + {
81 + fieldType = customColumn.Type;
82 + }
83 + }
84 +
85 var isDefault = true;
86
87 // Check to make sure we're in a scenario where we can handle variable resolution.
88 if (null != delayedFields)
89 {
90 // resolve localization and wix variables
62 - if (field.Type == IntermediateFieldType.String)
91 + if (fieldType == IntermediateFieldType.String)
92 {
93 var original = field.AsString();
94 if (!String.IsNullOrEmpty(original))
@@ -87,7 +116,7 @@ namespace WixToolset.Core.Bind
116 }
117
118 // Resolve file paths
90 - if (field.Type == IntermediateFieldType.Path)
119 + if (fieldType == IntermediateFieldType.Path)
120 {
121 var objectField = field.AsPath();
122
@@ -226,29 +255,5 @@ namespace WixToolset.Core.Bind
255
256 this.DelayedFields = delayedFields;
257 }
229 -
230 -#if false
231 - private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal)
232 - {
233 - string path = null;
234 - foreach (var extension in this.Extensions)
235 - {
236 - path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage);
237 - if (null != path)
238 - {
239 - break;
240 - }
241 - }
242 -
243 - throw new NotImplementedException(); // need to do default binder stuff
244 -
245 - //if (null == path)
246 - //{
247 - // throw new WixFileNotFoundException(sourceLineNumbers, source, type);
248 - //}
249 -
250 - //return path;
251 - }
252 -#endif
258 }
259 }
src/WixToolset.Core/Compiler.cs
+162 -113
@@ -8,6 +8,7 @@ namespace WixToolset.Core
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 + using System.Linq;
12 using System.Text.RegularExpressions;
13 using System.Xml.Linq;
14 using WixToolset.Data;
@@ -3667,20 +3668,8 @@ namespace WixToolset.Core
3668 {
3669 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3670 string tableId = null;
3670 -
3671 - string categories = null;
3672 - var columnCount = 0;
3673 - string columnNames = null;
3674 - string columnTypes = null;
3675 - string descriptions = null;
3676 - string keyColumns = null;
3677 - string keyTables = null;
3678 - string maxValues = null;
3679 - string minValues = null;
3680 - string modularizations = null;
3681 - string primaryKeys = null;
3682 - string sets = null;
3683 - var bootstrapperApplicationData = false;
3671 + var unreal = false;
3672 + var columns = new List<WixCustomTableColumnTuple>();
3673
3674 foreach (var attrib in node.Attributes())
3675 {
@@ -3692,7 +3681,7 @@ namespace WixToolset.Core
3681 tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3682 break;
3683 case "Unreal":
3695 - bootstrapperApplicationData = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3684 + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3685 break;
3686 default:
3687 this.Core.UnexpectedAttribute(node, attrib);
@@ -3722,22 +3711,20 @@ namespace WixToolset.Core
3711 switch (child.Name.LocalName)
3712 {
3713 case "Column":
3725 - ++columnCount;
3726 -
3727 - var category = String.Empty;
3714 string columnName = null;
3729 - string columnType = null;
3715 + var category = String.Empty;
3716 + IntermediateFieldType? columnType = null;
3717 var description = String.Empty;
3731 - var keyColumn = CompilerConstants.IntegerNotSet;
3718 + int? keyColumn = null;
3719 var keyTable = String.Empty;
3720 var localizable = false;
3734 - var maxValue = CompilerConstants.LongNotSet;
3735 - var minValue = CompilerConstants.LongNotSet;
3736 - var modularization = "None";
3721 + long? maxValue = null;
3722 + long? minValue = null;
3723 + var modularization = WixCustomTableColumnModularizeType.None;
3724 var nullable = false;
3725 var primaryKey = false;
3726 var setValues = String.Empty;
3740 - string typeName = null;
3727 + var columnUnreal = false;
3728 var width = 0;
3729
3730 foreach (var childAttrib in child.Attributes())
@@ -3769,7 +3756,43 @@ namespace WixToolset.Core
3756 minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue);
3757 break;
3758 case "Modularize":
3772 - modularization = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3759 + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3760 + switch (modularizeValue)
3761 + {
3762 + case "column":
3763 + modularization = WixCustomTableColumnModularizeType.Column;
3764 + break;
3765 + case "companionFile":
3766 + modularization = WixCustomTableColumnModularizeType.CompanionFile;
3767 + break;
3768 + case "condition":
3769 + modularization = WixCustomTableColumnModularizeType.Condition;
3770 + break;
3771 + case "controlEventArgument":
3772 + modularization = WixCustomTableColumnModularizeType.ControlEventArgument;
3773 + break;
3774 + case "controlText":
3775 + modularization = WixCustomTableColumnModularizeType.ControlText;
3776 + break;
3777 + case "icon":
3778 + modularization = WixCustomTableColumnModularizeType.Icon;
3779 + break;
3780 + case "none":
3781 + modularization = WixCustomTableColumnModularizeType.None;
3782 + break;
3783 + case "property":
3784 + modularization = WixCustomTableColumnModularizeType.Property;
3785 + break;
3786 + case "semicolonDelimited":
3787 + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited;
3788 + break;
3789 + case "":
3790 + break;
3791 + default:
3792 + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited"));
3793 + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below.
3794 + break;
3795 + }
3796 break;
3797 case "Nullable":
3798 nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
@@ -3785,24 +3808,28 @@ namespace WixToolset.Core
3808 switch (typeValue)
3809 {
3810 case "binary":
3788 - typeName = "OBJECT";
3811 + columnType = IntermediateFieldType.Path;
3812 break;
3813 case "int":
3791 - typeName = "SHORT";
3814 + columnType = IntermediateFieldType.Number;
3815 break;
3816 case "string":
3794 - typeName = "CHAR";
3817 + columnType = IntermediateFieldType.String;
3818 break;
3819 case "":
3820 break;
3821 default:
3822 this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string"));
3823 + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below.
3824 break;
3825 }
3826 break;
3827 case "Width":
3828 width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue);
3829 break;
3830 + case "Unreal":
3831 + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3832 + break;
3833 default:
3834 this.Core.UnexpectedAttribute(child, childAttrib);
3835 break;
@@ -3814,100 +3841,59 @@ namespace WixToolset.Core
3841 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id"));
3842 }
3843
3817 - if (null == typeName)
3844 + if (!columnType.HasValue)
3845 {
3846 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type"));
3847 }
3821 - else if ("SHORT" == typeName)
3848 + else if (columnType == IntermediateFieldType.Number)
3849 {
3850 if (2 != width && 4 != width)
3851 {
3852 this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width));
3853 }
3827 - columnType = String.Concat(nullable ? "I" : "i", width);
3854 }
3829 - else if ("CHAR" == typeName)
3855 + else if (columnType == IntermediateFieldType.Path)
3856 {
3831 - var typeChar = localizable ? "l" : "s";
3832 - columnType = String.Concat(nullable ? typeChar.ToUpper(CultureInfo.InvariantCulture) : typeChar.ToLower(CultureInfo.InvariantCulture), width);
3833 - }
3834 - else if ("OBJECT" == typeName)
3835 - {
3836 - if ("Binary" != category)
3857 + if (String.IsNullOrEmpty(category))
3858 {
3838 - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers));
3859 + category = "Binary";
3860 }
3840 - columnType = String.Concat(nullable ? "V" : "v", width);
3841 - }
3842 -
3843 - this.Core.ParseForExtensionElements(child);
3844 -
3845 - columnNames = String.Concat(columnNames, null == columnNames ? String.Empty : "\t", columnName);
3846 - columnTypes = String.Concat(columnTypes, null == columnTypes ? String.Empty : "\t", columnType);
3847 - if (primaryKey)
3848 - {
3849 - primaryKeys = String.Concat(primaryKeys, null == primaryKeys ? String.Empty : "\t", columnName);
3850 - }
3851 -
3852 - minValues = String.Concat(minValues, null == minValues ? String.Empty : "\t", CompilerConstants.LongNotSet != minValue ? minValue.ToString(CultureInfo.InvariantCulture) : String.Empty);
3853 - maxValues = String.Concat(maxValues, null == maxValues ? String.Empty : "\t", CompilerConstants.LongNotSet != maxValue ? maxValue.ToString(CultureInfo.InvariantCulture) : String.Empty);
3854 - keyTables = String.Concat(keyTables, null == keyTables ? String.Empty : "\t", keyTable);
3855 - keyColumns = String.Concat(keyColumns, null == keyColumns ? String.Empty : "\t", CompilerConstants.IntegerNotSet != keyColumn ? keyColumn.ToString(CultureInfo.InvariantCulture) : String.Empty);
3856 - categories = String.Concat(categories, null == categories ? String.Empty : "\t", category);
3857 - sets = String.Concat(sets, null == sets ? String.Empty : "\t", setValues);
3858 - descriptions = String.Concat(descriptions, null == descriptions ? String.Empty : "\t", description);
3859 - modularizations = String.Concat(modularizations, null == modularizations ? String.Empty : "\t", modularization);
3860 -
3861 - break;
3862 - case "Row":
3863 - string dataValue = null;
3864 -
3865 - foreach (var childAttrib in child.Attributes())
3866 - {
3867 - this.Core.ParseExtensionAttribute(child, childAttrib);
3868 - }
3869 -
3870 - foreach (var data in child.Elements())
3871 - {
3872 - var dataSourceLineNumbers = Preprocessor.GetSourceLineNumbers(data);
3873 - switch (data.Name.LocalName)
3861 + else if (category != "Binary")
3862 {
3875 - case "Data":
3876 - columnName = null;
3877 - foreach (var dataAttrib in data.Attributes())
3878 - {
3879 - switch (dataAttrib.Name.LocalName)
3880 - {
3881 - case "Column":
3882 - columnName = this.Core.GetAttributeValue(dataSourceLineNumbers, dataAttrib);
3883 - break;
3884 - default:
3885 - this.Core.UnexpectedAttribute(data, dataAttrib);
3886 - break;
3887 - }
3888 - }
3889 -
3890 - if (null == columnName)
3891 - {
3892 - this.Core.Write(ErrorMessages.ExpectedAttribute(dataSourceLineNumbers, data.Name.LocalName, "Column"));
3893 - }
3894 -
3895 - dataValue = String.Concat(dataValue, null == dataValue ? String.Empty : WixCustomRowTuple.FieldSeparator.ToString(), columnName, ":", Common.GetInnerText(data));
3896 - break;
3863 + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers));
3864 }
3865 }
3866
3900 - this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId);
3867 + this.Core.ParseForExtensionElements(child);
3868
3869 if (!this.Core.EncounteredError)
3870 {
3904 - this.Core.AddTuple(new WixCustomRowTuple(childSourceLineNumbers)
3871 + var attributes = primaryKey ? WixCustomTableColumnTupleAttributes.PrimaryKey : WixCustomTableColumnTupleAttributes.None;
3872 + attributes |= localizable ? WixCustomTableColumnTupleAttributes.Localizable : WixCustomTableColumnTupleAttributes.None;
3873 + attributes |= nullable ? WixCustomTableColumnTupleAttributes.Nullable : WixCustomTableColumnTupleAttributes.None;
3874 + attributes |= columnUnreal ? WixCustomTableColumnTupleAttributes.Unreal : WixCustomTableColumnTupleAttributes.None;
3875 +
3876 + columns.Add(new WixCustomTableColumnTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, columnName))
3877 {
3906 - Table = tableId,
3907 - FieldData = dataValue,
3878 + TableRef = tableId,
3879 + Name = columnName,
3880 + Type = columnType.Value,
3881 + Attributes = attributes,
3882 + Width = width,
3883 + Category = category,
3884 + Description = description,
3885 + KeyColumn = keyColumn,
3886 + KeyTable = keyTable,
3887 + MaxValue = maxValue,
3888 + MinValue = minValue,
3889 + Modularize = modularization,
3890 + Set = setValues,
3891 });
3892 }
3893 break;
3894 + case "Row":
3895 + this.ParseRow(child, tableId);
3896 + break;
3897 default:
3898 this.Core.UnexpectedElement(node, child);
3899 break;
@@ -3919,35 +3905,98 @@ namespace WixToolset.Core
3905 }
3906 }
3907
3922 - if (0 < columnCount)
3908 + if (columns.Count > 0)
3909 {
3924 - if (null == primaryKeys || 0 == primaryKeys.Length)
3910 + if (!columns.Where(c => c.PrimaryKey).Any())
3911 {
3912 this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers));
3913 }
3914
3915 if (!this.Core.EncounteredError)
3916 {
3917 + var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name));
3918 +
3919 this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId))
3920 {
3933 - ColumnCount = columnCount,
3921 ColumnNames = columnNames,
3935 - ColumnTypes = columnTypes,
3936 - PrimaryKeys = primaryKeys,
3937 - MinValues = minValues,
3938 - MaxValues = maxValues,
3939 - KeyTables = keyTables,
3940 - KeyColumns = keyColumns,
3941 - Categories = categories,
3942 - Sets = sets,
3943 - Descriptions = descriptions,
3944 - Modularizations = modularizations,
3945 - Unreal = bootstrapperApplicationData,
3922 + Unreal = unreal,
3923 });
3924 +
3925 + foreach (var column in columns)
3926 + {
3927 + this.Core.AddTuple(column);
3928 + }
3929 }
3930 }
3931 }
3932
3933 + private void ParseRow(XElement node, string tableId)
3934 + {
3935 + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3936 + var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant();
3937 +
3938 + foreach (var attrib in node.Attributes())
3939 + {
3940 + this.Core.ParseExtensionAttribute(node, attrib);
3941 + }
3942 +
3943 + foreach (var child in node.Elements())
3944 + {
3945 + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
3946 + switch (child.Name.LocalName)
3947 + {
3948 + case "Data":
3949 + string columnName = null;
3950 + string data = null;
3951 + foreach (var attrib in child.Attributes())
3952 + {
3953 + switch (attrib.Name.LocalName)
3954 + {
3955 + case "Column":
3956 + columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
3957 + break;
3958 + case "Value":
3959 + data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
3960 + break;
3961 + default:
3962 + this.Core.ParseExtensionAttribute(child, attrib);
3963 + break;
3964 + }
3965 + }
3966 +
3967 + if (null == columnName)
3968 + {
3969 + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column"));
3970 + }
3971 +
3972 + if (String.IsNullOrEmpty(data))
3973 + {
3974 + data = Common.GetInnerText(child);
3975 + }
3976 +
3977 + if (!this.Core.EncounteredError)
3978 + {
3979 + this.Core.AddTuple(new WixCustomTableCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, rowId, columnName))
3980 + {
3981 + RowId = rowId,
3982 + ColumnRef = columnName,
3983 + TableRef = tableId,
3984 + Data = data
3985 + });
3986 + }
3987 + break;
3988 + default:
3989 + this.Core.UnexpectedElement(node, child);
3990 + break;
3991 + }
3992 + }
3993 +
3994 + if (!this.Core.EncounteredError)
3995 + {
3996 + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId);
3997 + }
3998 + }
3999 +
4000 /// <summary>
4001 /// Parses a directory element.
4002 /// </summary>
src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
+1 -1
@@ -46,7 +46,7 @@ namespace WixToolsetTest.CoreIntegration
46 var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable");
47 Assert.Equal(3, customElements.Count);
48 Assert.Equal("<BundleCustomTable Id='one' Column2='two' />", customElements[0].GetTestXml());
49 - Assert.Equal("<BundleCustomTable Column2='&lt;' Id='&gt;' />", customElements[1].GetTestXml());
49 + Assert.Equal("<BundleCustomTable Id='&gt;' Column2='&lt;' />", customElements[1].GetTestXml());
50 Assert.Equal("<BundleCustomTable Id='1' Column2='2' />", customElements[2].GetTestXml());
51 }
52 }
src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
+115
@@ -472,6 +472,121 @@ namespace WixToolsetTest.CoreIntegration
472 }
473 }
474
475 + [Fact]
476 + public void PopulatesCustomTableWithLocalization()
477 + {
478 + var folder = TestData.Get(@"TestData");
479 +
480 + using (var fs = new DisposableFileSystem())
481 + {
482 + var baseFolder = fs.GetFolder();
483 + var intermediateFolder = Path.Combine(baseFolder, "obj");
484 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
485 +
486 + var result = WixRunner.Execute(new[]
487 + {
488 + "build",
489 + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"),
490 + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
491 + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
492 + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"),
493 + "-bindpath", Path.Combine(folder, "SingleFile", "data"),
494 + "-intermediateFolder", intermediateFolder,
495 + "-o", msiPath
496 + });
497 +
498 + result.AssertSuccess();
499 +
500 + Assert.True(File.Exists(msiPath));
501 + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" });
502 + Assert.Equal(new[]
503 + {
504 + "CustomTableLocalized:Row1\tThis is row one",
505 + "CustomTableLocalized:Row2\tThis is row two",
506 + }, results);
507 + }
508 + }
509 +
510 + [Fact]
511 + public void PopulatesCustomTableWithFilePath()
512 + {
513 + var folder = TestData.Get(@"TestData");
514 +
515 + using (var fs = new DisposableFileSystem())
516 + {
517 + var baseFolder = fs.GetFolder();
518 + var intermediateFolder = Path.Combine(baseFolder, "obj");
519 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
520 +
521 + var result = WixRunner.Execute(new[]
522 + {
523 + "build",
524 + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"),
525 + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
526 + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
527 + "-bindpath", Path.Combine(folder, "CustomTable", "data"),
528 + "-intermediateFolder", intermediateFolder,
529 + "-o", msiPath
530 + });
531 +
532 + result.AssertSuccess();
533 +
534 + Assert.True(File.Exists(msiPath));
535 + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" });
536 + Assert.Equal(new[]
537 + {
538 + "CustomTableWithFile:Row1\t[Binary data]",
539 + "CustomTableWithFile:Row2\t[Binary data]",
540 + }, results);
541 + }
542 + }
543 +
544 + [Fact]
545 + public void PopulatesCustomTableWithFilePathSerialized()
546 + {
547 + var folder = TestData.Get(@"TestData");
548 +
549 + using (var fs = new DisposableFileSystem())
550 + {
551 + var baseFolder = fs.GetFolder();
552 + var intermediateFolder = Path.Combine(baseFolder, "obj");
553 + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib");
554 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
555 +
556 + var result = WixRunner.Execute(new[]
557 + {
558 + "build",
559 + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"),
560 + "-bindpath", Path.Combine(folder, "CustomTable", "data"),
561 + "-intermediateFolder", intermediateFolder,
562 + "-o", wixlibPath
563 + });
564 +
565 + result.AssertSuccess();
566 +
567 + result = WixRunner.Execute(new[]
568 + {
569 + "build",
570 + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
571 + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
572 + "-lib", wixlibPath,
573 + "-bindpath", Path.Combine(folder, "CustomTable", "data"),
574 + "-intermediateFolder", intermediateFolder,
575 + "-o", msiPath
576 + });
577 +
578 + result.AssertSuccess();
579 +
580 + Assert.True(File.Exists(msiPath));
581 + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" });
582 + Assert.Equal(new[]
583 + {
584 + "CustomTableWithFile:Row1\t[Binary data]",
585 + "CustomTableWithFile:Row2\t[Binary data]",
586 + }, results);
587 + }
588 + }
589 +
590 [Fact]
591 public void UnrealCustomTableIsNotPresentInMsi()
592 {
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs new
+22
@@ -0,0 +1,22 @@
1 +<?xml version="1.0" encoding="utf-8" ?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <ComponentGroup Id="ProductComponents">
5 + <ComponentGroupRef Id="MinimalComponentGroup" />
6 + </ComponentGroup>
7 +
8 + <CustomTable Id="CustomTableWithFile">
9 + <Column Id="Column1" Type="string" PrimaryKey="yes" />
10 + <Column Id="Source" Type="binary" Width="0" />
11 + <Row>
12 + <Data Column="Column1">Row1</Data>
13 + <Data Column="Source">file1.txt</Data>
14 + </Row>
15 + <Row>
16 + <Data Column="Source">SourceDir\file2.txt</Data>
17 + <Data Column="Column1">Row2</Data>
18 + </Row>
19 + </CustomTable>
20 +
21 + </Fragment>
22 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl new
+7
@@ -0,0 +1,7 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
3 +
4 + <String Id="Loc1">This is row one</String>
5 + <String Id="Loc2">This is row two</String>
6 +
7 +</WixLocalization>
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs new
+21
@@ -0,0 +1,21 @@
1 +<?xml version="1.0" encoding="utf-8" ?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <ComponentGroup Id="ProductComponents">
5 + <ComponentGroupRef Id="MinimalComponentGroup" />
6 + </ComponentGroup>
7 +
8 + <CustomTable Id="CustomTableLocalized">
9 + <Column Id="Column1" Type="string" PrimaryKey="yes" />
10 + <Column Id="DataColumn" Type="string" Localizable="yes" Width="255" />
11 + <Row>
12 + <Data Column="Column1" Value="Row1" />
13 + <Data Column="DataColumn" Value="!(loc.Loc1)" />
14 + </Row>
15 + <Row>
16 + <Data Column="Column1" Value="Row2" />
17 + <Data Column="DataColumn" Value="!(loc.Loc2)" />
18 + </Row>
19 + </CustomTable>
20 + </Fragment>
21 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt new
+1
@@ -0,0 +1 @@
1 +This is file1.txt
\ No newline at end of file
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt new
+1
@@ -0,0 +1 @@
1 +This is file2.txt
\ No newline at end of file
src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt new
+1
@@ -0,0 +1 @@
1 +This is test.txt.
\ No newline at end of file
src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+6
@@ -34,7 +34,13 @@
34 <Content Include="TestData\Class\IconIndex0.wxs" CopyToOutputDirectory="PreserveNewest" />
35 <Content Include="TestData\Class\OldClassTableDef.msi" CopyToOutputDirectory="PreserveNewest" />
36 <Content Include="TestData\CustomAction\UnscheduledCustomAction.wxs" CopyToOutputDirectory="PreserveNewest" />
37 + <Content Include="TestData\CustomTable\CustomTableWithFile.wxs" CopyToOutputDirectory="PreserveNewest" />
38 + <Content Include="TestData\CustomTable\data\file1.txt" CopyToOutputDirectory="PreserveNewest" />
39 + <Content Include="TestData\CustomTable\data\test.txt" CopyToOutputDirectory="PreserveNewest" />
40 + <Content Include="TestData\CustomTable\data\file2.txt" CopyToOutputDirectory="PreserveNewest" />
41 + <Content Include="TestData\CustomTable\LocalizedCustomTable.wxs" CopyToOutputDirectory="PreserveNewest" />
42 <Content Include="TestData\CustomTable\CustomTable.wxs" CopyToOutputDirectory="PreserveNewest" />
43 + <Content Include="TestData\CustomTable\LocalizedCustomTable.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
44 <Content Include="TestData\DefaultDir\DefaultDir.wxs" CopyToOutputDirectory="PreserveNewest" />
45 <Content Include="TestData\DialogsInInstallUISequence\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
46 <Content Include="TestData\EnsureTable\EnsureTable.wxs" CopyToOutputDirectory="PreserveNewest" />