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