From 9317f7c8ea709da55e4602eaaba06952bbf315b7 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 3 Jun 2020 02:19:16 -0700 Subject: Redesign CustomTable tuples to support resolving binary columns --- ...CreateBootstrapperApplicationManifestCommand.cs | 27 +- .../Bind/CreateOutputFromIRCommand.cs | 447 +++++++++++---------- .../Bind/GenerateDatabaseCommand.cs | 7 +- .../Bind/LoadTableDefinitionsCommand.cs | 257 ++++++------ src/WixToolset.Core/Bind/ResolveFieldsCommand.cs | 57 +-- src/WixToolset.Core/Compiler.cs | 275 +++++++------ .../BundleManifestFixture.cs | 2 +- .../MsiQueryFixture.cs | 115 ++++++ .../TestData/CustomTable/CustomTableWithFile.wxs | 22 + .../CustomTable/LocalizedCustomTable.en-us.wxl | 7 + .../TestData/CustomTable/LocalizedCustomTable.wxs | 21 + .../TestData/CustomTable/data/file1.txt | 1 + .../TestData/CustomTable/data/file2.txt | 1 + .../TestData/CustomTable/data/test.txt | 1 + .../WixToolsetTest.CoreIntegration.csproj | 6 + 15 files changed, 731 insertions(+), 515 deletions(-) create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt 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 } var dataTablesById = this.Section.Tuples.OfType() - .Where(t => t.Unreal && t.Id != null) - .ToDictionary(t => t.Id.Id); - var dataRowsByTable = this.Section.Tuples.OfType() - .GroupBy(t => t.Table); - foreach (var tableDataRows in dataRowsByTable) + .Where(t => t.Unreal && t.Id != null) + .ToDictionary(t => t.Id.Id); + var cellsByTable = this.Section.Tuples.OfType() + .GroupBy(t => t.TableRef); + foreach (var tableCells in cellsByTable) { - var tableName = tableDataRows.Key; + var tableName = tableCells.Key; if (!dataTablesById.TryGetValue(tableName, out var tableTuple)) { // This should have been a linker error. continue; } - var columnNames = tableTuple.ColumnNames.Split('\t'); + var columnNames = tableTuple.ColumnNamesSeparated; // We simply assert that the table (and field) name is valid, because // this is up to the extension developer to get right. An author will @@ -307,17 +307,18 @@ namespace WixToolset.Core.Burn.Bundles } #endif // DEBUG - foreach (var rowTuple in tableDataRows) + foreach (var rowCells in tableCells.GroupBy(t => t.RowId)) { + var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data); + writer.WriteStartElement(tableName); - //var rowFields = rowTuple.FieldDataSeparated; - foreach (var field in rowTuple.FieldDataSeparated) + // Write all row data as attributes in table column order. + foreach (var column in columnNames) { - var splitField = field.Split(ColonCharacter, 2); - if (splitField.Length == 2) + if (rowDataByColumn.TryGetValue(column, out var data)) { - writer.WriteAttributeString(splitField[0], splitField[1]); + writer.WriteAttributeString(column, data); } } 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 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) - private static readonly char[] ColonCharacter = new[] { ':' }; - public CreateOutputFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable backendExtensions, IWindowsInstallerBackendHelper backendHelper) { this.Messaging = messaging; @@ -54,171 +52,174 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddSectionToOutput() { + var cellsByTableAndRowId = new Dictionary>(); + foreach (var tuple in this.Section.Tuples) { var unknownTuple = false; switch (tuple.Definition.Type) { - case TupleDefinitionType.AppSearch: - this.AddTupleDefaultly(tuple); - this.Output.EnsureTable(this.TableDefinitions["Signature"]); - break; + case TupleDefinitionType.AppSearch: + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Signature"]); + break; - case TupleDefinitionType.Assembly: - this.AddAssemblyTuple((AssemblyTuple)tuple); - break; + case TupleDefinitionType.Assembly: + this.AddAssemblyTuple((AssemblyTuple)tuple); + break; - case TupleDefinitionType.BBControl: - this.AddBBControlTuple((BBControlTuple)tuple); - break; + case TupleDefinitionType.BBControl: + this.AddBBControlTuple((BBControlTuple)tuple); + break; - case TupleDefinitionType.Class: - this.AddClassTuple((ClassTuple)tuple); - break; + case TupleDefinitionType.Class: + this.AddClassTuple((ClassTuple)tuple); + break; - case TupleDefinitionType.Control: - this.AddControlTuple((ControlTuple)tuple); - break; + case TupleDefinitionType.Control: + this.AddControlTuple((ControlTuple)tuple); + break; - case TupleDefinitionType.Component: - this.AddComponentTuple((ComponentTuple)tuple); - break; + case TupleDefinitionType.Component: + this.AddComponentTuple((ComponentTuple)tuple); + break; - case TupleDefinitionType.CustomAction: - this.AddCustomActionTuple((CustomActionTuple)tuple); - break; + case TupleDefinitionType.CustomAction: + this.AddCustomActionTuple((CustomActionTuple)tuple); + break; - case TupleDefinitionType.Dialog: - this.AddDialogTuple((DialogTuple)tuple); - break; + case TupleDefinitionType.Dialog: + this.AddDialogTuple((DialogTuple)tuple); + break; - case TupleDefinitionType.Directory: - this.AddDirectoryTuple((DirectoryTuple)tuple); - break; + case TupleDefinitionType.Directory: + this.AddDirectoryTuple((DirectoryTuple)tuple); + break; - case TupleDefinitionType.Environment: - this.AddEnvironmentTuple((EnvironmentTuple)tuple); - break; + case TupleDefinitionType.Environment: + this.AddEnvironmentTuple((EnvironmentTuple)tuple); + break; - case TupleDefinitionType.Error: - this.AddErrorTuple((ErrorTuple)tuple); - break; + case TupleDefinitionType.Error: + this.AddErrorTuple((ErrorTuple)tuple); + break; - case TupleDefinitionType.Feature: - this.AddFeatureTuple((FeatureTuple)tuple); - break; + case TupleDefinitionType.Feature: + this.AddFeatureTuple((FeatureTuple)tuple); + break; - case TupleDefinitionType.File: - this.AddFileTuple((FileTuple)tuple); - break; + case TupleDefinitionType.File: + this.AddFileTuple((FileTuple)tuple); + break; - case TupleDefinitionType.IniFile: - this.AddIniFileTuple((IniFileTuple)tuple); - break; + case TupleDefinitionType.IniFile: + this.AddIniFileTuple((IniFileTuple)tuple); + break; - case TupleDefinitionType.Media: - this.AddMediaTuple((MediaTuple)tuple); - break; + case TupleDefinitionType.Media: + this.AddMediaTuple((MediaTuple)tuple); + break; - case TupleDefinitionType.ModuleConfiguration: - this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); - break; + case TupleDefinitionType.ModuleConfiguration: + this.AddModuleConfigurationTuple((ModuleConfigurationTuple)tuple); + break; - case TupleDefinitionType.MsiEmbeddedUI: - this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); - break; + case TupleDefinitionType.MsiEmbeddedUI: + this.AddMsiEmbeddedUITuple((MsiEmbeddedUITuple)tuple); + break; - case TupleDefinitionType.MsiServiceConfig: - this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); - break; + case TupleDefinitionType.MsiServiceConfig: + this.AddMsiServiceConfigTuple((MsiServiceConfigTuple)tuple); + break; - case TupleDefinitionType.MsiServiceConfigFailureActions: - this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); - break; + case TupleDefinitionType.MsiServiceConfigFailureActions: + this.AddMsiServiceConfigFailureActionsTuple((MsiServiceConfigFailureActionsTuple)tuple); + break; - case TupleDefinitionType.MoveFile: - this.AddMoveFileTuple((MoveFileTuple)tuple); - break; + case TupleDefinitionType.MoveFile: + this.AddMoveFileTuple((MoveFileTuple)tuple); + break; - case TupleDefinitionType.ProgId: - this.AddTupleDefaultly(tuple); - this.Output.EnsureTable(this.TableDefinitions["Extension"]); - break; + case TupleDefinitionType.ProgId: + this.AddTupleDefaultly(tuple); + this.Output.EnsureTable(this.TableDefinitions["Extension"]); + break; - case TupleDefinitionType.Property: - this.AddPropertyTuple((PropertyTuple)tuple); - break; + case TupleDefinitionType.Property: + this.AddPropertyTuple((PropertyTuple)tuple); + break; - case TupleDefinitionType.RemoveFile: - this.AddRemoveFileTuple((RemoveFileTuple)tuple); - break; + case TupleDefinitionType.RemoveFile: + this.AddRemoveFileTuple((RemoveFileTuple)tuple); + break; - case TupleDefinitionType.Registry: - this.AddRegistryTuple((RegistryTuple)tuple); - break; + case TupleDefinitionType.Registry: + this.AddRegistryTuple((RegistryTuple)tuple); + break; - case TupleDefinitionType.RegLocator: - this.AddRegLocatorTuple((RegLocatorTuple)tuple); - break; + case TupleDefinitionType.RegLocator: + this.AddRegLocatorTuple((RegLocatorTuple)tuple); + break; - case TupleDefinitionType.RemoveRegistry: - this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); - break; + case TupleDefinitionType.RemoveRegistry: + this.AddRemoveRegistryTuple((RemoveRegistryTuple)tuple); + break; - case TupleDefinitionType.ServiceControl: - this.AddServiceControlTuple((ServiceControlTuple)tuple); - break; + case TupleDefinitionType.ServiceControl: + this.AddServiceControlTuple((ServiceControlTuple)tuple); + break; - case TupleDefinitionType.ServiceInstall: - this.AddServiceInstallTuple((ServiceInstallTuple)tuple); - break; + case TupleDefinitionType.ServiceInstall: + this.AddServiceInstallTuple((ServiceInstallTuple)tuple); + break; - case TupleDefinitionType.Shortcut: - this.AddShortcutTuple((ShortcutTuple)tuple); - break; + case TupleDefinitionType.Shortcut: + this.AddShortcutTuple((ShortcutTuple)tuple); + break; - case TupleDefinitionType.TextStyle: - this.AddTextStyleTuple((TextStyleTuple)tuple); - break; + case TupleDefinitionType.TextStyle: + this.AddTextStyleTuple((TextStyleTuple)tuple); + break; - case TupleDefinitionType.Upgrade: - this.AddUpgradeTuple((UpgradeTuple)tuple); - break; + case TupleDefinitionType.Upgrade: + this.AddUpgradeTuple((UpgradeTuple)tuple); + break; - case TupleDefinitionType.WixAction: - this.AddWixActionTuple((WixActionTuple)tuple); - break; + case TupleDefinitionType.WixAction: + this.AddWixActionTuple((WixActionTuple)tuple); + break; - case TupleDefinitionType.WixMediaTemplate: - this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); - break; + case TupleDefinitionType.WixMediaTemplate: + this.AddWixMediaTemplateTuple((WixMediaTemplateTuple)tuple); + break; - case TupleDefinitionType.WixCustomRow: - this.AddWixCustomRowTuple((WixCustomRowTuple)tuple); - break; + case TupleDefinitionType.WixCustomTableCell: + this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId); + break; - case TupleDefinitionType.WixEnsureTable: - this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); - break; + case TupleDefinitionType.WixEnsureTable: + this.AddWixEnsureTableTuple((WixEnsureTableTuple)tuple); + break; - // ignored. - case TupleDefinitionType.WixComponentGroup: - case TupleDefinitionType.WixDeltaPatchFile: - case TupleDefinitionType.WixFeatureGroup: - case TupleDefinitionType.WixPatchBaseline: + // ignored. + case TupleDefinitionType.WixComponentGroup: + case TupleDefinitionType.WixDeltaPatchFile: + case TupleDefinitionType.WixFeatureGroup: + case TupleDefinitionType.WixPatchBaseline: break; - // Already processed. - case TupleDefinitionType.WixCustomTable: - break; + // Already processed by LoadTableDefinitions. + case TupleDefinitionType.WixCustomTable: + case TupleDefinitionType.WixCustomTableColumn: + break; - case TupleDefinitionType.MustBeFromAnExtension: - unknownTuple = !this.AddTupleFromExtension(tuple); - break; + case TupleDefinitionType.MustBeFromAnExtension: + unknownTuple = !this.AddTupleFromExtension(tuple); + break; - default: - unknownTuple = !this.AddTupleDefaultly(tuple); - break; + default: + unknownTuple = !this.AddTupleDefaultly(tuple); + break; } if (unknownTuple) @@ -226,6 +227,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple)); } } + + this.AddIndexedCellTuples(cellsByTableAndRowId); } private void AddAssemblyTuple(AssemblyTuple tuple) @@ -383,16 +386,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDialogTuple(DialogTuple tuple) { var attributes = tuple.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0; - attributes|= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; - attributes|= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; - attributes|= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette: 0; - attributes|= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; - attributes|= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; - attributes|= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; - attributes|= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; - attributes|= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; - attributes|= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; - attributes|= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; + attributes |= tuple.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0; + attributes |= tuple.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0; + attributes |= tuple.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0; + attributes |= tuple.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0; + attributes |= tuple.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0; + attributes |= tuple.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0; + attributes |= tuple.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0; + attributes |= tuple.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0; + attributes |= tuple.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0; + attributes |= tuple.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0; var row = this.CreateRow(tuple, "Dialog"); row[0] = tuple.Id.Id; @@ -419,7 +422,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetName = "."; } - var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName ; + var defaultDir = String.IsNullOrEmpty(sourceName) ? targetName : targetName + ":" + sourceName; var row = this.CreateRow(tuple, "Directory"); row[0] = tuple.Id.Id; @@ -436,25 +439,25 @@ namespace WixToolset.Core.WindowsInstaller.Bind switch (tuple.Action) { - case EnvironmentActionType.Create: - action = "+"; - break; - case EnvironmentActionType.Set: - action = "="; - break; - case EnvironmentActionType.Remove: - action = "!"; - break; + case EnvironmentActionType.Create: + action = "+"; + break; + case EnvironmentActionType.Set: + action = "="; + break; + case EnvironmentActionType.Remove: + action = "!"; + break; } switch (tuple.Part) { - case EnvironmentPartType.First: - value = String.Concat(value, tuple.Separator, "[~]"); - break; - case EnvironmentPartType.Last: - value = String.Concat("[~]", tuple.Separator, value); - break; + case EnvironmentPartType.First: + value = String.Concat(value, tuple.Separator, "[~]"); + break; + case EnvironmentPartType.Last: + value = String.Concat("[~]", tuple.Separator, value); + break; } var row = this.CreateRow(tuple, "Environment"); @@ -661,40 +664,40 @@ namespace WixToolset.Core.WindowsInstaller.Bind switch (tuple.ValueType) { - case RegistryValueType.Binary: - value = String.Concat("#x", value); - break; - case RegistryValueType.Expandable: - value = String.Concat("#%", value); - break; - case RegistryValueType.Integer: - value = String.Concat("#", value); - break; - case RegistryValueType.MultiString: - switch (tuple.ValueAction) - { - case RegistryValueActionType.Append: - value = String.Concat("[~]", value); + case RegistryValueType.Binary: + value = String.Concat("#x", value); break; - case RegistryValueActionType.Prepend: - value = String.Concat(value, "[~]"); + case RegistryValueType.Expandable: + value = String.Concat("#%", value); break; - case RegistryValueActionType.Write: - default: - if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + case RegistryValueType.Integer: + value = String.Concat("#", value); + break; + case RegistryValueType.MultiString: + switch (tuple.ValueAction) { - value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + case RegistryValueActionType.Append: + value = String.Concat("[~]", value); + break; + case RegistryValueActionType.Prepend: + value = String.Concat(value, "[~]"); + break; + case RegistryValueActionType.Write: + default: + if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal)) + { + value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value); + } + break; + } + break; + case RegistryValueType.String: + // escape the leading '#' character for string registry keys + if (null != value && value.StartsWith("#", StringComparison.Ordinal)) + { + value = String.Concat("#", value); } break; - } - break; - case RegistryValueType.String: - // escape the leading '#' character for string registry keys - if (null != value && value.StartsWith("#", StringComparison.Ordinal)) - { - value = String.Concat("#", value); - } - break; } var row = this.CreateRow(tuple, "Registry"); @@ -757,7 +760,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[3] = tuple.Arguments; if (tuple.Wait.HasValue) { - row[4] = tuple.Wait.Value ? 1 : 0; + row[4] = tuple.Wait.Value ? 1 : 0; } row[5] = tuple.ComponentRef; } @@ -938,83 +941,89 @@ namespace WixToolset.Core.WindowsInstaller.Bind row[2] = tuple.Sequence; } } - - private void AddWixCustomRowTuple(WixCustomRowTuple tuple) - { - var customTableDefinition = this.TableDefinitions[tuple.Table]; - if (customTableDefinition.Unreal) + private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary> cellsByTableAndRowId) + { + var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId; + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) { - - return; + cells = new List(); + cellsByTableAndRowId.Add(tableAndRowId, cells); } - var customRow = this.CreateRow(tuple, customTableDefinition); + cells.Add(wixCustomTableCellTuple); + } -#if TODO // SectionId seems like a good thing to preserve. - customRow.SectionId = tuple.SectionId; -#endif + private void AddIndexedCellTuples(Dictionary> cellsByTableAndRowId) + { + foreach (var rowOfCells in cellsByTableAndRowId.Values) + { + var firstCellTuple = rowOfCells[0]; + var customTableDefinition = this.TableDefinitions[firstCellTuple.TableRef]; - var data = tuple.FieldDataSeparated; + if (customTableDefinition.Unreal) + { + return; + } - for (var i = 0; i < data.Length; ++i) - { - var foundColumn = false; - var item = data[i].Split(ColonCharacter, 2); + var customRow = this.CreateRow(firstCellTuple, customTableDefinition); + var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name); - for (var j = 0; j < customRow.Fields.Length; ++j) +#if TODO // SectionId seems like a good thing to preserve. + customRow.SectionId = tuple.SectionId; +#endif + foreach (var cell in rowOfCells) { - if (customRow.Fields[j].Column.Name == item[0]) + var data = cell.Data; + + if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField)) { - if (0 < item[1].Length) + if (!String.IsNullOrEmpty(data)) { - if (ColumnType.Number == customRow.Fields[j].Column.Type) + if (rowField.Column.Type == ColumnType.Number) { try { - customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture); + rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture); } catch (FormatException) { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); } catch (OverflowException) { - this.Messaging.Write(ErrorMessages.IllegalIntegerValue(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); + this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data)); } } - else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category) + else if (rowField.Column.Category == ColumnCategory.Identifier) { - if (Common.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category) + if (Common.IsIdentifier(data) || Common.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category) { - customRow.Fields[j].Data = item[1]; + rowField.Data = data; } else { - this.Messaging.Write(ErrorMessages.IllegalIdentifier(tuple.SourceLineNumbers, "Data", item[1])); + this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data)); } } else { - customRow.Fields[j].Data = item[1]; + rowField.Data = data; } } - foundColumn = true; - break; + } + else + { + this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef)); } } - if (!foundColumn) - { - this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(tuple.SourceLineNumbers, item[0])); - } - } - - for (var i = 0; i < customTableDefinition.Columns.Length; ++i) - { - if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + for (var i = 0; i < customTableDefinition.Columns.Length; ++i) { - this.Messaging.Write(ErrorMessages.NoDataForColumn(tuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) + { + this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellTuple.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); + } } } } 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 break; case ColumnType.Object: - if (null != row[i]) + var path = row.FieldAsString(i); + if (null != path) { needStream = true; try { - record.SetStream(i + 1, row.FieldAsString(i)); + record.SetStream(i + 1, path); } catch (Win32Exception e) { if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { - throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, row.FieldAsString(i))); + throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path)); } else { 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 public TableDefinitionCollection Execute() { var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); + var customColumnsById = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); - foreach (var tuple in this.Section.Tuples.OfType()) + if (customColumnsById.Any()) { - var customTableDefinition = this.CreateCustomTable(tuple); - tableDefinitions.Add(customTableDefinition); + foreach (var tuple in this.Section.Tuples.OfType()) + { + var customTableDefinition = this.CreateCustomTable(tuple, customColumnsById); + tableDefinitions.Add(customTableDefinition); + } } foreach (var backendExtension in this.BackendExtensions) @@ -56,177 +60,150 @@ namespace WixToolset.Core.WindowsInstaller.Bind return this.TableDefinitions; } - private TableDefinition CreateCustomTable(WixCustomTableTuple tuple) + private TableDefinition CreateCustomTable(WixCustomTableTuple tuple, Dictionary customColumnsById) { - var columnNames = tuple.ColumnNames.Split('\t'); - var columnTypes = tuple.ColumnTypes.Split('\t'); - var primaryKeys = tuple.PrimaryKeys.Split('\t'); - var minValues = tuple.MinValues?.Split('\t'); - var maxValues = tuple.MaxValues?.Split('\t'); - var keyTables = tuple.KeyTables?.Split('\t'); - var keyColumns = tuple.KeyColumns?.Split('\t'); - var categories = tuple.Categories?.Split('\t'); - var sets = tuple.Sets?.Split('\t'); - var descriptions = tuple.Descriptions?.Split('\t'); - var modularizations = tuple.Modularizations?.Split('\t'); - - var currentPrimaryKey = 0; - + var columnNames = tuple.ColumnNamesSeparated; var columns = new List(columnNames.Length); - for (var i = 0; i < columnNames.Length; ++i) + + foreach (var name in columnNames) { - var name = columnNames[i]; + var column = customColumnsById[tuple.Id.Id + "/" + name]; + var type = ColumnType.Unknown; - if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase)) - { - type = ColumnType.String; - } - else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase)) + if (column.Type == IntermediateFieldType.String) { - type = ColumnType.Localized; + type = column.Localizable ? ColumnType.Localized : ColumnType.String; } - else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase)) + else if (column.Type == IntermediateFieldType.Number) { type = ColumnType.Number; } - else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase)) + else if (column.Type == IntermediateFieldType.Path) { type = ColumnType.Object; } - var nullable = columnTypes[i].Substring(0, 1) == columnTypes[i].Substring(0, 1).ToUpperInvariant(); - var length = Convert.ToInt32(columnTypes[i].Substring(1), CultureInfo.InvariantCulture); - - var primaryKey = false; - if (currentPrimaryKey < primaryKeys.Length && primaryKeys[currentPrimaryKey] == columnNames[i]) - { - primaryKey = true; - currentPrimaryKey++; - } - - var minValue = String.IsNullOrEmpty(minValues?[i]) ? (int?)null : Convert.ToInt32(minValues[i], CultureInfo.InvariantCulture); - var maxValue = String.IsNullOrEmpty(maxValues?[i]) ? (int?)null : Convert.ToInt32(maxValues[i], CultureInfo.InvariantCulture); - var keyColumn = String.IsNullOrEmpty(keyColumns?[i]) ? (int?)null : Convert.ToInt32(keyColumns[i], CultureInfo.InvariantCulture); - var category = ColumnCategory.Unknown; - if (null != categories && null != categories[i] && 0 < categories[i].Length) + switch (column.Category) { - switch (categories[i]) - { - case "Text": - category = ColumnCategory.Text; - break; - case "UpperCase": - category = ColumnCategory.UpperCase; - break; - case "LowerCase": - category = ColumnCategory.LowerCase; - break; - case "Integer": - category = ColumnCategory.Integer; - break; - case "DoubleInteger": - category = ColumnCategory.DoubleInteger; - break; - case "TimeDate": - category = ColumnCategory.TimeDate; - break; - case "Identifier": - category = ColumnCategory.Identifier; - break; - case "Property": - category = ColumnCategory.Property; - break; - case "Filename": - category = ColumnCategory.Filename; - break; - case "WildCardFilename": - category = ColumnCategory.WildCardFilename; - break; - case "Path": - category = ColumnCategory.Path; - break; - case "Paths": - category = ColumnCategory.Paths; - break; - case "AnyPath": - category = ColumnCategory.AnyPath; - break; - case "DefaultDir": - category = ColumnCategory.DefaultDir; - break; - case "RegPath": - category = ColumnCategory.RegPath; - break; - case "Formatted": - category = ColumnCategory.Formatted; - break; - case "FormattedSddl": - category = ColumnCategory.FormattedSDDLText; - break; - case "Template": - category = ColumnCategory.Template; - break; - case "Condition": - category = ColumnCategory.Condition; - break; - case "Guid": - category = ColumnCategory.Guid; - break; - case "Version": - category = ColumnCategory.Version; - break; - case "Language": - category = ColumnCategory.Language; - break; - case "Binary": - category = ColumnCategory.Binary; - break; - case "CustomSource": - category = ColumnCategory.CustomSource; - break; - case "Cabinet": - category = ColumnCategory.Cabinet; - break; - case "Shortcut": - category = ColumnCategory.Shortcut; - break; - default: - break; - } + case "Text": + category = ColumnCategory.Text; + break; + case "UpperCase": + category = ColumnCategory.UpperCase; + break; + case "LowerCase": + category = ColumnCategory.LowerCase; + break; + case "Integer": + category = ColumnCategory.Integer; + break; + case "DoubleInteger": + category = ColumnCategory.DoubleInteger; + break; + case "TimeDate": + category = ColumnCategory.TimeDate; + break; + case "Identifier": + category = ColumnCategory.Identifier; + break; + case "Property": + category = ColumnCategory.Property; + break; + case "Filename": + category = ColumnCategory.Filename; + break; + case "WildCardFilename": + category = ColumnCategory.WildCardFilename; + break; + case "Path": + category = ColumnCategory.Path; + break; + case "Paths": + category = ColumnCategory.Paths; + break; + case "AnyPath": + category = ColumnCategory.AnyPath; + break; + case "DefaultDir": + category = ColumnCategory.DefaultDir; + break; + case "RegPath": + category = ColumnCategory.RegPath; + break; + case "Formatted": + category = ColumnCategory.Formatted; + break; + case "FormattedSddl": + category = ColumnCategory.FormattedSDDLText; + break; + case "Template": + category = ColumnCategory.Template; + break; + case "Condition": + category = ColumnCategory.Condition; + break; + case "Guid": + category = ColumnCategory.Guid; + break; + case "Version": + category = ColumnCategory.Version; + break; + case "Language": + category = ColumnCategory.Language; + break; + case "Binary": + category = ColumnCategory.Binary; + break; + case "CustomSource": + category = ColumnCategory.CustomSource; + break; + case "Cabinet": + category = ColumnCategory.Cabinet; + break; + case "Shortcut": + category = ColumnCategory.Shortcut; + break; + default: + break; } - var keyTable = keyTables?[i]; - var setValue = sets?[i]; - var description = descriptions?[i]; - var modString = modularizations?[i]; var modularization = ColumnModularizeType.None; - switch (modString) + switch (column.Modularize) { case null: - case "None": + case WixCustomTableColumnModularizeType.None: modularization = ColumnModularizeType.None; break; - case "Column": + case WixCustomTableColumnModularizeType.Column: modularization = ColumnModularizeType.Column; break; - case "Property": - modularization = ColumnModularizeType.Property; + case WixCustomTableColumnModularizeType.CompanionFile: + modularization = ColumnModularizeType.CompanionFile; break; - case "Condition": + case WixCustomTableColumnModularizeType.Condition: modularization = ColumnModularizeType.Condition; break; - case "CompanionFile": - modularization = ColumnModularizeType.CompanionFile; + case WixCustomTableColumnModularizeType.ControlEventArgument: + modularization = ColumnModularizeType.ControlEventArgument; + break; + case WixCustomTableColumnModularizeType.ControlText: + modularization = ColumnModularizeType.ControlText; + break; + case WixCustomTableColumnModularizeType.Icon: + modularization = ColumnModularizeType.Icon; + break; + case WixCustomTableColumnModularizeType.Property: + modularization = ColumnModularizeType.Property; break; - case "SemicolonDelimited": + case WixCustomTableColumnModularizeType.SemicolonDelimited: modularization = ColumnModularizeType.SemicolonDelimited; break; } - var columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, category, minValue, maxValue, keyTable, keyColumn, setValue, description, modularization, ColumnType.Localized == type, true); + 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); columns.Add(columnDefinition); } 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 { using System; using System.Collections.Generic; + using System.Linq; using WixToolset.Data; + using WixToolset.Data.Tuples; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; @@ -42,6 +44,9 @@ namespace WixToolset.Core.Bind var fileResolver = new FileResolver(this.BindPaths, this.Extensions); + // Build the column lookup only when needed. + Dictionary customColumnsById = null; + foreach (var sections in this.Intermediate.Sections) { foreach (var tuple in sections.Tuples) @@ -53,13 +58,37 @@ namespace WixToolset.Core.Bind continue; } + var fieldType = field.Type; + + // Custom table cells require an extra look up to the column definition as the + // cell's data type is always a string (because strings can store anything) but + // the column definition may be more specific. + if (tuple.Definition.Type == TupleDefinitionType.WixCustomTableCell) + { + // We only care about the Data in a CustomTable cell. + if (field.Name != nameof(WixCustomTableCellTupleFields.Data)) + { + continue; + } + + if (customColumnsById == null) + { + customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Tuples.OfType()).ToDictionary(t => t.Id.Id); + } + + if (customColumnsById.TryGetValue(tuple.Fields[(int)WixCustomTableCellTupleFields.TableRef].AsString() + "/" + tuple.Fields[(int)WixCustomTableCellTupleFields.ColumnRef].AsString(), out var customColumn)) + { + fieldType = customColumn.Type; + } + } + var isDefault = true; // Check to make sure we're in a scenario where we can handle variable resolution. if (null != delayedFields) { // resolve localization and wix variables - if (field.Type == IntermediateFieldType.String) + if (fieldType == IntermediateFieldType.String) { var original = field.AsString(); if (!String.IsNullOrEmpty(original)) @@ -87,7 +116,7 @@ namespace WixToolset.Core.Bind } // Resolve file paths - if (field.Type == IntermediateFieldType.Path) + if (fieldType == IntermediateFieldType.Path) { var objectField = field.AsPath(); @@ -226,29 +255,5 @@ namespace WixToolset.Core.Bind this.DelayedFields = delayedFields; } - -#if false - private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) - { - string path = null; - foreach (var extension in this.Extensions) - { - path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); - if (null != path) - { - break; - } - } - - throw new NotImplementedException(); // need to do default binder stuff - - //if (null == path) - //{ - // throw new WixFileNotFoundException(sourceLineNumbers, source, type); - //} - - //return path; - } -#endif } } 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 using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; + using System.Linq; using System.Text.RegularExpressions; using System.Xml.Linq; using WixToolset.Data; @@ -3667,20 +3668,8 @@ namespace WixToolset.Core { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string tableId = null; - - string categories = null; - var columnCount = 0; - string columnNames = null; - string columnTypes = null; - string descriptions = null; - string keyColumns = null; - string keyTables = null; - string maxValues = null; - string minValues = null; - string modularizations = null; - string primaryKeys = null; - string sets = null; - var bootstrapperApplicationData = false; + var unreal = false; + var columns = new List(); foreach (var attrib in node.Attributes()) { @@ -3692,7 +3681,7 @@ namespace WixToolset.Core tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); break; case "Unreal": - bootstrapperApplicationData = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); + unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -3722,22 +3711,20 @@ namespace WixToolset.Core switch (child.Name.LocalName) { case "Column": - ++columnCount; - - var category = String.Empty; string columnName = null; - string columnType = null; + var category = String.Empty; + IntermediateFieldType? columnType = null; var description = String.Empty; - var keyColumn = CompilerConstants.IntegerNotSet; + int? keyColumn = null; var keyTable = String.Empty; var localizable = false; - var maxValue = CompilerConstants.LongNotSet; - var minValue = CompilerConstants.LongNotSet; - var modularization = "None"; + long? maxValue = null; + long? minValue = null; + var modularization = WixCustomTableColumnModularizeType.None; var nullable = false; var primaryKey = false; var setValues = String.Empty; - string typeName = null; + var columnUnreal = false; var width = 0; foreach (var childAttrib in child.Attributes()) @@ -3769,7 +3756,43 @@ namespace WixToolset.Core minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue); break; case "Modularize": - modularization = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib); + switch (modularizeValue) + { + case "column": + modularization = WixCustomTableColumnModularizeType.Column; + break; + case "companionFile": + modularization = WixCustomTableColumnModularizeType.CompanionFile; + break; + case "condition": + modularization = WixCustomTableColumnModularizeType.Condition; + break; + case "controlEventArgument": + modularization = WixCustomTableColumnModularizeType.ControlEventArgument; + break; + case "controlText": + modularization = WixCustomTableColumnModularizeType.ControlText; + break; + case "icon": + modularization = WixCustomTableColumnModularizeType.Icon; + break; + case "none": + modularization = WixCustomTableColumnModularizeType.None; + break; + case "property": + modularization = WixCustomTableColumnModularizeType.Property; + break; + case "semicolonDelimited": + modularization = WixCustomTableColumnModularizeType.SemicolonDelimited; + break; + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. + break; + } break; case "Nullable": nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); @@ -3785,24 +3808,28 @@ namespace WixToolset.Core switch (typeValue) { case "binary": - typeName = "OBJECT"; + columnType = IntermediateFieldType.Path; break; case "int": - typeName = "SHORT"; + columnType = IntermediateFieldType.Number; break; case "string": - typeName = "CHAR"; + columnType = IntermediateFieldType.String; break; case "": break; default: this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string")); + columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below. break; } break; case "Width": width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue); break; + case "Unreal": + columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib); + break; default: this.Core.UnexpectedAttribute(child, childAttrib); break; @@ -3814,100 +3841,59 @@ namespace WixToolset.Core this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id")); } - if (null == typeName) + if (!columnType.HasValue) { this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type")); } - else if ("SHORT" == typeName) + else if (columnType == IntermediateFieldType.Number) { if (2 != width && 4 != width) { this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width)); } - columnType = String.Concat(nullable ? "I" : "i", width); } - else if ("CHAR" == typeName) + else if (columnType == IntermediateFieldType.Path) { - var typeChar = localizable ? "l" : "s"; - columnType = String.Concat(nullable ? typeChar.ToUpper(CultureInfo.InvariantCulture) : typeChar.ToLower(CultureInfo.InvariantCulture), width); - } - else if ("OBJECT" == typeName) - { - if ("Binary" != category) + if (String.IsNullOrEmpty(category)) { - this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); + category = "Binary"; } - columnType = String.Concat(nullable ? "V" : "v", width); - } - - this.Core.ParseForExtensionElements(child); - - columnNames = String.Concat(columnNames, null == columnNames ? String.Empty : "\t", columnName); - columnTypes = String.Concat(columnTypes, null == columnTypes ? String.Empty : "\t", columnType); - if (primaryKey) - { - primaryKeys = String.Concat(primaryKeys, null == primaryKeys ? String.Empty : "\t", columnName); - } - - minValues = String.Concat(minValues, null == minValues ? String.Empty : "\t", CompilerConstants.LongNotSet != minValue ? minValue.ToString(CultureInfo.InvariantCulture) : String.Empty); - maxValues = String.Concat(maxValues, null == maxValues ? String.Empty : "\t", CompilerConstants.LongNotSet != maxValue ? maxValue.ToString(CultureInfo.InvariantCulture) : String.Empty); - keyTables = String.Concat(keyTables, null == keyTables ? String.Empty : "\t", keyTable); - keyColumns = String.Concat(keyColumns, null == keyColumns ? String.Empty : "\t", CompilerConstants.IntegerNotSet != keyColumn ? keyColumn.ToString(CultureInfo.InvariantCulture) : String.Empty); - categories = String.Concat(categories, null == categories ? String.Empty : "\t", category); - sets = String.Concat(sets, null == sets ? String.Empty : "\t", setValues); - descriptions = String.Concat(descriptions, null == descriptions ? String.Empty : "\t", description); - modularizations = String.Concat(modularizations, null == modularizations ? String.Empty : "\t", modularization); - - break; - case "Row": - string dataValue = null; - - foreach (var childAttrib in child.Attributes()) - { - this.Core.ParseExtensionAttribute(child, childAttrib); - } - - foreach (var data in child.Elements()) - { - var dataSourceLineNumbers = Preprocessor.GetSourceLineNumbers(data); - switch (data.Name.LocalName) + else if (category != "Binary") { - case "Data": - columnName = null; - foreach (var dataAttrib in data.Attributes()) - { - switch (dataAttrib.Name.LocalName) - { - case "Column": - columnName = this.Core.GetAttributeValue(dataSourceLineNumbers, dataAttrib); - break; - default: - this.Core.UnexpectedAttribute(data, dataAttrib); - break; - } - } - - if (null == columnName) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(dataSourceLineNumbers, data.Name.LocalName, "Column")); - } - - dataValue = String.Concat(dataValue, null == dataValue ? String.Empty : WixCustomRowTuple.FieldSeparator.ToString(), columnName, ":", Common.GetInnerText(data)); - break; + this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers)); } } - this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId); + this.Core.ParseForExtensionElements(child); if (!this.Core.EncounteredError) { - this.Core.AddTuple(new WixCustomRowTuple(childSourceLineNumbers) + var attributes = primaryKey ? WixCustomTableColumnTupleAttributes.PrimaryKey : WixCustomTableColumnTupleAttributes.None; + attributes |= localizable ? WixCustomTableColumnTupleAttributes.Localizable : WixCustomTableColumnTupleAttributes.None; + attributes |= nullable ? WixCustomTableColumnTupleAttributes.Nullable : WixCustomTableColumnTupleAttributes.None; + attributes |= columnUnreal ? WixCustomTableColumnTupleAttributes.Unreal : WixCustomTableColumnTupleAttributes.None; + + columns.Add(new WixCustomTableColumnTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, columnName)) { - Table = tableId, - FieldData = dataValue, + TableRef = tableId, + Name = columnName, + Type = columnType.Value, + Attributes = attributes, + Width = width, + Category = category, + Description = description, + KeyColumn = keyColumn, + KeyTable = keyTable, + MaxValue = maxValue, + MinValue = minValue, + Modularize = modularization, + Set = setValues, }); } break; + case "Row": + this.ParseRow(child, tableId); + break; default: this.Core.UnexpectedElement(node, child); break; @@ -3919,35 +3905,98 @@ namespace WixToolset.Core } } - if (0 < columnCount) + if (columns.Count > 0) { - if (null == primaryKeys || 0 == primaryKeys.Length) + if (!columns.Where(c => c.PrimaryKey).Any()) { this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers)); } if (!this.Core.EncounteredError) { + var columnNames = String.Join(new string(WixCustomTableTuple.ColumnNamesSeparator, 1), columns.Select(c => c.Name)); + this.Core.AddTuple(new WixCustomTableTuple(sourceLineNumbers, new Identifier(AccessModifier.Public, tableId)) { - ColumnCount = columnCount, ColumnNames = columnNames, - ColumnTypes = columnTypes, - PrimaryKeys = primaryKeys, - MinValues = minValues, - MaxValues = maxValues, - KeyTables = keyTables, - KeyColumns = keyColumns, - Categories = categories, - Sets = sets, - Descriptions = descriptions, - Modularizations = modularizations, - Unreal = bootstrapperApplicationData, + Unreal = unreal, }); + + foreach (var column in columns) + { + this.Core.AddTuple(column); + } } } } + private void ParseRow(XElement node, string tableId) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant(); + + foreach (var attrib in node.Attributes()) + { + this.Core.ParseExtensionAttribute(node, attrib); + } + + foreach (var child in node.Elements()) + { + var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child); + switch (child.Name.LocalName) + { + case "Data": + string columnName = null; + string data = null; + foreach (var attrib in child.Attributes()) + { + switch (attrib.Name.LocalName) + { + case "Column": + columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + case "Value": + data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib); + break; + default: + this.Core.ParseExtensionAttribute(child, attrib); + break; + } + } + + if (null == columnName) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column")); + } + + if (String.IsNullOrEmpty(data)) + { + data = Common.GetInnerText(child); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddTuple(new WixCustomTableCellTuple(childSourceLineNumbers, new Identifier(AccessModifier.Private, tableId, rowId, columnName)) + { + RowId = rowId, + ColumnRef = columnName, + TableRef = tableId, + Data = data + }); + } + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + + if (!this.Core.EncounteredError) + { + this.Core.CreateSimpleReference(sourceLineNumbers, TupleDefinitions.WixCustomTable, tableId); + } + } + /// /// Parses a directory element. /// 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 var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTable"); Assert.Equal(3, customElements.Count); Assert.Equal("", customElements[0].GetTestXml()); - Assert.Equal("", customElements[1].GetTestXml()); + Assert.Equal("", customElements[1].GetTestXml()); Assert.Equal("", customElements[2].GetTestXml()); } } 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 @@ -472,6 +472,121 @@ namespace WixToolsetTest.CoreIntegration } } + [Fact] + public void PopulatesCustomTableWithLocalization() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" }); + Assert.Equal(new[] + { + "CustomTableLocalized:Row1\tThis is row one", + "CustomTableLocalized:Row2\tThis is row two", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePath() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + + [Fact] + public void PopulatesCustomTableWithFilePathSerialized() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib"); + var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"), + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", wixlibPath + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-lib", wixlibPath, + "-bindpath", Path.Combine(folder, "CustomTable", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" }); + Assert.Equal(new[] + { + "CustomTableWithFile:Row1\t[Binary data]", + "CustomTableWithFile:Row2\t[Binary data]", + }, results); + } + } + [Fact] public void UnrealCustomTableIsNotPresentInMsi() { 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 @@ + + + + + + + + + + + + Row1 + file1.txt + + + SourceDir\file2.txt + Row2 + + + + + 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 @@ + + + + This is row one + This is row two + + 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 @@ + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + -- cgit v1.2.3-55-g6feb