diff --git a/anbs_cp/Classes/Serializer.cs b/anbs_cp/Classes/Serializer.cs new file mode 100644 index 0000000..e8eafeb --- /dev/null +++ b/anbs_cp/Classes/Serializer.cs @@ -0,0 +1,25 @@ +using System.Text.Json; + +namespace anbs_cp.Classes; + +/// <summary> +/// Класс для сериализации моделей +/// </summary> +public static class Serializer +{ + /// <summary> + /// Сериализация данных <paramref name="data"/> в строку. + /// </summary> + /// <typeparam name="T">Тип данных</typeparam> + /// <param name="data">Данные</param> + /// <returns>Сериализованные данные</returns> + public static string Serialize<T>(T data) => JsonSerializer.Serialize(data); + + /// <summary> + /// Десериализация данных из json-строки <paramref name="json"/> + /// </summary> + /// <typeparam name="T">Ожидаемый тип данных</typeparam> + /// <param name="json">Сериализованные данные</param> + /// <returns>Данные</returns> + public static T? Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json); +} \ No newline at end of file diff --git a/anbs_cp/Classes/TypeConverter.cs b/anbs_cp/Classes/TypeConverter.cs index 06c88d3..60e768b 100644 --- a/anbs_cp/Classes/TypeConverter.cs +++ b/anbs_cp/Classes/TypeConverter.cs @@ -1,7 +1,5 @@ using System.Globalization; -using Newtonsoft.Json; - namespace anbs_cp.Classes; /// <summary> @@ -73,7 +71,7 @@ public static class TypeConverter /// <typeparam name="T">Тип</typeparam> /// <param name="value">Значение типа</param> /// <returns>Значение в <see cref="string"/></returns> - public static string TypeToStr<T> (T value) => JsonConvert.SerializeObject(value); + public static string TypeToStr<T> (T value) => Serializer.Serialize(value); #endregion @@ -159,7 +157,7 @@ public static class TypeConverter /// <param name="defaultValue">Значение по умолчанию</param> /// <returns>Значение в <see cref="T"/></returns> public static T StrToType<T>(string value, T defaultValue) => - JsonConvert.DeserializeObject<T>(value) ?? defaultValue; + Serializer.Deserialize<T>(value) ?? defaultValue; #endregion } \ No newline at end of file diff --git a/anbs_cp/Extensions/BoolExtensions.cs b/anbs_cp/Extensions/BoolExtensions.cs new file mode 100644 index 0000000..edd8cd7 --- /dev/null +++ b/anbs_cp/Extensions/BoolExtensions.cs @@ -0,0 +1,16 @@ +namespace anbs_cp.Extensions; + +/// <summary> +/// Расширение типа "правда/ложь" +/// </summary> +public static class BooleanExtensions +{ + /// <summary> + /// Вывод в строку <paramref name="ifTrue"/>, если выражение <paramref name="b"/> правдиво и <paramref name="ifFalse"/> в противном случае. + /// </summary> + /// <param name="b">Выражение типа правда/ложь</param> + /// <param name="ifTrue">Строка для правдивого выражения</param> + /// <param name="ifFalse">Строка для лживого выражения</param> + /// <returns>Вывод строки</returns> + public static string ExportToString (this bool b, string ifTrue, string ifFalse) => b ? ifTrue : ifFalse; +} \ No newline at end of file diff --git a/anbs_cp/Extensions/StringExtensions.cs b/anbs_cp/Extensions/StringExtensions.cs new file mode 100644 index 0000000..d58a98d --- /dev/null +++ b/anbs_cp/Extensions/StringExtensions.cs @@ -0,0 +1,14 @@ +namespace anbs_cp.Extensions; + +/// <summary> +/// Расширение строк +/// </summary> +public static class StringExtensions +{ + /// <summary> + /// Проверяет строку на пустоту + /// </summary> + /// <param name="s">Строка</param> + /// <returns>Строка пусть (null) или содержит только пробелы</returns> + public static bool IsNullOrWhiteSpace(this string? s) => s == null || s.Trim().Length == 0; +} \ No newline at end of file diff --git a/anbs_cp/Interfaces/ISerializable.cs b/anbs_cp/Interfaces/ISerializable.cs new file mode 100644 index 0000000..0ef7d8a --- /dev/null +++ b/anbs_cp/Interfaces/ISerializable.cs @@ -0,0 +1,19 @@ +namespace anbs_cp.Interfaces; + +/// <summary> +/// Интерфейс для сериализации объектов +/// </summary> +public interface ISerializable +{ + /// <summary> + /// Сериализовать элемент в формат json + /// </summary> + /// <returns>Строка в формате json</returns> + string Serialize(); + + /// <summary> + /// Восстановить элемент из формата json + /// </summary> + /// <param name="json">Строка в формате json</param> + void Deserialize(string json); +} \ No newline at end of file diff --git a/anbs_cp/Structs/TwoDimSize.cs b/anbs_cp/Structs/TwoDimSize.cs index 75476af..95b1415 100644 --- a/anbs_cp/Structs/TwoDimSize.cs +++ b/anbs_cp/Structs/TwoDimSize.cs @@ -1,11 +1,14 @@ -namespace anbs_cp.Structs; +using anbs_cp.Classes; +using anbs_cp.Interfaces; + +namespace anbs_cp.Structs; /// <summary> /// Двумерный размер /// </summary> /// <param name="width">Длина</param> /// <param name="height">Высота</param> -public struct TwoDimSize (int width = 0, int height = 0) +public struct TwoDimSize (int width = 0, int height = 0): ISerializable { #region Приватные поля /// <summary> @@ -14,7 +17,7 @@ public struct TwoDimSize (int width = 0, int height = 0) private int _pWidth = width; /// <summary> - /// Высота (приватное) + /// Ширина (приватное) /// </summary> private int _pHeight = height; #endregion @@ -30,7 +33,7 @@ public struct TwoDimSize (int width = 0, int height = 0) } /// <summary> - /// Высота + /// Ширина /// </summary> public int Height { @@ -38,4 +41,70 @@ public struct TwoDimSize (int width = 0, int height = 0) set => _pHeight = value < 0 ? 0 : value; } #endregion + + #region Методы + /// <summary> + /// Конвертация в строку + /// </summary> + /// <param name="delimiter">Делитель размера</param> + /// <returns>Строка</returns> + public readonly string ToString (char delimiter = ':') => $"{_pWidth}{delimiter}{_pHeight}"; + + /// <summary> + /// Получение размера из строки + /// </summary> + /// <param name="s">Строка</param> + /// <param name="delimiter">Разделитель размеров</param> + /// <returns>Модель размеров</returns> + /// <exception cref="ArgumentOutOfRangeException">Если в строке <paramref name="s"/> не содержится символа + /// <paramref name="delimiter"/> или таких разделителей слишком много</exception> + public static TwoDimSize Parse (string s, char delimiter = ':') + { + // Разделяю значения + string[] splitSizes = s.Split(delimiter); + + // Проверяю, что массив имеет ровно два элемента + if (splitSizes.Length != 2) + throw new ArgumentOutOfRangeException(delimiter.ToString(), + $"Похоже, что в строке {s} не содержится символа {delimiter} или таких разделителей слишком много!"); + + // Пытаюсь получить длину + if (!int.TryParse(splitSizes[0], out int width)) + width = 0; + + // Пытаюсь получить ширину + if (!int.TryParse(splitSizes[1], out int height)) + height = 0; + + // Вывожу значение + return new(width, height); + } + #endregion + + #region Реализация интерфейса ISerializable + /// <summary> + /// Сериализовать элемент в формат json + /// </summary> + /// <returns>Строка в формате json</returns> + public readonly string Serialize () => Serializer.Serialize(ToString()); + + /// <summary> + /// Восстановить элемент из формата json + /// </summary> + /// <param name="json">Строка в формате json</param> + public void Deserialize (string json) + { + // Десериализую строку + string deserialized = Serializer.Deserialize<string>(json) ?? "0:0"; + + // Перевожу строку в двумерный размер + TwoDimSize result = Parse(deserialized); + + // Присваиваю длину + _pWidth = result.Width; + + // Присваиваю ширину + _pHeight = result.Height; + } + #endregion } \ No newline at end of file diff --git a/anbs_cp/anbs_cp.csproj b/anbs_cp/anbs_cp.csproj index cbbba1c..54f25c9 100644 --- a/anbs_cp/anbs_cp.csproj +++ b/anbs_cp/anbs_cp.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <TargetFramework>net8.0</TargetFramework> - <Version>2023.1123.0</Version> + <Version>2023.1125.0</Version> <Authors>Александр Бабаев</Authors> <Product>Набор компонентов ANB Software</Product> <Description>Библиотека полезных методов языка C#</Description> @@ -41,7 +41,6 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> </ItemGroup> <ItemGroup> diff --git a/anbs_cpfn/Classes/Sanitizer.cs b/anbs_cpfn/Classes/Sanitizer.cs new file mode 100644 index 0000000..e5357ad --- /dev/null +++ b/anbs_cpfn/Classes/Sanitizer.cs @@ -0,0 +1,122 @@ +using anbs_cp.ForNet.Enums; + +using Ganss.Xss; + +namespace anbs_cp.ForNet.Classes; + +/// <summary> +/// Очистка текста от лишних HTML-тегов +/// </summary> +public static class Sanitizer +{ + /// <summary> + /// Очистка текста по уровню очистки + /// </summary> + /// <param name="html">Текст</param> + /// <param name="level">Уровень очистка</param> + /// <returns>Очищенный текст</returns> + public static string SanitizeHtml (string html, ESanitizerLevel level) + { + HtmlSanitizer sanitizer = new() + { + KeepChildNodes = true + }; + + switch (level) + { + case ESanitizerLevel.NoTags: + PrepareForNone(ref sanitizer); + break; + case ESanitizerLevel.TextFormatOnly: + PrepareForTextFormatOnly(ref sanitizer); + break; + case ESanitizerLevel.ImageAndLinks: + PrepareForImageAndLinks(ref sanitizer); + break; + case ESanitizerLevel.AllExceptIFrame: + PrepareForAllExceptIFrame(ref sanitizer); + break; + default: + PrepareForNone(ref sanitizer); + break; + } + + return level != ESanitizerLevel.All ? sanitizer.Sanitize(html) : html; + } + + /// <summary> + /// Очистка всех тегов + /// </summary> + /// <param name="sanitizer"><see cref="HtmlSanitizer"/></param> + private static void PrepareForNone (ref HtmlSanitizer sanitizer) + { + sanitizer.AllowedTags.Clear(); + sanitizer.AllowedSchemes.Clear(); + sanitizer.AllowedCssProperties.Clear(); + sanitizer.AllowedClasses.Clear(); + sanitizer.AllowedAttributes.Clear(); + sanitizer.AllowedAtRules.Clear(); + sanitizer.AllowDataAttributes = false; + } + + /// <summary> + /// Остаются только текстовые теги + /// </summary> + /// <param name="sanitizer"><see cref="HtmlSanitizer"/></param> + private static void PrepareForTextFormatOnly (ref HtmlSanitizer sanitizer) + { + string[] allowedTags = + { + "strong", "b", "em", "i", "u", "hr", "strike", "div", "ol", "ul", "li", "p", "span", "h1", "h2", "h3", "h4" + }; + string[] allowedAttributes = + { + "align", "bgcolor", "border", "cellpadding", "cellspacing", "charset", "checked", "class", "clear", "color", "cols", "colspan", + "datetime", "disabled", "headers", "height", "high", "hspace", "label", "lang", "list", "low", "max", "maxlength", "min", "name", + "nowrap", "placeholder", "required", "rev", "rows", "rowspan", "rules", "selected", "size", "span", "spellcheck", "style", "summary", + "tabindex", "title", "type", "valign", "value", "vspace", "width", "wrap" + }; + + sanitizer.AllowedTags.Clear(); + + sanitizer.AllowedTags.UnionWith(allowedTags); + + sanitizer.AllowedAtRules.Clear(); + sanitizer.AllowDataAttributes = false; + + sanitizer.AllowedAttributes.Clear(); + sanitizer.AllowedAttributes.UnionWith(allowedAttributes); + + } + + /// <summary> + /// Остаются текстовые теги + изображения и ссылки + /// </summary> + /// <param name="sanitizer"><see cref="HtmlSanitizer"/></param> + private static void PrepareForImageAndLinks (ref HtmlSanitizer sanitizer) + { + PrepareForTextFormatOnly(ref sanitizer); + string[] allowedTags = + { + "a", "img" + }; + + string[] allowedAttributes = + { + "alt", "href", "hreflang", "nohref", "rel", "src", "target" + }; + + sanitizer.AllowedTags.UnionWith(allowedTags); + + sanitizer.AllowedAttributes.UnionWith(allowedAttributes); + } + + /// <summary> + /// Остаются все теги, за исключением IFRAME + /// </summary> + /// <param name="sanitizer"><see cref="HtmlSanitizer"/></param> + private static void PrepareForAllExceptIFrame (ref HtmlSanitizer sanitizer) + { + sanitizer.AllowedTags.Remove("iframe"); + } +} \ No newline at end of file diff --git a/anbs_cpfn/Enums/ESanitizerLevel.cs b/anbs_cpfn/Enums/ESanitizerLevel.cs new file mode 100644 index 0000000..8beeb1a --- /dev/null +++ b/anbs_cpfn/Enums/ESanitizerLevel.cs @@ -0,0 +1,38 @@ +namespace anbs_cp.ForNet.Enums; + +/// <summary> +/// Уровень очистки текста +/// атрибуты описаны на стр. https://github.com/mganss/HtmlSanitizer/wiki/Options +/// </summary> +public enum ESanitizerLevel +{ + /// <summary> + /// Все html-теги под запретом + /// </summary> + NoTags = 0, + + /// <summary> + /// Доступны только: + /// * теги формата шрифта (жирный, курсив, подчёркнутый, зачёркнутый) + /// * теги расположения текста (слева, по центру, справа) + /// </summary> + TextFormatOnly = 1, + + /// <summary> + /// Доступны только: + /// * все теги уровня lvlTextFormatOnly + /// * теги ссылки + /// * теги изображения + /// </summary> + ImageAndLinks = 2, + + /// <summary> + /// Доступны все теги, кроме вставки с другого сайта + /// </summary> + AllExceptIFrame = 3, + + /// <summary> + /// Доступны все теги + /// </summary> + All = 4 +} \ No newline at end of file diff --git a/anbs_cpfn/anbs_cpfn.csproj b/anbs_cpfn/anbs_cpfn.csproj index 9f0ac1e..e8223a3 100644 --- a/anbs_cpfn/anbs_cpfn.csproj +++ b/anbs_cpfn/anbs_cpfn.csproj @@ -6,7 +6,7 @@ <Nullable>enable</Nullable> <GeneratePackageOnBuild>True</GeneratePackageOnBuild> <PackageId>ANBSoftware.ComponentsPackForNet</PackageId> - <Version>2023.11.15.0</Version> + <Version>2023.11.25.0</Version> <Authors>Александр Бабаев</Authors> <Product>Набор компонентов ANB Software для ASP.NET Core</Product> <Description>Библиотека полезных методов языка C# для ASP.NET Core</Description> @@ -20,6 +20,7 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="HtmlSanitizer" Version="8.0.795" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Html.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" /> diff --git a/anbsoftware.componentspack.sln.DotSettings b/anbsoftware.componentspack.sln.DotSettings index 94e58b5..04c25e0 100644 --- a/anbsoftware.componentspack.sln.DotSettings +++ b/anbsoftware.componentspack.sln.DotSettings @@ -12,16 +12,22 @@ <s:Boolean x:Key="/Default/UserDictionary/Words/=Glendower/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0412_0435_0440_043D_0451_043C/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0412_0438_0434_0435_043E_043A_0430_0440_0442_0430/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_0414_0435_0441_0435_0440_0438_0430_043B_0438_0437_0443_044E/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_041F_0430_0440_0441_0435_0440/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_0421_0435_0440_0438_0430_043B_0438_0437_043E_0432_0430_043D_043D_044B_0435/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_0421_0435_0440_0438_0430_043B_0438_0437_043E_0432_0430_0442_044C/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0421_043E_0437_0434_0430_0451_043C/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0432_0432_0435_0434_0451_043D/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0432_0438_0434_0435_043E_043A_0430_0440_0442_0435/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0434_0435_0441_0435_0440_0438_0430_043B_0438_0437_0430_0446_0438_044F/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0434_0435_0448_0438_0444_0440_043E_0432_0430_043D_0438_044F/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0434_0435_0448_0438_0444_0440_043E_0432_0447_0438_043A/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_0437_0430_0447_0451_0440_043A_043D_0443_0442_044B_0439/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0438_043C_0451_043D/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_043A_0440_0438_043F_0442_043E_0433_0440_0430_0444/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_043F_043E_0434_0447_0451_0440_043A_043D_0443_0442_044B_0439/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_043F_0443_0442_0451_043C/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=_0441_0435_0440_0438_0430_043B_0438_0437_0430_0446_0438_0438/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0441_0435_0440_0438_0430_043B_0438_0437_0430_0446_0438_044F/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0441_0447_0451_0442/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=_0441_0447_0451_0442_0447_0438_043A/@EntryIndexedValue">True</s:Boolean> diff --git a/demo/OsInfoFrm.cs b/demo/OsInfoFrm.cs index f92bea5..4c0e8b8 100644 --- a/demo/OsInfoFrm.cs +++ b/demo/OsInfoFrm.cs @@ -1,6 +1,5 @@ -using anbs_cp.OsInfo.Classes; - -using Newtonsoft.Json; +using anbs_cp.Classes; +using anbs_cp.OsInfo.Classes; namespace demo; public sealed partial class OsInfoFrm: Form @@ -13,16 +12,16 @@ public sealed partial class OsInfoFrm: Form private void OsInfoFrm_Load (object sender, EventArgs e) { textBox.Text = @"Процессор(ы) | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.Processors); + textBox.Text += Serializer.Serialize(OsInfo.Processors); textBox.Text += @"Оперативная память | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.RAM); + textBox.Text += Serializer.Serialize(OsInfo.RAM); textBox.Text += @"Видеокарта | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.Videos); + textBox.Text += Serializer.Serialize(OsInfo.Videos); textBox.Text += @"Диски | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.Drives); + textBox.Text += Serializer.Serialize(OsInfo.Drives); textBox.Text += @"Windows | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.Windows); + textBox.Text += Serializer.Serialize(OsInfo.Windows); textBox.Text += @"Net | "; - textBox.Text += JsonConvert.SerializeObject(OsInfo.Net); + textBox.Text += Serializer.Serialize(OsInfo.Net); } } diff --git a/demo/SampleMapperTest.cs b/demo/SampleMapperTest.cs index 0890aa8..2386164 100644 --- a/demo/SampleMapperTest.cs +++ b/demo/SampleMapperTest.cs @@ -1,5 +1,4 @@ using anbs_cp.Classes; -using Newtonsoft.Json; namespace demo; public sealed partial class SampleMapperTest: Form @@ -26,7 +25,7 @@ public sealed partial class SampleMapperTest: Form DemoDateTime = default }; - string serialize1 = JsonConvert.SerializeObject(demo2); + string serialize1 = Serializer.Serialize(demo2); SimpleMapper.MapMode mode = MapModeEdit.SelectedIndex switch { @@ -39,7 +38,7 @@ public sealed partial class SampleMapperTest: Form SimpleMapper.MapEx(demo1, ref demo2, mode, new List<string>()); - string serialize2 = JsonConvert.SerializeObject(demo2); + string serialize2 = Serializer.Serialize(demo2); // ReSharper disable once LocalizableElement ResultArea.Text = $"Класс Demo2 до связывания:\r\n{serialize1}\r\nи после:\r\n{serialize2}";