aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs27
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs447
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs7
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs257
-rw-r--r--src/WixToolset.Core/Bind/ResolveFieldsCommand.cs57
-rw-r--r--src/WixToolset.Core/Compiler.cs275
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs2
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs115
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs22
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl7
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs21
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj6
15 files changed, 731 insertions, 515 deletions
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
index 2a230a90..dba2a9ba 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
@@ -281,20 +281,20 @@ namespace WixToolset.Core.Burn.Bundles
281 } 281 }
282 282
283 var dataTablesById = this.Section.Tuples.OfType<WixCustomTableTuple>() 283 var dataTablesById = this.Section.Tuples.OfType<WixCustomTableTuple>()
284 .Where(t => t.Unreal && t.Id != null) 284 .Where(t => t.Unreal && t.Id != null)
285 .ToDictionary(t => t.Id.Id); 285 .ToDictionary(t => t.Id.Id);
286 var dataRowsByTable = this.Section.Tuples.OfType<WixCustomRowTuple>() 286 var cellsByTable = this.Section.Tuples.OfType<WixCustomTableCellTuple>()
287 .GroupBy(t => t.Table); 287 .GroupBy(t => t.TableRef);
288 foreach (var tableDataRows in dataRowsByTable) 288 foreach (var tableCells in cellsByTable)
289 { 289 {
290 var tableName = tableDataRows.Key; 290 var tableName = tableCells.Key;
291 if (!dataTablesById.TryGetValue(tableName, out var tableTuple)) 291 if (!dataTablesById.TryGetValue(tableName, out var tableTuple))
292 { 292 {
293 // This should have been a linker error. 293 // This should have been a linker error.
294 continue; 294 continue;
295 } 295 }
296 296
297 var columnNames = tableTuple.ColumnNames.Split('\t'); 297 var columnNames = tableTuple.ColumnNamesSeparated;
298 298
299 // We simply assert that the table (and field) name is valid, because 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 300 // this is up to the extension developer to get right. An author will
@@ -307,17 +307,18 @@ namespace WixToolset.Core.Burn.Bundles
307 } 307 }
308#endif // DEBUG 308#endif // DEBUG
309 309
310 foreach (var rowTuple in tableDataRows) 310 foreach (var rowCells in tableCells.GroupBy(t => t.RowId))
311 { 311 {
312 var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data);
313
312 writer.WriteStartElement(tableName); 314 writer.WriteStartElement(tableName);
313 315
314 //var rowFields = rowTuple.FieldDataSeparated; 316 // Write all row data as attributes in table column order.
315 foreach (var field in rowTuple.FieldDataSeparated) 317 foreach (var column in columnNames)
316 { 318 {
317 var splitField = field.Split(ColonCharacter, 2); 319 if (rowDataByColumn.TryGetValue(column, out var data))
318 if (splitField.Length == 2)
319 { 320 {
320 writer.WriteAttributeString(splitField[0], splitField[1]); 321 writer.WriteAttributeString(column, data);
321 } 322 }
322 } 323 }
323 324
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
index ffc4e84d..0c1aa312 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
@@ -18,8 +18,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
18 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB 18 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
19 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) 19 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
20 20
21 private static readonly char[] ColonCharacter = new[] { ':' };
22
23 public CreateOutputFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IWindowsInstallerBackendHelper backendHelper) 21 public CreateOutputFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IWindowsInstallerBackendHelper backendHelper)
24 { 22 {
25 this.Messaging = messaging; 23 this.Messaging = messaging;
@@ -54,171 +52,174 @@ namespace WixToolset.Core.WindowsInstaller.Bind
54 52
55 private void AddSectionToOutput() 53 private void AddSectionToOutput()
56 { 54 {
55 var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellTuple>>();
56
57 foreach (var tuple in this.Section.Tuples) 57 foreach (var tuple in this.Section.Tuples)
58 { 58 {
59 var unknownTuple = false; 59 var unknownTuple = false;
60 switch (tuple.Definition.Type) 60 switch (tuple.Definition.Type)
61 { 61 {
62 case TupleDefinitionType.AppSearch: 62 case TupleDefinitionType.AppSearch:
63 this.AddTupleDefaultly(tuple); 63 this.AddTupleDefaultly(tuple);
64 this.Output.EnsureTable(this.TableDefinitions["Signature"]); 64 this.Output.EnsureTable(this.TableDefinitions["Signature"]);
65 break; 65 break;
66 66
67 case TupleDefinitionType.Assembly: 67 case TupleDefinitionType.Assembly:
68 this.AddAssemblyTuple((AssemblyTuple)tuple); 68 this.AddAssemblyTuple((AssemblyTuple)tuple);
69 break; 69 break;
70 70
71 case TupleDefinitionType.BBControl: 71 case TupleDefinitionType.BBControl:
72 this.AddBBControlTuple((BBControlTuple)tuple); 72 this.AddBBControlTuple((BBControlTuple)tuple);
73 break; 73 break;
74 74
75 case TupleDefinitionType.Class: 75 case TupleDefinitionType.Class:
76 this.AddClassTuple((ClassTuple)tuple); 76 this.AddClassTuple((ClassTuple)tuple);
77 break; 77 break;
78 78
79 case TupleDefinitionType.Control: 79 case TupleDefinitionType.Control:
80 this.AddControlTuple((ControlTuple)tuple); 80 this.AddControlTuple((ControlTuple)tuple);
81 break; 81 break;
82 82
83 case TupleDefinitionType.Component: 83 case TupleDefinitionType.Component:
84 this.AddComponentTuple((ComponentTuple)tuple); 84 this.AddComponentTuple((ComponentTuple)tuple);
85 break; 85 break;
86 86
87 case TupleDefinitionType.CustomAction: 87 case TupleDefinitionType.CustomAction:
88 this.AddCustomActionTuple((CustomActionTuple)tuple); 88 this.AddCustomActionTuple((CustomActionTuple)tuple);
89 break; 89 break;
90 90
91 case TupleDefinitionType.Dialog: 91 case TupleDefinitionType.Dialog:
92 this.AddDialogTuple((DialogTuple)tuple); 92 this.AddDialogTuple((DialogTuple)tuple);
93 break; 93 break;
94 94
95 case TupleDefinitionType.Directory: 95 case TupleDefinitionType.Directory:
96 this.AddDirectoryTuple((DirectoryTuple)tuple); 96 this.AddDirectoryTuple((DirectoryTuple)tuple);
97 break; 97 break;
98 98
99 case TupleDefinitionType.Environment: 99 case TupleDefinitionType.Environment:
100 this.AddEnvironmentTuple((EnvironmentTuple)tuple); 100 this.AddEnvironmentTuple((EnvironmentTuple)tuple);
101 break; 101 break;
102 102
103 case TupleDefinitionType.Error: 103 case TupleDefinitionType.Error:
104 this.AddErrorTuple((ErrorTuple)tuple); 104 this.AddErrorTuple((ErrorTuple)tuple);
105 break; 105 break;
106 106
107 case TupleDefinitionType.Feature: 107 case TupleDefinitionType.Feature:
108 this.AddFeatureTuple((FeatureTuple)tuple); 108 this.AddFeatureTuple((FeatureTuple)tuple);
109 break; 109 break;
110 110
111 case TupleDefinitionType.File: 111 case TupleDefinitionType.File:
112 this.AddFileTuple((FileTuple)tuple); 112 this.AddFileTuple((FileTuple)tuple);
113 break; 113 break;
114 114
115 case TupleDefinitionType.IniFile: 115 case TupleDefinitionType.IniFile:
116 this.AddIniFileTuple((IniFileTuple)tuple); 116 this.AddIniFileTuple((IniFileTuple)tuple);
117 break; 117 break;
118 118
119 case TupleDefinitionType.Media: 119 case TupleDefinitionType.Media:
120 this.AddMediaTuple((MediaTuple)tuple); 120 this.AddMediaTuple((MediaTuple)tuple);
121 break; 121 break;
122 122
123 case TupleDefinitionType.ModuleConfiguration: 123 case TupleDefinitionType.ModuleConfiguration:
124 this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); 124 this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple);
125 break; 125 break;
126 126
127 case TupleDefinitionType.MsiEmbeddedUI: 127 case TupleDefinitionType.MsiEmbeddedUI:
128 this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); 128 this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple);
129 break; 129 break;
130 130
131 case TupleDefinitionType.MsiServiceConfig: 131 case TupleDefinitionType.MsiServiceConfig:
132 this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); 132 this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple);
133 break; 133 break;
134 134
135 case TupleDefinitionType.MsiServiceConfigFailureActions: 135 case TupleDefinitionType.MsiServiceConfigFailureActions:
136 this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); 136 this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple);
137 break; 137 break;
138 138
139 case TupleDefinitionType.MoveFile: 139 case TupleDefinitionType.MoveFile:
140 this.AddMoveFileTuple((MoveFileTuple)tuple); 140 this.AddMoveFileTuple((MoveFileTuple)tuple);
141 break; 141 break;
142 142
143 case TupleDefinitionType.ProgId: 143 case TupleDefinitionType.ProgId:
144 this.AddTupleDefaultly(tuple); 144 this.AddTupleDefaultly(tuple);
145 this.Output.EnsureTable(this.TableDefinitions["Extension"]); 145 this.Output.EnsureTable(this.TableDefinitions["Extension"]);
146 break; 146 break;
147 147
148 case TupleDefinitionType.Property: 148 case TupleDefinitionType.Property:
149 this.AddPropertyTuple((PropertyTuple)tuple); 149 this.AddPropertyTuple((PropertyTuple)tuple);
150 break; 150 break;
151 151
152 case TupleDefinitionType.RemoveFile: 152 case TupleDefinitionType.RemoveFile:
153 this.AddRemoveFileTuple((RemoveFileTuple)tuple); 153 this.AddRemoveFileTuple((RemoveFileTuple)tuple);
154 break; 154 break;
155 155
156 case TupleDefinitionType.Registry: 156 case TupleDefinitionType.Registry:
157 this.AddRegistryTuple((RegistryTuple)tuple); 157 this.AddRegistryTuple((RegistryTuple)tuple);
158 break; 158 break;
159 159
160 case TupleDefinitionType.RegLocator: 160 case TupleDefinitionType.RegLocator:
161 this.AddRegLocatorTuple((RegLocatorTuple)tuple); 161 this.AddRegLocatorTuple((RegLocatorTuple)tuple);
162 break; 162 break;
163 163
164 case TupleDefinitionType.RemoveRegistry: 164 case TupleDefinitionType.RemoveRegistry:
165 this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); 165 this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple);
166 break; 166 break;
167 167
168 case TupleDefinitionType.ServiceControl: 168 case TupleDefinitionType.ServiceControl:
169 this.AddServiceControlTuple((ServiceControlTuple)tuple); 169 this.AddServiceControlTuple((ServiceControlTuple)tuple);
170 break; 170 break;
171 171
172 case TupleDefinitionType.ServiceInstall: 172 case TupleDefinitionType.ServiceInstall:
173 this.AddServiceInstallTuple((ServiceInstallTuple)tuple); 173 this.AddServiceInstallTuple((ServiceInstallTuple)tuple);
174 break; 174 break;
175 175
176 case TupleDefinitionType.Shortcut: 176 case TupleDefinitionType.Shortcut:
177 this.AddShortcutTuple((ShortcutTuple)tuple); 177 this.AddShortcutTuple((ShortcutTuple)tuple);
178 break; 178 break;
179 179
180 case TupleDefinitionType.TextStyle: 180 case TupleDefinitionType.TextStyle:
181 this.AddTextStyleTuple((TextStyleTuple)tuple); 181 this.AddTextStyleTuple((TextStyleTuple)tuple);
182 break; 182 break;
183 183
184 case TupleDefinitionType.Upgrade: 184 case TupleDefinitionType.Upgrade:
185 this.AddUpgradeTuple((UpgradeTuple)tuple); 185 this.AddUpgradeTuple((UpgradeTuple)tuple);
186 break; 186 break;
187 187
188 case TupleDefinitionType.WixAction: 188 case TupleDefinitionType.WixAction:
189 this.AddWixActionTuple((WixActionTuple)tuple); 189 this.AddWixActionTuple((WixActionTuple)tuple);
190 break; 190 break;
191 191
192 case TupleDefinitionType.WixMediaTemplate: 192 case TupleDefinitionType.WixMediaTemplate:
193 this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); 193 this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple);
194 break; 194 break;
195 195
196 case TupleDefinitionType.WixCustomRow: 196 case TupleDefinitionType.WixCustomTableCell:
197 this.AddWixCustomRowTuple((WixCustomRowTuple)tuple); 197 this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId);
198 break; 198 break;
199 199
200 case TupleDefinitionType.WixEnsureTable: 200 case TupleDefinitionType.WixEnsureTable:
201 this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); 201 this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple);
202 break; 202 break;
203 203
204 // ignored. 204 // ignored.
205 case TupleDefinitionType.WixComponentGroup: 205 case TupleDefinitionType.WixComponentGroup:
206 case TupleDefinitionType.WixDeltaPatchFile: 206 case TupleDefinitionType.WixDeltaPatchFile:
207 case TupleDefinitionType.WixFeatureGroup: 207 case TupleDefinitionType.WixFeatureGroup:
208 case TupleDefinitionType.WixPatchBaseline: 208 case TupleDefinitionType.WixPatchBaseline:
209 break; 209 break;
210 210
211 // Already processed. 211 // Already processed by LoadTableDefinitions.
212 case TupleDefinitionType.WixCustomTable: 212 case TupleDefinitionType.WixCustomTable:
213 break; 213 case TupleDefinitionType.WixCustomTableColumn:
214 break;
214 215
215 case TupleDefinitionType.MustBeFromAnExtension: 216 case TupleDefinitionType.MustBeFromAnExtension:
216 unknownTuple = !this.AddTupleFromExtension(tuple); 217 unknownTuple = !this.AddTupleFromExtension(tuple);
217 break; 218 break;
218 219
219 default: 220 default:
220 unknownTuple = !this.AddTupleDefaultly(tuple); 221 unknownTuple = !this.AddTupleDefaultly(tuple);
221 break; 222 break;
222 } 223 }
223 224
224 if (unknownTuple) 225 if (unknownTuple)
@@ -226,6 +227,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
226 this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple)); 227 this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple));
227 } 228 }
228 } 229 }
230
231 this.AddIndexedCellTuples(cellsByTableAndRowId);
229 } 232 }
230 233
231 private void AddAssemblyTuple(AssemblyTuple tuple) 234 private void AddAssemblyTuple(AssemblyTuple tuple)
@@ -383,16 +386,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
383 private void AddDialogTuple(DialogTuple tuple) 386 private void AddDialogTuple(DialogTuple tuple)
384 { 387 {
385 var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; 388 var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0;
386 attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; 389 attributes |= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0;
387 attributes|= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; 390 attributes |= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0;
388 attributes|= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette: 0; 391 attributes |= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0;
389 attributes|= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; 392 attributes |= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0;
390 attributes|= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; 393 attributes |= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0;
391 attributes|= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; 394 attributes |= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0;
392 attributes|= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; 395 attributes |= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0;
393 attributes|= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; 396 attributes |= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0;
394 attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; 397 attributes |= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0;
395 attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; 398 attributes |= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0;
396 399
397 var row = this.CreateRow(tuple, "Dialog"); 400 var row = this.CreateRow(tuple, "Dialog");
398 row[0] = tuple.Id.Id; 401 row[0] = tuple.Id.Id;
@@ -419,7 +422,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
419 targetName = "."; 422 targetName = ".";
420 } 423 }
421 424
422 var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ; 425 var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName;
423 426
424 var row = this.CreateRow(tuple, "Directory"); 427 var row = this.CreateRow(tuple, "Directory");
425 row[0] = tuple.Id.Id; 428 row[0] = tuple.Id.Id;
@@ -436,25 +439,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind
436 439
437 switch (tuple.Action) 440 switch (tuple.Action)
438 { 441 {
439 case EnvironmentActionType.Create: 442 case EnvironmentActionType.Create:
440 action = "+"; 443 action = "+";
441 break; 444 break;
442 case EnvironmentActionType.Set: 445 case EnvironmentActionType.Set:
443 action = "="; 446 action = "=";
444 break; 447 break;
445 case EnvironmentActionType.Remove: 448 case EnvironmentActionType.Remove:
446 action = "!"; 449 action = "!";
447 break; 450 break;
448 } 451 }
449 452
450 switch (tuple.Part) 453 switch (tuple.Part)
451 { 454 {
452 case EnvironmentPartType.First: 455 case EnvironmentPartType.First:
453 value = String.Concat(value, tuple.Separator, "[~]"); 456 value = String.Concat(value, tuple.Separator, "[~]");
454 break; 457 break;
455 case EnvironmentPartType.Last: 458 case EnvironmentPartType.Last:
456 value = String.Concat("[~]", tuple.Separator, value); 459 value = String.Concat("[~]", tuple.Separator, value);
457 break; 460 break;
458 } 461 }
459 462
460 var row = this.CreateRow(tuple, "Environment"); 463 var row = this.CreateRow(tuple, "Environment");
@@ -661,40 +664,40 @@ namespace WixToolset.Core.WindowsInstaller.Bind
661 664
662 switch (tuple.ValueType) 665 switch (tuple.ValueType)
663 { 666 {
664 case RegistryValueType.Binary: 667 case RegistryValueType.Binary:
665 value = String.Concat("#x", value); 668 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);
678 break; 669 break;
679 case RegistryValueActionType.Prepend: 670 case RegistryValueType.Expandable:
680 value = String.Concat(value, "[~]"); 671 value = String.Concat("#%", value);
681 break; 672 break;
682 case RegistryValueActionType.Write: 673 case RegistryValueType.Integer:
683 default: 674 value = String.Concat("#", value);
684 if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) 675 break;
676 case RegistryValueType.MultiString:
677 switch (tuple.ValueAction)
685 { 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);
687 } 699 }
688 break; 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;
698 } 701 }
699 702
700 var row = this.CreateRow(tuple, "Registry"); 703 var row = this.CreateRow(tuple, "Registry");
@@ -757,7 +760,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
757 row[3] = tuple.Arguments; 760 row[3] = tuple.Arguments;
758 if (tuple.Wait.HasValue) 761 if (tuple.Wait.HasValue)
759 { 762 {
760 row[4] = tuple.Wait.Value ? 1 : 0; 763 row[4] = tuple.Wait.Value ? 1 : 0;
761 } 764 }
762 row[5] = tuple.ComponentRef; 765 row[5] = tuple.ComponentRef;
763 } 766 }
@@ -938,83 +941,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind
938 row[2] = tuple.Sequence; 941 row[2] = tuple.Sequence;
939 } 942 }
940 } 943 }
941
942 private void AddWixCustomRowTuple(WixCustomRowTuple tuple)
943 {
944 var customTableDefinition = this.TableDefinitions[tuple.Table];
945 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))
947 { 949 {
948 950 cells = new List<WixCustomTableCellTuple>();
949 return; 951 cellsByTableAndRowId.Add(tableAndRowId, cells);
950 } 952 }
951 953
952 var customRow = this.CreateRow(tuple, customTableDefinition); 954 cells.Add(wixCustomTableCellTuple);
955 }
953 956
954#if TODO // SectionId seems like a good thing to preserve. 957 private void AddIndexedCellTuples(Dictionary<string, List<WixCustomTableCellTuple>> cellsByTableAndRowId)
955 customRow.SectionId = tuple.SectionId; 958 {
956#endif 959 foreach (var rowOfCells in cellsByTableAndRowId.Values)
960 {
961 var firstCellTuple = rowOfCells[0];
962 var customTableDefinition = this.TableDefinitions[firstCellTuple.TableRef];
957 963
958 var data = tuple.FieldDataSeparated; 964 if (customTableDefinition.Unreal)
965 {
966 return;
967 }
959 968
960 for (var i = 0; i < data.Length; ++i) 969 var customRow = this.CreateRow(firstCellTuple, customTableDefinition);
961 { 970 var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name);
962 var foundColumn = false;
963 var item = data[i].Split(ColonCharacter, 2);
964 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)
966 { 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))
968 { 980 {
969 if (0 < item[1].Length) 981 if (!String.IsNullOrEmpty(data))
970 { 982 {
971 if (ColumnType.Number == customRow.Fields[j].Column.Type) 983 if (rowField.Column.Type == ColumnType.Number)
972 { 984 {
973 try 985 try
974 { 986 {
975 customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture); 987 rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture);
976 } 988 }
977 catch (FormatException) 989 catch (FormatException)
978 { 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));
980 } 992 }
981 catch (OverflowException) 993 catch (OverflowException)
982 { 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));
984 } 996 }
985 } 997 }
986 else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category) 998 else if (rowField.Column.Category == ColumnCategory.Identifier)
987 { 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)
989 { 1001 {
990 customRow.Fields[j].Data = item[1]; 1002 rowField.Data = data;
991 } 1003 }
992 else 1004 else
993 { 1005 {
994 this.Messaging.Write(ErrorMessages.IllegalIdentifier(tuple.SourceLineNumbers, "Data", item[1])); 1006 this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data));
995 } 1007 }
996 } 1008 }
997 else 1009 else
998 { 1010 {
999 customRow.Fields[j].Data = item[1]; 1011 rowField.Data = data;
1000 } 1012 }
1001 } 1013 }
1002 foundColumn = true; 1014 }
1003 break; 1015 else
1016 {
1017 this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef));
1004 } 1018 }
1005 } 1019 }
1006 1020
1007 if (!foundColumn) 1021 for (var i = 0; i < customTableDefinition.Columns.Length; ++i)
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))
1016 { 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 }
1018 } 1027 }
1019 } 1028 }
1020 } 1029 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index eff94e80..6edbdd1c 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -314,18 +314,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
314 break; 314 break;
315 315
316 case ColumnType.Object: 316 case ColumnType.Object:
317 if (null != row[i]) 317 var path = row.FieldAsString(i);
318 if (null != path)
318 { 319 {
319 needStream = true; 320 needStream = true;
320 try 321 try
321 { 322 {
322 record.SetStream(i + 1, row.FieldAsString(i)); 323 record.SetStream(i + 1, path);
323 } 324 }
324 catch (Win32Exception e) 325 catch (Win32Exception e)
325 { 326 {
326 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME 327 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
327 { 328 {
328 throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(i))); 329 throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path));
329 } 330 }
330 else 331 else
331 { 332 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs
index eba7bdbe..d7809034 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs
@@ -32,11 +32,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
32 public TableDefinitionCollection Execute() 32 public TableDefinitionCollection Execute()
33 { 33 {
34 var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); 34 var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
35 var customColumnsById = this.Section.Tuples.OfType<WixCustomTableColumnTuple>().ToDictionary(t => t.Id.Id);
35 36
36 foreach (var tuple in this.Section.Tuples.OfType<WixCustomTableTuple>()) 37 if (customColumnsById.Any())
37 { 38 {
38 var customTableDefinition = this.CreateCustomTable(tuple); 39 foreach (var tuple in this.Section.Tuples.OfType<WixCustomTableTuple>())
39 tableDefinitions.Add(customTableDefinition); 40 {
41 var customTableDefinition = this.CreateCustomTable(tuple, customColumnsById);
42 tableDefinitions.Add(customTableDefinition);
43 }
40 } 44 }
41 45
42 foreach (var backendExtension in this.BackendExtensions) 46 foreach (var backendExtension in this.BackendExtensions)
@@ -56,177 +60,150 @@ namespace WixToolset.Core.WindowsInstaller.Bind
56 return this.TableDefinitions; 60 return this.TableDefinitions;
57 } 61 }
58 62
59 private TableDefinition CreateCustomTable(WixCustomTableTuple tuple) 63 private TableDefinition CreateCustomTable(WixCustomTableTuple tuple, Dictionary<string, WixCustomTableColumnTuple> customColumnsById)
60 { 64 {
61 var columnNames = tuple.ColumnNames.Split('\t'); 65 var columnNames = tuple.ColumnNamesSeparated;
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
75 var columns = new List<ColumnDefinition>(columnNames.Length); 66 var columns = new List<ColumnDefinition>(columnNames.Length);
76 for (var i = 0; i < columnNames.Length; ++i) 67
68 foreach (var name in columnNames)
77 { 69 {
78 var name = columnNames[i]; 70 var column = customColumnsById[tuple.Id.Id + "/" + name];
71
79 var type = ColumnType.Unknown; 72 var type = ColumnType.Unknown;
80 73
81 if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase)) 74 if (column.Type == IntermediateFieldType.String)
82 {
83 type = ColumnType.String;
84 }
85 else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase))
86 { 75 {
87 type = ColumnType.Localized; 76 type = column.Localizable ? ColumnType.Localized : ColumnType.String;
88 } 77 }
89 else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase)) 78 else if (column.Type == IntermediateFieldType.Number)
90 { 79 {
91 type = ColumnType.Number; 80 type = ColumnType.Number;
92 } 81 }
93 else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase)) 82 else if (column.Type == IntermediateFieldType.Path)
94 { 83 {
95 type = ColumnType.Object; 84 type = ColumnType.Object;
96 } 85 }
97 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
112 var category = ColumnCategory.Unknown; 87 var category = ColumnCategory.Unknown;
113 if (null != categories && null != categories[i] && 0 < categories[i].Length) 88 switch (column.Category)
114 { 89 {
115 switch (categories[i]) 90 case "Text":
116 { 91 category = ColumnCategory.Text;
117 case "Text": 92 break;
118 category = ColumnCategory.Text; 93 case "UpperCase":
119 break; 94 category = ColumnCategory.UpperCase;
120 case "UpperCase": 95 break;
121 category = ColumnCategory.UpperCase; 96 case "LowerCase":
122 break; 97 category = ColumnCategory.LowerCase;
123 case "LowerCase": 98 break;
124 category = ColumnCategory.LowerCase; 99 case "Integer":
125 break; 100 category = ColumnCategory.Integer;
126 case "Integer": 101 break;
127 category = ColumnCategory.Integer; 102 case "DoubleInteger":
128 break; 103 category = ColumnCategory.DoubleInteger;
129 case "DoubleInteger": 104 break;
130 category = ColumnCategory.DoubleInteger; 105 case "TimeDate":
131 break; 106 category = ColumnCategory.TimeDate;
132 case "TimeDate": 107 break;
133 category = ColumnCategory.TimeDate; 108 case "Identifier":
134 break; 109 category = ColumnCategory.Identifier;
135 case "Identifier": 110 break;
136 category = ColumnCategory.Identifier; 111 case "Property":
137 break; 112 category = ColumnCategory.Property;
138 case "Property": 113 break;
139 category = ColumnCategory.Property; 114 case "Filename":
140 break; 115 category = ColumnCategory.Filename;
141 case "Filename": 116 break;
142 category = ColumnCategory.Filename; 117 case "WildCardFilename":
143 break; 118 category = ColumnCategory.WildCardFilename;
144 case "WildCardFilename": 119 break;
145 category = ColumnCategory.WildCardFilename; 120 case "Path":
146 break; 121 category = ColumnCategory.Path;
147 case "Path": 122 break;
148 category = ColumnCategory.Path; 123 case "Paths":
149 break; 124 category = ColumnCategory.Paths;
150 case "Paths": 125 break;
151 category = ColumnCategory.Paths; 126 case "AnyPath":
152 break; 127 category = ColumnCategory.AnyPath;
153 case "AnyPath": 128 break;
154 category = ColumnCategory.AnyPath; 129 case "DefaultDir":
155 break; 130 category = ColumnCategory.DefaultDir;
156 case "DefaultDir": 131 break;
157 category = ColumnCategory.DefaultDir; 132 case "RegPath":
158 break; 133 category = ColumnCategory.RegPath;
159 case "RegPath": 134 break;
160 category = ColumnCategory.RegPath; 135 case "Formatted":
161 break; 136 category = ColumnCategory.Formatted;
162 case "Formatted": 137 break;
163 category = ColumnCategory.Formatted; 138 case "FormattedSddl":
164 break; 139 category = ColumnCategory.FormattedSDDLText;
165 case "FormattedSddl": 140 break;
166 category = ColumnCategory.FormattedSDDLText; 141 case "Template":
167 break; 142 category = ColumnCategory.Template;
168 case "Template": 143 break;
169 category = ColumnCategory.Template; 144 case "Condition":
170 break; 145 category = ColumnCategory.Condition;
171 case "Condition": 146 break;
172 category = ColumnCategory.Condition; 147 case "Guid":
173 break; 148 category = ColumnCategory.Guid;
174 case "Guid": 149 break;
175 category = ColumnCategory.Guid; 150 case "Version":
176 break; 151 category = ColumnCategory.Version;
177 case "Version": 152 break;
178 category = ColumnCategory.Version; 153 case "Language":
179 break; 154 category = ColumnCategory.Language;
180 case "Language": 155 break;
181 category = ColumnCategory.Language; 156 case "Binary":
182 break; 157 category = ColumnCategory.Binary;
183 case "Binary": 158 break;
184 category = ColumnCategory.Binary; 159 case "CustomSource":
185 break; 160 category = ColumnCategory.CustomSource;
186 case "CustomSource": 161 break;
187 category = ColumnCategory.CustomSource; 162 case "Cabinet":
188 break; 163 category = ColumnCategory.Cabinet;
189 case "Cabinet": 164 break;
190 category = ColumnCategory.Cabinet; 165 case "Shortcut":
191 break; 166 category = ColumnCategory.Shortcut;
192 case "Shortcut": 167 break;
193 category = ColumnCategory.Shortcut; 168 default:
194 break; 169 break;
195 default:
196 break;
197 }
198 } 170 }
199 171
200 var keyTable = keyTables?[i];
201 var setValue = sets?[i];
202 var description = descriptions?[i];
203 var modString = modularizations?[i];
204 var modularization = ColumnModularizeType.None; 172 var modularization = ColumnModularizeType.None;
205 173
206 switch (modString) 174 switch (column.Modularize)
207 { 175 {
208 case null: 176 case null:
209 case "None": 177 case WixCustomTableColumnModularizeType.None:
210 modularization = ColumnModularizeType.None; 178 modularization = ColumnModularizeType.None;
211 break; 179 break;
212 case "Column": 180 case WixCustomTableColumnModularizeType.Column:
213 modularization = ColumnModularizeType.Column; 181 modularization = ColumnModularizeType.Column;
214 break; 182 break;
215 case "Property": 183 case WixCustomTableColumnModularizeType.CompanionFile:
216 modularization = ColumnModularizeType.Property; 184 modularization = ColumnModularizeType.CompanionFile;
217 break; 185 break;
218 case "Condition": 186 case WixCustomTableColumnModularizeType.Condition:
219 modularization = ColumnModularizeType.Condition; 187 modularization = ColumnModularizeType.Condition;
220 break; 188 break;
221 case "CompanionFile": 189 case WixCustomTableColumnModularizeType.ControlEventArgument:
222 modularization = ColumnModularizeType.CompanionFile; 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;
223 break; 200 break;
224 case "SemicolonDelimited": 201 case WixCustomTableColumnModularizeType.SemicolonDelimited:
225 modularization = ColumnModularizeType.SemicolonDelimited; 202 modularization = ColumnModularizeType.SemicolonDelimited;
226 break; 203 break;
227 } 204 }
228 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);
230 columns.Add(columnDefinition); 207 columns.Add(columnDefinition);
231 } 208 }
232 209
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
index 3e680a98..af7e262a 100644
--- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
@@ -4,7 +4,9 @@ namespace WixToolset.Core.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Linq;
7 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Data.Tuples;
8 using WixToolset.Extensibility; 10 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Data; 11 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services; 12 using WixToolset.Extensibility.Services;
@@ -42,6 +44,9 @@ namespace WixToolset.Core.Bind
42 44
43 var fileResolver = new FileResolver(this.BindPaths, this.Extensions); 45 var fileResolver = new FileResolver(this.BindPaths, this.Extensions);
44 46
47 // Build the column lookup only when needed.
48 Dictionary<string, WixCustomTableColumnTuple> customColumnsById = null;
49
45 foreach (var sections in this.Intermediate.Sections) 50 foreach (var sections in this.Intermediate.Sections)
46 { 51 {
47 foreach (var tuple in sections.Tuples) 52 foreach (var tuple in sections.Tuples)
@@ -53,13 +58,37 @@ namespace WixToolset.Core.Bind
53 continue; 58 continue;
54 } 59 }
55 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
56 var isDefault = true; 85 var isDefault = true;
57 86
58 // Check to make sure we're in a scenario where we can handle variable resolution. 87 // Check to make sure we're in a scenario where we can handle variable resolution.
59 if (null != delayedFields) 88 if (null != delayedFields)
60 { 89 {
61 // resolve localization and wix variables 90 // resolve localization and wix variables
62 if (field.Type == IntermediateFieldType.String) 91 if (fieldType == IntermediateFieldType.String)
63 { 92 {
64 var original = field.AsString(); 93 var original = field.AsString();
65 if (!String.IsNullOrEmpty(original)) 94 if (!String.IsNullOrEmpty(original))
@@ -87,7 +116,7 @@ namespace WixToolset.Core.Bind
87 } 116 }
88 117
89 // Resolve file paths 118 // Resolve file paths
90 if (field.Type == IntermediateFieldType.Path) 119 if (fieldType == IntermediateFieldType.Path)
91 { 120 {
92 var objectField = field.AsPath(); 121 var objectField = field.AsPath();
93 122
@@ -226,29 +255,5 @@ namespace WixToolset.Core.Bind
226 255
227 this.DelayedFields = delayedFields; 256 this.DelayedFields = delayedFields;
228 } 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
253 } 258 }
254} 259}
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs
index 32e9b9d6..35a73e00 100644
--- a/src/WixToolset.Core/Compiler.cs
+++ b/src/WixToolset.Core/Compiler.cs
@@ -8,6 +8,7 @@ namespace WixToolset.Core
8 using System.Diagnostics.CodeAnalysis; 8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization; 9 using System.Globalization;
10 using System.IO; 10 using System.IO;
11 using System.Linq;
11 using System.Text.RegularExpressions; 12 using System.Text.RegularExpressions;
12 using System.Xml.Linq; 13 using System.Xml.Linq;
13 using WixToolset.Data; 14 using WixToolset.Data;
@@ -3667,20 +3668,8 @@ namespace WixToolset.Core
3667 { 3668 {
3668 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); 3669 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3669 string tableId = null; 3670 string tableId = null;
3670 3671 var unreal = false;
3671 string categories = null; 3672 var columns = new List<WixCustomTableColumnTuple>();
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;
3684 3673
3685 foreach (var attrib in node.Attributes()) 3674 foreach (var attrib in node.Attributes())
3686 { 3675 {
@@ -3692,7 +3681,7 @@ namespace WixToolset.Core
3692 tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); 3681 tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3693 break; 3682 break;
3694 case "Unreal": 3683 case "Unreal":
3695 bootstrapperApplicationData = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); 3684 unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3696 break; 3685 break;
3697 default: 3686 default:
3698 this.Core.UnexpectedAttribute(node, attrib); 3687 this.Core.UnexpectedAttribute(node, attrib);
@@ -3722,22 +3711,20 @@ namespace WixToolset.Core
3722 switch (child.Name.LocalName) 3711 switch (child.Name.LocalName)
3723 { 3712 {
3724 case "Column": 3713 case "Column":
3725 ++columnCount;
3726
3727 var category = String.Empty;
3728 string columnName = null; 3714 string columnName = null;
3729 string columnType = null; 3715 var category = String.Empty;
3716 IntermediateFieldType? columnType = null;
3730 var description = String.Empty; 3717 var description = String.Empty;
3731 var keyColumn = CompilerConstants.IntegerNotSet; 3718 int? keyColumn = null;
3732 var keyTable = String.Empty; 3719 var keyTable = String.Empty;
3733 var localizable = false; 3720 var localizable = false;
3734 var maxValue = CompilerConstants.LongNotSet; 3721 long? maxValue = null;
3735 var minValue = CompilerConstants.LongNotSet; 3722 long? minValue = null;
3736 var modularization = "None"; 3723 var modularization = WixCustomTableColumnModularizeType.None;
3737 var nullable = false; 3724 var nullable = false;
3738 var primaryKey = false; 3725 var primaryKey = false;
3739 var setValues = String.Empty; 3726 var setValues = String.Empty;
3740 string typeName = null; 3727 var columnUnreal = false;
3741 var width = 0; 3728 var width = 0;
3742 3729
3743 foreach (var childAttrib in child.Attributes()) 3730 foreach (var childAttrib in child.Attributes())
@@ -3769,7 +3756,43 @@ namespace WixToolset.Core
3769 minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); 3756 minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue);
3770 break; 3757 break;
3771 case "Modularize": 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 }
3773 break; 3796 break;
3774 case "Nullable": 3797 case "Nullable":
3775 nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); 3798 nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
@@ -3785,24 +3808,28 @@ namespace WixToolset.Core
3785 switch (typeValue) 3808 switch (typeValue)
3786 { 3809 {
3787 case "binary": 3810 case "binary":
3788 typeName = "OBJECT"; 3811 columnType = IntermediateFieldType.Path;
3789 break; 3812 break;
3790 case "int": 3813 case "int":
3791 typeName = "SHORT"; 3814 columnType = IntermediateFieldType.Number;
3792 break; 3815 break;
3793 case "string": 3816 case "string":
3794 typeName = "CHAR"; 3817 columnType = IntermediateFieldType.String;
3795 break; 3818 break;
3796 case "": 3819 case "":
3797 break; 3820 break;
3798 default: 3821 default:
3799 this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); 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.
3800 break; 3824 break;
3801 } 3825 }
3802 break; 3826 break;
3803 case "Width": 3827 case "Width":
3804 width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); 3828 width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue);
3805 break; 3829 break;
3830 case "Unreal":
3831 columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3832 break;
3806 default: 3833 default:
3807 this.Core.UnexpectedAttribute(child, childAttrib); 3834 this.Core.UnexpectedAttribute(child, childAttrib);
3808 break; 3835 break;
@@ -3814,100 +3841,59 @@ namespace WixToolset.Core
3814 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); 3841 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id"));
3815 } 3842 }
3816 3843
3817 if (null == typeName) 3844 if (!columnType.HasValue)
3818 { 3845 {
3819 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); 3846 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type"));
3820 } 3847 }
3821 else if ("SHORT" == typeName) 3848 else if (columnType == IntermediateFieldType.Number)
3822 { 3849 {
3823 if (2 != width && 4 != width) 3850 if (2 != width && 4 != width)
3824 { 3851 {
3825 this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); 3852 this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width));
3826 } 3853 }
3827 columnType = String.Concat(nullable ? "I" : "i", width);
3828 } 3854 }
3829 else if ("CHAR" == typeName) 3855 else if (columnType == IntermediateFieldType.Path)
3830 { 3856 {
3831 var typeChar = localizable ? "l" : "s"; 3857 if (String.IsNullOrEmpty(category))
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)
3837 { 3858 {
3838 this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); 3859 category = "Binary";
3839 } 3860 }
3840 columnType = String.Concat(nullable ? "V" : "v", width); 3861 else if (category != "Binary")
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)
3874 { 3862 {
3875 case "Data": 3863 this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers));
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;
3897 } 3864 }
3898 } 3865 }
3899 3866
3900 this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId); 3867 this.Core.ParseForExtensionElements(child);
3901 3868
3902 if (!this.Core.EncounteredError) 3869 if (!this.Core.EncounteredError)
3903 { 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))
3905 { 3877 {
3906 Table = tableId, 3878 TableRef = tableId,
3907 FieldData = dataValue, 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,
3908 }); 3891 });
3909 } 3892 }
3910 break; 3893 break;
3894 case "Row":
3895 this.ParseRow(child, tableId);
3896 break;
3911 default: 3897 default:
3912 this.Core.UnexpectedElement(node, child); 3898 this.Core.UnexpectedElement(node, child);
3913 break; 3899 break;
@@ -3919,35 +3905,98 @@ namespace WixToolset.Core
3919 } 3905 }
3920 } 3906 }
3921 3907
3922 if (0 < columnCount) 3908 if (columns.Count > 0)
3923 { 3909 {
3924 if (null == primaryKeys || 0 == primaryKeys.Length) 3910 if (!columns.Where(c => c.PrimaryKey).Any())
3925 { 3911 {
3926 this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); 3912 this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers));
3927 } 3913 }
3928 3914
3929 if (!this.Core.EncounteredError) 3915 if (!this.Core.EncounteredError)
3930 { 3916 {
3917 var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name));
3918
3931 this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) 3919 this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId))
3932 { 3920 {
3933 ColumnCount = columnCount,
3934 ColumnNames = columnNames, 3921 ColumnNames = columnNames,
3935 ColumnTypes = columnTypes, 3922 Unreal = unreal,
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,
3946 }); 3923 });
3924
3925 foreach (var column in columns)
3926 {
3927 this.Core.AddTuple(column);
3928 }
3947 } 3929 }
3948 } 3930 }
3949 } 3931 }
3950 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
3951 /// <summary> 4000 /// <summary>
3952 /// Parses a directory element. 4001 /// Parses a directory element.
3953 /// </summary> 4002 /// </summary>
diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
index 53036919..80c00ef1 100644
--- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
@@ -46,7 +46,7 @@ namespace WixToolsetTest.CoreIntegration
46 var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable"); 46 var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable");
47 Assert.Equal(3, customElements.Count); 47 Assert.Equal(3, customElements.Count);
48 Assert.Equal("<BundleCustomTable Id='one' Column2='two' />", customElements[0].GetTestXml()); 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()); 50 Assert.Equal("<BundleCustomTable Id='1' Column2='2' />", customElements[2].GetTestXml());
51 } 51 }
52 } 52 }
diff --git a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
index aa8a0a0d..78a8f0a4 100644
--- a/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs
@@ -473,6 +473,121 @@ namespace WixToolsetTest.CoreIntegration
473 } 473 }
474 474
475 [Fact] 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]
476 public void UnrealCustomTableIsNotPresentInMsi() 591 public void UnrealCustomTableIsNotPresentInMsi()
477 { 592 {
478 var folder = TestData.Get(@"TestData"); 593 var folder = TestData.Get(@"TestData");
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs
new file mode 100644
index 00000000..ad5ed233
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs
@@ -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>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl
new file mode 100644
index 00000000..bc2ccf04
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl
@@ -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>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs
new file mode 100644
index 00000000..e1da74f8
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs
@@ -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>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt
new file mode 100644
index 00000000..97f701ce
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt
@@ -0,0 +1 @@
This is file1.txt \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt
new file mode 100644
index 00000000..46493186
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt
@@ -0,0 +1 @@
This is file2.txt \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt
new file mode 100644
index 00000000..cd0db0e1
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt
@@ -0,0 +1 @@
This is test.txt. \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
index f9f1ba44..51775cd0 100644
--- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
@@ -34,7 +34,13 @@
34 <Content Include="TestData\Class\IconIndex0.wxs" CopyToOutputDirectory="PreserveNewest" /> 34 <Content Include="TestData\Class\IconIndex0.wxs" CopyToOutputDirectory="PreserveNewest" />
35 <Content Include="TestData\Class\OldClassTableDef.msi" CopyToOutputDirectory="PreserveNewest" /> 35 <Content Include="TestData\Class\OldClassTableDef.msi" CopyToOutputDirectory="PreserveNewest" />
36 <Content Include="TestData\CustomAction\UnscheduledCustomAction.wxs" 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" />
37 <Content Include="TestData\CustomTable\CustomTable.wxs" CopyToOutputDirectory="PreserveNewest" /> 42 <Content Include="TestData\CustomTable\CustomTable.wxs" CopyToOutputDirectory="PreserveNewest" />
43 <Content Include="TestData\CustomTable\LocalizedCustomTable.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
38 <Content Include="TestData\DefaultDir\DefaultDir.wxs" CopyToOutputDirectory="PreserveNewest" /> 44 <Content Include="TestData\DefaultDir\DefaultDir.wxs" CopyToOutputDirectory="PreserveNewest" />
39 <Content Include="TestData\DialogsInInstallUISequence\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" /> 45 <Content Include="TestData\DialogsInInstallUISequence\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
40 <Content Include="TestData\EnsureTable\EnsureTable.wxs" CopyToOutputDirectory="PreserveNewest" /> 46 <Content Include="TestData\EnsureTable\EnsureTable.wxs" CopyToOutputDirectory="PreserveNewest" />