main
cs 304 lines 12.1 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.Xml;
9
10 /// <summary>
11 /// Field containing data for a column in a row.
12 /// </summary>
13 public class Field
14 {
15 private object data;
16
17 /// <summary>
18 /// Instantiates a new Field.
19 /// </summary>
20 /// <param name="columnDefinition">Column definition for this field.</param>
21 protected Field(ColumnDefinition columnDefinition)
22 {
23 this.Column = columnDefinition;
24 }
25
26 /// <summary>
27 /// Gets or sets the column definition for this field.
28 /// </summary>
29 /// <value>Column definition.</value>
30 public ColumnDefinition Column { get; private set; }
31
32 /// <summary>
33 /// Gets or sets the data for this field.
34 /// </summary>
35 /// <value>Data in the field.</value>
36 public object Data
37 {
38 get => this.data;
39 set => this.data = this.ValidateValue(this.Column, value);
40 }
41
42 /// <summary>
43 /// Gets or sets whether this field is modified.
44 /// </summary>
45 /// <value>Whether this field is modified.</value>
46 public bool Modified { get; set; }
47
48 /// <summary>
49 /// Gets or sets the previous data.
50 /// </summary>
51 /// <value>The previous data.</value>
52 public string PreviousData { get; set; }
53
54 /// <summary>
55 /// Instantiate a new Field object of the correct type.
56 /// </summary>
57 /// <param name="columnDefinition">The column definition for the field.</param>
58 /// <returns>The new Field object.</returns>
59 public static Field Create(ColumnDefinition columnDefinition)
60 {
61 return (ColumnType.Object == columnDefinition.Type) ? new ObjectField(columnDefinition) : new Field(columnDefinition);
62 }
63
64 /// <summary>
65 /// Sets the value of a particular field in the row without validating.
66 /// </summary>
67 /// <param name="value">Value of a field in the row.</param>
68 /// <returns>True if successful, false if validation failed.</returns>
69 public bool BestEffortSet(object value)
70 {
71 bool success = true;
72 object bestEffortValue = value;
73
74 try
75 {
76 bestEffortValue = this.ValidateValue(this.Column, value);
77 }
78 catch (InvalidOperationException)
79 {
80 success = false;
81 }
82
83 this.data = bestEffortValue;
84 return success;
85 }
86
87 /// <summary>
88 /// Determine if this field is identical to another field.
89 /// </summary>
90 /// <param name="field">The other field to compare to.</param>
91 /// <returns>true if they are equal; false otherwise.</returns>
92 public bool IsIdentical(Field field)
93 {
94 return (this.Column.Name == field.Column.Name &&
95 ((null != this.data && this.data.Equals(field.data)) || (null == this.data && null == field.data)));
96 }
97
98 /// <summary>
99 /// Overrides the built in object implementation to return the field's data as a string.
100 /// </summary>
101 /// <returns>Field's data as a string.</returns>
102 public override string ToString()
103 {
104 return this.AsString();
105 }
106
107 /// <summary>
108 /// Gets the field as an integer.
109 /// </summary>
110 /// <returns>Field's data as an integer.</returns>
111 public int AsInteger()
112 {
113 return (this.data is int) ? (int)this.data : Convert.ToInt32(this.data, CultureInfo.InvariantCulture);
114 }
115
116 /// <summary>
117 /// Gets the field as an integer that could be null.
118 /// </summary>
119 /// <returns>Field's data as an integer that could be null.</returns>
120 public int? AsNullableInteger()
121 {
122 return (null == this.data) ? (int?)null : (this.data is int) ? (int)this.data : Convert.ToInt32(this.data, CultureInfo.InvariantCulture);
123 }
124
125 /// <summary>
126 /// Gets the field as a string.
127 /// </summary>
128 /// <returns>Field's data as a string.</returns>
129 public string AsString()
130 {
131 return (null == this.data) ? null : Convert.ToString(this.data, CultureInfo.InvariantCulture);
132 }
133
134 /// <summary>
135 /// Validate a value for a column.
136 /// </summary>
137 /// <param name="column">The column.</param>
138 /// <param name="value">The value to validate.</param>
139 /// <returns>Validated value.</returns>
140 internal object ValidateValue(ColumnDefinition column, object value)
141 {
142 if (null == value)
143 {
144 if (!column.Nullable)
145 {
146 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with a null value because this is a required field.", column.Name));
147 }
148 }
149 else // check numerical values against their specified minimum and maximum values.
150 {
151 if (ColumnType.Number == column.Type && !column.IsLocalizable)
152 {
153 // For now all enums in the tables can be represented by integers. This if statement would need to
154 // be enhanced if that ever changes.
155 if (value is int || value.GetType().IsEnum)
156 {
157 var intValue = (int)value;
158
159 // validate the value against the minimum allowed value
160 if (column.MinValue.HasValue && column.MinValue > intValue)
161 {
162 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.", column.Name, intValue, column.MinValue));
163 }
164
165 // validate the value against the maximum allowed value
166 if (column.MaxValue.HasValue && column.MaxValue < intValue)
167 {
168 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.", column.Name, intValue, column.MaxValue));
169 }
170
171 return intValue;
172 }
173 else if (value is long longValue)
174 {
175 // validate the value against the minimum allowed value
176 if (column.MinValue.HasValue && column.MinValue > longValue)
177 {
178 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.", column.Name, longValue, column.MinValue));
179 }
180
181 // validate the value against the maximum allowed value
182 if (column.MaxValue.HasValue && column.MaxValue < longValue)
183 {
184 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.", column.Name, longValue, column.MaxValue));
185 }
186
187 return longValue;
188 }
189 else
190 {
191 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set number column '{0}' with a value of type '{1}'.", column.Name, value.GetType().ToString()));
192 }
193 }
194 else
195 {
196 if (!(value is string))
197 {
198 //throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot set string column '{0}' with a value of type '{1}'.", this.name, value.GetType().ToString()));
199 return value.ToString();
200 }
201 }
202 }
203
204 return value;
205 }
206
207 /// <summary>
208 /// Parse a field from the xml.
209 /// </summary>
210 /// <param name="reader">XmlReader where the intermediate is persisted.</param>
211 internal virtual void Read(XmlReader reader)
212 {
213 Debug.Assert("field" == reader.LocalName);
214
215 bool empty = reader.IsEmptyElement;
216
217 while (reader.MoveToNextAttribute())
218 {
219 switch (reader.LocalName)
220 {
221 case "modified":
222 this.Modified = reader.Value.Equals("yes");
223 break;
224 case "previousData":
225 this.PreviousData = reader.Value;
226 break;
227 }
228 }
229
230 if (!empty)
231 {
232 bool done = false;
233
234 while (!done && reader.Read())
235 {
236 switch (reader.NodeType)
237 {
238 case XmlNodeType.Element:
239 throw new XmlException();
240 case XmlNodeType.CDATA:
241 case XmlNodeType.Text:
242 case XmlNodeType.SignificantWhitespace:
243 if (0 < reader.Value.Length)
244 {
245 if (ColumnType.Number == this.Column.Type && !this.Column.IsLocalizable)
246 {
247 // older wix files could persist data as a long value (which would overflow an int)
248 // since the Convert class always throws exceptions for overflows, read in integral
249 // values as a long to avoid the overflow, then cast it to an int (this operation can
250 // overflow without throwing an exception inside an unchecked block)
251 this.data = unchecked((int)Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture));
252 }
253 else
254 {
255 this.data = reader.Value;
256 }
257 }
258 break;
259 case XmlNodeType.EndElement:
260 done = true;
261 break;
262 }
263 }
264
265 if (!done)
266 {
267 throw new XmlException();
268 }
269 }
270 }
271
272 /// <summary>
273 /// Persists a field in an XML format.
274 /// </summary>
275 /// <param name="writer">XmlWriter where the Field should persist itself as XML.</param>
276 internal virtual void Write(XmlWriter writer)
277 {
278 writer.WriteStartElement("field", WindowsInstallerData.XmlNamespaceUri);
279
280 if (this.Modified)
281 {
282 writer.WriteAttributeString("modified", "yes");
283 }
284
285 if (null != this.PreviousData)
286 {
287 writer.WriteAttributeString("previousData", this.PreviousData);
288 }
289
290 // Convert the data to a string that will persist nicely (nulls as String.Empty).
291 string text = Convert.ToString(this.data, CultureInfo.InvariantCulture);
292 if (this.Column.UseCData)
293 {
294 writer.WriteCData(text);
295 }
296 else
297 {
298 writer.WriteString(text);
299 }
300
301 writer.WriteEndElement();
302 }
303 }
304 }