aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Arnson <bob@joyofsetup.com>2019-11-01 18:03:29 -0400
committerBob Arnson <bob@firegiant.com>2019-11-01 18:05:48 -0400
commit2c6285da46439ec98f89f09e2029f801924014ed (patch)
treed302a6efd422572693f32ef7608e97c3da4307a2 /src
parent8b1861a0d9fc636f02ccb9452946863e31a46b78 (diff)
downloadwix-2c6285da46439ec98f89f09e2029f801924014ed.tar.gz
wix-2c6285da46439ec98f89f09e2029f801924014ed.tar.bz2
wix-2c6285da46439ec98f89f09e2029f801924014ed.zip
Handle CustomTable/@BootstrapperApplicationData.
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Converters/Wix3Converter.cs548
-rw-r--r--src/test/WixToolsetTest.Converters/ConverterFixture.cs28
2 files changed, 311 insertions, 265 deletions
diff --git a/src/WixToolset.Converters/Wix3Converter.cs b/src/WixToolset.Converters/Wix3Converter.cs
index 9fde7360..b4ce1064 100644
--- a/src/WixToolset.Converters/Wix3Converter.cs
+++ b/src/WixToolset.Converters/Wix3Converter.cs
@@ -25,6 +25,7 @@ namespace WixToolset.Converters
25 private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n". 25 private const char XDocumentNewLine = '\n'; // XDocument normalizes "\r\n" to just "\n".
26 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; 26 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
27 27
28 private static readonly XName CustomTableElementName = WixNamespace + "CustomTable";
28 private static readonly XName DirectoryElementName = WixNamespace + "Directory"; 29 private static readonly XName DirectoryElementName = WixNamespace + "Directory";
29 private static readonly XName FileElementName = WixNamespace + "File"; 30 private static readonly XName FileElementName = WixNamespace + "File";
30 private static readonly XName ExePackageElementName = WixNamespace + "ExePackage"; 31 private static readonly XName ExePackageElementName = WixNamespace + "ExePackage";
@@ -78,6 +79,7 @@ namespace WixToolset.Converters
78 { 79 {
79 this.ConvertElementMapping = new Dictionary<XName, Action<XElement>> 80 this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>
80 { 81 {
82 { Wix3Converter.CustomTableElementName, this.ConvertCustomTableElement },
81 { Wix3Converter.DirectoryElementName, this.ConvertDirectoryElement }, 83 { Wix3Converter.DirectoryElementName, this.ConvertDirectoryElement },
82 { Wix3Converter.FileElementName, this.ConvertFileElement }, 84 { Wix3Converter.FileElementName, this.ConvertFileElement },
83 { Wix3Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation }, 85 { Wix3Converter.ExePackageElementName, this.ConvertSuppressSignatureValidation },
@@ -292,363 +294,379 @@ namespace WixToolset.Converters
292 } 294 }
293 } 295 }
294 296
295 private void ConvertDirectoryElement(XElement element) 297 private void ConvertCustomTableElement(XElement element)
296 { 298 {
297 if (null == element.Attribute("Name")) 299 var bootstrapperApplicationData = element.Attribute("BootstrapperApplicationData");
300 if (bootstrapperApplicationData != null
301 && this.OnError(ConverterTestType.BootstrapperApplicationDataDeprecated, element, "The CustomTable element contains deprecated '{0}' attribute. Use the 'Unreal' attribute instead.", bootstrapperApplicationData.Name))
298 { 302 {
299 var attribute = element.Attribute("ShortName"); 303 element.Add(new XAttribute("Unreal", bootstrapperApplicationData.Value));
300 if (null != attribute) 304 bootstrapperApplicationData.Remove();
305 }
306 }
307
308 private void ConvertDirectoryElement(XElement element)
309 {
310 if (null == element.Attribute("Name"))
311 {
312 var attribute = element.Attribute("ShortName");
313 if (null != attribute)
314 {
315 var shortName = attribute.Value;
316 if (this.OnError(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName))
301 { 317 {
302 var shortName = attribute.Value; 318 element.Add(new XAttribute("Name", shortName));
303 if (this.OnError(ConverterTestType.AssignDirectoryNameFromShortName, element, "The directory ShortName attribute is being renamed to Name since Name wasn't specified for value '{0}'", shortName)) 319 attribute.Remove();
304 {
305 element.Add(new XAttribute("Name", shortName));
306 attribute.Remove();
307 }
308 } 320 }
309 } 321 }
310 } 322 }
323 }
311 324
312 private void ConvertFileElement(XElement element) 325 private void ConvertFileElement(XElement element)
326 {
327 if (null == element.Attribute("Id"))
313 { 328 {
314 if (null == element.Attribute("Id")) 329 var attribute = element.Attribute("Name");
330
331 if (null == attribute)
315 { 332 {
316 var attribute = element.Attribute("Name"); 333 attribute = element.Attribute("Source");
334 }
317 335
318 if (null == attribute) 336 if (null != attribute)
319 { 337 {
320 attribute = element.Attribute("Source"); 338 var name = Path.GetFileName(attribute.Value);
321 }
322 339
323 if (null != attribute) 340 if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name))
324 { 341 {
325 var name = Path.GetFileName(attribute.Value); 342 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
326 343 element.RemoveAttributes();
327 if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name)) 344 element.Add(new XAttribute("Id", GetIdentifierFromName(name)));
328 { 345 element.Add(attributes);
329 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
330 element.RemoveAttributes();
331 element.Add(new XAttribute("Id", GetIdentifierFromName(name)));
332 element.Add(attributes);
333 }
334 } 346 }
335 } 347 }
336 } 348 }
349 }
337 350
338 private void ConvertSuppressSignatureValidation(XElement element) 351 private void ConvertSuppressSignatureValidation(XElement element)
339 { 352 {
340 var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation"); 353 var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation");
341 354
342 if (null != suppressSignatureValidation) 355 if (null != suppressSignatureValidation)
356 {
357 if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' attribute instead.", suppressSignatureValidation.Name))
343 { 358 {
344 if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' attribute instead.", suppressSignatureValidation)) 359 if ("no" == suppressSignatureValidation.Value)
345 { 360 {
346 if ("no" == suppressSignatureValidation.Value) 361 element.Add(new XAttribute("EnableSignatureValidation", "yes"));
347 {
348 element.Add(new XAttribute("EnableSignatureValidation", "yes"));
349 }
350 } 362 }
351
352 suppressSignatureValidation.Remove();
353 } 363 }
364
365 suppressSignatureValidation.Remove();
354 } 366 }
367 }
355 368
356 private void ConvertCustomActionElement(XElement xCustomAction) 369 private void ConvertCustomActionElement(XElement xCustomAction)
357 { 370 {
358 var xBinaryKey = xCustomAction.Attribute("BinaryKey"); 371 var xBinaryKey = xCustomAction.Attribute("BinaryKey");
359 372
360 if (xBinaryKey?.Value == "WixCA") 373 if (xBinaryKey?.Value == "WixCA")
374 {
375 if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'UtilCA' instead."))
361 { 376 {
362 if (this.OnError(ConverterTestType.WixCABinaryIdRenamed, xCustomAction, "The WixCA custom action DLL Binary table id has been renamed. Use the id 'UtilCA' instead.")) 377 xBinaryKey.Value = "UtilCA";
363 {
364 xBinaryKey.Value = "UtilCA";
365 }
366 } 378 }
379 }
367 380
368 var xDllEntry = xCustomAction.Attribute("DllEntry"); 381 var xDllEntry = xCustomAction.Attribute("DllEntry");
369 382
370 if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64") 383 if (xDllEntry?.Value == "CAQuietExec" || xDllEntry?.Value == "CAQuietExec64")
384 {
385 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead."))
371 { 386 {
372 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The CAQuietExec and CAQuietExec64 custom action ids have been renamed. Use the ids 'WixQuietExec' and 'WixQuietExec64' instead.")) 387 xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec");
373 {
374 xDllEntry.Value = xDllEntry.Value.Replace("CAQuietExec", "WixQuietExec");
375 }
376 } 388 }
389 }
377 390
378 var xProperty = xCustomAction.Attribute("Property"); 391 var xProperty = xCustomAction.Attribute("Property");
379 392
380 if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine") 393 if (xProperty?.Value == "QtExecCmdLine" || xProperty?.Value == "QtExec64CmdLine")
394 {
395 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead."))
381 { 396 {
382 if (this.OnError(ConverterTestType.QuietExecCustomActionsRenamed, xCustomAction, "The QtExecCmdLine and QtExec64CmdLine property ids have been renamed. Use the ids 'WixQuietExecCmdLine' and 'WixQuietExec64CmdLine' instead.")) 397 xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec");
383 {
384 xProperty.Value = xProperty.Value.Replace("QtExec", "WixQuietExec");
385 }
386 } 398 }
387 } 399 }
400 }
401
402 private void ConvertPropertyElement(XElement xProperty)
403 {
404 var xId = xProperty.Attribute("Id");
405
406 if (xId.Value == "QtExecCmdTimeout")
407 {
408 this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.");
409 }
410 }
388 411
389 private void ConvertPropertyElement(XElement xProperty) 412 /// <summary>
413 /// Converts a Wix element.
414 /// </summary>
415 /// <param name="element">The Wix element to convert.</param>
416 /// <returns>The converted element.</returns>
417 private void ConvertElementWithoutNamespace(XElement element)
418 {
419 if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName))
390 { 420 {
391 var xId = xProperty.Attribute("Id"); 421 element.Name = WixNamespace.GetName(element.Name.LocalName);
392 422
393 if (xId.Value == "QtExecCmdTimeout") 423 element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace.
424
425 foreach (var elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace))
394 { 426 {
395 this.OnError(ConverterTestType.QtExecCmdTimeoutAmbiguous, xProperty, "QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout."); 427 elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName);
396 } 428 }
397 } 429 }
430 }
398 431
399 /// <summary> 432 private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types)
400 /// Converts a Wix element. 433 {
401 /// </summary> 434 if (null != types)
402 /// <param name="element">The Wix element to convert.</param>
403 /// <returns>The converted element.</returns>
404 private void ConvertElementWithoutNamespace(XElement element)
405 { 435 {
406 if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName)) 436 foreach (var type in types)
407 { 437 {
408 element.Name = WixNamespace.GetName(element.Name.LocalName);
409 438
410 element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace. 439 if (Enum.TryParse<ConverterTestType>(type, true, out var itt))
411 440 {
412 foreach (var elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace)) 441 yield return itt;
442 }
443 else // not a known ConverterTestType
413 { 444 {
414 elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName); 445 this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type);
415 } 446 }
416 } 447 }
417 } 448 }
449 }
418 450
419 private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types) 451 private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces)
452 {
453 foreach (var element in elements)
420 { 454 {
421 if (null != types) 455
456 if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns))
422 { 457 {
423 foreach (var type in types) 458 element.Name = ns.GetName(element.Name.LocalName);
424 { 459 }
425 460
426 if (Enum.TryParse<ConverterTestType>(type, true, out var itt)) 461 // Remove all the attributes and add them back to with their namespace updated (as necessary).
427 { 462 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
428 yield return itt; 463 element.RemoveAttributes();
429 } 464
430 else // not a known ConverterTestType 465 foreach (var attribute in attributes)
466 {
467 var convertedAttribute = attribute;
468
469 if (attribute.IsNamespaceDeclaration)
470 {
471 if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns))
431 { 472 {
432 this.OnError(ConverterTestType.ConverterTestTypeUnknown, null, "Unknown error type: '{0}'.", type); 473 convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName);
433 } 474 }
434 } 475 }
476 else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns))
477 {
478 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
479 }
480
481 element.Add(convertedAttribute);
435 } 482 }
436 } 483 }
484 }
485
486 /// <summary>
487 /// Determine if the whitespace preceding a node is appropriate for its depth level.
488 /// </summary>
489 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
490 /// <param name="level">The depth level that should match this whitespace.</param>
491 /// <param name="whitespace">The whitespace to validate.</param>
492 /// <returns>true if the whitespace is legal; false otherwise.</returns>
493 private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace)
494 {
495 // Strip off leading newlines; there can be an arbitrary number of these.
496 whitespace = whitespace.TrimStart(XDocumentNewLine);
437 497
438 private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces) 498 var indentation = new string(' ', level * indentationAmount);
499
500 return whitespace == indentation;
501 }
502
503 /// <summary>
504 /// Fix the whitespace in a whitespace node.
505 /// </summary>
506 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
507 /// <param name="level">The depth level of the desired whitespace.</param>
508 /// <param name="whitespace">The whitespace node to fix.</param>
509 private static void FixupWhitespace(int indentationAmount, int level, XText whitespace)
510 {
511 var value = new StringBuilder(whitespace.Value.Length);
512
513 // Keep any previous preceeding new lines.
514 var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count();
515
516 // Ensure there is always at least one new line before the indentation.
517 value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines);
518
519 whitespace.Value = value.Append(' ', level * indentationAmount).ToString();
520 }
521
522 /// <summary>
523 /// Output an error message to the console.
524 /// </summary>
525 /// <param name="converterTestType">The type of converter test.</param>
526 /// <param name="node">The node that caused the error.</param>
527 /// <param name="message">Detailed error message.</param>
528 /// <param name="args">Additional formatted string arguments.</param>
529 /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns>
530 private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args)
531 {
532 if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error
439 { 533 {
440 foreach (var element in elements) 534 return false;
441 { 535 }
442 536
443 if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out var ns)) 537 // Increase the error count.
444 { 538 this.Errors++;
445 element.Name = ns.GetName(element.Name.LocalName);
446 }
447 539
448 // Remove all the attributes and add them back to with their namespace updated (as necessary). 540 var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
449 IEnumerable<XAttribute> attributes = element.Attributes().ToList(); 541 var warning = this.ErrorsAsWarnings.Contains(converterTestType);
450 element.RemoveAttributes(); 542 var display = String.Format(CultureInfo.CurrentCulture, message, args);
451 543
452 foreach (var attribute in attributes) 544 var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString());
453 {
454 var convertedAttribute = attribute;
455 545
456 if (attribute.IsNamespaceDeclaration) 546 this.Messaging.Write(msg);
457 {
458 if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns))
459 {
460 convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName);
461 }
462 }
463 else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns))
464 {
465 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
466 }
467 547
468 element.Add(convertedAttribute); 548 return true;
469 } 549 }
470 } 550
551 /// <summary>
552 /// Return an identifier based on passed file/directory name
553 /// </summary>
554 /// <param name="name">File/directory name to generate identifer from</param>
555 /// <returns>A version of the name that is a legal identifier.</returns>
556 /// <remarks>This is duplicated from WiX's Common class.</remarks>
557 private static string GetIdentifierFromName(string name)
558 {
559 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
560
561 // MSI identifiers must begin with an alphabetic character or an
562 // underscore. Prefix all other values with an underscore.
563 if (AddPrefix.IsMatch(name))
564 {
565 result = String.Concat("_", result);
471 } 566 }
472 567
568 return result;
569 }
570
571 /// <summary>
572 /// Converter test types. These are used to condition error messages down to warnings.
573 /// </summary>
574 private enum ConverterTestType
575 {
473 /// <summary> 576 /// <summary>
474 /// Determine if the whitespace preceding a node is appropriate for its depth level. 577 /// Internal-only: displayed when a string cannot be converted to an ConverterTestType.
475 /// </summary> 578 /// </summary>
476 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> 579 ConverterTestTypeUnknown,
477 /// <param name="level">The depth level that should match this whitespace.</param>
478 /// <param name="whitespace">The whitespace to validate.</param>
479 /// <returns>true if the whitespace is legal; false otherwise.</returns>
480 private static bool LeadingWhitespaceValid(int indentationAmount, int level, string whitespace)
481 {
482 // Strip off leading newlines; there can be an arbitrary number of these.
483 whitespace = whitespace.TrimStart(XDocumentNewLine);
484 580
485 var indentation = new string(' ', level * indentationAmount); 581 /// <summary>
582 /// Displayed when an XML loading exception has occurred.
583 /// </summary>
584 XmlException,
486 585
487 return whitespace == indentation; 586 /// <summary>
488 } 587 /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file.
588 /// </summary>
589 UnauthorizedAccessException,
489 590
490 /// <summary> 591 /// <summary>
491 /// Fix the whitespace in a whitespace node. 592 /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'.
492 /// </summary> 593 /// </summary>
493 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param> 594 DeclarationEncodingWrong,
494 /// <param name="level">The depth level of the desired whitespace.</param>
495 /// <param name="whitespace">The whitespace node to fix.</param>
496 private static void FixupWhitespace(int indentationAmount, int level, XText whitespace)
497 {
498 var value = new StringBuilder(whitespace.Value.Length);
499 595
500 // Keep any previous preceeding new lines. 596 /// <summary>
501 var newlines = whitespace.Value.TakeWhile(c => c == XDocumentNewLine).Count(); 597 /// Displayed when the XML declaration is missing from the source file.
598 /// </summary>
599 DeclarationMissing,
502 600
503 // Ensure there is always at least one new line before the indentation. 601 /// <summary>
504 value.Append(XDocumentNewLine, newlines == 0 ? 1 : newlines); 602 /// Displayed when the whitespace preceding a CDATA node is wrong.
603 /// </summary>
604 WhitespacePrecedingCDATAWrong,
505 605
506 whitespace.Value = value.Append(' ', level * indentationAmount).ToString(); 606 /// <summary>
507 } 607 /// Displayed when the whitespace preceding a node is wrong.
608 /// </summary>
609 WhitespacePrecedingNodeWrong,
508 610
509 /// <summary> 611 /// <summary>
510 /// Output an error message to the console. 612 /// Displayed when an element is not empty as it should be.
511 /// </summary> 613 /// </summary>
512 /// <param name="converterTestType">The type of converter test.</param> 614 NotEmptyElement,
513 /// <param name="node">The node that caused the error.</param> 615
514 /// <param name="message">Detailed error message.</param> 616 /// <summary>
515 /// <param name="args">Additional formatted string arguments.</param> 617 /// Displayed when the whitespace following a CDATA node is wrong.
516 /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns> 618 /// </summary>
517 private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args) 619 WhitespaceFollowingCDATAWrong,
518 {
519 if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error
520 {
521 return false;
522 }
523 620
524 // Increase the error count. 621 /// <summary>
525 this.Errors++; 622 /// Displayed when the whitespace preceding an end element is wrong.
623 /// </summary>
624 WhitespacePrecedingEndElementWrong,
625
626 /// <summary>
627 /// Displayed when the xmlns attribute is missing from the document element.
628 /// </summary>
629 XmlnsMissing,
526 630
527 var sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber); 631 /// <summary>
528 var warning = this.ErrorsAsWarnings.Contains(converterTestType); 632 /// Displayed when the xmlns attribute on the document element is wrong.
529 var display = String.Format(CultureInfo.CurrentCulture, message, args); 633 /// </summary>
634 XmlnsValueWrong,
530 635
531 var msg = new Message(sourceLine, warning ? MessageLevel.Warning : MessageLevel.Error, (int)converterTestType, "{0} ({1})", display, converterTestType.ToString()); 636 /// <summary>
637 /// Assign an identifier to a File element when on Id attribute is specified.
638 /// </summary>
639 AssignAnonymousFileId,
532 640
533 this.Messaging.Write(msg); 641 /// <summary>
642 /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation.
643 /// </summary>
644 SuppressSignatureValidationDeprecated,
534 645
535 return true; 646 /// <summary>
536 } 647 /// WixCA Binary/@Id has been renamed to UtilCA.
648 /// </summary>
649 WixCABinaryIdRenamed,
537 650
538 /// <summary> 651 /// <summary>
539 /// Return an identifier based on passed file/directory name 652 /// QtExec custom actions have been renamed.
540 /// </summary> 653 /// </summary>
541 /// <param name="name">File/directory name to generate identifer from</param> 654 QuietExecCustomActionsRenamed,
542 /// <returns>A version of the name that is a legal identifier.</returns>
543 /// <remarks>This is duplicated from WiX's Common class.</remarks>
544 private static string GetIdentifierFromName(string name)
545 {
546 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
547 655
548 // MSI identifiers must begin with an alphabetic character or an 656 /// <summary>
549 // underscore. Prefix all other values with an underscore. 657 /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.
550 if (AddPrefix.IsMatch(name)) 658 /// </summary>
551 { 659 QtExecCmdTimeoutAmbiguous,
552 result = String.Concat("_", result);
553 }
554 660
555 return result; 661 /// <summary>
556 } 662 /// Directory/@ShortName may only be specified with Directory/@Name.
663 /// </summary>
664 AssignDirectoryNameFromShortName,
557 665
558 /// <summary> 666 /// <summary>
559 /// Converter test types. These are used to condition error messages down to warnings. 667 /// BootstrapperApplicationData attribute is deprecated and replaced with Unreal.
560 /// </summary> 668 /// </summary>
561 private enum ConverterTestType 669 BootstrapperApplicationDataDeprecated,
562 {
563 /// <summary>
564 /// Internal-only: displayed when a string cannot be converted to an ConverterTestType.
565 /// </summary>
566 ConverterTestTypeUnknown,
567
568 /// <summary>
569 /// Displayed when an XML loading exception has occurred.
570 /// </summary>
571 XmlException,
572
573 /// <summary>
574 /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file.
575 /// </summary>
576 UnauthorizedAccessException,
577
578 /// <summary>
579 /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'.
580 /// </summary>
581 DeclarationEncodingWrong,
582
583 /// <summary>
584 /// Displayed when the XML declaration is missing from the source file.
585 /// </summary>
586 DeclarationMissing,
587
588 /// <summary>
589 /// Displayed when the whitespace preceding a CDATA node is wrong.
590 /// </summary>
591 WhitespacePrecedingCDATAWrong,
592
593 /// <summary>
594 /// Displayed when the whitespace preceding a node is wrong.
595 /// </summary>
596 WhitespacePrecedingNodeWrong,
597
598 /// <summary>
599 /// Displayed when an element is not empty as it should be.
600 /// </summary>
601 NotEmptyElement,
602
603 /// <summary>
604 /// Displayed when the whitespace following a CDATA node is wrong.
605 /// </summary>
606 WhitespaceFollowingCDATAWrong,
607
608 /// <summary>
609 /// Displayed when the whitespace preceding an end element is wrong.
610 /// </summary>
611 WhitespacePrecedingEndElementWrong,
612
613 /// <summary>
614 /// Displayed when the xmlns attribute is missing from the document element.
615 /// </summary>
616 XmlnsMissing,
617
618 /// <summary>
619 /// Displayed when the xmlns attribute on the document element is wrong.
620 /// </summary>
621 XmlnsValueWrong,
622
623 /// <summary>
624 /// Assign an identifier to a File element when on Id attribute is specified.
625 /// </summary>
626 AssignAnonymousFileId,
627
628 /// <summary>
629 /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation.
630 /// </summary>
631 SuppressSignatureValidationDeprecated,
632
633 /// <summary>
634 /// WixCA Binary/@Id has been renamed to UtilCA.
635 /// </summary>
636 WixCABinaryIdRenamed,
637
638 /// <summary>
639 /// QtExec custom actions have been renamed.
640 /// </summary>
641 QuietExecCustomActionsRenamed,
642
643 /// <summary>
644 /// QtExecCmdTimeout was previously used for both CAQuietExec and CAQuietExec64. For WixQuietExec, use WixQuietExecCmdTimeout. For WixQuietExec64, use WixQuietExec64CmdTimeout.
645 /// </summary>
646 QtExecCmdTimeoutAmbiguous,
647
648 /// <summary>
649 /// Directory/@ShortName may only be specified with Directory/@Name.
650 /// </summary>
651 AssignDirectoryNameFromShortName,
652 }
653 } 670 }
654} 671}
672}
diff --git a/src/test/WixToolsetTest.Converters/ConverterFixture.cs b/src/test/WixToolsetTest.Converters/ConverterFixture.cs
index 71069333..e2c0be5b 100644
--- a/src/test/WixToolsetTest.Converters/ConverterFixture.cs
+++ b/src/test/WixToolsetTest.Converters/ConverterFixture.cs
@@ -450,6 +450,34 @@ namespace WixToolsetTest.Converters
450 } 450 }
451 451
452 [Fact] 452 [Fact]
453 public void CanConvertCustomTableBootstrapperApplicationData()
454 {
455 var parse = String.Join(Environment.NewLine,
456 "<?xml version='1.0' encoding='utf-8'?>",
457 "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>",
458 " <CustomTable Id='FgAppx' BootstrapperApplicationData='yes' />",
459 "</Wix>");
460
461 var expected = String.Join(Environment.NewLine,
462 "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
463 "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
464 " <CustomTable Id=\"FgAppx\" Unreal=\"yes\" />",
465 "</Wix>");
466
467 var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
468
469 var messaging = new DummyMessaging();
470 var converter = new Wix3Converter(messaging, 2, null, null);
471
472 var errors = converter.ConvertDocument(document);
473
474 var actual = UnformattedDocumentString(document);
475
476 Assert.Equal(1, errors);
477 Assert.Equal(expected, actual);
478 }
479
480 [Fact]
453 public void CanConvertShortNameDirectoryWithoutName() 481 public void CanConvertShortNameDirectoryWithoutName()
454 { 482 {
455 var parse = String.Join(Environment.NewLine, 483 var parse = String.Join(Environment.NewLine,