// 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.WindowsInstaller { using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml; /// /// Output is generated by the linker. /// public sealed class WindowsInstallerData { internal const string XmlNamespaceUri = "http://wixtoolset.org/schemas/v4/windowsinstallerdata"; internal const string XmlElementName = "windowsInstallerData"; private static readonly Version CurrentVersion = new Version("4.0.0.0"); private const string WixOutputStreamName = "wix-wid.xml"; /// /// Creates a new empty output object. /// /// The source line information for the output. public WindowsInstallerData(SourceLineNumber sourceLineNumbers) { this.SourceLineNumbers = sourceLineNumbers; this.SubStorages = new List(); this.Tables = new TableIndexedCollection(); } /// /// Gets the type of the output. /// /// Type of the output. public OutputType Type { get; set; } /// /// Gets or sets the codepage for this output. /// /// Codepage of the output. public int Codepage { get; set; } /// /// Gets the source line information for this output. /// /// The source line information for this output. public SourceLineNumber SourceLineNumbers { get; private set; } /// /// Gets the substorages in this output. /// /// The substorages in this output. public ICollection SubStorages { get; private set; } /// /// Gets the tables contained in this output. /// /// Collection of tables. public TableIndexedCollection Tables { get; private set; } /// /// Ensure this output contains a particular table. /// /// Definition of the table that should exist. /// Optional section to use for the table. If one is not provided, the entry section will be used. /// The table in this output. public Table EnsureTable(TableDefinition tableDefinition) { if (!this.Tables.TryGetTable(tableDefinition.Name, out var table)) { table = new Table(tableDefinition); this.Tables.Add(table); } return table; } /// /// Saves an output to a WixOutput container. /// /// Container to save to. public void Save(WixOutput wixout) { using (var writer = XmlWriter.Create(wixout.CreateDataStream(WixOutputStreamName))) { writer.WriteStartDocument(); this.Write(writer); writer.WriteEndDocument(); } } /// /// Gets table by name. /// public bool TryGetTable(string tableName, out Table table) => this.Tables.TryGetTable(tableName, out table); /// /// Loads an output from a path on disk. /// /// Path to output file saved on disk. /// Suppresses wix.dll version mismatch check. /// Output object. public static WindowsInstallerData Load(string path, bool suppressVersionCheck = false) { using (var wixOutput = WixOutput.Read(path)) { return WindowsInstallerData.Load(wixOutput, suppressVersionCheck); } } /// /// Loads an output from a WixOutput object. /// /// WixOutput object. /// Suppresses wix.dll version mismatch check. /// Output object. public static WindowsInstallerData Load(WixOutput wixOutput, bool suppressVersionCheck = false) { using (var stream = wixOutput.GetDataStream(WixOutputStreamName)) using (var reader = XmlReader.Create(stream, null, wixOutput.Uri.AbsoluteUri)) { try { reader.MoveToContent(); return WindowsInstallerData.Read(reader, suppressVersionCheck); } catch (XmlException xe) { throw new WixCorruptFileException(wixOutput.Uri.AbsoluteUri, "wixout", xe); } } } /// /// Processes an XmlReader and builds up the output object. /// /// Reader to get data from. /// Suppresses wix.dll version mismatch check. /// The Output represented by the Xml. internal static WindowsInstallerData Read(XmlReader reader, bool suppressVersionCheck) { if (!reader.LocalName.Equals(WindowsInstallerData.XmlElementName)) { throw new XmlException(); } var empty = reader.IsEmptyElement; var output = new WindowsInstallerData(SourceLineNumber.CreateFromUri(reader.BaseURI)); Version version = null; while (reader.MoveToNextAttribute()) { switch (reader.LocalName) { case "codepage": output.Codepage = Convert.ToInt32(reader.Value, CultureInfo.InvariantCulture.NumberFormat); break; case "type": switch (reader.Value) { case "Bundle": output.Type = OutputType.Bundle; break; case "Module": output.Type = OutputType.Module; break; case "Patch": output.Type = OutputType.Patch; break; case "PatchCreation": output.Type = OutputType.PatchCreation; break; case "Product": output.Type = OutputType.Product; break; case "Transform": output.Type = OutputType.Transform; break; default: throw new XmlException(); } break; case "version": version = new Version(reader.Value); break; } } if (!suppressVersionCheck && null != version && !WindowsInstallerData.CurrentVersion.Equals(version)) { throw new WixException(ErrorMessages.VersionMismatch(SourceLineNumber.CreateFromUri(reader.BaseURI), WindowsInstallerData.XmlElementName, version.ToString(), WindowsInstallerData.CurrentVersion.ToString())); } // loop through the rest of the xml building up the Output object TableDefinitionCollection tableDefinitions = null; var tables = new List(); if (!empty) { var done = false; // loop through all the fields in a row while (!done && reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: switch (reader.LocalName) { case "subStorage": output.SubStorages.Add(SubStorage.Read(reader)); break; case "table": if (null == tableDefinitions) { throw new XmlException(); } tables.Add(Table.Read(reader, tableDefinitions)); break; case "tableDefinitions": tableDefinitions = TableDefinitionCollection.Read(reader); break; default: throw new XmlException(); } break; case XmlNodeType.EndElement: done = true; break; } } if (!done) { throw new XmlException(); } } output.Tables = new TableIndexedCollection(tables); return output; } /// /// Persists an output in an XML format. /// /// XmlWriter where the Output should persist itself as XML. internal void Write(XmlWriter writer) { writer.WriteStartElement(WindowsInstallerData.XmlElementName, XmlNamespaceUri); writer.WriteAttributeString("type", this.Type.ToString()); if (0 != this.Codepage) { writer.WriteAttributeString("codepage", this.Codepage.ToString(CultureInfo.InvariantCulture)); } writer.WriteAttributeString("version", WindowsInstallerData.CurrentVersion.ToString()); // Collect all the table definitions and write them. var tableDefinitions = new TableDefinitionCollection(); foreach (var table in this.Tables) { tableDefinitions.Add(table.Definition); } tableDefinitions.Write(writer); foreach (var table in this.Tables.OrderBy(t => t.Name)) { table.Write(writer); } foreach (var subStorage in this.SubStorages) { subStorage.Write(writer); } writer.WriteEndElement(); } } }