aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Data/WindowsInstaller/Row.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Data/WindowsInstaller/Row.cs')
-rw-r--r--src/WixToolset.Data/WindowsInstaller/Row.cs387
1 files changed, 387 insertions, 0 deletions
diff --git a/src/WixToolset.Data/WindowsInstaller/Row.cs b/src/WixToolset.Data/WindowsInstaller/Row.cs
new file mode 100644
index 00000000..cb8b81d8
--- /dev/null
+++ b/src/WixToolset.Data/WindowsInstaller/Row.cs
@@ -0,0 +1,387 @@
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.WindowsInstaller
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Text;
9 using System.Xml;
10
11 /// <summary>
12 /// Row containing data for a table.
13 /// </summary>
14 public class Row
15 {
16 private static long rowCount;
17
18 /// <summary>
19 /// Creates a row that belongs to a table.
20 /// </summary>
21 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
22 /// <param name="table">Table this row belongs to and should get its column definitions from.</param>
23 /// <remarks>The compiler should use this constructor exclusively.</remarks>
24 public Row(SourceLineNumber sourceLineNumbers, Table table)
25 : this(sourceLineNumbers, table.Definition)
26 {
27 this.Table = table;
28 }
29
30 /// <summary>
31 /// Creates a row that does not belong to a table.
32 /// </summary>
33 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
34 /// <param name="tableDefinition">TableDefinition this row should get its column definitions from.</param>
35 /// <remarks>This constructor is used in cases where there isn't a clear owner of the row. The linker uses this constructor for the rows it generates.</remarks>
36 public Row(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition)
37 {
38 this.Number = rowCount++;
39 this.SourceLineNumbers = sourceLineNumbers;
40 this.Fields = new Field[tableDefinition.Columns.Count];
41 this.TableDefinition = tableDefinition;
42
43 for (var i = 0; i < this.Fields.Length; ++i)
44 {
45 this.Fields[i] = Field.Create(this.TableDefinition.Columns[i]);
46 }
47 }
48
49 /// <summary>
50 /// Creates a shallow copy of a row from another row.
51 /// </summary>
52 /// <param name="source">The row the data is copied from.</param>
53 protected Row(Row source)
54 {
55 this.Table = source.Table;
56 this.TableDefinition = source.TableDefinition;
57 this.Number = source.Number;
58 this.Operation = source.Operation;
59 this.Redundant = source.Redundant;
60 this.SectionId = source.SectionId;
61 this.SourceLineNumbers = source.SourceLineNumbers;
62 this.Fields = source.Fields;
63 }
64
65 /// <summary>
66 /// Gets or sets the row transform operation.
67 /// </summary>
68 /// <value>The row transform operation.</value>
69 public RowOperation Operation { get; set; }
70
71 /// <summary>
72 /// Gets or sets wether the row is a duplicate of another row thus redundant.
73 /// </summary>
74 public bool Redundant { get; set; }
75
76 /// <summary>
77 /// Gets or sets the SectionId property on the row.
78 /// </summary>
79 /// <value>The SectionId property on the row.</value>
80 public string SectionId { get; set; }
81
82 /// <summary>
83 /// Gets the source file and line number for the row.
84 /// </summary>
85 /// <value>Source file and line number.</value>
86 public SourceLineNumber SourceLineNumbers { get; }
87
88 /// <summary>
89 /// Gets the table this row belongs to.
90 /// </summary>
91 /// <value>null if Row does not belong to a Table, or owner Table otherwise.</value>
92 public Table Table { get; }
93
94 /// <summary>
95 /// Gets the table definition for this row.
96 /// </summary>
97 /// <remarks>A Row always has a TableDefinition, even if the Row does not belong to a Table.</remarks>
98 /// <value>TableDefinition for Row.</value>
99 public TableDefinition TableDefinition { get; }
100
101 /// <summary>
102 /// Gets the fields contained by this row.
103 /// </summary>
104 /// <value>Array of field objects</value>
105 public Field[] Fields { get; }
106
107 /// <summary>
108 /// Gets the unique number for the row.
109 /// </summary>
110 /// <value>Number for row.</value>
111 public long Number { get; }
112
113 /// <summary>
114 /// Gets or sets the value of a particular field in the row.
115 /// </summary>
116 /// <param name="field">field index.</param>
117 /// <value>Value of a field in the row.</value>
118 public object this[int field]
119 {
120 get { return this.Fields[field].Data; }
121 set { this.Fields[field].Data = value; }
122 }
123
124 /// <summary>
125 /// Gets the field as an integer.
126 /// </summary>
127 /// <returns>Field's data as an integer.</returns>
128 public int FieldAsInteger(int field)
129 {
130 return this.Fields[field].AsInteger();
131 }
132
133 /// <summary>
134 /// Gets the field as an integer that could be null.
135 /// </summary>
136 /// <returns>Field's data as an integer that could be null.</returns>
137 public int? FieldAsNullableInteger(int field)
138 {
139 return this.Fields[field].AsNullableInteger();
140 }
141
142 /// <summary>
143 /// Gets the field as a string.
144 /// </summary>
145 /// <returns>Field's data as a string.</returns>
146 public string FieldAsString(int field)
147 {
148 return this.Fields[field].AsString();
149 }
150
151 /// <summary>
152 /// Sets the value of a particular field in the row without validating.
153 /// </summary>
154 /// <param name="field">field index.</param>
155 /// <param name="value">Value of a field in the row.</param>
156 /// <returns>True if successful, false if validation failed.</returns>
157 public bool BestEffortSetField(int field, object value)
158 {
159 return this.Fields[field].BestEffortSet(value);
160 }
161
162 /// <summary>
163 /// Get the value used to represent the row in a keyed row collection.
164 /// </summary>
165 /// <returns>Primary key or row number if no primary key is available.</returns>
166 public string GetKey()
167 {
168 return this.GetPrimaryKey() ?? Convert.ToString(this.Number, CultureInfo.InvariantCulture);
169 }
170
171 /// <summary>
172 /// Get the primary key of this row.
173 /// </summary>
174 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
175 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
176 public string GetPrimaryKey(char delimiter = '/')
177 {
178 return this.GetPrimaryKey(delimiter, String.Empty);
179 }
180
181 /// <summary>
182 /// Get the primary key of this row.
183 /// </summary>
184 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
185 /// <param name="nullReplacement">String to represent null values in the primary key.</param>
186 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
187 public string GetPrimaryKey(char delimiter, string nullReplacement)
188 {
189 var foundPrimaryKey = false;
190 var primaryKey = new StringBuilder();
191
192 foreach (var field in this.Fields)
193 {
194 if (field.Column.PrimaryKey)
195 {
196 if (foundPrimaryKey)
197 {
198 primaryKey.Append(delimiter);
199 }
200
201 primaryKey.Append((null == field.Data) ? nullReplacement : Convert.ToString(field.Data, CultureInfo.InvariantCulture));
202
203 foundPrimaryKey = true;
204 }
205 else // primary keys must be the first columns of a row so the first non-primary key means we can stop looking.
206 {
207 break;
208 }
209 }
210
211 return foundPrimaryKey ? primaryKey.ToString() : null;
212 }
213
214 /// <summary>
215 /// Returns true if the specified field is null or an empty string.
216 /// </summary>
217 /// <param name="field">Index of the field to check.</param>
218 /// <returns>true if the specified field is null or an empty string, false otherwise.</returns>
219 public bool IsColumnEmpty(int field)
220 {
221 if (null == this.Fields[field].Data)
222 {
223 return true;
224 }
225
226 string dataString = this.Fields[field].Data as string;
227 if (null != dataString && 0 == dataString.Length)
228 {
229 return true;
230 }
231
232 return false;
233 }
234
235 /// <summary>
236 /// Tests if the passed in row is identical.
237 /// </summary>
238 /// <param name="row">Row to compare against.</param>
239 /// <returns>True if two rows are identical.</returns>
240 public bool IsIdentical(Row row)
241 {
242 bool identical = (this.TableDefinition.Name == row.TableDefinition.Name && this.Fields.Length == row.Fields.Length);
243
244 for (var i = 0; identical && i < this.Fields.Length; ++i)
245 {
246 if (!(this.Fields[i].IsIdentical(row.Fields[i])))
247 {
248 identical = false;
249 }
250 }
251
252 return identical;
253 }
254
255 /// <summary>
256 /// Returns a string representation of the Row.
257 /// </summary>
258 /// <returns>A string representation of the Row.</returns>
259 public override string ToString()
260 {
261 return String.Join("/", (object[])this.Fields);
262 }
263
264 /// <summary>
265 /// Creates a Row from the XmlReader.
266 /// </summary>
267 /// <param name="reader">Reader to get data from.</param>
268 /// <param name="table">Table for this row.</param>
269 /// <returns>New row object.</returns>
270 internal static Row Read(XmlReader reader, Table table)
271 {
272 Debug.Assert("row" == reader.LocalName);
273
274 bool empty = reader.IsEmptyElement;
275 RowOperation operation = RowOperation.None;
276 bool redundant = false;
277 string sectionId = null;
278 SourceLineNumber sourceLineNumbers = null;
279
280 while (reader.MoveToNextAttribute())
281 {
282 switch (reader.LocalName)
283 {
284 case "op":
285 operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true);
286 break;
287 case "redundant":
288 redundant = reader.Value.Equals("yes");
289 break;
290 case "sectionId":
291 sectionId = reader.Value;
292 break;
293 case "sourceLineNumber":
294 sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value);
295 break;
296 }
297 }
298
299 var row = table.CreateRow(sourceLineNumbers);
300 row.Operation = operation;
301 row.Redundant = redundant;
302 row.SectionId = sectionId;
303
304 // loop through all the fields in a row
305 if (!empty)
306 {
307 var done = false;
308 var field = 0;
309
310 // loop through all the fields in a row
311 while (!done && reader.Read())
312 {
313 switch (reader.NodeType)
314 {
315 case XmlNodeType.Element:
316 switch (reader.LocalName)
317 {
318 case "field":
319 if (row.Fields.Length <= field)
320 {
321 if (!reader.IsEmptyElement)
322 {
323 throw new XmlException();
324 }
325 }
326 else
327 {
328 row.Fields[field].Read(reader);
329 }
330 ++field;
331 break;
332 default:
333 throw new XmlException();
334 }
335 break;
336 case XmlNodeType.EndElement:
337 done = true;
338 break;
339 }
340 }
341
342 if (!done)
343 {
344 throw new XmlException();
345 }
346 }
347
348 return row;
349 }
350
351 /// <summary>
352 /// Persists a row in an XML format.
353 /// </summary>
354 /// <param name="writer">XmlWriter where the Row should persist itself as XML.</param>
355 internal void Write(XmlWriter writer)
356 {
357 writer.WriteStartElement("row", Intermediate.XmlNamespaceUri);
358
359 if (RowOperation.None != this.Operation)
360 {
361 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant());
362 }
363
364 if (this.Redundant)
365 {
366 writer.WriteAttributeString("redundant", "yes");
367 }
368
369 if (null != this.SectionId)
370 {
371 writer.WriteAttributeString("sectionId", this.SectionId);
372 }
373
374 if (null != this.SourceLineNumbers)
375 {
376 writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded());
377 }
378
379 for (int i = 0; i < this.Fields.Length; ++i)
380 {
381 this.Fields[i].Write(writer);
382 }
383
384 writer.WriteEndElement();
385 }
386 }
387}