diff options
Diffstat (limited to 'src/wixcop/Converter.cs')
-rw-r--r-- | src/wixcop/Converter.cs | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/src/wixcop/Converter.cs b/src/wixcop/Converter.cs new file mode 100644 index 00000000..a204ebe0 --- /dev/null +++ b/src/wixcop/Converter.cs | |||
@@ -0,0 +1,633 @@ | |||
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 WixCop | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Globalization; | ||
8 | using System.IO; | ||
9 | using System.Linq; | ||
10 | using System.Xml; | ||
11 | using System.Xml.Linq; | ||
12 | using WixToolset.Data; | ||
13 | using WixToolset.Extensibility.Services; | ||
14 | using WixToolset.Tools.Core; | ||
15 | |||
16 | /// <summary> | ||
17 | /// WiX source code converter. | ||
18 | /// </summary> | ||
19 | public class Converter | ||
20 | { | ||
21 | private const string XDocumentNewLine = "\n"; // XDocument normlizes "\r\n" to just "\n". | ||
22 | private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | ||
23 | |||
24 | private static readonly XName FileElementName = WixNamespace + "File"; | ||
25 | private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; | ||
26 | private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage"; | ||
27 | private static readonly XName MspPackageElementName = WixNamespace + "MspPackage"; | ||
28 | private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage"; | ||
29 | private static readonly XName PayloadElementName = WixNamespace + "Payload"; | ||
30 | private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix"; | ||
31 | |||
32 | private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>() | ||
33 | { | ||
34 | { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" }, | ||
35 | { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" }, | ||
36 | { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" }, | ||
37 | { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" }, | ||
38 | { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" }, | ||
39 | { "http://schemas.microsoft.com/wix/GamingExtension", "http://wixtoolset.org/schemas/v4/wxs/gaming" }, | ||
40 | { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" }, | ||
41 | { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" }, | ||
42 | { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" }, | ||
43 | { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" }, | ||
44 | { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" }, | ||
45 | { "http://schemas.microsoft.com/wix/TagExtension", "http://wixtoolset.org/schemas/v4/wxs/tag" }, | ||
46 | { "http://schemas.microsoft.com/wix/UtilExtension", "http://wixtoolset.org/schemas/v4/wxs/util" }, | ||
47 | { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" }, | ||
48 | { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" }, | ||
49 | { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" }, | ||
50 | { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" }, | ||
51 | { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" }, | ||
52 | { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" }, | ||
53 | { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" }, | ||
54 | { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" }, | ||
55 | { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" }, | ||
56 | { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" }, | ||
57 | { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" }, | ||
58 | { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" }, | ||
59 | }; | ||
60 | |||
61 | private Dictionary<XName, Action<XElement>> ConvertElementMapping; | ||
62 | |||
63 | /// <summary> | ||
64 | /// Instantiate a new Converter class. | ||
65 | /// </summary> | ||
66 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
67 | /// <param name="errorsAsWarnings">Test errors to display as warnings.</param> | ||
68 | /// <param name="ignoreErrors">Test errors to ignore.</param> | ||
69 | public Converter(IMessaging messaging, int indentationAmount, IEnumerable<string> errorsAsWarnings = null, IEnumerable<string> ignoreErrors = null) | ||
70 | { | ||
71 | // workaround IDE0009 bug | ||
72 | /*this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>() | ||
73 | { | ||
74 | { Converter.FileElementName, this.ConvertFileElement }, | ||
75 | { Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, | ||
76 | { Converter.MsiPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
77 | { Converter.MspPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
78 | { Converter.MsuPackageElementName, this.ConvertSuppressSignatureValidation }, | ||
79 | { Converter.PayloadElementName, this.ConvertSuppressSignatureValidation }, | ||
80 | { Converter.WixElementWithoutNamespaceName, this.ConvertWixElementWithoutNamespace }, | ||
81 | };*/ | ||
82 | this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>(); | ||
83 | this.ConvertElementMapping.Add(Converter.FileElementName, this.ConvertFileElement); | ||
84 | this.ConvertElementMapping.Add(Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation); | ||
85 | this.ConvertElementMapping.Add(Converter.MsiPackageElementName, this.ConvertSuppressSignatureValidation); | ||
86 | this.ConvertElementMapping.Add(Converter.MspPackageElementName, this.ConvertSuppressSignatureValidation); | ||
87 | this.ConvertElementMapping.Add(Converter.MsuPackageElementName, this.ConvertSuppressSignatureValidation); | ||
88 | this.ConvertElementMapping.Add(Converter.PayloadElementName, this.ConvertSuppressSignatureValidation); | ||
89 | this.ConvertElementMapping.Add(Converter.WixElementWithoutNamespaceName, this.ConvertWixElementWithoutNamespace); | ||
90 | |||
91 | this.Messaging = messaging; | ||
92 | |||
93 | this.IndentationAmount = indentationAmount; | ||
94 | |||
95 | this.ErrorsAsWarnings = new HashSet<ConverterTestType>(this.YieldConverterTypes(errorsAsWarnings)); | ||
96 | |||
97 | this.IgnoreErrors = new HashSet<ConverterTestType>(this.YieldConverterTypes(ignoreErrors)); | ||
98 | } | ||
99 | |||
100 | private int Errors { get; set; } | ||
101 | |||
102 | private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; } | ||
103 | |||
104 | private HashSet<ConverterTestType> IgnoreErrors { get; set; } | ||
105 | |||
106 | private IMessaging Messaging { get; } | ||
107 | |||
108 | private int IndentationAmount { get; set; } | ||
109 | |||
110 | private string SourceFile { get; set; } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Convert a file. | ||
114 | /// </summary> | ||
115 | /// <param name="sourceFile">The file to convert.</param> | ||
116 | /// <param name="saveConvertedFile">Option to save the converted errors that are found.</param> | ||
117 | /// <returns>The number of errors found.</returns> | ||
118 | public int ConvertFile(string sourceFile, bool saveConvertedFile) | ||
119 | { | ||
120 | XDocument document; | ||
121 | |||
122 | // Set the instance info. | ||
123 | this.Errors = 0; | ||
124 | this.SourceFile = sourceFile; | ||
125 | |||
126 | try | ||
127 | { | ||
128 | document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); | ||
129 | } | ||
130 | catch (XmlException e) | ||
131 | { | ||
132 | this.OnError(ConverterTestType.XmlException, (XObject)null, "The xml is invalid. Detail: '{0}'", e.Message); | ||
133 | |||
134 | return this.Errors; | ||
135 | } | ||
136 | |||
137 | this.ConvertDocument(document); | ||
138 | |||
139 | // Fix errors if requested and necessary. | ||
140 | if (saveConvertedFile && 0 < this.Errors) | ||
141 | { | ||
142 | try | ||
143 | { | ||
144 | using (StreamWriter writer = File.CreateText(this.SourceFile)) | ||
145 | { | ||
146 | document.Save(writer, SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces); | ||
147 | } | ||
148 | } | ||
149 | catch (UnauthorizedAccessException) | ||
150 | { | ||
151 | this.OnError(ConverterTestType.UnauthorizedAccessException, (XObject)null, "Could not write to file."); | ||
152 | } | ||
153 | } | ||
154 | |||
155 | return this.Errors; | ||
156 | } | ||
157 | |||
158 | /// <summary> | ||
159 | /// Convert a document. | ||
160 | /// </summary> | ||
161 | /// <param name="document">The document to convert.</param> | ||
162 | /// <returns>The number of errors found.</returns> | ||
163 | public int ConvertDocument(XDocument document) | ||
164 | { | ||
165 | XDeclaration declaration = document.Declaration; | ||
166 | |||
167 | // Convert the declaration. | ||
168 | if (null != declaration) | ||
169 | { | ||
170 | if (!String.Equals("utf-8", declaration.Encoding, StringComparison.OrdinalIgnoreCase)) | ||
171 | { | ||
172 | if (this.OnError(ConverterTestType.DeclarationEncodingWrong, document.Root, "The XML declaration encoding is not properly set to 'utf-8'.")) | ||
173 | { | ||
174 | declaration.Encoding = "utf-8"; | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | else // missing declaration | ||
179 | { | ||
180 | if (this.OnError(ConverterTestType.DeclarationMissing, (XNode)null, "This file is missing an XML declaration on the first line.")) | ||
181 | { | ||
182 | document.Declaration = new XDeclaration("1.0", "utf-8", null); | ||
183 | document.Root.AddBeforeSelf(new XText(XDocumentNewLine)); | ||
184 | } | ||
185 | } | ||
186 | |||
187 | // Start converting the nodes at the top. | ||
188 | this.ConvertNode(document.Root, 0); | ||
189 | |||
190 | return this.Errors; | ||
191 | } | ||
192 | |||
193 | /// <summary> | ||
194 | /// Convert a single xml node. | ||
195 | /// </summary> | ||
196 | /// <param name="node">The node to convert.</param> | ||
197 | /// <param name="level">The depth level of the node.</param> | ||
198 | /// <returns>The converted node.</returns> | ||
199 | private void ConvertNode(XNode node, int level) | ||
200 | { | ||
201 | // Convert this node's whitespace. | ||
202 | if ((XmlNodeType.Comment == node.NodeType && 0 > ((XComment)node).Value.IndexOf(XDocumentNewLine, StringComparison.Ordinal)) || | ||
203 | XmlNodeType.CDATA == node.NodeType || XmlNodeType.Element == node.NodeType || XmlNodeType.ProcessingInstruction == node.NodeType) | ||
204 | { | ||
205 | this.ConvertWhitespace(node, level); | ||
206 | } | ||
207 | |||
208 | // Convert this node if it is an element. | ||
209 | XElement element = node as XElement; | ||
210 | |||
211 | if (null != element) | ||
212 | { | ||
213 | this.ConvertElement(element); | ||
214 | |||
215 | // Convert all children of this element. | ||
216 | IEnumerable<XNode> children = element.Nodes().ToList(); | ||
217 | |||
218 | foreach (XNode child in children) | ||
219 | { | ||
220 | this.ConvertNode(child, level + 1); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | private void ConvertElement(XElement element) | ||
226 | { | ||
227 | // Gather any deprecated namespaces, then update this element tree based on those deprecations. | ||
228 | Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces = new Dictionary<XNamespace, XNamespace>(); | ||
229 | |||
230 | foreach (XAttribute declaration in element.Attributes().Where(a => a.IsNamespaceDeclaration)) | ||
231 | { | ||
232 | XNamespace ns; | ||
233 | |||
234 | if (Converter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out ns)) | ||
235 | { | ||
236 | if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName)) | ||
237 | { | ||
238 | deprecatedToUpdatedNamespaces.Add(declaration.Value, ns); | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | |||
243 | if (deprecatedToUpdatedNamespaces.Any()) | ||
244 | { | ||
245 | Converter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces); | ||
246 | } | ||
247 | |||
248 | // Convert the node in much greater detail. | ||
249 | Action<XElement> convert; | ||
250 | |||
251 | if (this.ConvertElementMapping.TryGetValue(element.Name, out convert)) | ||
252 | { | ||
253 | convert(element); | ||
254 | } | ||
255 | } | ||
256 | |||
257 | private void ConvertFileElement(XElement element) | ||
258 | { | ||
259 | if (null == element.Attribute("Id")) | ||
260 | { | ||
261 | XAttribute attribute = element.Attribute("Name"); | ||
262 | |||
263 | if (null == attribute) | ||
264 | { | ||
265 | attribute = element.Attribute("Source"); | ||
266 | } | ||
267 | |||
268 | if (null != attribute) | ||
269 | { | ||
270 | string name = Path.GetFileName(attribute.Value); | ||
271 | |||
272 | if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name)) | ||
273 | { | ||
274 | IEnumerable<XAttribute> attributes = element.Attributes().ToList(); | ||
275 | element.RemoveAttributes(); | ||
276 | element.Add(new XAttribute("Id", ToolsCommon.GetIdentifierFromName(name))); | ||
277 | element.Add(attributes); | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private void ConvertSuppressSignatureValidation(XElement element) | ||
284 | { | ||
285 | XAttribute suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); | ||
286 | |||
287 | if (null != suppressSignatureValidation) | ||
288 | { | ||
289 | if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' instead.", suppressSignatureValidation)) | ||
290 | { | ||
291 | if ("no" == suppressSignatureValidation.Value) | ||
292 | { | ||
293 | element.Add(new XAttribute("EnableSignatureValidation", "yes")); | ||
294 | } | ||
295 | } | ||
296 | |||
297 | suppressSignatureValidation.Remove(); | ||
298 | } | ||
299 | } | ||
300 | |||
301 | /// <summary> | ||
302 | /// Converts a Wix element. | ||
303 | /// </summary> | ||
304 | /// <param name="element">The Wix element to convert.</param> | ||
305 | /// <returns>The converted element.</returns> | ||
306 | private void ConvertWixElementWithoutNamespace(XElement element) | ||
307 | { | ||
308 | if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) | ||
309 | { | ||
310 | element.Name = WixNamespace.GetName(element.Name.LocalName); | ||
311 | |||
312 | element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. | ||
313 | |||
314 | foreach (XElement elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace)) | ||
315 | { | ||
316 | elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); | ||
317 | } | ||
318 | } | ||
319 | } | ||
320 | |||
321 | /// <summary> | ||
322 | /// Convert the whitespace adjacent to a node. | ||
323 | /// </summary> | ||
324 | /// <param name="node">The node to convert.</param> | ||
325 | /// <param name="level">The depth level of the node.</param> | ||
326 | private void ConvertWhitespace(XNode node, int level) | ||
327 | { | ||
328 | // Fix the whitespace before this node. | ||
329 | XText whitespace = node.PreviousNode as XText; | ||
330 | |||
331 | if (null != whitespace) | ||
332 | { | ||
333 | if (XmlNodeType.CDATA == node.NodeType) | ||
334 | { | ||
335 | if (this.OnError(ConverterTestType.WhitespacePrecedingCDATAWrong, node, "There should be no whitespace preceding a CDATA node.")) | ||
336 | { | ||
337 | whitespace.Remove(); | ||
338 | } | ||
339 | } | ||
340 | else | ||
341 | { | ||
342 | // TODO: this code complains about whitespace even after the file has been fixed. | ||
343 | if (!Converter.IsLegalWhitespace(this.IndentationAmount, level, whitespace.Value)) | ||
344 | { | ||
345 | if (this.OnError(ConverterTestType.WhitespacePrecedingNodeWrong, node, "The whitespace preceding this node is incorrect.")) | ||
346 | { | ||
347 | Converter.FixWhitespace(this.IndentationAmount, level, whitespace); | ||
348 | } | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | // Fix the whitespace after CDATA nodes. | ||
354 | XCData cdata = node as XCData; | ||
355 | |||
356 | if (null != cdata) | ||
357 | { | ||
358 | whitespace = cdata.NextNode as XText; | ||
359 | |||
360 | if (null != whitespace) | ||
361 | { | ||
362 | if (this.OnError(ConverterTestType.WhitespaceFollowingCDATAWrong, node, "There should be no whitespace following a CDATA node.")) | ||
363 | { | ||
364 | whitespace.Remove(); | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | else | ||
369 | { | ||
370 | // Fix the whitespace inside and after this node (except for Error which may contain just whitespace). | ||
371 | XElement element = node as XElement; | ||
372 | |||
373 | if (null != element && "Error" != element.Name.LocalName) | ||
374 | { | ||
375 | if (!element.HasElements && !element.IsEmpty && String.IsNullOrEmpty(element.Value.Trim())) | ||
376 | { | ||
377 | if (this.OnError(ConverterTestType.NotEmptyElement, element, "This should be an empty element since it contains nothing but whitespace.")) | ||
378 | { | ||
379 | element.RemoveNodes(); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | whitespace = node.NextNode as XText; | ||
384 | |||
385 | if (null != whitespace) | ||
386 | { | ||
387 | // TODO: this code crashes when level is 0, | ||
388 | // complains about whitespace even after the file has been fixed, | ||
389 | // and the error text doesn't match the error. | ||
390 | if (!Converter.IsLegalWhitespace(this.IndentationAmount, level - 1, whitespace.Value)) | ||
391 | { | ||
392 | if (this.OnError(ConverterTestType.WhitespacePrecedingEndElementWrong, whitespace, "The whitespace preceding this end element is incorrect.")) | ||
393 | { | ||
394 | Converter.FixWhitespace(this.IndentationAmount, level - 1, whitespace); | ||
395 | } | ||
396 | } | ||
397 | } | ||
398 | } | ||
399 | } | ||
400 | } | ||
401 | |||
402 | private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types) | ||
403 | { | ||
404 | if (null != types) | ||
405 | { | ||
406 | foreach (string type in types) | ||
407 | { | ||
408 | ConverterTestType itt; | ||
409 | |||
410 | if (Enum.TryParse<ConverterTestType>(type, true, out itt)) | ||
411 | { | ||
412 | yield return itt; | ||
413 | } | ||
414 | else // not a known ConverterTestType | ||
415 | { | ||
416 | this.OnError(ConverterTestType.ConverterTestTypeUnknown, (XObject)null, "Unknown error type: '{0}'.", type); | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | } | ||
421 | |||
422 | private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces) | ||
423 | { | ||
424 | foreach (XElement element in elements) | ||
425 | { | ||
426 | XNamespace ns; | ||
427 | |||
428 | if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out ns)) | ||
429 | { | ||
430 | element.Name = ns.GetName(element.Name.LocalName); | ||
431 | } | ||
432 | |||
433 | // Remove all the attributes and add them back to with their namespace updated (as necessary). | ||
434 | IEnumerable<XAttribute> attributes = element.Attributes().ToList(); | ||
435 | element.RemoveAttributes(); | ||
436 | |||
437 | foreach (XAttribute attribute in attributes) | ||
438 | { | ||
439 | XAttribute convertedAttribute = attribute; | ||
440 | |||
441 | if (attribute.IsNamespaceDeclaration) | ||
442 | { | ||
443 | if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns)) | ||
444 | { | ||
445 | convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName); | ||
446 | } | ||
447 | } | ||
448 | else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns)) | ||
449 | { | ||
450 | convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value); | ||
451 | } | ||
452 | |||
453 | element.Add(convertedAttribute); | ||
454 | } | ||
455 | } | ||
456 | } | ||
457 | |||
458 | /// <summary> | ||
459 | /// Determine if the whitespace preceding a node is appropriate for its depth level. | ||
460 | /// </summary> | ||
461 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
462 | /// <param name="level">The depth level that should match this whitespace.</param> | ||
463 | /// <param name="whitespace">The whitespace to validate.</param> | ||
464 | /// <returns>true if the whitespace is legal; false otherwise.</returns> | ||
465 | private static bool IsLegalWhitespace(int indentationAmount, int level, string whitespace) | ||
466 | { | ||
467 | // strip off leading newlines; there can be an arbitrary number of these | ||
468 | while (whitespace.StartsWith(XDocumentNewLine, StringComparison.Ordinal)) | ||
469 | { | ||
470 | whitespace = whitespace.Substring(XDocumentNewLine.Length); | ||
471 | } | ||
472 | |||
473 | // check the length | ||
474 | if (whitespace.Length != level * indentationAmount) | ||
475 | { | ||
476 | return false; | ||
477 | } | ||
478 | |||
479 | // check the spaces | ||
480 | foreach (char character in whitespace) | ||
481 | { | ||
482 | if (' ' != character) | ||
483 | { | ||
484 | return false; | ||
485 | } | ||
486 | } | ||
487 | |||
488 | return true; | ||
489 | } | ||
490 | |||
491 | /// <summary> | ||
492 | /// Fix the whitespace in a Whitespace node. | ||
493 | /// </summary> | ||
494 | /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> | ||
495 | /// <param name="level">The depth level of the desired whitespace.</param> | ||
496 | /// <param name="whitespace">The whitespace node to fix.</param> | ||
497 | private static void FixWhitespace(int indentationAmount, int level, XText whitespace) | ||
498 | { | ||
499 | int newLineCount = 0; | ||
500 | |||
501 | for (int i = 0; i + 1 < whitespace.Value.Length; ++i) | ||
502 | { | ||
503 | if (XDocumentNewLine == whitespace.Value.Substring(i, 2)) | ||
504 | { | ||
505 | ++i; // skip an extra character | ||
506 | ++newLineCount; | ||
507 | } | ||
508 | } | ||
509 | |||
510 | if (0 == newLineCount) | ||
511 | { | ||
512 | newLineCount = 1; | ||
513 | } | ||
514 | |||
515 | // reset the whitespace value | ||
516 | whitespace.Value = String.Empty; | ||
517 | |||
518 | // add the correct number of newlines | ||
519 | for (int i = 0; i < newLineCount; ++i) | ||
520 | { | ||
521 | whitespace.Value = String.Concat(whitespace.Value, XDocumentNewLine); | ||
522 | } | ||
523 | |||
524 | // add the correct number of spaces based on configured indentation amount | ||
525 | whitespace.Value = String.Concat(whitespace.Value, new string(' ', level * indentationAmount)); | ||
526 | } | ||
527 | |||
528 | /// <summary> | ||
529 | /// Output an error message to the console. | ||
530 | /// </summary> | ||
531 | /// <param name="converterTestType">The type of converter test.</param> | ||
532 | /// <param name="node">The node that caused the error.</param> | ||
533 | /// <param name="message">Detailed error message.</param> | ||
534 | /// <param name="args">Additional formatted string arguments.</param> | ||
535 | /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns> | ||
536 | private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) | ||
537 | { | ||
538 | if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error | ||
539 | { | ||
540 | return false; | ||
541 | } | ||
542 | |||
543 | // Increase the error count. | ||
544 | this.Errors++; | ||
545 | |||
546 | SourceLineNumber sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); | ||
547 | bool warning = this.ErrorsAsWarnings.Contains(converterTestType); | ||
548 | string display = String.Format(CultureInfo.CurrentCulture, message, args); | ||
549 | |||
550 | var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); | ||
551 | |||
552 | this.Messaging.Write(msg); | ||
553 | |||
554 | return true; | ||
555 | } | ||
556 | |||
557 | /// <summary> | ||
558 | /// Converter test types. These are used to condition error messages down to warnings. | ||
559 | /// </summary> | ||
560 | private enum ConverterTestType | ||
561 | { | ||
562 | /// <summary> | ||
563 | /// Internal-only: displayed when a string cannot be converted to an ConverterTestType. | ||
564 | /// </summary> | ||
565 | ConverterTestTypeUnknown, | ||
566 | |||
567 | /// <summary> | ||
568 | /// Displayed when an XML loading exception has occurred. | ||
569 | /// </summary> | ||
570 | XmlException, | ||
571 | |||
572 | /// <summary> | ||
573 | /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file. | ||
574 | /// </summary> | ||
575 | UnauthorizedAccessException, | ||
576 | |||
577 | /// <summary> | ||
578 | /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'. | ||
579 | /// </summary> | ||
580 | DeclarationEncodingWrong, | ||
581 | |||
582 | /// <summary> | ||
583 | /// Displayed when the XML declaration is missing from the source file. | ||
584 | /// </summary> | ||
585 | DeclarationMissing, | ||
586 | |||
587 | /// <summary> | ||
588 | /// Displayed when the whitespace preceding a CDATA node is wrong. | ||
589 | /// </summary> | ||
590 | WhitespacePrecedingCDATAWrong, | ||
591 | |||
592 | /// <summary> | ||
593 | /// Displayed when the whitespace preceding a node is wrong. | ||
594 | /// </summary> | ||
595 | WhitespacePrecedingNodeWrong, | ||
596 | |||
597 | /// <summary> | ||
598 | /// Displayed when an element is not empty as it should be. | ||
599 | /// </summary> | ||
600 | NotEmptyElement, | ||
601 | |||
602 | /// <summary> | ||
603 | /// Displayed when the whitespace following a CDATA node is wrong. | ||
604 | /// </summary> | ||
605 | WhitespaceFollowingCDATAWrong, | ||
606 | |||
607 | /// <summary> | ||
608 | /// Displayed when the whitespace preceding an end element is wrong. | ||
609 | /// </summary> | ||
610 | WhitespacePrecedingEndElementWrong, | ||
611 | |||
612 | /// <summary> | ||
613 | /// Displayed when the xmlns attribute is missing from the document element. | ||
614 | /// </summary> | ||
615 | XmlnsMissing, | ||
616 | |||
617 | /// <summary> | ||
618 | /// Displayed when the xmlns attribute on the document element is wrong. | ||
619 | /// </summary> | ||
620 | XmlnsValueWrong, | ||
621 | |||
622 | /// <summary> | ||
623 | /// Assign an identifier to a File element when on Id attribute is specified. | ||
624 | /// </summary> | ||
625 | AssignAnonymousFileId, | ||
626 | |||
627 | /// <summary> | ||
628 | /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation. | ||
629 | /// </summary> | ||
630 | SuppressSignatureValidationDeprecated, | ||
631 | } | ||
632 | } | ||
633 | } | ||