// 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.Core.WindowsInstaller.Bind { using System; using System.Globalization; using System.IO; using System.Text; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility.Services; internal class CreateIdtFileCommand { public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns) { this.Messaging = messaging; this.Table = table; this.Codepage = codepage; this.IntermediateFolder = intermediateFolder; this.KeepAddedColumns = keepAddedColumns; } private IMessaging Messaging { get; } private Table Table { get; } private int Codepage { get; set; } private string IntermediateFolder { get; } private bool KeepAddedColumns { get; } public string IdtPath { get; private set; } public void Execute() { // write out the table to an IDT file Encoding encoding; // If UTF8 encoding, use the UTF8-specific constructor to avoid writing // the byte order mark at the beginning of the file if (this.Codepage == Encoding.UTF8.CodePage) { encoding = new UTF8Encoding(false, true); } else { if (this.Codepage == 0) { this.Codepage = Encoding.ASCII.CodePage; } encoding = Encoding.GetEncoding(this.Codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); } this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt")); using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding)) { this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns); } } private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns) { if (table.Definition.Unreal) { return; } if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Count) { throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Count, TableDefinition.MaxColumnsInRealTable)); } // Tack on the table header, and flush before we start writing bytes directly to the stream. var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns); writer.Write(header); 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 table.Rows) { if (row.Redundant) { continue; } string rowString = this.RowToIdtDefinition(row, 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) { this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture))); rowBytes = convertEncoding.GetBytes(rowString); } binary.Write(rowBytes, 0, rowBytes.Length); } } } private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns) { var first = true; var columnString = new StringBuilder(); var dataString = new StringBuilder(); var tableString = new StringBuilder(); tableString.Append(definition.Name); foreach (var column in definition.Columns) { // conditionally keep columns added in a transform; otherwise, // break because columns can only be added at the end if (column.Added && !keepAddedColumns) { break; } if (!first) { columnString.Append('\t'); dataString.Append('\t'); } columnString.Append(column.Name); dataString.Append(ColumnIdtType(column)); if (column.PrimaryKey) { tableString.AppendFormat("\t{0}", column.Name); } first = false; } columnString.Append("\r\n"); columnString.Append(dataString); columnString.Append("\r\n"); columnString.Append(tableString); columnString.Append("\r\n"); return columnString.ToString(); } private string RowToIdtDefinition(Row row, bool keepAddedColumns) { var first = true; var sb = new StringBuilder(); foreach (var field in row.Fields) { // Conditionally keep columns added in a transform; otherwise, // break because columns can only be added at the end. if (field.Column.Added && !keepAddedColumns) { break; } if (first) { first = false; } else { sb.Append('\t'); } sb.Append(this.FieldToIdtValue(field)); } sb.Append("\r\n"); return sb.ToString(); } private string FieldToIdtValue(Field field) { var data = field.AsString(); if (String.IsNullOrEmpty(data)) { return data; } // Special field value idt-specific escaping. return data.Replace('\t', '\x10') .Replace('\r', '\x11') .Replace('\n', '\x19'); } /// /// Gets the type of the column in IDT format. /// /// IDT format for column type. private static string ColumnIdtType(ColumnDefinition column) { char typeCharacter; switch (column.Type) { case ColumnType.Number: typeCharacter = column.Nullable ? 'I' : 'i'; break; case ColumnType.Preserved: case ColumnType.String: typeCharacter = column.Nullable ? 'S' : 's'; break; case ColumnType.Localized: typeCharacter = column.Nullable ? 'L' : 'l'; break; case ColumnType.Object: typeCharacter = column.Nullable ? 'V' : 'v'; break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type)); } return String.Concat(typeCharacter, column.Length); } } }