aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Data/Row.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Data/Row.cs')
-rw-r--r--src/WixToolset.Data/Row.cs626
1 files changed, 0 insertions, 626 deletions
diff --git a/src/WixToolset.Data/Row.cs b/src/WixToolset.Data/Row.cs
deleted file mode 100644
index 4ebddc06..00000000
--- a/src/WixToolset.Data/Row.cs
+++ /dev/null
@@ -1,626 +0,0 @@
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.Text;
11 using System.Text.RegularExpressions;
12 using System.Xml;
13
14 /// <summary>
15 /// Row containing data for a table.
16 /// </summary>
17 public class Row
18 {
19 private static long rowCount;
20
21 private Field[] fields;
22
23 /// <summary>
24 /// Creates a row that belongs to a table.
25 /// </summary>
26 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
27 /// <param name="table">Table this row belongs to and should get its column definitions from.</param>
28 /// <remarks>The compiler should use this constructor exclusively.</remarks>
29 public Row(SourceLineNumber sourceLineNumbers, Table table)
30 : this(sourceLineNumbers, table.Definition)
31 {
32 this.Table = table;
33 }
34
35 /// <summary>
36 /// Creates a row that does not belong to a table.
37 /// </summary>
38 /// <param name="sourceLineNumbers">Original source lines for this row.</param>
39 /// <param name="tableDefinition">TableDefinition this row should get its column definitions from.</param>
40 /// <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>
41 public Row(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition)
42 {
43 this.Number = rowCount++;
44 this.SourceLineNumbers = sourceLineNumbers;
45 this.fields = new Field[tableDefinition.Columns.Count];
46 this.TableDefinition = tableDefinition;
47
48 for (int i = 0; i < this.fields.Length; ++i)
49 {
50 this.fields[i] = Field.Create(this.TableDefinition.Columns[i]);
51 }
52 }
53
54 /// <summary>
55 /// Creates a shallow copy of a row from another row.
56 /// </summary>
57 /// <param name="source">The row the data is copied from.</param>
58 protected Row(Row source)
59 {
60 this.Table = source.Table;
61 this.TableDefinition = source.TableDefinition;
62 this.Number = source.Number;
63 this.Access = source.Access;
64 this.Operation = source.Operation;
65 this.Redundant = source.Redundant;
66 this.SectionId = source.SectionId;
67 this.SourceLineNumbers = source.SourceLineNumbers;
68 this.fields = source.fields;
69 }
70
71 /// <summary>
72 /// Gets or sets the access to the row's primary key.
73 /// </summary>
74 /// <value>The row access modifier.</value>
75 public AccessModifier Access { get; set; }
76
77 /// <summary>
78 /// Gets or sets the row transform operation.
79 /// </summary>
80 /// <value>The row transform operation.</value>
81 public RowOperation Operation { get; set; }
82
83 /// <summary>
84 /// Gets or sets wether the row is a duplicate of another row thus redundant.
85 /// </summary>
86 public bool Redundant { get; set; }
87
88 /// <summary>
89 /// Gets the section for the row.
90 /// </summary>
91 /// <value>Section for the row.</value>
92 public Section Section { get { return (null == this.Table) ? null : this.Table.Section; } }
93
94 /// <summary>
95 /// Gets or sets the SectionId property on the row.
96 /// </summary>
97 /// <value>The SectionId property on the row.</value>
98 public string SectionId { get; set; }
99
100 /// <summary>
101 /// Gets the source file and line number for the row.
102 /// </summary>
103 /// <value>Source file and line number.</value>
104 public SourceLineNumber SourceLineNumbers { get; private set; }
105
106 /// <summary>
107 /// Gets the table this row belongs to.
108 /// </summary>
109 /// <value>null if Row does not belong to a Table, or owner Table otherwise.</value>
110 public Table Table { get; private set; }
111
112 /// <summary>
113 /// Gets the table definition for this row.
114 /// </summary>
115 /// <remarks>A Row always has a TableDefinition, even if the Row does not belong to a Table.</remarks>
116 /// <value>TableDefinition for Row.</value>
117 public TableDefinition TableDefinition { get; private set; }
118
119 /// <summary>
120 /// Gets the fields contained by this row.
121 /// </summary>
122 /// <value>Array of field objects</value>
123 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
124 public Field[] Fields
125 {
126 get { return this.fields; }
127 }
128
129 /// <summary>
130 /// Gets the unique number for the row.
131 /// </summary>
132 /// <value>Number for row.</value>
133 public long Number { get; private set; }
134
135 /// <summary>
136 /// Gets or sets the value of a particular field in the row.
137 /// </summary>
138 /// <param name="field">field index.</param>
139 /// <value>Value of a field in the row.</value>
140 public object this[int field]
141 {
142 get { return this.fields[field].Data; }
143 set { this.fields[field].Data = value; }
144 }
145
146 /// <summary>
147 /// Gets the field as an integer.
148 /// </summary>
149 /// <returns>Field's data as an integer.</returns>
150 public int FieldAsInteger(int field)
151 {
152 return this.fields[field].AsInteger();
153 }
154
155 /// <summary>
156 /// Gets the field as an integer that could be null.
157 /// </summary>
158 /// <returns>Field's data as an integer that could be null.</returns>
159 public int? FieldAsNullableInteger(int field)
160 {
161 return this.fields[field].AsNullableInteger();
162 }
163
164 /// <summary>
165 /// Gets the field as a string.
166 /// </summary>
167 /// <returns>Field's data as a string.</returns>
168 public string FieldAsString(int field)
169 {
170 return this.fields[field].AsString();
171 }
172
173 /// <summary>
174 /// Sets the value of a particular field in the row without validating.
175 /// </summary>
176 /// <param name="field">field index.</param>
177 /// <param name="value">Value of a field in the row.</param>
178 /// <returns>True if successful, false if validation failed.</returns>
179 public bool BestEffortSetField(int field, object value)
180 {
181 return this.fields[field].BestEffortSet(value);
182 }
183
184 /// <summary>
185 /// Get the value used to represent the row in a keyed row collection.
186 /// </summary>
187 /// <returns>Primary key or row number if no primary key is available.</returns>
188 public string GetKey()
189 {
190 return this.GetPrimaryKey() ?? Convert.ToString(this.Number, CultureInfo.InvariantCulture);
191 }
192
193 /// <summary>
194 /// Get the primary key of this row.
195 /// </summary>
196 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
197 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
198 public string GetPrimaryKey(char delimiter = '/')
199 {
200 return this.GetPrimaryKey(delimiter, String.Empty);
201 }
202
203 /// <summary>
204 /// Get the primary key of this row.
205 /// </summary>
206 /// <param name="delimiter">Delimiter character for multiple column primary keys.</param>
207 /// <param name="nullReplacement">String to represent null values in the primary key.</param>
208 /// <returns>The primary key or null if the row's table has no primary key columns.</returns>
209 public string GetPrimaryKey(char delimiter, string nullReplacement)
210 {
211 bool foundPrimaryKey = false;
212 StringBuilder primaryKey = new StringBuilder();
213
214 foreach (Field field in this.fields)
215 {
216 if (field.Column.PrimaryKey)
217 {
218 if (foundPrimaryKey)
219 {
220 primaryKey.Append(delimiter);
221 }
222
223 primaryKey.Append((null == field.Data) ? nullReplacement : Convert.ToString(field.Data, CultureInfo.InvariantCulture));
224
225 foundPrimaryKey = true;
226 }
227 else // primary keys must be the first columns of a row so the first non-primary key means we can stop looking.
228 {
229 break;
230 }
231 }
232
233 return foundPrimaryKey ? primaryKey.ToString() : null;
234 }
235
236 /// <summary>
237 /// Returns true if the specified field is null or an empty string.
238 /// </summary>
239 /// <param name="field">Index of the field to check.</param>
240 /// <returns>true if the specified field is null or an empty string, false otherwise.</returns>
241 public bool IsColumnEmpty(int field)
242 {
243 if (null == this.fields[field].Data)
244 {
245 return true;
246 }
247
248 string dataString = this.fields[field].Data as string;
249 if (null != dataString && 0 == dataString.Length)
250 {
251 return true;
252 }
253
254 return false;
255 }
256
257 /// <summary>
258 /// Tests if the passed in row is identical.
259 /// </summary>
260 /// <param name="row">Row to compare against.</param>
261 /// <returns>True if two rows are identical.</returns>
262 public bool IsIdentical(Row row)
263 {
264 bool identical = (this.TableDefinition.Name == row.TableDefinition.Name && this.fields.Length == row.fields.Length);
265
266 for (int i = 0; identical && i < this.fields.Length; ++i)
267 {
268 if (!(this.fields[i].IsIdentical(row.fields[i])))
269 {
270 identical = false;
271 }
272 }
273
274 return identical;
275 }
276
277 /// <summary>
278 /// Returns a string representation of the Row.
279 /// </summary>
280 /// <returns>A string representation of the Row.</returns>
281 public override string ToString()
282 {
283 return String.Join("/", (object[])this.fields);
284 }
285
286 /// <summary>
287 /// Creates a Row from the XmlReader.
288 /// </summary>
289 /// <param name="reader">Reader to get data from.</param>
290 /// <param name="table">Table for this row.</param>
291 /// <returns>New row object.</returns>
292 internal static Row Read(XmlReader reader, Table table)
293 {
294 Debug.Assert("row" == reader.LocalName);
295
296 bool empty = reader.IsEmptyElement;
297 AccessModifier access = AccessModifier.Public;
298 RowOperation operation = RowOperation.None;
299 bool redundant = false;
300 string sectionId = null;
301 SourceLineNumber sourceLineNumbers = null;
302
303 while (reader.MoveToNextAttribute())
304 {
305 switch (reader.LocalName)
306 {
307 case "access":
308 access = (AccessModifier)Enum.Parse(typeof(AccessModifier), reader.Value, true);
309 break;
310 case "op":
311 operation = (RowOperation)Enum.Parse(typeof(RowOperation), reader.Value, true);
312 break;
313 case "redundant":
314 redundant = reader.Value.Equals("yes");
315 break;
316 case "sectionId":
317 sectionId = reader.Value;
318 break;
319 case "sourceLineNumber":
320 sourceLineNumbers = SourceLineNumber.CreateFromEncoded(reader.Value);
321 break;
322 }
323 }
324
325 Row row = table.CreateRow(sourceLineNumbers);
326 row.Access = access;
327 row.Operation = operation;
328 row.Redundant = redundant;
329 row.SectionId = sectionId;
330
331 // loop through all the fields in a row
332 if (!empty)
333 {
334 bool done = false;
335 int field = 0;
336
337 // loop through all the fields in a row
338 while (!done && reader.Read())
339 {
340 switch (reader.NodeType)
341 {
342 case XmlNodeType.Element:
343 switch (reader.LocalName)
344 {
345 case "field":
346 if (row.Fields.Length <= field)
347 {
348 if (!reader.IsEmptyElement)
349 {
350 throw new XmlException();
351 }
352 }
353 else
354 {
355 row.fields[field].Read(reader);
356 }
357 ++field;
358 break;
359 default:
360 throw new XmlException();
361 }
362 break;
363 case XmlNodeType.EndElement:
364 done = true;
365 break;
366 }
367 }
368
369 if (!done)
370 {
371 throw new XmlException();
372 }
373 }
374
375 return row;
376 }
377
378 /// <summary>
379 /// Returns the row in a format usable in IDT files.
380 /// </summary>
381 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
382 /// <returns>String with tab delimited field values.</returns>
383 internal string ToIdtDefinition(bool keepAddedColumns)
384 {
385 bool first = true;
386 StringBuilder sb = new StringBuilder();
387
388 foreach (Field field in this.fields)
389 {
390 // Conditionally keep columns added in a transform; otherwise,
391 // break because columns can only be added at the end.
392 if (field.Column.Added && !keepAddedColumns)
393 {
394 break;
395 }
396
397 if (first)
398 {
399 first = false;
400 }
401 else
402 {
403 sb.Append('\t');
404 }
405
406 sb.Append(field.ToIdtValue());
407 }
408 sb.Append("\r\n");
409
410 return sb.ToString();
411 }
412
413 /// <summary>
414 /// Gets the modularized version of the field data.
415 /// </summary>
416 /// <param name="field">The field to modularize.</param>
417 /// <param name="modularizationGuid">String containing the GUID of the Merge Module to append the the field value, if appropriate.</param>
418 /// <param name="suppressModularizationIdentifiers">Optional collection of identifiers that should not be modularized.</param>
419 /// <remarks>moduleGuid is expected to be null when not being used to compile a Merge Module.</remarks>
420 /// <returns>The modularized version of the field data.</returns>
421 internal string GetModularizedValue(Field field, string modularizationGuid, ISet<string> suppressModularizationIdentifiers)
422 {
423 Debug.Assert(null != field.Data && 0 < ((string)field.Data).Length);
424 string fieldData = Convert.ToString(field.Data, CultureInfo.InvariantCulture);
425
426 if (null != modularizationGuid && ColumnModularizeType.None != field.Column.ModularizeType && !(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData)))
427 {
428 StringBuilder sb;
429 int start;
430 ColumnModularizeType modularizeType = field.Column.ModularizeType;
431
432 // special logic for the ControlEvent table's Argument column
433 // this column requires different modularization methods depending upon the value of the Event column
434 if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType)
435 {
436 switch (this[2].ToString())
437 {
438 case "CheckExistingTargetPath": // redirectable property name
439 case "CheckTargetPath":
440 case "DoAction": // custom action name
441 case "NewDialog": // dialog name
442 case "SelectionBrowse":
443 case "SetTargetPath":
444 case "SpawnDialog":
445 case "SpawnWaitDialog":
446 if (Common.IsIdentifier(fieldData))
447 {
448 modularizeType = ColumnModularizeType.Column;
449 }
450 else
451 {
452 modularizeType = ColumnModularizeType.Property;
453 }
454 break;
455 default: // formatted
456 modularizeType = ColumnModularizeType.Property;
457 break;
458 }
459 }
460 else if (ColumnModularizeType.ControlText == field.Column.ModularizeType)
461 {
462 // icons are stored in the Binary table, so they get column-type modularization
463 if (("Bitmap" == this[2].ToString() || "Icon" == this[2].ToString()) && Common.IsIdentifier(fieldData))
464 {
465 modularizeType = ColumnModularizeType.Column;
466 }
467 else
468 {
469 modularizeType = ColumnModularizeType.Property;
470 }
471 }
472
473 switch (modularizeType)
474 {
475 case ColumnModularizeType.Column:
476 // ensure the value is an identifier (otherwise it shouldn't be modularized this way)
477 if (!Common.IsIdentifier(fieldData))
478 {
479 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData));
480 }
481
482 // if we're not supposed to suppress modularization of this identifier
483 if (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData))
484 {
485 fieldData = String.Concat(fieldData, ".", modularizationGuid);
486 }
487 break;
488
489 case ColumnModularizeType.Property:
490 case ColumnModularizeType.Condition:
491 Regex regex;
492 if (ColumnModularizeType.Property == modularizeType)
493 {
494 regex = new Regex(@"\[(?<identifier>[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
495 }
496 else
497 {
498 Debug.Assert(ColumnModularizeType.Condition == modularizeType);
499
500 // This heinous looking regular expression is actually quite an elegant way
501 // to shred the entire condition into the identifiers that need to be
502 // modularized. Let's break it down piece by piece:
503 //
504 // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the
505 // regular expression is case insensitive so we don't have to worry about
506 // all the permutations of these strings.
507 // 2. Look for quoted strings. Quoted strings are just text and are ignored
508 // outright.
509 // 3. Look for environment variables. These look like identifiers we might
510 // otherwise be interested in but start with a percent sign. Like quoted
511 // strings these enviroment variable references are ignored outright.
512 // 4. Match all identifiers that are things that need to be modularized. Note
513 // the special characters (!, $, ?, &) that denote Component and Feature states.
514 regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
515
516 // less performant version of the above with captures showing where everything lives
517 // regex = new Regex(@"(?<operator>NOT|EQV|XOR|OR|AND|IMP)|(?<string>"".*?"")|(?<environment>%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
518 }
519
520 MatchCollection matches = regex.Matches(fieldData);
521
522 sb = new StringBuilder(fieldData);
523
524 // notice how this code walks backward through the list
525 // because it modifies the string as we through it
526 for (int i = matches.Count - 1; 0 <= i; i--)
527 {
528 Group group = matches[i].Groups["identifier"];
529 if (group.Success)
530 {
531 string identifier = group.Value;
532 if (!WindowsInstallerStandard.IsStandardProperty(identifier) && (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(identifier)))
533 {
534 sb.Insert(group.Index + group.Length, '.');
535 sb.Insert(group.Index + group.Length + 1, modularizationGuid);
536 }
537 }
538 }
539
540 fieldData = sb.ToString();
541 break;
542
543 case ColumnModularizeType.CompanionFile:
544 // if we're not supposed to ignore this identifier and the value does not start with
545 // a digit, we must have a companion file so modularize it
546 if ((null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData)) &&
547 0 < fieldData.Length && !Char.IsDigit(fieldData, 0))
548 {
549 fieldData = String.Concat(fieldData, ".", modularizationGuid);
550 }
551 break;
552
553 case ColumnModularizeType.Icon:
554 if (null == suppressModularizationIdentifiers || !suppressModularizationIdentifiers.Contains(fieldData))
555 {
556 start = fieldData.LastIndexOf(".", StringComparison.Ordinal);
557 if (-1 == start)
558 {
559 fieldData = String.Concat(fieldData, ".", modularizationGuid);
560 }
561 else
562 {
563 fieldData = String.Concat(fieldData.Substring(0, start), ".", modularizationGuid, fieldData.Substring(start));
564 }
565 }
566 break;
567
568 case ColumnModularizeType.SemicolonDelimited:
569 string[] keys = fieldData.Split(';');
570 for (int i = 0; i < keys.Length; ++i)
571 {
572 keys[i] = String.Concat(keys[i], ".", modularizationGuid);
573 }
574 fieldData = String.Join(";", keys);
575 break;
576 }
577 }
578
579 return fieldData;
580 }
581
582 /// <summary>
583 /// Persists a row in an XML format.
584 /// </summary>
585 /// <param name="writer">XmlWriter where the Row should persist itself as XML.</param>
586 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
587 "in a change to the way intermediate files are generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " +
588 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
589 internal void Write(XmlWriter writer)
590 {
591 writer.WriteStartElement("row", Intermediate.XmlNamespaceUri);
592
593 if (AccessModifier.Public != this.Access)
594 {
595 writer.WriteAttributeString("access", this.Access.ToString().ToLowerInvariant());
596 }
597
598 if (RowOperation.None != this.Operation)
599 {
600 writer.WriteAttributeString("op", this.Operation.ToString().ToLowerInvariant());
601 }
602
603 if (this.Redundant)
604 {
605 writer.WriteAttributeString("redundant", "yes");
606 }
607
608 if (null != this.SectionId)
609 {
610 writer.WriteAttributeString("sectionId", this.SectionId);
611 }
612
613 if (null != this.SourceLineNumbers)
614 {
615 writer.WriteAttributeString("sourceLineNumber", this.SourceLineNumbers.GetEncoded());
616 }
617
618 for (int i = 0; i < this.fields.Length; ++i)
619 {
620 this.fields[i].Write(writer);
621 }
622
623 writer.WriteEndElement();
624 }
625 }
626}