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='<' Id='>' />", customElements[1].GetTestXml());
49
+ Assert.Equal("<BundleCustomTable Id='>' Column2='<' />", 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" />