diff options
Diffstat (limited to 'src/WixToolset.Data/WindowsInstaller/Row.cs')
-rw-r--r-- | src/WixToolset.Data/WindowsInstaller/Row.cs | 387 |
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 | |||
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.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 | } | ||