diff options
| author | Bob Arnson <bob@joyofsetup.com> | 2019-11-01 18:03:29 -0400 |
|---|---|---|
| committer | Bob Arnson <bob@firegiant.com> | 2019-11-01 18:05:48 -0400 |
| commit | 2c6285da46439ec98f89f09e2029f801924014ed (patch) | |
| tree | d302a6efd422572693f32ef7608e97c3da4307a2 | |
| parent | 8b1861a0d9fc636f02ccb9452946863e31a46b78 (diff) | |
| download | wix-2c6285da46439ec98f89f09e2029f801924014ed.tar.gz wix-2c6285da46439ec98f89f09e2029f801924014ed.tar.bz2 wix-2c6285da46439ec98f89f09e2029f801924014ed.zip | |
Handle CustomTable/@BootstrapperApplicationData.
| -rw-r--r-- | src/WixToolset.Converters/Wix3Converter.cs | 548 | ||||
| -rw-r--r-- | src/test/WixToolsetTest.Converters/ConverterFixture.cs | 28 |
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, |
