main
cs 245 lines 8.24 KB
Raw
1 // 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.
2
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Extensibility.Services;
12
13 internal class CreateIdtFileCommand
14 {
15 public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns)
16 {
17 this.Messaging = messaging;
18 this.Table = table;
19 this.Codepage = codepage;
20 this.IntermediateFolder = intermediateFolder;
21 this.KeepAddedColumns = keepAddedColumns;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private Table Table { get; }
27
28 private int Codepage { get; set; }
29
30 private string IntermediateFolder { get; }
31
32 private bool KeepAddedColumns { get; }
33
34 public string IdtPath { get; private set; }
35
36 public void Execute()
37 {
38 // write out the table to an IDT file
39 var encoding = GetCodepageEncoding(this.Codepage);
40
41 this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt"));
42
43 using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding))
44 {
45 this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns);
46 }
47 }
48
49 private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns)
50 {
51 if (table.Definition.Unreal)
52 {
53 return;
54 }
55
56 if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length)
57 {
58 throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable));
59 }
60
61 // Tack on the table header, and flush before we start writing bytes directly to the stream.
62 var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns);
63 writer.Write(header);
64 writer.Flush();
65
66 using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true))
67 {
68 // Create an encoding that replaces characters with question marks, and doesn't throw. We'll
69 // use this in case of errors
70 Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage);
71
72 foreach (Row row in table.Rows)
73 {
74 string rowString = this.RowToIdtDefinition(row, keepAddedColumns);
75 byte[] rowBytes;
76
77 try
78 {
79 // GetBytes will throw an exception if any character doesn't match our current encoding
80 rowBytes = writer.Encoding.GetBytes(rowString);
81 }
82 catch (EncoderFallbackException)
83 {
84 this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.CodePage, CultureInfo.InvariantCulture)));
85
86 rowBytes = convertEncoding.GetBytes(rowString);
87 }
88
89 binary.Write(rowBytes, 0, rowBytes.Length);
90 }
91 }
92 }
93
94 private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns)
95 {
96 var first = true;
97 var columnString = new StringBuilder();
98 var dataString = new StringBuilder();
99 var tableString = new StringBuilder();
100
101 tableString.Append(definition.Name);
102 foreach (var column in definition.Columns)
103 {
104 // Conditionally keep columns added in a transform; otherwise,
105 // break because columns can only be added at the end.
106 if (column.Added && !keepAddedColumns)
107 {
108 break;
109 }
110
111 if (column.Unreal)
112 {
113 continue;
114 }
115
116 if (!first)
117 {
118 columnString.Append('\t');
119 dataString.Append('\t');
120 }
121
122 columnString.Append(column.Name);
123 dataString.Append(ColumnIdtType(column));
124
125 if (column.PrimaryKey)
126 {
127 tableString.AppendFormat("\t{0}", column.Name);
128 }
129
130 first = false;
131 }
132 columnString.Append("\r\n");
133 columnString.Append(dataString);
134 columnString.Append("\r\n");
135 columnString.Append(tableString);
136 columnString.Append("\r\n");
137
138 return columnString.ToString();
139 }
140
141 private string RowToIdtDefinition(Row row, bool keepAddedColumns)
142 {
143 var first = true;
144 var sb = new StringBuilder();
145
146 foreach (var field in row.Fields)
147 {
148 // Conditionally keep columns added in a transform; otherwise,
149 // break because columns can only be added at the end.
150 if (field.Column.Added && !keepAddedColumns)
151 {
152 break;
153 }
154
155 if (field.Column.Unreal)
156 {
157 continue;
158 }
159
160 if (first)
161 {
162 first = false;
163 }
164 else
165 {
166 sb.Append('\t');
167 }
168
169 sb.Append(this.FieldToIdtValue(field));
170 }
171 sb.Append("\r\n");
172
173 return sb.ToString();
174 }
175
176 private string FieldToIdtValue(Field field)
177 {
178 var data = field.AsString();
179
180 if (String.IsNullOrEmpty(data))
181 {
182 return data;
183 }
184
185 // Special field value idt-specific escaping.
186 return data.Replace('\t', '\x10')
187 .Replace('\r', '\x11')
188 .Replace('\n', '\x19');
189 }
190
191 private static Encoding GetCodepageEncoding(int codepage)
192 {
193 Encoding encoding;
194
195 // If UTF8 encoding, use the UTF8-specific constructor to avoid writing
196 // the byte order mark at the beginning of the file
197 if (codepage == Encoding.UTF8.CodePage)
198 {
199 encoding = new UTF8Encoding(false, true);
200 }
201 else
202 {
203 if (codepage == 0)
204 {
205 codepage = Encoding.ASCII.CodePage;
206 }
207
208 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
209
210 encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback());
211 }
212
213 return encoding;
214 }
215
216 /// <summary>
217 /// Gets the type of the column in IDT format.
218 /// </summary>
219 /// <value>IDT format for column type.</value>
220 private static string ColumnIdtType(ColumnDefinition column)
221 {
222 char typeCharacter;
223 switch (column.Type)
224 {
225 case ColumnType.Number:
226 typeCharacter = column.Nullable ? 'I' : 'i';
227 break;
228 case ColumnType.Preserved:
229 case ColumnType.String:
230 typeCharacter = column.Nullable ? 'S' : 's';
231 break;
232 case ColumnType.Localized:
233 typeCharacter = column.Nullable ? 'L' : 'l';
234 break;
235 case ColumnType.Object:
236 typeCharacter = column.Nullable ? 'V' : 'v';
237 break;
238 default:
239 throw new InvalidOperationException($"Unknown column type: {column.Type}");
240 }
241
242 return String.Concat(typeCharacter, column.Length);
243 }
244 }
245 }