From 2bb37beda887d120a0ddabf874ad25357101faa1 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Wed, 1 Nov 2017 10:59:45 -0700 Subject: Update to WiX Intermediate Representation --- src/WixToolset.Data.WindowsInstaller/Table.cs | 435 ++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 src/WixToolset.Data.WindowsInstaller/Table.cs (limited to 'src/WixToolset.Data.WindowsInstaller/Table.cs') diff --git a/src/WixToolset.Data.WindowsInstaller/Table.cs b/src/WixToolset.Data.WindowsInstaller/Table.cs new file mode 100644 index 00000000..4c0df0ad --- /dev/null +++ b/src/WixToolset.Data.WindowsInstaller/Table.cs @@ -0,0 +1,435 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Data +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Data.Rows; + + /// + /// Object that represents a table in a database. + /// + public sealed class Table + { + /// + /// Creates a table in a section. + /// + /// Section to add table to. + /// Definition of the table. + public Table(TableDefinition tableDefinition) + { + this.Definition = tableDefinition; + this.Rows = new List(); + } + + /// + /// Gets the table definition. + /// + /// Definition of the table. + public TableDefinition Definition { get; private set; } + + /// + /// Gets the name of the table. + /// + /// Name of the table. + public string Name + { + get { return this.Definition.Name; } + } + + /// + /// Gets or sets the table transform operation. + /// + /// The table transform operation. + public TableOperation Operation { get; set; } + + /// + /// Gets the rows contained in the table. + /// + /// Rows contained in the table. + public IList Rows { get; private set; } + + /// + /// Creates a new row in the table. + /// + /// Original source lines for this row. + /// Specifies whether to only create the row or add it to the table automatically. + /// Row created in table. + public Row CreateRow(SourceLineNumber sourceLineNumbers, bool add = true) + { + Row row; + + switch (this.Name) + { + case "BBControl": + row = new BBControlRow(sourceLineNumbers, this); + break; + case "WixBundlePackage": + row = new WixBundlePackageRow(sourceLineNumbers, this); + break; + case "WixBundleExePackage": + row = new WixBundleExePackageRow(sourceLineNumbers, this); + break; + case "WixBundleMsiPackage": + row = new WixBundleMsiPackageRow(sourceLineNumbers, this); + break; + case "WixBundleMspPackage": + row = new WixBundleMspPackageRow(sourceLineNumbers, this); + break; + case "WixBundleMsuPackage": + row = new WixBundleMsuPackageRow(sourceLineNumbers, this); + break; + case "Component": + row = new ComponentRow(sourceLineNumbers, this); + break; + case "WixBundleContainer": + row = new WixBundleContainerRow(sourceLineNumbers, this); + break; + case "Control": + row = new ControlRow(sourceLineNumbers, this); + break; + case "File": + row = new FileRow(sourceLineNumbers, this); + break; + case "WixBundleMsiFeature": + row = new WixBundleMsiFeatureRow(sourceLineNumbers, this); + break; + case "WixBundleMsiProperty": + row = new WixBundleMsiPropertyRow(sourceLineNumbers, this); + break; + case "Media": + row = new MediaRow(sourceLineNumbers, this); + break; + case "WixBundlePayload": + row = new WixBundlePayloadRow(sourceLineNumbers, this); + break; + case "Property": + row = new PropertyRow(sourceLineNumbers, this); + break; + case "WixRelatedBundle": + row = new WixRelatedBundleRow(sourceLineNumbers, this); + break; + case "WixBundleRelatedPackage": + row = new WixBundleRelatedPackageRow(sourceLineNumbers, this); + break; + case "WixBundleRollbackBoundary": + row = new WixBundleRollbackBoundaryRow(sourceLineNumbers, this); + break; + case "Upgrade": + row = new UpgradeRow(sourceLineNumbers, this); + break; + case "WixBundleVariable": + row = new WixBundleVariableRow(sourceLineNumbers, this); + break; + case "WixAction": + row = new WixActionRow(sourceLineNumbers, this); + break; + case "WixApprovedExeForElevation": + row = new WixApprovedExeForElevationRow(sourceLineNumbers, this); + break; + case "WixBundle": + row = new WixBundleRow(sourceLineNumbers, this); + break; + case "WixBundlePackageExitCode": + row = new WixBundlePackageExitCodeRow(sourceLineNumbers, this); + break; + case "WixBundlePatchTargetCode": + row = new WixBundlePatchTargetCodeRow(sourceLineNumbers, this); + break; + case "WixBundleSlipstreamMsp": + row = new WixBundleSlipstreamMspRow(sourceLineNumbers, this); + break; + case "WixBundleUpdate": + row = new WixBundleUpdateRow(sourceLineNumbers, this); + break; + case "WixBundleCatalog": + row = new WixBundleCatalogRow(sourceLineNumbers, this); + break; + case "WixChain": + row = new WixChainRow(sourceLineNumbers, this); + break; + case "WixChainItem": + row = new WixChainItemRow(sourceLineNumbers, this); + break; + case "WixBundlePackageCommandLine": + row = new WixBundlePackageCommandLineRow(sourceLineNumbers, this); + break; + case "WixComplexReference": + row = new WixComplexReferenceRow(sourceLineNumbers, this); + break; + case "WixDeltaPatchFile": + row = new WixDeltaPatchFileRow(sourceLineNumbers, this); + break; + case "WixDeltaPatchSymbolPaths": + row = new WixDeltaPatchSymbolPathsRow(sourceLineNumbers, this); + break; + case "WixFile": + row = new WixFileRow(sourceLineNumbers, this); + break; + case "WixGroup": + row = new WixGroupRow(sourceLineNumbers, this); + break; + case "WixMedia": + row = new WixMediaRow(sourceLineNumbers, this); + break; + case "WixMediaTemplate": + row = new WixMediaTemplateRow(sourceLineNumbers, this); + break; + case "WixMerge": + row = new WixMergeRow(sourceLineNumbers, this); + break; + case "WixPayloadProperties": + row = new WixPayloadPropertiesRow(sourceLineNumbers, this); + break; + case "WixProperty": + row = new WixPropertyRow(sourceLineNumbers, this); + break; + case "WixSimpleReference": + row = new WixSimpleReferenceRow(sourceLineNumbers, this); + break; + case "WixUpdateRegistration": + row = new WixUpdateRegistrationRow(sourceLineNumbers, this); + break; + + default: + row = new Row(sourceLineNumbers, this); + break; + } + + if (add) + { + this.Rows.Add(row); + } + + return row; + } + + /// + /// Parse a table from the xml. + /// + /// XmlReader where the intermediate is persisted. + /// Section to populate with persisted data. + /// TableDefinitions to use in the intermediate. + /// The parsed table. + internal static Table Read(XmlReader reader, TableDefinitionCollection tableDefinitions) + { + Debug.Assert("table" == reader.LocalName); + + bool empty = reader.IsEmptyElement; + TableOperation operation = TableOperation.None; + string name = null; + + while (reader.MoveToNextAttribute()) + { + switch (reader.LocalName) + { + case "name": + name = reader.Value; + break; + case "op": + switch (reader.Value) + { + case "add": + operation = TableOperation.Add; + break; + case "drop": + operation = TableOperation.Drop; + break; + default: + throw new XmlException(); + } + break; + } + } + + if (null == name) + { + throw new XmlException(); + } + + TableDefinition tableDefinition = tableDefinitions[name]; + Table table = new Table(tableDefinition); + table.Operation = operation; + + if (!empty) + { + bool done = false; + + // loop through all the rows in a table + while (!done && reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + switch (reader.LocalName) + { + case "row": + Row.Read(reader, table); + break; + default: + throw new XmlException(); + } + break; + case XmlNodeType.EndElement: + done = true; + break; + } + } + + if (!done) + { + throw new XmlException(); + } + } + + return table; + } + + /// + /// Modularize the table. + /// + /// String containing the GUID of the Merge Module, if appropriate. + /// Optional collection of identifiers that should not be modularized. + public void Modularize(string modularizationGuid, ISet suppressModularizationIdentifiers) + { + List modularizedColumns = new List(); + + // find the modularized columns + for (int i = 0; i < this.Definition.Columns.Count; i++) + { + if (ColumnModularizeType.None != this.Definition.Columns[i].ModularizeType) + { + modularizedColumns.Add(i); + } + } + + if (0 < modularizedColumns.Count) + { + foreach (Row row in this.Rows) + { + foreach (int modularizedColumn in modularizedColumns) + { + Field field = row.Fields[modularizedColumn]; + + if (null != field.Data) + { + field.Data = row.GetModularizedValue(field, modularizationGuid, suppressModularizationIdentifiers); + } + } + } + } + } + + /// + /// Persists a row in an XML format. + /// + /// XmlWriter where the Row should persist itself as XML. + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " + + "in a change to the way the intermediate files are generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " + + "Furthermore, there is no security hole here, as the strings won't need to make a round trip")] + internal void Write(XmlWriter writer) + { + if (null == writer) + { + throw new ArgumentNullException("writer"); + } + + writer.WriteStartElement("table", Intermediate.XmlNamespaceUri); + writer.WriteAttributeString("name", this.Name); + + if (TableOperation.None != this.Operation) + { + writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant()); + } + + foreach (Row row in this.Rows) + { + row.Write(writer); + } + + writer.WriteEndElement(); + } + + /// + /// Writes the table in IDT format to the provided stream. + /// + /// Stream to write the table to. + /// Whether to keep columns added in a transform. + public void ToIdtDefinition(StreamWriter writer, bool keepAddedColumns) + { + if (this.Definition.Unreal) + { + return; + } + + if (TableDefinition.MaxColumnsInRealTable < this.Definition.Columns.Count) + { + throw new WixException(WixDataErrors.TooManyColumnsInRealTable(this.Definition.Name, this.Definition.Columns.Count, TableDefinition.MaxColumnsInRealTable)); + } + + // Tack on the table header, and flush before we start writing bytes directly to the stream. + writer.Write(this.Definition.ToIdtDefinition(keepAddedColumns)); + writer.Flush(); + + using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true)) + { + // Create an encoding that replaces characters with question marks, and doesn't throw. We'll + // use this in case of errors + Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage); + + foreach (Row row in this.Rows) + { + if (row.Redundant) + { + continue; + } + + string rowString = row.ToIdtDefinition(keepAddedColumns); + byte[] rowBytes; + + try + { + // GetBytes will throw an exception if any character doesn't match our current encoding + rowBytes = writer.Encoding.GetBytes(rowString); + } + catch (EncoderFallbackException) + { + Messaging.Instance.OnMessage(WixDataErrors.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); + + rowBytes = convertEncoding.GetBytes(rowString); + } + + binary.Write(rowBytes, 0, rowBytes.Length); + } + } + } + + /// + /// Validates the rows of this OutputTable and throws if it collides on + /// primary keys. + /// + public void ValidateRows() + { + Dictionary primaryKeys = new Dictionary(); + + foreach (Row row in this.Rows) + { + string primaryKey = row.GetPrimaryKey(); + + SourceLineNumber collisionSourceLineNumber; + if (primaryKeys.TryGetValue(primaryKey, out collisionSourceLineNumber)) + { + throw new WixException(WixDataErrors.DuplicatePrimaryKey(collisionSourceLineNumber, primaryKey, this.Definition.Name)); + } + + primaryKeys.Add(primaryKey, row.SourceLineNumbers); + } + } + } +} -- cgit v1.2.3-55-g6feb