aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Data/Table.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Data/Table.cs')
-rw-r--r--src/WixToolset.Data/Table.cs446
1 files changed, 446 insertions, 0 deletions
diff --git a/src/WixToolset.Data/Table.cs b/src/WixToolset.Data/Table.cs
new file mode 100644
index 00000000..19a3a67d
--- /dev/null
+++ b/src/WixToolset.Data/Table.cs
@@ -0,0 +1,446 @@
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
3namespace WixToolset.Data
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 using System.Text;
12 using System.Xml;
13 using WixToolset.Data.Rows;
14
15 /// <summary>
16 /// Object that represents a table in a database.
17 /// </summary>
18 public sealed class Table
19 {
20 /// <summary>
21 /// Creates a table in a section.
22 /// </summary>
23 /// <param name="section">Section to add table to.</param>
24 /// <param name="tableDefinition">Definition of the table.</param>
25 public Table(Section section, TableDefinition tableDefinition)
26 {
27 this.Section = section;
28 this.Definition = tableDefinition;
29 this.Rows = new List<Row>();
30 }
31
32 /// <summary>
33 /// Gets the section for the table.
34 /// </summary>
35 /// <value>Section for the table.</value>
36 public Section Section { get; private set; }
37
38 /// <summary>
39 /// Gets the table definition.
40 /// </summary>
41 /// <value>Definition of the table.</value>
42 public TableDefinition Definition { get; private set; }
43
44 /// <summary>
45 /// Gets the name of the table.
46 /// </summary>
47 /// <value>Name of the table.</value>
48 public string Name
49 {
50 get { return this.Definition.Name; }
51 }
52
53 /// <summary>
54 /// Gets or sets the table transform operation.
55 /// </summary>
56 /// <value>The table transform operation.</value>
57 public TableOperation Operation { get; set; }
58
59 /// <summary>
60 /// Gets the rows contained in the table.
61 /// </summary>
62 /// <value>Rows contained in the table.</value>
63 public IList<Row> Rows { get; private set; }
64
65 /// <summary>
66 /// Creates a new row in the table.
67 /// </summary>
68 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
69 /// <param name="add">Specifies whether to only create the row or add it to the table automatically.</param>
70 /// <returns>Row created in table.</returns>
71 public Row CreateRow(SourceLineNumber sourceLineNumbers, bool add = true)
72 {
73 Row row;
74
75 switch (this.Name)
76 {
77 case "BBControl":
78 row = new BBControlRow(sourceLineNumbers, this);
79 break;
80 case "WixBundlePackage":
81 row = new WixBundlePackageRow(sourceLineNumbers, this);
82 break;
83 case "WixBundleExePackage":
84 row = new WixBundleExePackageRow(sourceLineNumbers, this);
85 break;
86 case "WixBundleMsiPackage":
87 row = new WixBundleMsiPackageRow(sourceLineNumbers, this);
88 break;
89 case "WixBundleMspPackage":
90 row = new WixBundleMspPackageRow(sourceLineNumbers, this);
91 break;
92 case "WixBundleMsuPackage":
93 row = new WixBundleMsuPackageRow(sourceLineNumbers, this);
94 break;
95 case "Component":
96 row = new ComponentRow(sourceLineNumbers, this);
97 break;
98 case "WixBundleContainer":
99 row = new WixBundleContainerRow(sourceLineNumbers, this);
100 break;
101 case "Control":
102 row = new ControlRow(sourceLineNumbers, this);
103 break;
104 case "File":
105 row = new FileRow(sourceLineNumbers, this);
106 break;
107 case "WixBundleMsiFeature":
108 row = new WixBundleMsiFeatureRow(sourceLineNumbers, this);
109 break;
110 case "WixBundleMsiProperty":
111 row = new WixBundleMsiPropertyRow(sourceLineNumbers, this);
112 break;
113 case "Media":
114 row = new MediaRow(sourceLineNumbers, this);
115 break;
116 case "WixBundlePayload":
117 row = new WixBundlePayloadRow(sourceLineNumbers, this);
118 break;
119 case "Property":
120 row = new PropertyRow(sourceLineNumbers, this);
121 break;
122 case "WixRelatedBundle":
123 row = new WixRelatedBundleRow(sourceLineNumbers, this);
124 break;
125 case "WixBundleRelatedPackage":
126 row = new WixBundleRelatedPackageRow(sourceLineNumbers, this);
127 break;
128 case "WixBundleRollbackBoundary":
129 row = new WixBundleRollbackBoundaryRow(sourceLineNumbers, this);
130 break;
131 case "Upgrade":
132 row = new UpgradeRow(sourceLineNumbers, this);
133 break;
134 case "WixBundleVariable":
135 row = new WixBundleVariableRow(sourceLineNumbers, this);
136 break;
137 case "WixAction":
138 row = new WixActionRow(sourceLineNumbers, this);
139 break;
140 case "WixApprovedExeForElevation":
141 row = new WixApprovedExeForElevationRow(sourceLineNumbers, this);
142 break;
143 case "WixBundle":
144 row = new WixBundleRow(sourceLineNumbers, this);
145 break;
146 case "WixBundlePackageExitCode":
147 row = new WixBundlePackageExitCodeRow(sourceLineNumbers, this);
148 break;
149 case "WixBundlePatchTargetCode":
150 row = new WixBundlePatchTargetCodeRow(sourceLineNumbers, this);
151 break;
152 case "WixBundleSlipstreamMsp":
153 row = new WixBundleSlipstreamMspRow(sourceLineNumbers, this);
154 break;
155 case "WixBundleUpdate":
156 row = new WixBundleUpdateRow(sourceLineNumbers, this);
157 break;
158 case "WixBundleCatalog":
159 row = new WixBundleCatalogRow(sourceLineNumbers, this);
160 break;
161 case "WixChain":
162 row = new WixChainRow(sourceLineNumbers, this);
163 break;
164 case "WixChainItem":
165 row = new WixChainItemRow(sourceLineNumbers, this);
166 break;
167 case "WixBundlePackageCommandLine":
168 row = new WixBundlePackageCommandLineRow(sourceLineNumbers, this);
169 break;
170 case "WixComplexReference":
171 row = new WixComplexReferenceRow(sourceLineNumbers, this);
172 break;
173 case "WixDeltaPatchFile":
174 row = new WixDeltaPatchFileRow(sourceLineNumbers, this);
175 break;
176 case "WixDeltaPatchSymbolPaths":
177 row = new WixDeltaPatchSymbolPathsRow(sourceLineNumbers, this);
178 break;
179 case "WixFile":
180 row = new WixFileRow(sourceLineNumbers, this);
181 break;
182 case "WixGroup":
183 row = new WixGroupRow(sourceLineNumbers, this);
184 break;
185 case "WixMedia":
186 row = new WixMediaRow(sourceLineNumbers, this);
187 break;
188 case "WixMediaTemplate":
189 row = new WixMediaTemplateRow(sourceLineNumbers, this);
190 break;
191 case "WixMerge":
192 row = new WixMergeRow(sourceLineNumbers, this);
193 break;
194 case "WixPayloadProperties":
195 row = new WixPayloadPropertiesRow(sourceLineNumbers, this);
196 break;
197 case "WixProperty":
198 row = new WixPropertyRow(sourceLineNumbers, this);
199 break;
200 case "WixSimpleReference":
201 row = new WixSimpleReferenceRow(sourceLineNumbers, this);
202 break;
203 case "WixUpdateRegistration":
204 row = new WixUpdateRegistrationRow(sourceLineNumbers, this);
205 break;
206 case "WixVariable":
207 row = new WixVariableRow(sourceLineNumbers, this);
208 break;
209
210 default:
211 row = new Row(sourceLineNumbers, this);
212 break;
213 }
214
215 if (add)
216 {
217 this.Rows.Add(row);
218 }
219
220 return row;
221 }
222
223 /// <summary>
224 /// Parse a table from the xml.
225 /// </summary>
226 /// <param name="reader">XmlReader where the intermediate is persisted.</param>
227 /// <param name="section">Section to populate with persisted data.</param>
228 /// <param name="tableDefinitions">TableDefinitions to use in the intermediate.</param>
229 /// <returns>The parsed table.</returns>
230 internal static Table Read(XmlReader reader, Section section, TableDefinitionCollection tableDefinitions)
231 {
232 Debug.Assert("table" == reader.LocalName);
233
234 bool empty = reader.IsEmptyElement;
235 TableOperation operation = TableOperation.None;
236 string name = null;
237
238 while (reader.MoveToNextAttribute())
239 {
240 switch (reader.LocalName)
241 {
242 case "name":
243 name = reader.Value;
244 break;
245 case "op":
246 switch (reader.Value)
247 {
248 case "add":
249 operation = TableOperation.Add;
250 break;
251 case "drop":
252 operation = TableOperation.Drop;
253 break;
254 default:
255 throw new XmlException();
256 }
257 break;
258 }
259 }
260
261 if (null == name)
262 {
263 throw new XmlException();
264 }
265
266 TableDefinition tableDefinition = tableDefinitions[name];
267 Table table = new Table(section, tableDefinition);
268 table.Operation = operation;
269
270 if (!empty)
271 {
272 bool done = false;
273
274 // loop through all the rows in a table
275 while (!done && reader.Read())
276 {
277 switch (reader.NodeType)
278 {
279 case XmlNodeType.Element:
280 switch (reader.LocalName)
281 {
282 case "row":
283 Row.Read(reader, table);
284 break;
285 default:
286 throw new XmlException();
287 }
288 break;
289 case XmlNodeType.EndElement:
290 done = true;
291 break;
292 }
293 }
294
295 if (!done)
296 {
297 throw new XmlException();
298 }
299 }
300
301 return table;
302 }
303
304 /// <summary>
305 /// Modularize the table.
306 /// </summary>
307 /// <param name="modularizationGuid">String containing the GUID of the Merge Module, if appropriate.</param>
308 /// <param name="suppressModularizationIdentifiers">Optional collection of identifiers that should not be modularized.</param>
309 public void Modularize(string modularizationGuid, ISet<string> suppressModularizationIdentifiers)
310 {
311 List<int> modularizedColumns = new List<int>();
312
313 // find the modularized columns
314 for (int i = 0; i < this.Definition.Columns.Count; i++)
315 {
316 if (ColumnModularizeType.None != this.Definition.Columns[i].ModularizeType)
317 {
318 modularizedColumns.Add(i);
319 }
320 }
321
322 if (0 < modularizedColumns.Count)
323 {
324 foreach (Row row in this.Rows)
325 {
326 foreach (int modularizedColumn in modularizedColumns)
327 {
328 Field field = row.Fields[modularizedColumn];
329
330 if (null != field.Data)
331 {
332 field.Data = row.GetModularizedValue(field, modularizationGuid, suppressModularizationIdentifiers);
333 }
334 }
335 }
336 }
337 }
338
339 /// <summary>
340 /// Persists a row in an XML format.
341 /// </summary>
342 /// <param name="writer">XmlWriter where the Row should persist itself as XML.</param>
343 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
344 "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. " +
345 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
346 internal void Write(XmlWriter writer)
347 {
348 if (null == writer)
349 {
350 throw new ArgumentNullException("writer");
351 }
352
353 writer.WriteStartElement("table", Intermediate.XmlNamespaceUri);
354 writer.WriteAttributeString("name", this.Name);
355
356 if (TableOperation.None != this.Operation)
357 {
358 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant());
359 }
360
361 foreach (Row row in this.Rows)
362 {
363 row.Write(writer);
364 }
365
366 writer.WriteEndElement();
367 }
368
369 /// <summary>
370 /// Writes the table in IDT format to the provided stream.
371 /// </summary>
372 /// <param name="writer">Stream to write the table to.</param>
373 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
374 public void ToIdtDefinition(StreamWriter writer, bool keepAddedColumns)
375 {
376 if (this.Definition.Unreal)
377 {
378 return;
379 }
380
381 if (TableDefinition.MaxColumnsInRealTable < this.Definition.Columns.Count)
382 {
383 throw new WixException(WixDataErrors.TooManyColumnsInRealTable(this.Definition.Name, this.Definition.Columns.Count, TableDefinition.MaxColumnsInRealTable));
384 }
385
386 // Tack on the table header, and flush before we start writing bytes directly to the stream.
387 writer.Write(this.Definition.ToIdtDefinition(keepAddedColumns));
388 writer.Flush();
389
390 using (NonClosingStreamWrapper wrapper = new NonClosingStreamWrapper(writer.BaseStream))
391 using (BufferedStream buffStream = new BufferedStream(wrapper))
392 {
393 // Create an encoding that replaces characters with question marks, and doesn't throw. We'll
394 // use this in case of errors
395 Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage);
396
397 foreach (Row row in this.Rows)
398 {
399 if (row.Redundant)
400 {
401 continue;
402 }
403
404 string rowString = row.ToIdtDefinition(keepAddedColumns);
405 byte[] rowBytes;
406
407 try
408 {
409 // GetBytes will throw an exception if any character doesn't match our current encoding
410 rowBytes = writer.Encoding.GetBytes(rowString);
411 }
412 catch (EncoderFallbackException)
413 {
414 Messaging.Instance.OnMessage(WixDataErrors.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture)));
415
416 rowBytes = convertEncoding.GetBytes(rowString);
417 }
418
419 buffStream.Write(rowBytes, 0, rowBytes.Length);
420 }
421 }
422 }
423
424 /// <summary>
425 /// Validates the rows of this OutputTable and throws if it collides on
426 /// primary keys.
427 /// </summary>
428 public void ValidateRows()
429 {
430 Dictionary<string, SourceLineNumber> primaryKeys = new Dictionary<string, SourceLineNumber>();
431
432 foreach (Row row in this.Rows)
433 {
434 string primaryKey = row.GetPrimaryKey();
435
436 SourceLineNumber collisionSourceLineNumber;
437 if (primaryKeys.TryGetValue(primaryKey, out collisionSourceLineNumber))
438 {
439 throw new WixException(WixDataErrors.DuplicatePrimaryKey(collisionSourceLineNumber, primaryKey, this.Definition.Name));
440 }
441
442 primaryKeys.Add(primaryKey, row.SourceLineNumbers);
443 }
444 }
445 }
446}