diff --git a/ObjectPrinting/ObjectExtensions.cs b/ObjectPrinting/ObjectExtensions.cs new file mode 100644 index 000000000..863421df6 --- /dev/null +++ b/ObjectPrinting/ObjectExtensions.cs @@ -0,0 +1,10 @@ +namespace ObjectPrinting +{ + public static class ObjectExtensions + { + public static string ToString(this T obj) + { + return ObjectPrinter.For().ToString(obj); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..ea98111e3 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,6 +5,7 @@ + diff --git a/ObjectPrinting/PrintedObject.cs b/ObjectPrinting/PrintedObject.cs new file mode 100644 index 000000000..cfe1e1989 --- /dev/null +++ b/ObjectPrinting/PrintedObject.cs @@ -0,0 +1,3 @@ +namespace ObjectPrinting; + +public record PrintedObject(string Name, object Current, object Parent); \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..b1b124523 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,230 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; namespace ObjectPrinting { public class PrintingConfig { - public string PrintToString(TOwner obj) + private readonly HashSet _excludedTypes; + private readonly HashSet _excludedProperties; + + private readonly Dictionary> _typeSerializers; + private readonly Dictionary> _propertySerializers; + + private readonly HashSet _printedObjects = []; + private readonly Type[] finalTypes = + [ + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + ]; + + public Dictionary> GetTypeSerializers => _typeSerializers; + public Dictionary> GetPropertySerializers => _propertySerializers; + + + public PrintingConfig() : this(null) + { } + + private PrintingConfig( + HashSet? excludedTypes = null, + HashSet? excludedProperties = null, + Dictionary>? typeSerializers = null, + Dictionary>? propertySerializers = null + ) { - return PrintToString(obj, 0); + _excludedTypes = excludedTypes ?? new HashSet(); + _excludedProperties = excludedProperties ?? new HashSet(); + _typeSerializers = typeSerializers ?? new Dictionary>(); + _propertySerializers = propertySerializers ?? new Dictionary>(); } - private string PrintToString(object obj, int nestingLevel) + internal PrintingConfig CopyWithChanges( + HashSet? excludedTypes = null, + HashSet? excludedProperties = null, + Dictionary>? typeSerializers = null, + Dictionary>? propertySerializers = null + ) { + return new PrintingConfig( + excludedTypes ?? _excludedTypes, + excludedProperties ?? _excludedProperties, + typeSerializers ?? _typeSerializers, + propertySerializers ?? _propertySerializers + ); + } + + public PropertyPrintingConfig Printing() { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + return new PropertyPrintingConfig( + CopyWithChanges(), + null! + ); + } + + public PropertyPrintingConfig Printing(Expression> memberSelector) + { + return new PropertyPrintingConfig( + CopyWithChanges(), + memberSelector + ); + } - var finalTypes = new[] + public PrintingConfig Excluding(Expression> memberSelector) + { + var excludedProperties = new HashSet(_excludedProperties) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) + GetPropertyNameFromSelector(memberSelector) }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + return CopyWithChanges( + excludedProperties: excludedProperties + ); + } + + public PrintingConfig Excluding() + { + var excludedTypes = new HashSet(_excludedTypes) + { + typeof(TPropType) + }; + + return CopyWithChanges( + excludedTypes: excludedTypes + ); + } + + public string GetPropertyNameFromSelector( + Expression> propertySelector) + { + if (propertySelector.Body is MemberExpression memberExpression) + { + if (memberExpression.Member is PropertyInfo propertyInfo) + { + return propertyInfo.Name; + } + } + + throw new ArgumentException( + "Expression must be a property selector: x => x.PropertyName", + nameof(propertySelector)); + } + + public string ToString(TOwner obj, int nestingLevel = 0) + { + return ToString((object)obj!, nestingLevel); + } + + private string ToString(object obj, int nestingLevel) + { + if (obj == null!) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + if (_typeSerializers.TryGetValue(type, out var serializer)) + { + return serializer(obj) + Environment.NewLine; + } + + if (finalTypes.Contains(type)) + return obj + Environment.NewLine; + var identation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); - var type = obj.GetType(); sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) + + var collectionToString = CollectionToString(obj, identation, nestingLevel); + if (collectionToString is not null) + sb.Append(collectionToString); + + foreach (var propertyInfo in GetOrderedProperties(obj)) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + if (IsExcluded(propertyInfo)) { + continue; + } + var propertyForPrint = PropertyToString(obj, nestingLevel, propertyInfo); + if (propertyForPrint is not null) + sb.Append(identation + propertyInfo.Name + " = " + propertyForPrint); } return sb.ToString(); } + + private IOrderedEnumerable GetOrderedProperties(object obj) + { + return obj.GetType() + .GetProperties() + .Where(p => p.GetIndexParameters().Length == 0) + .OrderBy(x => + { + var value = x.GetValue(obj); + if (value is null) + return int.MaxValue; + return value.GetType().GetProperties().Length; + }); + } + + private string? CollectionToString(object obj, string identation, int nestingLevel) + { + if (IsCollection(obj.GetType())) + { + var sb = new StringBuilder(); + sb.AppendLine(identation + "{"); + foreach (var item in (IEnumerable)obj) + { + var itemToPrint = ToString(item, nestingLevel + 1); + if (itemToPrint is not null) + sb.Append(identation + itemToPrint); + } + sb.AppendLine(identation + "}"); + return sb.ToString(); + } + + return null; + } + + private string? PropertyToString(object obj, int nestingLevel, PropertyInfo propertyInfo) + { + if ( + _propertySerializers.TryGetValue(propertyInfo.Name, out var propertySerializer) + && obj.GetType() == typeof(TOwner) + ) { + return propertySerializer((TOwner)obj) + Environment.NewLine; + } + + var printedObject = new PrintedObject(propertyInfo.Name, propertyInfo.GetValue(obj)!, obj); + if (!_printedObjects.Contains(printedObject)) + { + _printedObjects.Add(printedObject); + return ToString( + propertyInfo.GetValue(obj)!, + nestingLevel + 1); + } + + return null; + } + + private bool IsExcluded(PropertyInfo propertyInfo) + { + return _excludedProperties.Contains(propertyInfo.Name) + || _excludedTypes.Contains(propertyInfo.PropertyType); + } + + private bool IsCollection(Type type) + { + if (type == typeof(string)) + return false; + + if (typeof(IEnumerable).IsAssignableFrom(type)) + return true; + + if (type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + return true; + + return false; + } } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..e11d3620f --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq.Expressions; + +namespace ObjectPrinting +{ + public class PropertyPrintingConfig : IPropertyPrintingConfig + { + private readonly PrintingConfig _printingConfig; + private Expression>? _memberSelector; + + public PropertyPrintingConfig(PrintingConfig printingConfig, Expression> memberSelector) + { + _memberSelector = memberSelector; + _printingConfig = printingConfig; + } + + public PrintingConfig Using(Func print) + { + if (_memberSelector == null) + { + return UsingForType(print); + } + else + { + if (typeof(TPropType) == typeof(string)) + return UsingForProperty(print as Func); + return UsingForProperty(print); + } + } + + private PrintingConfig UsingForType(Func print) + { + var serializers = new Dictionary>(_printingConfig.GetTypeSerializers); + Func serializer = obj => print((TPropType)obj); + + if (serializers.ContainsKey(typeof(TPropType))) + { + var oldSerializer = serializers[typeof(TPropType)]; + serializers[typeof(TPropType)] = obj => serializer(oldSerializer(obj)); + } + else + serializers[typeof(TPropType)] = serializer; + return _printingConfig.CopyWithChanges(typeSerializers: serializers); + } + + private PrintingConfig UsingForProperty(Func print) + { + if (_memberSelector == null) + return _printingConfig; + + var serializers = new Dictionary>(_printingConfig.GetPropertySerializers); + var propertyName = _printingConfig.GetPropertyNameFromSelector(_memberSelector); + var memberSelector = _memberSelector.Compile(); + + Func ownerSerializer = owner => + { + TPropType propertyValue = memberSelector(owner); + return print(propertyValue); + }; + serializers[propertyName] = ownerSerializer; + return _printingConfig.CopyWithChanges(propertySerializers: serializers); + } + + private PrintingConfig UsingForProperty(Func print) + { + if (_memberSelector == null) + return _printingConfig; + + var serializers = new Dictionary>(_printingConfig.GetPropertySerializers); + var propertyName = _printingConfig.GetPropertyNameFromSelector(_memberSelector); + if (!serializers.ContainsKey(propertyName)) + { + return UsingForProperty(print as Func); + } + var oldSerializer = serializers[propertyName]; + Func ownerSerializer = owner => + { + return print(oldSerializer(owner)); + }; + serializers[propertyName] = ownerSerializer; + return _printingConfig.CopyWithChanges(propertySerializers: serializers); + } + + + public PrintingConfig Using(CultureInfo culture) + { + if (!typeof(TPropType).IsNumericType()) + { + throw new InvalidOperationException($"{typeof(TPropType).Name} is not numeric."); + } + var print = (TPropType p) => ((IFormattable)p!).ToString(null, culture); + + return Using(print); + } + + public PrintingConfig ParentConfig => _printingConfig; + } + + public interface IPropertyPrintingConfig + { + PrintingConfig ParentConfig { get; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..258b0e189 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,19 @@ +using System; + +namespace ObjectPrinting +{ + public static class PropertyPrintingConfigExtensions + { + public static string PrintToString(this T obj, Func, PrintingConfig> config) + { + return config(ObjectPrinter.For()).ToString(obj); + } + + public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) + { + return propConfig.Using(text => + text.Length > maxLen ? text.Substring(0, maxLen) : text + ); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..9aa758a1f 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; namespace ObjectPrinting.Tests { @@ -6,22 +10,134 @@ namespace ObjectPrinting.Tests public class ObjectPrinterAcceptanceTests { [Test] - public void Demo() + public void DemonstatingTest() { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "12345", Height = 0.1241, Age = 19, Parent = new Person { Name = "Alex2" } }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() //1. Исключить из сериализации свойства определенного типа + .Excluding() //2. Указать альтернативный способ сериализации для определенного типа + .Printing(p => p.Name).Using(p => p + "678910") //3. Для числовых типов указать культуру + .Printing().Using(new CultureInfo("ru-RU")) //4. Настроить сериализацию конкретного свойства //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .Printing(p => p.Name).TrimmedToLength(10) //6. Исключить из сериализации конкретного свойства + .Excluding(p => p.Age); - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + string s1 = printer.ToString(person); + Console.WriteLine(s1); + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + Console.WriteLine(ObjectExtensions.ToString(person)); //8. ...с конфигурированием + Console.WriteLine(person.PrintToString(config => config.Excluding())); + } + + [Test] + public void ExcludingPropertyAndTypesTest() + { + var person = new Person { Name = "Alex", Age = 19 }; + var printer = ObjectPrinter.For().Excluding(p => p.Age).Excluding(); + var expected = "Person\r\n\tId = Guid\r\n\tHeight = 0\r\n\tParent = null\r\n"; + + var actual = printer.ToString(person); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } + + [Test] + public void SerializePropertyTest() + { + var person = new Person { Name = "Alex", Age = 19 }; + var printer = ObjectPrinter.For().Printing(p => p.Age).Using(p => "Number"); + var expected = "Person\r\n\tId = Guid\r\n\tHeight = 0\r\n\tAge = Number\r\n\tName = Alex\r\n\tParent = null\r\n"; + + var actual = printer.ToString(person); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } + + [Test] + public void SerializeTypeTest() + { + var person = new Person { Name = "Alex", Age = 19 }; + var printer = ObjectPrinter.For().Printing().Using(p => "Number"); + var expected = "Person\r\n\tId = Guid\r\n\tHeight = 0\r\n\tAge = Number\r\n\tName = Alex\r\n\tParent = null\r\n"; + + var actual = printer.ToString(person); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } + + [Test] + public void SetCultureInfoTest() + { + var person = new Person { Height = 1.9, Age = 19 }; + var printer = ObjectPrinter + .For(); + var ruPrinter = printer.Printing().Using(new CultureInfo("ru-RU")); + var enPrinter = printer.Printing(p => p.Height).Using(new CultureInfo("en-US")); + var ruExpected = "Person\r\n\tId = Guid\r\n\tHeight = 1,9\r\n\tAge = 19\r\n\tName = null\r\n\tParent = null\r\n"; + var enExpected = "Person\r\n\tId = Guid\r\n\tHeight = 1.9\r\n\tAge = 19\r\n\tName = null\r\n\tParent = null\r\n"; + + var ruActual = ruPrinter.ToString(person); + var enActual = enPrinter.ToString(person); + Console.WriteLine(enActual); + Console.WriteLine(ruActual); + + ruActual.Should().Be(ruExpected); + enActual.Should().Be(enExpected); + } + + [Test] + public void SeparatePrintersTest() + { + var printer = ObjectPrinter + .For(); + var withAgePrinter = printer.Printing(p => p.Age).Using(p => "Age"); + var withoutAgePrinter = printer.Printing(p => p.Age).Using(p => "Not Age"); + + withAgePrinter.Should().NotBe(withoutAgePrinter); + } + + [Test] + public void MultipleSerializeTest() + { + var person = new Person { Name = "12345", Parent = new Person { Name = "12345" } }; + var propertyPrinter = ObjectPrinter + .For() + .Printing(p => p.Name).Using(p => p + "678910") + .Printing(p => p.Name).TrimmedToLength(10); + var typePrinter = ObjectPrinter + .For() + .Printing().Using(p => p + "678910") + .Printing().TrimmedToLength(10); + var propertyExpected = "Person\r\n\tId = Guid\r\n\tHeight = 0\r\n\tAge = 0\r\n\tName = 1234567891\r\n\tParent = Person\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tName = 1234567891\r\n\t\tParent = null\r\n"; + var typeExpected = "Person\r\n\tId = Guid\r\n\tHeight = 0\r\n\tAge = 0\r\n\tName = 1234567891\r\n\tParent = Person\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tName = 1234567891\r\n\t\tParent = null\r\n"; + + var propertyActual = propertyPrinter.ToString(person); + var typeActual = typePrinter.ToString(person); + Console.WriteLine(propertyActual); + Console.WriteLine(typeActual); + + typeActual.Should().Be(typeExpected); + propertyActual.Should().Be(propertyExpected); + } + + [Test] + public void LoopPrintingTest() + { + var person = new Person { Name = "Alex"}; + var person2 = new Person { Name = "Alex2", Parent = person }; + person.Parent = person2; + + Console.WriteLine(ObjectExtensions.ToString(person)); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterCollectionsTests.cs b/ObjectPrinting/Tests/ObjectPrinterCollectionsTests.cs new file mode 100644 index 000000000..c40671b19 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterCollectionsTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; + +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterCollectionsTests +{ + [Test] + public void PrintToString_DictionaryTest() + { + var dictionary = new Dictionary() + { + { 1, new Person() { Name = "Alex1" } }, + { 2, new Person() { Age = 22 } }, + { 3, new Person() { Height = 12.5 } } + }; + + var expected = "Dictionary`2\r\n\t{\r\n\tKeyValuePair`2\r\n\t\tKey = 1\r\n\t\tValue = Person\r\n\t\t\tId = Guid\r\n\t\t\tHeight = 0\r\n\t\t\tAge = 0\r\n\t\t\tName = Alex1\r\n\t\t\tParent = null\r\n\tKeyValuePair`2\r\n\t\tKey = 2\r\n\t\tValue = Person\r\n\t\t\tId = Guid\r\n\t\t\tHeight = 0\r\n\t\t\tAge = 22\r\n\t\t\tName = null\r\n\t\t\tParent = null\r\n\tKeyValuePair`2\r\n\t\tKey = 3\r\n\t\tValue = Person\r\n\t\t\tId = Guid\r\n\t\t\tHeight = 12,5\r\n\t\t\tAge = 0\r\n\t\t\tName = null\r\n\t\t\tParent = null\r\n\t}\r\n\tComparer = GenericEqualityComparer`1\r\n\tCount = 3\r\n\tKeys = KeyCollection\r\n\t\t{\r\n\t\t1\r\n\t\t2\r\n\t\t3\r\n\t\t}\r\n\t\tCount = 3\r\n\tValues = ValueCollection\r\n\t\t{\r\n\t\tPerson\r\n\t\tPerson\r\n\t\tPerson\r\n\t\t}\r\n\t\tCount = 3\r\n"; + var actual = ObjectExtensions.ToString(dictionary); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } + + [Test] + public void PrintToString_ListTest() + { + var collection = new List() + { + new() { Name = "Alex1" }, + new() { Age = 22 }, + new() { Height = 12.5 } + }; + var expected = "List`1\r\n\t{\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tName = Alex1\r\n\t\tParent = null\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 22\r\n\t\tName = null\r\n\t\tParent = null\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 12,5\r\n\t\tAge = 0\r\n\t\tName = null\r\n\t\tParent = null\r\n\t}\r\n\tCapacity = 4\r\n\tCount = 3\r\n"; + + var actual = ObjectExtensions.ToString(collection); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } + + [Test] + public void PrintToString_ArrayTest() + { + var collection = new Person[] + { + new() { Name = "Alex1" }, + new() { Age = 22 }, + new() { Height = 12.5 } + }; + var expected = "Person[]\r\n\t{\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tName = Alex1\r\n\t\tParent = null\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 0\r\n\t\tAge = 22\r\n\t\tName = null\r\n\t\tParent = null\r\n\tPerson\r\n\t\tId = Guid\r\n\t\tHeight = 12,5\r\n\t\tAge = 0\r\n\t\tName = null\r\n\t\tParent = null\r\n\t}\r\n\tLength = 3\r\n\tLongLength = Int64\r\n\tRank = 1\r\n\tIsReadOnly = Boolean\r\n\tIsFixedSize = Boolean\r\n\tIsSynchronized = Boolean\r\n\tSyncRoot = Person[]\r\n\t\t{\r\n\t\tPerson\r\n\t\tPerson\r\n\t\tPerson\r\n\t\t}\r\n"; + + var actual = ObjectExtensions.ToString(collection); + Console.WriteLine(actual); + + actual.Should().Be(expected); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..d53f52932 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -8,5 +8,6 @@ public class Person public string Name { get; set; } public double Height { get; set; } public int Age { get; set; } + public Person Parent { get; set; } } } \ No newline at end of file diff --git a/ObjectPrinting/TypeExtensions.cs b/ObjectPrinting/TypeExtensions.cs new file mode 100644 index 000000000..33dd9c95b --- /dev/null +++ b/ObjectPrinting/TypeExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace ObjectPrinting; + +public static class TypeExtensions +{ + public static bool IsNumericType(this Type type) + { + if (type == null) + return false; + + HashSet NumericTypes = new HashSet + { + typeof(byte), typeof(sbyte), + typeof(short), typeof(ushort), + typeof(int), typeof(uint), + typeof(long), typeof(ulong), + typeof(decimal), typeof(double), typeof(float) + }; + + Type underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType != null) + return NumericTypes.Contains(underlyingType); + + return NumericTypes.Contains(type); + } +} \ No newline at end of file diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ecb..53fe49b2f 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016