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