main
cs 359 lines 12.9 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.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.Length];
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 /// Gets or sets the row transform operation.
51 /// </summary>
52 /// <value>The row transform operation.</value>
53 public RowOperation Operation { get; set; }
54
55 /// <summary>
56 /// Gets the source file and line number for the row.
57 /// </summary>
58 /// <value>Source file and line number.</value>
59 public SourceLineNumber SourceLineNumbers { get; }
60
61 /// <summary>
62 /// Gets the table this row belongs to.
63 /// </summary>
64 /// <value>null if Row does not belong to a Table, or owner Table otherwise.</value>
65 public Table Table { get; }
66
67 /// <summary>
68 /// Gets the table definition for this row.
69 /// </summary>
70 /// <remarks>A Row always has a TableDefinition, even if the Row does not belong to a Table.</remarks>
71 /// <value>TableDefinition for Row.</value>
72 public TableDefinition TableDefinition { get; }
73
74 /// <summary>
75 /// Gets the fields contained by this row.
76 /// </summary>
77 /// <value>Array of field objects</value>
78 public Field[] Fields { get; }
79
80 /// <summary>
81 /// Gets the unique number for the row.
82 /// </summary>
83 /// <value>Number for row.</value>
84 public long Number { get; }
85
86 /// <summary>
87 /// Gets or sets the value of a particular field in the row.
88 /// </summary>
89 /// <param name="field">field index.</param>
90 /// <value>Value of a field in the row.</value>
91 public object this[int field]
92 {
93 get { return this.Fields[field].Data; }
94 set { this.Fields[field].Data = value; }
95 }
96
97 /// <summary>
98 /// Gets the field as an integer.
99 /// </summary>
100 /// <returns>Field's data as an integer.</returns>
101 public int FieldAsInteger(int field)
102 {
103 return this.Fields[field].AsInteger();
104 }
105
106 /// <summary>
107 /// Gets the field as an integer that could be null.
108 /// </summary>
109 /// <returns>Field's data as an integer that could be null.</returns>
110 public int? FieldAsNullableInteger(int field)
111 {
112 return this.Fields[field].AsNullableInteger();
113 }
114
115 /// <summary>
116 /// Gets the field as a string.
117 /// </summary>
118 /// <returns>Field's data as a string.</returns>
119 public string FieldAsString(int field)
120 {
121 return this.Fields[field].AsString();
122 }
123
124 /// <summary>
125 /// Sets the value of a particular field in the row without validating.
126 /// </summary>
127 /// <param name="field">field index.</param>
128 /// <param name="value">Value of a field in the row.</param>
129 /// <returns>True if successful, false if validation failed.</returns>
130 public bool BestEffortSetField(int field, object value)
131 {
132 return this.Fields[field].BestEffortSet(value);
133 }
134
135 /// <summary>
136 /// Get the value used to represent the row in a keyed row collection.
137 /// </summary>
138 /// <returns>Primary key or row number if no primary key is available.</returns>
139 public string GetKey()
140 {
141 return this.GetPrimaryKey() ?? Convert.ToString(this.Number, CultureInfo.InvariantCulture);
142 }
143
144 /// <summary>
145 /// Get the primary key of this row.
146 /// </summary>
147 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
148 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
149 public string GetPrimaryKey(char delimiter = '/')
150 {
151 return this.GetPrimaryKey(delimiter, String.Empty);
152 }
153
154 /// <summary>
155 /// Get the primary key of this row.
156 /// </summary>
157 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
158 /// <param name="nullReplacement">String to represent null values in the primary key.</param>
159 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
160 public string GetPrimaryKey(char delimiter, string nullReplacement)
161 {
162 var foundPrimaryKey = false;
163 var primaryKey = new StringBuilder();
164
165 foreach (var field in this.Fields)
166 {
167 if (field.Column.PrimaryKey)
168 {
169 if (foundPrimaryKey)
170 {
171 primaryKey.Append(delimiter);
172 }
173
174 primaryKey.Append((null == field.Data) ? nullReplacement : Convert.ToString(field.Data, CultureInfo.InvariantCulture));
175
176 foundPrimaryKey = true;
177 }
178 else // primary keys must be the first columns of a row so the first non-primary key means we can stop looking.
179 {
180 break;
181 }
182 }
183
184 return foundPrimaryKey ? primaryKey.ToString() : null;
185 }
186
187 /// <summary>
188 /// Returns true if the specified field is null.
189 /// </summary>
190 /// <param name="field">Index of the field to check.</param>
191 /// <returns>true if the specified field is null, false otherwise.</returns>
192 public bool IsColumnNull(int field) => this.Fields[field].Data == null;
193
194 /// <summary>
195 /// Returns true if the specified field is null or an empty string.
196 /// </summary>
197 /// <param name="field">Index of the field to check.</param>
198 /// <returns>true if the specified field is null or an empty string, false otherwise.</returns>
199 public bool IsColumnEmpty(int field)
200 {
201 if (this.IsColumnNull(field))
202 {
203 return true;
204 }
205
206 string dataString = this.Fields[field].Data as string;
207 if (null != dataString && 0 == dataString.Length)
208 {
209 return true;
210 }
211
212 return false;
213 }
214
215 /// <summary>
216 /// Tests if the passed in row is identical.
217 /// </summary>
218 /// <param name="row">Row to compare against.</param>
219 /// <returns>True if two rows are identical.</returns>
220 public bool IsIdentical(Row row)
221 {
222 bool identical = (this.TableDefinition.Name == row.TableDefinition.Name && this.Fields.Length == row.Fields.Length);
223
224 for (var i = 0; identical && i < this.Fields.Length; ++i)
225 {
226 if (!(this.Fields[i].IsIdentical(row.Fields[i])))
227 {
228 identical = false;
229 }
230 }
231
232 return identical;
233 }
234
235 /// <summary>
236 /// Copies this row to the target row.
237 /// </summary>
238 /// <param name="target">Row to copy data to.</param>
239 public void CopyTo(Row target)
240 {
241 for (var i = 0; i < this.Fields.Length; i++)
242 {
243 target[i] = this[i];
244 }
245 }
246
247 /// <summary>
248 /// Returns a string representation of the Row.
249 /// </summary>
250 /// <returns>A string representation of the Row.</returns>
251 public override string ToString()
252 {
253 return String.Join("/", (object[])this.Fields);
254 }
255
256 /// <summary>
257 /// Creates a Row from the XmlReader.
258 /// </summary>
259 /// <param name="reader">Reader to get data from.</param>
260 /// <param name="table">Table for this row.</param>
261 /// <returns>New row object.</returns>
262 internal static Row Read(XmlReader reader, Table table)
263 {
264 Debug.Assert("row" == reader.LocalName);
265
266 bool empty = reader.IsEmptyElement;
267 RowOperation operation = RowOperation.None;
268 SourceLineNumber sourceLineNumbers = null;
269
270 while (reader.MoveToNextAttribute())
271 {
272 switch (reader.LocalName)
273 {
274 case "op":
275 operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true);
276 break;
277 case "sourceLineNumber":
278 sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value);
279 break;
280 }
281 }
282
283 var row = table.CreateRow(sourceLineNumbers);
284 row.Operation = operation;
285
286 // loop through all the fields in a row
287 if (!empty)
288 {
289 var done = false;
290 var field = 0;
291
292 // loop through all the fields in a row
293 while (!done && reader.Read())
294 {
295 switch (reader.NodeType)
296 {
297 case XmlNodeType.Element:
298 switch (reader.LocalName)
299 {
300 case "field":
301 if (row.Fields.Length <= field)
302 {
303 if (!reader.IsEmptyElement)
304 {
305 throw new XmlException();
306 }
307 }
308 else
309 {
310 row.Fields[field].Read(reader);
311 }
312 ++field;
313 break;
314 default:
315 throw new XmlException();
316 }
317 break;
318 case XmlNodeType.EndElement:
319 done = true;
320 break;
321 }
322 }
323
324 if (!done)
325 {
326 throw new XmlException();
327 }
328 }
329
330 return row;
331 }
332
333 /// <summary>
334 /// Persists a row in an XML format.
335 /// </summary>
336 /// <param name="writer">XmlWriter where the Row should persist itself as XML.</param>
337 internal void Write(XmlWriter writer)
338 {
339 writer.WriteStartElement("row", WindowsInstallerData.XmlNamespaceUri);
340
341 if (RowOperation.None != this.Operation)
342 {
343 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant());
344 }
345
346 if (null != this.SourceLineNumbers)
347 {
348 writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded());
349 }
350
351 for (int i = 0; i < this.Fields.Length; ++i)
352 {
353 this.Fields[i].Write(writer);
354 }
355
356 writer.WriteEndElement();
357 }
358 }
359 }